Logo Search packages:      
Sourcecode: dash version File versions

expand.c

/*    $NetBSD: expand.c,v 1.56 2002/11/24 22:35:39 christos Exp $ */

/*-
 * Copyright (c) 1991, 1993
 *    The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Kenneth Almquist.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by the University of
 *    California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)expand.c      8.5 (Berkeley) 5/15/95";
#else
__RCSID("$NetBSD: expand.c,v 1.56 2002/11/24 22:35:39 christos Exp $");
#endif
#endif /* not lint */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#if defined(__GLIBC__)
#if !defined(FNMATCH_BROKEN)
#include <fnmatch.h>
#if !defined(GLOB_BROKEN)
#include <glob.h>
#endif
#else
#include <ctype.h>
#endif
#endif

/*
 * Routines to expand arguments to commands.  We have to deal with
 * backquotes, shell variables, and file metacharacters.
 */

#include "shell.h"
#include "main.h"
#include "nodes.h"
#include "eval.h"
#include "expand.h"
#include "syntax.h"
#include "parser.h"
#include "jobs.h"
#include "options.h"
#include "var.h"
#include "input.h"
#include "output.h"
#include "memalloc.h"
#include "error.h"
#include "mystring.h"
#include "show.h"

/*
 * _rmescape() flags
 */
#define RMESCAPE_ALLOC  0x1   /* Allocate a new string */
#define RMESCAPE_GLOB   0x2   /* Add backslashes for glob */
#define RMESCAPE_QUOTED 0x4   /* Remove CTLESC unless in quotes */
#define RMESCAPE_GROW   0x8   /* Grow strings instead of stalloc */
#define RMESCAPE_HEAP   0x10  /* Malloc strings instead of stalloc */

/* Add CTLESC when necessary. */
#define QUOTES_ESC      (EXP_FULL | EXP_CASE)
/* Do not skip NUL characters. */
#define QUOTES_KEEPNUL  EXP_TILDE

/*
 * Structure specifying which parts of the string should be searched
 * for IFS characters.
 */

struct ifsregion {
      struct ifsregion *next; /* next region in list */
      int begoff;       /* offset of start of region */
      int endoff;       /* offset of end of region */
      int nulonly;            /* search for nul bytes only */
};

/* output of current string */
static char *expdest;
/* list of back quote expressions */
static struct nodelist *argbackq;
/* first struct in list of ifs regions */
static struct ifsregion ifsfirst;
/* last struct in list */
static struct ifsregion *ifslastp;
/* holds expanded arg list */
static struct arglist exparg;

STATIC void argstr(char *, int);
STATIC char *exptilde(char *, char *, int);
STATIC void expbackq(union node *, int, int);
STATIC const char *subevalvar(char *, char *, int, int, int, int, int);
STATIC char *evalvar(char *, int);
STATIC size_t strtodest(const char *, const char *, int);
STATIC void memtodest(const unsigned char *, size_t, const char *, int);
STATIC ssize_t varvalue(char *, int, int);
STATIC void recordregion(int, int, int);
STATIC void removerecordregions(int); 
STATIC void ifsbreakup(char *, struct arglist *);
STATIC void ifsfree(void);
STATIC void expandmeta(struct strlist *, int);
#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN)
STATIC void addglob(const glob_t *);
#else
STATIC void expmeta(char *, char *);
#endif
STATIC void addfname(char *);
#if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
STATIC struct strlist *expsort(struct strlist *);
STATIC struct strlist *msort(struct strlist *, int);
#endif
STATIC int patmatch(char *, const char *);
#if !defined(__GLIBC__) || defined(FNMATCH_BROKEN)
STATIC int pmatch(const char *, const char *);
#else
#define pmatch(a, b) !fnmatch((a), (b), 0)
#endif
STATIC int cvtnum(long);
STATIC size_t esclen(const char *, const char *);
STATIC char *scanleft(char *, char *, char *, char *, int, int);
STATIC char *scanright(char *, char *, char *, char *, int, int);
STATIC void varunset(const char *, const char *, const char *, int)
      __attribute__((__noreturn__));


/*
 * Prepare a pattern for a glob(3) call.
 *
 * Returns an stalloced string.
 */

STATIC inline char *
preglob(const char *pattern, int quoted, int flag) {
      flag |= RMESCAPE_GLOB;
      if (quoted) {
            flag |= RMESCAPE_QUOTED;
      }
      return _rmescapes((char *)pattern, flag);
}


STATIC size_t
esclen(const char *start, const char *p) {
      size_t esc = 0;

      while (p > start && *--p == CTLESC) {
            esc++;
      }
      return esc;
}


/*
 * Expand shell variables and backquotes inside a here document.
 */

void
expandhere(union node *arg, int fd)
{
      herefd = fd;
      expandarg(arg, (struct arglist *)NULL, 0);
      xwrite(fd, stackblock(), expdest - (char *)stackblock());
}


/*
 * Perform variable substitution and command substitution on an argument,
 * placing the resulting list of arguments in arglist.  If EXP_FULL is true,
 * perform splitting and file name expansion.  When arglist is NULL, perform
 * here document expansion.
 */

