diff options
-rw-r--r-- | libc/misc/Makefile | 5 | ||||
-rw-r--r-- | libc/misc/wordexp/Makefile | 39 | ||||
-rw-r--r-- | libc/misc/wordexp/wordexp.c | 2244 |
3 files changed, 2287 insertions, 1 deletions
diff --git a/libc/misc/Makefile b/libc/misc/Makefile index 037e2154d..24a963516 100644 --- a/libc/misc/Makefile +++ b/libc/misc/Makefile @@ -27,10 +27,13 @@ include $(TOPDIR)Rules.mak DIRS = assert ctype dirent file fnmatch glob internals \ mntent syslog time utmp sysvipc statfs \ - error ttyent gnu search intl locale + error ttyent gnu search intl locale ifeq ($(strip $(UCLIBC_HAS_REGEX)),y) DIRS += regex endif +ifeq ($(strip $(UCLIBC_HAS_WORDEXP)),y) +DIRS += wordexp +endif ifeq ($(strip $(UCLIBC_HAS_THREADS)),y) DIRS += pthread endif diff --git a/libc/misc/wordexp/Makefile b/libc/misc/wordexp/Makefile new file mode 100644 index 000000000..edfd819d5 --- /dev/null +++ b/libc/misc/wordexp/Makefile @@ -0,0 +1,39 @@ +# Makefile for uClibc +# +# Copyright (C) 2003 Erik Andersen <andersen@uclibc.org> +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Library General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more +# details. +# +# You should have received a copy of the GNU Library General Public License +# along with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +TOPDIR=../../../ +include $(TOPDIR)Rules.mak + +CSRC=wordexp.c +COBJS=$(patsubst %.c,%.o, $(CSRC)) +OBJS=$(COBJS) + +all: $(OBJS) $(LIBC) + +$(LIBC): ar-target + +ar-target: $(OBJS) + $(AR) $(ARFLAGS) $(LIBC) $(OBJS) + +$(COBJS): %.o : %.c + $(CC) $(CFLAGS) -c $< -o $@ + $(STRIPTOOL) -x -R .note -R .comment $*.o + +clean: + rm -f *.[oa] *~ core + diff --git a/libc/misc/wordexp/wordexp.c b/libc/misc/wordexp/wordexp.c new file mode 100644 index 000000000..628b8f3fe --- /dev/null +++ b/libc/misc/wordexp/wordexp.c @@ -0,0 +1,2244 @@ +/* vi: set sw=4 ts=4: */ +/* POSIX.2 wordexp implementation. + Copyright (C) 1997, 1998, 1999 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Tim Waugh <tim@cyberelk.demon.co.uk>. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#define _GNU_SOURCE +#include <sys/cdefs.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> +#include <errno.h> +#include <assert.h> +#include <fnmatch.h> +#include <glob.h> +#include <wordexp.h> + +#define __WORDEXP_FULL +//#undef __WORDEXP_FULL + +/* + * This is a recursive-descent-style word expansion routine. + */ + +/* These variables are defined and initialized in the startup code. */ +//extern int __libc_argc; +//extern char **__libc_argv; + +/* FIXME!!!! */ +int __libc_argc; +char **__libc_argv; + +/* Some forward declarations */ +static int parse_dollars(char **word, size_t * word_length, + size_t * max_length, const char *words, + size_t * offset, int flags, wordexp_t * pwordexp, + const char *ifs, const char *ifs_white, + int quoted); +static int parse_backtick(char **word, size_t * word_length, + size_t * max_length, const char *words, + size_t * offset, int flags, wordexp_t * pwordexp, + const char *ifs, const char *ifs_white); +static int parse_dquote(char **word, size_t * word_length, + size_t * max_length, const char *words, + size_t * offset, int flags, wordexp_t * pwordexp, + const char *ifs, const char *ifs_white); + + + +/* The w_*() functions manipulate word lists. */ +#define W_CHUNK (100) + +/* Result of w_newword will be ignored if it's the last word. */ +static inline char *w_newword(size_t * actlen, size_t * maxlen) +{ + *actlen = *maxlen = 0; + return NULL; +} + +/* Add a character to the buffer, allocating room for it if needed. */ +static inline char *w_addchar(char *buffer, size_t * actlen, + size_t * maxlen, char ch) + /* (lengths exclude trailing zero) */ +{ + + if (*actlen == *maxlen) { + char *old_buffer = buffer; + assert(buffer == NULL || *maxlen != 0); + *maxlen += W_CHUNK; + buffer = realloc(buffer, 1 + *maxlen); + if (buffer == NULL) + free(old_buffer); + } + + if (buffer != NULL) { + buffer[*actlen] = ch; + buffer[++(*actlen)] = '\0'; + } + + return buffer; +} + +#define MAX( a, b ) ( ( ( a ) > ( b ) ) ? ( a ) : ( b ) ) +static char *w_addmem(char *buffer, size_t * actlen, size_t * maxlen, + const char *str, size_t len) +{ + /* Add a string to the buffer, allocating room for it if needed. + */ + if (*actlen + len > *maxlen) { + char *old_buffer = buffer; + assert(buffer == NULL || *maxlen != 0); + *maxlen += MAX(2 * len, W_CHUNK); + buffer = realloc(old_buffer, 1 + *maxlen); + if (buffer == NULL) + free(old_buffer); + } + + if (buffer != NULL) { + *((char *) mempcpy(&buffer[*actlen], str, len)) = '\0'; + *actlen += len; + } + return buffer; +} + +/* Add a string to the buffer, allocating room for it if needed. */ +static char *w_addstr(char *buffer, size_t * actlen, size_t * maxlen, + const char *str) + /* (lengths exclude trailing zero) */ +{ + size_t len; + assert(str != NULL); /* w_addstr only called from this file */ + len = strlen(str); + + return w_addmem(buffer, actlen, maxlen, str, len); +} + +/* Add a word to the wordlist */ +static int w_addword(wordexp_t * pwordexp, char *word) +{ + size_t num_p; + char **new_wordv; + + /* Internally, NULL acts like "". Convert NULLs to "" before + * the caller sees them. + */ + if (word == NULL) { + word = strdup(""); + if (word == NULL) + goto no_space; + } + + num_p = 2 + pwordexp->we_wordc + pwordexp->we_offs; + new_wordv = realloc(pwordexp->we_wordv, sizeof(char *) * num_p); + if (new_wordv != NULL) { + pwordexp->we_wordv = new_wordv; + pwordexp->we_wordv[pwordexp->we_offs + pwordexp->we_wordc++] = word; + pwordexp->we_wordv[pwordexp->we_offs + pwordexp->we_wordc] = NULL; + return 0; + } + + no_space: + return WRDE_NOSPACE; +} + +/* The parse_*() functions should leave *offset being the offset in 'words' + * to the last character processed. + */ +static int +parse_backslash(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset) +{ + /* We are poised _at_ a backslash, not in quotes */ + + switch (words[1 + *offset]) { + case 0: + /* Backslash is last character of input words */ + return WRDE_SYNTAX; + + case '\n': + ++(*offset); + break; + + default: + *word = w_addchar(*word, word_length, max_length, words[1 + *offset]); + if (*word == NULL) + return WRDE_NOSPACE; + + ++(*offset); + break; + } + + return 0; +} + +static int +parse_qtd_backslash(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset) +{ + /* We are poised _at_ a backslash, inside quotes */ + + switch (words[1 + *offset]) { + case 0: + /* Backslash is last character of input words */ + return WRDE_SYNTAX; + + case '\n': + ++(*offset); + break; + + case '$': + case '`': + case '"': + case '\\': + *word = + w_addchar(*word, word_length, max_length, words[1 + *offset]); + if (*word == NULL) + return WRDE_NOSPACE; + + ++(*offset); + break; + + default: + *word = w_addchar(*word, word_length, max_length, words[*offset]); + if (*word != NULL) + *word = + w_addchar(*word, word_length, max_length, + words[1 + *offset]); + + if (*word == NULL) + return WRDE_NOSPACE; + + ++(*offset); + break; + } + + return 0; +} + +static int +parse_tilde(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset, size_t wordc) +{ + /* We are poised _at_ a tilde */ + size_t i; + + if (*word_length != 0) { + if (!((*word)[*word_length - 1] == '=' && wordc == 0)) { + if (!((*word)[*word_length - 1] == ':' + && strchr(*word, '=') && wordc == 0)) { + *word = w_addchar(*word, word_length, max_length, '~'); + return *word ? 0 : WRDE_NOSPACE; + } + } + } + + for (i = 1 + *offset; words[i]; i++) { + if (words[i] == ':' || words[i] == '/' || words[i] == ' ' || + words[i] == '\t' || words[i] == 0) + break; + + if (words[i] == '\\') { + *word = w_addchar(*word, word_length, max_length, '~'); + return *word ? 0 : WRDE_NOSPACE; + } + } + + if (i == 1 + *offset) { + /* Tilde appears on its own */ + uid_t uid; + struct passwd pwd, *tpwd; + int buflen = 1000; + char *home; + char *buffer; + int result; + + /* POSIX.2 says ~ expands to $HOME and if HOME is unset the + results are unspecified. We do a lookup on the uid if + HOME is unset. */ + + home = getenv("HOME"); + if (home != NULL) { + *word = w_addstr(*word, word_length, max_length, home); + if (*word == NULL) + return WRDE_NOSPACE; + } else { + uid = getuid(); + buffer = alloca(buflen); + + while ((result = getpwuid_r(uid, &pwd, buffer, buflen, &tpwd)) + != 0 && errno == ERANGE) + { + buflen += 1000; + buffer = alloca(buflen); + } + + if (result == 0 && tpwd != NULL && pwd.pw_dir != NULL) { + *word = w_addstr(*word, word_length, max_length, pwd.pw_dir); + if (*word == NULL) + return WRDE_NOSPACE; + } else { + *word = w_addchar(*word, word_length, max_length, '~'); + if (*word == NULL) + return WRDE_NOSPACE; + } + } + } else { + /* Look up user name in database to get home directory */ + char *user = strndup(&words[1 + *offset], i - (1 + *offset)); + struct passwd pwd, *tpwd; + int buflen = 1000; + char *buffer = alloca(buflen); + int result; + + while ((result = getpwnam_r(user, &pwd, buffer, buflen, &tpwd)) != 0 + && errno == ERANGE) { + buflen += 1000; + buffer = alloca(buflen); + } + + if (result == 0 && tpwd != NULL && pwd.pw_dir) + *word = w_addstr(*word, word_length, max_length, pwd.pw_dir); + else { + /* (invalid login name) */ + *word = w_addchar(*word, word_length, max_length, '~'); + if (*word != NULL) + *word = w_addstr(*word, word_length, max_length, user); + } + + *offset = i - 1; + } + return *word ? 0 : WRDE_NOSPACE; +} + + +static int +do_parse_glob(const char *glob_word, char **word, size_t * word_length, + size_t * max_length, wordexp_t * pwordexp, const char *ifs, + const char *ifs_white) +{ + int error; + int match; + glob_t globbuf; + + error = glob(glob_word, GLOB_NOCHECK, NULL, &globbuf); + + if (error != 0) { + /* We can only run into memory problems. */ + assert(error == GLOB_NOSPACE); + return WRDE_NOSPACE; + } + + if (ifs && !*ifs) { + /* No field splitting allowed. */ + assert(globbuf.gl_pathv[0] != NULL); + *word = w_addstr(*word, word_length, max_length, globbuf.gl_pathv[0]); + for (match = 1; match < globbuf.gl_pathc && *word != NULL; ++match) { + *word = w_addchar(*word, word_length, max_length, ' '); + if (*word != NULL) + *word = w_addstr(*word, word_length, max_length, + globbuf.gl_pathv[match]); + } + + globfree(&globbuf); + return *word ? 0 : WRDE_NOSPACE; + } + + assert(ifs == NULL || *ifs != '\0'); + if (*word != NULL) { + free(*word); + *word = w_newword(word_length, max_length); + } + + for (match = 0; match < globbuf.gl_pathc; ++match) { + char *matching_word = strdup(globbuf.gl_pathv[match]); + + if (matching_word == NULL || w_addword(pwordexp, matching_word)) { + globfree(&globbuf); + return WRDE_NOSPACE; + } + } + + globfree(&globbuf); + return 0; +} + +static int +parse_glob(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset, int flags, + wordexp_t * pwordexp, const char *ifs, const char *ifs_white) +{ + /* We are poised just after a '*', a '[' or a '?'. */ + int error = WRDE_NOSPACE; + int quoted = 0; /* 1 if singly-quoted, 2 if doubly */ + int i; + wordexp_t glob_list; /* List of words to glob */ + + glob_list.we_wordc = 0; + glob_list.we_wordv = NULL; + glob_list.we_offs = 0; + for (; words[*offset] != '\0'; ++*offset) { + if ((ifs && strchr(ifs, words[*offset])) || + (!ifs && strchr(" \t\n", words[*offset]))) + /* Reached IFS */ + break; + + /* Sort out quoting */ + if (words[*offset] == '\'') { + if (quoted == 0) { + quoted = 1; + continue; + } else if (quoted == 1) { + quoted = 0; + continue; + } + } else if (words[*offset] == '"') { + if (quoted == 0) { + quoted = 2; + continue; + } else if (quoted == 2) { + quoted = 0; + continue; + } + } + + /* Sort out other special characters */ + if (quoted != 1 && words[*offset] == '$') { + error = parse_dollars(word, word_length, max_length, words, + offset, flags, &glob_list, ifs, + ifs_white, quoted == 2); + if (error) + goto tidy_up; + + continue; + } else if (words[*offset] == '\\') { + if (quoted) + error = parse_qtd_backslash(word, word_length, max_length, + words, offset); + else + error = parse_backslash(word, word_length, max_length, + words, offset); + + if (error) + goto tidy_up; + + continue; + } + + *word = w_addchar(*word, word_length, max_length, words[*offset]); + if (*word == NULL) + goto tidy_up; + } + + /* Don't forget to re-parse the character we stopped at. */ + --*offset; + + /* Glob the words */ + error = w_addword(&glob_list, *word); + *word = w_newword(word_length, max_length); + for (i = 0; error == 0 && i < glob_list.we_wordc; i++) + error = do_parse_glob(glob_list.we_wordv[i], word, word_length, + max_length, pwordexp, ifs, ifs_white); + + /* Now tidy up */ + tidy_up: + wordfree(&glob_list); + return error; +} + +static int +parse_squote(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset) +{ + /* We are poised just after a single quote */ + for (; words[*offset]; ++(*offset)) { + if (words[*offset] != '\'') { + *word = w_addchar(*word, word_length, max_length, words[*offset]); + if (*word == NULL) + return WRDE_NOSPACE; + } else + return 0; + } + + /* Unterminated string */ + return WRDE_SYNTAX; +} + +#ifdef __WORDEXP_FULL +static int eval_expr(char *expr, long int *result); + +static char *_itoa(unsigned long long int value, char *buflim) +{ + sprintf(buflim, "%llu", value); + return buflim; +} + +/* Functions to evaluate an arithmetic expression */ +static int eval_expr_val(char **expr, long int *result) +{ + int sgn = +1; + char *digit; + + /* Skip white space */ + for (digit = *expr; digit && *digit && isspace(*digit); ++digit); + + switch (*digit) { + case '(': + + /* Scan for closing paren */ + for (++digit; **expr && **expr != ')'; ++(*expr)); + + /* Is there one? */ + if (!**expr) + return WRDE_SYNTAX; + + *(*expr)++ = 0; + + if (eval_expr(digit, result)) + return WRDE_SYNTAX; + + return 0; + + case '+': /* Positive value */ + ++digit; + break; + + case '-': /* Negative value */ + ++digit; + sgn = -1; + break; + + default: + if (!isdigit(*digit)) + return WRDE_SYNTAX; + } + + *result = 0; + for (; *digit && isdigit(*digit); ++digit) + *result = (*result * 10) + (*digit - '0'); + + *expr = digit; + *result *= sgn; + return 0; +} + +static int eval_expr_multdiv(char **expr, long int *result) +{ + long int arg; + + /* Read a Value */ + if (eval_expr_val(expr, result) != 0) + return WRDE_SYNTAX; + + while (**expr) { + /* Skip white space */ + for (; *expr && **expr && isspace(**expr); ++(*expr)); + + if (**expr == '*') { + ++(*expr); + if (eval_expr_val(expr, &arg) != 0) + return WRDE_SYNTAX; + + *result *= arg; + } else if (**expr == '/') { + ++(*expr); + if (eval_expr_val(expr, &arg) != 0) + return WRDE_SYNTAX; + + *result /= arg; + } else + break; + } + + return 0; +} + +static int eval_expr(char *expr, long int *result) +{ + long int arg; + + /* Read a Multdiv */ + if (eval_expr_multdiv(&expr, result) != 0) + return WRDE_SYNTAX; + + while (*expr) { + /* Skip white space */ + for (; expr && *expr && isspace(*expr); ++expr); + + if (*expr == '+') { + ++expr; + if (eval_expr_multdiv(&expr, &arg) != 0) + return WRDE_SYNTAX; + + *result += arg; + } else if (*expr == '-') { + ++expr; + if (eval_expr_multdiv(&expr, &arg) != 0) + return WRDE_SYNTAX; + + *result -= arg; + } else + break; + } + + return 0; +} + +static int +parse_arith(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset, int flags, int bracket) +{ + /* We are poised just after "$((" or "$[" */ + int error; + int paren_depth = 1; + size_t expr_length; + size_t expr_maxlen; + char *expr; + + expr = w_newword(&expr_length, &expr_maxlen); + for (; words[*offset]; ++(*offset)) { + switch (words[*offset]) { + case '$': + error = parse_dollars(&expr, &expr_length, &expr_maxlen, + words, offset, flags, NULL, NULL, NULL, + 1); + /* The ``1'' here is to tell parse_dollars not to + * split the fields. + */ + if (error) { + free(expr); + return error; + } + break; + + case '`': + (*offset)++; + error = parse_backtick(&expr, &expr_length, &expr_maxlen, + words, offset, flags, NULL, NULL, NULL); + /* The first NULL here is to tell parse_backtick not to + * split the fields. + */ + if (error) { + free(expr); + return error; + } + break; + + case '\\': + error = parse_qtd_backslash(&expr, &expr_length, &expr_maxlen, + words, offset); + if (error) { + free(expr); + return error; + } + /* I think that a backslash within an + * arithmetic expansion is bound to + * cause an error sooner or later anyway though. + */ + break; + + case ')': + if (--paren_depth == 0) { + char result[21]; /* 21 = ceil(log10(2^64)) + 1 */ + long int numresult = 0; + long long int convertme; + + if (bracket || words[1 + *offset] != ')') { + free(expr); + return WRDE_SYNTAX; + } + + ++(*offset); + + /* Go - evaluate. */ + if (*expr && eval_expr(expr, &numresult) != 0) { + free(expr); + return WRDE_SYNTAX; + } + + if (numresult < 0) { + convertme = -numresult; + *word = w_addchar(*word, word_length, max_length, '-'); + if (!*word) { + free(expr); + return WRDE_NOSPACE; + } + } else + convertme = numresult; + + result[20] = '\0'; + *word = w_addstr(*word, word_length, max_length, + _itoa(convertme, &result[20])); + free(expr); + return *word ? 0 : WRDE_NOSPACE; + } + expr = + w_addchar(expr, &expr_length, &expr_maxlen, + words[*offset]); + if (expr == NULL) + return WRDE_NOSPACE; + + break; + + case ']': + if (bracket && paren_depth == 1) { + char result[21]; /* 21 = ceil(log10(2^64)) + 1 */ + long int numresult = 0; + + /* Go - evaluate. */ + if (*expr && eval_expr(expr, &numresult) != 0) { + free(expr); + return WRDE_SYNTAX; + } + + result[20] = '\0'; + *word = w_addstr(*word, word_length, max_length, + _itoa(numresult, &result[20])); + free(expr); + return *word ? 0 : WRDE_NOSPACE; + } + + free(expr); + return WRDE_SYNTAX; + + case '\n': + case ';': + case '{': + case '}': + free(expr); + return WRDE_BADCHAR; + + case '(': + ++paren_depth; + default: + expr = + w_addchar(expr, &expr_length, &expr_maxlen, + words[*offset]); + if (expr == NULL) + return WRDE_NOSPACE; + } + } + + /* Premature end */ + free(expr); + return WRDE_SYNTAX; +} + +/* Function called by child process in exec_comm() */ +static void +exec_comm_child(char *comm, int *fildes, int showerr, int noexec) +{ + const char *args[4] = { _PATH_BSHELL, "-c", comm, NULL }; + + /* Execute the command, or just check syntax? */ + if (noexec) + args[1] = "-nc"; + + /* Redirect output. */ + dup2(fildes[1], 1); + close(fildes[1]); + + /* Redirect stderr to /dev/null if we have to. */ + if (showerr == 0) { + int fd; + + close(2); + fd = open(_PATH_DEVNULL, O_WRONLY); + if (fd >= 0 && fd != 2) { + dup2(fd, 2); + close(fd); + } + } + + /* Make sure the subshell doesn't field-split on our behalf. */ + unsetenv("IFS"); + + close(fildes[0]); + execve(_PATH_BSHELL, (char *const *) args, __environ); + + /* Bad. What now? */ + abort(); +} + +/* Function to execute a command and retrieve the results */ +/* pwordexp contains NULL if field-splitting is forbidden */ +static int +exec_comm(char *comm, char **word, size_t * word_length, + size_t * max_length, int flags, wordexp_t * pwordexp, + const char *ifs, const char *ifs_white) +{ + int fildes[2]; + int bufsize = 128; + int buflen; + int i; + int status = 0; + size_t maxnewlines = 0; + char *buffer; + pid_t pid; + + /* Don't fork() unless necessary */ + if (!comm || !*comm) + return 0; + + if (pipe(fildes)) + /* Bad */ + return WRDE_NOSPACE; + + if ((pid = fork()) < 0) { + /* Bad */ + close(fildes[0]); + close(fildes[1]); + return WRDE_NOSPACE; + } + + if (pid == 0) + exec_comm_child(comm, fildes, (flags & WRDE_SHOWERR), 0); + + /* Parent */ + + close(fildes[1]); + buffer = alloca(bufsize); + + if (!pwordexp) + /* Quoted - no field splitting */ + { + while (1) { + if ((buflen = read(fildes[0], buffer, bufsize)) < 1) { + if (waitpid(pid, &status, WNOHANG) == 0) + continue; + if ((buflen = read(fildes[0], buffer, bufsize)) < 1) + break; + } + + maxnewlines += buflen; + + *word = w_addmem(*word, word_length, max_length, buffer, buflen); + if (*word == NULL) + goto no_space; + } + } else + /* Not quoted - split fields */ + { + int copying = 0; + + /* 'copying' is: + * 0 when searching for first character in a field not IFS white space + * 1 when copying the text of a field + * 2 when searching for possible non-whitespace IFS + * 3 when searching for non-newline after copying field + */ + + while (1) { + if ((buflen = read(fildes[0], buffer, bufsize)) < 1) { + if (waitpid(pid, &status, WNOHANG) == 0) + continue; + if ((buflen = read(fildes[0], buffer, bufsize)) < 1) + break; + } + + for (i = 0; i < buflen; ++i) { + if (strchr(ifs, buffer[i]) != NULL) { + /* Current character is IFS */ + if (strchr(ifs_white, buffer[i]) == NULL) { + /* Current character is IFS but not whitespace */ + if (copying == 2) { + /* current character + * | + * V + * eg: text<space><comma><space>moretext + * + * So, strip whitespace IFS (like at the start) + */ + copying = 0; + continue; + } + + copying = 0; + /* fall through and delimit field.. */ + } else { + if (buffer[i] == '\n') { + /* Current character is (IFS) newline */ + + /* If copying a field, this is the end of it, + but maybe all that's left is trailing newlines. + So start searching for a non-newline. */ + if (copying == 1) + copying = 3; + + continue; + } else { + /* Current character is IFS white space, but + not a newline */ + + /* If not either copying a field or searching + for non-newline after a field, ignore it */ + if (copying != 1 && copying != 3) + continue; + + /* End of field (search for non-ws IFS afterwards) */ + copying = 2; + } + } + + /* First IFS white space (non-newline), or IFS non-whitespace. + * Delimit the field. Nulls are converted by w_addword. */ + if (w_addword(pwordexp, *word) == WRDE_NOSPACE) + goto no_space; + + *word = w_newword(word_length, max_length); + + maxnewlines = 0; + /* fall back round the loop.. */ + } else { + /* Not IFS character */ + + if (copying == 3) { + /* Nothing but (IFS) newlines since the last field, + so delimit it here before starting new word */ + if (w_addword(pwordexp, *word) == WRDE_NOSPACE) + goto no_space; + + *word = w_newword(word_length, max_length); + } + + copying = 1; + + if (buffer[i] == '\n') /* happens if newline not in IFS */ + maxnewlines++; + else + maxnewlines = 0; + + *word = w_addchar(*word, word_length, max_length, + buffer[i]); + if (*word == NULL) + goto no_space; + } + } + } + } + + /* Chop off trailing newlines (required by POSIX.2) */ + /* Ensure we don't go back further than the beginning of the + substitution (i.e. remove maxnewlines bytes at most) */ + while (maxnewlines-- != 0 && + *word_length > 0 && (*word)[*word_length - 1] == '\n') { + (*word)[--*word_length] = '\0'; + + /* If the last word was entirely newlines, turn it into a new word + * which can be ignored if there's nothing following it. */ + if (*word_length == 0) { + free(*word); + *word = w_newword(word_length, max_length); + break; + } + } + + close(fildes[0]); + + /* Check for syntax error (re-execute but with "-n" flag) */ + if (buflen < 1 && status != 0) { + if ((pid = fork()) < 0) { + /* Bad */ + return WRDE_NOSPACE; + } + + if (pid == 0) { + fildes[0] = fildes[1] = -1; + exec_comm_child(comm, fildes, 0, 1); + } + + if (waitpid(pid, &status, 0) == pid && status != 0) + return WRDE_SYNTAX; + } + + return 0; + + no_space: + kill(pid, SIGKILL); + waitpid(pid, NULL, 0); + close(fildes[0]); + return WRDE_NOSPACE; +} + +static int +parse_comm(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset, int flags, + wordexp_t * pwordexp, const char *ifs, const char *ifs_white) +{ + /* We are poised just after "$(" */ + int paren_depth = 1; + int error = 0; + int quoted = 0; /* 1 for singly-quoted, 2 for doubly-quoted */ + size_t comm_length; + size_t comm_maxlen; + char *comm = w_newword(&comm_length, &comm_maxlen); + + for (; words[*offset]; ++(*offset)) { + switch (words[*offset]) { + case '\'': + if (quoted == 0) + quoted = 1; + else if (quoted == 1) + quoted = 0; + + break; + + case '"': + if (quoted == 0) + quoted = 2; + else if (quoted == 2) + quoted = 0; + + break; + + case ')': + if (!quoted && --paren_depth == 0) { + /* Go -- give script to the shell */ + if (comm) { + error = exec_comm(comm, word, word_length, max_length, + flags, pwordexp, ifs, ifs_white); + free(comm); + } + + return error; + } + + /* This is just part of the script */ + break; + + case '(': + if (!quoted) + ++paren_depth; + } + + comm = w_addchar(comm, &comm_length, &comm_maxlen, words[*offset]); + if (comm == NULL) + return WRDE_NOSPACE; + } + + /* Premature end */ + if (comm) + free(comm); + + return WRDE_SYNTAX; +} + +static int +parse_backtick(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset, int flags, + wordexp_t * pwordexp, const char *ifs, + const char *ifs_white) +{ + /* We are poised just after "`" */ + int error; + int squoting = 0; + size_t comm_length; + size_t comm_maxlen; + char *comm = w_newword(&comm_length, &comm_maxlen); + + for (; words[*offset]; ++(*offset)) { + switch (words[*offset]) { + case '`': + /* Go -- give the script to the shell */ + error = exec_comm(comm, word, word_length, max_length, flags, + pwordexp, ifs, ifs_white); + free(comm); + return error; + + case '\\': + if (squoting) { + error = parse_qtd_backslash(&comm, &comm_length, &comm_maxlen, + words, offset); + + if (error) { + free(comm); + return error; + } + + break; + } + + ++(*offset); + error = parse_backslash(&comm, &comm_length, &comm_maxlen, words, + offset); + + if (error) { + free(comm); + return error; + } + + break; + + case '\'': + squoting = 1 - squoting; + default: + comm = w_addchar(comm, &comm_length, &comm_maxlen, + words[*offset]); + if (comm == NULL) + return WRDE_NOSPACE; + } + } + + /* Premature end */ + free(comm); + return WRDE_SYNTAX; +} + +static int +parse_param(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset, int flags, + wordexp_t * pwordexp, const char *ifs, const char *ifs_white, + int quoted) +{ + /* We are poised just after "$" */ + enum action { + ACT_NONE, + ACT_RP_SHORT_LEFT = '#', + ACT_RP_LONG_LEFT = 'L', + ACT_RP_SHORT_RIGHT = '%', + ACT_RP_LONG_RIGHT = 'R', + ACT_NULL_ERROR = '?', + ACT_NULL_SUBST = '-', + ACT_NONNULL_SUBST = '+', + ACT_NULL_ASSIGN = '=' + }; + size_t env_length; + size_t env_maxlen; + size_t pat_length; + size_t pat_maxlen; + size_t start = *offset; + char *env; + char *pattern; + char *value = NULL; + enum action action = ACT_NONE; + int depth = 0; + int colon_seen = 0; + int seen_hash = 0; + int free_value = 0; + int pattern_is_quoted = 0; /* 1 for singly-quoted, 2 for doubly-quoted */ + int error; + int special = 0; + char buffer[21]; + int brace = words[*offset] == '{'; + + env = w_newword(&env_length, &env_maxlen); + pattern = w_newword(&pat_length, &pat_maxlen); + + if (brace) + ++ * offset; + + /* First collect the parameter name. */ + + if (words[*offset] == '#') { + seen_hash = 1; + if (!brace) + goto envsubst; + ++*offset; + } + + if (isalpha(words[*offset]) || words[*offset] == '_') { + /* Normal parameter name. */ + do { + env = w_addchar(env, &env_length, &env_maxlen, words[*offset]); + if (env == NULL) + goto no_space; + } + while (isalnum(words[++*offset]) || words[*offset] == '_'); + } else if (isdigit(words[*offset])) { + /* Numeric parameter name. */ + special = 1; + do { + env = w_addchar(env, &env_length, &env_maxlen, words[*offset]); + if (env == NULL) + goto no_space; + if (!brace) + goto envsubst; + } + while (isdigit(words[++*offset])); + } else if (strchr("*@$", words[*offset]) != NULL) { + /* Special parameter. */ + special = 1; + env = w_addchar(env, &env_length, &env_maxlen, words[*offset]); + if (env == NULL) + goto no_space; + ++*offset; + } else { + if (brace) + goto syntax; + } + + if (brace) { + /* Check for special action to be applied to the value. */ + switch (words[*offset]) { + case '}': + /* Evaluate. */ + goto envsubst; + + case '#': + action = ACT_RP_SHORT_LEFT; + if (words[1 + *offset] == '#') { + ++*offset; + action = ACT_RP_LONG_LEFT; + } + break; + + case '%': + action = ACT_RP_SHORT_RIGHT; + if (words[1 + *offset] == '%') { + ++*offset; + action = ACT_RP_LONG_RIGHT; + } + break; + + case ':': + if (strchr("-=?+", words[1 + *offset]) == NULL) + goto syntax; + + colon_seen = 1; + action = words[++*offset]; + break; + + case '-': + case '=': + case '?': + case '+': + action = words[*offset]; + break; + + default: + goto syntax; + } + + /* Now collect the pattern, but don't expand it yet. */ + ++*offset; + for (; words[*offset]; ++(*offset)) { + switch (words[*offset]) { + case '{': + if (!pattern_is_quoted) + ++depth; + break; + + case '}': + if (!pattern_is_quoted) { + if (depth == 0) + goto envsubst; + --depth; + } + break; + + case '\\': + if (pattern_is_quoted) + /* Quoted; treat as normal character. */ + break; + + /* Otherwise, it's an escape: next character is literal. */ + if (words[++*offset] == '\0') + goto syntax; + + pattern = w_addchar(pattern, &pat_length, &pat_maxlen, '\\'); + if (pattern == NULL) + goto no_space; + + break; + + case '\'': + if (pattern_is_quoted == 0) + pattern_is_quoted = 1; + else if (pattern_is_quoted == 1) + pattern_is_quoted = 0; + + break; + + case '"': + if (pattern_is_quoted == 0) + pattern_is_quoted = 2; + else if (pattern_is_quoted == 2) + pattern_is_quoted = 0; + + break; + } + + pattern = w_addchar(pattern, &pat_length, &pat_maxlen, + words[*offset]); + if (pattern == NULL) + goto no_space; + } + } + + /* End of input string -- remember to reparse the character that we + * stopped at. */ + --(*offset); + + envsubst: + if (words[start] == '{' && words[*offset] != '}') + goto syntax; + + if (env == NULL) { + if (seen_hash) { + /* $# expands to the number of positional parameters */ + buffer[20] = '\0'; + value = _itoa(__libc_argc - 1, &buffer[20]); + seen_hash = 0; + } else { + /* Just $ on its own */ + *offset = start - 1; + *word = w_addchar(*word, word_length, max_length, '$'); + return *word ? 0 : WRDE_NOSPACE; + } + } + /* Is it a numeric parameter? */ + else if (isdigit(env[0])) { + int n = atoi(env); + + if (n >= __libc_argc) + /* Substitute NULL. */ + value = NULL; + else + /* Replace with appropriate positional parameter. */ + value = __libc_argv[n]; + } + /* Is it a special parameter? */ + else if (special) { + /* Is it `$$'? */ + if (*env == '$') { + buffer[20] = '\0'; + value = _itoa(getpid(), &buffer[20]); + } + /* Is it `${#*}' or `${#@}'? */ + else if ((*env == '*' || *env == '@') && seen_hash) { + buffer[20] = '\0'; + value = _itoa(__libc_argc > 0 ? __libc_argc - 1 : 0, + &buffer[20]); + *word = w_addstr(*word, word_length, max_length, value); + free(env); + if (pattern) + free(pattern); + return *word ? 0 : WRDE_NOSPACE; + } + /* Is it `$*' or `$@' (unquoted) ? */ + else if (*env == '*' || (*env == '@' && !quoted)) { + size_t plist_len = 0; + int p; + char *end; + + /* Build up value parameter by parameter (copy them) */ + for (p = 1; __libc_argv[p]; ++p) + plist_len += strlen(__libc_argv[p]) + 1; /* for space */ + value = malloc(plist_len); + if (value == NULL) + goto no_space; + end = value; + *end = 0; + for (p = 1; __libc_argv[p]; ++p) { + if (p > 1) + *end++ = ' '; + end = stpcpy(end, __libc_argv[p]); + } + + free_value = 1; + } else { + /* Must be a quoted `$@' */ + assert(*env == '@' && quoted); + + /* Each parameter is a separate word ("$@") */ + if (__libc_argc == 2) + value = __libc_argv[1]; + else if (__libc_argc > 2) { + int p; + + /* Append first parameter to current word. */ + value = w_addstr(*word, word_length, max_length, + __libc_argv[1]); + if (value == NULL || w_addword(pwordexp, value)) + goto no_space; + + for (p = 2; __libc_argv[p + 1]; p++) { + char *newword = strdup(__libc_argv[p]); + + if (newword == NULL || w_addword(pwordexp, newword)) + goto no_space; + } + + /* Start a new word with the last parameter. */ + *word = w_newword(word_length, max_length); + value = __libc_argv[p]; + } else { + free(env); + free(pattern); + return 0; + } + } + } else + value = getenv(env); + + if (value == NULL && (flags & WRDE_UNDEF)) { + /* Variable not defined. */ + error = WRDE_BADVAL; + goto do_error; + } + + if (action != ACT_NONE) { + int expand_pattern = 0; + + /* First, find out if we need to expand pattern (i.e. if we will + * use it). */ + switch (action) { + case ACT_RP_SHORT_LEFT: + case ACT_RP_LONG_LEFT: + case ACT_RP_SHORT_RIGHT: + case ACT_RP_LONG_RIGHT: + /* Always expand for these. */ + expand_pattern = 1; + break; + + case ACT_NULL_ERROR: + case ACT_NULL_SUBST: + case ACT_NULL_ASSIGN: + if (!value || (!*value && colon_seen)) + /* If param is unset, or set but null and a colon has been seen, + the expansion of the pattern will be needed. */ + expand_pattern = 1; + + break; + + case ACT_NONNULL_SUBST: + /* Expansion of word will be needed if parameter is set and not null, + or set null but no colon has been seen. */ + if (value && (*value || !colon_seen)) + expand_pattern = 1; + + break; + + default: + assert(!"Unrecognised action!"); + } + + if (expand_pattern) { + /* We need to perform tilde expansion, parameter expansion, + command substitution, and arithmetic expansion. We also + have to be a bit careful with wildcard characters, as + pattern might be given to fnmatch soon. To do this, we + convert quotes to escapes. */ + + char *expanded; + size_t exp_len; + size_t exp_maxl; + char *p; + int quoted = 0; /* 1: single quotes; 2: double */ + + expanded = w_newword(&exp_len, &exp_maxl); + for (p = pattern; p && *p; p++) { + size_t offset; + + switch (*p) { + case '"': + if (quoted == 2) + quoted = 0; + else if (quoted == 0) + quoted = 2; + else + break; + + continue; + + case '\'': + if (quoted == 1) + quoted = 0; + else if (quoted == 0) + quoted = 1; + else + break; + + continue; + + case '*': + case '?': + if (quoted) { + /* Convert quoted wildchar to escaped wildchar. */ + expanded = w_addchar(expanded, &exp_len, + &exp_maxl, '\\'); + + if (expanded == NULL) + goto no_space; + } + break; + + case '$': + offset = 0; + error = parse_dollars(&expanded, &exp_len, &exp_maxl, p, + &offset, flags, NULL, NULL, NULL, 1); + if (error) { + if (free_value) + free(value); + + if (expanded) + free(expanded); + + goto do_error; + } + + p += offset; + continue; + + case '~': + if (quoted || exp_len) + break; + + offset = 0; + error = parse_tilde(&expanded, &exp_len, &exp_maxl, p, + &offset, 0); + if (error) { + if (free_value) + free(value); + + if (expanded) + free(expanded); + + goto do_error; + } + + p += offset; + continue; + + case '\\': + expanded = w_addchar(expanded, &exp_len, &exp_maxl, '\\'); + ++p; + assert(*p); /* checked when extracted initially */ + if (expanded == NULL) + goto no_space; + } + + expanded = w_addchar(expanded, &exp_len, &exp_maxl, *p); + + if (expanded == NULL) + goto no_space; + } + + if (pattern) + free(pattern); + + pattern = expanded; + } + + switch (action) { + case ACT_RP_SHORT_LEFT: + case ACT_RP_LONG_LEFT: + case ACT_RP_SHORT_RIGHT: + case ACT_RP_LONG_RIGHT: + { + char *p; + char c; + char *end; + + if (value == NULL || pattern == NULL || *pattern == '\0') + break; + + end = value + strlen(value); + + switch (action) { + case ACT_RP_SHORT_LEFT: + for (p = value; p <= end; ++p) { + c = *p; + *p = '\0'; + if (fnmatch(pattern, value, 0) != FNM_NOMATCH) { + *p = c; + if (free_value) { + char *newval = strdup(p); + + if (newval == NULL) { + free(value); + goto no_space; + } + free(value); + value = newval; + } else + value = p; + break; + } + *p = c; + } + + break; + + case ACT_RP_LONG_LEFT: + for (p = end; p >= value; --p) { + c = *p; + *p = '\0'; + if (fnmatch(pattern, value, 0) != FNM_NOMATCH) { + *p = c; + if (free_value) { + char *newval = strdup(p); + + if (newval == NULL) { + free(value); + goto no_space; + } + free(value); + value = newval; + } else + value = p; + break; + } + *p = c; + } + + break; + + case ACT_RP_SHORT_RIGHT: + for (p = end; p >= value; --p) { + if (fnmatch(pattern, p, 0) != FNM_NOMATCH) { + char *newval; + + newval = malloc(p - value + 1); + + if (newval == NULL) { + if (free_value) + free(value); + goto no_space; + } + + *(char *) mempcpy(newval, value, p - value) = '\0'; + if (free_value) + free(value); + value = newval; + free_value = 1; + break; + } + } + + break; + + case ACT_RP_LONG_RIGHT: + for (p = value; p <= end; ++p) { + if (fnmatch(pattern, p, 0) != FNM_NOMATCH) { + char *newval; + + newval = malloc(p - value + 1); + + if (newval == NULL) { + if (free_value) + free(value); + goto no_space; + } + + *(char *) mempcpy(newval, value, p - value) = '\0'; + if (free_value) + free(value); + value = newval; + free_value = 1; + break; + } + } + + break; + + default: + break; + } + + break; + } + + case ACT_NULL_ERROR: + if (value && *value) + /* Substitute parameter */ + break; + + error = 0; + if (!colon_seen && value) + /* Substitute NULL */ + ; + else if (*pattern) + fprintf(stderr, "%s: %s\n", env, pattern); + else { + fprintf(stderr, "%s: parameter null or not set\n", env); + error = WRDE_BADVAL; + } + + if (free_value) + free(value); + goto do_error; + + case ACT_NULL_SUBST: + if (value && *value) + /* Substitute parameter */ + break; + + if (free_value && value) + free(value); + + if (!colon_seen && value) + /* Substitute NULL */ + goto success; + + value = pattern ? strdup(pattern) : pattern; + free_value = 1; + + if (pattern && !value) + goto no_space; + + break; + + case ACT_NONNULL_SUBST: + if (value && (*value || !colon_seen)) { + if (free_value && value) + free(value); + + value = pattern ? strdup(pattern) : pattern; + free_value = 1; + + if (pattern && !value) + goto no_space; + + break; + } + + /* Substitute NULL */ + if (free_value) + free(value); + goto success; + + case ACT_NULL_ASSIGN: + if (value && *value) + /* Substitute parameter */ + break; + + if (!colon_seen && value) { + /* Substitute NULL */ + if (free_value) + free(value); + goto success; + } + + if (free_value && value) + free(value); + + value = pattern ? strdup(pattern) : pattern; + free_value = 1; + + if (pattern && !value) + goto no_space; + + setenv(env, value, 1); + break; + + default: + assert(!"Unrecognised action!"); + } + } + + free(env); + env = NULL; + free(pattern); + pattern = NULL; + + if (seen_hash) { + char param_length[21]; + + param_length[20] = '\0'; + *word = w_addstr(*word, word_length, max_length, + _itoa(value ? strlen(value) : 0, + ¶m_length[20])); + if (free_value) { + assert(value != NULL); + free(value); + } + + return *word ? 0 : WRDE_NOSPACE; + } + + if (value == NULL) + return 0; + + if (quoted || !pwordexp) { + /* Quoted - no field split */ + *word = w_addstr(*word, word_length, max_length, value); + if (free_value) + free(value); + + return *word ? 0 : WRDE_NOSPACE; + } else { + /* Need to field-split */ + char *value_copy = strdup(value); /* Don't modify value */ + char *field_begin = value_copy; + int seen_nonws_ifs = 0; + + if (free_value) + free(value); + + if (value_copy == NULL) + goto no_space; + + do { + char *field_end = field_begin; + char *next_field; + + /* If this isn't the first field, start a new word */ + if (field_begin != value_copy) { + if (w_addword(pwordexp, *word) == WRDE_NOSPACE) { + free(value_copy); + goto no_space; + } + + *word = w_newword(word_length, max_length); + } + + /* Skip IFS whitespace before the field */ + field_begin += strspn(field_begin, ifs_white); + + if (!seen_nonws_ifs && *field_begin == 0) + /* Nothing but whitespace */ + break; + + /* Search for the end of the field */ + field_end = field_begin + strcspn(field_begin, ifs); + + /* Set up pointer to the character after end of field and + skip whitespace IFS after it. */ + next_field = field_end + strspn(field_end, ifs_white); + + /* Skip at most one non-whitespace IFS character after the field */ + seen_nonws_ifs = 0; + if (*next_field && strchr(ifs, *next_field)) { + seen_nonws_ifs = 1; + next_field++; + } + + /* Null-terminate it */ + *field_end = 0; + + /* Tag a copy onto the current word */ + *word = w_addstr(*word, word_length, max_length, field_begin); + + if (*word == NULL && *field_begin != '\0') { + free(value_copy); + goto no_space; + } + + field_begin = next_field; + } + while (seen_nonws_ifs || *field_begin); + + free(value_copy); + } + + return 0; + + success: + error = 0; + goto do_error; + + no_space: + error = WRDE_NOSPACE; + goto do_error; + + syntax: + error = WRDE_SYNTAX; + + do_error: + if (env) + free(env); + + if (pattern) + free(pattern); + + return error; +} +#else +static inline int +parse_backtick(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset, int flags, + wordexp_t * pwordexp, const char *ifs, + const char *ifs_white) +{ + return 0; +} +#endif + +static int +parse_dollars(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset, int flags, + wordexp_t * pwordexp, const char *ifs, const char *ifs_white, + int quoted) +{ + /* We are poised _at_ "$" */ + switch (words[1 + *offset]) { + case '"': + case '\'': + case 0: + *word = w_addchar(*word, word_length, max_length, '$'); + return *word ? 0 : WRDE_NOSPACE; + +#ifdef __WORDEXP_FULL + case '(': + if (words[2 + *offset] == '(') { + /* Differentiate between $((1+3)) and $((echo);(ls)) */ + int i = 3 + *offset; + int depth = 0; + + while (words[i] && !(depth == 0 && words[i] == ')')) { + if (words[i] == '(') + ++depth; + else if (words[i] == ')') + --depth; + + ++i; + } + + if (words[i] == ')' && words[i + 1] == ')') { + (*offset) += 3; + /* Call parse_arith -- 0 is for "no brackets" */ + return parse_arith(word, word_length, max_length, words, + offset, flags, 0); + } + } + + if (flags & WRDE_NOCMD) + return WRDE_CMDSUB; + + (*offset) += 2; + return parse_comm(word, word_length, max_length, words, offset, + flags, quoted ? NULL : pwordexp, ifs, ifs_white); + + case '[': + (*offset) += 2; + /* Call parse_arith -- 1 is for "brackets" */ + return parse_arith(word, word_length, max_length, words, offset, + flags, 1); + + case '{': + default: + ++(*offset); /* parse_param needs to know if "{" is there */ + return parse_param(word, word_length, max_length, words, offset, + flags, pwordexp, ifs, ifs_white, quoted); +#else + default: + ++(*offset); /* parse_param needs to know if "{" is there */ + return 0; +#endif + } +} + +static int +parse_dquote(char **word, size_t * word_length, size_t * max_length, + const char *words, size_t * offset, int flags, + wordexp_t * pwordexp, const char *ifs, const char *ifs_white) +{ + /* We are poised just after a double-quote */ + int error; + + for (; words[*offset]; ++(*offset)) { + switch (words[*offset]) { + case '"': + return 0; + + case '$': + error = parse_dollars(word, word_length, max_length, words, offset, + flags, pwordexp, ifs, ifs_white, 1); + /* The ``1'' here is to tell parse_dollars not to + * split the fields. It may need to, however ("$@"). + */ + if (error) + return error; + + break; + + case '`': + if (flags & WRDE_NOCMD) + return WRDE_CMDSUB; + + ++(*offset); + error = parse_backtick(word, word_length, max_length, words, + offset, flags, NULL, NULL, NULL); + /* The first NULL here is to tell parse_backtick not to + * split the fields. + */ + if (error) + return error; + + break; + + case '\\': + error = parse_qtd_backslash(word, word_length, max_length, words, + offset); + + if (error) + return error; + + break; + + default: + *word = w_addchar(*word, word_length, max_length, words[*offset]); + if (*word == NULL) + return WRDE_NOSPACE; + } + } + + /* Unterminated string */ + return WRDE_SYNTAX; +} + +/* + * wordfree() is to be called after pwordexp is finished with. + */ + +void wordfree(wordexp_t * pwordexp) +{ + + /* wordexp can set pwordexp to NULL */ + if (pwordexp && pwordexp->we_wordv) { + char **wordv = pwordexp->we_wordv; + + for (wordv += pwordexp->we_offs; *wordv; ++wordv) + free(*wordv); + + free(pwordexp->we_wordv); + pwordexp->we_wordv = NULL; + } +} + +/* + * wordexp() + */ + +int wordexp(const char *words, wordexp_t * we, int flags) +{ + size_t words_offset; + size_t word_length; + size_t max_length; + char *word = w_newword(&word_length, &max_length); + int error; + char *ifs; + char ifs_white[4]; + wordexp_t old_word = *we; + + if (flags & WRDE_REUSE) { + /* Minimal implementation of WRDE_REUSE for now */ + wordfree(we); + old_word.we_wordv = NULL; + } + + if ((flags & WRDE_APPEND) == 0) { + we->we_wordc = 0; + + if (flags & WRDE_DOOFFS) { + we->we_wordv = calloc(1 + we->we_offs, sizeof(char *)); + if (we->we_wordv == NULL) { + error = WRDE_NOSPACE; + goto do_error; + } + } else { + we->we_wordv = calloc(1, sizeof(char *)); + if (we->we_wordv == NULL) { + error = WRDE_NOSPACE; + goto do_error; + } + + we->we_offs = 0; + } + } + + /* Find out what the field separators are. + * There are two types: whitespace and non-whitespace. + */ + ifs = getenv("IFS"); + + if (!ifs) + /* IFS unset - use <space><tab><newline>. */ + ifs = strcpy(ifs_white, " \t\n"); + else { + char *ifsch = ifs; + char *whch = ifs_white; + + /* Start off with no whitespace IFS characters */ + ifs_white[0] = '\0'; + + while (*ifsch != '\0') { + if ((*ifsch == ' ') || (*ifsch == '\t') || (*ifsch == '\n')) { + /* Whitespace IFS. See first whether it is already in our + collection. */ + char *runp = ifs_white; + + while (runp < whch && *runp != '\0' && *runp != *ifsch) + ++runp; + + if (runp == whch) + *whch++ = *ifsch; + } + + ++ifsch; + } + *whch = '\0'; + } + + for (words_offset = 0; words[words_offset]; ++words_offset) + switch (words[words_offset]) { + case '\\': + error = parse_backslash(&word, &word_length, &max_length, words, + &words_offset); + + if (error) + goto do_error; + + break; + + case '$': + error = parse_dollars(&word, &word_length, &max_length, words, + &words_offset, flags, we, ifs, ifs_white, + 0); + + if (error) + goto do_error; + + break; + + case '`': + if (flags & WRDE_NOCMD) { + error = WRDE_CMDSUB; + goto do_error; + } + + ++words_offset; + error = parse_backtick(&word, &word_length, &max_length, words, + &words_offset, flags, we, ifs, + ifs_white); + + if (error) + goto do_error; + + break; + + case '"': + ++words_offset; + error = parse_dquote(&word, &word_length, &max_length, words, + &words_offset, flags, we, ifs, ifs_white); + + if (error) + goto do_error; + + if (!word_length) { + error = w_addword(we, NULL); + + if (error) + return error; + } + + break; + + case '\'': + ++words_offset; + error = parse_squote(&word, &word_length, &max_length, words, + &words_offset); + + if (error) + goto do_error; + + if (!word_length) { + error = w_addword(we, NULL); + + if (error) + return error; + } + + break; + + case '~': + error = parse_tilde(&word, &word_length, &max_length, words, + &words_offset, we->we_wordc); + + if (error) + goto do_error; + + break; + + case '*': + case '[': + case '?': + error = parse_glob(&word, &word_length, &max_length, words, + &words_offset, flags, we, ifs, ifs_white); + + if (error) + goto do_error; + + break; + + default: + /* Is it a word separator? */ + if (strchr(" \t", words[words_offset]) == NULL) { + char ch = words[words_offset]; + + /* Not a word separator -- but is it a valid word char? */ + if (strchr("\n|&;<>(){}", ch)) { + /* Fail */ + error = WRDE_BADCHAR; + goto do_error; + } + + /* "Ordinary" character -- add it to word */ + word = w_addchar(word, &word_length, &max_length, ch); + if (word == NULL) { + error = WRDE_NOSPACE; + goto do_error; + } + + break; + } + + /* If a word has been delimited, add it to the list. */ + if (word != NULL) { + error = w_addword(we, word); + if (error) + goto do_error; + } + + word = w_newword(&word_length, &max_length); + } + + /* End of string */ + + /* There was a word separator at the end */ + if (word == NULL) /* i.e. w_newword */ + return 0; + + /* There was no field separator at the end */ + return w_addword(we, word); + + do_error: + /* Error: + * free memory used (unless error is WRDE_NOSPACE), and + * set we members back to what they were. + */ + + if (word != NULL) + free(word); + + if (error == WRDE_NOSPACE) + return WRDE_NOSPACE; + + if ((flags & WRDE_APPEND) == 0) + wordfree(we); + + *we = old_word; + return error; +} |