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