diff options
Diffstat (limited to '')
-rw-r--r-- | libexec_vconstruct_command.c | 882 |
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 |