]> git.proxmox.com Git - libgit2.git/blob - src/buffer.c
Add functions to manipulate filter lists
[libgit2.git] / src / buffer.c
1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7 #include "buffer.h"
8 #include "posix.h"
9 #include "git2/buffer.h"
10 #include <stdarg.h>
11 #include <ctype.h>
12
13 /* Used as default value for git_buf->ptr so that people can always
14 * assume ptr is non-NULL and zero terminated even for new git_bufs.
15 */
16 char git_buf__initbuf[1];
17
18 char git_buf__oom[1];
19
20 #define ENSURE_SIZE(b, d) \
21 if ((d) > buf->asize && git_buf_grow(b, (d)) < 0)\
22 return -1;
23
24
25 void git_buf_init(git_buf *buf, size_t initial_size)
26 {
27 buf->asize = 0;
28 buf->size = 0;
29 buf->ptr = git_buf__initbuf;
30
31 if (initial_size)
32 git_buf_grow(buf, initial_size);
33 }
34
35 int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom)
36 {
37 char *new_ptr;
38 size_t new_size;
39
40 if (buf->ptr == git_buf__oom)
41 return -1;
42
43 if (target_size <= buf->asize)
44 return 0;
45
46 if (buf->asize == 0) {
47 new_size = target_size;
48 new_ptr = NULL;
49 } else {
50 new_size = buf->asize;
51 new_ptr = buf->ptr;
52 }
53
54 /* grow the buffer size by 1.5, until it's big enough
55 * to fit our target size */
56 while (new_size < target_size)
57 new_size = (new_size << 1) - (new_size >> 1);
58
59 /* round allocation up to multiple of 8 */
60 new_size = (new_size + 7) & ~7;
61
62 new_ptr = git__realloc(new_ptr, new_size);
63
64 if (!new_ptr) {
65 if (mark_oom)
66 buf->ptr = git_buf__oom;
67 return -1;
68 }
69
70 buf->asize = new_size;
71 buf->ptr = new_ptr;
72
73 /* truncate the existing buffer size if necessary */
74 if (buf->size >= buf->asize)
75 buf->size = buf->asize - 1;
76 buf->ptr[buf->size] = '\0';
77
78 return 0;
79 }
80
81 void git_buf_free(git_buf *buf)
82 {
83 if (!buf) return;
84
85 if (buf->ptr != git_buf__initbuf && buf->ptr != git_buf__oom)
86 git__free(buf->ptr);
87
88 git_buf_init(buf, 0);
89 }
90
91 void git_buf_clear(git_buf *buf)
92 {
93 buf->size = 0;
94 if (buf->asize > 0)
95 buf->ptr[0] = '\0';
96 }
97
98 int git_buf_set(git_buf *buf, const char *data, size_t len)
99 {
100 if (len == 0 || data == NULL) {
101 git_buf_clear(buf);
102 } else {
103 if (data != buf->ptr) {
104 ENSURE_SIZE(buf, len + 1);
105 memmove(buf->ptr, data, len);
106 }
107 buf->size = len;
108 buf->ptr[buf->size] = '\0';
109 }
110 return 0;
111 }
112
113 int git_buf_sets(git_buf *buf, const char *string)
114 {
115 return git_buf_set(buf, string, string ? strlen(string) : 0);
116 }
117
118 int git_buf_putc(git_buf *buf, char c)
119 {
120 ENSURE_SIZE(buf, buf->size + 2);
121 buf->ptr[buf->size++] = c;
122 buf->ptr[buf->size] = '\0';
123 return 0;
124 }
125
126 int git_buf_put(git_buf *buf, const char *data, size_t len)
127 {
128 ENSURE_SIZE(buf, buf->size + len + 1);
129 memmove(buf->ptr + buf->size, data, len);
130 buf->size += len;
131 buf->ptr[buf->size] = '\0';
132 return 0;
133 }
134
135 int git_buf_puts(git_buf *buf, const char *string)
136 {
137 assert(string);
138 return git_buf_put(buf, string, strlen(string));
139 }
140
141 static const char b64str[64] =
142 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
143
144 int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
145 {
146 size_t extra = len % 3;
147 uint8_t *write, a, b, c;
148 const uint8_t *read = (const uint8_t *)data;
149
150 ENSURE_SIZE(buf, buf->size + 4 * ((len / 3) + !!extra) + 1);
151 write = (uint8_t *)&buf->ptr[buf->size];
152
153 /* convert each run of 3 bytes into 4 output bytes */
154 for (len -= extra; len > 0; len -= 3) {
155 a = *read++;
156 b = *read++;
157 c = *read++;
158
159 *write++ = b64str[a >> 2];
160 *write++ = b64str[(a & 0x03) << 4 | b >> 4];
161 *write++ = b64str[(b & 0x0f) << 2 | c >> 6];
162 *write++ = b64str[c & 0x3f];
163 }
164
165 if (extra > 0) {
166 a = *read++;
167 b = (extra > 1) ? *read++ : 0;
168
169 *write++ = b64str[a >> 2];
170 *write++ = b64str[(a & 0x03) << 4 | b >> 4];
171 *write++ = (extra > 1) ? b64str[(b & 0x0f) << 2] : '=';
172 *write++ = '=';
173 }
174
175 buf->size = ((char *)write) - buf->ptr;
176 buf->ptr[buf->size] = '\0';
177
178 return 0;
179 }
180
181 int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
182 {
183 int len;
184 const size_t expected_size = buf->size + (strlen(format) * 2);
185
186 ENSURE_SIZE(buf, expected_size);
187
188 while (1) {
189 va_list args;
190 va_copy(args, ap);
191
192 len = p_vsnprintf(
193 buf->ptr + buf->size,
194 buf->asize - buf->size,
195 format, args
196 );
197
198 if (len < 0) {
199 git__free(buf->ptr);
200 buf->ptr = git_buf__oom;
201 return -1;
202 }
203
204 if ((size_t)len + 1 <= buf->asize - buf->size) {
205 buf->size += len;
206 break;
207 }
208
209 ENSURE_SIZE(buf, buf->size + len + 1);
210 }
211
212 return 0;
213 }
214
215 int git_buf_printf(git_buf *buf, const char *format, ...)
216 {
217 int r;
218 va_list ap;
219
220 va_start(ap, format);
221 r = git_buf_vprintf(buf, format, ap);
222 va_end(ap);
223
224 return r;
225 }
226
227 void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf)
228 {
229 size_t copylen;
230
231 assert(data && datasize && buf);
232
233 data[0] = '\0';
234
235 if (buf->size == 0 || buf->asize <= 0)
236 return;
237
238 copylen = buf->size;
239 if (copylen > datasize - 1)
240 copylen = datasize - 1;
241 memmove(data, buf->ptr, copylen);
242 data[copylen] = '\0';
243 }
244
245 void git_buf_consume(git_buf *buf, const char *end)
246 {
247 if (end > buf->ptr && end <= buf->ptr + buf->size) {
248 size_t consumed = end - buf->ptr;
249 memmove(buf->ptr, end, buf->size - consumed);
250 buf->size -= consumed;
251 buf->ptr[buf->size] = '\0';
252 }
253 }
254
255 void git_buf_truncate(git_buf *buf, size_t len)
256 {
257 if (len < buf->size) {
258 buf->size = len;
259 buf->ptr[buf->size] = '\0';
260 }
261 }
262
263 void git_buf_shorten(git_buf *buf, size_t amount)
264 {
265 if (amount > buf->size)
266 amount = buf->size;
267
268 buf->size = buf->size - amount;
269 buf->ptr[buf->size] = '\0';
270 }
271
272 void git_buf_rtruncate_at_char(git_buf *buf, char separator)
273 {
274 ssize_t idx = git_buf_rfind_next(buf, separator);
275 git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx);
276 }
277
278 void git_buf_swap(git_buf *buf_a, git_buf *buf_b)
279 {
280 git_buf t = *buf_a;
281 *buf_a = *buf_b;
282 *buf_b = t;
283 }
284
285 char *git_buf_detach(git_buf *buf)
286 {
287 char *data = buf->ptr;
288
289 if (buf->asize == 0 || buf->ptr == git_buf__oom)
290 return NULL;
291
292 git_buf_init(buf, 0);
293
294 return data;
295 }
296
297 void git_buf_attach(git_buf *buf, char *ptr, size_t asize)
298 {
299 git_buf_free(buf);
300
301 if (ptr) {
302 buf->ptr = ptr;
303 buf->size = strlen(ptr);
304 if (asize)
305 buf->asize = (asize < buf->size) ? buf->size + 1 : asize;
306 else /* pass 0 to fall back on strlen + 1 */
307 buf->asize = buf->size + 1;
308 } else {
309 git_buf_grow(buf, asize);
310 }
311 }
312
313 int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
314 {
315 va_list ap;
316 int i;
317 size_t total_size = 0, original_size = buf->size;
318 char *out, *original = buf->ptr;
319
320 if (buf->size > 0 && buf->ptr[buf->size - 1] != separator)
321 ++total_size; /* space for initial separator */
322
323 /* Make two passes to avoid multiple reallocation */
324
325 va_start(ap, nbuf);
326 for (i = 0; i < nbuf; ++i) {
327 const char* segment;
328 size_t segment_len;
329
330 segment = va_arg(ap, const char *);
331 if (!segment)
332 continue;
333
334 segment_len = strlen(segment);
335 total_size += segment_len;
336 if (segment_len == 0 || segment[segment_len - 1] != separator)
337 ++total_size; /* space for separator */
338 }
339 va_end(ap);
340
341 /* expand buffer if needed */
342 if (total_size == 0)
343 return 0;
344 if (git_buf_grow(buf, buf->size + total_size + 1) < 0)
345 return -1;
346
347 out = buf->ptr + buf->size;
348
349 /* append separator to existing buf if needed */
350 if (buf->size > 0 && out[-1] != separator)
351 *out++ = separator;
352
353 va_start(ap, nbuf);
354 for (i = 0; i < nbuf; ++i) {
355 const char* segment;
356 size_t segment_len;
357
358 segment = va_arg(ap, const char *);
359 if (!segment)
360 continue;
361
362 /* deal with join that references buffer's original content */
363 if (segment >= original && segment < original + original_size) {
364 size_t offset = (segment - original);
365 segment = buf->ptr + offset;
366 segment_len = original_size - offset;
367 } else {
368 segment_len = strlen(segment);
369 }
370
371 /* skip leading separators */
372 if (out > buf->ptr && out[-1] == separator)
373 while (segment_len > 0 && *segment == separator) {
374 segment++;
375 segment_len--;
376 }
377
378 /* copy over next buffer */
379 if (segment_len > 0) {
380 memmove(out, segment, segment_len);
381 out += segment_len;
382 }
383
384 /* append trailing separator (except for last item) */
385 if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator)
386 *out++ = separator;
387 }
388 va_end(ap);
389
390 /* set size based on num characters actually written */
391 buf->size = out - buf->ptr;
392 buf->ptr[buf->size] = '\0';
393
394 return 0;
395 }
396
397 int git_buf_join(
398 git_buf *buf,
399 char separator,
400 const char *str_a,
401 const char *str_b)
402 {
403 size_t strlen_a = str_a ? strlen(str_a) : 0;
404 size_t strlen_b = strlen(str_b);
405 int need_sep = 0;
406 ssize_t offset_a = -1;
407
408 /* not safe to have str_b point internally to the buffer */
409 assert(str_b < buf->ptr || str_b > buf->ptr + buf->size);
410
411 /* figure out if we need to insert a separator */
412 if (separator && strlen_a) {
413 while (*str_b == separator) { str_b++; strlen_b--; }
414 if (str_a[strlen_a - 1] != separator)
415 need_sep = 1;
416 }
417
418 /* str_a could be part of the buffer */
419 if (str_a >= buf->ptr && str_a < buf->ptr + buf->size)
420 offset_a = str_a - buf->ptr;
421
422 if (git_buf_grow(buf, strlen_a + strlen_b + need_sep + 1) < 0)
423 return -1;
424
425 /* fix up internal pointers */
426 if (offset_a >= 0)
427 str_a = buf->ptr + offset_a;
428
429 /* do the actual copying */
430 if (offset_a != 0)
431 memmove(buf->ptr, str_a, strlen_a);
432 if (need_sep)
433 buf->ptr[strlen_a] = separator;
434 memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b);
435
436 buf->size = strlen_a + strlen_b + need_sep;
437 buf->ptr[buf->size] = '\0';
438
439 return 0;
440 }
441
442 void git_buf_rtrim(git_buf *buf)
443 {
444 while (buf->size > 0) {
445 if (!git__isspace(buf->ptr[buf->size - 1]))
446 break;
447
448 buf->size--;
449 }
450
451 buf->ptr[buf->size] = '\0';
452 }
453
454 int git_buf_cmp(const git_buf *a, const git_buf *b)
455 {
456 int result = memcmp(a->ptr, b->ptr, min(a->size, b->size));
457 return (result != 0) ? result :
458 (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
459 }
460
461 int git_buf_splice(
462 git_buf *buf,
463 size_t where,
464 size_t nb_to_remove,
465 const char *data,
466 size_t nb_to_insert)
467 {
468 assert(buf &&
469 where <= git_buf_len(buf) &&
470 where + nb_to_remove <= git_buf_len(buf));
471
472 /* Ported from git.git
473 * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
474 */
475 if (git_buf_grow(buf, git_buf_len(buf) + nb_to_insert - nb_to_remove) < 0)
476 return -1;
477
478 memmove(buf->ptr + where + nb_to_insert,
479 buf->ptr + where + nb_to_remove,
480 buf->size - where - nb_to_remove);
481
482 memcpy(buf->ptr + where, data, nb_to_insert);
483
484 buf->size = buf->size + nb_to_insert - nb_to_remove;
485 buf->ptr[buf->size] = '\0';
486 return 0;
487 }
488
489 /*
490 * Public buffers API
491 */
492
493 void git_buffer_free(git_buffer *buffer)
494 {
495 if (!buffer)
496 return;
497
498 if (buffer->ptr != NULL && buffer->available > 0)
499 git__free(buffer->ptr);
500
501 git__memzero(buffer, sizeof(*buffer));
502 }
503
504 static int git_buffer__resize(
505 git_buffer *buffer, size_t want_size, int preserve_data)
506 {
507 int non_allocated_buffer = 0;
508 char *new_ptr;
509
510 assert(buffer);
511
512 /* check if buffer->ptr points to memory owned elsewhere */
513 non_allocated_buffer = (buffer->ptr != NULL && buffer->available == 0);
514
515 if (non_allocated_buffer && !want_size)
516 want_size = buffer->size;
517
518 if (buffer->available >= want_size)
519 return 0;
520
521 if (non_allocated_buffer) {
522 new_ptr = NULL;
523 if (want_size < buffer->size)
524 want_size = buffer->size;
525 } else {
526 new_ptr = buffer->ptr;
527 }
528
529 want_size = (want_size + 7) & ~7; /* round up to multiple of 8 */
530
531 new_ptr = git__realloc(new_ptr, want_size);
532 GITERR_CHECK_ALLOC(new_ptr);
533
534 if (non_allocated_buffer && preserve_data)
535 memcpy(new_ptr, buffer->ptr, buffer->size);
536
537 buffer->ptr = new_ptr;
538 buffer->available = want_size;
539
540 return 0;
541 }
542
543 int git_buffer_resize(git_buffer *buffer, size_t want_size)
544 {
545 return git_buffer__resize(buffer, want_size, true);
546 }
547
548 int git_buffer_copy(
549 git_buffer *buffer, const void *data, size_t datalen)
550 {
551 if (git_buffer__resize(buffer, datalen + 1, false) < 0)
552 return -1;
553 memcpy(buffer->ptr, data, datalen);
554 buffer->ptr[datalen] = '\0';
555 buffer->size = datalen;
556 return 0;
557 }
558