void
expandarg(union node *arg, struct arglist *arglist, int flag)
{
      struct strlist *sp;
      char *p;

      argbackq = arg->narg.backquote;
      STARTSTACKSTR(expdest);
      ifsfirst.next = NULL;
      ifslastp = NULL;
      argstr(arg->narg.text, flag);
      p = _STPUTC('\0', expdest);
      expdest = p - 1;
      if (arglist == NULL) {
            return;                 /* here document expanded */
      }
      p = grabstackstr(p);
      exparg.lastp = &exparg.list;
      /*
       * TODO - EXP_REDIR
       */
      if (flag & EXP_FULL) {
            ifsbreakup(p, &exparg);
            *exparg.lastp = NULL;
            exparg.lastp = &exparg.list;
            expandmeta(exparg.list, flag);
      } else {
            if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
                  rmescapes(p);
            sp = (struct strlist *)stalloc(sizeof (struct strlist));
            sp->text = p;
            *exparg.lastp = sp;
            exparg.lastp = &sp->next;
      }
      if (ifsfirst.next)
            ifsfree();
      *exparg.lastp = NULL;
      if (exparg.list) {
            *arglist->lastp = exparg.list;
            arglist->lastp = exparg.lastp;
      }
}



/*
 * Perform variable and command substitution.  If EXP_FULL is set, output CTLESC
 * characters to allow for further processing.  Otherwise treat
 * $@ like $* since no splitting will be performed.
 */

STATIC void
argstr(char *p, int flag)
{
      static const char spclchars[] = {
            '=',
            ':',
            CTLQUOTEMARK,
            CTLENDVAR,
            CTLESC,
            CTLVAR,
            CTLBACKQ,
            CTLBACKQ | CTLQUOTE,
            CTLENDARI,
            0
      };
      const char *reject = spclchars;
      int c;
      int quotes = flag & QUOTES_ESC;
      int breakall = flag & EXP_WORD;
      int inquotes;
      size_t length;
      int startloc;

      if (!(flag & EXP_VARTILDE)) {
            reject += 2;
      } else if (flag & EXP_VARTILDE2) {
            reject++;
      }
      inquotes = 0;
      length = 0;
      if (flag & EXP_TILDE) {
            char *q;

            flag &= ~EXP_TILDE;
tilde:
            q = p;
            if (*q == CTLESC && (flag & EXP_QWORD))
                  q++;
            if (*q == '~')
                  p = exptilde(p, q, flag);
      }
start:
      startloc = expdest - (char *)stackblock();
      for (;;) {
            length += strcspn(p + length, reject);
            c = p[length];
            if (c && (!(c & 0x80) || c == CTLENDARI)) {
                  /* c == '=' || c == ':' || c == CTLENDARI */
                  length++;
            }
            if (length > 0) {
                  int newloc;
                  expdest = stnputs(p, length, expdest);
                  newloc = expdest - (char *)stackblock();
                  if (breakall && !inquotes && newloc > startloc) {
                        recordregion(startloc, newloc, 0);
                  }
                  startloc = newloc;
            }
            p += length + 1;
            length = 0;

            switch (c) {
            case '\0':
                  goto breakloop;
            case '=':
                  if (flag & EXP_VARTILDE2) {
                        p--;
                        continue;
                  }
                  flag |= EXP_VARTILDE2;
                  reject++;
                  /* fall through */
            case ':':
                  /*
                   * sort of a hack - expand tildes in variable
                   * assignments (after the first '=' and after ':'s).
                   */
                  if (*--p == '~') {
                        goto tilde;
                  }
                  continue;
            }

            switch (c) {
            case CTLENDVAR: /* ??? */
                  goto breakloop;
            case CTLQUOTEMARK:
                  /* "$@" syntax adherence hack */
                  if (
                        !inquotes &&
                        !memcmp(p, dolatstr, DOLATSTRLEN) &&
                        (p[4] == CTLQUOTEMARK || (
                              p[4] == CTLENDVAR &&
                              p[5] == CTLQUOTEMARK
                        ))
                  ) {
                        p = evalvar(p + 1, flag) + 1;
                        goto start;
                  }
                  inquotes = !inquotes;
addquote:
                  if (quotes) {
                        p--;
                        length++;
                        startloc++;
                  }
                  break;
            case CTLESC:
                  startloc++;
                  length++;
                  goto addquote;
            case CTLVAR:
                  p = evalvar(p, flag);
                  goto start;
            case CTLBACKQ:
                  c = 0;
            case CTLBACKQ|CTLQUOTE:
                  expbackq(argbackq->n, c, quotes);
                  argbackq = argbackq->next;
                  goto start;
            case CTLENDARI:
                  p--;
                  expari(quotes);
                  goto start;
            }
      }
breakloop:
      ;
}

