From 40feb616242c4e29395659ff1873c9fa35b31dcd Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Tue, 9 May 2023 15:54:24 -0700 Subject: til_settings: recursive til_settings_as_arg() Currently in rototiller the only (de)serialization format of settings is the args strings, so this is a rather critical piece to get in for recursive settings to really be usable. This is a quick and dirty implementation utilizing open_memstream() which despite being POSIX has spotty support (I don't think MacOS has it for instance). So that's probably something to rip out in the future. Nonetheless, this moves things forward and works fine on GNU. --- src/til_settings.c | 94 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/src/til_settings.c b/src/til_settings.c index 4631849..239e359 100644 --- a/src/til_settings.c +++ b/src/til_settings.c @@ -464,62 +464,76 @@ int til_setting_spec_check(const til_setting_spec_t *spec, const char *value) } -/* wrapper around sprintf for convenient buffer size computation */ -/* supply NULL buf when computing size, size and offset are ignored. - * supply non-NULL for actual writing into buf of size bytes @ offset. - * return value is number of bytes (potentially if !buf) written - */ -static int snpf(char *buf, size_t size, off_t offset, const char *format, ...) +static inline void fputc_escaped(FILE *out, int c, unsigned depth) { - size_t avail = 0; - va_list ap; - int r; - - if (buf) { - assert(size > offset); + unsigned escapes = 0; - avail = size - offset; - buf += offset; + for (unsigned i = 0; i < depth; i++) { + escapes <<= 1; + escapes += 1; } - va_start(ap, format); - r = vsnprintf(buf, avail, format, ap); - va_end(ap); + for (unsigned i = 0; i < escapes; i++) + fputc('\\', out); - return r; + fputc(c, out); } -char * til_settings_as_arg(const til_settings_t *settings) +static inline void fputs_escaped(FILE *out, const char *value, unsigned depth) { - char *buf = NULL; - size_t off, size; - - /* intentionally avoided open_memstream for portability reasons */ - for (;;) { - unsigned i; + char c; + + while ((c = *value++)) { + switch (c) { + case '\'': /* this isn't strictly necessary, but let's just make settings-as-arg easily quotable for shell purposes, excessive escaping is otherwise benign */ + case '=': + case ',': + case '\\': + fputc_escaped(out, c, depth); + break; + default: + fputc(c, out); + break; + } + } +} - for (i = off = 0; i < settings->num; i++) { - if (i > 0) - off += snpf(buf, size, off, ","); - off += snpf(buf, size, off, "%s", settings->settings[i]->key); +static void settings_as_arg(const til_settings_t *settings, unsigned depth, FILE *out) +{ + for (size_t i = 0; i < settings->num; i++) { + if (i > 0) + fputc_escaped(out, ',', depth); + if (settings->settings[i]->key) { + fputs_escaped(out, settings->settings[i]->key, depth); if (settings->settings[i]->value) - off += snpf(buf, size, off, "=%s", settings->settings[i]->value); + fputc_escaped(out, '=', depth); } - if (!buf) { - size = off + 1; - buf = calloc(size, sizeof(char)); - if (!buf) - return NULL; - - continue; + if (settings->settings[i]->value_as_nested_settings) { + settings_as_arg(settings->settings[i]->value_as_nested_settings, depth + 1, out); + } else if (settings->settings[i]->value) { + fputs_escaped(out, settings->settings[i]->value, depth); } - - break; } +} - return buf; + +char * til_settings_as_arg(const til_settings_t *settings) +{ + FILE *out; + char *outbuf; + size_t outsize; + + out = open_memstream(&outbuf, &outsize); /* TODO FIXME: open_memstream() isn't portable */ + if (!out) + return NULL; + + settings_as_arg(settings, 0, out); + + fclose(out); + + return outbuf; } -- cgit v1.2.3