/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1982-2012 AT&T Intellectual Property *
* and is licensed under the *
* Eclipse Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* A copy of the License is available at *
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
* *
* Information and Software Systems Research *
* AT&T Research *
* Florham Park NJ *
* *
* David Korn <dgk@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
/*
* Shell macro expander
* expands ~
* expands ${...}
* expands $(...)
* expands $((...))
* expands `...`
*
* David Korn
* AT&T Labs
*
*/
#include "defs.h"
#include <fcin.h>
#include <pwd.h>
#include <ctype.h>
#include <regex.h>
#include "name.h"
#include "variables.h"
#include "shlex.h"
#include "io.h"
#include "jobs.h"
#include "shnodes.h"
#include "path.h"
#include "national.h"
#include "streval.h"
#ifndef STR_GROUP
# define STR_GROUP 0
#endif
#if SHOPT_MULTIBYTE
#else
# define mbchar(p) (*(unsigned char*)p++)
#endif /* SHOPT_MULTIBYTE */
#if _WINIX
static int Skip;
#endif /*_WINIX */
static int _c_;
typedef struct _mac_
{
} Mac_t;
/* type of macro expansions */
static int substring(const char*, const char*, int[], int);
static void tilde_expand2(Shell_t*,int);
static char *mac_getstring(char*);
static int charlen(const char*,int);
#if SHOPT_MULTIBYTE
static char *lastchar(const char*,const char*);
#endif /* SHOPT_MULTIBYTE */
{
return(addr);
}
/*
* perform only parameter substitution and catch failures
*/
{
if(string)
{
int jmp_val;
if(jmp_val == 0)
return(string);
}
return("");
}
/*
* Perform parameter expansion, command substitution, and arithmetic
* expansion on <str>.
* If <mode> greater than 1 file expansion is performed if the result
* yields a single pathname.
* If <mode> negative, than expansion rules for assignment are applied.
*/
{
if(mode<0)
else
if(mode==2)
{
/* expand only if unique */
else if(mode>1)
}
return(str);
}
/*
* Perform all the expansions on the argument <argp>
*/
{
else
else
if(!arghead)
{
}
else
{
}
if(!arghead)
{
}
else
{
}
return(flags);
}
/*
* Expand here document which is stored in <infile> or <string>
* The result is written to <outfile>
*/
{
register int c,n;
register char *cp;
if(infile)
else
while(1)
{
#if SHOPT_MULTIBYTE
if(mbwide())
{
do
{
{
case -1: /* illegal multi-byte char */
case 0:
case 1:
break;
default:
/* use state of alpha character */
n=state['a'];
}
}
while(n == 0);
}
else
#endif /* SHOPT_MULTIBYTE */
continue;
switch(n)
{
case S_EOF:
if((n=fcfill()) <=0)
{
/* ignore 0 byte when reading from file */
if(n==0 && fcfile())
continue;
return;
}
continue;
case S_ESC:
fcgetc(c);
if(c>0)
cp++;
continue;
case S_GRAVE:
break;
case S_DOL:
c = fcget();
if(c=='.')
goto regular;
switch(n=sh_lexstates[ST_DOL][c])
{
{
int offset2;
if(n==S_LBRA)
{
c = fcget();
fcseek(-1);
{
break;
}
}
else if(n==S_ALP)
{
fcseek(-1);
}
break;
}
case S_PAR:
break;
case S_EOF:
if((c=fcfill()) > 0)
goto again;
/* FALL THRU */
default:
fcseek(-1);
break;
}
}
}
}
/*
* expand argument but do not trim pattern characters
*/
{
return(sp);
if(flags&ARG_OPTIMIZE)
{
}
else
return(sp);
}
/*
* Process the characters up to <endch> or end of input string
*/
{
register int c,n;
int ansi_c = 0;
int paren = 0;
int ere = 0;
int brace = 0;
char *resume = 0;
/* handle // operator specially */
cp++;
while(1)
{
#if SHOPT_MULTIBYTE
if(mbwide())
{
do
{
{
case -1: /* illegal multi-byte char */
case 0:
len = 1;
case 1:
break;
default:
/* treat as if alpha */
n=state['a'];
}
}
while(n == 0);
}
else
#endif /* SHOPT_MULTIBYTE */
{
}
switch(n)
{
case S_ESC:
if(ansi_c)
{
/* process ANSI-C escape character */
if(c)
#if SHOPT_MULTIBYTE
{
int i;
for(i=0;i<n;i++)
}
else
#endif /* SHOPT_MULTIBYTE */
break;
}
else if(sh_isoption(SH_BRACEEXPAND) && mp->pattern==4 && (*cp==',' || *cp==LBRACE || *cp==RBRACE || *cp=='.'))
break;
{
if(c)
if(c= cp[-1])
{
if(c==ESCAPE)
}
else
cp--;
break;
}
n = S_PAT;
{
/* preserve \digit for pattern matching */
/* also \alpha for extended patterns */
{
break;
break;
}
/* followed by file expansion */
{
{
/* convert \\\$ into \$' */
}
break;
}
{
/* add \ for file expansion */
break;
}
}
break;
{
/* eliminate \ */
if(c)
/* check new-line joining */
}
break;
break;
if(c)
{
else
}
if(n==S_GRAVE)
{
char *dp;
if(!ERROR_translating())
break;
while(n=c, c= *++cp)
{
if(c=='\\' && n==c)
c = 0;
else if(c=='"' && n!='\\')
break;
}
{
break;
}
fcclose();
break;
}
{
ansi_c = 1;
}
break;
case S_ENDCH:
goto pattern;
{
brace--;
goto pattern;
continue;
}
case S_EOF:
if(c)
{
else
}
{
fcclose();
resume = 0;
continue;
}
c += (n!=S_EOF);
if(tilde>=0)
goto done;
case S_QUOTE:
break;
case S_LIT:
{
cp +=2;
break;
}
break;
if(c)
{
else
}
if(n==S_LIT)
{
continue;
else
}
else
break;
case S_BRACT:
{
{
e_badsubscript,*cp);
}
if(offset)
{
}
break;
}
case S_PAT:
{
{
paren++;
{
char *p = cp;
if(c=='A'||c=='E'||c=='K'||c=='P'||c=='X')
{
ere = 1;
break;
}
}
}
else if(n==RPAREN)
--paren;
}
goto pattern;
case S_COM:
{
if(c)
{
}
}
break;
case S_BRACE:
{
brace++;
}
{
/* mark beginning of {a,b} */
break;
}
break;
if(c)
break;
case S_EQ:
{
}
break;
case S_SLASH:
case S_COLON:
if(tilde >=0)
{
if(c)
#if _WINIX
if(Skip)
{
Skip = 0;
}
#endif /*_WINIX */
tilde = -1;
c=0;
}
#if 0
goto pattern;
#else
{
goto pattern;
}
#endif
break;
case S_DOT:
{
}
break;
}
}
done:
}
/*
* copy <str> to stack performing sub-expression substitutions
*/
static void mac_substitute(Mac_t *mp, register char *cp,char *str,register int subexp[],int subsize)
{
register int c,n;
while(1)
{
if(c==0)
break;
{
if(c)
if(n=='\\' || n==RBRACE)
{
first--;
continue;
}
if((c=subexp[2*n])>=0)
{
}
}
else if(n==0)
break;
}
}
#if SHOPT_FILESCAN
/*
* compute the arguments $1 ... $n and $# from the current line as needed
* save line offsets in the offsets array.
*/
{
if(m==0)
return(0);
if(m<0)
m = 0;
else if(n<=m)
m = n-1;
else
m--;
if(m >= MAX_OFFSETS-1)
m = MAX_OFFSETS-2;
n -= m;
while(1)
{
if(c==S_DELIM)
if(++m < MAX_OFFSETS)
if(c==S_SPACE)
if(--n==0 || c==S_EOF)
{
{
n++;
m--;
}
break;
}
}
if(n)
if(size)
return((char*)first);
}
#endif /* SHOPT_FILESCAN */
/*
* get the prefix after name reference resolution
*/
{
if(cp)
{
*cp = 0;
*cp = '.';
cp[1] = 0;
{
int n;
char *sp;
{
if(sub)
}
if(sub)
{
id[n++] = '[';
}
return(id);
}
}
}
/*
* copy to ']' onto the stack and return offset to it
*/
{
return(loc);
}
/*
* if name is a discipline function, run the function and put the results
* on the stack so that ${x.foo} behaves like ${ x.foo;}
*/
{
if(np)
{
/* treat ${x.foo} as ${x.foo;} */
union
{
} t;
union
{
} d;
memset(&t,0,sizeof(t));
memset(&d,0,sizeof(d));
return(1);
}
return(0);
}
{
int count = 0;
count++;
return(count);
}
{
char *cp;
if(len==0)
{
}
return(cp);
}
/*
* This routine handles $param, ${parm}, and ${param op word}
* The input stream is assumed to be a string
*/
{
register int c;
register char *v,*argp=0;
char *idx = 0;
idbuff[0] = 0;
idbuff[1] = 0;
{
case S_RBRA:
goto nosub;
/* This code handles ${#} */
c = mode;
/* FALL THRU */
case S_SPC1:
{
{
if(c=='#')
#ifdef SHOPT_TYPEDEF
else if(c=='@')
{
goto retry1;
}
#endif /* SHOPT_TYPEDEF */
else
mode = c;
goto retry1;
}
{
mode = c;
goto retry1;
}
}
/* FALL THRU */
case S_SPC2:
var = 0;
*id = c;
if(isastchar(c))
{
mode = c;
#if SHOPT_FILESCAN
{
}
else
#endif /* SHOPT_FILESCAN */
dolg = (v!=0);
}
break;
case S_LBRA:
if(type)
goto nosub;
goto retry1;
case S_PAR:
if(type)
goto nosub;
return(1);
case S_DIG:
var = 0;
c -= '0';
if(type)
{
register int d;
c = 10*c + (d-'0');
fcseek(-1);
}
idnum = c;
if(c==0)
#if SHOPT_FILESCAN
{
}
#endif /* SHOPT_FILESCAN */
{
}
else
v = 0;
break;
case S_ALP:
if(c=='.' && type==0)
goto nosub;
do
{
register int d;
np = 0;
do
{
if(LEN==1)
else
}
{
{
fcget();
if(c=='.' || c==LBRACT)
{
}
else
break;
}
else
{
if(d!='.')
{
mode = '@';
v[-1] = 0;
goto nosub;
}
else if(d!='.')
}
}
}
while(type && c=='.');
{
/* ${x.} or ${x..} */
{
nv_local = 1;
}
else
{
}
}
{
{
if((d=fcpeek(0))==c)
type = M_NAMESCAN;
else
type = M_NAMECOUNT;
break;
}
goto nosub;
}
{
if(c=='=' || (c==':' && d=='='))
}
#if SHOPT_FILESCAN
{
}
else
#endif /* SHOPT_FILESCAN */
{
if(!np)
{
}
}
var = 0;
{
{
return(1);
}
}
{
#if SHOPT_FILESCAN
#else
else
#endif /* SHOPT_FILESCAN */
np = 0;
}
if(type)
{
{
#if SHOPT_FIXEDARRAY
#else
#endif /* SHOPT_FIXEDARRAY */
if(ap)
{
dolmax =1;
if(array_assoc(ap))
bysub = 1;
}
else
{
np = 0;
}
}
if(!isbracechar(c))
goto nosub;
else
}
else
fcseek(-1);
{
{
peek++;
}
{
peek++;
}
if(cc==0)
}
{
char *savptr;
{
addsub = 1;
}
#ifdef SHOPT_TYPEDEF
{
if(nq)
else
}
#endif /* SHOPT_TYPEDEF */
#if SHOPT_FILESCAN
#endif /* SHOPT_FILESCAN */
else
{
{
if(ap)
else
}
else
/* special case --- ignore leading zeros */
if((mp->let || (mp->arith&&nv_isattr(np,(NV_LJUST|NV_RJUST|NV_ZFILL)))) && !nv_isattr(np,NV_INTEGER) && (offset==0 || isspace(c) || strchr(",.+-*/=%&|^?!<>",c)))
}
else
}
else
{
v = 0;
{
v = id;
}
}
if(ap)
{
#if SHOPT_OPTIMIZE
#endif
dolg = -1;
else
{
dolg = 0;
}
}
break;
case S_EOF:
fcseek(-1);
default:
goto nosub;
}
{
if(c!=RBRACE)
{
if(type==M_NAMECOUNT)
{
v = ltos(c);
}
else
{
dolg = -1;
}
}
{
if(dolg<0)
{
bysub=1;
}
else if(v)
{
v = "0";
else
}
}
else
{
else if(dolg>0)
{
#if SHOPT_FILESCAN
{
}
else
#endif /* SHOPT_FILESCAN */
}
else if(dolg<0)
c = array_elem(ap);
else
c = (v!=0);
v = ltos(c);
}
c = RBRACE;
}
nulflg = 0;
if(type && c==':')
{
nulflg=1;
else if(c!='%' && c!='#')
{
c = ':';
}
}
if(type)
{
if(!isbracechar(c))
{
if(!nulflg)
c = ':';
}
if(c!=RBRACE)
{
{
if(newops)
{
{
if(d=='(')
type = 0;
}
fcseek(-1);
newquote = 0;
}
else if(c=='?' || c=='=')
if(!oldpat)
/* add null byte */
if(c!='=')
}
else
{
}
}
}
else
{
fcseek(-1);
c=0;
}
if(c==':') /* ${name:expr1[:expr2]} */
{
char *ptr;
{
{
type = 0;
if(type==0)
#if SHOPT_FILESCAN
{
if(!v)
}
#endif /* SHOPT_FILESCAN */
else
v = 0;
}
else if(ap)
{
if(type<0)
{
if(array_assoc(ap))
else
}
if(array_assoc(ap))
{
}
else if(type > 0)
{
else
v = 0;
}
}
else if(type>0)
v = 0;
if(!v)
}
else if(v)
{
type = 0;
v = 0;
#if SHOPT_MULTIBYTE
else if(mbwide())
{
mbinit();
for(c=type;c;c--)
mbchar(v);
c = ':';
}
#endif /* SHOPT_MULTIBYTE */
else
v += type;
}
if(*ptr==':')
{
{
v = 0;
}
{
if(dolg>=0)
{
}
else
}
{
#if SHOPT_MULTIBYTE
if(mbwide())
{
char *vp = v;
mbinit();
while(type-->0)
{
c = 1;
vp += c;
}
c = ':';
}
#endif /* SHOPT_MULTIBYTE */
}
else
}
if(*ptr)
argp = 0;
}
/* check for substring operations */
else if(c == '#' || c == '%' || c=='/')
{
if(c=='/')
{
{
c = type;
type = '/';
argp++;
}
else
type = 0;
}
else
{
if(type==c) /* ## or %% */
argp++;
else
type = 0;
}
{
}
if(v || c=='/' && offset>=0)
}
/* check for quoted @ */
if(v && (!nulflg || *v ) && c!='+')
{
char *vlast;
while(1)
{
if(!v)
v= "";
if(c=='/' || c=='#' || c== '%')
{
int index = 0;
if(c!='/')
nmatch = 0;
while(1)
{
if(c=='%')
else
if(nmatch)
{
vlast = v;
vsize_last = vsize;
}
else if(c=='#')
vsize = 0;
if(vsize)
if(nmatch==0)
v += vsize;
else
v += match[1];
if(*v && c=='/' && type)
{
/* avoid infinite loop */
{
nmatch = 0;
v++;
}
continue;
}
vsize = -1;
break;
}
if(replen==0)
}
if(vsize)
if(addsub)
{
}
break;
{
if(nv_nextsub(np) == 0)
break;
if(bysub)
else
if(array_assoc(ap))
{
break;
}
else
{
break;
}
}
else if(dolg>=0)
{
break;
#if SHOPT_FILESCAN
{
break;
{
break;
}
}
else
#endif /* SHOPT_FILESCAN */
}
else if(!np)
{
break;
}
else
{
{
break;
}
if(ap)
if(nv_nextsub(np) == 0)
break;
if(bysub)
else
}
{
if(!np)
}
else if(d)
{
else
}
}
if(arrmax)
}
else if(argp)
{
if(c=='?')
{
if(np)
else if(idnum)
if(*argp)
{
}
else if(v)
else
}
else if(c=='=')
{
if(np)
{
nulflg = 0;
goto retry2;
}
else
}
}
else if(var && sh_isoption(SH_NOUNSET) && type<=M_TREE && (!np || nv_isnull(np) || (nv_isarray(np) && !np->nvalue.cp)))
{
if(np)
{
if(nv_isarray(np))
{
}
else
}
}
if(np)
if(pattern)
if(repstr)
if(idx)
return(1);
{
fcseek(-1);
return(1);
}
if(type)
fcseek(-1);
return(0);
}
/*
* This routine handles command substitution
* <type> is 0 for older `...` version
*/
{
register int c;
register char *str;
#ifdef SHOPT_COSHELL
return;
#endif /*SHOPT_COSHELL */
if(type)
{
sp = 0;
fcseek(-1);
if(!t)
{
else
else if(num)
else
return;
}
}
else
{
while(fcgetc(c)!='`' && c)
{
if(c==ESCAPE)
{
fcgetc(c);
}
}
/* disable verbose and don't save in history file */
type = 1;
}
#if KSHELL
if(t)
{
{
/* special case $(<file) and $(<#file) */
register int fd;
int r;
else
{
else
goto out_offset;
}
type = 3;
}
else
}
else
if(was_history)
if(was_verbose)
#else
#endif
newlines = 0;
/* read command substitution output and put on stack or here-doc */
{
}
{
#if SHOPT_CRNL
/* eliminate <cr> */
register char *dp;
{
c--;
str++;
}
while(c>1)
{
str++;
c--;
{
c--;
}
}
if(c)
#endif /* SHOPT_CRNL */
/* delay appending trailing new-lines */
if(c < 0)
{
newlines += nextnewlines;
continue;
}
if(newlines >0)
{
else
}
else if(lastc)
{
lastc = 0;
}
if(++c < bufsize)
str[c] = 0;
else
{
/* can't write past buffer so save last character */
c -= len;
str[c] = 0;
}
}
if(was_interactive)
{
while(newlines--)
else
}
if(lastc)
{
lastc = 0;
}
return;
}
/*
* copy <str> onto the stack
*/
{
register char *state;
{
/* prevent leading 0's from becomming octal constants */
{
break;
}
}
{
/* insert \ before file expansion characters */
while(size-->0)
{
#if SHOPT_MULTIBYTE
{
continue;
}
#endif
{
if(c==S_BRACT)
{
nopat = 0;
}
continue;
}
c=1;
{
{
{
c=0;
}
else
c=1;
}
else
c=1;
}
c=1;
{
cp++;
}
else
c=0;
if(c)
{
}
}
}
{
/* split words at ifs characters */
{
while(c = *sp++)
{
if(state[c]==0)
}
sp = "*?[{";
while(c = *sp++)
{
if(state[c]==0)
}
}
while(size-->0)
{
#if SHOPT_MULTIBYTE
{
continue;
}
#endif
{
/* don't allow extended patterns in this case */
}
else if(n==S_PAT)
{
#if SHOPT_MULTIBYTE
if(n==S_MBYTE)
{
continue;
if(n==-2)
n = 0;
cp += n;
size -= n;
n= S_DELIM;
}
#endif /* SHOPT_MULTIBYTE */
{
size--;
#if SHOPT_MULTIBYTE
{
if(n==-2)
n = 0;
cp += n;
size -= n;
n=S_DELIM;
}
else
#endif /* SHOPT_MULTIBYTE */
if(n==S_DELIM)
size--;
}
if(n==S_DELIM)
size--;
if(size<=0)
break;
cp--;
continue;
}
}
{
cp = "&|()";
while(c = *cp++)
{
state[c] = 0;
}
cp = "*?[{";
while(c = *cp++)
{
state[c] = 0;
}
}
}
else
}
/*
* Terminate field.
* If field is null count field if <split> is non-zero
* Do filename expansion of required
*/
{
register int count=0;
{
{
#if SHOPT_BRACEPAT
#else
#endif /* SHOPT_BRACEPAT */
if(count)
else if(split) /* pattern is null string */
else /* pattern expands to nothing */
count = -1;
}
if(count==0)
{
}
if(count>=0)
{
}
}
}
/*
* Finds the right substring of STRING using the expression PAT
* the longest substring is found when FLAG is set.
*/
{
if(flag)
{
{
return(n);
}
return(0);
}
{
#if SHOPT_MULTIBYTE
if(mbwide())
#endif /* SHOPT_MULTIBYTE */
{
nmatch = n;
break;
}
sp--;
}
return(0);
if(nmatch)
{
nmatch *=2;
while(--nmatch>=0)
}
return(n);
}
#if SHOPT_MULTIBYTE
{
register int c;
mbinit();
while(*str)
{
c = 1;
break;
str += c;
}
return(str);
}
#endif /* SHOPT_MULTIBYTE */
{
if(!string)
return(0);
#if SHOPT_MULTIBYTE
if(mbwide())
{
register int n=0;
mbinit();
if(len>0)
{
n++;
}
n++;
return(n);
}
else
#endif /* SHOPT_MULTIBYTE */
{
if(len<0)
return(len);
}
}
/*
* This is the default tilde discipline function
*/
{
if(!cp)
return(0);
}
/*
* <offset> is byte offset for beginning of tilde string
*/
{
static int beenhere=0;
{
beenhere = 1;
}
av[2] = 0;
if(np)
else
{
n--;
n--;
if(n)
}
else
}
/*
* This routine is used to resolve ~ expansion.
* A ~ by itself is replaced with the users login directory.
* A ~- is replaced by the previous working directory in shell.
* A ~+ is replaced by the present working directory in shell.
* If ~name is replaced with login directory of name.
* If string doesn't start with ~ or ~... not found then 0 returned.
*/
{
register char *cp;
register int c;
if(*string++!='~')
return(NIL(char*));
if((c = *string)==0)
{
return(cp);
}
{
if(c=='+')
else
return(cp);
}
#if _WINIX
if(fcgetc(c)=='/')
{
char *str;
do
{
stakputc(c);
n++;
}
while (fcgetc(c) && c!='/');
stakputc(0);
if(c)
fcseek(-1);
Skip = n;
{
goto skip;
}
Skip = 0;
}
#endif /* _WINIX */
return(NIL(char*));
#if _WINIX
skip:
#endif /* _WINIX */
if(!logins_tree)
{
}
}
/*
* return values for special macros
*/
{
if(c!='$')
switch(c)
{
case '@':
case '*':
case '#':
#if SHOPT_FILESCAN
{
}
#endif /* SHOPT_FILESCAN */
case '!':
#if SHOPT_COSHELL
#else
#endif /* SHOPT_COSHELL */
break;
case '$':
if(nv_isnull(SH_DOLLARNOD))
return(nv_getval(SH_DOLLARNOD));
case '-':
case '?':
case 0:
else
}
return(NIL(char*));
}
/*
* Handle macro expansion errors
*/
{
if(np)
}
/*
* \ characters are stripped from string. The \ are stripped in the
* replacement string unless followed by a digit or \.
*/
{
register int c;
while(c = *cp++)
{
{
c = *cp++;
}
else if(!rep && c=='/')
{
cp[-1] = 0;
continue;
}
if(rep)
*dp++ = c;
}
if(rep)
*dp = 0;
return(rep);
}