STATIC char *
exptilde(char *startp, char *p, int flag)
{
      char c;
      char *name;
      struct passwd *pw;
      const char *home;
      int quotes = flag & QUOTES_ESC;
      int startloc;

      name = p + 1;

      while ((c = *++p) != '\0') {
            switch(c) {
            case CTLESC:
                  return (startp);
            case CTLQUOTEMARK:
                  return (startp);
            case ':':
                  if (flag & EXP_VARTILDE)
                        goto done;
                  break;
            case '/':
            case CTLENDVAR:
                  goto done;
            }
      }
done:
      *p = '\0';
      if (*name == '\0') {
            if ((home = lookupvar(homestr)) == NULL)
                  goto lose;
      } else {
            if ((pw = getpwnam(name)) == NULL)
                  goto lose;
            home = pw->pw_dir;
      }
      if (*home == '\0')
            goto lose;
      *p = c;
      startloc = expdest - (char *)stackblock();
      strtodest(home, SQSYNTAX, quotes);
      recordregion(startloc, expdest - (char *)stackblock(), 0);
      return (p);
lose:
      *p = c;
      return (startp);
}


STATIC void 
removerecordregions(int endoff)
{
      if (ifslastp == NULL)
            return;

      if (ifsfirst.endoff > endoff) {
            while (ifsfirst.next != NULL) {
                  struct ifsregion *ifsp;
                  INTOFF;
                  ifsp = ifsfirst.next->next;
                  ckfree(ifsfirst.next);
                  ifsfirst.next = ifsp;
                  INTON;
            }
            if (ifsfirst.begoff > endoff)
                  ifslastp = NULL;
            else {
                  ifslastp = &ifsfirst;
                  ifsfirst.endoff = endoff;
            }
            return;
      }
      
      ifslastp = &ifsfirst;
      while (ifslastp->next && ifslastp->next->begoff < endoff)
            ifslastp=ifslastp->next;
      while (ifslastp->next != NULL) {
            struct ifsregion *ifsp;
            INTOFF;
            ifsp = ifslastp->next->next;
            ckfree(ifslastp->next);
            ifslastp->next = ifsp;
            INTON;
      }
      if (ifslastp->endoff > endoff)
            ifslastp->endoff = endoff;
}


/*
 * Expand arithmetic expression.  Backup to start of expression,
 * evaluate, place result in (backed up) result, adjust string position.
 */
void
expari(int quotes)
{
      char *p, *start;
      int begoff;
      int flag;
      int len;

      /*    ifsfree(); */

      /*
       * This routine is slightly over-complicated for
       * efficiency.  Next we scan backwards looking for the
       * start of arithmetic.
       */
      start = stackblock();
      p = expdest - 1;
      *p = '\0';
      p--;
      do {
            int esc;

            while (*p != CTLARI) {
                  p--;
#ifdef DEBUG
                  if (p < start) {
                        error("missing CTLARI (shouldn't happen)");
                  }
#endif
            }

            esc = esclen(start, p);
            if (!(esc % 2)) {
                  break;
            }

            p -= esc + 1;
      } while (1);

      begoff = p - start;

      removerecordregions(begoff);

      flag = p[1];

      expdest = p;

      if (quotes)
            rmescapes(p + 2);

      len = cvtnum(arith(p + 2));

      if (flag != '"')
            recordregion(begoff, begoff + len, 0);
}


/*
 * Expand stuff in backwards quotes.
 */

STATIC void
expbackq(union node *cmd, int quoted, int quotes)
{
      struct backcmd in;
      int i;
      char buf[128];
      char *p;
      char *dest;
      int startloc;
      char const *syntax = quoted? DQSYNTAX : BASESYNTAX;
      struct stackmark smark;

      INTOFF;
      setstackmark(&smark);
      dest = expdest;
      startloc = dest - (char *)stackblock();
      grabstackstr(dest);
      evalbackcmd(cmd, (struct backcmd *) &in);
      popstackmark(&smark);

      p = in.buf;
      i = in.nleft;
      if (i == 0)
            goto read;
      for (;;) {
            memtodest(p, i, syntax, quotes);
read:
            if (in.fd < 0)
                  break;
            do {
                  i = read(in.fd, buf, sizeof buf);
            } while (i < 0 && errno == EINTR);
            TRACE(("expbackq: read returns %d\n", i));
            if (i <= 0)
                  break;
            p = buf;
      }

      if (in.buf)
            ckfree(in.buf);
      if (in.fd >= 0) {
            close(in.fd);
            back_exitstatus = waitforjob(in.jp);
      }
      INTON;

      /* Eat all trailing newlines */
      dest = expdest;
      for (; dest > (char *)stackblock() && dest[-1] == '\n';)
            STUNPUTC(dest);
      expdest = dest;

      if (quoted == 0)
            recordregion(startloc, dest - (char *)stackblock(), 0);
      TRACE(("evalbackq: size=%d: \"%.*s\"\n",
            (dest - (char *)stackblock()) - startloc,
            (dest - (char *)stackblock()) - startloc,
            stackblock() + startloc));
}


STATIC char *
scanleft(
      char *startp, char *rmesc, char *rmescend, char *str, int quotes,
      int zero
) {
      char *loc;
      char *loc2;
      char c;

      loc = startp;
      loc2 = rmesc;
      do {
            int match;
            const char *s = loc2;
            c = *loc2;
            if (zero) {
                  *loc2 = '\0';
                  s = rmesc;
            }
            match = pmatch(str, s);
            *loc2 = c;
            if (match)
                  return loc;
            if (quotes && *loc == CTLESC)
                  loc++;
            loc++;
            loc2++;
      } while (c);
      return 0;
}


