aboutsummaryrefslogtreecommitdiffstats
path: root/libexec_vconstruct_command.c
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2024-05-05 10:24:40 +0200
committerMattias Andrée <maandree@kth.se>2024-05-05 10:24:40 +0200
commit3dfd6c11ed5599ab8baf4a6114f4ccb34150de6e (patch)
treeb7717583b99f29028c85c3423cf43b104dfa8943 /libexec_vconstruct_command.c
downloadlibexec-3dfd6c11ed5599ab8baf4a6114f4ccb34150de6e.tar.gz
libexec-3dfd6c11ed5599ab8baf4a6114f4ccb34150de6e.tar.bz2
libexec-3dfd6c11ed5599ab8baf4a6114f4ccb34150de6e.tar.xz
First commit
Signed-off-by: Mattias Andrée <maandree@kth.se>
Diffstat (limited to 'libexec_vconstruct_command.c')
-rw-r--r--libexec_vconstruct_command.c882
1 files changed, 882 insertions, 0 deletions
diff --git a/libexec_vconstruct_command.c b/libexec_vconstruct_command.c
new file mode 100644
index 0000000..bb3626f
--- /dev/null
+++ b/libexec_vconstruct_command.c
@@ -0,0 +1,882 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#define FLAG_MINUS 0x01
+#define FLAG_PLUS 0x02
+#define FLAG_HASH 0x04
+#define FLAG_ZERO 0x08
+#define FLAG_SPACE 0x10
+
+
+#define TYPESIZE_CHAR -2
+#define TYPESIZE_SHORT -1
+#define TYPESIZE_INT 0
+#define TYPESIZE_LONG +1
+#define TYPESIZE_LONG_LONG +2
+#define TYPESIZE_SIZE 3
+#define TYPESIZE_PTRDIFF 4
+#define TYPESIZE_INTPTR 5
+#define TYPESIZE_INTMAX 6
+
+
+union anyarray_const {
+ const void *v;
+ const signed char *hhi;
+ const signed short int *hi;
+ const signed int *i;
+ const signed long int *li;
+ const signed long long int *lli;
+ const ssize_t *zi;
+ const ptrdiff_t *ti;
+ const intptr_t *pi;
+ const intmax_t *ji;
+ const unsigned char *hhu;
+ const unsigned short int *hu;
+ const unsigned int *u;
+ const unsigned long int *lu;
+ const unsigned long long int *llu;
+ const size_t *zu;
+ const ptrdiff_t *tu;
+ const uintptr_t *pu;
+ const uintmax_t *ju;
+ const char *c;
+ const char *const *s;
+};
+
+union anyvalue {
+ intmax_t i;
+ uintmax_t u;
+ char c[2];
+ const char *s;
+};
+
+struct partial_string {
+ char *s;
+ size_t len;
+ size_t size;
+ int started;
+};
+
+struct argument_format {
+ size_t array_size;
+ size_t width1;
+ size_t width2;
+ int array_input;
+ int array_null_terminated;
+ int typesize;
+ int has_dot;
+ char typeclass;
+ unsigned flags;
+};
+
+struct ignore_stack {
+ char *stack;
+ size_t top;
+ size_t size;
+ size_t active;
+};
+
+
+static int
+resize_partial_string(struct partial_string *str, size_t req_size)
+{
+ void *new = realloc(str->s, req_size);
+ if (!new)
+ return -1;
+ str->s = new;
+ str->size = req_size;
+ return 0;
+}
+
+
+static int
+resize_partial_string_if_full(struct partial_string *str, size_t extra)
+{
+ if (str->len == str->size)
+ if (resize_partial_string(str, str->len + extra))
+ return -1;
+ return 0;
+}
+
+
+static char *
+make_unsigned(uintmax_t value, char fmt, const char *plus, const char *prefix, size_t minwidth)
+{
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+ const char *fmtstr;
+ char *ret, *p;
+ size_t len, zeroes, totlen;
+
+ if (fmt == 'd' || fmt == 'i' || fmt == 'u')
+ fmtstr = "%ju";
+ else if (fmt == 'o')
+ fmtstr = "%jo";
+ else if (fmt == 'x')
+ fmtstr = "%jx";
+ else if (fmt == 'X')
+ fmtstr = "%jX";
+ else
+ abort();
+
+ len = (size_t)snprintf(NULL, 0, fmtstr, value);
+ zeroes = len < minwidth ? minwidth - len : 0;
+
+ totlen = strlen(plus) + 1;
+ if (strlen(prefix) > SIZE_MAX - totlen)
+ goto enomem;
+ totlen += strlen(prefix);
+ if (zeroes + len > SIZE_MAX - totlen)
+ goto enomem;
+ totlen += zeroes + len;
+
+ ret = malloc(totlen);
+ if (!ret)
+ goto enomem;
+
+ p = stpcpy(stpcpy(ret, plus), prefix);
+ while (zeroes--)
+ *p++ = '0';
+ sprintf(p, fmtstr, value);
+
+ return ret;
+
+enomem:
+ errno = ENOMEM;
+ return NULL;
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+}
+
+
+static char *
+make_signed(intmax_t value, char fmt, const char *plus, const char *prefix, size_t minwidth)
+{
+ if (value < 0)
+ return make_unsigned((uintmax_t)-(value + 1) + 1U, fmt, "-", prefix, minwidth);
+ else
+ return make_unsigned((uintmax_t)value, fmt, plus, prefix, minwidth);
+}
+
+
+static unsigned
+get_flag(char c)
+{
+ return c == '-' ? FLAG_MINUS :
+ c == '+' ? FLAG_PLUS :
+ c == '#' ? FLAG_HASH :
+ c == '0' ? FLAG_ZERO :
+ c == ' ' ? FLAG_SPACE : 0U;
+}
+
+
+static unsigned
+remove_ignored_flags(unsigned flags, char fmt)
+{
+ if (fmt == 's' || fmt == 'c') {
+ flags &= FLAG_MINUS;
+ } else if (fmt == 'i' || fmt == 'd') {
+ if (flags & FLAG_PLUS)
+ flags &= (unsigned)~FLAG_SPACE;
+ } else {
+ flags &= (unsigned)~(FLAG_PLUS | FLAG_SPACE);
+ }
+ return flags;
+}
+
+
+static const char *
+read_size(size_t *out, const char *fmt, va_list args, int optional)
+{
+ size_t digit;
+
+ if (*fmt == '*') {
+ fmt++;
+ *out = va_arg(args, size_t);
+ } else if (!optional && !isdigit(*fmt)) {
+ goto einval;
+ } else {
+ *out = 0;
+ while (isdigit(*fmt)) {
+ digit = (size_t)(*fmt++ - '0');
+ if (*out > (SIZE_MAX - digit) / 10U)
+ goto einval;
+ *out = *out * 10U + digit;
+ }
+ }
+
+ return fmt;
+
+einval:
+ errno = EINVAL;
+ return NULL;
+}
+
+
+static const char *
+get_typesize(int *out, const char *fmt)
+{
+ *out = 0;
+
+ for (; *fmt == 'h'; fmt++)
+ if (--*out < -2)
+ goto einval;
+
+ for (; *fmt == 'l'; fmt++)
+ if (*out < 0 || ++*out > +2)
+ goto einval;
+
+ if (*fmt == 'z' || *fmt == 't' || *fmt == 'T' || *fmt == 'j') {
+ if (*out)
+ goto einval;
+ *out = *fmt == 'z' ? TYPESIZE_SIZE :
+ *fmt == 't' ? TYPESIZE_PTRDIFF :
+ *fmt == 'T' ? TYPESIZE_INTPTR : /* non-standard */
+ TYPESIZE_INTMAX;
+ fmt++;
+ }
+
+ /*
+ * TODO add =N for intN_t/uintN_t (arbitrary N)
+ * TODO add =+N for int_leastN_t/uint_leastN_t (arbitrary N)
+ * TODO add =/N for int_fastN_t/uint_fastN_t (arbitrary N)
+ * non-standard
+ */
+
+ if (*fmt == 's' || *fmt == 'c') {
+ if (*out) {
+ /* TODO %ls should be `const wchar_t *` */
+ /* TODO %lc should be `wint_t` */
+ goto einval;
+ }
+ }
+
+ return fmt;
+
+einval:
+ errno = EINVAL;
+ return NULL;
+}
+
+
+static intmax_t
+get_signed_va(va_list args, int typesize)
+{
+ if (typesize == TYPESIZE_CHAR)
+ return (intmax_t)(signed char)va_arg(args, signed int);
+ else if (typesize == TYPESIZE_SHORT)
+ return (intmax_t)(signed short int)va_arg(args, signed int);
+ else if (typesize == TYPESIZE_INT)
+ return (intmax_t)va_arg(args, signed int);
+ else if (typesize == TYPESIZE_LONG)
+ return (intmax_t)va_arg(args, signed long int);
+ else if (typesize == TYPESIZE_LONG_LONG)
+ return (intmax_t)va_arg(args, signed long long int);
+ else if (typesize == TYPESIZE_SIZE) /* TODO since ssize_t may be bounded to -1, may it be bounded above INTMAX_MAX? */
+ return (intmax_t)va_arg(args, ssize_t);
+ else if (typesize == TYPESIZE_PTRDIFF)
+ return (intmax_t)va_arg(args, ptrdiff_t);
+ else if (typesize == TYPESIZE_INTPTR)
+ return (intmax_t)va_arg(args, intptr_t);
+ else if (typesize == TYPESIZE_INTMAX)
+ return (intmax_t)va_arg(args, intmax_t);
+ else
+ abort();
+}
+
+
+static uintmax_t
+get_unsigned_va(va_list args, int typesize)
+{
+ if (typesize == TYPESIZE_CHAR)
+ return (uintmax_t)(unsigned char)va_arg(args, unsigned int);
+ else if (typesize == TYPESIZE_SHORT)
+ return (uintmax_t)(unsigned short int)va_arg(args, unsigned int);
+ else if (typesize == TYPESIZE_INT)
+ return (uintmax_t)va_arg(args, unsigned int);
+ else if (typesize == TYPESIZE_LONG)
+ return (uintmax_t)va_arg(args, unsigned long int);
+ else if (typesize == TYPESIZE_LONG_LONG)
+ return (uintmax_t)va_arg(args, unsigned long long int);
+ else if (typesize == TYPESIZE_SIZE)
+ return (uintmax_t)va_arg(args, size_t);
+ else if (typesize == TYPESIZE_PTRDIFF)
+ return (uintmax_t)va_arg(args, ptrdiff_t); /* TODO ptrdiff_t is signed */
+ else if (typesize == TYPESIZE_INTPTR)
+ return (uintmax_t)va_arg(args, uintptr_t);
+ else if (typesize == TYPESIZE_INTMAX)
+ return (uintmax_t)va_arg(args, uintmax_t);
+ else
+ abort();
+}
+
+
+static intmax_t
+get_signed_array(union anyarray_const *arrayp, int typesize)
+{
+ if (typesize == TYPESIZE_CHAR)
+ return (intmax_t)*arrayp->hhi++;
+ else if (typesize == TYPESIZE_SHORT)
+ return (intmax_t)*arrayp->hi++;
+ else if (typesize == TYPESIZE_INT)
+ return (intmax_t)*arrayp->i++;
+ else if (typesize == TYPESIZE_LONG)
+ return (intmax_t)*arrayp->li++;
+ else if (typesize == TYPESIZE_LONG_LONG)
+ return (intmax_t)*arrayp->lli++;
+ else if (typesize == TYPESIZE_SIZE)
+ return (intmax_t)*arrayp->zi++; /* TODO since ssize_t may be bounded to -1, may it be bounded above INTMAX_MAX? */
+ else if (typesize == TYPESIZE_PTRDIFF)
+ return (intmax_t)*arrayp->ti++;
+ else if (typesize == TYPESIZE_INTPTR)
+ return (intmax_t)*arrayp->pi++;
+ else if (typesize == TYPESIZE_INTMAX)
+ return (intmax_t)*arrayp->ji++;
+ else
+ abort();
+}
+
+
+static uintmax_t
+get_unsigned_array(union anyarray_const *arrayp, int typesize)
+{
+ if (typesize == TYPESIZE_CHAR)
+ return (uintmax_t)*arrayp->hhu++;
+ else if (typesize == TYPESIZE_SHORT)
+ return (uintmax_t)*arrayp->hu++;
+ else if (typesize == TYPESIZE_INT)
+ return (uintmax_t)*arrayp->u++;
+ else if (typesize == TYPESIZE_LONG)
+ return (uintmax_t)*arrayp->lu++;
+ else if (typesize == TYPESIZE_LONG_LONG)
+ return (uintmax_t)*arrayp->llu++;
+ else if (typesize == TYPESIZE_SIZE)
+ return (uintmax_t)*arrayp->zu++;
+ else if (typesize == TYPESIZE_PTRDIFF)
+ return (uintmax_t)*arrayp->tu++; /* TODO ptrdiff_t is signed */
+ else if (typesize == TYPESIZE_INTPTR)
+ return (uintmax_t)*arrayp->pu++;
+ else if (typesize == TYPESIZE_INTMAX)
+ return (uintmax_t)*arrayp->ju++;
+ else
+ abort();
+}
+
+
+static int
+get_value_va(union anyvalue *out, va_list args, char fmt, int typesize)
+{
+ if (fmt == 'i' || fmt == 'd') {
+ out->i = get_signed_va(args, typesize);
+
+ } else if (fmt == 'u' || fmt == 'o' || fmt == 'x' || fmt == 'X') {
+ out->u = get_unsigned_va(args, typesize);
+
+ } else if (fmt == 's') {
+ if (typesize)
+ abort();
+ out->s = va_arg(args, const char *);
+
+ } else if (fmt == 'c') {
+ if (typesize)
+ abort();
+ out->c[0] = (char)va_arg(args, int);
+ out->c[1] = '\0';
+
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int
+get_value_array(union anyvalue *out, union anyarray_const *arrayp, char fmt, int typesize)
+{
+ if (fmt == 'i' || fmt == 'd') {
+ out->i = get_signed_array(arrayp, typesize);
+ return !!out->i;
+
+ } else if (fmt == 'u' || fmt == 'o' || fmt == 'x' || fmt == 'X') {
+ out->u = get_unsigned_array(arrayp, typesize);
+ return !!out->u;
+
+ } else if (fmt == 's') {
+ if (typesize)
+ abort();
+ out->s = *arrayp->s++;
+ return !!out->s;
+
+ } else if (fmt == 'c') {
+ if (typesize)
+ abort();
+ out->c[0] = *arrayp->c++;
+ out->c[1] = '\0';
+ return !!out->c[0];
+
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+
+static char *
+create_string(const union anyvalue *value, char fmt, const char *plus, const char *prefix, int has_dot, size_t width)
+{
+ if (fmt == 'i' || fmt == 'd') {
+ return make_signed(value->i, fmt, plus, value->i ? prefix : "", width);
+
+ } else if (fmt == 'u' || fmt == 'o' || fmt == 'x' || fmt == 'X') {
+ return make_unsigned(value->u, fmt, "", value->u ? prefix : "", width);
+
+ } else if (fmt == 's') {
+ return strndup(value->s, has_dot ? width : strlen(value->s));
+
+ } else if (fmt == 'c') {
+ return strndup(value->c, has_dot ? width : 1);
+
+ } else {
+ errno = EINVAL;
+ return NULL;
+ }
+}
+
+
+static int
+pad_and_insert(struct partial_string *arg, const char *substring, size_t min_width, char pad_char, unsigned pad_before)
+{
+ size_t pad_len, substring_len;
+
+ substring_len = strlen(substring);
+ pad_len = substring_len < min_width ? min_width - substring_len : 0;
+ if (substring_len + pad_len > SIZE_MAX - arg->len) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (substring_len + pad_len + arg->len > arg->size)
+ if (resize_partial_string(arg, substring_len + pad_len + arg->len))
+ return -1;
+
+ if (pad_before) {
+ memset(&arg->s[arg->len], pad_char, pad_len);
+ arg->len += pad_len;
+ }
+
+ memcpy(&arg->s[arg->len], substring, substring_len);
+ arg->len += substring_len;
+
+ if (!pad_before) {
+ memset(&arg->s[arg->len], pad_char, pad_len);
+ arg->len += pad_len;
+ }
+
+ return 0;
+}
+
+
+static int
+insert_argument(struct partial_string *arg, const union anyvalue *value, char fmt,
+ unsigned flags, size_t width1, int has_dot, size_t width2)
+{
+ const char *plus, *prefix;
+ char *substring;
+
+ plus = (flags & FLAG_PLUS) ? "+" : (flags & FLAG_SPACE) ? " " : "";
+
+ prefix = !(flags & FLAG_HASH) ? "" :
+ fmt == 'o' ? "0" :
+ fmt == 'x' ? "0x" :
+ fmt == 'X' ? "0X" : "";
+
+ if (fmt != 's' && fmt != 'c') {
+ if (!has_dot) {
+ width2 = width1;
+ width1 = 0;
+ }
+ }
+
+ substring = create_string(value, fmt, plus, prefix, has_dot, width2);
+ if (!substring)
+ return -1;
+
+ if (pad_and_insert(arg, substring, width1, (flags & FLAG_ZERO) ? '0' : ' ', flags & FLAG_MINUS)) {
+ free(substring);
+ return -1;
+ }
+
+ free(substring);
+ return 0;
+}
+
+
+static int
+get_and_insert_arguments(struct partial_string *arg, va_list args, const struct argument_format *arg_fmt, int ignoring)
+{
+ union anyarray_const array;
+ union anyvalue value;
+ unsigned int flags;
+ size_t n;
+ int r;
+
+ flags = remove_ignored_flags(arg_fmt->flags, arg_fmt->typeclass);
+
+ if (arg_fmt->array_input) {
+ array.v = va_arg(args, const void *);
+ n = arg_fmt->array_size;
+ while (arg_fmt->array_null_terminated || n--) {
+ r = get_value_array(&value, &array, arg_fmt->typeclass, arg_fmt->typesize);
+ if (r < 0)
+ return -1;
+ else if (!r && arg_fmt->array_null_terminated)
+ break;
+ if (!ignoring)
+ if (insert_argument(arg, &value, arg_fmt->typeclass, flags,
+ arg_fmt->width1, arg_fmt->has_dot, arg_fmt->width2))
+ return -1;
+ }
+
+ } else {
+ if (get_value_va(&value, args, arg_fmt->typeclass, arg_fmt->typesize))
+ return -1;
+ if (!ignoring)
+ if (insert_argument(arg, &value, arg_fmt->typeclass, flags,
+ arg_fmt->width1, arg_fmt->has_dot, arg_fmt->width2))
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static const char *
+get_argument_format(struct argument_format *out, const char *fmt, va_list args)
+{
+ unsigned flag;
+
+ memset(out, 0, sizeof(*out));
+
+ if (*fmt == '[') {
+ fmt++;
+ out->array_input = 1;
+ if (*fmt == ']') {
+ out->array_null_terminated = 1;
+ } else {
+ fmt = read_size(&out->array_size, fmt, args, 1);
+ if (!fmt)
+ goto fail;
+ }
+ if (*fmt != ']')
+ goto einval;
+ fmt++;
+ }
+
+ while ((flag = get_flag(*fmt))) {
+ out->flags |= flag;
+ fmt++;
+ }
+
+ fmt = read_size(&out->width1, fmt, args, 0);
+ if (!fmt)
+ goto fail;
+ if (*fmt == '.') {
+ out->has_dot = 1;
+ out->flags &= (unsigned)~FLAG_ZERO;
+ fmt++;
+ fmt = read_size(&out->width2, fmt, args, 0);
+ if (!fmt)
+ goto fail;
+ }
+
+ fmt = get_typesize(&out->typesize, fmt);
+ if (!fmt)
+ goto fail;
+
+ out->typeclass = *fmt++;
+
+ return fmt;
+
+einval:
+ errno = EINVAL;
+fail:
+ return NULL;
+}
+
+
+static void
+destroy_ignore_stack(struct ignore_stack *stack)
+{
+ free(stack->stack);
+ stack->stack = NULL;
+}
+
+
+static int
+push_ignore_stack(struct ignore_stack *stack, va_list args)
+{
+ int show;
+ void *new;
+ size_t new_size;
+
+ if (stack->top == stack->size) {
+ new_size = stack->size + 8;
+ new = realloc(stack->stack, new_size * sizeof(*stack->stack));
+ if (!new)
+ return -1;
+ stack->stack = new;
+ stack->size = new_size;
+ }
+
+ show = va_arg(args, int);
+ stack->stack[stack->top++] = (char)!show;
+ stack->active += (size_t)!show;
+ return 0;
+}
+
+
+static int
+pop_ignore_stack(struct ignore_stack *stack)
+{
+ if (!stack->top) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (stack->stack[--stack->top])
+ stack->active -= 1;
+ return 0;
+}
+
+
+static int
+is_ignoring(struct ignore_stack *stack)
+{
+ return stack->active > 0;
+}
+
+
+static const char *
+format(const char *fmt, struct partial_string *arg, struct ignore_stack *ignore_stack, va_list args)
+{
+
+ struct argument_format arg_fmt;
+
+ /* TODO $< should work as <(), * or number before < to select fd (default: STDOUT_FILENO) */
+ /* TODO $> should work as >(), * or number before > to select fd (default: STDIN_FILENO) */
+ /* TODO add support for embedding via %S and %C (do not set arg->started) */
+ /* TODO add support for floating point values */
+
+ if (*fmt == '$' || *fmt == '\'' || *fmt == '"' || *fmt == '_') {
+ if (!is_ignoring(ignore_stack)) {
+ if (arg->len == arg->size)
+ if (resize_partial_string(arg, arg->size + 32))
+ goto fail;
+ arg->s[arg->len++] = *fmt == '_' ? ' ' : *fmt;
+ arg->started = 1;
+ }
+ fmt++;
+
+ } else if (*fmt == '(') {
+ fmt++;
+ if (push_ignore_stack(ignore_stack, args))
+ goto fail;
+
+ } else if (*fmt == ')') {
+ fmt++;
+ if (pop_ignore_stack(ignore_stack))
+ goto fail;
+
+ } else {
+ fmt = get_argument_format(&arg_fmt, fmt, args);
+ if (!fmt)
+ goto fail;
+ if (get_and_insert_arguments(arg, args, &arg_fmt, is_ignoring(ignore_stack)))
+ goto fail;
+ arg->started = 1;
+ }
+
+ return fmt;
+
+fail:
+ return NULL;
+}
+
+
+static char **
+format_command(const char *fmt, va_list args, size_t *narguments_out)
+{
+ size_t command_size = 0;
+ char **command = NULL;
+ struct ignore_stack ignore_stack;
+ struct partial_string arg;
+ void *new;
+
+ *narguments_out = 0;
+
+ memset(&ignore_stack, 0, sizeof(ignore_stack));
+ ignore_stack.stack = NULL;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.s = NULL;
+
+next_arg:
+ arg.started = 0;
+ arg.len = 0;
+ while (*fmt) {
+ if (*fmt == '$') {
+ fmt++;
+ fmt = format(fmt, &arg, &ignore_stack, args);
+ if (!fmt)
+ goto fail;
+
+ } else if (isspace(*fmt)) {
+ fmt++;
+ if (!arg.started || is_ignoring(&ignore_stack))
+ continue;
+ new = realloc(command, (command_size + 1) * sizeof(*command));
+ if (!new)
+ goto fail;
+ command = new;
+ if (resize_partial_string_if_full(&arg, 32))
+ goto fail;
+ arg.s[arg.len] = '\0';
+ command[command_size] = strdup(arg.s);
+ if (!command[command_size])
+ goto fail;
+ command_size++;
+ goto next_arg;
+
+ } else if (*fmt == '\'' || *fmt == '"') {
+ arg.started = 1;
+ /* TODO add support for quotes */
+ errno = EINVAL;
+ goto fail;
+
+ } else {
+ if (!is_ignoring(&ignore_stack)) {
+ if (resize_partial_string_if_full(&arg, 32))
+ goto fail;
+ arg.s[arg.len++] = *fmt;
+ arg.started = 1;
+ }
+ fmt++;
+ }
+ }
+
+ new = realloc(command, (command_size + 2) * sizeof(*command));
+ if (!new)
+ goto fail;
+ command = new;
+ if (arg.started) {
+ if (resize_partial_string_if_full(&arg, 1))
+ goto fail;
+ arg.s[arg.len] = '\0';
+ command[command_size++] = arg.s;
+ } else {
+ free(arg.s);
+ }
+ command[command_size] = NULL;
+ destroy_ignore_stack(&ignore_stack);
+
+ *narguments_out = command_size;
+ return command;
+
+fail:
+ free(arg.s);
+ destroy_ignore_stack(&ignore_stack);
+ while (command_size)
+ free(command[--command_size]);
+ free(command);
+ return NULL;
+}
+
+
+static int
+append_arguments(struct libexec_command *cmd, va_list args)
+{
+ size_t n = 0, i;
+ va_list args2;
+ void *new;
+
+ va_copy(args2, args);
+ while (va_arg(args2, const char *))
+ n += 1;
+ va_end(args2);
+
+ new = realloc(cmd->arguments, (cmd->narguments + n + 1) * sizeof(*cmd->arguments));
+ if (!new)
+ return -1;
+ cmd->arguments = new;
+
+ for (i = 0; i < n; i++) {
+ cmd->arguments[cmd->narguments + i] = strdup(va_arg(args, const char *));
+ if (!cmd->arguments[cmd->narguments + i]) {
+ while (i)
+ free(cmd->arguments[cmd->narguments + --i]);
+ if (!cmd->narguments) {
+ free(cmd->arguments);
+ cmd->arguments = NULL;
+ }
+ return -1;
+ }
+ }
+
+ cmd->arguments[cmd->narguments + n] = va_arg(args, char *); /* that's a NULL */
+ cmd->narguments += n;
+
+ return 0;
+}
+
+
+int
+libexec_vconstruct_command(struct libexec_command *cmd, const char *fmt, va_list args)
+{
+ size_t n;
+ char **command = NULL;
+ void *new;
+
+ if (!cmd) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!fmt) {
+ return append_arguments(cmd, args);
+
+ } else {
+ command = format_command(fmt, args, &n);
+ if (!command)
+ return -1;
+ if (!cmd->narguments) {
+ cmd->arguments = command;
+ cmd->narguments = n;
+ } else {
+ new = realloc(cmd->arguments, (cmd->narguments + n + 1) * sizeof(*cmd->arguments));
+ if (!new) {
+ while (n)
+ free(command[--n]);
+ free(command);
+ return -1;
+ }
+ cmd->arguments = new;
+ memcpy(&cmd->arguments[cmd->narguments], command, (n + 1) * sizeof(*command));
+ cmd->narguments += n;
+ free(command);
+ }
+ return 0;
+ }
+}
+
+
+#else
+TESTED_ELSEWHERE /* libexec_construct_command.c */
+#endif