Logo Search packages:      
Sourcecode: uclibc version File versions  Download package

wordexp.c

/* 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,
                                                      &param_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;
}

Generated by  Doxygen 1.6.0   Back to index