Index: head/bin/sh/TOUR =================================================================== --- head/bin/sh/TOUR (revision 245688) +++ head/bin/sh/TOUR (revision 245689) @@ -1,319 +1,315 @@ # @(#)TOUR 8.1 (Berkeley) 5/31/93 # $FreeBSD$ NOTE -- This is the original TOUR paper distributed with ash and does not represent the current state of the shell. It is provided anyway since it provides helpful information for how the shell is structured, but be warned that things have changed -- the current shell is still under development. ================================================================ A Tour through Ash Copyright 1989 by Kenneth Almquist. DIRECTORIES: The subdirectory bltin contains commands which can be compiled stand-alone. The rest of the source is in the main ash directory. SOURCE CODE GENERATORS: Files whose names begin with "mk" are programs that generate source code. A complete list of these programs is: program input files generates ------- ----------- --------- mkbuiltins builtins builtins.h builtins.c mkinit *.c init.c mknodes nodetypes nodes.h nodes.c mksyntax - syntax.h syntax.c mktokens - token.h There are undoubtedly too many of these. Mkinit searches all the C source files for entries looking like: - INIT { - x = 1; /* executed during initialization */ - } - RESET { x = 2; /* executed when the shell does a longjmp back to the main command loop */ } It pulls this code out into routines which are when particular events occur. The intent is to improve modularity by isolating the information about which modules need to be explicitly initialized/reset within the modules themselves. Mkinit recognizes several constructs for placing declarations in the init.c file. INCLUDE "file.h" includes a file. The storage class MKINIT makes a declaration available in the init.c file, for example: MKINIT int funcnest; /* depth of function calls */ MKINIT alone on a line introduces a structure or union declara- tion: MKINIT struct redirtab { short renamed[10]; }; Preprocessor #define statements are copied to init.c without any special action to request this. EXCEPTIONS: Code for dealing with exceptions appears in exceptions.c. The C language doesn't include exception handling, so I implement it using setjmp and longjmp. The global variable exception contains the type of exception. EXERROR is raised by calling error. EXINT is an interrupt. INTERRUPTS: In an interactive shell, an interrupt will cause an EXINT exception to return to the main command loop. (Exception: EXINT is not raised if the user traps interrupts using the trap command.) The INTOFF and INTON macros (defined in exception.h) provide uninterruptible critical sections. Between the execution of INTOFF and the execution of INTON, interrupt signals will be held for later delivery. INTOFF and INTON can be nested. MEMALLOC.C: Memalloc.c defines versions of malloc and realloc which call error when there is no memory left. It also defines a stack oriented memory allocation scheme. Allocating off a stack is probably more efficient than allocation using malloc, but the big advantage is that when an exception occurs all we have to do to free up the memory in use at the time of the exception is to restore the stack pointer. The stack is implemented using a linked list of blocks. STPUTC: If the stack were contiguous, it would be easy to store strings on the stack without knowing in advance how long the string was going to be: p = stackptr; *p++ = c; /* repeated as many times as needed */ stackptr = p; The following three macros (defined in memalloc.h) perform these operations, but grow the stack if you run off the end: STARTSTACKSTR(p); STPUTC(c, p); /* repeated as many times as needed */ grabstackstr(p); We now start a top-down look at the code: MAIN.C: The main routine performs some initialization, executes the user's profile if necessary, and calls cmdloop. Cmdloop repeatedly parses and executes commands. OPTIONS.C: This file contains the option processing code. It is called from main to parse the shell arguments when the shell is invoked, and it also contains the set builtin. The -i and -m op- tions (the latter turns on job control) require changes in signal handling. The routines setjobctl (in jobs.c) and setinteractive (in trap.c) are called to handle changes to these options. PARSING: The parser code is all in parser.c. A recursive des- cent parser is used. Syntax tables (generated by mksyntax) are used to classify characters during lexical analysis. There are four tables: one for normal use, one for use when inside single quotes and dollar single quotes, one for use when inside double quotes and one for use in arithmetic. The tables are machine dependent because they are indexed by character variables and the range of a char varies from machine to machine. PARSE OUTPUT: The output of the parser consists of a tree of nodes. The various types of nodes are defined in the file node- types. Nodes of type NARG are used to represent both words and the con- tents of here documents. An early version of ash kept the con- tents of here documents in temporary files, but keeping here do- cuments in memory typically results in significantly better per- formance. It would have been nice to make it an option to use temporary files for here documents, for the benefit of small machines, but the code to keep track of when to delete the tem- porary files was complex and I never fixed all the bugs in it. (AT&T has been maintaining the Bourne shell for more than ten years, and to the best of my knowledge they still haven't gotten it to handle temporary files correctly in obscure cases.) The text field of a NARG structure points to the text of the word. The text consists of ordinary characters and a number of special codes defined in parser.h. The special codes are: CTLVAR Variable substitution CTLENDVAR End of variable substitution CTLBACKQ Command substitution CTLBACKQ|CTLQUOTE Command substitution inside double quotes CTLESC Escape next character A variable substitution contains the following elements: CTLVAR type name '=' [ alternative-text CTLENDVAR ] The type field is a single character specifying the type of sub- stitution. The possible types are: VSNORMAL $var VSMINUS ${var-text} VSMINUS|VSNUL ${var:-text} VSPLUS ${var+text} VSPLUS|VSNUL ${var:+text} VSQUESTION ${var?text} VSQUESTION|VSNUL ${var:?text} VSASSIGN ${var=text} VSASSIGN|VSNUL ${var:=text} In addition, the type field will have the VSQUOTE flag set if the variable is enclosed in double quotes. The name of the variable comes next, terminated by an equals sign. If the type is not VSNORMAL, then the text field in the substitution follows, ter- minated by a CTLENDVAR byte. Commands in back quotes are parsed and stored in a linked list. The locations of these commands in the string are indicated by CTLBACKQ and CTLBACKQ+CTLQUOTE characters, depending upon whether the back quotes were enclosed in double quotes. The character CTLESC escapes the next character, so that in case any of the CTL characters mentioned above appear in the input, they can be passed through transparently. CTLESC is also used to escape '*', '?', '[', and '!' characters which were quoted by the user and thus should not be used for file name generation. CTLESC characters have proved to be particularly tricky to get right. In the case of here documents which are not subject to variable and command substitution, the parser doesn't insert any CTLESC characters to begin with (so the contents of the text field can be written without any processing). Other here docu- ments, and words which are not subject to splitting and file name generation, have the CTLESC characters removed during the vari- able and command substitution phase. Words which are subject to splitting and file name generation have the CTLESC characters re- moved as part of the file name phase. EXECUTION: Command execution is handled by the following files: eval.c The top level routines. redir.c Code to handle redirection of input and output. jobs.c Code to handle forking, waiting, and job control. exec.c Code to do path searches and the actual exec sys call. expand.c Code to evaluate arguments. var.c Maintains the variable symbol table. Called from expand.c. EVAL.C: Evaltree recursively executes a parse tree. The exit status is returned in the global variable exitstatus. The alter- native entry evalbackcmd is called to evaluate commands in back quotes. It saves the result in memory if the command is a buil- tin; otherwise it forks off a child to execute the command and connects the standard output of the child to a pipe. JOBS.C: To create a process, you call makejob to return a job structure, and then call forkshell (passing the job structure as an argument) to create the process. Waitforjob waits for a job to complete. These routines take care of process groups if job control is defined. REDIR.C: Ash allows file descriptors to be redirected and then restored without forking off a child process. This is accom- plished by duplicating the original file descriptors. The redir- tab structure records where the file descriptors have been dupli- cated to. EXEC.C: The routine find_command locates a command, and enters the command in the hash table if it is not already there. The third argument specifies whether it is to print an error message if the command is not found. (When a pipeline is set up, find_command is called for all the commands in the pipeline be- fore any forking is done, so to get the commands into the hash table of the parent process. But to make command hashing as transparent as possible, we silently ignore errors at that point and only print error messages if the command cannot be found later.) The routine shellexec is the interface to the exec system call. EXPAND.C: Arguments are processed in three passes. The first (performed by the routine argstr) performs variable and command substitution. The second (ifsbreakup) performs word splitting and the third (expandmeta) performs file name generation. VAR.C: Variables are stored in a hash table. Probably we should switch to extensible hashing. The variable name is stored in the same string as the value (using the format "name=value") so that no string copying is needed to create the environment of a com- mand. Variables which the shell references internally are preal- located so that the shell can reference the values of these vari- ables without doing a lookup. When a program is run, the code in eval.c sticks any environment variables which precede the command (as in "PATH=xxx command") in the variable table as the simplest way to strip duplicates, and then calls "environment" to get the value of the environment. BUILTIN COMMANDS: The procedures for handling these are scat- tered throughout the code, depending on which location appears most appropriate. They can be recognized because their names al- ways end in "cmd". The mapping from names to procedures is specified in the file builtins, which is processed by the mkbuilt- ins command. A builtin command is invoked with argc and argv set up like a normal program. A builtin command is allowed to overwrite its arguments. Builtin routines can call nextopt to do option pars- ing. This is kind of like getopt, but you don't pass argc and argv to it. Builtin routines can also call error. This routine normally terminates the shell (or returns to the main command loop if the shell is interactive), but when called from a builtin command it causes the builtin command to terminate with an exit status of 2. The directory bltins contains commands which can be compiled in- dependently but can also be built into the shell for efficiency reasons. The makefile in this directory compiles these programs in the normal fashion (so that they can be run regardless of whether the invoker is ash), but also creates a library named bltinlib.a which can be linked with ash. The header file bltin.h takes care of most of the differences between the ash and the stand-alone environment. The user should call the main routine "main", and #define main to be the name of the routine to use when the program is linked into ash. This #define should appear before bltin.h is included; bltin.h will #undef main if the pro- gram is to be compiled stand-alone. CD.C: This file defines the cd and pwd builtins. SIGNALS: Trap.c implements the trap command. The routine set- signal figures out what action should be taken when a signal is received and invokes the signal system call to set the signal ac- tion appropriately. When a signal that a user has set a trap for is caught, the routine "onsig" sets a flag. The routine dotrap is called at appropriate points to actually handle the signal. When an interrupt is caught and no trap has been set for that signal, the routine "onint" in error.c is called. OUTPUT: Ash uses it's own output routines. There are three out- put structures allocated. "Output" represents the standard out- put, "errout" the standard error, and "memout" contains output which is to be stored in memory. This last is used when a buil- tin command appears in backquotes, to allow its output to be col- lected without doing any I/O through the UNIX operating system. The variables out1 and out2 normally point to output and errout, respectively, but they are set to point to memout when appropri- ate inside backquotes. INPUT: The basic input routine is pgetc, which reads from the current input file. There is a stack of input files; the current input file is the top file on this stack. The code allows the input to come from a string rather than a file. (This is for the -c option and the "." and eval builtin commands.) The global variable plinno is saved and restored when files are pushed and popped from the stack. The parser routines store the number of the current line in this variable. DEBUGGING: If DEBUG is defined in shell.h, then the shell will write debugging information to the file $HOME/trace. Most of this is done using the TRACE macro, which takes a set of printf arguments inside two sets of parenthesis. Example: "TRACE(("n=%d0, n))". The double parenthesis are necessary be- cause the preprocessor can't handle functions with a variable number of arguments. Defining DEBUG also causes the shell to generate a core dump if it is sent a quit signal. The tracing code is in show.c. Index: head/bin/sh/init.h =================================================================== --- head/bin/sh/init.h (revision 245688) +++ head/bin/sh/init.h (revision 245689) @@ -1,37 +1,36 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)init.h 8.2 (Berkeley) 5/4/95 * $FreeBSD$ */ -void init(void); void reset(void); Index: head/bin/sh/main.c =================================================================== --- head/bin/sh/main.c (revision 245688) +++ head/bin/sh/main.c (revision 245689) @@ -1,340 +1,340 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static char const copyright[] = "@(#) Copyright (c) 1991, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 5/28/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include "shell.h" #include "main.h" #include "mail.h" #include "options.h" #include "output.h" #include "parser.h" #include "nodes.h" #include "expand.h" #include "eval.h" #include "jobs.h" #include "input.h" #include "trap.h" #include "var.h" #include "show.h" #include "memalloc.h" #include "error.h" #include "init.h" #include "mystring.h" #include "exec.h" #include "cd.h" #include "builtins.h" int rootpid; int rootshell; struct jmploc main_handler; int localeisutf8, initial_localeisutf8; static void cmdloop(int); static void read_profile(char *); static char *find_dot_file(char *); /* * Main routine. We initialize things, parse the arguments, execute * profiles if we're a login shell, and then call cmdloop to execute * commands. The setjmp call sets up the location to jump to when an * exception occurs. When an exception occurs the variable "state" * is used to figure out how far we had gotten. */ int main(int argc, char *argv[]) { struct stackmark smark, smark2; volatile int state; char *shinit; (void) setlocale(LC_ALL, ""); initcharset(); state = 0; if (setjmp(main_handler.loc)) { switch (exception) { case EXEXEC: exitstatus = exerrno; break; case EXERROR: exitstatus = 2; break; default: break; } if (state == 0 || iflag == 0 || ! rootshell || exception == EXEXIT) exitshell(exitstatus); reset(); if (exception == EXINT) out2fmt_flush("\n"); popstackmark(&smark); FORCEINTON; /* enable interrupts */ if (state == 1) goto state1; else if (state == 2) goto state2; else if (state == 3) goto state3; else goto state4; } handler = &main_handler; #ifdef DEBUG opentrace(); trputs("Shell args: "); trargs(argv); #endif rootpid = getpid(); rootshell = 1; - init(); + initvar(); setstackmark(&smark); setstackmark(&smark2); procargs(argc, argv); pwd_init(iflag); if (iflag) chkmail(1); if (argv[0] && argv[0][0] == '-') { state = 1; read_profile("/etc/profile"); state1: state = 2; if (privileged == 0) read_profile("${HOME-}/.profile"); else read_profile("/etc/suid_profile"); } state2: state = 3; if (!privileged && iflag) { if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') { state = 3; read_profile(shinit); } } state3: state = 4; popstackmark(&smark2); if (minusc) { evalstring(minusc, sflag ? 0 : EV_EXIT); } if (sflag || minusc == NULL) { state4: /* XXX ??? - why isn't this before the "if" statement */ cmdloop(1); } exitshell(exitstatus); /*NOTREACHED*/ return 0; } /* * Read and execute commands. "Top" is nonzero for the top level command * loop; it turns on prompting if the shell is interactive. */ static void cmdloop(int top) { union node *n; struct stackmark smark; int inter; int numeof = 0; TRACE(("cmdloop(%d) called\n", top)); setstackmark(&smark); for (;;) { if (pendingsigs) dotrap(); inter = 0; if (iflag && top) { inter++; showjobs(1, SHOWJOBS_DEFAULT); chkmail(0); flushout(&output); } n = parsecmd(inter); /* showtree(n); DEBUG */ if (n == NEOF) { if (!top || numeof >= 50) break; if (!stoppedjobs()) { if (!Iflag) break; out2fmt_flush("\nUse \"exit\" to leave shell.\n"); } numeof++; } else if (n != NULL && nflag == 0) { job_warning = (job_warning == 2) ? 1 : 0; numeof = 0; evaltree(n, 0); } popstackmark(&smark); setstackmark(&smark); if (evalskip != 0) { if (evalskip == SKIPFILE) evalskip = 0; break; } } popstackmark(&smark); } /* * Read /etc/profile or .profile. Return on error. */ static void read_profile(char *name) { int fd; const char *expandedname; expandedname = expandstr(name); if (expandedname == NULL) return; INTOFF; if ((fd = open(expandedname, O_RDONLY)) >= 0) setinputfd(fd, 1); INTON; if (fd < 0) return; cmdloop(0); popfile(); } /* * Read a file containing shell functions. */ void readcmdfile(const char *name) { setinputfile(name, 1); cmdloop(0); popfile(); } /* * 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 *basename) { char *fullname; const char *path = pathval(); struct stat statb; /* don't try this for absolute or relative paths */ if( strchr(basename, '/')) return basename; while ((fullname = padvance(&path, basename)) != 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); } return basename; } int dotcmd(int argc, char **argv) { char *filename, *fullname; if (argc < 2) error("missing filename"); exitstatus = 0; /* * Because we have historically not supported any options, * only treat "--" specially. */ filename = argc > 2 && strcmp(argv[1], "--") == 0 ? argv[2] : argv[1]; fullname = find_dot_file(filename); setinputfile(fullname, 1); commandname = fullname; cmdloop(0); popfile(); return exitstatus; } int exitcmd(int argc, char **argv) { if (stoppedjobs()) return 0; if (argc > 1) exitshell(number(argv[1])); else exitshell_savedstatus(); } Index: head/bin/sh/mkinit.c =================================================================== --- head/bin/sh/mkinit.c (revision 245688) +++ head/bin/sh/mkinit.c (revision 245689) @@ -1,486 +1,480 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static char const copyright[] = "@(#) Copyright (c) 1991, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)mkinit.c 8.2 (Berkeley) 5/4/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); /* * This program scans all the source files for code to handle various * special events and combines this code into one file. This (allegedly) * improves the structure of the program since there is no need for * anyone outside of a module to know that that module performs special * operations on particular events. * * Usage: mkinit sourcefile... */ #include #include #include #include #include #include #include /* * OUTFILE is the name of the output file. Output is initially written * to the file OUTTEMP, which is then moved to OUTFILE. */ #define OUTFILE "init.c" #define OUTTEMP "init.c.new" /* * A text structure is basically just a string that grows as more characters * are added onto the end of it. It is implemented as a linked list of * blocks of characters. The routines addstr and addchar append a string * or a single character, respectively, to a text structure. Writetext * writes the contents of a text structure to a file. */ #define BLOCKSIZE 512 struct text { char *nextc; int nleft; struct block *start; struct block *last; }; struct block { struct block *next; char text[BLOCKSIZE]; }; /* * There is one event structure for each event that mkinit handles. */ struct event { - const char *name; /* name of event (e.g. INIT) */ + const char *name; /* name of event (e.g. RESET) */ const char *routine; /* name of routine called on event */ const char *comment; /* comment describing routine */ struct text code; /* code for handling event */ }; char writer[] = "\ /*\n\ * This file was generated by the mkinit program.\n\ */\n\ \n"; -char init[] = "\ -/*\n\ - * Initialization code.\n\ - */\n"; - char reset[] = "\ /*\n\ * This routine is called when an error or an interrupt occurs in an\n\ * interactive shell and control is returned to the main command loop.\n\ */\n"; struct event event[] = { - { "INIT", "init", init, { NULL, 0, NULL, NULL } }, { "RESET", "reset", reset, { NULL, 0, NULL, NULL } }, { NULL, NULL, NULL, { NULL, 0, NULL, NULL } } }; const char *curfile; /* current file */ int linno; /* current line */ char *header_files[200]; /* list of header files */ struct text defines; /* #define statements */ struct text decls; /* declarations */ int amiddecls; /* for formatting */ void readfile(const char *); int match(const char *, const char *); int gooddefine(const char *); void doevent(struct event *, FILE *, const char *); void doinclude(char *); void dodecl(char *, FILE *); void output(void); void addstr(const char *, struct text *); void addchar(int, struct text *); void writetext(struct text *, FILE *); FILE *ckfopen(const char *, const char *); void *ckmalloc(size_t); char *savestr(const char *); void error(const char *); #define equal(s1, s2) (strcmp(s1, s2) == 0) int main(int argc __unused, char *argv[]) { char **ap; header_files[0] = savestr("\"shell.h\""); header_files[1] = savestr("\"mystring.h\""); header_files[2] = savestr("\"init.h\""); for (ap = argv + 1 ; *ap ; ap++) readfile(*ap); output(); rename(OUTTEMP, OUTFILE); exit(0); } /* * Parse an input file. */ void readfile(const char *fname) { FILE *fp; char line[1024]; struct event *ep; fp = ckfopen(fname, "r"); curfile = fname; linno = 0; amiddecls = 0; while (fgets(line, sizeof line, fp) != NULL) { linno++; for (ep = event ; ep->name ; ep++) { if (line[0] == ep->name[0] && match(ep->name, line)) { doevent(ep, fp, fname); break; } } if (line[0] == 'I' && match("INCLUDE", line)) doinclude(line); if (line[0] == 'M' && match("MKINIT", line)) dodecl(line, fp); if (line[0] == '#' && gooddefine(line)) { char *cp; char line2[1024]; static const char undef[] = "#undef "; strcpy(line2, line); memcpy(line2, undef, sizeof(undef) - 1); cp = line2 + sizeof(undef) - 1; while(*cp && (*cp == ' ' || *cp == '\t')) cp++; while(*cp && *cp != ' ' && *cp != '\t' && *cp != '\n') cp++; *cp++ = '\n'; *cp = '\0'; addstr(line2, &defines); addstr(line, &defines); } } fclose(fp); } int match(const char *name, const char *line) { const char *p, *q; p = name, q = line; while (*p) { if (*p++ != *q++) return 0; } if (*q != '{' && *q != ' ' && *q != '\t' && *q != '\n') return 0; return 1; } int gooddefine(const char *line) { const char *p; if (! match("#define", line)) return 0; /* not a define */ p = line + 7; while (*p == ' ' || *p == '\t') p++; while (*p != ' ' && *p != '\t') { if (*p == '(') return 0; /* macro definition */ p++; } while (*p != '\n' && *p != '\0') p++; if (p[-1] == '\\') return 0; /* multi-line definition */ return 1; } void doevent(struct event *ep, FILE *fp, const char *fname) { char line[1024]; int indent; const char *p; sprintf(line, "\n /* from %s: */\n", fname); addstr(line, &ep->code); addstr(" {\n", &ep->code); for (;;) { linno++; if (fgets(line, sizeof line, fp) == NULL) error("Unexpected EOF"); if (equal(line, "}\n")) break; indent = 6; for (p = line ; *p == '\t' ; p++) indent += 8; for ( ; *p == ' ' ; p++) indent++; if (*p == '\n' || *p == '#') indent = 0; while (indent >= 8) { addchar('\t', &ep->code); indent -= 8; } while (indent > 0) { addchar(' ', &ep->code); indent--; } addstr(p, &ep->code); } addstr(" }\n", &ep->code); } void doinclude(char *line) { char *p; char *name; char **pp; for (p = line ; *p != '"' && *p != '<' && *p != '\0' ; p++); if (*p == '\0') error("Expecting '\"' or '<'"); name = p; while (*p != ' ' && *p != '\t' && *p != '\n') p++; if (p[-1] != '"' && p[-1] != '>') error("Missing terminator"); *p = '\0'; /* name now contains the name of the include file */ for (pp = header_files ; *pp && ! equal(*pp, name) ; pp++); if (*pp == NULL) *pp = savestr(name); } void dodecl(char *line1, FILE *fp) { char line[1024]; char *p, *q; if (strcmp(line1, "MKINIT\n") == 0) { /* start of struct/union decl */ addchar('\n', &decls); do { linno++; if (fgets(line, sizeof line, fp) == NULL) error("Unterminated structure declaration"); addstr(line, &decls); } while (line[0] != '}'); amiddecls = 0; } else { if (! amiddecls) addchar('\n', &decls); q = NULL; for (p = line1 + 6 ; *p && strchr("=/\n", *p) == NULL; p++) continue; if (*p == '=') { /* eliminate initialization */ for (q = p ; *q && *q != ';' ; q++); if (*q == '\0') q = NULL; else { while (p[-1] == ' ') p--; *p = '\0'; } } addstr("extern", &decls); addstr(line1 + 6, &decls); if (q != NULL) addstr(q, &decls); amiddecls = 1; } } /* * Write the output to the file OUTTEMP. */ void output(void) { FILE *fp; char **pp; struct event *ep; fp = ckfopen(OUTTEMP, "w"); fputs(writer, fp); for (pp = header_files ; *pp ; pp++) fprintf(fp, "#include %s\n", *pp); fputs("\n\n\n", fp); writetext(&defines, fp); fputs("\n\n", fp); writetext(&decls, fp); for (ep = event ; ep->name ; ep++) { fputs("\n\n\n", fp); fputs(ep->comment, fp); fprintf(fp, "\nvoid\n%s(void)\n{\n", ep->routine); writetext(&ep->code, fp); fprintf(fp, "}\n"); } fclose(fp); } /* * A text structure is simply a block of text that is kept in memory. * Addstr appends a string to the text struct, and addchar appends a single * character. */ void addstr(const char *s, struct text *text) { while (*s) { if (--text->nleft < 0) addchar(*s++, text); else *text->nextc++ = *s++; } } void addchar(int c, struct text *text) { struct block *bp; if (--text->nleft < 0) { bp = ckmalloc(sizeof *bp); if (text->start == NULL) text->start = bp; else text->last->next = bp; text->last = bp; text->nextc = bp->text; text->nleft = BLOCKSIZE - 1; } *text->nextc++ = c; } /* * Write the contents of a text structure to a file. */ void writetext(struct text *text, FILE *fp) { struct block *bp; if (text->start != NULL) { for (bp = text->start ; bp != text->last ; bp = bp->next) fwrite(bp->text, sizeof (char), BLOCKSIZE, fp); fwrite(bp->text, sizeof (char), BLOCKSIZE - text->nleft, fp); } } FILE * ckfopen(const char *file, const char *mode) { FILE *fp; if ((fp = fopen(file, mode)) == NULL) { fprintf(stderr, "Can't open %s: %s\n", file, strerror(errno)); exit(2); } return fp; } void * ckmalloc(size_t nbytes) { char *p; if ((p = malloc(nbytes)) == NULL) error("Out of space"); return p; } char * savestr(const char *s) { char *p; p = ckmalloc(strlen(s) + 1); strcpy(p, s); return p; } void error(const char *msg) { if (curfile != NULL) fprintf(stderr, "%s:%d: ", curfile, linno); fprintf(stderr, "%s\n", msg); exit(2); } Index: head/bin/sh/var.c =================================================================== --- head/bin/sh/var.c (revision 245688) +++ head/bin/sh/var.c (revision 245689) @@ -1,945 +1,933 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include /* * Shell variables. */ #include #include #include "shell.h" #include "output.h" #include "expand.h" #include "nodes.h" /* for other headers */ #include "eval.h" /* defines cmdenviron */ #include "exec.h" #include "syntax.h" #include "options.h" #include "mail.h" #include "var.h" #include "memalloc.h" #include "error.h" #include "mystring.h" #include "parser.h" #include "builtins.h" #ifndef NO_HISTORY #include "myhistedit.h" #endif #define VTABSIZE 39 struct varinit { struct var *var; int flags; const char *text; void (*func)(const char *); }; #ifndef NO_HISTORY struct var vhistsize; struct var vterm; #endif struct var vifs; struct var vmail; struct var vmpath; struct var vpath; struct var vppid; struct var vps1; struct var vps2; struct var vps4; struct var vvers; static struct var voptind; struct var vdisvfork; int forcelocal; static const struct varinit varinit[] = { #ifndef NO_HISTORY { &vhistsize, VUNSET, "HISTSIZE=", sethistsize }, #endif { &vifs, 0, "IFS= \t\n", NULL }, { &vmail, VUNSET, "MAIL=", NULL }, { &vmpath, VUNSET, "MAILPATH=", NULL }, { &vpath, 0, "PATH=" _PATH_DEFPATH, changepath }, { &vppid, VUNSET, "PPID=", NULL }, /* * vps1 depends on uid */ { &vps2, 0, "PS2=> ", NULL }, { &vps4, 0, "PS4=+ ", NULL }, #ifndef NO_HISTORY { &vterm, VUNSET, "TERM=", setterm }, #endif { &voptind, 0, "OPTIND=1", getoptsreset }, { &vdisvfork, VUNSET, "SH_DISABLE_VFORK=", NULL }, { NULL, 0, NULL, NULL } }; static struct var *vartab[VTABSIZE]; static const char *const locale_names[7] = { "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", "LC_MESSAGES", NULL }; static const int locale_categories[7] = { LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME, LC_MESSAGES, 0 }; static int varequal(const char *, const char *); static struct var *find_var(const char *, struct var ***, int *); static int localevar(const char *); -/* - * Initialize the variable symbol tables and import the environment. - */ +extern char **environ; -#ifdef mkinit -INCLUDE "var.h" -MKINIT char **environ; -INIT { - char **envp; - - initvar(); - for (envp = environ ; *envp ; envp++) { - if (strchr(*envp, '=')) { - setvareq(*envp, VEXPORT|VTEXTFIXED); - } - } -} -#endif - - /* - * This routine initializes the builtin variables. It is called when the - * shell is initialized. + * This routine initializes the builtin variables and imports the environment. + * It is called when the shell is initialized. */ void initvar(void) { char ppid[20]; const struct varinit *ip; struct var *vp; struct var **vpp; + char **envp; for (ip = varinit ; (vp = ip->var) != NULL ; ip++) { if (find_var(ip->text, &vpp, &vp->name_len) != NULL) continue; vp->next = *vpp; *vpp = vp; vp->text = __DECONST(char *, ip->text); vp->flags = ip->flags | VSTRFIXED | VTEXTFIXED; vp->func = ip->func; } /* * PS1 depends on uid */ if (find_var("PS1", &vpp, &vps1.name_len) == NULL) { vps1.next = *vpp; *vpp = &vps1; vps1.text = __DECONST(char *, geteuid() ? "PS1=$ " : "PS1=# "); vps1.flags = VSTRFIXED|VTEXTFIXED; } if ((vppid.flags & VEXPORT) == 0) { fmtstr(ppid, sizeof(ppid), "%d", (int)getppid()); setvarsafe("PPID", ppid, 0); } + for (envp = environ ; *envp ; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } } /* * Safe version of setvar, returns 1 on success 0 on failure. */ int setvarsafe(const char *name, const char *val, int flags) { struct jmploc jmploc; struct jmploc *const savehandler = handler; int err = 0; int inton; inton = is_int_on(); if (setjmp(jmploc.loc)) err = 1; else { handler = &jmploc; setvar(name, val, flags); } handler = savehandler; SETINTON(inton); return err; } /* * Set the value of a variable. The flags argument is stored with the * flags of the variable. If val is NULL, the variable is unset. */ void setvar(const char *name, const char *val, int flags) { const char *p; int len; int namelen; char *nameeq; int isbad; isbad = 0; p = name; if (! is_name(*p)) isbad = 1; p++; for (;;) { if (! is_in_name(*p)) { if (*p == '\0' || *p == '=') break; isbad = 1; } p++; } namelen = p - name; if (isbad) error("%.*s: bad variable name", namelen, name); len = namelen + 2; /* 2 is space for '=' and '\0' */ if (val == NULL) { flags |= VUNSET; } else { len += strlen(val); } nameeq = ckmalloc(len); memcpy(nameeq, name, namelen); nameeq[namelen] = '='; if (val) scopy(val, nameeq + namelen + 1); else nameeq[namelen + 1] = '\0'; setvareq(nameeq, flags); } static int localevar(const char *s) { const char *const *ss; if (*s != 'L') return 0; if (varequal(s + 1, "ANG")) return 1; if (strncmp(s + 1, "C_", 2) != 0) return 0; if (varequal(s + 3, "ALL")) return 1; for (ss = locale_names; *ss ; ss++) if (varequal(s + 3, *ss + 3)) return 1; return 0; } /* * Sets/unsets an environment variable from a pointer that may actually be a * pointer into environ where the string should not be manipulated. */ static void change_env(const char *s, int set) { char *eqp; char *ss; ss = savestr(s); if ((eqp = strchr(ss, '=')) != NULL) *eqp = '\0'; if (set && eqp != NULL) (void) setenv(ss, eqp + 1, 1); else (void) unsetenv(ss); ckfree(ss); return; } /* * 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. */ void setvareq(char *s, int flags) { struct var *vp, **vpp; int nlen; if (aflag) flags |= VEXPORT; if (forcelocal && !(flags & (VNOSET | VNOLOCAL))) mklocal(s); vp = find_var(s, &vpp, &nlen); if (vp != NULL) { if (vp->flags & VREADONLY) error("%.*s: is read only", vp->name_len, s); if (flags & VNOSET) return; INTOFF; if (vp->func && (flags & VNOFUNC) == 0) (*vp->func)(s + vp->name_len + 1); if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) ckfree(vp->text); vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET); vp->flags |= flags; vp->text = s; /* * We could roll this to a function, to handle it as * a regular variable function callback, but why bother? * * Note: this assumes iflag is not set to 1 initially. - * As part of init(), this is called before arguments + * As part of initvar(), this is called before arguments * are looked at. */ if ((vp == &vmpath || (vp == &vmail && ! mpathset())) && iflag == 1) chkmail(1); if ((vp->flags & VEXPORT) && localevar(s)) { change_env(s, 1); (void) setlocale(LC_ALL, ""); updatecharset(); } INTON; return; } /* not found */ if (flags & VNOSET) return; vp = ckmalloc(sizeof (*vp)); vp->flags = flags; vp->text = s; vp->name_len = nlen; vp->next = *vpp; vp->func = NULL; INTOFF; *vpp = vp; if ((vp->flags & VEXPORT) && localevar(s)) { change_env(s, 1); (void) setlocale(LC_ALL, ""); updatecharset(); } INTON; } /* * Process a linked list of variable assignments. */ void listsetvar(struct strlist *list, int flags) { struct strlist *lp; INTOFF; for (lp = list ; lp ; lp = lp->next) { setvareq(savestr(lp->text), flags); } INTON; } /* * Find the value of a variable. Returns NULL if not set. */ char * lookupvar(const char *name) { struct var *v; v = find_var(name, NULL, NULL); if (v == NULL || v->flags & VUNSET) return NULL; return v->text + v->name_len + 1; } /* * Search the environment of a builtin command. If the second argument * is nonzero, return the value of a variable even if it hasn't been * exported. */ char * bltinlookup(const char *name, int doall) { struct strlist *sp; struct var *v; char *result; result = NULL; for (sp = cmdenviron ; sp ; sp = sp->next) { if (varequal(sp->text, name)) result = strchr(sp->text, '=') + 1; } if (result != NULL) return result; v = find_var(name, NULL, NULL); if (v == NULL || v->flags & VUNSET || (!doall && (v->flags & VEXPORT) == 0)) return NULL; return v->text + v->name_len + 1; } /* * Set up locale for a builtin (LANG/LC_* assignments). */ void bltinsetlocale(void) { struct strlist *lp; int act = 0; char *loc, *locdef; int i; for (lp = cmdenviron ; lp ; lp = lp->next) { if (localevar(lp->text)) { act = 1; break; } } if (!act) return; loc = bltinlookup("LC_ALL", 0); INTOFF; if (loc != NULL) { setlocale(LC_ALL, loc); INTON; updatecharset(); return; } locdef = bltinlookup("LANG", 0); for (i = 0; locale_names[i] != NULL; i++) { loc = bltinlookup(locale_names[i], 0); if (loc == NULL) loc = locdef; if (loc != NULL) setlocale(locale_categories[i], loc); } INTON; updatecharset(); } /* * Undo the effect of bltinlocaleset(). */ void bltinunsetlocale(void) { struct strlist *lp; INTOFF; for (lp = cmdenviron ; lp ; lp = lp->next) { if (localevar(lp->text)) { setlocale(LC_ALL, ""); updatecharset(); return; } } INTON; } /* * Update the localeisutf8 flag. */ void updatecharset(void) { char *charset; charset = nl_langinfo(CODESET); localeisutf8 = !strcmp(charset, "UTF-8"); } void initcharset(void) { updatecharset(); initial_localeisutf8 = localeisutf8; } /* * Generate a list of exported variables. This routine is used to construct * the third argument to execve when executing a program. */ char ** environment(void) { int nenv; struct var **vpp; struct var *vp; char **env, **ep; nenv = 0; for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { for (vp = *vpp ; vp ; vp = vp->next) if (vp->flags & VEXPORT) nenv++; } ep = env = stalloc((nenv + 1) * sizeof *env); for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { for (vp = *vpp ; vp ; vp = vp->next) if (vp->flags & VEXPORT) *ep++ = vp->text; } *ep = NULL; return env; } static int var_compare(const void *a, const void *b) { const char *const *sa, *const *sb; sa = a; sb = b; /* * This compares two var=value strings which creates a different * order from what you would probably expect. POSIX is somewhat * ambiguous on what should be sorted exactly. */ return strcoll(*sa, *sb); } /* * Command to list all variables which are set. This is invoked from the * set command when it is called without any options or operands. */ int showvarscmd(int argc __unused, char **argv __unused) { struct var **vpp; struct var *vp; const char *s; const char **vars; int i, n; /* * POSIX requires us to sort the variables. */ n = 0; for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) { for (vp = *vpp; vp; vp = vp->next) { if (!(vp->flags & VUNSET)) n++; } } INTOFF; vars = ckmalloc(n * sizeof(*vars)); i = 0; for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) { for (vp = *vpp; vp; vp = vp->next) { if (!(vp->flags & VUNSET)) vars[i++] = vp->text; } } qsort(vars, n, sizeof(*vars), var_compare); for (i = 0; i < n; i++) { /* * Skip improper variable names so the output remains usable as * shell input. */ if (!isassignment(vars[i])) continue; s = strchr(vars[i], '='); s++; outbin(vars[i], s - vars[i], out1); out1qstr(s); out1c('\n'); } ckfree(vars); INTON; return 0; } /* * The export and readonly commands. */ int exportcmd(int argc __unused, char **argv) { struct var **vpp; struct var *vp; char **ap; char *name; char *p; char *cmdname; int ch, values; int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT; cmdname = argv[0]; values = 0; while ((ch = nextopt("p")) != '\0') { switch (ch) { case 'p': values = 1; break; } } if (values && *argptr != NULL) error("-p requires no arguments"); if (*argptr != NULL) { for (ap = argptr; (name = *ap) != NULL; ap++) { if ((p = strchr(name, '=')) != NULL) { p++; } else { vp = find_var(name, NULL, NULL); if (vp != NULL) { vp->flags |= flag; if ((vp->flags & VEXPORT) && localevar(vp->text)) { change_env(vp->text, 1); (void) setlocale(LC_ALL, ""); updatecharset(); } continue; } } setvar(name, p, flag); } } else { for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { for (vp = *vpp ; vp ; vp = vp->next) { if (vp->flags & flag) { if (values) { /* * Skip improper variable names * so the output remains usable * as shell input. */ if (!isassignment(vp->text)) continue; out1str(cmdname); out1c(' '); } if (values && !(vp->flags & VUNSET)) { outbin(vp->text, vp->name_len + 1, out1); out1qstr(vp->text + vp->name_len + 1); } else outbin(vp->text, vp->name_len, out1); out1c('\n'); } } } } return 0; } /* * The "local" command. */ int localcmd(int argc __unused, char **argv __unused) { char *name; if (! in_function()) error("Not in a function"); while ((name = *argptr++) != NULL) { mklocal(name); } return 0; } /* * Make a variable a local variable. When a variable is made local, it's * value and flags are saved in a localvar structure. The saved values * will be restored when the shell function returns. We handle the name * "-" as a special case. */ void mklocal(char *name) { struct localvar *lvp; struct var **vpp; struct var *vp; INTOFF; lvp = ckmalloc(sizeof (struct localvar)); if (name[0] == '-' && name[1] == '\0') { lvp->text = ckmalloc(sizeof optlist); memcpy(lvp->text, optlist, sizeof optlist); vp = NULL; } else { vp = find_var(name, &vpp, NULL); if (vp == NULL) { if (strchr(name, '=')) setvareq(savestr(name), VSTRFIXED | VNOLOCAL); else setvar(name, NULL, VSTRFIXED | VNOLOCAL); vp = *vpp; /* the new variable */ lvp->text = NULL; lvp->flags = VUNSET; } else { lvp->text = vp->text; lvp->flags = vp->flags; vp->flags |= VSTRFIXED|VTEXTFIXED; if (name[vp->name_len] == '=') setvareq(savestr(name), VNOLOCAL); } } lvp->vp = vp; lvp->next = localvars; localvars = lvp; INTON; } /* * Called after a function returns. */ void poplocalvars(void) { struct localvar *lvp; struct var *vp; while ((lvp = localvars) != NULL) { localvars = lvp->next; vp = lvp->vp; if (vp == NULL) { /* $- saved */ memcpy(optlist, lvp->text, sizeof optlist); ckfree(lvp->text); optschanged(); } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { (void)unsetvar(vp->text); } else { if ((vp->flags & VTEXTFIXED) == 0) ckfree(vp->text); vp->flags = lvp->flags; vp->text = lvp->text; } ckfree(lvp); } } int setvarcmd(int argc, char **argv) { if (argc <= 2) return unsetcmd(argc, argv); else if (argc == 3) setvar(argv[1], argv[2], 0); else error("too many arguments"); return 0; } /* * The unset builtin command. */ int unsetcmd(int argc __unused, char **argv __unused) { char **ap; int i; int flg_func = 0; int flg_var = 0; int ret = 0; while ((i = nextopt("vf")) != '\0') { if (i == 'f') flg_func = 1; else flg_var = 1; } if (flg_func == 0 && flg_var == 0) flg_var = 1; for (ap = argptr; *ap ; ap++) { if (flg_func) ret |= unsetfunc(*ap); if (flg_var) ret |= unsetvar(*ap); } return ret; } /* * Unset the specified variable. */ int unsetvar(const char *s) { struct var **vpp; struct var *vp; vp = find_var(s, &vpp, NULL); if (vp == NULL) return (0); if (vp->flags & VREADONLY) return (1); INTOFF; if (vp->text[vp->name_len + 1] != '\0') setvar(s, nullstr, 0); if ((vp->flags & VEXPORT) && localevar(vp->text)) { change_env(s, 0); setlocale(LC_ALL, ""); updatecharset(); } vp->flags &= ~VEXPORT; vp->flags |= VUNSET; if ((vp->flags & VSTRFIXED) == 0) { if ((vp->flags & VTEXTFIXED) == 0) ckfree(vp->text); *vpp = vp->next; ckfree(vp); } INTON; return (0); } /* * Returns true if the two strings specify the same varable. The first * variable name is terminated by '='; the second may be terminated by * either '=' or '\0'. */ static int varequal(const char *p, const char *q) { while (*p == *q++) { if (*p++ == '=') return 1; } if (*p == '=' && *(q - 1) == '\0') return 1; return 0; } /* * Search for a variable. * 'name' may be terminated by '=' or a NUL. * vppp is set to the pointer to vp, or the list head if vp isn't found * lenp is set to the number of charactets in 'name' */ static struct var * find_var(const char *name, struct var ***vppp, int *lenp) { unsigned int hashval; int len; struct var *vp, **vpp; const char *p = name; hashval = 0; while (*p && *p != '=') hashval = 2 * hashval + (unsigned char)*p++; len = p - name; if (lenp) *lenp = len; vpp = &vartab[hashval % VTABSIZE]; if (vppp) *vppp = vpp; for (vp = *vpp ; vp ; vpp = &vp->next, vp = *vpp) { if (vp->name_len != len) continue; if (memcmp(vp->text, name, len) != 0) continue; if (vppp) *vppp = vpp; return vp; } return NULL; }