STATIC char *
scanright(
      char *startp, char *rmesc, char *rmescend, char *str, int quotes,
      int zero
) {
      int esc = 0;
      char *loc;
      char *loc2;

      for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) {
            int match;
            char c = *loc2;
            const char *s = loc2;
            if (zero) {
                  *loc2 = '\0';
                  s = rmesc;
            }
            match = pmatch(str, s);
            *loc2 = c;
            if (match)
                  return loc;
            loc--;
            if (quotes) {
                  if (--esc < 0) {
                        esc = esclen(startp, loc);
                  }
                  if (esc % 2) {
                        esc--;
                        loc--;
                  }
            }
      }
      return 0;
}

STATIC const char *
subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags, int quotes)
{
      char *startp;
      char *loc;
      int saveherefd = herefd;
      struct nodelist *saveargbackq = argbackq;
      int amount;
      char *rmesc, *rmescend;
      int zero;
      char *(*scan)(char *, char *, char *, char *, int , int);

      herefd = -1;
      argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0);
      STPUTC('\0', expdest);
      herefd = saveherefd;
      argbackq = saveargbackq;
      startp = stackblock() + startloc;

      switch (subtype) {
      case VSASSIGN:
            setvar(str, startp, 0);
            amount = startp - expdest;
            STADJUST(amount, expdest);
            return startp;

      case VSQUESTION:
            varunset(p, str, startp, varflags);
            /* NOTREACHED */
      }

      subtype -= VSTRIMRIGHT;
#ifdef DEBUG
      if (subtype < 0 || subtype > 3)
            abort();
#endif

      rmesc = startp;
      rmescend = stackblock() + strloc;
      if (quotes) {
            rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW);
            if (rmesc != startp) {
                  rmescend = expdest;
                  startp = stackblock() + startloc;
            }
      }
      rmescend--;
      str = stackblock() + strloc;
      preglob(str, varflags & VSQUOTE, 0);

      /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */
      zero = subtype >> 1;
      /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */
      scan = (subtype & 1) ^ zero ? scanleft : scanright;

      loc = scan(startp, rmesc, rmescend, str, quotes, zero);
      if (loc) {
            if (zero) {
                  memmove(startp, loc, str - loc);
                  loc = startp + (str - loc) - 1;
            }
            *loc = '\0';
            amount = loc - expdest;
            STADJUST(amount, expdest);
      }
      return loc;
}


/*
 * Expand a variable, and return a pointer to the next character in the
 * input string.
 */
STATIC char *
evalvar(char *p, int flag)
{
      int subtype;
      int varflags;
      char *var;
      int patloc;
      int c;
      int startloc;
      ssize_t varlen;
      int easy;
      int quotes;
      int quoted;

      quotes = flag & QUOTES_ESC;
      varflags = *p++;
      subtype = varflags & VSTYPE;
      quoted = varflags & VSQUOTE;
      var = p;
      easy = (!quoted || (*var == '@' && shellparam.nparam));
      startloc = expdest - (char *)stackblock();
      p = strchr(p, '=') + 1;

again:
      varlen = varvalue(var, varflags, flag);
      if (varflags & VSNUL)
            varlen--;

      if (subtype == VSPLUS) {
            varlen = -1 - varlen;
            goto vsplus;
      }

      if (subtype == VSMINUS) {
vsplus:
            if (varlen < 0) {
                  argstr(
                        p, flag | EXP_TILDE |
                              (quoted ?  EXP_QWORD : EXP_WORD)
                  );
                  goto end;
            }
            if (easy)
                  goto record;
            goto end;
      }

      if (subtype == VSASSIGN || subtype == VSQUESTION) {
            if (varlen < 0) {
                  if (subevalvar(p, var, 0, subtype, startloc,
                               varflags, 0)) {
                        varflags &= ~VSNUL;
                        /* 
                         * Remove any recorded regions beyond 
                         * start of variable 
                         */
                        removerecordregions(startloc);
                        goto again;
                  }
                  goto end;
            }
            if (easy)
                  goto record;
            goto end;
      }

      if (varlen < 0 && uflag)
            varunset(p, var, 0, 0);

      if (subtype == VSLENGTH) {
            cvtnum(varlen > 0 ? varlen : 0);
            goto record;
      }

      if (subtype == VSNORMAL) {
            if (!easy)
                  goto end;
record:
            recordregion(startloc, expdest - (char *)stackblock(), quoted);
            goto end;
      }

#ifdef DEBUG
      switch (subtype) {
      case VSTRIMLEFT:
      case VSTRIMLEFTMAX:
      case VSTRIMRIGHT:
      case VSTRIMRIGHTMAX:
            break;
      default:
            abort();
      }
#endif

      if (varlen >= 0) {
            /*
             * Terminate the string and start recording the pattern
             * right after it
             */
            STPUTC('\0', expdest);
            patloc = expdest - (char *)stackblock();
            if (subevalvar(p, NULL, patloc, subtype,
                         startloc, varflags, quotes) == 0) {
                  int amount = expdest - (
                        (char *)stackblock() + patloc - 1
                  );
                  STADJUST(-amount, expdest);
            }
            /* Remove any recorded regions beyond start of variable */
            removerecordregions(startloc);
            goto record;
      }

end:
      if (subtype != VSNORMAL) {    /* skip to end of alternative */
            int nesting = 1;
            for (;;) {
                  if ((c = *p++) == CTLESC)
                        p++;
                  else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
                        if (varlen >= 0)
                              argbackq = argbackq->next;
                  } else if (c == CTLVAR) {
                        if ((*p++ & VSTYPE) != VSNORMAL)
                              nesting++;
                  } else if (c == CTLENDVAR) {
                        if (--nesting == 0)
                              break;
                  }
            }
      }
      return p;
}


