1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00
cde/src/cmd/ksh93/sh/macro.c
Johnothan King c4f980eb29
Introduce usage of __builtin_unreachable() and noreturn (#248)
This commit adds an UNREACHABLE() macro that expands to either the
__builtin_unreachable() compiler builtin (for release builds) or
abort(3) (for development builds). This is used to mark code paths
that are never to be reached.

It also adds the 'noreturn' attribute to functions that never
return: path_exec(), sh_done() and sh_syntax(). The UNREACHABLE()
macro is not added after calling these.

The purpose of these is:
* to slightly improve GCC/Clang compiler optimizations;
* to fix a few compiler warnings;
* to add code clarity.

Changes of note:

src/cmd/ksh93/sh/io.c: outexcept():
- Avoid using __builtin_unreachable() here since errormsg can
  return despite using ERROR_system(1), as shp->jmplist->mode is
  temporarily set to 0. See: https://github.com/att/ast/issues/1336

src/cmd/ksh93/tests/io.sh:
- Add a regression test for the ksh2020 bug referenced above.

src/lib/libast/features/common:
- Detect the existence of either the C11 stdnoreturn.h header or
  the GCC noreturn attribute, preferring the former when available.
- Test for the existence of __builtin_unreachable(). Use it for
  release builds. On development builds, use abort() instead, which
  crahses reliably for debugging when unreachable code is reached.

Co-authored-by: Martijn Dekker <martijn@inlv.org>
2021-04-05 00:28:24 +01:00

2863 lines
61 KiB
C

/***********************************************************************
* *
* 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 *
* http://www.eclipse.org/org/documents/epl-v10.html *
* (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"
#undef STR_GROUP
#ifndef STR_GROUP
# define STR_GROUP 0
#endif
#if _WINIX
static int Skip;
#endif /*_WINIX */
static int _c_;
typedef struct _mac_
{
Shell_t *shp; /* pointer to shell interpreter */
Sfio_t *sp; /* stream pointer for here-document */
struct argnod **arghead; /* address of head of argument list */
char *ifsp; /* pointer to IFS value */
int fields; /* number of fields */
short quoted; /* set when word has quotes */
unsigned char ifs; /* first byte of IFS */
char atmode; /* when processing $@ */
char quote; /* set within double quoted contexts */
char lit; /* set within single quotes */
char split; /* set when word splitting is possible */
char pattern; /* set when file expansion follows */
char patfound; /* set if pattern character found */
char assign; /* set for assignments */
char arith; /* set for ((...)) */
char let; /* set when expanding let arguments */
char zeros; /* strip leading zeros when set */
char arrayok; /* $x[] ok for arrays */
char subcopy; /* set when copying subscript */
int dotdot; /* set for .. in subscript */
void *nvwalk; /* for name space walking */
} Mac_t;
#undef ESCAPE
#define ESCAPE '\\'
#define isescchar(s) ((s)>S_QUOTE)
#define isqescchar(s) ((s)>=S_QUOTE)
#define isbracechar(c) ((c)==RBRACE || (_c_=sh_lexstates[ST_BRACE][c])==S_MOD1 ||_c_==S_MOD2)
#define ltos(x) fmtbase((long)(x),0,0)
/* type of macro expansions */
#define M_BRACE 1 /* ${var} */
#define M_TREE 2 /* ${var.} */
#define M_SIZE 3 /* ${#var} */
#define M_VNAME 4 /* ${!var} */
#define M_SUBNAME 5 /* ${!var[sub]} */
#define M_NAMESCAN 6 /* ${!var*} */
#define M_NAMECOUNT 7 /* ${#var*} */
#define M_TYPE 8 /* ${@var} */
static noreturn void mac_error(Namval_t*);
static int substring(const char*, const char*, int[], int);
static void copyto(Mac_t*, int, int);
static void comsubst(Mac_t*, Shnode_t*, int);
static int varsub(Mac_t*);
static void mac_copy(Mac_t*,const char*, int);
static void tilde_expand2(Shell_t*,int);
static char *sh_tilde(Shell_t*,const char*);
static char *special(Shell_t *,int);
static void endfield(Mac_t*,int);
static char *mac_getstring(char*);
static int charlen(const char*,int);
#if SHOPT_MULTIBYTE
static char *lastchar(const char*,const char*);
#else
# define lastchar(string,endstring) (endstring)
#endif /* SHOPT_MULTIBYTE */
void *sh_macopen(Shell_t *shp)
{
void *addr = sh_newof(0,Mac_t,1,0);
Mac_t *mp = (Mac_t*)addr;
mp->shp = shp;
return(addr);
}
/*
* perform only parameter substitution and catch failures
*/
char *sh_mactry(Shell_t *shp,register char *string)
{
if(string)
{
int jmp_val;
int savexit = shp->savexit;
struct checkpt buff;
sh_pushcontext(shp,&buff,SH_JMPSUB);
jmp_val = sigsetjmp(buff.buff,0);
if(jmp_val == 0)
string = sh_mactrim(shp,string,0);
sh_popcontext(shp,&buff);
shp->savexit = savexit;
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, then expansion rules for assignment are applied.
*/
char *sh_mactrim(Shell_t *shp, char *str, register int mode)
{
register Mac_t *mp = (Mac_t*)shp->mac_context;
Stk_t *stkp = shp->stk;
Mac_t savemac;
savemac = *mp;
stkseek(stkp,0);
mp->arith = (mode==3);
mp->let = 0;
shp->argaddr = 0;
mp->pattern = (mode==1||mode==2);
mp->patfound = 0;
mp->assign = 0;
if(mode<0)
mp->assign = -mode;
mp->quoted = mp->lit = mp->split = mp->quote = 0;
mp->sp = 0;
if(mp->ifsp=nv_getval(sh_scoped(shp,IFSNOD)))
mp->ifs = *mp->ifsp;
else
mp->ifs = ' ';
stkseek(stkp,0);
fcsopen(str);
copyto(mp,0,mp->arith);
str = stkfreeze(stkp,1);
if(mode==2)
{
/* expand only if unique */
struct argnod *arglist=0;
if((mode=path_expand(shp,str,&arglist))==1)
str = arglist->argval;
else if(mode>1)
{
errormsg(SH_DICT,ERROR_exit(1),e_ambiguous,str);
UNREACHABLE();
}
sh_trim(str);
}
*mp = savemac;
return(str);
}
/*
* Perform all the expansions on the argument <argp>
*/
int sh_macexpand(Shell_t* shp, register struct argnod *argp, struct argnod **arghead,int flag)
{
register int flags = argp->argflag;
register char *str = argp->argval;
register Mac_t *mp = (Mac_t*)shp->mac_context;
char **saveargaddr = shp->argaddr;
Mac_t savemac;
Stk_t *stkp = shp->stk;
savemac = *mp;
mp->sp = 0;
if(mp->ifsp=nv_getval(sh_scoped(shp,IFSNOD)))
mp->ifs = *mp->ifsp;
else
mp->ifs = ' ';
if((flag&ARG_OPTIMIZE) && !shp->indebug && !(flags&ARG_MESSAGE))
shp->argaddr = (char**)&argp->argchn.ap;
else
shp->argaddr = 0;
mp->arghead = arghead;
mp->quoted = mp->lit = mp->quote = 0;
mp->arith = ((flag&ARG_ARITH)!=0);
mp->let = ((flag&ARG_LET)!=0);
mp->split = !(flag&ARG_ASSIGN);
mp->assign = !mp->split;
mp->pattern = mp->split && !(flag&ARG_NOGLOB) && !sh_isoption(SH_NOGLOB);
mp->arrayok = mp->arith || (flag&ARG_ARRAYOK);
str = argp->argval;
fcsopen(str);
mp->fields = 0;
mp->atmode = 0;
if(!arghead)
{
mp->split = 0;
mp->pattern = ((flag&ARG_EXP)!=0);
stkseek(stkp,0);
}
else
{
stkseek(stkp,ARGVAL);
*stkptr(stkp,ARGVAL-1) = 0;
}
mp->patfound = 0;
if(mp->pattern)
mp->arrayok = 0;
copyto(mp,0,mp->arith);
if(!arghead)
{
argp->argchn.cp = stkfreeze(stkp,1);
if(shp->argaddr)
argp->argflag |= ARG_MAKE;
}
else
{
endfield(mp,mp->quoted|mp->atmode);
flags = mp->fields;
if(flags==1 && shp->argaddr)
argp->argchn.ap = *arghead;
}
shp->argaddr = saveargaddr;
*mp = savemac;
return(flags);
}
/*
* Expand here document which is stored in <infile> or <string>
* The result is written to <outfile>
*/
void sh_machere(Shell_t *shp,Sfio_t *infile, Sfio_t *outfile, char *string)
{
register int c,n;
register const char *state = sh_lexstates[ST_QUOTE];
register char *cp;
register Mac_t *mp = (Mac_t*)shp->mac_context;
Lex_t *lp = (Lex_t*)mp->shp->lex_context;
Fcin_t save;
Mac_t savemac;
Stk_t *stkp = shp->stk;
savemac = *mp;
stkseek(stkp,0);
shp->argaddr = 0;
mp->sp = outfile;
mp->split = mp->assign = mp->pattern = mp->patfound = mp->lit = mp->arith = mp->let = 0;
mp->quote = 1;
mp->ifsp = nv_getval(sh_scoped(shp,IFSNOD));
mp->ifs = ' ';
fcsave(&save);
if(infile)
fcfopen(infile);
else
fcsopen(string);
fcnotify(0,lp);
cp = fcseek(0);
while(1)
{
if(mbwide())
{
do
{
ssize_t len;
switch(len = mbsize(cp))
{
case -1: /* illegal multi-byte char */
case 0:
case 1:
n=state[*(unsigned char*)cp++];
break;
default:
/* use state of alpha character */
n=state['a'];
cp += len;
}
}
while(n == 0);
}
else
while((n=state[*(unsigned char*)cp++])==0)
;
if(n==S_NL || n==S_QUOTE || n==S_RBRA)
continue;
if(c=(cp-1)-fcseek(0))
sfwrite(outfile,fcseek(0),c);
cp = fcseek(c+1);
switch(n)
{
case S_EOF:
if((n=fcfill()) <=0)
{
/* ignore 0 byte when reading from file */
if(n==0 && fcfile())
continue;
fcrestore(&save);
*mp = savemac;
return;
}
cp = fcseek(-1);
continue;
case S_ESC:
fcgetc(c);
cp=fcseek(-1);
if(c>0)
cp++;
if(!isescchar(state[c]))
sfputc(outfile,ESCAPE);
continue;
case S_GRAVE:
comsubst(mp,(Shnode_t*)0,0);
break;
case S_DOL:
c = fcget();
if(c=='.')
goto regular;
again:
switch(n=sh_lexstates[ST_DOL][c])
{
case S_ALP: case S_SPC1: case S_SPC2:
case S_DIG: case S_LBRA:
{
Fcin_t save2;
int offset = stktell(stkp);
int offset2;
fcnotify(0,lp);
sfputc(stkp,c);
if(n==S_LBRA)
{
c = fcget();
fcseek(-1);
if(sh_lexstates[ST_NORM][c]==S_BREAK)
{
comsubst(mp,(Shnode_t*)0,2);
break;
}
sh_lexskip(lp,RBRACE,1,ST_BRACE);
}
else if(n==S_ALP)
{
while(fcgetc(c),isaname(c))
sfputc(stkp,c);
fcseek(-1);
}
sfputc(stkp,0);
offset2 = stktell(stkp);
fcsave(&save2);
fcsopen(stkptr(stkp,offset));
varsub(mp);
if(c=stktell(stkp)-offset2)
sfwrite(outfile,(char*)stkptr(stkp,offset2),c);
fcrestore(&save2);
stkseek(stkp,offset);
break;
}
case S_PAR:
comsubst(mp,(Shnode_t*)0,3);
break;
case S_EOF:
if((c=fcfill()) > 0)
goto again;
/* FALLTHROUGH */
default:
regular:
sfputc(outfile,'$');
fcseek(-1);
break;
}
}
cp = fcseek(0);
}
}
/*
* expand argument but do not trim pattern characters
*/
char *sh_macpat(Shell_t *shp,register struct argnod *arg, int flags)
{
register char *sp = arg->argval;
if((arg->argflag&ARG_RAW))
return(sp);
sh_stats(STAT_ARGEXPAND);
if(flags&ARG_OPTIMIZE)
arg->argchn.ap=0;
if(!(sp=arg->argchn.cp))
{
sh_macexpand(shp,arg,NIL(struct argnod**),flags|ARG_ARRAYOK);
sp = arg->argchn.cp;
if(!(flags&ARG_OPTIMIZE) || !(arg->argflag&ARG_MAKE))
arg->argchn.cp = 0;
arg->argflag &= ~ARG_MAKE;
}
else
sh_stats(STAT_ARGHITS);
return(sp);
}
/*
* Process the characters up to <endch> or end of input string
*/
static void copyto(register Mac_t *mp,int endch, int newquote)
{
register int c,n;
register const char *state = sh_lexstates[ST_MACRO];
register char *cp,*first;
Lex_t *lp = (Lex_t*)mp->shp->lex_context;
int tilde = -1;
int oldquote = mp->quote;
int ansi_c = 0;
int paren = 0;
int ere = 0;
int brace = 0;
Sfio_t *sp = mp->sp;
Stk_t *stkp = mp->shp->stk;
char *resume = 0;
mp->sp = NIL(Sfio_t*);
mp->quote = newquote;
first = cp = fcseek(0);
if(!mp->quote && *cp=='~' && cp[1]!=LPAREN)
tilde = stktell(stkp);
/* handle // operator specially */
if(mp->pattern==2 && *cp=='/')
cp++;
while(1)
{
if(mbwide())
{
ssize_t len;
do
{
switch(len = mbsize(cp))
{
case -1: /* illegal multi-byte char */
case 0:
len = 1;
/* FALLTHROUGH */
case 1:
n = state[*(unsigned char*)cp++];
break;
default:
/* treat as if alpha */
cp += len;
n=state['a'];
}
}
while(n == 0);
c = (cp-len) - first;
}
else
{
while((n=state[*(unsigned char*)cp++])==0);
c = (cp-1) - first;
}
switch(n)
{
case S_ESC:
if(ansi_c)
{
/* process ANSI-C escape character */
char *addr= --cp;
if(c)
sfwrite(stkp,first,c);
c = chresc(cp,&addr);
cp = addr;
first = fcseek(cp-first);
if(mbwide() && c > UCHAR_MAX)
{
int i;
unsigned char mb[8];
n = mbconv((char*)mb, c);
for(i=0;i<n;i++)
sfputc(stkp,mb[i]);
}
else
sfputc(stkp,c);
if(c==ESCAPE && mp->pattern)
sfputc(stkp,ESCAPE);
break;
}
#if SHOPT_BRACEPAT
else if(sh_isoption(SH_BRACEEXPAND) && mp->pattern==4 && (*cp==',' || *cp==LBRACE || *cp==RBRACE || *cp=='.'))
break;
#endif
else if(mp->split && endch && !mp->quote && !mp->lit)
{
if(c)
mac_copy(mp,first,c);
cp = fcseek(c+2);
if(c= cp[-1])
{
sfputc(stkp,c);
if(c==ESCAPE)
sfputc(stkp,ESCAPE);
}
else
cp--;
first = cp;
break;
}
n = state[*(unsigned char*)cp];
if(n==S_ENDCH && *cp!=endch)
n = S_PAT;
if(mp->pattern)
{
/* preserve \digit for pattern matching */
/* also \alpha for extended patterns */
if(!mp->lit && !mp->quote)
{
int nc = *(unsigned char*)cp;
if((n==S_DIG || ((paren+ere) && (sh_lexstates[ST_DOL][nc]==S_ALP) || nc=='<' || nc=='>')))
break;
if(ere && mp->pattern==1 && strchr(".[()*+?{|^$&!",*cp))
break;
}
/* followed by file expansion */
if(!mp->lit && (n==S_ESC || (!mp->quote &&
(n==S_PAT||n==S_ENDCH||n==S_SLASH||n==S_BRACT||*cp=='-'))))
{
cp += (n!=S_EOF);
if(ere && n==S_ESC && *cp =='\\' && cp[1]=='$')
{
/* convert \\\$ into \$' */
sfwrite(stkp,first,c+1);
cp = first = fcseek(c+3);
}
break;
}
if(!(ere && *cp=='$') && (mp->lit || (mp->quote && !isqescchar(n) && n!=S_ENDCH)))
{
/* add \ for file expansion */
sfwrite(stkp,first,c+1);
first = fcseek(c);
break;
}
}
if(mp->lit)
break;
if(!mp->quote || isqescchar(n) || n==S_ENDCH)
{
/* eliminate \ */
if(c)
sfwrite(stkp,first,c);
/* check new-line joining */
first = fcseek(c+1);
}
cp += (n!=S_EOF);
break;
case S_GRAVE: case S_DOL:
if(mp->lit)
break;
if(c)
{
if(mp->split && !mp->quote && endch)
mac_copy(mp,first,c);
else
sfwrite(stkp,first,c);
}
first = fcseek(c+1);
c = mp->pattern;
if(n==S_GRAVE)
comsubst(mp,(Shnode_t*)0,0);
else if((n= *cp) == '"' && !mp->quote)
{
int off = stktell(stkp);
char *dp;
cp = first = fcseek(1);
mp->quote = 1;
if(!ERROR_translating())
break;
while(n=c, c= *++cp)
{
if(c=='\\' && n==c)
c = 0;
else if(c=='"' && n!='\\')
break;
}
n = cp-first;
sfwrite(stkp,first,n);
sfputc(stkp,0);
cp = stkptr(stkp,off);
dp = (char*)sh_translate(cp);
stkseek(stkp,off);
if(dp==cp)
{
cp = first;
break;
}
resume = fcseek(n);
fcclose();
fcsopen(dp);
cp = first = fcseek(0);
break;
}
else if(n==0 || !varsub(mp))
{
if(n=='\'' && !mp->quote)
ansi_c = 1;
else if(mp->quote || n!='"')
sfputc(stkp,'$');
}
cp = first = fcseek(0);
if(mp->quote && cp)
mp->pattern = c;
break;
case S_ENDCH:
if((mp->lit || cp[-1]!=endch || mp->quote!=newquote))
goto pattern;
if(endch==RBRACE && mp->pattern && brace)
{
brace--;
if(*cp==LPAREN && mp->pattern!=2)
goto pattern;
continue;
}
/* FALLTHROUGH */
case S_EOF:
if(c)
{
if(mp->split && !mp->quote && !mp->lit && endch)
mac_copy(mp,first,c);
else
sfwrite(stkp,first,c);
}
if(n==S_EOF && resume)
{
fcclose();
fcsopen(resume);
resume = 0;
cp = first = fcseek(0);
continue;
}
c += (n!=S_EOF);
first = fcseek(c);
if(tilde>=0)
tilde_expand2(mp->shp,tilde);
goto done;
case S_QUOTE:
if(mp->lit || mp->arith)
break;
/* FALLTHROUGH */
case S_LIT:
if(mp->arith)
{
if((*cp=='`' || *cp=='[') && cp[1]=='\'')
cp +=2;
break;
}
if(n==S_LIT && mp->quote)
break;
if(c)
{
if(mp->split && endch && !mp->quote && !mp->lit)
mac_copy(mp,first,c);
else
sfwrite(stkp,first,c);
}
first = fcseek(c+1);
if(n==S_LIT)
{
if(mp->quote)
continue;
if(mp->lit)
mp->lit = ansi_c = 0;
else
mp->lit = 1;
}
else
mp->quote = !mp->quote;
mp->quoted++;
break;
case S_BRACT:
if(mp->arith || (((mp->assign&1) || endch==RBRACT) &&
!(mp->quote || mp->lit)))
{
int offset=0,oldpat = mp->pattern;
int oldarith = mp->arith, oldsub=mp->subcopy;
sfwrite(stkp,first,++c);
if(mp->assign&1)
{
if(first[c-2]=='.')
offset = stktell(stkp);
if(isastchar(*cp) && cp[1]==']')
{
errormsg(SH_DICT,ERROR_exit(1),e_badsubscript,*cp);
UNREACHABLE();
}
}
first = fcseek(c);
mp->pattern = 4;
mp->arith = 0;
mp->subcopy = 0;
copyto(mp,RBRACT,0);
mp->subcopy = oldsub;
mp->arith = oldarith;
mp->pattern = oldpat;
sfputc(stkp,RBRACT);
if(offset)
{
cp = stkptr(stkp,stktell(stkp));
if(sh_checkid(stkptr(stkp,offset),cp)!=cp)
stkseek(stkp,stktell(stkp)-2);
}
cp = first = fcseek(0);
break;
}
/* FALLTHROUGH */
case S_PAT:
if(mp->pattern && !(mp->quote || mp->lit))
{
mp->patfound = mp->pattern;
if((n=cp[-1])==LPAREN)
{
paren++;
if((cp-first)>1 && cp[-2]=='~')
{
char *p = cp;
while((c=mbchar(p)) && c!=RPAREN)
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(mp->pattern==4 && (mp->quote || mp->lit))
{
if(c)
{
sfwrite(stkp,first,c);
first = fcseek(c);
}
sfputc(stkp,ESCAPE);
}
break;
case S_BRACE:
if(!(mp->quote || mp->lit))
{
#if SHOPT_BRACEPAT
mp->patfound = mp->split && sh_isoption(SH_BRACEEXPAND);
#else
mp->patfound = 0;
#endif
brace++;
}
pattern:
if(!mp->pattern || !(mp->quote || mp->lit))
{
/* mark beginning of {a,b} */
#if SHOPT_BRACEPAT
if(n==S_BRACE && endch==0 && mp->pattern)
mp->pattern=4;
#endif
if(n==S_SLASH && mp->pattern==2)
mp->pattern=3;
break;
}
if(mp->pattern==3)
break;
if(c)
sfwrite(stkp,first,c);
first = fcseek(c);
sfputc(stkp,ESCAPE);
break;
case S_EQ:
if(mp->assign==1)
{
if(*cp=='~' && !endch && !mp->quote && !mp->lit)
tilde = stktell(stkp)+(c+1);
mp->assign = 2;
}
break;
case S_SLASH:
case S_COLON:
if(tilde >=0)
{
if(c)
sfwrite(stkp,first,c);
first = fcseek(c);
tilde_expand2(mp->shp,tilde);
#if _WINIX
if(Skip)
{
first = cp = fcseek(Skip);
Skip = 0;
}
#endif /*_WINIX */
tilde = -1;
c=0;
}
if(n==S_COLON && mp->assign==2 && *cp=='~' && endch==0 && !mp->quote &&!mp->lit)
tilde = stktell(stkp)+(c+1);
else if(n==S_SLASH && mp->pattern==2)
{
if(mp->quote || mp->lit)
goto pattern;
sfwrite(stkp,first,c+1);
first = fcseek(c+1);
c = stktell(stkp);
sh_lexskip(lp,RBRACE,0,ST_NESTED);
stkseek(stkp,c);
cp = fcseek(-1);
sfwrite(stkp,first,cp-first);
first=cp;
}
break;
case S_DOT:
if(*cp=='.' && mp->subcopy==1)
{
sfwrite(stkp,first,c);
sfputc(stkp,0);
mp->dotdot = stktell(stkp);
cp = first = fcseek(c+2);
}
break;
}
}
done:
mp->sp = sp;
mp->quote = oldquote;
}
/*
* 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;
register char *first=cp;
while(1)
{
while((c= *cp++) && c!=ESCAPE);
if(c==0)
break;
if((n= *cp++)=='\\' || n==RBRACE || (n>='0' && n<='9' && (n-='0')<subsize))
{
c = cp-first-2;
if(c)
mac_copy(mp,first,c);
first=cp;
if(n=='\\' || n==RBRACE)
{
first--;
continue;
}
if((c=subexp[2*n])>=0)
{
if((n=subexp[2*n+1]-c)>0)
mac_copy(mp,str+c,n);
}
}
else if(n==0)
break;
}
if(n=cp-first-1)
mac_copy(mp,first,n);
}
#if SHOPT_FILESCAN
#define MAX_OFFSETS (sizeof(shp->offsets)/sizeof(shp->offsets[0]))
#define MAX_ARGN (32*1024)
/*
* compute the arguments $1 ... $n and $# from the current line as needed
* save line offsets in the offsets array.
*/
static char *getdolarg(Shell_t *shp, int n, int *size)
{
register int c=S_DELIM, d=shp->ifstable['\\'];
register unsigned char *first,*last,*cp = (unsigned char*)shp->cur_line;
register int m=shp->offsets[0],delim=0;
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;
cp += shp->offsets[m+1];
n -= m;
shp->ifstable['\\'] = 0;
shp->ifstable[0] = S_EOF;
while(1)
{
if(c==S_DELIM)
while(shp->ifstable[*cp++]==S_SPACE);
first = --cp;
if(++m < MAX_OFFSETS)
shp->offsets[m] = (first-(unsigned char*)shp->cur_line);
while((c=shp->ifstable[*cp++])==0);
last = cp-1;
if(c==S_SPACE)
while((c=shp->ifstable[*cp++])==S_SPACE);
if(--n==0 || c==S_EOF)
{
if(last==first && c==S_EOF && (!delim || (m>1)))
{
n++;
m--;
}
break;
}
delim = (c==S_DELIM);
}
shp->ifstable['\\'] = d;
if(m > shp->offsets[0])
shp->offsets[0] = m;
if(n)
first = last = 0;
if(size)
*size = last-first;
return((char*)first);
}
#endif /* SHOPT_FILESCAN */
/*
* get the prefix after name reference resolution
*/
static char *prefix(Shell_t *shp, char *id)
{
Namval_t *np;
register char *sub=0, *cp = strchr(id,'.');
if(cp)
{
*cp = 0;
np = nv_search(id, shp->var_tree,0);
*cp = '.';
if(isastchar(cp[1]))
cp[1] = 0;
if(np && nv_isref(np))
{
int n;
char *sp;
shp->argaddr = 0;
while(nv_isref(np) && np->nvalue.cp)
{
sub = nv_refsub(np);
np = nv_refnode(np);
if(sub)
nv_putsub(np,sub,0L);
}
id = (char*)sh_malloc(strlen(cp)+1+(n=strlen(sp=nv_name(np)))+ (sub?strlen(sub)+3:1));
memcpy(id,sp,n);
if(sub)
{
id[n++] = '[';
strcpy(&id[n],sub);
n+= strlen(sub)+1;
id[n-1] = ']';
}
strcpy(&id[n],cp);
return(id);
}
}
return(sh_strdup(id));
}
/*
* copy to ']' onto the stack and return offset to it
*/
static int subcopy(Mac_t *mp, int flag)
{
int split = mp->split;
int xpattern = mp->pattern;
int loc = stktell(mp->shp->stk);
int xarith = mp->arith;
int arrayok = mp->arrayok;
mp->split = 0;
mp->arith = 0;
mp->pattern = flag?4:0;
mp->arrayok=1;
mp->subcopy++;
mp->dotdot = 0;
copyto(mp,RBRACT,0);
mp->subcopy = 0;
mp->pattern = xpattern;
mp->split = split;
mp->arith = xarith;
mp->arrayok = arrayok;
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;}
*/
int sh_macfun(Shell_t *shp, const char *name, int offset)
{
Namval_t *np, *nq;
np = nv_bfsearch(name,shp->fun_tree,&nq,(char**)0);
if(np)
{
/* treat ${x.foo} as ${x.foo;} */
union
{
struct comnod com;
Shnode_t node;
} t;
union
{
struct argnod arg;
struct dolnod dol;
char buff[sizeof(struct dolnod)+sizeof(char*)];
} d;
memset(&t,0,sizeof(t));
memset(&d,0,sizeof(d));
t.node.com.comarg = &d.arg;
t.node.com.comline = shp->inlineno;
d.dol.dolnum = 1;
d.dol.dolval[0] = sh_strdup(name);
stkseek(shp->stk,offset);
comsubst((Mac_t*)shp->mac_context,&t.node,2);
free(d.dol.dolval[0]);
return(1);
}
return(0);
}
static int namecount(Mac_t *mp,const char *prefix)
{
int count = 0;
mp->nvwalk = nv_diropen((Namval_t*)0,prefix);
while(nv_dirnext(mp->nvwalk))
count++;
nv_dirclose(mp->nvwalk);
return(count);
}
static char *nextname(Mac_t *mp,const char *prefix, int len)
{
char *cp;
if(len==0)
{
mp->nvwalk = nv_diropen((Namval_t*)0,prefix);
return((char*)mp->nvwalk);
}
if(!(cp=nv_dirnext(mp->nvwalk)))
nv_dirclose(mp->nvwalk);
return(cp);
}
/*
* This routine handles $param, ${param}, and ${param op word}
* The input stream is assumed to be a string
*/
static int varsub(Mac_t *mp)
{
register int c;
register int type=0; /* M_xxx */
register char *v,*argp=0;
register Namval_t *np = NIL(Namval_t*);
register int dolg=0, mode=0;
Lex_t *lp = (Lex_t*)mp->shp->lex_context;
Namarr_t *ap=0;
int dolmax=0, vsize= -1, offset= -1, nulflg, replen=0, bysub=0;
char idbuff[3], *id = idbuff, *pattern=0, *repstr=0, *arrmax=0;
char *idx = 0;
int var=1,addsub=0,oldpat=mp->pattern,idnum=0,flag=0,d;
Stk_t *stkp = mp->shp->stk;
retry1:
mp->zeros = 0;
idbuff[0] = 0;
idbuff[1] = 0;
c = fcmbget(&LEN);
switch(isascii(c)?sh_lexstates[ST_DOL][c]:S_ALP)
{
case S_RBRA:
if(type<M_SIZE)
goto nosub;
/* This code handles ${#} */
c = mode;
mode = type = 0;
/* FALLTHROUGH */
case S_SPC1:
if(type==M_BRACE)
{
if(isaletter(mode=fcpeek(0)) || mode=='.')
{
if(c=='#')
type = M_SIZE;
#if SHOPT_TYPEDEF
else if(c=='@')
{
type = M_TYPE;
goto retry1;
}
#endif /* SHOPT_TYPEDEF */
else
type = M_VNAME;
mode = c;
goto retry1;
}
else if(c=='#' && (isadigit(mode)||fcpeek(1)==RBRACE))
{
type = M_SIZE;
mode = c;
goto retry1;
}
}
/* FALLTHROUGH */
case S_SPC2:
var = 0;
*id = c;
v = special(mp->shp,c);
if(isastchar(c))
{
mode = c;
#if SHOPT_FILESCAN
if(mp->shp->cur_line)
{
v = getdolarg(mp->shp,1,(int*)0);
dolmax = MAX_ARGN;
}
else
#endif /* SHOPT_FILESCAN */
dolmax = mp->shp->st.dolc+1;
mp->atmode = (v && mp->quoted && c=='@');
dolg = (v!=0);
}
break;
case S_LBRA:
if(type)
goto nosub;
type = M_BRACE;
goto retry1;
case S_PAR:
if(type)
goto nosub;
comsubst(mp,(Shnode_t*)0,3);
return(1);
case S_DIG:
var = 0;
c -= '0';
mp->shp->argaddr = 0;
if(type)
{
register int d;
while((d=fcget()),isadigit(d))
c = 10*c + (d-'0');
fcseek(-1);
}
idnum = c;
if(c==0)
v = special(mp->shp,c);
#if SHOPT_FILESCAN
else if(mp->shp->cur_line)
{
mp->shp->used_pos = 1;
v = getdolarg(mp->shp,c,&vsize);
}
#endif /* SHOPT_FILESCAN */
else if(c <= mp->shp->st.dolc)
{
mp->shp->used_pos = 1;
v = mp->shp->st.dolv[c];
}
else
v = 0;
/* Handle 'set -u'/'set -o nounset' for positional parameters */
if(!v && sh_isoption(SH_NOUNSET))
{
d=fcget();
fcseek(-1);
if(!(d && strchr(":+-?=",d)))
{
errormsg(SH_DICT,ERROR_exit(1),e_notset,ltos(c));
UNREACHABLE();
}
}
break;
case S_ALP:
if(c=='.' && type==0)
goto nosub;
offset = stktell(stkp);
do
{
register int d;
np = 0;
do
{
if(LEN==1)
sfputc(stkp,c);
else
sfwrite(stkp,fcseek(0)-LEN,LEN);
}
while((d=c,(c=fcmbget(&LEN)),isaname(c))||type && c=='.');
while(c==LBRACT && (type||mp->arrayok))
{
mp->shp->argaddr=0;
if((c=fcmbget(&LEN),isastchar(c)) && fcpeek(0)==RBRACT && d!='.')
{
if(type==M_VNAME)
type = M_SUBNAME;
idbuff[0] = mode = c;
fcget();
c = fcmbget(&LEN);
if(c=='.' || c==LBRACT)
{
sfputc(stkp,LBRACT);
sfputc(stkp,mode);
sfputc(stkp,RBRACT);
}
else
flag = NV_ARRAY;
break;
}
else
{
fcseek(-LEN);
c = stktell(stkp);
if(d!='.')
sfputc(stkp,LBRACT);
v = stkptr(stkp,subcopy(mp,1));
if(type && mp->dotdot)
{
mode = '@';
v[-1] = 0;
if(type==M_VNAME)
type = M_SUBNAME;
else if(type==M_SIZE)
goto nosub;
}
else if(d!='.')
sfputc(stkp,RBRACT);
c = fcmbget(&LEN);
if(c==0 && type==M_VNAME)
type = M_SUBNAME;
}
}
}
while(type && c=='.');
if(type!=M_VNAME && c==RBRACE && type && fcpeek(-2)=='.')
{
/* ${x.} or ${x..} */
if(fcpeek(-3) == '.')
{
stkseek(stkp,stktell(stkp)-2);
nv_local = 1;
}
else
{
stkseek(stkp,stktell(stkp)-1);
type = M_TREE;
}
}
sfputc(stkp,0);
id=stkptr(stkp,offset);
if(isastchar(c) && type)
{
if(type==M_VNAME || type==M_SIZE)
{
idbuff[0] = mode = c;
if((d=fcpeek(0))==c)
idbuff[1] = fcget();
if(type==M_VNAME)
type = M_NAMESCAN;
else
type = M_NAMECOUNT;
break;
}
goto nosub;
}
flag |= NV_NOASSIGN|NV_VARNAME|NV_NOADD;
if(c=='=' || c=='?' || (c==':' && ((d=fcpeek(0))=='=' || d=='?')))
{
if(c=='=' || (c==':' && d=='='))
flag |= NV_ASSIGN;
flag &= ~NV_NOADD;
}
#if SHOPT_FILESCAN
if(mp->shp->cur_line && *id=='R' && strcmp(id,"REPLY")==0)
{
mp->shp->argaddr=0;
np = REPLYNOD;
}
else
#endif /* SHOPT_FILESCAN */
{
if(mp->shp->argaddr)
flag &= ~NV_NOADD;
/*
* Get a node pointer (np) to the parameter, if any.
*
* The IFS node always exists, so we must special-case IFS by checking if it has
* a value; otherwise it always follows the code path for a set parameter, so is
* not subject to 'set -u', and may test as set even after 'unset -v IFS'.
*/
if(!(*id=='I' && strcmp(id,"IFS")==0 && nv_getval(sh_scoped(mp->shp,IFSNOD)) == NIL(char*)))
np = nv_open(id,mp->shp->var_tree,flag|NV_NOFAIL);
if(!np)
{
sfprintf(mp->shp->strbuf,"%s%c",id,0);
id = sfstruse(mp->shp->strbuf);
}
}
if(isastchar(mode))
var = 0;
if((!np || nv_isnull(np)) && type==M_BRACE && c==RBRACE && !(flag&NV_ARRAY) && strchr(id,'.'))
{
if(sh_macfun(mp->shp,id,offset))
{
fcmbget(&LEN);
return(1);
}
}
if(np && (flag&NV_NOADD) && nv_isnull(np))
{
if(nv_isattr(np,NV_NOFREE))
nv_offattr(np,NV_NOFREE);
#if SHOPT_FILESCAN
else if(np!=REPLYNOD || !mp->shp->cur_line)
#else
else
#endif /* SHOPT_FILESCAN */
np = 0;
}
ap = np?nv_arrayptr(np):0;
if(type)
{
if(mp->dotdot)
{
Namval_t *nq;
#if SHOPT_FIXEDARRAY
if(ap && !ap->fixed && (nq=nv_opensub(np)))
#else
if(ap && (nq=nv_opensub(np)))
#endif /* SHOPT_FIXEDARRAY */
ap = nv_arrayptr(np=nq);
if(ap)
{
nv_putsub(np,v,ARRAY_SCAN);
v = stkptr(stkp,mp->dotdot);
dolmax =1;
if(array_assoc(ap))
arrmax = sh_strdup(v);
else if((dolmax = (int)sh_arith(mp->shp,v))<0)
dolmax += array_maxindex(np);
if(type==M_SUBNAME)
bysub = 1;
}
else
{
if((int)sh_arith(mp->shp,v))
np = 0;
}
}
else if(ap && (isastchar(mode)||type==M_TREE) && !(ap->nelem&ARRAY_SCAN) && type!=M_SIZE)
nv_putsub(np,NIL(char*),ARRAY_SCAN);
if(!isbracechar(c))
goto nosub;
else
fcseek(-LEN);
}
else
fcseek(-1);
if(type<=1 && np && nv_isvtree(np) && mp->pattern==1 && !mp->split)
{
int cc=fcmbget(&LEN),peek=LEN;
if(type && cc=='}')
{
cc = fcmbget(&LEN);
peek++;
}
if(mp->quote && cc=='"')
{
cc = fcmbget(&LEN);
peek++;
}
fcseek(-peek);
if(cc==0)
mp->assign = 1;
}
if((type==M_VNAME||type==M_SUBNAME) && mp->shp->argaddr && strcmp(nv_name(np),id))
mp->shp->argaddr = 0;
c = (type>M_BRACE && isastchar(mode));
/*
* Check if the parameter is set or unset.
*/
#if SHOPT_OPTIMIZE
if(np && type==M_BRACE && mp->shp->argaddr)
nv_optimize(np); /* needed before calling nv_isnull() */
#endif /* SHOPT_OPTIMIZE */
if(np && (type==M_BRACE ? (!nv_isnull(np) || np==SH_LEVELNOD) : (type==M_TREE || !c || !ap)))
{
/* Either the parameter is set, or it's a special type of expansion where 'unset' doesn't apply. */
char *savptr;
c = *((unsigned char*)stkptr(stkp,offset-1));
savptr = stkfreeze(stkp,0);
if(type==M_VNAME || (type==M_SUBNAME && ap))
{
type = M_BRACE;
v = nv_name(np);
if(ap && !mp->dotdot && !(ap->nelem&ARRAY_UNDEF))
addsub = 1;
}
#if SHOPT_TYPEDEF
else if(type==M_TYPE)
{
Namval_t *nq = nv_type(np);
type = M_BRACE;
if(nq)
nv_typename(nq,mp->shp->strbuf);
else
nv_attribute(np,mp->shp->strbuf,"typeset",1);
v = sfstruse(mp->shp->strbuf);
}
#endif /* SHOPT_TYPEDEF */
#if SHOPT_FILESCAN
else if(mp->shp->cur_line && np==REPLYNOD)
v = mp->shp->cur_line;
#endif /* SHOPT_FILESCAN */
else if(type==M_TREE)
v = nv_getvtree(np,(Namfun_t*)0);
else
{
if(type && fcpeek(0)=='+')
{
if(ap)
v = nv_arrayisset(np,ap)?(char*)"x":0;
else
v = nv_isnull(np)?0:(char*)"x";
}
else
v = nv_getval(np);
mp->atmode = (v && mp->quoted && mode=='@');
/* 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)))
mp->zeros = 1;
}
if(savptr==stakptr(0))
stkseek(stkp,offset);
else
stkset(stkp,savptr,offset);
}
else
{
/* The parameter is unset. */
if(sh_isoption(SH_NOUNSET) && !isastchar(mode) && (type==M_VNAME || type==M_SIZE))
{
errormsg(SH_DICT,ERROR_exit(1),e_notset,id);
UNREACHABLE();
}
v = 0;
if(type==M_VNAME)
{
v = id;
type = M_BRACE;
}
else if(type==M_TYPE)
type = M_BRACE;
}
stkseek(stkp,offset);
if(ap)
{
#if SHOPT_OPTIMIZE
if(mp->shp->argaddr)
nv_optimize(np);
#endif
if(isastchar(mode) && array_elem(ap)> !c)
dolg = -1;
else
{
ap->nelem &= ~ARRAY_SCAN;
dolg = 0;
}
}
break;
case S_EOF:
fcseek(-1);
default:
goto nosub;
}
if(type)
c = fcmbget(&LEN);
else
c = fcget();
if(type>M_TREE)
{
if(c!=RBRACE)
mac_error(np);
if(type==M_NAMESCAN || type==M_NAMECOUNT)
{
mp->shp->last_root = mp->shp->var_tree;
id = idx = prefix(mp->shp,id);
stkseek(stkp,offset);
if(type==M_NAMECOUNT)
{
c = namecount(mp,id);
v = ltos(c);
}
else
{
/* M_NAMESCAN: ${!prefix@} or ${!prefix*}. These work like $@, $*. */
dolmax = strlen(id);
dolg = -1;
nextname(mp,id,0);
/* Check if the prefix (id) itself exists. If so, start with that. */
if(nv_open(id,mp->shp->var_tree,NV_NOREF|NV_NOADD|NV_VARNAME|NV_NOFAIL))
v = id;
else
v = nextname(mp,id,dolmax);
}
}
else if(type==M_SUBNAME)
{
if(dolg<0)
{
v = nv_getsub(np);
bysub=1;
}
else if(v)
{
if(!ap || isastchar(mode))
v = "0";
else
v = nv_getsub(np);
}
}
else
{
if(!isastchar(mode))
c = charlen(v,vsize);
else if(dolg>0)
{
#if SHOPT_FILESCAN
if(mp->shp->cur_line)
{
getdolarg(mp->shp,MAX_ARGN,(int*)0);
c = mp->shp->offsets[0];
}
else
#endif /* SHOPT_FILESCAN */
c = mp->shp->st.dolc;
}
else if(dolg<0)
c = array_elem(ap);
else
c = (v!=0);
dolg = dolmax = 0;
v = ltos(c);
}
c = RBRACE;
}
nulflg = 0;
if(type && c==':')
{
c = fcmbget(&LEN);
if(isascii(c) &&sh_lexstates[ST_BRACE][c]==S_MOD1 && c!='*' && c!= ':')
nulflg=1;
else if(c!='%' && c!='#')
{
fcseek(-LEN);
c = ':';
}
}
if(type)
{
if(!isbracechar(c))
{
if(!nulflg)
mac_error(np);
fcseek(-LEN);
c = ':';
}
if(c!=RBRACE)
{
int newops = (c=='#' || c == '%' || c=='/');
offset = stktell(stkp);
if(newops && sh_isoption(SH_NOUNSET) && *id && id!=idbuff && (!np || nv_isnull(np)))
{
errormsg(SH_DICT,ERROR_exit(1),e_notset,id);
UNREACHABLE();
}
if(c=='/' ||c==':' || ((!v || (nulflg && *v==0)) ^ (c=='+'||c=='#'||c=='%')))
{
int newquote = mp->quote;
int split = mp->split;
int quoted = mp->quoted;
int arith = mp->arith;
int zeros = mp->zeros;
int assign = mp->assign;
if(newops)
{
type = fcget();
if(type=='%' || type=='#')
{
int d = fcmbget(&LEN);
fcseek(-LEN);
if(d=='(')
type = 0;
}
fcseek(-1);
mp->pattern = 1+(c=='/');
mp->split = 0;
mp->quoted = 0;
mp->assign &= ~1;
mp->arith = mp->zeros = 0;
newquote = 0;
}
else if(c=='?' || c=='=')
mp->split = mp->pattern = 0;
copyto(mp,RBRACE,newquote);
if(!oldpat)
mp->patfound = 0;
mp->pattern = oldpat;
mp->split = split;
mp->quoted = quoted;
mp->arith = arith;
mp->zeros = zeros;
mp->assign = assign;
/* add null byte */
sfputc(stkp,0);
if(c!='=')
stkseek(stkp,stktell(stkp)-1);
}
else
{
sh_lexskip(lp, RBRACE, 0, sh_lexstates[ST_BRACE][c]==S_MOD1 ? ST_QUOTE : ST_NESTED);
stkseek(stkp,offset);
}
argp=stkptr(stkp,offset);
}
}
else
{
fcseek(-1);
c=0;
}
if(c==':') /* ${name:expr1[:expr2]} */
{
char *ptr;
type = (int)sh_strnum(argp,&ptr,1);
if(isastchar(mode))
{
if(id==idbuff) /* ${@} or ${*} */
{
if(type<0 && (type+= dolmax)<0)
type = 0;
if(type==0)
v = special(mp->shp,dolg=0);
#if SHOPT_FILESCAN
else if(mp->shp->cur_line)
{
v = getdolarg(mp->shp,dolg=type,&vsize);
if(!v)
dolmax = type;
}
#endif /* SHOPT_FILESCAN */
else if(type < dolmax)
v = mp->shp->st.dolv[dolg=type];
else
v = 0;
}
else if(ap)
{
if(type<0)
{
if(array_assoc(ap))
type = -type;
else
type += array_maxindex(np);
}
if(array_assoc(ap))
{
while(type-- >0 && (v=0,nv_nextsub(np)))
v = nv_getval(np);
}
else if(type > 0)
{
if(nv_putsub(np,NIL(char*),type|ARRAY_SCAN))
v = nv_getval(np);
else
v = 0;
}
}
else if(type>0)
v = 0;
if(!v)
mp->atmode = 0;
}
else if(v)
{
vsize = charlen(v,vsize);
if(type<0 && (type += vsize)<0)
type = 0;
if(vsize < type)
v = 0;
else if(mbwide())
{
mbinit();
for(c=type;c;c--)
mbchar(v);
c = ':';
}
else
v += type;
vsize = v?strlen(v):0;
}
if(*ptr==':')
{
if((type = (int)sh_strnum(ptr+1,&ptr,1)) <=0)
{
v = 0;
mp->atmode = 0;
}
else if(isastchar(mode))
{
if(dolg>=0)
{
if(dolg+type < dolmax)
dolmax = dolg+type;
}
else
dolmax = type;
}
else if(type < vsize)
{
if(mbwide())
{
char *vp = v;
mbinit();
while(type-->0)
{
if((c=mbsize(vp))<1)
c = 1;
vp += c;
}
type = vp-v;
c = ':';
}
vsize = type;
}
else
vsize = v?strlen(v):0;
}
if(*ptr)
mac_error(np);
stkseek(stkp,offset);
argp = 0;
}
/* check for substring operations */
else if(c == '#' || c == '%' || c=='/')
{
if(c=='/')
{
if(type=='/' || type=='#' || type=='%')
{
c = type;
type = '/';
argp++;
}
else
type = 0;
}
else
{
if(type==c) /* ## or %% */
argp++;
else
type = 0;
}
pattern = sh_strdup(argp);
if((type=='/' || c=='/') && (repstr = mac_getstring(pattern)))
{
Mac_t savemac;
char *first = fcseek(0);
int n = stktell(stkp);
savemac = *mp;
fcsopen(repstr);
mp->pattern = 3;
mp->split = 0;
copyto(mp,0,0);
sfputc(stkp,0);
repstr = sh_strdup(stkptr(stkp,n));
replen = strlen(repstr);
stkseek(stkp,n);
*mp = savemac;
fcsopen(first);
}
if(v || c=='/' && offset>=0)
stkseek(stkp,offset);
}
/* check for quoted @ */
if(mode=='@' && mp->quote && !v && c!='-')
mp->quoted-=2;
retry2:
if(v && (!nulflg || *v ) && c!='+')
{
int ofs_size = 0;
regoff_t match[2*(MATCH_MAX+1)];
int nmatch, nmatch_prev, vsize_last;
char *vlast = NIL(char*);
while(1)
{
if(!v)
v= "";
if(c=='/' || c=='#' || c== '%')
{
int index = 0;
flag = (type || c=='/')?(STR_GROUP|STR_MAXIMAL):STR_GROUP;
if(c!='/')
flag |= STR_LEFT;
nmatch = 0;
while(1)
{
vsize = strlen(v);
nmatch_prev = nmatch;
if(c=='%')
nmatch=substring(v,pattern,match,flag&STR_MAXIMAL);
else
nmatch=strgrpmatch(v,pattern,match,elementsof(match)/2,flag);
if(nmatch && replen>0)
sh_setmatch(mp->shp,v,vsize,nmatch,match,index++);
if(nmatch)
{
vlast = v;
vsize_last = vsize;
vsize = match[0];
}
else if(c=='#')
vsize = 0;
if(vsize)
mac_copy(mp,v,vsize);
if(nmatch && replen>0 && (match[1] || !nmatch_prev))
mac_substitute(mp,repstr,v,match,nmatch);
if(nmatch==0)
v += vsize;
else
v += match[1];
if(*v && c=='/' && type)
{
/* avoid infinite loop */
if(nmatch && match[1]==0)
{
nmatch = 0;
mac_copy(mp,v,1);
v++;
}
continue;
}
vsize = -1;
break;
}
if(replen==0)
sh_setmatch(mp->shp,vlast,vsize_last,nmatch,match,index++);
}
if(vsize)
mac_copy(mp,v,vsize>0?vsize:strlen(v));
if(addsub)
{
mp->shp->instance++;
sfprintf(mp->shp->strbuf,"[%s]",nv_getsub(np));
mp->shp->instance--;
v = sfstruse(mp->shp->strbuf);
mac_copy(mp, v, strlen(v));
}
if(dolg==0 && dolmax==0)
break;
if(mp->dotdot)
{
if(nv_nextsub(np) == 0)
break;
if(bysub)
v = nv_getsub(np);
else
v = nv_getval(np);
if(array_assoc(ap))
{
if(strcmp(bysub?v:nv_getsub(np),arrmax)>0)
break;
}
else
{
if(nv_aindex(np) > dolmax)
break;
}
}
else if(dolg>=0)
{
if(++dolg >= dolmax)
break;
#if SHOPT_FILESCAN
if(mp->shp->cur_line)
{
if(dolmax==MAX_ARGN && isastchar(mode))
break;
if(!(v=getdolarg(mp->shp,dolg,&vsize)))
{
dolmax = dolg;
break;
}
}
else
#endif /* SHOPT_FILESCAN */
v = mp->shp->st.dolv[dolg];
}
else if(!np)
{
if(!(v = nextname(mp,id,dolmax)))
break;
}
else
{
if(dolmax && --dolmax <=0)
{
nv_putsub(np,NIL(char*),ARRAY_UNDEF);
break;
}
if(ap)
ap->nelem |= ARRAY_SCAN;
if(nv_nextsub(np) == 0)
break;
if(bysub)
v = nv_getsub(np);
else
v = nv_getval(np);
}
if(mp->split && (!mp->quote || mode=='@'))
{
if(!np)
mp->pattern = 0;
endfield(mp,mp->quoted);
mp->atmode = mode=='@';
mp->pattern = oldpat;
}
else
{
Sfio_t *sfio_ptr = (mp->sp) ? mp->sp : stkp;
/*
* We're joining fields into one; write the output field separator, which may be multi-byte.
* For "$@" it's a space, for "$*" it's the 1st char of IFS (space if unset, none if empty).
*/
if(mode == '@' || !mp->ifsp) /* if expanding $@ or if IFS is unset... */
sfputc(sfio_ptr, ' ');
else if(mp->ifs) /* else if IFS is non-empty... */
{
if(!mbwide() || mp->ifs < 128) /* if single-byte char... */
sfputc(sfio_ptr, mp->ifs);
else
{
if(!ofs_size) /* only calculate this once per expansion */
{
ofs_size = mbsize(mp->ifsp);
if(ofs_size<0) /* invalid mb char: fall back to using first byte */
ofs_size = 1;
}
sfwrite(sfio_ptr, mp->ifsp, ofs_size);
}
}
}
}
if(arrmax)
free((void*)arrmax);
}
else if(argp)
{
if(c=='/' && replen>0 && pattern && strmatch("",pattern))
mac_substitute(mp,repstr,v,0,0);
if(c=='?')
{
if(np)
id = nv_name(np);
else if(idnum)
id = ltos(idnum);
if(*argp)
{
sfputc(stkp,0);
errormsg(SH_DICT,ERROR_exit(1),"%s: %s",id,argp);
}
else if(v)
errormsg(SH_DICT,ERROR_exit(1),e_nullset,id);
else
errormsg(SH_DICT,ERROR_exit(1),e_notset,id);
UNREACHABLE();
}
else if(c=='=')
{
if(np)
{
if(mp->shp->subshell)
np = sh_assignok(np,1);
nv_putval(np,argp,0);
v = nv_getval(np);
nulflg = 0;
stkseek(stkp,offset);
goto retry2;
}
else
mac_error(np);
}
}
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))
{
sfprintf(mp->shp->strbuf,"%s[%s]\0",nv_name(np),nv_getsub(np));
id = sfstruse(mp->shp->strbuf);
}
else
id = nv_name(np);
nv_close(np);
}
errormsg(SH_DICT,ERROR_exit(1),e_notset,id);
UNREACHABLE();
}
if(np)
nv_close(np);
if(pattern)
free(pattern);
if(repstr)
free(repstr);
if(idx)
free(idx);
return(1);
nosub:
if(type==M_BRACE && sh_lexstates[ST_NORM][c]==S_BREAK)
{
fcseek(-1);
comsubst(mp,(Shnode_t*)0,2);
return(1);
}
if(type)
mac_error(np);
fcseek(-1);
nv_close(np);
return(0);
}
/*
* This routine handles command substitution
* <type> is 0 for older `...` version
*/
static void comsubst(Mac_t *mp,register Shnode_t* t, int type)
{
Sfdouble_t num;
register int c;
register char *str;
Sfio_t *sp;
Stk_t *stkp = mp->shp->stk;
Fcin_t save;
struct slnod *saveslp = mp->shp->st.staklist;
struct _mac_ savemac;
int savtop = stktell(stkp);
char lastc=0, *savptr = stkfreeze(stkp,0);
int was_history = sh_isstate(SH_HISTORY);
int was_verbose = sh_isstate(SH_VERBOSE);
int was_interactive = sh_isstate(SH_INTERACTIVE);
int newlines,bufsize,nextnewlines;
Sfoff_t foff;
Namval_t *np;
mp->shp->argaddr = 0;
savemac = *mp;
mp->shp->st.staklist=0;
if(type)
{
sp = 0;
fcseek(-1);
if(!t)
t = sh_dolparen((Lex_t*)mp->shp->lex_context);
if(t && t->tre.tretyp==TARITH)
{
mp->shp->inarith = 1;
fcsave(&save);
if(t->ar.arcomp)
num = arith_exec(t->ar.arcomp);
else if((t->ar.arexpr->argflag&ARG_RAW))
num = sh_arith(mp->shp,t->ar.arexpr->argval);
else
num = sh_arith(mp->shp,sh_mactrim(mp->shp,t->ar.arexpr->argval,3));
mp->shp->inarith = 0;
out_offset:
stkset(stkp,savptr,savtop);
*mp = savemac;
if((Sflong_t)num!=num)
sfprintf(mp->shp->strbuf,"%.*Lg",LDBL_DIG,num);
else if(num)
sfprintf(mp->shp->strbuf,"%lld",(Sflong_t)num);
else
sfprintf(mp->shp->strbuf,"%Lg",num);
str = sfstruse(mp->shp->strbuf);
mac_copy(mp,str,strlen(str));
mp->shp->st.staklist = saveslp;
fcrestore(&save);
return;
}
}
else
{
while(fcgetc(c)!='`' && c)
{
if(c==ESCAPE)
{
fcgetc(c);
if(!(isescchar(sh_lexstates[ST_QUOTE][c]) ||
(c=='"' && mp->quote)))
sfputc(stkp,ESCAPE);
}
sfputc(stkp,c);
}
sfputc(stkp,'\n'); /* a=`some_alias` leaked memory, a=`some_alias<LF>` did not! TODO: non-hack fix */
c = stktell(stkp);
str=stkfreeze(stkp,1);
/* disable verbose and don't save in history file */
sh_offstate(SH_HISTORY);
sh_offstate(SH_VERBOSE);
if(mp->sp)
sfsync(mp->sp); /* flush before executing command */
sp = sfnew(NIL(Sfio_t*),str,c,-1,SF_STRING|SF_READ);
c = mp->shp->inlineno;
mp->shp->inlineno = error_info.line+mp->shp->st.firstline;
t = (Shnode_t*)sh_parse(mp->shp, sp,SH_EOF|SH_NL);
mp->shp->inlineno = c;
type = 1;
}
#if KSHELL
if(t)
{
fcsave(&save);
sfclose(sp);
if(t->tre.tretyp==0 && !t->com.comarg && !t->com.comset)
{
/* special case $(<file) and $(<#file) */
register int fd;
int r;
struct checkpt buff;
struct ionod *ip=0;
sh_pushcontext(mp->shp,&buff,SH_JMPIO);
if((ip=t->tre.treio) &&
((ip->iofile&IOLSEEK) || !(ip->iofile&IOUFD)) &&
(r=sigsetjmp(buff.buff,0))==0)
fd = sh_redirect(mp->shp,ip,3);
else
fd = sh_chkopen(e_devnull);
sh_popcontext(mp->shp,&buff);
if(r==0 && ip && (ip->iofile&IOLSEEK))
{
if(sp=mp->shp->sftable[fd])
num = sftell(sp);
else
num = lseek(fd, (off_t)0, SEEK_CUR);
goto out_offset;
}
if(!(sp=mp->shp->sftable[fd]))
{
char *cp = (char*)sh_malloc(IOBSIZE+1);
sp = sfnew(NIL(Sfio_t*),cp,IOBSIZE,fd,SF_READ|SF_MALLOC);
}
type = 3;
}
else
sp = sh_subshell(mp->shp,t,sh_isstate(SH_ERREXIT),type);
fcrestore(&save);
}
else
sp = sfopen(NIL(Sfio_t*),"","sr");
sh_freeup(mp->shp);
mp->shp->st.staklist = saveslp;
if(was_history)
sh_onstate(SH_HISTORY);
if(was_verbose)
sh_onstate(SH_VERBOSE);
#else
sp = sfpopen(NIL(Sfio_t*),str,"r");
#endif
*mp = savemac;
np = sh_scoped(mp->shp,IFSNOD);
nv_putval(np,mp->ifsp,NV_RDONLY);
mp->ifsp = nv_getval(np);
stkset(stkp,savptr,savtop);
newlines = 0;
sfsetbuf(sp,(void*)sp,0);
bufsize = sfvalue(sp);
/* read command substitution output and put on stack or here-doc */
sfpool(sp, NIL(Sfio_t*), SF_WRITE);
sh_offstate(SH_INTERACTIVE);
if((foff = sfseek(sp,(Sfoff_t)0,SEEK_END)) > 0)
{
size_t soff = stktell(stkp);
sfseek(sp,(Sfoff_t)0,SEEK_SET);
stkseek(stkp,soff+foff+64);
stkseek(stkp,soff);
}
while((str=(char*)sfreserve(sp,SF_UNBOUND,0)) && (c=bufsize=sfvalue(sp))>0)
{
#if SHOPT_CRNL
/* eliminate <cr> */
register char *dp;
char *buff = str;
while(c>1 && (*str !='\r'|| str[1]!='\n'))
{
c--;
str++;
}
dp = str;
while(c>1)
{
str++;
c--;
while(c>1 && (*str!='\r' || str[1]!='\n'))
{
c--;
*dp++ = *str++;
}
}
if(c)
*dp++ = *str++;
str = buff;
c = dp-str;
#endif /* SHOPT_CRNL */
/* delay appending trailing new-lines */
for(nextnewlines=0; c-->0 && str[c]=='\n'; nextnewlines++);
if(c < 0)
{
newlines += nextnewlines;
continue;
}
if(newlines >0)
{
if(mp->sp)
sfnputc(mp->sp,'\n',newlines);
else if(!mp->quote && mp->split && mp->shp->ifstable['\n'])
endfield(mp,0);
else
sfnputc(stkp,'\n',newlines);
}
else if(lastc)
{
mac_copy(mp,&lastc,1);
lastc = 0;
}
newlines = nextnewlines;
if(++c < bufsize)
str[c] = 0;
else
{
ssize_t len = 1;
/* can't write past buffer so save last character */
c -= len;
lastc = str[c];
str[c] = 0;
}
mac_copy(mp,str,c);
}
if(was_interactive)
sh_onstate(SH_INTERACTIVE);
if(--newlines>0 && mp->shp->ifstable['\n']==S_DELIM)
{
if(mp->sp)
sfnputc(mp->sp,'\n',newlines);
else if(!mp->quote && mp->split)
while(newlines--)
endfield(mp,1);
else
sfnputc(stkp,'\n',newlines);
}
if(lastc)
{
mac_copy(mp,&lastc,1);
lastc = 0;
}
sfclose(sp);
return;
}
/*
* copy <str> onto the stack
*/
static void mac_copy(register Mac_t *mp,register const char *str, register int size)
{
register char *state;
register const char *cp=str;
register int c,n,nopat,len;
Stk_t *stkp=mp->shp->stk;
int oldpat = mp->pattern;
nopat = (mp->quote||(mp->assign==1)||mp->arith);
if(mp->zeros)
{
/* prevent leading 0's from becoming octal constants */
while(size>1 && *str=='0')
{
if(str[1]=='x' || str[1]=='X')
break;
str++,size--;
}
mp->zeros = 0;
cp = str;
}
if(mp->sp)
sfwrite(mp->sp,str,size);
else if(mp->pattern>=2 || (mp->pattern && nopat) || mp->assign==3)
{
state = sh_lexstates[ST_MACRO];
/* insert \ before file expansion characters */
while(size-->0)
{
if(mbwide() && (len=mbsize(cp))>1)
{
cp += len;
size -= (len-1);
continue;
}
c = state[n= *(unsigned char*)cp++];
if(mp->assign==3 && mp->pattern!=4)
{
if(c==S_BRACT)
{
nopat = 0;
mp->pattern = 4;
}
continue;
}
if(nopat&&(c==S_PAT||c==S_ESC||c==S_BRACT||c==S_ENDCH) && mp->pattern!=3)
c=1;
else if(mp->pattern==4 && (c==S_ESC||c==S_BRACT||c==S_ENDCH || isastchar(n)))
{
if(c==S_ENDCH && oldpat!=4)
{
if(*cp==0 || *cp=='.' || *cp=='[')
{
mp->pattern = oldpat;
c=0;
}
else
c=1;
}
else
c=1;
}
else if(mp->pattern==2 && c==S_SLASH)
c=1;
else if(mp->pattern==3 && c==S_ESC && (state[*(unsigned char*)cp]==S_DIG||(*cp==ESCAPE)))
{
if(!(c=mp->quote))
cp++;
}
else
c=0;
if(c)
{
if(c = (cp-1) - str)
sfwrite(stkp,str,c);
sfputc(stkp,ESCAPE);
str = cp-1;
}
}
if(c = cp-str)
sfwrite(stkp,str,c);
}
else if(!mp->quote && mp->split && (mp->ifs||mp->pattern))
{
/* split words at ifs characters */
state = mp->shp->ifstable;
if(mp->pattern)
{
char *sp = "&|()";
while(c = *sp++)
{
if(state[c]==0)
state[c] = S_EPAT;
}
sp = "*?[{";
while(c = *sp++)
{
if(state[c]==0)
state[c] = S_PAT;
}
if(state[ESCAPE]==0)
state[ESCAPE] = S_ESC;
}
while(size-->0)
{
n=state[c= *(unsigned char*)cp++];
if(mbwide() && n!=S_MBYTE && (len=mbsize(cp-1))>1)
{
sfwrite(stkp,cp-1, len);
cp += --len;
size -= len;
continue;
}
if(n==S_ESC || n==S_EPAT)
{
/* don't allow extended patterns in this case */
mp->patfound = mp->pattern;
sfputc(stkp,ESCAPE);
}
else if(n==S_PAT)
mp->patfound = mp->pattern;
else if(n && mp->ifs)
{
if(mbwide() && n==S_MBYTE)
{
if(sh_strchr(mp->ifsp,cp-1)<0)
{
/*
* The multi-byte character that was found has the same initial
* byte as the IFS delimiter, but it's a different character. Put
* the first byte onto the stack and continue; multi-byte characters
* otherwise lose their initial byte.
*/
sfputc(stkp,c);
continue;
}
n = mbsize(cp-1) - 1;
if(n==-2)
n = 0;
cp += n;
size -= n;
n= S_DELIM;
}
if(n==S_SPACE || n==S_NL)
{
while(size>0 && ((n=state[c= *(unsigned char*)cp++])==S_SPACE||n==S_NL))
size--;
if(mbwide() && n==S_MBYTE && sh_strchr(mp->ifsp,cp-1)>=0)
{
n = mbsize(cp-1) - 1;
if(n==-2)
n = 0;
cp += n;
size -= n;
n=S_DELIM;
}
else if(n==S_DELIM)
size--;
}
endfield(mp,n==S_DELIM||mp->quoted);
mp->patfound = 0;
if(n==S_DELIM)
while(size>0 && ((n=state[c= *(unsigned char*)cp++])==S_SPACE||n==S_NL))
size--;
if(size<=0)
break;
cp--;
continue;
}
sfputc(stkp,c);
}
if(mp->pattern)
{
cp = "&|()";
while(c = *cp++)
{
if(state[c]==S_EPAT)
state[c] = 0;
}
cp = "*?[{";
while(c = *cp++)
{
if(state[c]==S_PAT)
state[c] = 0;
}
if(mp->shp->ifstable[ESCAPE]==S_ESC)
mp->shp->ifstable[ESCAPE] = 0;
}
}
else
sfwrite(stkp,str,size);
}
/*
* Terminate field.
* If field is null count field if <split> is non-zero
* Do filename expansion if required
*/
static void endfield(register Mac_t *mp,int split)
{
register struct argnod *argp;
register int count=0;
Stk_t *stkp = mp->shp->stk;
if(stktell(stkp) > ARGVAL || split)
{
argp = (struct argnod*)stkfreeze(stkp,1);
argp->argnxt.cp = 0;
argp->argflag = 0;
mp->atmode = 0;
if(mp->patfound)
{
mp->shp->argaddr = 0;
#if SHOPT_BRACEPAT
if(sh_isoption(SH_BRACEEXPAND))
count = path_generate(mp->shp,argp,mp->arghead);
else
#endif /* SHOPT_BRACEPAT */
count = path_expand(mp->shp,argp->argval,mp->arghead);
if(count)
mp->fields += count;
else if(split) /* pattern is null string */
*argp->argval = 0;
else /* pattern expands to nothing */
count = -1;
}
if(count==0)
{
argp->argchn.ap = *mp->arghead;
*mp->arghead = argp;
mp->fields++;
}
if(count>=0)
{
(*mp->arghead)->argflag |= ARG_MAKE;
if(mp->assign || sh_isoption(SH_NOGLOB))
argp->argflag |= ARG_RAW|ARG_EXP;
}
stkseek(stkp,ARGVAL);
}
mp->quoted = mp->quote;
}
/*
* Finds the right substring of STRING using the expression PAT
* the longest substring is found when FLAG is set.
*/
static int substring(register const char *string,const char *pat,int match[], int flag)
{
register const char *sp=string;
register int size,len,nmatch,n;
int smatch[2*(MATCH_MAX+1)];
if(flag)
{
if(n=strgrpmatch(sp,pat,smatch,elementsof(smatch)/2,STR_RIGHT|STR_MAXIMAL))
{
memcpy(match,smatch,n*2*sizeof(smatch[0]));
return(n);
}
return(0);
}
size = len = strlen(sp);
sp += size;
while(sp>=string)
{
if(mbwide())
sp = lastchar(string,sp);
if(n=strgrpmatch(sp,pat,smatch,elementsof(smatch)/2,STR_RIGHT|STR_LEFT|STR_MAXIMAL))
{
nmatch = n;
memcpy(match,smatch,n*2*sizeof(smatch[0]));
size = sp-string;
break;
}
sp--;
}
if(size==len)
return(0);
if(nmatch)
{
nmatch *=2;
while(--nmatch>=0)
match[nmatch] += size;
}
return(n);
}
#if SHOPT_MULTIBYTE
static char *lastchar(const char *string, const char *endstring)
{
register char *str = (char*)string;
register int c;
mbinit();
while(*str)
{
if((c=mbsize(str))<0)
c = 1;
if(str+c > endstring)
break;
str += c;
}
return(str);
}
#endif /* SHOPT_MULTIBYTE */
static int charlen(const char *string,int len)
{
if(!string)
return(0);
if(mbwide())
{
register const char *str = string, *strmax=string+len;
register int n=0;
mbinit();
if(len>0)
{
while(str<strmax && mbchar(str))
n++;
}
else while(mbchar(str))
n++;
return(n);
}
else
{
if(len<0)
return(strlen(string));
return(len);
}
}
/*
* <offset> is byte offset for beginning of tilde string
*/
static void tilde_expand2(Shell_t *shp, register int offset)
{
char *cp = NIL(char*); /* character pointer for tilde expansion result */
char *stakp = stakptr(0); /* current stack object (&stakp[offset] is tilde string) */
int curoff = staktell(); /* current offset of current stack object */
static char block; /* for disallowing tilde expansion in .get/.set to change ${.sh.tilde} */
/*
* Allow overriding tilde expansion with a .sh.tilde.set or .get discipline function.
*/
if(!block && SH_TILDENOD->nvfun && SH_TILDENOD->nvfun->disc)
{
stakfreeze(1); /* terminate current stack object to avoid data corruption */
block++;
nv_putval(SH_TILDENOD, &stakp[offset], 0);
cp = nv_getval(SH_TILDENOD);
block--;
if(cp[0]=='\0' || cp[0]=='~')
cp = NIL(char*); /* do not use empty or unexpanded result */
stakset(stakp,curoff); /* restore stack to state on function entry */
}
/*
* Perform default tilde expansion unless overridden.
* Write the result to the stack, if any.
*/
stakputc(0);
if(!cp)
cp = sh_tilde(shp,&stakp[offset]);
if(cp)
{
stakseek(offset);
if(!(cp[0]=='/' && !cp[1] && fcpeek(0)=='/'))
stakputs(cp); /* for ~ == /, avoid ~/foo -> //foo */
}
else
stakseek(curoff);
}
/*
* 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.
*/
static char *sh_tilde(Shell_t *shp,register const char *string)
{
register char *cp;
register int c;
register struct passwd *pw = NIL(struct passwd*);
register Namval_t *np=0;
unsigned int save;
static Dt_t *logins_tree;
if(*string++!='~')
return(NIL(char*));
if((c = *string)==0)
{
static char *username;
if(cp = nv_getval(sh_scoped(shp, HOME)))
return(cp);
/* Fallback for unset HOME: get username and treat ~ like ~username */
if(!username && !((pw = getpwuid(getuid())) && (username = sh_strdup(pw->pw_name))))
return(NIL(char*));
string = username;
}
if((c=='-' || c=='+') && string[1]==0)
{
if(c=='+')
cp = nv_getval(sh_scoped(shp,PWDNOD));
else
cp = nv_getval(sh_scoped(shp,OLDPWDNOD));
return(cp);
}
#if _WINIX
if(fcgetc(c)=='/')
{
char *str;
int n=0,offset=staktell();
stakputs(string);
do
{
stakputc(c);
n++;
}
while (fcgetc(c) && c!='/');
stakputc(0);
if(c)
fcseek(-1);
str = stakseek(offset);
Skip = n;
if(logins_tree && (np=nv_search(str,logins_tree,0)))
return(nv_getval(np));
if(pw = getpwnam(str))
{
string = str;
goto skip;
}
Skip = 0;
}
#endif /* _WINIX */
if(logins_tree && (np=nv_search(string,logins_tree,0)))
return(nv_getval(np));
if(!pw && !(pw = getpwnam(string)))
return(NIL(char*));
#if _WINIX
skip:
#endif /* _WINIX */
if(!logins_tree)
{
logins_tree = dtopen(&_Nvdisc,Dtbag);
dtuserdata(logins_tree,shp,1);
}
if(np=nv_search(string,logins_tree,NV_ADD))
{
save = shp->subshell;
shp->subshell = 0;
nv_putval(np, pw->pw_dir,0);
shp->subshell = save;
}
return(pw->pw_dir);
}
/*
* return values for special macros
*/
static char *special(Shell_t *shp,register int c)
{
if(c!='$')
shp->argaddr = 0;
switch(c)
{
case '@':
case '*':
return(shp->st.dolc>0?shp->st.dolv[1]:NIL(char*));
case '#':
#if SHOPT_FILESCAN
if(shp->cur_line)
{
getdolarg(shp,MAX_ARGN,(int*)0);
return(ltos(shp->offsets[0]));
}
#endif /* SHOPT_FILESCAN */
return(ltos(shp->st.dolc));
case '!':
if(shp->bckpid)
return(ltos(shp->bckpid));
break;
case '$':
if(nv_isnull(SH_DOLLARNOD))
return(ltos(shp->gd->pid));
return(nv_getval(SH_DOLLARNOD));
case '-':
return(sh_argdolminus(shp->arg_context));
case '?':
return(ltos(shp->savexit));
case 0:
if(sh_isstate(SH_PROFILE) || shp->fn_depth==0 || !shp->st.cmdname)
return(shp->shname);
else
return(shp->st.cmdname);
}
/* Handle 'set -u'/'set -o nounset' for special parameters */
if(sh_isoption(SH_NOUNSET))
{
int d=fcget();
fcseek(-1);
if(!(d && strchr(":+-?=",d)))
{
char c_str[2];
c_str[0]=(char)c;
c_str[1]='\0';
errormsg(SH_DICT,ERROR_exit(1),e_notset,c_str);
UNREACHABLE();
}
}
return(NIL(char*));
}
/*
* Handle macro expansion errors
*/
static noreturn void mac_error(Namval_t *np)
{
if(np)
nv_close(np);
errormsg(SH_DICT,ERROR_exit(1),e_subst,fcfirst());
UNREACHABLE();
}
/*
* Given pattern/string, replace / with 0 and return pointer to string
* \ characters are stripped from string. The \ are stripped in the
* replacement string unless followed by a digit or \.
*/
static char *mac_getstring(char *pattern)
{
register char *cp=pattern, *rep=0, *dp;
register int c;
while(c = *cp++)
{
if(c==ESCAPE && (!rep || (*cp && strchr("&|()[]*?",*cp))))
{
c = *cp++;
}
else if(!rep && c=='/')
{
cp[-1] = 0;
rep = dp = cp;
continue;
}
if(rep)
*dp++ = c;
}
if(rep)
*dp = 0;
return(rep);
}