1 | /* SDSLib 2.0 -- A C dynamic strings library |
2 | * |
3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com> |
4 | * Copyright (c) 2015, Oran Agra |
5 | * Copyright (c) 2015, Redis Labs, Inc |
6 | * All rights reserved. |
7 | * |
8 | * Redistribution and use in source and binary forms, with or without |
9 | * modification, are permitted provided that the following conditions are met: |
10 | * |
11 | * * Redistributions of source code must retain the above copyright notice, |
12 | * this list of conditions and the following disclaimer. |
13 | * * Redistributions in binary form must reproduce the above copyright |
14 | * notice, this list of conditions and the following disclaimer in the |
15 | * documentation and/or other materials provided with the distribution. |
16 | * * Neither the name of Redis nor the names of its contributors may be used |
17 | * to endorse or promote products derived from this software without |
18 | * specific prior written permission. |
19 | * |
20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
30 | * POSSIBILITY OF SUCH DAMAGE. |
31 | */ |
32 | |
33 | #include <stdio.h> |
34 | #include <stdlib.h> |
35 | #include <string.h> |
36 | #include <ctype.h> |
37 | #include <assert.h> |
38 | #include <limits.h> |
39 | #include "sds.hpp" |
40 | |
41 | namespace duckdb_hll { |
42 | |
43 | static inline int sdsHdrSize(char type) { |
44 | switch(type&SDS_TYPE_MASK) { |
45 | case SDS_TYPE_5: |
46 | return sizeof(struct sdshdr5); |
47 | case SDS_TYPE_8: |
48 | return sizeof(struct sdshdr8); |
49 | case SDS_TYPE_16: |
50 | return sizeof(struct sdshdr16); |
51 | case SDS_TYPE_32: |
52 | return sizeof(struct sdshdr32); |
53 | case SDS_TYPE_64: |
54 | return sizeof(struct sdshdr64); |
55 | } |
56 | return 0; |
57 | } |
58 | |
59 | static inline char sdsReqType(size_t string_size) { |
60 | if (string_size < 1<<5) |
61 | return SDS_TYPE_5; |
62 | if (string_size < 1<<8) |
63 | return SDS_TYPE_8; |
64 | if (string_size < 1<<16) |
65 | return SDS_TYPE_16; |
66 | #if (LONG_MAX == LLONG_MAX) |
67 | if (string_size < 1ll<<32) |
68 | return SDS_TYPE_32; |
69 | return SDS_TYPE_64; |
70 | #else |
71 | return SDS_TYPE_32; |
72 | #endif |
73 | } |
74 | |
75 | /* Create a new sds string with the content specified by the 'init' pointer |
76 | * and 'initlen'. |
77 | * If NULL is used for 'init' the string is initialized with zero bytes. |
78 | * If SDS_NOINIT is used, the buffer is left uninitialized; |
79 | * |
80 | * The string is always null-termined (all the sds strings are, always) so |
81 | * even if you create an sds string with: |
82 | * |
83 | * mystring = sdsnewlen("abc",3); |
84 | * |
85 | * You can print the string with printf() as there is an implicit \0 at the |
86 | * end of the string. However the string is binary safe and can contain |
87 | * \0 characters in the middle, as the length is stored in the sds header. */ |
88 | sds sdsnewlen(const void *init, size_t initlen) { |
89 | void *sh; |
90 | sds s; |
91 | char type = sdsReqType(string_size: initlen); |
92 | /* Empty strings are usually created in order to append. Use type 8 |
93 | * since type 5 is not good at this. */ |
94 | if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; |
95 | int hdrlen = sdsHdrSize(type); |
96 | unsigned char *fp; /* flags pointer. */ |
97 | |
98 | sh = malloc(size: hdrlen+initlen+1); |
99 | if (!init) |
100 | memset(s: sh, c: 0, n: hdrlen+initlen+1); |
101 | if (sh == NULL) return NULL; |
102 | s = (char*)sh+hdrlen; |
103 | fp = ((unsigned char*)s)-1; |
104 | switch(type) { |
105 | case SDS_TYPE_5: { |
106 | *fp = type | (initlen << SDS_TYPE_BITS); |
107 | break; |
108 | } |
109 | case SDS_TYPE_8: { |
110 | SDS_HDR_VAR(8,s); |
111 | sh->len = initlen; |
112 | sh->alloc = initlen; |
113 | *fp = type; |
114 | break; |
115 | } |
116 | case SDS_TYPE_16: { |
117 | SDS_HDR_VAR(16,s); |
118 | sh->len = initlen; |
119 | sh->alloc = initlen; |
120 | *fp = type; |
121 | break; |
122 | } |
123 | case SDS_TYPE_32: { |
124 | SDS_HDR_VAR(32,s); |
125 | sh->len = initlen; |
126 | sh->alloc = initlen; |
127 | *fp = type; |
128 | break; |
129 | } |
130 | case SDS_TYPE_64: { |
131 | SDS_HDR_VAR(64,s); |
132 | sh->len = initlen; |
133 | sh->alloc = initlen; |
134 | *fp = type; |
135 | break; |
136 | } |
137 | } |
138 | if (initlen && init) |
139 | memcpy(dest: s, src: init, n: initlen); |
140 | s[initlen] = '\0'; |
141 | return s; |
142 | } |
143 | |
144 | /* Create an empty (zero length) sds string. Even in this case the string |
145 | * always has an implicit null term. */ |
146 | sds sdsempty(void) { |
147 | return sdsnewlen(init: "" ,initlen: 0); |
148 | } |
149 | |
150 | /* Create a new sds string starting from a null terminated C string. */ |
151 | sds sdsnew(const char *init) { |
152 | size_t initlen = (init == NULL) ? 0 : strlen(s: init); |
153 | return sdsnewlen(init, initlen); |
154 | } |
155 | |
156 | /* Duplicate an sds string. */ |
157 | sds sdsdup(const sds s) { |
158 | return sdsnewlen(init: s, initlen: sdslen(s)); |
159 | } |
160 | |
161 | /* Free an sds string. No operation is performed if 's' is NULL. */ |
162 | void sdsfree(sds s) { |
163 | if (s == NULL) return; |
164 | free(ptr: (char*)s-sdsHdrSize(type: s[-1])); |
165 | } |
166 | |
167 | /* Set the sds string length to the length as obtained with strlen(), so |
168 | * considering as content only up to the first null term character. |
169 | * |
170 | * This function is useful when the sds string is hacked manually in some |
171 | * way, like in the following example: |
172 | * |
173 | * s = sdsnew("foobar"); |
174 | * s[2] = '\0'; |
175 | * sdsupdatelen(s); |
176 | * printf("%d\n", sdslen(s)); |
177 | * |
178 | * The output will be "2", but if we comment out the call to sdsupdatelen() |
179 | * the output will be "6" as the string was modified but the logical length |
180 | * remains 6 bytes. */ |
181 | void sdsupdatelen(sds s) { |
182 | size_t reallen = strlen(s: s); |
183 | sdssetlen(s, newlen: reallen); |
184 | } |
185 | |
186 | /* Modify an sds string in-place to make it empty (zero length). |
187 | * However all the existing buffer is not discarded but set as free space |
188 | * so that next append operations will not require allocations up to the |
189 | * number of bytes previously available. */ |
190 | void sdsclear(sds s) { |
191 | sdssetlen(s, newlen: 0); |
192 | s[0] = '\0'; |
193 | } |
194 | |
195 | /* Enlarge the free space at the end of the sds string so that the caller |
196 | * is sure that after calling this function can overwrite up to addlen |
197 | * bytes after the end of the string, plus one more byte for nul term. |
198 | * |
199 | * Note: this does not change the *length* of the sds string as returned |
200 | * by sdslen(), but only the free buffer space we have. */ |
201 | sds sdsMakeRoomFor(sds s, size_t addlen) { |
202 | void *sh, *newsh; |
203 | size_t avail = sdsavail(s); |
204 | size_t len, newlen; |
205 | char type, oldtype = s[-1] & SDS_TYPE_MASK; |
206 | int hdrlen; |
207 | |
208 | /* Return ASAP if there is enough space left. */ |
209 | if (avail >= addlen) return s; |
210 | |
211 | len = sdslen(s); |
212 | sh = (char*)s-sdsHdrSize(type: oldtype); |
213 | newlen = (len+addlen); |
214 | if (newlen < SDS_MAX_PREALLOC) |
215 | newlen *= 2; |
216 | else |
217 | newlen += SDS_MAX_PREALLOC; |
218 | |
219 | type = sdsReqType(string_size: newlen); |
220 | |
221 | /* Don't use type 5: the user is appending to the string and type 5 is |
222 | * not able to remember empty space, so sdsMakeRoomFor() must be called |
223 | * at every appending operation. */ |
224 | if (type == SDS_TYPE_5) type = SDS_TYPE_8; |
225 | |
226 | hdrlen = sdsHdrSize(type); |
227 | if (oldtype==type) { |
228 | newsh = realloc(ptr: sh, size: hdrlen+newlen+1); |
229 | if (newsh == NULL) return NULL; |
230 | s = (char*)newsh+hdrlen; |
231 | } else { |
232 | /* Since the header size changes, need to move the string forward, |
233 | * and can't use realloc */ |
234 | newsh = malloc(size: hdrlen+newlen+1); |
235 | if (newsh == NULL) return NULL; |
236 | memcpy(dest: (char*)newsh+hdrlen, src: s, n: len+1); |
237 | free(ptr: sh); |
238 | s = (char*)newsh+hdrlen; |
239 | s[-1] = type; |
240 | sdssetlen(s, newlen: len); |
241 | } |
242 | sdssetalloc(s, newlen); |
243 | return s; |
244 | } |
245 | |
246 | /* Reallocate the sds string so that it has no free space at the end. The |
247 | * contained string remains not altered, but next concatenation operations |
248 | * will require a reallocation. |
249 | * |
250 | * After the call, the passed sds string is no longer valid and all the |
251 | * references must be substituted with the new pointer returned by the call. */ |
252 | sds sdsRemoveFreeSpace(sds s) { |
253 | void *sh, *newsh; |
254 | char type, oldtype = s[-1] & SDS_TYPE_MASK; |
255 | int hdrlen, oldhdrlen = sdsHdrSize(type: oldtype); |
256 | size_t len = sdslen(s); |
257 | sh = (char*)s-oldhdrlen; |
258 | |
259 | /* Check what would be the minimum SDS header that is just good enough to |
260 | * fit this string. */ |
261 | type = sdsReqType(string_size: len); |
262 | hdrlen = sdsHdrSize(type); |
263 | |
264 | /* If the type is the same, or at least a large enough type is still |
265 | * required, we just realloc(), letting the allocator to do the copy |
266 | * only if really needed. Otherwise if the change is huge, we manually |
267 | * reallocate the string to use the different header type. */ |
268 | if (oldtype==type || type > SDS_TYPE_8) { |
269 | newsh = realloc(ptr: sh, size: oldhdrlen+len+1); |
270 | if (newsh == NULL) return NULL; |
271 | s = (char*)newsh+oldhdrlen; |
272 | } else { |
273 | newsh = malloc(size: hdrlen+len+1); |
274 | if (newsh == NULL) return NULL; |
275 | memcpy(dest: (char*)newsh+hdrlen, src: s, n: len+1); |
276 | free(ptr: sh); |
277 | s = (char*)newsh+hdrlen; |
278 | s[-1] = type; |
279 | sdssetlen(s, newlen: len); |
280 | } |
281 | sdssetalloc(s, newlen: len); |
282 | return s; |
283 | } |
284 | |
285 | /* Return the total size of the allocation of the specified sds string, |
286 | * including: |
287 | * 1) The sds header before the pointer. |
288 | * 2) The string. |
289 | * 3) The free buffer at the end if any. |
290 | * 4) The implicit null term. |
291 | */ |
292 | size_t sdsAllocSize(sds s) { |
293 | size_t alloc = sdsalloc(s); |
294 | return sdsHdrSize(type: s[-1])+alloc+1; |
295 | } |
296 | |
297 | /* Return the pointer of the actual SDS allocation (normally SDS strings |
298 | * are referenced by the start of the string buffer). */ |
299 | void *sdsAllocPtr(sds s) { |
300 | return (void*) (s-sdsHdrSize(type: s[-1])); |
301 | } |
302 | |
303 | /* Increment the sds length and decrements the left free space at the |
304 | * end of the string according to 'incr'. Also set the null term |
305 | * in the new end of the string. |
306 | * |
307 | * This function is used in order to fix the string length after the |
308 | * user calls sdsMakeRoomFor(), writes something after the end of |
309 | * the current string, and finally needs to set the new length. |
310 | * |
311 | * Note: it is possible to use a negative increment in order to |
312 | * right-trim the string. |
313 | * |
314 | * Usage example: |
315 | * |
316 | * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the |
317 | * following schema, to cat bytes coming from the kernel to the end of an |
318 | * sds string without copying into an intermediate buffer: |
319 | * |
320 | * oldlen = sdslen(s); |
321 | * s = sdsMakeRoomFor(s, BUFFER_SIZE); |
322 | * nread = read(fd, s+oldlen, BUFFER_SIZE); |
323 | * ... check for nread <= 0 and handle it ... |
324 | * sdsIncrLen(s, nread); |
325 | */ |
326 | void sdsIncrLen(sds s, ssize_t incr) { |
327 | unsigned char flags = s[-1]; |
328 | size_t len; |
329 | switch(flags&SDS_TYPE_MASK) { |
330 | case SDS_TYPE_5: { |
331 | unsigned char *fp = ((unsigned char*)s)-1; |
332 | unsigned char oldlen = SDS_TYPE_5_LEN(flags); |
333 | assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); |
334 | *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); |
335 | len = oldlen+incr; |
336 | break; |
337 | } |
338 | case SDS_TYPE_8: { |
339 | SDS_HDR_VAR(8,s); |
340 | assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); |
341 | len = (sh->len += incr); |
342 | break; |
343 | } |
344 | case SDS_TYPE_16: { |
345 | SDS_HDR_VAR(16,s); |
346 | assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); |
347 | len = (sh->len += incr); |
348 | break; |
349 | } |
350 | case SDS_TYPE_32: { |
351 | SDS_HDR_VAR(32,s); |
352 | assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); |
353 | len = (sh->len += incr); |
354 | break; |
355 | } |
356 | case SDS_TYPE_64: { |
357 | SDS_HDR_VAR(64,s); |
358 | assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); |
359 | len = (sh->len += incr); |
360 | break; |
361 | } |
362 | default: len = 0; /* Just to avoid compilation warnings. */ |
363 | } |
364 | s[len] = '\0'; |
365 | } |
366 | |
367 | /* Grow the sds to have the specified length. Bytes that were not part of |
368 | * the original length of the sds will be set to zero. |
369 | * |
370 | * if the specified length is smaller than the current length, no operation |
371 | * is performed. */ |
372 | sds sdsgrowzero(sds s, size_t len) { |
373 | size_t curlen = sdslen(s); |
374 | |
375 | if (len <= curlen) return s; |
376 | s = sdsMakeRoomFor(s,addlen: len-curlen); |
377 | if (s == NULL) return NULL; |
378 | |
379 | /* Make sure added region doesn't contain garbage */ |
380 | memset(s: s+curlen,c: 0,n: (len-curlen+1)); /* also set trailing \0 byte */ |
381 | sdssetlen(s, newlen: len); |
382 | return s; |
383 | } |
384 | |
385 | /* Append the specified binary-safe string pointed by 't' of 'len' bytes to the |
386 | * end of the specified sds string 's'. |
387 | * |
388 | * After the call, the passed sds string is no longer valid and all the |
389 | * references must be substituted with the new pointer returned by the call. */ |
390 | sds sdscatlen(sds s, const void *t, size_t len) { |
391 | size_t curlen = sdslen(s); |
392 | |
393 | s = sdsMakeRoomFor(s,addlen: len); |
394 | if (s == NULL) return NULL; |
395 | memcpy(dest: s+curlen, src: t, n: len); |
396 | sdssetlen(s, newlen: curlen+len); |
397 | s[curlen+len] = '\0'; |
398 | return s; |
399 | } |
400 | |
401 | /* Append the specified null termianted C string to the sds string 's'. |
402 | * |
403 | * After the call, the passed sds string is no longer valid and all the |
404 | * references must be substituted with the new pointer returned by the call. */ |
405 | sds sdscat(sds s, const char *t) { |
406 | return sdscatlen(s, t, len: strlen(s: t)); |
407 | } |
408 | |
409 | /* Append the specified sds 't' to the existing sds 's'. |
410 | * |
411 | * After the call, the modified sds string is no longer valid and all the |
412 | * references must be substituted with the new pointer returned by the call. */ |
413 | sds sdscatsds(sds s, const sds t) { |
414 | return sdscatlen(s, t, len: sdslen(s: t)); |
415 | } |
416 | |
417 | /* Destructively modify the sds string 's' to hold the specified binary |
418 | * safe string pointed by 't' of length 'len' bytes. */ |
419 | sds sdscpylen(sds s, const char *t, size_t len) { |
420 | if (sdsalloc(s) < len) { |
421 | s = sdsMakeRoomFor(s,addlen: len-sdslen(s)); |
422 | if (s == NULL) return NULL; |
423 | } |
424 | memcpy(dest: s, src: t, n: len); |
425 | s[len] = '\0'; |
426 | sdssetlen(s, newlen: len); |
427 | return s; |
428 | } |
429 | |
430 | /* Like sdscpylen() but 't' must be a null-termined string so that the length |
431 | * of the string is obtained with strlen(). */ |
432 | sds sdscpy(sds s, const char *t) { |
433 | return sdscpylen(s, t, len: strlen(s: t)); |
434 | } |
435 | |
436 | /* Helper for sdscatlonglong() doing the actual number -> string |
437 | * conversion. 's' must point to a string with room for at least |
438 | * SDS_LLSTR_SIZE bytes. |
439 | * |
440 | * The function returns the length of the null-terminated string |
441 | * representation stored at 's'. */ |
442 | #define SDS_LLSTR_SIZE 21 |
443 | int sdsll2str(char *s, long long value) { |
444 | char *p, aux; |
445 | unsigned long long v; |
446 | size_t l; |
447 | |
448 | /* Generate the string representation, this method produces |
449 | * an reversed string. */ |
450 | v = (value < 0) ? -value : value; |
451 | p = s; |
452 | do { |
453 | *p++ = '0'+(v%10); |
454 | v /= 10; |
455 | } while(v); |
456 | if (value < 0) *p++ = '-'; |
457 | |
458 | /* Compute length and add null term. */ |
459 | l = p-s; |
460 | *p = '\0'; |
461 | |
462 | /* Reverse the string. */ |
463 | p--; |
464 | while(s < p) { |
465 | aux = *s; |
466 | *s = *p; |
467 | *p = aux; |
468 | s++; |
469 | p--; |
470 | } |
471 | return l; |
472 | } |
473 | |
474 | /* Identical sdsll2str(), but for unsigned long long type. */ |
475 | int sdsull2str(char *s, unsigned long long v) { |
476 | char *p, aux; |
477 | size_t l; |
478 | |
479 | /* Generate the string representation, this method produces |
480 | * an reversed string. */ |
481 | p = s; |
482 | do { |
483 | *p++ = '0'+(v%10); |
484 | v /= 10; |
485 | } while(v); |
486 | |
487 | /* Compute length and add null term. */ |
488 | l = p-s; |
489 | *p = '\0'; |
490 | |
491 | /* Reverse the string. */ |
492 | p--; |
493 | while(s < p) { |
494 | aux = *s; |
495 | *s = *p; |
496 | *p = aux; |
497 | s++; |
498 | p--; |
499 | } |
500 | return l; |
501 | } |
502 | |
503 | /* Create an sds string from a long long value. It is much faster than: |
504 | * |
505 | * sdscatprintf(sdsempty(),"%lld\n", value); |
506 | */ |
507 | sds sdsfromlonglong(long long value) { |
508 | char buf[SDS_LLSTR_SIZE]; |
509 | int len = sdsll2str(s: buf,value); |
510 | |
511 | return sdsnewlen(init: buf,initlen: len); |
512 | } |
513 | |
514 | /* Like sdscatprintf() but gets va_list instead of being variadic. */ |
515 | sds sdscatvprintf(sds s, const char *fmt, va_list ap) { |
516 | va_list cpy; |
517 | char staticbuf[1024], *buf = staticbuf, *t; |
518 | size_t buflen = strlen(s: fmt)*2; |
519 | |
520 | /* We try to start using a static buffer for speed. |
521 | * If not possible we revert to heap allocation. */ |
522 | if (buflen > sizeof(staticbuf)) { |
523 | buf = (char*) malloc(size: buflen); |
524 | if (buf == NULL) return NULL; |
525 | } else { |
526 | buflen = sizeof(staticbuf); |
527 | } |
528 | |
529 | /* Try with buffers two times bigger every time we fail to |
530 | * fit the string in the current buffer size. */ |
531 | while(1) { |
532 | buf[buflen-2] = '\0'; |
533 | va_copy(cpy,ap); |
534 | vsnprintf(s: buf, maxlen: buflen, format: fmt, arg: cpy); |
535 | va_end(cpy); |
536 | if (buf[buflen-2] != '\0') { |
537 | if (buf != staticbuf) free(ptr: buf); |
538 | buflen *= 2; |
539 | buf = (char*) malloc(size: buflen); |
540 | if (buf == NULL) return NULL; |
541 | continue; |
542 | } |
543 | break; |
544 | } |
545 | |
546 | /* Finally concat the obtained string to the SDS string and return it. */ |
547 | t = sdscat(s, t: buf); |
548 | if (buf != staticbuf) free(ptr: buf); |
549 | return t; |
550 | } |
551 | |
552 | /* Append to the sds string 's' a string obtained using printf-alike format |
553 | * specifier. |
554 | * |
555 | * After the call, the modified sds string is no longer valid and all the |
556 | * references must be substituted with the new pointer returned by the call. |
557 | * |
558 | * Example: |
559 | * |
560 | * s = sdsnew("Sum is: "); |
561 | * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). |
562 | * |
563 | * Often you need to create a string from scratch with the printf-alike |
564 | * format. When this is the need, just use sdsempty() as the target string: |
565 | * |
566 | * s = sdscatprintf(sdsempty(), "... your format ...", args); |
567 | */ |
568 | sds sdscatprintf(sds s, const char *fmt, ...) { |
569 | va_list ap; |
570 | char *t; |
571 | va_start(ap, fmt); |
572 | t = sdscatvprintf(s,fmt,ap); |
573 | va_end(ap); |
574 | return t; |
575 | } |
576 | |
577 | /* This function is similar to sdscatprintf, but much faster as it does |
578 | * not rely on sprintf() family functions implemented by the libc that |
579 | * are often very slow. Moreover directly handling the sds string as |
580 | * new data is concatenated provides a performance improvement. |
581 | * |
582 | * However this function only handles an incompatible subset of printf-alike |
583 | * format specifiers: |
584 | * |
585 | * %s - C String |
586 | * %S - SDS string |
587 | * %i - signed int |
588 | * %I - 64 bit signed integer (long long, int64_t) |
589 | * %u - unsigned int |
590 | * %U - 64 bit unsigned integer (unsigned long long, uint64_t) |
591 | * %% - Verbatim "%" character. |
592 | */ |
593 | sds sdscatfmt(sds s, char const *fmt, ...) { |
594 | size_t initlen = sdslen(s); |
595 | const char *f = fmt; |
596 | long i; |
597 | va_list ap; |
598 | |
599 | va_start(ap,fmt); |
600 | f = fmt; /* Next format specifier byte to process. */ |
601 | i = initlen; /* Position of the next byte to write to dest str. */ |
602 | while(*f) { |
603 | char next, *str; |
604 | size_t l; |
605 | long long num; |
606 | unsigned long long unum; |
607 | |
608 | /* Make sure there is always space for at least 1 char. */ |
609 | if (sdsavail(s)==0) { |
610 | s = sdsMakeRoomFor(s,addlen: 1); |
611 | } |
612 | |
613 | switch(*f) { |
614 | case '%': |
615 | next = *(f+1); |
616 | f++; |
617 | switch(next) { |
618 | case 's': |
619 | case 'S': |
620 | str = va_arg(ap,char*); |
621 | l = (next == 's') ? strlen(s: str) : sdslen(s: str); |
622 | if (sdsavail(s) < l) { |
623 | s = sdsMakeRoomFor(s,addlen: l); |
624 | } |
625 | memcpy(dest: s+i,src: str,n: l); |
626 | sdsinclen(s,inc: l); |
627 | i += l; |
628 | break; |
629 | case 'i': |
630 | case 'I': |
631 | if (next == 'i') |
632 | num = va_arg(ap,int); |
633 | else |
634 | num = va_arg(ap,long long); |
635 | { |
636 | char buf[SDS_LLSTR_SIZE]; |
637 | l = sdsll2str(s: buf,value: num); |
638 | if (sdsavail(s) < l) { |
639 | s = sdsMakeRoomFor(s,addlen: l); |
640 | } |
641 | memcpy(dest: s+i,src: buf,n: l); |
642 | sdsinclen(s,inc: l); |
643 | i += l; |
644 | } |
645 | break; |
646 | case 'u': |
647 | case 'U': |
648 | if (next == 'u') |
649 | unum = va_arg(ap,unsigned int); |
650 | else |
651 | unum = va_arg(ap,unsigned long long); |
652 | { |
653 | char buf[SDS_LLSTR_SIZE]; |
654 | l = sdsull2str(s: buf,v: unum); |
655 | if (sdsavail(s) < l) { |
656 | s = sdsMakeRoomFor(s,addlen: l); |
657 | } |
658 | memcpy(dest: s+i,src: buf,n: l); |
659 | sdsinclen(s,inc: l); |
660 | i += l; |
661 | } |
662 | break; |
663 | default: /* Handle %% and generally %<unknown>. */ |
664 | s[i++] = next; |
665 | sdsinclen(s,inc: 1); |
666 | break; |
667 | } |
668 | break; |
669 | default: |
670 | s[i++] = *f; |
671 | sdsinclen(s,inc: 1); |
672 | break; |
673 | } |
674 | f++; |
675 | } |
676 | va_end(ap); |
677 | |
678 | /* Add null-term */ |
679 | s[i] = '\0'; |
680 | return s; |
681 | } |
682 | |
683 | /* Remove the part of the string from left and from right composed just of |
684 | * contiguous characters found in 'cset', that is a null terminted C string. |
685 | * |
686 | * After the call, the modified sds string is no longer valid and all the |
687 | * references must be substituted with the new pointer returned by the call. |
688 | * |
689 | * Example: |
690 | * |
691 | * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); |
692 | * s = sdstrim(s,"Aa. :"); |
693 | * printf("%s\n", s); |
694 | * |
695 | * Output will be just "Hello World". |
696 | */ |
697 | sds sdstrim(sds s, const char *cset) { |
698 | char *start, *end, *sp, *ep; |
699 | size_t len; |
700 | |
701 | sp = start = s; |
702 | ep = end = s+sdslen(s)-1; |
703 | while(sp <= end && strchr(s: cset, c: *sp)) sp++; |
704 | while(ep > sp && strchr(s: cset, c: *ep)) ep--; |
705 | len = (sp > ep) ? 0 : ((ep-sp)+1); |
706 | if (s != sp) memmove(dest: s, src: sp, n: len); |
707 | s[len] = '\0'; |
708 | sdssetlen(s,newlen: len); |
709 | return s; |
710 | } |
711 | |
712 | /* Turn the string into a smaller (or equal) string containing only the |
713 | * substring specified by the 'start' and 'end' indexes. |
714 | * |
715 | * start and end can be negative, where -1 means the last character of the |
716 | * string, -2 the penultimate character, and so forth. |
717 | * |
718 | * The interval is inclusive, so the start and end characters will be part |
719 | * of the resulting string. |
720 | * |
721 | * The string is modified in-place. |
722 | * |
723 | * Example: |
724 | * |
725 | * s = sdsnew("Hello World"); |
726 | * sdsrange(s,1,-1); => "ello World" |
727 | */ |
728 | void sdsrange(sds s, ssize_t start, ssize_t end) { |
729 | size_t newlen, len = sdslen(s); |
730 | |
731 | if (len == 0) return; |
732 | if (start < 0) { |
733 | start = len+start; |
734 | if (start < 0) start = 0; |
735 | } |
736 | if (end < 0) { |
737 | end = len+end; |
738 | if (end < 0) end = 0; |
739 | } |
740 | newlen = (start > end) ? 0 : (end-start)+1; |
741 | if (newlen != 0) { |
742 | if (start >= (ssize_t)len) { |
743 | newlen = 0; |
744 | } else if (end >= (ssize_t)len) { |
745 | end = len-1; |
746 | newlen = (start > end) ? 0 : (end-start)+1; |
747 | } |
748 | } else { |
749 | start = 0; |
750 | } |
751 | if (start && newlen) memmove(dest: s, src: s+start, n: newlen); |
752 | s[newlen] = 0; |
753 | sdssetlen(s,newlen); |
754 | } |
755 | |
756 | /* Apply tolower() to every character of the sds string 's'. */ |
757 | void sdstolower(sds s) { |
758 | size_t len = sdslen(s), j; |
759 | |
760 | for (j = 0; j < len; j++) s[j] = tolower(c: s[j]); |
761 | } |
762 | |
763 | /* Apply toupper() to every character of the sds string 's'. */ |
764 | void sdstoupper(sds s) { |
765 | size_t len = sdslen(s), j; |
766 | |
767 | for (j = 0; j < len; j++) s[j] = toupper(c: s[j]); |
768 | } |
769 | |
770 | /* Compare two sds strings s1 and s2 with memcmp(). |
771 | * |
772 | * Return value: |
773 | * |
774 | * positive if s1 > s2. |
775 | * negative if s1 < s2. |
776 | * 0 if s1 and s2 are exactly the same binary string. |
777 | * |
778 | * If two strings share exactly the same prefix, but one of the two has |
779 | * additional characters, the longer string is considered to be greater than |
780 | * the smaller one. */ |
781 | int sdscmp(const sds s1, const sds s2) { |
782 | size_t l1, l2, minlen; |
783 | int cmp; |
784 | |
785 | l1 = sdslen(s: s1); |
786 | l2 = sdslen(s: s2); |
787 | minlen = (l1 < l2) ? l1 : l2; |
788 | cmp = memcmp(s1: s1,s2: s2,n: minlen); |
789 | if (cmp == 0) return l1>l2? 1: (l1<l2? -1: 0); |
790 | return cmp; |
791 | } |
792 | |
793 | /* Split 's' with separator in 'sep'. An array |
794 | * of sds strings is returned. *count will be set |
795 | * by reference to the number of tokens returned. |
796 | * |
797 | * On out of memory, zero length string, zero length |
798 | * separator, NULL is returned. |
799 | * |
800 | * Note that 'sep' is able to split a string using |
801 | * a multi-character separator. For example |
802 | * sdssplit("foo_-_bar","_-_"); will return two |
803 | * elements "foo" and "bar". |
804 | * |
805 | * This version of the function is binary-safe but |
806 | * requires length arguments. sdssplit() is just the |
807 | * same function but for zero-terminated strings. |
808 | */ |
809 | sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) { |
810 | int elements = 0, slots = 5; |
811 | long start = 0, j; |
812 | sds *tokens; |
813 | |
814 | if (seplen < 1 || len < 0) return NULL; |
815 | |
816 | tokens = (sds*) malloc(size: sizeof(sds)*slots); |
817 | if (tokens == NULL) return NULL; |
818 | |
819 | if (len == 0) { |
820 | *count = 0; |
821 | return tokens; |
822 | } |
823 | for (j = 0; j < (len-(seplen-1)); j++) { |
824 | /* make sure there is room for the next element and the final one */ |
825 | if (slots < elements+2) { |
826 | sds *newtokens; |
827 | |
828 | slots *= 2; |
829 | newtokens = (sds*) realloc(ptr: tokens,size: sizeof(sds)*slots); |
830 | if (newtokens == NULL) goto cleanup; |
831 | tokens = newtokens; |
832 | } |
833 | /* search the separator */ |
834 | if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s1: s+j,s2: sep,n: seplen) == 0)) { |
835 | tokens[elements] = sdsnewlen(init: s+start,initlen: j-start); |
836 | if (tokens[elements] == NULL) goto cleanup; |
837 | elements++; |
838 | start = j+seplen; |
839 | j = j+seplen-1; /* skip the separator */ |
840 | } |
841 | } |
842 | /* Add the final element. We are sure there is room in the tokens array. */ |
843 | tokens[elements] = sdsnewlen(init: s+start,initlen: len-start); |
844 | if (tokens[elements] == NULL) goto cleanup; |
845 | elements++; |
846 | *count = elements; |
847 | return tokens; |
848 | |
849 | cleanup: |
850 | { |
851 | int i; |
852 | for (i = 0; i < elements; i++) sdsfree(s: tokens[i]); |
853 | free(ptr: tokens); |
854 | *count = 0; |
855 | return NULL; |
856 | } |
857 | } |
858 | |
859 | /* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ |
860 | void sdsfreesplitres(sds *tokens, int count) { |
861 | if (!tokens) return; |
862 | while(count--) |
863 | sdsfree(s: tokens[count]); |
864 | free(ptr: tokens); |
865 | } |
866 | |
867 | /* Append to the sds string "s" an escaped string representation where |
868 | * all the non-printable characters (tested with isprint()) are turned into |
869 | * escapes in the form "\n\r\a...." or "\x<hex-number>". |
870 | * |
871 | * After the call, the modified sds string is no longer valid and all the |
872 | * references must be substituted with the new pointer returned by the call. */ |
873 | sds sdscatrepr(sds s, const char *p, size_t len) { |
874 | s = sdscatlen(s,t: "\"" ,len: 1); |
875 | while(len--) { |
876 | switch(*p) { |
877 | case '\\': |
878 | case '"': |
879 | s = sdscatprintf(s,fmt: "\\%c" ,*p); |
880 | break; |
881 | case '\n': s = sdscatlen(s,t: "\\n" ,len: 2); break; |
882 | case '\r': s = sdscatlen(s,t: "\\r" ,len: 2); break; |
883 | case '\t': s = sdscatlen(s,t: "\\t" ,len: 2); break; |
884 | case '\a': s = sdscatlen(s,t: "\\a" ,len: 2); break; |
885 | case '\b': s = sdscatlen(s,t: "\\b" ,len: 2); break; |
886 | default: |
887 | if (isprint(*p)) |
888 | s = sdscatprintf(s,fmt: "%c" ,*p); |
889 | else |
890 | s = sdscatprintf(s,fmt: "\\x%02x" ,(unsigned char)*p); |
891 | break; |
892 | } |
893 | p++; |
894 | } |
895 | return sdscatlen(s,t: "\"" ,len: 1); |
896 | } |
897 | |
898 | /* Helper function for sdssplitargs() that returns non zero if 'c' |
899 | * is a valid hex digit. */ |
900 | int is_hex_digit(char c) { |
901 | return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || |
902 | (c >= 'A' && c <= 'F'); |
903 | } |
904 | |
905 | /* Helper function for sdssplitargs() that converts a hex digit into an |
906 | * integer from 0 to 15 */ |
907 | int hex_digit_to_int(char c) { |
908 | switch(c) { |
909 | case '0': return 0; |
910 | case '1': return 1; |
911 | case '2': return 2; |
912 | case '3': return 3; |
913 | case '4': return 4; |
914 | case '5': return 5; |
915 | case '6': return 6; |
916 | case '7': return 7; |
917 | case '8': return 8; |
918 | case '9': return 9; |
919 | case 'a': case 'A': return 10; |
920 | case 'b': case 'B': return 11; |
921 | case 'c': case 'C': return 12; |
922 | case 'd': case 'D': return 13; |
923 | case 'e': case 'E': return 14; |
924 | case 'f': case 'F': return 15; |
925 | default: return 0; |
926 | } |
927 | } |
928 | |
929 | /* Split a line into arguments, where every argument can be in the |
930 | * following programming-language REPL-alike form: |
931 | * |
932 | * foo bar "newline are supported\n" and "\xff\x00otherstuff" |
933 | * |
934 | * The number of arguments is stored into *argc, and an array |
935 | * of sds is returned. |
936 | * |
937 | * The caller should free the resulting array of sds strings with |
938 | * sdsfreesplitres(). |
939 | * |
940 | * Note that sdscatrepr() is able to convert back a string into |
941 | * a quoted string in the same format sdssplitargs() is able to parse. |
942 | * |
943 | * The function returns the allocated tokens on success, even when the |
944 | * input string is empty, or NULL if the input contains unbalanced |
945 | * quotes or closed quotes followed by non space characters |
946 | * as in: "foo"bar or "foo' |
947 | */ |
948 | sds *sdssplitargs(const char *line, int *argc) { |
949 | const char *p = line; |
950 | char *current = NULL; |
951 | char **vector = NULL; |
952 | |
953 | *argc = 0; |
954 | while(1) { |
955 | /* skip blanks */ |
956 | while(*p && isspace(*p)) p++; |
957 | if (*p) { |
958 | /* get a token */ |
959 | int inq=0; /* set to 1 if we are in "quotes" */ |
960 | int insq=0; /* set to 1 if we are in 'single quotes' */ |
961 | int done=0; |
962 | |
963 | if (current == NULL) current = sdsempty(); |
964 | while(!done) { |
965 | if (inq) { |
966 | if (*p == '\\' && *(p+1) == 'x' && |
967 | is_hex_digit(c: *(p+2)) && |
968 | is_hex_digit(c: *(p+3))) |
969 | { |
970 | unsigned char byte; |
971 | |
972 | byte = (hex_digit_to_int(c: *(p+2))*16)+ |
973 | hex_digit_to_int(c: *(p+3)); |
974 | current = sdscatlen(s: current,t: (char*)&byte,len: 1); |
975 | p += 3; |
976 | } else if (*p == '\\' && *(p+1)) { |
977 | char c; |
978 | |
979 | p++; |
980 | switch(*p) { |
981 | case 'n': c = '\n'; break; |
982 | case 'r': c = '\r'; break; |
983 | case 't': c = '\t'; break; |
984 | case 'b': c = '\b'; break; |
985 | case 'a': c = '\a'; break; |
986 | default: c = *p; break; |
987 | } |
988 | current = sdscatlen(s: current,t: &c,len: 1); |
989 | } else if (*p == '"') { |
990 | /* closing quote must be followed by a space or |
991 | * nothing at all. */ |
992 | if (*(p+1) && !isspace(*(p+1))) goto err; |
993 | done=1; |
994 | } else if (!*p) { |
995 | /* unterminated quotes */ |
996 | goto err; |
997 | } else { |
998 | current = sdscatlen(s: current,t: p,len: 1); |
999 | } |
1000 | } else if (insq) { |
1001 | if (*p == '\\' && *(p+1) == '\'') { |
1002 | p++; |
1003 | current = sdscatlen(s: current,t: "'" ,len: 1); |
1004 | } else if (*p == '\'') { |
1005 | /* closing quote must be followed by a space or |
1006 | * nothing at all. */ |
1007 | if (*(p+1) && !isspace(*(p+1))) goto err; |
1008 | done=1; |
1009 | } else if (!*p) { |
1010 | /* unterminated quotes */ |
1011 | goto err; |
1012 | } else { |
1013 | current = sdscatlen(s: current,t: p,len: 1); |
1014 | } |
1015 | } else { |
1016 | switch(*p) { |
1017 | case ' ': |
1018 | case '\n': |
1019 | case '\r': |
1020 | case '\t': |
1021 | case '\0': |
1022 | done=1; |
1023 | break; |
1024 | case '"': |
1025 | inq=1; |
1026 | break; |
1027 | case '\'': |
1028 | insq=1; |
1029 | break; |
1030 | default: |
1031 | current = sdscatlen(s: current,t: p,len: 1); |
1032 | break; |
1033 | } |
1034 | } |
1035 | if (*p) p++; |
1036 | } |
1037 | /* add the token to the vector */ |
1038 | vector = (char**) realloc(ptr: vector,size: ((*argc)+1)*sizeof(char*)); |
1039 | vector[*argc] = current; |
1040 | (*argc)++; |
1041 | current = NULL; |
1042 | } else { |
1043 | /* Even on empty input string return something not NULL. */ |
1044 | if (vector == NULL) vector = (char**) malloc(size: sizeof(void*)); |
1045 | return vector; |
1046 | } |
1047 | } |
1048 | |
1049 | err: |
1050 | while((*argc)--) |
1051 | sdsfree(s: vector[*argc]); |
1052 | free(ptr: vector); |
1053 | if (current) sdsfree(s: current); |
1054 | *argc = 0; |
1055 | return NULL; |
1056 | } |
1057 | |
1058 | /* Modify the string substituting all the occurrences of the set of |
1059 | * characters specified in the 'from' string to the corresponding character |
1060 | * in the 'to' array. |
1061 | * |
1062 | * For instance: sdsmapchars(mystring, "ho", "01", 2) |
1063 | * will have the effect of turning the string "hello" into "0ell1". |
1064 | * |
1065 | * The function returns the sds string pointer, that is always the same |
1066 | * as the input pointer since no resize is needed. */ |
1067 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { |
1068 | size_t j, i, l = sdslen(s); |
1069 | |
1070 | for (j = 0; j < l; j++) { |
1071 | for (i = 0; i < setlen; i++) { |
1072 | if (s[j] == from[i]) { |
1073 | s[j] = to[i]; |
1074 | break; |
1075 | } |
1076 | } |
1077 | } |
1078 | return s; |
1079 | } |
1080 | |
1081 | /* Join an array of C strings using the specified separator (also a C string). |
1082 | * Returns the result as an sds string. */ |
1083 | sds sdsjoin(char **argv, int argc, char *sep) { |
1084 | sds join = sdsempty(); |
1085 | int j; |
1086 | |
1087 | for (j = 0; j < argc; j++) { |
1088 | join = sdscat(s: join, t: argv[j]); |
1089 | if (j != argc-1) join = sdscat(s: join,t: sep); |
1090 | } |
1091 | return join; |
1092 | } |
1093 | |
1094 | /* Like sdsjoin, but joins an array of SDS strings. */ |
1095 | sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { |
1096 | sds join = sdsempty(); |
1097 | int j; |
1098 | |
1099 | for (j = 0; j < argc; j++) { |
1100 | join = sdscatsds(s: join, t: argv[j]); |
1101 | if (j != argc-1) join = sdscatlen(s: join,t: sep,len: seplen); |
1102 | } |
1103 | return join; |
1104 | } |
1105 | |
1106 | /* Wrappers to the allocators used by SDS. Note that SDS will actually |
1107 | * just use the macros defined into sdsalloc.h in order to avoid to pay |
1108 | * the overhead of function calls. Here we define these wrappers only for |
1109 | * the programs SDS is linked to, if they want to touch the SDS internals |
1110 | * even if they use a different allocator. */ |
1111 | void *sdmalloc(size_t size) { return malloc(size: size); } |
1112 | void *sdrealloc(void *ptr, size_t size) { return realloc(ptr: ptr,size: size); } |
1113 | void sdfree(void *ptr) { free(ptr: ptr); } |
1114 | |
1115 | } |