/*
 * Put a string on the stack.
 */

STATIC void
memtodest(const unsigned char *p, size_t len, const char *syntax, int quotes) {
      char *q;

      if (unlikely(!len))
            return;

      q = makestrspace(len * 2, expdest);

      do {
            int c = *p++;
            if (c) {
                  if ((quotes & QUOTES_ESC) &&
                      (syntax[c] == CCTL || syntax[c] == CBACK))
                        USTPUTC(CTLESC, q);
            } else if (!(quotes & QUOTES_KEEPNUL))
                  continue;
            USTPUTC(c, q);
      } while (--len);

      expdest = q;
}


STATIC size_t
strtodest(p, syntax, quotes)
      const char *p;
      const char *syntax;
      int quotes;
{
      size_t len = strlen(p);
      memtodest(p, len, syntax, quotes);
      return len;
}



/*
 * Add the value of a specialized variable to the stack string.
 */

STATIC ssize_t
varvalue(char *name, int varflags, int flags)
{
      int num;
      char *p;
      int i;
      int sep;
      char sepc;
      char **ap;
      char const *syntax;
      int quoted = varflags & VSQUOTE;
      int subtype = varflags & VSTYPE;
      int discard = subtype == VSPLUS || subtype == VSLENGTH;
      int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL;
      ssize_t len = 0;

      sep = quoted ? ((flags & EXP_FULL) << CHAR_BIT) : 0;
      syntax = quoted ? DQSYNTAX : BASESYNTAX;

      switch (*name) {
      case '$':
            num = rootpid;
            goto numvar;
      case '?':
            num = exitstatus;
            goto numvar;
      case '#':
            num = shellparam.nparam;
            goto numvar;
      case '!':
            num = backgndpid;
            if (num == 0)
                  return -1;
numvar:
            len = cvtnum(num);
            break;
      case '-':
            p = makestrspace(NOPTS, expdest);
            for (i = NOPTS - 1; i >= 0; i--) {
                  if (optlist[i]) {
                        USTPUTC(optletters[i], p);
                        len++;
                  }
            }
            expdest = p;
            break;
      case '@':
            if (sep)
                  goto param;
            /* fall through */
      case '*':
            sep = ifsset() ? ifsval()[0] : ' ';
param:
            if (!(ap = shellparam.p))
                  return -1;
            sepc = sep;
            while ((p = *ap++)) {
                  len += strtodest(p, syntax, quotes);

                  if (*ap && sep) {
                        len++;
                        memtodest(&sepc, 1, syntax, quotes);
                  }
            }
            break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
            num = atoi(name);
            if (num < 0 || num > shellparam.nparam)
                  return -1;
            p = num ? shellparam.p[num - 1] : arg0;
            goto value;
      default:
            p = lookupvar(name);
value:
            if (!p)
                  return -1;

            len = strtodest(p, syntax, quotes);
            break;
      }

      if (discard)
            STADJUST(-len, expdest);
      return len;
}



/*
 * Record the fact that we have to scan this region of the
 * string for IFS characters.
 */

STATIC void
recordregion(int start, int end, int nulonly)
{
      struct ifsregion *ifsp;

      if (ifslastp == NULL) {
            ifsp = &ifsfirst;
      } else {
            INTOFF;
            ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
            ifsp->next = NULL;
            ifslastp->next = ifsp;
            INTON;
      }
      ifslastp = ifsp;
      ifslastp->begoff = start;
      ifslastp->endoff = end;
      ifslastp->nulonly = nulonly;
}



/*
 * Break the argument string into pieces based upon IFS and add the
 * strings to the argument list.  The regions of the string to be
 * searched for IFS characters have been stored by recordregion.
 */
