diff options
author | Eric Andersen <andersen@codepoet.org> | 2003-08-24 05:11:50 +0000 |
---|---|---|
committer | Eric Andersen <andersen@codepoet.org> | 2003-08-24 05:11:50 +0000 |
commit | f3679a2f8168d6f9c66763a7841366f51504bbe3 (patch) | |
tree | f2a5c29ad2dabbe9df3be7793ff059480a5b8d71 /libc/misc | |
parent | 50660812be5588036a14fc85af16bccef68fac02 (diff) |
conditionally add wordexp, in this case ripped from glibc.
I hate to add this stuff, but it is specified by SuSv3...
Diffstat (limited to 'libc/misc')
-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) |