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