STATIC void
ifsbreakup(char *string, struct arglist *arglist)
{
      struct ifsregion *ifsp;
      struct strlist *sp;
      char *start;
      char *p;
      char *q;
      const char *ifs, *realifs;
      int ifsspc;
      int nulonly;


      start = string;
      if (ifslastp != NULL) {
            ifsspc = 0;
            nulonly = 0;
            realifs = ifsset() ? ifsval() : defifs;
            ifsp = &ifsfirst;
            do {
                  p = string + ifsp->begoff;
                  nulonly = ifsp->nulonly;
                  ifs = nulonly ? nullstr : realifs;
                  ifsspc = 0;
                  while (p < string + ifsp->endoff) {
                        q = p;
                        if (*p == CTLESC)
                              p++;
                        if (strchr(ifs, *p)) {
                              if (!nulonly)
                                    ifsspc = (strchr(defifs, *p) != NULL);
                              /* Ignore IFS whitespace at start */
                              if (q == start && ifsspc) {
                                    p++;
                                    start = p;
                                    continue;
                              }
                              *q = '\0';
                              sp = (struct strlist *)stalloc(sizeof *sp);
                              sp->text = start;
                              *arglist->lastp = sp;
                              arglist->lastp = &sp->next;
                              p++;
                              if (!nulonly) {
                                    for (;;) {
                                          if (p >= string + ifsp->endoff) {
                                                break;
                                          }
                                          q = p;
                                          if (*p == CTLESC)
                                                p++;
                                          if (strchr(ifs, *p) == NULL ) {
                                                p = q;
                                                break;
                                          } else if (strchr(defifs, *p) == NULL) {
                                                if (ifsspc) {
                                                      p++;
                                                      ifsspc = 0;
                                                } else {
                                                      p = q;
                                                      break;
                                                }
                                          } else
                                                p++;
                                    }
                              }
                              start = p;
                        } else
                              p++;
                  }
            } while ((ifsp = ifsp->next) != NULL);
            if (nulonly)
                  goto add;
      }

      if (!*start)
            return;

add:
      sp = (struct strlist *)stalloc(sizeof *sp);
      sp->text = start;
      *arglist->lastp = sp;
      arglist->lastp = &sp->next;
}

STATIC void
ifsfree(void)
{
      struct ifsregion *p;

      INTOFF;
      p = ifsfirst.next;
      do {
            struct ifsregion *ifsp;
            ifsp = p->next;
            ckfree(p);
            p = ifsp;
      } while (p);
      ifslastp = NULL;
      ifsfirst.next = NULL;
      INTON;
}



/*
 * Expand shell metacharacters.  At this point, the only control characters
 * should be escapes.  The results are stored in the list exparg.
 */

#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN)
STATIC void
expandmeta(str, flag)
      struct strlist *str;
      int flag;
{
      /* TODO - EXP_REDIR */

      while (str) {
            const char *p;
            glob_t pglob;
            int i;

            if (fflag)
                  goto nometa;
            INTOFF;
            p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP);
            i = glob(p, GLOB_NOMAGIC, 0, &pglob);
            if (p != str->text)
                  ckfree(p);
            switch (i) {
            case 0:
                  if (!(pglob.gl_flags & GLOB_MAGCHAR))
                        goto nometa2;
                  addglob(&pglob);
                  globfree(&pglob);
                  INTON;
                  break;
            case GLOB_NOMATCH:
nometa2:
                  globfree(&pglob);
                  INTON;
nometa:
                  *exparg.lastp = str;
                  rmescapes(str->text);
                  exparg.lastp = &str->next;
                  break;
            default:    /* GLOB_NOSPACE */
                  error("Out of space");
            }
            str = str->next;
      }
}


/*
 * Add the result of glob(3) to the list.
 */

STATIC void
addglob(pglob)
      const glob_t *pglob;
{
      char **p = pglob->gl_pathv;

      do {
            addfname(*p);
      } while (*++p);
}


#else /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */
STATIC char *expdir;


STATIC void
expandmeta(struct strlist *str, int flag)
{
      static const char metachars[] = {
            '*', '?', '[', 0
      };
      /* TODO - EXP_REDIR */

      while (str) {
            struct strlist **savelastp;
            struct strlist *sp;
            char *p;

            if (fflag)
                  goto nometa;
            if (!strpbrk(str->text, metachars))
                  goto nometa;
            savelastp = exparg.lastp;

            INTOFF;
            p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP);
            {
                  int i = strlen(str->text);
                  expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
            }

            expmeta(expdir, p);
            ckfree(expdir);
            if (p != str->text)
                  ckfree(p);
            INTON;
            if (exparg.lastp == savelastp) {
                  /*
                   * no matches
                   */
nometa:
                  *exparg.lastp = str;
                  rmescapes(str->text);
                  exparg.lastp = &str->next;
            } else {
                  *exparg.lastp = NULL;
                  *savelastp = sp = expsort(*savelastp);
                  while (sp->next != NULL)
                        sp = sp->next;
                  exparg.lastp = &sp->next;
            }
            str = str->next;
      }
}


/*
 * Do metacharacter (i.e. *, ?, [...]) expansion.
 */

