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