/*- * Copyright (c) 2014 Dag-Erling Smørgrav * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* size of static buffer used for short strings */ #define STATIC_BUF_LEN 16 #define STATIC_BUF_SIZE (STATIC_BUF_LEN * sizeof(char_t)) /* threshold at which we switch from exponential to linear growth */ #define LARGE_BUF_LEN 4096 #define LARGE_BUF_SIZE (LARGE_BUF_LEN * sizeof(char_t)) /* minimum buffer size to store len characters + terminating zero */ #define L2S(len) (((len) + 1) * sizeof(char_t)) /* n rounded up to nearest multiple of p */ #define RUP(n, p) ((((n) + (p) - 1) / (p)) * (p)) /* * Managed string structure */ struct cryb_string { char_t *buf; /* pointer to buffer */ size_t size; /* size of buffer in bytes */ size_t len; /* length of string in characters */ char_t staticbuf[STATIC_BUF_LEN]; }; /* * Allocate a new string */ string * string_new(void) { string *str; if ((str = malloc(sizeof *str)) == NULL) return (NULL); str->buf = str->staticbuf; str->size = sizeof str->staticbuf; str->len = 0; str->staticbuf[0] = 0; return (str); } /* * Return the length of a string */ size_t string_len(const string *str) { return (str->len); } /* * Duplicate an existing string */ string * string_dup(const string *str) { string *newstr; if ((newstr = string_new()) == NULL) return (NULL); if (string_expand(newstr, str->len) != 0) { string_delete(newstr); return (NULL); } memcpy(newstr->buf, str->buf, L2S(str->len)); newstr->len = str->len; return (newstr); } /* * Duplicate an existing null-terminated string */ string * string_dup_cs(const char_t *cs, size_t len) { string *newstr; if ((newstr = string_new()) == NULL) return (NULL); if (string_append_cs(newstr, cs, len) < 0) { string_delete(newstr); return (NULL); } return (newstr); } /* * Delete a string */ void string_delete(string *str) { if (str != NULL) { if (str->buf != str->staticbuf) free(str->buf); free(str); } } /* * Expand the underlying storage for a string so it can hold up to newlen * characters. */ int string_expand(string *str, size_t newlen) { size_t newsize; char_t *newbuf; /* does it already fit? */ if (L2S(newlen) <= str->size) return (0); /* compute the new size */ if (L2S(newlen) < LARGE_BUF_SIZE) { /* below the threshold, grow exponentially. */ newsize = str->size; while (newsize < L2S(newlen)) newsize *= 2; } else { /* above it, grow linearly. */ newsize = RUP(L2S(newlen), LARGE_BUF_SIZE); } /* allocate / reallocate */ if (str->buf == str->staticbuf) { /* we've been using the static buffer until now */ if ((newbuf = malloc(newsize)) == NULL) return (-1); memcpy(newbuf, str->staticbuf, L2S(str->len)); } else { /* we're already using an allocated buffer */ if ((newbuf = realloc(str->buf, newsize)) == NULL) return (-1); } /* replace */ str->buf = newbuf; str->size = newsize; return (0); } /* * Shrink the underlying storage for a string to the minimum required to * hold its current contents. */ void string_shrink(string *str) { size_t newsize; char_t *newbuf; if (str->buf != str->staticbuf) { if (L2S(str->len) <= STATIC_BUF_SIZE) { memcpy(str->staticbuf, str->buf, L2S(str->len)); free(str->buf); newbuf = str->staticbuf; newsize = STATIC_BUF_SIZE; } else if (L2S(str->len) >= LARGE_BUF_SIZE) { newsize = RUP(L2S(str->len), LARGE_BUF_SIZE); newbuf = realloc(str->buf, newsize); } else { newsize = LARGE_BUF_SIZE; while (newsize / 2 > L2S(str->len)) newsize = newsize / 2; newbuf = realloc(str->buf, newsize); } str->buf = newbuf; str->size = newsize; } } /* * Truncate a string to the specified length, and shrink the underlying * storage accordingly. */ ssize_t string_trunc(string *str, size_t len) { if (len < str->len) { str->buf[len] = 0; str->len = len; string_shrink(str); } return (str->len); } /* * Append a single character to the string. */ ssize_t string_append_c(string *str, char_t ch) { ssize_t ret; if ((ret = string_expand(str, str->len + 1)) < 0) return (ret); str->buf[str->len++] = ch; str->buf[str->len] = 0; return (str->len); } /* * Append a null-terminated string to the string. */ ssize_t string_append_cs(string *str, const char_t *cs, size_t len) { ssize_t ret; while (*cs && len--) { if ((ret = string_expand(str, str->len + 1)) < 0) return (ret); str->buf[str->len++] = *cs++; str->buf[str->len] = 0; } return (str->len); } /* * Append one string to another. */ ssize_t string_append_string(string *str, const string *other, size_t len) { ssize_t ret; if (len > other->len) len = other->len; if ((ret = string_expand(str, str->len + len)) < 0) return (ret); memcpy(str->buf + str->len, other->buf, len * sizeof(char_t)); str->len += len; str->buf[str->len] = 0; return (str->len); } /* * Append to a string using printf() */ ssize_t string_printf(string *str, const char_t *fmt, ...) { ssize_t ret; va_list ap; va_start(ap, fmt); ret = string_vprintf(str, fmt, ap); va_end(ap); return (ret); } /* * Append to a string using vprintf() */ ssize_t string_vprintf(string *str, const char_t *fmt, va_list ap) { va_list ap2; ssize_t res, ret; int len; /* * Try to to print into the remaining space. If that fails, * expand the underlying storage and try again. */ for (;;) { res = str->size / sizeof(char_t) - str->len; va_copy(ap2, ap); len = vsnprintf(str->buf + str->len, res, fmt, ap); va_end(ap2); if (len < res) break; str->buf[str->len] = 0; if ((ret = string_expand(str, str->len + len)) < 0) return (ret); } str->len += len; str->buf[str->len] = 0; return (str->len); } /* * Compare two strings, returning a negative value if the first is * lexically less than the second, a positive value if the opposite is * true, and zero if they are equal. */ int string_compare(const string *s1, const string *s2) { const char_t *p1, *p2; for (p1 = s1->buf, p2 = s2->buf; *p1 && *p2; ++p1, ++p2) if (*p1 != *p2) return (*p1 < *p2 ? -1 : 1); return (*p1 ? 1 : *p2 ? -1 : 0); } /* * Compare two strings, returning true (non-zero) if they are equal and * false (zero) if they are not. */ int string_equal(const string *s1, const string *s2) { const char_t *p1, *p2; for (p1 = s1->buf, p2 = s2->buf; *p1 && *p2; ++p1, ++p2) if (*p1 != *p2) return (0); return (*p1 || *p2 ? 0 : 1); }