STATIC void
expmeta(char *enddir, char *name)
{
      char *p;
      const char *cp;
      char *start;
      char *endname;
      int metaflag;
      struct stat64 statb;
      DIR *dirp;
      struct dirent *dp;
      int atend;
      int matchdot;

      metaflag = 0;
      start = name;
      for (p = name; *p; p++) {
            if (*p == '*' || *p == '?')
                  metaflag = 1;
            else if (*p == '[') {
                  char *q = p + 1;
                  if (*q == '!')
                        q++;
                  for (;;) {
                        if (*q == '\\')
                              q++;
                        if (*q == '/' || *q == '\0')
                              break;
                        if (*++q == ']') {
                              metaflag = 1;
                              break;
                        }
                  }
            } else if (*p == '\\')
                  p++;
            else if (*p == '/') {
                  if (metaflag)
                        goto out;
                  start = p + 1;
            }
      }
out:
      if (metaflag == 0) {    /* we've reached the end of the file name */
            if (enddir != expdir)
                  metaflag++;
            p = name;
            do {
                  if (*p == '\\')
                        p++;
                  *enddir++ = *p;
            } while (*p++);
            if (metaflag == 0 || lstat64(expdir, &statb) >= 0)
                  addfname(expdir);
            return;
      }
      endname = p;
      if (name < start) {
            p = name;
            do {
                  if (*p == '\\')
                        p++;
                  *enddir++ = *p++;
            } while (p < start);
      }
      if (enddir == expdir) {
            cp = ".";
      } else if (enddir == expdir + 1 && *expdir == '/') {
            cp = "/";
      } else {
            cp = expdir;
            enddir[-1] = '\0';
      }
      if ((dirp = opendir(cp)) == NULL)
            return;
      if (enddir != expdir)
            enddir[-1] = '/';
      if (*endname == 0) {
            atend = 1;
      } else {
            atend = 0;
            *endname++ = '\0';
      }
      matchdot = 0;
      p = start;
      if (*p == '\\')
            p++;
      if (*p == '.')
            matchdot++;
      while (! int_pending() && (dp = readdir(dirp)) != NULL) {
            if (dp->d_name[0] == '.' && ! matchdot)
                  continue;
            if (pmatch(start, dp->d_name)) {
                  if (atend) {
                        scopy(dp->d_name, enddir);
                        addfname(expdir);
                  } else {
                        for (p = enddir, cp = dp->d_name;
                             (*p++ = *cp++) != '\0';)
                              continue;
                        p[-1] = '/';
                        expmeta(p, endname);
                  }
            }
      }
      closedir(dirp);
      if (! atend)
            endname[-1] = '/';
}
#endif      /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */


/*
 * Add a file name to the list.
 */

STATIC void
addfname(char *name)
{
      struct strlist *sp;

      sp = (struct strlist *)stalloc(sizeof *sp);
      sp->text = sstrdup(name);
      *exparg.lastp = sp;
      exparg.lastp = &sp->next;
}


#if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
/*
 * Sort the results of file name expansion.  It calculates the number of
 * strings to sort and then calls msort (short for merge sort) to do the
 * work.
 */

STATIC struct strlist *
expsort(struct strlist *str)
{
      int len;
      struct strlist *sp;

      len = 0;
      for (sp = str ; sp ; sp = sp->next)
            len++;
      return msort(str, len);
}


STATIC struct strlist *
msort(struct strlist *list, int len)
{
      struct strlist *p, *q = NULL;
      struct strlist **lpp;
      int half;
      int n;

      if (len <= 1)
            return list;
      half = len >> 1;
      p = list;
      for (n = half ; --n >= 0 ; ) {
            q = p;
            p = p->next;
      }
      q->next = NULL;               /* terminate first half of list */
      q = msort(list, half);        /* sort first half of list */
      p = msort(p, len - half);           /* sort second half */
      lpp = &list;
      for (;;) {
            if (strcmp(p->text, q->text) < 0) {
                  *lpp = p;
                  lpp = &p->next;
                  if ((p = *lpp) == NULL) {
                        *lpp = q;
                        break;
                  }
            } else {
                  *lpp = q;
                  lpp = &q->next;
                  if ((q = *lpp) == NULL) {
                        *lpp = p;
                        break;
                  }
            }
      }
      return list;
}
#endif


/*
 * Returns true if the pattern matches the string.
 */

STATIC inline int
patmatch(char *pattern, const char *string)
{
      return pmatch(preglob(pattern, 0, 0), string);
}


#if !defined(__GLIBC__) || defined(FNMATCH_BROKEN)
STATIC int ccmatch(const char *p, int chr, const char **r)
{
      static const struct class {
            char name[10];
            int (*fn)(int);
      } classes[] = {
            { .name = ":alnum:]", .fn = isalnum },
            { .name = ":cntrl:]", .fn = iscntrl },
            { .name = ":lower:]", .fn = islower },
            { .name = ":space:]", .fn = isspace },
            { .name = ":alpha:]", .fn = isalpha },
            { .name = ":digit:]", .fn = isdigit },
            { .name = ":print:]", .fn = isprint },
            { .name = ":upper:]", .fn = isupper },
            { .name = ":blank:]", .fn = isblank },
            { .name = ":graph:]", .fn = isgraph },
            { .name = ":punct:]", .fn = ispunct },
            { .name = ":xdigit:]", .fn = isxdigit },
      };
      const struct class *class, *end;

      end = classes + sizeof(classes) / sizeof(classes[0]);
      for (class = classes; class < end; class++) {
            const char *q;

            q = prefix(p, class->name);
            if (!q)
                  continue;
            *r = q;
            return class->fn(chr);
      }

      *r = 0;
      return 0;
}

