/* * This is a small library that provides support for "smart" strings in * ANSI C. It defines a new type for strings: smartstr_t, as well as a set of * functions that deal with this type. The library features automatic * allocation and resizing to protect the programmer against the most common * errors when working with variable-size strings in C. * * https://sourceforge.net/projects/smartstr/ * * Copyright (C) 2019-2023 Mateusz Viste * Published under the terms of the MIT license. */ #include <stdarg.h> /* va_start() */ #include <stdlib.h> /* calloc() */ #include <stdio.h> /* snprintf() */ #include <string.h> /* memcpy(), strlen() */ #include "smartstr.h" /* initial size (in bytes) for new smartstr allocations */ #define SMARTSTR_INITSZ 32 /* define the internal struct (opaque to the user) */ struct smartstr_struct { unsigned long allocsz; unsigned long curlen; char s[1]; }; /* reallocs **s to be able to hold a string of at least newsz bytes. * reallocation may both increase or decrease *s alloc size * this function will never truncate existing content */ static int smartstr_realloc(smartstr_t **s, unsigned long newsz) { smartstr_t *new; unsigned long newalloc; if (newsz < (*s)->curlen) newsz = (*s)->curlen + 1; if (newsz < SMARTSTR_INITSZ) newsz = SMARTSTR_INITSZ; /* find out the size of the new allocation */ newalloc = (*s)->allocsz; while (newsz > newalloc) newalloc *= 2; while (newsz < (newalloc / 2)) newalloc /= 2; if (newalloc == (*s)->allocsz) return(0); /* no job to do */ /* ready to do the mem job now */ new = realloc(*s, sizeof(smartstr_t) + newsz); if (new == NULL) return(-1); new->allocsz = newsz; *s = new; return(0); } /**************************************************************************** *** PUBLIC INTERFACE ******************************************************* ****************************************************************************/ int smartstr_addf(smartstr_t **out, const char *fmt, ...) { int res = 0; va_list ap; unsigned long availspace; long reslen; if (out == NULL) return(-2); if (*out == NULL) { *out = smartstr_new(); if (*out == NULL) return(-1); } RETRY: availspace = (*out)->allocsz - (*out)->curlen; va_start(ap, fmt); /* I need to disable the "string is not a literal" warning here so clang does not whine about it */ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" reslen = vsnprintf((*out)->s + (*out)->curlen, availspace, fmt, ap); #pragma clang diagnostic pop va_end(ap); /* if failed, then realloc and try again */ if (reslen >= (long)availspace) { (*out)->s[(*out)->curlen] = 0; /* trim the result of the snprintf() attempt */ if (smartstr_realloc(out, (*out)->curlen + (unsigned long)reslen + 1) == 0) goto RETRY; res = -1; /* otherwise we are out of memory! */ } else if (reslen < 0) { /* snprintf() error! */ (*out)->s[(*out)->curlen] = 0; res = -4; } else { (*out)->curlen += (unsigned long)reslen; } return(res); } int smartstr_adds(smartstr_t **out, const char *s) { unsigned long availspace; size_t slen; if (out == NULL) return(-2); if (*out == NULL) { *out = smartstr_new(); if (*out == NULL) return(-1); } if ((s == NULL) || (s[0] == 0)) return(0); /* appending an empty string is super fast */ slen = strlen(s); /* compute avail storage space */ availspace = (*out)->allocsz - (*out)->curlen; /* increase storage if needed */ if (availspace <= slen) { if (smartstr_realloc(out, (*out)->curlen + slen + 1) != 0) return(-1); } /* copy string and update length */ memcpy((*out)->s + (*out)->curlen, s, slen + 1); /* +1 because I'm interested in the terminator as well */ (*out)->curlen += slen; return(0); } int smartstr_addc(smartstr_t **out, const char c) { if (out == NULL) return(-1); if (*out == NULL) { *out = smartstr_new(); if (*out == NULL) return(-1); } /* realloc if needed */ if ((*out)->curlen + 1 >= (*out)->allocsz) { if (smartstr_realloc(out, (*out)->curlen + 1) != 0) return(-2); } /* append char */ (*out)->s[(*out)->curlen++] = c; (*out)->s[(*out)->curlen] = 0; /* I'm a friend of Sarah Connor - is she here? */ /* */ return(0); } int smartstr_addhead(smartstr_t **out, const char *head, size_t hlen) { if (hlen == 0) return(0); /* adding an empty string is easy */ if (*out == NULL) { *out = smartstr_new(); if (*out == NULL) return(-1); } /* realloc space if needed */ if (smartstr_realloc(out, (*out)->curlen + hlen) != 0) return(-1); /* move current content right by hlen offset */ memmove((*out)->s + hlen, (*out)->s, (*out)->curlen + 1); /* insert head */ memcpy((*out)->s, head, hlen); /* update curlen */ (*out)->curlen += hlen; return(0); } void smartstr_truncate(smartstr_t **s, size_t maxlen) { /* do I need to do anything at all? */ if ((s == NULL) || (*s == NULL)) return; if (maxlen >= (*s)->curlen) return; /* curlen > maxlen, let's truncate */ (*s)->curlen = maxlen; (*s)->s[maxlen] = 0; /* adjust memory allocation, perhaps some bytes can be saved */ smartstr_realloc(s, 0); } int smartstr_cmp(const smartstr_t *s1, const smartstr_t *s2) { /* watch out for NULL situations */ if ((s1 == NULL) && (s2 == NULL)) return(0); if ((s1 == NULL) || (s2 == NULL)) return(1); /* cmp string lengths */ if (s1->curlen != s2->curlen) return(1); /* same length */ if (s1->curlen == 0) return(0); /* two empty strings are always equal */ return(memcmp(s1->s, s2->s, s1->curlen)); } int smartstr_set(smartstr_t **out, const char *init) { unsigned long initlen; if (out == NULL) return(-1); if (*out == NULL) { *out = smartstr_new(); if (*out == NULL) return(-1); } /* */ if (init == NULL) { initlen = 0; } else { initlen = strlen(init); } if (smartstr_realloc(out, initlen + 1) != 0) { return(-1); } if (init != NULL) memcpy((*out)->s, init, initlen + 1); (*out)->curlen = initlen; return(0); } int smartstr_cat(smartstr_t **s1, const smartstr_t *s2) { unsigned long targetsz; if (s1 == NULL) return(-1); if (s2 == NULL) return(0); if (*s1 == NULL) { *s1 = smartstr_new(); if (*s1 == NULL) return(-1); } /* make sure that s1 is big enough */ targetsz = (*s1)->curlen + s2->curlen + 1; /* do a realloc if needed */ if (smartstr_realloc(s1, targetsz) != 0) return(-2); /* now we good to do the actual copying */ memcpy((*s1)->s + (*s1)->curlen, s2->s, s2->curlen + 1); /* +1 to catch s2's NULL terminator */ (*s1)->curlen += s2->curlen; return(0); } size_t smartstr_len(const smartstr_t *s) { if (s == NULL) return(0); return(s->curlen); } smartstr_t *smartstr_new(void) { smartstr_t *s; s = calloc(1, sizeof(smartstr_t) + SMARTSTR_INITSZ); if (s == NULL) return(NULL); s->allocsz = SMARTSTR_INITSZ; return(s); } void smartstr_free(smartstr_t **s) { free(*s); *s = NULL; } const char *smartstr_ptr(const smartstr_t *s) { if (s == NULL) return(NULL); return(s->s); }