ash: cleanup part 2
No binary (build failed)
—
(log yok)
| @@ -65,6 +65,13 @@ | |
| #error "Do not even bother, ash will not run on uClinux" | |
| #endif | |
| + | #if DEBUG |
| + | #define TRACE(param) trace param |
| + | #define TRACEV(param) tracev param |
| + | #else |
| + | #define TRACE(param) |
| + | #define TRACEV(param) |
| + | #endif |
| #ifdef __GLIBC__ | |
| /* glibc sucks */ | |
| @@ -141,6 +148,11 @@ static char optlist[NOPTS]; | |
| /* ============ Misc data */ | |
| + | static char nullstr[1]; /* zero length string */ |
| + | static const char homestr[] = "HOME"; |
| + | static const char snlfmt[] = "%s\n"; |
| + | static const char illnum[] = "Illegal number: %s"; |
| + | |
| static int isloginsh; | |
| /* pid of main shell */ | |
| static int rootpid; | |
| @@ -380,6 +392,11 @@ out2str(const char *p) | |
| * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up. | |
| */ | |
| + | struct strlist { |
| + | struct strlist *next; |
| + | char *text; |
| + | }; |
| + | |
| struct strpush { | |
| struct strpush *prev; /* preceding string on stack */ | |
| char *prevstring; | |
| @@ -623,6 +640,16 @@ stunalloc(void *p) | |
| stacknxt = p; | |
| } | |
| + | /* |
| + | * Like strdup but works with the ash stack. |
| + | */ |
| + | static char * |
| + | ststrdup(const char *p) |
| + | { |
| + | size_t len = strlen(p) + 1; |
| + | return memcpy(stalloc(len), p, len); |
| + | } |
| + | |
| static void | |
| setstackmark(struct stackmark *mark) | |
| { | |
| @@ -785,584 +812,1602 @@ stack_putstr(const char *s, char *p) | |
| return stack_nputstr(s, strlen(s), p); | |
| } | |
| + | static char * |
| + | _STPUTC(int c, char *p) |
| + | { |
| + | if (p == sstrend) |
| + | p = growstackstr(); |
| + | *p++ = c; |
| + | return p; |
| + | } |
| - | /* ============ Unsorted yet */ |
| - | |
| - | |
| - | static void setpwd(const char *, int); |
| - | |
| - | /* expand.h */ |
| + | #define STARTSTACKSTR(p) ((p) = stackblock()) |
| + | #define STPUTC(c, p) ((p) = _STPUTC((c), (p))) |
| + | #define CHECKSTRSPACE(n, p) \ |
| + | ({ \ |
| + | char *q = (p); \ |
| + | size_t l = (n); \ |
| + | size_t m = sstrend - q; \ |
| + | if (l > m) \ |
| + | (p) = makestrspace(l, q); \ |
| + | 0; \ |
| + | }) |
| + | #define USTPUTC(c, p) (*p++ = (c)) |
| + | #define STACKSTRNUL(p) ((p) == sstrend ? (p = growstackstr(), *p = '\0') : (*p = '\0')) |
| + | #define STUNPUTC(p) (--p) |
| + | #define STTOPC(p) p[-1] |
| + | #define STADJUST(amount, p) (p += (amount)) |
| - | struct strlist { |
| - | struct strlist *next; |
| - | char *text; |
| - | }; |
| + | #define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock()) |
| + | #define ungrabstackstr(s, p) stunalloc((s)) |
| + | #define stackstrend() ((void *)sstrend) |
| - | struct arglist { |
| - | struct strlist *list; |
| - | struct strlist **lastp; |
| - | }; |
| + | /* ============ String helpers */ |
| /* | |
| - | * expandarg() flags |
| + | * prefix -- see if pfx is a prefix of string. |
| */ | |
| - | #define EXP_FULL 0x1 /* perform word splitting & file globbing */ |
| - | #define EXP_TILDE 0x2 /* do normal tilde expansion */ |
| - | #define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ |
| - | #define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ |
| - | #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ |
| - | #define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */ |
| - | #define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ |
| - | #define EXP_WORD 0x80 /* expand word in parameter expansion */ |
| - | #define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ |
| - | |
| - | |
| - | union node; |
| - | static void expandarg(union node *, struct arglist *, int); |
| - | #define rmescapes(p) _rmescapes((p), 0) |
| - | static char *_rmescapes(char *, int); |
| - | static int casematch(union node *, char *); |
| - | |
| - | #if ENABLE_ASH_MATH_SUPPORT |
| - | static void expari(int); |
| - | #endif |
| - | |
| - | /* eval.h */ |
| - | |
| + | static char * |
| + | prefix(const char *string, const char *pfx) |
| + | { |
| + | while (*pfx) { |
| + | if (*pfx++ != *string++) |
| + | return 0; |
| + | } |
| + | return (char *) string; |
| + | } |
| + | /* |
| + | * Check for a valid number. This should be elsewhere. |
| + | */ |
| + | static int |
| + | is_number(const char *p) |
| + | { |
| + | do { |
| + | if (!isdigit(*p)) |
| + | return 0; |
| + | } while (*++p != '\0'); |
| + | return 1; |
| + | } |
| - | struct backcmd { /* result of evalbackcmd */ |
| - | int fd; /* file descriptor to read from */ |
| - | char *buf; /* buffer */ |
| - | int nleft; /* number of chars in buffer */ |
| - | struct job *jp; /* job structure for command */ |
| - | }; |
| + | /* |
| + | * Convert a string of digits to an integer, printing an error message on |
| + | * failure. |
| + | */ |
| + | static int |
| + | number(const char *s) |
| + | { |
| + | if (!is_number(s)) |
| + | ash_msg_and_raise_error(illnum, s); |
| + | return atoi(s); |
| + | } |
| /* | |
| - | * This file was generated by the mknodes program. |
| + | * Produce a possibly single quoted string suitable as input to the shell. |
| + | * The return string is allocated on the stack. |
| */ | |
| + | static char * |
| + | single_quote(const char *s) |
| + | { |
| + | char *p; |
| - | #define NCMD 0 |
| - | #define NPIPE 1 |
| - | #define NREDIR 2 |
| - | #define NBACKGND 3 |
| - | #define NSUBSHELL 4 |
| - | #define NAND 5 |
| - | #define NOR 6 |
| - | #define NSEMI 7 |
| - | #define NIF 8 |
| - | #define NWHILE 9 |
| - | #define NUNTIL 10 |
| - | #define NFOR 11 |
| - | #define NCASE 12 |
| - | #define NCLIST 13 |
| - | #define NDEFUN 14 |
| - | #define NARG 15 |
| - | #define NTO 16 |
| - | #define NCLOBBER 17 |
| - | #define NFROM 18 |
| - | #define NFROMTO 19 |
| - | #define NAPPEND 20 |
| - | #define NTOFD 21 |
| - | #define NFROMFD 22 |
| - | #define NHERE 23 |
| - | #define NXHERE 24 |
| - | #define NNOT 25 |
| + | STARTSTACKSTR(p); |
| + | do { |
| + | char *q; |
| + | size_t len; |
| - | struct ncmd { |
| - | int type; |
| - | union node *assign; |
| - | union node *args; |
| - | union node *redirect; |
| - | }; |
| + | len = strchrnul(s, '\'') - s; |
| - | struct npipe { |
| - | int type; |
| - | int backgnd; |
| - | struct nodelist *cmdlist; |
| - | }; |
| + | q = p = makestrspace(len + 3, p); |
| - | struct nredir { |
| - | int type; |
| - | union node *n; |
| - | union node *redirect; |
| - | }; |
| + | *q++ = '\''; |
| + | q = memcpy(q, s, len) + len; |
| + | *q++ = '\''; |
| + | s += len; |
| - | struct nbinary { |
| - | int type; |
| - | union node *ch1; |
| - | union node *ch2; |
| - | }; |
| + | STADJUST(q - p, p); |
| - | struct nif { |
| - | int type; |
| - | union node *test; |
| - | union node *ifpart; |
| - | union node *elsepart; |
| - | }; |
| + | len = strspn(s, "'"); |
| + | if (!len) |
| + | break; |
| - | struct nfor { |
| - | int type; |
| - | union node *args; |
| - | union node *body; |
| - | char *var; |
| - | }; |
| + | q = p = makestrspace(len + 3, p); |
| - | struct ncase { |
| - | int type; |
| - | union node *expr; |
| - | union node *cases; |
| - | }; |
| + | *q++ = '"'; |
| + | q = memcpy(q, s, len) + len; |
| + | *q++ = '"'; |
| + | s += len; |
| - | struct nclist { |
| - | int type; |
| - | union node *next; |
| - | union node *pattern; |
| - | union node *body; |
| - | }; |
| + | STADJUST(q - p, p); |
| + | } while (*s); |
| - | struct narg { |
| - | int type; |
| - | union node *next; |
| - | char *text; |
| - | struct nodelist *backquote; |
| - | }; |
| + | USTPUTC(0, p); |
| - | struct nfile { |
| - | int type; |
| - | union node *next; |
| - | int fd; |
| - | union node *fname; |
| - | char *expfname; |
| - | }; |
| + | return stackblock(); |
| + | } |
| - | struct ndup { |
| - | int type; |
| - | union node *next; |
| - | int fd; |
| - | int dupfd; |
| - | union node *vname; |
| - | }; |
| - | struct nhere { |
| - | int type; |
| - | union node *next; |
| - | int fd; |
| - | union node *doc; |
| - | }; |
| + | /* ============ ... */ |
| - | struct nnot { |
| - | int type; |
| - | union node *com; |
| - | }; |
| + | static char **argptr; /* argument list for builtin commands */ |
| + | static char *optionarg; /* set by nextopt (like getopt) */ |
| + | static char *optptr; /* used by nextopt */ |
| - | union node { |
| - | int type; |
| - | struct ncmd ncmd; |
| - | struct npipe npipe; |
| - | struct nredir nredir; |
| - | struct nbinary nbinary; |
| - | struct nif nif; |
| - | struct nfor nfor; |
| - | struct ncase ncase; |
| - | struct nclist nclist; |
| - | struct narg narg; |
| - | struct nfile nfile; |
| - | struct ndup ndup; |
| - | struct nhere nhere; |
| - | struct nnot nnot; |
| - | }; |
| + | /* |
| + | * XXX - should get rid of. have all builtins use getopt(3). the |
| + | * library getopt must have the BSD extension static variable "optreset" |
| + | * otherwise it can't be used within the shell safely. |
| + | * |
| + | * Standard option processing (a la getopt) for builtin routines. The |
| + | * only argument that is passed to nextopt is the option string; the |
| + | * other arguments are unnecessary. It return the character, or '\0' on |
| + | * end of input. |
| + | */ |
| + | static int |
| + | nextopt(const char *optstring) |
| + | { |
| + | char *p; |
| + | const char *q; |
| + | char c; |
| - | struct nodelist { |
| - | struct nodelist *next; |
| - | union node *n; |
| - | }; |
| + | p = optptr; |
| + | if (p == NULL || *p == '\0') { |
| + | p = *argptr; |
| + | if (p == NULL || *p != '-' || *++p == '\0') |
| + | return '\0'; |
| + | argptr++; |
| + | if (LONE_DASH(p)) /* check for "--" */ |
| + | return '\0'; |
| + | } |
| + | c = *p++; |
| + | for (q = optstring; *q != c; ) { |
| + | if (*q == '\0') |
| + | ash_msg_and_raise_error("Illegal option -%c", c); |
| + | if (*++q == ':') |
| + | q++; |
| + | } |
| + | if (*++q == ':') { |
| + | if (*p == '\0' && (p = *argptr++) == NULL) |
| + | ash_msg_and_raise_error("No arg for -%c option", c); |
| + | optionarg = p; |
| + | p = NULL; |
| + | } |
| + | optptr = p; |
| + | return c; |
| + | } |
| - | struct funcnode { |
| - | int count; |
| - | union node n; |
| - | }; |
| + | /* ============ Variables */ |
| - | static void freefunc(struct funcnode *); |
| - | /* parser.h */ |
| + | /* flags */ |
| + | #define VEXPORT 0x01 /* variable is exported */ |
| + | #define VREADONLY 0x02 /* variable cannot be modified */ |
| + | #define VSTRFIXED 0x04 /* variable struct is statically allocated */ |
| + | #define VTEXTFIXED 0x08 /* text is statically allocated */ |
| + | #define VSTACK 0x10 /* text is allocated on the stack */ |
| + | #define VUNSET 0x20 /* the variable is not set */ |
| + | #define VNOFUNC 0x40 /* don't call the callback function */ |
| + | #define VNOSET 0x80 /* do not set variable - just readonly test */ |
| + | #define VNOSAVE 0x100 /* when text is on the heap before setvareq */ |
| + | #ifdef DYNAMIC_VAR |
| + | # define VDYNAMIC 0x200 /* dynamic variable */ |
| + | # else |
| + | # define VDYNAMIC 0 |
| + | #endif |
| - | /* control characters in argument strings */ |
| - | #define CTL_FIRST '\201' /* first 'special' character */ |
| - | #define CTLESC '\201' /* escape next character */ |
| - | #define CTLVAR '\202' /* variable defn */ |
| - | #define CTLENDVAR '\203' |
| - | #define CTLBACKQ '\204' |
| - | #define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ |
| - | /* CTLBACKQ | CTLQUOTE == '\205' */ |
| - | #define CTLARI '\206' /* arithmetic expression */ |
| - | #define CTLENDARI '\207' |
| - | #define CTLQUOTEMARK '\210' |
| - | #define CTL_LAST '\210' /* last 'special' character */ |
| + | #if ENABLE_LOCALE_SUPPORT |
| + | static void change_lc_all(const char *value); |
| + | static void change_lc_ctype(const char *value); |
| + | #endif |
| - | /* variable substitution byte (follows CTLVAR) */ |
| - | #define VSTYPE 0x0f /* type of variable substitution */ |
| - | #define VSNUL 0x10 /* colon--treat the empty string as unset */ |
| - | #define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ |
| + | static const char defpathvar[] = "PATH=/usr/local/bin:/usr/bin:/sbin:/bin"; |
| + | #ifdef IFS_BROKEN |
| + | static const char defifsvar[] = "IFS= \t\n"; |
| + | #define defifs (defifsvar + 4) |
| + | #else |
| + | static const char defifs[] = " \t\n"; |
| + | #endif |
| - | /* values of VSTYPE field */ |
| - | #define VSNORMAL 0x1 /* normal variable: $var or ${var} */ |
| - | #define VSMINUS 0x2 /* ${var-text} */ |
| - | #define VSPLUS 0x3 /* ${var+text} */ |
| - | #define VSQUESTION 0x4 /* ${var?message} */ |
| - | #define VSASSIGN 0x5 /* ${var=text} */ |
| - | #define VSTRIMRIGHT 0x6 /* ${var%pattern} */ |
| - | #define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ |
| - | #define VSTRIMLEFT 0x8 /* ${var#pattern} */ |
| - | #define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ |
| - | #define VSLENGTH 0xa /* ${#var} */ |
| + | struct var { |
| + | struct var *next; /* next entry in hash list */ |
| + | int flags; /* flags are defined above */ |
| + | const char *text; /* name=value */ |
| + | void (*func)(const char *); /* function to be called when */ |
| + | /* the variable gets set/unset */ |
| + | }; |
| - | /* values of checkkwd variable */ |
| - | #define CHKALIAS 0x1 |
| - | #define CHKKWD 0x2 |
| - | #define CHKNL 0x4 |
| + | struct localvar { |
| + | struct localvar *next; /* next local variable in list */ |
| + | struct var *vp; /* the variable that was made local */ |
| + | int flags; /* saved flags */ |
| + | const char *text; /* saved text */ |
| + | }; |
| - | #define IBUFSIZ (BUFSIZ + 1) |
| + | /* Forward decls for varinit[] */ |
| + | #if ENABLE_ASH_MAIL |
| + | static void chkmail(void); |
| + | static void changemail(const char *); |
| + | #endif |
| + | static void changepath(const char *); |
| + | #if ENABLE_ASH_GETOPTS |
| + | static void getoptsreset(const char *); |
| + | #endif |
| + | #if ENABLE_ASH_RANDOM_SUPPORT |
| + | static void change_random(const char *); |
| + | #endif |
| + | |
| + | static struct var varinit[] = { |
| + | #ifdef IFS_BROKEN |
| + | { 0, VSTRFIXED|VTEXTFIXED, defifsvar, 0 }, |
| + | #else |
| + | { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "IFS\0", 0 }, |
| + | #endif |
| + | |
| + | #if ENABLE_ASH_MAIL |
| + | { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0", changemail }, |
| + | { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail }, |
| + | #endif |
| + | |
| + | { 0, VSTRFIXED|VTEXTFIXED, defpathvar, changepath }, |
| + | { 0, VSTRFIXED|VTEXTFIXED, "PS1=$ ", 0 }, |
| + | { 0, VSTRFIXED|VTEXTFIXED, "PS2=> ", 0 }, |
| + | { 0, VSTRFIXED|VTEXTFIXED, "PS4=+ ", 0 }, |
| + | #if ENABLE_ASH_GETOPTS |
| + | { 0, VSTRFIXED|VTEXTFIXED, "OPTIND=1", getoptsreset }, |
| + | #endif |
| + | #if ENABLE_ASH_RANDOM_SUPPORT |
| + | {0, VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random }, |
| + | #endif |
| + | #if ENABLE_LOCALE_SUPPORT |
| + | {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_ALL\0", change_lc_all }, |
| + | {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_CTYPE\0", change_lc_ctype }, |
| + | #endif |
| + | #if ENABLE_FEATURE_EDITING_SAVEHISTORY |
| + | {0, VSTRFIXED | VTEXTFIXED | VUNSET, "HISTFILE\0", NULL }, |
| + | #endif |
| + | }; |
| + | |
| + | #define vifs varinit[0] |
| + | #if ENABLE_ASH_MAIL |
| + | #define vmail (&vifs)[1] |
| + | #define vmpath (&vmail)[1] |
| + | #else |
| + | #define vmpath vifs |
| + | #endif |
| + | #define vpath (&vmpath)[1] |
| + | #define vps1 (&vpath)[1] |
| + | #define vps2 (&vps1)[1] |
| + | #define vps4 (&vps2)[1] |
| + | #define voptind (&vps4)[1] |
| + | #if ENABLE_ASH_GETOPTS |
| + | #define vrandom (&voptind)[1] |
| + | #else |
| + | #define vrandom (&vps4)[1] |
| + | #endif |
| + | #define defpath (defpathvar + 5) |
| /* | |
| - | * NEOF is returned by parsecmd when it encounters an end of file. It |
| - | * must be distinct from NULL, so we use the address of a variable that |
| - | * happens to be handy. |
| + | * The following macros access the values of the above variables. |
| + | * They have to skip over the name. They return the null string |
| + | * for unset variables. |
| */ | |
| - | static int plinno = 1; /* input line number */ |
| + | #define ifsval() (vifs.text + 4) |
| + | #define ifsset() ((vifs.flags & VUNSET) == 0) |
| + | #define mailval() (vmail.text + 5) |
| + | #define mpathval() (vmpath.text + 9) |
| + | #define pathval() (vpath.text + 5) |
| + | #define ps1val() (vps1.text + 4) |
| + | #define ps2val() (vps2.text + 4) |
| + | #define ps4val() (vps4.text + 4) |
| + | #define optindval() (voptind.text + 7) |
| - | /* number of characters left in input buffer */ |
| - | static int parsenleft; /* copy of parsefile->nleft */ |
| - | static int parselleft; /* copy of parsefile->lleft */ |
| + | #define mpathset() ((vmpath.flags & VUNSET) == 0) |
| - | /* next character in input buffer */ |
| - | static char *parsenextc; /* copy of parsefile->nextc */ |
| + | static struct var **hashvar(const char *); |
| - | #define basebuf bb_common_bufsiz1 /* buffer for top level input file */ |
| + | static int loopnest; /* current loop nesting level */ |
| + | /* |
| + | * The parsefile structure pointed to by the global variable parsefile |
| + | * contains information about the current file being read. |
| + | */ |
| + | struct redirtab { |
| + | struct redirtab *next; |
| + | int renamed[10]; |
| + | int nullredirs; |
| + | }; |
| - | static int tokpushback; /* last token pushed back */ |
| - | #define NEOF ((union node *)&tokpushback) |
| - | static int parsebackquote; /* nonzero if we are inside backquotes */ |
| - | static int doprompt; /* if set, prompt the user */ |
| - | static int needprompt; /* true if interactive and at start of line */ |
| - | static int lasttoken; /* last token read */ |
| - | static char *wordtext; /* text of last word returned by readtoken */ |
| - | static int checkkwd; |
| - | static struct nodelist *backquotelist; |
| - | static union node *redirnode; |
| - | static struct heredoc *heredoc; |
| - | static int quoteflag; /* set if (part of) last token was quoted */ |
| + | static struct redirtab *redirlist; |
| + | static int nullredirs; |
| - | static void fixredir(union node *, const char *, int); |
| - | static char *endofname(const char *); |
| + | extern char **environ; |
| - | /* shell.h */ |
| + | static int preverrout_fd; /* save fd2 before print debug if xflag is set. */ |
| - | static char nullstr[1]; /* zero length string */ |
| - | static const char spcstr[] = " "; |
| - | static const char snlfmt[] = "%s\n"; |
| - | static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' }; |
| - | static const char illnum[] = "Illegal number: %s"; |
| - | static const char homestr[] = "HOME"; |
| - | |
| - | #if DEBUG |
| - | #define TRACE(param) trace param |
| - | #define TRACEV(param) tracev param |
| - | #else |
| - | #define TRACE(param) |
| - | #define TRACEV(param) |
| + | struct shparam { |
| + | int nparam; /* # of positional parameters (without $0) */ |
| + | unsigned char malloc; /* if parameter list dynamically allocated */ |
| + | char **p; /* parameter list */ |
| + | #if ENABLE_ASH_GETOPTS |
| + | int optind; /* next parameter to be processed by getopts */ |
| + | int optoff; /* used by getopts */ |
| #endif | |
| + | }; |
| - | #if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96) |
| - | #define __builtin_expect(x, expected_value) (x) |
| - | #endif |
| + | static struct shparam shellparam; /* $@ current positional parameters */ |
| - | #define xlikely(x) __builtin_expect((x),1) |
| + | #define VTABSIZE 39 |
| + | static struct var *vartab[VTABSIZE]; |
| - | #define TEOF 0 |
| - | #define TNL 1 |
| - | #define TREDIR 2 |
| - | #define TWORD 3 |
| - | #define TSEMI 4 |
| - | #define TBACKGND 5 |
| - | #define TAND 6 |
| - | #define TOR 7 |
| - | #define TPIPE 8 |
| - | #define TLP 9 |
| - | #define TRP 10 |
| - | #define TENDCASE 11 |
| - | #define TENDBQUOTE 12 |
| - | #define TNOT 13 |
| - | #define TCASE 14 |
| - | #define TDO 15 |
| - | #define TDONE 16 |
| - | #define TELIF 17 |
| - | #define TELSE 18 |
| - | #define TESAC 19 |
| - | #define TFI 20 |
| - | #define TFOR 21 |
| - | #define TIF 22 |
| - | #define TIN 23 |
| - | #define TTHEN 24 |
| - | #define TUNTIL 25 |
| - | #define TWHILE 26 |
| - | #define TBEGIN 27 |
| - | #define TEND 28 |
| + | #if ENABLE_ASH_GETOPTS |
| + | static void |
| + | getoptsreset(const char *value) |
| + | { |
| + | shellparam.optind = number(value); |
| + | shellparam.optoff = -1; |
| + | } |
| + | #endif |
| - | /* first char is indicating which tokens mark the end of a list */ |
| - | static const char *const tokname_array[] = { |
| - | "\1end of file", |
| - | "\0newline", |
| - | "\0redirection", |
| - | "\0word", |
| - | "\0;", |
| - | "\0&", |
| - | "\0&&", |
| - | "\0||", |
| - | "\0|", |
| - | "\0(", |
| - | "\1)", |
| - | "\1;;", |
| - | "\1`", |
| - | #define KWDOFFSET 13 |
| - | /* the following are keywords */ |
| - | "\0!", |
| - | "\0case", |
| - | "\1do", |
| - | "\1done", |
| - | "\1elif", |
| - | "\1else", |
| - | "\1esac", |
| - | "\1fi", |
| - | "\0for", |
| - | "\0if", |
| - | "\0in", |
| - | "\1then", |
| - | "\0until", |
| - | "\0while", |
| - | "\0{", |
| - | "\1}", |
| - | }; |
| + | #define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) |
| + | #define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) |
| - | static const char * |
| - | tokname(int tok) |
| + | /* |
| + | * Return of a legal variable name (a letter or underscore followed by zero or |
| + | * more letters, underscores, and digits). |
| + | */ |
| + | static char * |
| + | endofname(const char *name) |
| { | |
| - | static char buf[16]; |
| + | char *p; |
| - | if (tok >= TSEMI) |
| - | buf[0] = '"'; |
| - | sprintf(buf + (tok >= TSEMI), "%s%c", |
| - | tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); |
| - | return buf; |
| + | p = (char *) name; |
| + | if (!is_name(*p)) |
| + | return p; |
| + | while (*++p) { |
| + | if (!is_in_name(*p)) |
| + | break; |
| + | } |
| + | return p; |
| } | |
| - | /* Wrapper around strcmp for qsort/bsearch/... */ |
| + | /* |
| + | * Compares two strings up to the first = or '\0'. The first |
| + | * string must be terminated by '='; the second may be terminated by |
| + | * either '=' or '\0'. |
| + | */ |
| static int | |
| - | pstrcmp(const void *a, const void *b) |
| + | varcmp(const char *p, const char *q) |
| { | |
| - | return strcmp((const char *) a, (*(const char *const *) b) + 1); |
| + | int c, d; |
| + | |
| + | while ((c = *p) == (d = *q)) { |
| + | if (!c || c == '=') |
| + | goto out; |
| + | p++; |
| + | q++; |
| + | } |
| + | if (c == '=') |
| + | c = 0; |
| + | if (d == '=') |
| + | d = 0; |
| + | out: |
| + | return c - d; |
| } | |
| - | static const char *const * |
| - | findkwd(const char *s) |
| + | static int |
| + | varequal(const char *a, const char *b) |
| { | |
| - | return bsearch(s, tokname_array + KWDOFFSET, |
| - | (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET, |
| - | sizeof(const char *), pstrcmp); |
| + | return !varcmp(a, b); |
| } | |
| - | /* Syntax classes */ |
| - | #define CWORD 0 /* character is nothing special */ |
| - | #define CNL 1 /* newline character */ |
| - | #define CBACK 2 /* a backslash character */ |
| - | #define CSQUOTE 3 /* single quote */ |
| - | #define CDQUOTE 4 /* double quote */ |
| - | #define CENDQUOTE 5 /* a terminating quote */ |
| - | #define CBQUOTE 6 /* backwards single quote */ |
| - | #define CVAR 7 /* a dollar sign */ |
| - | #define CENDVAR 8 /* a '}' character */ |
| - | #define CLP 9 /* a left paren in arithmetic */ |
| - | #define CRP 10 /* a right paren in arithmetic */ |
| - | #define CENDFILE 11 /* end of file */ |
| - | #define CCTL 12 /* like CWORD, except it must be escaped */ |
| - | #define CSPCL 13 /* these terminate a word */ |
| - | #define CIGN 14 /* character should be ignored */ |
| - | |
| - | #if ENABLE_ASH_ALIAS |
| - | #define SYNBASE 130 |
| - | #define PEOF -130 |
| - | #define PEOA -129 |
| - | #define PEOA_OR_PEOF PEOA |
| - | #else |
| - | #define SYNBASE 129 |
| - | #define PEOF -129 |
| - | #define PEOA_OR_PEOF PEOF |
| - | #endif |
| + | /* |
| + | * Find the appropriate entry in the hash table from the name. |
| + | */ |
| + | static struct var ** |
| + | hashvar(const char *p) |
| + | { |
| + | unsigned hashval; |
| - | #define is_digit(c) ((unsigned)((c) - '0') <= 9) |
| - | #define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) |
| - | #define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) |
| + | hashval = ((unsigned char) *p) << 4; |
| + | while (*p && *p != '=') |
| + | hashval += (unsigned char) *p++; |
| + | return &vartab[hashval % VTABSIZE]; |
| + | } |
| - | /* C99 say: "char" declaration may be signed or unsigned default */ |
| - | #define SC2INT(chr2may_be_negative_int) (int)((signed char)chr2may_be_negative_int) |
| + | static int |
| + | vpcmp(const void *a, const void *b) |
| + | { |
| + | return varcmp(*(const char **)a, *(const char **)b); |
| + | } |
| /* | |
| - | * is_special(c) evaluates to 1 for c in "!#$*-0123456789?@"; 0 otherwise |
| - | * (assuming ascii char codes, as the original implementation did) |
| + | * This routine initializes the builtin variables. |
| */ | |
| - | #define is_special(c) \ |
| - | ( (((unsigned int)c) - 33 < 32) \ |
| - | && ((0xc1ff920dUL >> (((unsigned int)c) - 33)) & 1)) |
| + | static void |
| + | initvar(void) |
| + | { |
| + | struct var *vp; |
| + | struct var *end; |
| + | struct var **vpp; |
| - | #define digit_val(c) ((c) - '0') |
| + | /* |
| + | * PS1 depends on uid |
| + | */ |
| + | #if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT |
| + | vps1.text = "PS1=\\w \\$ "; |
| + | #else |
| + | if (!geteuid()) |
| + | vps1.text = "PS1=# "; |
| + | #endif |
| + | vp = varinit; |
| + | end = vp + sizeof(varinit) / sizeof(varinit[0]); |
| + | do { |
| + | vpp = hashvar(vp->text); |
| + | vp->next = *vpp; |
| + | *vpp = vp; |
| + | } while (++vp < end); |
| + | } |
| + | |
| + | static struct var ** |
| + | findvar(struct var **vpp, const char *name) |
| + | { |
| + | for (; *vpp; vpp = &(*vpp)->next) { |
| + | if (varequal((*vpp)->text, name)) { |
| + | break; |
| + | } |
| + | } |
| + | return vpp; |
| + | } |
| /* | |
| - | * This file was generated by the mksyntax program. |
| + | * Find the value of a variable. Returns NULL if not set. |
| */ | |
| + | static char * |
| + | lookupvar(const char *name) |
| + | { |
| + | struct var *v; |
| - | #if ENABLE_ASH_OPTIMIZE_FOR_SIZE |
| - | #define USE_SIT_FUNCTION |
| + | v = *findvar(hashvar(name), name); |
| + | if (v) { |
| + | #ifdef DYNAMIC_VAR |
| + | /* |
| + | * Dynamic variables are implemented roughly the same way they are |
| + | * in bash. Namely, they're "special" so long as they aren't unset. |
| + | * As soon as they're unset, they're no longer dynamic, and dynamic |
| + | * lookup will no longer happen at that point. -- PFM. |
| + | */ |
| + | if ((v->flags & VDYNAMIC)) |
| + | (*v->func)(NULL); |
| #endif | |
| + | if (!(v->flags & VUNSET)) |
| + | return strchrnul(v->text, '=') + 1; |
| + | } |
| + | return NULL; |
| + | } |
| - | /* number syntax index */ |
| - | #define BASESYNTAX 0 /* not in quotes */ |
| - | #define DQSYNTAX 1 /* in double quotes */ |
| - | #define SQSYNTAX 2 /* in single quotes */ |
| - | #define ARISYNTAX 3 /* in arithmetic */ |
| - | |
| - | #if ENABLE_ASH_MATH_SUPPORT |
| - | static const char S_I_T[][4] = { |
| - | #if ENABLE_ASH_ALIAS |
| - | {CSPCL, CIGN, CIGN, CIGN}, /* 0, PEOA */ |
| - | #endif |
| - | {CSPCL, CWORD, CWORD, CWORD}, /* 1, ' ' */ |
| - | {CNL, CNL, CNL, CNL}, /* 2, \n */ |
| - | {CWORD, CCTL, CCTL, CWORD}, /* 3, !*-/:=?[]~ */ |
| - | {CDQUOTE, CENDQUOTE, CWORD, CWORD}, /* 4, '"' */ |
| - | {CVAR, CVAR, CWORD, CVAR}, /* 5, $ */ |
| - | {CSQUOTE, CWORD, CENDQUOTE, CWORD}, /* 6, "'" */ |
| - | {CSPCL, CWORD, CWORD, CLP}, /* 7, ( */ |
| - | {CSPCL, CWORD, CWORD, CRP}, /* 8, ) */ |
| - | {CBACK, CBACK, CCTL, CBACK}, /* 9, \ */ |
| - | {CBQUOTE, CBQUOTE, CWORD, CBQUOTE}, /* 10, ` */ |
| - | {CENDVAR, CENDVAR, CWORD, CENDVAR}, /* 11, } */ |
| - | #ifndef USE_SIT_FUNCTION |
| - | {CENDFILE, CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ |
| - | {CWORD, CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ |
| - | {CCTL, CCTL, CCTL, CCTL} /* 14, CTLESC ... */ |
| - | #endif |
| - | }; |
| - | #else |
| - | static const char S_I_T[][3] = { |
| - | #if ENABLE_ASH_ALIAS |
| - | {CSPCL, CIGN, CIGN}, /* 0, PEOA */ |
| - | #endif |
| - | {CSPCL, CWORD, CWORD}, /* 1, ' ' */ |
| - | {CNL, CNL, CNL}, /* 2, \n */ |
| - | {CWORD, CCTL, CCTL}, /* 3, !*-/:=?[]~ */ |
| - | {CDQUOTE, CENDQUOTE, CWORD}, /* 4, '"' */ |
| - | {CVAR, CVAR, CWORD}, /* 5, $ */ |
| - | {CSQUOTE, CWORD, CENDQUOTE}, /* 6, "'" */ |
| - | {CSPCL, CWORD, CWORD}, /* 7, ( */ |
| - | {CSPCL, CWORD, CWORD}, /* 8, ) */ |
| - | {CBACK, CBACK, CCTL}, /* 9, \ */ |
| - | {CBQUOTE, CBQUOTE, CWORD}, /* 10, ` */ |
| - | {CENDVAR, CENDVAR, CWORD}, /* 11, } */ |
| - | #ifndef USE_SIT_FUNCTION |
| - | {CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ |
| - | {CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ |
| - | {CCTL, CCTL, CCTL} /* 14, CTLESC ... */ |
| - | #endif |
| - | }; |
| - | #endif /* ASH_MATH_SUPPORT */ |
| + | /* |
| + | * Search the environment of a builtin command. |
| + | */ |
| + | static char * |
| + | bltinlookup(const char *name) |
| + | { |
| + | struct strlist *sp; |
| - | #ifdef USE_SIT_FUNCTION |
| + | for (sp = cmdenviron; sp; sp = sp->next) { |
| + | if (varequal(sp->text, name)) |
| + | return strchrnul(sp->text, '=') + 1; |
| + | } |
| + | return lookupvar(name); |
| + | } |
| - | #define U_C(c) ((unsigned char)(c)) |
| + | /* |
| + | * Same as setvar except that the variable and value are passed in |
| + | * the first argument as name=value. Since the first argument will |
| + | * be actually stored in the table, it should not be a string that |
| + | * will go away. |
| + | * Called with interrupts off. |
| + | */ |
| + | static void |
| + | setvareq(char *s, int flags) |
| + | { |
| + | struct var *vp, **vpp; |
| + | |
| + | vpp = hashvar(s); |
| + | flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1)); |
| + | vp = *findvar(vpp, s); |
| + | if (vp) { |
| + | if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) { |
| + | const char *n; |
| + | |
| + | if (flags & VNOSAVE) |
| + | free(s); |
| + | n = vp->text; |
| + | ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n); |
| + | } |
| + | |
| + | if (flags & VNOSET) |
| + | return; |
| + | |
| + | if (vp->func && (flags & VNOFUNC) == 0) |
| + | (*vp->func)(strchrnul(s, '=') + 1); |
| + | |
| + | if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) |
| + | free((char*)vp->text); |
| + | |
| + | flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); |
| + | } else { |
| + | if (flags & VNOSET) |
| + | return; |
| + | /* not found */ |
| + | vp = ckmalloc(sizeof(*vp)); |
| + | vp->next = *vpp; |
| + | vp->func = NULL; |
| + | *vpp = vp; |
| + | } |
| + | if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE))) |
| + | s = ckstrdup(s); |
| + | vp->text = s; |
| + | vp->flags = flags; |
| + | } |
| + | |
| + | /* |
| + | * Set the value of a variable. The flags argument is ored with the |
| + | * flags of the variable. If val is NULL, the variable is unset. |
| + | */ |
| + | static void |
| + | setvar(const char *name, const char *val, int flags) |
| + | { |
| + | char *p, *q; |
| + | size_t namelen; |
| + | char *nameeq; |
| + | size_t vallen; |
| + | |
| + | q = endofname(name); |
| + | p = strchrnul(q, '='); |
| + | namelen = p - name; |
| + | if (!namelen || p != q) |
| + | ash_msg_and_raise_error("%.*s: bad variable name", namelen, name); |
| + | vallen = 0; |
| + | if (val == NULL) { |
| + | flags |= VUNSET; |
| + | } else { |
| + | vallen = strlen(val); |
| + | } |
| + | INT_OFF; |
| + | nameeq = ckmalloc(namelen + vallen + 2); |
| + | p = memcpy(nameeq, name, namelen) + namelen; |
| + | if (val) { |
| + | *p++ = '='; |
| + | p = memcpy(p, val, vallen) + vallen; |
| + | } |
| + | *p = '\0'; |
| + | setvareq(nameeq, flags | VNOSAVE); |
| + | INT_ON; |
| + | } |
| + | #if ENABLE_ASH_GETOPTS |
| + | /* |
| + | * Safe version of setvar, returns 1 on success 0 on failure. |
| + | */ |
| static int | |
| - | SIT(int c, int syntax) |
| + | setvarsafe(const char *name, const char *val, int flags) |
| { | |
| - | static const char spec_symbls[] = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~"; |
| - | #if ENABLE_ASH_ALIAS |
| - | static const char syntax_index_table[] = { |
| - | 1, 2, 1, 3, 4, 5, 1, 6, /* "\t\n !\"$&'" */ |
| - | 7, 8, 3, 3, 3, 3, 1, 1, /* "()*-/:;<" */ |
| - | 3, 1, 3, 3, 9, 3, 10, 1, /* "=>?[\\]`|" */ |
| - | 11, 3 /* "}~" */ |
| - | }; |
| - | #else |
| - | static const char syntax_index_table[] = { |
| - | 0, 1, 0, 2, 3, 4, 0, 5, /* "\t\n !\"$&'" */ |
| - | 6, 7, 2, 2, 2, 2, 0, 0, /* "()*-/:;<" */ |
| - | 2, 0, 2, 2, 8, 2, 9, 0, /* "=>?[\\]`|" */ |
| - | 10, 2 /* "}~" */ |
| - | }; |
| - | #endif |
| - | const char *s; |
| - | int indx; |
| + | int err; |
| + | volatile int saveint; |
| + | struct jmploc *volatile savehandler = exception_handler; |
| + | struct jmploc jmploc; |
| - | if (c == PEOF) /* 2^8+2 */ |
| - | return CENDFILE; |
| - | #if ENABLE_ASH_ALIAS |
| - | if (c == PEOA) /* 2^8+1 */ |
| - | indx = 0; |
| - | else |
| - | #endif |
| - | if (U_C(c) >= U_C(CTLESC) && U_C(c) <= U_C(CTLQUOTEMARK)) |
| - | return CCTL; |
| + | SAVE_INT(saveint); |
| + | if (setjmp(jmploc.loc)) |
| + | err = 1; |
| else { | |
| - | s = strchr(spec_symbls, c); |
| - | if (s == NULL || *s == '\0') |
| - | return CWORD; |
| - | indx = syntax_index_table[(s - spec_symbls)]; |
| + | exception_handler = &jmploc; |
| + | setvar(name, val, flags); |
| + | err = 0; |
| } | |
| - | return S_I_T[indx][syntax]; |
| + | exception_handler = savehandler; |
| + | RESTORE_INT(saveint); |
| + | return err; |
| } | |
| + | #endif |
| - | #else /* USE_SIT_FUNCTION */ |
| - | |
| - | #define SIT(c, syntax) S_I_T[(int)syntax_index_table[((int)c)+SYNBASE]][syntax] |
| + | /* |
| + | * Unset the specified variable. |
| + | */ |
| + | static int |
| + | unsetvar(const char *s) |
| + | { |
| + | struct var **vpp; |
| + | struct var *vp; |
| + | int retval; |
| - | #if ENABLE_ASH_ALIAS |
| - | #define CSPCL_CIGN_CIGN_CIGN 0 |
| - | #define CSPCL_CWORD_CWORD_CWORD 1 |
| - | #define CNL_CNL_CNL_CNL 2 |
| - | #define CWORD_CCTL_CCTL_CWORD 3 |
| - | #define CDQUOTE_CENDQUOTE_CWORD_CWORD 4 |
| - | #define CVAR_CVAR_CWORD_CVAR 5 |
| - | #define CSQUOTE_CWORD_CENDQUOTE_CWORD 6 |
| - | #define CSPCL_CWORD_CWORD_CLP 7 |
| - | #define CSPCL_CWORD_CWORD_CRP 8 |
| - | #define CBACK_CBACK_CCTL_CBACK 9 |
| - | #define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 10 |
| - | #define CENDVAR_CENDVAR_CWORD_CENDVAR 11 |
| - | #define CENDFILE_CENDFILE_CENDFILE_CENDFILE 12 |
| - | #define CWORD_CWORD_CWORD_CWORD 13 |
| - | #define CCTL_CCTL_CCTL_CCTL 14 |
| - | #else |
| - | #define CSPCL_CWORD_CWORD_CWORD 0 |
| - | #define CNL_CNL_CNL_CNL 1 |
| - | #define CWORD_CCTL_CCTL_CWORD 2 |
| - | #define CDQUOTE_CENDQUOTE_CWORD_CWORD 3 |
| - | #define CVAR_CVAR_CWORD_CVAR 4 |
| - | #define CSQUOTE_CWORD_CENDQUOTE_CWORD 5 |
| - | #define CSPCL_CWORD_CWORD_CLP 6 |
| - | #define CSPCL_CWORD_CWORD_CRP 7 |
| - | #define CBACK_CBACK_CCTL_CBACK 8 |
| - | #define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 9 |
| - | #define CENDVAR_CENDVAR_CWORD_CENDVAR 10 |
| - | #define CENDFILE_CENDFILE_CENDFILE_CENDFILE 11 |
| - | #define CWORD_CWORD_CWORD_CWORD 12 |
| - | #define CCTL_CCTL_CCTL_CCTL 13 |
| - | #endif |
| + | vpp = findvar(hashvar(s), s); |
| + | vp = *vpp; |
| + | retval = 2; |
| + | if (vp) { |
| + | int flags = vp->flags; |
| - | static const char syntax_index_table[258] = { |
| - | /* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */ |
| + | retval = 1; |
| + | if (flags & VREADONLY) |
| + | goto out; |
| + | #ifdef DYNAMIC_VAR |
| + | vp->flags &= ~VDYNAMIC; |
| + | #endif |
| + | if (flags & VUNSET) |
| + | goto ok; |
| + | if ((flags & VSTRFIXED) == 0) { |
| + | INT_OFF; |
| + | if ((flags & (VTEXTFIXED|VSTACK)) == 0) |
| + | free((char*)vp->text); |
| + | *vpp = vp->next; |
| + | free(vp); |
| + | INT_ON; |
| + | } else { |
| + | setvar(s, 0, 0); |
| + | vp->flags &= ~VEXPORT; |
| + | } |
| + | ok: |
| + | retval = 0; |
| + | } |
| + | out: |
| + | return retval; |
| + | } |
| + | |
| + | /* |
| + | * Process a linked list of variable assignments. |
| + | */ |
| + | static void |
| + | listsetvar(struct strlist *list_set_var, int flags) |
| + | { |
| + | struct strlist *lp = list_set_var; |
| + | |
| + | if (!lp) |
| + | return; |
| + | INT_OFF; |
| + | do { |
| + | setvareq(lp->text, flags); |
| + | } while ((lp = lp->next)); |
| + | INT_ON; |
| + | } |
| + | |
| + | /* |
| + | * Generate a list of variables satisfying the given conditions. |
| + | */ |
| + | static char ** |
| + | listvars(int on, int off, char ***end) |
| + | { |
| + | struct var **vpp; |
| + | struct var *vp; |
| + | char **ep; |
| + | int mask; |
| + | |
| + | STARTSTACKSTR(ep); |
| + | vpp = vartab; |
| + | mask = on | off; |
| + | do { |
| + | for (vp = *vpp; vp; vp = vp->next) { |
| + | if ((vp->flags & mask) == on) { |
| + | if (ep == stackstrend()) |
| + | ep = growstackstr(); |
| + | *ep++ = (char *) vp->text; |
| + | } |
| + | } |
| + | } while (++vpp < vartab + VTABSIZE); |
| + | if (ep == stackstrend()) |
| + | ep = growstackstr(); |
| + | if (end) |
| + | *end = ep; |
| + | *ep++ = NULL; |
| + | return grabstackstr(ep); |
| + | } |
| + | |
| + | |
| + | /* ============ Path search helper |
| + | * |
| + | * The variable path (passed by reference) should be set to the start |
| + | * of the path before the first call; padvance will update |
| + | * this value as it proceeds. Successive calls to padvance will return |
| + | * the possible path expansions in sequence. If an option (indicated by |
| + | * a percent sign) appears in the path entry then the global variable |
| + | * pathopt will be set to point to it; otherwise pathopt will be set to |
| + | * NULL. |
| + | */ |
| + | static const char *pathopt; /* set by padvance */ |
| + | |
| + | static char * |
| + | padvance(const char **path, const char *name) |
| + | { |
| + | const char *p; |
| + | char *q; |
| + | const char *start; |
| + | size_t len; |
| + | |
| + | if (*path == NULL) |
| + | return NULL; |
| + | start = *path; |
| + | for (p = start; *p && *p != ':' && *p != '%'; p++); |
| + | len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ |
| + | while (stackblocksize() < len) |
| + | growstackblock(); |
| + | q = stackblock(); |
| + | if (p != start) { |
| + | memcpy(q, start, p - start); |
| + | q += p - start; |
| + | *q++ = '/'; |
| + | } |
| + | strcpy(q, name); |
| + | pathopt = NULL; |
| + | if (*p == '%') { |
| + | pathopt = ++p; |
| + | while (*p && *p != ':') p++; |
| + | } |
| + | if (*p == ':') |
| + | *path = p + 1; |
| + | else |
| + | *path = NULL; |
| + | return stalloc(len); |
| + | } |
| + | |
| + | |
| + | /* ============ Prompt */ |
| + | |
| + | static int doprompt; /* if set, prompt the user */ |
| + | static int needprompt; /* true if interactive and at start of line */ |
| + | |
| + | #if ENABLE_FEATURE_EDITING |
| + | static line_input_t *line_input_state; |
| + | static const char *cmdedit_prompt; |
| + | static void |
| + | putprompt(const char *s) |
| + | { |
| + | if (ENABLE_ASH_EXPAND_PRMT) { |
| + | free((char*)cmdedit_prompt); |
| + | cmdedit_prompt = xstrdup(s); |
| + | return; |
| + | } |
| + | cmdedit_prompt = s; |
| + | } |
| + | #else |
| + | static void |
| + | putprompt(const char *s) |
| + | { |
| + | out2str(s); |
| + | } |
| + | #endif |
| + | |
| + | #if ENABLE_ASH_EXPAND_PRMT |
| + | /* expandstr() needs parsing machinery, so it is far away ahead... */ |
| + | static const char *expandstr(const char *ps); |
| + | #else |
| + | #define expandstr(s) s |
| + | #endif |
| + | |
| + | static void |
| + | setprompt(int whichprompt) |
| + | { |
| + | const char *prompt; |
| + | #if ENABLE_ASH_EXPAND_PRMT |
| + | struct stackmark smark; |
| + | #endif |
| + | |
| + | needprompt = 0; |
| + | |
| + | switch (whichprompt) { |
| + | case 1: |
| + | prompt = ps1val(); |
| + | break; |
| + | case 2: |
| + | prompt = ps2val(); |
| + | break; |
| + | default: /* 0 */ |
| + | prompt = nullstr; |
| + | } |
| + | #if ENABLE_ASH_EXPAND_PRMT |
| + | setstackmark(&smark); |
| + | stalloc(stackblocksize()); |
| + | #endif |
| + | putprompt(expandstr(prompt)); |
| + | #if ENABLE_ASH_EXPAND_PRMT |
| + | popstackmark(&smark); |
| + | #endif |
| + | } |
| + | |
| + | |
| + | /* ============ The cd and pwd commands */ |
| + | |
| + | #define CD_PHYSICAL 1 |
| + | #define CD_PRINT 2 |
| + | |
| + | static int docd(const char *, int); |
| + | |
| + | static char *curdir = nullstr; /* current working directory */ |
| + | static char *physdir = nullstr; /* physical working directory */ |
| + | |
| + | static int |
| + | cdopt(void) |
| + | { |
| + | int flags = 0; |
| + | int i, j; |
| + | |
| + | j = 'L'; |
| + | while ((i = nextopt("LP"))) { |
| + | if (i != j) { |
| + | flags ^= CD_PHYSICAL; |
| + | j = i; |
| + | } |
| + | } |
| + | |
| + | return flags; |
| + | } |
| + | |
| + | /* |
| + | * Update curdir (the name of the current directory) in response to a |
| + | * cd command. |
| + | */ |
| + | static const char * |
| + | updatepwd(const char *dir) |
| + | { |
| + | char *new; |
| + | char *p; |
| + | char *cdcomppath; |
| + | const char *lim; |
| + | |
| + | cdcomppath = ststrdup(dir); |
| + | STARTSTACKSTR(new); |
| + | if (*dir != '/') { |
| + | if (curdir == nullstr) |
| + | return 0; |
| + | new = stack_putstr(curdir, new); |
| + | } |
| + | new = makestrspace(strlen(dir) + 2, new); |
| + | lim = stackblock() + 1; |
| + | if (*dir != '/') { |
| + | if (new[-1] != '/') |
| + | USTPUTC('/', new); |
| + | if (new > lim && *lim == '/') |
| + | lim++; |
| + | } else { |
| + | USTPUTC('/', new); |
| + | cdcomppath++; |
| + | if (dir[1] == '/' && dir[2] != '/') { |
| + | USTPUTC('/', new); |
| + | cdcomppath++; |
| + | lim++; |
| + | } |
| + | } |
| + | p = strtok(cdcomppath, "/"); |
| + | while (p) { |
| + | switch (*p) { |
| + | case '.': |
| + | if (p[1] == '.' && p[2] == '\0') { |
| + | while (new > lim) { |
| + | STUNPUTC(new); |
| + | if (new[-1] == '/') |
| + | break; |
| + | } |
| + | break; |
| + | } else if (p[1] == '\0') |
| + | break; |
| + | /* fall through */ |
| + | default: |
| + | new = stack_putstr(p, new); |
| + | USTPUTC('/', new); |
| + | } |
| + | p = strtok(0, "/"); |
| + | } |
| + | if (new > lim) |
| + | STUNPUTC(new); |
| + | *new = 0; |
| + | return stackblock(); |
| + | } |
| + | |
| + | /* |
| + | * Find out what the current directory is. If we already know the current |
| + | * directory, this routine returns immediately. |
| + | */ |
| + | static char * |
| + | getpwd(void) |
| + | { |
| + | char *dir = getcwd(0, 0); |
| + | return dir ? dir : nullstr; |
| + | } |
| + | |
| + | static void |
| + | setpwd(const char *val, int setold) |
| + | { |
| + | char *oldcur, *dir; |
| + | |
| + | oldcur = dir = curdir; |
| + | |
| + | if (setold) { |
| + | setvar("OLDPWD", oldcur, VEXPORT); |
| + | } |
| + | INT_OFF; |
| + | if (physdir != nullstr) { |
| + | if (physdir != oldcur) |
| + | free(physdir); |
| + | physdir = nullstr; |
| + | } |
| + | if (oldcur == val || !val) { |
| + | char *s = getpwd(); |
| + | physdir = s; |
| + | if (!val) |
| + | dir = s; |
| + | } else |
| + | dir = ckstrdup(val); |
| + | if (oldcur != dir && oldcur != nullstr) { |
| + | free(oldcur); |
| + | } |
| + | curdir = dir; |
| + | INT_ON; |
| + | setvar("PWD", dir, VEXPORT); |
| + | } |
| + | |
| + | static void hashcd(void); |
| + | |
| + | /* |
| + | * Actually do the chdir. We also call hashcd to let the routines in exec.c |
| + | * know that the current directory has changed. |
| + | */ |
| + | static int |
| + | docd(const char *dest, int flags) |
| + | { |
| + | const char *dir = 0; |
| + | int err; |
| + | |
| + | TRACE(("docd(\"%s\", %d) called\n", dest, flags)); |
| + | |
| + | INT_OFF; |
| + | if (!(flags & CD_PHYSICAL)) { |
| + | dir = updatepwd(dest); |
| + | if (dir) |
| + | dest = dir; |
| + | } |
| + | err = chdir(dest); |
| + | if (err) |
| + | goto out; |
| + | setpwd(dir, 1); |
| + | hashcd(); |
| + | out: |
| + | INT_ON; |
| + | return err; |
| + | } |
| + | |
| + | static int |
| + | cdcmd(int argc, char **argv) |
| + | { |
| + | const char *dest; |
| + | const char *path; |
| + | const char *p; |
| + | char c; |
| + | struct stat statb; |
| + | int flags; |
| + | |
| + | flags = cdopt(); |
| + | dest = *argptr; |
| + | if (!dest) |
| + | dest = bltinlookup(homestr); |
| + | else if (LONE_DASH(dest)) { |
| + | dest = bltinlookup("OLDPWD"); |
| + | flags |= CD_PRINT; |
| + | } |
| + | if (!dest) |
| + | dest = nullstr; |
| + | if (*dest == '/') |
| + | goto step7; |
| + | if (*dest == '.') { |
| + | c = dest[1]; |
| + | dotdot: |
| + | switch (c) { |
| + | case '\0': |
| + | case '/': |
| + | goto step6; |
| + | case '.': |
| + | c = dest[2]; |
| + | if (c != '.') |
| + | goto dotdot; |
| + | } |
| + | } |
| + | if (!*dest) |
| + | dest = "."; |
| + | path = bltinlookup("CDPATH"); |
| + | if (!path) { |
| + | step6: |
| + | step7: |
| + | p = dest; |
| + | goto docd; |
| + | } |
| + | do { |
| + | c = *path; |
| + | p = padvance(&path, dest); |
| + | if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { |
| + | if (c && c != ':') |
| + | flags |= CD_PRINT; |
| + | docd: |
| + | if (!docd(p, flags)) |
| + | goto out; |
| + | break; |
| + | } |
| + | } while (path); |
| + | ash_msg_and_raise_error("can't cd to %s", dest); |
| + | /* NOTREACHED */ |
| + | out: |
| + | if (flags & CD_PRINT) |
| + | out1fmt(snlfmt, curdir); |
| + | return 0; |
| + | } |
| + | |
| + | static int |
| + | pwdcmd(int argc, char **argv) |
| + | { |
| + | int flags; |
| + | const char *dir = curdir; |
| + | |
| + | flags = cdopt(); |
| + | if (flags) { |
| + | if (physdir == nullstr) |
| + | setpwd(dir, 0); |
| + | dir = physdir; |
| + | } |
| + | out1fmt(snlfmt, dir); |
| + | return 0; |
| + | } |
| + | |
| + | |
| + | /* ============ Unsorted yet */ |
| + | |
| + | |
| + | /* expand.h */ |
| + | |
| + | struct arglist { |
| + | struct strlist *list; |
| + | struct strlist **lastp; |
| + | }; |
| + | |
| + | /* |
| + | * expandarg() flags |
| + | */ |
| + | #define EXP_FULL 0x1 /* perform word splitting & file globbing */ |
| + | #define EXP_TILDE 0x2 /* do normal tilde expansion */ |
| + | #define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ |
| + | #define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ |
| + | #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ |
| + | #define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */ |
| + | #define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ |
| + | #define EXP_WORD 0x80 /* expand word in parameter expansion */ |
| + | #define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ |
| + | |
| + | |
| + | union node; |
| + | static void expandarg(union node *, struct arglist *, int); |
| + | #define rmescapes(p) _rmescapes((p), 0) |
| + | static char *_rmescapes(char *, int); |
| + | static int casematch(union node *, char *); |
| + | |
| + | #if ENABLE_ASH_MATH_SUPPORT |
| + | static void expari(int); |
| + | #endif |
| + | |
| + | /* eval.h */ |
| + | |
| + | |
| + | |
| + | struct backcmd { /* result of evalbackcmd */ |
| + | int fd; /* file descriptor to read from */ |
| + | char *buf; /* buffer */ |
| + | int nleft; /* number of chars in buffer */ |
| + | struct job *jp; /* job structure for command */ |
| + | }; |
| + | |
| + | /* |
| + | * This file was generated by the mknodes program. |
| + | */ |
| + | |
| + | #define NCMD 0 |
| + | #define NPIPE 1 |
| + | #define NREDIR 2 |
| + | #define NBACKGND 3 |
| + | #define NSUBSHELL 4 |
| + | #define NAND 5 |
| + | #define NOR 6 |
| + | #define NSEMI 7 |
| + | #define NIF 8 |
| + | #define NWHILE 9 |
| + | #define NUNTIL 10 |
| + | #define NFOR 11 |
| + | #define NCASE 12 |
| + | #define NCLIST 13 |
| + | #define NDEFUN 14 |
| + | #define NARG 15 |
| + | #define NTO 16 |
| + | #define NCLOBBER 17 |
| + | #define NFROM 18 |
| + | #define NFROMTO 19 |
| + | #define NAPPEND 20 |
| + | #define NTOFD 21 |
| + | #define NFROMFD 22 |
| + | #define NHERE 23 |
| + | #define NXHERE 24 |
| + | #define NNOT 25 |
| + | |
| + | |
| + | struct ncmd { |
| + | int type; |
| + | union node *assign; |
| + | union node *args; |
| + | union node *redirect; |
| + | }; |
| + | |
| + | struct npipe { |
| + | int type; |
| + | int backgnd; |
| + | struct nodelist *cmdlist; |
| + | }; |
| + | |
| + | struct nredir { |
| + | int type; |
| + | union node *n; |
| + | union node *redirect; |
| + | }; |
| + | |
| + | struct nbinary { |
| + | int type; |
| + | union node *ch1; |
| + | union node *ch2; |
| + | }; |
| + | |
| + | struct nif { |
| + | int type; |
| + | union node *test; |
| + | union node *ifpart; |
| + | union node *elsepart; |
| + | }; |
| + | |
| + | struct nfor { |
| + | int type; |
| + | union node *args; |
| + | union node *body; |
| + | char *var; |
| + | }; |
| + | |
| + | struct ncase { |
| + | int type; |
| + | union node *expr; |
| + | union node *cases; |
| + | }; |
| + | |
| + | struct nclist { |
| + | int type; |
| + | union node *next; |
| + | union node *pattern; |
| + | union node *body; |
| + | }; |
| + | |
| + | struct narg { |
| + | int type; |
| + | union node *next; |
| + | char *text; |
| + | struct nodelist *backquote; |
| + | }; |
| + | |
| + | struct nfile { |
| + | int type; |
| + | union node *next; |
| + | int fd; |
| + | union node *fname; |
| + | char *expfname; |
| + | }; |
| + | |
| + | struct ndup { |
| + | int type; |
| + | union node *next; |
| + | int fd; |
| + | int dupfd; |
| + | union node *vname; |
| + | }; |
| + | |
| + | struct nhere { |
| + | int type; |
| + | union node *next; |
| + | int fd; |
| + | union node *doc; |
| + | }; |
| + | |
| + | struct nnot { |
| + | int type; |
| + | union node *com; |
| + | }; |
| + | |
| + | union node { |
| + | int type; |
| + | struct ncmd ncmd; |
| + | struct npipe npipe; |
| + | struct nredir nredir; |
| + | struct nbinary nbinary; |
| + | struct nif nif; |
| + | struct nfor nfor; |
| + | struct ncase ncase; |
| + | struct nclist nclist; |
| + | struct narg narg; |
| + | struct nfile nfile; |
| + | struct ndup ndup; |
| + | struct nhere nhere; |
| + | struct nnot nnot; |
| + | }; |
| + | |
| + | struct nodelist { |
| + | struct nodelist *next; |
| + | union node *n; |
| + | }; |
| + | |
| + | struct funcnode { |
| + | int count; |
| + | union node n; |
| + | }; |
| + | |
| + | |
| + | static void freefunc(struct funcnode *); |
| + | /* parser.h */ |
| + | |
| + | /* control characters in argument strings */ |
| + | #define CTL_FIRST '\201' /* first 'special' character */ |
| + | #define CTLESC '\201' /* escape next character */ |
| + | #define CTLVAR '\202' /* variable defn */ |
| + | #define CTLENDVAR '\203' |
| + | #define CTLBACKQ '\204' |
| + | #define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ |
| + | /* CTLBACKQ | CTLQUOTE == '\205' */ |
| + | #define CTLARI '\206' /* arithmetic expression */ |
| + | #define CTLENDARI '\207' |
| + | #define CTLQUOTEMARK '\210' |
| + | #define CTL_LAST '\210' /* last 'special' character */ |
| + | |
| + | /* variable substitution byte (follows CTLVAR) */ |
| + | #define VSTYPE 0x0f /* type of variable substitution */ |
| + | #define VSNUL 0x10 /* colon--treat the empty string as unset */ |
| + | #define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ |
| + | |
| + | /* values of VSTYPE field */ |
| + | #define VSNORMAL 0x1 /* normal variable: $var or ${var} */ |
| + | #define VSMINUS 0x2 /* ${var-text} */ |
| + | #define VSPLUS 0x3 /* ${var+text} */ |
| + | #define VSQUESTION 0x4 /* ${var?message} */ |
| + | #define VSASSIGN 0x5 /* ${var=text} */ |
| + | #define VSTRIMRIGHT 0x6 /* ${var%pattern} */ |
| + | #define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ |
| + | #define VSTRIMLEFT 0x8 /* ${var#pattern} */ |
| + | #define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ |
| + | #define VSLENGTH 0xa /* ${#var} */ |
| + | |
| + | /* values of checkkwd variable */ |
| + | #define CHKALIAS 0x1 |
| + | #define CHKKWD 0x2 |
| + | #define CHKNL 0x4 |
| + | |
| + | #define IBUFSIZ (BUFSIZ + 1) |
| + | |
| + | /* |
| + | * NEOF is returned by parsecmd when it encounters an end of file. It |
| + | * must be distinct from NULL, so we use the address of a variable that |
| + | * happens to be handy. |
| + | */ |
| + | static int plinno = 1; /* input line number */ |
| + | |
| + | /* number of characters left in input buffer */ |
| + | static int parsenleft; /* copy of parsefile->nleft */ |
| + | static int parselleft; /* copy of parsefile->lleft */ |
| + | |
| + | /* next character in input buffer */ |
| + | static char *parsenextc; /* copy of parsefile->nextc */ |
| + | |
| + | #define basebuf bb_common_bufsiz1 /* buffer for top level input file */ |
| + | |
| + | |
| + | static int tokpushback; /* last token pushed back */ |
| + | #define NEOF ((union node *)&tokpushback) |
| + | static int parsebackquote; /* nonzero if we are inside backquotes */ |
| + | static int lasttoken; /* last token read */ |
| + | static char *wordtext; /* text of last word returned by readtoken */ |
| + | static int checkkwd; |
| + | static struct nodelist *backquotelist; |
| + | static union node *redirnode; |
| + | static struct heredoc *heredoc; |
| + | static int quoteflag; /* set if (part of) last token was quoted */ |
| + | |
| + | static void fixredir(union node *, const char *, int); |
| + | static char *endofname(const char *); |
| + | |
| + | /* shell.h */ |
| + | |
| + | static const char spcstr[] = " "; |
| + | static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' }; |
| + | |
| + | #if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96) |
| + | #define __builtin_expect(x, expected_value) (x) |
| + | #endif |
| + | |
| + | #define xlikely(x) __builtin_expect((x),1) |
| + | |
| + | |
| + | #define TEOF 0 |
| + | #define TNL 1 |
| + | #define TREDIR 2 |
| + | #define TWORD 3 |
| + | #define TSEMI 4 |
| + | #define TBACKGND 5 |
| + | #define TAND 6 |
| + | #define TOR 7 |
| + | #define TPIPE 8 |
| + | #define TLP 9 |
| + | #define TRP 10 |
| + | #define TENDCASE 11 |
| + | #define TENDBQUOTE 12 |
| + | #define TNOT 13 |
| + | #define TCASE 14 |
| + | #define TDO 15 |
| + | #define TDONE 16 |
| + | #define TELIF 17 |
| + | #define TELSE 18 |
| + | #define TESAC 19 |
| + | #define TFI 20 |
| + | #define TFOR 21 |
| + | #define TIF 22 |
| + | #define TIN 23 |
| + | #define TTHEN 24 |
| + | #define TUNTIL 25 |
| + | #define TWHILE 26 |
| + | #define TBEGIN 27 |
| + | #define TEND 28 |
| + | |
| + | /* first char is indicating which tokens mark the end of a list */ |
| + | static const char *const tokname_array[] = { |
| + | "\1end of file", |
| + | "\0newline", |
| + | "\0redirection", |
| + | "\0word", |
| + | "\0;", |
| + | "\0&", |
| + | "\0&&", |
| + | "\0||", |
| + | "\0|", |
| + | "\0(", |
| + | "\1)", |
| + | "\1;;", |
| + | "\1`", |
| + | #define KWDOFFSET 13 |
| + | /* the following are keywords */ |
| + | "\0!", |
| + | "\0case", |
| + | "\1do", |
| + | "\1done", |
| + | "\1elif", |
| + | "\1else", |
| + | "\1esac", |
| + | "\1fi", |
| + | "\0for", |
| + | "\0if", |
| + | "\0in", |
| + | "\1then", |
| + | "\0until", |
| + | "\0while", |
| + | "\0{", |
| + | "\1}", |
| + | }; |
| + | |
| + | static const char * |
| + | tokname(int tok) |
| + | { |
| + | static char buf[16]; |
| + | |
| + | if (tok >= TSEMI) |
| + | buf[0] = '"'; |
| + | sprintf(buf + (tok >= TSEMI), "%s%c", |
| + | tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); |
| + | return buf; |
| + | } |
| + | |
| + | /* Wrapper around strcmp for qsort/bsearch/... */ |
| + | static int |
| + | pstrcmp(const void *a, const void *b) |
| + | { |
| + | return strcmp((const char *) a, (*(const char *const *) b) + 1); |
| + | } |
| + | |
| + | static const char *const * |
| + | findkwd(const char *s) |
| + | { |
| + | return bsearch(s, tokname_array + KWDOFFSET, |
| + | (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET, |
| + | sizeof(const char *), pstrcmp); |
| + | } |
| + | |
| + | /* Syntax classes */ |
| + | #define CWORD 0 /* character is nothing special */ |
| + | #define CNL 1 /* newline character */ |
| + | #define CBACK 2 /* a backslash character */ |
| + | #define CSQUOTE 3 /* single quote */ |
| + | #define CDQUOTE 4 /* double quote */ |
| + | #define CENDQUOTE 5 /* a terminating quote */ |
| + | #define CBQUOTE 6 /* backwards single quote */ |
| + | #define CVAR 7 /* a dollar sign */ |
| + | #define CENDVAR 8 /* a '}' character */ |
| + | #define CLP 9 /* a left paren in arithmetic */ |
| + | #define CRP 10 /* a right paren in arithmetic */ |
| + | #define CENDFILE 11 /* end of file */ |
| + | #define CCTL 12 /* like CWORD, except it must be escaped */ |
| + | #define CSPCL 13 /* these terminate a word */ |
| + | #define CIGN 14 /* character should be ignored */ |
| + | |
| + | #if ENABLE_ASH_ALIAS |
| + | #define SYNBASE 130 |
| + | #define PEOF -130 |
| + | #define PEOA -129 |
| + | #define PEOA_OR_PEOF PEOA |
| + | #else |
| + | #define SYNBASE 129 |
| + | #define PEOF -129 |
| + | #define PEOA_OR_PEOF PEOF |
| + | #endif |
| + | |
| + | /* C99 say: "char" declaration may be signed or unsigned default */ |
| + | #define SC2INT(chr2may_be_negative_int) (int)((signed char)chr2may_be_negative_int) |
| + | |
| + | /* |
| + | * is_special(c) evaluates to 1 for c in "!#$*-0123456789?@"; 0 otherwise |
| + | * (assuming ascii char codes, as the original implementation did) |
| + | */ |
| + | #define is_special(c) \ |
| + | ( (((unsigned int)c) - 33 < 32) \ |
| + | && ((0xc1ff920dUL >> (((unsigned int)c) - 33)) & 1)) |
| + | |
| + | #define digit_val(c) ((c) - '0') |
| + | |
| + | /* |
| + | * This file was generated by the mksyntax program. |
| + | */ |
| + | |
| + | #if ENABLE_ASH_OPTIMIZE_FOR_SIZE |
| + | #define USE_SIT_FUNCTION |
| + | #endif |
| + | |
| + | /* number syntax index */ |
| + | #define BASESYNTAX 0 /* not in quotes */ |
| + | #define DQSYNTAX 1 /* in double quotes */ |
| + | #define SQSYNTAX 2 /* in single quotes */ |
| + | #define ARISYNTAX 3 /* in arithmetic */ |
| + | |
| + | #if ENABLE_ASH_MATH_SUPPORT |
| + | static const char S_I_T[][4] = { |
| + | #if ENABLE_ASH_ALIAS |
| + | {CSPCL, CIGN, CIGN, CIGN}, /* 0, PEOA */ |
| + | #endif |
| + | {CSPCL, CWORD, CWORD, CWORD}, /* 1, ' ' */ |
| + | {CNL, CNL, CNL, CNL}, /* 2, \n */ |
| + | {CWORD, CCTL, CCTL, CWORD}, /* 3, !*-/:=?[]~ */ |
| + | {CDQUOTE, CENDQUOTE, CWORD, CWORD}, /* 4, '"' */ |
| + | {CVAR, CVAR, CWORD, CVAR}, /* 5, $ */ |
| + | {CSQUOTE, CWORD, CENDQUOTE, CWORD}, /* 6, "'" */ |
| + | {CSPCL, CWORD, CWORD, CLP}, /* 7, ( */ |
| + | {CSPCL, CWORD, CWORD, CRP}, /* 8, ) */ |
| + | {CBACK, CBACK, CCTL, CBACK}, /* 9, \ */ |
| + | {CBQUOTE, CBQUOTE, CWORD, CBQUOTE}, /* 10, ` */ |
| + | {CENDVAR, CENDVAR, CWORD, CENDVAR}, /* 11, } */ |
| + | #ifndef USE_SIT_FUNCTION |
| + | {CENDFILE, CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ |
| + | {CWORD, CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ |
| + | {CCTL, CCTL, CCTL, CCTL} /* 14, CTLESC ... */ |
| + | #endif |
| + | }; |
| + | #else |
| + | static const char S_I_T[][3] = { |
| + | #if ENABLE_ASH_ALIAS |
| + | {CSPCL, CIGN, CIGN}, /* 0, PEOA */ |
| + | #endif |
| + | {CSPCL, CWORD, CWORD}, /* 1, ' ' */ |
| + | {CNL, CNL, CNL}, /* 2, \n */ |
| + | {CWORD, CCTL, CCTL}, /* 3, !*-/:=?[]~ */ |
| + | {CDQUOTE, CENDQUOTE, CWORD}, /* 4, '"' */ |
| + | {CVAR, CVAR, CWORD}, /* 5, $ */ |
| + | {CSQUOTE, CWORD, CENDQUOTE}, /* 6, "'" */ |
| + | {CSPCL, CWORD, CWORD}, /* 7, ( */ |
| + | {CSPCL, CWORD, CWORD}, /* 8, ) */ |
| + | {CBACK, CBACK, CCTL}, /* 9, \ */ |
| + | {CBQUOTE, CBQUOTE, CWORD}, /* 10, ` */ |
| + | {CENDVAR, CENDVAR, CWORD}, /* 11, } */ |
| + | #ifndef USE_SIT_FUNCTION |
| + | {CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ |
| + | {CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ |
| + | {CCTL, CCTL, CCTL} /* 14, CTLESC ... */ |
| + | #endif |
| + | }; |
| + | #endif /* ASH_MATH_SUPPORT */ |
| + | |
| + | #ifdef USE_SIT_FUNCTION |
| + | |
| + | #define U_C(c) ((unsigned char)(c)) |
| + | |
| + | static int |
| + | SIT(int c, int syntax) |
| + | { |
| + | static const char spec_symbls[] = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~"; |
| + | #if ENABLE_ASH_ALIAS |
| + | static const char syntax_index_table[] = { |
| + | 1, 2, 1, 3, 4, 5, 1, 6, /* "\t\n !\"$&'" */ |
| + | 7, 8, 3, 3, 3, 3, 1, 1, /* "()*-/:;<" */ |
| + | 3, 1, 3, 3, 9, 3, 10, 1, /* "=>?[\\]`|" */ |
| + | 11, 3 /* "}~" */ |
| + | }; |
| + | #else |
| + | static const char syntax_index_table[] = { |
| + | 0, 1, 0, 2, 3, 4, 0, 5, /* "\t\n !\"$&'" */ |
| + | 6, 7, 2, 2, 2, 2, 0, 0, /* "()*-/:;<" */ |
| + | 2, 0, 2, 2, 8, 2, 9, 0, /* "=>?[\\]`|" */ |
| + | 10, 2 /* "}~" */ |
| + | }; |
| + | #endif |
| + | const char *s; |
| + | int indx; |
| + | |
| + | if (c == PEOF) /* 2^8+2 */ |
| + | return CENDFILE; |
| + | #if ENABLE_ASH_ALIAS |
| + | if (c == PEOA) /* 2^8+1 */ |
| + | indx = 0; |
| + | else |
| + | #endif |
| + | if (U_C(c) >= U_C(CTLESC) && U_C(c) <= U_C(CTLQUOTEMARK)) |
| + | return CCTL; |
| + | else { |
| + | s = strchr(spec_symbls, c); |
| + | if (s == NULL || *s == '\0') |
| + | return CWORD; |
| + | indx = syntax_index_table[(s - spec_symbls)]; |
| + | } |
| + | return S_I_T[indx][syntax]; |
| + | } |
| + | |
| + | #else /* USE_SIT_FUNCTION */ |
| + | |
| + | #define SIT(c, syntax) S_I_T[(int)syntax_index_table[((int)c)+SYNBASE]][syntax] |
| + | |
| + | #if ENABLE_ASH_ALIAS |
| + | #define CSPCL_CIGN_CIGN_CIGN 0 |
| + | #define CSPCL_CWORD_CWORD_CWORD 1 |
| + | #define CNL_CNL_CNL_CNL 2 |
| + | #define CWORD_CCTL_CCTL_CWORD 3 |
| + | #define CDQUOTE_CENDQUOTE_CWORD_CWORD 4 |
| + | #define CVAR_CVAR_CWORD_CVAR 5 |
| + | #define CSQUOTE_CWORD_CENDQUOTE_CWORD 6 |
| + | #define CSPCL_CWORD_CWORD_CLP 7 |
| + | #define CSPCL_CWORD_CWORD_CRP 8 |
| + | #define CBACK_CBACK_CCTL_CBACK 9 |
| + | #define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 10 |
| + | #define CENDVAR_CENDVAR_CWORD_CENDVAR 11 |
| + | #define CENDFILE_CENDFILE_CENDFILE_CENDFILE 12 |
| + | #define CWORD_CWORD_CWORD_CWORD 13 |
| + | #define CCTL_CCTL_CCTL_CCTL 14 |
| + | #else |
| + | #define CSPCL_CWORD_CWORD_CWORD 0 |
| + | #define CNL_CNL_CNL_CNL 1 |
| + | #define CWORD_CCTL_CCTL_CWORD 2 |
| + | #define CDQUOTE_CENDQUOTE_CWORD_CWORD 3 |
| + | #define CVAR_CVAR_CWORD_CVAR 4 |
| + | #define CSQUOTE_CWORD_CENDQUOTE_CWORD 5 |
| + | #define CSPCL_CWORD_CWORD_CLP 6 |
| + | #define CSPCL_CWORD_CWORD_CRP 7 |
| + | #define CBACK_CBACK_CCTL_CBACK 8 |
| + | #define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 9 |
| + | #define CENDVAR_CENDVAR_CWORD_CENDVAR 10 |
| + | #define CENDFILE_CENDFILE_CENDFILE_CENDFILE 11 |
| + | #define CWORD_CWORD_CWORD_CWORD 12 |
| + | #define CCTL_CCTL_CCTL_CCTL 13 |
| + | #endif |
| + | |
| + | static const char syntax_index_table[258] = { |
| + | /* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */ |
| /* 0 PEOF */ CENDFILE_CENDFILE_CENDFILE_CENDFILE, | |
| #if ENABLE_ASH_ALIAS | |
| /* 1 PEOA */ CSPCL_CIGN_CIGN_CIGN, | |
| @@ -1743,13 +2788,6 @@ static int ulimitcmd(int, char **); | |
| static int killcmd(int, char **); | |
| #endif | |
| - | /* mail.h */ |
| - | |
| - | #if ENABLE_ASH_MAIL |
| - | static void chkmail(void); |
| - | static void changemail(const char *); |
| - | #endif |
| - | |
| /* exec.h */ | |
| /* values of cmdtype */ | |
| @@ -1879,14 +2917,10 @@ struct cmdentry { | |
| #define DO_ALTPATH 0x08 /* using alternate path */ | |
| #define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ | |
| - | static const char *pathopt; /* set by padvance */ |
| - | |
| static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN; | |
| static char *padvance(const char **, const char *); | |
| static void find_command(char *, struct cmdentry *, int, const char *); | |
| static struct builtincmd *find_builtin(const char *); | |
| - | static void hashcd(void); |
| - | static void changepath(const char *); |
| static void defun(char *, union node *); | |
| static void unsetfunc(const char *); | |
| @@ -1905,222 +2939,11 @@ static arith_t arith(const char *expr, int *perrcode); | |
| #if ENABLE_ASH_RANDOM_SUPPORT | |
| static unsigned long rseed; | |
| - | static void change_random(const char *); |
| # ifndef DYNAMIC_VAR | |
| # define DYNAMIC_VAR | |
| # endif | |
| #endif | |
| - | /* var.h */ |
| - | |
| - | /* |
| - | * Shell variables. |
| - | */ |
| - | |
| - | #if ENABLE_ASH_GETOPTS |
| - | static void getoptsreset(const char *); |
| - | #endif |
| - | |
| - | /* flags */ |
| - | #define VEXPORT 0x01 /* variable is exported */ |
| - | #define VREADONLY 0x02 /* variable cannot be modified */ |
| - | #define VSTRFIXED 0x04 /* variable struct is statically allocated */ |
| - | #define VTEXTFIXED 0x08 /* text is statically allocated */ |
| - | #define VSTACK 0x10 /* text is allocated on the stack */ |
| - | #define VUNSET 0x20 /* the variable is not set */ |
| - | #define VNOFUNC 0x40 /* don't call the callback function */ |
| - | #define VNOSET 0x80 /* do not set variable - just readonly test */ |
| - | #define VNOSAVE 0x100 /* when text is on the heap before setvareq */ |
| - | #ifdef DYNAMIC_VAR |
| - | # define VDYNAMIC 0x200 /* dynamic variable */ |
| - | # else |
| - | # define VDYNAMIC 0 |
| - | #endif |
| - | |
| - | struct var { |
| - | struct var *next; /* next entry in hash list */ |
| - | int flags; /* flags are defined above */ |
| - | const char *text; /* name=value */ |
| - | void (*func)(const char *); /* function to be called when */ |
| - | /* the variable gets set/unset */ |
| - | }; |
| - | |
| - | struct localvar { |
| - | struct localvar *next; /* next local variable in list */ |
| - | struct var *vp; /* the variable that was made local */ |
| - | int flags; /* saved flags */ |
| - | const char *text; /* saved text */ |
| - | }; |
| - | |
| - | |
| - | static struct localvar *localvars; |
| - | |
| - | /* |
| - | * Shell variables. |
| - | */ |
| - | #if ENABLE_LOCALE_SUPPORT |
| - | static void change_lc_all(const char *value); |
| - | static void change_lc_ctype(const char *value); |
| - | #endif |
| - | |
| - | |
| - | #define VTABSIZE 39 |
| - | |
| - | static const char defpathvar[] = "PATH=/usr/local/bin:/usr/bin:/sbin:/bin"; |
| - | #ifdef IFS_BROKEN |
| - | static const char defifsvar[] = "IFS= \t\n"; |
| - | #define defifs (defifsvar + 4) |
| - | #else |
| - | static const char defifs[] = " \t\n"; |
| - | #endif |
| - | |
| - | |
| - | static struct var varinit[] = { |
| - | #ifdef IFS_BROKEN |
| - | { 0, VSTRFIXED|VTEXTFIXED, defifsvar, 0 }, |
| - | #else |
| - | { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "IFS\0", 0 }, |
| - | #endif |
| - | |
| - | #if ENABLE_ASH_MAIL |
| - | { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0", changemail }, |
| - | { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail }, |
| - | #endif |
| - | |
| - | { 0, VSTRFIXED|VTEXTFIXED, defpathvar, changepath }, |
| - | { 0, VSTRFIXED|VTEXTFIXED, "PS1=$ ", 0 }, |
| - | { 0, VSTRFIXED|VTEXTFIXED, "PS2=> ", 0 }, |
| - | { 0, VSTRFIXED|VTEXTFIXED, "PS4=+ ", 0 }, |
| - | #if ENABLE_ASH_GETOPTS |
| - | { 0, VSTRFIXED|VTEXTFIXED, "OPTIND=1", getoptsreset }, |
| - | #endif |
| - | #if ENABLE_ASH_RANDOM_SUPPORT |
| - | {0, VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random }, |
| - | #endif |
| - | #if ENABLE_LOCALE_SUPPORT |
| - | {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_ALL\0", change_lc_all }, |
| - | {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_CTYPE\0", change_lc_ctype }, |
| - | #endif |
| - | #if ENABLE_FEATURE_EDITING_SAVEHISTORY |
| - | {0, VSTRFIXED | VTEXTFIXED | VUNSET, "HISTFILE\0", NULL }, |
| - | #endif |
| - | }; |
| - | |
| - | #define vifs varinit[0] |
| - | #if ENABLE_ASH_MAIL |
| - | #define vmail (&vifs)[1] |
| - | #define vmpath (&vmail)[1] |
| - | #else |
| - | #define vmpath vifs |
| - | #endif |
| - | #define vpath (&vmpath)[1] |
| - | #define vps1 (&vpath)[1] |
| - | #define vps2 (&vps1)[1] |
| - | #define vps4 (&vps2)[1] |
| - | #define voptind (&vps4)[1] |
| - | #if ENABLE_ASH_GETOPTS |
| - | #define vrandom (&voptind)[1] |
| - | #else |
| - | #define vrandom (&vps4)[1] |
| - | #endif |
| - | #define defpath (defpathvar + 5) |
| - | |
| - | /* |
| - | * The following macros access the values of the above variables. |
| - | * They have to skip over the name. They return the null string |
| - | * for unset variables. |
| - | */ |
| - | |
| - | #define ifsval() (vifs.text + 4) |
| - | #define ifsset() ((vifs.flags & VUNSET) == 0) |
| - | #define mailval() (vmail.text + 5) |
| - | #define mpathval() (vmpath.text + 9) |
| - | #define pathval() (vpath.text + 5) |
| - | #define ps1val() (vps1.text + 4) |
| - | #define ps2val() (vps2.text + 4) |
| - | #define ps4val() (vps4.text + 4) |
| - | #define optindval() (voptind.text + 7) |
| - | |
| - | #define mpathset() ((vmpath.flags & VUNSET) == 0) |
| - | |
| - | static void setvar(const char *, const char *, int); |
| - | static void setvareq(char *, int); |
| - | static void listsetvar(struct strlist *, int); |
| - | static char *lookupvar(const char *); |
| - | static char *bltinlookup(const char *); |
| - | static char **listvars(int, int, char ***); |
| - | #define environment() listvars(VEXPORT, VUNSET, 0) |
| - | static int showvars(const char *, int, int); |
| - | static void poplocalvars(void); |
| - | static int unsetvar(const char *); |
| - | #if ENABLE_ASH_GETOPTS |
| - | static int setvarsafe(const char *, const char *, int); |
| - | #endif |
| - | static int varcmp(const char *, const char *); |
| - | static struct var **hashvar(const char *); |
| - | |
| - | |
| - | static int varequal(const char *a, const char *b) |
| - | { |
| - | return !varcmp(a, b); |
| - | } |
| - | |
| - | |
| - | static int loopnest; /* current loop nesting level */ |
| - | |
| - | /* |
| - | * The parsefile structure pointed to by the global variable parsefile |
| - | * contains information about the current file being read. |
| - | */ |
| - | |
| - | |
| - | struct redirtab { |
| - | struct redirtab *next; |
| - | int renamed[10]; |
| - | int nullredirs; |
| - | }; |
| - | |
| - | static struct redirtab *redirlist; |
| - | static int nullredirs; |
| - | |
| - | extern char **environ; |
| - | |
| - | |
| - | static int preverrout_fd; /* save fd2 before print debug if xflag is set. */ |
| - | |
| - | |
| - | /* |
| - | * Initialization code. |
| - | */ |
| - | |
| - | /* |
| - | * This routine initializes the builtin variables. |
| - | */ |
| - | |
| - | static void initvar(void) |
| - | { |
| - | struct var *vp; |
| - | struct var *end; |
| - | struct var **vpp; |
| - | |
| - | /* |
| - | * PS1 depends on uid |
| - | */ |
| - | #if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT |
| - | vps1.text = "PS1=\\w \\$ "; |
| - | #else |
| - | if (!geteuid()) |
| - | vps1.text = "PS1=# "; |
| - | #endif |
| - | vp = varinit; |
| - | end = vp + sizeof(varinit) / sizeof(varinit[0]); |
| - | do { |
| - | vpp = hashvar(vp->text); |
| - | vp->next = *vpp; |
| - | *vpp = vp; |
| - | } while (++vp < end); |
| - | } |
| - | |
| /* PEOF (the end of file marker) */ | |
| enum { | |
| @@ -2133,10 +2956,6 @@ enum { | |
| * and restores it when files are pushed and popped. The user of this | |
| * package must set its value. | |
| */ | |
| - | |
| - | static int pgetc(void); |
| - | static int pgetc2(void); |
| - | static int preadbuffer(void); |
| static void pungetc(void); | |
| static void pushstring(char *, void *); | |
| static void popstring(void); | |
| @@ -2146,7 +2965,6 @@ static void popfile(void); | |
| static void popallfiles(void); | |
| static void closescript(void); | |
| - | |
| /* jobs.h */ | |
| @@ -2218,36 +3036,7 @@ static void showjobs(FILE *, int); | |
| static void readcmdfile(char *); | |
| - | |
| - | |
| - | static char *_STPUTC(int c, char *p) |
| - | { |
| - | if (p == sstrend) |
| - | p = growstackstr(); |
| - | *p++ = c; |
| - | return p; |
| - | } |
| - | |
| - | #define STARTSTACKSTR(p) ((p) = stackblock()) |
| - | #define STPUTC(c, p) ((p) = _STPUTC((c), (p))) |
| - | #define CHECKSTRSPACE(n, p) \ |
| - | ({ \ |
| - | char *q = (p); \ |
| - | size_t l = (n); \ |
| - | size_t m = sstrend - q; \ |
| - | if (l > m) \ |
| - | (p) = makestrspace(l, q); \ |
| - | 0; \ |
| - | }) |
| - | #define USTPUTC(c, p) (*p++ = (c)) |
| - | #define STACKSTRNUL(p) ((p) == sstrend? (p = growstackstr(), *p = '\0') : (*p = '\0')) |
| - | #define STUNPUTC(p) (--p) |
| - | #define STTOPC(p) p[-1] |
| - | #define STADJUST(amount, p) (p += (amount)) |
| - | |
| - | #define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock()) |
| - | #define ungrabstackstr(s, p) stunalloc((s)) |
| - | #define stackstrend() ((void *)sstrend) |
| + | |
| /* mystring.h */ | |
| @@ -2258,29 +3047,12 @@ static char *prefix(const char *, const char *); | |
| static int number(const char *); | |
| static int is_number(const char *); | |
| static char *single_quote(const char *); | |
| - | static char *sstrdup(const char *); |
| #define equal(s1, s2) (strcmp(s1, s2) == 0) | |
| #define scopy(s1, s2) ((void)strcpy(s2, s1)) | |
| /* options.h */ | |
| - | struct shparam { |
| - | int nparam; /* # of positional parameters (without $0) */ |
| - | unsigned char malloc; /* if parameter list dynamically allocated */ |
| - | char **p; /* parameter list */ |
| - | #if ENABLE_ASH_GETOPTS |
| - | int optind; /* next parameter to be processed by getopts */ |
| - | int optoff; /* used by getopts */ |
| - | #endif |
| - | }; |
| - | |
| - | |
| - | static struct shparam shellparam; /* $@ current positional parameters */ |
| - | static char **argptr; /* argument list for builtin commands */ |
| - | static char *optionarg; /* set by nextopt (like getopt) */ |
| - | static char *optptr; /* used by nextopt */ |
| - | |
| static char *minusc; /* argument to -c option */ | |
| @@ -2550,250 +3322,6 @@ __lookupalias(const char *name) { | |
| } | |
| #endif /* ASH_ALIAS */ | |
| - | |
| - | /* cd.c */ |
| - | |
| - | /* |
| - | * The cd and pwd commands. |
| - | */ |
| - | |
| - | #define CD_PHYSICAL 1 |
| - | #define CD_PRINT 2 |
| - | |
| - | static int docd(const char *, int); |
| - | static int cdopt(void); |
| - | |
| - | static char *curdir = nullstr; /* current working directory */ |
| - | static char *physdir = nullstr; /* physical working directory */ |
| - | |
| - | static int |
| - | cdopt(void) |
| - | { |
| - | int flags = 0; |
| - | int i, j; |
| - | |
| - | j = 'L'; |
| - | while ((i = nextopt("LP"))) { |
| - | if (i != j) { |
| - | flags ^= CD_PHYSICAL; |
| - | j = i; |
| - | } |
| - | } |
| - | |
| - | return flags; |
| - | } |
| - | |
| - | static int |
| - | cdcmd(int argc, char **argv) |
| - | { |
| - | const char *dest; |
| - | const char *path; |
| - | const char *p; |
| - | char c; |
| - | struct stat statb; |
| - | int flags; |
| - | |
| - | flags = cdopt(); |
| - | dest = *argptr; |
| - | if (!dest) |
| - | dest = bltinlookup(homestr); |
| - | else if (LONE_DASH(dest)) { |
| - | dest = bltinlookup("OLDPWD"); |
| - | flags |= CD_PRINT; |
| - | } |
| - | if (!dest) |
| - | dest = nullstr; |
| - | if (*dest == '/') |
| - | goto step7; |
| - | if (*dest == '.') { |
| - | c = dest[1]; |
| - | dotdot: |
| - | switch (c) { |
| - | case '\0': |
| - | case '/': |
| - | goto step6; |
| - | case '.': |
| - | c = dest[2]; |
| - | if (c != '.') |
| - | goto dotdot; |
| - | } |
| - | } |
| - | if (!*dest) |
| - | dest = "."; |
| - | path = bltinlookup("CDPATH"); |
| - | if (!path) { |
| - | step6: |
| - | step7: |
| - | p = dest; |
| - | goto docd; |
| - | } |
| - | do { |
| - | c = *path; |
| - | p = padvance(&path, dest); |
| - | if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { |
| - | if (c && c != ':') |
| - | flags |= CD_PRINT; |
| - | docd: |
| - | if (!docd(p, flags)) |
| - | goto out; |
| - | break; |
| - | } |
| - | } while (path); |
| - | ash_msg_and_raise_error("can't cd to %s", dest); |
| - | /* NOTREACHED */ |
| - | out: |
| - | if (flags & CD_PRINT) |
| - | out1fmt(snlfmt, curdir); |
| - | return 0; |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Update curdir (the name of the current directory) in response to a |
| - | * cd command. |
| - | */ |
| - | static const char * updatepwd(const char *dir) |
| - | { |
| - | char *new; |
| - | char *p; |
| - | char *cdcomppath; |
| - | const char *lim; |
| - | |
| - | cdcomppath = sstrdup(dir); |
| - | STARTSTACKSTR(new); |
| - | if (*dir != '/') { |
| - | if (curdir == nullstr) |
| - | return 0; |
| - | new = stack_putstr(curdir, new); |
| - | } |
| - | new = makestrspace(strlen(dir) + 2, new); |
| - | lim = stackblock() + 1; |
| - | if (*dir != '/') { |
| - | if (new[-1] != '/') |
| - | USTPUTC('/', new); |
| - | if (new > lim && *lim == '/') |
| - | lim++; |
| - | } else { |
| - | USTPUTC('/', new); |
| - | cdcomppath++; |
| - | if (dir[1] == '/' && dir[2] != '/') { |
| - | USTPUTC('/', new); |
| - | cdcomppath++; |
| - | lim++; |
| - | } |
| - | } |
| - | p = strtok(cdcomppath, "/"); |
| - | while (p) { |
| - | switch (*p) { |
| - | case '.': |
| - | if (p[1] == '.' && p[2] == '\0') { |
| - | while (new > lim) { |
| - | STUNPUTC(new); |
| - | if (new[-1] == '/') |
| - | break; |
| - | } |
| - | break; |
| - | } else if (p[1] == '\0') |
| - | break; |
| - | /* fall through */ |
| - | default: |
| - | new = stack_putstr(p, new); |
| - | USTPUTC('/', new); |
| - | } |
| - | p = strtok(0, "/"); |
| - | } |
| - | if (new > lim) |
| - | STUNPUTC(new); |
| - | *new = 0; |
| - | return stackblock(); |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Actually do the chdir. We also call hashcd to let the routines in exec.c |
| - | * know that the current directory has changed. |
| - | */ |
| - | static int |
| - | docd(const char *dest, int flags) |
| - | { |
| - | const char *dir = 0; |
| - | int err; |
| - | |
| - | TRACE(("docd(\"%s\", %d) called\n", dest, flags)); |
| - | |
| - | INT_OFF; |
| - | if (!(flags & CD_PHYSICAL)) { |
| - | dir = updatepwd(dest); |
| - | if (dir) |
| - | dest = dir; |
| - | } |
| - | err = chdir(dest); |
| - | if (err) |
| - | goto out; |
| - | setpwd(dir, 1); |
| - | hashcd(); |
| - | out: |
| - | INT_ON; |
| - | return err; |
| - | } |
| - | |
| - | /* |
| - | * Find out what the current directory is. If we already know the current |
| - | * directory, this routine returns immediately. |
| - | */ |
| - | static char * getpwd(void) |
| - | { |
| - | char *dir = getcwd(0, 0); |
| - | return dir ? dir : nullstr; |
| - | } |
| - | |
| - | static int |
| - | pwdcmd(int argc, char **argv) |
| - | { |
| - | int flags; |
| - | const char *dir = curdir; |
| - | |
| - | flags = cdopt(); |
| - | if (flags) { |
| - | if (physdir == nullstr) |
| - | setpwd(dir, 0); |
| - | dir = physdir; |
| - | } |
| - | out1fmt(snlfmt, dir); |
| - | return 0; |
| - | } |
| - | |
| - | static void |
| - | setpwd(const char *val, int setold) |
| - | { |
| - | char *oldcur, *dir; |
| - | |
| - | oldcur = dir = curdir; |
| - | |
| - | if (setold) { |
| - | setvar("OLDPWD", oldcur, VEXPORT); |
| - | } |
| - | INT_OFF; |
| - | if (physdir != nullstr) { |
| - | if (physdir != oldcur) |
| - | free(physdir); |
| - | physdir = nullstr; |
| - | } |
| - | if (oldcur == val || !val) { |
| - | char *s = getpwd(); |
| - | physdir = s; |
| - | if (!val) |
| - | dir = s; |
| - | } else |
| - | dir = ckstrdup(val); |
| - | if (oldcur != dir && oldcur != nullstr) { |
| - | free(oldcur); |
| - | } |
| - | curdir = dir; |
| - | INT_ON; |
| - | setvar("PWD", dir, VEXPORT); |
| - | } |
| - | |
| /* eval.c */ | |
| /* | |
| @@ -3235,7 +3763,8 @@ evalbackcmd(union node *n, struct backcmd *result) | |
| } | |
| #if ENABLE_ASH_CMDCMD | |
| - | static char ** parse_command_args(char **argv, const char **path) |
| + | static char ** |
| + | parse_command_args(char **argv, const char **path) |
| { | |
| char *cp, c; | |
| @@ -3275,12 +3804,6 @@ static int isassignment(const char *p) | |
| return *q == '='; | |
| } | |
| - | #if ENABLE_ASH_EXPAND_PRMT |
| - | static const char *expandstr(const char *ps); |
| - | #else |
| - | #define expandstr(s) s |
| - | #endif |
| - | |
| /* | |
| * Execute a simple command. | |
| */ | |
| @@ -3543,6 +4066,40 @@ evalbltin(const struct builtincmd *cmd, int argc, char **argv) | |
| return i; | |
| } | |
| + | static struct localvar *localvars; |
| + | |
| + | /* |
| + | * Called after a function returns. |
| + | * Interrupts must be off. |
| + | */ |
| + | static void |
| + | poplocalvars(void) |
| + | { |
| + | struct localvar *lvp; |
| + | struct var *vp; |
| + | |
| + | while ((lvp = localvars) != NULL) { |
| + | localvars = lvp->next; |
| + | vp = lvp->vp; |
| + | TRACE(("poplocalvar %s", vp ? vp->text : "-")); |
| + | if (vp == NULL) { /* $- saved */ |
| + | memcpy(optlist, lvp->text, sizeof(optlist)); |
| + | free((char*)lvp->text); |
| + | optschanged(); |
| + | } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { |
| + | unsetvar(vp->text); |
| + | } else { |
| + | if (vp->func) |
| + | (*vp->func)(strchrnul(lvp->text, '=') + 1); |
| + | if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) |
| + | free((char*)vp->text); |
| + | vp->flags = lvp->flags; |
| + | vp->text = lvp->text; |
| + | } |
| + | free(lvp); |
| + | } |
| + | } |
| + | |
| static int | |
| evalfun(struct funcnode *func, int argc, char **argv, int flags) | |
| { | |
| @@ -3588,7 +4145,8 @@ funcdone: | |
| } | |
| - | static int goodname(const char *p) |
| + | static int |
| + | goodname(const char *p) |
| { | |
| return !*endofname(p); | |
| } | |
| @@ -3738,6 +4296,7 @@ static void delete_cmd_entry(void); | |
| * Exec a program. Never returns. If you change this routine, you may | |
| * have to change the find_command routine as well. | |
| */ | |
| + | #define environment() listvars(VEXPORT, VUNSET, 0) |
| static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN; | |
| static void | |
| shellexec(char **argv, const char *path, int idx) | |
| @@ -3845,50 +4404,6 @@ tryexec(char *cmd, char **argv, char **envp) | |
| } | |
| - | /* |
| - | * Do a path search. The variable path (passed by reference) should be |
| - | * set to the start of the path before the first call; padvance will update |
| - | * this value as it proceeds. Successive calls to padvance will return |
| - | * the possible path expansions in sequence. If an option (indicated by |
| - | * a percent sign) appears in the path entry then the global variable |
| - | * pathopt will be set to point to it; otherwise pathopt will be set to |
| - | * NULL. |
| - | */ |
| - | static char * |
| - | padvance(const char **path, const char *name) |
| - | { |
| - | const char *p; |
| - | char *q; |
| - | const char *start; |
| - | size_t len; |
| - | |
| - | if (*path == NULL) |
| - | return NULL; |
| - | start = *path; |
| - | for (p = start; *p && *p != ':' && *p != '%'; p++); |
| - | len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ |
| - | while (stackblocksize() < len) |
| - | growstackblock(); |
| - | q = stackblock(); |
| - | if (p != start) { |
| - | memcpy(q, start, p - start); |
| - | q += p - start; |
| - | *q++ = '/'; |
| - | } |
| - | strcpy(q, name); |
| - | pathopt = NULL; |
| - | if (*p == '%') { |
| - | pathopt = ++p; |
| - | while (*p && *p != ':') p++; |
| - | } |
| - | if (*p == ':') |
| - | *path = p + 1; |
| - | else |
| - | *path = NULL; |
| - | return stalloc(len); |
| - | } |
| - | |
| - | |
| /*** Command hashing code ***/ | |
| static void | |
| @@ -5652,7 +6167,7 @@ addfname(const char *name) | |
| struct strlist *sp; | |
| sp = stalloc(sizeof(*sp)); | |
| - | sp->text = sstrdup(name); |
| + | sp->text = ststrdup(name); |
| *exparg.lastp = sp; | |
| exparg.lastp = &sp->next; | |
| } | |
| @@ -5987,8 +6502,7 @@ static void pushfile(void); | |
| * Read a character from the script, returning PEOF on end of file. | |
| * Nul characters in the input are silently discarded. | |
| */ | |
| - | |
| - | |
| + | static int preadbuffer(void); |
| #define pgetc_as_macro() (--parsenleft >= 0? SC2INT(*parsenextc++) : preadbuffer()) | |
| #if ENABLE_ASH_OPTIMIZE_FOR_SIZE | |
| @@ -6007,12 +6521,12 @@ pgetc(void) | |
| } | |
| #endif | |
| - | |
| /* | |
| * Same as pgetc(), but ignores PEOA. | |
| */ | |
| #if ENABLE_ASH_ALIAS | |
| - | static int pgetc2(void) |
| + | static int |
| + | pgetc2(void) |
| { | |
| int c; | |
| @@ -6022,7 +6536,8 @@ static int pgetc2(void) | |
| return c; | |
| } | |
| #else | |
| - | static int pgetc2(void) |
| + | static int |
| + | pgetc2(void) |
| { | |
| return pgetc_macro(); | |
| } | |
| @@ -6031,8 +6546,8 @@ static int pgetc2(void) | |
| /* | |
| * Read a line from the script. | |
| */ | |
| - | |
| - | static char * pfgets(char *line, int len) |
| + | static char * |
| + | pfgets(char *line, int len) |
| { | |
| char *p = line; | |
| int nleft = len; | |
| @@ -6053,27 +6568,6 @@ static char * pfgets(char *line, int len) | |
| return line; | |
| } | |
| - | |
| - | #if ENABLE_FEATURE_EDITING |
| - | static line_input_t *line_input_state; |
| - | //static SKIP_ASH_EXPAND_PRMT(const) char *cmdedit_prompt; |
| - | static const char *cmdedit_prompt; |
| - | static void putprompt(const char *s) |
| - | { |
| - | if (ENABLE_ASH_EXPAND_PRMT) { |
| - | free((char*)cmdedit_prompt); |
| - | cmdedit_prompt = xstrdup(s); |
| - | return; |
| - | } |
| - | cmdedit_prompt = s; |
| - | } |
| - | #else |
| - | static void putprompt(const char *s) |
| - | { |
| - | out2str(s); |
| - | } |
| - | #endif |
| - | |
| #if ENABLE_FEATURE_EDITING_VI | |
| #define setvimode(on) do { \ | |
| if (on) line_input_state->flags |= VI_MODE; \ | |
| @@ -6083,8 +6577,8 @@ static void putprompt(const char *s) | |
| #define setvimode(on) viflag = 0 /* forcibly keep the option off */ | |
| #endif | |
| - | |
| - | static int preadfd(void) |
| + | static int |
| + | preadfd(void) |
| { | |
| int nr; | |
| char *buf = parsefile->buf; | |
| @@ -7838,187 +8332,80 @@ chkmail(void) | |
| { | |
| const char *mpath; | |
| char *p; | |
| - | char *q; |
| - | time_t *mtp; |
| - | struct stackmark smark; |
| - | struct stat statb; |
| - | |
| - | setstackmark(&smark); |
| - | mpath = mpathset() ? mpathval() : mailval(); |
| - | for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) { |
| - | p = padvance(&mpath, nullstr); |
| - | if (p == NULL) |
| - | break; |
| - | if (*p == '\0') |
| - | continue; |
| - | for (q = p; *q; q++); |
| - | #if DEBUG |
| - | if (q[-1] != '/') |
| - | abort(); |
| - | #endif |
| - | q[-1] = '\0'; /* delete trailing '/' */ |
| - | if (stat(p, &statb) < 0) { |
| - | *mtp = 0; |
| - | continue; |
| - | } |
| - | if (!mail_var_path_changed && statb.st_mtime != *mtp) { |
| - | fprintf( |
| - | stderr, snlfmt, |
| - | pathopt ? pathopt : "you have mail" |
| - | ); |
| - | } |
| - | *mtp = statb.st_mtime; |
| - | } |
| - | mail_var_path_changed = 0; |
| - | popstackmark(&smark); |
| - | } |
| - | |
| - | |
| - | static void |
| - | changemail(const char *val) |
| - | { |
| - | mail_var_path_changed++; |
| - | } |
| - | |
| - | #endif /* ASH_MAIL */ |
| - | |
| - | /* |
| - | * Take commands from a file. To be compatible we should do a path |
| - | * search for the file, which is necessary to find sub-commands. |
| - | */ |
| - | static char * |
| - | find_dot_file(char *name) |
| - | { |
| - | char *fullname; |
| - | const char *path = pathval(); |
| - | struct stat statb; |
| - | |
| - | /* don't try this for absolute or relative paths */ |
| - | if (strchr(name, '/')) |
| - | return name; |
| - | |
| - | while ((fullname = padvance(&path, name)) != NULL) { |
| - | if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { |
| - | /* |
| - | * Don't bother freeing here, since it will |
| - | * be freed by the caller. |
| - | */ |
| - | return fullname; |
| - | } |
| - | stunalloc(fullname); |
| - | } |
| - | |
| - | /* not found in the PATH */ |
| - | ash_msg_and_raise_error("%s: not found", name); |
| - | /* NOTREACHED */ |
| - | } |
| - | |
| - | /* mystring.c */ |
| - | |
| - | /* |
| - | * String functions. |
| - | * |
| - | * number(s) Convert a string of digits to an integer. |
| - | * is_number(s) Return true if s is a string of digits. |
| - | */ |
| - | |
| - | /* |
| - | * prefix -- see if pfx is a prefix of string. |
| - | */ |
| - | static char * |
| - | prefix(const char *string, const char *pfx) |
| - | { |
| - | while (*pfx) { |
| - | if (*pfx++ != *string++) |
| - | return 0; |
| - | } |
| - | return (char *) string; |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Convert a string of digits to an integer, printing an error message on |
| - | * failure. |
| - | */ |
| - | static int |
| - | number(const char *s) |
| - | { |
| - | if (!is_number(s)) |
| - | ash_msg_and_raise_error(illnum, s); |
| - | return atoi(s); |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Check for a valid number. This should be elsewhere. |
| - | */ |
| - | static int |
| - | is_number(const char *p) |
| - | { |
| - | do { |
| - | if (!is_digit(*p)) |
| - | return 0; |
| - | } while (*++p != '\0'); |
| - | return 1; |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Produce a possibly single quoted string suitable as input to the shell. |
| - | * The return string is allocated on the stack. |
| - | */ |
| - | static char * |
| - | single_quote(const char *s) |
| - | { |
| - | char *p; |
| - | |
| - | STARTSTACKSTR(p); |
| - | |
| - | do { |
| - | char *q; |
| - | size_t len; |
| - | |
| - | len = strchrnul(s, '\'') - s; |
| - | |
| - | q = p = makestrspace(len + 3, p); |
| - | |
| - | *q++ = '\''; |
| - | q = memcpy(q, s, len) + len; |
| - | *q++ = '\''; |
| - | s += len; |
| - | |
| - | STADJUST(q - p, p); |
| + | char *q; |
| + | time_t *mtp; |
| + | struct stackmark smark; |
| + | struct stat statb; |
| - | len = strspn(s, "'"); |
| - | if (!len) |
| + | setstackmark(&smark); |
| + | mpath = mpathset() ? mpathval() : mailval(); |
| + | for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) { |
| + | p = padvance(&mpath, nullstr); |
| + | if (p == NULL) |
| break; | |
| + | if (*p == '\0') |
| + | continue; |
| + | for (q = p; *q; q++); |
| + | #if DEBUG |
| + | if (q[-1] != '/') |
| + | abort(); |
| + | #endif |
| + | q[-1] = '\0'; /* delete trailing '/' */ |
| + | if (stat(p, &statb) < 0) { |
| + | *mtp = 0; |
| + | continue; |
| + | } |
| + | if (!mail_var_path_changed && statb.st_mtime != *mtp) { |
| + | fprintf( |
| + | stderr, snlfmt, |
| + | pathopt ? pathopt : "you have mail" |
| + | ); |
| + | } |
| + | *mtp = statb.st_mtime; |
| + | } |
| + | mail_var_path_changed = 0; |
| + | popstackmark(&smark); |
| + | } |
| - | q = p = makestrspace(len + 3, p); |
| - | |
| - | *q++ = '"'; |
| - | q = memcpy(q, s, len) + len; |
| - | *q++ = '"'; |
| - | s += len; |
| - | |
| - | STADJUST(q - p, p); |
| - | } while (*s); |
| - | |
| - | USTPUTC(0, p); |
| - | return stackblock(); |
| + | static void |
| + | changemail(const char *val) |
| + | { |
| + | mail_var_path_changed++; |
| } | |
| + | #endif /* ASH_MAIL */ |
| /* | |
| - | * Like strdup but works with the ash stack. |
| + | * Take commands from a file. To be compatible we should do a path |
| + | * search for the file, which is necessary to find sub-commands. |
| */ | |
| static char * | |
| - | sstrdup(const char *p) |
| + | find_dot_file(char *name) |
| { | |
| - | size_t len = strlen(p) + 1; |
| - | return memcpy(stalloc(len), p, len); |
| - | } |
| + | char *fullname; |
| + | const char *path = pathval(); |
| + | struct stat statb; |
| + | |
| + | /* don't try this for absolute or relative paths */ |
| + | if (strchr(name, '/')) |
| + | return name; |
| + | |
| + | while ((fullname = padvance(&path, name)) != NULL) { |
| + | if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { |
| + | /* |
| + | * Don't bother freeing here, since it will |
| + | * be freed by the caller. |
| + | */ |
| + | return fullname; |
| + | } |
| + | stunalloc(fullname); |
| + | } |
| + | /* not found in the PATH */ |
| + | ash_msg_and_raise_error("%s: not found", name); |
| + | /* NOTREACHED */ |
| + | } |
| static void | |
| calcsize(union node *n) | |
| @@ -8199,949 +8586,614 @@ copynode(union node *n) | |
| return new; | |
| } | |
| - | |
| - | static struct nodelist * |
| - | copynodelist(struct nodelist *lp) |
| - | { |
| - | struct nodelist *start; |
| - | struct nodelist **lpp; |
| - | |
| - | lpp = &start; |
| - | while (lp) { |
| - | *lpp = funcblock; |
| - | funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist)); |
| - | (*lpp)->n = copynode(lp->n); |
| - | lp = lp->next; |
| - | lpp = &(*lpp)->next; |
| - | } |
| - | *lpp = NULL; |
| - | return start; |
| - | } |
| - | |
| - | |
| - | static char * |
| - | nodeckstrdup(char *s) |
| - | { |
| - | char *rtn = funcstring; |
| - | |
| - | strcpy(funcstring, s); |
| - | funcstring += strlen(s) + 1; |
| - | return rtn; |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Free a parse tree. |
| - | */ |
| - | static void |
| - | freefunc(struct funcnode *f) |
| - | { |
| - | if (f && --f->count < 0) |
| - | free(f); |
| - | } |
| - | |
| - | |
| - | static void setoption(int, int); |
| - | |
| - | |
| - | static void |
| - | optschanged(void) |
| - | { |
| - | #if DEBUG |
| - | opentrace(); |
| - | #endif |
| - | setinteractive(iflag); |
| - | setjobctl(mflag); |
| - | setvimode(viflag); |
| - | } |
| - | |
| - | static void minus_o(char *name, int val) |
| - | { |
| - | int i; |
| - | |
| - | if (name) { |
| - | for (i = 0; i < NOPTS; i++) { |
| - | if (equal(name, optnames(i))) { |
| - | optlist[i] = val; |
| - | return; |
| - | } |
| - | } |
| - | ash_msg_and_raise_error("Illegal option -o %s", name); |
| - | } |
| - | out1str("Current option settings\n"); |
| - | for (i = 0; i < NOPTS; i++) |
| - | out1fmt("%-16s%s\n", optnames(i), |
| - | optlist[i] ? "on" : "off"); |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Process shell options. The global variable argptr contains a pointer |
| - | * to the argument list; we advance it past the options. |
| - | */ |
| - | static void |
| - | options(int cmdline) |
| - | { |
| - | char *p; |
| - | int val; |
| - | int c; |
| - | |
| - | if (cmdline) |
| - | minusc = NULL; |
| - | while ((p = *argptr) != NULL) { |
| - | argptr++; |
| - | c = *p++; |
| - | if (c == '-') { |
| - | val = 1; |
| - | if (p[0] == '\0' || LONE_DASH(p)) { |
| - | if (!cmdline) { |
| - | /* "-" means turn off -x and -v */ |
| - | if (p[0] == '\0') |
| - | xflag = vflag = 0; |
| - | /* "--" means reset params */ |
| - | else if (*argptr == NULL) |
| - | setparam(argptr); |
| - | } |
| - | break; /* "-" or "--" terminates options */ |
| - | } |
| - | } else if (c == '+') { |
| - | val = 0; |
| - | } else { |
| - | argptr--; |
| - | break; |
| - | } |
| - | while ((c = *p++) != '\0') { |
| - | if (c == 'c' && cmdline) { |
| - | minusc = p; /* command is after shell args*/ |
| - | } else if (c == 'o') { |
| - | minus_o(*argptr, val); |
| - | if (*argptr) |
| - | argptr++; |
| - | } else if (cmdline && (c == '-')) { // long options |
| - | if (strcmp(p, "login") == 0) |
| - | isloginsh = 1; |
| - | break; |
| - | } else { |
| - | setoption(c, val); |
| - | } |
| - | } |
| - | } |
| - | } |
| - | |
| - | |
| - | static void |
| - | setoption(int flag, int val) |
| - | { |
| - | int i; |
| - | |
| - | for (i = 0; i < NOPTS; i++) { |
| - | if (optletters(i) == flag) { |
| - | optlist[i] = val; |
| - | return; |
| - | } |
| - | } |
| - | ash_msg_and_raise_error("Illegal option -%c", flag); |
| - | /* NOTREACHED */ |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Set the shell parameters. |
| - | */ |
| - | static void |
| - | setparam(char **argv) |
| - | { |
| - | char **newparam; |
| - | char **ap; |
| - | int nparam; |
| - | |
| - | for (nparam = 0; argv[nparam]; nparam++); |
| - | ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap)); |
| - | while (*argv) { |
| - | *ap++ = ckstrdup(*argv++); |
| - | } |
| - | *ap = NULL; |
| - | freeparam(&shellparam); |
| - | shellparam.malloc = 1; |
| - | shellparam.nparam = nparam; |
| - | shellparam.p = newparam; |
| - | #if ENABLE_ASH_GETOPTS |
| - | shellparam.optind = 1; |
| - | shellparam.optoff = -1; |
| - | #endif |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Free the list of positional parameters. |
| - | */ |
| - | static void |
| - | freeparam(volatile struct shparam *param) |
| + | |
| + | static struct nodelist * |
| + | copynodelist(struct nodelist *lp) |
| { | |
| - | char **ap; |
| + | struct nodelist *start; |
| + | struct nodelist **lpp; |
| - | if (param->malloc) { |
| - | for (ap = param->p; *ap; ap++) |
| - | free(*ap); |
| - | free(param->p); |
| + | lpp = &start; |
| + | while (lp) { |
| + | *lpp = funcblock; |
| + | funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist)); |
| + | (*lpp)->n = copynode(lp->n); |
| + | lp = lp->next; |
| + | lpp = &(*lpp)->next; |
| } | |
| + | *lpp = NULL; |
| + | return start; |
| } | |
| - | /* |
| - | * The shift builtin command. |
| - | */ |
| - | static int |
| - | shiftcmd(int argc, char **argv) |
| + | static char * |
| + | nodeckstrdup(char *s) |
| { | |
| - | int n; |
| - | char **ap1, **ap2; |
| + | char *rtn = funcstring; |
| - | n = 1; |
| - | if (argc > 1) |
| - | n = number(argv[1]); |
| - | if (n > shellparam.nparam) |
| - | ash_msg_and_raise_error("can't shift that many"); |
| - | INT_OFF; |
| - | shellparam.nparam -= n; |
| - | for (ap1 = shellparam.p; --n >= 0; ap1++) { |
| - | if (shellparam.malloc) |
| - | free(*ap1); |
| - | } |
| - | ap2 = shellparam.p; |
| - | while ((*ap2++ = *ap1++) != NULL); |
| - | #if ENABLE_ASH_GETOPTS |
| - | shellparam.optind = 1; |
| - | shellparam.optoff = -1; |
| - | #endif |
| - | INT_ON; |
| - | return 0; |
| + | strcpy(funcstring, s); |
| + | funcstring += strlen(s) + 1; |
| + | return rtn; |
| } | |
| /* | |
| - | * The set command builtin. |
| + | * Free a parse tree. |
| */ | |
| - | static int |
| - | setcmd(int argc, char **argv) |
| + | static void |
| + | freefunc(struct funcnode *f) |
| { | |
| - | if (argc == 1) |
| - | return showvars(nullstr, 0, VUNSET); |
| - | INT_OFF; |
| - | options(0); |
| - | optschanged(); |
| - | if (*argptr != NULL) { |
| - | setparam(argptr); |
| - | } |
| - | INT_ON; |
| - | return 0; |
| + | if (f && --f->count < 0) |
| + | free(f); |
| } | |
| - | #if ENABLE_ASH_GETOPTS |
| static void | |
| - | getoptsreset(const char *value) |
| + | optschanged(void) |
| { | |
| - | shellparam.optind = number(value); |
| - | shellparam.optoff = -1; |
| - | } |
| + | #if DEBUG |
| + | opentrace(); |
| #endif | |
| - | |
| - | #if ENABLE_LOCALE_SUPPORT |
| - | static void change_lc_all(const char *value) |
| - | { |
| - | if (value && *value != '\0') |
| - | setlocale(LC_ALL, value); |
| - | } |
| - | |
| - | static void change_lc_ctype(const char *value) |
| - | { |
| - | if (value && *value != '\0') |
| - | setlocale(LC_CTYPE, value); |
| + | setinteractive(iflag); |
| + | setjobctl(mflag); |
| + | setvimode(viflag); |
| } | |
| - | #endif |
| - | |
| - | #if ENABLE_ASH_RANDOM_SUPPORT |
| - | /* Roughly copied from bash.. */ |
| - | static void change_random(const char *value) |
| + | static void |
| + | minus_o(char *name, int val) |
| { | |
| - | if (value == NULL) { |
| - | /* "get", generate */ |
| - | char buf[16]; |
| + | int i; |
| - | rseed = rseed * 1103515245 + 12345; |
| - | sprintf(buf, "%d", (unsigned int)((rseed & 32767))); |
| - | /* set without recursion */ |
| - | setvar(vrandom.text, buf, VNOFUNC); |
| - | vrandom.flags &= ~VNOFUNC; |
| - | } else { |
| - | /* set/reset */ |
| - | rseed = strtoul(value, (char **)NULL, 10); |
| + | if (name) { |
| + | for (i = 0; i < NOPTS; i++) { |
| + | if (equal(name, optnames(i))) { |
| + | optlist[i] = val; |
| + | return; |
| + | } |
| + | } |
| + | ash_msg_and_raise_error("Illegal option -o %s", name); |
| } | |
| + | out1str("Current option settings\n"); |
| + | for (i = 0; i < NOPTS; i++) |
| + | out1fmt("%-16s%s\n", optnames(i), |
| + | optlist[i] ? "on" : "off"); |
| } | |
| - | #endif |
| - | #if ENABLE_ASH_GETOPTS |
| - | static int |
| - | getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff) |
| + | static void |
| + | setoption(int flag, int val) |
| { | |
| - | char *p, *q; |
| - | char c = '?'; |
| - | int done = 0; |
| - | int err = 0; |
| - | char s[12]; |
| - | char **optnext; |
| - | |
| - | if (*param_optind < 1) |
| - | return 1; |
| - | optnext = optfirst + *param_optind - 1; |
| - | |
| - | if (*param_optind <= 1 || *optoff < 0 || strlen(optnext[-1]) < *optoff) |
| - | p = NULL; |
| - | else |
| - | p = optnext[-1] + *optoff; |
| - | if (p == NULL || *p == '\0') { |
| - | /* Current word is done, advance */ |
| - | p = *optnext; |
| - | if (p == NULL || *p != '-' || *++p == '\0') { |
| - | atend: |
| - | p = NULL; |
| - | done = 1; |
| - | goto out; |
| - | } |
| - | optnext++; |
| - | if (LONE_DASH(p)) /* check for "--" */ |
| - | goto atend; |
| - | } |
| - | |
| - | c = *p++; |
| - | for (q = optstr; *q != c; ) { |
| - | if (*q == '\0') { |
| - | if (optstr[0] == ':') { |
| - | s[0] = c; |
| - | s[1] = '\0'; |
| - | err |= setvarsafe("OPTARG", s, 0); |
| - | } else { |
| - | fprintf(stderr, "Illegal option -%c\n", c); |
| - | (void) unsetvar("OPTARG"); |
| - | } |
| - | c = '?'; |
| - | goto out; |
| - | } |
| - | if (*++q == ':') |
| - | q++; |
| - | } |
| + | int i; |
| - | if (*++q == ':') { |
| - | if (*p == '\0' && (p = *optnext) == NULL) { |
| - | if (optstr[0] == ':') { |
| - | s[0] = c; |
| - | s[1] = '\0'; |
| - | err |= setvarsafe("OPTARG", s, 0); |
| - | c = ':'; |
| - | } else { |
| - | fprintf(stderr, "No arg for -%c option\n", c); |
| - | (void) unsetvar("OPTARG"); |
| - | c = '?'; |
| - | } |
| - | goto out; |
| + | for (i = 0; i < NOPTS; i++) { |
| + | if (optletters(i) == flag) { |
| + | optlist[i] = val; |
| + | return; |
| } | |
| - | |
| - | if (p == *optnext) |
| - | optnext++; |
| - | err |= setvarsafe("OPTARG", p, 0); |
| - | p = NULL; |
| - | } else |
| - | err |= setvarsafe("OPTARG", nullstr, 0); |
| - | out: |
| - | *optoff = p ? p - *(optnext - 1) : -1; |
| - | *param_optind = optnext - optfirst + 1; |
| - | fmtstr(s, sizeof(s), "%d", *param_optind); |
| - | err |= setvarsafe("OPTIND", s, VNOFUNC); |
| - | s[0] = c; |
| - | s[1] = '\0'; |
| - | err |= setvarsafe(optvar, s, 0); |
| - | if (err) { |
| - | *param_optind = 1; |
| - | *optoff = -1; |
| - | flush_stdout_stderr(); |
| - | raise_exception(EXERROR); |
| } | |
| - | return done; |
| + | ash_msg_and_raise_error("Illegal option -%c", flag); |
| + | /* NOTREACHED */ |
| } | |
| /* | |
| - | * The getopts builtin. Shellparam.optnext points to the next argument |
| - | * to be processed. Shellparam.optptr points to the next character to |
| - | * be processed in the current argument. If shellparam.optnext is NULL, |
| - | * then it's the first time getopts has been called. |
| + | * Process shell options. The global variable argptr contains a pointer |
| + | * to the argument list; we advance it past the options. |
| */ | |
| - | static int |
| - | getoptscmd(int argc, char **argv) |
| + | static void |
| + | options(int cmdline) |
| { | |
| - | char **optbase; |
| + | char *p; |
| + | int val; |
| + | int c; |
| - | if (argc < 3) |
| - | ash_msg_and_raise_error("Usage: getopts optstring var [arg]"); |
| - | else if (argc == 3) { |
| - | optbase = shellparam.p; |
| - | if (shellparam.optind > shellparam.nparam + 1) { |
| - | shellparam.optind = 1; |
| - | shellparam.optoff = -1; |
| + | if (cmdline) |
| + | minusc = NULL; |
| + | while ((p = *argptr) != NULL) { |
| + | argptr++; |
| + | c = *p++; |
| + | if (c == '-') { |
| + | val = 1; |
| + | if (p[0] == '\0' || LONE_DASH(p)) { |
| + | if (!cmdline) { |
| + | /* "-" means turn off -x and -v */ |
| + | if (p[0] == '\0') |
| + | xflag = vflag = 0; |
| + | /* "--" means reset params */ |
| + | else if (*argptr == NULL) |
| + | setparam(argptr); |
| + | } |
| + | break; /* "-" or "--" terminates options */ |
| + | } |
| + | } else if (c == '+') { |
| + | val = 0; |
| + | } else { |
| + | argptr--; |
| + | break; |
| } | |
| - | } else { |
| - | optbase = &argv[3]; |
| - | if (shellparam.optind > argc - 2) { |
| - | shellparam.optind = 1; |
| - | shellparam.optoff = -1; |
| + | while ((c = *p++) != '\0') { |
| + | if (c == 'c' && cmdline) { |
| + | minusc = p; /* command is after shell args*/ |
| + | } else if (c == 'o') { |
| + | minus_o(*argptr, val); |
| + | if (*argptr) |
| + | argptr++; |
| + | } else if (cmdline && (c == '-')) { // long options |
| + | if (strcmp(p, "login") == 0) |
| + | isloginsh = 1; |
| + | break; |
| + | } else { |
| + | setoption(c, val); |
| + | } |
| } | |
| } | |
| - | |
| - | return getopts(argv[1], argv[2], optbase, &shellparam.optind, |
| - | &shellparam.optoff); |
| } | |
| - | #endif /* ASH_GETOPTS */ |
| + | |
| /* | |
| - | * XXX - should get rid of. have all builtins use getopt(3). the |
| - | * library getopt must have the BSD extension static variable "optreset" |
| - | * otherwise it can't be used within the shell safely. |
| - | * |
| - | * Standard option processing (a la getopt) for builtin routines. The |
| - | * only argument that is passed to nextopt is the option string; the |
| - | * other arguments are unnecessary. It return the character, or '\0' on |
| - | * end of input. |
| + | * Set the shell parameters. |
| */ | |
| - | static int |
| - | nextopt(const char *optstring) |
| + | static void |
| + | setparam(char **argv) |
| { | |
| - | char *p; |
| - | const char *q; |
| - | char c; |
| + | char **newparam; |
| + | char **ap; |
| + | int nparam; |
| - | p = optptr; |
| - | if (p == NULL || *p == '\0') { |
| - | p = *argptr; |
| - | if (p == NULL || *p != '-' || *++p == '\0') |
| - | return '\0'; |
| - | argptr++; |
| - | if (LONE_DASH(p)) /* check for "--" */ |
| - | return '\0'; |
| - | } |
| - | c = *p++; |
| - | for (q = optstring; *q != c; ) { |
| - | if (*q == '\0') |
| - | ash_msg_and_raise_error("Illegal option -%c", c); |
| - | if (*++q == ':') |
| - | q++; |
| - | } |
| - | if (*++q == ':') { |
| - | if (*p == '\0' && (p = *argptr++) == NULL) |
| - | ash_msg_and_raise_error("No arg for -%c option", c); |
| - | optionarg = p; |
| - | p = NULL; |
| + | for (nparam = 0; argv[nparam]; nparam++); |
| + | ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap)); |
| + | while (*argv) { |
| + | *ap++ = ckstrdup(*argv++); |
| } | |
| - | optptr = p; |
| - | return c; |
| + | *ap = NULL; |
| + | freeparam(&shellparam); |
| + | shellparam.malloc = 1; |
| + | shellparam.nparam = nparam; |
| + | shellparam.p = newparam; |
| + | #if ENABLE_ASH_GETOPTS |
| + | shellparam.optind = 1; |
| + | shellparam.optoff = -1; |
| + | #endif |
| } | |
| - | /* parser.c */ |
| - | |
| - | |
| /* | |
| - | * Shell command parser. |
| + | * Free the list of positional parameters. |
| */ | |
| - | |
| - | #define EOFMARKLEN 79 |
| - | |
| - | struct heredoc { |
| - | struct heredoc *next; /* next here document in list */ |
| - | union node *here; /* redirection node */ |
| - | char *eofmark; /* string indicating end of input */ |
| - | int striptabs; /* if set, strip leading tabs */ |
| - | }; |
| - | |
| - | static struct heredoc *heredoclist; /* list of here documents to read */ |
| - | |
| - | static union node *list(int); |
| - | static union node *andor(void); |
| - | static union node *pipeline(void); |
| - | static union node *command(void); |
| - | static union node *simplecmd(void); |
| - | static union node *makename(void); |
| - | static void parsefname(void); |
| - | static void parseheredoc(void); |
| - | static char peektoken(void); |
| - | static int readtoken(void); |
| - | static int xxreadtoken(void); |
| - | static int readtoken1(int firstc, int syntax, char *eofmark, int striptabs); |
| - | static int noexpand(char *); |
| - | static void setprompt(int); |
| - | |
| - | static void raise_error_syntax(const char *) ATTRIBUTE_NORETURN; |
| static void | |
| - | raise_error_syntax(const char *msg) |
| + | freeparam(volatile struct shparam *param) |
| { | |
| - | ash_msg_and_raise_error("Syntax error: %s", msg); |
| - | /* NOTREACHED */ |
| + | char **ap; |
| + | |
| + | if (param->malloc) { |
| + | for (ap = param->p; *ap; ap++) |
| + | free(*ap); |
| + | free(param->p); |
| + | } |
| } | |
| + | |
| /* | |
| - | * Called when an unexpected token is read during the parse. The argument |
| - | * is the token that is expected, or -1 if more than one type of token can |
| - | * occur at this point. |
| + | * The shift builtin command. |
| */ | |
| - | static void raise_error_unexpected_syntax(int) ATTRIBUTE_NORETURN; |
| - | static void |
| - | raise_error_unexpected_syntax(int token) |
| + | static int |
| + | shiftcmd(int argc, char **argv) |
| { | |
| - | char msg[64]; |
| - | int l; |
| + | int n; |
| + | char **ap1, **ap2; |
| - | l = sprintf(msg, "%s unexpected", tokname(lasttoken)); |
| - | if (token >= 0) |
| - | sprintf(msg + l, " (expecting %s)", tokname(token)); |
| - | raise_error_syntax(msg); |
| - | /* NOTREACHED */ |
| + | n = 1; |
| + | if (argc > 1) |
| + | n = number(argv[1]); |
| + | if (n > shellparam.nparam) |
| + | ash_msg_and_raise_error("can't shift that many"); |
| + | INT_OFF; |
| + | shellparam.nparam -= n; |
| + | for (ap1 = shellparam.p; --n >= 0; ap1++) { |
| + | if (shellparam.malloc) |
| + | free(*ap1); |
| + | } |
| + | ap2 = shellparam.p; |
| + | while ((*ap2++ = *ap1++) != NULL); |
| + | #if ENABLE_ASH_GETOPTS |
| + | shellparam.optind = 1; |
| + | shellparam.optoff = -1; |
| + | #endif |
| + | INT_ON; |
| + | return 0; |
| } | |
| + | |
| /* | |
| - | * Read and parse a command. Returns NEOF on end of file. (NULL is a |
| - | * valid parse tree indicating a blank line.) |
| + | * POSIX requires that 'set' (but not export or readonly) output the |
| + | * variables in lexicographic order - by the locale's collating order (sigh). |
| + | * Maybe we could keep them in an ordered balanced binary tree |
| + | * instead of hashed lists. |
| + | * For now just roll 'em through qsort for printing... |
| */ | |
| - | static union node * |
| - | parsecmd(int interact) |
| + | static int |
| + | showvars(const char *sep_prefix, int on, int off) |
| { | |
| - | int t; |
| + | const char *sep; |
| + | char **ep, **epend; |
| - | tokpushback = 0; |
| - | doprompt = interact; |
| - | if (doprompt) |
| - | setprompt(doprompt); |
| - | needprompt = 0; |
| - | t = readtoken(); |
| - | if (t == TEOF) |
| - | return NEOF; |
| - | if (t == TNL) |
| - | return NULL; |
| - | tokpushback++; |
| - | return list(1); |
| - | } |
| + | ep = listvars(on, off, &epend); |
| + | qsort(ep, epend - ep, sizeof(char *), vpcmp); |
| + | sep = *sep_prefix ? spcstr : sep_prefix; |
| - | static union node * |
| - | list(int nlflag) |
| - | { |
| - | union node *n1, *n2, *n3; |
| - | int tok; |
| + | for (; ep < epend; ep++) { |
| + | const char *p; |
| + | const char *q; |
| - | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| - | if (nlflag == 2 && peektoken()) |
| - | return NULL; |
| - | n1 = NULL; |
| - | for (;;) { |
| - | n2 = andor(); |
| - | tok = readtoken(); |
| - | if (tok == TBACKGND) { |
| - | if (n2->type == NPIPE) { |
| - | n2->npipe.backgnd = 1; |
| - | } else { |
| - | if (n2->type != NREDIR) { |
| - | n3 = stalloc(sizeof(struct nredir)); |
| - | n3->nredir.n = n2; |
| - | n3->nredir.redirect = NULL; |
| - | n2 = n3; |
| - | } |
| - | n2->type = NBACKGND; |
| - | } |
| - | } |
| - | if (n1 == NULL) { |
| - | n1 = n2; |
| - | } else { |
| - | n3 = stalloc(sizeof(struct nbinary)); |
| - | n3->type = NSEMI; |
| - | n3->nbinary.ch1 = n1; |
| - | n3->nbinary.ch2 = n2; |
| - | n1 = n3; |
| - | } |
| - | switch (tok) { |
| - | case TBACKGND: |
| - | case TSEMI: |
| - | tok = readtoken(); |
| - | /* fall through */ |
| - | case TNL: |
| - | if (tok == TNL) { |
| - | parseheredoc(); |
| - | if (nlflag == 1) |
| - | return n1; |
| - | } else { |
| - | tokpushback++; |
| - | } |
| - | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| - | if (peektoken()) |
| - | return n1; |
| - | break; |
| - | case TEOF: |
| - | if (heredoclist) |
| - | parseheredoc(); |
| - | else |
| - | pungetc(); /* push back EOF on input */ |
| - | return n1; |
| - | default: |
| - | if (nlflag == 1) |
| - | raise_error_unexpected_syntax(-1); |
| - | tokpushback++; |
| - | return n1; |
| - | } |
| + | p = strchrnul(*ep, '='); |
| + | q = nullstr; |
| + | if (*p) |
| + | q = single_quote(++p); |
| + | out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q); |
| + | } |
| + | return 0; |
| + | } |
| + | |
| + | /* |
| + | * The set command builtin. |
| + | */ |
| + | static int |
| + | setcmd(int argc, char **argv) |
| + | { |
| + | if (argc == 1) |
| + | return showvars(nullstr, 0, VUNSET); |
| + | INT_OFF; |
| + | options(0); |
| + | optschanged(); |
| + | if (*argptr != NULL) { |
| + | setparam(argptr); |
| } | |
| + | INT_ON; |
| + | return 0; |
| } | |
| - | static union node * |
| - | andor(void) |
| + | #if ENABLE_LOCALE_SUPPORT |
| + | static void change_lc_all(const char *value) |
| { | |
| - | union node *n1, *n2, *n3; |
| - | int t; |
| - | |
| - | n1 = pipeline(); |
| - | for (;;) { |
| - | t = readtoken(); |
| - | if (t == TAND) { |
| - | t = NAND; |
| - | } else if (t == TOR) { |
| - | t = NOR; |
| - | } else { |
| - | tokpushback++; |
| - | return n1; |
| - | } |
| - | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| - | n2 = pipeline(); |
| - | n3 = stalloc(sizeof(struct nbinary)); |
| - | n3->type = t; |
| - | n3->nbinary.ch1 = n1; |
| - | n3->nbinary.ch2 = n2; |
| - | n1 = n3; |
| - | } |
| + | if (value && *value != '\0') |
| + | setlocale(LC_ALL, value); |
| } | |
| + | static void change_lc_ctype(const char *value) |
| + | { |
| + | if (value && *value != '\0') |
| + | setlocale(LC_CTYPE, value); |
| + | } |
| + | #endif |
| - | static union node * |
| - | pipeline(void) |
| + | #if ENABLE_ASH_RANDOM_SUPPORT |
| + | /* Roughly copied from bash.. */ |
| + | static void change_random(const char *value) |
| { | |
| - | union node *n1, *n2, *pipenode; |
| - | struct nodelist *lp, *prev; |
| - | int negate; |
| + | if (value == NULL) { |
| + | /* "get", generate */ |
| + | char buf[16]; |
| - | negate = 0; |
| - | TRACE(("pipeline: entered\n")); |
| - | if (readtoken() == TNOT) { |
| - | negate = !negate; |
| - | checkkwd = CHKKWD | CHKALIAS; |
| - | } else |
| - | tokpushback++; |
| - | n1 = command(); |
| - | if (readtoken() == TPIPE) { |
| - | pipenode = stalloc(sizeof(struct npipe)); |
| - | pipenode->type = NPIPE; |
| - | pipenode->npipe.backgnd = 0; |
| - | lp = stalloc(sizeof(struct nodelist)); |
| - | pipenode->npipe.cmdlist = lp; |
| - | lp->n = n1; |
| - | do { |
| - | prev = lp; |
| - | lp = stalloc(sizeof(struct nodelist)); |
| - | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| - | lp->n = command(); |
| - | prev->next = lp; |
| - | } while (readtoken() == TPIPE); |
| - | lp->next = NULL; |
| - | n1 = pipenode; |
| - | } |
| - | tokpushback++; |
| - | if (negate) { |
| - | n2 = stalloc(sizeof(struct nnot)); |
| - | n2->type = NNOT; |
| - | n2->nnot.com = n1; |
| - | return n2; |
| + | rseed = rseed * 1103515245 + 12345; |
| + | sprintf(buf, "%d", (unsigned int)((rseed & 32767))); |
| + | /* set without recursion */ |
| + | setvar(vrandom.text, buf, VNOFUNC); |
| + | vrandom.flags &= ~VNOFUNC; |
| + | } else { |
| + | /* set/reset */ |
| + | rseed = strtoul(value, (char **)NULL, 10); |
| } | |
| - | return n1; |
| } | |
| + | #endif |
| - | static union node * |
| - | command(void) |
| + | #if ENABLE_ASH_GETOPTS |
| + | static int |
| + | getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff) |
| { | |
| - | union node *n1, *n2; |
| - | union node *ap, **app; |
| - | union node *cp, **cpp; |
| - | union node *redir, **rpp; |
| - | union node **rpp2; |
| - | int t; |
| - | |
| - | redir = NULL; |
| - | rpp2 = &redir; |
| + | char *p, *q; |
| + | char c = '?'; |
| + | int done = 0; |
| + | int err = 0; |
| + | char s[12]; |
| + | char **optnext; |
| - | switch (readtoken()) { |
| - | default: |
| - | raise_error_unexpected_syntax(-1); |
| - | /* NOTREACHED */ |
| - | case TIF: |
| - | n1 = stalloc(sizeof(struct nif)); |
| - | n1->type = NIF; |
| - | n1->nif.test = list(0); |
| - | if (readtoken() != TTHEN) |
| - | raise_error_unexpected_syntax(TTHEN); |
| - | n1->nif.ifpart = list(0); |
| - | n2 = n1; |
| - | while (readtoken() == TELIF) { |
| - | n2->nif.elsepart = stalloc(sizeof(struct nif)); |
| - | n2 = n2->nif.elsepart; |
| - | n2->type = NIF; |
| - | n2->nif.test = list(0); |
| - | if (readtoken() != TTHEN) |
| - | raise_error_unexpected_syntax(TTHEN); |
| - | n2->nif.ifpart = list(0); |
| - | } |
| - | if (lasttoken == TELSE) |
| - | n2->nif.elsepart = list(0); |
| - | else { |
| - | n2->nif.elsepart = NULL; |
| - | tokpushback++; |
| - | } |
| - | t = TFI; |
| - | break; |
| - | case TWHILE: |
| - | case TUNTIL: { |
| - | int got; |
| - | n1 = stalloc(sizeof(struct nbinary)); |
| - | n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL; |
| - | n1->nbinary.ch1 = list(0); |
| - | if ((got=readtoken()) != TDO) { |
| - | TRACE(("expecting DO got %s %s\n", tokname(got), |
| - | got == TWORD ? wordtext : "")); |
| - | raise_error_unexpected_syntax(TDO); |
| - | } |
| - | n1->nbinary.ch2 = list(0); |
| - | t = TDONE; |
| - | break; |
| - | } |
| - | case TFOR: |
| - | if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) |
| - | raise_error_syntax("Bad for loop variable"); |
| - | n1 = stalloc(sizeof(struct nfor)); |
| - | n1->type = NFOR; |
| - | n1->nfor.var = wordtext; |
| - | checkkwd = CHKKWD | CHKALIAS; |
| - | if (readtoken() == TIN) { |
| - | app = ≈ |
| - | while (readtoken() == TWORD) { |
| - | n2 = stalloc(sizeof(struct narg)); |
| - | n2->type = NARG; |
| - | n2->narg.text = wordtext; |
| - | n2->narg.backquote = backquotelist; |
| - | *app = n2; |
| - | app = &n2->narg.next; |
| - | } |
| - | *app = NULL; |
| - | n1->nfor.args = ap; |
| - | if (lasttoken != TNL && lasttoken != TSEMI) |
| - | raise_error_unexpected_syntax(-1); |
| - | } else { |
| - | n2 = stalloc(sizeof(struct narg)); |
| - | n2->type = NARG; |
| - | n2->narg.text = (char *)dolatstr; |
| - | n2->narg.backquote = NULL; |
| - | n2->narg.next = NULL; |
| - | n1->nfor.args = n2; |
| - | /* |
| - | * Newline or semicolon here is optional (but note |
| - | * that the original Bourne shell only allowed NL). |
| - | */ |
| - | if (lasttoken != TNL && lasttoken != TSEMI) |
| - | tokpushback++; |
| - | } |
| - | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| - | if (readtoken() != TDO) |
| - | raise_error_unexpected_syntax(TDO); |
| - | n1->nfor.body = list(0); |
| - | t = TDONE; |
| - | break; |
| - | case TCASE: |
| - | n1 = stalloc(sizeof(struct ncase)); |
| - | n1->type = NCASE; |
| - | if (readtoken() != TWORD) |
| - | raise_error_unexpected_syntax(TWORD); |
| - | n1->ncase.expr = n2 = stalloc(sizeof(struct narg)); |
| - | n2->type = NARG; |
| - | n2->narg.text = wordtext; |
| - | n2->narg.backquote = backquotelist; |
| - | n2->narg.next = NULL; |
| - | do { |
| - | checkkwd = CHKKWD | CHKALIAS; |
| - | } while (readtoken() == TNL); |
| - | if (lasttoken != TIN) |
| - | raise_error_unexpected_syntax(TIN); |
| - | cpp = &n1->ncase.cases; |
| - | next_case: |
| - | checkkwd = CHKNL | CHKKWD; |
| - | t = readtoken(); |
| - | while (t != TESAC) { |
| - | if (lasttoken == TLP) |
| - | readtoken(); |
| - | *cpp = cp = stalloc(sizeof(struct nclist)); |
| - | cp->type = NCLIST; |
| - | app = &cp->nclist.pattern; |
| - | for (;;) { |
| - | *app = ap = stalloc(sizeof(struct narg)); |
| - | ap->type = NARG; |
| - | ap->narg.text = wordtext; |
| - | ap->narg.backquote = backquotelist; |
| - | if (readtoken() != TPIPE) |
| - | break; |
| - | app = &ap->narg.next; |
| - | readtoken(); |
| - | } |
| - | ap->narg.next = NULL; |
| - | if (lasttoken != TRP) |
| - | raise_error_unexpected_syntax(TRP); |
| - | cp->nclist.body = list(2); |
| + | if (*param_optind < 1) |
| + | return 1; |
| + | optnext = optfirst + *param_optind - 1; |
| - | cpp = &cp->nclist.next; |
| + | if (*param_optind <= 1 || *optoff < 0 || strlen(optnext[-1]) < *optoff) |
| + | p = NULL; |
| + | else |
| + | p = optnext[-1] + *optoff; |
| + | if (p == NULL || *p == '\0') { |
| + | /* Current word is done, advance */ |
| + | p = *optnext; |
| + | if (p == NULL || *p != '-' || *++p == '\0') { |
| + | atend: |
| + | p = NULL; |
| + | done = 1; |
| + | goto out; |
| + | } |
| + | optnext++; |
| + | if (LONE_DASH(p)) /* check for "--" */ |
| + | goto atend; |
| + | } |
| - | checkkwd = CHKNL | CHKKWD; |
| - | t = readtoken(); |
| - | if (t != TESAC) { |
| - | if (t != TENDCASE) |
| - | raise_error_unexpected_syntax(TENDCASE); |
| - | goto next_case; |
| + | c = *p++; |
| + | for (q = optstr; *q != c; ) { |
| + | if (*q == '\0') { |
| + | if (optstr[0] == ':') { |
| + | s[0] = c; |
| + | s[1] = '\0'; |
| + | err |= setvarsafe("OPTARG", s, 0); |
| + | } else { |
| + | fprintf(stderr, "Illegal option -%c\n", c); |
| + | unsetvar("OPTARG"); |
| } | |
| + | c = '?'; |
| + | goto out; |
| } | |
| - | *cpp = NULL; |
| - | goto redir; |
| - | case TLP: |
| - | n1 = stalloc(sizeof(struct nredir)); |
| - | n1->type = NSUBSHELL; |
| - | n1->nredir.n = list(0); |
| - | n1->nredir.redirect = NULL; |
| - | t = TRP; |
| - | break; |
| - | case TBEGIN: |
| - | n1 = list(0); |
| - | t = TEND; |
| - | break; |
| - | case TWORD: |
| - | case TREDIR: |
| - | tokpushback++; |
| - | return simplecmd(); |
| + | if (*++q == ':') |
| + | q++; |
| } | |
| - | if (readtoken() != t) |
| - | raise_error_unexpected_syntax(t); |
| + | if (*++q == ':') { |
| + | if (*p == '\0' && (p = *optnext) == NULL) { |
| + | if (optstr[0] == ':') { |
| + | s[0] = c; |
| + | s[1] = '\0'; |
| + | err |= setvarsafe("OPTARG", s, 0); |
| + | c = ':'; |
| + | } else { |
| + | fprintf(stderr, "No arg for -%c option\n", c); |
| + | unsetvar("OPTARG"); |
| + | c = '?'; |
| + | } |
| + | goto out; |
| + | } |
| - | redir: |
| - | /* Now check for redirection which may follow command */ |
| - | checkkwd = CHKKWD | CHKALIAS; |
| - | rpp = rpp2; |
| - | while (readtoken() == TREDIR) { |
| - | *rpp = n2 = redirnode; |
| - | rpp = &n2->nfile.next; |
| - | parsefname(); |
| + | if (p == *optnext) |
| + | optnext++; |
| + | err |= setvarsafe("OPTARG", p, 0); |
| + | p = NULL; |
| + | } else |
| + | err |= setvarsafe("OPTARG", nullstr, 0); |
| + | out: |
| + | *optoff = p ? p - *(optnext - 1) : -1; |
| + | *param_optind = optnext - optfirst + 1; |
| + | fmtstr(s, sizeof(s), "%d", *param_optind); |
| + | err |= setvarsafe("OPTIND", s, VNOFUNC); |
| + | s[0] = c; |
| + | s[1] = '\0'; |
| + | err |= setvarsafe(optvar, s, 0); |
| + | if (err) { |
| + | *param_optind = 1; |
| + | *optoff = -1; |
| + | flush_stdout_stderr(); |
| + | raise_exception(EXERROR); |
| } | |
| - | tokpushback++; |
| - | *rpp = NULL; |
| - | if (redir) { |
| - | if (n1->type != NSUBSHELL) { |
| - | n2 = stalloc(sizeof(struct nredir)); |
| - | n2->type = NREDIR; |
| - | n2->nredir.n = n1; |
| - | n1 = n2; |
| + | return done; |
| + | } |
| + | |
| + | |
| + | /* |
| + | * The getopts builtin. Shellparam.optnext points to the next argument |
| + | * to be processed. Shellparam.optptr points to the next character to |
| + | * be processed in the current argument. If shellparam.optnext is NULL, |
| + | * then it's the first time getopts has been called. |
| + | */ |
| + | static int |
| + | getoptscmd(int argc, char **argv) |
| + | { |
| + | char **optbase; |
| + | |
| + | if (argc < 3) |
| + | ash_msg_and_raise_error("Usage: getopts optstring var [arg]"); |
| + | if (argc == 3) { |
| + | optbase = shellparam.p; |
| + | if (shellparam.optind > shellparam.nparam + 1) { |
| + | shellparam.optind = 1; |
| + | shellparam.optoff = -1; |
| + | } |
| + | } else { |
| + | optbase = &argv[3]; |
| + | if (shellparam.optind > argc - 2) { |
| + | shellparam.optind = 1; |
| + | shellparam.optoff = -1; |
| } | |
| - | n1->nredir.redirect = redir; |
| } | |
| - | return n1; |
| + | |
| + | return getopts(argv[1], argv[2], optbase, &shellparam.optind, |
| + | &shellparam.optoff); |
| } | |
| + | #endif /* ASH_GETOPTS */ |
| - | static union node * |
| - | simplecmd(void) |
| + | /* ============ Shell parser */ |
| + | |
| + | static void raise_error_syntax(const char *) ATTRIBUTE_NORETURN; |
| + | static void |
| + | raise_error_syntax(const char *msg) |
| { | |
| - | union node *args, **app; |
| - | union node *n = NULL; |
| - | union node *vars, **vpp; |
| - | union node **rpp, *redir; |
| - | int savecheckkwd; |
| + | ash_msg_and_raise_error("Syntax error: %s", msg); |
| + | /* NOTREACHED */ |
| + | } |
| - | args = NULL; |
| - | app = &args; |
| - | vars = NULL; |
| - | vpp = &vars; |
| - | redir = NULL; |
| - | rpp = &redir; |
| + | /* |
| + | * Called when an unexpected token is read during the parse. The argument |
| + | * is the token that is expected, or -1 if more than one type of token can |
| + | * occur at this point. |
| + | */ |
| + | static void raise_error_unexpected_syntax(int) ATTRIBUTE_NORETURN; |
| + | static void |
| + | raise_error_unexpected_syntax(int token) |
| + | { |
| + | char msg[64]; |
| + | int l; |
| - | savecheckkwd = CHKALIAS; |
| + | l = sprintf(msg, "%s unexpected", tokname(lasttoken)); |
| + | if (token >= 0) |
| + | sprintf(msg + l, " (expecting %s)", tokname(token)); |
| + | raise_error_syntax(msg); |
| + | /* NOTREACHED */ |
| + | } |
| + | |
| + | #define EOFMARKLEN 79 |
| + | |
| + | struct heredoc { |
| + | struct heredoc *next; /* next here document in list */ |
| + | union node *here; /* redirection node */ |
| + | char *eofmark; /* string indicating end of input */ |
| + | int striptabs; /* if set, strip leading tabs */ |
| + | }; |
| + | |
| + | static struct heredoc *heredoclist; /* list of here documents to read */ |
| + | |
| + | /* parsing is heavily cross-recursive, need these forward decls */ |
| + | static union node *andor(void); |
| + | static union node *pipeline(void); |
| + | static union node *parse_command(void); |
| + | static void parseheredoc(void); |
| + | static char peektoken(void); |
| + | static int readtoken(void); |
| + | |
| + | static union node * |
| + | list(int nlflag) |
| + | { |
| + | union node *n1, *n2, *n3; |
| + | int tok; |
| + | |
| + | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| + | if (nlflag == 2 && peektoken()) |
| + | return NULL; |
| + | n1 = NULL; |
| for (;;) { | |
| - | checkkwd = savecheckkwd; |
| - | switch (readtoken()) { |
| - | case TWORD: |
| - | n = stalloc(sizeof(struct narg)); |
| - | n->type = NARG; |
| - | n->narg.text = wordtext; |
| - | n->narg.backquote = backquotelist; |
| - | if (savecheckkwd && isassignment(wordtext)) { |
| - | *vpp = n; |
| - | vpp = &n->narg.next; |
| + | n2 = andor(); |
| + | tok = readtoken(); |
| + | if (tok == TBACKGND) { |
| + | if (n2->type == NPIPE) { |
| + | n2->npipe.backgnd = 1; |
| } else { | |
| - | *app = n; |
| - | app = &n->narg.next; |
| - | savecheckkwd = 0; |
| - | } |
| - | break; |
| - | case TREDIR: |
| - | *rpp = n = redirnode; |
| - | rpp = &n->nfile.next; |
| - | parsefname(); /* read name of redirection file */ |
| - | break; |
| - | case TLP: |
| - | if (args && app == &args->narg.next |
| - | && !vars && !redir |
| - | ) { |
| - | struct builtincmd *bcmd; |
| - | const char *name; |
| - | |
| - | /* We have a function */ |
| - | if (readtoken() != TRP) |
| - | raise_error_unexpected_syntax(TRP); |
| - | name = n->narg.text; |
| - | if (!goodname(name) |
| - | || ((bcmd = find_builtin(name)) && IS_BUILTIN_SPECIAL(bcmd)) |
| - | ) { |
| - | raise_error_syntax("Bad function name"); |
| + | if (n2->type != NREDIR) { |
| + | n3 = stalloc(sizeof(struct nredir)); |
| + | n3->nredir.n = n2; |
| + | n3->nredir.redirect = NULL; |
| + | n2 = n3; |
| } | |
| - | n->type = NDEFUN; |
| - | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| - | n->narg.next = command(); |
| - | return n; |
| + | n2->type = NBACKGND; |
| + | } |
| + | } |
| + | if (n1 == NULL) { |
| + | n1 = n2; |
| + | } else { |
| + | n3 = stalloc(sizeof(struct nbinary)); |
| + | n3->type = NSEMI; |
| + | n3->nbinary.ch1 = n1; |
| + | n3->nbinary.ch2 = n2; |
| + | n1 = n3; |
| + | } |
| + | switch (tok) { |
| + | case TBACKGND: |
| + | case TSEMI: |
| + | tok = readtoken(); |
| + | /* fall through */ |
| + | case TNL: |
| + | if (tok == TNL) { |
| + | parseheredoc(); |
| + | if (nlflag == 1) |
| + | return n1; |
| + | } else { |
| + | tokpushback++; |
| } | |
| - | /* fall through */ |
| + | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| + | if (peektoken()) |
| + | return n1; |
| + | break; |
| + | case TEOF: |
| + | if (heredoclist) |
| + | parseheredoc(); |
| + | else |
| + | pungetc(); /* push back EOF on input */ |
| + | return n1; |
| default: | |
| + | if (nlflag == 1) |
| + | raise_error_unexpected_syntax(-1); |
| tokpushback++; | |
| - | goto out; |
| + | return n1; |
| } | |
| } | |
| - | out: |
| - | *app = NULL; |
| - | *vpp = NULL; |
| - | *rpp = NULL; |
| - | n = stalloc(sizeof(struct ncmd)); |
| - | n->type = NCMD; |
| - | n->ncmd.args = args; |
| - | n->ncmd.assign = vars; |
| - | n->ncmd.redirect = redir; |
| - | return n; |
| + | } |
| + | |
| + | static union node * |
| + | andor(void) |
| + | { |
| + | union node *n1, *n2, *n3; |
| + | int t; |
| + | |
| + | n1 = pipeline(); |
| + | for (;;) { |
| + | t = readtoken(); |
| + | if (t == TAND) { |
| + | t = NAND; |
| + | } else if (t == TOR) { |
| + | t = NOR; |
| + | } else { |
| + | tokpushback++; |
| + | return n1; |
| + | } |
| + | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| + | n2 = pipeline(); |
| + | n3 = stalloc(sizeof(struct nbinary)); |
| + | n3->type = t; |
| + | n3->nbinary.ch1 = n1; |
| + | n3->nbinary.ch2 = n2; |
| + | n1 = n3; |
| + | } |
| + | } |
| + | |
| + | static union node * |
| + | pipeline(void) |
| + | { |
| + | union node *n1, *n2, *pipenode; |
| + | struct nodelist *lp, *prev; |
| + | int negate; |
| + | |
| + | negate = 0; |
| + | TRACE(("pipeline: entered\n")); |
| + | if (readtoken() == TNOT) { |
| + | negate = !negate; |
| + | checkkwd = CHKKWD | CHKALIAS; |
| + | } else |
| + | tokpushback++; |
| + | n1 = parse_command(); |
| + | if (readtoken() == TPIPE) { |
| + | pipenode = stalloc(sizeof(struct npipe)); |
| + | pipenode->type = NPIPE; |
| + | pipenode->npipe.backgnd = 0; |
| + | lp = stalloc(sizeof(struct nodelist)); |
| + | pipenode->npipe.cmdlist = lp; |
| + | lp->n = n1; |
| + | do { |
| + | prev = lp; |
| + | lp = stalloc(sizeof(struct nodelist)); |
| + | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| + | lp->n = parse_command(); |
| + | prev->next = lp; |
| + | } while (readtoken() == TPIPE); |
| + | lp->next = NULL; |
| + | n1 = pipenode; |
| + | } |
| + | tokpushback++; |
| + | if (negate) { |
| + | n2 = stalloc(sizeof(struct nnot)); |
| + | n2->type = NNOT; |
| + | n2->nnot.com = n1; |
| + | return n2; |
| + | } |
| + | return n1; |
| } | |
| static union node * | |
| @@ -9164,7 +9216,7 @@ fixredir(union node *n, const char *text, int err) | |
| if (!err) | |
| n->ndup.vname = NULL; | |
| - | if (is_digit(text[0]) && text[1] == '\0') |
| + | if (isdigit(text[0]) && text[1] == '\0') |
| n->ndup.dupfd = digit_val(text[0]); | |
| else if (LONE_DASH(text)) | |
| n->ndup.dupfd = -1; | |
| @@ -9175,6 +9227,27 @@ fixredir(union node *n, const char *text, int err) | |
| } | |
| } | |
| + | /* |
| + | * Returns true if the text contains nothing to expand (no dollar signs |
| + | * or backquotes). |
| + | */ |
| + | static int |
| + | noexpand(char *text) |
| + | { |
| + | char *p; |
| + | char c; |
| + | |
| + | p = text; |
| + | while ((c = *p++) != '\0') { |
| + | if (c == CTLQUOTEMARK) |
| + | continue; |
| + | if (c == CTLESC) |
| + | p++; |
| + | else if (SIT(c, BASESYNTAX) == CCTL) |
| + | return 0; |
| + | } |
| + | return 1; |
| + | } |
| static void | |
| parsefname(void) | |
| @@ -9209,276 +9282,277 @@ parsefname(void) | |
| } | |
| } | |
| - | |
| - | /* |
| - | * Input any here documents. |
| - | */ |
| - | static void |
| - | parseheredoc(void) |
| - | { |
| - | struct heredoc *here; |
| - | union node *n; |
| - | |
| - | here = heredoclist; |
| - | heredoclist = 0; |
| - | |
| - | while (here) { |
| - | if (needprompt) { |
| - | setprompt(2); |
| - | } |
| - | readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, |
| - | here->eofmark, here->striptabs); |
| - | n = stalloc(sizeof(struct narg)); |
| - | n->narg.type = NARG; |
| - | n->narg.next = NULL; |
| - | n->narg.text = wordtext; |
| - | n->narg.backquote = backquotelist; |
| - | here->here->nhere.doc = n; |
| - | here = here->next; |
| - | } |
| - | } |
| - | |
| - | static char |
| - | peektoken(void) |
| - | { |
| - | int t; |
| - | |
| - | t = readtoken(); |
| - | tokpushback++; |
| - | return tokname_array[t][0]; |
| - | } |
| - | |
| - | static int |
| - | readtoken(void) |
| + | static union node * |
| + | simplecmd(void) |
| { | |
| - | int t; |
| - | #if DEBUG |
| - | int alreadyseen = tokpushback; |
| - | #endif |
| - | |
| - | #if ENABLE_ASH_ALIAS |
| - | top: |
| - | #endif |
| - | |
| - | t = xxreadtoken(); |
| - | |
| - | /* |
| - | * eat newlines |
| - | */ |
| - | if (checkkwd & CHKNL) { |
| - | while (t == TNL) { |
| - | parseheredoc(); |
| - | t = xxreadtoken(); |
| - | } |
| - | } |
| - | |
| - | if (t != TWORD || quoteflag) { |
| - | goto out; |
| - | } |
| + | union node *args, **app; |
| + | union node *n = NULL; |
| + | union node *vars, **vpp; |
| + | union node **rpp, *redir; |
| + | int savecheckkwd; |
| - | /* |
| - | * check for keywords |
| - | */ |
| - | if (checkkwd & CHKKWD) { |
| - | const char *const *pp; |
| + | args = NULL; |
| + | app = &args; |
| + | vars = NULL; |
| + | vpp = &vars; |
| + | redir = NULL; |
| + | rpp = &redir; |
| - | pp = findkwd(wordtext); |
| - | if (pp) { |
| - | lasttoken = t = pp - tokname_array; |
| - | TRACE(("keyword %s recognized\n", tokname(t))); |
| - | goto out; |
| - | } |
| - | } |
| + | savecheckkwd = CHKALIAS; |
| + | for (;;) { |
| + | checkkwd = savecheckkwd; |
| + | switch (readtoken()) { |
| + | case TWORD: |
| + | n = stalloc(sizeof(struct narg)); |
| + | n->type = NARG; |
| + | n->narg.text = wordtext; |
| + | n->narg.backquote = backquotelist; |
| + | if (savecheckkwd && isassignment(wordtext)) { |
| + | *vpp = n; |
| + | vpp = &n->narg.next; |
| + | } else { |
| + | *app = n; |
| + | app = &n->narg.next; |
| + | savecheckkwd = 0; |
| + | } |
| + | break; |
| + | case TREDIR: |
| + | *rpp = n = redirnode; |
| + | rpp = &n->nfile.next; |
| + | parsefname(); /* read name of redirection file */ |
| + | break; |
| + | case TLP: |
| + | if (args && app == &args->narg.next |
| + | && !vars && !redir |
| + | ) { |
| + | struct builtincmd *bcmd; |
| + | const char *name; |
| - | if (checkkwd & CHKALIAS) { |
| - | #if ENABLE_ASH_ALIAS |
| - | struct alias *ap; |
| - | ap = lookupalias(wordtext, 1); |
| - | if (ap != NULL) { |
| - | if (*ap->val) { |
| - | pushstring(ap->val, ap); |
| + | /* We have a function */ |
| + | if (readtoken() != TRP) |
| + | raise_error_unexpected_syntax(TRP); |
| + | name = n->narg.text; |
| + | if (!goodname(name) |
| + | || ((bcmd = find_builtin(name)) && IS_BUILTIN_SPECIAL(bcmd)) |
| + | ) { |
| + | raise_error_syntax("Bad function name"); |
| + | } |
| + | n->type = NDEFUN; |
| + | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| + | n->narg.next = parse_command(); |
| + | return n; |
| } | |
| - | goto top; |
| + | /* fall through */ |
| + | default: |
| + | tokpushback++; |
| + | goto out; |
| } | |
| - | #endif |
| } | |
| out: | |
| - | checkkwd = 0; |
| - | #if DEBUG |
| - | if (!alreadyseen) |
| - | TRACE(("token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); |
| - | else |
| - | TRACE(("reread token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); |
| - | #endif |
| - | return t; |
| + | *app = NULL; |
| + | *vpp = NULL; |
| + | *rpp = NULL; |
| + | n = stalloc(sizeof(struct ncmd)); |
| + | n->type = NCMD; |
| + | n->ncmd.args = args; |
| + | n->ncmd.assign = vars; |
| + | n->ncmd.redirect = redir; |
| + | return n; |
| } | |
| - | |
| - | /* |
| - | * Read the next input token. |
| - | * If the token is a word, we set backquotelist to the list of cmds in |
| - | * backquotes. We set quoteflag to true if any part of the word was |
| - | * quoted. |
| - | * If the token is TREDIR, then we set redirnode to a structure containing |
| - | * the redirection. |
| - | * In all cases, the variable startlinno is set to the number of the line |
| - | * on which the token starts. |
| - | * |
| - | * [Change comment: here documents and internal procedures] |
| - | * [Readtoken shouldn't have any arguments. Perhaps we should make the |
| - | * word parsing code into a separate routine. In this case, readtoken |
| - | * doesn't need to have any internal procedures, but parseword does. |
| - | * We could also make parseoperator in essence the main routine, and |
| - | * have parseword (readtoken1?) handle both words and redirection.] |
| - | */ |
| - | #define NEW_xxreadtoken |
| - | #ifdef NEW_xxreadtoken |
| - | /* singles must be first! */ |
| - | static const char xxreadtoken_chars[7] = { '\n', '(', ')', '&', '|', ';', 0 }; |
| - | |
| - | static const char xxreadtoken_tokens[] = { |
| - | TNL, TLP, TRP, /* only single occurrence allowed */ |
| - | TBACKGND, TPIPE, TSEMI, /* if single occurrence */ |
| - | TEOF, /* corresponds to trailing nul */ |
| - | TAND, TOR, TENDCASE, /* if double occurrence */ |
| - | }; |
| - | |
| - | #define xxreadtoken_doubles \ |
| - | (sizeof(xxreadtoken_tokens) - sizeof(xxreadtoken_chars)) |
| - | #define xxreadtoken_singles \ |
| - | (sizeof(xxreadtoken_chars) - xxreadtoken_doubles - 1) |
| - | |
| - | static int xxreadtoken(void) |
| + | static union node * |
| + | parse_command(void) |
| { | |
| - | int c; |
| + | union node *n1, *n2; |
| + | union node *ap, **app; |
| + | union node *cp, **cpp; |
| + | union node *redir, **rpp; |
| + | union node **rpp2; |
| + | int t; |
| - | if (tokpushback) { |
| - | tokpushback = 0; |
| - | return lasttoken; |
| - | } |
| - | if (needprompt) { |
| - | setprompt(2); |
| - | } |
| - | startlinno = plinno; |
| - | for (;;) { /* until token or start of word found */ |
| - | c = pgetc_macro(); |
| + | redir = NULL; |
| + | rpp2 = &redir; |
| - | if ((c != ' ') && (c != '\t') |
| - | #if ENABLE_ASH_ALIAS |
| - | && (c != PEOA) |
| - | #endif |
| - | ) { |
| - | if (c == '#') { |
| - | while ((c = pgetc()) != '\n' && c != PEOF); |
| - | pungetc(); |
| - | } else if (c == '\\') { |
| - | if (pgetc() != '\n') { |
| - | pungetc(); |
| - | goto READTOKEN1; |
| - | } |
| - | startlinno = ++plinno; |
| - | if (doprompt) |
| - | setprompt(2); |
| - | } else { |
| - | const char *p |
| - | = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1; |
| + | switch (readtoken()) { |
| + | default: |
| + | raise_error_unexpected_syntax(-1); |
| + | /* NOTREACHED */ |
| + | case TIF: |
| + | n1 = stalloc(sizeof(struct nif)); |
| + | n1->type = NIF; |
| + | n1->nif.test = list(0); |
| + | if (readtoken() != TTHEN) |
| + | raise_error_unexpected_syntax(TTHEN); |
| + | n1->nif.ifpart = list(0); |
| + | n2 = n1; |
| + | while (readtoken() == TELIF) { |
| + | n2->nif.elsepart = stalloc(sizeof(struct nif)); |
| + | n2 = n2->nif.elsepart; |
| + | n2->type = NIF; |
| + | n2->nif.test = list(0); |
| + | if (readtoken() != TTHEN) |
| + | raise_error_unexpected_syntax(TTHEN); |
| + | n2->nif.ifpart = list(0); |
| + | } |
| + | if (lasttoken == TELSE) |
| + | n2->nif.elsepart = list(0); |
| + | else { |
| + | n2->nif.elsepart = NULL; |
| + | tokpushback++; |
| + | } |
| + | t = TFI; |
| + | break; |
| + | case TWHILE: |
| + | case TUNTIL: { |
| + | int got; |
| + | n1 = stalloc(sizeof(struct nbinary)); |
| + | n1->type = (lasttoken == TWHILE) ? NWHILE : NUNTIL; |
| + | n1->nbinary.ch1 = list(0); |
| + | got = readtoken(); |
| + | if (got != TDO) { |
| + | TRACE(("expecting DO got %s %s\n", tokname(got), |
| + | got == TWORD ? wordtext : "")); |
| + | raise_error_unexpected_syntax(TDO); |
| + | } |
| + | n1->nbinary.ch2 = list(0); |
| + | t = TDONE; |
| + | break; |
| + | } |
| + | case TFOR: |
| + | if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) |
| + | raise_error_syntax("Bad for loop variable"); |
| + | n1 = stalloc(sizeof(struct nfor)); |
| + | n1->type = NFOR; |
| + | n1->nfor.var = wordtext; |
| + | checkkwd = CHKKWD | CHKALIAS; |
| + | if (readtoken() == TIN) { |
| + | app = ≈ |
| + | while (readtoken() == TWORD) { |
| + | n2 = stalloc(sizeof(struct narg)); |
| + | n2->type = NARG; |
| + | n2->narg.text = wordtext; |
| + | n2->narg.backquote = backquotelist; |
| + | *app = n2; |
| + | app = &n2->narg.next; |
| + | } |
| + | *app = NULL; |
| + | n1->nfor.args = ap; |
| + | if (lasttoken != TNL && lasttoken != TSEMI) |
| + | raise_error_unexpected_syntax(-1); |
| + | } else { |
| + | n2 = stalloc(sizeof(struct narg)); |
| + | n2->type = NARG; |
| + | n2->narg.text = (char *)dolatstr; |
| + | n2->narg.backquote = NULL; |
| + | n2->narg.next = NULL; |
| + | n1->nfor.args = n2; |
| + | /* |
| + | * Newline or semicolon here is optional (but note |
| + | * that the original Bourne shell only allowed NL). |
| + | */ |
| + | if (lasttoken != TNL && lasttoken != TSEMI) |
| + | tokpushback++; |
| + | } |
| + | checkkwd = CHKNL | CHKKWD | CHKALIAS; |
| + | if (readtoken() != TDO) |
| + | raise_error_unexpected_syntax(TDO); |
| + | n1->nfor.body = list(0); |
| + | t = TDONE; |
| + | break; |
| + | case TCASE: |
| + | n1 = stalloc(sizeof(struct ncase)); |
| + | n1->type = NCASE; |
| + | if (readtoken() != TWORD) |
| + | raise_error_unexpected_syntax(TWORD); |
| + | n1->ncase.expr = n2 = stalloc(sizeof(struct narg)); |
| + | n2->type = NARG; |
| + | n2->narg.text = wordtext; |
| + | n2->narg.backquote = backquotelist; |
| + | n2->narg.next = NULL; |
| + | do { |
| + | checkkwd = CHKKWD | CHKALIAS; |
| + | } while (readtoken() == TNL); |
| + | if (lasttoken != TIN) |
| + | raise_error_unexpected_syntax(TIN); |
| + | cpp = &n1->ncase.cases; |
| + | next_case: |
| + | checkkwd = CHKNL | CHKKWD; |
| + | t = readtoken(); |
| + | while (t != TESAC) { |
| + | if (lasttoken == TLP) |
| + | readtoken(); |
| + | *cpp = cp = stalloc(sizeof(struct nclist)); |
| + | cp->type = NCLIST; |
| + | app = &cp->nclist.pattern; |
| + | for (;;) { |
| + | *app = ap = stalloc(sizeof(struct narg)); |
| + | ap->type = NARG; |
| + | ap->narg.text = wordtext; |
| + | ap->narg.backquote = backquotelist; |
| + | if (readtoken() != TPIPE) |
| + | break; |
| + | app = &ap->narg.next; |
| + | readtoken(); |
| + | } |
| + | ap->narg.next = NULL; |
| + | if (lasttoken != TRP) |
| + | raise_error_unexpected_syntax(TRP); |
| + | cp->nclist.body = list(2); |
| - | if (c != PEOF) { |
| - | if (c == '\n') { |
| - | plinno++; |
| - | needprompt = doprompt; |
| - | } |
| + | cpp = &cp->nclist.next; |
| - | p = strchr(xxreadtoken_chars, c); |
| - | if (p == NULL) { |
| - | READTOKEN1: |
| - | return readtoken1(c, BASESYNTAX, (char *) NULL, 0); |
| - | } |
| + | checkkwd = CHKNL | CHKKWD; |
| + | t = readtoken(); |
| + | if (t != TESAC) { |
| + | if (t != TENDCASE) |
| + | raise_error_unexpected_syntax(TENDCASE); |
| + | goto next_case; |
| + | } |
| + | } |
| + | *cpp = NULL; |
| + | goto redir; |
| + | case TLP: |
| + | n1 = stalloc(sizeof(struct nredir)); |
| + | n1->type = NSUBSHELL; |
| + | n1->nredir.n = list(0); |
| + | n1->nredir.redirect = NULL; |
| + | t = TRP; |
| + | break; |
| + | case TBEGIN: |
| + | n1 = list(0); |
| + | t = TEND; |
| + | break; |
| + | case TWORD: |
| + | case TREDIR: |
| + | tokpushback++; |
| + | return simplecmd(); |
| + | } |
| - | if (p - xxreadtoken_chars >= xxreadtoken_singles) { |
| - | if (pgetc() == *p) { /* double occurrence? */ |
| - | p += xxreadtoken_doubles + 1; |
| - | } else { |
| - | pungetc(); |
| - | } |
| - | } |
| - | } |
| - | return lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars]; |
| - | } |
| - | } |
| - | } /* for */ |
| - | } |
| - | #else |
| - | #define RETURN(token) return lasttoken = token |
| - | static int |
| - | xxreadtoken(void) |
| - | { |
| - | int c; |
| + | if (readtoken() != t) |
| + | raise_error_unexpected_syntax(t); |
| - | if (tokpushback) { |
| - | tokpushback = 0; |
| - | return lasttoken; |
| - | } |
| - | if (needprompt) { |
| - | setprompt(2); |
| + | redir: |
| + | /* Now check for redirection which may follow command */ |
| + | checkkwd = CHKKWD | CHKALIAS; |
| + | rpp = rpp2; |
| + | while (readtoken() == TREDIR) { |
| + | *rpp = n2 = redirnode; |
| + | rpp = &n2->nfile.next; |
| + | parsefname(); |
| } | |
| - | startlinno = plinno; |
| - | for (;;) { /* until token or start of word found */ |
| - | c = pgetc_macro(); |
| - | switch (c) { |
| - | case ' ': case '\t': |
| - | #if ENABLE_ASH_ALIAS |
| - | case PEOA: |
| - | #endif |
| - | continue; |
| - | case '#': |
| - | while ((c = pgetc()) != '\n' && c != PEOF); |
| - | pungetc(); |
| - | continue; |
| - | case '\\': |
| - | if (pgetc() == '\n') { |
| - | startlinno = ++plinno; |
| - | if (doprompt) |
| - | setprompt(2); |
| - | continue; |
| - | } |
| - | pungetc(); |
| - | goto breakloop; |
| - | case '\n': |
| - | plinno++; |
| - | needprompt = doprompt; |
| - | RETURN(TNL); |
| - | case PEOF: |
| - | RETURN(TEOF); |
| - | case '&': |
| - | if (pgetc() == '&') |
| - | RETURN(TAND); |
| - | pungetc(); |
| - | RETURN(TBACKGND); |
| - | case '|': |
| - | if (pgetc() == '|') |
| - | RETURN(TOR); |
| - | pungetc(); |
| - | RETURN(TPIPE); |
| - | case ';': |
| - | if (pgetc() == ';') |
| - | RETURN(TENDCASE); |
| - | pungetc(); |
| - | RETURN(TSEMI); |
| - | case '(': |
| - | RETURN(TLP); |
| - | case ')': |
| - | RETURN(TRP); |
| - | default: |
| - | goto breakloop; |
| + | tokpushback++; |
| + | *rpp = NULL; |
| + | if (redir) { |
| + | if (n1->type != NSUBSHELL) { |
| + | n2 = stalloc(sizeof(struct nredir)); |
| + | n2->type = NREDIR; |
| + | n2->nredir.n = n1; |
| + | n1 = n2; |
| } | |
| + | n1->nredir.redirect = redir; |
| } | |
| - | breakloop: |
| - | return readtoken1(c, BASESYNTAX, (char *)NULL, 0); |
| - | #undef RETURN |
| + | return n1; |
| } | |
| - | #endif /* NEW_xxreadtoken */ |
| - | |
| /* | |
| * If eofmark is NULL, read a word or a redirection symbol. If eofmark | |
| @@ -9696,7 +9770,7 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) | |
| if ((c == '>' || c == '<') | |
| && quotef == 0 | |
| && len <= 2 | |
| - | && (*out == '\0' || is_digit(*out))) { |
| + | && (*out == '\0' || isdigit(*out))) { |
| PARSEREDIR(); | |
| return lasttoken = TREDIR; | |
| } else { | |
| @@ -9864,11 +9938,11 @@ parsesub: { | |
| STPUTC(c, out); | |
| c = pgetc(); | |
| } while (c > PEOA_OR_PEOF && is_in_name(c)); | |
| - | } else if (is_digit(c)) { |
| + | } else if (isdigit(c)) { |
| do { | |
| STPUTC(c, out); | |
| c = pgetc(); | |
| - | } while (is_digit(c)); |
| + | } while (isdigit(c)); |
| } else if (is_special(c)) { | |
| USTPUTC(c, out); | |
| c = pgetc(); | |
| @@ -10040,10 +10114,8 @@ parsebackq: { | |
| if (oldstyle) | |
| doprompt = saveprompt; | |
| - | else { |
| - | if (readtoken() != TRP) |
| - | raise_error_unexpected_syntax(TRP); |
| - | } |
| + | else if (readtoken() != TRP) |
| + | raise_error_unexpected_syntax(TRP); |
| (*nlpp)->n = n; | |
| if (oldstyle) { | |
| @@ -10072,1611 +10144,1572 @@ parsebackq: { | |
| else | |
| USTPUTC(CTLBACKQ, out); | |
| if (oldstyle) | |
| - | goto parsebackq_oldreturn; |
| - | goto parsebackq_newreturn; |
| - | } |
| - | |
| - | #if ENABLE_ASH_MATH_SUPPORT |
| - | /* |
| - | * Parse an arithmetic expansion (indicate start of one and set state) |
| - | */ |
| - | parsearith: { |
| - | if (++arinest == 1) { |
| - | prevsyntax = syntax; |
| - | syntax = ARISYNTAX; |
| - | USTPUTC(CTLARI, out); |
| - | if (dblquote) |
| - | USTPUTC('"', out); |
| - | else |
| - | USTPUTC(' ', out); |
| - | } else { |
| - | /* |
| - | * we collapse embedded arithmetic expansion to |
| - | * parenthesis, which should be equivalent |
| - | */ |
| - | USTPUTC('(', out); |
| - | } |
| - | goto parsearith_return; |
| - | } |
| - | #endif |
| - | |
| - | } /* end of readtoken */ |
| - | |
| - | |
| - | /* |
| - | * Returns true if the text contains nothing to expand (no dollar signs |
| - | * or backquotes). |
| - | */ |
| - | static int |
| - | noexpand(char *text) |
| - | { |
| - | char *p; |
| - | char c; |
| - | |
| - | p = text; |
| - | while ((c = *p++) != '\0') { |
| - | if (c == CTLQUOTEMARK) |
| - | continue; |
| - | if (c == CTLESC) |
| - | p++; |
| - | else if (SIT(c, BASESYNTAX) == CCTL) |
| - | return 0; |
| - | } |
| - | return 1; |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Return of a legal variable name (a letter or underscore followed by zero or |
| - | * more letters, underscores, and digits). |
| - | */ |
| - | static char * |
| - | endofname(const char *name) |
| - | { |
| - | char *p; |
| - | |
| - | p = (char *) name; |
| - | if (!is_name(*p)) |
| - | return p; |
| - | while (*++p) { |
| - | if (!is_in_name(*p)) |
| - | break; |
| - | } |
| - | return p; |
| - | } |
| - | |
| - | |
| - | /* |
| - | * called by editline -- any expansions to the prompt |
| - | * should be added here. |
| - | */ |
| - | #if ENABLE_ASH_EXPAND_PRMT |
| - | static const char * |
| - | expandstr(const char *ps) |
| - | { |
| - | union node n; |
| - | |
| - | /* XXX Fix (char *) cast. */ |
| - | setinputstring((char *)ps); |
| - | readtoken1(pgetc(), DQSYNTAX, nullstr, 0); |
| - | popfile(); |
| - | |
| - | n.narg.type = NARG; |
| - | n.narg.next = NULL; |
| - | n.narg.text = wordtext; |
| - | n.narg.backquote = backquotelist; |
| - | |
| - | expandarg(&n, NULL, 0); |
| - | return stackblock(); |
| - | } |
| - | #endif |
| - | |
| - | static void setprompt(int whichprompt) |
| - | { |
| - | const char *prompt; |
| - | #if ENABLE_ASH_EXPAND_PRMT |
| - | struct stackmark smark; |
| - | #endif |
| - | |
| - | needprompt = 0; |
| - | |
| - | switch (whichprompt) { |
| - | case 1: |
| - | prompt = ps1val(); |
| - | break; |
| - | case 2: |
| - | prompt = ps2val(); |
| - | break; |
| - | default: /* 0 */ |
| - | prompt = nullstr; |
| - | } |
| - | #if ENABLE_ASH_EXPAND_PRMT |
| - | setstackmark(&smark); |
| - | stalloc(stackblocksize()); |
| - | #endif |
| - | putprompt(expandstr(prompt)); |
| - | #if ENABLE_ASH_EXPAND_PRMT |
| - | popstackmark(&smark); |
| - | #endif |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Execute a command or commands contained in a string. |
| - | */ |
| - | static int |
| - | evalstring(char *s, int mask) |
| - | { |
| - | union node *n; |
| - | struct stackmark smark; |
| - | int skip; |
| - | |
| - | setinputstring(s); |
| - | setstackmark(&smark); |
| - | |
| - | skip = 0; |
| - | while ((n = parsecmd(0)) != NEOF) { |
| - | evaltree(n, 0); |
| - | popstackmark(&smark); |
| - | skip = evalskip; |
| - | if (skip) |
| - | break; |
| - | } |
| - | popfile(); |
| - | |
| - | skip &= mask; |
| - | evalskip = skip; |
| - | return skip; |
| + | goto parsebackq_oldreturn; |
| + | goto parsebackq_newreturn; |
| } | |
| + | #if ENABLE_ASH_MATH_SUPPORT |
| /* | |
| - | * The eval command. |
| + | * Parse an arithmetic expansion (indicate start of one and set state) |
| */ | |
| - | static int |
| - | evalcmd(int argc, char **argv) |
| - | { |
| - | char *p; |
| - | char *concat; |
| - | char **ap; |
| - | |
| - | if (argc > 1) { |
| - | p = argv[1]; |
| - | if (argc > 2) { |
| - | STARTSTACKSTR(concat); |
| - | ap = argv + 2; |
| - | for (;;) { |
| - | concat = stack_putstr(p, concat); |
| - | p = *ap++; |
| - | if (p == NULL) |
| - | break; |
| - | STPUTC(' ', concat); |
| - | } |
| - | STPUTC('\0', concat); |
| - | p = grabstackstr(concat); |
| - | } |
| - | evalstring(p, ~SKIPEVAL); |
| - | |
| + | parsearith: { |
| + | if (++arinest == 1) { |
| + | prevsyntax = syntax; |
| + | syntax = ARISYNTAX; |
| + | USTPUTC(CTLARI, out); |
| + | if (dblquote) |
| + | USTPUTC('"', out); |
| + | else |
| + | USTPUTC(' ', out); |
| + | } else { |
| + | /* |
| + | * we collapse embedded arithmetic expansion to |
| + | * parenthesis, which should be equivalent |
| + | */ |
| + | USTPUTC('(', out); |
| } | |
| - | return exitstatus; |
| + | goto parsearith_return; |
| } | |
| + | #endif |
| + | |
| + | } /* end of readtoken */ |
| /* | |
| - | * Read and execute commands. "Top" is nonzero for the top level command |
| - | * loop; it turns on prompting if the shell is interactive. |
| + | * Read the next input token. |
| + | * If the token is a word, we set backquotelist to the list of cmds in |
| + | * backquotes. We set quoteflag to true if any part of the word was |
| + | * quoted. |
| + | * If the token is TREDIR, then we set redirnode to a structure containing |
| + | * the redirection. |
| + | * In all cases, the variable startlinno is set to the number of the line |
| + | * on which the token starts. |
| + | * |
| + | * [Change comment: here documents and internal procedures] |
| + | * [Readtoken shouldn't have any arguments. Perhaps we should make the |
| + | * word parsing code into a separate routine. In this case, readtoken |
| + | * doesn't need to have any internal procedures, but parseword does. |
| + | * We could also make parseoperator in essence the main routine, and |
| + | * have parseword (readtoken1?) handle both words and redirection.] |
| */ | |
| - | static int |
| - | cmdloop(int top) |
| - | { |
| - | union node *n; |
| - | struct stackmark smark; |
| - | int inter; |
| - | int numeof = 0; |
| - | |
| - | TRACE(("cmdloop(%d) called\n", top)); |
| - | for (;;) { |
| - | int skip; |
| + | #define NEW_xxreadtoken |
| + | #ifdef NEW_xxreadtoken |
| + | /* singles must be first! */ |
| + | static const char xxreadtoken_chars[7] = { '\n', '(', ')', '&', '|', ';', 0 }; |
| - | setstackmark(&smark); |
| - | #if JOBS |
| - | if (jobctl) |
| - | showjobs(stderr, SHOW_CHANGED); |
| - | #endif |
| - | inter = 0; |
| - | if (iflag && top) { |
| - | inter++; |
| - | #if ENABLE_ASH_MAIL |
| - | chkmail(); |
| - | #endif |
| - | } |
| - | n = parsecmd(inter); |
| - | /* showtree(n); DEBUG */ |
| - | if (n == NEOF) { |
| - | if (!top || numeof >= 50) |
| - | break; |
| - | if (!stoppedjobs()) { |
| - | if (!Iflag) |
| - | break; |
| - | out2str("\nUse \"exit\" to leave shell.\n"); |
| - | } |
| - | numeof++; |
| - | } else if (nflag == 0) { |
| - | job_warning = (job_warning == 2) ? 1 : 0; |
| - | numeof = 0; |
| - | evaltree(n, 0); |
| - | } |
| - | popstackmark(&smark); |
| - | skip = evalskip; |
| + | static const char xxreadtoken_tokens[] = { |
| + | TNL, TLP, TRP, /* only single occurrence allowed */ |
| + | TBACKGND, TPIPE, TSEMI, /* if single occurrence */ |
| + | TEOF, /* corresponds to trailing nul */ |
| + | TAND, TOR, TENDCASE, /* if double occurrence */ |
| + | }; |
| - | if (skip) { |
| - | evalskip = 0; |
| - | return skip & SKIPEVAL; |
| - | } |
| - | } |
| - | return 0; |
| - | } |
| + | #define xxreadtoken_doubles \ |
| + | (sizeof(xxreadtoken_tokens) - sizeof(xxreadtoken_chars)) |
| + | #define xxreadtoken_singles \ |
| + | (sizeof(xxreadtoken_chars) - xxreadtoken_doubles - 1) |
| static int | |
| - | dotcmd(int argc, char **argv) |
| + | xxreadtoken(void) |
| { | |
| - | struct strlist *sp; |
| - | volatile struct shparam saveparam; |
| - | int status = 0; |
| + | int c; |
| - | for (sp = cmdenviron; sp; sp = sp->next) |
| - | setvareq(xstrdup(sp->text), VSTRFIXED | VTEXTFIXED); |
| + | if (tokpushback) { |
| + | tokpushback = 0; |
| + | return lasttoken; |
| + | } |
| + | if (needprompt) { |
| + | setprompt(2); |
| + | } |
| + | startlinno = plinno; |
| + | for (;;) { /* until token or start of word found */ |
| + | c = pgetc_macro(); |
| - | if (argc >= 2) { /* That's what SVR2 does */ |
| - | char *fullname; |
| + | if ((c != ' ') && (c != '\t') |
| + | #if ENABLE_ASH_ALIAS |
| + | && (c != PEOA) |
| + | #endif |
| + | ) { |
| + | if (c == '#') { |
| + | while ((c = pgetc()) != '\n' && c != PEOF); |
| + | pungetc(); |
| + | } else if (c == '\\') { |
| + | if (pgetc() != '\n') { |
| + | pungetc(); |
| + | goto READTOKEN1; |
| + | } |
| + | startlinno = ++plinno; |
| + | if (doprompt) |
| + | setprompt(2); |
| + | } else { |
| + | const char *p |
| + | = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1; |
| - | fullname = find_dot_file(argv[1]); |
| + | if (c != PEOF) { |
| + | if (c == '\n') { |
| + | plinno++; |
| + | needprompt = doprompt; |
| + | } |
| - | if (argc > 2) { |
| - | saveparam = shellparam; |
| - | shellparam.malloc = 0; |
| - | shellparam.nparam = argc - 2; |
| - | shellparam.p = argv + 2; |
| - | }; |
| + | p = strchr(xxreadtoken_chars, c); |
| + | if (p == NULL) { |
| + | READTOKEN1: |
| + | return readtoken1(c, BASESYNTAX, (char *) NULL, 0); |
| + | } |
| - | setinputfile(fullname, INPUT_PUSH_FILE); |
| - | commandname = fullname; |
| - | cmdloop(0); |
| - | popfile(); |
| + | if (p - xxreadtoken_chars >= xxreadtoken_singles) { |
| + | if (pgetc() == *p) { /* double occurrence? */ |
| + | p += xxreadtoken_doubles + 1; |
| + | } else { |
| + | pungetc(); |
| + | } |
| + | } |
| + | } |
| + | return lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars]; |
| + | } |
| + | } |
| + | } /* for */ |
| + | } |
| + | #else |
| + | #define RETURN(token) return lasttoken = token |
| + | static int |
| + | xxreadtoken(void) |
| + | { |
| + | int c; |
| - | if (argc > 2) { |
| - | freeparam(&shellparam); |
| - | shellparam = saveparam; |
| - | }; |
| - | status = exitstatus; |
| + | if (tokpushback) { |
| + | tokpushback = 0; |
| + | return lasttoken; |
| + | } |
| + | if (needprompt) { |
| + | setprompt(2); |
| + | } |
| + | startlinno = plinno; |
| + | for (;;) { /* until token or start of word found */ |
| + | c = pgetc_macro(); |
| + | switch (c) { |
| + | case ' ': case '\t': |
| + | #if ENABLE_ASH_ALIAS |
| + | case PEOA: |
| + | #endif |
| + | continue; |
| + | case '#': |
| + | while ((c = pgetc()) != '\n' && c != PEOF); |
| + | pungetc(); |
| + | continue; |
| + | case '\\': |
| + | if (pgetc() == '\n') { |
| + | startlinno = ++plinno; |
| + | if (doprompt) |
| + | setprompt(2); |
| + | continue; |
| + | } |
| + | pungetc(); |
| + | goto breakloop; |
| + | case '\n': |
| + | plinno++; |
| + | needprompt = doprompt; |
| + | RETURN(TNL); |
| + | case PEOF: |
| + | RETURN(TEOF); |
| + | case '&': |
| + | if (pgetc() == '&') |
| + | RETURN(TAND); |
| + | pungetc(); |
| + | RETURN(TBACKGND); |
| + | case '|': |
| + | if (pgetc() == '|') |
| + | RETURN(TOR); |
| + | pungetc(); |
| + | RETURN(TPIPE); |
| + | case ';': |
| + | if (pgetc() == ';') |
| + | RETURN(TENDCASE); |
| + | pungetc(); |
| + | RETURN(TSEMI); |
| + | case '(': |
| + | RETURN(TLP); |
| + | case ')': |
| + | RETURN(TRP); |
| + | default: |
| + | goto breakloop; |
| + | } |
| } | |
| - | return status; |
| - | } |
| - | |
| - | static int |
| - | exitcmd(int argc, char **argv) |
| - | { |
| - | if (stoppedjobs()) |
| - | return 0; |
| - | if (argc > 1) |
| - | exitstatus = number(argv[1]); |
| - | raise_exception(EXEXIT); |
| - | /* NOTREACHED */ |
| - | } |
| - | |
| - | #if ENABLE_ASH_BUILTIN_ECHO |
| - | static int |
| - | echocmd(int argc, char **argv) |
| - | { |
| - | return bb_echo(argv); |
| + | breakloop: |
| + | return readtoken1(c, BASESYNTAX, (char *)NULL, 0); |
| + | #undef RETURN |
| } | |
| - | #endif |
| + | #endif /* NEW_xxreadtoken */ |
| - | #if ENABLE_ASH_BUILTIN_TEST |
| static int | |
| - | testcmd(int argc, char **argv) |
| + | readtoken(void) |
| { | |
| - | return bb_test(argc, argv); |
| - | } |
| + | int t; |
| + | #if DEBUG |
| + | int alreadyseen = tokpushback; |
| #endif | |
| - | /* |
| - | * Read a file containing shell functions. |
| - | */ |
| - | static void |
| - | readcmdfile(char *name) |
| - | { |
| - | setinputfile(name, INPUT_PUSH_FILE); |
| - | cmdloop(0); |
| - | popfile(); |
| - | } |
| - | |
| - | |
| - | /* redir.c */ |
| - | |
| - | /* |
| - | * Code for dealing with input/output redirection. |
| - | */ |
| - | |
| - | #define EMPTY -2 /* marks an unused slot in redirtab */ |
| - | #ifndef PIPE_BUF |
| - | # define PIPESIZE 4096 /* amount of buffering in a pipe */ |
| - | #else |
| - | # define PIPESIZE PIPE_BUF |
| + | #if ENABLE_ASH_ALIAS |
| + | top: |
| #endif | |
| - | /* |
| - | * Open a file in noclobber mode. |
| - | * The code was copied from bash. |
| - | */ |
| - | static int |
| - | noclobberopen(const char *fname) |
| - | { |
| - | int r, fd; |
| - | struct stat finfo, finfo2; |
| + | t = xxreadtoken(); |
| /* | |
| - | * If the file exists and is a regular file, return an error |
| - | * immediately. |
| + | * eat newlines |
| */ | |
| - | r = stat(fname, &finfo); |
| - | if (r == 0 && S_ISREG(finfo.st_mode)) { |
| - | errno = EEXIST; |
| - | return -1; |
| + | if (checkkwd & CHKNL) { |
| + | while (t == TNL) { |
| + | parseheredoc(); |
| + | t = xxreadtoken(); |
| + | } |
| } | |
| - | /* |
| - | * If the file was not present (r != 0), make sure we open it |
| - | * exclusively so that if it is created before we open it, our open |
| - | * will fail. Make sure that we do not truncate an existing file. |
| - | * Note that we don't turn on O_EXCL unless the stat failed -- if the |
| - | * file was not a regular file, we leave O_EXCL off. |
| - | */ |
| - | if (r != 0) |
| - | return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); |
| - | fd = open(fname, O_WRONLY|O_CREAT, 0666); |
| - | |
| - | /* If the open failed, return the file descriptor right away. */ |
| - | if (fd < 0) |
| - | return fd; |
| - | |
| - | /* |
| - | * OK, the open succeeded, but the file may have been changed from a |
| - | * non-regular file to a regular file between the stat and the open. |
| - | * We are assuming that the O_EXCL open handles the case where FILENAME |
| - | * did not exist and is symlinked to an existing file between the stat |
| - | * and open. |
| - | */ |
| + | if (t != TWORD || quoteflag) { |
| + | goto out; |
| + | } |
| /* | |
| - | * If we can open it and fstat the file descriptor, and neither check |
| - | * revealed that it was a regular file, and the file has not been |
| - | * replaced, return the file descriptor. |
| + | * check for keywords |
| */ | |
| - | if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) |
| - | && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino) |
| - | return fd; |
| - | |
| - | /* The file has been replaced. badness. */ |
| - | close(fd); |
| - | errno = EEXIST; |
| - | return -1; |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Handle here documents. Normally we fork off a process to write the |
| - | * data to a pipe. If the document is short, we can stuff the data in |
| - | * the pipe without forking. |
| - | */ |
| - | static int |
| - | openhere(union node *redir) |
| - | { |
| - | int pip[2]; |
| - | size_t len = 0; |
| + | if (checkkwd & CHKKWD) { |
| + | const char *const *pp; |
| - | if (pipe(pip) < 0) |
| - | ash_msg_and_raise_error("Pipe call failed"); |
| - | if (redir->type == NHERE) { |
| - | len = strlen(redir->nhere.doc->narg.text); |
| - | if (len <= PIPESIZE) { |
| - | full_write(pip[1], redir->nhere.doc->narg.text, len); |
| + | pp = findkwd(wordtext); |
| + | if (pp) { |
| + | lasttoken = t = pp - tokname_array; |
| + | TRACE(("keyword %s recognized\n", tokname(t))); |
| goto out; | |
| } | |
| } | |
| - | if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { |
| - | close(pip[0]); |
| - | signal(SIGINT, SIG_IGN); |
| - | signal(SIGQUIT, SIG_IGN); |
| - | signal(SIGHUP, SIG_IGN); |
| - | #ifdef SIGTSTP |
| - | signal(SIGTSTP, SIG_IGN); |
| + | |
| + | if (checkkwd & CHKALIAS) { |
| + | #if ENABLE_ASH_ALIAS |
| + | struct alias *ap; |
| + | ap = lookupalias(wordtext, 1); |
| + | if (ap != NULL) { |
| + | if (*ap->val) { |
| + | pushstring(ap->val, ap); |
| + | } |
| + | goto top; |
| + | } |
| #endif | |
| - | signal(SIGPIPE, SIG_DFL); |
| - | if (redir->type == NHERE) |
| - | full_write(pip[1], redir->nhere.doc->narg.text, len); |
| - | else |
| - | expandhere(redir->nhere.doc, pip[1]); |
| - | _exit(0); |
| } | |
| out: | |
| - | close(pip[1]); |
| - | return pip[0]; |
| + | checkkwd = 0; |
| + | #if DEBUG |
| + | if (!alreadyseen) |
| + | TRACE(("token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); |
| + | else |
| + | TRACE(("reread token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); |
| + | #endif |
| + | return t; |
| } | |
| - | static int |
| - | openredirect(union node *redir) |
| + | static char |
| + | peektoken(void) |
| { | |
| - | char *fname; |
| - | int f; |
| + | int t; |
| - | switch (redir->nfile.type) { |
| - | case NFROM: |
| - | fname = redir->nfile.expfname; |
| - | f = open(fname, O_RDONLY); |
| - | if (f < 0) |
| - | goto eopen; |
| - | break; |
| - | case NFROMTO: |
| - | fname = redir->nfile.expfname; |
| - | f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666); |
| - | if (f < 0) |
| - | goto ecreate; |
| - | break; |
| - | case NTO: |
| - | /* Take care of noclobber mode. */ |
| - | if (Cflag) { |
| - | fname = redir->nfile.expfname; |
| - | f = noclobberopen(fname); |
| - | if (f < 0) |
| - | goto ecreate; |
| - | break; |
| - | } |
| - | /* FALLTHROUGH */ |
| - | case NCLOBBER: |
| - | fname = redir->nfile.expfname; |
| - | f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666); |
| - | if (f < 0) |
| - | goto ecreate; |
| - | break; |
| - | case NAPPEND: |
| - | fname = redir->nfile.expfname; |
| - | f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666); |
| - | if (f < 0) |
| - | goto ecreate; |
| - | break; |
| - | default: |
| - | #if DEBUG |
| - | abort(); |
| - | #endif |
| - | /* Fall through to eliminate warning. */ |
| - | case NTOFD: |
| - | case NFROMFD: |
| - | f = -1; |
| - | break; |
| - | case NHERE: |
| - | case NXHERE: |
| - | f = openhere(redir); |
| - | break; |
| - | } |
| + | t = readtoken(); |
| + | tokpushback++; |
| + | return tokname_array[t][0]; |
| + | } |
| + | |
| + | /* |
| + | * Read and parse a command. Returns NEOF on end of file. (NULL is a |
| + | * valid parse tree indicating a blank line.) |
| + | */ |
| + | static union node * |
| + | parsecmd(int interact) |
| + | { |
| + | int t; |
| - | return f; |
| - | ecreate: |
| - | ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent")); |
| - | eopen: |
| - | ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file")); |
| + | tokpushback = 0; |
| + | doprompt = interact; |
| + | if (doprompt) |
| + | setprompt(doprompt); |
| + | needprompt = 0; |
| + | t = readtoken(); |
| + | if (t == TEOF) |
| + | return NEOF; |
| + | if (t == TNL) |
| + | return NULL; |
| + | tokpushback++; |
| + | return list(1); |
| } | |
| + | /* |
| + | * Input any here documents. |
| + | */ |
| static void | |
| - | dupredirect(union node *redir, int f) |
| + | parseheredoc(void) |
| { | |
| - | int fd = redir->nfile.fd; |
| + | struct heredoc *here; |
| + | union node *n; |
| - | if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) { |
| - | if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ |
| - | copyfd(redir->ndup.dupfd, fd); |
| - | } |
| - | return; |
| - | } |
| + | here = heredoclist; |
| + | heredoclist = 0; |
| - | if (f != fd) { |
| - | copyfd(f, fd); |
| - | close(f); |
| + | while (here) { |
| + | if (needprompt) { |
| + | setprompt(2); |
| + | } |
| + | readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, |
| + | here->eofmark, here->striptabs); |
| + | n = stalloc(sizeof(struct narg)); |
| + | n->narg.type = NARG; |
| + | n->narg.next = NULL; |
| + | n->narg.text = wordtext; |
| + | n->narg.backquote = backquotelist; |
| + | here->here->nhere.doc = n; |
| + | here = here->next; |
| } | |
| } | |
| /* | |
| - | * Process a list of redirection commands. If the REDIR_PUSH flag is set, |
| - | * old file descriptors are stashed away so that the redirection can be |
| - | * undone by calling popredir. If the REDIR_BACKQ flag is set, then the |
| - | * standard output, and the standard error if it becomes a duplicate of |
| - | * stdout, is saved in memory. |
| + | * called by editline -- any expansions to the prompt |
| + | * should be added here. |
| */ | |
| - | static void |
| - | redirect(union node *redir, int flags) |
| + | #if ENABLE_ASH_EXPAND_PRMT |
| + | static const char * |
| + | expandstr(const char *ps) |
| { | |
| - | union node *n; |
| - | struct redirtab *sv; |
| - | int i; |
| - | int fd; |
| - | int newfd; |
| - | int *p; |
| - | nullredirs++; |
| - | if (!redir) { |
| - | return; |
| - | } |
| - | sv = NULL; |
| - | INT_OFF; |
| - | if (flags & REDIR_PUSH) { |
| - | struct redirtab *q; |
| - | q = ckmalloc(sizeof(struct redirtab)); |
| - | q->next = redirlist; |
| - | redirlist = q; |
| - | q->nullredirs = nullredirs - 1; |
| - | for (i = 0; i < 10; i++) |
| - | q->renamed[i] = EMPTY; |
| - | nullredirs = 0; |
| - | sv = q; |
| - | } |
| - | n = redir; |
| - | do { |
| - | fd = n->nfile.fd; |
| - | if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) |
| - | && n->ndup.dupfd == fd) |
| - | continue; /* redirect from/to same file descriptor */ |
| + | union node n; |
| - | newfd = openredirect(n); |
| - | if (fd == newfd) |
| - | continue; |
| - | if (sv && *(p = &sv->renamed[fd]) == EMPTY) { |
| - | i = fcntl(fd, F_DUPFD, 10); |
| + | /* XXX Fix (char *) cast. */ |
| + | setinputstring((char *)ps); |
| + | readtoken1(pgetc(), DQSYNTAX, nullstr, 0); |
| + | popfile(); |
| - | if (i == -1) { |
| - | i = errno; |
| - | if (i != EBADF) { |
| - | close(newfd); |
| - | errno = i; |
| - | ash_msg_and_raise_error("%d: %m", fd); |
| - | /* NOTREACHED */ |
| - | } |
| - | } else { |
| - | *p = i; |
| - | close(fd); |
| - | } |
| - | } else { |
| - | close(fd); |
| - | } |
| - | dupredirect(n, newfd); |
| - | } while ((n = n->nfile.next)); |
| - | INT_ON; |
| - | if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0) |
| - | preverrout_fd = sv->renamed[2]; |
| + | n.narg.type = NARG; |
| + | n.narg.next = NULL; |
| + | n.narg.text = wordtext; |
| + | n.narg.backquote = backquotelist; |
| + | |
| + | expandarg(&n, NULL, 0); |
| + | return stackblock(); |
| } | |
| + | #endif |
| /* | |
| - | * Undo the effects of the last redirection. |
| + | * Execute a command or commands contained in a string. |
| */ | |
| - | static void |
| - | popredir(int drop) |
| + | static int |
| + | evalstring(char *s, int mask) |
| { | |
| - | struct redirtab *rp; |
| - | int i; |
| + | union node *n; |
| + | struct stackmark smark; |
| + | int skip; |
| - | if (--nullredirs >= 0) |
| - | return; |
| - | INT_OFF; |
| - | rp = redirlist; |
| - | for (i = 0; i < 10; i++) { |
| - | if (rp->renamed[i] != EMPTY) { |
| - | if (!drop) { |
| - | close(i); |
| - | copyfd(rp->renamed[i], i); |
| - | } |
| - | close(rp->renamed[i]); |
| - | } |
| + | setinputstring(s); |
| + | setstackmark(&smark); |
| + | |
| + | skip = 0; |
| + | while ((n = parsecmd(0)) != NEOF) { |
| + | evaltree(n, 0); |
| + | popstackmark(&smark); |
| + | skip = evalskip; |
| + | if (skip) |
| + | break; |
| } | |
| - | redirlist = rp->next; |
| - | nullredirs = rp->nullredirs; |
| - | free(rp); |
| - | INT_ON; |
| - | } |
| + | popfile(); |
| - | /* |
| - | * Undo all redirections. Called on error or interrupt. |
| - | */ |
| + | skip &= mask; |
| + | evalskip = skip; |
| + | return skip; |
| + | } |
| /* | |
| - | * Discard all saved file descriptors. |
| + | * The eval command. |
| */ | |
| - | static void |
| - | clearredir(int drop) |
| + | static int |
| + | evalcmd(int argc, char **argv) |
| { | |
| - | for (;;) { |
| - | nullredirs = 0; |
| - | if (!redirlist) |
| - | break; |
| - | popredir(drop); |
| + | char *p; |
| + | char *concat; |
| + | char **ap; |
| + | |
| + | if (argc > 1) { |
| + | p = argv[1]; |
| + | if (argc > 2) { |
| + | STARTSTACKSTR(concat); |
| + | ap = argv + 2; |
| + | for (;;) { |
| + | concat = stack_putstr(p, concat); |
| + | p = *ap++; |
| + | if (p == NULL) |
| + | break; |
| + | STPUTC(' ', concat); |
| + | } |
| + | STPUTC('\0', concat); |
| + | p = grabstackstr(concat); |
| + | } |
| + | evalstring(p, ~SKIPEVAL); |
| + | |
| } | |
| + | return exitstatus; |
| } | |
| - | |
| /* | |
| - | * Copy a file descriptor to be >= to. Returns -1 |
| - | * if the source file descriptor is closed, EMPTY if there are no unused |
| - | * file descriptors left. |
| + | * Read and execute commands. "Top" is nonzero for the top level command |
| + | * loop; it turns on prompting if the shell is interactive. |
| */ | |
| static int | |
| - | copyfd(int from, int to) |
| + | cmdloop(int top) |
| { | |
| - | int newfd; |
| + | union node *n; |
| + | struct stackmark smark; |
| + | int inter; |
| + | int numeof = 0; |
| + | |
| + | TRACE(("cmdloop(%d) called\n", top)); |
| + | for (;;) { |
| + | int skip; |
| + | |
| + | setstackmark(&smark); |
| + | #if JOBS |
| + | if (jobctl) |
| + | showjobs(stderr, SHOW_CHANGED); |
| + | #endif |
| + | inter = 0; |
| + | if (iflag && top) { |
| + | inter++; |
| + | #if ENABLE_ASH_MAIL |
| + | chkmail(); |
| + | #endif |
| + | } |
| + | n = parsecmd(inter); |
| + | /* showtree(n); DEBUG */ |
| + | if (n == NEOF) { |
| + | if (!top || numeof >= 50) |
| + | break; |
| + | if (!stoppedjobs()) { |
| + | if (!Iflag) |
| + | break; |
| + | out2str("\nUse \"exit\" to leave shell.\n"); |
| + | } |
| + | numeof++; |
| + | } else if (nflag == 0) { |
| + | job_warning = (job_warning == 2) ? 1 : 0; |
| + | numeof = 0; |
| + | evaltree(n, 0); |
| + | } |
| + | popstackmark(&smark); |
| + | skip = evalskip; |
| - | newfd = fcntl(from, F_DUPFD, to); |
| - | if (newfd < 0) { |
| - | if (errno == EMFILE) |
| - | return EMPTY; |
| - | ash_msg_and_raise_error("%d: %m", from); |
| + | if (skip) { |
| + | evalskip = 0; |
| + | return skip & SKIPEVAL; |
| + | } |
| } | |
| - | return newfd; |
| + | return 0; |
| } | |
| - | |
| static int | |
| - | redirectsafe(union node *redir, int flags) |
| + | dotcmd(int argc, char **argv) |
| { | |
| - | int err; |
| - | volatile int saveint; |
| - | struct jmploc *volatile savehandler = exception_handler; |
| - | struct jmploc jmploc; |
| - | |
| - | SAVE_INT(saveint); |
| - | err = setjmp(jmploc.loc) * 2; |
| - | if (!err) { |
| - | exception_handler = &jmploc; |
| - | redirect(redir, flags); |
| - | } |
| - | exception_handler = savehandler; |
| - | if (err && exception != EXERROR) |
| - | longjmp(exception_handler->loc, 1); |
| - | RESTORE_INT(saveint); |
| - | return err; |
| - | } |
| - | |
| - | /* show.c */ |
| - | |
| - | #if DEBUG |
| - | static void shtree(union node *, int, char *, FILE*); |
| - | static void shcmd(union node *, FILE *); |
| - | static void sharg(union node *, FILE *); |
| - | static void indent(int, char *, FILE *); |
| - | static void trstring(char *); |
| + | struct strlist *sp; |
| + | volatile struct shparam saveparam; |
| + | int status = 0; |
| + | for (sp = cmdenviron; sp; sp = sp->next) |
| + | setvareq(xstrdup(sp->text), VSTRFIXED | VTEXTFIXED); |
| - | static void |
| - | showtree(union node *n) |
| - | { |
| - | trputs("showtree called\n"); |
| - | shtree(n, 1, NULL, stdout); |
| - | } |
| + | if (argc >= 2) { /* That's what SVR2 does */ |
| + | char *fullname; |
| + | fullname = find_dot_file(argv[1]); |
| - | static void |
| - | shtree(union node *n, int ind, char *pfx, FILE *fp) |
| - | { |
| - | struct nodelist *lp; |
| - | const char *s; |
| + | if (argc > 2) { |
| + | saveparam = shellparam; |
| + | shellparam.malloc = 0; |
| + | shellparam.nparam = argc - 2; |
| + | shellparam.p = argv + 2; |
| + | }; |
| - | if (n == NULL) |
| - | return; |
| + | setinputfile(fullname, INPUT_PUSH_FILE); |
| + | commandname = fullname; |
| + | cmdloop(0); |
| + | popfile(); |
| - | indent(ind, pfx, fp); |
| - | switch (n->type) { |
| - | case NSEMI: |
| - | s = "; "; |
| - | goto binop; |
| - | case NAND: |
| - | s = " && "; |
| - | goto binop; |
| - | case NOR: |
| - | s = " || "; |
| - | binop: |
| - | shtree(n->nbinary.ch1, ind, NULL, fp); |
| - | /* if (ind < 0) */ |
| - | fputs(s, fp); |
| - | shtree(n->nbinary.ch2, ind, NULL, fp); |
| - | break; |
| - | case NCMD: |
| - | shcmd(n, fp); |
| - | if (ind >= 0) |
| - | putc('\n', fp); |
| - | break; |
| - | case NPIPE: |
| - | for (lp = n->npipe.cmdlist; lp; lp = lp->next) { |
| - | shcmd(lp->n, fp); |
| - | if (lp->next) |
| - | fputs(" | ", fp); |
| - | } |
| - | if (n->npipe.backgnd) |
| - | fputs(" &", fp); |
| - | if (ind >= 0) |
| - | putc('\n', fp); |
| - | break; |
| - | default: |
| - | fprintf(fp, "<node type %d>", n->type); |
| - | if (ind >= 0) |
| - | putc('\n', fp); |
| - | break; |
| + | if (argc > 2) { |
| + | freeparam(&shellparam); |
| + | shellparam = saveparam; |
| + | }; |
| + | status = exitstatus; |
| } | |
| + | return status; |
| } | |
| - | |
| - | static void |
| - | shcmd(union node *cmd, FILE *fp) |
| + | static int |
| + | exitcmd(int argc, char **argv) |
| { | |
| - | union node *np; |
| - | int first; |
| - | const char *s; |
| - | int dftfd; |
| - | |
| - | first = 1; |
| - | for (np = cmd->ncmd.args; np; np = np->narg.next) { |
| - | if (! first) |
| - | putchar(' '); |
| - | sharg(np, fp); |
| - | first = 0; |
| - | } |
| - | for (np = cmd->ncmd.redirect; np; np = np->nfile.next) { |
| - | if (! first) |
| - | putchar(' '); |
| - | switch (np->nfile.type) { |
| - | case NTO: s = ">"; dftfd = 1; break; |
| - | case NCLOBBER: s = ">|"; dftfd = 1; break; |
| - | case NAPPEND: s = ">>"; dftfd = 1; break; |
| - | case NTOFD: s = ">&"; dftfd = 1; break; |
| - | case NFROM: s = "<"; dftfd = 0; break; |
| - | case NFROMFD: s = "<&"; dftfd = 0; break; |
| - | case NFROMTO: s = "<>"; dftfd = 0; break; |
| - | default: s = "*error*"; dftfd = 0; break; |
| - | } |
| - | if (np->nfile.fd != dftfd) |
| - | fprintf(fp, "%d", np->nfile.fd); |
| - | fputs(s, fp); |
| - | if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { |
| - | fprintf(fp, "%d", np->ndup.dupfd); |
| - | } else { |
| - | sharg(np->nfile.fname, fp); |
| - | } |
| - | first = 0; |
| - | } |
| + | if (stoppedjobs()) |
| + | return 0; |
| + | if (argc > 1) |
| + | exitstatus = number(argv[1]); |
| + | raise_exception(EXEXIT); |
| + | /* NOTREACHED */ |
| } | |
| - | |
| - | static void |
| - | sharg(union node *arg, FILE *fp) |
| + | #if ENABLE_ASH_BUILTIN_ECHO |
| + | static int |
| + | echocmd(int argc, char **argv) |
| { | |
| - | char *p; |
| - | struct nodelist *bqlist; |
| - | int subtype; |
| - | |
| - | if (arg->type != NARG) { |
| - | out1fmt("<node type %d>\n", arg->type); |
| - | abort(); |
| - | } |
| - | bqlist = arg->narg.backquote; |
| - | for (p = arg->narg.text; *p; p++) { |
| - | switch (*p) { |
| - | case CTLESC: |
| - | putc(*++p, fp); |
| - | break; |
| - | case CTLVAR: |
| - | putc('$', fp); |
| - | putc('{', fp); |
| - | subtype = *++p; |
| - | if (subtype == VSLENGTH) |
| - | putc('#', fp); |
| - | |
| - | while (*p != '=') |
| - | putc(*p++, fp); |
| - | |
| - | if (subtype & VSNUL) |
| - | putc(':', fp); |
| - | |
| - | switch (subtype & VSTYPE) { |
| - | case VSNORMAL: |
| - | putc('}', fp); |
| - | break; |
| - | case VSMINUS: |
| - | putc('-', fp); |
| - | break; |
| - | case VSPLUS: |
| - | putc('+', fp); |
| - | break; |
| - | case VSQUESTION: |
| - | putc('?', fp); |
| - | break; |
| - | case VSASSIGN: |
| - | putc('=', fp); |
| - | break; |
| - | case VSTRIMLEFT: |
| - | putc('#', fp); |
| - | break; |
| - | case VSTRIMLEFTMAX: |
| - | putc('#', fp); |
| - | putc('#', fp); |
| - | break; |
| - | case VSTRIMRIGHT: |
| - | putc('%', fp); |
| - | break; |
| - | case VSTRIMRIGHTMAX: |
| - | putc('%', fp); |
| - | putc('%', fp); |
| - | break; |
| - | case VSLENGTH: |
| - | break; |
| - | default: |
| - | out1fmt("<subtype %d>", subtype); |
| - | } |
| - | break; |
| - | case CTLENDVAR: |
| - | putc('}', fp); |
| - | break; |
| - | case CTLBACKQ: |
| - | case CTLBACKQ|CTLQUOTE: |
| - | putc('$', fp); |
| - | putc('(', fp); |
| - | shtree(bqlist->n, -1, NULL, fp); |
| - | putc(')', fp); |
| - | break; |
| - | default: |
| - | putc(*p, fp); |
| - | break; |
| - | } |
| - | } |
| + | return bb_echo(argv); |
| } | |
| + | #endif |
| + | #if ENABLE_ASH_BUILTIN_TEST |
| + | static int |
| + | testcmd(int argc, char **argv) |
| + | { |
| + | return bb_test(argc, argv); |
| + | } |
| + | #endif |
| + | /* |
| + | * Read a file containing shell functions. |
| + | */ |
| static void | |
| - | indent(int amount, char *pfx, FILE *fp) |
| + | readcmdfile(char *name) |
| { | |
| - | int i; |
| - | |
| - | for (i = 0; i < amount; i++) { |
| - | if (pfx && i == amount - 1) |
| - | fputs(pfx, fp); |
| - | putc('\t', fp); |
| - | } |
| + | setinputfile(name, INPUT_PUSH_FILE); |
| + | cmdloop(0); |
| + | popfile(); |
| } | |
| + | /* redir.c */ |
| + | |
| /* | |
| - | * Debugging stuff. |
| + | * Code for dealing with input/output redirection. |
| */ | |
| + | #define EMPTY -2 /* marks an unused slot in redirtab */ |
| + | #ifndef PIPE_BUF |
| + | # define PIPESIZE 4096 /* amount of buffering in a pipe */ |
| + | #else |
| + | # define PIPESIZE PIPE_BUF |
| + | #endif |
| - | static FILE *tracefile; |
| - | |
| - | |
| - | static void |
| - | trputc(int c) |
| + | /* |
| + | * Open a file in noclobber mode. |
| + | * The code was copied from bash. |
| + | */ |
| + | static int |
| + | noclobberopen(const char *fname) |
| { | |
| - | if (debug != 1) |
| - | return; |
| - | putc(c, tracefile); |
| - | } |
| + | int r, fd; |
| + | struct stat finfo, finfo2; |
| - | static void |
| - | trace(const char *fmt, ...) |
| - | { |
| - | va_list va; |
| + | /* |
| + | * If the file exists and is a regular file, return an error |
| + | * immediately. |
| + | */ |
| + | r = stat(fname, &finfo); |
| + | if (r == 0 && S_ISREG(finfo.st_mode)) { |
| + | errno = EEXIST; |
| + | return -1; |
| + | } |
| - | if (debug != 1) |
| - | return; |
| - | va_start(va, fmt); |
| - | (void) vfprintf(tracefile, fmt, va); |
| - | va_end(va); |
| - | } |
| + | /* |
| + | * If the file was not present (r != 0), make sure we open it |
| + | * exclusively so that if it is created before we open it, our open |
| + | * will fail. Make sure that we do not truncate an existing file. |
| + | * Note that we don't turn on O_EXCL unless the stat failed -- if the |
| + | * file was not a regular file, we leave O_EXCL off. |
| + | */ |
| + | if (r != 0) |
| + | return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); |
| + | fd = open(fname, O_WRONLY|O_CREAT, 0666); |
| - | static void |
| - | tracev(const char *fmt, va_list va) |
| - | { |
| - | if (debug != 1) |
| - | return; |
| - | (void) vfprintf(tracefile, fmt, va); |
| - | } |
| + | /* If the open failed, return the file descriptor right away. */ |
| + | if (fd < 0) |
| + | return fd; |
| + | /* |
| + | * OK, the open succeeded, but the file may have been changed from a |
| + | * non-regular file to a regular file between the stat and the open. |
| + | * We are assuming that the O_EXCL open handles the case where FILENAME |
| + | * did not exist and is symlinked to an existing file between the stat |
| + | * and open. |
| + | */ |
| - | static void |
| - | trputs(const char *s) |
| - | { |
| - | if (debug != 1) |
| - | return; |
| - | fputs(s, tracefile); |
| + | /* |
| + | * If we can open it and fstat the file descriptor, and neither check |
| + | * revealed that it was a regular file, and the file has not been |
| + | * replaced, return the file descriptor. |
| + | */ |
| + | if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) |
| + | && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino) |
| + | return fd; |
| + | |
| + | /* The file has been replaced. badness. */ |
| + | close(fd); |
| + | errno = EEXIST; |
| + | return -1; |
| } | |
| - | static void |
| - | trstring(char *s) |
| + | /* |
| + | * Handle here documents. Normally we fork off a process to write the |
| + | * data to a pipe. If the document is short, we can stuff the data in |
| + | * the pipe without forking. |
| + | */ |
| + | static int |
| + | openhere(union node *redir) |
| { | |
| - | char *p; |
| - | char c; |
| + | int pip[2]; |
| + | size_t len = 0; |
| - | if (debug != 1) |
| - | return; |
| - | putc('"', tracefile); |
| - | for (p = s; *p; p++) { |
| - | switch (*p) { |
| - | case '\n': c = 'n'; goto backslash; |
| - | case '\t': c = 't'; goto backslash; |
| - | case '\r': c = 'r'; goto backslash; |
| - | case '"': c = '"'; goto backslash; |
| - | case '\\': c = '\\'; goto backslash; |
| - | case CTLESC: c = 'e'; goto backslash; |
| - | case CTLVAR: c = 'v'; goto backslash; |
| - | case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; |
| - | case CTLBACKQ: c = 'q'; goto backslash; |
| - | case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; |
| - | backslash: |
| - | putc('\\', tracefile); |
| - | putc(c, tracefile); |
| - | break; |
| - | default: |
| - | if (*p >= ' ' && *p <= '~') |
| - | putc(*p, tracefile); |
| - | else { |
| - | putc('\\', tracefile); |
| - | putc(*p >> 6 & 03, tracefile); |
| - | putc(*p >> 3 & 07, tracefile); |
| - | putc(*p & 07, tracefile); |
| - | } |
| - | break; |
| + | if (pipe(pip) < 0) |
| + | ash_msg_and_raise_error("Pipe call failed"); |
| + | if (redir->type == NHERE) { |
| + | len = strlen(redir->nhere.doc->narg.text); |
| + | if (len <= PIPESIZE) { |
| + | full_write(pip[1], redir->nhere.doc->narg.text, len); |
| + | goto out; |
| } | |
| } | |
| - | putc('"', tracefile); |
| - | } |
| - | |
| - | |
| - | static void |
| - | trargs(char **ap) |
| - | { |
| - | if (debug != 1) |
| - | return; |
| - | while (*ap) { |
| - | trstring(*ap++); |
| - | if (*ap) |
| - | putc(' ', tracefile); |
| + | if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { |
| + | close(pip[0]); |
| + | signal(SIGINT, SIG_IGN); |
| + | signal(SIGQUIT, SIG_IGN); |
| + | signal(SIGHUP, SIG_IGN); |
| + | #ifdef SIGTSTP |
| + | signal(SIGTSTP, SIG_IGN); |
| + | #endif |
| + | signal(SIGPIPE, SIG_DFL); |
| + | if (redir->type == NHERE) |
| + | full_write(pip[1], redir->nhere.doc->narg.text, len); |
| else | |
| - | putc('\n', tracefile); |
| + | expandhere(redir->nhere.doc, pip[1]); |
| + | _exit(0); |
| } | |
| + | out: |
| + | close(pip[1]); |
| + | return pip[0]; |
| } | |
| - | |
| - | static void |
| - | opentrace(void) |
| + | static int |
| + | openredirect(union node *redir) |
| { | |
| - | char s[100]; |
| - | #ifdef O_APPEND |
| - | int flags; |
| - | #endif |
| + | char *fname; |
| + | int f; |
| - | if (debug != 1) { |
| - | if (tracefile) |
| - | fflush(tracefile); |
| - | /* leave open because libedit might be using it */ |
| - | return; |
| - | } |
| - | scopy("./trace", s); |
| - | if (tracefile) { |
| - | if (!freopen(s, "a", tracefile)) { |
| - | fprintf(stderr, "Can't re-open %s\n", s); |
| - | debug = 0; |
| - | return; |
| - | } |
| - | } else { |
| - | tracefile = fopen(s, "a"); |
| - | if (tracefile == NULL) { |
| - | fprintf(stderr, "Can't open %s\n", s); |
| - | debug = 0; |
| - | return; |
| + | switch (redir->nfile.type) { |
| + | case NFROM: |
| + | fname = redir->nfile.expfname; |
| + | f = open(fname, O_RDONLY); |
| + | if (f < 0) |
| + | goto eopen; |
| + | break; |
| + | case NFROMTO: |
| + | fname = redir->nfile.expfname; |
| + | f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666); |
| + | if (f < 0) |
| + | goto ecreate; |
| + | break; |
| + | case NTO: |
| + | /* Take care of noclobber mode. */ |
| + | if (Cflag) { |
| + | fname = redir->nfile.expfname; |
| + | f = noclobberopen(fname); |
| + | if (f < 0) |
| + | goto ecreate; |
| + | break; |
| } | |
| + | /* FALLTHROUGH */ |
| + | case NCLOBBER: |
| + | fname = redir->nfile.expfname; |
| + | f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666); |
| + | if (f < 0) |
| + | goto ecreate; |
| + | break; |
| + | case NAPPEND: |
| + | fname = redir->nfile.expfname; |
| + | f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666); |
| + | if (f < 0) |
| + | goto ecreate; |
| + | break; |
| + | default: |
| + | #if DEBUG |
| + | abort(); |
| + | #endif |
| + | /* Fall through to eliminate warning. */ |
| + | case NTOFD: |
| + | case NFROMFD: |
| + | f = -1; |
| + | break; |
| + | case NHERE: |
| + | case NXHERE: |
| + | f = openhere(redir); |
| + | break; |
| } | |
| - | #ifdef O_APPEND |
| - | flags = fcntl(fileno(tracefile), F_GETFL, 0); |
| - | if (flags >= 0) |
| - | fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); |
| - | #endif |
| - | setlinebuf(tracefile); |
| - | fputs("\nTracing started.\n", tracefile); |
| - | } |
| - | #endif /* DEBUG */ |
| - | |
| - | |
| - | /* trap.c */ |
| - | |
| - | /* |
| - | * Sigmode records the current value of the signal handlers for the various |
| - | * modes. A value of zero means that the current handler is not known. |
| - | * S_HARD_IGN indicates that the signal was ignored on entry to the shell, |
| - | */ |
| - | |
| - | #define S_DFL 1 /* default signal handling (SIG_DFL) */ |
| - | #define S_CATCH 2 /* signal is caught */ |
| - | #define S_IGN 3 /* signal is ignored (SIG_IGN) */ |
| - | #define S_HARD_IGN 4 /* signal is ignored permenantly */ |
| - | #define S_RESET 5 /* temporary - to reset a hard ignored sig */ |
| + | return f; |
| + | ecreate: |
| + | ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent")); |
| + | eopen: |
| + | ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file")); |
| + | } |
| - | /* |
| - | * The trap builtin. |
| - | */ |
| - | static int |
| - | trapcmd(int argc, char **argv) |
| + | static void |
| + | dupredirect(union node *redir, int f) |
| { | |
| - | char *action; |
| - | char **ap; |
| - | int signo; |
| - | |
| - | nextopt(nullstr); |
| - | ap = argptr; |
| - | if (!*ap) { |
| - | for (signo = 0; signo < NSIG; signo++) { |
| - | if (trap[signo] != NULL) { |
| - | const char *sn; |
| + | int fd = redir->nfile.fd; |
| - | sn = get_signame(signo); |
| - | out1fmt("trap -- %s %s\n", |
| - | single_quote(trap[signo]), sn); |
| - | } |
| + | if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) { |
| + | if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ |
| + | copyfd(redir->ndup.dupfd, fd); |
| } | |
| - | return 0; |
| + | return; |
| } | |
| - | if (!ap[1]) |
| - | action = NULL; |
| - | else |
| - | action = *ap++; |
| - | while (*ap) { |
| - | signo = get_signum(*ap); |
| - | if (signo < 0) |
| - | ash_msg_and_raise_error("%s: bad trap", *ap); |
| - | INT_OFF; |
| - | if (action) { |
| - | if (LONE_DASH(action)) |
| - | action = NULL; |
| - | else |
| - | action = ckstrdup(action); |
| - | } |
| - | if (trap[signo]) |
| - | free(trap[signo]); |
| - | trap[signo] = action; |
| - | if (signo != 0) |
| - | setsignal(signo); |
| - | INT_ON; |
| - | ap++; |
| + | |
| + | if (f != fd) { |
| + | copyfd(f, fd); |
| + | close(f); |
| } | |
| - | return 0; |
| } | |
| /* | |
| - | * Clear traps on a fork. |
| + | * Process a list of redirection commands. If the REDIR_PUSH flag is set, |
| + | * old file descriptors are stashed away so that the redirection can be |
| + | * undone by calling popredir. If the REDIR_BACKQ flag is set, then the |
| + | * standard output, and the standard error if it becomes a duplicate of |
| + | * stdout, is saved in memory. |
| */ | |
| static void | |
| - | clear_traps(void) |
| + | redirect(union node *redir, int flags) |
| { | |
| - | char **tp; |
| + | union node *n; |
| + | struct redirtab *sv; |
| + | int i; |
| + | int fd; |
| + | int newfd; |
| + | int *p; |
| + | nullredirs++; |
| + | if (!redir) { |
| + | return; |
| + | } |
| + | sv = NULL; |
| + | INT_OFF; |
| + | if (flags & REDIR_PUSH) { |
| + | struct redirtab *q; |
| + | q = ckmalloc(sizeof(struct redirtab)); |
| + | q->next = redirlist; |
| + | redirlist = q; |
| + | q->nullredirs = nullredirs - 1; |
| + | for (i = 0; i < 10; i++) |
| + | q->renamed[i] = EMPTY; |
| + | nullredirs = 0; |
| + | sv = q; |
| + | } |
| + | n = redir; |
| + | do { |
| + | fd = n->nfile.fd; |
| + | if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) |
| + | && n->ndup.dupfd == fd) |
| + | continue; /* redirect from/to same file descriptor */ |
| - | for (tp = trap; tp < &trap[NSIG]; tp++) { |
| - | if (*tp && **tp) { /* trap not NULL or SIG_IGN */ |
| - | INT_OFF; |
| - | free(*tp); |
| - | *tp = NULL; |
| - | if (tp != &trap[0]) |
| - | setsignal(tp - trap); |
| - | INT_ON; |
| + | newfd = openredirect(n); |
| + | if (fd == newfd) |
| + | continue; |
| + | if (sv && *(p = &sv->renamed[fd]) == EMPTY) { |
| + | i = fcntl(fd, F_DUPFD, 10); |
| + | |
| + | if (i == -1) { |
| + | i = errno; |
| + | if (i != EBADF) { |
| + | close(newfd); |
| + | errno = i; |
| + | ash_msg_and_raise_error("%d: %m", fd); |
| + | /* NOTREACHED */ |
| + | } |
| + | } else { |
| + | *p = i; |
| + | close(fd); |
| + | } |
| + | } else { |
| + | close(fd); |
| } | |
| - | } |
| + | dupredirect(n, newfd); |
| + | } while ((n = n->nfile.next)); |
| + | INT_ON; |
| + | if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0) |
| + | preverrout_fd = sv->renamed[2]; |
| } | |
| /* | |
| - | * Set the signal handler for the specified signal. The routine figures |
| - | * out what it should be set to. |
| + | * Undo the effects of the last redirection. |
| */ | |
| static void | |
| - | setsignal(int signo) |
| + | popredir(int drop) |
| { | |
| - | int action; |
| - | char *t, tsig; |
| - | struct sigaction act; |
| - | |
| - | t = trap[signo]; |
| - | if (t == NULL) |
| - | action = S_DFL; |
| - | else if (*t != '\0') |
| - | action = S_CATCH; |
| - | else |
| - | action = S_IGN; |
| - | if (rootshell && action == S_DFL) { |
| - | switch (signo) { |
| - | case SIGINT: |
| - | if (iflag || minusc || sflag == 0) |
| - | action = S_CATCH; |
| - | break; |
| - | case SIGQUIT: |
| - | #if DEBUG |
| - | if (debug) |
| - | break; |
| - | #endif |
| - | /* FALLTHROUGH */ |
| - | case SIGTERM: |
| - | if (iflag) |
| - | action = S_IGN; |
| - | break; |
| - | #if JOBS |
| - | case SIGTSTP: |
| - | case SIGTTOU: |
| - | if (mflag) |
| - | action = S_IGN; |
| - | break; |
| - | #endif |
| - | } |
| - | } |
| + | struct redirtab *rp; |
| + | int i; |
| - | t = &sigmode[signo - 1]; |
| - | tsig = *t; |
| - | if (tsig == 0) { |
| - | /* |
| - | * current setting unknown |
| - | */ |
| - | if (sigaction(signo, 0, &act) == -1) { |
| - | /* |
| - | * Pretend it worked; maybe we should give a warning |
| - | * here, but other shells don't. We don't alter |
| - | * sigmode, so that we retry every time. |
| - | */ |
| - | return; |
| - | } |
| - | if (act.sa_handler == SIG_IGN) { |
| - | if (mflag |
| - | && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU) |
| - | ) { |
| - | tsig = S_IGN; /* don't hard ignore these */ |
| - | } else |
| - | tsig = S_HARD_IGN; |
| - | } else { |
| - | tsig = S_RESET; /* force to be set */ |
| - | } |
| - | } |
| - | if (tsig == S_HARD_IGN || tsig == action) |
| + | if (--nullredirs >= 0) |
| return; | |
| - | switch (action) { |
| - | case S_CATCH: |
| - | act.sa_handler = onsig; |
| - | break; |
| - | case S_IGN: |
| - | act.sa_handler = SIG_IGN; |
| - | break; |
| - | default: |
| - | act.sa_handler = SIG_DFL; |
| + | INT_OFF; |
| + | rp = redirlist; |
| + | for (i = 0; i < 10; i++) { |
| + | if (rp->renamed[i] != EMPTY) { |
| + | if (!drop) { |
| + | close(i); |
| + | copyfd(rp->renamed[i], i); |
| + | } |
| + | close(rp->renamed[i]); |
| + | } |
| } | |
| - | *t = action; |
| - | act.sa_flags = 0; |
| - | sigfillset(&act.sa_mask); |
| - | sigaction(signo, &act, 0); |
| + | redirlist = rp->next; |
| + | nullredirs = rp->nullredirs; |
| + | free(rp); |
| + | INT_ON; |
| } | |
| + | /* |
| + | * Undo all redirections. Called on error or interrupt. |
| + | */ |
| /* | |
| - | * Ignore a signal. |
| + | * Discard all saved file descriptors. |
| */ | |
| static void | |
| - | ignoresig(int signo) |
| + | clearredir(int drop) |
| { | |
| - | if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) { |
| - | signal(signo, SIG_IGN); |
| + | for (;;) { |
| + | nullredirs = 0; |
| + | if (!redirlist) |
| + | break; |
| + | popredir(drop); |
| } | |
| - | sigmode[signo - 1] = S_HARD_IGN; |
| } | |
| /* | |
| - | * Signal handler. |
| + | * Copy a file descriptor to be >= to. Returns -1 |
| + | * if the source file descriptor is closed, EMPTY if there are no unused |
| + | * file descriptors left. |
| */ | |
| + | static int |
| + | copyfd(int from, int to) |
| + | { |
| + | int newfd; |
| + | |
| + | newfd = fcntl(from, F_DUPFD, to); |
| + | if (newfd < 0) { |
| + | if (errno == EMFILE) |
| + | return EMPTY; |
| + | ash_msg_and_raise_error("%d: %m", from); |
| + | } |
| + | return newfd; |
| + | } |
| + | |
| + | static int |
| + | redirectsafe(union node *redir, int flags) |
| + | { |
| + | int err; |
| + | volatile int saveint; |
| + | struct jmploc *volatile savehandler = exception_handler; |
| + | struct jmploc jmploc; |
| + | |
| + | SAVE_INT(saveint); |
| + | err = setjmp(jmploc.loc) * 2; |
| + | if (!err) { |
| + | exception_handler = &jmploc; |
| + | redirect(redir, flags); |
| + | } |
| + | exception_handler = savehandler; |
| + | if (err && exception != EXERROR) |
| + | longjmp(exception_handler->loc, 1); |
| + | RESTORE_INT(saveint); |
| + | return err; |
| + | } |
| + | |
| + | /* show.c */ |
| + | |
| + | #if DEBUG |
| + | static void shtree(union node *, int, char *, FILE*); |
| + | static void shcmd(union node *, FILE *); |
| + | static void sharg(union node *, FILE *); |
| + | static void indent(int, char *, FILE *); |
| + | static void trstring(char *); |
| + | |
| static void | |
| - | onsig(int signo) |
| + | showtree(union node *n) |
| { | |
| - | gotsig[signo - 1] = 1; |
| - | pendingsigs = signo; |
| + | trputs("showtree called\n"); |
| + | shtree(n, 1, NULL, stdout); |
| + | } |
| - | if (exsig || (signo == SIGINT && !trap[SIGINT])) { |
| - | if (!suppressint) |
| - | raise_interrupt(); |
| - | intpending = 1; |
| + | static void |
| + | shtree(union node *n, int ind, char *pfx, FILE *fp) |
| + | { |
| + | struct nodelist *lp; |
| + | const char *s; |
| + | |
| + | if (n == NULL) |
| + | return; |
| + | |
| + | indent(ind, pfx, fp); |
| + | switch (n->type) { |
| + | case NSEMI: |
| + | s = "; "; |
| + | goto binop; |
| + | case NAND: |
| + | s = " && "; |
| + | goto binop; |
| + | case NOR: |
| + | s = " || "; |
| + | binop: |
| + | shtree(n->nbinary.ch1, ind, NULL, fp); |
| + | /* if (ind < 0) */ |
| + | fputs(s, fp); |
| + | shtree(n->nbinary.ch2, ind, NULL, fp); |
| + | break; |
| + | case NCMD: |
| + | shcmd(n, fp); |
| + | if (ind >= 0) |
| + | putc('\n', fp); |
| + | break; |
| + | case NPIPE: |
| + | for (lp = n->npipe.cmdlist; lp; lp = lp->next) { |
| + | shcmd(lp->n, fp); |
| + | if (lp->next) |
| + | fputs(" | ", fp); |
| + | } |
| + | if (n->npipe.backgnd) |
| + | fputs(" &", fp); |
| + | if (ind >= 0) |
| + | putc('\n', fp); |
| + | break; |
| + | default: |
| + | fprintf(fp, "<node type %d>", n->type); |
| + | if (ind >= 0) |
| + | putc('\n', fp); |
| + | break; |
| } | |
| } | |
| + | static void |
| + | shcmd(union node *cmd, FILE *fp) |
| + | { |
| + | union node *np; |
| + | int first; |
| + | const char *s; |
| + | int dftfd; |
| + | |
| + | first = 1; |
| + | for (np = cmd->ncmd.args; np; np = np->narg.next) { |
| + | if (! first) |
| + | putchar(' '); |
| + | sharg(np, fp); |
| + | first = 0; |
| + | } |
| + | for (np = cmd->ncmd.redirect; np; np = np->nfile.next) { |
| + | if (! first) |
| + | putchar(' '); |
| + | switch (np->nfile.type) { |
| + | case NTO: s = ">"; dftfd = 1; break; |
| + | case NCLOBBER: s = ">|"; dftfd = 1; break; |
| + | case NAPPEND: s = ">>"; dftfd = 1; break; |
| + | case NTOFD: s = ">&"; dftfd = 1; break; |
| + | case NFROM: s = "<"; dftfd = 0; break; |
| + | case NFROMFD: s = "<&"; dftfd = 0; break; |
| + | case NFROMTO: s = "<>"; dftfd = 0; break; |
| + | default: s = "*error*"; dftfd = 0; break; |
| + | } |
| + | if (np->nfile.fd != dftfd) |
| + | fprintf(fp, "%d", np->nfile.fd); |
| + | fputs(s, fp); |
| + | if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { |
| + | fprintf(fp, "%d", np->ndup.dupfd); |
| + | } else { |
| + | sharg(np->nfile.fname, fp); |
| + | } |
| + | first = 0; |
| + | } |
| + | } |
| - | /* |
| - | * Called to execute a trap. Perhaps we should avoid entering new trap |
| - | * handlers while we are executing a trap handler. |
| - | */ |
| - | static int |
| - | dotrap(void) |
| + | static void |
| + | sharg(union node *arg, FILE *fp) |
| { | |
| char *p; | |
| - | char *q; |
| - | int i; |
| - | int savestatus; |
| - | int skip = 0; |
| + | struct nodelist *bqlist; |
| + | int subtype; |
| - | savestatus = exitstatus; |
| - | pendingsigs = 0; |
| - | xbarrier(); |
| + | if (arg->type != NARG) { |
| + | out1fmt("<node type %d>\n", arg->type); |
| + | abort(); |
| + | } |
| + | bqlist = arg->narg.backquote; |
| + | for (p = arg->narg.text; *p; p++) { |
| + | switch (*p) { |
| + | case CTLESC: |
| + | putc(*++p, fp); |
| + | break; |
| + | case CTLVAR: |
| + | putc('$', fp); |
| + | putc('{', fp); |
| + | subtype = *++p; |
| + | if (subtype == VSLENGTH) |
| + | putc('#', fp); |
| - | for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) { |
| - | if (!*q) |
| - | continue; |
| - | *q = '\0'; |
| + | while (*p != '=') |
| + | putc(*p++, fp); |
| - | p = trap[i + 1]; |
| - | if (!p) |
| - | continue; |
| - | skip = evalstring(p, SKIPEVAL); |
| - | exitstatus = savestatus; |
| - | if (skip) |
| + | if (subtype & VSNUL) |
| + | putc(':', fp); |
| + | |
| + | switch (subtype & VSTYPE) { |
| + | case VSNORMAL: |
| + | putc('}', fp); |
| + | break; |
| + | case VSMINUS: |
| + | putc('-', fp); |
| + | break; |
| + | case VSPLUS: |
| + | putc('+', fp); |
| + | break; |
| + | case VSQUESTION: |
| + | putc('?', fp); |
| + | break; |
| + | case VSASSIGN: |
| + | putc('=', fp); |
| + | break; |
| + | case VSTRIMLEFT: |
| + | putc('#', fp); |
| + | break; |
| + | case VSTRIMLEFTMAX: |
| + | putc('#', fp); |
| + | putc('#', fp); |
| + | break; |
| + | case VSTRIMRIGHT: |
| + | putc('%', fp); |
| + | break; |
| + | case VSTRIMRIGHTMAX: |
| + | putc('%', fp); |
| + | putc('%', fp); |
| + | break; |
| + | case VSLENGTH: |
| + | break; |
| + | default: |
| + | out1fmt("<subtype %d>", subtype); |
| + | } |
| + | break; |
| + | case CTLENDVAR: |
| + | putc('}', fp); |
| + | break; |
| + | case CTLBACKQ: |
| + | case CTLBACKQ|CTLQUOTE: |
| + | putc('$', fp); |
| + | putc('(', fp); |
| + | shtree(bqlist->n, -1, NULL, fp); |
| + | putc(')', fp); |
| + | break; |
| + | default: |
| + | putc(*p, fp); |
| break; | |
| + | } |
| } | |
| + | } |
| - | return skip; |
| + | |
| + | static void |
| + | indent(int amount, char *pfx, FILE *fp) |
| + | { |
| + | int i; |
| + | |
| + | for (i = 0; i < amount; i++) { |
| + | if (pfx && i == amount - 1) |
| + | fputs(pfx, fp); |
| + | putc('\t', fp); |
| + | } |
| } | |
| /* | |
| - | * Controls whether the shell is interactive or not. |
| + | * Debugging stuff. |
| */ | |
| + | |
| + | |
| + | static FILE *tracefile; |
| + | |
| + | |
| static void | |
| - | setinteractive(int on) |
| + | trputc(int c) |
| { | |
| - | static int is_interactive; |
| + | if (debug != 1) |
| + | return; |
| + | putc(c, tracefile); |
| + | } |
| - | if (++on == is_interactive) |
| + | static void |
| + | trace(const char *fmt, ...) |
| + | { |
| + | va_list va; |
| + | |
| + | if (debug != 1) |
| return; | |
| - | is_interactive = on; |
| - | setsignal(SIGINT); |
| - | setsignal(SIGQUIT); |
| - | setsignal(SIGTERM); |
| - | #if !ENABLE_FEATURE_SH_EXTRA_QUIET |
| - | if (is_interactive > 1) { |
| - | /* Looks like they want an interactive shell */ |
| - | static int do_banner; |
| + | va_start(va, fmt); |
| + | (void) vfprintf(tracefile, fmt, va); |
| + | va_end(va); |
| + | } |
| - | if (!do_banner) { |
| - | out1fmt( |
| - | "\n\n" |
| - | "%s Built-in shell (ash)\n" |
| - | "Enter 'help' for a list of built-in commands." |
| - | "\n\n", |
| - | BB_BANNER); |
| - | do_banner++; |
| - | } |
| - | } |
| - | #endif |
| + | static void |
| + | tracev(const char *fmt, va_list va) |
| + | { |
| + | if (debug != 1) |
| + | return; |
| + | (void) vfprintf(tracefile, fmt, va); |
| } | |
| - | #if !ENABLE_FEATURE_SH_EXTRA_QUIET |
| - | /*** List the available builtins ***/ |
| + | static void |
| + | trputs(const char *s) |
| + | { |
| + | if (debug != 1) |
| + | return; |
| + | fputs(s, tracefile); |
| + | } |
| + | |
| - | static int helpcmd(int argc, char **argv) |
| + | static void |
| + | trstring(char *s) |
| { | |
| - | int col, i; |
| + | char *p; |
| + | char c; |
| - | out1fmt("\nBuilt-in commands:\n-------------------\n"); |
| - | for (col = 0, i = 0; i < NUMBUILTINS; i++) { |
| - | col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), |
| - | builtincmd[i].name + 1); |
| - | if (col > 60) { |
| - | out1fmt("\n"); |
| - | col = 0; |
| + | if (debug != 1) |
| + | return; |
| + | putc('"', tracefile); |
| + | for (p = s; *p; p++) { |
| + | switch (*p) { |
| + | case '\n': c = 'n'; goto backslash; |
| + | case '\t': c = 't'; goto backslash; |
| + | case '\r': c = 'r'; goto backslash; |
| + | case '"': c = '"'; goto backslash; |
| + | case '\\': c = '\\'; goto backslash; |
| + | case CTLESC: c = 'e'; goto backslash; |
| + | case CTLVAR: c = 'v'; goto backslash; |
| + | case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; |
| + | case CTLBACKQ: c = 'q'; goto backslash; |
| + | case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; |
| + | backslash: |
| + | putc('\\', tracefile); |
| + | putc(c, tracefile); |
| + | break; |
| + | default: |
| + | if (*p >= ' ' && *p <= '~') |
| + | putc(*p, tracefile); |
| + | else { |
| + | putc('\\', tracefile); |
| + | putc(*p >> 6 & 03, tracefile); |
| + | putc(*p >> 3 & 07, tracefile); |
| + | putc(*p & 07, tracefile); |
| + | } |
| + | break; |
| } | |
| } | |
| - | #if ENABLE_FEATURE_SH_STANDALONE_SHELL |
| - | for (i = 0; i < NUM_APPLETS; i++) { |
| - | col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), applets[i].name); |
| - | if (col > 60) { |
| - | out1fmt("\n"); |
| - | col = 0; |
| - | } |
| + | putc('"', tracefile); |
| + | } |
| + | |
| + | |
| + | static void |
| + | trargs(char **ap) |
| + | { |
| + | if (debug != 1) |
| + | return; |
| + | while (*ap) { |
| + | trstring(*ap++); |
| + | if (*ap) |
| + | putc(' ', tracefile); |
| + | else |
| + | putc('\n', tracefile); |
| } | |
| - | #endif |
| - | out1fmt("\n\n"); |
| - | return EXIT_SUCCESS; |
| } | |
| - | #endif /* FEATURE_SH_EXTRA_QUIET */ |
| - | /* |
| - | * Called to exit the shell. |
| - | */ |
| static void | |
| - | exitshell(void) |
| + | opentrace(void) |
| { | |
| - | struct jmploc loc; |
| - | char *p; |
| - | int status; |
| + | char s[100]; |
| + | #ifdef O_APPEND |
| + | int flags; |
| + | #endif |
| - | status = exitstatus; |
| - | TRACE(("pid %d, exitshell(%d)\n", getpid(), status)); |
| - | if (setjmp(loc.loc)) { |
| - | if (exception == EXEXIT) |
| - | /* dash bug: it just does _exit(exitstatus) here |
| - | * but we have to do setjobctl(0) first! |
| - | * (bug is still not fixed in dash-0.5.3 - if you run dash |
| - | * under Midnight Commander, on exit from dash MC is backgrounded) */ |
| - | status = exitstatus; |
| - | goto out; |
| + | if (debug != 1) { |
| + | if (tracefile) |
| + | fflush(tracefile); |
| + | /* leave open because libedit might be using it */ |
| + | return; |
| } | |
| - | exception_handler = &loc; |
| - | p = trap[0]; |
| - | if (p) { |
| - | trap[0] = NULL; |
| - | evalstring(p, 0); |
| + | scopy("./trace", s); |
| + | if (tracefile) { |
| + | if (!freopen(s, "a", tracefile)) { |
| + | fprintf(stderr, "Can't re-open %s\n", s); |
| + | debug = 0; |
| + | return; |
| + | } |
| + | } else { |
| + | tracefile = fopen(s, "a"); |
| + | if (tracefile == NULL) { |
| + | fprintf(stderr, "Can't open %s\n", s); |
| + | debug = 0; |
| + | return; |
| + | } |
| } | |
| - | flush_stdout_stderr(); |
| - | out: |
| - | setjobctl(0); |
| - | _exit(status); |
| - | /* NOTREACHED */ |
| + | #ifdef O_APPEND |
| + | flags = fcntl(fileno(tracefile), F_GETFL, 0); |
| + | if (flags >= 0) |
| + | fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); |
| + | #endif |
| + | setlinebuf(tracefile); |
| + | fputs("\nTracing started.\n", tracefile); |
| } | |
| + | #endif /* DEBUG */ |
| - | /* var.c */ |
| - | |
| - | static struct var *vartab[VTABSIZE]; |
| - | static int vpcmp(const void *, const void *); |
| - | static struct var **findvar(struct var **, const char *); |
| + | /* trap.c */ |
| /* | |
| - | * Initialize the variable symbol tables and import the environment |
| + | * Sigmode records the current value of the signal handlers for the various |
| + | * modes. A value of zero means that the current handler is not known. |
| + | * S_HARD_IGN indicates that the signal was ignored on entry to the shell, |
| */ | |
| + | #define S_DFL 1 /* default signal handling (SIG_DFL) */ |
| + | #define S_CATCH 2 /* signal is caught */ |
| + | #define S_IGN 3 /* signal is ignored (SIG_IGN) */ |
| + | #define S_HARD_IGN 4 /* signal is ignored permenantly */ |
| + | #define S_RESET 5 /* temporary - to reset a hard ignored sig */ |
| + | |
| - | #if ENABLE_ASH_GETOPTS |
| /* | |
| - | * Safe version of setvar, returns 1 on success 0 on failure. |
| + | * The trap builtin. |
| */ | |
| static int | |
| - | setvarsafe(const char *name, const char *val, int flags) |
| + | trapcmd(int argc, char **argv) |
| { | |
| - | int err; |
| - | volatile int saveint; |
| - | struct jmploc *volatile savehandler = exception_handler; |
| - | struct jmploc jmploc; |
| + | char *action; |
| + | char **ap; |
| + | int signo; |
| - | SAVE_INT(saveint); |
| - | if (setjmp(jmploc.loc)) |
| - | err = 1; |
| - | else { |
| - | exception_handler = &jmploc; |
| - | setvar(name, val, flags); |
| - | err = 0; |
| + | nextopt(nullstr); |
| + | ap = argptr; |
| + | if (!*ap) { |
| + | for (signo = 0; signo < NSIG; signo++) { |
| + | if (trap[signo] != NULL) { |
| + | const char *sn; |
| + | |
| + | sn = get_signame(signo); |
| + | out1fmt("trap -- %s %s\n", |
| + | single_quote(trap[signo]), sn); |
| + | } |
| + | } |
| + | return 0; |
| + | } |
| + | if (!ap[1]) |
| + | action = NULL; |
| + | else |
| + | action = *ap++; |
| + | while (*ap) { |
| + | signo = get_signum(*ap); |
| + | if (signo < 0) |
| + | ash_msg_and_raise_error("%s: bad trap", *ap); |
| + | INT_OFF; |
| + | if (action) { |
| + | if (LONE_DASH(action)) |
| + | action = NULL; |
| + | else |
| + | action = ckstrdup(action); |
| + | } |
| + | if (trap[signo]) |
| + | free(trap[signo]); |
| + | trap[signo] = action; |
| + | if (signo != 0) |
| + | setsignal(signo); |
| + | INT_ON; |
| + | ap++; |
| } | |
| - | exception_handler = savehandler; |
| - | RESTORE_INT(saveint); |
| - | return err; |
| + | return 0; |
| } | |
| - | #endif |
| /* | |
| - | * Set the value of a variable. The flags argument is ored with the |
| - | * flags of the variable. If val is NULL, the variable is unset. |
| + | * Clear traps on a fork. |
| */ | |
| static void | |
| - | setvar(const char *name, const char *val, int flags) |
| + | clear_traps(void) |
| { | |
| - | char *p, *q; |
| - | size_t namelen; |
| - | char *nameeq; |
| - | size_t vallen; |
| + | char **tp; |
| - | q = endofname(name); |
| - | p = strchrnul(q, '='); |
| - | namelen = p - name; |
| - | if (!namelen || p != q) |
| - | ash_msg_and_raise_error("%.*s: bad variable name", namelen, name); |
| - | vallen = 0; |
| - | if (val == NULL) { |
| - | flags |= VUNSET; |
| - | } else { |
| - | vallen = strlen(val); |
| - | } |
| - | INT_OFF; |
| - | nameeq = ckmalloc(namelen + vallen + 2); |
| - | p = memcpy(nameeq, name, namelen) + namelen; |
| - | if (val) { |
| - | *p++ = '='; |
| - | p = memcpy(p, val, vallen) + vallen; |
| + | for (tp = trap; tp < &trap[NSIG]; tp++) { |
| + | if (*tp && **tp) { /* trap not NULL or SIG_IGN */ |
| + | INT_OFF; |
| + | free(*tp); |
| + | *tp = NULL; |
| + | if (tp != &trap[0]) |
| + | setsignal(tp - trap); |
| + | INT_ON; |
| + | } |
| } | |
| - | *p = '\0'; |
| - | setvareq(nameeq, flags | VNOSAVE); |
| - | INT_ON; |
| } | |
| /* | |
| - | * Same as setvar except that the variable and value are passed in |
| - | * the first argument as name=value. Since the first argument will |
| - | * be actually stored in the table, it should not be a string that |
| - | * will go away. |
| - | * Called with interrupts off. |
| + | * Set the signal handler for the specified signal. The routine figures |
| + | * out what it should be set to. |
| */ | |
| static void | |
| - | setvareq(char *s, int flags) |
| + | setsignal(int signo) |
| { | |
| - | struct var *vp, **vpp; |
| - | |
| - | vpp = hashvar(s); |
| - | flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1)); |
| - | vp = *findvar(vpp, s); |
| - | if (vp) { |
| - | if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) { |
| - | const char *n; |
| + | int action; |
| + | char *t, tsig; |
| + | struct sigaction act; |
| - | if (flags & VNOSAVE) |
| - | free(s); |
| - | n = vp->text; |
| - | ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n); |
| + | t = trap[signo]; |
| + | if (t == NULL) |
| + | action = S_DFL; |
| + | else if (*t != '\0') |
| + | action = S_CATCH; |
| + | else |
| + | action = S_IGN; |
| + | if (rootshell && action == S_DFL) { |
| + | switch (signo) { |
| + | case SIGINT: |
| + | if (iflag || minusc || sflag == 0) |
| + | action = S_CATCH; |
| + | break; |
| + | case SIGQUIT: |
| + | #if DEBUG |
| + | if (debug) |
| + | break; |
| + | #endif |
| + | /* FALLTHROUGH */ |
| + | case SIGTERM: |
| + | if (iflag) |
| + | action = S_IGN; |
| + | break; |
| + | #if JOBS |
| + | case SIGTSTP: |
| + | case SIGTTOU: |
| + | if (mflag) |
| + | action = S_IGN; |
| + | break; |
| + | #endif |
| } | |
| + | } |
| - | if (flags & VNOSET) |
| + | t = &sigmode[signo - 1]; |
| + | tsig = *t; |
| + | if (tsig == 0) { |
| + | /* |
| + | * current setting unknown |
| + | */ |
| + | if (sigaction(signo, 0, &act) == -1) { |
| + | /* |
| + | * Pretend it worked; maybe we should give a warning |
| + | * here, but other shells don't. We don't alter |
| + | * sigmode, so that we retry every time. |
| + | */ |
| return; | |
| + | } |
| + | if (act.sa_handler == SIG_IGN) { |
| + | if (mflag |
| + | && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU) |
| + | ) { |
| + | tsig = S_IGN; /* don't hard ignore these */ |
| + | } else |
| + | tsig = S_HARD_IGN; |
| + | } else { |
| + | tsig = S_RESET; /* force to be set */ |
| + | } |
| + | } |
| + | if (tsig == S_HARD_IGN || tsig == action) |
| + | return; |
| + | switch (action) { |
| + | case S_CATCH: |
| + | act.sa_handler = onsig; |
| + | break; |
| + | case S_IGN: |
| + | act.sa_handler = SIG_IGN; |
| + | break; |
| + | default: |
| + | act.sa_handler = SIG_DFL; |
| + | } |
| + | *t = action; |
| + | act.sa_flags = 0; |
| + | sigfillset(&act.sa_mask); |
| + | sigaction(signo, &act, 0); |
| + | } |
| - | if (vp->func && (flags & VNOFUNC) == 0) |
| - | (*vp->func)(strchrnul(s, '=') + 1); |
| - | |
| - | if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) |
| - | free((char*)vp->text); |
| - | flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); |
| - | } else { |
| - | if (flags & VNOSET) |
| - | return; |
| - | /* not found */ |
| - | vp = ckmalloc(sizeof(*vp)); |
| - | vp->next = *vpp; |
| - | vp->func = NULL; |
| - | *vpp = vp; |
| + | /* |
| + | * Ignore a signal. |
| + | */ |
| + | static void |
| + | ignoresig(int signo) |
| + | { |
| + | if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) { |
| + | signal(signo, SIG_IGN); |
| } | |
| - | if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE))) |
| - | s = ckstrdup(s); |
| - | vp->text = s; |
| - | vp->flags = flags; |
| + | sigmode[signo - 1] = S_HARD_IGN; |
| } | |
| /* | |
| - | * Process a linked list of variable assignments. |
| + | * Signal handler. |
| */ | |
| static void | |
| - | listsetvar(struct strlist *list_set_var, int flags) |
| + | onsig(int signo) |
| { | |
| - | struct strlist *lp = list_set_var; |
| + | gotsig[signo - 1] = 1; |
| + | pendingsigs = signo; |
| - | if (!lp) |
| - | return; |
| - | INT_OFF; |
| - | do { |
| - | setvareq(lp->text, flags); |
| - | } while ((lp = lp->next)); |
| - | INT_ON; |
| + | if (exsig || (signo == SIGINT && !trap[SIGINT])) { |
| + | if (!suppressint) |
| + | raise_interrupt(); |
| + | intpending = 1; |
| + | } |
| } | |
| /* | |
| - | * Find the value of a variable. Returns NULL if not set. |
| + | * Called to execute a trap. Perhaps we should avoid entering new trap |
| + | * handlers while we are executing a trap handler. |
| */ | |
| - | static char * |
| - | lookupvar(const char *name) |
| + | static int |
| + | dotrap(void) |
| { | |
| - | struct var *v; |
| + | char *p; |
| + | char *q; |
| + | int i; |
| + | int savestatus; |
| + | int skip = 0; |
| - | v = *findvar(hashvar(name), name); |
| - | if (v) { |
| - | #ifdef DYNAMIC_VAR |
| - | /* |
| - | * Dynamic variables are implemented roughly the same way they are |
| - | * in bash. Namely, they're "special" so long as they aren't unset. |
| - | * As soon as they're unset, they're no longer dynamic, and dynamic |
| - | * lookup will no longer happen at that point. -- PFM. |
| - | */ |
| - | if ((v->flags & VDYNAMIC)) |
| - | (*v->func)(NULL); |
| - | #endif |
| - | if (!(v->flags & VUNSET)) |
| - | return strchrnul(v->text, '=') + 1; |
| + | savestatus = exitstatus; |
| + | pendingsigs = 0; |
| + | xbarrier(); |
| + | |
| + | for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) { |
| + | if (!*q) |
| + | continue; |
| + | *q = '\0'; |
| + | |
| + | p = trap[i + 1]; |
| + | if (!p) |
| + | continue; |
| + | skip = evalstring(p, SKIPEVAL); |
| + | exitstatus = savestatus; |
| + | if (skip) |
| + | break; |
| } | |
| - | return NULL; |
| + | return skip; |
| } | |
| - | |
| /* | |
| - | * Search the environment of a builtin command. |
| + | * Controls whether the shell is interactive or not. |
| */ | |
| - | static char * |
| - | bltinlookup(const char *name) |
| + | static void |
| + | setinteractive(int on) |
| { | |
| - | struct strlist *sp; |
| + | static int is_interactive; |
| + | |
| + | if (++on == is_interactive) |
| + | return; |
| + | is_interactive = on; |
| + | setsignal(SIGINT); |
| + | setsignal(SIGQUIT); |
| + | setsignal(SIGTERM); |
| + | #if !ENABLE_FEATURE_SH_EXTRA_QUIET |
| + | if (is_interactive > 1) { |
| + | /* Looks like they want an interactive shell */ |
| + | static int do_banner; |
| - | for (sp = cmdenviron; sp; sp = sp->next) { |
| - | if (varequal(sp->text, name)) |
| - | return strchrnul(sp->text, '=') + 1; |
| + | if (!do_banner) { |
| + | out1fmt( |
| + | "\n\n" |
| + | "%s Built-in shell (ash)\n" |
| + | "Enter 'help' for a list of built-in commands." |
| + | "\n\n", |
| + | BB_BANNER); |
| + | do_banner++; |
| + | } |
| } | |
| - | return lookupvar(name); |
| + | #endif |
| } | |
| + | #if !ENABLE_FEATURE_SH_EXTRA_QUIET |
| + | /*** List the available builtins ***/ |
| - | /* |
| - | * Generate a list of variables satisfying the given conditions. |
| - | */ |
| - | static char ** |
| - | listvars(int on, int off, char ***end) |
| + | static int |
| + | helpcmd(int argc, char **argv) |
| { | |
| - | struct var **vpp; |
| - | struct var *vp; |
| - | char **ep; |
| - | int mask; |
| + | int col, i; |
| - | STARTSTACKSTR(ep); |
| - | vpp = vartab; |
| - | mask = on | off; |
| - | do { |
| - | for (vp = *vpp; vp; vp = vp->next) |
| - | if ((vp->flags & mask) == on) { |
| - | if (ep == stackstrend()) |
| - | ep = growstackstr(); |
| - | *ep++ = (char *) vp->text; |
| - | } |
| - | } while (++vpp < vartab + VTABSIZE); |
| - | if (ep == stackstrend()) |
| - | ep = growstackstr(); |
| - | if (end) |
| - | *end = ep; |
| - | *ep++ = NULL; |
| - | return grabstackstr(ep); |
| + | out1fmt("\nBuilt-in commands:\n-------------------\n"); |
| + | for (col = 0, i = 0; i < NUMBUILTINS; i++) { |
| + | col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), |
| + | builtincmd[i].name + 1); |
| + | if (col > 60) { |
| + | out1fmt("\n"); |
| + | col = 0; |
| + | } |
| + | } |
| + | #if ENABLE_FEATURE_SH_STANDALONE_SHELL |
| + | for (i = 0; i < NUM_APPLETS; i++) { |
| + | col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), applets[i].name); |
| + | if (col > 60) { |
| + | out1fmt("\n"); |
| + | col = 0; |
| + | } |
| + | } |
| + | #endif |
| + | out1fmt("\n\n"); |
| + | return EXIT_SUCCESS; |
| } | |
| - | |
| + | #endif /* FEATURE_SH_EXTRA_QUIET */ |
| /* | |
| - | * POSIX requires that 'set' (but not export or readonly) output the |
| - | * variables in lexicographic order - by the locale's collating order (sigh). |
| - | * Maybe we could keep them in an ordered balanced binary tree |
| - | * instead of hashed lists. |
| - | * For now just roll 'em through qsort for printing... |
| + | * Called to exit the shell. |
| */ | |
| - | static int |
| - | showvars(const char *sep_prefix, int on, int off) |
| + | static void |
| + | exitshell(void) |
| { | |
| - | const char *sep; |
| - | char **ep, **epend; |
| - | |
| - | ep = listvars(on, off, &epend); |
| - | qsort(ep, epend - ep, sizeof(char *), vpcmp); |
| - | |
| - | sep = *sep_prefix ? spcstr : sep_prefix; |
| - | |
| - | for (; ep < epend; ep++) { |
| - | const char *p; |
| - | const char *q; |
| - | |
| - | p = strchrnul(*ep, '='); |
| - | q = nullstr; |
| - | if (*p) |
| - | q = single_quote(++p); |
| + | struct jmploc loc; |
| + | char *p; |
| + | int status; |
| - | out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q); |
| + | status = exitstatus; |
| + | TRACE(("pid %d, exitshell(%d)\n", getpid(), status)); |
| + | if (setjmp(loc.loc)) { |
| + | if (exception == EXEXIT) |
| + | /* dash bug: it just does _exit(exitstatus) here |
| + | * but we have to do setjobctl(0) first! |
| + | * (bug is still not fixed in dash-0.5.3 - if you run dash |
| + | * under Midnight Commander, on exit from dash MC is backgrounded) */ |
| + | status = exitstatus; |
| + | goto out; |
| } | |
| - | |
| - | return 0; |
| + | exception_handler = &loc; |
| + | p = trap[0]; |
| + | if (p) { |
| + | trap[0] = NULL; |
| + | evalstring(p, 0); |
| + | } |
| + | flush_stdout_stderr(); |
| + | out: |
| + | setjobctl(0); |
| + | _exit(status); |
| + | /* NOTREACHED */ |
| } | |
| @@ -11780,39 +11813,6 @@ localcmd(int argc, char **argv) | |
| } | |
| - | /* |
| - | * Called after a function returns. |
| - | * Interrupts must be off. |
| - | */ |
| - | static void |
| - | poplocalvars(void) |
| - | { |
| - | struct localvar *lvp; |
| - | struct var *vp; |
| - | |
| - | while ((lvp = localvars) != NULL) { |
| - | localvars = lvp->next; |
| - | vp = lvp->vp; |
| - | TRACE(("poplocalvar %s", vp ? vp->text : "-")); |
| - | if (vp == NULL) { /* $- saved */ |
| - | memcpy(optlist, lvp->text, sizeof(optlist)); |
| - | free((char*)lvp->text); |
| - | optschanged(); |
| - | } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { |
| - | unsetvar(vp->text); |
| - | } else { |
| - | if (vp->func) |
| - | (*vp->func)(strchrnul(lvp->text, '=') + 1); |
| - | if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) |
| - | free((char*)vp->text); |
| - | vp->flags = lvp->flags; |
| - | vp->text = lvp->text; |
| - | } |
| - | free(lvp); |
| - | } |
| - | } |
| - | |
| - | |
| /* | |
| * The unset builtin command. We unset the function before we unset the | |
| * variable to allow a function to be unset when there is a readonly variable | |
| @@ -11844,104 +11844,6 @@ unsetcmd(int argc, char **argv) | |
| } | |
| - | /* |
| - | * Unset the specified variable. |
| - | */ |
| - | static int |
| - | unsetvar(const char *s) |
| - | { |
| - | struct var **vpp; |
| - | struct var *vp; |
| - | int retval; |
| - | |
| - | vpp = findvar(hashvar(s), s); |
| - | vp = *vpp; |
| - | retval = 2; |
| - | if (vp) { |
| - | int flags = vp->flags; |
| - | |
| - | retval = 1; |
| - | if (flags & VREADONLY) |
| - | goto out; |
| - | #ifdef DYNAMIC_VAR |
| - | vp->flags &= ~VDYNAMIC; |
| - | #endif |
| - | if (flags & VUNSET) |
| - | goto ok; |
| - | if ((flags & VSTRFIXED) == 0) { |
| - | INT_OFF; |
| - | if ((flags & (VTEXTFIXED|VSTACK)) == 0) |
| - | free((char*)vp->text); |
| - | *vpp = vp->next; |
| - | free(vp); |
| - | INT_ON; |
| - | } else { |
| - | setvar(s, 0, 0); |
| - | vp->flags &= ~VEXPORT; |
| - | } |
| - | ok: |
| - | retval = 0; |
| - | } |
| - | out: |
| - | return retval; |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Find the appropriate entry in the hash table from the name. |
| - | */ |
| - | static struct var ** |
| - | hashvar(const char *p) |
| - | { |
| - | unsigned int hashval; |
| - | |
| - | hashval = ((unsigned char) *p) << 4; |
| - | while (*p && *p != '=') |
| - | hashval += (unsigned char) *p++; |
| - | return &vartab[hashval % VTABSIZE]; |
| - | } |
| - | |
| - | |
| - | /* |
| - | * Compares two strings up to the first = or '\0'. The first |
| - | * string must be terminated by '='; the second may be terminated by |
| - | * either '=' or '\0'. |
| - | */ |
| - | static int |
| - | varcmp(const char *p, const char *q) |
| - | { |
| - | int c, d; |
| - | |
| - | while ((c = *p) == (d = *q)) { |
| - | if (!c || c == '=') |
| - | goto out; |
| - | p++; |
| - | q++; |
| - | } |
| - | if (c == '=') |
| - | c = 0; |
| - | if (d == '=') |
| - | d = 0; |
| - | out: |
| - | return c - d; |
| - | } |
| - | |
| - | static int |
| - | vpcmp(const void *a, const void *b) |
| - | { |
| - | return varcmp(*(const char **)a, *(const char **)b); |
| - | } |
| - | |
| - | static struct var ** |
| - | findvar(struct var **vpp, const char *name) |
| - | { |
| - | for (; *vpp; vpp = &(*vpp)->next) { |
| - | if (varequal((*vpp)->text, name)) { |
| - | break; |
| - | } |
| - | } |
| - | return vpp; |
| - | } |
| /* setmode.c */ | |
| #include <sys/times.h> | |
| @@ -12274,7 +12176,7 @@ static int umaskcmd(int argc, char **argv) | |
| out1fmt("%.4o\n", mask); | |
| } | |
| } else { | |
| - | if (is_digit((unsigned char) *ap)) { |
| + | if (isdigit((unsigned char) *ap)) { |
| mask = 0; | |
| do { | |
| if (*ap >= '8' || *ap < '0') | |
| @@ -13012,7 +12914,7 @@ static arith_t arith(const char *expr, int *perrcode) | |
| lasttok = TOK_NUM; | |
| continue; | |
| } | |
| - | if (is_digit(arithval)) { |
| + | if (isdigit(arithval)) { |
| numstackptr->var = NULL; | |
| #if ENABLE_ASH_MATH_SUPPORT_64 | |
| numstackptr->val = strtoll(expr, (char **) &expr, 0); | |