STATIC int
pmatch(const char *pattern, const char *string)
{
      const char *p, *q;
      char c;

      p = pattern;
      q = string;
      for (;;) {
            switch (c = *p++) {
            case '\0':
                  goto breakloop;
            case '\\':
                  if (*p) {
                        c = *p++;
                  }
                  goto dft;
            case '?':
                  if (*q++ == '\0')
                        return 0;
                  break;
            case '*':
                  c = *p;
                  while (c == '*')
                        c = *++p;
                  if (c != '\\' && c != '?' && c != '*' && c != '[') {
                        while (*q != c) {
                              if (*q == '\0')
                                    return 0;
                              q++;
                        }
                  }
                  do {
                        if (pmatch(p, q))
                              return 1;
                  } while (*q++ != '\0');
                  return 0;
            case '[': {
                  const char *startp;
                  int invert, found;
                  char chr;

                  startp = p;
                  invert = 0;
                  if (*p == '!') {
                        invert++;
                        p++;
                  }
                  found = 0;
                  chr = *q++;
                  if (chr == '\0')
                        return 0;
                  c = *p++;
                  do {
                        if (!c) {
                              p = startp;
                              c = *p;
                              goto dft;
                        }
                        if (c == '[') {
                              const char *r;

                              found |= ccmatch(p, chr, &r);
                              if (r) {
                                    p = r;
                                    continue;
                              }
                        } else if (c == '\\')
                              c = *p++;
                        if (*p == '-' && p[1] != ']') {
                              p++;
                              if (*p == '\\')
                                    p++;
                              if (chr >= c && chr <= *p)
                                    found = 1;
                              p++;
                        } else {
                              if (chr == c)
                                    found = 1;
                        }
                  } while ((c = *p++) != ']');
                  if (found == invert)
                        return 0;
                  break;
            }
dft:          default:
                  if (*q++ != c)
                        return 0;
                  break;
            }
      }
breakloop:
      if (*q != '\0')
            return 0;
      return 1;
}
#endif



/*
 * Remove any CTLESC characters from a string.
 */

char *
_rmescapes(char *str, int flag)
{
      char *p, *q, *r;
      static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 };
      unsigned inquotes;
      int notescaped;
      int globbing;

      p = strpbrk(str, qchars);
      if (!p) {
            return str;
      }
      q = p;
      r = str;
      if (flag & RMESCAPE_ALLOC) {
            size_t len = p - str;
            size_t fulllen = len + strlen(p) + 1;

            if (flag & RMESCAPE_GROW) {
                  r = makestrspace(fulllen, expdest);
            } else if (flag & RMESCAPE_HEAP) {
                  r = ckmalloc(fulllen);
            } else {
                  r = stalloc(fulllen);
            }
            q = r;
            if (len > 0) {
#ifdef _GNU_SOURCE
                  q = mempcpy(q, str, len);
#else
                  memcpy(q, str, len);
                  q += len;
#endif
            }
      }
      inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED;
      globbing = flag & RMESCAPE_GLOB;
      notescaped = globbing;
      while (*p) {
            if (*p == CTLQUOTEMARK) {
                  inquotes = ~inquotes;
                  p++;
                  notescaped = globbing;
                  continue;
            }
            if (*p == '\\') {
                  /* naked back slash */
                  notescaped = 0;
                  goto copy;
            }
            if (*p == CTLESC) {
                  p++;
                  if (notescaped && inquotes && *p != '/') {
                        *q++ = '\\';
                  }
            }
            notescaped = globbing;
copy:
            *q++ = *p++;
      }
      *q = '\0';
      if (flag & RMESCAPE_GROW) {
            expdest = r;
            STADJUST(q - r + 1, expdest);
      }
      return r;
}



/*
 * See if a pattern matches in a case statement.
 */

int
casematch(union node *pattern, char *val)
{
      struct stackmark smark;
      int result;

      setstackmark(&smark);
      argbackq = pattern->narg.backquote;
      STARTSTACKSTR(expdest);
      ifslastp = NULL;
      argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
      STACKSTRNUL(expdest);
      result = patmatch(stackblock(), val);
      popstackmark(&smark);
      return result;
}

/*
 * Our own itoa().
 */

STATIC int
cvtnum(long num)
{
      int len;

      expdest = makestrspace(32, expdest);
      len = fmtstr(expdest, 32, "%ld", num);
      STADJUST(len, expdest);
      return len;
}

STATIC void
varunset(const char *end, const char *var, const char *umsg, int varflags)
{
      const char *msg;
      const char *tail;

      tail = nullstr;
      msg = "parameter not set";
      if (umsg) {
            if (*end == CTLENDVAR) {
                  if (varflags & VSNUL)
                        tail = " or null";
            } else
                  msg = umsg;
      }
      error("%.*s: %s%s", end - var - 1, var, msg, tail);
}

Generated by  Doxygen 1.6.0   Back to index