diff options
author | June McEnroe <june@causal.agency> | 2020-03-09 04:00:31 -0400 |
---|---|---|
committer | June McEnroe <june@causal.agency> | 2020-03-09 04:00:31 -0400 |
commit | 40b33b6b04d81d2c6ca52175a83c31fa1bc91621 (patch) | |
tree | 8f56816b0766def94e119754c95fb2b1d12e2baf | |
parent | Remove 1sh sources (diff) | |
download | src-40b33b6b04d81d2c6ca52175a83c31fa1bc91621.tar.gz src-40b33b6b04d81d2c6ca52175a83c31fa1bc91621.zip |
Import /usr/src/bin/sh from FreeBSD 12.1-RELEASE
Diffstat (limited to '')
602 files changed, 29608 insertions, 0 deletions
diff --git a/bin/1sh/Makefile b/bin/1sh/Makefile new file mode 100644 index 00000000..619d5e24 --- /dev/null +++ b/bin/1sh/Makefile @@ -0,0 +1,72 @@ +# @(#)Makefile 8.4 (Berkeley) 5/5/95 +# $FreeBSD: releng/12.1/bin/sh/Makefile 338374 2018-08-29 16:59:19Z brd $ + +.include <src.opts.mk> + +CONFS= dot.profile profile +CONFSDIR_dot.profile= /root +CONFSNAME_dot.profile= .profile +PACKAGE=runtime +PROG= sh +INSTALLFLAGS= -S +SHSRCS= alias.c arith_yacc.c arith_yylex.c cd.c echo.c error.c eval.c \ + exec.c expand.c \ + histedit.c input.c jobs.c kill.c mail.c main.c memalloc.c miscbltin.c \ + mystring.c options.c output.c parser.c printf.c redir.c show.c \ + test.c trap.c var.c +GENSRCS= builtins.c nodes.c syntax.c +GENHDRS= builtins.h nodes.h syntax.h token.h +SRCS= ${SHSRCS} ${GENSRCS} ${GENHDRS} + +# MLINKS for Shell built in commands for which there are no userland +# utilities of the same name are handled with the associated manpage, +# builtin.1 in share/man/man1/. + +LIBADD= edit + +CFLAGS+=-DSHELL -I. -I${.CURDIR} +# for debug: +# DEBUG_FLAGS+= -g -DDEBUG=2 -fno-inline +WARNS?= 2 +WFORMAT=0 + +.PATH: ${.CURDIR}/bltin \ + ${.CURDIR:H}/kill \ + ${.CURDIR:H}/test \ + ${SRCTOP}/usr.bin/printf + +CLEANFILES+= mknodes mksyntax +CLEANFILES+= ${GENSRCS} ${GENHDRS} + +build-tools: mknodes mksyntax + +.ORDER: builtins.c builtins.h +builtins.h: .NOMETA +builtins.c builtins.h: mkbuiltins builtins.def + sh ${.CURDIR}/mkbuiltins ${.CURDIR} + +mknodes mksyntax: ${BUILD_TOOLS_META} + +.ORDER: nodes.c nodes.h +nodes.h: .NOMETA +nodes.c nodes.h: mknodes nodetypes nodes.c.pat + ${BTOOLSPATH:U.}/mknodes ${.CURDIR}/nodetypes ${.CURDIR}/nodes.c.pat + +.ORDER: syntax.c syntax.h +syntax.h: .NOMETA +syntax.c syntax.h: mksyntax + ${BTOOLSPATH:U.}/mksyntax + +token.h: mktokens + sh ${.CURDIR}/mktokens + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +beforeinstallconfig: + rm -f ${DESTDIR}/.profile + +afterinstallconfig: + ${INSTALL_LINK} ${TAG_ARGS} ${DESTDIR}/root/.profile ${DESTDIR}/.profile + +.include <bsd.prog.mk> diff --git a/bin/1sh/Makefile.depend b/bin/1sh/Makefile.depend new file mode 100644 index 00000000..4351f3a3 --- /dev/null +++ b/bin/1sh/Makefile.depend @@ -0,0 +1,19 @@ +# $FreeBSD: releng/12.1/bin/sh/Makefile.depend 325188 2017-10-31 00:07:04Z bdrewery $ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libedit \ + lib/ncurses/ncursesw \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/1sh/TOUR b/bin/1sh/TOUR new file mode 100644 index 00000000..3f196c9b --- /dev/null +++ b/bin/1sh/TOUR @@ -0,0 +1,301 @@ +# @(#)TOUR 8.1 (Berkeley) 5/31/93 +# $FreeBSD: releng/12.1/bin/sh/TOUR 317882 2017-05-06 13:28:42Z jilles $ + +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.def builtins.h builtins.c + mknodes nodetypes nodes.h nodes.c + mksyntax - syntax.h syntax.c + mktokens - token.h + +There are undoubtedly too many of these. + +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 Parameter expansion + CTLENDVAR End of parameter expansion + CTLBACKQ Command substitution + CTLBACKQ|CTLQUOTE Command substitution inside double quotes + CTLARI Arithmetic expansion + CTLENDARI End of arithmetic expansion + 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} + VSTRIMLEFT ${var#text} + VSTRIMLEFTMAX ${var##text} + VSTRIMRIGHT ${var%text} + VSTRIMRIGHTMAX ${var%%text} + VSLENGTH ${#var} + VSERROR delayed error + +In addition, the type field will have the VSQUOTE flag set if the +variable is enclosed in double quotes and the VSLINENO flag if +LINENO is being expanded (the parameter name is the decimal line +number). The parameter's name comes next, terminated by an equals +sign. If the type is not VSNORMAL (including when it is VSLENGTH), +then the text field in the substitution follows, terminated by a +CTLENDVAR byte. + +The type VSERROR is used to allow parsing bad substitutions like +${var[7]} and generate an error when they are expanded. + +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. + +Arithmetic expansion starts with CTLARI and ends with CTLENDARI. + +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 file name generation, +have the CTLESC characters removed during the variable and command +substitution phase. Words which are subject to file name +generation have the CTLESC characters removed 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: As the routine argstr generates words by parameter +expansion, command substitution and arithmetic expansion, it +performs word splitting on the result. As each word is output, +the routine expandmeta performs file name generation (if enabled). + +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.def, which is processed by the +mkbuiltins 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 non- +special 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 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 program is to be compiled +stand-alone. A similar approach is used for a few utilities from +bin and usr.bin. + +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 its 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. diff --git a/bin/1sh/alias.c b/bin/1sh/alias.c new file mode 100644 index 00000000..8f553cd1 --- /dev/null +++ b/bin/1sh/alias.c @@ -0,0 +1,256 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)alias.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/alias.c 317039 2017-04-16 22:10:02Z jilles $"); + +#include <stdlib.h> +#include "shell.h" +#include "output.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "options.h" +#include "builtins.h" + +#define ATABSIZE 39 + +static struct alias *atab[ATABSIZE]; +static int aliases; + +static void setalias(const char *, const char *); +static int unalias(const char *); +static struct alias **hashalias(const char *); + +static +void +setalias(const char *name, const char *val) +{ + struct alias *ap, **app; + + unalias(name); + app = hashalias(name); + INTOFF; + ap = ckmalloc(sizeof (struct alias)); + ap->name = savestr(name); + ap->val = savestr(val); + ap->flag = 0; + ap->next = *app; + *app = ap; + aliases++; + INTON; +} + +static void +freealias(struct alias *ap) +{ + ckfree(ap->name); + ckfree(ap->val); + ckfree(ap); +} + +static int +unalias(const char *name) +{ + struct alias *ap, **app; + + app = hashalias(name); + + for (ap = *app; ap; app = &(ap->next), ap = ap->next) { + if (equal(name, ap->name)) { + /* + * if the alias is currently in use (i.e. its + * buffer is being used by the input routine) we + * just null out the name instead of freeing it. + * We could clear it out later, but this situation + * is so rare that it hardly seems worth it. + */ + if (ap->flag & ALIASINUSE) + *ap->name = '\0'; + else { + INTOFF; + *app = ap->next; + freealias(ap); + INTON; + } + aliases--; + return (0); + } + } + + return (1); +} + +static void +rmaliases(void) +{ + struct alias *ap, **app; + int i; + + INTOFF; + for (i = 0; i < ATABSIZE; i++) { + app = &atab[i]; + while (*app) { + ap = *app; + if (ap->flag & ALIASINUSE) { + *ap->name = '\0'; + app = &(*app)->next; + } else { + *app = ap->next; + freealias(ap); + } + } + } + aliases = 0; + INTON; +} + +struct alias * +lookupalias(const char *name, int check) +{ + struct alias *ap; + + if (aliases == 0) + return (NULL); + for (ap = *hashalias(name); ap; ap = ap->next) { + if (equal(name, ap->name)) { + if (check && (ap->flag & ALIASINUSE)) + return (NULL); + return (ap); + } + } + + return (NULL); +} + +static int +comparealiases(const void *p1, const void *p2) +{ + const struct alias *const *a1 = p1; + const struct alias *const *a2 = p2; + + return strcmp((*a1)->name, (*a2)->name); +} + +static void +printalias(const struct alias *a) +{ + out1fmt("%s=", a->name); + out1qstr(a->val); + out1c('\n'); +} + +static void +printaliases(void) +{ + int i, j; + struct alias **sorted, *ap; + + INTOFF; + sorted = ckmalloc(aliases * sizeof(*sorted)); + j = 0; + for (i = 0; i < ATABSIZE; i++) + for (ap = atab[i]; ap; ap = ap->next) + if (*ap->name != '\0') + sorted[j++] = ap; + qsort(sorted, aliases, sizeof(*sorted), comparealiases); + for (i = 0; i < aliases; i++) { + printalias(sorted[i]); + if (int_pending()) + break; + } + ckfree(sorted); + INTON; +} + +int +aliascmd(int argc __unused, char **argv __unused) +{ + char *n, *v; + int ret = 0; + struct alias *ap; + + nextopt(""); + + if (*argptr == NULL) { + printaliases(); + return (0); + } + while ((n = *argptr++) != NULL) { + if ((v = strchr(n+1, '=')) == NULL) /* n+1: funny ksh stuff */ + if ((ap = lookupalias(n, 0)) == NULL) { + warning("%s: not found", n); + ret = 1; + } else + printalias(ap); + else { + *v++ = '\0'; + setalias(n, v); + } + } + + return (ret); +} + +int +unaliascmd(int argc __unused, char **argv __unused) +{ + int i; + + while ((i = nextopt("a")) != '\0') { + if (i == 'a') { + rmaliases(); + return (0); + } + } + for (i = 0; *argptr; argptr++) + i |= unalias(*argptr); + + return (i); +} + +static struct alias ** +hashalias(const char *p) +{ + unsigned int hashval; + + hashval = (unsigned char)*p << 4; + while (*p) + hashval+= *p++; + return &atab[hashval % ATABSIZE]; +} diff --git a/bin/1sh/alias.h b/bin/1sh/alias.h new file mode 100644 index 00000000..625d1a50 --- /dev/null +++ b/bin/1sh/alias.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)alias.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/alias.h 314436 2017-02-28 23:42:47Z imp $ + */ + +#define ALIASINUSE 1 + +struct alias { + struct alias *next; + char *name; + char *val; + int flag; +}; + +struct alias *lookupalias(const char *, int); diff --git a/bin/1sh/arith.h b/bin/1sh/arith.h new file mode 100644 index 00000000..ecaad30f --- /dev/null +++ b/bin/1sh/arith.h @@ -0,0 +1,37 @@ +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)arith.h 1.1 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/arith.h 315511 2017-03-18 20:41:07Z jilles $ + */ + +#include "shell.h" + +#define DIGITS(var) (3 + (2 + CHAR_BIT * sizeof((var))) / 3) + +arith_t arith(const char *); diff --git a/bin/1sh/arith_yacc.c b/bin/1sh/arith_yacc.c new file mode 100644 index 00000000..6a96465a --- /dev/null +++ b/bin/1sh/arith_yacc.c @@ -0,0 +1,381 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/arith_yacc.c 345117 2019-03-13 21:53:10Z jilles $"); + +#include <limits.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include "arith.h" +#include "arith_yacc.h" +#include "expand.h" +#include "shell.h" +#include "error.h" +#include "memalloc.h" +#include "output.h" +#include "options.h" +#include "var.h" + +#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +static const char *arith_startbuf; + +const char *arith_buf; +union yystype yylval; + +static int last_token; + +#define ARITH_PRECEDENCE(op, prec) [op - ARITH_BINOP_MIN] = prec + +static const char prec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = { + ARITH_PRECEDENCE(ARITH_MUL, 0), + ARITH_PRECEDENCE(ARITH_DIV, 0), + ARITH_PRECEDENCE(ARITH_REM, 0), + ARITH_PRECEDENCE(ARITH_ADD, 1), + ARITH_PRECEDENCE(ARITH_SUB, 1), + ARITH_PRECEDENCE(ARITH_LSHIFT, 2), + ARITH_PRECEDENCE(ARITH_RSHIFT, 2), + ARITH_PRECEDENCE(ARITH_LT, 3), + ARITH_PRECEDENCE(ARITH_LE, 3), + ARITH_PRECEDENCE(ARITH_GT, 3), + ARITH_PRECEDENCE(ARITH_GE, 3), + ARITH_PRECEDENCE(ARITH_EQ, 4), + ARITH_PRECEDENCE(ARITH_NE, 4), + ARITH_PRECEDENCE(ARITH_BAND, 5), + ARITH_PRECEDENCE(ARITH_BXOR, 6), + ARITH_PRECEDENCE(ARITH_BOR, 7), +}; + +#define ARITH_MAX_PREC 8 + +int letcmd(int, char **); + +static __dead2 void yyerror(const char *s) +{ + error("arithmetic expression: %s: \"%s\"", s, arith_startbuf); + /* NOTREACHED */ +} + +static arith_t arith_lookupvarint(char *varname) +{ + const char *str; + char *p; + arith_t result; + + str = lookupvar(varname); + if (uflag && str == NULL) + yyerror("variable not set"); + if (str == NULL || *str == '\0') + str = "0"; + errno = 0; + result = strtoarith_t(str, &p); + if (errno != 0 || *p != '\0') + yyerror("variable conversion error"); + return result; +} + +static inline int arith_prec(int op) +{ + return prec[op - ARITH_BINOP_MIN]; +} + +static inline int higher_prec(int op1, int op2) +{ + return arith_prec(op1) < arith_prec(op2); +} + +static arith_t do_binop(int op, arith_t a, arith_t b) +{ + + switch (op) { + default: + case ARITH_REM: + case ARITH_DIV: + if (!b) + yyerror("division by zero"); + if (a == ARITH_MIN && b == -1) + yyerror("divide error"); + return op == ARITH_REM ? a % b : a / b; + case ARITH_MUL: + return (uintmax_t)a * (uintmax_t)b; + case ARITH_ADD: + return (uintmax_t)a + (uintmax_t)b; + case ARITH_SUB: + return (uintmax_t)a - (uintmax_t)b; + case ARITH_LSHIFT: + return (uintmax_t)a << (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_RSHIFT: + return a >> (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_LT: + return a < b; + case ARITH_LE: + return a <= b; + case ARITH_GT: + return a > b; + case ARITH_GE: + return a >= b; + case ARITH_EQ: + return a == b; + case ARITH_NE: + return a != b; + case ARITH_BAND: + return a & b; + case ARITH_BXOR: + return a ^ b; + case ARITH_BOR: + return a | b; + } +} + +static arith_t assignment(int var, int noeval); + +static arith_t primary(int token, union yystype *val, int op, int noeval) +{ + arith_t result; + +again: + switch (token) { + case ARITH_LPAREN: + result = assignment(op, noeval); + if (last_token != ARITH_RPAREN) + yyerror("expecting ')'"); + last_token = yylex(); + return result; + case ARITH_NUM: + last_token = op; + return val->val; + case ARITH_VAR: + last_token = op; + return noeval ? val->val : arith_lookupvarint(val->name); + case ARITH_ADD: + token = op; + *val = yylval; + op = yylex(); + goto again; + case ARITH_SUB: + *val = yylval; + return -primary(op, val, yylex(), noeval); + case ARITH_NOT: + *val = yylval; + return !primary(op, val, yylex(), noeval); + case ARITH_BNOT: + *val = yylval; + return ~primary(op, val, yylex(), noeval); + default: + yyerror("expecting primary"); + } +} + +static arith_t binop2(arith_t a, int op, int precedence, int noeval) +{ + for (;;) { + union yystype val; + arith_t b; + int op2; + int token; + + token = yylex(); + val = yylval; + + b = primary(token, &val, yylex(), noeval); + + op2 = last_token; + if (op2 >= ARITH_BINOP_MIN && op2 < ARITH_BINOP_MAX && + higher_prec(op2, op)) { + b = binop2(b, op2, arith_prec(op), noeval); + op2 = last_token; + } + + a = noeval ? b : do_binop(op, a, b); + + if (op2 < ARITH_BINOP_MIN || op2 >= ARITH_BINOP_MAX || + arith_prec(op2) >= precedence) + return a; + + op = op2; + } +} + +static arith_t binop(int token, union yystype *val, int op, int noeval) +{ + arith_t a = primary(token, val, op, noeval); + + op = last_token; + if (op < ARITH_BINOP_MIN || op >= ARITH_BINOP_MAX) + return a; + + return binop2(a, op, ARITH_MAX_PREC, noeval); +} + +static arith_t and(int token, union yystype *val, int op, int noeval) +{ + arith_t a = binop(token, val, op, noeval); + arith_t b; + + op = last_token; + if (op != ARITH_AND) + return a; + + token = yylex(); + *val = yylval; + + b = and(token, val, yylex(), noeval | !a); + + return a && b; +} + +static arith_t or(int token, union yystype *val, int op, int noeval) +{ + arith_t a = and(token, val, op, noeval); + arith_t b; + + op = last_token; + if (op != ARITH_OR) + return a; + + token = yylex(); + *val = yylval; + + b = or(token, val, yylex(), noeval | !!a); + + return a || b; +} + +static arith_t cond(int token, union yystype *val, int op, int noeval) +{ + arith_t a = or(token, val, op, noeval); + arith_t b; + arith_t c; + + if (last_token != ARITH_QMARK) + return a; + + b = assignment(yylex(), noeval | !a); + + if (last_token != ARITH_COLON) + yyerror("expecting ':'"); + + token = yylex(); + *val = yylval; + + c = cond(token, val, yylex(), noeval | !!a); + + return a ? b : c; +} + +static arith_t assignment(int var, int noeval) +{ + union yystype val = yylval; + int op = yylex(); + arith_t result; + char sresult[DIGITS(result) + 1]; + + if (var != ARITH_VAR) + return cond(var, &val, op, noeval); + + if (op != ARITH_ASS && (op < ARITH_ASS_MIN || op >= ARITH_ASS_MAX)) + return cond(var, &val, op, noeval); + + result = assignment(yylex(), noeval); + if (noeval) + return result; + + if (op != ARITH_ASS) + result = do_binop(op - 11, arith_lookupvarint(val.name), result); + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, result); + setvar(val.name, sresult, 0); + return result; +} + +arith_t arith(const char *s) +{ + struct stackmark smark; + arith_t result; + + setstackmark(&smark); + + arith_buf = arith_startbuf = s; + + result = assignment(yylex(), 0); + + if (last_token) + yyerror("expecting EOF"); + + popstackmark(&smark); + + return result; +} + +/* + * The exp(1) builtin. + */ +int +letcmd(int argc, char **argv) +{ + const char *p; + char *concat; + char **ap; + arith_t i; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + /* + * Concatenate arguments. + */ + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + while (*p) + STPUTC(*p++, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + } else + p = ""; + + i = arith(p); + + out1fmt(ARITH_FORMAT_STR "\n", i); + return !i; +} diff --git a/bin/1sh/arith_yacc.h b/bin/1sh/arith_yacc.h new file mode 100644 index 00000000..433481bb --- /dev/null +++ b/bin/1sh/arith_yacc.h @@ -0,0 +1,94 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * $FreeBSD: releng/12.1/bin/sh/arith_yacc.h 345117 2019-03-13 21:53:10Z jilles $ + */ + +#define ARITH_ASS 1 + +#define ARITH_OR 2 +#define ARITH_AND 3 +#define ARITH_BAD 4 +#define ARITH_NUM 5 +#define ARITH_VAR 6 +#define ARITH_NOT 7 + +#define ARITH_BINOP_MIN 8 +#define ARITH_LE 8 +#define ARITH_GE 9 +#define ARITH_LT 10 +#define ARITH_GT 11 +#define ARITH_EQ 12 +#define ARITH_REM 13 +#define ARITH_BAND 14 +#define ARITH_LSHIFT 15 +#define ARITH_RSHIFT 16 +#define ARITH_MUL 17 +#define ARITH_ADD 18 +#define ARITH_BOR 19 +#define ARITH_SUB 20 +#define ARITH_BXOR 21 +#define ARITH_DIV 22 +#define ARITH_NE 23 +#define ARITH_BINOP_MAX 24 + +#define ARITH_ASS_MIN 24 +#define ARITH_REMASS 24 +#define ARITH_BANDASS 25 +#define ARITH_LSHIFTASS 26 +#define ARITH_RSHIFTASS 27 +#define ARITH_MULASS 28 +#define ARITH_ADDASS 29 +#define ARITH_BORASS 30 +#define ARITH_SUBASS 31 +#define ARITH_BXORASS 32 +#define ARITH_DIVASS 33 +#define ARITH_ASS_MAX 34 + +#define ARITH_LPAREN 34 +#define ARITH_RPAREN 35 +#define ARITH_BNOT 36 +#define ARITH_QMARK 37 +#define ARITH_COLON 38 + +extern const char *arith_buf; + +union yystype { + arith_t val; + char *name; +}; + +extern union yystype yylval; + +arith_t strtoarith_t(const char *restrict nptr, char **restrict endptr); +int yylex(void); diff --git a/bin/1sh/arith_yylex.c b/bin/1sh/arith_yylex.c new file mode 100644 index 00000000..9238c925 --- /dev/null +++ b/bin/1sh/arith_yylex.c @@ -0,0 +1,276 @@ +/*- + * Copyright (c) 2002 + * Herbert Xu. + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/arith_yylex.c 345117 2019-03-13 21:53:10Z jilles $"); + +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include "shell.h" +#include "arith_yacc.h" +#include "expand.h" +#include "error.h" +#include "memalloc.h" +#include "parser.h" +#include "syntax.h" + +#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +arith_t +strtoarith_t(const char *restrict nptr, char **restrict endptr) +{ + arith_t val; + + while (isspace((unsigned char)*nptr)) + nptr++; + switch (*nptr) { + case '-': + return strtoimax(nptr, endptr, 0); + case '0': + return (arith_t)strtoumax(nptr, endptr, 0); + default: + val = (arith_t)strtoumax(nptr, endptr, 0); + if (val >= 0) + return val; + else if (val == ARITH_MIN) { + errno = ERANGE; + return ARITH_MIN; + } else { + errno = ERANGE; + return ARITH_MAX; + } + } +} + +int +yylex(void) +{ + int value; + const char *buf = arith_buf; + char *end; + const char *p; + + for (;;) { + value = *buf; + switch (value) { + case ' ': + case '\t': + case '\n': + buf++; + continue; + default: + return ARITH_BAD; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + yylval.val = strtoarith_t(buf, &end); + arith_buf = end; + return ARITH_NUM; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + p = buf; + while (buf++, is_in_name(*buf)) + ; + yylval.name = stalloc(buf - p + 1); + memcpy(yylval.name, p, buf - p); + yylval.name[buf - p] = '\0'; + value = ARITH_VAR; + goto out; + case '=': + value += ARITH_ASS - '='; +checkeq: + buf++; +checkeqcur: + if (*buf != '=') + goto out; + value += 11; + break; + case '>': + switch (*++buf) { + case '=': + value += ARITH_GE - '>'; + break; + case '>': + value += ARITH_RSHIFT - '>'; + goto checkeq; + default: + value += ARITH_GT - '>'; + goto out; + } + break; + case '<': + switch (*++buf) { + case '=': + value += ARITH_LE - '<'; + break; + case '<': + value += ARITH_LSHIFT - '<'; + goto checkeq; + default: + value += ARITH_LT - '<'; + goto out; + } + break; + case '|': + if (*++buf != '|') { + value += ARITH_BOR - '|'; + goto checkeqcur; + } + value += ARITH_OR - '|'; + break; + case '&': + if (*++buf != '&') { + value += ARITH_BAND - '&'; + goto checkeqcur; + } + value += ARITH_AND - '&'; + break; + case '!': + if (*++buf != '=') { + value += ARITH_NOT - '!'; + goto out; + } + value += ARITH_NE - '!'; + break; + case 0: + goto out; + case '(': + value += ARITH_LPAREN - '('; + break; + case ')': + value += ARITH_RPAREN - ')'; + break; + case '*': + value += ARITH_MUL - '*'; + goto checkeq; + case '/': + value += ARITH_DIV - '/'; + goto checkeq; + case '%': + value += ARITH_REM - '%'; + goto checkeq; + case '+': + if (buf[1] == '+') + return ARITH_BAD; + value += ARITH_ADD - '+'; + goto checkeq; + case '-': + if (buf[1] == '-') + return ARITH_BAD; + value += ARITH_SUB - '-'; + goto checkeq; + case '~': + value += ARITH_BNOT - '~'; + break; + case '^': + value += ARITH_BXOR - '^'; + goto checkeq; + case '?': + value += ARITH_QMARK - '?'; + break; + case ':': + value += ARITH_COLON - ':'; + break; + } + break; + } + + buf++; +out: + arith_buf = buf; + return value; +} diff --git a/bin/1sh/bltin/bltin.h b/bin/1sh/bltin/bltin.h new file mode 100644 index 00000000..7121fee0 --- /dev/null +++ b/bin/1sh/bltin/bltin.h @@ -0,0 +1,81 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)bltin.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/bltin/bltin.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* + * This file is included by programs which are optionally built into the + * shell. If SHELL is defined, we try to map the standard UNIX library + * routines to ash routines using defines. + */ + +#include "../shell.h" +#include "../mystring.h" +#ifdef SHELL +#include "../error.h" +#include "../output.h" +#include "builtins.h" +#define FILE struct output +#undef stdout +#define stdout out1 +#undef stderr +#define stderr out2 +#define printf out1fmt +#undef putc +#define putc(c, file) outc(c, file) +#undef putchar +#define putchar(c) out1c(c) +#define fprintf outfmt +#define fputs outstr +#define fwrite(ptr, size, nmemb, file) outbin(ptr, (size) * (nmemb), file) +#define fflush flushout +#define INITARGS(argv) +#define warnx warning +#define warn(fmt, ...) warning(fmt ": %s", __VA_ARGS__, strerror(errno)) +#define errx(exitstatus, ...) error(__VA_ARGS__) + +#else +#undef NULL +#include <stdio.h> +#undef main +#define INITARGS(argv) if ((commandname = argv[0]) == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else +#endif + +#include <unistd.h> + +pointer stalloc(int); +int killjob(const char *, int); + +extern char *commandname; diff --git a/bin/1sh/bltin/echo.c b/bin/1sh/bltin/echo.c new file mode 100644 index 00000000..2613955b --- /dev/null +++ b/bin/1sh/bltin/echo.c @@ -0,0 +1,109 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)echo.c 8.2 (Berkeley) 5/4/95 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/bltin/echo.c 326025 2017-11-20 19:49:47Z pfg $"); + +/* + * Echo command. + */ + +#define main echocmd + +#include "bltin.h" + +/* #define eflag 1 */ + +int +main(int argc, char *argv[]) +{ + char **ap; + char *p; + char c; + int count; + int nflag = 0; +#ifndef eflag + int eflag = 0; +#endif + + ap = argv; + if (argc) + ap++; + if ((p = *ap) != NULL) { + if (equal(p, "-n")) { + nflag++; + ap++; + } else if (equal(p, "-e")) { +#ifndef eflag + eflag++; +#endif + ap++; + } + } + while ((p = *ap++) != NULL) { + while ((c = *p++) != '\0') { + if (c == '\\' && eflag) { + switch (*p++) { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'c': return 0; /* exit */ + case 'e': c = '\033'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\\': break; /* c = '\\' */ + case '0': + c = 0; + count = 3; + while (--count >= 0 && (unsigned)(*p - '0') < 8) + c = (c << 3) + (*p++ - '0'); + break; + default: + p--; + break; + } + } + putchar(c); + } + if (*ap) + putchar(' '); + } + if (! nflag) + putchar('\n'); + return 0; +} diff --git a/bin/1sh/builtins.def b/bin/1sh/builtins.def new file mode 100644 index 00000000..5986a5a6 --- /dev/null +++ b/bin/1sh/builtins.def @@ -0,0 +1,96 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. 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. +# +# @(#)builtins.def 8.4 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/builtins.def 319576 2017-06-04 21:02:48Z bdrewery $ + +# +# This file lists all the builtin commands. The first column is the name +# of a C routine. +# The -j flag specifies that this command is to be excluded from systems +# without job control. +# The -h flag specifies that this command is to be excluded from systems +# based on the NO_HISTORY compile-time symbol. +# The -n flag specifies that this command can safely be run in the same +# process when it is the only command in a command substitution. Some +# commands have special logic defined in safe_builtin(). +# The -s flag specifies that this is a POSIX 'special built-in' command. +# The rest of the line specifies the command name or names used to run the +# command. The entry for bltincmd, which is run when the user does not specify +# a command, must come first. +# +# NOTE: bltincmd must come first! + +bltincmd -n builtin +aliascmd alias +bgcmd -j bg +bindcmd bind +breakcmd -s break -s continue +cdcmd cd chdir +commandcmd -n command +dotcmd -s . +echocmd -n echo +evalcmd -s eval +execcmd -s exec +exitcmd -s exit +letcmd let +exportcmd -s export -s readonly +#exprcmd expr +falsecmd -n false +fgcmd -j fg +freebsd_wordexpcmd freebsd_wordexp +getoptscmd getopts +hashcmd hash +histcmd -h fc +jobidcmd -n jobid +jobscmd -n jobs +killcmd -n kill +localcmd local +printfcmd -n printf +pwdcmd -n pwd +readcmd read +returncmd -s return +setcmd -s set +setvarcmd setvar +shiftcmd -s shift +testcmd -n test [ +timescmd -n -s times +trapcmd -s trap +truecmd -n -s : true +typecmd -n type +ulimitcmd ulimit +umaskcmd umask +unaliascmd unalias +unsetcmd -s unset +waitcmd wait +wordexpcmd wordexp diff --git a/bin/1sh/cd.c b/bin/1sh/cd.c new file mode 100644 index 00000000..de1350dc --- /dev/null +++ b/bin/1sh/cd.c @@ -0,0 +1,430 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)cd.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/cd.c 336320 2018-07-15 21:55:17Z jilles $"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +/* + * The cd and pwd commands. + */ + +#include "shell.h" +#include "var.h" +#include "nodes.h" /* for jobs.h */ +#include "jobs.h" +#include "options.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "exec.h" +#include "redir.h" +#include "mystring.h" +#include "show.h" +#include "cd.h" +#include "builtins.h" + +static int cdlogical(char *); +static int cdphysical(char *); +static int docd(char *, int, int); +static char *getcomponent(char **); +static char *findcwd(char *); +static void updatepwd(char *); +static char *getpwd(void); +static char *getpwd2(void); + +static char *curdir = NULL; /* current working directory */ + +int +cdcmd(int argc __unused, char **argv __unused) +{ + const char *dest; + const char *path; + char *p; + struct stat statb; + int ch, phys, print = 0, getcwderr = 0; + int rc; + int errno1 = ENOENT; + + phys = Pflag; + while ((ch = nextopt("eLP")) != '\0') { + switch (ch) { + case 'e': + getcwderr = 1; + break; + case 'L': + phys = 0; + break; + case 'P': + phys = 1; + break; + } + } + + if (*argptr != NULL && argptr[1] != NULL) + error("too many arguments"); + + if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME", 1)) == NULL) + error("HOME not set"); + if (*dest == '\0') + dest = "."; + if (dest[0] == '-' && dest[1] == '\0') { + dest = bltinlookup("OLDPWD", 1); + if (dest == NULL) + error("OLDPWD not set"); + print = 1; + } + if (dest[0] == '/' || + (dest[0] == '.' && (dest[1] == '/' || dest[1] == '\0')) || + (dest[0] == '.' && dest[1] == '.' && (dest[2] == '/' || dest[2] == '\0')) || + (path = bltinlookup("CDPATH", 1)) == NULL) + path = ""; + while ((p = padvance(&path, NULL, dest)) != NULL) { + if (stat(p, &statb) < 0) { + if (errno != ENOENT) + errno1 = errno; + } else if (!S_ISDIR(statb.st_mode)) + errno1 = ENOTDIR; + else { + if (!print) { + /* + * XXX - rethink + */ + if (p[0] == '.' && p[1] == '/' && p[2] != '\0') + print = strcmp(p + 2, dest); + else + print = strcmp(p, dest); + } + rc = docd(p, print, phys); + if (rc >= 0) + return getcwderr ? rc : 0; + if (errno != ENOENT) + errno1 = errno; + } + } + error("%s: %s", dest, strerror(errno1)); + /*NOTREACHED*/ + return 0; +} + + +/* + * Actually change the directory. In an interactive shell, print the + * directory name if "print" is nonzero. + */ +static int +docd(char *dest, int print, int phys) +{ + int rc; + + TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys)); + + /* If logical cd fails, fall back to physical. */ + if ((phys || (rc = cdlogical(dest)) < 0) && (rc = cdphysical(dest)) < 0) + return (-1); + + if (print && iflag && curdir) { + out1fmt("%s\n", curdir); + /* + * Ignore write errors to preserve the invariant that the + * current directory is changed iff the exit status is 0 + * (or 1 if -e was given and the full pathname could not be + * determined). + */ + flushout(out1); + outclearerror(out1); + } + + return (rc); +} + +static int +cdlogical(char *dest) +{ + char *p; + char *q; + char *component; + char *path; + struct stat statb; + int first; + int badstat; + + /* + * Check each component of the path. If we find a symlink or + * something we can't stat, clear curdir to force a getcwd() + * next time we get the value of the current directory. + */ + badstat = 0; + path = stsavestr(dest); + STARTSTACKSTR(p); + if (*dest == '/') { + STPUTC('/', p); + path++; + } + first = 1; + while ((q = getcomponent(&path)) != NULL) { + if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0')) + continue; + if (! first) + STPUTC('/', p); + first = 0; + component = q; + STPUTS(q, p); + if (equal(component, "..")) + continue; + STACKSTRNUL(p); + if (lstat(stackblock(), &statb) < 0) { + badstat = 1; + break; + } + } + + INTOFF; + if ((p = findcwd(badstat ? NULL : dest)) == NULL || chdir(p) < 0) { + INTON; + return (-1); + } + updatepwd(p); + INTON; + return (0); +} + +static int +cdphysical(char *dest) +{ + char *p; + int rc = 0; + + INTOFF; + if (chdir(dest) < 0) { + INTON; + return (-1); + } + p = findcwd(NULL); + if (p == NULL) { + warning("warning: failed to get name of current directory"); + rc = 1; + } + updatepwd(p); + INTON; + return (rc); +} + +/* + * Get the next component of the path name pointed to by *path. + * This routine overwrites *path and the string pointed to by it. + */ +static char * +getcomponent(char **path) +{ + char *p; + char *start; + + if ((p = *path) == NULL) + return NULL; + start = *path; + while (*p != '/' && *p != '\0') + p++; + if (*p == '\0') { + *path = NULL; + } else { + *p++ = '\0'; + *path = p; + } + return start; +} + + +static char * +findcwd(char *dir) +{ + char *new; + char *p; + char *path; + + /* + * If our argument is NULL, we don't know the current directory + * any more because we traversed a symbolic link or something + * we couldn't stat(). + */ + if (dir == NULL || curdir == NULL) + return getpwd2(); + path = stsavestr(dir); + STARTSTACKSTR(new); + if (*dir != '/') { + STPUTS(curdir, new); + if (STTOPC(new) == '/') + STUNPUTC(new); + } + while ((p = getcomponent(&path)) != NULL) { + if (equal(p, "..")) { + while (new > stackblock() && (STUNPUTC(new), *new) != '/'); + } else if (*p != '\0' && ! equal(p, ".")) { + STPUTC('/', new); + STPUTS(p, new); + } + } + if (new == stackblock()) + STPUTC('/', new); + STACKSTRNUL(new); + return stackblock(); +} + +/* + * Update curdir (the name of the current directory) in response to a + * cd command. We also call hashcd to let the routines in exec.c know + * that the current directory has changed. + */ +static void +updatepwd(char *dir) +{ + char *prevdir; + + hashcd(); /* update command hash table */ + + setvar("PWD", dir, VEXPORT); + setvar("OLDPWD", curdir, VEXPORT); + prevdir = curdir; + curdir = dir ? savestr(dir) : NULL; + ckfree(prevdir); +} + +int +pwdcmd(int argc __unused, char **argv __unused) +{ + char *p; + int ch, phys; + + phys = Pflag; + while ((ch = nextopt("LP")) != '\0') { + switch (ch) { + case 'L': + phys = 0; + break; + case 'P': + phys = 1; + break; + } + } + + if (*argptr != NULL) + error("too many arguments"); + + if (!phys && getpwd()) { + out1str(curdir); + out1c('\n'); + } else { + if ((p = getpwd2()) == NULL) + error(".: %s", strerror(errno)); + out1str(p); + out1c('\n'); + } + + return 0; +} + +/* + * Get the current directory and cache the result in curdir. + */ +static char * +getpwd(void) +{ + char *p; + + if (curdir) + return curdir; + + p = getpwd2(); + if (p != NULL) + curdir = savestr(p); + + return curdir; +} + +#define MAXPWD 256 + +/* + * Return the current directory. + */ +static char * +getpwd2(void) +{ + char *pwd; + int i; + + for (i = MAXPWD;; i *= 2) { + pwd = stalloc(i); + if (getcwd(pwd, i) != NULL) + return pwd; + stunalloc(pwd); + if (errno != ERANGE) + break; + } + + return NULL; +} + +/* + * Initialize PWD in a new shell. + * If the shell is interactive, we need to warn if this fails. + */ +void +pwd_init(int warn) +{ + char *pwd; + struct stat stdot, stpwd; + + pwd = lookupvar("PWD"); + if (pwd && *pwd == '/' && stat(".", &stdot) != -1 && + stat(pwd, &stpwd) != -1 && + stdot.st_dev == stpwd.st_dev && + stdot.st_ino == stpwd.st_ino) { + if (curdir) + ckfree(curdir); + curdir = savestr(pwd); + } + if (getpwd() == NULL && warn) + out2fmt_flush("sh: cannot determine working directory\n"); + setvar("PWD", curdir, VEXPORT); +} diff --git a/bin/1sh/cd.h b/bin/1sh/cd.h new file mode 100644 index 00000000..dfa5c3e5 --- /dev/null +++ b/bin/1sh/cd.h @@ -0,0 +1,32 @@ +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * $FreeBSD: releng/12.1/bin/sh/cd.h 314436 2017-02-28 23:42:47Z imp $ + */ + +void pwd_init(int); diff --git a/bin/1sh/dot.profile b/bin/1sh/dot.profile new file mode 100644 index 00000000..da1daa5e --- /dev/null +++ b/bin/1sh/dot.profile @@ -0,0 +1,16 @@ +# $FreeBSD: releng/12.1/bin/sh/dot.profile 338374 2018-08-29 16:59:19Z brd $ +# +PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:~/bin +export PATH +HOME=/root +export HOME +TERM=${TERM:-xterm} +export TERM +PAGER=less +export PAGER + +# Query terminal size; useful for serial lines. +if [ -x /usr/bin/resizewin ] ; then /usr/bin/resizewin -z ; fi + +# Uncomment to display a random cookie on each login. +# if [ -x /usr/bin/fortune ] ; then /usr/bin/fortune -s ; fi diff --git a/bin/1sh/error.c b/bin/1sh/error.c new file mode 100644 index 00000000..463cc7b6 --- /dev/null +++ b/bin/1sh/error.c @@ -0,0 +1,199 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)error.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/error.c 314436 2017-02-28 23:42:47Z imp $"); + +/* + * Errors and exceptions. + */ + +#include "shell.h" +#include "eval.h" +#include "main.h" +#include "options.h" +#include "output.h" +#include "error.h" +#include "nodes.h" /* show.h needs nodes.h */ +#include "show.h" +#include "trap.h" +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + + +/* + * Code to handle exceptions in C. + */ + +struct jmploc *handler; +volatile sig_atomic_t exception; +volatile sig_atomic_t suppressint; +volatile sig_atomic_t intpending; + + +static void exverror(int, const char *, va_list) __printf0like(2, 0) __dead2; + +/* + * Called to raise an exception. Since C doesn't include exceptions, we + * just do a longjmp to the exception handler. The type of exception is + * stored in the global variable "exception". + * + * Interrupts are disabled; they should be reenabled when the exception is + * caught. + */ + +void +exraise(int e) +{ + INTOFF; + if (handler == NULL) + abort(); + exception = e; + longjmp(handler->loc, 1); +} + + +/* + * Called from trap.c when a SIGINT is received and not suppressed, or when + * an interrupt is pending and interrupts are re-enabled using INTON. + * (If the user specifies that SIGINT is to be trapped or ignored using the + * trap builtin, then this routine is not called.) Suppressint is nonzero + * when interrupts are held using the INTOFF macro. If SIGINTs are not + * suppressed and the shell is not a root shell, then we want to be + * terminated if we get here, as if we were terminated directly by a SIGINT. + * Arrange for this here. + */ + +void +onint(void) +{ + sigset_t sigs; + + intpending = 0; + sigemptyset(&sigs); + sigprocmask(SIG_SETMASK, &sigs, NULL); + + /* + * This doesn't seem to be needed, since main() emits a newline. + */ +#if 0 + if (tcgetpgrp(0) == getpid()) + write(STDERR_FILENO, "\n", 1); +#endif + if (rootshell && iflag) + exraise(EXINT); + else { + signal(SIGINT, SIG_DFL); + kill(getpid(), SIGINT); + _exit(128 + SIGINT); + } +} + + +static void +vwarning(const char *msg, va_list ap) +{ + if (commandname) + outfmt(out2, "%s: ", commandname); + else if (arg0) + outfmt(out2, "%s: ", arg0); + doformat(out2, msg, ap); + out2fmt_flush("\n"); +} + + +void +warning(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vwarning(msg, ap); + va_end(ap); +} + + +/* + * Exverror is called to raise the error exception. If the first argument + * is not NULL then error prints an error message using printf style + * formatting. It then raises the error exception. + */ +static void +exverror(int cond, const char *msg, va_list ap) +{ + /* + * An interrupt trumps an error. Certain places catch error + * exceptions or transform them to a plain nonzero exit code + * in child processes, and if an error exception can be handled, + * an interrupt can be handled as well. + * + * exraise() will disable interrupts for the exception handler. + */ + FORCEINTON; + +#ifdef DEBUG + if (msg) + TRACE(("exverror(%d, \"%s\") pid=%d\n", cond, msg, getpid())); + else + TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid())); +#endif + if (msg) + vwarning(msg, ap); + flushall(); + exraise(cond); +} + + +void +error(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + exverror(EXERROR, msg, ap); + va_end(ap); +} + + +void +exerror(int cond, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + exverror(cond, msg, ap); + va_end(ap); +} diff --git a/bin/1sh/error.h b/bin/1sh/error.h new file mode 100644 index 00000000..b6d6b413 --- /dev/null +++ b/bin/1sh/error.h @@ -0,0 +1,95 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)error.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/error.h 319591 2017-06-04 21:58:02Z jilles $ + */ + +/* + * We enclose jmp_buf in a structure so that we can declare pointers to + * jump locations. The global variable handler contains the location to + * jump to when an exception occurs, and the global variable exception + * contains a code identifying the exception. To implement nested + * exception handlers, the user should save the value of handler on entry + * to an inner scope, set handler to point to a jmploc structure for the + * inner scope, and restore handler on exit from the scope. + */ + +#include <setjmp.h> +#include <signal.h> + +struct jmploc { + jmp_buf loc; +}; + +extern struct jmploc *handler; +extern volatile sig_atomic_t exception; + +/* exceptions */ +#define EXINT 0 /* SIGINT received */ +#define EXERROR 1 /* a generic error */ +#define EXEXEC 2 /* command execution failed */ +#define EXEXIT 3 /* call exitshell(exitstatus) */ + + +/* + * These macros allow the user to suspend the handling of interrupt signals + * over a period of time. This is similar to SIGHOLD to or sigblock, but + * much more efficient and portable. (But hacking the kernel is so much + * more fun than worrying about efficiency and portability. :-)) + */ + +extern volatile sig_atomic_t suppressint; +extern volatile sig_atomic_t intpending; + +#define INTOFF suppressint++ +#define INTON { if (--suppressint == 0 && intpending) onint(); } +#define is_int_on() suppressint +#define SETINTON(s) do { suppressint = (s); if (suppressint == 0 && intpending) onint(); } while (0) +#define FORCEINTON {suppressint = 0; if (intpending) onint();} +#define SET_PENDING_INT intpending = 1 +#define CLEAR_PENDING_INT intpending = 0 +#define int_pending() intpending + +void exraise(int) __dead2; +void onint(void) __dead2; +void warning(const char *, ...) __printflike(1, 2); +void error(const char *, ...) __printf0like(1, 2) __dead2; +void exerror(int, const char *, ...) __printf0like(2, 3) __dead2; + + +/* + * BSD setjmp saves the signal mask, which violates ANSI C and takes time, + * so we use _setjmp instead. + */ + +#define setjmp(jmploc) _setjmp(jmploc) +#define longjmp(jmploc, val) _longjmp(jmploc, val) diff --git a/bin/1sh/eval.c b/bin/1sh/eval.c new file mode 100644 index 00000000..67a40691 --- /dev/null +++ b/bin/1sh/eval.c @@ -0,0 +1,1381 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/eval.c 327212 2017-12-26 16:23:18Z jilles $"); + +#include <paths.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/resource.h> +#include <errno.h> + +/* + * Evaluate a command. + */ + +#include "shell.h" +#include "nodes.h" +#include "syntax.h" +#include "expand.h" +#include "parser.h" +#include "jobs.h" +#include "eval.h" +#include "builtins.h" +#include "options.h" +#include "exec.h" +#include "redir.h" +#include "input.h" +#include "output.h" +#include "trap.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "show.h" +#include "mystring.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + + +int evalskip; /* set if we are skipping commands */ +int skipcount; /* number of levels to skip */ +static int loopnest; /* current loop nesting level */ +int funcnest; /* depth of function calls */ +static int builtin_flags; /* evalcommand flags for builtins */ + + +char *commandname; +struct arglist *cmdenviron; +int exitstatus; /* exit status of last command */ +int oexitstatus; /* saved exit status */ + + +static void evalloop(union node *, int); +static void evalfor(union node *, int); +static union node *evalcase(union node *); +static void evalsubshell(union node *, int); +static void evalredir(union node *, int); +static void exphere(union node *, struct arglist *); +static void expredir(union node *); +static void evalpipe(union node *); +static int is_valid_fast_cmdsubst(union node *n); +static void evalcommand(union node *, int, struct backcmd *); +static void prehash(union node *); + + +/* + * Called to reset things after an exception. + */ + +void +reseteval(void) +{ + evalskip = 0; + loopnest = 0; +} + + +/* + * The eval command. + */ + +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 (;;) { + STPUTS(p, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + evalstring(p, builtin_flags); + } else + exitstatus = 0; + return exitstatus; +} + + +/* + * Execute a command or commands contained in a string. + */ + +void +evalstring(const char *s, int flags) +{ + union node *n; + struct stackmark smark; + int flags_exit; + int any; + + flags_exit = flags & EV_EXIT; + flags &= ~EV_EXIT; + any = 0; + setstackmark(&smark); + setinputstring(s, 1); + while ((n = parsecmd(0)) != NEOF) { + if (n != NULL && !nflag) { + if (flags_exit && preadateof()) + evaltree(n, flags | EV_EXIT); + else + evaltree(n, flags); + any = 1; + if (evalskip) + break; + } + popstackmark(&smark); + setstackmark(&smark); + } + popfile(); + popstackmark(&smark); + if (!any) + exitstatus = 0; + if (flags_exit) + exraise(EXEXIT); +} + + +/* + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. + */ + +void +evaltree(union node *n, int flags) +{ + int do_etest; + union node *next; + struct stackmark smark; + + setstackmark(&smark); + do_etest = 0; + if (n == NULL) { + TRACE(("evaltree(NULL) called\n")); + exitstatus = 0; + goto out; + } + do { + next = NULL; +#ifndef NO_HISTORY + displayhist = 1; /* show history substitutions done with fc */ +#endif + TRACE(("evaltree(%p: %d) called\n", (void *)n, n->type)); + switch (n->type) { + case NSEMI: + evaltree(n->nbinary.ch1, flags & ~EV_EXIT); + if (evalskip) + goto out; + next = n->nbinary.ch2; + break; + case NAND: + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip || exitstatus != 0) { + goto out; + } + next = n->nbinary.ch2; + break; + case NOR: + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip || exitstatus == 0) + goto out; + next = n->nbinary.ch2; + break; + case NREDIR: + evalredir(n, flags); + break; + case NSUBSHELL: + evalsubshell(n, flags); + do_etest = !(flags & EV_TESTED); + break; + case NBACKGND: + evalsubshell(n, flags); + break; + case NIF: { + evaltree(n->nif.test, EV_TESTED); + if (evalskip) + goto out; + if (exitstatus == 0) + next = n->nif.ifpart; + else if (n->nif.elsepart) + next = n->nif.elsepart; + else + exitstatus = 0; + break; + } + case NWHILE: + case NUNTIL: + evalloop(n, flags & ~EV_EXIT); + break; + case NFOR: + evalfor(n, flags & ~EV_EXIT); + break; + case NCASE: + next = evalcase(n); + break; + case NCLIST: + next = n->nclist.body; + break; + case NCLISTFALLTHRU: + if (n->nclist.body) { + evaltree(n->nclist.body, flags & ~EV_EXIT); + if (evalskip) + goto out; + } + next = n->nclist.next; + break; + case NDEFUN: + defun(n->narg.text, n->narg.next); + exitstatus = 0; + break; + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + if (evalskip) + goto out; + exitstatus = !exitstatus; + break; + + case NPIPE: + evalpipe(n); + do_etest = !(flags & EV_TESTED); + break; + case NCMD: + evalcommand(n, flags, (struct backcmd *)NULL); + do_etest = !(flags & EV_TESTED); + break; + default: + out1fmt("Node type = %d\n", n->type); + flushout(&output); + break; + } + n = next; + popstackmark(&smark); + setstackmark(&smark); + } while (n != NULL); +out: + popstackmark(&smark); + if (pendingsig) + dotrap(); + if (eflag && exitstatus != 0 && do_etest) + exitshell(exitstatus); + if (flags & EV_EXIT) + exraise(EXEXIT); +} + + +static void +evalloop(union node *n, int flags) +{ + int status; + + loopnest++; + status = 0; + for (;;) { + if (!evalskip) + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + if (evalskip == SKIPRETURN) + status = exitstatus; + break; + } + if (n->type == NWHILE) { + if (exitstatus != 0) + break; + } else { + if (exitstatus == 0) + break; + } + evaltree(n->nbinary.ch2, flags); + status = exitstatus; + } + loopnest--; + exitstatus = status; +} + + + +static void +evalfor(union node *n, int flags) +{ + struct arglist arglist; + union node *argp; + int i; + int status; + + emptyarglist(&arglist); + for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { + oexitstatus = exitstatus; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + + loopnest++; + status = 0; + for (i = 0; i < arglist.count; i++) { + setvar(n->nfor.var, arglist.args[i], 0); + evaltree(n->nfor.body, flags); + status = exitstatus; + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + } + loopnest--; + exitstatus = status; +} + + +/* + * Evaluate a case statement, returning the selected tree. + * + * The exit status needs care to get right. + */ + +static union node * +evalcase(union node *n) +{ + union node *cp; + union node *patp; + struct arglist arglist; + + emptyarglist(&arglist); + oexitstatus = exitstatus; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) { + if (casematch(patp, arglist.args[0])) { + while (cp->nclist.next && + cp->type == NCLISTFALLTHRU && + cp->nclist.body == NULL) + cp = cp->nclist.next; + if (cp->nclist.next && + cp->type == NCLISTFALLTHRU) + return (cp); + if (cp->nclist.body == NULL) + exitstatus = 0; + return (cp->nclist.body); + } + } + } + exitstatus = 0; + return (NULL); +} + + + +/* + * Kick off a subshell to evaluate a tree. + */ + +static void +evalsubshell(union node *n, int flags) +{ + struct job *jp; + int backgnd = (n->type == NBACKGND); + + oexitstatus = exitstatus; + expredir(n->nredir.redirect); + if ((!backgnd && flags & EV_EXIT && !have_traps()) || + forkshell(jp = makejob(n, 1), n, backgnd) == 0) { + if (backgnd) + flags &=~ EV_TESTED; + redirect(n->nredir.redirect, 0); + evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */ + } else if (! backgnd) { + INTOFF; + exitstatus = waitforjob(jp, (int *)NULL); + INTON; + } else + exitstatus = 0; +} + + +/* + * Evaluate a redirected compound command. + */ + +static void +evalredir(union node *n, int flags) +{ + struct jmploc jmploc; + struct jmploc *savehandler; + volatile int in_redirect = 1; + + oexitstatus = exitstatus; + expredir(n->nredir.redirect); + savehandler = handler; + if (setjmp(jmploc.loc)) { + int e; + + handler = savehandler; + e = exception; + popredir(); + if (e == EXERROR || e == EXEXEC) { + if (in_redirect) { + exitstatus = 2; + FORCEINTON; + return; + } + } + longjmp(handler->loc, 1); + } else { + INTOFF; + handler = &jmploc; + redirect(n->nredir.redirect, REDIR_PUSH); + in_redirect = 0; + INTON; + evaltree(n->nredir.n, flags); + } + INTOFF; + handler = savehandler; + popredir(); + INTON; +} + + +static void +exphere(union node *redir, struct arglist *fn) +{ + struct jmploc jmploc; + struct jmploc *savehandler; + struct localvar *savelocalvars; + int need_longjmp = 0; + unsigned char saveoptreset; + + redir->nhere.expdoc = ""; + savelocalvars = localvars; + localvars = NULL; + saveoptreset = shellparam.reset; + forcelocal++; + savehandler = handler; + if (setjmp(jmploc.loc)) + need_longjmp = exception != EXERROR && exception != EXEXEC; + else { + handler = &jmploc; + expandarg(redir->nhere.doc, fn, 0); + redir->nhere.expdoc = fn->args[0]; + INTOFF; + } + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + if (need_longjmp) + longjmp(handler->loc, 1); + INTON; +} + + +/* + * Compute the names of the files in a redirection list. + */ + +static void +expredir(union node *n) +{ + union node *redir; + + for (redir = n ; redir ; redir = redir->nfile.next) { + struct arglist fn; + emptyarglist(&fn); + switch (redir->type) { + case NFROM: + case NTO: + case NFROMTO: + case NAPPEND: + case NCLOBBER: + expandarg(redir->nfile.fname, &fn, EXP_TILDE); + redir->nfile.expfname = fn.args[0]; + break; + case NFROMFD: + case NTOFD: + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_TILDE); + fixredir(redir, fn.args[0], 1); + } + break; + case NXHERE: + exphere(redir, &fn); + break; + } + } +} + + + +/* + * Evaluate a pipeline. All the processes in the pipeline are children + * of the process creating the pipeline. (This differs from some versions + * of the shell, which make the last process in a pipeline the parent + * of all the rest.) + */ + +static void +evalpipe(union node *n) +{ + struct job *jp; + struct nodelist *lp; + int pipelen; + int prevfd; + int pip[2]; + + TRACE(("evalpipe(%p) called\n", (void *)n)); + pipelen = 0; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) + pipelen++; + INTOFF; + jp = makejob(n, pipelen); + prevfd = -1; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + prehash(lp->n); + pip[1] = -1; + if (lp->next) { + if (pipe(pip) < 0) { + if (prevfd >= 0) + close(prevfd); + error("Pipe call failed: %s", strerror(errno)); + } + } + if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { + INTON; + if (prevfd > 0) { + dup2(prevfd, 0); + close(prevfd); + } + if (pip[1] >= 0) { + if (!(prevfd >= 0 && pip[0] == 0)) + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + } + evaltree(lp->n, EV_EXIT); + } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + if (pip[1] != -1) + close(pip[1]); + } + INTON; + if (n->npipe.backgnd == 0) { + INTOFF; + exitstatus = waitforjob(jp, (int *)NULL); + TRACE(("evalpipe: job done exit status %d\n", exitstatus)); + INTON; + } else + exitstatus = 0; +} + + + +static int +is_valid_fast_cmdsubst(union node *n) +{ + + return (n->type == NCMD); +} + +/* + * Execute a command inside back quotes. If it's a builtin command, we + * want to save its output in a block obtained from malloc. Otherwise + * we fork off a subprocess and get the output of the command via a pipe. + * Should be called with interrupts off. + */ + +void +evalbackcmd(union node *n, struct backcmd *result) +{ + int pip[2]; + struct job *jp; + struct stackmark smark; + struct jmploc jmploc; + struct jmploc *savehandler; + struct localvar *savelocalvars; + unsigned char saveoptreset; + + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + if (n == NULL) { + exitstatus = 0; + return; + } + setstackmark(&smark); + exitstatus = oexitstatus; + if (is_valid_fast_cmdsubst(n)) { + savelocalvars = localvars; + localvars = NULL; + saveoptreset = shellparam.reset; + forcelocal++; + savehandler = handler; + if (setjmp(jmploc.loc)) { + if (exception == EXERROR || exception == EXEXEC) + exitstatus = 2; + else if (exception != 0) { + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + longjmp(handler->loc, 1); + } + } else { + handler = &jmploc; + evalcommand(n, EV_BACKCMD, result); + } + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + } else { + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + jp = makejob(n, 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCEINTON; + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + evaltree(n, EV_EXIT); + } + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + } + popstackmark(&smark); + TRACE(("evalbackcmd done: fd=%d buf=%p nleft=%d jp=%p\n", + result->fd, result->buf, result->nleft, result->jp)); +} + +static int +mustexpandto(const char *argtext, const char *mask) +{ + for (;;) { + if (*argtext == CTLQUOTEMARK || *argtext == CTLQUOTEEND) { + argtext++; + continue; + } + if (*argtext == CTLESC) + argtext++; + else if (BASESYNTAX[(int)*argtext] == CCTL) + return (0); + if (*argtext != *mask) + return (0); + if (*argtext == '\0') + return (1); + argtext++; + mask++; + } +} + +static int +isdeclarationcmd(struct narg *arg) +{ + int have_command = 0; + + if (arg == NULL) + return (0); + while (mustexpandto(arg->text, "command")) { + have_command = 1; + arg = &arg->next->narg; + if (arg == NULL) + return (0); + /* + * To also allow "command -p" and "command --" as part of + * a declaration command, add code here. + * We do not do this, as ksh does not do it either and it + * is not required by POSIX. + */ + } + return (mustexpandto(arg->text, "export") || + mustexpandto(arg->text, "readonly") || + (mustexpandto(arg->text, "local") && + (have_command || !isfunc("local")))); +} + +static void +xtracecommand(struct arglist *varlist, int argc, char **argv) +{ + char sep = 0; + const char *text, *p, *ps4; + int i; + + ps4 = expandstr(ps4val()); + out2str(ps4 != NULL ? ps4 : ps4val()); + for (i = 0; i < varlist->count; i++) { + text = varlist->args[i]; + if (sep != 0) + out2c(' '); + p = strchr(text, '='); + if (p != NULL) { + p++; + outbin(text, p - text, out2); + out2qstr(p); + } else + out2qstr(text); + sep = ' '; + } + for (i = 0; i < argc; i++) { + text = argv[i]; + if (sep != 0) + out2c(' '); + out2qstr(text); + sep = ' '; + } + out2c('\n'); + flushout(&errout); +} + +/* + * Check if a builtin can safely be executed in the same process, + * even though it should be in a subshell (command substitution). + * Note that jobid, jobs, times and trap can show information not + * available in a child process; this is deliberate. + * The arguments should already have been expanded. + */ +static int +safe_builtin(int idx, int argc, char **argv) +{ + /* Generated from builtins.def. */ + if (safe_builtin_always(idx)) + return (1); + if (idx == EXPORTCMD || idx == TRAPCMD || idx == ULIMITCMD || + idx == UMASKCMD) + return (argc <= 1 || (argc == 2 && argv[1][0] == '-')); + if (idx == SETCMD) + return (argc <= 1 || (argc == 2 && (argv[1][0] == '-' || + argv[1][0] == '+') && argv[1][1] == 'o' && + argv[1][2] == '\0')); + return (0); +} + +/* + * Execute a simple command. + * Note: This may or may not return if (flags & EV_EXIT). + */ + +static void +evalcommand(union node *cmd, int flags, struct backcmd *backcmd) +{ + union node *argp; + struct arglist arglist; + struct arglist varlist; + char **argv; + int argc; + char **envp; + int varflag; + int mode; + int pip[2]; + struct cmdentry cmdentry; + struct job *jp; + struct jmploc jmploc; + struct jmploc *savehandler; + char *savecmdname; + struct shparam saveparam; + struct localvar *savelocalvars; + struct parsefile *savetopfile; + volatile int e; + char *lastarg; + int signaled; + int do_clearcmdentry; + const char *path = pathval(); + int i; + + /* First expand the arguments. */ + TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags)); + emptyarglist(&arglist); + emptyarglist(&varlist); + varflag = 1; + jp = NULL; + do_clearcmdentry = 0; + oexitstatus = exitstatus; + exitstatus = 0; + /* Add one slot at the beginning for tryexec(). */ + appendarglist(&arglist, nullstr); + for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { + if (varflag && isassignment(argp->narg.text)) { + expandarg(argp, varflag == 1 ? &varlist : &arglist, + EXP_VARTILDE); + continue; + } else if (varflag == 1) + varflag = isdeclarationcmd(&argp->narg) ? 2 : 0; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + appendarglist(&arglist, nullstr); + expredir(cmd->ncmd.redirect); + argc = arglist.count - 2; + argv = &arglist.args[1]; + + argv[argc] = NULL; + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = argv[argc - 1]; + + /* Print the command if xflag is set. */ + if (xflag) + xtracecommand(&varlist, argc, argv); + + /* Now locate the command. */ + if (argc == 0) { + /* Variable assignment(s) without command */ + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.index = BLTINCMD; + cmdentry.special = 0; + } else { + static const char PATH[] = "PATH="; + int cmd_flags = 0, bltinonly = 0; + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + for (i = 0; i < varlist.count; i++) + if (strncmp(varlist.args[i], PATH, sizeof(PATH) - 1) == 0) { + path = varlist.args[i] + sizeof(PATH) - 1; + /* + * On `PATH=... command`, we need to make + * sure that the command isn't using the + * non-updated hash table of the outer PATH + * setting and we need to make sure that + * the hash table isn't filled with items + * from the temporary setting. + * + * It would be better to forbit using and + * updating the table while this command + * runs, by the command finding mechanism + * is heavily integrated with hash handling, + * so we just delete the hash before and after + * the command runs. Partly deleting like + * changepatch() does doesn't seem worth the + * bookinging effort, since most such runs add + * directories in front of the new PATH. + */ + clearcmdentry(); + do_clearcmdentry = 1; + } + + for (;;) { + if (bltinonly) { + cmdentry.u.index = find_builtin(*argv, &cmdentry.special); + if (cmdentry.u.index < 0) { + cmdentry.u.index = BLTINCMD; + argv--; + argc++; + break; + } + } else + find_command(argv[0], &cmdentry, cmd_flags, path); + /* implement the bltin and command builtins here */ + if (cmdentry.cmdtype != CMDBUILTIN) + break; + if (cmdentry.u.index == BLTINCMD) { + if (argc == 1) + break; + argv++; + argc--; + bltinonly = 1; + } else if (cmdentry.u.index == COMMANDCMD) { + if (argc == 1) + break; + if (!strcmp(argv[1], "-p")) { + if (argc == 2) + break; + if (argv[2][0] == '-') { + if (strcmp(argv[2], "--")) + break; + if (argc == 3) + break; + argv += 3; + argc -= 3; + } else { + argv += 2; + argc -= 2; + } + path = _PATH_STDPATH; + clearcmdentry(); + do_clearcmdentry = 1; + } else if (!strcmp(argv[1], "--")) { + if (argc == 2) + break; + argv += 2; + argc -= 2; + } else if (argv[1][0] == '-') + break; + else { + argv++; + argc--; + } + cmd_flags |= DO_NOFUNC; + bltinonly = 0; + } else + break; + } + /* + * Special builtins lose their special properties when + * called via 'command'. + */ + if (cmd_flags & DO_NOFUNC) + cmdentry.special = 0; + } + + /* Fork off a child process if necessary. */ + if (((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN) + && ((flags & EV_EXIT) == 0 || have_traps())) + || ((flags & EV_BACKCMD) != 0 + && (cmdentry.cmdtype != CMDBUILTIN || + !safe_builtin(cmdentry.u.index, argc, argv)))) { + jp = makejob(cmd, 1); + mode = FORK_FG; + if (flags & EV_BACKCMD) { + mode = FORK_NOJOB; + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + } + if (cmdentry.cmdtype == CMDNORMAL && + cmd->ncmd.redirect == NULL && + varlist.count == 0 && + (mode == FORK_FG || mode == FORK_NOJOB) && + !disvforkset() && !iflag && !mflag) { + vforkexecshell(jp, argv, environment(), path, + cmdentry.u.index, flags & EV_BACKCMD ? pip : NULL); + goto parent; + } + if (forkshell(jp, cmd, mode) != 0) + goto parent; /* at end of routine */ + if (flags & EV_BACKCMD) { + FORCEINTON; + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + flags &= ~EV_BACKCMD; + } + flags |= EV_EXIT; + } + + /* This is the child process if a fork occurred. */ + /* Execute the command. */ + if (cmdentry.cmdtype == CMDFUNCTION) { +#ifdef DEBUG + trputs("Shell function: "); trargs(argv); +#endif + saveparam = shellparam; + shellparam.malloc = 0; + shellparam.reset = 1; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; + shellparam.optp = NULL; + shellparam.optnext = NULL; + INTOFF; + savelocalvars = localvars; + localvars = NULL; + reffunc(cmdentry.u.func); + savehandler = handler; + if (setjmp(jmploc.loc)) { + popredir(); + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + funcnest--; + handler = savehandler; + longjmp(handler->loc, 1); + } + handler = &jmploc; + funcnest++; + redirect(cmd->ncmd.redirect, REDIR_PUSH); + INTON; + for (i = 0; i < varlist.count; i++) + mklocal(varlist.args[i]); + exitstatus = oexitstatus; + evaltree(getfuncnode(cmdentry.u.func), + flags & (EV_TESTED | EV_EXIT)); + INTOFF; + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + handler = savehandler; + funcnest--; + popredir(); + INTON; + if (evalskip == SKIPRETURN) { + evalskip = 0; + skipcount = 0; + } + if (jp) + exitshell(exitstatus); + } else if (cmdentry.cmdtype == CMDBUILTIN) { +#ifdef DEBUG + trputs("builtin command: "); trargs(argv); +#endif + mode = (cmdentry.u.index == EXECCMD)? 0 : REDIR_PUSH; + if (flags == EV_BACKCMD) { + memout.nextc = memout.buf; + mode |= REDIR_BACKQ; + } + savecmdname = commandname; + savetopfile = getcurrentfile(); + cmdenviron = &varlist; + e = -1; + savehandler = handler; + if (setjmp(jmploc.loc)) { + e = exception; + if (e == EXINT) + exitstatus = SIGINT+128; + else if (e != EXEXIT) + exitstatus = 2; + goto cmddone; + } + handler = &jmploc; + redirect(cmd->ncmd.redirect, mode); + outclearerror(out1); + /* + * If there is no command word, redirection errors should + * not be fatal but assignment errors should. + */ + if (argc == 0) + cmdentry.special = 1; + listsetvar(cmdenviron, cmdentry.special ? 0 : VNOSET); + if (argc > 0) + bltinsetlocale(); + commandname = argv[0]; + argptr = argv + 1; + nextopt_optptr = NULL; /* initialize nextopt */ + builtin_flags = flags; + exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv); + flushall(); + if (outiserror(out1)) { + warning("write error on stdout"); + if (exitstatus == 0 || exitstatus == 1) + exitstatus = 2; + } +cmddone: + if (argc > 0) + bltinunsetlocale(); + cmdenviron = NULL; + out1 = &output; + out2 = &errout; + freestdout(); + handler = savehandler; + commandname = savecmdname; + if (jp) + exitshell(exitstatus); + if (flags == EV_BACKCMD) { + backcmd->buf = memout.buf; + backcmd->nleft = memout.buf != NULL ? + memout.nextc - memout.buf : 0; + memout.buf = NULL; + memout.nextc = NULL; + memout.bufend = NULL; + memout.bufsize = 64; + } + if (cmdentry.u.index != EXECCMD) + popredir(); + if (e != -1) { + if ((e != EXERROR && e != EXEXEC) + || cmdentry.special) + exraise(e); + popfilesupto(savetopfile); + if (flags != EV_BACKCMD) + FORCEINTON; + } + } else { +#ifdef DEBUG + trputs("normal command: "); trargs(argv); +#endif + redirect(cmd->ncmd.redirect, 0); + for (i = 0; i < varlist.count; i++) + setvareq(varlist.args[i], VEXPORT|VSTACK); + envp = environment(); + shellexec(argv, envp, path, cmdentry.u.index); + /*NOTREACHED*/ + } + goto out; + +parent: /* parent process gets here (if we forked) */ + if (mode == FORK_FG) { /* argument to fork */ + INTOFF; + exitstatus = waitforjob(jp, &signaled); + INTON; + if (iflag && loopnest > 0 && signaled) { + evalskip = SKIPBREAK; + skipcount = loopnest; + } + } else if (mode == FORK_NOJOB) { + backcmd->fd = pip[0]; + close(pip[1]); + backcmd->jp = jp; + } + +out: + if (lastarg) + setvar("_", lastarg, 0); + if (do_clearcmdentry) + clearcmdentry(); +} + + + +/* + * Search for a command. This is called before we fork so that the + * location of the command will be available in the parent as well as + * the child. The check for "goodname" is an overly conservative + * check that the name will not be subject to expansion. + */ + +static void +prehash(union node *n) +{ + struct cmdentry entry; + + if (n && n->type == NCMD && n->ncmd.args) + if (goodname(n->ncmd.args->narg.text)) + find_command(n->ncmd.args->narg.text, &entry, 0, + pathval()); +} + + + +/* + * Builtin commands. Builtin commands whose functions are closely + * tied to evaluation are implemented here. + */ + +/* + * No command given, a bltin command with no arguments, or a bltin command + * with an invalid name. + */ + +int +bltincmd(int argc, char **argv) +{ + if (argc > 1) { + out2fmt_flush("%s: not found\n", argv[1]); + return 127; + } + /* + * Preserve exitstatus of a previous possible command substitution + * as POSIX mandates + */ + return exitstatus; +} + + +/* + * Handle break and continue commands. Break, continue, and return are + * all handled by setting the evalskip flag. The evaluation routines + * above all check this flag, and if it is set they start skipping + * commands rather than executing them. The variable skipcount is + * the number of loops to break/continue, or the number of function + * levels to return. (The latter is always 1.) It should probably + * be an error to break out of more loops than exist, but it isn't + * in the standard shell so we don't make it one here. + */ + +int +breakcmd(int argc, char **argv) +{ + long n; + char *end; + + if (argc > 1) { + /* Allow arbitrarily large numbers. */ + n = strtol(argv[1], &end, 10); + if (!is_digit(argv[1][0]) || *end != '\0') + error("Illegal number: %s", argv[1]); + } else + n = 1; + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; + skipcount = n; + } + return 0; +} + +/* + * The `command' command. + */ +int +commandcmd(int argc __unused, char **argv __unused) +{ + const char *path; + int ch; + int cmd = -1; + + path = bltinlookup("PATH", 1); + + while ((ch = nextopt("pvV")) != '\0') { + switch (ch) { + case 'p': + path = _PATH_STDPATH; + break; + case 'v': + cmd = TYPECMD_SMALLV; + break; + case 'V': + cmd = TYPECMD_BIGV; + break; + } + } + + if (cmd != -1) { + if (*argptr == NULL || argptr[1] != NULL) + error("wrong number of arguments"); + return typecmd_impl(2, argptr - 1, cmd, path); + } + if (*argptr != NULL) + error("commandcmd bad call"); + + /* + * Do nothing successfully if no command was specified; + * ksh also does this. + */ + return 0; +} + + +/* + * The return command. + */ + +int +returncmd(int argc, char **argv) +{ + int ret = argc > 1 ? number(argv[1]) : oexitstatus; + + evalskip = SKIPRETURN; + skipcount = 1; + return ret; +} + + +int +falsecmd(int argc __unused, char **argv __unused) +{ + return 1; +} + + +int +truecmd(int argc __unused, char **argv __unused) +{ + return 0; +} + + +int +execcmd(int argc, char **argv) +{ + int i; + + /* + * Because we have historically not supported any options, + * only treat "--" specially. + */ + if (argc > 1 && strcmp(argv[1], "--") == 0) + argc--, argv++; + if (argc > 1) { + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + for (i = 0; i < cmdenviron->count; i++) + setvareq(cmdenviron->args[i], VEXPORT|VSTACK); + shellexec(argv + 1, environment(), pathval(), 0); + + } + return 0; +} + + +int +timescmd(int argc __unused, char **argv __unused) +{ + struct rusage ru; + long shumins, shsmins, chumins, chsmins; + double shusecs, shssecs, chusecs, chssecs; + + if (getrusage(RUSAGE_SELF, &ru) < 0) + return 1; + shumins = ru.ru_utime.tv_sec / 60; + shusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; + shsmins = ru.ru_stime.tv_sec / 60; + shssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; + if (getrusage(RUSAGE_CHILDREN, &ru) < 0) + return 1; + chumins = ru.ru_utime.tv_sec / 60; + chusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; + chsmins = ru.ru_stime.tv_sec / 60; + chssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; + out1fmt("%ldm%.3fs %ldm%.3fs\n%ldm%.3fs %ldm%.3fs\n", shumins, + shusecs, shsmins, shssecs, chumins, chusecs, chsmins, chssecs); + return 0; +} diff --git a/bin/1sh/eval.h b/bin/1sh/eval.h new file mode 100644 index 00000000..d413ccd5 --- /dev/null +++ b/bin/1sh/eval.h @@ -0,0 +1,70 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)eval.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/eval.h 314436 2017-02-28 23:42:47Z imp $ + */ + +extern char *commandname; /* currently executing command */ +extern int exitstatus; /* exit status of last command */ +extern int oexitstatus; /* saved exit status */ +extern struct arglist *cmdenviron; /* environment for builtin command */ + + +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 */ +}; + +void reseteval(void); + +/* flags in argument to evaltree/evalstring */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ +#define EV_BACKCMD 04 /* command executing within back quotes */ + +void evalstring(const char *, int); +union node; /* BLETCH for ansi C */ +void evaltree(union node *, int); +void evalbackcmd(union node *, struct backcmd *); + +/* in_function returns nonzero if we are currently evaluating a function */ +#define in_function() funcnest +extern int funcnest; +extern int evalskip; +extern int skipcount; + +/* reasons for skipping commands (see comment on breakcmd routine) */ +#define SKIPBREAK 1 +#define SKIPCONT 2 +#define SKIPRETURN 3 diff --git a/bin/1sh/exec.c b/bin/1sh/exec.c new file mode 100644 index 00000000..3eea5afa --- /dev/null +++ b/bin/1sh/exec.c @@ -0,0 +1,784 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/exec.c 336320 2018-07-15 21:55:17Z jilles $"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <paths.h> +#include <stdlib.h> + +/* + * When commands are first encountered, they are entered in a hash table. + * This ensures that a full path search will not have to be done for them + * on each invocation. + * + * We should investigate converting to a linear search, even though that + * would make the command name "hash" a misnomer. + */ + +#include "shell.h" +#include "main.h" +#include "nodes.h" +#include "parser.h" +#include "redir.h" +#include "eval.h" +#include "exec.h" +#include "builtins.h" +#include "var.h" +#include "options.h" +#include "input.h" +#include "output.h" +#include "syntax.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "show.h" +#include "jobs.h" +#include "alias.h" + + +#define CMDTABLESIZE 31 /* should be prime */ + + + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + int special; /* flag for special builtin commands */ + signed char cmdtype; /* index identifying command */ + char cmdname[]; /* name of command */ +}; + + +static struct tblentry *cmdtable[CMDTABLESIZE]; +static int cmdtable_cd = 0; /* cmdtable contains cd-dependent entries */ +int exerrno = 0; /* Last exec error */ + + +static void tryexec(char *, char **, char **); +static void printentry(struct tblentry *, int); +static struct tblentry *cmdlookup(const char *, int); +static void delete_cmd_entry(void); +static void addcmdentry(const char *, struct cmdentry *); + + + +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + * + * The argv array may be changed and element argv[-1] should be writable. + */ + +void +shellexec(char **argv, char **envp, const char *path, int idx) +{ + char *cmdname; + const char *opt; + int e; + + if (strchr(argv[0], '/') != NULL) { + tryexec(argv[0], argv, envp); + e = errno; + } else { + e = ENOENT; + while ((cmdname = padvance(&path, &opt, argv[0])) != NULL) { + if (--idx < 0 && opt == NULL) { + tryexec(cmdname, argv, envp); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + if (e == ENOEXEC) + break; + } + stunalloc(cmdname); + } + } + + /* Map to POSIX errors */ + if (e == ENOENT || e == ENOTDIR) { + exerrno = 127; + exerror(EXEXEC, "%s: not found", argv[0]); + } else { + exerrno = 126; + exerror(EXEXEC, "%s: %s", argv[0], strerror(e)); + } +} + + +static void +tryexec(char *cmd, char **argv, char **envp) +{ + int e, in; + ssize_t n; + char buf[256]; + + execve(cmd, argv, envp); + e = errno; + if (e == ENOEXEC) { + INTOFF; + in = open(cmd, O_RDONLY | O_NONBLOCK); + if (in != -1) { + n = pread(in, buf, sizeof buf, 0); + close(in); + if (n > 0 && memchr(buf, '\0', n) != NULL) { + errno = ENOEXEC; + return; + } + } + *argv = cmd; + *--argv = __DECONST(char *, _PATH_BSHELL); + execve(_PATH_BSHELL, argv, envp); + } + errno = e; +} + +/* + * 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 popt is not NULL, options + * are processed: if an option (indicated by a percent sign) appears in + * the path entry then *popt will be set to point to it; else *popt will be + * set to NULL. If popt is NULL, percent signs are not special. + */ + +char * +padvance(const char **path, const char **popt, const char *name) +{ + const char *p, *start; + char *q; + size_t len, namelen; + + if (*path == NULL) + return NULL; + start = *path; + if (popt != NULL) + for (p = start; *p && *p != ':' && *p != '%'; p++) + ; /* nothing */ + else + for (p = start; *p && *p != ':'; p++) + ; /* nothing */ + namelen = strlen(name); + len = p - start + namelen + 2; /* "2" is for '/' and '\0' */ + STARTSTACKSTR(q); + CHECKSTRSPACE(len, q); + if (p != start) { + memcpy(q, start, p - start); + q += p - start; + *q++ = '/'; + } + memcpy(q, name, namelen + 1); + if (popt != NULL) { + if (*p == '%') { + *popt = ++p; + while (*p && *p != ':') p++; + } else + *popt = NULL; + } + if (*p == ':') + *path = p + 1; + else + *path = NULL; + return stalloc(len); +} + + + +/*** Command hashing code ***/ + + +int +hashcmd(int argc __unused, char **argv __unused) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + int verbose; + struct cmdentry entry; + char *name; + int errors; + + errors = 0; + verbose = 0; + while ((c = nextopt("rv")) != '\0') { + if (c == 'r') { + clearcmdentry(); + } else if (c == 'v') { + verbose++; + } + } + if (*argptr == NULL) { + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL) + printentry(cmdp, verbose); + } + } + return 0; + } + while ((name = *argptr) != NULL) { + if ((cmdp = cmdlookup(name, 0)) != NULL + && cmdp->cmdtype == CMDNORMAL) + delete_cmd_entry(); + find_command(name, &entry, DO_ERR, pathval()); + if (entry.cmdtype == CMDUNKNOWN) + errors = 1; + else if (verbose) { + cmdp = cmdlookup(name, 0); + if (cmdp != NULL) + printentry(cmdp, verbose); + else { + outfmt(out2, "%s: not found\n", name); + errors = 1; + } + flushall(); + } + argptr++; + } + return errors; +} + + +static void +printentry(struct tblentry *cmdp, int verbose) +{ + int idx; + const char *path, *opt; + char *name; + + if (cmdp->cmdtype == CMDNORMAL) { + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, &opt, cmdp->cmdname); + stunalloc(name); + } while (--idx >= 0); + out1str(name); + } else if (cmdp->cmdtype == CMDBUILTIN) { + out1fmt("builtin %s", cmdp->cmdname); + } else if (cmdp->cmdtype == CMDFUNCTION) { + out1fmt("function %s", cmdp->cmdname); + if (verbose) { + INTOFF; + name = commandtext(getfuncnode(cmdp->param.func)); + out1c(' '); + out1str(name); + ckfree(name); + INTON; + } +#ifdef DEBUG + } else { + error("internal error: cmdtype %d", cmdp->cmdtype); +#endif + } + out1c('\n'); +} + + + +/* + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. + */ + +void +find_command(const char *name, struct cmdentry *entry, int act, + const char *path) +{ + struct tblentry *cmdp, loc_cmd; + int idx; + const char *opt; + char *fullname; + struct stat statb; + int e; + int i; + int spec; + int cd; + + /* If name contains a slash, don't use the hash table */ + if (strchr(name, '/') != NULL) { + entry->cmdtype = CMDNORMAL; + entry->u.index = 0; + entry->special = 0; + return; + } + + cd = 0; + + /* If name is in the table, we're done */ + if ((cmdp = cmdlookup(name, 0)) != NULL) { + if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC) + cmdp = NULL; + else + goto success; + } + + /* Check for builtin next */ + if ((i = find_builtin(name, &spec)) >= 0) { + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + cmdp = &loc_cmd; + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.index = i; + cmdp->special = spec; + INTON; + goto success; + } + + /* We have to search path. */ + + e = ENOENT; + idx = -1; + for (;(fullname = padvance(&path, &opt, name)) != NULL; + stunalloc(fullname)) { + idx++; + if (opt) { + if (strncmp(opt, "func", 4) == 0) { + /* handled below */ + } else { + continue; /* ignore unimplemented options */ + } + } + if (fullname[0] != '/') + cd = 1; + if (stat(fullname, &statb) < 0) { + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + continue; + } + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + continue; + if (opt) { /* this is a %func directory */ + readcmdfile(fullname); + if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION) + error("%s not defined in %s", name, fullname); + stunalloc(fullname); + goto success; + } +#ifdef notdef + if (statb.st_uid == geteuid()) { + if ((statb.st_mode & 0100) == 0) + goto loop; + } else if (statb.st_gid == getegid()) { + if ((statb.st_mode & 010) == 0) + goto loop; + } else { + if ((statb.st_mode & 01) == 0) + goto loop; + } +#endif + TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); + INTOFF; + stunalloc(fullname); + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + cmdp = &loc_cmd; + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + cmdp->special = 0; + INTON; + goto success; + } + + if (act & DO_ERR) { + if (e == ENOENT || e == ENOTDIR) + outfmt(out2, "%s: not found\n", name); + else + outfmt(out2, "%s: %s\n", name, strerror(e)); + } + entry->cmdtype = CMDUNKNOWN; + entry->u.index = 0; + entry->special = 0; + return; + +success: + if (cd) + cmdtable_cd = 1; + entry->cmdtype = cmdp->cmdtype; + entry->u = cmdp->param; + entry->special = cmdp->special; +} + + + +/* + * Search the table of builtin commands. + */ + +int +find_builtin(const char *name, int *special) +{ + const unsigned char *bp; + size_t len; + + len = strlen(name); + for (bp = builtincmd ; *bp ; bp += 2 + bp[0]) { + if (bp[0] == len && memcmp(bp + 2, name, len) == 0) { + *special = (bp[1] & BUILTIN_SPECIAL) != 0; + return bp[1] & ~BUILTIN_SPECIAL; + } + } + return -1; +} + + + +/* + * Called when a cd is done. If any entry in cmdtable depends on the current + * directory, simply clear cmdtable completely. + */ + +void +hashcd(void) +{ + if (cmdtable_cd) + clearcmdentry(); +} + + + +/* + * Called before PATH is changed. The argument is the new value of PATH; + * pathval() still returns the old value at this point. Called with + * interrupts off. + */ + +void +changepath(const char *newval __unused) +{ + clearcmdentry(); +} + + +/* + * Clear out cached utility locations. + */ + +void +clearcmdentry(void) +{ + struct tblentry **tblp; + struct tblentry **pp; + struct tblentry *cmdp; + + INTOFF; + for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) { + pp = tblp; + while ((cmdp = *pp) != NULL) { + if (cmdp->cmdtype == CMDNORMAL) { + *pp = cmdp->next; + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + cmdtable_cd = 0; + INTON; +} + + +/* + * Locate a command in the command hash table. If "add" is nonzero, + * add the command to the table if it is not already present. The + * variable "lastcmdentry" is set to point to the address of the link + * pointing to the entry, so that delete_cmd_entry can delete the + * entry. + */ + +static struct tblentry **lastcmdentry; + + +static struct tblentry * +cmdlookup(const char *name, int add) +{ + unsigned int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + size_t len; + + p = name; + hashval = (unsigned char)*p << 4; + while (*p) + hashval += *p++; + pp = &cmdtable[hashval % CMDTABLESIZE]; + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (equal(cmdp->cmdname, name)) + break; + pp = &cmdp->next; + } + if (add && cmdp == NULL) { + INTOFF; + len = strlen(name); + cmdp = *pp = ckmalloc(sizeof (struct tblentry) + len + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + memcpy(cmdp->cmdname, name, len + 1); + INTON; + } + lastcmdentry = pp; + return cmdp; +} + +/* + * Delete the command entry returned on the last lookup. + */ + +static void +delete_cmd_entry(void) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = *lastcmdentry; + *lastcmdentry = cmdp->next; + ckfree(cmdp); + INTON; +} + + + +/* + * Add a new command entry, replacing any existing command entry for + * the same name. + */ + +static void +addcmdentry(const char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + } + cmdp->cmdtype = entry->cmdtype; + cmdp->param = entry->u; + cmdp->special = entry->special; + INTON; +} + + +/* + * Define a shell function. + */ + +void +defun(const char *name, union node *func) +{ + struct cmdentry entry; + + INTOFF; + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); + entry.special = 0; + addcmdentry(name, &entry); + INTON; +} + + +/* + * Delete a function if it exists. + * Called with interrupts off. + */ + +int +unsetfunc(const char *name) +{ + struct tblentry *cmdp; + + if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + delete_cmd_entry(); + return (0); + } + return (0); +} + + +/* + * Check if a function by a certain name exists. + */ +int +isfunc(const char *name) +{ + struct tblentry *cmdp; + cmdp = cmdlookup(name, 0); + return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION); +} + + +/* + * Shared code for the following builtin commands: + * type, command -v, command -V + */ + +int +typecmd_impl(int argc, char **argv, int cmd, const char *path) +{ + struct cmdentry entry; + struct tblentry *cmdp; + const char *const *pp; + struct alias *ap; + int i; + int error1 = 0; + + if (path != pathval()) + clearcmdentry(); + + for (i = 1; i < argc; i++) { + /* First look at the keywords */ + for (pp = parsekwd; *pp; pp++) + if (**pp == *argv[i] && equal(*pp, argv[i])) + break; + + if (*pp) { + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is a shell keyword\n", argv[i]); + continue; + } + + /* Then look at the aliases */ + if ((ap = lookupalias(argv[i], 1)) != NULL) { + if (cmd == TYPECMD_SMALLV) { + out1fmt("alias %s=", argv[i]); + out1qstr(ap->val); + outcslow('\n', out1); + } else + out1fmt("%s is an alias for %s\n", argv[i], + ap->val); + continue; + } + + /* Then check if it is a tracked alias */ + if ((cmdp = cmdlookup(argv[i], 0)) != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + entry.special = cmdp->special; + } + else { + /* Finally use brute force */ + find_command(argv[i], &entry, 0, path); + } + + switch (entry.cmdtype) { + case CMDNORMAL: { + if (strchr(argv[i], '/') == NULL) { + const char *path2 = path; + const char *opt2; + char *name; + int j = entry.u.index; + do { + name = padvance(&path2, &opt2, argv[i]); + stunalloc(name); + } while (--j >= 0); + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", name); + else + out1fmt("%s is%s %s\n", argv[i], + (cmdp && cmd == TYPECMD_TYPE) ? + " a tracked alias for" : "", + name); + } else { + if (eaccess(argv[i], X_OK) == 0) { + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is %s\n", argv[i], + argv[i]); + } else { + if (cmd != TYPECMD_SMALLV) + outfmt(out2, "%s: %s\n", + argv[i], strerror(errno)); + error1 |= 127; + } + } + break; + } + case CMDFUNCTION: + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is a shell function\n", argv[i]); + break; + + case CMDBUILTIN: + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else if (entry.special) + out1fmt("%s is a special shell builtin\n", + argv[i]); + else + out1fmt("%s is a shell builtin\n", argv[i]); + break; + + default: + if (cmd != TYPECMD_SMALLV) + outfmt(out2, "%s: not found\n", argv[i]); + error1 |= 127; + break; + } + } + + if (path != pathval()) + clearcmdentry(); + + return error1; +} + +/* + * Locate and print what a word is... + */ + +int +typecmd(int argc, char **argv) +{ + if (argc > 2 && strcmp(argv[1], "--") == 0) + argc--, argv++; + return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1)); +} diff --git a/bin/1sh/exec.h b/bin/1sh/exec.h new file mode 100644 index 00000000..86cda649 --- /dev/null +++ b/bin/1sh/exec.h @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)exec.h 8.3 (Berkeley) 6/8/95 + * $FreeBSD: releng/12.1/bin/sh/exec.h 336320 2018-07-15 21:55:17Z jilles $ + */ + +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDBUILTIN 1 /* command is a shell builtin */ +#define CMDFUNCTION 2 /* command is a shell function */ + +/* values for typecmd_impl's third parameter */ +enum { + TYPECMD_SMALLV, /* command -v */ + TYPECMD_BIGV, /* command -V */ + TYPECMD_TYPE /* type */ +}; + +union node; +struct cmdentry { + int cmdtype; + union param { + int index; + struct funcdef *func; + } u; + int special; +}; + + +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_NOFUNC 0x02 /* don't return shell functions, for command */ + +extern int exerrno; /* last exec error */ + +void shellexec(char **, char **, const char *, int) __dead2; +char *padvance(const char **, const char **, const char *); +void find_command(const char *, struct cmdentry *, int, const char *); +int find_builtin(const char *, int *); +void hashcd(void); +void changepath(const char *); +void defun(const char *, union node *); +int unsetfunc(const char *); +int isfunc(const char *); +int typecmd_impl(int, char **, int, const char *); +void clearcmdentry(void); diff --git a/bin/1sh/expand.c b/bin/1sh/expand.c new file mode 100644 index 00000000..8fde14f5 --- /dev/null +++ b/bin/1sh/expand.c @@ -0,0 +1,1553 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1997-2005 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * Copyright (c) 2010-2015 + * Jilles Tjoelker <jilles@stack.nl>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/expand.c 341767 2018-12-09 19:14:21Z jilles $"); + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <dirent.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> +#include <wctype.h> + +/* + * Routines to expand arguments to commands. We have to deal with + * backquotes, shell variables, and file metacharacters. + */ + +#include "shell.h" +#include "main.h" +#include "nodes.h" +#include "eval.h" +#include "expand.h" +#include "syntax.h" +#include "parser.h" +#include "jobs.h" +#include "options.h" +#include "var.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "arith.h" +#include "show.h" +#include "builtins.h" + +enum wordstate { WORD_IDLE, WORD_WS_DELIMITED, WORD_QUOTEMARK }; + +struct worddest { + struct arglist *list; + enum wordstate state; +}; + +static char *expdest; /* output of current string */ + +static const char *argstr(const char *, struct nodelist **restrict, int, + struct worddest *); +static const char *exptilde(const char *, int); +static const char *expari(const char *, struct nodelist **restrict, int, + struct worddest *); +static void expbackq(union node *, int, int, struct worddest *); +static const char *subevalvar_trim(const char *, struct nodelist **restrict, + int, int, int); +static const char *subevalvar_misc(const char *, struct nodelist **restrict, + const char *, int, int, int); +static const char *evalvar(const char *, struct nodelist **restrict, int, + struct worddest *); +static int varisset(const char *, int); +static void strtodest(const char *, int, int, int, struct worddest *); +static void reprocess(int, int, int, int, struct worddest *); +static void varvalue(const char *, int, int, int, struct worddest *); +static void expandmeta(char *, struct arglist *); +static void expmeta(char *, char *, struct arglist *); +static int expsortcmp(const void *, const void *); +static int patmatch(const char *, const char *); +static void cvtnum(int, char *); +static int collate_range_cmp(wchar_t, wchar_t); + +void +emptyarglist(struct arglist *list) +{ + + list->args = list->smallarg; + list->count = 0; + list->capacity = sizeof(list->smallarg) / sizeof(list->smallarg[0]); +} + +void +appendarglist(struct arglist *list, char *str) +{ + char **newargs; + int newcapacity; + + if (list->count >= list->capacity) { + newcapacity = list->capacity * 2; + if (newcapacity < 16) + newcapacity = 16; + if (newcapacity > INT_MAX / (int)sizeof(newargs[0])) + error("Too many entries in arglist"); + newargs = stalloc(newcapacity * sizeof(newargs[0])); + memcpy(newargs, list->args, list->count * sizeof(newargs[0])); + list->args = newargs; + list->capacity = newcapacity; + } + list->args[list->count++] = str; +} + +static int +collate_range_cmp(wchar_t c1, wchar_t c2) +{ + wchar_t s1[2], s2[2]; + + s1[0] = c1; + s1[1] = L'\0'; + s2[0] = c2; + s2[1] = L'\0'; + return (wcscoll(s1, s2)); +} + +static char * +stputs_quotes(const char *data, const char *syntax, char *p) +{ + while (*data) { + CHECKSTRSPACE(2, p); + if (syntax[(int)*data] == CCTL) + USTPUTC(CTLESC, p); + USTPUTC(*data++, p); + } + return (p); +} +#define STPUTS_QUOTES(data, syntax, p) p = stputs_quotes((data), syntax, p) + +static char * +nextword(char c, int flag, char *p, struct worddest *dst) +{ + int is_ws; + + is_ws = c == '\t' || c == '\n' || c == ' '; + if (p != stackblock() || (is_ws ? dst->state == WORD_QUOTEMARK : + dst->state != WORD_WS_DELIMITED) || c == '\0') { + STPUTC('\0', p); + if (flag & EXP_GLOB) + expandmeta(grabstackstr(p), dst->list); + else + appendarglist(dst->list, grabstackstr(p)); + dst->state = is_ws ? WORD_WS_DELIMITED : WORD_IDLE; + } else if (!is_ws && dst->state == WORD_WS_DELIMITED) + dst->state = WORD_IDLE; + /* Reserve space while the stack string is empty. */ + appendarglist(dst->list, NULL); + dst->list->count--; + STARTSTACKSTR(p); + return p; +} +#define NEXTWORD(c, flag, p, dstlist) p = nextword(c, flag, p, dstlist) + +static char * +stputs_split(const char *data, const char *syntax, int flag, char *p, + struct worddest *dst) +{ + const char *ifs; + char c; + + ifs = ifsset() ? ifsval() : " \t\n"; + while (*data) { + CHECKSTRSPACE(2, p); + c = *data++; + if (strchr(ifs, c) != NULL) { + NEXTWORD(c, flag, p, dst); + continue; + } + if (flag & EXP_GLOB && syntax[(int)c] == CCTL) + USTPUTC(CTLESC, p); + USTPUTC(c, p); + } + return (p); +} +#define STPUTS_SPLIT(data, syntax, flag, p, dst) p = stputs_split((data), syntax, flag, p, dst) + +/* + * Perform expansions on an argument, placing the resulting list of arguments + * in arglist. Parameter expansion, command substitution and arithmetic + * expansion are always performed; additional expansions can be requested + * via flag (EXP_*). + * The result is left in the stack string. + * When arglist is NULL, perform here document expansion. + * + * When doing something that may cause this to be re-entered, make sure + * the stack string is empty via grabstackstr() and do not assume expdest + * remains valid. + */ +void +expandarg(union node *arg, struct arglist *arglist, int flag) +{ + struct worddest exparg; + struct nodelist *argbackq; + + if (fflag) + flag &= ~EXP_GLOB; + argbackq = arg->narg.backquote; + exparg.list = arglist; + exparg.state = WORD_IDLE; + STARTSTACKSTR(expdest); + argstr(arg->narg.text, &argbackq, flag, &exparg); + if (arglist == NULL) { + STACKSTRNUL(expdest); + return; /* here document expanded */ + } + if ((flag & EXP_SPLIT) == 0 || expdest != stackblock() || + exparg.state == WORD_QUOTEMARK) { + STPUTC('\0', expdest); + if (flag & EXP_SPLIT) { + if (flag & EXP_GLOB) + expandmeta(grabstackstr(expdest), exparg.list); + else + appendarglist(exparg.list, grabstackstr(expdest)); + } + } + if ((flag & EXP_SPLIT) == 0) + appendarglist(arglist, grabstackstr(expdest)); +} + + + +/* + * Perform parameter expansion, command substitution and arithmetic + * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE. + * Processing ends at a CTLENDVAR or CTLENDARI character as well as '\0'. + * This is used to expand word in ${var+word} etc. + * If EXP_GLOB or EXP_CASE are set, keep and/or generate CTLESC + * characters to allow for further processing. + * + * If EXP_SPLIT is set, dst receives any complete words produced. + */ +static const char * +argstr(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + char c; + int quotes = flag & (EXP_GLOB | EXP_CASE); /* do CTLESC */ + int firsteq = 1; + int split_lit; + int lit_quoted; + + split_lit = flag & EXP_SPLIT_LIT; + lit_quoted = flag & EXP_LIT_QUOTED; + flag &= ~(EXP_SPLIT_LIT | EXP_LIT_QUOTED); + if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) + p = exptilde(p, flag); + for (;;) { + CHECKSTRSPACE(2, expdest); + switch (c = *p++) { + case '\0': + return (p - 1); + case CTLENDVAR: + case CTLENDARI: + return (p); + case CTLQUOTEMARK: + lit_quoted = 1; + /* "$@" syntax adherence hack */ + if (p[0] == CTLVAR && (p[1] & VSQUOTE) != 0 && + p[2] == '@' && p[3] == '=') + break; + if ((flag & EXP_SPLIT) != 0 && expdest == stackblock()) + dst->state = WORD_QUOTEMARK; + break; + case CTLQUOTEEND: + lit_quoted = 0; + break; + case CTLESC: + c = *p++; + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + if (quotes) + USTPUTC(CTLESC, expdest); + USTPUTC(c, expdest); + break; + case CTLVAR: + p = evalvar(p, argbackq, flag, dst); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + expbackq((*argbackq)->n, c & CTLQUOTE, flag, dst); + *argbackq = (*argbackq)->next; + break; + case CTLARI: + p = expari(p, argbackq, flag, dst); + break; + case ':': + case '=': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + USTPUTC(c, expdest); + if (flag & EXP_VARTILDE && *p == '~' && + (c != '=' || firsteq)) { + if (c == '=') + firsteq = 0; + p = exptilde(p, flag); + } + break; + default: + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + USTPUTC(c, expdest); + } + } +} + +/* + * Perform tilde expansion, placing the result in the stack string and + * returning the next position in the input string to process. + */ +static const char * +exptilde(const char *p, int flag) +{ + char c; + const char *startp = p; + const char *user; + struct passwd *pw; + char *home; + int len; + + for (;;) { + c = *p; + switch(c) { + case CTLESC: /* This means CTL* are always considered quoted. */ + case CTLVAR: + case CTLBACKQ: + case CTLBACKQ | CTLQUOTE: + case CTLARI: + case CTLENDARI: + case CTLQUOTEMARK: + return (startp); + case ':': + if ((flag & EXP_VARTILDE) == 0) + break; + /* FALLTHROUGH */ + case '\0': + case '/': + case CTLENDVAR: + len = p - startp - 1; + STPUTBIN(startp + 1, len, expdest); + STACKSTRNUL(expdest); + user = expdest - len; + if (*user == '\0') { + home = lookupvar("HOME"); + } else { + pw = getpwnam(user); + home = pw != NULL ? pw->pw_dir : NULL; + } + STADJUST(-len, expdest); + if (home == NULL || *home == '\0') + return (startp); + strtodest(home, flag, VSNORMAL, 1, NULL); + return (p); + } + p++; + } +} + + +/* + * Expand arithmetic expression. + */ +static const char * +expari(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + char *q, *start; + arith_t result; + int begoff; + int quoted; + int adj; + + quoted = *p++ == '"'; + begoff = expdest - stackblock(); + p = argstr(p, argbackq, 0, NULL); + STPUTC('\0', expdest); + start = stackblock() + begoff; + + q = grabstackstr(expdest); + result = arith(start); + ungrabstackstr(q, expdest); + + start = stackblock() + begoff; + adj = start - expdest; + STADJUST(adj, expdest); + + CHECKSTRSPACE((int)(DIGITS(result) + 1), expdest); + fmtstr(expdest, DIGITS(result), ARITH_FORMAT_STR, result); + adj = strlen(expdest); + STADJUST(adj, expdest); + /* + * If this is quoted, a '-' must not indicate a range in [...]. + * If this is not quoted, splitting may occur. + */ + if (quoted ? + result < 0 && begoff > 1 && flag & (EXP_GLOB | EXP_CASE) : + flag & EXP_SPLIT) + reprocess(expdest - adj - stackblock(), flag, VSNORMAL, quoted, + dst); + return p; +} + + +/* + * Perform command substitution. + */ +static void +expbackq(union node *cmd, int quoted, int flag, struct worddest *dst) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest = expdest; + char lastc; + char const *syntax = quoted? DQSYNTAX : BASESYNTAX; + int quotes = flag & (EXP_GLOB | EXP_CASE); + size_t nnl; + const char *ifs; + int startloc; + + INTOFF; + p = grabstackstr(dest); + evalbackcmd(cmd, &in); + ungrabstackstr(p, dest); + + p = in.buf; + startloc = dest - stackblock(); + nnl = 0; + if (!quoted && flag & EXP_SPLIT) + ifs = ifsset() ? ifsval() : " \t\n"; + else + ifs = ""; + /* Remove trailing newlines */ + for (;;) { + if (--in.nleft < 0) { + if (in.fd < 0) + break; + while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR) + ; + TRACE(("expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; + in.nleft = i - 1; + } + lastc = *p++; + if (lastc == '\0') + continue; + if (nnl > 0 && lastc != '\n') { + NEXTWORD('\n', flag, dest, dst); + nnl = 0; + } + if (strchr(ifs, lastc) != NULL) { + if (lastc == '\n') + nnl++; + else + NEXTWORD(lastc, flag, dest, dst); + } else { + CHECKSTRSPACE(2, dest); + if (quotes && syntax[(int)lastc] == CCTL) + USTPUTC(CTLESC, dest); + USTPUTC(lastc, dest); + } + } + while (dest > stackblock() + startloc && STTOPC(dest) == '\n') + STUNPUTC(dest); + + if (in.fd >= 0) + close(in.fd); + if (in.buf) + ckfree(in.buf); + if (in.jp) { + p = grabstackstr(dest); + exitstatus = waitforjob(in.jp, (int *)NULL); + ungrabstackstr(p, dest); + } + TRACE(("expbackq: done\n")); + expdest = dest; + INTON; +} + + + +static void +recordleft(const char *str, const char *loc, char *startp) +{ + int amount; + + amount = ((str - 1) - (loc - startp)) - expdest; + STADJUST(amount, expdest); + while (loc != str - 1) + *startp++ = *loc++; +} + +static const char * +subevalvar_trim(const char *p, struct nodelist **restrict argbackq, int strloc, + int subtype, int startloc) +{ + char *startp; + char *loc = NULL; + char *str; + int c = 0; + int amount; + + p = argstr(p, argbackq, EXP_CASE | EXP_TILDE, NULL); + STACKSTRNUL(expdest); + startp = stackblock() + startloc; + str = stackblock() + strloc; + + switch (subtype) { + case VSTRIMLEFT: + for (loc = startp; loc < str; loc++) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp)) { + *loc = c; + recordleft(str, loc, startp); + return p; + } + *loc = c; + } + break; + + case VSTRIMLEFTMAX: + for (loc = str - 1; loc >= startp;) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp)) { + *loc = c; + recordleft(str, loc, startp); + return p; + } + *loc = c; + loc--; + } + break; + + case VSTRIMRIGHT: + for (loc = str - 1; loc >= startp;) { + if (patmatch(str, loc)) { + amount = loc - expdest; + STADJUST(amount, expdest); + return p; + } + loc--; + } + break; + + case VSTRIMRIGHTMAX: + for (loc = startp; loc < str - 1; loc++) { + if (patmatch(str, loc)) { + amount = loc - expdest; + STADJUST(amount, expdest); + return p; + } + } + break; + + + default: + abort(); + } + amount = (expdest - stackblock() - strloc) + 1; + STADJUST(-amount, expdest); + return p; +} + + +static const char * +subevalvar_misc(const char *p, struct nodelist **restrict argbackq, + const char *var, int subtype, int startloc, int varflags) +{ + const char *end; + char *startp; + int amount; + + end = argstr(p, argbackq, EXP_TILDE, NULL); + STACKSTRNUL(expdest); + startp = stackblock() + startloc; + + switch (subtype) { + case VSASSIGN: + setvar(var, startp, 0); + amount = startp - expdest; + STADJUST(amount, expdest); + return end; + + case VSQUESTION: + if (*p != CTLENDVAR) { + outfmt(out2, "%s\n", startp); + error((char *)NULL); + } + error("%.*s: parameter %snot set", (int)(p - var - 1), + var, (varflags & VSNUL) ? "null or " : ""); + + default: + abort(); + } +} + + +/* + * Expand a variable, and return a pointer to the next character in the + * input string. + */ + +static const char * +evalvar(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + int subtype; + int varflags; + const char *var; + const char *val; + int patloc; + int c; + int set; + int special; + int startloc; + int varlen; + int varlenb; + char buf[21]; + + varflags = (unsigned char)*p++; + subtype = varflags & VSTYPE; + var = p; + special = 0; + if (! is_name(*p)) + special = 1; + p = strchr(p, '=') + 1; + if (varflags & VSLINENO) { + set = 1; + special = 1; + val = NULL; + } else if (special) { + set = varisset(var, varflags & VSNUL); + val = NULL; + } else { + val = bltinlookup(var, 1); + if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) { + val = NULL; + set = 0; + } else + set = 1; + } + varlen = 0; + startloc = expdest - stackblock(); + if (!set && uflag && *var != '@' && *var != '*') { + switch (subtype) { + case VSNORMAL: + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + case VSLENGTH: + error("%.*s: parameter not set", (int)(p - var - 1), + var); + } + } + if (set && subtype != VSPLUS) { + /* insert the value of the variable */ + if (special) { + if (varflags & VSLINENO) { + if (p - var > (ptrdiff_t)sizeof(buf)) + abort(); + memcpy(buf, var, p - var - 1); + buf[p - var - 1] = '\0'; + strtodest(buf, flag, subtype, + varflags & VSQUOTE, dst); + } else + varvalue(var, varflags & VSQUOTE, subtype, flag, + dst); + if (subtype == VSLENGTH) { + varlenb = expdest - stackblock() - startloc; + varlen = varlenb; + if (localeisutf8) { + val = stackblock() + startloc; + for (;val != expdest; val++) + if ((*val & 0xC0) == 0x80) + varlen--; + } + STADJUST(-varlenb, expdest); + } + } else { + if (subtype == VSLENGTH) { + for (;*val; val++) + if (!localeisutf8 || + (*val & 0xC0) != 0x80) + varlen++; + } + else + strtodest(val, flag, subtype, + varflags & VSQUOTE, dst); + } + } + + if (subtype == VSPLUS) + set = ! set; + + switch (subtype) { + case VSLENGTH: + cvtnum(varlen, buf); + strtodest(buf, flag, VSNORMAL, varflags & VSQUOTE, dst); + break; + + case VSNORMAL: + return p; + + case VSPLUS: + case VSMINUS: + if (!set) { + return argstr(p, argbackq, + flag | (flag & EXP_SPLIT ? EXP_SPLIT_LIT : 0) | + (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0), dst); + } + break; + + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + if (!set) + break; + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - stackblock(); + p = subevalvar_trim(p, argbackq, patloc, subtype, startloc); + reprocess(startloc, flag, VSNORMAL, varflags & VSQUOTE, dst); + if (flag & EXP_SPLIT && *var == '@' && varflags & VSQUOTE) + dst->state = WORD_QUOTEMARK; + return p; + + case VSASSIGN: + case VSQUESTION: + if (!set) { + p = subevalvar_misc(p, argbackq, var, subtype, + startloc, varflags); + /* assert(subtype == VSASSIGN); */ + val = lookupvar(var); + strtodest(val, flag, subtype, varflags & VSQUOTE, dst); + return p; + } + break; + + case VSERROR: + c = p - var - 1; + error("${%.*s%s}: Bad substitution", c, var, + (c > 0 && *p != CTLENDVAR) ? "..." : ""); + + default: + abort(); + } + + { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + if ((c = *p++) == CTLESC) + p++; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) + *argbackq = (*argbackq)->next; + else if (c == CTLVAR) { + if ((*p++ & VSTYPE) != VSNORMAL) + nesting++; + } else if (c == CTLENDVAR) { + if (--nesting == 0) + break; + } + } + } + return p; +} + + + +/* + * Test whether a special or positional parameter is set. + */ + +static int +varisset(const char *name, int nulok) +{ + + if (*name == '!') + return backgndpidset(); + else if (*name == '@' || *name == '*') { + if (*shellparam.p == NULL) + return 0; + + if (nulok) { + char **av; + + for (av = shellparam.p; *av; av++) + if (**av != '\0') + return 1; + return 0; + } + } else if (is_digit(*name)) { + char *ap; + long num; + + errno = 0; + num = strtol(name, NULL, 10); + if (errno != 0 || num > shellparam.nparam) + return 0; + + if (num == 0) + ap = arg0; + else + ap = shellparam.p[num - 1]; + + if (nulok && (ap == NULL || *ap == '\0')) + return 0; + } + return 1; +} + +static void +strtodest(const char *p, int flag, int subtype, int quoted, + struct worddest *dst) +{ + if (subtype == VSLENGTH || subtype == VSTRIMLEFT || + subtype == VSTRIMLEFTMAX || subtype == VSTRIMRIGHT || + subtype == VSTRIMRIGHTMAX) + STPUTS(p, expdest); + else if (flag & EXP_SPLIT && !quoted && dst != NULL) + STPUTS_SPLIT(p, BASESYNTAX, flag, expdest, dst); + else if (flag & (EXP_GLOB | EXP_CASE)) + STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest); + else + STPUTS(p, expdest); +} + +static void +reprocess(int startloc, int flag, int subtype, int quoted, + struct worddest *dst) +{ + static char *buf = NULL; + static size_t buflen = 0; + char *startp; + size_t len, zpos, zlen; + + startp = stackblock() + startloc; + len = expdest - startp; + if (len >= SIZE_MAX / 2 || len > PTRDIFF_MAX) + abort(); + INTOFF; + if (len >= buflen) { + ckfree(buf); + buf = NULL; + } + if (buflen < 128) + buflen = 128; + while (len >= buflen) + buflen <<= 1; + if (buf == NULL) + buf = ckmalloc(buflen); + INTON; + memcpy(buf, startp, len); + buf[len] = '\0'; + STADJUST(-(ptrdiff_t)len, expdest); + for (zpos = 0;;) { + zlen = strlen(buf + zpos); + strtodest(buf + zpos, flag, subtype, quoted, dst); + zpos += zlen + 1; + if (zpos == len + 1) + break; + if (flag & EXP_SPLIT && (quoted || (zlen > 0 && zpos < len))) + NEXTWORD('\0', flag, expdest, dst); + } +} + +/* + * Add the value of a special or positional parameter to the stack string. + */ + +static void +varvalue(const char *name, int quoted, int subtype, int flag, + struct worddest *dst) +{ + int num; + char *p; + int i; + int splitlater; + char sep[2]; + char **ap; + char buf[(NSHORTOPTS > 10 ? NSHORTOPTS : 10) + 1]; + + if (subtype == VSLENGTH) + flag &= ~EXP_FULL; + splitlater = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX || + subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX; + + switch (*name) { + case '$': + num = rootpid; + break; + case '?': + num = oexitstatus; + break; + case '#': + num = shellparam.nparam; + break; + case '!': + num = backgndpidval(); + break; + case '-': + p = buf; + for (i = 0 ; i < NSHORTOPTS ; i++) { + if (optval[i]) + *p++ = optletter[i]; + } + *p = '\0'; + strtodest(buf, flag, subtype, quoted, dst); + return; + case '@': + if (flag & EXP_SPLIT && quoted) { + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + strtodest(p, flag, subtype, quoted, dst); + if (*ap) { + if (splitlater) + STPUTC('\0', expdest); + else + NEXTWORD('\0', flag, expdest, + dst); + } + } + if (shellparam.nparam > 0) + dst->state = WORD_QUOTEMARK; + return; + } + /* FALLTHROUGH */ + case '*': + if (ifsset()) + sep[0] = ifsval()[0]; + else + sep[0] = ' '; + sep[1] = '\0'; + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + strtodest(p, flag, subtype, quoted, dst); + if (!*ap) + break; + if (sep[0]) + strtodest(sep, flag, subtype, quoted, dst); + else if (flag & EXP_SPLIT && !quoted && **ap != '\0') { + if (splitlater) + STPUTC('\0', expdest); + else + NEXTWORD('\0', flag, expdest, dst); + } + } + return; + default: + if (is_digit(*name)) { + num = atoi(name); + if (num == 0) + p = arg0; + else if (num > 0 && num <= shellparam.nparam) + p = shellparam.p[num - 1]; + else + return; + strtodest(p, flag, subtype, quoted, dst); + } + return; + } + cvtnum(num, buf); + strtodest(buf, flag, subtype, quoted, dst); +} + + + +static char expdir[PATH_MAX]; +#define expdir_end (expdir + sizeof(expdir)) + +/* + * Perform pathname generation and remove control characters. + * At this point, the only control characters should be CTLESC. + * The results are stored in the list dstlist. + */ +static void +expandmeta(char *pattern, struct arglist *dstlist) +{ + char *p; + int firstmatch; + char c; + + firstmatch = dstlist->count; + p = pattern; + for (; (c = *p) != '\0'; p++) { + /* fast check for meta chars */ + if (c == '*' || c == '?' || c == '[') { + INTOFF; + expmeta(expdir, pattern, dstlist); + INTON; + break; + } + } + if (dstlist->count == firstmatch) { + /* + * no matches + */ + rmescapes(pattern); + appendarglist(dstlist, pattern); + } else { + qsort(&dstlist->args[firstmatch], + dstlist->count - firstmatch, + sizeof(dstlist->args[0]), expsortcmp); + } +} + + +/* + * Do metacharacter (i.e. *, ?, [...]) expansion. + */ + +static void +expmeta(char *enddir, char *name, struct arglist *arglist) +{ + const char *p; + const char *q; + const char *start; + char *endname; + int metaflag; + struct stat statb; + DIR *dirp; + struct dirent *dp; + int atend; + int matchdot; + int esc; + int namlen; + + metaflag = 0; + start = name; + for (p = name; esc = 0, *p; p += esc + 1) { + if (*p == '*' || *p == '?') + metaflag = 1; + else if (*p == '[') { + q = p + 1; + if (*q == '!' || *q == '^') + q++; + for (;;) { + if (*q == CTLESC) + q++; + if (*q == '/' || *q == '\0') + break; + if (*++q == ']') { + metaflag = 1; + break; + } + } + } else if (*p == '\0') + break; + else { + if (*p == CTLESC) + esc++; + if (p[esc] == '/') { + if (metaflag) + break; + start = p + esc + 1; + } + } + } + if (metaflag == 0) { /* we've reached the end of the file name */ + if (enddir != expdir) + metaflag++; + for (p = name ; ; p++) { + if (*p == CTLESC) + p++; + *enddir++ = *p; + if (*p == '\0') + break; + if (enddir == expdir_end) + return; + } + if (metaflag == 0 || lstat(expdir, &statb) >= 0) + appendarglist(arglist, stsavestr(expdir)); + return; + } + endname = name + (p - name); + if (start != name) { + p = name; + while (p < start) { + if (*p == CTLESC) + p++; + *enddir++ = *p++; + if (enddir == expdir_end) + return; + } + } + if (enddir == expdir) { + p = "."; + } else if (enddir == expdir + 1 && *expdir == '/') { + p = "/"; + } else { + p = expdir; + enddir[-1] = '\0'; + } + if ((dirp = opendir(p)) == NULL) + return; + if (enddir != expdir) + enddir[-1] = '/'; + if (*endname == 0) { + atend = 1; + } else { + atend = 0; + *endname = '\0'; + endname += esc + 1; + } + matchdot = 0; + p = start; + if (*p == CTLESC) + p++; + if (*p == '.') + matchdot++; + while (! int_pending() && (dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.' && ! matchdot) + continue; + if (patmatch(start, dp->d_name)) { + namlen = dp->d_namlen; + if (enddir + namlen + 1 > expdir_end) + continue; + memcpy(enddir, dp->d_name, namlen + 1); + if (atend) + appendarglist(arglist, stsavestr(expdir)); + else { + if (dp->d_type != DT_UNKNOWN && + dp->d_type != DT_DIR && + dp->d_type != DT_LNK) + continue; + if (enddir + namlen + 2 > expdir_end) + continue; + enddir[namlen] = '/'; + enddir[namlen + 1] = '\0'; + expmeta(enddir + namlen + 1, endname, arglist); + } + } + } + closedir(dirp); + if (! atend) + endname[-esc - 1] = esc ? CTLESC : '/'; +} + + +static int +expsortcmp(const void *p1, const void *p2) +{ + const char *s1 = *(const char * const *)p1; + const char *s2 = *(const char * const *)p2; + + return (strcoll(s1, s2)); +} + + + +static wchar_t +get_wc(const char **p) +{ + wchar_t c; + int chrlen; + + chrlen = mbtowc(&c, *p, 4); + if (chrlen == 0) + return 0; + else if (chrlen == -1) + c = 0; + else + *p += chrlen; + return c; +} + + +/* + * See if a character matches a character class, starting at the first colon + * of "[:class:]". + * If a valid character class is recognized, a pointer to the next character + * after the final closing bracket is stored into *end, otherwise a null + * pointer is stored into *end. + */ +static int +match_charclass(const char *p, wchar_t chr, const char **end) +{ + char name[20]; + const char *nameend; + wctype_t cclass; + + *end = NULL; + p++; + nameend = strstr(p, ":]"); + if (nameend == NULL || (size_t)(nameend - p) >= sizeof(name) || + nameend == p) + return 0; + memcpy(name, p, nameend - p); + name[nameend - p] = '\0'; + *end = nameend + 2; + cclass = wctype(name); + /* An unknown class matches nothing but is valid nevertheless. */ + if (cclass == 0) + return 0; + return iswctype(chr, cclass); +} + + +/* + * Returns true if the pattern matches the string. + */ + +static int +patmatch(const char *pattern, const char *string) +{ + const char *p, *q, *end; + const char *bt_p, *bt_q; + char c; + wchar_t wc, wc2; + + p = pattern; + q = string; + bt_p = NULL; + bt_q = NULL; + for (;;) { + switch (c = *p++) { + case '\0': + if (*q != '\0') + goto backtrack; + return 1; + case CTLESC: + if (*q++ != *p++) + goto backtrack; + break; + case '?': + if (*q == '\0') + return 0; + if (localeisutf8) { + wc = get_wc(&q); + /* + * A '?' does not match invalid UTF-8 but a + * '*' does, so backtrack. + */ + if (wc == 0) + goto backtrack; + } else + q++; + break; + case '*': + c = *p; + while (c == '*') + c = *++p; + /* + * If the pattern ends here, we know the string + * matches without needing to look at the rest of it. + */ + if (c == '\0') + return 1; + /* + * First try the shortest match for the '*' that + * could work. We can forget any earlier '*' since + * there is no way having it match more characters + * can help us, given that we are already here. + */ + bt_p = p; + bt_q = q; + break; + case '[': { + const char *savep, *saveq; + int invert, found; + wchar_t chr; + + savep = p, saveq = q; + invert = 0; + if (*p == '!' || *p == '^') { + invert++; + p++; + } + found = 0; + if (*q == '\0') + return 0; + if (localeisutf8) { + chr = get_wc(&q); + if (chr == 0) + goto backtrack; + } else + chr = (unsigned char)*q++; + c = *p++; + do { + if (c == '\0') { + p = savep, q = saveq; + c = '['; + goto dft; + } + if (c == '[' && *p == ':') { + found |= match_charclass(p, chr, &end); + if (end != NULL) { + p = end; + continue; + } + } + if (c == CTLESC) + c = *p++; + if (localeisutf8 && c & 0x80) { + p--; + wc = get_wc(&p); + if (wc == 0) /* bad utf-8 */ + return 0; + } else + wc = (unsigned char)c; + if (*p == '-' && p[1] != ']') { + p++; + if (*p == CTLESC) + p++; + if (localeisutf8) { + wc2 = get_wc(&p); + if (wc2 == 0) /* bad utf-8 */ + return 0; + } else + wc2 = (unsigned char)*p++; + if ( collate_range_cmp(chr, wc) >= 0 + && collate_range_cmp(chr, wc2) <= 0 + ) + found = 1; + } else { + if (chr == wc) + found = 1; + } + } while ((c = *p++) != ']'); + if (found == invert) + goto backtrack; + break; + } +dft: default: + if (*q == '\0') + return 0; + if (*q++ == c) + break; +backtrack: + /* + * If we have a mismatch (other than hitting the end + * of the string), go back to the last '*' seen and + * have it match one additional character. + */ + if (bt_p == NULL) + return 0; + if (*bt_q == '\0') + return 0; + bt_q++; + p = bt_p; + q = bt_q; + break; + } + } +} + + + +/* + * Remove any CTLESC and CTLQUOTEMARK characters from a string. + */ + +void +rmescapes(char *str) +{ + char *p, *q; + + p = str; + while (*p != CTLESC && *p != CTLQUOTEMARK && *p != CTLQUOTEEND) { + if (*p++ == '\0') + return; + } + q = p; + while (*p) { + if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) { + p++; + continue; + } + if (*p == CTLESC) + p++; + *q++ = *p++; + } + *q = '\0'; +} + + + +/* + * See if a pattern matches in a case statement. + */ + +int +casematch(union node *pattern, const char *val) +{ + struct stackmark smark; + struct nodelist *argbackq; + int result; + char *p; + + setstackmark(&smark); + argbackq = pattern->narg.backquote; + STARTSTACKSTR(expdest); + argstr(pattern->narg.text, &argbackq, EXP_TILDE | EXP_CASE, NULL); + STPUTC('\0', expdest); + p = grabstackstr(expdest); + result = patmatch(p, val); + popstackmark(&smark); + return result; +} + +/* + * Our own itoa(). + */ + +static void +cvtnum(int num, char *buf) +{ + char temp[32]; + int neg = num < 0; + char *p = temp + 31; + + temp[31] = '\0'; + + do { + *--p = num % 10 + '0'; + } while ((num /= 10) != 0); + + if (neg) + *--p = '-'; + + memcpy(buf, p, temp + 32 - p); +} + +/* + * Do most of the work for wordexp(3). + */ + +int +wordexpcmd(int argc, char **argv) +{ + size_t len; + int i; + + out1fmt("%08x", argc - 1); + for (i = 1, len = 0; i < argc; i++) + len += strlen(argv[i]); + out1fmt("%08x", (int)len); + for (i = 1; i < argc; i++) + outbin(argv[i], strlen(argv[i]) + 1, out1); + return (0); +} + +/* + * Do most of the work for wordexp(3), new version. + */ + +int +freebsd_wordexpcmd(int argc __unused, char **argv __unused) +{ + struct arglist arglist; + union node *args, *n; + size_t len; + int ch; + int protected = 0; + int fd = -1; + int i; + + while ((ch = nextopt("f:p")) != '\0') { + switch (ch) { + case 'f': + fd = number(shoptarg); + break; + case 'p': + protected = 1; + break; + } + } + if (*argptr != NULL) + error("wrong number of arguments"); + if (fd < 0) + error("missing fd"); + INTOFF; + setinputfd(fd, 1); + INTON; + args = parsewordexp(); + popfile(); /* will also close fd */ + if (protected) + for (n = args; n != NULL; n = n->narg.next) { + if (n->narg.backquote != NULL) { + outcslow('C', out1); + error("command substitution disabled"); + } + } + outcslow(' ', out1); + emptyarglist(&arglist); + for (n = args; n != NULL; n = n->narg.next) + expandarg(n, &arglist, EXP_FULL | EXP_TILDE); + for (i = 0, len = 0; i < arglist.count; i++) + len += strlen(arglist.args[i]); + out1fmt("%016x %016zx", arglist.count, len); + for (i = 0; i < arglist.count; i++) + outbin(arglist.args[i], strlen(arglist.args[i]) + 1, out1); + return (0); +} diff --git a/bin/1sh/expand.h b/bin/1sh/expand.h new file mode 100644 index 00000000..2bd9bbd9 --- /dev/null +++ b/bin/1sh/expand.h @@ -0,0 +1,62 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)expand.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/expand.h 314436 2017-02-28 23:42:47Z imp $ + */ + +struct arglist { + char **args; + int count; + int capacity; + char *smallarg[1]; +}; + +/* + * expandarg() flags + */ +#define EXP_SPLIT 0x1 /* perform word splitting */ +#define EXP_TILDE 0x2 /* do normal tilde expansion */ +#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_SPLIT_LIT 0x20 /* IFS split literal text ${v+-a b c} */ +#define EXP_LIT_QUOTED 0x40 /* for EXP_SPLIT_LIT, start off quoted */ +#define EXP_GLOB 0x80 /* perform file globbing */ + +#define EXP_FULL (EXP_SPLIT | EXP_GLOB) + + +void emptyarglist(struct arglist *); +void appendarglist(struct arglist *, char *); +union node; +void expandarg(union node *, struct arglist *, int); +void rmescapes(char *); +int casematch(union node *, const char *); diff --git a/bin/1sh/funcs/cmv b/bin/1sh/funcs/cmv new file mode 100644 index 00000000..28e755d5 --- /dev/null +++ b/bin/1sh/funcs/cmv @@ -0,0 +1,49 @@ +#!/bin/sh + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. 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. +# +# @(#)cmv 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/funcs/cmv 314436 2017-02-28 23:42:47Z imp $ + +# Conditional move--don't replace an existing file. + +cmv() { + if test $# != 2 + then echo "cmv: arg count" + return 2 + fi + if test -f "$2" -o -w "$2" + then echo "$2 exists" + return 2 + fi + /bin/mv "$1" "$2" +} diff --git a/bin/1sh/funcs/dirs b/bin/1sh/funcs/dirs new file mode 100644 index 00000000..7ce7d5ce --- /dev/null +++ b/bin/1sh/funcs/dirs @@ -0,0 +1,73 @@ +#!/bin/sh + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. 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. +# +# @(#)dirs 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/funcs/dirs 314436 2017-02-28 23:42:47Z imp $ + +# pushd, popd, and dirs --- written by Chris Bertin +# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris +# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW + +pushd () { + SAVE=`pwd` + if [ "$1" = "" ] + then if [ "$DSTACK" = "" ] + then echo "pushd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 || return + shift 1 + DSTACK="$*" + else cd $1 > /dev/null || return + fi + DSTACK="$SAVE $DSTACK" + dirs +} + +popd () { + if [ "$DSTACK" = "" ] + then echo "popd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 + shift + DSTACK=$* + dirs +} + +dirs () { + echo "`pwd` $DSTACK" + return 0 +} diff --git a/bin/1sh/funcs/login b/bin/1sh/funcs/login new file mode 100644 index 00000000..e43466ea --- /dev/null +++ b/bin/1sh/funcs/login @@ -0,0 +1,38 @@ +#!/bin/sh + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. 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. +# +# @(#)login 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/funcs/login 314436 2017-02-28 23:42:47Z imp $ + +# replaces the login builtin in the BSD shell +login () exec login "$@" diff --git a/bin/1sh/funcs/newgrp b/bin/1sh/funcs/newgrp new file mode 100644 index 00000000..7b2761d4 --- /dev/null +++ b/bin/1sh/funcs/newgrp @@ -0,0 +1,37 @@ +#!/bin/sh + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. 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. +# +# @(#)newgrp 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/funcs/newgrp 314436 2017-02-28 23:42:47Z imp $ + +newgrp() exec newgrp "$@" diff --git a/bin/1sh/funcs/popd b/bin/1sh/funcs/popd new file mode 100644 index 00000000..4408af4e --- /dev/null +++ b/bin/1sh/funcs/popd @@ -0,0 +1,73 @@ +#!/bin/sh + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. 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. +# +# @(#)popd 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/funcs/popd 314436 2017-02-28 23:42:47Z imp $ + +# pushd, popd, and dirs --- written by Chris Bertin +# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris +# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW + +pushd () { + SAVE=`pwd` + if [ "$1" = "" ] + then if [ "$DSTACK" = "" ] + then echo "pushd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 || return + shift 1 + DSTACK="$*" + else cd $1 > /dev/null || return + fi + DSTACK="$SAVE $DSTACK" + dirs +} + +popd () { + if [ "$DSTACK" = "" ] + then echo "popd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 + shift + DSTACK=$* + dirs +} + +dirs () { + echo "`pwd` $DSTACK" + return 0 +} diff --git a/bin/1sh/funcs/pushd b/bin/1sh/funcs/pushd new file mode 100644 index 00000000..11e3199c --- /dev/null +++ b/bin/1sh/funcs/pushd @@ -0,0 +1,73 @@ +#!/bin/sh + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. 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. +# +# @(#)pushd 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/funcs/pushd 314436 2017-02-28 23:42:47Z imp $ + +# pushd, popd, and dirs --- written by Chris Bertin +# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris +# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW + +pushd () { + SAVE=`pwd` + if [ "$1" = "" ] + then if [ "$DSTACK" = "" ] + then echo "pushd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 || return + shift 1 + DSTACK="$*" + else cd $1 > /dev/null || return + fi + DSTACK="$SAVE $DSTACK" + dirs +} + +popd () { + if [ "$DSTACK" = "" ] + then echo "popd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 + shift + DSTACK=$* + dirs +} + +dirs () { + echo "`pwd` $DSTACK" + return 0 +} diff --git a/bin/1sh/funcs/suspend b/bin/1sh/funcs/suspend new file mode 100644 index 00000000..d1d1f41b --- /dev/null +++ b/bin/1sh/funcs/suspend @@ -0,0 +1,39 @@ +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. 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. +# +# @(#)suspend 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/funcs/suspend 314436 2017-02-28 23:42:47Z imp $ + +suspend() { + local - + set +m + kill -TSTP 0 +} diff --git a/bin/1sh/histedit.c b/bin/1sh/histedit.c new file mode 100644 index 00000000..74edf24f --- /dev/null +++ b/bin/1sh/histedit.c @@ -0,0 +1,523 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/histedit.c 345613 2019-03-27 21:53:44Z jilles $"); + +#include <sys/param.h> +#include <limits.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +/* + * Editline and history functions (and glue). + */ +#include "shell.h" +#include "parser.h" +#include "var.h" +#include "options.h" +#include "main.h" +#include "output.h" +#include "mystring.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#include "error.h" +#include "eval.h" +#include "memalloc.h" +#include "builtins.h" + +#define MAXHISTLOOPS 4 /* max recursions through fc */ +#define DEFEDITOR "ed" /* default editor *should* be $EDITOR */ + +History *hist; /* history cookie */ +EditLine *el; /* editline cookie */ +int displayhist; +static FILE *el_in, *el_out, *el_err; + +static char *fc_replace(const char *, char *, char *); +static int not_fcnumber(const char *); +static int str_to_event(const char *, int); + +/* + * Set history and editing status. Called whenever the status may + * have changed (figures out what to do). + */ +void +histedit(void) +{ + +#define editing (Eflag || Vflag) + + if (iflag) { + if (!hist) { + /* + * turn history on + */ + INTOFF; + hist = history_init(); + INTON; + + if (hist != NULL) + sethistsize(histsizeval()); + else + out2fmt_flush("sh: can't initialize history\n"); + } + if (editing && !el && isatty(0)) { /* && isatty(2) ??? */ + /* + * turn editing on + */ + char *term; + + INTOFF; + if (el_in == NULL) + el_in = fdopen(0, "r"); + if (el_err == NULL) + el_err = fdopen(1, "w"); + if (el_out == NULL) + el_out = fdopen(2, "w"); + if (el_in == NULL || el_err == NULL || el_out == NULL) + goto bad; + term = lookupvar("TERM"); + if (term) + setenv("TERM", term, 1); + else + unsetenv("TERM"); + el = el_init(arg0, el_in, el_out, el_err); + if (el != NULL) { + if (hist) + el_set(el, EL_HIST, history, hist); + el_set(el, EL_PROMPT, getprompt); + el_set(el, EL_ADDFN, "sh-complete", + "Filename completion", + _el_fn_sh_complete); + } else { +bad: + out2fmt_flush("sh: can't initialize editing\n"); + } + INTON; + } else if (!editing && el) { + INTOFF; + el_end(el); + el = NULL; + INTON; + } + if (el) { + if (Vflag) + el_set(el, EL_EDITOR, "vi"); + else if (Eflag) + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_BIND, "^I", "sh-complete", NULL); + el_source(el, NULL); + } + } else { + INTOFF; + if (el) { /* no editing if not interactive */ + el_end(el); + el = NULL; + } + if (hist) { + history_end(hist); + hist = NULL; + } + INTON; + } +} + + +void +sethistsize(const char *hs) +{ + int histsize; + HistEvent he; + + if (hist != NULL) { + if (hs == NULL || !is_number(hs)) + histsize = 100; + else + histsize = atoi(hs); + history(hist, &he, H_SETSIZE, histsize); + history(hist, &he, H_SETUNIQUE, 1); + } +} + +void +setterm(const char *term) +{ + if (rootshell && el != NULL && term != NULL) + el_set(el, EL_TERMINAL, term); +} + +int +histcmd(int argc, char **argv __unused) +{ + int ch; + const char *editor = NULL; + HistEvent he; + int lflg = 0, nflg = 0, rflg = 0, sflg = 0; + int i, retval; + const char *firststr, *laststr; + int first, last, direction; + char *pat = NULL, *repl = NULL; + static int active = 0; + struct jmploc jmploc; + struct jmploc *savehandler; + char editfilestr[PATH_MAX]; + char *volatile editfile; + FILE *efp = NULL; + int oldhistnum; + + if (hist == NULL) + error("history not active"); + + if (argc == 1) + error("missing history argument"); + + while (not_fcnumber(*argptr) && (ch = nextopt("e:lnrs")) != '\0') + switch ((char)ch) { + case 'e': + editor = shoptarg; + break; + case 'l': + lflg = 1; + break; + case 'n': + nflg = 1; + break; + case 'r': + rflg = 1; + break; + case 's': + sflg = 1; + break; + } + + savehandler = handler; + /* + * If executing... + */ + if (lflg == 0 || editor || sflg) { + lflg = 0; /* ignore */ + editfile = NULL; + /* + * Catch interrupts to reset active counter and + * cleanup temp files. + */ + if (setjmp(jmploc.loc)) { + active = 0; + if (editfile) + unlink(editfile); + handler = savehandler; + longjmp(handler->loc, 1); + } + handler = &jmploc; + if (++active > MAXHISTLOOPS) { + active = 0; + displayhist = 0; + error("called recursively too many times"); + } + /* + * Set editor. + */ + if (sflg == 0) { + if (editor == NULL && + (editor = bltinlookup("FCEDIT", 1)) == NULL && + (editor = bltinlookup("EDITOR", 1)) == NULL) + editor = DEFEDITOR; + if (editor[0] == '-' && editor[1] == '\0') { + sflg = 1; /* no edit */ + editor = NULL; + } + } + } + + /* + * If executing, parse [old=new] now + */ + if (lflg == 0 && *argptr != NULL && + ((repl = strchr(*argptr, '=')) != NULL)) { + pat = *argptr; + *repl++ = '\0'; + argptr++; + } + /* + * determine [first] and [last] + */ + if (*argptr == NULL) { + firststr = lflg ? "-16" : "-1"; + laststr = "-1"; + } else if (argptr[1] == NULL) { + firststr = argptr[0]; + laststr = lflg ? "-1" : argptr[0]; + } else if (argptr[2] == NULL) { + firststr = argptr[0]; + laststr = argptr[1]; + } else + error("too many arguments"); + /* + * Turn into event numbers. + */ + first = str_to_event(firststr, 0); + last = str_to_event(laststr, 1); + + if (rflg) { + i = last; + last = first; + first = i; + } + /* + * XXX - this should not depend on the event numbers + * always increasing. Add sequence numbers or offset + * to the history element in next (diskbased) release. + */ + direction = first < last ? H_PREV : H_NEXT; + + /* + * If editing, grab a temp file. + */ + if (editor) { + int fd; + INTOFF; /* easier */ + sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP); + if ((fd = mkstemp(editfilestr)) < 0) + error("can't create temporary file %s", editfile); + editfile = editfilestr; + if ((efp = fdopen(fd, "w")) == NULL) { + close(fd); + error("Out of space"); + } + } + + /* + * Loop through selected history events. If listing or executing, + * do it now. Otherwise, put into temp file and call the editor + * after. + * + * The history interface needs rethinking, as the following + * convolutions will demonstrate. + */ + history(hist, &he, H_FIRST); + retval = history(hist, &he, H_NEXT_EVENT, first); + for (;retval != -1; retval = history(hist, &he, direction)) { + if (lflg) { + if (!nflg) + out1fmt("%5d ", he.num); + out1str(he.str); + } else { + const char *s = pat ? + fc_replace(he.str, pat, repl) : he.str; + + if (sflg) { + if (displayhist) { + out2str(s); + flushout(out2); + } + evalstring(s, 0); + if (displayhist && hist) { + /* + * XXX what about recursive and + * relative histnums. + */ + oldhistnum = he.num; + history(hist, &he, H_ENTER, s); + /* + * XXX H_ENTER moves the internal + * cursor, set it back to the current + * entry. + */ + history(hist, &he, + H_NEXT_EVENT, oldhistnum); + } + } else + fputs(s, efp); + } + /* + * At end? (if we were to lose last, we'd sure be + * messed up). + */ + if (he.num == last) + break; + } + if (editor) { + char *editcmd; + + fclose(efp); + INTON; + editcmd = stalloc(strlen(editor) + strlen(editfile) + 2); + sprintf(editcmd, "%s %s", editor, editfile); + evalstring(editcmd, 0); /* XXX - should use no JC command */ + readcmdfile(editfile); /* XXX - should read back - quick tst */ + unlink(editfile); + } + + if (lflg == 0 && active > 0) + --active; + if (displayhist) + displayhist = 0; + handler = savehandler; + return 0; +} + +static char * +fc_replace(const char *s, char *p, char *r) +{ + char *dest; + int plen = strlen(p); + + STARTSTACKSTR(dest); + while (*s) { + if (*s == *p && strncmp(s, p, plen) == 0) { + STPUTS(r, dest); + s += plen; + *p = '\0'; /* so no more matches */ + } else + STPUTC(*s++, dest); + } + STPUTC('\0', dest); + dest = grabstackstr(dest); + + return (dest); +} + +static int +not_fcnumber(const char *s) +{ + if (s == NULL) + return (0); + if (*s == '-') + s++; + return (!is_number(s)); +} + +static int +str_to_event(const char *str, int last) +{ + HistEvent he; + const char *s = str; + int relative = 0; + int i, retval; + + retval = history(hist, &he, H_FIRST); + switch (*s) { + case '-': + relative = 1; + /*FALLTHROUGH*/ + case '+': + s++; + } + if (is_number(s)) { + i = atoi(s); + if (relative) { + while (retval != -1 && i--) { + retval = history(hist, &he, H_NEXT); + } + if (retval == -1) + retval = history(hist, &he, H_LAST); + } else { + retval = history(hist, &he, H_NEXT_EVENT, i); + if (retval == -1) { + /* + * the notion of first and last is + * backwards to that of the history package + */ + retval = history(hist, &he, last ? H_FIRST : H_LAST); + } + } + if (retval == -1) + error("history number %s not found (internal error)", + str); + } else { + /* + * pattern + */ + retval = history(hist, &he, H_PREV_STR, str); + if (retval == -1) + error("history pattern not found: %s", str); + } + return (he.num); +} + +int +bindcmd(int argc, char **argv) +{ + int ret; + FILE *old; + FILE *out; + + if (el == NULL) + error("line editing is disabled"); + + INTOFF; + + out = out1fp(); + if (out == NULL) + error("Out of space"); + + el_get(el, EL_GETFP, 1, &old); + el_set(el, EL_SETFP, 1, out); + + ret = el_parse(el, argc, __DECONST(const char **, argv)); + + el_set(el, EL_SETFP, 1, old); + + fclose(out); + + INTON; + + return ret; +} + +#else +#include "error.h" + +int +histcmd(int argc, char **argv) +{ + + error("not compiled with history support"); + /*NOTREACHED*/ + return (0); +} + +int +bindcmd(int argc, char **argv) +{ + + error("not compiled with line editing support"); + return (0); +} +#endif diff --git a/bin/1sh/input.c b/bin/1sh/input.c new file mode 100644 index 00000000..f2295a02 --- /dev/null +++ b/bin/1sh/input.c @@ -0,0 +1,518 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)input.c 8.3 (Berkeley) 6/9/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/input.c 314436 2017-02-28 23:42:47Z imp $"); + +#include <stdio.h> /* defines BUFSIZ */ +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +/* + * This file implements the input routines used by the parser. + */ + +#include "shell.h" +#include "redir.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "options.h" +#include "memalloc.h" +#include "error.h" +#include "alias.h" +#include "parser.h" +#include "myhistedit.h" +#include "trap.h" + +#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ + +struct strpush { + struct strpush *prev; /* preceding string on stack */ + const char *prevstring; + int prevnleft; + int prevlleft; + struct alias *ap; /* if push was associated with an alias */ +}; + +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ + +struct parsefile { + struct parsefile *prev; /* preceding file on stack */ + int linno; /* current line */ + int fd; /* file descriptor (or -1 if string) */ + int nleft; /* number of chars left in this line */ + int lleft; /* number of lines left in this buffer */ + const char *nextc; /* next char in buffer */ + char *buf; /* input buffer */ + struct strpush *strpush; /* for pushing strings at this level */ + struct strpush basestrpush; /* so pushing one is fast */ +}; + + +int plinno = 1; /* input line number */ +int parsenleft; /* copy of parsefile->nleft */ +static int parselleft; /* copy of parsefile->lleft */ +const char *parsenextc; /* copy of parsefile->nextc */ +static char basebuf[BUFSIZ + 1];/* buffer for top level input file */ +static struct parsefile basepf = { /* top level input file */ + .nextc = basebuf, + .buf = basebuf +}; +static struct parsefile *parsefile = &basepf; /* current input file */ +int whichprompt; /* 1 == PS1, 2 == PS2 */ + +EditLine *el; /* cookie for editline package */ + +static void pushfile(void); +static int preadfd(void); +static void popstring(void); + +void +resetinput(void) +{ + popallfiles(); + parselleft = parsenleft = 0; /* clear input buffer */ +} + + + +/* + * Read a character from the script, returning PEOF on end of file. + * Nul characters in the input are silently discarded. + */ + +int +pgetc(void) +{ + return pgetc_macro(); +} + + +static int +preadfd(void) +{ + int nr; + parsenextc = parsefile->buf; + +retry: +#ifndef NO_HISTORY + if (parsefile->fd == 0 && el) { + static const char *rl_cp; + static int el_len; + + if (rl_cp == NULL) { + el_resize(el); + rl_cp = el_gets(el, &el_len); + } + if (rl_cp == NULL) + nr = el_len == 0 ? 0 : -1; + else { + nr = el_len; + if (nr > BUFSIZ) + nr = BUFSIZ; + memcpy(parsefile->buf, rl_cp, nr); + if (nr != el_len) { + el_len -= nr; + rl_cp += nr; + } else + rl_cp = NULL; + } + } else +#endif + nr = read(parsefile->fd, parsefile->buf, BUFSIZ); + + if (nr <= 0) { + if (nr < 0) { + if (errno == EINTR) + goto retry; + if (parsefile->fd == 0 && errno == EWOULDBLOCK) { + int flags = fcntl(0, F_GETFL, 0); + if (flags >= 0 && flags & O_NONBLOCK) { + flags &=~ O_NONBLOCK; + if (fcntl(0, F_SETFL, flags) >= 0) { + out2fmt_flush("sh: turning off NDELAY mode\n"); + goto retry; + } + } + } + } + nr = -1; + } + return nr; +} + +/* + * Refill the input buffer and return the next input character: + * + * 1) If a string was pushed back on the input, pop it; + * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading + * from a string so we can't refill the buffer, return EOF. + * 3) If there is more in this buffer, use it else call read to fill it. + * 4) Process input up to the next newline, deleting nul characters. + */ + +int +preadbuffer(void) +{ + char *p, *q, *r, *end; + char savec; + + while (parsefile->strpush) { + /* + * Add a space to the end of an alias to ensure that the + * alias remains in use while parsing its last word. + * This avoids alias recursions. + */ + if (parsenleft == -1 && parsefile->strpush->ap != NULL) + return ' '; + popstring(); + if (--parsenleft >= 0) + return (*parsenextc++); + } + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return PEOF; + +again: + if (parselleft <= 0) { + if ((parselleft = preadfd()) == -1) { + parselleft = parsenleft = EOF_NLEFT; + return PEOF; + } + } + + p = parsefile->buf + (parsenextc - parsefile->buf); + end = p + parselleft; + *end = '\0'; + q = strchrnul(p, '\n'); + if (q != end && *q == '\0') { + /* delete nul characters */ + for (r = q; q != end; q++) { + if (*q != '\0') + *r++ = *q; + } + parselleft -= end - r; + if (parselleft == 0) + goto again; + end = p + parselleft; + *end = '\0'; + q = strchrnul(p, '\n'); + } + if (q == end) { + parsenleft = parselleft; + parselleft = 0; + } else /* *q == '\n' */ { + q++; + parsenleft = q - parsenextc; + parselleft -= parsenleft; + } + parsenleft--; + + savec = *q; + *q = '\0'; + +#ifndef NO_HISTORY + if (parsefile->fd == 0 && hist && + parsenextc[strspn(parsenextc, " \t\n")] != '\0') { + HistEvent he; + INTOFF; + history(hist, &he, whichprompt == 1 ? H_ENTER : H_ADD, + parsenextc); + INTON; + } +#endif + + if (vflag) { + out2str(parsenextc); + flushout(out2); + } + + *q = savec; + + return *parsenextc++; +} + +/* + * Returns if we are certain we are at EOF. Does not cause any more input + * to be read from the outside world. + */ + +int +preadateof(void) +{ + if (parsenleft > 0) + return 0; + if (parsefile->strpush) + return 0; + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return 1; + return 0; +} + +/* + * Undo the last call to pgetc. Only one character may be pushed back. + * PEOF may be pushed back. + */ + +void +pungetc(void) +{ + parsenleft++; + parsenextc--; +} + +/* + * Push a string back onto the input at this current parsefile level. + * We handle aliases this way. + */ +void +pushstring(const char *s, int len, struct alias *ap) +{ + struct strpush *sp; + + INTOFF; +/*out2fmt_flush("*** calling pushstring: %s, %d\n", s, len);*/ + if (parsefile->strpush) { + sp = ckmalloc(sizeof (struct strpush)); + sp->prev = parsefile->strpush; + parsefile->strpush = sp; + } else + sp = parsefile->strpush = &(parsefile->basestrpush); + sp->prevstring = parsenextc; + sp->prevnleft = parsenleft; + sp->prevlleft = parselleft; + sp->ap = ap; + if (ap) + ap->flag |= ALIASINUSE; + parsenextc = s; + parsenleft = len; + INTON; +} + +static void +popstring(void) +{ + struct strpush *sp = parsefile->strpush; + + INTOFF; + if (sp->ap) { + if (parsenextc != sp->ap->val && + (parsenextc[-1] == ' ' || parsenextc[-1] == '\t')) + forcealias(); + sp->ap->flag &= ~ALIASINUSE; + } + parsenextc = sp->prevstring; + parsenleft = sp->prevnleft; + parselleft = sp->prevlleft; +/*out2fmt_flush("*** calling popstring: restoring to '%s'\n", parsenextc);*/ + parsefile->strpush = sp->prev; + if (sp != &(parsefile->basestrpush)) + ckfree(sp); + INTON; +} + +/* + * Set the input to take input from a file. If push is set, push the + * old input onto the stack first. + */ + +void +setinputfile(const char *fname, int push) +{ + int fd; + int fd2; + + INTOFF; + if ((fd = open(fname, O_RDONLY | O_CLOEXEC)) < 0) + error("cannot open %s: %s", fname, strerror(errno)); + if (fd < 10) { + fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 10); + close(fd); + if (fd2 < 0) + error("Out of file descriptors"); + fd = fd2; + } + setinputfd(fd, push); + INTON; +} + + +/* + * Like setinputfile, but takes an open file descriptor (which should have + * its FD_CLOEXEC flag already set). Call this with interrupts off. + */ + +void +setinputfd(int fd, int push) +{ + if (push) { + pushfile(); + parsefile->buf = ckmalloc(BUFSIZ + 1); + } + if (parsefile->fd > 0) + close(parsefile->fd); + parsefile->fd = fd; + if (parsefile->buf == NULL) + parsefile->buf = ckmalloc(BUFSIZ + 1); + parselleft = parsenleft = 0; + plinno = 1; +} + + +/* + * Like setinputfile, but takes input from a string. + */ + +void +setinputstring(const char *string, int push) +{ + INTOFF; + if (push) + pushfile(); + parsenextc = string; + parselleft = parsenleft = strlen(string); + parsefile->buf = NULL; + plinno = 1; + INTON; +} + + + +/* + * To handle the "." command, a stack of input files is used. Pushfile + * adds a new entry to the stack and popfile restores the previous level. + */ + +static void +pushfile(void) +{ + struct parsefile *pf; + + parsefile->nleft = parsenleft; + parsefile->lleft = parselleft; + parsefile->nextc = parsenextc; + parsefile->linno = plinno; + pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile)); + pf->prev = parsefile; + pf->fd = -1; + pf->strpush = NULL; + pf->basestrpush.prev = NULL; + parsefile = pf; +} + + +void +popfile(void) +{ + struct parsefile *pf = parsefile; + + INTOFF; + if (pf->fd >= 0) + close(pf->fd); + if (pf->buf) + ckfree(pf->buf); + while (pf->strpush) + popstring(); + parsefile = pf->prev; + ckfree(pf); + parsenleft = parsefile->nleft; + parselleft = parsefile->lleft; + parsenextc = parsefile->nextc; + plinno = parsefile->linno; + INTON; +} + + +/* + * Return current file (to go back to it later using popfilesupto()). + */ + +struct parsefile * +getcurrentfile(void) +{ + return parsefile; +} + + +/* + * Pop files until the given file is on top again. Useful for regular + * builtins that read shell commands from files or strings. + * If the given file is not an active file, an error is raised. + */ + +void +popfilesupto(struct parsefile *file) +{ + while (parsefile != file && parsefile != &basepf) + popfile(); + if (parsefile != file) + error("popfilesupto() misused"); +} + +/* + * Return to top level. + */ + +void +popallfiles(void) +{ + while (parsefile != &basepf) + popfile(); +} + + + +/* + * Close the file(s) that the shell is reading commands from. Called + * after a fork is done. + */ + +void +closescript(void) +{ + popallfiles(); + if (parsefile->fd > 0) { + close(parsefile->fd); + parsefile->fd = 0; + } +} diff --git a/bin/1sh/input.h b/bin/1sh/input.h new file mode 100644 index 00000000..1d0047cc --- /dev/null +++ b/bin/1sh/input.h @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)input.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/input.h 314436 2017-02-28 23:42:47Z imp $ + */ + +/* PEOF (the end of file marker) is defined in syntax.h */ + +/* + * The input line number. Input.c just defines this variable, and saves + * and restores it when files are pushed and popped. The user of this + * package must set its value. + */ +extern int plinno; +extern int parsenleft; /* number of characters left in input buffer */ +extern const char *parsenextc; /* next character in input buffer */ + +struct alias; +struct parsefile; + +void resetinput(void); +int pgetc(void); +int preadbuffer(void); +int preadateof(void); +void pungetc(void); +void pushstring(const char *, int, struct alias *); +void setinputfile(const char *, int); +void setinputfd(int, int); +void setinputstring(const char *, int); +void popfile(void); +struct parsefile *getcurrentfile(void); +void popfilesupto(struct parsefile *); +void popallfiles(void); +void closescript(void); + +#define pgetc_macro() (--parsenleft >= 0? *parsenextc++ : preadbuffer()) diff --git a/bin/1sh/jobs.c b/bin/1sh/jobs.c new file mode 100644 index 00000000..aa06e7de --- /dev/null +++ b/bin/1sh/jobs.c @@ -0,0 +1,1569 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/jobs.c 345487 2019-03-24 22:10:26Z jilles $"); + +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> + +#include "shell.h" +#if JOBS +#include <termios.h> +#undef CEOF /* syntax.h redefines this */ +#endif +#include "redir.h" +#include "exec.h" +#include "show.h" +#include "main.h" +#include "parser.h" +#include "nodes.h" +#include "jobs.h" +#include "options.h" +#include "trap.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "var.h" +#include "builtins.h" + + +/* + * A job structure contains information about a job. A job is either a + * single process or a set of processes contained in a pipeline. In the + * latter case, pidlist will be non-NULL, and will point to a -1 terminated + * array of pids. + */ + +struct procstat { + pid_t pid; /* process id */ + int status; /* status flags (defined above) */ + char *cmd; /* text of command being run */ +}; + + +/* states */ +#define JOBSTOPPED 1 /* all procs are stopped */ +#define JOBDONE 2 /* all procs are completed */ + + +struct job { + struct procstat ps0; /* status of process */ + struct procstat *ps; /* status or processes when more than one */ + short nprocs; /* number of processes */ + pid_t pgrp; /* process group of this job */ + char state; /* true if job is finished */ + char used; /* true if this entry is in used */ + char changed; /* true if status has changed */ + char foreground; /* true if running in the foreground */ + char remembered; /* true if $! referenced */ + char pipefail; /* pass any non-zero status */ +#if JOBS + char jobctl; /* job running under job control */ + struct job *next; /* job used after this one */ +#endif +}; + + +static struct job *jobtab; /* array of jobs */ +static int njobs; /* size of array */ +static pid_t backgndpid = -1; /* pid of last background process */ +static struct job *bgjob = NULL; /* last background process */ +#if JOBS +static struct job *jobmru; /* most recently used job list */ +static pid_t initialpgrp; /* pgrp of shell on invocation */ +#endif +static int ttyfd = -1; + +/* mode flags for dowait */ +#define DOWAIT_BLOCK 0x1 /* wait until a child exits */ +#define DOWAIT_SIG 0x2 /* if DOWAIT_BLOCK, abort on signal */ +#define DOWAIT_SIG_TRAP 0x4 /* if DOWAIT_SIG, abort on trapped signal only */ + +#if JOBS +static void restartjob(struct job *); +#endif +static void freejob(struct job *); +static int waitcmdloop(struct job *); +static struct job *getjob_nonotfound(const char *); +static struct job *getjob(const char *); +pid_t killjob(const char *, int); +static pid_t dowait(int, struct job *); +static void checkzombies(void); +static void cmdtxt(union node *); +static void cmdputs(const char *); +#if JOBS +static void setcurjob(struct job *); +static void deljob(struct job *); +static struct job *getcurjob(struct job *); +#endif +static int getjobstatus(const struct job *); +static void printjobcmd(struct job *); +static void showjob(struct job *, int); + + +/* + * Turn job control on and off. + */ + +static int jobctl; + +#if JOBS +static void +jobctl_notty(void) +{ + if (ttyfd >= 0) { + close(ttyfd); + ttyfd = -1; + } + if (!iflag) { + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + jobctl = 1; + return; + } + out2fmt_flush("sh: can't access tty; job control turned off\n"); + mflag = 0; +} + +void +setjobctl(int on) +{ + int i; + + if (on == jobctl || rootshell == 0) + return; + if (on) { + if (ttyfd != -1) + close(ttyfd); + if ((ttyfd = open(_PATH_TTY, O_RDWR | O_CLOEXEC)) < 0) { + i = 0; + while (i <= 2 && !isatty(i)) + i++; + if (i > 2 || + (ttyfd = fcntl(i, F_DUPFD_CLOEXEC, 10)) < 0) { + jobctl_notty(); + return; + } + } + if (ttyfd < 10) { + /* + * Keep our TTY file descriptor out of the way of + * the user's redirections. + */ + if ((i = fcntl(ttyfd, F_DUPFD_CLOEXEC, 10)) < 0) { + jobctl_notty(); + return; + } + close(ttyfd); + ttyfd = i; + } + do { /* while we are in the background */ + initialpgrp = tcgetpgrp(ttyfd); + if (initialpgrp < 0) { + jobctl_notty(); + return; + } + if (initialpgrp != getpgrp()) { + if (!iflag) { + initialpgrp = -1; + jobctl_notty(); + return; + } + kill(0, SIGTTIN); + continue; + } + } while (0); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + setpgid(0, rootpid); + tcsetpgrp(ttyfd, rootpid); + } else { /* turning job control off */ + setpgid(0, initialpgrp); + if (ttyfd >= 0) { + tcsetpgrp(ttyfd, initialpgrp); + close(ttyfd); + ttyfd = -1; + } + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + } + jobctl = on; +} +#endif + + +#if JOBS +int +fgcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + pid_t pgrp; + int status; + + nextopt(""); + jp = getjob(*argptr); + if (jp->jobctl == 0) + error("job not created under job control"); + printjobcmd(jp); + flushout(&output); + pgrp = jp->ps[0].pid; + if (ttyfd >= 0) + tcsetpgrp(ttyfd, pgrp); + restartjob(jp); + jp->foreground = 1; + INTOFF; + status = waitforjob(jp, (int *)NULL); + INTON; + return status; +} + + +int +bgcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + + nextopt(""); + do { + jp = getjob(*argptr); + if (jp->jobctl == 0) + error("job not created under job control"); + if (jp->state == JOBDONE) + continue; + restartjob(jp); + jp->foreground = 0; + out1fmt("[%td] ", jp - jobtab + 1); + printjobcmd(jp); + } while (*argptr != NULL && *++argptr != NULL); + return 0; +} + + +static void +restartjob(struct job *jp) +{ + struct procstat *ps; + int i; + + if (jp->state == JOBDONE) + return; + setcurjob(jp); + INTOFF; + kill(-jp->ps[0].pid, SIGCONT); + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + if (WIFSTOPPED(ps->status)) { + ps->status = -1; + jp->state = 0; + } + } + INTON; +} +#endif + + +int +jobscmd(int argc __unused, char *argv[] __unused) +{ + char *id; + int ch, mode; + + mode = SHOWJOBS_DEFAULT; + while ((ch = nextopt("lps")) != '\0') { + switch (ch) { + case 'l': + mode = SHOWJOBS_VERBOSE; + break; + case 'p': + mode = SHOWJOBS_PGIDS; + break; + case 's': + mode = SHOWJOBS_PIDS; + break; + } + } + + if (*argptr == NULL) + showjobs(0, mode); + else + while ((id = *argptr++) != NULL) + showjob(getjob(id), mode); + + return (0); +} + +static int getjobstatus(const struct job *jp) +{ + int i, status; + + if (!jp->pipefail) + return (jp->ps[jp->nprocs - 1].status); + for (i = jp->nprocs - 1; i >= 0; i--) { + status = jp->ps[i].status; + if (status != 0) + return (status); + } + return (0); +} + +static void +printjobcmd(struct job *jp) +{ + struct procstat *ps; + int i; + + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + out1str(ps->cmd); + if (i > 0) + out1str(" | "); + } + out1c('\n'); +} + +static void +showjob(struct job *jp, int mode) +{ + char s[64]; + char statebuf[16]; + const char *statestr, *coredump; + struct procstat *ps; + struct job *j; + int col, curr, i, jobno, prev, procno, status; + char c; + + procno = (mode == SHOWJOBS_PGIDS) ? 1 : jp->nprocs; + jobno = jp - jobtab + 1; + curr = prev = 0; +#if JOBS + if ((j = getcurjob(NULL)) != NULL) { + curr = j - jobtab + 1; + if ((j = getcurjob(j)) != NULL) + prev = j - jobtab + 1; + } +#endif + coredump = ""; + status = getjobstatus(jp); + if (jp->state == 0) { + statestr = "Running"; +#if JOBS + } else if (jp->state == JOBSTOPPED) { + ps = jp->ps + jp->nprocs - 1; + while (!WIFSTOPPED(ps->status) && ps > jp->ps) + ps--; + if (WIFSTOPPED(ps->status)) + i = WSTOPSIG(ps->status); + else + i = -1; + statestr = strsignal(i); + if (statestr == NULL) + statestr = "Suspended"; +#endif + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == 0) + statestr = "Done"; + else { + fmtstr(statebuf, sizeof(statebuf), "Done(%d)", + WEXITSTATUS(status)); + statestr = statebuf; + } + } else { + i = WTERMSIG(status); + statestr = strsignal(i); + if (statestr == NULL) + statestr = "Unknown signal"; + if (WCOREDUMP(status)) + coredump = " (core dumped)"; + } + + for (ps = jp->ps ; procno > 0 ; ps++, procno--) { /* for each process */ + if (mode == SHOWJOBS_PIDS || mode == SHOWJOBS_PGIDS) { + out1fmt("%d\n", (int)ps->pid); + continue; + } + if (mode != SHOWJOBS_VERBOSE && ps != jp->ps) + continue; + if (jobno == curr && ps == jp->ps) + c = '+'; + else if (jobno == prev && ps == jp->ps) + c = '-'; + else + c = ' '; + if (ps == jp->ps) + fmtstr(s, 64, "[%d] %c ", jobno, c); + else + fmtstr(s, 64, " %c ", c); + out1str(s); + col = strlen(s); + if (mode == SHOWJOBS_VERBOSE) { + fmtstr(s, 64, "%d ", (int)ps->pid); + out1str(s); + col += strlen(s); + } + if (ps == jp->ps) { + out1str(statestr); + out1str(coredump); + col += strlen(statestr) + strlen(coredump); + } + do { + out1c(' '); + col++; + } while (col < 30); + if (mode == SHOWJOBS_VERBOSE) { + out1str(ps->cmd); + out1c('\n'); + } else + printjobcmd(jp); + } +} + +/* + * Print a list of jobs. If "change" is nonzero, only print jobs whose + * statuses have changed since the last call to showjobs. + * + * If the shell is interrupted in the process of creating a job, the + * result may be a job structure containing zero processes. Such structures + * will be freed here. + */ + +void +showjobs(int change, int mode) +{ + int jobno; + struct job *jp; + + TRACE(("showjobs(%d) called\n", change)); + checkzombies(); + for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) { + if (! jp->used) + continue; + if (jp->nprocs == 0) { + freejob(jp); + continue; + } + if (change && ! jp->changed) + continue; + showjob(jp, mode); + if (mode == SHOWJOBS_DEFAULT || mode == SHOWJOBS_VERBOSE) { + jp->changed = 0; + /* Hack: discard jobs for which $! has not been + * referenced in interactive mode when they terminate. + */ + if (jp->state == JOBDONE && !jp->remembered && + (iflag || jp != bgjob)) { + freejob(jp); + } + } + } +} + + +/* + * Mark a job structure as unused. + */ + +static void +freejob(struct job *jp) +{ + struct procstat *ps; + int i; + + INTOFF; + if (bgjob == jp) + bgjob = NULL; + for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) { + if (ps->cmd != nullstr) + ckfree(ps->cmd); + } + if (jp->ps != &jp->ps0) + ckfree(jp->ps); + jp->used = 0; +#if JOBS + deljob(jp); +#endif + INTON; +} + + + +int +waitcmd(int argc __unused, char **argv __unused) +{ + struct job *job; + int retval; + + nextopt(""); + if (*argptr == NULL) + return (waitcmdloop(NULL)); + + do { + job = getjob_nonotfound(*argptr); + if (job == NULL) + retval = 127; + else + retval = waitcmdloop(job); + argptr++; + } while (*argptr != NULL); + + return (retval); +} + +static int +waitcmdloop(struct job *job) +{ + int status, retval, sig; + struct job *jp; + + /* + * Loop until a process is terminated or stopped, or a SIGINT is + * received. + */ + + do { + if (job != NULL) { + if (job->state == JOBDONE) { + status = getjobstatus(job); + if (WIFEXITED(status)) + retval = WEXITSTATUS(status); + else + retval = WTERMSIG(status) + 128; + if (! iflag || ! job->changed) + freejob(job); + else { + job->remembered = 0; + if (job == bgjob) + bgjob = NULL; + } + return retval; + } + } else { + for (jp = jobtab ; jp < jobtab + njobs; jp++) + if (jp->used && jp->state == JOBDONE) { + if (! iflag || ! jp->changed) + freejob(jp); + else { + jp->remembered = 0; + if (jp == bgjob) + bgjob = NULL; + } + } + for (jp = jobtab ; ; jp++) { + if (jp >= jobtab + njobs) { /* no running procs */ + return 0; + } + if (jp->used && jp->state == 0) + break; + } + } + } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1); + + sig = pendingsig_waitcmd; + pendingsig_waitcmd = 0; + return sig + 128; +} + + + +int +jobidcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + int i; + + nextopt(""); + jp = getjob(*argptr); + for (i = 0 ; i < jp->nprocs ; ) { + out1fmt("%d", (int)jp->ps[i].pid); + out1c(++i < jp->nprocs? ' ' : '\n'); + } + return 0; +} + + + +/* + * Convert a job name to a job structure. + */ + +static struct job * +getjob_nonotfound(const char *name) +{ + int jobno; + struct job *found, *jp; + size_t namelen; + pid_t pid; + int i; + + if (name == NULL) { +#if JOBS + name = "%+"; +#else + error("No current job"); +#endif + } + if (name[0] == '%') { + if (is_digit(name[1])) { + jobno = number(name + 1); + if (jobno > 0 && jobno <= njobs + && jobtab[jobno - 1].used != 0) + return &jobtab[jobno - 1]; +#if JOBS + } else if ((name[1] == '%' || name[1] == '+') && + name[2] == '\0') { + if ((jp = getcurjob(NULL)) == NULL) + error("No current job"); + return (jp); + } else if (name[1] == '-' && name[2] == '\0') { + if ((jp = getcurjob(NULL)) == NULL || + (jp = getcurjob(jp)) == NULL) + error("No previous job"); + return (jp); +#endif + } else if (name[1] == '?') { + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && strstr(jp->ps[0].cmd, name + 2) != NULL) { + if (found) + error("%s: ambiguous", name); + found = jp; + } + } + if (found != NULL) + return (found); + } else { + namelen = strlen(name); + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && strncmp(jp->ps[0].cmd, name + 1, + namelen - 1) == 0) { + if (found) + error("%s: ambiguous", name); + found = jp; + } + } + if (found) + return found; + } + } else if (is_number(name)) { + pid = (pid_t)number(name); + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && jp->ps[jp->nprocs - 1].pid == pid) + return jp; + } + } + return NULL; +} + + +static struct job * +getjob(const char *name) +{ + struct job *jp; + + jp = getjob_nonotfound(name); + if (jp == NULL) + error("No such job: %s", name); + return (jp); +} + + +int +killjob(const char *name, int sig) +{ + struct job *jp; + int i, ret; + + jp = getjob(name); + if (jp->state == JOBDONE) + return 0; + if (jp->jobctl) + return kill(-jp->ps[0].pid, sig); + ret = -1; + errno = ESRCH; + for (i = 0; i < jp->nprocs; i++) + if (jp->ps[i].status == -1 || WIFSTOPPED(jp->ps[i].status)) { + if (kill(jp->ps[i].pid, sig) == 0) + ret = 0; + } else + ret = 0; + return ret; +} + +/* + * Return a new job structure, + */ + +struct job * +makejob(union node *node __unused, int nprocs) +{ + int i; + struct job *jp; + + for (i = njobs, jp = jobtab ; ; jp++) { + if (--i < 0) { + INTOFF; + if (njobs == 0) { + jobtab = ckmalloc(4 * sizeof jobtab[0]); +#if JOBS + jobmru = NULL; +#endif + } else { + jp = ckmalloc((njobs + 4) * sizeof jobtab[0]); + memcpy(jp, jobtab, njobs * sizeof jp[0]); +#if JOBS + /* Relocate `next' pointers and list head */ + if (jobmru != NULL) + jobmru = &jp[jobmru - jobtab]; + for (i = 0; i < njobs; i++) + if (jp[i].next != NULL) + jp[i].next = &jp[jp[i].next - + jobtab]; +#endif + if (bgjob != NULL) + bgjob = &jp[bgjob - jobtab]; + /* Relocate `ps' pointers */ + for (i = 0; i < njobs; i++) + if (jp[i].ps == &jobtab[i].ps0) + jp[i].ps = &jp[i].ps0; + ckfree(jobtab); + jobtab = jp; + } + jp = jobtab + njobs; + for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0) + ; + INTON; + break; + } + if (jp->used == 0) + break; + } + INTOFF; + jp->state = 0; + jp->used = 1; + jp->changed = 0; + jp->nprocs = 0; + jp->foreground = 0; + jp->remembered = 0; + jp->pipefail = pipefailflag; +#if JOBS + jp->jobctl = jobctl; + jp->next = NULL; +#endif + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); + } else { + jp->ps = &jp->ps0; + } + INTON; + TRACE(("makejob(%p, %d) returns %%%td\n", (void *)node, nprocs, + jp - jobtab + 1)); + return jp; +} + +#if JOBS +static void +setcurjob(struct job *cj) +{ + struct job *jp, *prev; + + for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { + if (jp == cj) { + if (prev != NULL) + prev->next = jp->next; + else + jobmru = jp->next; + jp->next = jobmru; + jobmru = cj; + return; + } + } + cj->next = jobmru; + jobmru = cj; +} + +static void +deljob(struct job *j) +{ + struct job *jp, *prev; + + for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { + if (jp == j) { + if (prev != NULL) + prev->next = jp->next; + else + jobmru = jp->next; + return; + } + } +} + +/* + * Return the most recently used job that isn't `nj', and preferably one + * that is stopped. + */ +static struct job * +getcurjob(struct job *nj) +{ + struct job *jp; + + /* Try to find a stopped one.. */ + for (jp = jobmru; jp != NULL; jp = jp->next) + if (jp->used && jp != nj && jp->state == JOBSTOPPED) + return (jp); + /* Otherwise the most recently used job that isn't `nj' */ + for (jp = jobmru; jp != NULL; jp = jp->next) + if (jp->used && jp != nj) + return (jp); + + return (NULL); +} + +#endif + +/* + * Fork of a subshell. If we are doing job control, give the subshell its + * own process group. Jp is a job structure that the job is to be added to. + * N is the command that will be evaluated by the child. Both jp and n may + * be NULL. The mode parameter can be one of the following: + * FORK_FG - Fork off a foreground process. + * FORK_BG - Fork off a background process. + * FORK_NOJOB - Like FORK_FG, but don't give the process its own + * process group even if job control is on. + * + * When job control is turned off, background processes have their standard + * input redirected to /dev/null (except for the second and later processes + * in a pipeline). + */ + +pid_t +forkshell(struct job *jp, union node *n, int mode) +{ + pid_t pid; + pid_t pgrp; + + TRACE(("forkshell(%%%td, %p, %d) called\n", jp - jobtab, (void *)n, + mode)); + INTOFF; + if (mode == FORK_BG && (jp == NULL || jp->nprocs == 0)) + checkzombies(); + flushall(); + pid = fork(); + if (pid == -1) { + TRACE(("Fork failed, errno=%d\n", errno)); + INTON; + error("Cannot fork: %s", strerror(errno)); + } + if (pid == 0) { + struct job *p; + int wasroot; + int i; + + TRACE(("Child shell %d\n", (int)getpid())); + wasroot = rootshell; + rootshell = 0; + handler = &main_handler; + closescript(); + INTON; + forcelocal = 0; + clear_traps(); +#if JOBS + jobctl = 0; /* do job control only in root shell */ + if (wasroot && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = getpid(); + else + pgrp = jp->ps[0].pid; + if (setpgid(0, pgrp) == 0 && mode == FORK_FG && + ttyfd >= 0) { + /*** this causes superfluous TIOCSPGRPS ***/ + if (tcsetpgrp(ttyfd, pgrp) < 0) + error("tcsetpgrp failed, errno=%d", errno); + } + setsignal(SIGTSTP); + setsignal(SIGTTOU); + } else if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(_PATH_DEVNULL, O_RDONLY) != 0) + error("cannot open %s: %s", + _PATH_DEVNULL, strerror(errno)); + } + } +#else + if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(_PATH_DEVNULL, O_RDONLY) != 0) + error("cannot open %s: %s", + _PATH_DEVNULL, strerror(errno)); + } + } +#endif + INTOFF; + for (i = njobs, p = jobtab ; --i >= 0 ; p++) + if (p->used) + freejob(p); + INTON; + if (wasroot && iflag) { + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); + } + return pid; + } + if (rootshell && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + setpgid(pid, pgrp); + } + if (mode == FORK_BG) { + if (bgjob != NULL && bgjob->state == JOBDONE && + !bgjob->remembered && !iflag) + freejob(bgjob); + backgndpid = pid; /* set $! */ + bgjob = jp; + } + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; + if (iflag && rootshell && n) + ps->cmd = commandtext(n); + jp->foreground = mode == FORK_FG; +#if JOBS + setcurjob(jp); +#endif + } + INTON; + TRACE(("In parent shell: child = %d\n", (int)pid)); + return pid; +} + + +pid_t +vforkexecshell(struct job *jp, char **argv, char **envp, const char *path, int idx, int pip[2]) +{ + pid_t pid; + struct jmploc jmploc; + struct jmploc *savehandler; + + TRACE(("vforkexecshell(%%%td, %s, %p) called\n", jp - jobtab, argv[0], + (void *)pip)); + INTOFF; + flushall(); + savehandler = handler; + pid = vfork(); + if (pid == -1) { + TRACE(("Vfork failed, errno=%d\n", errno)); + INTON; + error("Cannot fork: %s", strerror(errno)); + } + if (pid == 0) { + TRACE(("Child shell %d\n", (int)getpid())); + if (setjmp(jmploc.loc)) + _exit(exception == EXEXEC ? exerrno : 2); + if (pip != NULL) { + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + } + handler = &jmploc; + shellexec(argv, envp, path, idx); + } + handler = savehandler; + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; + jp->foreground = 1; +#if JOBS + setcurjob(jp); +#endif + } + INTON; + TRACE(("In parent shell: child = %d\n", (int)pid)); + return pid; +} + + +/* + * Wait for job to finish. + * + * Under job control we have the problem that while a child process is + * running interrupts generated by the user are sent to the child but not + * to the shell. This means that an infinite loop started by an inter- + * active user may be hard to kill. With job control turned off, an + * interactive user may place an interactive program inside a loop. If + * the interactive program catches interrupts, the user doesn't want + * these interrupts to also abort the loop. The approach we take here + * is to have the shell ignore interrupt signals while waiting for a + * foreground process to terminate, and then send itself an interrupt + * signal if the child process was terminated by an interrupt signal. + * Unfortunately, some programs want to do a bit of cleanup and then + * exit on interrupt; unless these processes terminate themselves by + * sending a signal to themselves (instead of calling exit) they will + * confuse this approach. + */ + +int +waitforjob(struct job *jp, int *signaled) +{ +#if JOBS + int propagate_int = jp->jobctl && jp->foreground; +#endif + int status; + int st; + + INTOFF; + TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1)); + while (jp->state == 0) + if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG | + DOWAIT_SIG_TRAP : 0), jp) == -1) + dotrap(); +#if JOBS + if (jp->jobctl) { + if (ttyfd >= 0 && tcsetpgrp(ttyfd, rootpid) < 0) + error("tcsetpgrp failed, errno=%d\n", errno); + } + if (jp->state == JOBSTOPPED) + setcurjob(jp); +#endif + status = getjobstatus(jp); + if (signaled != NULL) + *signaled = WIFSIGNALED(status); + /* convert to 8 bits */ + if (WIFEXITED(status)) + st = WEXITSTATUS(status); +#if JOBS + else if (WIFSTOPPED(status)) + st = WSTOPSIG(status) + 128; +#endif + else + st = WTERMSIG(status) + 128; + if (! JOBS || jp->state == JOBDONE) + freejob(jp); + if (int_pending()) { + if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGINT) + CLEAR_PENDING_INT; + } +#if JOBS + else if (rootshell && propagate_int && + WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) + kill(getpid(), SIGINT); +#endif + INTON; + return st; +} + + +static void +dummy_handler(int sig __unused) +{ +} + +/* + * Wait for a process to terminate. + */ + +static pid_t +dowait(int mode, struct job *job) +{ + struct sigaction sa, osa; + sigset_t mask, omask; + pid_t pid; + int status; + struct procstat *sp; + struct job *jp; + struct job *thisjob; + const char *sigstr; + int done; + int stopped; + int sig; + int coredump; + int wflags; + int restore_sigchld; + + TRACE(("dowait(%d, %p) called\n", mode, job)); + restore_sigchld = 0; + if ((mode & DOWAIT_SIG) != 0) { + sigfillset(&mask); + sigprocmask(SIG_BLOCK, &mask, &omask); + INTOFF; + if (!issigchldtrapped()) { + restore_sigchld = 1; + sa.sa_handler = dummy_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGCHLD, &sa, &osa); + } + } + do { +#if JOBS + if (iflag) + wflags = WUNTRACED | WCONTINUED; + else +#endif + wflags = 0; + if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK) + wflags |= WNOHANG; + pid = wait3(&status, wflags, (struct rusage *)NULL); + TRACE(("wait returns %d, status=%d\n", (int)pid, status)); + if (pid == 0 && (mode & DOWAIT_SIG) != 0) { + pid = -1; + if (((mode & DOWAIT_SIG_TRAP) != 0 ? + pendingsig : pendingsig_waitcmd) != 0) { + errno = EINTR; + break; + } + sigsuspend(&omask); + if (int_pending()) + break; + } + } while (pid == -1 && errno == EINTR); + if (pid == -1 && errno == ECHILD && job != NULL) + job->state = JOBDONE; + if ((mode & DOWAIT_SIG) != 0) { + if (restore_sigchld) + sigaction(SIGCHLD, &osa, NULL); + sigprocmask(SIG_SETMASK, &omask, NULL); + INTON; + } + if (pid <= 0) + return pid; + INTOFF; + thisjob = NULL; + for (jp = jobtab ; jp < jobtab + njobs ; jp++) { + if (jp->used && jp->nprocs > 0) { + done = 1; + stopped = 1; + for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) { + if (sp->pid == -1) + continue; + if (sp->pid == pid && (sp->status == -1 || + WIFSTOPPED(sp->status))) { + TRACE(("Changing status of proc %d from 0x%x to 0x%x\n", + (int)pid, sp->status, + status)); + if (WIFCONTINUED(status)) { + sp->status = -1; + jp->state = 0; + } else + sp->status = status; + thisjob = jp; + } + if (sp->status == -1) + stopped = 0; + else if (WIFSTOPPED(sp->status)) + done = 0; + } + if (stopped) { /* stopped or done */ + int state = done? JOBDONE : JOBSTOPPED; + if (jp->state != state) { + TRACE(("Job %td: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state)); + jp->state = state; + if (jp != job) { + if (done && !jp->remembered && + !iflag && jp != bgjob) + freejob(jp); +#if JOBS + else if (done) + deljob(jp); +#endif + } + } + } + } + } + INTON; + if (!thisjob || thisjob->state == 0) + ; + else if ((!rootshell || !iflag || thisjob == job) && + thisjob->foreground && thisjob->state != JOBSTOPPED) { + sig = 0; + coredump = 0; + for (sp = thisjob->ps; sp < thisjob->ps + thisjob->nprocs; sp++) + if (WIFSIGNALED(sp->status)) { + sig = WTERMSIG(sp->status); + coredump = WCOREDUMP(sp->status); + } + if (sig > 0 && sig != SIGINT && sig != SIGPIPE) { + sigstr = strsignal(sig); + if (sigstr != NULL) + out2str(sigstr); + else + out2str("Unknown signal"); + if (coredump) + out2str(" (core dumped)"); + out2c('\n'); + flushout(out2); + } + } else { + TRACE(("Not printing status, rootshell=%d, job=%p\n", rootshell, job)); + thisjob->changed = 1; + } + return pid; +} + + + +/* + * return 1 if there are stopped jobs, otherwise 0 + */ +int job_warning = 0; +int +stoppedjobs(void) +{ + int jobno; + struct job *jp; + + if (job_warning) + return (0); + for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) { + if (jp->used == 0) + continue; + if (jp->state == JOBSTOPPED) { + out2fmt_flush("You have stopped jobs.\n"); + job_warning = 2; + return (1); + } + } + + return (0); +} + + +static void +checkzombies(void) +{ + while (njobs > 0 && dowait(0, NULL) > 0) + ; +} + + +int +backgndpidset(void) +{ + return backgndpid != -1; +} + + +pid_t +backgndpidval(void) +{ + if (bgjob != NULL && !forcelocal) + bgjob->remembered = 1; + return backgndpid; +} + +/* + * Return a string identifying a command (to be printed by the + * jobs command. + */ + +static char *cmdnextc; +static int cmdnleft; +#define MAXCMDTEXT 200 + +char * +commandtext(union node *n) +{ + char *name; + + cmdnextc = name = ckmalloc(MAXCMDTEXT); + cmdnleft = MAXCMDTEXT - 4; + cmdtxt(n); + *cmdnextc = '\0'; + return name; +} + + +static void +cmdtxtdogroup(union node *n) +{ + cmdputs("; do "); + cmdtxt(n); + cmdputs("; done"); +} + + +static void +cmdtxtredir(union node *n, const char *op, int deffd) +{ + char s[2]; + + if (n->nfile.fd != deffd) { + s[0] = n->nfile.fd + '0'; + s[1] = '\0'; + cmdputs(s); + } + cmdputs(op); + if (n->type == NTOFD || n->type == NFROMFD) { + if (n->ndup.dupfd >= 0) + s[0] = n->ndup.dupfd + '0'; + else + s[0] = '-'; + s[1] = '\0'; + cmdputs(s); + } else { + cmdtxt(n->nfile.fname); + } +} + + +static void +cmdtxt(union node *n) +{ + union node *np; + struct nodelist *lp; + + if (n == NULL) + return; + switch (n->type) { + case NSEMI: + cmdtxt(n->nbinary.ch1); + cmdputs("; "); + cmdtxt(n->nbinary.ch2); + break; + case NAND: + cmdtxt(n->nbinary.ch1); + cmdputs(" && "); + cmdtxt(n->nbinary.ch2); + break; + case NOR: + cmdtxt(n->nbinary.ch1); + cmdputs(" || "); + cmdtxt(n->nbinary.ch2); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + cmdtxt(lp->n); + if (lp->next) + cmdputs(" | "); + } + break; + case NSUBSHELL: + cmdputs("("); + cmdtxt(n->nredir.n); + cmdputs(")"); + break; + case NREDIR: + case NBACKGND: + cmdtxt(n->nredir.n); + break; + case NIF: + cmdputs("if "); + cmdtxt(n->nif.test); + cmdputs("; then "); + cmdtxt(n->nif.ifpart); + cmdputs("..."); + break; + case NWHILE: + cmdputs("while "); + cmdtxt(n->nbinary.ch1); + cmdtxtdogroup(n->nbinary.ch2); + break; + case NUNTIL: + cmdputs("until "); + cmdtxt(n->nbinary.ch1); + cmdtxtdogroup(n->nbinary.ch2); + break; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in ..."); + break; + case NCASE: + cmdputs("case "); + cmdputs(n->ncase.expr->narg.text); + cmdputs(" in ..."); + break; + case NDEFUN: + cmdputs(n->narg.text); + cmdputs("() ..."); + break; + case NNOT: + cmdputs("! "); + cmdtxt(n->nnot.com); + break; + case NCMD: + for (np = n->ncmd.args ; np ; np = np->narg.next) { + cmdtxt(np); + if (np->narg.next) + cmdputs(" "); + } + for (np = n->ncmd.redirect ; np ; np = np->nfile.next) { + cmdputs(" "); + cmdtxt(np); + } + break; + case NARG: + cmdputs(n->narg.text); + break; + case NTO: + cmdtxtredir(n, ">", 1); + break; + case NAPPEND: + cmdtxtredir(n, ">>", 1); + break; + case NTOFD: + cmdtxtredir(n, ">&", 1); + break; + case NCLOBBER: + cmdtxtredir(n, ">|", 1); + break; + case NFROM: + cmdtxtredir(n, "<", 0); + break; + case NFROMTO: + cmdtxtredir(n, "<>", 0); + break; + case NFROMFD: + cmdtxtredir(n, "<&", 0); + break; + case NHERE: + case NXHERE: + cmdputs("<<..."); + break; + default: + cmdputs("???"); + break; + } +} + + + +static void +cmdputs(const char *s) +{ + const char *p; + char *q; + char c; + int subtype = 0; + + if (cmdnleft <= 0) + return; + p = s; + q = cmdnextc; + while ((c = *p++) != '\0') { + if (c == CTLESC) + *q++ = *p++; + else if (c == CTLVAR) { + *q++ = '$'; + if (--cmdnleft > 0) + *q++ = '{'; + subtype = *p++; + if ((subtype & VSTYPE) == VSLENGTH && --cmdnleft > 0) + *q++ = '#'; + } else if (c == '=' && subtype != 0) { + *q = "}-+?=##%%\0X"[(subtype & VSTYPE) - VSNORMAL]; + if (*q) + q++; + else + cmdnleft++; + if (((subtype & VSTYPE) == VSTRIMLEFTMAX || + (subtype & VSTYPE) == VSTRIMRIGHTMAX) && + --cmdnleft > 0) + *q = q[-1], q++; + subtype = 0; + } else if (c == CTLENDVAR) { + *q++ = '}'; + } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE) { + cmdnleft -= 5; + if (cmdnleft > 0) { + *q++ = '$'; + *q++ = '('; + *q++ = '.'; + *q++ = '.'; + *q++ = '.'; + *q++ = ')'; + } + } else if (c == CTLARI) { + cmdnleft -= 2; + if (cmdnleft > 0) { + *q++ = '$'; + *q++ = '('; + *q++ = '('; + } + p++; + } else if (c == CTLENDARI) { + if (--cmdnleft > 0) { + *q++ = ')'; + *q++ = ')'; + } + } else if (c == CTLQUOTEMARK || c == CTLQUOTEEND) + cmdnleft++; /* ignore */ + else + *q++ = c; + if (--cmdnleft <= 0) { + *q++ = '.'; + *q++ = '.'; + *q++ = '.'; + break; + } + } + cmdnextc = q; +} diff --git a/bin/1sh/jobs.h b/bin/1sh/jobs.h new file mode 100644 index 00000000..98c2eb5c --- /dev/null +++ b/bin/1sh/jobs.h @@ -0,0 +1,67 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)jobs.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/jobs.h 327475 2018-01-01 22:31:52Z jilles $ + */ + +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 + +#include <signal.h> /* for sig_atomic_t */ + +struct job; + +enum { + SHOWJOBS_DEFAULT, /* job number, status, command */ + SHOWJOBS_VERBOSE, /* job number, PID, status, command */ + SHOWJOBS_PIDS, /* PID only */ + SHOWJOBS_PGIDS /* PID of the group leader only */ +}; + +extern int job_warning; /* user was warned about stopped jobs */ + +void setjobctl(int); +void showjobs(int, int); +struct job *makejob(union node *, int); +pid_t forkshell(struct job *, union node *, int); +pid_t vforkexecshell(struct job *, char **, char **, const char *, int, int []); +int waitforjob(struct job *, int *); +int stoppedjobs(void); +int backgndpidset(void); +pid_t backgndpidval(void); +char *commandtext(union node *); + +#if ! JOBS +#define setjobctl(on) /* do nothing */ +#endif diff --git a/bin/1sh/mail.c b/bin/1sh/mail.c new file mode 100644 index 00000000..8c6b59ae --- /dev/null +++ b/bin/1sh/mail.c @@ -0,0 +1,119 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)mail.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/mail.c 336303 2018-07-15 09:14:30Z jilles $"); + +/* + * Routines to check for mail. (Perhaps make part of main.c?) + */ + +#include "shell.h" +#include "mail.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> + + +#define MAXMBOXES 10 + + +static int nmboxes; /* number of mailboxes */ +static time_t mailtime[MAXMBOXES]; /* times of mailboxes */ + + + +/* + * Print appropriate message(s) if mail has arrived. If the argument is + * nozero, then the value of MAIL has changed, so we just update the + * values. + */ + +void +chkmail(int silent) +{ + int i; + char *mpath; + char *p; + char *msg; + struct stackmark smark; + struct stat statb; + + if (silent) + nmboxes = 10; + if (nmboxes == 0) + return; + setstackmark(&smark); + mpath = stsavestr(mpathset()? mpathval() : mailval()); + for (i = 0 ; i < nmboxes ; i++) { + p = mpath; + if (*p == '\0') + break; + mpath = strchrnul(mpath, ':'); + if (*mpath != '\0') { + *mpath++ = '\0'; + if (p == mpath - 1) + continue; + } + msg = strchr(p, '%'); + if (msg != NULL) + *msg++ = '\0'; +#ifdef notdef /* this is what the System V shell claims to do (it lies) */ + if (stat(p, &statb) < 0) + statb.st_mtime = 0; + if (statb.st_mtime > mailtime[i] && ! silent) { + out2str(msg? msg : "you have mail"); + out2c('\n'); + } + mailtime[i] = statb.st_mtime; +#else /* this is what it should do */ + if (stat(p, &statb) < 0) + statb.st_size = 0; + if (statb.st_size > mailtime[i] && ! silent) { + out2str(msg? msg : "you have mail"); + out2c('\n'); + } + mailtime[i] = statb.st_size; +#endif + } + nmboxes = i; + popstackmark(&smark); +} diff --git a/bin/1sh/mail.h b/bin/1sh/mail.h new file mode 100644 index 00000000..bc124ba3 --- /dev/null +++ b/bin/1sh/mail.h @@ -0,0 +1,38 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)mail.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/mail.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +void chkmail(int); diff --git a/bin/1sh/main.c b/bin/1sh/main.c new file mode 100644 index 00000000..98f7275d --- /dev/null +++ b/bin/1sh/main.c @@ -0,0 +1,352 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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 <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/main.c 336320 2018-07-15 21:55:17Z jilles $"); + +#include <stdio.h> +#include <signal.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <locale.h> +#include <errno.h> + +#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 "mystring.h" +#include "exec.h" +#include "cd.h" +#include "redir.h" +#include "builtins.h" + +int rootpid; +int rootshell; +struct jmploc main_handler; +int localeisutf8, initial_localeisutf8; + +static void reset(void); +static void cmdloop(int); +static void read_profile(const 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; + INTOFF; + initvar(); + setstackmark(&smark); + setstackmark(&smark2); + procargs(argc, argv); + pwd_init(iflag); + INTON; + 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); + } +state4: + if (sflag || minusc == NULL) { + cmdloop(1); + } + exitshell(exitstatus); + /*NOTREACHED*/ + return 0; +} + +static void +reset(void) +{ + reseteval(); + resetinput(); +} + +/* + * 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 (pendingsig) + 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 == SKIPRETURN) + evalskip = 0; + break; + } + } + popstackmark(&smark); +} + + + +/* + * Read /etc/profile or .profile. Return on error. + */ + +static void +read_profile(const char *name) +{ + int fd; + const char *expandedname; + + expandedname = expandstr(name); + if (expandedname == NULL) + return; + INTOFF; + if ((fd = open(expandedname, O_RDONLY | O_CLOEXEC)) >= 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 *opt; + 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, &opt, 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(); +} diff --git a/bin/1sh/main.h b/bin/1sh/main.h new file mode 100644 index 00000000..329a5c8b --- /dev/null +++ b/bin/1sh/main.h @@ -0,0 +1,42 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)main.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/main.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +extern int rootpid; /* pid of main shell */ +extern int rootshell; /* true if we aren't a child of the main shell */ +extern struct jmploc main_handler; /* top level exception handler */ + +void readcmdfile(const char *); diff --git a/bin/1sh/memalloc.c b/bin/1sh/memalloc.c new file mode 100644 index 00000000..dacc8c90 --- /dev/null +++ b/bin/1sh/memalloc.c @@ -0,0 +1,344 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)memalloc.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/memalloc.c 326025 2017-11-20 19:49:47Z pfg $"); + +#include <sys/param.h> +#include "shell.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "expand.h" +#include <stdlib.h> +#include <unistd.h> + +/* + * Like malloc, but returns an error when out of space. + */ + +pointer +ckmalloc(size_t nbytes) +{ + pointer p; + + INTOFF; + p = malloc(nbytes); + INTON; + if (p == NULL) + error("Out of space"); + return p; +} + + +/* + * Same for realloc. + */ + +pointer +ckrealloc(pointer p, int nbytes) +{ + INTOFF; + p = realloc(p, nbytes); + INTON; + if (p == NULL) + error("Out of space"); + return p; +} + +void +ckfree(pointer p) +{ + INTOFF; + free(p); + INTON; +} + + +/* + * Make a copy of a string in safe storage. + */ + +char * +savestr(const char *s) +{ + char *p; + size_t len; + + len = strlen(s); + p = ckmalloc(len + 1); + memcpy(p, s, len + 1); + return p; +} + + +/* + * Parse trees for commands are allocated in lifo order, so we use a stack + * to make this more efficient, and also to avoid all sorts of exception + * handling code to handle interrupts in the middle of a parse. + * + * The size 496 was chosen because with 16-byte alignment the total size + * for the allocated block is 512. + */ + +#define MINSIZE 496 /* minimum size of a block. */ + + +struct stack_block { + struct stack_block *prev; + /* Data follows */ +}; +#define SPACE(sp) ((char*)(sp) + ALIGN(sizeof(struct stack_block))) + +static struct stack_block *stackp; +char *stacknxt; +int stacknleft; +char *sstrend; + + +static void +stnewblock(int nbytes) +{ + struct stack_block *sp; + int allocsize; + + if (nbytes < MINSIZE) + nbytes = MINSIZE; + + allocsize = ALIGN(sizeof(struct stack_block)) + ALIGN(nbytes); + + INTOFF; + sp = ckmalloc(allocsize); + sp->prev = stackp; + stacknxt = SPACE(sp); + stacknleft = allocsize - (stacknxt - (char*)sp); + sstrend = stacknxt + stacknleft; + stackp = sp; + INTON; +} + + +pointer +stalloc(int nbytes) +{ + char *p; + + nbytes = ALIGN(nbytes); + if (nbytes > stacknleft) + stnewblock(nbytes); + p = stacknxt; + stacknxt += nbytes; + stacknleft -= nbytes; + return p; +} + + +void +stunalloc(pointer p) +{ + if (p == NULL) { /*DEBUG */ + write(STDERR_FILENO, "stunalloc\n", 10); + abort(); + } + stacknleft += stacknxt - (char *)p; + stacknxt = p; +} + + +char * +stsavestr(const char *s) +{ + char *p; + size_t len; + + len = strlen(s); + p = stalloc(len + 1); + memcpy(p, s, len + 1); + return p; +} + + +void +setstackmark(struct stackmark *mark) +{ + mark->stackp = stackp; + mark->stacknxt = stacknxt; + mark->stacknleft = stacknleft; + /* Ensure this block stays in place. */ + if (stackp != NULL && stacknxt == SPACE(stackp)) + stalloc(1); +} + + +void +popstackmark(struct stackmark *mark) +{ + struct stack_block *sp; + + INTOFF; + while (stackp != mark->stackp) { + sp = stackp; + stackp = sp->prev; + ckfree(sp); + } + stacknxt = mark->stacknxt; + stacknleft = mark->stacknleft; + sstrend = stacknxt + stacknleft; + INTON; +} + + +/* + * When the parser reads in a string, it wants to stick the string on the + * stack and only adjust the stack pointer when it knows how big the + * string is. Stackblock (defined in stack.h) returns a pointer to a block + * of space on top of the stack and stackblocklen returns the length of + * this block. Growstackblock will grow this space by at least one byte, + * possibly moving it (like realloc). Grabstackblock actually allocates the + * part of the block that has been used. + */ + +static void +growstackblock(int min) +{ + char *p; + int newlen; + char *oldspace; + int oldlen; + struct stack_block *sp; + struct stack_block *oldstackp; + + if (min < stacknleft) + min = stacknleft; + if ((unsigned int)min >= + INT_MAX / 2 - ALIGN(sizeof(struct stack_block))) + error("Out of space"); + min += stacknleft; + min += ALIGN(sizeof(struct stack_block)); + newlen = 512; + while (newlen < min) + newlen <<= 1; + oldspace = stacknxt; + oldlen = stacknleft; + + if (stackp != NULL && stacknxt == SPACE(stackp)) { + INTOFF; + oldstackp = stackp; + stackp = oldstackp->prev; + sp = ckrealloc((pointer)oldstackp, newlen); + sp->prev = stackp; + stackp = sp; + stacknxt = SPACE(sp); + stacknleft = newlen - (stacknxt - (char*)sp); + sstrend = stacknxt + stacknleft; + INTON; + } else { + newlen -= ALIGN(sizeof(struct stack_block)); + p = stalloc(newlen); + if (oldlen != 0) + memcpy(p, oldspace, oldlen); + stunalloc(p); + } +} + + + +/* + * The following routines are somewhat easier to use that the above. + * The user declares a variable of type STACKSTR, which may be declared + * to be a register. The macro STARTSTACKSTR initializes things. Then + * the user uses the macro STPUTC to add characters to the string. In + * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is + * grown as necessary. When the user is done, she can just leave the + * string there and refer to it using stackblock(). Or she can allocate + * the space for it using grabstackstr(). If it is necessary to allow + * someone else to use the stack temporarily and then continue to grow + * the string, the user should use grabstack to allocate the space, and + * then call ungrabstr(p) to return to the previous mode of operation. + * + * USTPUTC is like STPUTC except that it doesn't check for overflow. + * CHECKSTACKSPACE can be called before USTPUTC to ensure that there + * is space for at least one character. + */ + +static char * +growstrstackblock(int n, int min) +{ + growstackblock(min); + return stackblock() + n; +} + +char * +growstackstr(void) +{ + int len; + + len = stackblocksize(); + return (growstrstackblock(len, 0)); +} + + +/* + * Called from CHECKSTRSPACE. + */ + +char * +makestrspace(int min, char *p) +{ + int len; + + len = p - stackblock(); + return (growstrstackblock(len, min)); +} + + +char * +stputbin(const char *data, size_t len, char *p) +{ + CHECKSTRSPACE(len, p); + memcpy(p, data, len); + return (p + len); +} + +char * +stputs(const char *data, char *p) +{ + return (stputbin(data, strlen(data), p)); +} diff --git a/bin/1sh/memalloc.h b/bin/1sh/memalloc.h new file mode 100644 index 00000000..f3acc03d --- /dev/null +++ b/bin/1sh/memalloc.h @@ -0,0 +1,88 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)memalloc.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/memalloc.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <string.h> + +struct stackmark { + struct stack_block *stackp; + char *stacknxt; + int stacknleft; +}; + + +extern char *stacknxt; +extern int stacknleft; +extern char *sstrend; + +pointer ckmalloc(size_t); +pointer ckrealloc(pointer, int); +void ckfree(pointer); +char *savestr(const char *); +pointer stalloc(int); +void stunalloc(pointer); +char *stsavestr(const char *); +void setstackmark(struct stackmark *); +void popstackmark(struct stackmark *); +char *growstackstr(void); +char *makestrspace(int, char *); +char *stputbin(const char *data, size_t len, char *p); +char *stputs(const char *data, char *p); + + + +#define stackblock() stacknxt +#define stackblocksize() stacknleft +#define grabstackblock(n) stalloc(n) +#define STARTSTACKSTR(p) p = stackblock() +#define STPUTC(c, p) do { if (p == sstrend) p = growstackstr(); *p++ = (c); } while(0) +#define CHECKSTRSPACE(n, p) { if ((size_t)(sstrend - p) < n) p = makestrspace(n, p); } +#define USTPUTC(c, p) (*p++ = (c)) +/* + * STACKSTRNUL's use is where we want to be able to turn a stack + * (non-sentinel, character counting string) into a C string, + * and later pretend the NUL is not there. + * Note: Because of STACKSTRNUL's semantics, STACKSTRNUL cannot be used + * on a stack that will grabstackstr()ed. + */ +#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 - stackblock()) +#define ungrabstackstr(s, p) stunalloc((s)) +#define STPUTBIN(s, len, p) p = stputbin((s), (len), p) +#define STPUTS(s, p) p = stputs((s), p) diff --git a/bin/1sh/miscbltin.c b/bin/1sh/miscbltin.c new file mode 100644 index 00000000..123593b0 --- /dev/null +++ b/bin/1sh/miscbltin.c @@ -0,0 +1,534 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)miscbltin.c 8.4 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/miscbltin.c 326025 2017-11-20 19:49:47Z pfg $"); + +/* + * Miscellaneous builtins. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "shell.h" +#include "options.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "syntax.h" +#include "trap.h" + +#undef eflag + +int readcmd(int, char **); +int umaskcmd(int, char **); +int ulimitcmd(int, char **); + +/* + * The read builtin. The -r option causes backslashes to be treated like + * ordinary characters. + * + * This uses unbuffered input, which may be avoidable in some cases. + * + * Note that if IFS=' :' then read x y should work so that: + * 'a b' x='a', y='b' + * ' a b ' x='a', y='b' + * ':b' x='', y='b' + * ':' x='', y='' + * '::' x='', y='' + * ': :' x='', y='' + * ':::' x='', y='::' + * ':b c:' x='', y='b c:' + */ + +int +readcmd(int argc __unused, char **argv __unused) +{ + char **ap; + int backslash; + char c; + int rflag; + char *prompt; + const char *ifs; + char *p; + int startword; + int status; + int i; + int is_ifs; + int saveall = 0; + ptrdiff_t lastnonifs, lastnonifsws; + struct timeval tv; + char *tvptr; + fd_set ifds; + ssize_t nread; + int sig; + + rflag = 0; + prompt = NULL; + tv.tv_sec = -1; + tv.tv_usec = 0; + while ((i = nextopt("erp:t:")) != '\0') { + switch(i) { + case 'p': + prompt = shoptarg; + break; + case 'e': + break; + case 'r': + rflag = 1; + break; + case 't': + tv.tv_sec = strtol(shoptarg, &tvptr, 0); + if (tvptr == shoptarg) + error("timeout value"); + switch(*tvptr) { + case 0: + case 's': + break; + case 'h': + tv.tv_sec *= 60; + /* FALLTHROUGH */ + case 'm': + tv.tv_sec *= 60; + break; + default: + error("timeout unit"); + } + break; + } + } + if (prompt && isatty(0)) { + out2str(prompt); + flushall(); + } + if (*(ap = argptr) == NULL) + error("arg count"); + if ((ifs = bltinlookup("IFS", 1)) == NULL) + ifs = " \t\n"; + + if (tv.tv_sec >= 0) { + /* + * Wait for something to become available. + */ + FD_ZERO(&ifds); + FD_SET(0, &ifds); + status = select(1, &ifds, NULL, NULL, &tv); + /* + * If there's nothing ready, return an error. + */ + if (status <= 0) { + sig = pendingsig; + return (128 + (sig != 0 ? sig : SIGALRM)); + } + } + + status = 0; + startword = 2; + backslash = 0; + STARTSTACKSTR(p); + lastnonifs = lastnonifsws = -1; + for (;;) { + nread = read(STDIN_FILENO, &c, 1); + if (nread == -1) { + if (errno == EINTR) { + sig = pendingsig; + if (sig == 0) + continue; + status = 128 + sig; + break; + } + warning("read error: %s", strerror(errno)); + status = 2; + break; + } else if (nread != 1) { + status = 1; + break; + } + if (c == '\0') + continue; + CHECKSTRSPACE(1, p); + if (backslash) { + backslash = 0; + if (c != '\n') { + startword = 0; + lastnonifs = lastnonifsws = p - stackblock(); + USTPUTC(c, p); + } + continue; + } + if (!rflag && c == '\\') { + backslash++; + continue; + } + if (c == '\n') + break; + if (strchr(ifs, c)) + is_ifs = strchr(" \t\n", c) ? 1 : 2; + else + is_ifs = 0; + + if (startword != 0) { + if (is_ifs == 1) { + /* Ignore leading IFS whitespace */ + if (saveall) + USTPUTC(c, p); + continue; + } + if (is_ifs == 2 && startword == 1) { + /* Only one non-whitespace IFS per word */ + startword = 2; + if (saveall) { + lastnonifsws = p - stackblock(); + USTPUTC(c, p); + } + continue; + } + } + + if (is_ifs == 0) { + /* append this character to the current variable */ + startword = 0; + if (saveall) + /* Not just a spare terminator */ + saveall++; + lastnonifs = lastnonifsws = p - stackblock(); + USTPUTC(c, p); + continue; + } + + /* end of variable... */ + startword = is_ifs; + + if (ap[1] == NULL) { + /* Last variable needs all IFS chars */ + saveall++; + if (is_ifs == 2) + lastnonifsws = p - stackblock(); + USTPUTC(c, p); + continue; + } + + STACKSTRNUL(p); + setvar(*ap, stackblock(), 0); + ap++; + STARTSTACKSTR(p); + lastnonifs = lastnonifsws = -1; + } + STACKSTRNUL(p); + + /* + * Remove trailing IFS chars: always remove whitespace, don't remove + * non-whitespace unless it was naked + */ + if (saveall <= 1) + lastnonifsws = lastnonifs; + stackblock()[lastnonifsws + 1] = '\0'; + setvar(*ap, stackblock(), 0); + + /* Set any remaining args to "" */ + while (*++ap != NULL) + setvar(*ap, "", 0); + return status; +} + + + +int +umaskcmd(int argc __unused, char **argv __unused) +{ + char *ap; + int mask; + int i; + int symbolic_mode = 0; + + while ((i = nextopt("S")) != '\0') { + symbolic_mode = 1; + } + + INTOFF; + mask = umask(0); + umask(mask); + INTON; + + if ((ap = *argptr) == NULL) { + if (symbolic_mode) { + char u[4], g[4], o[4]; + + i = 0; + if ((mask & S_IRUSR) == 0) + u[i++] = 'r'; + if ((mask & S_IWUSR) == 0) + u[i++] = 'w'; + if ((mask & S_IXUSR) == 0) + u[i++] = 'x'; + u[i] = '\0'; + + i = 0; + if ((mask & S_IRGRP) == 0) + g[i++] = 'r'; + if ((mask & S_IWGRP) == 0) + g[i++] = 'w'; + if ((mask & S_IXGRP) == 0) + g[i++] = 'x'; + g[i] = '\0'; + + i = 0; + if ((mask & S_IROTH) == 0) + o[i++] = 'r'; + if ((mask & S_IWOTH) == 0) + o[i++] = 'w'; + if ((mask & S_IXOTH) == 0) + o[i++] = 'x'; + o[i] = '\0'; + + out1fmt("u=%s,g=%s,o=%s\n", u, g, o); + } else { + out1fmt("%.4o\n", mask); + } + } else { + if (is_digit(*ap)) { + mask = 0; + do { + if (*ap >= '8' || *ap < '0') + error("Illegal number: %s", *argptr); + mask = (mask << 3) + (*ap - '0'); + } while (*++ap != '\0'); + umask(mask); + } else { + void *set; + INTOFF; + if ((set = setmode (ap)) == NULL) + error("Illegal number: %s", ap); + + mask = getmode (set, ~mask & 0777); + umask(~mask & 0777); + free(set); + INTON; + } + } + return 0; +} + +/* + * ulimit builtin + * + * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and + * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with + * ash by J.T. Conklin. + * + * Public domain. + */ + +struct limits { + const char *name; + const char *units; + int cmd; + short factor; /* multiply by to get rlim_{cur,max} values */ + char option; +}; + +static const struct limits limits[] = { +#ifdef RLIMIT_CPU + { "cpu time", "seconds", RLIMIT_CPU, 1, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { "file size", "512-blocks", RLIMIT_FSIZE, 512, 'f' }, +#endif +#ifdef RLIMIT_DATA + { "data seg size", "kbytes", RLIMIT_DATA, 1024, 'd' }, +#endif +#ifdef RLIMIT_STACK + { "stack size", "kbytes", RLIMIT_STACK, 1024, 's' }, +#endif +#ifdef RLIMIT_CORE + { "core file size", "512-blocks", RLIMIT_CORE, 512, 'c' }, +#endif +#ifdef RLIMIT_RSS + { "max memory size", "kbytes", RLIMIT_RSS, 1024, 'm' }, +#endif +#ifdef RLIMIT_MEMLOCK + { "locked memory", "kbytes", RLIMIT_MEMLOCK, 1024, 'l' }, +#endif +#ifdef RLIMIT_NPROC + { "max user processes", (char *)0, RLIMIT_NPROC, 1, 'u' }, +#endif +#ifdef RLIMIT_NOFILE + { "open files", (char *)0, RLIMIT_NOFILE, 1, 'n' }, +#endif +#ifdef RLIMIT_VMEM + { "virtual mem size", "kbytes", RLIMIT_VMEM, 1024, 'v' }, +#endif +#ifdef RLIMIT_SWAP + { "swap limit", "kbytes", RLIMIT_SWAP, 1024, 'w' }, +#endif +#ifdef RLIMIT_SBSIZE + { "socket buffer size", "bytes", RLIMIT_SBSIZE, 1, 'b' }, +#endif +#ifdef RLIMIT_NPTS + { "pseudo-terminals", (char *)0, RLIMIT_NPTS, 1, 'p' }, +#endif +#ifdef RLIMIT_KQUEUES + { "kqueues", (char *)0, RLIMIT_KQUEUES, 1, 'k' }, +#endif +#ifdef RLIMIT_UMTXP + { "umtx shared locks", (char *)0, RLIMIT_UMTXP, 1, 'o' }, +#endif + { (char *) 0, (char *)0, 0, 0, '\0' } +}; + +enum limithow { SOFT = 0x1, HARD = 0x2 }; + +static void +printlimit(enum limithow how, const struct rlimit *limit, + const struct limits *l) +{ + rlim_t val = 0; + + if (how & SOFT) + val = limit->rlim_cur; + else if (how & HARD) + val = limit->rlim_max; + if (val == RLIM_INFINITY) + out1str("unlimited\n"); + else + { + val /= l->factor; + out1fmt("%jd\n", (intmax_t)val); + } +} + +int +ulimitcmd(int argc __unused, char **argv __unused) +{ + rlim_t val = 0; + enum limithow how = SOFT | HARD; + const struct limits *l; + int set, all = 0; + int optc, what; + struct rlimit limit; + + what = 'f'; + while ((optc = nextopt("HSatfdsmcnuvlbpwko")) != '\0') + switch (optc) { + case 'H': + how = HARD; + break; + case 'S': + how = SOFT; + break; + case 'a': + all = 1; + break; + default: + what = optc; + } + + for (l = limits; l->name && l->option != what; l++) + ; + if (!l->name) + error("internal error (%c)", what); + + set = *argptr ? 1 : 0; + if (set) { + char *p = *argptr; + + if (all || argptr[1]) + error("too many arguments"); + if (strcmp(p, "unlimited") == 0) + val = RLIM_INFINITY; + else { + char *end; + uintmax_t uval; + + if (*p < '0' || *p > '9') + error("bad number"); + errno = 0; + uval = strtoumax(p, &end, 10); + if (errno != 0 || *end != '\0') + error("bad number"); + if (uval > UINTMAX_MAX / l->factor) + error("bad number"); + uval *= l->factor; + val = (rlim_t)uval; + if (val < 0 || (uintmax_t)val != uval || + val == RLIM_INFINITY) + error("bad number"); + } + } + if (all) { + for (l = limits; l->name; l++) { + char optbuf[40]; + if (getrlimit(l->cmd, &limit) < 0) + error("can't get limit: %s", strerror(errno)); + + if (l->units) + snprintf(optbuf, sizeof(optbuf), + "(%s, -%c) ", l->units, l->option); + else + snprintf(optbuf, sizeof(optbuf), + "(-%c) ", l->option); + out1fmt("%-18s %18s ", l->name, optbuf); + printlimit(how, &limit, l); + } + return 0; + } + + if (getrlimit(l->cmd, &limit) < 0) + error("can't get limit: %s", strerror(errno)); + if (set) { + if (how & SOFT) + limit.rlim_cur = val; + if (how & HARD) + limit.rlim_max = val; + if (setrlimit(l->cmd, &limit) < 0) + error("bad limit: %s", strerror(errno)); + } else + printlimit(how, &limit, l); + return 0; +} diff --git a/bin/1sh/mkbuiltins b/bin/1sh/mkbuiltins new file mode 100755 index 00000000..4032b2d2 --- /dev/null +++ b/bin/1sh/mkbuiltins @@ -0,0 +1,137 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. 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. +# +# @(#)mkbuiltins 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/mkbuiltins 328934 2018-02-06 15:41:35Z arichardson $ + +temp=`mktemp -t ka` +havehist=1 +if [ "X$1" = "X-h" ]; then + havehist=0 + shift +fi +srcdir=$1 +havejobs=0 +if grep '^#define[ ]*JOBS[ ]*1' $srcdir/shell.h > /dev/null +then havejobs=1 +fi +exec > builtins.c +cat <<\! +/* + * This file was generated by the mkbuiltins program. + */ + +#include <stdlib.h> +#include "shell.h" +#include "builtins.h" + +! +awk '/^[^#]/ {if(('$havejobs' || $2 != "-j") && ('$havehist' || $2 != "-h")) \ + print $0}' $srcdir/builtins.def | sed 's/-[hj]//' > $temp +echo 'int (*const builtinfunc[])(int, char **) = {' +awk '/^[^#]/ { printf "\t%s,\n", $1}' $temp +echo '}; + +const unsigned char builtincmd[] = {' +awk '{ for (i = 2 ; i <= NF ; i++) { + if ($i == "-s") { + spc = 1; + } else if ($i == "-n") { + # Handled later for builtins.h + continue + } else { + printf "\t\"\\%03o\\%03o%s\"\n", length($i), (spc ? 128 : 0) + NR-1, $i + spc = 0; + } + }}' $temp +echo '};' + +exec > builtins.h +cat <<\! +/* + * This file was generated by the mkbuiltins program. + */ + +#include <sys/cdefs.h> +! +tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ < $temp | + awk '{ printf "#define %s %d\n", $1, NR-1}' +echo ' +#define BUILTIN_SPECIAL 0x80 + +extern int (*const builtinfunc[])(int, char **); +extern const unsigned char builtincmd[]; +' +awk '{ printf "int %s(int, char **);\n", $1}' $temp + +# Build safe_builtin_always() +cat <<EOF + +static inline int +safe_builtin_always(int idx) +{ +EOF +awk ' +BEGIN { printed = 0 } +{ + for (i = 2 ; i <= NF ; i++) { + if ($i == "-s") { + continue + } else if ($i == "-n") { + nofork = 1; + } else { + if (nofork == 0) { + continue + } + if (printed == 1) { + printf " || \n\t " + } else { + printf "\tif (" + } + printf "idx == " toupper($1) + printed = 1 + nofork = 0; + # Only need to check each once + break + } + } +}' $temp + +cat << EOF +) + return (1); + return(0); +} +EOF + +rm -f $temp diff --git a/bin/1sh/mknodes.c b/bin/1sh/mknodes.c new file mode 100644 index 00000000..41a93b09 --- /dev/null +++ b/bin/1sh/mknodes.c @@ -0,0 +1,461 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + */ + +#if 0 +#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 +static char sccsid[] = "@(#)mknodes.c 8.2 (Berkeley) 5/4/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/mknodes.c 326025 2017-11-20 19:49:47Z pfg $"); + +/* + * This program reads the nodetypes file and nodes.c.pat file. It generates + * the files nodes.h and nodes.c. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> + +#define MAXTYPES 50 /* max number of node types */ +#define MAXFIELDS 20 /* max fields in a structure */ +#define BUFLEN 100 /* size of character buffers */ + +/* field types */ +#define T_NODE 1 /* union node *field */ +#define T_NODELIST 2 /* struct nodelist *field */ +#define T_STRING 3 +#define T_INT 4 /* int field */ +#define T_OTHER 5 /* other */ +#define T_TEMP 6 /* don't copy this field */ + + +struct field { /* a structure field */ + char *name; /* name of field */ + int type; /* type of field */ + char *decl; /* declaration of field */ +}; + + +struct str { /* struct representing a node structure */ + char *tag; /* structure tag */ + int nfields; /* number of fields in the structure */ + struct field field[MAXFIELDS]; /* the fields of the structure */ + int done; /* set if fully parsed */ +}; + + +static int ntypes; /* number of node types */ +static char *nodename[MAXTYPES]; /* names of the nodes */ +static struct str *nodestr[MAXTYPES]; /* type of structure used by the node */ +static int nstr; /* number of structures */ +static struct str str[MAXTYPES]; /* the structures */ +static struct str *curstr; /* current structure */ +static char line[1024]; +static int linno; +static char *linep; + +static void parsenode(void); +static void parsefield(void); +static void output(char *); +static void outsizes(FILE *); +static void outfunc(FILE *, int); +static void indent(int, FILE *); +static int nextfield(char *); +static void skipbl(void); +static int readline(FILE *); +static void error(const char *, ...) __printf0like(1, 2) __dead2; +static char *savestr(const char *); + + +int +main(int argc, char *argv[]) +{ + FILE *infp; + + if (argc != 3) + error("usage: mknodes file"); + if ((infp = fopen(argv[1], "r")) == NULL) + error("Can't open %s: %s", argv[1], strerror(errno)); + while (readline(infp)) { + if (line[0] == ' ' || line[0] == '\t') + parsefield(); + else if (line[0] != '\0') + parsenode(); + } + fclose(infp); + output(argv[2]); + exit(0); +} + + + +static void +parsenode(void) +{ + char name[BUFLEN]; + char tag[BUFLEN]; + struct str *sp; + + if (curstr && curstr->nfields > 0) + curstr->done = 1; + nextfield(name); + if (! nextfield(tag)) + error("Tag expected"); + if (*linep != '\0') + error("Garbage at end of line"); + nodename[ntypes] = savestr(name); + for (sp = str ; sp < str + nstr ; sp++) { + if (strcmp(sp->tag, tag) == 0) + break; + } + if (sp >= str + nstr) { + sp->tag = savestr(tag); + sp->nfields = 0; + curstr = sp; + nstr++; + } + nodestr[ntypes] = sp; + ntypes++; +} + + +static void +parsefield(void) +{ + char name[BUFLEN]; + char type[BUFLEN]; + char decl[2 * BUFLEN]; + struct field *fp; + + if (curstr == NULL || curstr->done) + error("No current structure to add field to"); + if (! nextfield(name)) + error("No field name"); + if (! nextfield(type)) + error("No field type"); + fp = &curstr->field[curstr->nfields]; + fp->name = savestr(name); + if (strcmp(type, "nodeptr") == 0) { + fp->type = T_NODE; + sprintf(decl, "union node *%s", name); + } else if (strcmp(type, "nodelist") == 0) { + fp->type = T_NODELIST; + sprintf(decl, "struct nodelist *%s", name); + } else if (strcmp(type, "string") == 0) { + fp->type = T_STRING; + sprintf(decl, "char *%s", name); + } else if (strcmp(type, "int") == 0) { + fp->type = T_INT; + sprintf(decl, "int %s", name); + } else if (strcmp(type, "other") == 0) { + fp->type = T_OTHER; + } else if (strcmp(type, "temp") == 0) { + fp->type = T_TEMP; + } else { + error("Unknown type %s", type); + } + if (fp->type == T_OTHER || fp->type == T_TEMP) { + skipbl(); + fp->decl = savestr(linep); + } else { + if (*linep) + error("Garbage at end of line"); + fp->decl = savestr(decl); + } + curstr->nfields++; +} + + +static const char writer[] = "\ +/*\n\ + * This file was generated by the mknodes program.\n\ + */\n\ +\n"; + +static void +output(char *file) +{ + FILE *hfile; + FILE *cfile; + FILE *patfile; + int i; + struct str *sp; + struct field *fp; + char *p; + + if ((patfile = fopen(file, "r")) == NULL) + error("Can't open %s: %s", file, strerror(errno)); + if ((hfile = fopen("nodes.h", "w")) == NULL) + error("Can't create nodes.h: %s", strerror(errno)); + if ((cfile = fopen("nodes.c", "w")) == NULL) + error("Can't create nodes.c"); + fputs(writer, hfile); + for (i = 0 ; i < ntypes ; i++) + fprintf(hfile, "#define %s %d\n", nodename[i], i); + fputs("\n\n\n", hfile); + for (sp = str ; sp < &str[nstr] ; sp++) { + fprintf(hfile, "struct %s {\n", sp->tag); + for (i = sp->nfields, fp = sp->field ; --i >= 0 ; fp++) { + fprintf(hfile, " %s;\n", fp->decl); + } + fputs("};\n\n\n", hfile); + } + fputs("union node {\n", hfile); + fprintf(hfile, " int type;\n"); + for (sp = str ; sp < &str[nstr] ; sp++) { + fprintf(hfile, " struct %s %s;\n", sp->tag, sp->tag); + } + fputs("};\n\n\n", hfile); + fputs("struct nodelist {\n", hfile); + fputs("\tstruct nodelist *next;\n", hfile); + fputs("\tunion node *n;\n", hfile); + fputs("};\n\n\n", hfile); + fputs("struct funcdef;\n", hfile); + fputs("struct funcdef *copyfunc(union node *);\n", hfile); + fputs("union node *getfuncnode(struct funcdef *);\n", hfile); + fputs("void reffunc(struct funcdef *);\n", hfile); + fputs("void unreffunc(struct funcdef *);\n", hfile); + if (ferror(hfile)) + error("Can't write to nodes.h"); + if (fclose(hfile)) + error("Can't close nodes.h"); + + fputs(writer, cfile); + while (fgets(line, sizeof line, patfile) != NULL) { + for (p = line ; *p == ' ' || *p == '\t' ; p++); + if (strcmp(p, "%SIZES\n") == 0) + outsizes(cfile); + else if (strcmp(p, "%CALCSIZE\n") == 0) + outfunc(cfile, 1); + else if (strcmp(p, "%COPY\n") == 0) + outfunc(cfile, 0); + else + fputs(line, cfile); + } + fclose(patfile); + if (ferror(cfile)) + error("Can't write to nodes.c"); + if (fclose(cfile)) + error("Can't close nodes.c"); +} + + + +static void +outsizes(FILE *cfile) +{ + int i; + + fprintf(cfile, "static const short nodesize[%d] = {\n", ntypes); + for (i = 0 ; i < ntypes ; i++) { + fprintf(cfile, " ALIGN(sizeof (struct %s)),\n", nodestr[i]->tag); + } + fprintf(cfile, "};\n"); +} + + +static void +outfunc(FILE *cfile, int calcsize) +{ + struct str *sp; + struct field *fp; + int i; + + fputs(" if (n == NULL)\n", cfile); + if (calcsize) + fputs(" return;\n", cfile); + else + fputs(" return NULL;\n", cfile); + if (calcsize) + fputs(" result->blocksize += nodesize[n->type];\n", cfile); + else { + fputs(" new = state->block;\n", cfile); + fputs(" state->block = (char *)state->block + nodesize[n->type];\n", cfile); + } + fputs(" switch (n->type) {\n", cfile); + for (sp = str ; sp < &str[nstr] ; sp++) { + for (i = 0 ; i < ntypes ; i++) { + if (nodestr[i] == sp) + fprintf(cfile, " case %s:\n", nodename[i]); + } + for (i = sp->nfields ; --i >= 1 ; ) { + fp = &sp->field[i]; + switch (fp->type) { + case T_NODE: + if (calcsize) { + indent(12, cfile); + fprintf(cfile, "calcsize(n->%s.%s, result);\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = copynode(n->%s.%s, state);\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + case T_NODELIST: + if (calcsize) { + indent(12, cfile); + fprintf(cfile, "sizenodelist(n->%s.%s, result);\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = copynodelist(n->%s.%s, state);\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + case T_STRING: + if (calcsize) { + indent(12, cfile); + fprintf(cfile, "result->stringsize += strlen(n->%s.%s) + 1;\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = nodesavestr(n->%s.%s, state);\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + case T_INT: + case T_OTHER: + if (! calcsize) { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = n->%s.%s;\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + } + } + indent(12, cfile); + fputs("break;\n", cfile); + } + fputs(" };\n", cfile); + if (! calcsize) + fputs(" new->type = n->type;\n", cfile); +} + + +static void +indent(int amount, FILE *fp) +{ + while (amount >= 8) { + putc('\t', fp); + amount -= 8; + } + while (--amount >= 0) { + putc(' ', fp); + } +} + + +static int +nextfield(char *buf) +{ + char *p, *q; + + p = linep; + while (*p == ' ' || *p == '\t') + p++; + q = buf; + while (*p != ' ' && *p != '\t' && *p != '\0') + *q++ = *p++; + *q = '\0'; + linep = p; + return (q > buf); +} + + +static void +skipbl(void) +{ + while (*linep == ' ' || *linep == '\t') + linep++; +} + + +static int +readline(FILE *infp) +{ + char *p; + + if (fgets(line, 1024, infp) == NULL) + return 0; + for (p = line ; *p != '#' && *p != '\n' && *p != '\0' ; p++); + while (p > line && (p[-1] == ' ' || p[-1] == '\t')) + p--; + *p = '\0'; + linep = line; + linno++; + if (p - line > BUFLEN) + error("Line too long"); + return 1; +} + + + +static void +error(const char *msg, ...) +{ + va_list va; + va_start(va, msg); + + (void) fprintf(stderr, "line %d: ", linno); + (void) vfprintf(stderr, msg, va); + (void) fputc('\n', stderr); + + va_end(va); + + exit(2); +} + + + +static char * +savestr(const char *s) +{ + char *p; + + if ((p = malloc(strlen(s) + 1)) == NULL) + error("Out of space"); + (void) strcpy(p, s); + return p; +} diff --git a/bin/1sh/mksyntax.c b/bin/1sh/mksyntax.c new file mode 100644 index 00000000..d21c4565 --- /dev/null +++ b/bin/1sh/mksyntax.c @@ -0,0 +1,332 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + */ + +#if 0 +#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 +static char sccsid[] = "@(#)mksyntax.c 8.2 (Berkeley) 5/4/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/mksyntax.c 334008 2018-05-21 21:52:48Z jilles $"); + +/* + * This program creates syntax.h and syntax.c. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "parser.h" + + +struct synclass { + const char *name; + const char *comment; +}; + +/* Syntax classes */ +static const struct synclass synclass[] = { + { "CWORD", "character is nothing special" }, + { "CNL", "newline character" }, + { "CQNL", "newline character in quotes" }, + { "CBACK", "a backslash character" }, + { "CSBACK", "a backslash character in single quotes" }, + { "CSQUOTE", "single quote" }, + { "CDQUOTE", "double quote" }, + { "CENDQUOTE", "a terminating quote" }, + { "CBQUOTE", "backwards single quote" }, + { "CVAR", "a dollar sign" }, + { "CENDVAR", "a '}' character" }, + { "CLP", "a left paren in arithmetic" }, + { "CRP", "a right paren in arithmetic" }, + { "CEOF", "end of file" }, + { "CCTL", "like CWORD, except it must be escaped" }, + { "CSPCL", "these terminate a word" }, + { "CIGN", "character should be ignored" }, + { NULL, NULL } +}; + + +/* + * Syntax classes for is_ functions. Warning: if you add new classes + * you may have to change the definition of the is_in_name macro. + */ +static const struct synclass is_entry[] = { + { "ISDIGIT", "a digit" }, + { "ISUPPER", "an upper case letter" }, + { "ISLOWER", "a lower case letter" }, + { "ISUNDER", "an underscore" }, + { "ISSPECL", "the name of a special parameter" }, + { NULL, NULL } +}; + +static const char writer[] = "\ +/*\n\ + * This file was generated by the mksyntax program.\n\ + */\n\ +\n"; + + +static FILE *cfile; +static FILE *hfile; + +static void add_default(void); +static void finish(void); +static void init(const char *); +static void add(const char *, const char *); +static void output_type_macros(void); + +int +main(int argc __unused, char **argv __unused) +{ + int i; + char buf[80]; + int pos; + + /* Create output files */ + if ((cfile = fopen("syntax.c", "w")) == NULL) { + perror("syntax.c"); + exit(2); + } + if ((hfile = fopen("syntax.h", "w")) == NULL) { + perror("syntax.h"); + exit(2); + } + fputs(writer, hfile); + fputs(writer, cfile); + + fputs("#include <sys/cdefs.h>\n", hfile); + fputs("#include <limits.h>\n\n", hfile); + + /* Generate the #define statements in the header file */ + fputs("/* Syntax classes */\n", hfile); + for (i = 0 ; synclass[i].name ; i++) { + sprintf(buf, "#define %s %d", synclass[i].name, i); + fputs(buf, hfile); + for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07) + putc('\t', hfile); + fprintf(hfile, "/* %s */\n", synclass[i].comment); + } + putc('\n', hfile); + fputs("/* Syntax classes for is_ functions */\n", hfile); + for (i = 0 ; is_entry[i].name ; i++) { + sprintf(buf, "#define %s %#o", is_entry[i].name, 1 << i); + fputs(buf, hfile); + for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07) + putc('\t', hfile); + fprintf(hfile, "/* %s */\n", is_entry[i].comment); + } + putc('\n', hfile); + fputs("#define SYNBASE (1 - CHAR_MIN)\n", hfile); + fputs("#define PEOF -SYNBASE\n\n", hfile); + putc('\n', hfile); + fputs("#define BASESYNTAX (basesyntax + SYNBASE)\n", hfile); + fputs("#define DQSYNTAX (dqsyntax + SYNBASE)\n", hfile); + fputs("#define SQSYNTAX (sqsyntax + SYNBASE)\n", hfile); + fputs("#define ARISYNTAX (arisyntax + SYNBASE)\n", hfile); + putc('\n', hfile); + output_type_macros(); /* is_digit, etc. */ + putc('\n', hfile); + + /* Generate the syntax tables. */ + fputs("#include \"parser.h\"\n", cfile); + fputs("#include \"shell.h\"\n", cfile); + fputs("#include \"syntax.h\"\n\n", cfile); + + fputs("/* syntax table used when not in quotes */\n", cfile); + init("basesyntax"); + add_default(); + add("\n", "CNL"); + add("\\", "CBACK"); + add("'", "CSQUOTE"); + add("\"", "CDQUOTE"); + add("`", "CBQUOTE"); + add("$", "CVAR"); + add("}", "CENDVAR"); + add("<>();&| \t", "CSPCL"); + finish(); + + fputs("\n/* syntax table used when in double quotes */\n", cfile); + init("dqsyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CBACK"); + add("\"", "CENDQUOTE"); + add("`", "CBQUOTE"); + add("$", "CVAR"); + add("}", "CENDVAR"); + /* ':/' for tilde expansion, '-^]' for [a\-x] pattern ranges */ + add("!*?[]=~:/-^", "CCTL"); + finish(); + + fputs("\n/* syntax table used when in single quotes */\n", cfile); + init("sqsyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CSBACK"); + add("'", "CENDQUOTE"); + /* ':/' for tilde expansion, '-^]' for [a\-x] pattern ranges */ + add("!*?[]=~:/-^", "CCTL"); + finish(); + + fputs("\n/* syntax table used when in arithmetic */\n", cfile); + init("arisyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CBACK"); + add("`", "CBQUOTE"); + add("\"", "CIGN"); + add("$", "CVAR"); + add("}", "CENDVAR"); + add("(", "CLP"); + add(")", "CRP"); + finish(); + + fputs("\n/* character classification table */\n", cfile); + init("is_type"); + add("0123456789", "ISDIGIT"); + add("abcdefghijklmnopqrstuvwxyz", "ISLOWER"); + add("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ISUPPER"); + add("_", "ISUNDER"); + add("#?$!-*@", "ISSPECL"); + finish(); + + exit(0); +} + + +/* + * Output the header and declaration of a syntax table. + */ + +static void +init(const char *name) +{ + fprintf(hfile, "extern const char %s[];\n", name); + fprintf(cfile, "const char %s[SYNBASE + CHAR_MAX + 1] = {\n", name); +} + + +static void +add_one(const char *key, const char *type) +{ + fprintf(cfile, "\t[SYNBASE + %s] = %s,\n", key, type); +} + + +/* + * Add default values to the syntax table. + */ + +static void +add_default(void) +{ + add_one("PEOF", "CEOF"); + add_one("CTLESC", "CCTL"); + add_one("CTLVAR", "CCTL"); + add_one("CTLENDVAR", "CCTL"); + add_one("CTLBACKQ", "CCTL"); + add_one("CTLBACKQ + CTLQUOTE", "CCTL"); + add_one("CTLARI", "CCTL"); + add_one("CTLENDARI", "CCTL"); + add_one("CTLQUOTEMARK", "CCTL"); + add_one("CTLQUOTEEND", "CCTL"); +} + + +/* + * Output the footer of a syntax table. + */ + +static void +finish(void) +{ + fputs("};\n", cfile); +} + + +/* + * Add entries to the syntax table. + */ + +static void +add(const char *p, const char *type) +{ + for (; *p; ++p) { + char c = *p; + switch (c) { + case '\t': c = 't'; break; + case '\n': c = 'n'; break; + case '\'': c = '\''; break; + case '\\': c = '\\'; break; + + default: + fprintf(cfile, "\t[SYNBASE + '%c'] = %s,\n", c, type); + continue; + } + fprintf(cfile, "\t[SYNBASE + '\\%c'] = %s,\n", c, type); + } +} + + +/* + * Output character classification macros (e.g. is_digit). If digits are + * contiguous, we can test for them quickly. + */ + +static const char *macro[] = { + "#define is_digit(c)\t((unsigned int)((c) - '0') <= 9)", + "#define is_eof(c)\t((c) == PEOF)", + "#define is_alpha(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER))", + "#define is_name(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER))", + "#define is_in_name(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER|ISDIGIT))", + "#define is_special(c)\t((is_type+SYNBASE)[(int)c] & (ISSPECL|ISDIGIT))", + "#define digit_val(c)\t((c) - '0')", + NULL +}; + +static void +output_type_macros(void) +{ + const char **pp; + + for (pp = macro ; *pp ; pp++) + fprintf(hfile, "%s\n", *pp); +} diff --git a/bin/1sh/mktokens b/bin/1sh/mktokens new file mode 100644 index 00000000..39163663 --- /dev/null +++ b/bin/1sh/mktokens @@ -0,0 +1,93 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. 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. +# +# @(#)mktokens 8.1 (Berkeley) 5/31/93 +# $FreeBSD: releng/12.1/bin/sh/mktokens 328934 2018-02-06 15:41:35Z arichardson $ + +# The following is a list of tokens. The second column is nonzero if the +# token marks the end of a list. The third column is the name to print in +# error messages. + +temp=`mktemp -t ka` +cat > $temp <<\! +TEOF 1 end of file +TNL 0 newline +TSEMI 0 ";" +TBACKGND 0 "&" +TAND 0 "&&" +TOR 0 "||" +TPIPE 0 "|" +TLP 0 "(" +TRP 1 ")" +TENDCASE 1 ";;" +TFALLTHRU 1 ";&" +TREDIR 0 redirection +TWORD 0 word +TIF 0 "if" +TTHEN 1 "then" +TELSE 1 "else" +TELIF 1 "elif" +TFI 1 "fi" +TWHILE 0 "while" +TUNTIL 0 "until" +TFOR 0 "for" +TDO 1 "do" +TDONE 1 "done" +TBEGIN 0 "{" +TEND 1 "}" +TCASE 0 "case" +TESAC 1 "esac" +TNOT 0 "!" +! +nl=`wc -l $temp` +exec > token.h +awk '{print "#define " $1 " " NR-1}' $temp +echo ' +/* Array indicating which tokens mark the end of a list */ +static const char tokendlist[] = {' +awk '{print "\t" $2 ","}' $temp +echo '}; + +static const char *const tokname[] = {' +sed -e 's/"/\\"/g' \ + -e 's/[^ ]*[ ][ ]*[^ ]*[ ][ ]*\(.*\)/ "\1",/' \ + $temp +echo '}; +' +sed 's/"//g' $temp | awk ' +/TIF/{print "#define KWDOFFSET " NR-1; print ""; print "const char *const parsekwd[] = {"} +/TIF/,/neverfound/{print " \"" $3 "\","}' +echo ' 0 +};' + +rm $temp diff --git a/bin/1sh/myhistedit.h b/bin/1sh/myhistedit.h new file mode 100644 index 00000000..3f1b4d64 --- /dev/null +++ b/bin/1sh/myhistedit.h @@ -0,0 +1,44 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)myhistedit.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/myhistedit.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <histedit.h> + +extern History *hist; +extern EditLine *el; +extern int displayhist; + +void histedit(void); +void sethistsize(const char *); +void setterm(const char *); + diff --git a/bin/1sh/mystring.c b/bin/1sh/mystring.c new file mode 100644 index 00000000..aecfb38c --- /dev/null +++ b/bin/1sh/mystring.c @@ -0,0 +1,100 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)mystring.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/mystring.c 326025 2017-11-20 19:49:47Z pfg $"); + +/* + * String functions. + * + * equal(s1, s2) Return true if strings are equal. + * number(s) Convert a string of digits to an integer. + * is_number(s) Return true if s is a string of digits. + */ + +#include <stdlib.h> +#include "shell.h" +#include "syntax.h" +#include "error.h" +#include "mystring.h" + + +char nullstr[1]; /* zero length string */ + +/* + * equal - #defined in mystring.h + */ + + +/* + * Convert a string of digits to an integer, printing an error message on + * failure. + */ + +int +number(const char *s) +{ + if (! is_number(s)) + error("Illegal number: %s", s); + return atoi(s); +} + + + +/* + * Check for a valid number. This should be elsewhere. + */ + +int +is_number(const char *p) +{ + const char *q; + + if (*p == '\0') + return 0; + while (*p == '0') + p++; + for (q = p; *q != '\0'; q++) + if (! is_digit(*q)) + return 0; + if (q - p > 10 || + (q - p == 10 && memcmp(p, "2147483647", 10) > 0)) + return 0; + return 1; +} diff --git a/bin/1sh/mystring.h b/bin/1sh/mystring.h new file mode 100644 index 00000000..9f4960e5 --- /dev/null +++ b/bin/1sh/mystring.h @@ -0,0 +1,43 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)mystring.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/mystring.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <string.h> + +int number(const char *); +int is_number(const char *); + +#define equal(s1, s2) (strcmp(s1, s2) == 0) diff --git a/bin/1sh/nodes.c.pat b/bin/1sh/nodes.c.pat new file mode 100644 index 00000000..b687b570 --- /dev/null +++ b/bin/1sh/nodes.c.pat @@ -0,0 +1,193 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/nodes.c.pat 314436 2017-02-28 23:42:47Z imp $ + */ + +#include <sys/param.h> +#include <stdlib.h> +#include <stddef.h> +/* + * Routine for dealing with parsed shell commands. + */ + +#include "shell.h" +#include "nodes.h" +#include "memalloc.h" +#include "mystring.h" + + +struct nodesize { + int blocksize; /* size of structures in function */ + int stringsize; /* size of strings in node */ +}; + +struct nodecopystate { + pointer block; /* block to allocate function from */ + char *string; /* block to allocate strings from */ +}; + +%SIZES + + +static void calcsize(union node *, struct nodesize *); +static void sizenodelist(struct nodelist *, struct nodesize *); +static union node *copynode(union node *, struct nodecopystate *); +static struct nodelist *copynodelist(struct nodelist *, struct nodecopystate *); +static char *nodesavestr(const char *, struct nodecopystate *); + + +struct funcdef { + unsigned int refcount; + union node n; +}; + +/* + * Make a copy of a parse tree. + */ + +struct funcdef * +copyfunc(union node *n) +{ + struct nodesize sz; + struct nodecopystate st; + struct funcdef *fn; + + if (n == NULL) + return NULL; + sz.blocksize = offsetof(struct funcdef, n); + sz.stringsize = 0; + calcsize(n, &sz); + fn = ckmalloc(sz.blocksize + sz.stringsize); + fn->refcount = 1; + st.block = (char *)fn + offsetof(struct funcdef, n); + st.string = (char *)fn + sz.blocksize; + copynode(n, &st); + return fn; +} + + +union node * +getfuncnode(struct funcdef *fn) +{ + return fn == NULL ? NULL : &fn->n; +} + + +static void +calcsize(union node *n, struct nodesize *result) +{ + %CALCSIZE +} + + + +static void +sizenodelist(struct nodelist *lp, struct nodesize *result) +{ + while (lp) { + result->blocksize += ALIGN(sizeof(struct nodelist)); + calcsize(lp->n, result); + lp = lp->next; + } +} + + + +static union node * +copynode(union node *n, struct nodecopystate *state) +{ + union node *new; + + %COPY + return new; +} + + +static struct nodelist * +copynodelist(struct nodelist *lp, struct nodecopystate *state) +{ + struct nodelist *start; + struct nodelist **lpp; + + lpp = &start; + while (lp) { + *lpp = state->block; + state->block = (char *)state->block + + ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n, state); + lp = lp->next; + lpp = &(*lpp)->next; + } + *lpp = NULL; + return start; +} + + + +static char * +nodesavestr(const char *s, struct nodecopystate *state) +{ + const char *p = s; + char *q = state->string; + char *rtn = state->string; + + while ((*q++ = *p++) != '\0') + continue; + state->string = q; + return rtn; +} + + +void +reffunc(struct funcdef *fn) +{ + if (fn) + fn->refcount++; +} + + +/* + * Decrement the reference count of a function definition, freeing it + * if it falls to 0. + */ + +void +unreffunc(struct funcdef *fn) +{ + if (fn) { + fn->refcount--; + if (fn->refcount > 0) + return; + ckfree(fn); + } +} diff --git a/bin/1sh/nodetypes b/bin/1sh/nodetypes new file mode 100644 index 00000000..51facbe8 --- /dev/null +++ b/bin/1sh/nodetypes @@ -0,0 +1,145 @@ +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. 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. +# +# @(#)nodetypes 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/nodetypes 314436 2017-02-28 23:42:47Z imp $ + +# This file describes the nodes used in parse trees. Unindented lines +# contain a node type followed by a structure tag. Subsequent indented +# lines specify the fields of the structure. Several node types can share +# the same structure, in which case the fields of the structure should be +# specified only once. +# +# A field of a structure is described by the name of the field followed +# by a type. The currently implemented types are: +# nodeptr - a pointer to a node +# nodelist - a pointer to a list of nodes +# string - a pointer to a nul terminated string +# int - an integer +# other - any type that can be copied by assignment +# temp - a field that doesn't have to be copied when the node is copied +# The last two types should be followed by the text of a C declaration for +# the field. + +NSEMI nbinary # two commands separated by a semicolon + type int + ch1 nodeptr # the first child + ch2 nodeptr # the second child + +NCMD ncmd # a simple command + type int + args nodeptr # the arguments + redirect nodeptr # list of file redirections + +NPIPE npipe # a pipeline + type int + backgnd int # set to run pipeline in background + cmdlist nodelist # the commands in the pipeline + +NREDIR nredir # redirection (of a compex command) + type int + n nodeptr # the command + redirect nodeptr # list of file redirections + +NBACKGND nredir # run command in background +NSUBSHELL nredir # run command in a subshell + +NAND nbinary # the && operator +NOR nbinary # the || operator + +NIF nif # the if statement. Elif clauses are handled + type int # using multiple if nodes. + test nodeptr # if test + ifpart nodeptr # then ifpart + elsepart nodeptr # else elsepart + +NWHILE nbinary # the while statement. First child is the test +NUNTIL nbinary # the until statement + +NFOR nfor # the for statement + type int + args nodeptr # for var in args + body nodeptr # do body; done + var string # the for variable + +NCASE ncase # a case statement + type int + expr nodeptr # the word to switch on + cases nodeptr # the list of cases (NCLIST nodes) + +NCLIST nclist # a case ending with ;; + type int + next nodeptr # the next case in list + pattern nodeptr # list of patterns for this case + body nodeptr # code to execute for this case + +NCLISTFALLTHRU nclist # a case ending with ;& + +NDEFUN narg # define a function. The "next" field contains + # the body of the function. + +NARG narg # represents a word + type int + next nodeptr # next word in list + text string # the text of the word + backquote nodelist # list of commands in back quotes + +NTO nfile # fd> fname +NFROM nfile # fd< fname +NFROMTO nfile # fd<> fname +NAPPEND nfile # fd>> fname +NCLOBBER nfile # fd>| fname + type int + fd int # file descriptor being redirected + next nodeptr # next redirection in list + fname nodeptr # file name, in a NARG node + expfname temp char *expfname # actual file name + +NTOFD ndup # fd<&dupfd +NFROMFD ndup # fd>&dupfd + type int + fd int # file descriptor being redirected + next nodeptr # next redirection in list + dupfd int # file descriptor to duplicate + vname nodeptr # file name if fd>&$var + + +NHERE nhere # fd<<\! +NXHERE nhere # fd<<! + type int + fd int # file descriptor being redirected + next nodeptr # next redirection in list + doc nodeptr # input to command (NARG node) + expdoc temp const char *expdoc # actual document (for NXHERE) + +NNOT nnot # ! command (actually pipeline) + type int + com nodeptr diff --git a/bin/1sh/options.c b/bin/1sh/options.c new file mode 100644 index 00000000..6327bd1d --- /dev/null +++ b/bin/1sh/options.c @@ -0,0 +1,594 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)options.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/options.c 326025 2017-11-20 19:49:47Z pfg $"); + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#define DEFINE_OPTIONS +#include "options.h" +#undef DEFINE_OPTIONS +#include "nodes.h" /* for other header files */ +#include "eval.h" +#include "jobs.h" +#include "input.h" +#include "output.h" +#include "trap.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "builtins.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + +char *arg0; /* value of $0 */ +struct shparam shellparam; /* current positional parameters */ +char **argptr; /* argument list for builtin commands */ +char *shoptarg; /* set by nextopt (like getopt) */ +char *nextopt_optptr; /* used by nextopt */ + +char *minusc; /* argument to -c option */ + + +static void options(int); +static void minus_o(char *, int); +static void setoption(int, int); +static void setoptionbyindex(int, int); +static void setparam(int, char **); +static int getopts(char *, char *, char **, char ***, char **); + + +/* + * Process the shell command line arguments. + */ + +void +procargs(int argc, char **argv) +{ + int i; + char *scriptname; + + argptr = argv; + if (argc > 0) + argptr++; + for (i = 0; i < NOPTS; i++) + optval[i] = 2; + privileged = (getuid() != geteuid() || getgid() != getegid()); + options(1); + if (*argptr == NULL && minusc == NULL) + sflag = 1; + if (iflag != 0 && sflag == 1 && isatty(0) && isatty(1)) { + iflag = 1; + if (Eflag == 2) + Eflag = 1; + } + if (mflag == 2) + mflag = iflag; + for (i = 0; i < NOPTS; i++) + if (optval[i] == 2) + optval[i] = 0; + arg0 = argv[0]; + if (sflag == 0 && minusc == NULL) { + scriptname = *argptr++; + setinputfile(scriptname, 0); + commandname = arg0 = scriptname; + } + /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + if (argptr && minusc && *argptr) + arg0 = *argptr++; + + shellparam.p = argptr; + shellparam.reset = 1; + /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */ + while (*argptr) { + shellparam.nparam++; + argptr++; + } + optschanged(); +} + + +void +optschanged(void) +{ + setinteractive(); +#ifndef NO_HISTORY + histedit(); +#endif + setjobctl(mflag); +} + +/* + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. + * If cmdline is true, process the shell's argv; otherwise, process arguments + * to the set special builtin. + */ + +static void +options(int cmdline) +{ + char *kp, *p; + int val; + int c; + + if (cmdline) + minusc = NULL; + while ((p = *argptr) != NULL) { + argptr++; + if ((c = *p++) == '-') { + val = 1; + /* A "-" or "--" terminates options */ + if (p[0] == '\0') + goto end_options1; + if (p[0] == '-' && p[1] == '\0') + goto end_options2; + /** + * For the benefit of `#!' lines in shell scripts, + * treat a string of '-- *#.*' the same as '--'. + * This is needed so that a script starting with: + * #!/bin/sh -- # -*- perl -*- + * will continue to work after a change is made to + * kern/imgact_shell.c to NOT token-ize the options + * specified on a '#!' line. A bit of a kludge, + * but that trick is recommended in documentation + * for some scripting languages, and we might as + * well continue to support it. + */ + if (p[0] == '-') { + kp = p + 1; + while (*kp == ' ' || *kp == '\t') + kp++; + if (*kp == '#' || *kp == '\0') + goto end_options2; + } + } else if (c == '+') { + val = 0; + } else { + argptr--; + break; + } + while ((c = *p++) != '\0') { + if (c == 'c' && cmdline) { + char *q; + + q = *argptr++; + if (q == NULL || minusc != NULL) + error("Bad -c option"); + minusc = q; + } else if (c == 'o') { + minus_o(*argptr, val); + if (*argptr) + argptr++; + } else + setoption(c, val); + } + } + return; + + /* When processing `set', a single "-" means turn off -x and -v */ +end_options1: + if (!cmdline) { + xflag = vflag = 0; + return; + } + + /* + * When processing `set', a "--" means the remaining arguments + * replace the positional parameters in the active shell. If + * there are no remaining options, then all the positional + * parameters are cleared (equivalent to doing ``shift $#''). + */ +end_options2: + if (!cmdline) { + if (*argptr == NULL) + setparam(0, argptr); + return; + } + + /* + * At this point we are processing options given to 'sh' on a command + * line. If an end-of-options marker ("-" or "--") is followed by an + * arg of "#", then skip over all remaining arguments. Some scripting + * languages (e.g.: perl) document that /bin/sh will implement this + * behavior, and they recommend that users take advantage of it to + * solve certain issues that can come up when writing a perl script. + * Yes, this feature is in /bin/sh to help users write perl scripts. + */ + p = *argptr; + if (p != NULL && p[0] == '#' && p[1] == '\0') { + while (*argptr != NULL) + argptr++; + /* We need to keep the final argument */ + argptr--; + } +} + +static void +minus_o(char *name, int val) +{ + int i; + const unsigned char *on; + size_t len; + + if (name == NULL) { + if (val) { + /* "Pretty" output. */ + out1str("Current option settings\n"); + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + out1fmt("%-16.*s%s\n", *on, on + 1, + optval[i] ? "on" : "off"); + } else { + /* Output suitable for re-input to shell. */ + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + out1fmt("%s %co %.*s%s", + i % 6 == 0 ? "set" : "", + optval[i] ? '-' : '+', + *on, on + 1, + i % 6 == 5 || i == NOPTS - 1 ? "\n" : ""); + } + } else { + len = strlen(name); + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + if (*on == len && memcmp(on + 1, name, len) == 0) { + setoptionbyindex(i, val); + return; + } + error("Illegal option -o %s", name); + } +} + + +static void +setoptionbyindex(int idx, int val) +{ + if (&optval[idx] == &privileged && !val && privileged) { + if (setgid(getgid()) == -1) + error("setgid"); + if (setuid(getuid()) == -1) + error("setuid"); + } + optval[idx] = val; + if (val) { + /* #%$ hack for ksh semantics */ + if (&optval[idx] == &Vflag) + Eflag = 0; + else if (&optval[idx] == &Eflag) + Vflag = 0; + } +} + +static void +setoption(int flag, int val) +{ + int i; + + for (i = 0; i < NSHORTOPTS; i++) + if (optletter[i] == flag) { + setoptionbyindex(i, val); + return; + } + error("Illegal option -%c", flag); +} + + +/* + * Set the shell parameters. + */ + +static void +setparam(int argc, char **argv) +{ + char **newparam; + char **ap; + + ap = newparam = ckmalloc((argc + 1) * sizeof *ap); + while (*argv) { + *ap++ = savestr(*argv++); + } + *ap = NULL; + freeparam(&shellparam); + shellparam.malloc = 1; + shellparam.nparam = argc; + shellparam.p = newparam; + shellparam.optp = NULL; + shellparam.reset = 1; + shellparam.optnext = NULL; +} + + +/* + * Free the list of positional parameters. + */ + +void +freeparam(struct shparam *param) +{ + char **ap; + + if (param->malloc) { + for (ap = param->p ; *ap ; ap++) + ckfree(*ap); + ckfree(param->p); + } + if (param->optp) { + for (ap = param->optp ; *ap ; ap++) + ckfree(*ap); + ckfree(param->optp); + } +} + + + +/* + * The shift builtin command. + */ + +int +shiftcmd(int argc, char **argv) +{ + int i, n; + + n = 1; + if (argc > 1) + n = number(argv[1]); + if (n > shellparam.nparam) + return 1; + INTOFF; + shellparam.nparam -= n; + if (shellparam.malloc) + for (i = 0; i < n; i++) + ckfree(shellparam.p[i]); + memmove(shellparam.p, shellparam.p + n, + (shellparam.nparam + 1) * sizeof(shellparam.p[0])); + shellparam.reset = 1; + INTON; + return 0; +} + + + +/* + * The set builtin command. + */ + +int +setcmd(int argc, char **argv) +{ + if (argc == 1) + return showvarscmd(argc, argv); + INTOFF; + options(0); + optschanged(); + if (*argptr != NULL) { + setparam(argc - (argptr - argv), argptr); + } + INTON; + return 0; +} + + +void +getoptsreset(const char *value) +{ + while (*value == '0') + value++; + if (strcmp(value, "1") == 0) + shellparam.reset = 1; +} + +/* + * 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. + */ + +int +getoptscmd(int argc, char **argv) +{ + char **optbase = NULL, **ap; + int i; + + if (argc < 3) + error("usage: getopts optstring var [arg]"); + + if (shellparam.reset == 1) { + INTOFF; + if (shellparam.optp) { + for (ap = shellparam.optp ; *ap ; ap++) + ckfree(*ap); + ckfree(shellparam.optp); + shellparam.optp = NULL; + } + if (argc > 3) { + shellparam.optp = ckmalloc((argc - 2) * sizeof *ap); + memset(shellparam.optp, '\0', (argc - 2) * sizeof *ap); + for (i = 0; i < argc - 3; i++) + shellparam.optp[i] = savestr(argv[i + 3]); + } + INTON; + optbase = argc == 3 ? shellparam.p : shellparam.optp; + shellparam.optnext = optbase; + shellparam.optptr = NULL; + shellparam.reset = 0; + } else + optbase = shellparam.optp ? shellparam.optp : shellparam.p; + + return getopts(argv[1], argv[2], optbase, &shellparam.optnext, + &shellparam.optptr); +} + +static int +getopts(char *optstr, char *optvar, char **optfirst, char ***optnext, + char **optptr) +{ + char *p, *q; + char c = '?'; + int done = 0; + int ind = 0; + int err = 0; + char s[10]; + const char *newoptarg = NULL; + + if ((p = *optptr) == NULL || *p == '\0') { + /* Current word is done, advance */ + if (*optnext == NULL) + return 1; + p = **optnext; + if (p == NULL || *p != '-' || *++p == '\0') { +atend: + ind = *optnext - optfirst + 1; + *optnext = NULL; + p = NULL; + done = 1; + goto out; + } + (*optnext)++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + goto atend; + } + + c = *p++; + for (q = optstr; *q != c; ) { + if (*q == '\0') { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + newoptarg = s; + } + else + out2fmt_flush("Illegal option -%c\n", c); + c = '?'; + goto out; + } + if (*++q == ':') + q++; + } + + if (*++q == ':') { + if (*p == '\0' && (p = **optnext) == NULL) { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + newoptarg = s; + c = ':'; + } + else { + out2fmt_flush("No arg for -%c option\n", c); + c = '?'; + } + goto out; + } + + if (p == **optnext) + (*optnext)++; + newoptarg = p; + p = NULL; + } + +out: + if (*optnext != NULL) + ind = *optnext - optfirst + 1; + *optptr = p; + if (newoptarg != NULL) + err |= setvarsafe("OPTARG", newoptarg, 0); + else { + INTOFF; + err |= unsetvar("OPTARG"); + INTON; + } + fmtstr(s, sizeof(s), "%d", ind); + err |= setvarsafe("OPTIND", s, VNOFUNC); + s[0] = c; + s[1] = '\0'; + err |= setvarsafe(optvar, s, 0); + if (err) { + *optnext = NULL; + *optptr = NULL; + flushall(); + exraise(EXERROR); + } + return done; +} + +/* + * 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 returns the option, or '\0' on + * end of input. + */ + +int +nextopt(const char *optstring) +{ + char *p; + const char *q; + char c; + + if ((p = nextopt_optptr) == NULL || *p == '\0') { + p = *argptr; + if (p == NULL || *p != '-' || *++p == '\0') + return '\0'; + argptr++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + return '\0'; + } + c = *p++; + for (q = optstring ; *q != c ; ) { + if (*q == '\0') + error("Illegal option -%c", c); + if (*++q == ':') + q++; + } + if (*++q == ':') { + if (*p == '\0' && (p = *argptr++) == NULL) + error("No arg for -%c option", c); + shoptarg = p; + p = NULL; + } + nextopt_optptr = p; + return c; +} diff --git a/bin/1sh/options.h b/bin/1sh/options.h new file mode 100644 index 00000000..007ea312 --- /dev/null +++ b/bin/1sh/options.h @@ -0,0 +1,117 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)options.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/options.h 345487 2019-03-24 22:10:26Z jilles $ + */ + +struct shparam { + int nparam; /* # of positional parameters (without $0) */ + unsigned char malloc; /* if parameter list dynamically allocated */ + unsigned char reset; /* if getopts has been reset */ + char **p; /* parameter list */ + char **optp; /* parameter list for getopts */ + char **optnext; /* next parameter to be processed by getopts */ + char *optptr; /* used by getopts */ +}; + + + +#define eflag optval[0] +#define fflag optval[1] +#define Iflag optval[2] +#define iflag optval[3] +#define mflag optval[4] +#define nflag optval[5] +#define sflag optval[6] +#define xflag optval[7] +#define vflag optval[8] +#define Vflag optval[9] +#define Eflag optval[10] +#define Cflag optval[11] +#define aflag optval[12] +#define bflag optval[13] +#define uflag optval[14] +#define privileged optval[15] +#define Tflag optval[16] +#define Pflag optval[17] +#define hflag optval[18] +#define nologflag optval[19] +#define pipefailflag optval[20] + +#define NSHORTOPTS 19 +#define NOPTS 21 + +extern char optval[NOPTS]; +extern const char optletter[NSHORTOPTS]; +#ifdef DEFINE_OPTIONS +char optval[NOPTS]; +const char optletter[NSHORTOPTS] = "efIimnsxvVECabupTPh"; +static const unsigned char optname[] = + "\007errexit" + "\006noglob" + "\011ignoreeof" + "\013interactive" + "\007monitor" + "\006noexec" + "\005stdin" + "\006xtrace" + "\007verbose" + "\002vi" + "\005emacs" + "\011noclobber" + "\011allexport" + "\006notify" + "\007nounset" + "\012privileged" + "\012trapsasync" + "\010physical" + "\010trackall" + "\005nolog" + "\010pipefail" +; +#endif + + +extern char *minusc; /* argument to -c option */ +extern char *arg0; /* $0 */ +extern struct shparam shellparam; /* $@ */ +extern char **argptr; /* argument list for builtin commands */ +extern char *shoptarg; /* set by nextopt */ +extern char *nextopt_optptr; /* used by nextopt */ + +void procargs(int, char **); +void optschanged(void); +void freeparam(struct shparam *); +int nextopt(const char *); +void getoptsreset(const char *); diff --git a/bin/1sh/output.c b/bin/1sh/output.c new file mode 100644 index 00000000..26a83873 --- /dev/null +++ b/bin/1sh/output.c @@ -0,0 +1,376 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)output.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/output.c 345613 2019-03-27 21:53:44Z jilles $"); + +/* + * Shell output routines. We use our own output routines because: + * When a builtin command is interrupted we have to discard + * any pending output. + * When a builtin command appears in back quotes, we want to + * save the output of the command in a region obtained + * via malloc, rather than doing a fork and reading the + * output of the command via a pipe. + */ + +#include <stdio.h> /* defines BUFSIZ */ +#include <string.h> +#include <stdarg.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <wchar.h> +#include <wctype.h> + +#include "shell.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "var.h" + + +#define OUTBUFSIZ BUFSIZ +#define MEM_OUT -2 /* output to dynamically allocated memory */ +#define OUTPUT_ERR 01 /* error occurred on output */ + +static int doformat_wr(void *, const char *, int); + +struct output output = {NULL, NULL, NULL, OUTBUFSIZ, 1, 0}; +struct output errout = {NULL, NULL, NULL, 256, 2, 0}; +struct output memout = {NULL, NULL, NULL, 64, MEM_OUT, 0}; +struct output *out1 = &output; +struct output *out2 = &errout; + +void +outcslow(int c, struct output *file) +{ + outc(c, file); +} + +void +out1str(const char *p) +{ + outstr(p, out1); +} + +void +out1qstr(const char *p) +{ + outqstr(p, out1); +} + +void +out2str(const char *p) +{ + outstr(p, out2); +} + +void +out2qstr(const char *p) +{ + outqstr(p, out2); +} + +void +outstr(const char *p, struct output *file) +{ + outbin(p, strlen(p), file); +} + +static void +byteseq(int ch, struct output *file) +{ + char seq[4]; + + seq[0] = '\\'; + seq[1] = (ch >> 6 & 0x3) + '0'; + seq[2] = (ch >> 3 & 0x7) + '0'; + seq[3] = (ch & 0x7) + '0'; + outbin(seq, 4, file); +} + +static void +outdqstr(const char *p, struct output *file) +{ + const char *end; + mbstate_t mbs; + size_t clen; + wchar_t wc; + + memset(&mbs, '\0', sizeof(mbs)); + end = p + strlen(p); + outstr("$'", file); + while ((clen = mbrtowc(&wc, p, end - p + 1, &mbs)) != 0) { + if (clen == (size_t)-2) { + while (p < end) + byteseq(*p++, file); + break; + } + if (clen == (size_t)-1) { + memset(&mbs, '\0', sizeof(mbs)); + byteseq(*p++, file); + continue; + } + if (wc == L'\n') + outcslow('\n', file), p++; + else if (wc == L'\r') + outstr("\\r", file), p++; + else if (wc == L'\t') + outstr("\\t", file), p++; + else if (!iswprint(wc)) { + for (; clen > 0; clen--) + byteseq(*p++, file); + } else { + if (wc == L'\'' || wc == L'\\') + outcslow('\\', file); + outbin(p, clen, file); + p += clen; + } + } + outcslow('\'', file); +} + +/* Like outstr(), but quote for re-input into the shell. */ +void +outqstr(const char *p, struct output *file) +{ + int i; + + if (p[0] == '\0') { + outstr("''", file); + return; + } + for (i = 0; p[i] != '\0'; i++) { + if ((p[i] > '\0' && p[i] < ' ' && p[i] != '\n') || + (p[i] & 0x80) != 0 || p[i] == '\'') { + outdqstr(p, file); + return; + } + } + + if (p[strcspn(p, "|&;<>()$`\\\" \n*?[~#=")] == '\0' || + strcmp(p, "[") == 0) { + outstr(p, file); + return; + } + + outcslow('\'', file); + outstr(p, file); + outcslow('\'', file); +} + +void +outbin(const void *data, size_t len, struct output *file) +{ + const char *p; + + p = data; + while (len-- > 0) + outc(*p++, file); +} + +void +emptyoutbuf(struct output *dest) +{ + int offset, newsize; + + if (dest->buf == NULL) { + INTOFF; + dest->buf = ckmalloc(dest->bufsize); + dest->nextc = dest->buf; + dest->bufend = dest->buf + dest->bufsize; + INTON; + } else if (dest->fd == MEM_OUT) { + offset = dest->nextc - dest->buf; + newsize = dest->bufsize << 1; + INTOFF; + dest->buf = ckrealloc(dest->buf, newsize); + dest->bufsize = newsize; + dest->bufend = dest->buf + newsize; + dest->nextc = dest->buf + offset; + INTON; + } else { + flushout(dest); + } +} + + +void +flushall(void) +{ + flushout(&output); + flushout(&errout); +} + + +void +flushout(struct output *dest) +{ + + if (dest->buf == NULL || dest->nextc == dest->buf || dest->fd < 0) + return; + if (xwrite(dest->fd, dest->buf, dest->nextc - dest->buf) < 0) + dest->flags |= OUTPUT_ERR; + dest->nextc = dest->buf; +} + + +void +freestdout(void) +{ + output.nextc = output.buf; +} + + +int +outiserror(struct output *file) +{ + return (file->flags & OUTPUT_ERR); +} + + +void +outclearerror(struct output *file) +{ + file->flags &= ~OUTPUT_ERR; +} + + +void +outfmt(struct output *file, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(file, fmt, ap); + va_end(ap); +} + + +void +out1fmt(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out1, fmt, ap); + va_end(ap); +} + +void +out2fmt_flush(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out2, fmt, ap); + va_end(ap); + flushout(out2); +} + +void +fmtstr(char *outbuf, int length, const char *fmt, ...) +{ + va_list ap; + + INTOFF; + va_start(ap, fmt); + vsnprintf(outbuf, length, fmt, ap); + va_end(ap); + INTON; +} + +static int +doformat_wr(void *cookie, const char *buf, int len) +{ + struct output *o; + + o = (struct output *)cookie; + outbin(buf, len, o); + + return (len); +} + +void +doformat(struct output *dest, const char *f, va_list ap) +{ + FILE *fp; + + if ((fp = fwopen(dest, doformat_wr)) != NULL) { + vfprintf(fp, f, ap); + fclose(fp); + } +} + +FILE * +out1fp(void) +{ + return fwopen(out1, doformat_wr); +} + +/* + * Version of write which resumes after a signal is caught. + */ + +int +xwrite(int fd, const char *buf, int nbytes) +{ + int ntry; + int i; + int n; + + n = nbytes; + ntry = 0; + for (;;) { + i = write(fd, buf, n); + if (i > 0) { + if ((n -= i) <= 0) + return nbytes; + buf += i; + ntry = 0; + } else if (i == 0) { + if (++ntry > 10) + return nbytes - n; + } else if (errno != EINTR) { + return -1; + } + } +} diff --git a/bin/1sh/output.h b/bin/1sh/output.h new file mode 100644 index 00000000..867b7169 --- /dev/null +++ b/bin/1sh/output.h @@ -0,0 +1,87 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)output.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/output.h 345613 2019-03-27 21:53:44Z jilles $ + */ + +#ifndef OUTPUT_INCL + +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> + +struct output { + char *nextc; + char *bufend; + char *buf; + int bufsize; + short fd; + short flags; +}; + +extern struct output output; /* to fd 1 */ +extern struct output errout; /* to fd 2 */ +extern struct output memout; +extern struct output *out1; /* &memout if backquote, otherwise &output */ +extern struct output *out2; /* &memout if backquote with 2>&1, otherwise + &errout */ + +void outcslow(int, struct output *); +void out1str(const char *); +void out1qstr(const char *); +void out2str(const char *); +void out2qstr(const char *); +void outstr(const char *, struct output *); +void outqstr(const char *, struct output *); +void outbin(const void *, size_t, struct output *); +void emptyoutbuf(struct output *); +void flushall(void); +void flushout(struct output *); +void freestdout(void); +int outiserror(struct output *); +void outclearerror(struct output *); +void outfmt(struct output *, const char *, ...) __printflike(2, 3); +void out1fmt(const char *, ...) __printflike(1, 2); +void out2fmt_flush(const char *, ...) __printflike(1, 2); +void fmtstr(char *, int, const char *, ...) __printflike(3, 4); +void doformat(struct output *, const char *, va_list) __printflike(2, 0); +FILE *out1fp(void); +int xwrite(int, const char *, int); + +#define outc(c, file) ((file)->nextc == (file)->bufend ? (emptyoutbuf(file), *(file)->nextc++ = (c)) : (*(file)->nextc++ = (c))) +#define out1c(c) outc(c, out1); +#define out2c(c) outcslow(c, out2); + +#define OUTPUT_INCL +#endif diff --git a/bin/1sh/parser.c b/bin/1sh/parser.c new file mode 100644 index 00000000..97d6f52a --- /dev/null +++ b/bin/1sh/parser.c @@ -0,0 +1,2122 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/parser.c 334008 2018-05-21 21:52:48Z jilles $"); + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "expand.h" /* defines rmescapes() */ +#include "syntax.h" +#include "options.h" +#include "input.h" +#include "output.h" +#include "var.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "show.h" +#include "eval.h" +#include "exec.h" /* to check for special builtins */ +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + +/* + * Shell command parser. + */ + +#define PROMPTLEN 128 + +/* values of checkkwd variable */ +#define CHKALIAS 0x1 +#define CHKKWD 0x2 +#define CHKNL 0x4 + +/* values returned by readtoken */ +#include "token.h" + + + +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 */ +}; + +struct parser_temp { + struct parser_temp *next; + void *data; +}; + + +static struct heredoc *heredoclist; /* list of here documents to read */ +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 int tokpushback; /* last token pushed back */ +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 int startlinno; /* line # where last token started */ +static int funclinno; /* line # where the current function started */ +static struct parser_temp *parser_temp; + +#define NOEOFMARK ((const char *)&heredoclist) + + +static union node *list(int); +static union node *andor(void); +static union node *pipeline(void); +static union node *command(void); +static union node *simplecmd(union node **, union node *); +static union node *makename(void); +static union node *makebinary(int type, union node *n1, union node *n2); +static void parsefname(void); +static void parseheredoc(void); +static int peektoken(void); +static int readtoken(void); +static int xxreadtoken(void); +static int readtoken1(int, const char *, const char *, int); +static int noexpand(char *); +static void consumetoken(int); +static void synexpect(int) __dead2; +static void synerror(const char *) __dead2; +static void setprompt(int); +static int pgetc_linecont(void); + + +static void * +parser_temp_alloc(size_t len) +{ + struct parser_temp *t; + + INTOFF; + t = ckmalloc(sizeof(*t)); + t->data = NULL; + t->next = parser_temp; + parser_temp = t; + t->data = ckmalloc(len); + INTON; + return t->data; +} + + +static void * +parser_temp_realloc(void *ptr, size_t len) +{ + struct parser_temp *t; + + INTOFF; + t = parser_temp; + if (ptr != t->data) + error("bug: parser_temp_realloc misused"); + t->data = ckrealloc(t->data, len); + INTON; + return t->data; +} + + +static void +parser_temp_free_upto(void *ptr) +{ + struct parser_temp *t; + int done = 0; + + INTOFF; + while (parser_temp != NULL && !done) { + t = parser_temp; + parser_temp = t->next; + done = t->data == ptr; + ckfree(t->data); + ckfree(t); + } + INTON; + if (!done) + error("bug: parser_temp_free_upto misused"); +} + + +static void +parser_temp_free_all(void) +{ + struct parser_temp *t; + + INTOFF; + while (parser_temp != NULL) { + t = parser_temp; + parser_temp = t->next; + ckfree(t->data); + ckfree(t); + } + INTON; +} + + +/* + * Read and parse a command. Returns NEOF on end of file. (NULL is a + * valid parse tree indicating a blank line.) + */ + +union node * +parsecmd(int interact) +{ + int t; + + /* This assumes the parser is not re-entered, + * which could happen if we add command substitution on PS1/PS2. + */ + parser_temp_free_all(); + heredoclist = NULL; + + tokpushback = 0; + checkkwd = 0; + doprompt = interact; + if (doprompt) + setprompt(1); + else + setprompt(0); + needprompt = 0; + t = readtoken(); + if (t == TEOF) + return NEOF; + if (t == TNL) + return NULL; + tokpushback++; + return list(1); +} + + +/* + * Read and parse words for wordexp. + * Returns a list of NARG nodes; NULL if there are no words. + */ +union node * +parsewordexp(void) +{ + union node *n, *first = NULL, **pnext; + int t; + + /* This assumes the parser is not re-entered, + * which could happen if we add command substitution on PS1/PS2. + */ + parser_temp_free_all(); + heredoclist = NULL; + + tokpushback = 0; + checkkwd = 0; + doprompt = 0; + setprompt(0); + needprompt = 0; + pnext = &first; + while ((t = readtoken()) != TEOF) { + if (t != TWORD) + synexpect(TWORD); + n = makename(); + *pnext = n; + pnext = &n->narg.next; + } + return first; +} + + +static union node * +list(int nlflag) +{ + union node *ntop, *n1, *n2, *n3; + int tok; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return NULL; + ntop = n1 = NULL; + for (;;) { + n2 = andor(); + tok = readtoken(); + if (tok == TBACKGND) { + if (n2 != NULL && n2->type == NPIPE) { + n2->npipe.backgnd = 1; + } else if (n2 != NULL && n2->type == NREDIR) { + n2->type = NBACKGND; + } else { + n3 = (union node *)stalloc(sizeof (struct nredir)); + n3->type = NBACKGND; + n3->nredir.n = n2; + n3->nredir.redirect = NULL; + n2 = n3; + } + } + if (ntop == NULL) + ntop = n2; + else if (n1 == NULL) { + n1 = makebinary(NSEMI, ntop, n2); + ntop = n1; + } + else { + n3 = makebinary(NSEMI, n1->nbinary.ch2, n2); + n1->nbinary.ch2 = n3; + n1 = n3; + } + switch (tok) { + case TBACKGND: + case TSEMI: + tok = readtoken(); + /* FALLTHROUGH */ + case TNL: + if (tok == TNL) { + parseheredoc(); + if (nlflag) + return ntop; + } else if (tok == TEOF && nlflag) { + parseheredoc(); + return ntop; + } else { + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return ntop; + break; + case TEOF: + if (heredoclist) + parseheredoc(); + else + pungetc(); /* push back EOF on input */ + return ntop; + default: + if (nlflag) + synexpect(-1); + tokpushback++; + return ntop; + } + } +} + + + +static union node * +andor(void) +{ + union node *n; + int t; + + n = pipeline(); + for (;;) { + if ((t = readtoken()) == TAND) { + t = NAND; + } else if (t == TOR) { + t = NOR; + } else { + tokpushback++; + return n; + } + n = makebinary(t, n, pipeline()); + } +} + + + +static union node * +pipeline(void) +{ + union node *n1, *n2, *pipenode; + struct nodelist *lp, *prev; + int negate, t; + + negate = 0; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + TRACE(("pipeline: entered\n")); + while (readtoken() == TNOT) + negate = !negate; + tokpushback++; + n1 = command(); + if (readtoken() == TPIPE) { + pipenode = (union node *)stalloc(sizeof (struct npipe)); + pipenode->type = NPIPE; + pipenode->npipe.backgnd = 0; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + pipenode->npipe.cmdlist = lp; + lp->n = n1; + do { + prev = lp; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + checkkwd = CHKNL | CHKKWD | CHKALIAS; + t = readtoken(); + tokpushback++; + if (t == TNOT) + lp->n = pipeline(); + else + lp->n = command(); + prev->next = lp; + } while (readtoken() == TPIPE); + lp->next = NULL; + n1 = pipenode; + } + tokpushback++; + if (negate) { + n2 = (union node *)stalloc(sizeof (struct nnot)); + n2->type = NNOT; + n2->nnot.com = n1; + return n2; + } else + return n1; +} + + + +static union node * +command(void) +{ + union node *n1, *n2; + union node *ap, **app; + union node *cp, **cpp; + union node *redir, **rpp; + int t; + int is_subshell; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + is_subshell = 0; + redir = NULL; + n1 = NULL; + rpp = &redir; + + /* Check for redirection which may precede command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + + switch (readtoken()) { + case TIF: + n1 = (union node *)stalloc(sizeof (struct nif)); + n1->type = NIF; + if ((n1->nif.test = list(0)) == NULL) + synexpect(-1); + consumetoken(TTHEN); + n1->nif.ifpart = list(0); + n2 = n1; + while (readtoken() == TELIF) { + n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif)); + n2 = n2->nif.elsepart; + n2->type = NIF; + if ((n2->nif.test = list(0)) == NULL) + synexpect(-1); + consumetoken(TTHEN); + n2->nif.ifpart = list(0); + } + if (lasttoken == TELSE) + n2->nif.elsepart = list(0); + else { + n2->nif.elsepart = NULL; + tokpushback++; + } + consumetoken(TFI); + checkkwd = CHKKWD | CHKALIAS; + break; + case TWHILE: + case TUNTIL: + t = lasttoken; + if ((n1 = list(0)) == NULL) + synexpect(-1); + consumetoken(TDO); + n1 = makebinary((t == TWHILE)? NWHILE : NUNTIL, n1, list(0)); + consumetoken(TDONE); + checkkwd = CHKKWD | CHKALIAS; + break; + case TFOR: + if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) + synerror("Bad for loop variable"); + n1 = (union node *)stalloc(sizeof (struct nfor)); + n1->type = NFOR; + n1->nfor.var = wordtext; + while (readtoken() == TNL) + ; + if (lasttoken == TWORD && ! quoteflag && equal(wordtext, "in")) { + app = ≈ + while (readtoken() == TWORD) { + n2 = makename(); + *app = n2; + app = &n2->narg.next; + } + *app = NULL; + n1->nfor.args = ap; + if (lasttoken != TNL && lasttoken != TSEMI) + synexpect(-1); + } else { + static char argvars[5] = { + CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' + }; + n2 = (union node *)stalloc(sizeof (struct narg)); + n2->type = NARG; + n2->narg.text = argvars; + 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 ((t = readtoken()) == TDO) + t = TDONE; + else if (t == TBEGIN) + t = TEND; + else + synexpect(-1); + n1->nfor.body = list(0); + consumetoken(t); + checkkwd = CHKKWD | CHKALIAS; + break; + case TCASE: + n1 = (union node *)stalloc(sizeof (struct ncase)); + n1->type = NCASE; + consumetoken(TWORD); + n1->ncase.expr = makename(); + while (readtoken() == TNL); + if (lasttoken != TWORD || ! equal(wordtext, "in")) + synerror("expecting \"in\""); + cpp = &n1->ncase.cases; + checkkwd = CHKNL | CHKKWD, readtoken(); + while (lasttoken != TESAC) { + *cpp = cp = (union node *)stalloc(sizeof (struct nclist)); + cp->type = NCLIST; + app = &cp->nclist.pattern; + if (lasttoken == TLP) + readtoken(); + for (;;) { + *app = ap = makename(); + checkkwd = CHKNL | CHKKWD; + if (readtoken() != TPIPE) + break; + app = &ap->narg.next; + readtoken(); + } + ap->narg.next = NULL; + if (lasttoken != TRP) + synexpect(TRP); + cp->nclist.body = list(0); + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) != TESAC) { + if (t == TENDCASE) + ; + else if (t == TFALLTHRU) + cp->type = NCLISTFALLTHRU; + else + synexpect(TENDCASE); + checkkwd = CHKNL | CHKKWD, readtoken(); + } + cpp = &cp->nclist.next; + } + *cpp = NULL; + checkkwd = CHKKWD | CHKALIAS; + break; + case TLP: + n1 = (union node *)stalloc(sizeof (struct nredir)); + n1->type = NSUBSHELL; + n1->nredir.n = list(0); + n1->nredir.redirect = NULL; + consumetoken(TRP); + checkkwd = CHKKWD | CHKALIAS; + is_subshell = 1; + break; + case TBEGIN: + n1 = list(0); + consumetoken(TEND); + checkkwd = CHKKWD | CHKALIAS; + break; + /* A simple command must have at least one redirection or word. */ + case TBACKGND: + case TSEMI: + case TAND: + case TOR: + case TPIPE: + case TENDCASE: + case TFALLTHRU: + case TEOF: + case TNL: + case TRP: + if (!redir) + synexpect(-1); + case TWORD: + tokpushback++; + n1 = simplecmd(rpp, redir); + return n1; + default: + synexpect(-1); + } + + /* Now check for redirection which may follow command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + *rpp = NULL; + if (redir) { + if (!is_subshell) { + n2 = (union node *)stalloc(sizeof (struct nredir)); + n2->type = NREDIR; + n2->nredir.n = n1; + n1 = n2; + } + n1->nredir.redirect = redir; + } + + return n1; +} + + +static union node * +simplecmd(union node **rpp, union node *redir) +{ + union node *args, **app; + union node **orig_rpp = rpp; + union node *n = NULL; + int special; + int savecheckkwd; + + /* If we don't have any redirections already, then we must reset */ + /* rpp to be the address of the local redir variable. */ + if (redir == NULL) + rpp = &redir; + + args = NULL; + app = &args; + /* + * We save the incoming value, because we need this for shell + * functions. There can not be a redirect or an argument between + * the function name and the open parenthesis. + */ + orig_rpp = rpp; + + savecheckkwd = CHKALIAS; + + for (;;) { + checkkwd = savecheckkwd; + if (readtoken() == TWORD) { + n = makename(); + *app = n; + app = &n->narg.next; + if (savecheckkwd != 0 && !isassignment(wordtext)) + savecheckkwd = 0; + } else if (lasttoken == TREDIR) { + *rpp = n = redirnode; + rpp = &n->nfile.next; + parsefname(); /* read name of redirection file */ + } else if (lasttoken == TLP && app == &args->narg.next + && rpp == orig_rpp) { + /* We have a function */ + consumetoken(TRP); + funclinno = plinno; + /* + * - Require plain text. + * - Functions with '/' cannot be called. + * - Reject name=(). + * - Reject ksh extended glob patterns. + */ + if (!noexpand(n->narg.text) || quoteflag || + strchr(n->narg.text, '/') || + strchr("!%*+-=?@}~", + n->narg.text[strlen(n->narg.text) - 1])) + synerror("Bad function name"); + rmescapes(n->narg.text); + if (find_builtin(n->narg.text, &special) >= 0 && + special) + synerror("Cannot override a special builtin with a function"); + n->type = NDEFUN; + n->narg.next = command(); + funclinno = 0; + return n; + } else { + tokpushback++; + break; + } + } + *app = NULL; + *rpp = NULL; + n = (union node *)stalloc(sizeof (struct ncmd)); + n->type = NCMD; + n->ncmd.args = args; + n->ncmd.redirect = redir; + return n; +} + +static union node * +makename(void) +{ + union node *n; + + n = (union node *)stalloc(sizeof (struct narg)); + n->type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + return n; +} + +static union node * +makebinary(int type, union node *n1, union node *n2) +{ + union node *n; + + n = (union node *)stalloc(sizeof (struct nbinary)); + n->type = type; + n->nbinary.ch1 = n1; + n->nbinary.ch2 = n2; + return (n); +} + +void +forcealias(void) +{ + checkkwd |= CHKALIAS; +} + +void +fixredir(union node *n, const char *text, int err) +{ + TRACE(("Fix redir %s %d\n", text, err)); + if (!err) + n->ndup.vname = NULL; + + if (is_digit(text[0]) && text[1] == '\0') + n->ndup.dupfd = digit_val(text[0]); + else if (text[0] == '-' && text[1] == '\0') + n->ndup.dupfd = -1; + else { + + if (err) + synerror("Bad fd number"); + else + n->ndup.vname = makename(); + } +} + + +static void +parsefname(void) +{ + union node *n = redirnode; + + consumetoken(TWORD); + if (n->type == NHERE) { + struct heredoc *here = heredoc; + struct heredoc *p; + + if (quoteflag == 0) + n->type = NXHERE; + TRACE(("Here document %d\n", n->type)); + if (here->striptabs) { + while (*wordtext == '\t') + wordtext++; + } + if (! noexpand(wordtext)) + synerror("Illegal eof marker for << redirection"); + rmescapes(wordtext); + here->eofmark = wordtext; + here->next = NULL; + if (heredoclist == NULL) + heredoclist = here; + else { + for (p = heredoclist ; p->next ; p = p->next); + p->next = here; + } + } else if (n->type == NTOFD || n->type == NFROMFD) { + fixredir(n, wordtext, 0); + } else { + n->nfile.fname = makename(); + } +} + + +/* + * Input any here documents. + */ + +static void +parseheredoc(void) +{ + struct heredoc *here; + union node *n; + + while (heredoclist) { + here = heredoclist; + heredoclist = here->next; + if (needprompt) { + setprompt(2); + needprompt = 0; + } + readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, + here->eofmark, here->striptabs); + n = makename(); + here->here->nhere.doc = n; + } +} + +static int +peektoken(void) +{ + int t; + + t = readtoken(); + tokpushback++; + return (t); +} + +static int +readtoken(void) +{ + int t; + struct alias *ap; +#ifdef DEBUG + int alreadyseen = tokpushback; +#endif + + top: + t = xxreadtoken(); + + /* + * eat newlines + */ + if (checkkwd & CHKNL) { + while (t == TNL) { + parseheredoc(); + t = xxreadtoken(); + } + } + + /* + * check for keywords and aliases + */ + if (t == TWORD && !quoteflag) + { + const char * const *pp; + + if (checkkwd & CHKKWD) + for (pp = parsekwd; *pp; pp++) { + if (**pp == *wordtext && equal(*pp, wordtext)) + { + lasttoken = t = pp - parsekwd + KWDOFFSET; + TRACE(("keyword %s recognized\n", tokname[t])); + goto out; + } + } + if (checkkwd & CHKALIAS && + (ap = lookupalias(wordtext, 1)) != NULL) { + pushstring(ap->val, strlen(ap->val), ap); + goto top; + } + } +out: + if (t != TNOT) + checkkwd = 0; + +#ifdef 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); +} + + +/* + * 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 RETURN(token) return lasttoken = token + +static int +xxreadtoken(void) +{ + int c; + + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + needprompt = 0; + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + switch (c) { + case ' ': case '\t': + continue; + case '#': + while ((c = pgetc()) != '\n' && c != PEOF); + pungetc(); + continue; + case '\\': + if (pgetc() == '\n') { + startlinno = ++plinno; + if (doprompt) + setprompt(2); + else + setprompt(0); + continue; + } + pungetc(); + /* FALLTHROUGH */ + default: + return readtoken1(c, BASESYNTAX, (char *)NULL, 0); + case '\n': + plinno++; + needprompt = doprompt; + RETURN(TNL); + case PEOF: + RETURN(TEOF); + case '&': + if (pgetc_linecont() == '&') + RETURN(TAND); + pungetc(); + RETURN(TBACKGND); + case '|': + if (pgetc_linecont() == '|') + RETURN(TOR); + pungetc(); + RETURN(TPIPE); + case ';': + c = pgetc_linecont(); + if (c == ';') + RETURN(TENDCASE); + else if (c == '&') + RETURN(TFALLTHRU); + pungetc(); + RETURN(TSEMI); + case '(': + RETURN(TLP); + case ')': + RETURN(TRP); + } + } +#undef RETURN +} + + +#define MAXNEST_static 8 +struct tokenstate +{ + const char *syntax; /* *SYNTAX */ + int parenlevel; /* levels of parentheses in arithmetic */ + enum tokenstate_category + { + TSTATE_TOP, + TSTATE_VAR_OLD, /* ${var+-=?}, inherits dquotes */ + TSTATE_VAR_NEW, /* other ${var...}, own dquote state */ + TSTATE_ARITH + } category; +}; + + +/* + * Check to see whether we are at the end of the here document. When this + * is called, c is set to the first character of the next input line. If + * we are at the end of the here document, this routine sets the c to PEOF. + * The new value of c is returned. + */ + +static int +checkend(int c, const char *eofmark, int striptabs) +{ + if (striptabs) { + while (c == '\t') + c = pgetc(); + } + if (c == *eofmark) { + int c2; + const char *q; + + for (q = eofmark + 1; c2 = pgetc(), *q != '\0' && c2 == *q; q++) + ; + if ((c2 == PEOF || c2 == '\n') && *q == '\0') { + c = PEOF; + if (c2 == '\n') { + plinno++; + needprompt = doprompt; + } + } else { + pungetc(); + pushstring(eofmark + 1, q - (eofmark + 1), NULL); + } + } else if (c == '\n' && *eofmark == '\0') { + c = PEOF; + plinno++; + needprompt = doprompt; + } + return (c); +} + + +/* + * Parse a redirection operator. The variable "out" points to a string + * specifying the fd to be redirected. The variable "c" contains the + * first character of the redirection operator. + */ + +static void +parseredir(char *out, int c) +{ + char fd = *out; + union node *np; + + np = (union node *)stalloc(sizeof (struct nfile)); + if (c == '>') { + np->nfile.fd = 1; + c = pgetc_linecont(); + if (c == '>') + np->type = NAPPEND; + else if (c == '&') + np->type = NTOFD; + else if (c == '|') + np->type = NCLOBBER; + else { + np->type = NTO; + pungetc(); + } + } else { /* c == '<' */ + np->nfile.fd = 0; + c = pgetc_linecont(); + if (c == '<') { + if (sizeof (struct nfile) != sizeof (struct nhere)) { + np = (union node *)stalloc(sizeof (struct nhere)); + np->nfile.fd = 0; + } + np->type = NHERE; + heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc)); + heredoc->here = np; + if ((c = pgetc_linecont()) == '-') { + heredoc->striptabs = 1; + } else { + heredoc->striptabs = 0; + pungetc(); + } + } else if (c == '&') + np->type = NFROMFD; + else if (c == '>') + np->type = NFROMTO; + else { + np->type = NFROM; + pungetc(); + } + } + if (fd != '\0') + np->nfile.fd = digit_val(fd); + redirnode = np; +} + +/* + * Called to parse command substitutions. + */ + +static char * +parsebackq(char *out, struct nodelist **pbqlist, + int oldstyle, int dblquote, int quoted) +{ + struct nodelist **nlpp; + union node *n; + char *volatile str; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + size_t savelen; + int saveprompt; + const int bq_startlinno = plinno; + char *volatile ostr = NULL; + struct parsefile *const savetopfile = getcurrentfile(); + struct heredoc *const saveheredoclist = heredoclist; + struct heredoc *here; + + str = NULL; + if (setjmp(jmploc.loc)) { + popfilesupto(savetopfile); + if (str) + ckfree(str); + if (ostr) + ckfree(ostr); + heredoclist = saveheredoclist; + handler = savehandler; + if (exception == EXERROR) { + startlinno = bq_startlinno; + synerror("Error in command substitution"); + } + longjmp(handler->loc, 1); + } + INTOFF; + savelen = out - stackblock(); + if (savelen > 0) { + str = ckmalloc(savelen); + memcpy(str, stackblock(), savelen); + } + handler = &jmploc; + heredoclist = NULL; + INTON; + if (oldstyle) { + /* We must read until the closing backquote, giving special + treatment to some slashes, and then push the string and + reread it as input, interpreting it normally. */ + char *oout; + int c; + int olen; + + + STARTSTACKSTR(oout); + for (;;) { + if (needprompt) { + setprompt(2); + needprompt = 0; + } + CHECKSTRSPACE(2, oout); + c = pgetc_linecont(); + if (c == '`') + break; + switch (c) { + case '\\': + c = pgetc(); + if (c != '\\' && c != '`' && c != '$' + && (!dblquote || c != '"')) + USTPUTC('\\', oout); + break; + + case '\n': + plinno++; + needprompt = doprompt; + break; + + case PEOF: + startlinno = plinno; + synerror("EOF in backquote substitution"); + break; + + default: + break; + } + USTPUTC(c, oout); + } + USTPUTC('\0', oout); + olen = oout - stackblock(); + INTOFF; + ostr = ckmalloc(olen); + memcpy(ostr, stackblock(), olen); + setinputstring(ostr, 1); + INTON; + } + nlpp = pbqlist; + while (*nlpp) + nlpp = &(*nlpp)->next; + *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + (*nlpp)->next = NULL; + + if (oldstyle) { + saveprompt = doprompt; + doprompt = 0; + } + + n = list(0); + + if (oldstyle) { + if (peektoken() != TEOF) + synexpect(-1); + doprompt = saveprompt; + } else + consumetoken(TRP); + + (*nlpp)->n = n; + if (oldstyle) { + /* + * Start reading from old file again, ignoring any pushed back + * tokens left from the backquote parsing + */ + popfile(); + tokpushback = 0; + } + STARTSTACKSTR(out); + CHECKSTRSPACE(savelen + 1, out); + INTOFF; + if (str) { + memcpy(out, str, savelen); + STADJUST(savelen, out); + ckfree(str); + str = NULL; + } + if (ostr) { + ckfree(ostr); + ostr = NULL; + } + here = saveheredoclist; + if (here != NULL) { + while (here->next != NULL) + here = here->next; + here->next = heredoclist; + heredoclist = saveheredoclist; + } + handler = savehandler; + INTON; + if (quoted) + USTPUTC(CTLBACKQ | CTLQUOTE, out); + else + USTPUTC(CTLBACKQ, out); + return out; +} + + +/* + * Called to parse a backslash escape sequence inside $'...'. + * The backslash has already been read. + */ +static char * +readcstyleesc(char *out) +{ + int c, vc, i, n; + unsigned int v; + + c = pgetc(); + switch (c) { + case '\0': + synerror("Unterminated quoted string"); + case '\n': + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + return out; + case '\\': + case '\'': + case '"': + v = c; + break; + case 'a': v = '\a'; break; + case 'b': v = '\b'; break; + case 'e': v = '\033'; break; + case 'f': v = '\f'; break; + case 'n': v = '\n'; break; + case 'r': v = '\r'; break; + case 't': v = '\t'; break; + case 'v': v = '\v'; break; + case 'x': + v = 0; + for (;;) { + c = pgetc(); + if (c >= '0' && c <= '9') + v = (v << 4) + c - '0'; + else if (c >= 'A' && c <= 'F') + v = (v << 4) + c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + v = (v << 4) + c - 'a' + 10; + else + break; + } + pungetc(); + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + v = c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + } else + pungetc(); + } else + pungetc(); + break; + case 'c': + c = pgetc(); + if (c < 0x3f || c > 0x7a || c == 0x60) + synerror("Bad escape sequence"); + if (c == '\\' && pgetc() != '\\') + synerror("Bad escape sequence"); + if (c == '?') + v = 127; + else + v = c & 0x1f; + break; + case 'u': + case 'U': + n = c == 'U' ? 8 : 4; + v = 0; + for (i = 0; i < n; i++) { + c = pgetc(); + if (c >= '0' && c <= '9') + v = (v << 4) + c - '0'; + else if (c >= 'A' && c <= 'F') + v = (v << 4) + c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + v = (v << 4) + c - 'a' + 10; + else + synerror("Bad escape sequence"); + } + if (v == 0 || (v >= 0xd800 && v <= 0xdfff)) + synerror("Bad escape sequence"); + /* We really need iconv here. */ + if (initial_localeisutf8 && v > 127) { + CHECKSTRSPACE(4, out); + /* + * We cannot use wctomb() as the locale may have + * changed. + */ + if (v <= 0x7ff) { + USTPUTC(0xc0 | v >> 6, out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0xffff) { + USTPUTC(0xe0 | v >> 12, out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0x10ffff) { + USTPUTC(0xf0 | v >> 18, out); + USTPUTC(0x80 | ((v >> 12) & 0x3f), out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } + } + if (v > 127) + v = '?'; + break; + default: + synerror("Bad escape sequence"); + } + vc = (char)v; + /* + * We can't handle NUL bytes. + * POSIX says we should skip till the closing quote. + */ + if (vc == '\0') { + while ((c = pgetc()) != '\'') { + if (c == '\\') + c = pgetc(); + if (c == PEOF) + synerror("Unterminated quoted string"); + if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } + } + pungetc(); + return out; + } + if (SQSYNTAX[vc] == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(vc, out); + return out; +} + + +/* + * If eofmark is NULL, read a word or a redirection symbol. If eofmark + * is not NULL, read a here document. In the latter case, eofmark is the + * word which marks the end of the document and striptabs is true if + * leading tabs should be stripped from the document. The argument firstc + * is the first character of the input token or document. + * + * Because C does not have internal subroutines, I have simulated them + * using goto's to implement the subroutine linkage. The following macros + * will run code that appears at the end of readtoken1. + */ + +#define PARSESUB() {goto parsesub; parsesub_return:;} +#define PARSEARITH() {goto parsearith; parsearith_return:;} + +static int +readtoken1(int firstc, char const *initialsyntax, const char *eofmark, + int striptabs) +{ + int c = firstc; + char *out; + int len; + struct nodelist *bqlist; + int quotef; + int newvarnest; + int level; + int synentry; + struct tokenstate state_static[MAXNEST_static]; + int maxnest = MAXNEST_static; + struct tokenstate *state = state_static; + int sqiscstyle = 0; + + startlinno = plinno; + quotef = 0; + bqlist = NULL; + newvarnest = 0; + level = 0; + state[level].syntax = initialsyntax; + state[level].parenlevel = 0; + state[level].category = TSTATE_TOP; + + STARTSTACKSTR(out); + loop: { /* for each line, until end of word */ + if (eofmark && eofmark != NOEOFMARK) + /* set c to PEOF if at end of here document */ + c = checkend(c, eofmark, striptabs); + for (;;) { /* until end of line or end of word */ + CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ + + synentry = state[level].syntax[c]; + + switch(synentry) { + case CNL: /* '\n' */ + if (level == 0) + goto endword; /* exit outer loop */ + /* FALLTHROUGH */ + case CQNL: + USTPUTC(c, out); + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + c = pgetc(); + goto loop; /* continue outer loop */ + case CSBACK: + if (sqiscstyle) { + out = readcstyleesc(out); + break; + } + /* FALLTHROUGH */ + case CWORD: + USTPUTC(c, out); + break; + case CCTL: + if (eofmark == NULL || initialsyntax != SQSYNTAX) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + break; + case CBACK: /* backslash */ + c = pgetc(); + if (c == PEOF) { + USTPUTC('\\', out); + pungetc(); + } else if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + if (state[level].syntax == DQSYNTAX && + c != '\\' && c != '`' && c != '$' && + (c != '"' || (eofmark != NULL && + newvarnest == 0)) && + (c != '}' || state[level].category != TSTATE_VAR_OLD)) + USTPUTC('\\', out); + if ((eofmark == NULL || + newvarnest > 0) && + state[level].syntax == BASESYNTAX) + USTPUTC(CTLQUOTEMARK, out); + if (SQSYNTAX[c] == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + if ((eofmark == NULL || + newvarnest > 0) && + state[level].syntax == BASESYNTAX && + state[level].category == TSTATE_VAR_OLD) + USTPUTC(CTLQUOTEEND, out); + quotef++; + } + break; + case CSQUOTE: + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = SQSYNTAX; + sqiscstyle = 0; + break; + case CDQUOTE: + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = DQSYNTAX; + break; + case CENDQUOTE: + if (eofmark != NULL && newvarnest == 0) + USTPUTC(c, out); + else { + if (state[level].category == TSTATE_VAR_OLD) + USTPUTC(CTLQUOTEEND, out); + state[level].syntax = BASESYNTAX; + quotef++; + } + break; + case CVAR: /* '$' */ + PARSESUB(); /* parse substitution */ + break; + case CENDVAR: /* '}' */ + if (level > 0 && + ((state[level].category == TSTATE_VAR_OLD && + state[level].syntax == + state[level - 1].syntax) || + (state[level].category == TSTATE_VAR_NEW && + state[level].syntax == BASESYNTAX))) { + if (state[level].category == TSTATE_VAR_NEW) + newvarnest--; + level--; + USTPUTC(CTLENDVAR, out); + } else { + USTPUTC(c, out); + } + break; + case CLP: /* '(' in arithmetic */ + state[level].parenlevel++; + USTPUTC(c, out); + break; + case CRP: /* ')' in arithmetic */ + if (state[level].parenlevel > 0) { + USTPUTC(c, out); + --state[level].parenlevel; + } else { + if (pgetc_linecont() == ')') { + if (level > 0 && + state[level].category == TSTATE_ARITH) { + level--; + USTPUTC(CTLENDARI, out); + } else + USTPUTC(')', out); + } else { + /* + * unbalanced parens + * (don't 2nd guess - no error) + */ + pungetc(); + USTPUTC(')', out); + } + } + break; + case CBQUOTE: /* '`' */ + out = parsebackq(out, &bqlist, 1, + state[level].syntax == DQSYNTAX && + (eofmark == NULL || newvarnest > 0), + state[level].syntax == DQSYNTAX || state[level].syntax == ARISYNTAX); + break; + case CEOF: + goto endword; /* exit outer loop */ + case CIGN: + break; + default: + if (level == 0) + goto endword; /* exit outer loop */ + USTPUTC(c, out); + } + c = pgetc_macro(); + } + } +endword: + if (state[level].syntax == ARISYNTAX) + synerror("Missing '))'"); + if (state[level].syntax != BASESYNTAX && eofmark == NULL) + synerror("Unterminated quoted string"); + if (state[level].category == TSTATE_VAR_OLD || + state[level].category == TSTATE_VAR_NEW) { + startlinno = plinno; + synerror("Missing '}'"); + } + if (state != state_static) + parser_temp_free_upto(state); + USTPUTC('\0', out); + len = out - stackblock(); + out = stackblock(); + if (eofmark == NULL) { + if ((c == '>' || c == '<') + && quotef == 0 + && len <= 2 + && (*out == '\0' || is_digit(*out))) { + parseredir(out, c); + return lasttoken = TREDIR; + } else { + pungetc(); + } + } + quoteflag = quotef; + backquotelist = bqlist; + grabstackblock(len); + wordtext = out; + return lasttoken = TWORD; +/* end of readtoken routine */ + + +/* + * Parse a substitution. At this point, we have read the dollar sign + * and nothing else. + */ + +parsesub: { + int subtype; + int typeloc; + int flags; + char *p; + static const char types[] = "}-+?="; + int linno; + int length; + int c1; + + c = pgetc_linecont(); + if (c == '(') { /* $(command) or $((arith)) */ + if (pgetc_linecont() == '(') { + PARSEARITH(); + } else { + pungetc(); + out = parsebackq(out, &bqlist, 0, + state[level].syntax == DQSYNTAX && + (eofmark == NULL || newvarnest > 0), + state[level].syntax == DQSYNTAX || + state[level].syntax == ARISYNTAX); + } + } else if (c == '{' || is_name(c) || is_special(c)) { + USTPUTC(CTLVAR, out); + typeloc = out - stackblock(); + USTPUTC(VSNORMAL, out); + subtype = VSNORMAL; + flags = 0; + if (c == '{') { + c = pgetc_linecont(); + subtype = 0; + } +varname: + if (!is_eof(c) && is_name(c)) { + length = 0; + do { + STPUTC(c, out); + c = pgetc_linecont(); + length++; + } while (!is_eof(c) && is_in_name(c)); + if (length == 6 && + strncmp(out - length, "LINENO", length) == 0) { + /* Replace the variable name with the + * current line number. */ + STADJUST(-6, out); + CHECKSTRSPACE(11, out); + linno = plinno; + if (funclinno != 0) + linno -= funclinno - 1; + length = snprintf(out, 11, "%d", linno); + if (length > 10) + length = 10; + out += length; + flags |= VSLINENO; + } + } else if (is_digit(c)) { + if (subtype != VSNORMAL) { + do { + STPUTC(c, out); + c = pgetc_linecont(); + } while (is_digit(c)); + } else { + USTPUTC(c, out); + c = pgetc_linecont(); + } + } else if (is_special(c)) { + c1 = c; + c = pgetc_linecont(); + if (subtype == 0 && c1 == '#') { + subtype = VSLENGTH; + if (strchr(types, c) == NULL && c != ':' && + c != '#' && c != '%') + goto varname; + c1 = c; + c = pgetc_linecont(); + if (c1 != '}' && c == '}') { + pungetc(); + c = c1; + goto varname; + } + pungetc(); + c = c1; + c1 = '#'; + subtype = 0; + } + USTPUTC(c1, out); + } else { + subtype = VSERROR; + if (c == '}') + pungetc(); + else if (c == '\n' || c == PEOF) + synerror("Unexpected end of line in substitution"); + else if (BASESYNTAX[c] != CCTL) + USTPUTC(c, out); + } + if (subtype == 0) { + switch (c) { + case ':': + flags |= VSNUL; + c = pgetc_linecont(); + /*FALLTHROUGH*/ + default: + p = strchr(types, c); + if (p == NULL) { + if (c == '\n' || c == PEOF) + synerror("Unexpected end of line in substitution"); + if (flags == VSNUL) + STPUTC(':', out); + if (BASESYNTAX[c] != CCTL) + STPUTC(c, out); + subtype = VSERROR; + } else + subtype = p - types + VSNORMAL; + break; + case '%': + case '#': + { + int cc = c; + subtype = c == '#' ? VSTRIMLEFT : + VSTRIMRIGHT; + c = pgetc_linecont(); + if (c == cc) + subtype++; + else + pungetc(); + break; + } + } + } else if (subtype != VSERROR) { + if (subtype == VSLENGTH && c != '}') + subtype = VSERROR; + pungetc(); + } + STPUTC('=', out); + if (state[level].syntax == DQSYNTAX || + state[level].syntax == ARISYNTAX) + flags |= VSQUOTE; + *(stackblock() + typeloc) = subtype | flags; + if (subtype != VSNORMAL) { + if (level + 1 >= maxnest) { + maxnest *= 2; + if (state == state_static) { + state = parser_temp_alloc( + maxnest * sizeof(*state)); + memcpy(state, state_static, + MAXNEST_static * sizeof(*state)); + } else + state = parser_temp_realloc(state, + maxnest * sizeof(*state)); + } + level++; + state[level].parenlevel = 0; + if (subtype == VSMINUS || subtype == VSPLUS || + subtype == VSQUESTION || subtype == VSASSIGN) { + /* + * For operators that were in the Bourne shell, + * inherit the double-quote state. + */ + state[level].syntax = state[level - 1].syntax; + state[level].category = TSTATE_VAR_OLD; + } else { + /* + * The other operators take a pattern, + * so go to BASESYNTAX. + * Also, ' and " are now special, even + * in here documents. + */ + state[level].syntax = BASESYNTAX; + state[level].category = TSTATE_VAR_NEW; + newvarnest++; + } + } + } else if (c == '\'' && state[level].syntax == BASESYNTAX) { + /* $'cstylequotes' */ + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = SQSYNTAX; + sqiscstyle = 1; + } else { + USTPUTC('$', out); + pungetc(); + } + goto parsesub_return; +} + + +/* + * Parse an arithmetic expansion (indicate start of one and set state) + */ +parsearith: { + + if (level + 1 >= maxnest) { + maxnest *= 2; + if (state == state_static) { + state = parser_temp_alloc( + maxnest * sizeof(*state)); + memcpy(state, state_static, + MAXNEST_static * sizeof(*state)); + } else + state = parser_temp_realloc(state, + maxnest * sizeof(*state)); + } + level++; + state[level].syntax = ARISYNTAX; + state[level].parenlevel = 0; + state[level].category = TSTATE_ARITH; + USTPUTC(CTLARI, out); + if (state[level - 1].syntax == DQSYNTAX) + USTPUTC('"',out); + else + USTPUTC(' ',out); + goto parsearith_return; +} + +} /* 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 (BASESYNTAX[(int)c] == CCTL) + return 0; + } + return 1; +} + + +/* + * Return true if the argument is a legal variable name (a letter or + * underscore followed by zero or more letters, underscores, and digits). + */ + +int +goodname(const char *name) +{ + const char *p; + + p = name; + if (! is_name(*p)) + return 0; + while (*++p) { + if (! is_in_name(*p)) + return 0; + } + return 1; +} + + +int +isassignment(const char *p) +{ + if (!is_name(*p)) + return 0; + p++; + for (;;) { + if (*p == '=') + return 1; + else if (!is_in_name(*p)) + return 0; + p++; + } +} + + +static void +consumetoken(int token) +{ + if (readtoken() != token) + synexpect(token); +} + + +/* + * 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 +synexpect(int token) +{ + char msg[64]; + + if (token >= 0) { + fmtstr(msg, 64, "%s unexpected (expecting %s)", + tokname[lasttoken], tokname[token]); + } else { + fmtstr(msg, 64, "%s unexpected", tokname[lasttoken]); + } + synerror(msg); +} + + +static void +synerror(const char *msg) +{ + if (commandname) + outfmt(out2, "%s: %d: ", commandname, startlinno); + else if (arg0) + outfmt(out2, "%s: ", arg0); + outfmt(out2, "Syntax error: %s\n", msg); + error((char *)NULL); +} + +static void +setprompt(int which) +{ + whichprompt = which; + if (which == 0) + return; + +#ifndef NO_HISTORY + if (!el) +#endif + { + out2str(getprompt(NULL)); + flushout(out2); + } +} + +static int +pgetc_linecont(void) +{ + int c; + + while ((c = pgetc_macro()) == '\\') { + c = pgetc(); + if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + pungetc(); + /* Allow the backslash to be pushed back. */ + pushstring("\\", 1, NULL); + return (pgetc()); + } + } + return (c); +} + +/* + * called by editline -- any expansions to the prompt + * should be added here. + */ +char * +getprompt(void *unused __unused) +{ + static char ps[PROMPTLEN]; + const char *fmt; + const char *pwd; + int i, trim; + static char internal_error[] = "??"; + + /* + * Select prompt format. + */ + switch (whichprompt) { + case 0: + fmt = ""; + break; + case 1: + fmt = ps1val(); + break; + case 2: + fmt = ps2val(); + break; + default: + return internal_error; + } + + /* + * Format prompt string. + */ + for (i = 0; (i < PROMPTLEN - 1) && (*fmt != '\0'); i++, fmt++) + if (*fmt == '\\') + switch (*++fmt) { + + /* + * Hostname. + * + * \h specifies just the local hostname, + * \H specifies fully-qualified hostname. + */ + case 'h': + case 'H': + ps[i] = '\0'; + gethostname(&ps[i], PROMPTLEN - i - 1); + ps[PROMPTLEN - 1] = '\0'; + /* Skip to end of hostname. */ + trim = (*fmt == 'h') ? '.' : '\0'; + while ((ps[i] != '\0') && (ps[i] != trim)) + i++; + --i; + break; + + /* + * Working directory. + * + * \W specifies just the final component, + * \w specifies the entire path. + */ + case 'W': + case 'w': + pwd = lookupvar("PWD"); + if (pwd == NULL || *pwd == '\0') + pwd = "?"; + if (*fmt == 'W' && + *pwd == '/' && pwd[1] != '\0') + strlcpy(&ps[i], strrchr(pwd, '/') + 1, + PROMPTLEN - i); + else + strlcpy(&ps[i], pwd, PROMPTLEN - i); + /* Skip to end of path. */ + while (ps[i + 1] != '\0') + i++; + break; + + /* + * Superuser status. + * + * '$' for normal users, '#' for root. + */ + case '$': + ps[i] = (geteuid() != 0) ? '$' : '#'; + break; + + /* + * A literal \. + */ + case '\\': + ps[i] = '\\'; + break; + + /* + * Emit unrecognized formats verbatim. + */ + default: + ps[i] = '\\'; + if (i < PROMPTLEN - 2) + ps[++i] = *fmt; + break; + } + else + ps[i] = *fmt; + ps[i] = '\0'; + return (ps); +} + + +const char * +expandstr(const char *ps) +{ + union node n; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + const int saveprompt = doprompt; + struct parsefile *const savetopfile = getcurrentfile(); + struct parser_temp *const saveparser_temp = parser_temp; + const char *result = NULL; + + if (!setjmp(jmploc.loc)) { + handler = &jmploc; + parser_temp = NULL; + setinputstring(ps, 1); + doprompt = 0; + readtoken1(pgetc(), DQSYNTAX, NOEOFMARK, 0); + if (backquotelist != NULL) + error("Command substitution not allowed here"); + + n.narg.type = NARG; + n.narg.next = NULL; + n.narg.text = wordtext; + n.narg.backquote = backquotelist; + + expandarg(&n, NULL, 0); + result = stackblock(); + INTOFF; + } + handler = savehandler; + doprompt = saveprompt; + popfilesupto(savetopfile); + if (parser_temp != saveparser_temp) { + parser_temp_free_all(); + parser_temp = saveparser_temp; + } + if (result != NULL) { + INTON; + } else if (exception == EXINT) + raise(SIGINT); + return result; +} diff --git a/bin/1sh/parser.h b/bin/1sh/parser.h new file mode 100644 index 00000000..de9d393a --- /dev/null +++ b/bin/1sh/parser.h @@ -0,0 +1,87 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)parser.h 8.3 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/parser.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* control characters in argument strings */ +#define CTLESC '\300' +#define CTLVAR '\301' +#define CTLENDVAR '\371' +#define CTLBACKQ '\372' +#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ +/* CTLBACKQ | CTLQUOTE == '\373' */ +#define CTLARI '\374' +#define CTLENDARI '\375' +#define CTLQUOTEMARK '\376' +#define CTLQUOTEEND '\377' /* only for ${v+-...} */ + +/* variable substitution byte (follows CTLVAR) */ +#define VSTYPE 0x0f /* type of variable substitution */ +#define VSNUL 0x10 /* colon--treat the empty string as unset */ +#define VSLINENO 0x20 /* expansion of $LINENO, the line number \ + follows immediately */ +#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 VSTRIMLEFT 0x6 /* ${var#pattern} */ +#define VSTRIMLEFTMAX 0x7 /* ${var##pattern} */ +#define VSTRIMRIGHT 0x8 /* ${var%pattern} */ +#define VSTRIMRIGHTMAX 0x9 /* ${var%%pattern} */ +#define VSLENGTH 0xa /* ${#var} */ +#define VSERROR 0xb /* Syntax error, issue when expanded */ + + +/* + * NEOF is returned by parsecmd when it encounters an end of file. It + * must be distinct from NULL. + */ +#define NEOF ((union node *)-1) +extern int whichprompt; /* 1 == PS1, 2 == PS2 */ +extern const char *const parsekwd[]; + + +union node *parsecmd(int); +union node *parsewordexp(void); +void forcealias(void); +void fixredir(union node *, const char *, int); +int goodname(const char *); +int isassignment(const char *); +char *getprompt(void *); +const char *expandstr(const char *); diff --git a/bin/1sh/profile b/bin/1sh/profile new file mode 100644 index 00000000..35869039 --- /dev/null +++ b/bin/1sh/profile @@ -0,0 +1,18 @@ +# $FreeBSD: releng/12.1/bin/sh/profile 337849 2018-08-15 14:41:24Z brd $ +# +# System-wide .profile file for sh(1). +# +# Uncomment this to give you the default 4.2 behavior, where disk +# information is shown in K-Blocks +# BLOCKSIZE=K; export BLOCKSIZE +# +# For the setting of languages and character sets please see +# login.conf(5) and in particular the charset and lang options. +# For full locales list check /usr/share/locale/* +# You should also read the setlocale(3) man page for information +# on how to achieve more precise control of locale settings. +# +# Check system messages +# msgs -q +# Allow terminal messages +# mesg y diff --git a/bin/1sh/redir.c b/bin/1sh/redir.c new file mode 100644 index 00000000..494c88cd --- /dev/null +++ b/bin/1sh/redir.c @@ -0,0 +1,365 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)redir.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/redir.c 326025 2017-11-20 19:49:47Z pfg $"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> + +/* + * Code for dealing with input/output redirection. + */ + +#include "shell.h" +#include "nodes.h" +#include "jobs.h" +#include "expand.h" +#include "redir.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "options.h" + + +#define EMPTY -2 /* marks an unused slot in redirtab */ +#define CLOSED -1 /* fd was not open before redir */ + + +struct redirtab { + struct redirtab *next; + int renamed[10]; + int fd0_redirected; + unsigned int empty_redirs; +}; + + +static struct redirtab *redirlist; + +/* + * We keep track of whether or not fd0 has been redirected. This is for + * background commands, where we want to redirect fd0 to /dev/null only + * if it hasn't already been redirected. +*/ +static int fd0_redirected = 0; + +/* Number of redirtabs that have not been allocated. */ +static unsigned int empty_redirs = 0; + +static void openredirect(union node *, char[10 ]); +static int openhere(union node *); + + +/* + * 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. +* + * We suppress interrupts so that we won't leave open file + * descriptors around. Because the signal handler remains + * installed and we do not use system call restart, interrupts + * will still abort blocking opens such as fifos (they will fail + * with EINTR). There is, however, a race condition if an interrupt + * arrives after INTOFF and before open blocks. + */ + +void +redirect(union node *redir, int flags) +{ + union node *n; + struct redirtab *sv = NULL; + int i; + int fd; + char memory[10]; /* file descriptors to write to memory */ + + INTOFF; + for (i = 10 ; --i >= 0 ; ) + memory[i] = 0; + memory[1] = flags & REDIR_BACKQ; + if (flags & REDIR_PUSH) { + empty_redirs++; + if (redir != NULL) { + sv = ckmalloc(sizeof (struct redirtab)); + for (i = 0 ; i < 10 ; i++) + sv->renamed[i] = EMPTY; + sv->fd0_redirected = fd0_redirected; + sv->empty_redirs = empty_redirs - 1; + sv->next = redirlist; + redirlist = sv; + empty_redirs = 0; + } + } + for (n = redir ; n ; n = n->nfile.next) { + fd = n->nfile.fd; + if (fd == 0) + fd0_redirected = 1; + if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) && + n->ndup.dupfd == fd) + continue; /* redirect from/to same file descriptor */ + + if ((flags & REDIR_PUSH) && sv->renamed[fd] == EMPTY) { + INTOFF; + if ((i = fcntl(fd, F_DUPFD_CLOEXEC, 10)) == -1) { + switch (errno) { + case EBADF: + i = CLOSED; + break; + default: + INTON; + error("%d: %s", fd, strerror(errno)); + break; + } + } + sv->renamed[fd] = i; + INTON; + } + openredirect(n, memory); + INTON; + INTOFF; + } + if (memory[1]) + out1 = &memout; + if (memory[2]) + out2 = &memout; + INTON; +} + + +static void +openredirect(union node *redir, char memory[10]) +{ + struct stat sb; + int fd = redir->nfile.fd; + const char *fname; + int f; + int e; + + memory[fd] = 0; + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDONLY)) < 0) + error("cannot open %s: %s", fname, strerror(errno)); + break; + case NFROMTO: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDWR|O_CREAT, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NTO: + if (Cflag) { + fname = redir->nfile.expfname; + if (stat(fname, &sb) == -1) { + if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + } else if (!S_ISREG(sb.st_mode)) { + if ((f = open(fname, O_WRONLY, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + if (fstat(f, &sb) != -1 && S_ISREG(sb.st_mode)) { + close(f); + error("cannot create %s: %s", fname, + strerror(EEXIST)); + } + } else + error("cannot create %s: %s", fname, + strerror(EEXIST)); + break; + } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NAPPEND: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NTOFD: + case NFROMFD: + if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ + if (memory[redir->ndup.dupfd]) + memory[fd] = 1; + else { + if (dup2(redir->ndup.dupfd, fd) < 0) + error("%d: %s", redir->ndup.dupfd, + strerror(errno)); + } + } else { + close(fd); + } + return; + case NHERE: + case NXHERE: + f = openhere(redir); + break; + default: + abort(); + } + if (f != fd) { + if (dup2(f, fd) == -1) { + e = errno; + close(f); + error("%d: %s", fd, strerror(e)); + } + close(f); + } +} + + +/* + * 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) +{ + const char *p; + int pip[2]; + size_t len = 0; + int flags; + ssize_t written = 0; + + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + + if (redir->type == NXHERE) + p = redir->nhere.expdoc; + else + p = redir->nhere.doc->narg.text; + len = strlen(p); + if (len == 0) + goto out; + flags = fcntl(pip[1], F_GETFL, 0); + if (flags != -1 && fcntl(pip[1], F_SETFL, flags | O_NONBLOCK) != -1) { + written = write(pip[1], p, len); + if (written < 0) + written = 0; + if ((size_t)written == len) + goto out; + fcntl(pip[1], F_SETFL, flags); + } + + 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); + signal(SIGTSTP, SIG_IGN); + signal(SIGPIPE, SIG_DFL); + xwrite(pip[1], p + written, len - written); + _exit(0); + } +out: + close(pip[1]); + return pip[0]; +} + + + +/* + * Undo the effects of the last redirection. + */ + +void +popredir(void) +{ + struct redirtab *rp = redirlist; + int i; + + INTOFF; + if (empty_redirs > 0) { + empty_redirs--; + INTON; + return; + } + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] != EMPTY) { + if (rp->renamed[i] >= 0) { + dup2(rp->renamed[i], i); + close(rp->renamed[i]); + } else { + close(i); + } + } + } + fd0_redirected = rp->fd0_redirected; + empty_redirs = rp->empty_redirs; + redirlist = rp->next; + ckfree(rp); + INTON; +} + +/* Return true if fd 0 has already been redirected at least once. */ +int +fd0_redirected_p(void) +{ + return fd0_redirected != 0; +} + +/* + * Discard all saved file descriptors. + */ + +void +clearredir(void) +{ + struct redirtab *rp; + int i; + + for (rp = redirlist ; rp ; rp = rp->next) { + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] >= 0) { + close(rp->renamed[i]); + } + rp->renamed[i] = EMPTY; + } + } +} diff --git a/bin/1sh/redir.h b/bin/1sh/redir.h new file mode 100644 index 00000000..6946f6a7 --- /dev/null +++ b/bin/1sh/redir.h @@ -0,0 +1,47 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)redir.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/redir.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* flags passed to redirect */ +#define REDIR_PUSH 01 /* save previous values of file descriptors */ +#define REDIR_BACKQ 02 /* save the command output in memory */ + +union node; +void redirect(union node *, int); +void popredir(void); +int fd0_redirected_p(void); +void clearredir(void); + diff --git a/bin/1sh/sh.1 b/bin/1sh/sh.1 new file mode 100644 index 00000000..e8c056c6 --- /dev/null +++ b/bin/1sh/sh.1 @@ -0,0 +1,2890 @@ +.\"- +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Kenneth Almquist. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. 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. +.\" +.\" from: @(#)sh.1 8.6 (Berkeley) 5/4/95 +.\" $FreeBSD: releng/12.1/bin/sh/sh.1 345487 2019-03-24 22:10:26Z jilles $ +.\" +.Dd February 24, 2019 +.Dt SH 1 +.Os +.Sh NAME +.Nm sh +.Nd command interpreter (shell) +.Sh SYNOPSIS +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Oo +.Ar script +.Op Ar arg ... +.Oc +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Fl c Ar string +.Oo +.Ar name +.Op Ar arg ... +.Oc +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Fl s +.Op Ar arg ... +.Sh DESCRIPTION +The +.Nm +utility is the standard command interpreter for the system. +The current version of +.Nm +is close to the +.St -p1003.1 +specification for the shell. +It only supports features +designated by POSIX, +plus a few Berkeley extensions. +This man page is not intended to be a tutorial nor a complete +specification of the shell. +.Ss Overview +The shell is a command that reads lines from +either a file or the terminal, interprets them, and +generally executes other commands. +It is the program that is started when a user logs into the system, +although a user can select a different shell with the +.Xr chsh 1 +command. +The shell +implements a language that has flow control constructs, +a macro facility that provides a variety of features in +addition to data storage, along with built-in history and line +editing capabilities. +It incorporates many features to +aid interactive use and has the advantage that the interpretative +language is common to both interactive and non-interactive +use (shell scripts). +That is, commands can be typed directly +to the running shell or can be put into a file, +which can be executed directly by the shell. +.Ss Invocation +.\" +.\" XXX This next sentence is incredibly confusing. +.\" +If no arguments are present and if the standard input of the shell +is connected to a terminal +(or if the +.Fl i +option is set), +the shell is considered an interactive shell. +An interactive shell +generally prompts before each command and handles programming +and command errors differently (as described below). +When first starting, the shell inspects argument 0, and +if it begins with a dash +.Pq Ql - , +the shell is also considered a login shell. +This is normally done automatically by the system +when the user first logs in. +A login shell first reads commands +from the files +.Pa /etc/profile +and then +.Pa .profile +in a user's home directory, +if they exist. +If the environment variable +.Ev ENV +is set on entry to a shell, or is set in the +.Pa .profile +of a login shell, the shell then subjects its value to parameter expansion +and arithmetic expansion and reads commands from the named file. +Therefore, a user should place commands that are to be executed only +at login time in the +.Pa .profile +file, and commands that are executed for every shell inside the +.Ev ENV +file. +The user can set the +.Ev ENV +variable to some file by placing the following line in the file +.Pa .profile +in the home directory, +substituting for +.Pa .shrc +the filename desired: +.Pp +.Dl "ENV=$HOME/.shrc; export ENV" +.Pp +The first non-option argument specified on the command line +will be treated as the +name of a file from which to read commands (a shell script), and +the remaining arguments are set as the positional parameters +of the shell +.Li ( $1 , $2 , +etc.). +Otherwise, the shell reads commands +from its standard input. +.Pp +Unlike older versions of +.Nm +the +.Ev ENV +script is only sourced on invocation of interactive shells. +This +closes a well-known, and sometimes easily exploitable security +hole related to poorly thought out +.Ev ENV +scripts. +.Ss Argument List Processing +All of the single letter options to +.Nm +have a corresponding long name, +with the exception of +.Fl c +and +.Fl /+o . +These long names are provided next to the single letter options +in the descriptions below. +The long name for an option may be specified as an argument to the +.Fl /+o +option of +.Nm . +Once the shell is running, +the long name for an option may be specified as an argument to the +.Fl /+o +option of the +.Ic set +built-in command +(described later in the section called +.Sx Built-in Commands ) . +Introducing an option with a dash +.Pq Ql - +enables the option, +while using a plus +.Pq Ql + +disables the option. +A +.Dq Li -- +or plain +.Ql - +will stop option processing and will force the remaining +words on the command line to be treated as arguments. +The +.Fl /+o +and +.Fl c +options do not have long names. +They take arguments and are described after the single letter options. +.Bl -tag -width indent +.It Fl a Li allexport +Flag variables for export when assignments are made to them. +.It Fl b Li notify +Enable asynchronous notification of background job +completion. +(UNIMPLEMENTED) +.It Fl C Li noclobber +Do not overwrite existing files with +.Ql > . +.It Fl E Li emacs +Enable the built-in +.Xr emacs 1 +command line editor (disables the +.Fl V +option if it has been set; +set automatically when interactive on terminals). +.It Fl e Li errexit +Exit immediately if any untested command fails in non-interactive mode. +The exit status of a command is considered to be +explicitly tested if the command is part of the list used to control +an +.Ic if , elif , while , +or +.Ic until ; +if the command is the left +hand operand of an +.Dq Li && +or +.Dq Li || +operator; or if the command is a pipeline preceded by the +.Ic !\& +keyword. +If a shell function is executed and its exit status is explicitly +tested, all commands of the function are considered to be tested as +well. +.Pp +It is recommended to check for failures explicitly +instead of relying on +.Fl e +because it tends to behave in unexpected ways, +particularly in larger scripts. +.It Fl f Li noglob +Disable pathname expansion. +.It Fl h Li trackall +A do-nothing option for POSIX compliance. +.It Fl I Li ignoreeof +Ignore +.Dv EOF Ap s +from input when in interactive mode. +.It Fl i Li interactive +Force the shell to behave interactively. +.It Fl m Li monitor +Turn on job control (set automatically when interactive). +A new process group is created for each pipeline (called a job). +It is possible to suspend jobs or to have them run in the foreground or +in the background. +In a non-interactive shell, +this option can be set even if no terminal is available +and is useful to place processes in separate process groups. +.It Fl n Li noexec +If not interactive, read commands but do not +execute them. +This is useful for checking the +syntax of shell scripts. +.It Fl P Li physical +Change the default for the +.Ic cd +and +.Ic pwd +commands from +.Fl L +(logical directory layout) +to +.Fl P +(physical directory layout). +.It Fl p Li privileged +Turn on privileged mode. +This mode is enabled on startup +if either the effective user or group ID is not equal to the +real user or group ID. +Turning this mode off sets the +effective user and group IDs to the real user and group IDs. +When this mode is enabled for interactive shells, the file +.Pa /etc/suid_profile +is sourced instead of +.Pa ~/.profile +after +.Pa /etc/profile +is sourced, and the contents of the +.Ev ENV +variable are ignored. +.It Fl s Li stdin +Read commands from standard input (set automatically +if no file arguments are present). +This option has +no effect when set after the shell has already started +running (i.e., when set with the +.Ic set +command). +.It Fl T Li trapsasync +When waiting for a child, execute traps immediately. +If this option is not set, +traps are executed after the child exits, +as specified in +.St -p1003.2 . +This nonstandard option is useful for putting guarding shells around +children that block signals. +The surrounding shell may kill the child +or it may just return control to the tty and leave the child alone, +like this: +.Bd -literal -offset indent +sh -T -c "trap 'exit 1' 2 ; some-blocking-program" +.Ed +.It Fl u Li nounset +Write a message to standard error when attempting +to expand a variable, a positional parameter or +the special parameter +.Va \&! +that is not set, and if the +shell is not interactive, exit immediately. +.It Fl V Li vi +Enable the built-in +.Xr vi 1 +command line editor (disables +.Fl E +if it has been set). +.It Fl v Li verbose +The shell writes its input to standard error +as it is read. +Useful for debugging. +.It Fl x Li xtrace +Write each command +(preceded by the value of the +.Va PS4 +variable subjected to parameter expansion and arithmetic expansion) +to standard error before it is executed. +Useful for debugging. +.It Li nolog +Another do-nothing option for POSIX compliance. +It only has a long name. +.It Li pipefail +Change the exit status of a pipeline to the last non-zero exit status of +any command in the pipeline, if any. +Since an exit due to +.Dv SIGPIPE +counts as a non-zero exit status, +this option may cause non-zero exit status for successful pipelines +if a command such as +.Xr head 1 +in the pipeline terminates with status 0 without reading its +input completely. +This option only has a long name. +.El +.Pp +The +.Fl c +option causes the commands to be read from the +.Ar string +operand instead of from the standard input. +Keep in mind that this option only accepts a single string as its +argument, hence multi-word strings must be quoted. +.Pp +The +.Fl /+o +option takes as its only argument the long name of an option +to be enabled or disabled. +For example, the following two invocations of +.Nm +both enable the built-in +.Xr emacs 1 +command line editor: +.Bd -literal -offset indent +set -E +set -o emacs +.Ed +.Pp +If used without an argument, the +.Fl o +option displays the current option settings in a human-readable format. +If +.Cm +o +is used without an argument, the current option settings are output +in a format suitable for re-input into the shell. +.Ss Lexical Structure +The shell reads input in terms of lines from a file and breaks +it up into words at whitespace (blanks and tabs), and at +certain sequences of +characters called +.Dq operators , +which are special to the shell. +There are two types of operators: control operators and +redirection operators (their meaning is discussed later). +The following is a list of valid operators: +.Bl -tag -width indent +.It Control operators: +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li & Ta Li && Ta Li \&( Ta Li \&) Ta Li \en +.It Li ;; Ta Li ;& Ta Li \&; Ta Li \&| Ta Li || +.El +.It Redirection operators: +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li < Ta Li > Ta Li << Ta Li >> Ta Li <> +.It Li <& Ta Li >& Ta Li <<- Ta Li >| Ta \& +.El +.El +.Pp +The character +.Ql # +introduces a comment if used at the beginning of a word. +The word starting with +.Ql # +and the rest of the line are ignored. +.Pp +ASCII +.Dv NUL +characters (character code 0) are not allowed in shell input. +.Ss Quoting +Quoting is used to remove the special meaning of certain characters +or words to the shell, such as operators, whitespace, keywords, +or alias names. +.Pp +There are four types of quoting: matched single quotes, +dollar-single quotes, +matched double quotes, and backslash. +.Bl -tag -width indent +.It Single Quotes +Enclosing characters in single quotes preserves the literal +meaning of all the characters (except single quotes, making +it impossible to put single-quotes in a single-quoted string). +.It Dollar-Single Quotes +Enclosing characters between +.Li $' +and +.Li ' +preserves the literal meaning of all characters +except backslashes and single quotes. +A backslash introduces a C-style escape sequence: +.Bl -tag -width xUnnnnnnnn +.It \ea +Alert (ring the terminal bell) +.It \eb +Backspace +.It \ec Ns Ar c +The control character denoted by +.Li ^ Ns Ar c +in +.Xr stty 1 . +If +.Ar c +is a backslash, it must be doubled. +.It \ee +The ESC character (ASCII 0x1b) +.It \ef +Formfeed +.It \en +Newline +.It \er +Carriage return +.It \et +Horizontal tab +.It \ev +Vertical tab +.It \e\e +Literal backslash +.It \e\&' +Literal single-quote +.It \e\&" +Literal double-quote +.It \e Ns Ar nnn +The byte whose octal value is +.Ar nnn +(one to three digits) +.It \ex Ns Ar nn +The byte whose hexadecimal value is +.Ar nn +(one or more digits only the last two of which are used) +.It \eu Ns Ar nnnn +The Unicode code point +.Ar nnnn +(four hexadecimal digits) +.It \eU Ns Ar nnnnnnnn +The Unicode code point +.Ar nnnnnnnn +(eight hexadecimal digits) +.El +.Pp +The sequences for Unicode code points are currently only useful with +UTF-8 locales. +They reject code point 0 and UTF-16 surrogates. +.Pp +If an escape sequence would produce a byte with value 0, +that byte and the rest of the string until the matching single-quote +are ignored. +.Pp +Any other string starting with a backslash is an error. +.It Double Quotes +Enclosing characters within double quotes preserves the literal +meaning of all characters except dollar sign +.Pq Ql $ , +backquote +.Pq Ql ` , +and backslash +.Pq Ql \e . +The backslash inside double quotes is historically weird. +It remains literal unless it precedes the following characters, +which it serves to quote: +.Pp +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li $ Ta Li ` Ta Li \&" Ta Li \e Ta Li \en +.El +.It Backslash +A backslash preserves the literal meaning of the following +character, with the exception of the newline character +.Pq Ql \en . +A backslash preceding a newline is treated as a line continuation. +.El +.Ss Keywords +Keywords or reserved words are words that have special meaning to the +shell and are recognized at the beginning of a line and +after a control operator. +The following are keywords: +.Bl -column "doneXX" "elifXX" "elseXX" "untilXX" "whileX" -offset center +.It Li \&! Ta { Ta } Ta Ic case Ta Ic do +.It Ic done Ta Ic elif Ta Ic else Ta Ic esac Ta Ic fi +.It Ic for Ta Ic if Ta Ic then Ta Ic until Ta Ic while +.El +.Ss Aliases +An alias is a name and corresponding value set using the +.Ic alias +built-in command. +Wherever the command word of a simple command may occur, +and after checking for keywords if a keyword may occur, the shell +checks the word to see if it matches an alias. +If it does, it replaces it in the input stream with its value. +For example, if there is an alias called +.Dq Li lf +with the value +.Dq Li "ls -F" , +then the input +.Pp +.Dl "lf foobar" +.Pp +would become +.Pp +.Dl "ls -F foobar" +.Pp +Aliases are also recognized after an alias +whose value ends with a space or tab. +For example, if there is also an alias called +.Dq Li nohup +with the value +.Dq Li "nohup " , +then the input +.Pp +.Dl "nohup lf foobar" +.Pp +would become +.Pp +.Dl "nohup ls -F foobar" +.Pp +Aliases provide a convenient way for naive users to +create shorthands for commands without having to learn how +to create functions with arguments. +Using aliases in scripts is discouraged +because the command that defines them must be executed +before the code that uses them is parsed. +This is fragile and not portable. +.Pp +An alias name may be escaped in a command line, so that it is not +replaced by its alias value, by using quoting characters within or +adjacent to the alias name. +This is most often done by prefixing +an alias name with a backslash to execute a function, built-in, or +normal program with the same name. +See the +.Sx Quoting +subsection. +.Ss Commands +The shell interprets the words it reads according to a +language, the specification of which is outside the scope +of this man page (refer to the BNF in the +.St -p1003.2 +document). +Essentially though, a line is read and if +the first word of the line (or after a control operator) +is not a keyword, then the shell has recognized a +simple command. +Otherwise, a complex command or some +other special construct may have been recognized. +.Ss Simple Commands +If a simple command has been recognized, the shell performs +the following actions: +.Bl -enum +.It +Leading words of the form +.Dq Li name=value +are stripped off and assigned to the environment of +the simple command +(they do not affect expansions). +Redirection operators and +their arguments (as described below) are stripped +off and saved for processing. +.It +The remaining words are expanded as described in +the section called +.Sx Word Expansions , +and the first remaining word is considered the command +name and the command is located. +The remaining +words are considered the arguments of the command. +If no command name resulted, then the +.Dq Li name=value +variable assignments recognized in 1) affect the +current shell. +.It +Redirections are performed as described in +the next section. +.El +.Ss Redirections +Redirections are used to change where a command reads its input +or sends its output. +In general, redirections open, close, or +duplicate an existing reference to a file. +The overall format +used for redirection is: +.Pp +.D1 Oo Ar n Oc Ar redir-op file +.Pp +The +.Ar redir-op +is one of the redirection operators mentioned +previously. +The following gives some examples of how these +operators can be used. +Note that stdin and stdout are commonly used abbreviations +for standard input and standard output respectively. +.Bl -tag -width "1234567890XX" -offset indent +.It Oo Ar n Oc Ns Li > Ar file +redirect stdout (or file descriptor +.Ar n ) +to +.Ar file +.It Oo Ar n Oc Ns Li >| Ar file +same as above, but override the +.Fl C +option +.It Oo Ar n Oc Ns Li >> Ar file +append stdout (or file descriptor +.Ar n ) +to +.Ar file +.It Oo Ar n Oc Ns Li < Ar file +redirect stdin (or file descriptor +.Ar n ) +from +.Ar file +.It Oo Ar n Oc Ns Li <> Ar file +redirect stdin (or file descriptor +.Ar n ) +to and from +.Ar file +.It Oo Ar n1 Oc Ns Li <& Ns Ar n2 +duplicate stdin (or file descriptor +.Ar n1 ) +from file descriptor +.Ar n2 +.It Oo Ar n Oc Ns Li <&- +close stdin (or file descriptor +.Ar n ) +.It Oo Ar n1 Oc Ns Li >& Ns Ar n2 +duplicate stdout (or file descriptor +.Ar n1 ) +to file descriptor +.Ar n2 +.It Oo Ar n Oc Ns Li >&- +close stdout (or file descriptor +.Ar n ) +.El +.Pp +The following redirection is often called a +.Dq here-document . +.Bd -unfilled -offset indent +.Oo Ar n Oc Ns Li << Ar delimiter +.Ar here-doc-text +.Ar ... +.Ar delimiter +.Ed +.Pp +All the text on successive lines up to the delimiter is +saved away and made available to the command on standard +input, or file descriptor +.Ar n +if it is specified. +If the +.Ar delimiter +as specified on the initial line is quoted, then the +.Ar here-doc-text +is treated literally, otherwise the text is subjected to +parameter expansion, command substitution, and arithmetic +expansion (as described in the section on +.Sx Word Expansions ) . +If the operator is +.Dq Li <<- +instead of +.Dq Li << , +then leading tabs +in the +.Ar here-doc-text +are stripped. +.Ss Search and Execution +There are three types of commands: shell functions, +built-in commands, and normal programs. +The command is searched for (by name) in that order. +The three types of commands are all executed in a different way. +.Pp +When a shell function is executed, all of the shell positional +parameters (except +.Li $0 , +which remains unchanged) are +set to the arguments of the shell function. +The variables which are explicitly placed in the environment of +the command (by placing assignments to them before the +function name) are made local to the function and are set +to the values given. +Then the command given in the function definition is executed. +The positional parameters are restored to their original values +when the command completes. +This all occurs within the current shell. +.Pp +Shell built-in commands are executed internally to the shell, without +spawning a new process. +There are two kinds of built-in commands: regular and special. +Assignments before special builtins persist after they finish +executing and assignment errors, redirection errors and certain +operand errors cause a script to be aborted. +Special builtins cannot be overridden with a function. +Both regular and special builtins can affect the shell in ways +normal programs cannot. +.Pp +Otherwise, if the command name does not match a function +or built-in command, the command is searched for as a normal +program in the file system (as described in the next section). +When a normal program is executed, the shell runs the program, +passing the arguments and the environment to the program. +If the program is not a normal executable file +(i.e., if it does not begin with the +.Dq "magic number" +whose ASCII representation is +.Dq Li #! , +resulting in an +.Er ENOEXEC +return value from +.Xr execve 2 ) +but appears to be a text file, +the shell will run a new instance of +.Nm +to interpret it. +.Pp +Note that previous versions of this document +and the source code itself misleadingly and sporadically +refer to a shell script without a magic number +as a +.Dq "shell procedure" . +.Ss Path Search +When locating a command, the shell first looks to see if +it has a shell function by that name. +Then it looks for a +built-in command by that name. +If a built-in command is not found, +one of two things happen: +.Bl -enum +.It +Command names containing a slash are simply executed without +performing any searches. +.It +The shell searches each entry in the +.Va PATH +variable +in turn for the command. +The value of the +.Va PATH +variable should be a series of +entries separated by colons. +Each entry consists of a +directory name. +The current directory +may be indicated implicitly by an empty directory name, +or explicitly by a single period. +.El +.Ss Command Exit Status +Each command has an exit status that can influence the behavior +of other shell commands. +The paradigm is that a command exits +with zero for normal or success, and non-zero for failure, +error, or a false indication. +The man page for each command +should indicate the various exit codes and what they mean. +Additionally, the built-in commands return exit codes, as does +an executed shell function. +.Pp +If a command is terminated by a signal, its exit status is greater than 128. +The signal name can be found by passing the exit status to +.Li kill -l . +.Pp +If there is no command word, +the exit status is the exit status of the last command substitution executed, +or zero if the command does not contain any command substitutions. +.Ss Complex Commands +Complex commands are combinations of simple commands +with control operators or keywords, together creating a larger complex +command. +More generally, a command is one of the following: +.Bl -item -offset indent +.It +simple command +.It +pipeline +.It +list or compound-list +.It +compound command +.It +function definition +.El +.Pp +Unless otherwise stated, the exit status of a command is +that of the last simple command executed by the command, +or zero if no simple command was executed. +.Ss Pipelines +A pipeline is a sequence of one or more commands separated +by the control operator +.Ql \&| . +The standard output of all but +the last command is connected to the standard input +of the next command. +The standard output of the last +command is inherited from the shell, as usual. +.Pp +The format for a pipeline is: +.Pp +.D1 Oo Li \&! Oc Ar command1 Op Li \&| Ar command2 ... +.Pp +The standard output of +.Ar command1 +is connected to the standard input of +.Ar command2 . +The standard input, standard output, or +both of a command is considered to be assigned by the +pipeline before any redirection specified by redirection +operators that are part of the command. +.Pp +Note that unlike some other shells, +.Nm +executes each process in a pipeline with more than one command +in a subshell environment and as a child of the +.Nm +process. +.Pp +If the pipeline is not in the background (discussed later), +the shell waits for all commands to complete. +.Pp +If the keyword +.Ic !\& +does not precede the pipeline, the +exit status is the exit status of the last command specified +in the pipeline if the +.Cm pipefail +option is not set or all commands returned zero, +or the last non-zero exit status of any command in the pipeline otherwise. +Otherwise, the exit status is the logical +NOT of that exit status. +That is, if +that status is zero, the exit status is 1; if +that status is greater than zero, the exit status +is zero. +.Pp +Because pipeline assignment of standard input or standard +output or both takes place before redirection, it can be +modified by redirection. +For example: +.Pp +.Dl "command1 2>&1 | command2" +.Pp +sends both the standard output and standard error of +.Ar command1 +to the standard input of +.Ar command2 . +.Pp +A +.Ql \&; +or newline terminator causes the preceding +AND-OR-list +(described below in the section called +.Sx Short-Circuit List Operators ) +to be executed sequentially; +an +.Ql & +causes asynchronous execution of the preceding AND-OR-list. +.Ss Background Commands (&) +If a command is terminated by the control operator ampersand +.Pq Ql & , +the shell executes the command in a subshell environment (see +.Sx Grouping Commands Together +below) and asynchronously; +the shell does not wait for the command to finish +before executing the next command. +.Pp +The format for running a command in background is: +.Pp +.D1 Ar command1 Li & Op Ar command2 Li & Ar ... +.Pp +If the shell is not interactive, the standard input of an +asynchronous command is set to +.Pa /dev/null . +.Pp +The exit status is zero. +.Ss Lists (Generally Speaking) +A list is a sequence of zero or more commands separated by +newlines, semicolons, or ampersands, +and optionally terminated by one of these three characters. +The commands in a +list are executed in the order they are written. +If command is followed by an ampersand, the shell starts the +command and immediately proceeds onto the next command; +otherwise it waits for the command to terminate before +proceeding to the next one. +.Ss Short-Circuit List Operators +.Dq Li && +and +.Dq Li || +are AND-OR list operators. +.Dq Li && +executes the first command, and then executes the second command +if the exit status of the first command is zero. +.Dq Li || +is similar, but executes the second command if the exit +status of the first command is nonzero. +.Dq Li && +and +.Dq Li || +both have the same priority. +.Ss Flow-Control Constructs (if, while, for, case) +The syntax of the +.Ic if +command is: +.Bd -unfilled -offset indent -compact +.Ic if Ar list +.Ic then Ar list +.Oo Ic elif Ar list +.Ic then Ar list Oc Ar ... +.Op Ic else Ar list +.Ic fi +.Ed +.Pp +The exit status is that of selected +.Ic then +or +.Ic else +list, +or zero if no list was selected. +.Pp +The syntax of the +.Ic while +command is: +.Bd -unfilled -offset indent -compact +.Ic while Ar list +.Ic do Ar list +.Ic done +.Ed +.Pp +The two lists are executed repeatedly while the exit status of the +first list is zero. +The +.Ic until +command is similar, but has the word +.Ic until +in place of +.Ic while , +which causes it to +repeat until the exit status of the first list is zero. +.Pp +The exit status is that of the last execution of the second list, +or zero if it was never executed. +.Pp +The syntax of the +.Ic for +command is: +.Bd -unfilled -offset indent -compact +.Ic for Ar variable Op Ic in Ar word ... +.Ic do Ar list +.Ic done +.Ed +.Pp +If +.Ic in +and the following words are omitted, +.Ic in Li \&"$@\&" +is used instead. +The words are expanded, and then the list is executed +repeatedly with the variable set to each word in turn. +The +.Ic do +and +.Ic done +commands may be replaced with +.Ql { +and +.Ql } . +.Pp +The syntax of the +.Ic break +and +.Ic continue +commands is: +.D1 Ic break Op Ar num +.D1 Ic continue Op Ar num +.Pp +The +.Ic break +command terminates the +.Ar num +innermost +.Ic for +or +.Ic while +loops. +The +.Ic continue +command continues with the next iteration of the innermost loop. +These are implemented as special built-in commands. +.Pp +The syntax of the +.Ic case +command is: +.Bd -unfilled -offset indent -compact +.Ic case Ar word Ic in +.Ar pattern ) Ar list Li ;; +.Ar ... +.Ic esac +.Ed +.Pp +The pattern can actually be one or more patterns +(see +.Sx Shell Patterns +described later), +separated by +.Ql \&| +characters. +Tilde expansion, parameter expansion, command substitution, +arithmetic expansion and quote removal are applied to the word. +Then, each pattern is expanded in turn using tilde expansion, +parameter expansion, command substitution and arithmetic expansion and +the expanded form of the word is checked against it. +If a match is found, the corresponding list is executed. +If the selected list is terminated by the control operator +.Ql ;& +instead of +.Ql ;; , +execution continues with the next list, +continuing until a list terminated with +.Ql ;; +or the end of the +.Ic case +command. +.Ss Grouping Commands Together +Commands may be grouped by writing either +.Pp +.Sm off +.Bd -literal -offset -ident +.Po Ar list Pc +.Ed +.Sm on +.Pp +or +.Bd -literal -offset -ident +.No { Ar list ; } +.Ed +.Pp +The first form executes the commands in a subshell environment. +A subshell environment has its own copy of: +.Bl -enum +.It +The current working directory as set by +.Ic cd . +.It +The file creation mask as set by +.Ic umask . +.It +Resource limits as set by +.Ic ulimit . +.It +References to open files. +.It +Traps as set by +.Ic trap . +.It +Known jobs. +.It +Positional parameters and variables. +.It +Shell options. +.It +Shell functions. +.It +Shell aliases. +.El +.Pp +These are copied from the parent shell environment, +except that trapped (but not ignored) signals are reset to the default action +and known jobs are cleared. +Any changes do not affect the parent shell environment. +.Pp +A subshell environment may be implemented as a child process or differently. +If job control is enabled in an interactive shell, +commands grouped in parentheses can be suspended and continued as a unit. +.Pp +For compatibility with other shells, +two open parentheses in sequence should be separated by whitespace. +.Pp +The second form never forks another shell, +so it is slightly more efficient. +Grouping commands together this way allows the user to +redirect their output as though they were one program: +.Bd -literal -offset indent +{ echo -n "hello"; echo " world"; } > greeting +.Ed +.Ss Functions +The syntax of a function definition is +.Pp +.D1 Ar name Li \&( \&) Ar command +.Pp +A function definition is an executable statement; when +executed it installs a function named +.Ar name +and returns an +exit status of zero. +The +.Ar command +is normally a list +enclosed between +.Ql { +and +.Ql } . +.Pp +Variables may be declared to be local to a function by +using the +.Ic local +command. +This should appear as the first statement of a function, +and the syntax is: +.Pp +.D1 Ic local Oo Ar variable ... Oc Op Fl +.Pp +The +.Ic local +command is implemented as a built-in command. +The exit status is zero +unless the command is not in a function or a variable name is invalid. +.Pp +When a variable is made local, it inherits the initial +value and exported and readonly flags from the variable +with the same name in the surrounding scope, if there is +one. +Otherwise, the variable is initially unset. +The shell +uses dynamic scoping, so that if the variable +.Va x +is made local to function +.Em f , +which then calls function +.Em g , +references to the variable +.Va x +made inside +.Em g +will refer to the variable +.Va x +declared inside +.Em f , +not to the global variable named +.Va x . +.Pp +The only special parameter that can be made local is +.Ql - . +Making +.Ql - +local causes any shell options +(including those that only have long names) +that are +changed via the +.Ic set +command inside the function to be +restored to their original values when the function +returns. +.Pp +The syntax of the +.Ic return +command is +.Pp +.D1 Ic return Op Ar exitstatus +.Pp +It terminates the current executional scope, returning from the closest +nested function or sourced script; +if no function or sourced script is being executed, +it exits the shell instance. +The +.Ic return +command is implemented as a special built-in command. +.Ss Variables and Parameters +The shell maintains a set of parameters. +A parameter +denoted by a name +(consisting solely +of alphabetics, numerics, and underscores, +and starting with an alphabetic or an underscore) +is called a variable. +When starting up, +the shell turns all environment variables with valid names into shell +variables. +New variables can be set using the form +.Pp +.D1 Ar name Ns = Ns Ar value +.Pp +A parameter can also be denoted by a number +or a special character as explained below. +.Pp +Assignments are expanded differently from other words: +tilde expansion is also performed after the equals sign and after any colon +and usernames are also terminated by colons, +and field splitting and pathname expansion are not performed. +.Pp +This special expansion applies not only to assignments that form a simple +command by themselves or precede a command word, +but also to words passed to the +.Ic export , +.Ic local +or +.Ic readonly +built-in commands that have this form. +For this, the builtin's name must be literal +(not the result of an expansion) +and may optionally be preceded by one or more literal instances of +.Ic command +without options. +.Ss Positional Parameters +A positional parameter is a parameter denoted by a number greater than zero. +The shell sets these initially to the values of its command line +arguments that follow the name of the shell script. +The +.Ic set +built-in command can also be used to set or reset them. +.Ss Special Parameters +Special parameters are parameters denoted by a single special character +or the digit zero. +They are shown in the following list, exactly as they would appear in input +typed by the user or in the source of a shell script. +.Bl -hang +.It Li $* +Expands to the positional parameters, starting from one. +When +the expansion occurs within a double-quoted string +it expands to a single field with the value of each parameter +separated by the first character of the +.Va IFS +variable, +or by a space if +.Va IFS +is unset. +.It Li $@ +Expands to the positional parameters, starting from one. +When +the expansion occurs within double-quotes, each positional +parameter expands as a separate argument. +If there are no positional parameters, the +expansion of +.Li @ +generates zero arguments, even when +.Li @ +is double-quoted. +What this basically means, for example, is +if +.Li $1 +is +.Dq Li abc +and +.Li $2 +is +.Dq Li "def ghi" , +then +.Li \&"$@\&" +expands to +the two arguments: +.Bd -literal -offset indent +"abc" "def ghi" +.Ed +.It Li $# +Expands to the number of positional parameters. +.It Li $? +Expands to the exit status of the most recent pipeline. +.It Li $- +(hyphen) Expands to the current option flags (the single-letter +option names concatenated into a string) as specified on +invocation, by the +.Ic set +built-in command, or implicitly +by the shell. +.It Li $$ +Expands to the process ID of the invoked shell. +A subshell +retains the same value of +.Va $ +as its parent. +.It Li $! +Expands to the process ID of the most recent background +command executed from the current shell. +For a +pipeline, the process ID is that of the last command in the +pipeline. +If this parameter is referenced, the shell will remember +the process ID and its exit status until the +.Ic wait +built-in command reports completion of the process. +.It Li $0 +(zero) Expands to the name of the shell script if passed on the command line, +the +.Ar name +operand if given (with +.Fl c ) +or otherwise argument 0 passed to the shell. +.El +.Ss Special Variables +The following variables are set by the shell or +have special meaning to it: +.Bl -tag -width ".Va HISTSIZE" +.It Va CDPATH +The search path used with the +.Ic cd +built-in. +.It Va EDITOR +The fallback editor used with the +.Ic fc +built-in. +If not set, the default editor is +.Xr ed 1 . +.It Va FCEDIT +The default editor used with the +.Ic fc +built-in. +.It Va HISTSIZE +The number of previous commands that are accessible. +.It Va HOME +The user's home directory, +used in tilde expansion and as a default directory for the +.Ic cd +built-in. +.It Va IFS +Input Field Separators. +This is initialized at startup to +.Aq space , +.Aq tab , +and +.Aq newline +in that order. +This value also applies if +.Va IFS +is unset, but not if it is set to the empty string. +See the +.Sx White Space Splitting +section for more details. +.It Va LINENO +The current line number in the script or function. +.It Va MAIL +The name of a mail file, that will be checked for the arrival of new +mail. +Overridden by +.Va MAILPATH . +.It Va MAILPATH +A colon +.Pq Ql \&: +separated list of file names, for the shell to check for incoming +mail. +This variable overrides the +.Va MAIL +setting. +There is a maximum of 10 mailboxes that can be monitored at once. +.It Va OPTIND +The index of the next argument to be processed by +.Ic getopts . +This is initialized to 1 at startup. +.It Va PATH +The default search path for executables. +See the +.Sx Path Search +section for details. +.It Va PPID +The parent process ID of the invoked shell. +This is set at startup +unless this variable is in the environment. +A later change of parent process ID is not reflected. +A subshell retains the same value of +.Va PPID . +.It Va PS1 +The primary prompt string, which defaults to +.Dq Li "$ " , +unless you are the superuser, in which case it defaults to +.Dq Li "# " . +.Va PS1 +may include any of the following formatting sequences, +which are replaced by the given information: +.Bl -tag -width indent +.It Li \eH +This system's fully-qualified hostname (FQDN). +.It Li \eh +This system's hostname. +.It Li \eW +The final component of the current working directory. +.It Li \ew +The entire path of the current working directory. +.It Li \e$ +Superuser status. +.Dq Li "$ " +for normal users and +.Dq Li "# " +for superusers. +.It Li \e\e +A literal backslash. +.El +.It Va PS2 +The secondary prompt string, which defaults to +.Dq Li "> " . +.Va PS2 +may include any of the formatting sequences from +.Va PS1 . +.It Va PS4 +The prefix for the trace output (if +.Fl x +is active). +The default is +.Dq Li "+ " . +.El +.Ss Word Expansions +This clause describes the various expansions that are +performed on words. +Not all expansions are performed on +every word, as explained later. +.Pp +Tilde expansions, parameter expansions, command substitutions, +arithmetic expansions, and quote removals that occur within +a single word expand to a single field. +It is only field +splitting or pathname expansion that can create multiple +fields from a single word. +The single exception to this rule is +the expansion of the special parameter +.Va @ +within double-quotes, +as was described above. +.Pp +The order of word expansion is: +.Bl -enum +.It +Tilde Expansion, Parameter Expansion, Command Substitution, +Arithmetic Expansion (these all occur at the same time). +.It +Field Splitting is performed on fields generated by step (1) +unless the +.Va IFS +variable is null. +.It +Pathname Expansion (unless the +.Fl f +option is in effect). +.It +Quote Removal. +.El +.Pp +The +.Ql $ +character is used to introduce parameter expansion, command +substitution, or arithmetic expansion. +.Ss Tilde Expansion (substituting a user's home directory) +A word beginning with an unquoted tilde character +.Pq Ql ~ +is +subjected to tilde expansion. +All the characters up to a slash +.Pq Ql / +or the end of the word are treated as a username +and are replaced with the user's home directory. +If the +username is missing (as in +.Pa ~/foobar ) , +the tilde is replaced with the value of the +.Va HOME +variable (the current user's home directory). +.Ss Parameter Expansion +The format for parameter expansion is as follows: +.Pp +.D1 Li ${ Ns Ar expression Ns Li } +.Pp +where +.Ar expression +consists of all characters until the matching +.Ql } . +Any +.Ql } +escaped by a backslash or within a single-quoted or double-quoted +string, and characters in +embedded arithmetic expansions, command substitutions, and variable +expansions, are not examined in determining the matching +.Ql } . +If the variants with +.Ql + , +.Ql - , +.Ql = +or +.Ql ?\& +occur within a double-quoted string, +as an extension there may be unquoted parts +(via double-quotes inside the expansion); +.Ql } +within such parts are also not examined in determining the matching +.Ql } . +.Pp +The simplest form for parameter expansion is: +.Pp +.D1 Li ${ Ns Ar parameter Ns Li } +.Pp +The value, if any, of +.Ar parameter +is substituted. +.Pp +The parameter name or symbol can be enclosed in braces, which are +optional except for positional parameters with more than one digit or +when parameter is followed by a character that could be interpreted as +part of the name. +If a parameter expansion occurs inside double-quotes: +.Bl -enum +.It +Field splitting is not performed on the results of the +expansion, with the exception of the special parameter +.Va @ . +.It +Pathname expansion is not performed on the results of the +expansion. +.El +.Pp +In addition, a parameter expansion can be modified by using one of the +following formats. +.Bl -tag -width indent +.It Li ${ Ns Ar parameter Ns Li :- Ns Ar word Ns Li } +Use Default Values. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +is substituted; otherwise, the value of +.Ar parameter +is substituted. +.It Li ${ Ns Ar parameter Ns Li := Ns Ar word Ns Li } +Assign Default Values. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +is assigned to +.Ar parameter . +In all cases, the +final value of +.Ar parameter +is substituted. +Quoting inside +.Ar word +does not prevent field splitting or pathname expansion. +Only variables, not positional +parameters or special parameters, can be +assigned in this way. +.It Li ${ Ns Ar parameter Ns Li :? Ns Oo Ar word Oc Ns Li } +Indicate Error if Null or Unset. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +(or a message indicating it is unset if +.Ar word +is omitted) is written to standard +error and the shell exits with a nonzero +exit status. +Otherwise, the value of +.Ar parameter +is substituted. +An +interactive shell need not exit. +.It Li ${ Ns Ar parameter Ns Li :+ Ns Ar word Ns Li } +Use Alternate Value. +If +.Ar parameter +is unset or null, null is substituted; +otherwise, the expansion of +.Ar word +is substituted. +.El +.Pp +In the parameter expansions shown previously, use of the colon in the +format results in a test for a parameter that is unset or null; omission +of the colon results in a test for a parameter that is only unset. +.Pp +The +.Ar word +inherits the type of quoting +(unquoted, double-quoted or here-document) +from the surroundings, +with the exception that a backslash that quotes a closing brace is removed +during quote removal. +.Bl -tag -width indent +.It Li ${# Ns Ar parameter Ns Li } +String Length. +The length in characters of +the value of +.Ar parameter . +.El +.Pp +The following four varieties of parameter expansion provide for substring +processing. +In each case, pattern matching notation +(see +.Sx Shell Patterns ) , +rather than regular expression notation, +is used to evaluate the patterns. +If parameter is one of the special parameters +.Va * +or +.Va @ , +the result of the expansion is unspecified. +Enclosing the full parameter expansion string in double-quotes does not +cause the following four varieties of pattern characters to be quoted, +whereas quoting characters within the braces has this effect. +.Bl -tag -width indent +.It Li ${ Ns Ar parameter Ns Li % Ns Ar word Ns Li } +Remove Smallest Suffix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the smallest portion of the +suffix matched by the pattern deleted. +.It Li ${ Ns Ar parameter Ns Li %% Ns Ar word Ns Li } +Remove Largest Suffix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the largest portion of the +suffix matched by the pattern deleted. +.It Li ${ Ns Ar parameter Ns Li # Ns Ar word Ns Li } +Remove Smallest Prefix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the smallest portion of the +prefix matched by the pattern deleted. +.It Li ${ Ns Ar parameter Ns Li ## Ns Ar word Ns Li } +Remove Largest Prefix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the largest portion of the +prefix matched by the pattern deleted. +.El +.Ss Command Substitution +Command substitution allows the output of a command to be substituted in +place of the command name itself. +Command substitution occurs when +the command is enclosed as follows: +.Pp +.D1 Li $( Ns Ar command Ns Li )\& +.Pp +or the backquoted version: +.Pp +.D1 Li ` Ns Ar command Ns Li ` +.Pp +The shell expands the command substitution by executing command +and replacing the command substitution +with the standard output of the command, +removing sequences of one or more newlines at the end of the substitution. +Embedded newlines before the end of the output are not removed; +however, during field splitting, they may be translated into spaces +depending on the value of +.Va IFS +and the quoting that is in effect. +The command is executed in a subshell environment, +except that the built-in commands +.Ic jobid , +.Ic jobs , +and +.Ic trap +return information about the parent shell environment +and +.Ic times +returns information about the same process +if they are the only command in a command substitution. +.Pp +If a command substitution of the +.Li $( +form begins with a subshell, +the +.Li $( +and +.Li (\& +must be separated by whitespace +to avoid ambiguity with arithmetic expansion. +.Ss Arithmetic Expansion +Arithmetic expansion provides a mechanism for evaluating an arithmetic +expression and substituting its value. +The format for arithmetic expansion is as follows: +.Pp +.D1 Li $(( Ns Ar expression Ns Li )) +.Pp +The +.Ar expression +is treated as if it were in double-quotes, except +that a double-quote inside the expression is not treated specially. +The +shell expands all tokens in the +.Ar expression +for parameter expansion, +command substitution, +arithmetic expansion +and quote removal. +.Pp +The allowed expressions are a subset of C expressions, +summarized below. +.Bl -tag -width "Variables" -offset indent +.It Values +All values are of type +.Ft intmax_t . +.It Constants +Decimal, octal (starting with +.Li 0 ) +and hexadecimal (starting with +.Li 0x ) +integer constants. +.It Variables +Shell variables can be read and written +and contain integer constants. +.It Unary operators +.Li "! ~ + -" +.It Binary operators +.Li "* / % + - << >> < <= > >= == != & ^ | && ||"\& +.It Assignment operators +.Li "= += -= *= /= %= <<= >>= &= ^= |=" +.It Conditional operator +.Li "? :"\& +.El +.Pp +The result of the expression is substituted in decimal. +.Ss White Space Splitting (Field Splitting) +In certain contexts, +after parameter expansion, command substitution, and +arithmetic expansion the shell scans the results of +expansions and substitutions that did not occur in double-quotes for +field splitting and multiple fields can result. +.Pp +Characters in +.Va IFS +that are whitespace +.Po +.Aq space , +.Aq tab , +and +.Aq newline +.Pc +are treated differently from other characters in +.Va IFS . +.Pp +Whitespace in +.Va IFS +at the beginning or end of a word is discarded. +.Pp +Subsequently, a field is delimited by either +.Bl -enum +.It +a non-whitespace character in +.Va IFS +with any whitespace in +.Va IFS +surrounding it, or +.It +one or more whitespace characters in +.Va IFS . +.El +.Pp +If a word ends with a non-whitespace character in +.Va IFS , +there is no empty field after this character. +.Pp +If no field is delimited, the word is discarded. +In particular, if a word consists solely of an unquoted substitution +and the result of the substitution is null, +it is removed by field splitting even if +.Va IFS +is null. +.Ss Pathname Expansion (File Name Generation) +Unless the +.Fl f +option is set, +file name generation is performed +after word splitting is complete. +Each word is +viewed as a series of patterns, separated by slashes. +The +process of expansion replaces the word with the names of +all existing files whose names can be formed by replacing +each pattern with a string that matches the specified pattern. +There are two restrictions on this: first, a pattern cannot match +a string containing a slash, and second, +a pattern cannot match a string starting with a period +unless the first character of the pattern is a period. +The next section describes the patterns used for +Pathname Expansion, +the four varieties of parameter expansion for substring processing and the +.Ic case +command. +.Ss Shell Patterns +A pattern consists of normal characters, which match themselves, +and meta-characters. +The meta-characters are +.Ql * , +.Ql \&? , +and +.Ql \&[ . +These characters lose their special meanings if they are quoted. +When command or variable substitution is performed and the dollar sign +or back quotes are not double-quoted, the value of the +variable or the output of the command is scanned for these +characters and they are turned into meta-characters. +.Pp +An asterisk +.Pq Ql * +matches any string of characters. +A question mark +.Pq Ql \&? +matches any single character. +A left bracket +.Pq Ql \&[ +introduces a character class. +The end of the character class is indicated by a +.Ql \&] ; +if the +.Ql \&] +is missing then the +.Ql \&[ +matches a +.Ql \&[ +rather than introducing a character class. +A character class matches any of the characters between the square brackets. +A locale-dependent range of characters may be specified using a minus sign. +A named class of characters (see +.Xr wctype 3 ) +may be specified by surrounding the name with +.Ql \&[:\& +and +.Ql :\&] . +For example, +.Ql \&[\&[:alpha:\&]\&] +is a shell pattern that matches a single letter. +The character class may be complemented by making an exclamation point +.Pq Ql !\& +the first character of the character class. +A caret +.Pq Ql ^ +has the same effect but is non-standard. +.Pp +To include a +.Ql \&] +in a character class, make it the first character listed +(after the +.Ql \&! +or +.Ql ^ , +if any). +To include a +.Ql - , +make it the first or last character listed. +.Ss Built-in Commands +This section lists the built-in commands. +.Bl -tag -width indent +.It Ic \&: +A null command that returns a 0 (true) exit value. +.It Ic \&. Ar file +The commands in the specified file are read and executed by the shell. +The +.Ic return +command may be used to return to the +.Ic \&. +command's caller. +If +.Ar file +contains any +.Ql / +characters, it is used as is. +Otherwise, the shell searches the +.Va PATH +for the file. +If it is not found in the +.Va PATH , +it is sought in the current working directory. +.It Ic \&[ +A built-in equivalent of +.Xr test 1 . +.It Ic alias Oo Ar name Ns Oo = Ns Ar string Oc ... Oc +If +.Ar name Ns = Ns Ar string +is specified, the shell defines the alias +.Ar name +with value +.Ar string . +If just +.Ar name +is specified, the value of the alias +.Ar name +is printed. +With no arguments, the +.Ic alias +built-in command prints the names and values of all defined aliases +(see +.Ic unalias ) . +Alias values are written with appropriate quoting so that they are +suitable for re-input to the shell. +Also see the +.Sx Aliases +subsection. +.It Ic bg Op Ar job ... +Continue the specified jobs +(or the current job if no jobs are given) +in the background. +.It Ic bind Oo Fl aeklrsv Oc Oo Ar key Oo Ar command Oc Oc +List or alter key bindings for the line editor. +This command is documented in +.Xr editrc 5 . +.It Ic break Op Ar num +See the +.Sx Flow-Control Constructs +subsection. +.It Ic builtin Ar cmd Op Ar arg ... +Execute the specified built-in command, +.Ar cmd . +This is useful when the user wishes to override a shell function +with the same name as a built-in command. +.It Ic cd Oo Fl L | P Oc Oo Fl e Oc Op Ar directory +.It Ic cd Fl +Switch to the specified +.Ar directory , +to the directory specified in the +.Va HOME +environment variable if no +.Ar directory +is specified or +to the directory specified in the +.Va OLDPWD +environment variable if +.Ar directory +is +.Fl . +If +.Ar directory +does not begin with +.Pa / , \&. , +or +.Pa .. , +then the directories listed in the +.Va CDPATH +variable will be +searched for the specified +.Ar directory . +If +.Va CDPATH +is unset, the current directory is searched. +The format of +.Va CDPATH +is the same as that of +.Va PATH . +In an interactive shell, +the +.Ic cd +command will print out the name of the directory +that it actually switched to +if the +.Va CDPATH +mechanism was used or if +.Ar directory +was +.Fl . +.Pp +If the +.Fl P +option is specified, +.Pa .. +is handled physically and symbolic links are resolved before +.Pa .. +components are processed. +If the +.Fl L +option is specified, +.Pa .. +is handled logically. +This is the default. +.Pp +The +.Fl e +option causes +.Ic cd +to return exit status 1 if the full pathname of the new directory +cannot be determined reliably or at all. +Normally this is not considered an error, +although a warning is printed. +.Pp +If changing the directory fails, the exit status is greater than 1. +If the directory is changed, the exit status is 0, or also 1 if +.Fl e +was given. +.It Ic chdir +A synonym for the +.Ic cd +built-in command. +.It Ic command Oo Fl p Oc Op Ar utility Op Ar argument ... +.It Ic command Oo Fl p Oc Fl v Ar utility +.It Ic command Oo Fl p Oc Fl V Ar utility +The first form of invocation executes the specified +.Ar utility , +ignoring shell functions in the search. +If +.Ar utility +is a special builtin, +it is executed as if it were a regular builtin. +.Pp +If the +.Fl p +option is specified, the command search is performed using a +default value of +.Va PATH +that is guaranteed to find all of the standard utilities. +.Pp +If the +.Fl v +option is specified, +.Ar utility +is not executed but a description of its interpretation by the shell is +printed. +For ordinary commands the output is the path name; for shell built-in +commands, shell functions and keywords only the name is written. +Aliases are printed as +.Dq Ic alias Ar name Ns = Ns Ar value . +.Pp +The +.Fl V +option is identical to +.Fl v +except for the output. +It prints +.Dq Ar utility Ic is Ar description +where +.Ar description +is either +the path name to +.Ar utility , +a special shell builtin, +a shell builtin, +a shell function, +a shell keyword +or +an alias for +.Ar value . +.It Ic continue Op Ar num +See the +.Sx Flow-Control Constructs +subsection. +.It Ic echo Oo Fl e | n Oc Op Ar string ... +Print a space-separated list of the arguments to the standard output +and append a newline character. +.Bl -tag -width indent +.It Fl n +Suppress the output of the trailing newline. +.It Fl e +Process C-style backslash escape sequences. +The +.Ic echo +command understands the following character escapes: +.Bl -tag -width indent +.It \ea +Alert (ring the terminal bell) +.It \eb +Backspace +.It \ec +Suppress the trailing newline (this has the side-effect of truncating the +line if it is not the last character) +.It \ee +The ESC character (ASCII 0x1b) +.It \ef +Formfeed +.It \en +Newline +.It \er +Carriage return +.It \et +Horizontal tab +.It \ev +Vertical tab +.It \e\e +Literal backslash +.It \e0nnn +(Zero) The character whose octal value is +.Ar nnn +.El +.Pp +If +.Ar string +is not enclosed in quotes then the backslash itself must be escaped +with a backslash to protect it from the shell. +For example +.Bd -literal -offset indent +$ echo -e "a\evb" +a + b +$ echo -e a\e\evb +a + b +$ echo -e "a\e\eb" +a\eb +$ echo -e a\e\e\e\eb +a\eb +.Ed +.El +.Pp +Only one of the +.Fl e +and +.Fl n +options may be specified. +.It Ic eval Ar string ... +Concatenate all the arguments with spaces. +Then re-parse and execute the command. +.It Ic exec Op Ar command Op arg ... +Unless +.Ar command +is omitted, +the shell process is replaced with the specified program +(which must be a real program, not a shell built-in command or function). +Any redirections on the +.Ic exec +command are marked as permanent, +so that they are not undone when the +.Ic exec +command finishes. +.It Ic exit Op Ar exitstatus +Terminate the shell process. +If +.Ar exitstatus +is given +it is used as the exit status of the shell. +Otherwise, if the shell is executing an +.Cm EXIT +trap, the exit status of the last command before the trap is used; +if the shell is executing a trap for a signal, +the shell exits by resending the signal to itself. +Otherwise, the exit status of the preceding command is used. +The exit status should be an integer between 0 and 255. +.It Ic export Ar name ... +.It Ic export Op Fl p +The specified names are exported so that they will +appear in the environment of subsequent commands. +The only way to un-export a variable is to +.Ic unset +it. +The shell allows the value of a variable to be set +at the same time as it is exported by writing +.Pp +.D1 Ic export Ar name Ns = Ns Ar value +.Pp +With no arguments the +.Ic export +command lists the names +of all exported variables. +If the +.Fl p +option is specified, the exported variables are printed as +.Dq Ic export Ar name Ns = Ns Ar value +lines, suitable for re-input to the shell. +.It Ic false +A null command that returns a non-zero (false) exit value. +.It Ic fc Oo Fl e Ar editor Oc Op Ar first Op Ar last +.It Ic fc Fl l Oo Fl nr Oc Op Ar first Op Ar last +.It Ic fc Fl s Oo Ar old Ns = Ns Ar new Oc Op Ar first +The +.Ic fc +built-in command lists, or edits and re-executes, +commands previously entered to an interactive shell. +.Bl -tag -width indent +.It Fl e Ar editor +Use the editor named by +.Ar editor +to edit the commands. +The +.Ar editor +string is a command name, +subject to search via the +.Va PATH +variable. +The value in the +.Va FCEDIT +variable is used as a default when +.Fl e +is not specified. +If +.Va FCEDIT +is null or unset, the value of the +.Va EDITOR +variable is used. +If +.Va EDITOR +is null or unset, +.Xr ed 1 +is used as the editor. +.It Fl l No (ell) +List the commands rather than invoking +an editor on them. +The commands are written in the +sequence indicated by the +.Ar first +and +.Ar last +operands, as affected by +.Fl r , +with each command preceded by the command number. +.It Fl n +Suppress command numbers when listing with +.Fl l . +.It Fl r +Reverse the order of the commands listed +(with +.Fl l ) +or edited +(with neither +.Fl l +nor +.Fl s ) . +.It Fl s +Re-execute the command without invoking an editor. +.It Ar first +.It Ar last +Select the commands to list or edit. +The number of previous commands that can be accessed +are determined by the value of the +.Va HISTSIZE +variable. +The value of +.Ar first +or +.Ar last +or both are one of the following: +.Bl -tag -width indent +.It Oo Cm + Oc Ns Ar num +A positive number representing a command number; +command numbers can be displayed with the +.Fl l +option. +.It Fl Ar num +A negative decimal number representing the +command that was executed +.Ar num +of +commands previously. +For example, \-1 is the immediately previous command. +.It Ar string +A string indicating the most recently entered command +that begins with that string. +If the +.Ar old Ns = Ns Ar new +operand is not also specified with +.Fl s , +the string form of the first operand cannot contain an embedded equal sign. +.El +.El +.Pp +The following variables affect the execution of +.Ic fc : +.Bl -tag -width ".Va HISTSIZE" +.It Va FCEDIT +Name of the editor to use for history editing. +.It Va HISTSIZE +The number of previous commands that are accessible. +.El +.It Ic fg Op Ar job +Move the specified +.Ar job +or the current job to the foreground. +.It Ic getopts Ar optstring var +The POSIX +.Ic getopts +command. +The +.Ic getopts +command deprecates the older +.Xr getopt 1 +command. +The first argument should be a series of letters, each possibly +followed by a colon which indicates that the option takes an argument. +The specified variable is set to the parsed option. +The index of +the next argument is placed into the shell variable +.Va OPTIND . +If an option takes an argument, it is placed into the shell variable +.Va OPTARG . +If an invalid option is encountered, +.Ar var +is set to +.Ql \&? . +It returns a false value (1) when it encounters the end of the options. +A new set of arguments may be parsed by assigning +.Li OPTIND=1 . +.It Ic hash Oo Fl rv Oc Op Ar command ... +The shell maintains a hash table which remembers the locations of commands. +With no arguments whatsoever, the +.Ic hash +command prints out the contents of this table. +.Pp +With arguments, the +.Ic hash +command removes each specified +.Ar command +from the hash table (unless they are functions) and then locates it. +With the +.Fl v +option, +.Ic hash +prints the locations of the commands as it finds them. +The +.Fl r +option causes the +.Ic hash +command to delete all the entries in the hash table except for functions. +.It Ic jobid Op Ar job +Print the process IDs of the processes in the specified +.Ar job . +If the +.Ar job +argument is omitted, use the current job. +.It Ic jobs Oo Fl lps Oc Op Ar job ... +Print information about the specified jobs, or all jobs if no +.Ar job +argument is given. +The information printed includes job ID, status and command name. +.Pp +If the +.Fl l +option is specified, the PID of each job is also printed. +If the +.Fl p +option is specified, only the process IDs for the process group leaders +are printed, one per line. +If the +.Fl s +option is specified, only the PIDs of the job commands are printed, one per +line. +.It Ic kill +A built-in equivalent of +.Xr kill 1 +that additionally supports sending signals to jobs. +.It Ic local Oo Ar variable ... Oc Op Fl +See the +.Sx Functions +subsection. +.It Ic printf +A built-in equivalent of +.Xr printf 1 . +.It Ic pwd Op Fl L | P +Print the path of the current directory. +The built-in command may +differ from the program of the same name because the +built-in command remembers what the current directory +is rather than recomputing it each time. +This makes +it faster. +However, if the current directory is +renamed, +the built-in version of +.Xr pwd 1 +will continue to print the old name for the directory. +.Pp +If the +.Fl P +option is specified, symbolic links are resolved. +If the +.Fl L +option is specified, the shell's notion of the current directory +is printed (symbolic links are not resolved). +This is the default. +.It Ic read Oo Fl p Ar prompt Oc Oo +.Fl t Ar timeout Oc Oo Fl er Oc Ar variable ... +The +.Ar prompt +is printed if the +.Fl p +option is specified +and the standard input is a terminal. +Then a line is +read from the standard input. +The trailing newline +is deleted from the line and the line is split as +described in the section on +.Sx White Space Splitting (Field Splitting)\& +above, and +the pieces are assigned to the variables in order. +If there are more pieces than variables, the remaining +pieces (along with the characters in +.Va IFS +that separated them) +are assigned to the last variable. +If there are more variables than pieces, the remaining +variables are assigned the null string. +.Pp +Backslashes are treated specially, unless the +.Fl r +option is +specified. +If a backslash is followed by +a newline, the backslash and the newline will be +deleted. +If a backslash is followed by any other +character, the backslash will be deleted and the following +character will be treated as though it were not in +.Va IFS , +even if it is. +.Pp +If the +.Fl t +option is specified and the +.Ar timeout +elapses before a complete line of input is supplied, +the +.Ic read +command will return an exit status as if terminated by +.Dv SIGALRM +without assigning any values. +The +.Ar timeout +value may optionally be followed by one of +.Ql s , +.Ql m +or +.Ql h +to explicitly specify seconds, minutes or hours. +If none is supplied, +.Ql s +is assumed. +.Pp +The +.Fl e +option exists only for backward compatibility with older scripts. +.Pp +The exit status is 0 on success, 1 on end of file, +between 2 and 128 if an error occurs +and greater than 128 if a trapped signal interrupts +.Ic read . +.It Ic readonly Oo Fl p Oc Op Ar name ... +Each specified +.Ar name +is marked as read only, +so that it cannot be subsequently modified or unset. +The shell allows the value of a variable to be set +at the same time as it is marked read only +by using the following form: +.Pp +.D1 Ic readonly Ar name Ns = Ns Ar value +.Pp +With no arguments the +.Ic readonly +command lists the names of all read only variables. +If the +.Fl p +option is specified, the read-only variables are printed as +.Dq Ic readonly Ar name Ns = Ns Ar value +lines, suitable for re-input to the shell. +.It Ic return Op Ar exitstatus +See the +.Sx Functions +subsection. +.It Ic set Oo Fl /+abCEefIimnpTuVvx Oc Oo Fl /+o Ar longname Oc Oo +.Fl c Ar string Oc Op Fl - Ar arg ... +The +.Ic set +command performs three different functions: +.Bl -item +.It +With no arguments, it lists the values of all shell variables. +.It +If options are given, +either in short form or using the long +.Dq Fl /+o Ar longname +form, +it sets or clears the specified options as described in the section called +.Sx Argument List Processing . +.It +If the +.Dq Fl - +option is specified, +.Ic set +will replace the shell's positional parameters with the subsequent +arguments. +If no arguments follow the +.Dq Fl - +option, +all the positional parameters will be cleared, +which is equivalent to executing the command +.Dq Li "shift $#" . +The +.Dq Fl - +flag may be omitted when specifying arguments to be used +as positional replacement parameters. +This is not recommended, +because the first argument may begin with a dash +.Pq Ql - +or a plus +.Pq Ql + , +which the +.Ic set +command will interpret as a request to enable or disable options. +.El +.It Ic setvar Ar variable value +Assigns the specified +.Ar value +to the specified +.Ar variable . +The +.Ic setvar +command is intended to be used in functions that +assign values to variables whose names are passed as parameters. +In general it is better to write +.Dq Ar variable Ns = Ns Ar value +rather than using +.Ic setvar . +.It Ic shift Op Ar n +Shift the positional parameters +.Ar n +times, or once if +.Ar n +is not specified. +A shift sets the value of +.Li $1 +to the value of +.Li $2 , +the value of +.Li $2 +to the value of +.Li $3 , +and so on, +decreasing the value of +.Li $# +by one. +For portability, shifting if there are zero positional parameters +should be avoided, since the shell may abort. +.It Ic test +A built-in equivalent of +.Xr test 1 . +.It Ic times +Print the amount of time spent executing the shell process and its children. +The first output line shows the user and system times for the shell process +itself, the second one contains the user and system times for the +children. +.It Ic trap Oo Ar action Oc Ar signal ... +.It Ic trap Fl l +Cause the shell to parse and execute +.Ar action +when any specified +.Ar signal +is received. +The signals are specified by name or number. +In addition, the pseudo-signal +.Cm EXIT +may be used to specify an +.Ar action +that is performed when the shell terminates. +The +.Ar action +may be an empty string or a dash +.Pq Ql - ; +the former causes the specified signal to be ignored +and the latter causes the default action to be taken. +Omitting the +.Ar action +and using only signal numbers is another way to request the default action. +In a subshell or utility environment, +the shell resets trapped (but not ignored) signals to the default action. +The +.Ic trap +command has no effect on signals that were ignored on entry to the shell. +.Pp +Option +.Fl l +causes the +.Ic trap +command to display a list of valid signal names. +.It Ic true +A null command that returns a 0 (true) exit value. +.It Ic type Op Ar name ... +Interpret each +.Ar name +as a command and print the resolution of the command search. +Possible resolutions are: +shell keyword, alias, special shell builtin, shell builtin, command, +tracked alias +and not found. +For aliases the alias expansion is printed; +for commands and tracked aliases +the complete pathname of the command is printed. +.It Ic ulimit Oo Fl HSabcdfklmnopstuvw Oc Op Ar limit +Set or display resource limits (see +.Xr getrlimit 2 ) . +If +.Ar limit +is specified, the named resource will be set; +otherwise the current resource value will be displayed. +.Pp +If +.Fl H +is specified, the hard limits will be set or displayed. +While everybody is allowed to reduce a hard limit, +only the superuser can increase it. +The +.Fl S +option +specifies the soft limits instead. +When displaying limits, +only one of +.Fl S +or +.Fl H +can be given. +The default is to display the soft limits, +and to set both the hard and the soft limits. +.Pp +Option +.Fl a +causes the +.Ic ulimit +command to display all resources. +The parameter +.Ar limit +is not acceptable in this mode. +.Pp +The remaining options specify which resource value is to be +displayed or modified. +They are mutually exclusive. +.Bl -tag -width indent +.It Fl b Ar sbsize +The maximum size of socket buffer usage, in bytes. +.It Fl c Ar coredumpsize +The maximal size of core dump files, in 512-byte blocks. +Setting +.Ar coredumpsize +to 0 prevents core dump files from being created. +.It Fl d Ar datasize +The maximal size of the data segment of a process, in kilobytes. +.It Fl f Ar filesize +The maximal size of a file, in 512-byte blocks. +.It Fl k Ar kqueues +The maximal number of kqueues +(see +.Xr kqueue 2 ) +for this user ID. +.It Fl l Ar lockedmem +The maximal size of memory that can be locked by a process, in +kilobytes. +.It Fl m Ar memoryuse +The maximal resident set size of a process, in kilobytes. +.It Fl n Ar nofiles +The maximal number of descriptors that could be opened by a process. +.It Fl o Ar umtxp +The maximal number of process-shared locks +(see +.Xr pthread 3 ) +for this user ID. +.It Fl p Ar pseudoterminals +The maximal number of pseudo-terminals for this user ID. +.It Fl s Ar stacksize +The maximal size of the stack segment, in kilobytes. +.It Fl t Ar time +The maximal amount of CPU time to be used by each process, in seconds. +.It Fl u Ar userproc +The maximal number of simultaneous processes for this user ID. +.It Fl v Ar virtualmem +The maximal virtual size of a process, in kilobytes. +.It Fl w Ar swapuse +The maximum amount of swap space reserved or used for this user ID, +in kilobytes. +.El +.It Ic umask Oo Fl S Oc Op Ar mask +Set the file creation mask (see +.Xr umask 2 ) +to the octal or symbolic (see +.Xr chmod 1 ) +value specified by +.Ar mask . +If the argument is omitted, the current mask value is printed. +If the +.Fl S +option is specified, the output is symbolic, otherwise the output is octal. +.It Ic unalias Oo Fl a Oc Op Ar name ... +The specified alias names are removed. +If +.Fl a +is specified, all aliases are removed. +.It Ic unset Oo Fl fv Oc Ar name ... +The specified variables or functions are unset and unexported. +If the +.Fl v +option is specified or no options are given, the +.Ar name +arguments are treated as variable names. +If the +.Fl f +option is specified, the +.Ar name +arguments are treated as function names. +.It Ic wait Op Ar job ... +Wait for each specified +.Ar job +to complete and return the exit status of the last process in the +last specified +.Ar job . +If any +.Ar job +specified is unknown to the shell, it is treated as if it +were a known job that exited with exit status 127. +If no operands are given, wait for all jobs to complete +and return an exit status of zero. +.El +.Ss Command Line Editing +When +.Nm +is being used interactively from a terminal, the current command +and the command history +(see +.Ic fc +in +.Sx Built-in Commands ) +can be edited using +.Nm vi Ns -mode +command line editing. +This mode uses commands similar +to a subset of those described in the +.Xr vi 1 +man page. +The command +.Dq Li "set -o vi" +(or +.Dq Li "set -V" ) +enables +.Nm vi Ns -mode +editing and places +.Nm +into +.Nm vi +insert mode. +With +.Nm vi Ns -mode +enabled, +.Nm +can be switched between insert mode and command mode by typing +.Aq ESC . +Hitting +.Aq return +while in command mode will pass the line to the shell. +.Pp +Similarly, the +.Dq Li "set -o emacs" +(or +.Dq Li "set -E" ) +command can be used to enable a subset of +.Nm emacs Ns -style +command line editing features. +.Sh ENVIRONMENT +The following environment variables affect the execution of +.Nm : +.Bl -tag -width ".Ev LANGXXXXXX" +.It Ev ENV +Initialization file for interactive shells. +.It Ev LANG , Ev LC_* +Locale settings. +These are inherited by children of the shell, +and is used in a limited manner by the shell itself. +.It Ev OLDPWD +The previous current directory. +This is used and updated by +.Ic cd . +.It Ev PWD +An absolute pathname for the current directory, +possibly containing symbolic links. +This is used and updated by the shell. +.It Ev TERM +The default terminal setting for the shell. +This is inherited by children of the shell, and is used in the history +editing modes. +.El +.Pp +Additionally, environment variables are turned into shell variables +at startup, +which may affect the shell as described under +.Sx Special Variables . +.Sh FILES +.Bl -tag -width "/etc/suid_profileXX" -compact +.It Pa ~/.profile +User's login profile. +.It Pa /etc/profile +System login profile. +.It Pa /etc/shells +Shell database. +.It Pa /etc/suid_profile +Privileged shell profile. +.El +.Sh EXIT STATUS +Errors that are detected by the shell, such as a syntax error, will +cause the shell to exit with a non-zero exit status. +If the shell is not an interactive shell, the execution of the shell +file will be aborted. +Otherwise the shell will return the exit status of the last command +executed, or if the +.Ic exit +builtin is used with a numeric argument, it +will return the argument. +.Sh SEE ALSO +.Xr builtin 1 , +.Xr chsh 1 , +.Xr echo 1 , +.Xr ed 1 , +.Xr emacs 1 , +.Xr kill 1 , +.Xr printf 1 , +.Xr pwd 1 , +.Xr test 1 , +.Xr vi 1 , +.Xr execve 2 , +.Xr getrlimit 2 , +.Xr umask 2 , +.Xr wctype 3 , +.Xr editrc 5 , +.Xr shells 5 +.Sh HISTORY +A +.Nm +command, the Thompson shell, appeared in +.At v1 . +It was superseded in +.At v7 +by the Bourne shell, which inherited the name +.Nm . +.Pp +This version of +.Nm +was rewritten in 1989 under the +.Bx +license after the Bourne shell from +.At V.4 . +.Sh AUTHORS +This version of +.Nm +was originally written by +.An Kenneth Almquist . +.Sh BUGS +The +.Nm +utility does not recognize multibyte characters other than UTF-8. +Splitting using +.Va IFS +does not recognize multibyte characters. diff --git a/bin/1sh/shell.h b/bin/1sh/shell.h new file mode 100644 index 00000000..98ac98df --- /dev/null +++ b/bin/1sh/shell.h @@ -0,0 +1,77 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)shell.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/shell.h 345117 2019-03-13 21:53:10Z jilles $ + */ + +#ifndef SHELL_H_ +#define SHELL_H_ + +#include <inttypes.h> + +/* + * The follow should be set to reflect the type of system you have: + * JOBS -> 1 if you have Berkeley job control, 0 otherwise. + * define DEBUG=1 to compile in debugging (set global "debug" to turn on) + * define DEBUG=2 to compile in and turn on debugging. + * + * When debugging is on, debugging info will be written to ./trace and + * a quit signal will generate a core dump. + */ + + +#define JOBS 1 +/* #define DEBUG 1 */ + +/* + * Type of used arithmetics. SUSv3 requires us to have at least signed long. + */ +typedef intmax_t arith_t; +#define ARITH_FORMAT_STR "%" PRIdMAX +#define ARITH_MIN INTMAX_MIN +#define ARITH_MAX INTMAX_MAX + +typedef void *pointer; + +#include <sys/cdefs.h> + +extern char nullstr[1]; /* null string */ + +#ifdef DEBUG +#define TRACE(param) sh_trace param +#else +#define TRACE(param) +#endif + +#endif /* !SHELL_H_ */ diff --git a/bin/1sh/show.c b/bin/1sh/show.c new file mode 100644 index 00000000..e9c79883 --- /dev/null +++ b/bin/1sh/show.c @@ -0,0 +1,410 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)show.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/show.c 326025 2017-11-20 19:49:47Z pfg $"); + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "mystring.h" +#include "show.h" + + +#ifdef 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 *); + + +void +showtree(union node *n) +{ + trputs("showtree called\n"); + shtree(n, 1, NULL, stdout); +} + + +static void +shtree(union node *n, int ind, char *pfx, FILE *fp) +{ + struct nodelist *lp; + 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; + 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 NAPPEND: s = ">>"; dftfd = 1; break; + case NTOFD: s = ">&"; dftfd = 1; break; + case NCLOBBER: s = ">|"; dftfd = 1; break; + case NFROM: s = "<"; dftfd = 0; break; + case NFROMTO: s = "<>"; dftfd = 0; break; + case NFROMFD: s = "<&"; dftfd = 0; break; + case NHERE: s = "<<"; dftfd = 0; break; + case NXHERE: 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) { + if (np->ndup.dupfd >= 0) + fprintf(fp, "%d", np->ndup.dupfd); + else + fprintf(fp, "-"); + } else if (np->nfile.type == NHERE) { + fprintf(fp, "HERE"); + } else if (np->nfile.type == NXHERE) { + fprintf(fp, "XHERE"); + } else { + sharg(np->nfile.fname, fp); + } + first = 0; + } +} + + + +static void +sharg(union node *arg, FILE *fp) +{ + char *p; + struct nodelist *bqlist; + int subtype; + + if (arg->type != NARG) { + printf("<node type %d>\n", arg->type); + fflush(stdout); + 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: + printf("<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; + } + } +} + + +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); + } +} + + +/* + * Debugging stuff. + */ + + +FILE *tracefile; + +#if DEBUG >= 2 +int debug = 1; +#else +int debug = 0; +#endif + + +void +trputc(int c) +{ + if (tracefile == NULL) + return; + putc(c, tracefile); + if (c == '\n') + fflush(tracefile); +} + + +void +sh_trace(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + if (tracefile != NULL) { + (void) vfprintf(tracefile, fmt, va); + if (strchr(fmt, '\n')) + (void) fflush(tracefile); + } + va_end(va); +} + + +void +trputs(const char *s) +{ + if (tracefile == NULL) + return; + fputs(s, tracefile); + if (strchr(s, '\n')) + fflush(tracefile); +} + + +static void +trstring(char *s) +{ + char *p; + char c; + + if (tracefile == NULL) + 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; + } + } + putc('"', tracefile); +} + + +void +trargs(char **ap) +{ + if (tracefile == NULL) + return; + while (*ap) { + trstring(*ap++); + if (*ap) + putc(' ', tracefile); + else + putc('\n', tracefile); + } + fflush(tracefile); +} + + +void +opentrace(void) +{ + char s[100]; + int flags; + + if (!debug) + return; +#ifdef not_this_way + { + char *p; + if ((p = getenv("HOME")) == NULL) { + if (geteuid() == 0) + p = "/"; + else + p = "/tmp"; + } + strcpy(s, p); + strcat(s, "/trace"); + } +#else + strcpy(s, "./trace"); +#endif /* not_this_way */ + if ((tracefile = fopen(s, "a")) == NULL) { + fprintf(stderr, "Can't open %s: %s\n", s, strerror(errno)); + return; + } + if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0) + fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); + fputs("\nTracing started.\n", tracefile); + fflush(tracefile); +} +#endif /* DEBUG */ diff --git a/bin/1sh/show.h b/bin/1sh/show.h new file mode 100644 index 00000000..332685c9 --- /dev/null +++ b/bin/1sh/show.h @@ -0,0 +1,42 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)show.h 1.1 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/show.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +void showtree(union node *); +#ifdef DEBUG +void sh_trace(const char *, ...) __printflike(1, 2); +void trargs(char **); +void trputc(int); +void trputs(const char *); +void opentrace(void); +#endif diff --git a/bin/1sh/tests/Makefile b/bin/1sh/tests/Makefile new file mode 100644 index 00000000..454536a4 --- /dev/null +++ b/bin/1sh/tests/Makefile @@ -0,0 +1,14 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/Makefile 322438 2017-08-12 19:17:48Z jilles $ + +.include <bsd.own.mk> + +TESTS_SUBDIRS+= builtins +TESTS_SUBDIRS+= errors +TESTS_SUBDIRS+= execution +TESTS_SUBDIRS+= expansion +TESTS_SUBDIRS+= invocation +TESTS_SUBDIRS+= parameters +TESTS_SUBDIRS+= parser +TESTS_SUBDIRS+= set-e + +.include <bsd.test.mk> diff --git a/bin/1sh/tests/Makefile.depend b/bin/1sh/tests/Makefile.depend new file mode 100644 index 00000000..c127cc67 --- /dev/null +++ b/bin/1sh/tests/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/Makefile.depend 296587 2016-03-09 22:46:01Z bdrewery $ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/1sh/tests/builtins/Makefile b/bin/1sh/tests/builtins/Makefile new file mode 100644 index 00000000..f3427cf4 --- /dev/null +++ b/bin/1sh/tests/builtins/Makefile @@ -0,0 +1,187 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/Makefile 336320 2018-07-15 21:55:17Z jilles $ + +PACKAGE= tests + +.include <src.opts.mk> + +TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T} + +.PATH: ${.CURDIR:H} +ATF_TESTS_SH= functional_test + +${PACKAGE}FILES+= alias.0 alias.0.stdout +${PACKAGE}FILES+= alias.1 alias.1.stderr +${PACKAGE}FILES+= alias3.0 alias3.0.stdout +${PACKAGE}FILES+= alias4.0 +${PACKAGE}FILES+= break1.0 +${PACKAGE}FILES+= break2.0 break2.0.stdout +${PACKAGE}FILES+= break3.0 +${PACKAGE}FILES+= break4.4 +${PACKAGE}FILES+= break5.4 +${PACKAGE}FILES+= break6.0 +${PACKAGE}FILES+= builtin1.0 +${PACKAGE}FILES+= case1.0 +${PACKAGE}FILES+= case2.0 +${PACKAGE}FILES+= case3.0 +${PACKAGE}FILES+= case4.0 +${PACKAGE}FILES+= case5.0 +${PACKAGE}FILES+= case6.0 +${PACKAGE}FILES+= case7.0 +${PACKAGE}FILES+= case8.0 +${PACKAGE}FILES+= case9.0 +${PACKAGE}FILES+= case10.0 +${PACKAGE}FILES+= case11.0 +${PACKAGE}FILES+= case12.0 +${PACKAGE}FILES+= case13.0 +${PACKAGE}FILES+= case14.0 +${PACKAGE}FILES+= case15.0 +${PACKAGE}FILES+= case16.0 +${PACKAGE}FILES+= case17.0 +${PACKAGE}FILES+= case18.0 +${PACKAGE}FILES+= case19.0 +${PACKAGE}FILES+= case20.0 +${PACKAGE}FILES+= case21.0 +${PACKAGE}FILES+= case22.0 +${PACKAGE}FILES+= case23.0 +${PACKAGE}FILES+= cd1.0 +${PACKAGE}FILES+= cd2.0 +${PACKAGE}FILES+= cd3.0 +${PACKAGE}FILES+= cd4.0 +${PACKAGE}FILES+= cd5.0 +${PACKAGE}FILES+= cd6.0 +${PACKAGE}FILES+= cd7.0 +${PACKAGE}FILES+= cd8.0 +${PACKAGE}FILES+= cd9.0 cd9.0.stdout +${PACKAGE}FILES+= cd10.0 +${PACKAGE}FILES+= cd11.0 +${PACKAGE}FILES+= command1.0 +${PACKAGE}FILES+= command2.0 +${PACKAGE}FILES+= command3.0 +${PACKAGE}FILES+= command3.0.stdout +${PACKAGE}FILES+= command4.0 +${PACKAGE}FILES+= command5.0 +${PACKAGE}FILES+= command5.0.stdout +${PACKAGE}FILES+= command6.0 +${PACKAGE}FILES+= command6.0.stdout +${PACKAGE}FILES+= command7.0 +${PACKAGE}FILES+= command8.0 +${PACKAGE}FILES+= command9.0 +${PACKAGE}FILES+= command10.0 +${PACKAGE}FILES+= command11.0 +${PACKAGE}FILES+= command12.0 +${PACKAGE}FILES+= dot1.0 +${PACKAGE}FILES+= dot2.0 +${PACKAGE}FILES+= dot3.0 +${PACKAGE}FILES+= dot4.0 +${PACKAGE}FILES+= echo1.0 +${PACKAGE}FILES+= echo2.0 +${PACKAGE}FILES+= echo3.0 +${PACKAGE}FILES+= eval1.0 +${PACKAGE}FILES+= eval2.0 +${PACKAGE}FILES+= eval3.0 +${PACKAGE}FILES+= eval4.0 +${PACKAGE}FILES+= eval5.0 +${PACKAGE}FILES+= eval6.0 +${PACKAGE}FILES+= eval7.0 +${PACKAGE}FILES+= eval8.7 +${PACKAGE}FILES+= exec1.0 +${PACKAGE}FILES+= exec2.0 +${PACKAGE}FILES+= exit1.0 +${PACKAGE}FILES+= exit2.8 +${PACKAGE}FILES+= exit3.0 +${PACKAGE}FILES+= export1.0 +${PACKAGE}FILES+= fc1.0 +${PACKAGE}FILES+= fc2.0 +${PACKAGE}FILES+= for1.0 +${PACKAGE}FILES+= for2.0 +${PACKAGE}FILES+= for3.0 +${PACKAGE}FILES+= getopts1.0 getopts1.0.stdout +${PACKAGE}FILES+= getopts2.0 getopts2.0.stdout +${PACKAGE}FILES+= getopts3.0 +${PACKAGE}FILES+= getopts4.0 +${PACKAGE}FILES+= getopts5.0 +${PACKAGE}FILES+= getopts6.0 +${PACKAGE}FILES+= getopts7.0 +${PACKAGE}FILES+= getopts8.0 getopts8.0.stdout +${PACKAGE}FILES+= getopts9.0 getopts9.0.stdout +${PACKAGE}FILES+= getopts10.0 +${PACKAGE}FILES+= hash1.0 hash1.0.stdout +${PACKAGE}FILES+= hash2.0 hash2.0.stdout +${PACKAGE}FILES+= hash3.0 hash3.0.stdout +${PACKAGE}FILES+= hash4.0 +${PACKAGE}FILES+= jobid1.0 +${PACKAGE}FILES+= jobid2.0 +${PACKAGE}FILES+= kill1.0 kill2.0 +${PACKAGE}FILES+= lineno.0 lineno.0.stdout +${PACKAGE}FILES+= lineno2.0 +${PACKAGE}FILES+= lineno3.0 lineno3.0.stdout +${PACKAGE}FILES+= local1.0 +${PACKAGE}FILES+= local2.0 +${PACKAGE}FILES+= local3.0 +${PACKAGE}FILES+= local4.0 +${PACKAGE}FILES+= local5.0 +${PACKAGE}FILES+= local6.0 +${PACKAGE}FILES+= local7.0 +.if ${MK_NLS} != "no" +${PACKAGE}FILES+= locale1.0 +.endif +${PACKAGE}FILES+= locale2.0 +${PACKAGE}FILES+= printf1.0 +${PACKAGE}FILES+= printf2.0 +${PACKAGE}FILES+= printf3.0 +${PACKAGE}FILES+= printf4.0 +${PACKAGE}FILES+= read1.0 read1.0.stdout +${PACKAGE}FILES+= read2.0 +${PACKAGE}FILES+= read3.0 read3.0.stdout +${PACKAGE}FILES+= read4.0 read4.0.stdout +${PACKAGE}FILES+= read5.0 +${PACKAGE}FILES+= read6.0 +${PACKAGE}FILES+= read7.0 +${PACKAGE}FILES+= read8.0 +${PACKAGE}FILES+= read9.0 +${PACKAGE}FILES+= return1.0 +${PACKAGE}FILES+= return2.1 +${PACKAGE}FILES+= return3.1 +${PACKAGE}FILES+= return4.0 +${PACKAGE}FILES+= return5.0 +${PACKAGE}FILES+= return6.4 +${PACKAGE}FILES+= return7.4 +${PACKAGE}FILES+= return8.0 +${PACKAGE}FILES+= set1.0 +${PACKAGE}FILES+= set2.0 +${PACKAGE}FILES+= set3.0 +${PACKAGE}FILES+= trap1.0 +${PACKAGE}FILES+= trap10.0 +${PACKAGE}FILES+= trap11.0 +${PACKAGE}FILES+= trap12.0 +${PACKAGE}FILES+= trap13.0 +${PACKAGE}FILES+= trap14.0 +${PACKAGE}FILES+= trap15.0 +${PACKAGE}FILES+= trap16.0 +${PACKAGE}FILES+= trap17.0 +${PACKAGE}FILES+= trap2.0 +${PACKAGE}FILES+= trap3.0 +${PACKAGE}FILES+= trap4.0 +${PACKAGE}FILES+= trap5.0 +${PACKAGE}FILES+= trap6.0 +${PACKAGE}FILES+= trap7.0 +${PACKAGE}FILES+= trap8.0 +${PACKAGE}FILES+= trap9.0 +${PACKAGE}FILES+= type1.0 type1.0.stderr +${PACKAGE}FILES+= type2.0 +${PACKAGE}FILES+= type3.0 +${PACKAGE}FILES+= unalias.0 +${PACKAGE}FILES+= var-assign.0 +${PACKAGE}FILES+= var-assign2.0 +${PACKAGE}FILES+= wait1.0 +${PACKAGE}FILES+= wait2.0 +${PACKAGE}FILES+= wait3.0 +${PACKAGE}FILES+= wait4.0 +${PACKAGE}FILES+= wait5.0 +${PACKAGE}FILES+= wait6.0 +${PACKAGE}FILES+= wait7.0 +${PACKAGE}FILES+= wait8.0 +${PACKAGE}FILES+= wait9.127 +${PACKAGE}FILES+= wait10.0 + +.include <bsd.test.mk> diff --git a/bin/1sh/tests/builtins/Makefile.depend b/bin/1sh/tests/builtins/Makefile.depend new file mode 100644 index 00000000..60d10fe4 --- /dev/null +++ b/bin/1sh/tests/builtins/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/Makefile.depend 296587 2016-03-09 22:46:01Z bdrewery $ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/1sh/tests/builtins/alias.0 b/bin/1sh/tests/builtins/alias.0 new file mode 100644 index 00000000..4337f897 --- /dev/null +++ b/bin/1sh/tests/builtins/alias.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/alias.0 190285 2009-03-22 21:12:00Z stefanf $ +set -e + +unalias -a +alias foo=bar +alias bar= +alias quux="1 2 3" +alias +alias foo diff --git a/bin/1sh/tests/builtins/alias.0.stdout b/bin/1sh/tests/builtins/alias.0.stdout new file mode 100644 index 00000000..52efaf0b --- /dev/null +++ b/bin/1sh/tests/builtins/alias.0.stdout @@ -0,0 +1,4 @@ +bar='' +foo=bar +quux='1 2 3' +foo=bar diff --git a/bin/1sh/tests/builtins/alias.1 b/bin/1sh/tests/builtins/alias.1 new file mode 100644 index 00000000..76615ade --- /dev/null +++ b/bin/1sh/tests/builtins/alias.1 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/alias.1 149781 2005-09-04 11:59:59Z stefanf $ +unalias -a +alias foo diff --git a/bin/1sh/tests/builtins/alias.1.stderr b/bin/1sh/tests/builtins/alias.1.stderr new file mode 100644 index 00000000..c9f4011b --- /dev/null +++ b/bin/1sh/tests/builtins/alias.1.stderr @@ -0,0 +1 @@ +alias: foo: not found diff --git a/bin/1sh/tests/builtins/alias3.0 b/bin/1sh/tests/builtins/alias3.0 new file mode 100644 index 00000000..cb0c2f78 --- /dev/null +++ b/bin/1sh/tests/builtins/alias3.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/alias3.0 242767 2012-11-08 13:36:19Z jilles $ +set -e + +unalias -a +alias foo=bar +alias bar= +alias quux="1 2 3" +alias foo=bar +alias bar= +alias quux="1 2 3" +alias +alias foo diff --git a/bin/1sh/tests/builtins/alias3.0.stdout b/bin/1sh/tests/builtins/alias3.0.stdout new file mode 100644 index 00000000..52efaf0b --- /dev/null +++ b/bin/1sh/tests/builtins/alias3.0.stdout @@ -0,0 +1,4 @@ +bar='' +foo=bar +quux='1 2 3' +foo=bar diff --git a/bin/1sh/tests/builtins/alias4.0 b/bin/1sh/tests/builtins/alias4.0 new file mode 100644 index 00000000..adc5724a --- /dev/null +++ b/bin/1sh/tests/builtins/alias4.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/alias4.0 254849 2013-08-25 11:42:53Z jilles $ + +unalias -a +alias -- diff --git a/bin/1sh/tests/builtins/break1.0 b/bin/1sh/tests/builtins/break1.0 new file mode 100644 index 00000000..ae59928e --- /dev/null +++ b/bin/1sh/tests/builtins/break1.0 @@ -0,0 +1,16 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/break1.0 211349 2010-08-15 21:06:53Z jilles $ + +if [ "$1" != nested ]; then + while :; do + set -- nested + . "$0" + echo bad2 + exit 2 + done + exit 0 +fi +# To trigger the bug, the following commands must be at the top level, +# with newlines in between. +break +echo bad1 +exit 1 diff --git a/bin/1sh/tests/builtins/break2.0 b/bin/1sh/tests/builtins/break2.0 new file mode 100644 index 00000000..521d504f --- /dev/null +++ b/bin/1sh/tests/builtins/break2.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/break2.0 211467 2010-08-18 20:26:50Z jilles $ + +# It is not immediately obvious that this should work, and someone probably +# relies on it. + +while :; do + trap 'break' USR1 + kill -USR1 $$ + echo bad + exit 1 +done +echo good diff --git a/bin/1sh/tests/builtins/break2.0.stdout b/bin/1sh/tests/builtins/break2.0.stdout new file mode 100644 index 00000000..12799ccb --- /dev/null +++ b/bin/1sh/tests/builtins/break2.0.stdout @@ -0,0 +1 @@ +good diff --git a/bin/1sh/tests/builtins/break3.0 b/bin/1sh/tests/builtins/break3.0 new file mode 100644 index 00000000..08105f9d --- /dev/null +++ b/bin/1sh/tests/builtins/break3.0 @@ -0,0 +1,15 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/break3.0 211612 2010-08-22 11:07:46Z jilles $ + +# We accept this and people might rely on it. +# However, various other shells do not accept it. + +f() { + break + echo bad1 +} + +while :; do + f + echo bad2 + exit 2 +done diff --git a/bin/1sh/tests/builtins/break4.4 b/bin/1sh/tests/builtins/break4.4 new file mode 100644 index 00000000..e804e45d --- /dev/null +++ b/bin/1sh/tests/builtins/break4.4 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/break4.4 251180 2013-05-31 14:45:25Z jilles $ + +# Although this is not specified by POSIX, some configure scripts (gawk 4.1.0) +# appear to depend on it. + +break +exit 4 diff --git a/bin/1sh/tests/builtins/break5.4 b/bin/1sh/tests/builtins/break5.4 new file mode 100644 index 00000000..f4d25df6 --- /dev/null +++ b/bin/1sh/tests/builtins/break5.4 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/break5.4 251180 2013-05-31 14:45:25Z jilles $ + +# Although this is not specified by POSIX, some configure scripts (gawk 4.1.0) +# appear to depend on it. +# In some uncommitted code, the subshell environment corrupted the outer +# shell environment's state. + +(for i in a b c; do + exit 3 +done) +break +exit 4 diff --git a/bin/1sh/tests/builtins/break6.0 b/bin/1sh/tests/builtins/break6.0 new file mode 100644 index 00000000..f7fbc728 --- /dev/null +++ b/bin/1sh/tests/builtins/break6.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/break6.0 268927 2014-07-20 20:29:09Z jilles $ +# Per POSIX, this need only work if LONG_MAX > 4294967295. + +while :; do + break 4294967296 + echo bad + exit 3 +done diff --git a/bin/1sh/tests/builtins/builtin1.0 b/bin/1sh/tests/builtins/builtin1.0 new file mode 100644 index 00000000..8c997721 --- /dev/null +++ b/bin/1sh/tests/builtins/builtin1.0 @@ -0,0 +1,31 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/builtin1.0 201431 2010-01-03 15:01:38Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +builtin : || echo "Bad return code at $LINENO" +builtin true || echo "Bad return code at $LINENO" +builtin ls 2>/dev/null && echo "Bad return code at $LINENO" +check '"$(builtin pwd)" = "$(pwd)"' +check '-z "$(builtin :)"' +check '-z "$(builtin true)"' +check '-z "$( (builtin nosuchtool) 2>/dev/null)"' +check '-z "$(builtin nosuchtool 2>/dev/null)"' +check '-z "$(builtin nosuchtool 2>/dev/null; :)"' +check '-z "$( (builtin ls) 2>/dev/null)"' +check '-z "$(builtin ls 2>/dev/null)"' +check '-z "$(builtin ls 2>/dev/null; :)"' +check '-n "$( (builtin nosuchtool) 2>&1)"' +check '-n "$(builtin nosuchtool 2>&1)"' +check '-n "$(builtin nosuchtool 2>&1; :)"' +check '-n "$( (builtin ls) 2>&1)"' +check '-n "$(builtin ls 2>&1)"' +check '-n "$(builtin ls 2>&1; :)"' + +exit $((failures > 0)) diff --git a/bin/1sh/tests/builtins/case1.0 b/bin/1sh/tests/builtins/case1.0 new file mode 100644 index 00000000..fb03d18d --- /dev/null +++ b/bin/1sh/tests/builtins/case1.0 @@ -0,0 +1,13 @@ +#$FreeBSD: releng/12.1/bin/sh/tests/builtins/case1.0 172440 2007-10-04 16:14:48Z stefanf $ +f() +{ + false + case $1 in + foo) true ;; + bar) false ;; + esac +} + +f foo || exit 1 +f bar && exit 1 +f quux || exit 1 diff --git a/bin/1sh/tests/builtins/case10.0 b/bin/1sh/tests/builtins/case10.0 new file mode 100644 index 00000000..ded33163 --- /dev/null +++ b/bin/1sh/tests/builtins/case10.0 @@ -0,0 +1,16 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case10.0 223546 2011-06-25 20:37:43Z jilles $ + +case ! in +[\!!]) ;; +*) echo Failed at $LINENO ;; +esac + +case ! in +['!'!]) ;; +*) echo Failed at $LINENO ;; +esac + +case ! in +["!"!]) ;; +*) echo Failed at $LINENO ;; +esac diff --git a/bin/1sh/tests/builtins/case11.0 b/bin/1sh/tests/builtins/case11.0 new file mode 100644 index 00000000..c8b0624e --- /dev/null +++ b/bin/1sh/tests/builtins/case11.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case11.0 228007 2011-11-26 22:28:25Z jilles $ + +false +case x in +*) +esac diff --git a/bin/1sh/tests/builtins/case12.0 b/bin/1sh/tests/builtins/case12.0 new file mode 100644 index 00000000..0eebd805 --- /dev/null +++ b/bin/1sh/tests/builtins/case12.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case12.0 228007 2011-11-26 22:28:25Z jilles $ + +false +case x in +y) +esac diff --git a/bin/1sh/tests/builtins/case13.0 b/bin/1sh/tests/builtins/case13.0 new file mode 100644 index 00000000..c8aa9c1e --- /dev/null +++ b/bin/1sh/tests/builtins/case13.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case13.0 228943 2011-12-28 23:51:17Z jilles $ + +case ^ in +[\^^]) ;; +*) echo Failed at $LINENO ;; +esac + +case s in +[\^^]) echo Failed at $LINENO ;; +[s\]]) ;; +*) echo Failed at $LINENO ;; +esac diff --git a/bin/1sh/tests/builtins/case14.0 b/bin/1sh/tests/builtins/case14.0 new file mode 100644 index 00000000..6ac6f78e --- /dev/null +++ b/bin/1sh/tests/builtins/case14.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case14.0 230154 2012-01-15 20:04:05Z jilles $ + +case `false` in +no) exit 3 ;; +esac diff --git a/bin/1sh/tests/builtins/case15.0 b/bin/1sh/tests/builtins/case15.0 new file mode 100644 index 00000000..71f6f55c --- /dev/null +++ b/bin/1sh/tests/builtins/case15.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case15.0 230154 2012-01-15 20:04:05Z jilles $ + +case x in +`false`) exit 3 ;; +esac diff --git a/bin/1sh/tests/builtins/case16.0 b/bin/1sh/tests/builtins/case16.0 new file mode 100644 index 00000000..2a1e2fdf --- /dev/null +++ b/bin/1sh/tests/builtins/case16.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case16.0 230154 2012-01-15 20:04:05Z jilles $ + +f() { return 42; } +f +case x in +x) [ $? = 42 ] ;; +esac diff --git a/bin/1sh/tests/builtins/case17.0 b/bin/1sh/tests/builtins/case17.0 new file mode 100644 index 00000000..d27717ba --- /dev/null +++ b/bin/1sh/tests/builtins/case17.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case17.0 230161 2012-01-15 21:39:38Z jilles $ + +! case x in x) false ;& y) esac diff --git a/bin/1sh/tests/builtins/case18.0 b/bin/1sh/tests/builtins/case18.0 new file mode 100644 index 00000000..3a33646e --- /dev/null +++ b/bin/1sh/tests/builtins/case18.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case18.0 230161 2012-01-15 21:39:38Z jilles $ + +case x$(false) in +x) ;& +y) [ $? != 0 ] ;; +z) false ;; +esac diff --git a/bin/1sh/tests/builtins/case19.0 b/bin/1sh/tests/builtins/case19.0 new file mode 100644 index 00000000..fda14ac4 --- /dev/null +++ b/bin/1sh/tests/builtins/case19.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case19.0 230161 2012-01-15 21:39:38Z jilles $ + +[ "`case x in +x) false ;& +y) ;& +z) echo $? ;; +esac`" != 0 ] diff --git a/bin/1sh/tests/builtins/case2.0 b/bin/1sh/tests/builtins/case2.0 new file mode 100644 index 00000000..473943a1 --- /dev/null +++ b/bin/1sh/tests/builtins/case2.0 @@ -0,0 +1,106 @@ +# Generated by ./test-fnmatch -s 1, do not edit. +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case2.0 207821 2010-05-09 16:15:40Z jilles $ +failures= +failed() { printf '%s\n' "Failed: $1 '$2' '$3'"; failures=x$failures; } +testmatch() { eval "case \$2 in ''$1) ;; *) failed testmatch \"\$@\";; esac"; } +testnomatch() { eval "case \$2 in ''$1) failed testnomatch \"\$@\";; esac"; } +testmatch '' '' +testmatch 'a' 'a' +testnomatch 'a' 'b' +testnomatch 'a' 'A' +testmatch '*' 'a' +testmatch '*' 'aa' +testmatch '*a' 'a' +testnomatch '*a' 'b' +testnomatch '*a*' 'b' +testmatch '*a*b*' 'ab' +testmatch '*a*b*' 'qaqbq' +testmatch '*a*bb*' 'qaqbqbbq' +testmatch '*a*bc*' 'qaqbqbcq' +testmatch '*a*bb*' 'qaqbqbb' +testmatch '*a*bc*' 'qaqbqbc' +testmatch '*a*bb' 'qaqbqbb' +testmatch '*a*bc' 'qaqbqbc' +testnomatch '*a*bb' 'qaqbqbbq' +testnomatch '*a*bc' 'qaqbqbcq' +testnomatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaa' +testmatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaaa' +testmatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaaaa' +testnomatch '.*.*.*.*.*.*.*.*.*.*' '.........' +testmatch '.*.*.*.*.*.*.*.*.*.*' '..........' +testmatch '.*.*.*.*.*.*.*.*.*.*' '...........' +testnomatch '*?*?*?*?*?*?*?*?*?*?*' '123456789' +testnomatch '??????????*' '123456789' +testnomatch '*??????????' '123456789' +testmatch '*?*?*?*?*?*?*?*?*?*?*' '1234567890' +testmatch '??????????*' '1234567890' +testmatch '*??????????' '1234567890' +testmatch '*?*?*?*?*?*?*?*?*?*?*' '12345678901' +testmatch '??????????*' '12345678901' +testmatch '*??????????' '12345678901' +testmatch '[x]' 'x' +testmatch '[*]' '*' +testmatch '[?]' '?' +testmatch '[' '[' +testmatch '[[]' '[' +testnomatch '[[]' 'x' +testnomatch '[*]' '' +testnomatch '[*]' 'x' +testnomatch '[?]' 'x' +testmatch '*[*]*' 'foo*foo' +testnomatch '*[*]*' 'foo' +testmatch '[0-9]' '0' +testmatch '[0-9]' '5' +testmatch '[0-9]' '9' +testnomatch '[0-9]' '/' +testnomatch '[0-9]' ':' +testnomatch '[0-9]' '*' +testnomatch '[!0-9]' '0' +testnomatch '[!0-9]' '5' +testnomatch '[!0-9]' '9' +testmatch '[!0-9]' '/' +testmatch '[!0-9]' ':' +testmatch '[!0-9]' '*' +testmatch '*[0-9]' 'a0' +testmatch '*[0-9]' 'a5' +testmatch '*[0-9]' 'a9' +testnomatch '*[0-9]' 'a/' +testnomatch '*[0-9]' 'a:' +testnomatch '*[0-9]' 'a*' +testnomatch '*[!0-9]' 'a0' +testnomatch '*[!0-9]' 'a5' +testnomatch '*[!0-9]' 'a9' +testmatch '*[!0-9]' 'a/' +testmatch '*[!0-9]' 'a:' +testmatch '*[!0-9]' 'a*' +testmatch '*[0-9]' 'a00' +testmatch '*[0-9]' 'a55' +testmatch '*[0-9]' 'a99' +testmatch '*[0-9]' 'a0a0' +testmatch '*[0-9]' 'a5a5' +testmatch '*[0-9]' 'a9a9' +testmatch '\*' '*' +testmatch '\?' '?' +testmatch '\[x]' '[x]' +testmatch '\[' '[' +testmatch '\\' '\' +testmatch '*\**' 'foo*foo' +testnomatch '*\**' 'foo' +testmatch '*\\*' 'foo\foo' +testnomatch '*\\*' 'foo' +testmatch '\(' '(' +testmatch '\a' 'a' +testnomatch '\*' 'a' +testnomatch '\?' 'a' +testnomatch '\*' '\*' +testnomatch '\?' '\?' +testnomatch '\[x]' '\[x]' +testnomatch '\[x]' '\x' +testnomatch '\[' '\[' +testnomatch '\(' '\(' +testnomatch '\a' '\a' +testmatch '.*' '.' +testmatch '.*' '..' +testmatch '.*' '.a' +testmatch 'a*' 'a.' +[ -z "$failures" ] diff --git a/bin/1sh/tests/builtins/case20.0 b/bin/1sh/tests/builtins/case20.0 new file mode 100644 index 00000000..bbcf5605 --- /dev/null +++ b/bin/1sh/tests/builtins/case20.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case20.0 287148 2015-08-25 21:55:15Z jilles $ + +# Shells do not agree about what this pattern should match, but it is +# certain that it must not crash and the missing close bracket must not +# be simply ignored. + +case B in +[[:alpha:]) echo bad ;; +esac diff --git a/bin/1sh/tests/builtins/case21.0 b/bin/1sh/tests/builtins/case21.0 new file mode 100644 index 00000000..3158fb65 --- /dev/null +++ b/bin/1sh/tests/builtins/case21.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case21.0 318258 2017-05-13 20:28:32Z jilles $ + +case 5 in +[0$((-9))]) ;; +*) echo bad1 ;; +esac + +case - in +[0$((-9))]) echo bad2 ;; +esac diff --git a/bin/1sh/tests/builtins/case22.0 b/bin/1sh/tests/builtins/case22.0 new file mode 100644 index 00000000..740d2b4e --- /dev/null +++ b/bin/1sh/tests/builtins/case22.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case22.0 318269 2017-05-14 13:14:19Z jilles $ + +case 5 in +[0"$((-9))"]) echo bad1 ;; +esac + +case - in +[0"$((-9))"]) ;; +*) echo bad2 ;; +esac diff --git a/bin/1sh/tests/builtins/case23.0 b/bin/1sh/tests/builtins/case23.0 new file mode 100644 index 00000000..b9b2136e --- /dev/null +++ b/bin/1sh/tests/builtins/case23.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case23.0 333092 2018-04-29 17:46:08Z jilles $ + +case [ in +[[:alpha:]]) echo bad +esac diff --git a/bin/1sh/tests/builtins/case3.0 b/bin/1sh/tests/builtins/case3.0 new file mode 100644 index 00000000..c6b067ac --- /dev/null +++ b/bin/1sh/tests/builtins/case3.0 @@ -0,0 +1,95 @@ +# Generated by ./test-fnmatch -s 2, do not edit. +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case3.0 207821 2010-05-09 16:15:40Z jilles $ +failures= +failed() { printf '%s\n' "Failed: $1 '$2' '$3'"; failures=x$failures; } +# We do not treat a backslash specially in this case, +# but this is not the case in all shells. +netestmatch() { case $2 in $1) ;; *) failed netestmatch "$@";; esac; } +netestnomatch() { case $2 in $1) failed netestnomatch "$@";; esac; } +netestmatch '' '' +netestmatch 'a' 'a' +netestnomatch 'a' 'b' +netestnomatch 'a' 'A' +netestmatch '*' 'a' +netestmatch '*' 'aa' +netestmatch '*a' 'a' +netestnomatch '*a' 'b' +netestnomatch '*a*' 'b' +netestmatch '*a*b*' 'ab' +netestmatch '*a*b*' 'qaqbq' +netestmatch '*a*bb*' 'qaqbqbbq' +netestmatch '*a*bc*' 'qaqbqbcq' +netestmatch '*a*bb*' 'qaqbqbb' +netestmatch '*a*bc*' 'qaqbqbc' +netestmatch '*a*bb' 'qaqbqbb' +netestmatch '*a*bc' 'qaqbqbc' +netestnomatch '*a*bb' 'qaqbqbbq' +netestnomatch '*a*bc' 'qaqbqbcq' +netestnomatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaa' +netestmatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaaa' +netestmatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaaaa' +netestnomatch '.*.*.*.*.*.*.*.*.*.*' '.........' +netestmatch '.*.*.*.*.*.*.*.*.*.*' '..........' +netestmatch '.*.*.*.*.*.*.*.*.*.*' '...........' +netestnomatch '*?*?*?*?*?*?*?*?*?*?*' '123456789' +netestnomatch '??????????*' '123456789' +netestnomatch '*??????????' '123456789' +netestmatch '*?*?*?*?*?*?*?*?*?*?*' '1234567890' +netestmatch '??????????*' '1234567890' +netestmatch '*??????????' '1234567890' +netestmatch '*?*?*?*?*?*?*?*?*?*?*' '12345678901' +netestmatch '??????????*' '12345678901' +netestmatch '*??????????' '12345678901' +netestmatch '[x]' 'x' +netestmatch '[*]' '*' +netestmatch '[?]' '?' +netestmatch '[' '[' +netestmatch '[[]' '[' +netestnomatch '[[]' 'x' +netestnomatch '[*]' '' +netestnomatch '[*]' 'x' +netestnomatch '[?]' 'x' +netestmatch '*[*]*' 'foo*foo' +netestnomatch '*[*]*' 'foo' +netestmatch '[0-9]' '0' +netestmatch '[0-9]' '5' +netestmatch '[0-9]' '9' +netestnomatch '[0-9]' '/' +netestnomatch '[0-9]' ':' +netestnomatch '[0-9]' '*' +netestnomatch '[!0-9]' '0' +netestnomatch '[!0-9]' '5' +netestnomatch '[!0-9]' '9' +netestmatch '[!0-9]' '/' +netestmatch '[!0-9]' ':' +netestmatch '[!0-9]' '*' +netestmatch '*[0-9]' 'a0' +netestmatch '*[0-9]' 'a5' +netestmatch '*[0-9]' 'a9' +netestnomatch '*[0-9]' 'a/' +netestnomatch '*[0-9]' 'a:' +netestnomatch '*[0-9]' 'a*' +netestnomatch '*[!0-9]' 'a0' +netestnomatch '*[!0-9]' 'a5' +netestnomatch '*[!0-9]' 'a9' +netestmatch '*[!0-9]' 'a/' +netestmatch '*[!0-9]' 'a:' +netestmatch '*[!0-9]' 'a*' +netestmatch '*[0-9]' 'a00' +netestmatch '*[0-9]' 'a55' +netestmatch '*[0-9]' 'a99' +netestmatch '*[0-9]' 'a0a0' +netestmatch '*[0-9]' 'a5a5' +netestmatch '*[0-9]' 'a9a9' +netestmatch '\*' '\*' +netestmatch '\?' '\?' +netestmatch '\' '\' +netestnomatch '\\' '\' +netestmatch '\\' '\\' +netestmatch '*\*' 'foo\foo' +netestnomatch '*\*' 'foo' +netestmatch '.*' '.' +netestmatch '.*' '..' +netestmatch '.*' '.a' +netestmatch 'a*' 'a.' +[ -z "$failures" ] diff --git a/bin/1sh/tests/builtins/case4.0 b/bin/1sh/tests/builtins/case4.0 new file mode 100644 index 00000000..adea7d08 --- /dev/null +++ b/bin/1sh/tests/builtins/case4.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case4.0 220654 2011-04-15 15:14:58Z jilles $ + +set -- "*" +case x in +"$1") echo failed ;; +esac diff --git a/bin/1sh/tests/builtins/case5.0 b/bin/1sh/tests/builtins/case5.0 new file mode 100644 index 00000000..79eab7f4 --- /dev/null +++ b/bin/1sh/tests/builtins/case5.0 @@ -0,0 +1,57 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case5.0 221646 2011-05-08 11:32:20Z jilles $ + +unset LC_ALL +LC_CTYPE=en_US.UTF-8 +export LC_CTYPE + +c1=e +# a umlaut +c2=$(printf '\303\244') +# euro sign +c3=$(printf '\342\202\254') +# some sort of 't' outside BMP +c4=$(printf '\360\235\225\245') + +ok=0 +case $c1$c2$c3$c4 in +*) ok=1 ;; +esac +if [ $ok = 0 ]; then + echo wrong at $LINENO + exit 3 +fi + +case $c1$c2$c3$c4 in +$c1$c2$c3$c4) ;; +*) echo wrong at $LINENO ;; +esac + +case $c1$c2$c3$c4 in +"$c1$c2$c3$c4") ;; +*) echo wrong at $LINENO ;; +esac + +case $c1$c2$c3$c4 in +????) ;; +*) echo wrong at $LINENO ;; +esac + +case $c1.$c2.$c3.$c4 in +?.?.?.?) ;; +*) echo wrong at $LINENO ;; +esac + +case $c1$c2$c3$c4 in +[!a][!b][!c][!d]) ;; +*) echo wrong at $LINENO ;; +esac + +case $c1$c2$c3$c4 in +[$c1][$c2][$c3][$c4]) ;; +*) echo wrong at $LINENO ;; +esac + +case $c1$c2$c3$c4 in +["$c1"]["$c2"]["$c3"]["$c4"]) ;; +*) echo wrong at $LINENO ;; +esac diff --git a/bin/1sh/tests/builtins/case6.0 b/bin/1sh/tests/builtins/case6.0 new file mode 100644 index 00000000..4cc04a05 --- /dev/null +++ b/bin/1sh/tests/builtins/case6.0 @@ -0,0 +1,52 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case6.0 223007 2011-06-12 12:27:17Z jilles $ + +unset LC_ALL +LC_CTYPE=de_DE.ISO8859-1 +export LC_CTYPE + +c1=e +# o umlaut +c2=$(printf '\366') +# non-break space +c3=$(printf '\240') +c4=$(printf '\240') +# $c2$c3$c4 form one utf-8 character + +ok=0 +case $c1$c2$c3$c4 in +*) ok=1 ;; +esac +if [ $ok = 0 ]; then + echo wrong at $LINENO + exit 3 +fi + +case $c1$c2$c3$c4 in +$c1$c2$c3$c4) ;; +*) echo wrong at $LINENO ;; +esac + +case $c1$c2$c3$c4 in +"$c1$c2$c3$c4") ;; +*) echo wrong at $LINENO ;; +esac + +case $c1$c2$c3$c4 in +????) ;; +*) echo wrong at $LINENO ;; +esac + +case $c1$c2$c3$c4 in +[!$c2][!b][!c][!d]) ;; +*) echo wrong at $LINENO ;; +esac + +case $c1$c2$c3$c4 in +[$c1][$c2][$c3][$c4]) ;; +*) echo wrong at $LINENO ;; +esac + +case $c1$c2$c3$c4 in +["$c1"]["$c2"]["$c3"]["$c4"]) ;; +*) echo wrong at $LINENO ;; +esac diff --git a/bin/1sh/tests/builtins/case7.0 b/bin/1sh/tests/builtins/case7.0 new file mode 100644 index 00000000..09d8689a --- /dev/null +++ b/bin/1sh/tests/builtins/case7.0 @@ -0,0 +1,24 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case7.0 302829 2016-07-14 09:34:42Z ache $ + +# Character ranges in a locale other than the POSIX locale, not specified +# by POSIX. + +unset LC_ALL +LC_CTYPE=de_DE.ISO8859-1 +export LC_CTYPE +LC_COLLATE=de_DE.ISO8859-1 +export LC_COLLATE + +c1=e +# o umlaut +c2=$(printf '\366') + +case $c1$c2 in +[a-z][a-z]) ;; +*) echo wrong at $LINENO ;; +esac + +case $c1$c2 in +[a-f][n-p]) ;; +*) echo wrong at $LINENO ;; +esac diff --git a/bin/1sh/tests/builtins/case8.0 b/bin/1sh/tests/builtins/case8.0 new file mode 100644 index 00000000..b1938bf0 --- /dev/null +++ b/bin/1sh/tests/builtins/case8.0 @@ -0,0 +1,32 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case8.0 223120 2011-06-15 21:48:10Z jilles $ + +case aZ_ in +[[:alpha:]_][[:upper:]_][[:alpha:]_]) ;; +*) echo Failed at $LINENO ;; +esac + +case ' ' in +[[:alpha:][:digit:]]) echo Failed at $LINENO ;; +[![:alpha:][:digit:]]) ;; +*) echo Failed at $LINENO ;; +esac + +case '.X.' in +*[[:lower:]]*) echo Failed at $LINENO ;; +*[[:upper:]]*) ;; +*) echo Failed at $LINENO ;; +esac + +case ' ' in +[![:print:]]) echo Failed at $LINENO ;; +[![:alnum:][:punct:]]) ;; +*) echo Failed at $LINENO ;; +esac + +case ' +' in +[[:print:]]) echo Failed at $LINENO ;; +[' +'[:digit:]]) ;; +*) echo Failed at $LINENO ;; +esac diff --git a/bin/1sh/tests/builtins/case9.0 b/bin/1sh/tests/builtins/case9.0 new file mode 100644 index 00000000..b415fb02 --- /dev/null +++ b/bin/1sh/tests/builtins/case9.0 @@ -0,0 +1,39 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/case9.0 223186 2011-06-17 13:03:49Z jilles $ + +errors=0 + +f() { + result= + case $1 in + a) result=${result}a ;; + b) result=${result}b ;& + c) result=${result}c ;& + d) result=${result}d ;; + e) result=${result}e ;& + esac +} + +check() { + f "$1" + if [ "$result" != "$2" ]; then + printf "For %s, expected %s got %s\n" "$1" "$2" "$result" + errors=$((errors + 1)) + fi +} + +check '' '' +check a a +check b bcd +check c cd +check d d +check e e + +if ! (case 1 in + 1) false ;& + 2) true ;; +esac) then + echo "Subshell bad" + errors=$((errors + 1)) +fi + +exit $((errors != 0)) diff --git a/bin/1sh/tests/builtins/cd1.0 b/bin/1sh/tests/builtins/cd1.0 new file mode 100644 index 00000000..8379e3c5 --- /dev/null +++ b/bin/1sh/tests/builtins/cd1.0 @@ -0,0 +1,30 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/cd1.0 228975 2011-12-30 00:04:11Z uqs $ +set -e + +P=${TMPDIR:-/tmp} +cd $P +T=$(mktemp -d sh-test.XXXXXX) + +chmod 0 $T +if [ `id -u` -ne 0 ]; then + # Root can always cd, regardless of directory permissions. + cd -L $T 2>/dev/null && exit 1 + [ "$PWD" = "$P" ] + [ "$(pwd)" = "$P" ] + cd -P $T 2>/dev/null && exit 1 + [ "$PWD" = "$P" ] + [ "$(pwd)" = "$P" ] +fi + +chmod 755 $T +cd $T +mkdir -p 1/2/3 +ln -s 1/2 link1 +ln -s 2/3 1/link2 +(cd -L 1/../1 && [ "$(pwd -L)" = "$P/$T/1" ]) +(cd -L link1 && [ "$(pwd -L)" = "$P/$T/link1" ]) +(cd -L link1 && [ "$(pwd -P)" = "$P/$T/1/2" ]) +(cd -P link1 && [ "$(pwd -L)" = "$P/$T/1/2" ]) +(cd -P link1 && [ "$(pwd -P)" = "$P/$T/1/2" ]) + +rm -rf ${P}/${T} diff --git a/bin/1sh/tests/builtins/cd10.0 b/bin/1sh/tests/builtins/cd10.0 new file mode 100644 index 00000000..0b90f36a --- /dev/null +++ b/bin/1sh/tests/builtins/cd10.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/cd10.0 320340 2017-06-25 21:53:08Z jilles $ + +# Precondition +(cd /bin) || exit +# Verify write error is ignored. +$SH +m -ic 'CDPATH=/:; cd bin 1</dev/null' diff --git a/bin/1sh/tests/builtins/cd11.0 b/bin/1sh/tests/builtins/cd11.0 new file mode 100644 index 00000000..ab310da0 --- /dev/null +++ b/bin/1sh/tests/builtins/cd11.0 @@ -0,0 +1,24 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/cd11.0 336320 2018-07-15 21:55:17Z jilles $ + +set -e +T=$(mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXX") +trap 'rm -rf "$T"' 0 + +mkdir "$T/%?^&*" +cd -P "$T/%?^&*" +D=$(pwd) + +mkdir a a/1 b b/1 b/2 + +CDPATH=$D/a: +# Basic test. +cd 1 >/dev/null +[ "$(pwd)" = "$D/a/1" ] +# Test that the current directory is not checked before CDPATH. +cd "$D/b" +cd 1 >/dev/null +[ "$(pwd)" = "$D/a/1" ] +# Test not using a CDPATH entry. +cd "$D/b" +cd 2 +[ "$(pwd)" = "$D/b/2" ] diff --git a/bin/1sh/tests/builtins/cd2.0 b/bin/1sh/tests/builtins/cd2.0 new file mode 100644 index 00000000..ea7e396b --- /dev/null +++ b/bin/1sh/tests/builtins/cd2.0 @@ -0,0 +1,16 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/cd2.0 216019 2010-11-28 22:49:58Z jilles $ +set -e + +L=$(getconf PATH_MAX / 2>/dev/null) || L=4096 +[ "$L" -lt 100000 ] 2>/dev/null || L=4096 +L=$((L+100)) +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) +trap 'rm -rf ${T}' 0 +cd $T +D=$T +while [ ${#D} -lt $L ]; do + mkdir veryverylongdirectoryname + cd veryverylongdirectoryname + D=$D/veryverylongdirectoryname +done +[ $(pwd | wc -c) -eq $((${#D} + 1)) ] # +\n diff --git a/bin/1sh/tests/builtins/cd3.0 b/bin/1sh/tests/builtins/cd3.0 new file mode 100644 index 00000000..09aa74ca --- /dev/null +++ b/bin/1sh/tests/builtins/cd3.0 @@ -0,0 +1,21 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/cd3.0 222154 2011-05-20 22:55:18Z jilles $ + +# If fully successful, cd -Pe must be like cd -P. + +set -e + +cd "${TMPDIR:-/tmp}" +cd -Pe / +[ "$PWD" = / ] +[ "$(pwd)" = / ] +cd "${TMPDIR:-/tmp}" +cd -eP / +[ "$PWD" = / ] +[ "$(pwd)" = / ] + +set +e + +# If cd -Pe cannot chdir, the exit status must be greater than 1. + +v=$( (cd -Pe /var/empty/nonexistent) 2>&1 >/dev/null) +[ $? -gt 1 ] && [ -n "$v" ] diff --git a/bin/1sh/tests/builtins/cd4.0 b/bin/1sh/tests/builtins/cd4.0 new file mode 100644 index 00000000..ba32302e --- /dev/null +++ b/bin/1sh/tests/builtins/cd4.0 @@ -0,0 +1,38 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/cd4.0 222154 2011-05-20 22:55:18Z jilles $ + +# This test assumes that whatever mechanism cd -P uses to determine the +# pathname to the current directory if it is longer than PATH_MAX requires +# read permission on all parent directories. It also works if this +# requirement always applies. + +set -e +L=$(getconf PATH_MAX / 2>/dev/null) || L=4096 +[ "$L" -lt 100000 ] 2>/dev/null || L=4096 +L=$((L+100)) +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) +trap 'chmod u+r ${T}; rm -rf ${T}' 0 +cd -Pe $T +D=$(pwd) +chmod u-r "$D" +if [ -r "$D" ]; then + # Running as root, cannot test. + exit 0 +fi +set +e +while [ ${#D} -lt $L ]; do + mkdir veryverylongdirectoryname || exit + cd -Pe veryverylongdirectoryname 2>/dev/null + r=$? + [ $r -gt 1 ] && exit $r + if [ $r -eq 1 ]; then + # Verify that the directory was changed correctly. + cd -Pe .. || exit + [ "$(pwd)" = "$D" ] || exit + # Verify that omitting -e results in success. + cd -P veryverylongdirectoryname 2>/dev/null || exit + exit 0 + fi + D=$D/veryverylongdirectoryname +done +echo "cd -Pe never returned 1" +exit 0 diff --git a/bin/1sh/tests/builtins/cd5.0 b/bin/1sh/tests/builtins/cd5.0 new file mode 100644 index 00000000..9a9fbf4d --- /dev/null +++ b/bin/1sh/tests/builtins/cd5.0 @@ -0,0 +1,23 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/cd5.0 222379 2011-05-27 19:36:07Z jilles $ + +set -e +T=$(mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXX") +trap 'rm -rf "$T"' 0 + +cd -P "$T" +D=$(pwd) + +mkdir a a/1 b b/1 b/2 + +CDPATH=$D/a: +# Basic test. +cd 1 >/dev/null +[ "$(pwd)" = "$D/a/1" ] +# Test that the current directory is not checked before CDPATH. +cd "$D/b" +cd 1 >/dev/null +[ "$(pwd)" = "$D/a/1" ] +# Test not using a CDPATH entry. +cd "$D/b" +cd 2 +[ "$(pwd)" = "$D/b/2" ] diff --git a/bin/1sh/tests/builtins/cd6.0 b/bin/1sh/tests/builtins/cd6.0 new file mode 100644 index 00000000..7bfa20be --- /dev/null +++ b/bin/1sh/tests/builtins/cd6.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/cd6.0 222381 2011-05-27 20:01:46Z jilles $ + +set -e +cd -P /bin +d=$PWD +CDPATH=/: +cd -P . +[ "$d" = "$PWD" ] +cd -P ./ +[ "$d" = "$PWD" ] diff --git a/bin/1sh/tests/builtins/cd7.0 b/bin/1sh/tests/builtins/cd7.0 new file mode 100644 index 00000000..b5a522aa --- /dev/null +++ b/bin/1sh/tests/builtins/cd7.0 @@ -0,0 +1,15 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/cd7.0 222381 2011-05-27 20:01:46Z jilles $ + +set -e +cd /usr/bin +[ "$PWD" = /usr/bin ] +CDPATH=/: +cd . +[ "$PWD" = /usr/bin ] +cd ./ +[ "$PWD" = /usr/bin ] +cd .. +[ "$PWD" = /usr ] +cd /usr/bin +cd ../ +[ "$PWD" = /usr ] diff --git a/bin/1sh/tests/builtins/cd8.0 b/bin/1sh/tests/builtins/cd8.0 new file mode 100644 index 00000000..477616da --- /dev/null +++ b/bin/1sh/tests/builtins/cd8.0 @@ -0,0 +1,26 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/cd8.0 230095 2012-01-13 23:32:27Z jilles $ + +# The exact wording of the error message is not standardized, but giving +# a description of the errno is useful. + +LC_ALL=C +export LC_ALL +r=0 + +t() { + exec 3>&1 + errmsg=`cd "$1" 2>&1 >&3 3>&-` + exec 3>&- + case $errmsg in + *[Nn]ot\ a\ directory*) + ;; + *) + printf "Wrong error message for %s: %s\n" "$1" "$errmsg" + r=3 + ;; + esac +} + +t /dev/tty +t /dev/tty/x +exit $r diff --git a/bin/1sh/tests/builtins/cd9.0 b/bin/1sh/tests/builtins/cd9.0 new file mode 100644 index 00000000..f3ccfb70 --- /dev/null +++ b/bin/1sh/tests/builtins/cd9.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/cd9.0 293371 2016-01-07 21:46:07Z jilles $ + +cd /dev +cd /bin +cd - >/dev/null +pwd +cd - >/dev/null +pwd diff --git a/bin/1sh/tests/builtins/cd9.0.stdout b/bin/1sh/tests/builtins/cd9.0.stdout new file mode 100644 index 00000000..dac16a76 --- /dev/null +++ b/bin/1sh/tests/builtins/cd9.0.stdout @@ -0,0 +1,2 @@ +/dev +/bin diff --git a/bin/1sh/tests/builtins/command1.0 b/bin/1sh/tests/builtins/command1.0 new file mode 100644 index 00000000..497ffdf5 --- /dev/null +++ b/bin/1sh/tests/builtins/command1.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command1.0 151797 2005-10-28 14:02:42Z stefanf $ +true() { + false +} +command true diff --git a/bin/1sh/tests/builtins/command10.0 b/bin/1sh/tests/builtins/command10.0 new file mode 100644 index 00000000..b043be14 --- /dev/null +++ b/bin/1sh/tests/builtins/command10.0 @@ -0,0 +1,14 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command10.0 204802 2010-03-06 17:31:09Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +check '"$(f() { shift x; }; { command eval f 2>/dev/null; } >/dev/null; echo hi)" = hi' + +exit $((failures > 0)) diff --git a/bin/1sh/tests/builtins/command11.0 b/bin/1sh/tests/builtins/command11.0 new file mode 100644 index 00000000..14cdae27 --- /dev/null +++ b/bin/1sh/tests/builtins/command11.0 @@ -0,0 +1,14 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command11.0 205154 2010-03-14 14:24:35Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +check '"$({ command eval \{ shift x\; \} 2\>/dev/null; } >/dev/null; echo hi)" = hi' + +exit $((failures > 0)) diff --git a/bin/1sh/tests/builtins/command12.0 b/bin/1sh/tests/builtins/command12.0 new file mode 100644 index 00000000..865f690b --- /dev/null +++ b/bin/1sh/tests/builtins/command12.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command12.0 257929 2013-11-10 23:00:39Z jilles $ + +alias aa=echo\ \'\"\' +cmd=$(command -v aa) +alias aa=echo\ bad +eval "$cmd" +[ "$(eval aa)" = \" ] diff --git a/bin/1sh/tests/builtins/command2.0 b/bin/1sh/tests/builtins/command2.0 new file mode 100644 index 00000000..6994b584 --- /dev/null +++ b/bin/1sh/tests/builtins/command2.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command2.0 151797 2005-10-28 14:02:42Z stefanf $ +PATH= +command -p cat < /dev/null diff --git a/bin/1sh/tests/builtins/command3.0 b/bin/1sh/tests/builtins/command3.0 new file mode 100644 index 00000000..20d56604 --- /dev/null +++ b/bin/1sh/tests/builtins/command3.0 @@ -0,0 +1,14 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command3.0 211399 2010-08-16 17:18:08Z jilles $ +command -v ls +command -v true +command -v /bin/ls + +fun() { + : +} +command -v fun +command -v break +command -v if + +alias foo=bar +command -v foo diff --git a/bin/1sh/tests/builtins/command3.0.stdout b/bin/1sh/tests/builtins/command3.0.stdout new file mode 100644 index 00000000..67b86915 --- /dev/null +++ b/bin/1sh/tests/builtins/command3.0.stdout @@ -0,0 +1,7 @@ +/bin/ls +true +/bin/ls +fun +break +if +alias foo=bar diff --git a/bin/1sh/tests/builtins/command4.0 b/bin/1sh/tests/builtins/command4.0 new file mode 100644 index 00000000..fa7ab6ce --- /dev/null +++ b/bin/1sh/tests/builtins/command4.0 @@ -0,0 +1,2 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command4.0 211973 2010-08-29 20:53:24Z jilles $ +! command -v nonexisting diff --git a/bin/1sh/tests/builtins/command5.0 b/bin/1sh/tests/builtins/command5.0 new file mode 100644 index 00000000..e99a36fe --- /dev/null +++ b/bin/1sh/tests/builtins/command5.0 @@ -0,0 +1,15 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command5.0 211399 2010-08-16 17:18:08Z jilles $ +command -V ls +command -V true +command -V /bin/ls + +fun() { + : +} +command -V fun +command -V break +command -V if +command -V { + +alias foo=bar +command -V foo diff --git a/bin/1sh/tests/builtins/command5.0.stdout b/bin/1sh/tests/builtins/command5.0.stdout new file mode 100644 index 00000000..523f7b22 --- /dev/null +++ b/bin/1sh/tests/builtins/command5.0.stdout @@ -0,0 +1,8 @@ +ls is /bin/ls +true is a shell builtin +/bin/ls is /bin/ls +fun is a shell function +break is a special shell builtin +if is a shell keyword +{ is a shell keyword +foo is an alias for bar diff --git a/bin/1sh/tests/builtins/command6.0 b/bin/1sh/tests/builtins/command6.0 new file mode 100644 index 00000000..3f762875 --- /dev/null +++ b/bin/1sh/tests/builtins/command6.0 @@ -0,0 +1,22 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command6.0 218356 2011-02-05 23:00:24Z jilles $ +PATH=/var/empty +case $(command -pV ls) in +*/var/empty/ls*) + echo "Failed: \$(command -pV ls) should not match */var/empty/ls*" ;; +"ls is"*" "/*/ls) ;; +*) + echo "Failed: \$(command -pV ls) match \"ls is\"*\" \"/*/ls" ;; +esac +command -pV true +command -pV /bin/ls + +fun() { + : +} +command -pV fun +command -pV break +command -pV if +command -pV { + +alias foo=bar +command -pV foo diff --git a/bin/1sh/tests/builtins/command6.0.stdout b/bin/1sh/tests/builtins/command6.0.stdout new file mode 100644 index 00000000..3180207a --- /dev/null +++ b/bin/1sh/tests/builtins/command6.0.stdout @@ -0,0 +1,7 @@ +true is a shell builtin +/bin/ls is /bin/ls +fun is a shell function +break is a special shell builtin +if is a shell keyword +{ is a shell keyword +foo is an alias for bar diff --git a/bin/1sh/tests/builtins/command7.0 b/bin/1sh/tests/builtins/command7.0 new file mode 100644 index 00000000..213c2c50 --- /dev/null +++ b/bin/1sh/tests/builtins/command7.0 @@ -0,0 +1,34 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command7.0 218356 2011-02-05 23:00:24Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +check '"$(PATH=/libexec command -V ld-elf.so.1)" = "ld-elf.so.1 is /libexec/ld-elf.so.1"' +check '"$(PATH=/libexec command -V ld-elf.so.1; :)" = "ld-elf.so.1 is /libexec/ld-elf.so.1"' +check '"$(PATH=/libexec command -pv ld-elf.so.1)" = ""' +check '"$(PATH=/libexec command -pv ld-elf.so.1; :)" = ""' + +PATH=/libexec:$PATH + +check '"$(command -V ld-elf.so.1)" = "ld-elf.so.1 is /libexec/ld-elf.so.1"' +check '"$(command -V ld-elf.so.1; :)" = "ld-elf.so.1 is /libexec/ld-elf.so.1"' +check '"$(command -pv ld-elf.so.1)" = ""' +check '"$(command -pv ld-elf.so.1; :)" = ""' + +PATH=/libexec + +check '"$(command -v ls)" = ""' +case $(command -pv ls) in +/*/ls) ;; +*) + echo "Failed: \$(command -pv ls) match /*/ls" + : $((failures += 1)) ;; +esac + +exit $((failures > 0)) diff --git a/bin/1sh/tests/builtins/command8.0 b/bin/1sh/tests/builtins/command8.0 new file mode 100644 index 00000000..f8350092 --- /dev/null +++ b/bin/1sh/tests/builtins/command8.0 @@ -0,0 +1,45 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command8.0 213738 2010-10-12 18:20:38Z obrien $ +IFS=, + +SPECIAL="break,\ + :,\ + continue,\ + . /dev/null,\ + eval,\ + exec,\ + export -p,\ + readonly -p,\ + set,\ + shift 0,\ + times,\ + trap,\ + unset foo" + +set -e + +# Check that special builtins can be executed via "command". + +set -- ${SPECIAL} +for cmd in "$@" +do + ${SH} -c "v=:; while \$v; do v=false; command ${cmd}; done" >/dev/null +done + +while :; do + command break + echo Error on line $LINENO +done + +set p q r +command shift 2 +if [ $# -ne 1 ]; then + echo Error on line $LINENO +fi + +( + command exec >/dev/null + echo Error on line $LINENO +) + +set +e +! command shift 2 2>/dev/null diff --git a/bin/1sh/tests/builtins/command9.0 b/bin/1sh/tests/builtins/command9.0 new file mode 100644 index 00000000..704415f8 --- /dev/null +++ b/bin/1sh/tests/builtins/command9.0 @@ -0,0 +1,14 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/command9.0 204801 2010-03-06 17:09:22Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +check '"$({ command eval shift x 2>/dev/null; } >/dev/null; echo hi)" = hi' + +exit $((failures > 0)) diff --git a/bin/1sh/tests/builtins/dot1.0 b/bin/1sh/tests/builtins/dot1.0 new file mode 100644 index 00000000..e714e57a --- /dev/null +++ b/bin/1sh/tests/builtins/dot1.0 @@ -0,0 +1,21 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/dot1.0 208629 2010-05-28 22:08:34Z jilles $ + +failures= +failure() { + echo "Error at line $1" >&2 + failures=x$failures +} + +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) || exit +trap 'rm -rf $T' 0 +cd $T || exit 3 +unset x +echo 'x=2' >testscript +. ./testscript +[ "$x" = 2 ] || failure $LINENO +cd / || exit 3 +x=1 +PATH=$T:$PATH . testscript +[ "$x" = 2 ] || failure $LINENO + +test -z "$failures" diff --git a/bin/1sh/tests/builtins/dot2.0 b/bin/1sh/tests/builtins/dot2.0 new file mode 100644 index 00000000..e3b39569 --- /dev/null +++ b/bin/1sh/tests/builtins/dot2.0 @@ -0,0 +1,21 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/dot2.0 208630 2010-05-28 22:40:24Z jilles $ + +failures= +failure() { + echo "Error at line $1" >&2 + failures=x$failures +} + +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) || exit +trap 'rm -rf $T' 0 +cd $T || exit 3 +unset x +echo 'x=2' >testscript +. -- ./testscript +[ "$x" = 2 ] || failure $LINENO +cd / || exit 3 +x=1 +PATH=$T:$PATH . -- testscript +[ "$x" = 2 ] || failure $LINENO + +test -z "$failures" diff --git a/bin/1sh/tests/builtins/dot3.0 b/bin/1sh/tests/builtins/dot3.0 new file mode 100644 index 00000000..7759666e --- /dev/null +++ b/bin/1sh/tests/builtins/dot3.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/dot3.0 219390 2011-03-07 23:52:23Z jilles $ + +# . should return 0 if no command was executed. + +if false; then + exit 3 +else + . /dev/null + exit $? +fi diff --git a/bin/1sh/tests/builtins/dot4.0 b/bin/1sh/tests/builtins/dot4.0 new file mode 100644 index 00000000..60b6ff4c --- /dev/null +++ b/bin/1sh/tests/builtins/dot4.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/dot4.0 222174 2011-05-22 12:15:14Z jilles $ + +v=abcd +v=$v$v$v$v +v=$v$v$v$v +v=$v$v$v$v +v=$v$v$v$v +v=$v$v$v$v +r=$( ( + trap 'exit 0' 0 + . "$v" +) 2>&1 >/dev/null) && [ -n "$r" ] diff --git a/bin/1sh/tests/builtins/echo1.0 b/bin/1sh/tests/builtins/echo1.0 new file mode 100644 index 00000000..98a5b8bd --- /dev/null +++ b/bin/1sh/tests/builtins/echo1.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/echo1.0 305305 2016-09-02 21:13:46Z jilles $ + +# Not specified by POSIX. + +[ "`echo -n a b; echo c d; echo e f`" = "a bc d +e f" ] diff --git a/bin/1sh/tests/builtins/echo2.0 b/bin/1sh/tests/builtins/echo2.0 new file mode 100644 index 00000000..a7521a62 --- /dev/null +++ b/bin/1sh/tests/builtins/echo2.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/echo2.0 305305 2016-09-02 21:13:46Z jilles $ + +# Not specified by POSIX. + +a=`echo -e '\a\b\e\f\n\r\t\v\\\\\0041\c'; echo .` +b=`printf '\a\b\033\f\n\r\t\v\\\\!.'` +[ "$a" = "$b" ] diff --git a/bin/1sh/tests/builtins/echo3.0 b/bin/1sh/tests/builtins/echo3.0 new file mode 100644 index 00000000..c77eeb85 --- /dev/null +++ b/bin/1sh/tests/builtins/echo3.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/echo3.0 305305 2016-09-02 21:13:46Z jilles $ + +# Not specified by POSIX. + +[ "`echo -e 'a\cb' c; echo d`" = "ad" ] diff --git a/bin/1sh/tests/builtins/eval1.0 b/bin/1sh/tests/builtins/eval1.0 new file mode 100644 index 00000000..5e2e2e8e --- /dev/null +++ b/bin/1sh/tests/builtins/eval1.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/eval1.0 193178 2009-05-31 17:23:27Z stefanf $ +set -e + +eval +eval "" "" +eval "true" +! eval "false + +" diff --git a/bin/1sh/tests/builtins/eval2.0 b/bin/1sh/tests/builtins/eval2.0 new file mode 100644 index 00000000..0bb5fa7a --- /dev/null +++ b/bin/1sh/tests/builtins/eval2.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/eval2.0 194897 2009-06-24 20:22:54Z jilles $ + +eval ' +false + +' && exit 1 +exit 0 diff --git a/bin/1sh/tests/builtins/eval3.0 b/bin/1sh/tests/builtins/eval3.0 new file mode 100644 index 00000000..d9da6bc1 --- /dev/null +++ b/bin/1sh/tests/builtins/eval3.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/eval3.0 196607 2009-08-27 22:23:23Z jilles $ + +eval 'false;' && exit 1 +eval 'true;' || exit 1 +eval 'false; +' && exit 1 +eval 'true; +' || exit 1 +exit 0 diff --git a/bin/1sh/tests/builtins/eval4.0 b/bin/1sh/tests/builtins/eval4.0 new file mode 100644 index 00000000..2591e3d7 --- /dev/null +++ b/bin/1sh/tests/builtins/eval4.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/eval4.0 210738 2010-08-01 22:39:07Z jilles $ + +# eval should preserve $? from command substitutions when starting +# the parsed command. +[ $(eval 'echo $?' $(false)) = 1 ] diff --git a/bin/1sh/tests/builtins/eval5.0 b/bin/1sh/tests/builtins/eval5.0 new file mode 100644 index 00000000..a00f4de6 --- /dev/null +++ b/bin/1sh/tests/builtins/eval5.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/eval5.0 210829 2010-08-03 22:17:29Z jilles $ + +# eval should return 0 if no command was executed. +eval $(false) diff --git a/bin/1sh/tests/builtins/eval6.0 b/bin/1sh/tests/builtins/eval6.0 new file mode 100644 index 00000000..7a0548e5 --- /dev/null +++ b/bin/1sh/tests/builtins/eval6.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/eval6.0 249220 2013-04-06 22:30:46Z jilles $ + +# eval should preserve $? from command substitutions when starting +# the parsed command. +[ $(false; eval 'echo $?' $(:)) = 0 ] diff --git a/bin/1sh/tests/builtins/eval7.0 b/bin/1sh/tests/builtins/eval7.0 new file mode 100644 index 00000000..b9b09d5a --- /dev/null +++ b/bin/1sh/tests/builtins/eval7.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/eval7.0 272983 2014-10-12 13:12:06Z jilles $ +# Assumes that break can break out of a loop outside eval. + +while :; do + eval "break +echo bad1" + echo bad2 + exit 3 +done diff --git a/bin/1sh/tests/builtins/eval8.7 b/bin/1sh/tests/builtins/eval8.7 new file mode 100644 index 00000000..7420386e --- /dev/null +++ b/bin/1sh/tests/builtins/eval8.7 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/eval8.7 272983 2014-10-12 13:12:06Z jilles $ + +f() { + eval "return 7 +echo bad2" +} +f diff --git a/bin/1sh/tests/builtins/exec1.0 b/bin/1sh/tests/builtins/exec1.0 new file mode 100644 index 00000000..b4932749 --- /dev/null +++ b/bin/1sh/tests/builtins/exec1.0 @@ -0,0 +1,25 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/exec1.0 213738 2010-10-12 18:20:38Z obrien $ + +failures= +failure() { + echo "Error at line $1" >&2 + failures=x$failures +} + +( + exec >/dev/null + echo bad +) +[ $? = 0 ] || failure $LINENO +( + exec ${SH} -c 'exit 42' + echo bad +) +[ $? = 42 ] || failure $LINENO +( + exec /var/empty/nosuch + echo bad +) 2>/dev/null +[ $? = 127 ] || failure $LINENO + +test -z "$failures" diff --git a/bin/1sh/tests/builtins/exec2.0 b/bin/1sh/tests/builtins/exec2.0 new file mode 100644 index 00000000..93d32b13 --- /dev/null +++ b/bin/1sh/tests/builtins/exec2.0 @@ -0,0 +1,25 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/exec2.0 213738 2010-10-12 18:20:38Z obrien $ + +failures= +failure() { + echo "Error at line $1" >&2 + failures=x$failures +} + +( + exec -- >/dev/null + echo bad +) +[ $? = 0 ] || failure $LINENO +( + exec -- ${SH} -c 'exit 42' + echo bad +) +[ $? = 42 ] || failure $LINENO +( + exec -- /var/empty/nosuch + echo bad +) 2>/dev/null +[ $? = 127 ] || failure $LINENO + +test -z "$failures" diff --git a/bin/1sh/tests/builtins/exit1.0 b/bin/1sh/tests/builtins/exit1.0 new file mode 100644 index 00000000..45b7b0ec --- /dev/null +++ b/bin/1sh/tests/builtins/exit1.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/exit1.0 216871 2011-01-01 15:25:15Z jilles $ + +# exit with an argument should overwrite the exit status in an EXIT trap. + +trap 'true; exit $?' 0 +false diff --git a/bin/1sh/tests/builtins/exit2.8 b/bin/1sh/tests/builtins/exit2.8 new file mode 100644 index 00000000..d5f97159 --- /dev/null +++ b/bin/1sh/tests/builtins/exit2.8 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/exit2.8 217172 2011-01-08 23:00:38Z jilles $ + +# exit without arguments is the same as exit $? outside a trap. + +trap 'true; true' 0 +(exit 8) +exit diff --git a/bin/1sh/tests/builtins/exit3.0 b/bin/1sh/tests/builtins/exit3.0 new file mode 100644 index 00000000..c3835a46 --- /dev/null +++ b/bin/1sh/tests/builtins/exit3.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/exit3.0 217175 2011-01-08 23:08:13Z jilles $ + +# exit without arguments differs from exit $? in an EXIT trap. + +trap 'false; exit' 0 diff --git a/bin/1sh/tests/builtins/export1.0 b/bin/1sh/tests/builtins/export1.0 new file mode 100644 index 00000000..35ec714f --- /dev/null +++ b/bin/1sh/tests/builtins/export1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/export1.0 223183 2011-06-17 10:21:24Z jilles $ + +env @badness=1 ${SH} -c 'v=`export -p`; eval "$v"' diff --git a/bin/1sh/tests/builtins/fc1.0 b/bin/1sh/tests/builtins/fc1.0 new file mode 100644 index 00000000..86b6afe8 --- /dev/null +++ b/bin/1sh/tests/builtins/fc1.0 @@ -0,0 +1,27 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/fc1.0 213738 2010-10-12 18:20:38Z obrien $ +set -e +trap 'echo Broken pipe -- test failed' PIPE + +P=${TMPDIR:-/tmp} +cd $P +T=$(mktemp -d sh-test.XXXXXX) +cd $T + +mkfifo input output error +HISTFILE=/dev/null ${SH} +m -i <input >output 2>error & +{ + # Syntax error + echo ')' >&3 + # Read error message, shell will read new input now + read dummy <&5 + # Execute bad command again + echo 'fc -e true' >&3 + # Verify that the shell is still running + echo 'echo continued' >&3 || rc=3 + echo 'exit' >&3 || rc=3 + read line <&4 && [ "$line" = continued ] && : ${rc:=0} +} 3>input 4<output 5<error + +rm input output error +rmdir ${P}/${T} +exit ${rc:-3} diff --git a/bin/1sh/tests/builtins/fc2.0 b/bin/1sh/tests/builtins/fc2.0 new file mode 100644 index 00000000..ac267566 --- /dev/null +++ b/bin/1sh/tests/builtins/fc2.0 @@ -0,0 +1,34 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/fc2.0 213738 2010-10-12 18:20:38Z obrien $ +set -e +trap 'echo Broken pipe -- test failed' PIPE + +P=${TMPDIR:-/tmp} +cd $P +T=$(mktemp -d sh-test.XXXXXX) +cd $T + +mkfifo input output error +HISTFILE=/dev/null ${SH} +m -i <input >output 2>error & +exec 3>input +{ + # Command not found, containing slash + echo '/var/empty/nonexistent' >&3 + # Read error message, shell will read new input now + read dummy <&5 + # Execute bad command again + echo 'fc -e true; echo continued' >&3 + read dummy <&5 + read line <&4 && [ "$line" = continued ] && : ${rc:=0} + exec 3>&- + # Old sh duplicates itself after the fc, producing another line + # of output. + if read line <&4; then + echo "Extraneous output: $line" + rc=1 + fi +} 4<output 5<error +exec 3>&- + +rm input output error +rmdir ${P}/${T} +exit ${rc:-3} diff --git a/bin/1sh/tests/builtins/for1.0 b/bin/1sh/tests/builtins/for1.0 new file mode 100644 index 00000000..7906a193 --- /dev/null +++ b/bin/1sh/tests/builtins/for1.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/for1.0 226892 2011-10-28 23:02:21Z jilles $ + +false +for i in `false`; do exit 3; done diff --git a/bin/1sh/tests/builtins/for2.0 b/bin/1sh/tests/builtins/for2.0 new file mode 100644 index 00000000..b4c59710 --- /dev/null +++ b/bin/1sh/tests/builtins/for2.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/for2.0 230463 2012-01-22 14:00:33Z jilles $ + +r=x +f() { return 42; } +f +for i in x; do + r=$? +done +[ "$r" = 42 ] diff --git a/bin/1sh/tests/builtins/for3.0 b/bin/1sh/tests/builtins/for3.0 new file mode 100644 index 00000000..0e07a2a2 --- /dev/null +++ b/bin/1sh/tests/builtins/for3.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/for3.0 230463 2012-01-22 14:00:33Z jilles $ + +r=x +f() { return 42; } +for i in x`f`; do + r=$? +done +[ "$r" = 42 ] diff --git a/bin/1sh/tests/builtins/getopts1.0 b/bin/1sh/tests/builtins/getopts1.0 new file mode 100644 index 00000000..99cc05b3 --- /dev/null +++ b/bin/1sh/tests/builtins/getopts1.0 @@ -0,0 +1,25 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/getopts1.0 297752 2016-04-09 16:06:13Z jilles $ + +printf -- '-1-\n' +set -- -abc +getopts "ab:" OPTION +printf '%s\n' "${OPTION}" + +# In this case 'getopts' should realize that we have not provided the +# required argument for "-b". +# Note that Solaris 10's (UNIX 03) /usr/xpg4/bin/sh, /bin/sh, and /bin/ksh; +# ksh93 20090505; pdksh 5.2.14p2; mksh R39c; bash 4.1 PL7; and zsh 4.3.10. +# all recognize that "b" is missing its argument on the *first* iteration +# of 'getopts' and do not produce the "a" in $OPTION. +printf -- '-2-\n' +set -- -ab +getopts "ab:" OPTION +printf '%s\n' "${OPTION}" +getopts "ab:" OPTION 3>&2 2>&1 >&3 3>&- +printf '%s\n' "${OPTION}" + +# The 'shift' is aimed at causing an error. +printf -- '-3-\n' +shift 1 +getopts "ab:" OPTION +printf '%s\n' "${OPTION}" diff --git a/bin/1sh/tests/builtins/getopts1.0.stdout b/bin/1sh/tests/builtins/getopts1.0.stdout new file mode 100644 index 00000000..a0a347ee --- /dev/null +++ b/bin/1sh/tests/builtins/getopts1.0.stdout @@ -0,0 +1,8 @@ +-1- +a +-2- +a +No arg for -b option +? +-3- +? diff --git a/bin/1sh/tests/builtins/getopts10.0 b/bin/1sh/tests/builtins/getopts10.0 new file mode 100644 index 00000000..45297c82 --- /dev/null +++ b/bin/1sh/tests/builtins/getopts10.0 @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/getopts10.0 293359 2016-01-07 20:48:24Z jilles $ + +set -- -x arg +opt=not +getopts x opt +r1=$? OPTIND1=$OPTIND opt1=$opt +: $(: $((OPTIND = 1))) +getopts x opt +r2=$? OPTIND2=$OPTIND +[ "$r1" = 0 ] && [ "$OPTIND1" = 2 ] && [ "$opt1" = x ] && [ "$r2" != 0 ] && + [ "$OPTIND2" = 2 ] diff --git a/bin/1sh/tests/builtins/getopts2.0 b/bin/1sh/tests/builtins/getopts2.0 new file mode 100644 index 00000000..2f83d992 --- /dev/null +++ b/bin/1sh/tests/builtins/getopts2.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/getopts2.0 297752 2016-04-09 16:06:13Z jilles $ +set - -ax +getopts ax option +set -C +getopts ax option +printf '%s\n' "$option" diff --git a/bin/1sh/tests/builtins/getopts2.0.stdout b/bin/1sh/tests/builtins/getopts2.0.stdout new file mode 100644 index 00000000..587be6b4 --- /dev/null +++ b/bin/1sh/tests/builtins/getopts2.0.stdout @@ -0,0 +1 @@ +x diff --git a/bin/1sh/tests/builtins/getopts3.0 b/bin/1sh/tests/builtins/getopts3.0 new file mode 100644 index 00000000..5a24adbb --- /dev/null +++ b/bin/1sh/tests/builtins/getopts3.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/getopts3.0 265616 2014-05-07 21:45:25Z jilles $ + +shift $# +getopts x opt +r=$? +[ "$r" != 0 ] && [ "$OPTIND" = 1 ] diff --git a/bin/1sh/tests/builtins/getopts4.0 b/bin/1sh/tests/builtins/getopts4.0 new file mode 100644 index 00000000..8e68e38b --- /dev/null +++ b/bin/1sh/tests/builtins/getopts4.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/getopts4.0 265616 2014-05-07 21:45:25Z jilles $ + +set -- -x +opt=not +getopts x opt +r1=$? OPTIND1=$OPTIND opt1=$opt +getopts x opt +r2=$? OPTIND2=$OPTIND +[ "$r1" = 0 ] && [ "$OPTIND1" = 2 ] && [ "$opt1" = x ] && [ "$r2" != 0 ] && + [ "$OPTIND2" = 2 ] diff --git a/bin/1sh/tests/builtins/getopts5.0 b/bin/1sh/tests/builtins/getopts5.0 new file mode 100644 index 00000000..7ce25e31 --- /dev/null +++ b/bin/1sh/tests/builtins/getopts5.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/getopts5.0 265616 2014-05-07 21:45:25Z jilles $ + +set -- -x arg +opt=not +getopts x opt +r1=$? OPTIND1=$OPTIND opt1=$opt +getopts x opt +r2=$? OPTIND2=$OPTIND +[ "$r1" = 0 ] && [ "$OPTIND1" = 2 ] && [ "$opt1" = x ] && [ "$r2" != 0 ] && + [ "$OPTIND2" = 2 ] diff --git a/bin/1sh/tests/builtins/getopts6.0 b/bin/1sh/tests/builtins/getopts6.0 new file mode 100644 index 00000000..d968204a --- /dev/null +++ b/bin/1sh/tests/builtins/getopts6.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/getopts6.0 265844 2014-05-10 17:42:21Z jilles $ + +set -- -x -y +getopts :x var || echo "First getopts bad: $?" +getopts :x var +r=$? +[ r != 0 ] && [ "$OPTIND" = 3 ] diff --git a/bin/1sh/tests/builtins/getopts7.0 b/bin/1sh/tests/builtins/getopts7.0 new file mode 100644 index 00000000..b8f884a4 --- /dev/null +++ b/bin/1sh/tests/builtins/getopts7.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/getopts7.0 265844 2014-05-10 17:42:21Z jilles $ + +set -- -x +getopts :x: var +r=$? +[ r != 0 ] && [ "$OPTIND" = 2 ] diff --git a/bin/1sh/tests/builtins/getopts8.0 b/bin/1sh/tests/builtins/getopts8.0 new file mode 100644 index 00000000..841b3f4e --- /dev/null +++ b/bin/1sh/tests/builtins/getopts8.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/getopts8.0 265849 2014-05-10 19:18:49Z jilles $ + +set -- -yz -wx +opt=wrong1 OPTARG=wrong2 +while getopts :x opt; do + echo "$opt:${OPTARG-unset}" +done +echo "OPTIND=$OPTIND" diff --git a/bin/1sh/tests/builtins/getopts8.0.stdout b/bin/1sh/tests/builtins/getopts8.0.stdout new file mode 100644 index 00000000..f10cefcd --- /dev/null +++ b/bin/1sh/tests/builtins/getopts8.0.stdout @@ -0,0 +1,5 @@ +?:y +?:z +?:w +x:unset +OPTIND=3 diff --git a/bin/1sh/tests/builtins/getopts9.0 b/bin/1sh/tests/builtins/getopts9.0 new file mode 100644 index 00000000..a4864f53 --- /dev/null +++ b/bin/1sh/tests/builtins/getopts9.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/getopts9.0 297752 2016-04-09 16:06:13Z jilles $ + +args='-ab' +getopts ab opt $args +printf '%s\n' "$?:$opt:$OPTARG" +for dummy in dummy1 dummy2; do + getopts ab opt $args + printf '%s\n' "$?:$opt:$OPTARG" +done diff --git a/bin/1sh/tests/builtins/getopts9.0.stdout b/bin/1sh/tests/builtins/getopts9.0.stdout new file mode 100644 index 00000000..4d32063f --- /dev/null +++ b/bin/1sh/tests/builtins/getopts9.0.stdout @@ -0,0 +1,3 @@ +0:a: +0:b: +1:?: diff --git a/bin/1sh/tests/builtins/hash1.0 b/bin/1sh/tests/builtins/hash1.0 new file mode 100644 index 00000000..646066f8 --- /dev/null +++ b/bin/1sh/tests/builtins/hash1.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/hash1.0 149791 2005-09-05 09:42:10Z stefanf $ +cat /dev/null +hash +hash -r +hash diff --git a/bin/1sh/tests/builtins/hash1.0.stdout b/bin/1sh/tests/builtins/hash1.0.stdout new file mode 100644 index 00000000..3afc3e7b --- /dev/null +++ b/bin/1sh/tests/builtins/hash1.0.stdout @@ -0,0 +1 @@ +/bin/cat diff --git a/bin/1sh/tests/builtins/hash2.0 b/bin/1sh/tests/builtins/hash2.0 new file mode 100644 index 00000000..739638c4 --- /dev/null +++ b/bin/1sh/tests/builtins/hash2.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/hash2.0 149791 2005-09-05 09:42:10Z stefanf $ +hash +hash cat +hash diff --git a/bin/1sh/tests/builtins/hash2.0.stdout b/bin/1sh/tests/builtins/hash2.0.stdout new file mode 100644 index 00000000..3afc3e7b --- /dev/null +++ b/bin/1sh/tests/builtins/hash2.0.stdout @@ -0,0 +1 @@ +/bin/cat diff --git a/bin/1sh/tests/builtins/hash3.0 b/bin/1sh/tests/builtins/hash3.0 new file mode 100644 index 00000000..a0bfd48c --- /dev/null +++ b/bin/1sh/tests/builtins/hash3.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/hash3.0 149791 2005-09-05 09:42:10Z stefanf $ +hash -v cat +hash diff --git a/bin/1sh/tests/builtins/hash3.0.stdout b/bin/1sh/tests/builtins/hash3.0.stdout new file mode 100644 index 00000000..a34864cd --- /dev/null +++ b/bin/1sh/tests/builtins/hash3.0.stdout @@ -0,0 +1,2 @@ +/bin/cat +/bin/cat diff --git a/bin/1sh/tests/builtins/hash4.0 b/bin/1sh/tests/builtins/hash4.0 new file mode 100644 index 00000000..4faf4e70 --- /dev/null +++ b/bin/1sh/tests/builtins/hash4.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/hash4.0 231535 2012-02-11 21:06:45Z jilles $ + +exec 3>&1 +m=`hash nosuchtool 2>&1 >&3` +r=$? +[ "$r" != 0 ] && [ -n "$m" ] diff --git a/bin/1sh/tests/builtins/jobid1.0 b/bin/1sh/tests/builtins/jobid1.0 new file mode 100644 index 00000000..4610a710 --- /dev/null +++ b/bin/1sh/tests/builtins/jobid1.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/jobid1.0 254412 2013-08-16 13:48:11Z jilles $ +# Non-standard builtin. + +: & +p1=$! +p2=$(jobid) +[ "${p1:?}" = "${p2:?}" ] diff --git a/bin/1sh/tests/builtins/jobid2.0 b/bin/1sh/tests/builtins/jobid2.0 new file mode 100644 index 00000000..b8af0d06 --- /dev/null +++ b/bin/1sh/tests/builtins/jobid2.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/jobid2.0 254413 2013-08-16 13:56:43Z jilles $ + +: & +p1=$(jobid) +p2=$(jobid --) +p3=$(jobid %+) +p4=$(jobid -- %+) +[ "${p1:?}" = "${p2:?}" ] && [ "${p2:?}" = "${p3:?}" ] && +[ "${p3:?}" = "${p4:?}" ] && [ "${p4:?}" = "${p1:?}" ] diff --git a/bin/1sh/tests/builtins/kill1.0 b/bin/1sh/tests/builtins/kill1.0 new file mode 100644 index 00000000..1b22f517 --- /dev/null +++ b/bin/1sh/tests/builtins/kill1.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/kill1.0 262931 2014-03-08 19:44:34Z jilles $ + +: & +p1=$! +: & +p2=$! +wait $p2 +kill %1 diff --git a/bin/1sh/tests/builtins/kill2.0 b/bin/1sh/tests/builtins/kill2.0 new file mode 100644 index 00000000..ff71c8af --- /dev/null +++ b/bin/1sh/tests/builtins/kill2.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/kill2.0 263206 2014-03-15 14:58:48Z jilles $ + +sleep 1 | sleep 1 & +kill %+ +wait "$!" +r=$? +[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = TERM ] diff --git a/bin/1sh/tests/builtins/lineno.0 b/bin/1sh/tests/builtins/lineno.0 new file mode 100644 index 00000000..6b1557a7 --- /dev/null +++ b/bin/1sh/tests/builtins/lineno.0 @@ -0,0 +1,16 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/lineno.0 179023 2008-05-15 19:58:44Z stefanf $ +echo $LINENO +echo $LINENO + +f() { + echo $LINENO + echo $LINENO +} + +f + +echo ${LINENO:-foo} +echo ${LINENO=foo} +echo ${LINENO:+foo} +echo ${LINENO+foo} +echo ${#LINENO} diff --git a/bin/1sh/tests/builtins/lineno.0.stdout b/bin/1sh/tests/builtins/lineno.0.stdout new file mode 100644 index 00000000..82583a93 --- /dev/null +++ b/bin/1sh/tests/builtins/lineno.0.stdout @@ -0,0 +1,9 @@ +2 +3 +2 +3 +12 +13 +foo +foo +2 diff --git a/bin/1sh/tests/builtins/lineno2.0 b/bin/1sh/tests/builtins/lineno2.0 new file mode 100644 index 00000000..154875d3 --- /dev/null +++ b/bin/1sh/tests/builtins/lineno2.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/lineno2.0 262565 2014-02-27 16:54:43Z jilles $ + +f() { + : ${LINENO+${x?}} +} + +unset -v x +command eval f 2>/dev/null && exit 3 +x=1 +f diff --git a/bin/1sh/tests/builtins/lineno3.0 b/bin/1sh/tests/builtins/lineno3.0 new file mode 100644 index 00000000..f0c6be19 --- /dev/null +++ b/bin/1sh/tests/builtins/lineno3.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/lineno3.0 272482 2014-10-03 20:24:56Z jilles $ + +echo before: $LINENO +dummy=$'a\0 +' +echo after: $LINENO diff --git a/bin/1sh/tests/builtins/lineno3.0.stdout b/bin/1sh/tests/builtins/lineno3.0.stdout new file mode 100644 index 00000000..6e0e4ac8 --- /dev/null +++ b/bin/1sh/tests/builtins/lineno3.0.stdout @@ -0,0 +1,2 @@ +before: 3 +after: 6 diff --git a/bin/1sh/tests/builtins/local1.0 b/bin/1sh/tests/builtins/local1.0 new file mode 100644 index 00000000..53b54b52 --- /dev/null +++ b/bin/1sh/tests/builtins/local1.0 @@ -0,0 +1,13 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/local1.0 238469 2012-07-15 10:22:13Z jilles $ +# A commonly used but non-POSIX builtin. + +f() { + local x + x=2 + [ "$x" = 2 ] +} +x=1 +f || exit 3 +[ "$x" = 1 ] || exit 3 +f || exit 3 +[ "$x" = 1 ] || exit 3 diff --git a/bin/1sh/tests/builtins/local2.0 b/bin/1sh/tests/builtins/local2.0 new file mode 100644 index 00000000..7c1bbe6f --- /dev/null +++ b/bin/1sh/tests/builtins/local2.0 @@ -0,0 +1,17 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/local2.0 251797 2013-06-15 22:22:03Z jilles $ + +f() { + local - + set -a + case $- in + *a*) : ;; + *) echo In-function \$- bad + esac +} +case $- in +*a*) echo Initial \$- bad +esac +f +case $- in +*a*) echo Final \$- bad +esac diff --git a/bin/1sh/tests/builtins/local3.0 b/bin/1sh/tests/builtins/local3.0 new file mode 100644 index 00000000..bb041366 --- /dev/null +++ b/bin/1sh/tests/builtins/local3.0 @@ -0,0 +1,26 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/local3.0 251797 2013-06-15 22:22:03Z jilles $ + +f() { + local "$@" + set -a + x=7 + case $- in + *a*) : ;; + *) echo In-function \$- bad + esac + [ "$x" = 7 ] || echo In-function \$x bad +} +x=1 +case $- in +*a*) echo Initial \$- bad +esac +f x - +case $- in +*a*) echo Intermediate \$- bad +esac +[ "$x" = 1 ] || echo Intermediate \$x bad +f - x +case $- in +*a*) echo Final \$- bad +esac +[ "$x" = 1 ] || echo Final \$x bad diff --git a/bin/1sh/tests/builtins/local4.0 b/bin/1sh/tests/builtins/local4.0 new file mode 100644 index 00000000..3d5ec0d4 --- /dev/null +++ b/bin/1sh/tests/builtins/local4.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/local4.0 254339 2013-08-14 21:59:48Z jilles $ + +f() { + local -- x + x=2 + [ "$x" = 2 ] +} +x=1 +f || exit 3 +[ "$x" = 1 ] || exit 3 +f || exit 3 +[ "$x" = 1 ] || exit 3 diff --git a/bin/1sh/tests/builtins/local5.0 b/bin/1sh/tests/builtins/local5.0 new file mode 100644 index 00000000..b2e1879f --- /dev/null +++ b/bin/1sh/tests/builtins/local5.0 @@ -0,0 +1,15 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/local5.0 293635 2016-01-10 16:31:28Z jilles $ + +f() { + local PATH IFS elem + IFS=: + for elem in ''$PATH''; do + PATH=/var/empty/$elem:$PATH + done + ls -d / >/dev/null +} + +p1=$(command -v ls) +f +p2=$(command -v ls) +[ "$p1" = "$p2" ] diff --git a/bin/1sh/tests/builtins/local6.0 b/bin/1sh/tests/builtins/local6.0 new file mode 100644 index 00000000..1f31be32 --- /dev/null +++ b/bin/1sh/tests/builtins/local6.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/local6.0 294582 2016-01-22 18:10:36Z jilles $ + +f() { + local x + readonly x=2 +} +x=3 +f +x=4 +[ "$x" = 4 ] diff --git a/bin/1sh/tests/builtins/local7.0 b/bin/1sh/tests/builtins/local7.0 new file mode 100644 index 00000000..4ab4db53 --- /dev/null +++ b/bin/1sh/tests/builtins/local7.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/local7.0 294593 2016-01-22 20:10:08Z jilles $ + +f() { + local x + readonly x=2 +} +unset x +f +x=4 +[ "$x" = 4 ] diff --git a/bin/1sh/tests/builtins/locale1.0 b/bin/1sh/tests/builtins/locale1.0 new file mode 100644 index 00000000..1737a7e8 --- /dev/null +++ b/bin/1sh/tests/builtins/locale1.0 @@ -0,0 +1,134 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/locale1.0 218819 2011-02-18 20:37:09Z jilles $ +# Note: this test depends on strerror() using locale. + +failures=0 + +check() { + if ! eval "[ $1 ]"; then + echo "Failed: $1 at $2" + : $((failures += 1)) + fi +} + +unset LANG LC_ALL LC_COLLATE LC_CTYPE LC_MONETARY LC_NUMERIC LC_TIME LC_MESSAGES +unset LANGUAGE + +msgeng="No such file or directory" +msgdut="Bestand of map niet gevonden" + +# Verify C locale error message. +case $(command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +# Various locale variables that should not affect the message. +case $(LC_ALL=C command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_ALL=C LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_ALL=C LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_CTYPE=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +# Verify Dutch message. +case $(export LANG=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(export LC_MESSAGES=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(export LC_ALL=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_ALL=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +# Verify that command assignments do not set the locale persistently. +case $(command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in + *"$msgdut"*"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in + *"$msgdut"*"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_ALL=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in + *"$msgdut"*"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +# Check special builtin; add colon invocation to avoid depending on certain fix. +case $(LC_ALL=nl_NL.ISO8859-1 . /var/empty/foo 2>&1; :) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +# Assignments on special builtins are exported to that builtin; the export +# is not persistent. +case $(LC_ALL=nl_NL.ISO8859-1 . /dev/null; . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(export LC_ALL; LC_ALL=nl_NL.ISO8859-1 . /dev/null; . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +exit $((failures > 0)) diff --git a/bin/1sh/tests/builtins/locale2.0 b/bin/1sh/tests/builtins/locale2.0 new file mode 100644 index 00000000..fd83df10 --- /dev/null +++ b/bin/1sh/tests/builtins/locale2.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/locale2.0 317912 2017-05-07 19:49:46Z jilles $ + +$SH -c 'LC_ALL=C true; kill -INT $$; echo continued' +r=$? +[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = INT ] diff --git a/bin/1sh/tests/builtins/printf1.0 b/bin/1sh/tests/builtins/printf1.0 new file mode 100644 index 00000000..a4ba8d8d --- /dev/null +++ b/bin/1sh/tests/builtins/printf1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/printf1.0 214853 2010-11-05 21:47:58Z jilles $ + +[ "$(printf '%c\0%s%d' x '\' 010 | tr '\0' Z)" = 'xZ\8' ] diff --git a/bin/1sh/tests/builtins/printf2.0 b/bin/1sh/tests/builtins/printf2.0 new file mode 100644 index 00000000..97d5bd61 --- /dev/null +++ b/bin/1sh/tests/builtins/printf2.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/printf2.0 214853 2010-11-05 21:47:58Z jilles $ + +[ "$(printf '%cZ%s%d' x '\' 010)" = 'xZ\8' ] diff --git a/bin/1sh/tests/builtins/printf3.0 b/bin/1sh/tests/builtins/printf3.0 new file mode 100644 index 00000000..327f4da4 --- /dev/null +++ b/bin/1sh/tests/builtins/printf3.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/printf3.0 216606 2010-12-20 23:06:57Z jilles $ + +set -e +v=$(! printf "%d" @wrong 2>/dev/null) +[ "$v" = "0" ] diff --git a/bin/1sh/tests/builtins/printf4.0 b/bin/1sh/tests/builtins/printf4.0 new file mode 100644 index 00000000..c00d53f3 --- /dev/null +++ b/bin/1sh/tests/builtins/printf4.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/printf4.0 216606 2010-12-20 23:06:57Z jilles $ + +set -e +v=$(! printf "%d" 4wrong 2>/dev/null) +[ "$v" = "4" ] diff --git a/bin/1sh/tests/builtins/read1.0 b/bin/1sh/tests/builtins/read1.0 new file mode 100644 index 00000000..e0d75152 --- /dev/null +++ b/bin/1sh/tests/builtins/read1.0 @@ -0,0 +1,26 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/read1.0 190300 2009-03-22 23:00:52Z stefanf $ +set -e + +echo "1 2 3" | { read a; echo "x${a}x"; } +echo "1 2 3" | { read a b; echo "x${a}x${b}x"; } +echo "1 2 3" | { read a b c; echo "x${a}x${b}x${c}x"; } +echo "1 2 3" | { read a b c d; echo "x${a}x${b}x${c}x${d}x"; } + +echo " 1 2 3 " | { read a b c; echo "x${a}x${b}x${c}x"; } +echo " 1 2 3 " | { unset IFS; read a b c; echo "x${a}x${b}x${c}x"; } +echo " 1 2 3 " | { IFS=$(printf ' \t\n') read a b c; echo "x${a}x${b}x${c}x"; } +echo " 1 2 3 " | { IFS= read a b; echo "x${a}x${b}x"; } + +echo " 1,2 3 " | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; } +echo ", 2 ,3" | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; } +echo " 1 ,,3" | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; } +echo " 1 , , 3" | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; } +echo " 1 ,2 3," | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; } +echo " 1 ,2 3,," | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; } + +echo " 1,2 3 " | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; } +echo ", 2 ,3" | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; } +echo " 1 ,,3" | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; } +echo " 1 , , 3" | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; } +echo " 1 ,2 3," | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; } +echo " 1 ,2 3,," | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; } diff --git a/bin/1sh/tests/builtins/read1.0.stdout b/bin/1sh/tests/builtins/read1.0.stdout new file mode 100644 index 00000000..dbcb1af9 --- /dev/null +++ b/bin/1sh/tests/builtins/read1.0.stdout @@ -0,0 +1,20 @@ +x1 2 3x +x1x2 3x +x1x2x3x +x1x2x3xx +x1x2x3x +x1x2x3x +x1x2x3x +x 1 2 3 xx +x1x2x3x +xx2x3x +x1xx3x +x1xx3x +x1x2x3x +x1x2x3,,x +x1x2x3x +xx2x3x +x1xx3x +x1xx3x +x1x2x3x +x1x2x3,,x diff --git a/bin/1sh/tests/builtins/read2.0 b/bin/1sh/tests/builtins/read2.0 new file mode 100644 index 00000000..3747f0bf --- /dev/null +++ b/bin/1sh/tests/builtins/read2.0 @@ -0,0 +1,31 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/read2.0 212187 2010-09-03 21:17:33Z jilles $ + +set -e +{ + echo 1 + echo two + echo three +} | { + read x + [ "$x" = 1 ] + (read x + [ "$x" = two ]) + read x + [ "$x" = three ] +} + +T=`mktemp sh-test.XXXXXX` +trap 'rm -f "$T"' 0 +{ + echo 1 + echo two + echo three +} >$T +{ + read x + [ "$x" = 1 ] + (read x + [ "$x" = two ]) + read x + [ "$x" = three ] +} <$T diff --git a/bin/1sh/tests/builtins/read3.0 b/bin/1sh/tests/builtins/read3.0 new file mode 100644 index 00000000..84dec7cb --- /dev/null +++ b/bin/1sh/tests/builtins/read3.0 @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/read3.0 212330 2010-09-08 18:32:23Z jilles $ + +printf '%s\n' 'a\ b c' | { read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' 'a b\ c' | { read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' 'a\:b:c' | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' 'a:b\:c' | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\ a' | { read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\:a' | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\\' | { read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\\\ a' | { read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\\\ a' | { read -r a b; printf '%s\n' "x${a}x${b}x"; } diff --git a/bin/1sh/tests/builtins/read3.0.stdout b/bin/1sh/tests/builtins/read3.0.stdout new file mode 100644 index 00000000..8ed98ca9 --- /dev/null +++ b/bin/1sh/tests/builtins/read3.0.stdout @@ -0,0 +1,9 @@ +xa bxcx +xaxb cx +xa:bxcx +xaxb:cx +x axx +x:axx +x\xx +x\ axx +x\\\xax diff --git a/bin/1sh/tests/builtins/read4.0 b/bin/1sh/tests/builtins/read4.0 new file mode 100644 index 00000000..f3109c2e --- /dev/null +++ b/bin/1sh/tests/builtins/read4.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/read4.0 212339 2010-09-08 20:35:43Z jilles $ + +printf '%s\n' '\a\ b c' | { read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\a b\ c' | { read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\a\:b:c' | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\a:b\:c' | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\\ a' | { read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\\:a' | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\\\ a' | { read a b; printf '%s\n' "x${a}x${b}x"; } +printf '%s\n' '\\\:a' | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; } diff --git a/bin/1sh/tests/builtins/read4.0.stdout b/bin/1sh/tests/builtins/read4.0.stdout new file mode 100644 index 00000000..a8747a46 --- /dev/null +++ b/bin/1sh/tests/builtins/read4.0.stdout @@ -0,0 +1,8 @@ +xa bxcx +xaxb cx +xa:bxcx +xaxb:cx +x\xax +x\xax +x\ axx +x\:axx diff --git a/bin/1sh/tests/builtins/read5.0 b/bin/1sh/tests/builtins/read5.0 new file mode 100644 index 00000000..2263a656 --- /dev/null +++ b/bin/1sh/tests/builtins/read5.0 @@ -0,0 +1,32 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/read5.0 218821 2011-02-18 20:51:13Z jilles $ + +unset LC_ALL +LC_CTYPE=en_US.ISO8859-1 +export LC_CTYPE + +# Note: the first and last characters are not whitespace. +# Exclude backslash and newline. +bad1=`printf %03o \'\\\\` +bad2=`printf %03o \'' +'` +e= +for i in 0 1 2 3; do + for j in 0 1 2 3 4 5 6 7; do + for k in 0 1 2 3 4 5 6 7; do + case $i$j$k in + 000|$bad1|$bad2) continue ;; + esac + e="$e\\$i$j$k" + done + done +done +e=`printf "$e"` +[ "${#e}" = 253 ] || echo length bad + +r1=`printf '%s\n' "$e" | { read -r x; printf '%s' "$x"; }` +[ "$r1" = "$e" ] || echo "read with -r bad" +r2=`printf '%s\n' "$e" | { read x; printf '%s' "$x"; }` +[ "$r2" = "$e" ] || echo "read without -r bad 1" +IFS= +r3=`printf '%s\n' "$e" | { read x; printf '%s' "$x"; }` +[ "$r3" = "$e" ] || echo "read without -r bad 2" diff --git a/bin/1sh/tests/builtins/read6.0 b/bin/1sh/tests/builtins/read6.0 new file mode 100644 index 00000000..d924ec51 --- /dev/null +++ b/bin/1sh/tests/builtins/read6.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/read6.0 247190 2013-02-23 15:15:41Z jilles $ + +: | read x +r=$? +[ "$r" = 1 ] diff --git a/bin/1sh/tests/builtins/read7.0 b/bin/1sh/tests/builtins/read7.0 new file mode 100644 index 00000000..00da247f --- /dev/null +++ b/bin/1sh/tests/builtins/read7.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/read7.0 250214 2013-05-03 15:28:31Z jilles $ + +{ errmsg=`read x <&- 2>&1 >&3`; } 3>&1 +r=$? +[ "$r" -ge 2 ] && [ "$r" -le 128 ] && [ -n "$errmsg" ] diff --git a/bin/1sh/tests/builtins/read8.0 b/bin/1sh/tests/builtins/read8.0 new file mode 100644 index 00000000..f9426936 --- /dev/null +++ b/bin/1sh/tests/builtins/read8.0 @@ -0,0 +1,17 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/read8.0 286826 2015-08-16 12:57:17Z jilles $ + +read a b c <<\EOF +\ +A\ + \ + \ + \ +B\ + \ + \ +C\ + \ + \ + \ +EOF +[ "$a.$b.$c" = "A.B.C" ] diff --git a/bin/1sh/tests/builtins/read9.0 b/bin/1sh/tests/builtins/read9.0 new file mode 100644 index 00000000..5c5521db --- /dev/null +++ b/bin/1sh/tests/builtins/read9.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/read9.0 287308 2015-08-30 17:24:22Z jilles $ + +empty='' +read a b c <<EOF +\ \ A B\ \ B C\ \ $empty +EOF +read d e <<EOF +D\ $empty +EOF +[ "$a.$b.$c.$d.$e" = " A.B B.C .D ." ] diff --git a/bin/1sh/tests/builtins/return1.0 b/bin/1sh/tests/builtins/return1.0 new file mode 100644 index 00000000..3f804642 --- /dev/null +++ b/bin/1sh/tests/builtins/return1.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/return1.0 149791 2005-09-05 09:42:10Z stefanf $ +f() { + return 0 + exit 1 +} + +f diff --git a/bin/1sh/tests/builtins/return2.1 b/bin/1sh/tests/builtins/return2.1 new file mode 100644 index 00000000..05204bae --- /dev/null +++ b/bin/1sh/tests/builtins/return2.1 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/return2.1 149791 2005-09-05 09:42:10Z stefanf $ +f() { + true && return 1 + return 0 +} + +f diff --git a/bin/1sh/tests/builtins/return3.1 b/bin/1sh/tests/builtins/return3.1 new file mode 100644 index 00000000..f45192bb --- /dev/null +++ b/bin/1sh/tests/builtins/return3.1 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/return3.1 149791 2005-09-05 09:42:10Z stefanf $ +return 1 +exit 0 diff --git a/bin/1sh/tests/builtins/return4.0 b/bin/1sh/tests/builtins/return4.0 new file mode 100644 index 00000000..d54cf8d6 --- /dev/null +++ b/bin/1sh/tests/builtins/return4.0 @@ -0,0 +1,16 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/return4.0 208629 2010-05-28 22:08:34Z jilles $ + +failures= +failure() { + echo "Error at line $1" >&2 + failures=x$failures +} + +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) || exit +trap 'rm -rf $T' 0 +cd $T || exit 3 +echo 'return 42; exit 4' >testscript +. ./testscript +[ "$?" = 42 ] || failure $LINENO + +test -z "$failures" diff --git a/bin/1sh/tests/builtins/return5.0 b/bin/1sh/tests/builtins/return5.0 new file mode 100644 index 00000000..61d29292 --- /dev/null +++ b/bin/1sh/tests/builtins/return5.0 @@ -0,0 +1,17 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/return5.0 211349 2010-08-15 21:06:53Z jilles $ + +if [ "$1" != nested ]; then + f() { + set -- nested + . "$0" + # Allow return to return from the function or the dot script. + return 4 + } + f + exit $(($? ^ 4)) +fi +# To trigger the bug, the following commands must be at the top level, +# with newlines in between. +return 4 +echo bad +exit 1 diff --git a/bin/1sh/tests/builtins/return6.4 b/bin/1sh/tests/builtins/return6.4 new file mode 100644 index 00000000..d70e805b --- /dev/null +++ b/bin/1sh/tests/builtins/return6.4 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/return6.4 212475 2010-09-11 15:07:40Z jilles $ + +while return 4; do exit 3; done diff --git a/bin/1sh/tests/builtins/return7.4 b/bin/1sh/tests/builtins/return7.4 new file mode 100644 index 00000000..7ecfe46e --- /dev/null +++ b/bin/1sh/tests/builtins/return7.4 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/return7.4 212475 2010-09-11 15:07:40Z jilles $ + +f() { + while return 4; do exit 3; done +} +f diff --git a/bin/1sh/tests/builtins/return8.0 b/bin/1sh/tests/builtins/return8.0 new file mode 100644 index 00000000..ebe6a78b --- /dev/null +++ b/bin/1sh/tests/builtins/return8.0 @@ -0,0 +1,13 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/return8.0 255215 2013-09-04 22:10:16Z jilles $ + +if [ "$1" = nested ]; then + return 17 +fi + +f() { + set -- nested + . "$0" + return $(($? ^ 1)) +} +f +exit $(($? ^ 16)) diff --git a/bin/1sh/tests/builtins/set1.0 b/bin/1sh/tests/builtins/set1.0 new file mode 100644 index 00000000..fc39fade --- /dev/null +++ b/bin/1sh/tests/builtins/set1.0 @@ -0,0 +1,32 @@ +# $FreeBSD$ + +set +C +set +f +set -e + +settings=$(set +o) +set -C +set -f +set +e +case $- in +*C*) ;; +*) echo missing C ;; +esac +case $- in +*f*) ;; +*) echo missing C ;; +esac +case $- in +*e*) echo bad e ;; +esac +eval "$settings" +case $- in +*C*) echo bad C ;; +esac +case $- in +*f*) echo bad f ;; +esac +case $- in +*e*) ;; +*) echo missing e ;; +esac diff --git a/bin/1sh/tests/builtins/set2.0 b/bin/1sh/tests/builtins/set2.0 new file mode 100644 index 00000000..9d7b2ed0 --- /dev/null +++ b/bin/1sh/tests/builtins/set2.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/set2.0 223183 2011-06-17 10:21:24Z jilles $ + +! env @badness=1 ${SH} -c 'v=`set`; eval "$v"' 2>&1 | grep @badness diff --git a/bin/1sh/tests/builtins/set3.0 b/bin/1sh/tests/builtins/set3.0 new file mode 100644 index 00000000..5dca36e5 --- /dev/null +++ b/bin/1sh/tests/builtins/set3.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/set3.0 296578 2016-03-09 21:05:21Z jilles $ + +settings1=$(set +o) && set -o nolog && settings2=$(set +o) && +[ "$settings1" != "$settings2" ] diff --git a/bin/1sh/tests/builtins/trap1.0 b/bin/1sh/tests/builtins/trap1.0 new file mode 100644 index 00000000..996fe91e --- /dev/null +++ b/bin/1sh/tests/builtins/trap1.0 @@ -0,0 +1,22 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap1.0 213738 2010-10-12 18:20:38Z obrien $ + +test "$(trap 'echo trapped' EXIT; :)" = trapped || exit 1 + +test "$(trap 'echo trapped' EXIT; /usr/bin/true)" = trapped || exit 1 + +result=$(${SH} -c 'trap "echo trapped" EXIT; /usr/bin/false') +test $? -eq 1 || exit 1 +test "$result" = trapped || exit 1 + +result=$(${SH} -c 'trap "echo trapped" EXIT; exec /usr/bin/false') +test $? -eq 1 || exit 1 +test -z "$result" || exit 1 + +result=0 +trap 'result=$((result+1))' INT +kill -INT $$ +test "$result" -eq 1 || exit 1 +(kill -INT $$) +test "$result" -eq 2 || exit 1 + +exit 0 diff --git a/bin/1sh/tests/builtins/trap10.0 b/bin/1sh/tests/builtins/trap10.0 new file mode 100644 index 00000000..75b3dd3e --- /dev/null +++ b/bin/1sh/tests/builtins/trap10.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap10.0 230212 2012-01-16 11:07:46Z dumbbell $ + +# Check that the return statement will not break the EXIT trap, ie. all +# trap commands are executed before the script exits. + +test "$(trap 'printf trap; echo ped' EXIT; f() { return; }; f)" = trapped || exit 1 diff --git a/bin/1sh/tests/builtins/trap11.0 b/bin/1sh/tests/builtins/trap11.0 new file mode 100644 index 00000000..b1e04746 --- /dev/null +++ b/bin/1sh/tests/builtins/trap11.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap11.0 230212 2012-01-16 11:07:46Z dumbbell $ + +# Check that the return statement will not break the USR1 trap, ie. all +# trap commands are executed before the script resumes. + +result=$(${SH} -c 'trap "printf trap; echo ped" USR1; f() { return $(kill -USR1 $$); }; f') +test $? -eq 0 || exit 1 +test "$result" = trapped || exit 1 diff --git a/bin/1sh/tests/builtins/trap12.0 b/bin/1sh/tests/builtins/trap12.0 new file mode 100644 index 00000000..bb805450 --- /dev/null +++ b/bin/1sh/tests/builtins/trap12.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap12.0 247720 2013-03-03 17:33:59Z jilles $ + +f() { + trap 'return 42' USR1 + kill -USR1 $$ + return 3 +} +f +r=$? +[ "$r" = 42 ] diff --git a/bin/1sh/tests/builtins/trap13.0 b/bin/1sh/tests/builtins/trap13.0 new file mode 100644 index 00000000..bb2b0ce3 --- /dev/null +++ b/bin/1sh/tests/builtins/trap13.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap13.0 257399 2013-10-30 21:36:15Z jilles $ + +{ + trap 'exit 0' INT + ${SH} -c 'kill -INT $PPID' + exit 3 +} & +wait $! diff --git a/bin/1sh/tests/builtins/trap14.0 b/bin/1sh/tests/builtins/trap14.0 new file mode 100644 index 00000000..e2e79649 --- /dev/null +++ b/bin/1sh/tests/builtins/trap14.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap14.0 257399 2013-10-30 21:36:15Z jilles $ + +{ + trap - INT + ${SH} -c 'kill -INT $PPID' & + wait +} & +wait $! +r=$? +[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = INT ] diff --git a/bin/1sh/tests/builtins/trap15.0 b/bin/1sh/tests/builtins/trap15.0 new file mode 100644 index 00000000..97836e17 --- /dev/null +++ b/bin/1sh/tests/builtins/trap15.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap15.0 281718 2015-04-18 23:49:57Z bdrewery $ + +(${SH} -c 'term(){ exit 5;}; trap term TERM; kill -TERM $$') & +wait >/dev/null 2>&1 $! +[ $? -eq 5 ] diff --git a/bin/1sh/tests/builtins/trap16.0 b/bin/1sh/tests/builtins/trap16.0 new file mode 100644 index 00000000..0e3010dc --- /dev/null +++ b/bin/1sh/tests/builtins/trap16.0 @@ -0,0 +1,20 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap16.0 281718 2015-04-18 23:49:57Z bdrewery $ + +traps=$(${SH} -c 'trap "echo bad" 0; trap - 0; trap') +[ -z "$traps" ] || exit 1 +traps=$(${SH} -c 'trap "echo bad" 0; trap "" 0; trap') +expected_traps=$(${SH} -c 'trap "" EXIT; trap') +[ "$traps" = "$expected_traps" ] || exit 2 +traps=$(${SH} -c 'trap "echo bad" 0; trap 0; trap') +[ -z "$traps" ] || exit 3 +traps=$(${SH} -c 'trap "echo bad" 0; trap -- 0; trap') +[ -z "$traps" ] || exit 4 +traps=$(${SH} -c 'trap "echo bad" 0 1 2; trap - 0 1 2; trap') +[ -z "$traps" ] || exit 5 +traps=$(${SH} -c 'trap "echo bad" 0 1 2; trap "" 0 1 2; trap') +expected_traps=$(${SH} -c 'trap "" EXIT HUP INT; trap') +[ "$traps" = "$expected_traps" ] || exit 6 +traps=$(${SH} -c 'trap "echo bad" 0 1 2; trap 0 1 2; trap') +[ -z "$traps" ] || exit 7 +traps=$(${SH} -c 'trap "echo bad" 0 1 2; trap -- 0 1 2; trap') +[ -z "$traps" ] || exit 8 diff --git a/bin/1sh/tests/builtins/trap17.0 b/bin/1sh/tests/builtins/trap17.0 new file mode 100644 index 00000000..4ee48129 --- /dev/null +++ b/bin/1sh/tests/builtins/trap17.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap17.0 297360 2016-03-28 18:58:40Z jilles $ +# This use-after-free bug probably needs non-default settings to show up. + +v1=nothing v2=nothing +trap 'trap "echo bad" USR1 +v1=trap_received +v2=trap_invoked +:' USR1 +kill -USR1 "$$" +[ "$v1.$v2" = trap_received.trap_invoked ] diff --git a/bin/1sh/tests/builtins/trap2.0 b/bin/1sh/tests/builtins/trap2.0 new file mode 100644 index 00000000..eb17a03e --- /dev/null +++ b/bin/1sh/tests/builtins/trap2.0 @@ -0,0 +1,52 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap2.0 194517 2009-06-19 22:15:59Z jilles $ +# This is really a test for outqstr(), which is readily accessible via trap. + +runtest() +{ + teststring=$1 + trap -- "$teststring" USR1 + traps=$(trap) + if [ "$teststring" != "-" ] && [ -z "$traps" ]; then + # One possible reading of POSIX requires the above to return an + # empty string because backquote commands are executed in a + # subshell and subshells shall reset traps. However, an example + # in the normative description of the trap builtin shows the + # same usage as here, it is useful and our /bin/sh allows it. + echo '$(trap) is broken' + exit 1 + fi + trap - USR1 + eval "$traps" + traps2=$(trap) + if [ "$traps" != "$traps2" ]; then + echo "Mismatch for '$teststring'" + exit 1 + fi +} + +runtest 'echo' +runtest 'echo hi' +runtest "'echo' 'hi'" +runtest '"echo" $PATH' +runtest '\echo "$PATH"' +runtest ' 0' +runtest '0 ' +runtest ' 1' +runtest '1 ' +i=1 +while [ $i -le 127 ]; do + c=$(printf \\"$(printf %o $i)") + if [ $i -lt 48 ] || [ $i -gt 57 ]; then + runtest "$c" + fi + runtest " $c$c" + runtest "a$c" + i=$((i+1)) +done +IFS=, +runtest ' ' +runtest ',' +unset IFS +runtest ' ' + +exit 0 diff --git a/bin/1sh/tests/builtins/trap3.0 b/bin/1sh/tests/builtins/trap3.0 new file mode 100644 index 00000000..007cb0e6 --- /dev/null +++ b/bin/1sh/tests/builtins/trap3.0 @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap3.0 218889 2011-02-20 14:18:58Z jilles $ + +{ + trap '' garbage && exit 3 + trap - garbage && exit 3 + trap true garbage && exit 3 + trap '' 99999 && exit 3 + trap - 99999 && exit 3 + trap true 99999 && exit 3 +} 2>/dev/null +exit 0 diff --git a/bin/1sh/tests/builtins/trap4.0 b/bin/1sh/tests/builtins/trap4.0 new file mode 100644 index 00000000..e8f8ffe2 --- /dev/null +++ b/bin/1sh/tests/builtins/trap4.0 @@ -0,0 +1,17 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap4.0 217035 2011-01-05 23:17:29Z jilles $ + +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) +trap 'rm -rf $T' 0 +cd $T || exit 3 +mkfifo fifo1 + +v=$( + exec 3>&1 + : <fifo1 & + { + wait $! + trap 'trap "" PIPE; echo trapped >&3 2>/dev/null' PIPE + echo x 2>/dev/null + } >fifo1 +) +test "$v" = trapped diff --git a/bin/1sh/tests/builtins/trap5.0 b/bin/1sh/tests/builtins/trap5.0 new file mode 100644 index 00000000..1db05759 --- /dev/null +++ b/bin/1sh/tests/builtins/trap5.0 @@ -0,0 +1,19 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap5.0 217461 2011-01-15 21:09:00Z jilles $ + +set -e +trap - USR1 +initial=$(trap) +trap -- -l USR1 +added=$(trap) +[ -n "$added" ] +trap - USR1 +second=$(trap) +[ "$initial" = "$second" ] +eval "$added" +added2=$(trap) +added3=$(trap --) +[ "$added" = "$added2" ] +[ "$added2" = "$added3" ] +trap -- - USR1 +third=$(trap) +[ "$initial" = "$third" ] diff --git a/bin/1sh/tests/builtins/trap6.0 b/bin/1sh/tests/builtins/trap6.0 new file mode 100644 index 00000000..ee4a25c6 --- /dev/null +++ b/bin/1sh/tests/builtins/trap6.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap6.0 217472 2011-01-16 13:56:41Z jilles $ + +v=$( + ${SH} -c 'trap "echo ok; exit" USR1; kill -USR1 $$' & + # Suppress possible message about exit on signal + wait $! >/dev/null 2>&1 +) +r=$(kill -l $?) +[ "$v" = "ok" ] && { [ "$r" = "USR1" ] || [ "$r" = "usr1" ]; } diff --git a/bin/1sh/tests/builtins/trap7.0 b/bin/1sh/tests/builtins/trap7.0 new file mode 100644 index 00000000..84b80006 --- /dev/null +++ b/bin/1sh/tests/builtins/trap7.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap7.0 217996 2011-01-27 23:08:20Z jilles $ + +[ "$(trap 'echo trapped' EXIT)" = trapped ] diff --git a/bin/1sh/tests/builtins/trap8.0 b/bin/1sh/tests/builtins/trap8.0 new file mode 100644 index 00000000..1d4e8afc --- /dev/null +++ b/bin/1sh/tests/builtins/trap8.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap8.0 218889 2011-02-20 14:18:58Z jilles $ + +# I am not sure if POSIX requires the shell to continue processing +# further trap names in the same trap command after an invalid one. + +test -n "$(trap true garbage TERM 2>/dev/null || trap)" || exit 3 +exit 0 diff --git a/bin/1sh/tests/builtins/trap9.0 b/bin/1sh/tests/builtins/trap9.0 new file mode 100644 index 00000000..f53ac49a --- /dev/null +++ b/bin/1sh/tests/builtins/trap9.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/trap9.0 230211 2012-01-16 10:59:44Z dumbbell $ + +test "$(trap 'printf trap; echo ped' EXIT; f() { :; }; f)" = trapped || exit 1 diff --git a/bin/1sh/tests/builtins/type1.0 b/bin/1sh/tests/builtins/type1.0 new file mode 100644 index 00000000..09ae8692 --- /dev/null +++ b/bin/1sh/tests/builtins/type1.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/type1.0 165931 2007-01-11 00:25:20Z stefanf $ +command -v not-here && exit 1 +command -v /not-here && exit 1 +command -V not-here && exit 1 +command -V /not-here && exit 1 +type not-here && exit 1 +type /not-here && exit 1 +exit 0 diff --git a/bin/1sh/tests/builtins/type1.0.stderr b/bin/1sh/tests/builtins/type1.0.stderr new file mode 100644 index 00000000..7853418a --- /dev/null +++ b/bin/1sh/tests/builtins/type1.0.stderr @@ -0,0 +1,4 @@ +not-here: not found +/not-here: No such file or directory +not-here: not found +/not-here: No such file or directory diff --git a/bin/1sh/tests/builtins/type2.0 b/bin/1sh/tests/builtins/type2.0 new file mode 100644 index 00000000..9c8efc77 --- /dev/null +++ b/bin/1sh/tests/builtins/type2.0 @@ -0,0 +1,26 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/type2.0 201344 2009-12-31 17:44:24Z jilles $ + +failures=0 + +check() { + if ! eval "$*"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +check 'PATH=/libexec type ld-elf.so.1 >/dev/null' +check '! PATH=/libexec type ls 2>/dev/null' + +PATH=/libexec:$PATH + +check 'type ld-elf.so.1 >/dev/null' + +PATH=/libexec + +check 'type ld-elf.so.1 >/dev/null' +check '! type ls 2>/dev/null' +check 'PATH=/bin type ls >/dev/null' +check '! PATH=/bin type ld-elf.so.1 2>/dev/null' + +exit $((failures > 0)) diff --git a/bin/1sh/tests/builtins/type3.0 b/bin/1sh/tests/builtins/type3.0 new file mode 100644 index 00000000..777af5e1 --- /dev/null +++ b/bin/1sh/tests/builtins/type3.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/type3.0 255072 2013-08-30 12:09:59Z jilles $ + +[ "$(type type)" = "$(type -- type)" ] diff --git a/bin/1sh/tests/builtins/unalias.0 b/bin/1sh/tests/builtins/unalias.0 new file mode 100644 index 00000000..ad2fe0c7 --- /dev/null +++ b/bin/1sh/tests/builtins/unalias.0 @@ -0,0 +1,21 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/unalias.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e + +alias false=true +false +unalias false +false && exit 1 +unalias false && exit 1 + +alias a1=foo a2=bar +unalias a1 a2 +unalias a1 && exit 1 +unalias a2 && exit 1 +alias a2=bar +unalias a1 a2 && exit 1 + +alias a1=foo a2=bar +unalias -a +unalias a1 && exit 1 +unalias a2 && exit 1 +exit 0 diff --git a/bin/1sh/tests/builtins/var-assign.0 b/bin/1sh/tests/builtins/var-assign.0 new file mode 100644 index 00000000..ff54d487 --- /dev/null +++ b/bin/1sh/tests/builtins/var-assign.0 @@ -0,0 +1,55 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/var-assign.0 327281 2017-12-28 08:22:26Z eadler $ +IFS=, + +SPECIAL="break,\ + :,\ + continue,\ + . /dev/null, + eval, + exec, + export -p, + readonly -p, + set, + shift 0, + times, + trap, + unset foo" + +UTILS="alias,\ + bg,\ + bind,\ + cd,\ + command echo,\ + echo,\ + false,\ + fc -l,\ + fg,\ + getopts a var,\ + hash,\ + jobs,\ + printf a,\ + pwd,\ + read var < /dev/null,\ + test,\ + true,\ + type ls,\ + ulimit,\ + umask,\ + unalias -a,\ + wait" + +set -e + +# For special built-ins variable assignments affect the shell environment. +set -- ${SPECIAL} +for cmd in "$@" +do + ${SH} -c "VAR=1; VAR=0 ${cmd}; exit \${VAR}" >/dev/null 2>&1 +done + +# For other built-ins and utilities they do not. +set -- ${UTILS} +for cmd in "$@" +do + ${SH} -c "VAR=0; VAR=1 ${cmd}; exit \${VAR}" >/dev/null 2>&1 +done diff --git a/bin/1sh/tests/builtins/var-assign2.0 b/bin/1sh/tests/builtins/var-assign2.0 new file mode 100644 index 00000000..e5b01ec7 --- /dev/null +++ b/bin/1sh/tests/builtins/var-assign2.0 @@ -0,0 +1,55 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/var-assign2.0 213738 2010-10-12 18:20:38Z obrien $ +IFS=, + +SPECIAL="break,\ + :,\ + continue,\ + . /dev/null,\ + eval,\ + exec,\ + export -p,\ + readonly -p,\ + set,\ + shift 0,\ + times,\ + trap,\ + unset foo" + +UTILS="alias,\ + bg,\ + bind,\ + cd,\ + command echo,\ + echo,\ + false,\ + fc -l,\ + fg,\ + getopts a var,\ + hash,\ + jobs,\ + printf a,\ + pwd,\ + read var < /dev/null,\ + test,\ + true,\ + type ls,\ + ulimit,\ + umask,\ + unalias -a,\ + wait" + +set -e + +# With 'command', variable assignments do not affect the shell environment. + +set -- ${SPECIAL} +for cmd in "$@" +do + ${SH} -c "VAR=0; VAR=1 command ${cmd}; exit \${VAR}" >/dev/null 2>&1 +done + +set -- ${UTILS} +for cmd in "$@" +do + ${SH} -c "VAR=0; VAR=1 command ${cmd}; exit \${VAR}" >/dev/null 2>&1 +done diff --git a/bin/1sh/tests/builtins/wait1.0 b/bin/1sh/tests/builtins/wait1.0 new file mode 100644 index 00000000..aab8c291 --- /dev/null +++ b/bin/1sh/tests/builtins/wait1.0 @@ -0,0 +1,23 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/wait1.0 208476 2010-05-23 22:10:20Z jilles $ + +failures= +failure() { + echo "Error at line $1" >&2 + failures=x$failures +} + +exit 4 & p4=$! +exit 8 & p8=$! +wait $p4 +[ $? = 4 ] || failure $LINENO +wait $p8 +[ $? = 8 ] || failure $LINENO + +exit 3 & p3=$! +exit 7 & p7=$! +wait $p7 +[ $? = 7 ] || failure $LINENO +wait $p3 +[ $? = 3 ] || failure $LINENO + +test -z "$failures" diff --git a/bin/1sh/tests/builtins/wait10.0 b/bin/1sh/tests/builtins/wait10.0 new file mode 100644 index 00000000..a6d62405 --- /dev/null +++ b/bin/1sh/tests/builtins/wait10.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/wait10.0 251430 2013-06-05 19:40:52Z jilles $ +# Init cannot be a child of the shell. +exit 49 & p49=$! +wait 1 "$p49" +[ "$?" = 49 ] diff --git a/bin/1sh/tests/builtins/wait2.0 b/bin/1sh/tests/builtins/wait2.0 new file mode 100644 index 00000000..f6a4db94 --- /dev/null +++ b/bin/1sh/tests/builtins/wait2.0 @@ -0,0 +1,15 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/wait2.0 208476 2010-05-23 22:10:20Z jilles $ + +failures= +failure() { + echo "Error at line $1" >&2 + failures=x$failures +} + +for i in 1 2 3 4 5 6 7 8 9 10; do + exit $i & +done +wait || failure $LINENO +wait || failure $LINENO + +test -z "$failures" diff --git a/bin/1sh/tests/builtins/wait3.0 b/bin/1sh/tests/builtins/wait3.0 new file mode 100644 index 00000000..fd835b1b --- /dev/null +++ b/bin/1sh/tests/builtins/wait3.0 @@ -0,0 +1,21 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/wait3.0 236771 2012-06-08 22:54:25Z jilles $ + +failures= +failure() { + echo "Error at line $1" >&2 + failures=x$failures +} + +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) +trap 'rm -rf $T' 0 +cd $T || exit 3 +mkfifo fifo1 +for i in 1 2 3 4 5 6 7 8 9 10; do + exit $i 4<fifo1 & +done +exec 3>fifo1 +wait || failure $LINENO +(${SH} -c echo >&3) 2>/dev/null && failure $LINENO +wait || failure $LINENO + +test -z "$failures" diff --git a/bin/1sh/tests/builtins/wait4.0 b/bin/1sh/tests/builtins/wait4.0 new file mode 100644 index 00000000..de4b3ad4 --- /dev/null +++ b/bin/1sh/tests/builtins/wait4.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/wait4.0 247206 2013-02-23 22:50:57Z jilles $ + +T=`mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX` +trap 'rm -rf $T' 0 +cd $T || exit 3 +mkfifo fifo1 +trapped= +trap trapped=1 QUIT +{ kill -QUIT $$; sleep 1; exit 4; } >fifo1 & +wait $! <fifo1 +r=$? +[ "$r" -gt 128 ] && [ -n "$trapped" ] diff --git a/bin/1sh/tests/builtins/wait5.0 b/bin/1sh/tests/builtins/wait5.0 new file mode 100644 index 00000000..6ce6de8f --- /dev/null +++ b/bin/1sh/tests/builtins/wait5.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/wait5.0 247206 2013-02-23 22:50:57Z jilles $ + +T=`mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX` +trap 'rm -rf $T' 0 +cd $T || exit 3 +mkfifo fifo1 +trapped= +trap trapped=1 QUIT +{ kill -QUIT $$; sleep 1; exit 4; } >fifo1 & +wait <fifo1 +r=$? +[ "$r" -gt 128 ] && [ -n "$trapped" ] diff --git a/bin/1sh/tests/builtins/wait6.0 b/bin/1sh/tests/builtins/wait6.0 new file mode 100644 index 00000000..a3730c1f --- /dev/null +++ b/bin/1sh/tests/builtins/wait6.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/wait6.0 248349 2013-03-15 20:29:31Z jilles $ + +wait -- diff --git a/bin/1sh/tests/builtins/wait7.0 b/bin/1sh/tests/builtins/wait7.0 new file mode 100644 index 00000000..b4e26d54 --- /dev/null +++ b/bin/1sh/tests/builtins/wait7.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/wait7.0 248349 2013-03-15 20:29:31Z jilles $ + +: & +wait -- $! diff --git a/bin/1sh/tests/builtins/wait8.0 b/bin/1sh/tests/builtins/wait8.0 new file mode 100644 index 00000000..3c6512fd --- /dev/null +++ b/bin/1sh/tests/builtins/wait8.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/wait8.0 251429 2013-06-05 19:08:22Z jilles $ + +exit 44 & p44=$! +exit 45 & p45=$! +exit 7 & p7=$! +wait "$p44" "$p7" "$p45" +[ "$?" = 45 ] diff --git a/bin/1sh/tests/builtins/wait9.127 b/bin/1sh/tests/builtins/wait9.127 new file mode 100644 index 00000000..30148075 --- /dev/null +++ b/bin/1sh/tests/builtins/wait9.127 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/builtins/wait9.127 251430 2013-06-05 19:40:52Z jilles $ +# Init cannot be a child of the shell. +wait 1 diff --git a/bin/1sh/tests/errors/Makefile b/bin/1sh/tests/errors/Makefile new file mode 100644 index 00000000..a25cf4f8 --- /dev/null +++ b/bin/1sh/tests/errors/Makefile @@ -0,0 +1,35 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/Makefile 319575 2017-06-04 20:52:55Z jilles $ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T} + +.PATH: ${.CURDIR:H} +ATF_TESTS_SH= functional_test + +${PACKAGE}FILES+= assignment-error1.0 +${PACKAGE}FILES+= assignment-error2.0 +${PACKAGE}FILES+= backquote-error1.0 +${PACKAGE}FILES+= backquote-error2.0 +${PACKAGE}FILES+= bad-binary1.126 +${PACKAGE}FILES+= bad-keyword1.0 +${PACKAGE}FILES+= bad-parm-exp1.0 +${PACKAGE}FILES+= bad-parm-exp2.2 bad-parm-exp2.2.stderr +${PACKAGE}FILES+= bad-parm-exp3.2 bad-parm-exp3.2.stderr +${PACKAGE}FILES+= bad-parm-exp4.2 bad-parm-exp4.2.stderr +${PACKAGE}FILES+= bad-parm-exp5.2 bad-parm-exp5.2.stderr +${PACKAGE}FILES+= bad-parm-exp6.2 bad-parm-exp6.2.stderr +${PACKAGE}FILES+= bad-parm-exp7.0 +${PACKAGE}FILES+= bad-parm-exp8.0 +${PACKAGE}FILES+= option-error.0 +${PACKAGE}FILES+= redirection-error.0 +${PACKAGE}FILES+= redirection-error2.2 +${PACKAGE}FILES+= redirection-error3.0 +${PACKAGE}FILES+= redirection-error4.0 +${PACKAGE}FILES+= redirection-error5.0 +${PACKAGE}FILES+= redirection-error6.0 +${PACKAGE}FILES+= redirection-error7.0 +${PACKAGE}FILES+= redirection-error8.0 +${PACKAGE}FILES+= write-error1.0 + +.include <bsd.test.mk> diff --git a/bin/1sh/tests/errors/Makefile.depend b/bin/1sh/tests/errors/Makefile.depend new file mode 100644 index 00000000..8c66cd28 --- /dev/null +++ b/bin/1sh/tests/errors/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/Makefile.depend 296587 2016-03-09 22:46:01Z bdrewery $ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/1sh/tests/errors/assignment-error1.0 b/bin/1sh/tests/errors/assignment-error1.0 new file mode 100644 index 00000000..38c9553f --- /dev/null +++ b/bin/1sh/tests/errors/assignment-error1.0 @@ -0,0 +1,30 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/assignment-error1.0 213738 2010-10-12 18:20:38Z obrien $ +IFS=, + +SPECIAL="break,\ + :,\ + continue,\ + . /dev/null,\ + eval,\ + exec,\ + export -p,\ + readonly -p,\ + set,\ + shift,\ + times,\ + trap,\ + unset foo" + +# If there is no command word, the shell must abort on an assignment error. +${SH} -c "readonly a=0; a=2; exit 0" 2>/dev/null && exit 1 + +# Special built-in utilities must abort on an assignment error. +set -- ${SPECIAL} +for cmd in "$@" +do + ${SH} -c "readonly a=0; a=2 ${cmd}; exit 0" 2>/dev/null && exit 1 +done + +# Other utilities must not abort; we currently still execute them. +${SH} -c 'readonly a=0; a=1 true; exit $a' 2>/dev/null || exit 1 +${SH} -c 'readonly a=0; a=1 command :; exit $a' 2>/dev/null || exit 1 diff --git a/bin/1sh/tests/errors/assignment-error2.0 b/bin/1sh/tests/errors/assignment-error2.0 new file mode 100644 index 00000000..1d39d01b --- /dev/null +++ b/bin/1sh/tests/errors/assignment-error2.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/assignment-error2.0 216870 2011-01-01 13:26:18Z jilles $ + +set -e +HOME=/ +readonly HOME +cd /sbin +{ HOME=/bin cd; } 2>/dev/null || : +[ "$(pwd)" != /bin ] diff --git a/bin/1sh/tests/errors/backquote-error1.0 b/bin/1sh/tests/errors/backquote-error1.0 new file mode 100644 index 00000000..4d39ddb4 --- /dev/null +++ b/bin/1sh/tests/errors/backquote-error1.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/backquote-error1.0 213738 2010-10-12 18:20:38Z obrien $ + +echo 'echo `for` echo ".BAD"CODE.' | ${SH} +m -i 2>&1 | grep -q BADCODE && exit 1 +exit 0 diff --git a/bin/1sh/tests/errors/backquote-error2.0 b/bin/1sh/tests/errors/backquote-error2.0 new file mode 100644 index 00000000..0669617c --- /dev/null +++ b/bin/1sh/tests/errors/backquote-error2.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/backquote-error2.0 213738 2010-10-12 18:20:38Z obrien $ + +${SH} -c 'echo `echo .BA"DCODE.` +echo ".BAD"CODE.' 2>&1 | grep -q BADCODE && exit 1 +echo '`"`' | ${SH} -n 2>/dev/null && exit 1 +echo '`'"'"'`' | ${SH} -n 2>/dev/null && exit 1 +exit 0 diff --git a/bin/1sh/tests/errors/bad-binary1.126 b/bin/1sh/tests/errors/bad-binary1.126 new file mode 100644 index 00000000..24988e0e --- /dev/null +++ b/bin/1sh/tests/errors/bad-binary1.126 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/bad-binary1.126 218320 2011-02-05 12:54:59Z jilles $ +# Checking for binary "scripts" without magic number is permitted but not +# required by POSIX. However, it is preferable to getting errors like +# Syntax error: word unexpected (expecting ")") +# from trying to execute ELF binaries for the wrong architecture. + +T=`mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXXXX"` || exit +trap 'rm -rf "${T}"' 0 +printf '\0echo bad\n' >"$T/testshellproc" +chmod 755 "$T/testshellproc" +PATH=$T:$PATH +testshellproc 2>/dev/null diff --git a/bin/1sh/tests/errors/bad-keyword1.0 b/bin/1sh/tests/errors/bad-keyword1.0 new file mode 100644 index 00000000..3a98871d --- /dev/null +++ b/bin/1sh/tests/errors/bad-keyword1.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/bad-keyword1.0 216398 2010-12-12 21:18:16Z jilles $ + +echo ':; fi' | ${SH} -n 2>/dev/null && exit 1 +exit 0 diff --git a/bin/1sh/tests/errors/bad-parm-exp1.0 b/bin/1sh/tests/errors/bad-parm-exp1.0 new file mode 100644 index 00000000..6fdd9039 --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp1.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/bad-parm-exp1.0 164004 2006-11-05 18:41:23Z stefanf $ +false && { + ${} + ${foo/} + ${foo@bar} +} +: diff --git a/bin/1sh/tests/errors/bad-parm-exp2.2 b/bin/1sh/tests/errors/bad-parm-exp2.2 new file mode 100644 index 00000000..e7655fce --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp2.2 @@ -0,0 +1,2 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/bad-parm-exp2.2 270101 2014-08-17 14:26:12Z jilles $ +eval '${}' diff --git a/bin/1sh/tests/errors/bad-parm-exp2.2.stderr b/bin/1sh/tests/errors/bad-parm-exp2.2.stderr new file mode 100644 index 00000000..51ea69ca --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp2.2.stderr @@ -0,0 +1 @@ +eval: ${}: Bad substitution diff --git a/bin/1sh/tests/errors/bad-parm-exp3.2 b/bin/1sh/tests/errors/bad-parm-exp3.2 new file mode 100644 index 00000000..7acda667 --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp3.2 @@ -0,0 +1,2 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/bad-parm-exp3.2 270101 2014-08-17 14:26:12Z jilles $ +eval '${foo/}' diff --git a/bin/1sh/tests/errors/bad-parm-exp3.2.stderr b/bin/1sh/tests/errors/bad-parm-exp3.2.stderr new file mode 100644 index 00000000..70473f9a --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp3.2.stderr @@ -0,0 +1 @@ +eval: ${foo/}: Bad substitution diff --git a/bin/1sh/tests/errors/bad-parm-exp4.2 b/bin/1sh/tests/errors/bad-parm-exp4.2 new file mode 100644 index 00000000..d9b0977f --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp4.2 @@ -0,0 +1,2 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/bad-parm-exp4.2 270101 2014-08-17 14:26:12Z jilles $ +eval '${foo:@abc}' diff --git a/bin/1sh/tests/errors/bad-parm-exp4.2.stderr b/bin/1sh/tests/errors/bad-parm-exp4.2.stderr new file mode 100644 index 00000000..3363f517 --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp4.2.stderr @@ -0,0 +1 @@ +eval: ${foo:@...}: Bad substitution diff --git a/bin/1sh/tests/errors/bad-parm-exp5.2 b/bin/1sh/tests/errors/bad-parm-exp5.2 new file mode 100644 index 00000000..6309c902 --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp5.2 @@ -0,0 +1,2 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/bad-parm-exp5.2 270101 2014-08-17 14:26:12Z jilles $ +eval '${/}' diff --git a/bin/1sh/tests/errors/bad-parm-exp5.2.stderr b/bin/1sh/tests/errors/bad-parm-exp5.2.stderr new file mode 100644 index 00000000..13763f8e --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp5.2.stderr @@ -0,0 +1 @@ +eval: ${/}: Bad substitution diff --git a/bin/1sh/tests/errors/bad-parm-exp6.2 b/bin/1sh/tests/errors/bad-parm-exp6.2 new file mode 100644 index 00000000..c58ea713 --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp6.2 @@ -0,0 +1,2 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/bad-parm-exp6.2 270101 2014-08-17 14:26:12Z jilles $ +eval '${#foo^}' diff --git a/bin/1sh/tests/errors/bad-parm-exp6.2.stderr b/bin/1sh/tests/errors/bad-parm-exp6.2.stderr new file mode 100644 index 00000000..cc56f65b --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp6.2.stderr @@ -0,0 +1 @@ +eval: ${foo...}: Bad substitution diff --git a/bin/1sh/tests/errors/bad-parm-exp7.0 b/bin/1sh/tests/errors/bad-parm-exp7.0 new file mode 100644 index 00000000..11469e23 --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp7.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/bad-parm-exp7.0 287081 2015-08-23 20:44:53Z jilles $ + +v=1 +eval ": $(printf '${v-${\372}}')" diff --git a/bin/1sh/tests/errors/bad-parm-exp8.0 b/bin/1sh/tests/errors/bad-parm-exp8.0 new file mode 100644 index 00000000..3a545eea --- /dev/null +++ b/bin/1sh/tests/errors/bad-parm-exp8.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/bad-parm-exp8.0 287081 2015-08-23 20:44:53Z jilles $ + +v=1 +eval ": $(printf '${v-${w\372}}')" diff --git a/bin/1sh/tests/errors/option-error.0 b/bin/1sh/tests/errors/option-error.0 new file mode 100644 index 00000000..d28d709f --- /dev/null +++ b/bin/1sh/tests/errors/option-error.0 @@ -0,0 +1,46 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/option-error.0 213738 2010-10-12 18:20:38Z obrien $ +IFS=, + +SPECIAL="break abc,\ + continue abc,\ + ., + exit abc, + export -x, + readonly -x, + return abc, + set -z, + shift abc, + trap -y, + unset -y" + +UTILS="alias -y,\ + cat -z,\ + cd abc def,\ + command break abc,\ + expr 1 +,\ + fc -z,\ + getopts,\ + hash -z,\ + jobs -z,\ + printf,\ + pwd abc,\ + read,\ + test abc =,\ + ulimit -z,\ + umask -z,\ + unalias -z,\ + wait abc" + +# Special built-in utilities must abort on an option or operand error. +set -- ${SPECIAL} +for cmd in "$@" +do + ${SH} -c "${cmd}; exit 0" 2>/dev/null && exit 1 +done + +# Other utilities must not abort. +set -- ${UTILS} +for cmd in "$@" +do + ${SH} -c "${cmd}; exit 0" 2>/dev/null || exit 1 +done diff --git a/bin/1sh/tests/errors/redirection-error.0 b/bin/1sh/tests/errors/redirection-error.0 new file mode 100644 index 00000000..221bbb07 --- /dev/null +++ b/bin/1sh/tests/errors/redirection-error.0 @@ -0,0 +1,53 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/redirection-error.0 213738 2010-10-12 18:20:38Z obrien $ +IFS=, + +SPECIAL="break,\ + :,\ + continue,\ + . /dev/null, + eval, + exec, + export -p, + readonly -p, + set, + shift, + times, + trap, + unset foo" + +UTILS="alias,\ + bg,\ + bind,\ + cd,\ + command echo,\ + echo,\ + false,\ + fc -l,\ + fg,\ + getopts a -a,\ + hash,\ + jobs,\ + printf a,\ + pwd,\ + read var < /dev/null,\ + test,\ + true,\ + type ls,\ + ulimit,\ + umask,\ + unalias -a,\ + wait" + +# Special built-in utilities must abort on a redirection error. +set -- ${SPECIAL} +for cmd in "$@" +do + ${SH} -c "${cmd} > /; exit 0" 2>/dev/null && exit 1 +done + +# Other utilities must not abort. +set -- ${UTILS} +for cmd in "$@" +do + ${SH} -c "${cmd} > /; exit 0" 2>/dev/null || exit 1 +done diff --git a/bin/1sh/tests/errors/redirection-error2.2 b/bin/1sh/tests/errors/redirection-error2.2 new file mode 100644 index 00000000..a81406f4 --- /dev/null +++ b/bin/1sh/tests/errors/redirection-error2.2 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/redirection-error2.2 213738 2010-10-12 18:20:38Z obrien $ + +# sh should fail gracefully on this bad redirect +${SH} -c 'echo 1 >&$a' 2>/dev/null diff --git a/bin/1sh/tests/errors/redirection-error3.0 b/bin/1sh/tests/errors/redirection-error3.0 new file mode 100644 index 00000000..586beed8 --- /dev/null +++ b/bin/1sh/tests/errors/redirection-error3.0 @@ -0,0 +1,54 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/redirection-error3.0 213738 2010-10-12 18:20:38Z obrien $ +IFS=, + +SPECIAL="break,\ + :,\ + continue,\ + . /dev/null,\ + eval,\ + exec,\ + export -p,\ + readonly -p,\ + set,\ + shift,\ + times,\ + trap,\ + unset foo" + +UTILS="alias,\ + bg,\ + bind,\ + cd,\ + command echo,\ + echo,\ + false,\ + fc -l,\ + fg,\ + getopts a -a,\ + hash,\ + jobs,\ + printf a,\ + pwd,\ + read var < /dev/null,\ + test,\ + true,\ + type ls,\ + ulimit,\ + umask,\ + unalias -a,\ + wait" + +# When used with 'command', neither special built-in utilities nor other +# utilities must abort on a redirection error. + +set -- ${SPECIAL} +for cmd in "$@" +do + ${SH} -c "command ${cmd} > /; exit 0" 2>/dev/null || exit 1 +done + +set -- ${UTILS} +for cmd in "$@" +do + ${SH} -c "command ${cmd} > /; exit 0" 2>/dev/null || exit 1 +done diff --git a/bin/1sh/tests/errors/redirection-error4.0 b/bin/1sh/tests/errors/redirection-error4.0 new file mode 100644 index 00000000..ed8ae732 --- /dev/null +++ b/bin/1sh/tests/errors/redirection-error4.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/redirection-error4.0 205138 2010-03-13 22:53:17Z jilles $ +# A redirection error should not abort the shell if there is no command word. +exec 2>/dev/null +</var/empty/x +</var/empty/x y=2 +y=2 </var/empty/x +exit 0 diff --git a/bin/1sh/tests/errors/redirection-error5.0 b/bin/1sh/tests/errors/redirection-error5.0 new file mode 100644 index 00000000..72bc9867 --- /dev/null +++ b/bin/1sh/tests/errors/redirection-error5.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/redirection-error5.0 205153 2010-03-14 13:51:12Z jilles $ +# A redirection error on a subshell should not abort the shell. +exec 2>/dev/null +( echo bad ) </var/empty/x +exit 0 diff --git a/bin/1sh/tests/errors/redirection-error6.0 b/bin/1sh/tests/errors/redirection-error6.0 new file mode 100644 index 00000000..ccfc925f --- /dev/null +++ b/bin/1sh/tests/errors/redirection-error6.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/redirection-error6.0 205154 2010-03-14 14:24:35Z jilles $ +# A redirection error on a compound command should not abort the shell. +exec 2>/dev/null +{ echo bad; } </var/empty/x +if :; then echo bad; fi </var/empty/x +for i in 1; do echo bad; done </var/empty/x +i=0 +while [ $i = 0 ]; do echo bad; i=1; done </var/empty/x +i=0 +until [ $i != 0 ]; do echo bad; i=1; done </var/empty/x +case i in *) echo bad ;; esac </var/empty/x +exit 0 diff --git a/bin/1sh/tests/errors/redirection-error7.0 b/bin/1sh/tests/errors/redirection-error7.0 new file mode 100644 index 00000000..3ce8777b --- /dev/null +++ b/bin/1sh/tests/errors/redirection-error7.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/redirection-error7.0 216851 2010-12-31 18:20:17Z jilles $ + +! dummy=$( + exec 3>&1 >&2 2>&3 + ulimit -n 9 + exec 9<. +) && [ -n "$dummy" ] diff --git a/bin/1sh/tests/errors/redirection-error8.0 b/bin/1sh/tests/errors/redirection-error8.0 new file mode 100644 index 00000000..b6c2f410 --- /dev/null +++ b/bin/1sh/tests/errors/redirection-error8.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/redirection-error8.0 319575 2017-06-04 20:52:55Z jilles $ + +$SH -c '{ { :; } </var/empty/x; } 2>/dev/null || kill -INT $$; echo continued' +r=$? +[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = INT ] diff --git a/bin/1sh/tests/errors/write-error1.0 b/bin/1sh/tests/errors/write-error1.0 new file mode 100644 index 00000000..6e4f363f --- /dev/null +++ b/bin/1sh/tests/errors/write-error1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/errors/write-error1.0 244924 2013-01-01 12:48:24Z jilles $ + +! echo >&- 2>/dev/null diff --git a/bin/1sh/tests/execution/Makefile b/bin/1sh/tests/execution/Makefile new file mode 100644 index 00000000..966bc93c --- /dev/null +++ b/bin/1sh/tests/execution/Makefile @@ -0,0 +1,64 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/Makefile 345487 2019-03-24 22:10:26Z jilles $ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T} + +.PATH: ${.CURDIR:H} +ATF_TESTS_SH= functional_test + +${PACKAGE}FILES+= bg1.0 +${PACKAGE}FILES+= bg2.0 +${PACKAGE}FILES+= bg3.0 +${PACKAGE}FILES+= bg4.0 +${PACKAGE}FILES+= bg5.0 +${PACKAGE}FILES+= bg6.0 bg6.0.stdout +${PACKAGE}FILES+= bg7.0 +${PACKAGE}FILES+= bg8.0 +${PACKAGE}FILES+= bg9.0 +${PACKAGE}FILES+= bg10.0 bg10.0.stdout +${PACKAGE}FILES+= fork1.0 +${PACKAGE}FILES+= fork2.0 +${PACKAGE}FILES+= fork3.0 +${PACKAGE}FILES+= func1.0 +${PACKAGE}FILES+= func2.0 +${PACKAGE}FILES+= func3.0 +${PACKAGE}FILES+= hash1.0 +${PACKAGE}FILES+= int-cmd1.0 +${PACKAGE}FILES+= killed1.0 +${PACKAGE}FILES+= killed2.0 +${PACKAGE}FILES+= not1.0 +${PACKAGE}FILES+= not2.0 +${PACKAGE}FILES+= path1.0 +${PACKAGE}FILES+= pipefail1.0 +${PACKAGE}FILES+= pipefail2.42 +${PACKAGE}FILES+= pipefail3.42 +${PACKAGE}FILES+= pipefail4.42 +${PACKAGE}FILES+= pipefail5.42 +${PACKAGE}FILES+= pipefail6.42 +${PACKAGE}FILES+= pipefail7.0 +${PACKAGE}FILES+= redir1.0 +${PACKAGE}FILES+= redir2.0 +${PACKAGE}FILES+= redir3.0 +${PACKAGE}FILES+= redir4.0 +${PACKAGE}FILES+= redir5.0 +${PACKAGE}FILES+= redir6.0 +${PACKAGE}FILES+= redir7.0 +${PACKAGE}FILES+= set-C1.0 +${PACKAGE}FILES+= set-n1.0 +${PACKAGE}FILES+= set-n2.0 +${PACKAGE}FILES+= set-n3.0 +${PACKAGE}FILES+= set-n4.0 +${PACKAGE}FILES+= set-x1.0 +${PACKAGE}FILES+= set-x2.0 +${PACKAGE}FILES+= set-x3.0 +${PACKAGE}FILES+= set-x4.0 +${PACKAGE}FILES+= shellproc1.0 +${PACKAGE}FILES+= subshell1.0 subshell1.0.stdout +${PACKAGE}FILES+= subshell2.0 +${PACKAGE}FILES+= subshell3.0 +${PACKAGE}FILES+= subshell4.0 +${PACKAGE}FILES+= unknown1.0 +${PACKAGE}FILES+= var-assign1.0 + +.include <bsd.test.mk> diff --git a/bin/1sh/tests/execution/Makefile.depend b/bin/1sh/tests/execution/Makefile.depend new file mode 100644 index 00000000..746c07a0 --- /dev/null +++ b/bin/1sh/tests/execution/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/Makefile.depend 296587 2016-03-09 22:46:01Z bdrewery $ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/1sh/tests/execution/bg1.0 b/bin/1sh/tests/execution/bg1.0 new file mode 100644 index 00000000..b7277fa8 --- /dev/null +++ b/bin/1sh/tests/execution/bg1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/bg1.0 221027 2011-04-25 20:54:12Z jilles $ + +: `false` & diff --git a/bin/1sh/tests/execution/bg10.0 b/bin/1sh/tests/execution/bg10.0 new file mode 100644 index 00000000..9eb15ed2 --- /dev/null +++ b/bin/1sh/tests/execution/bg10.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/bg10.0 258535 2013-11-24 23:12:13Z jilles $ +# The redirection overrides the </dev/null implicit in a background command. + +echo yes | ${SH} -c '{ cat & wait; } <&0' diff --git a/bin/1sh/tests/execution/bg10.0.stdout b/bin/1sh/tests/execution/bg10.0.stdout new file mode 100644 index 00000000..7cfab5b0 --- /dev/null +++ b/bin/1sh/tests/execution/bg10.0.stdout @@ -0,0 +1 @@ +yes diff --git a/bin/1sh/tests/execution/bg2.0 b/bin/1sh/tests/execution/bg2.0 new file mode 100644 index 00000000..d0593687 --- /dev/null +++ b/bin/1sh/tests/execution/bg2.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/bg2.0 221027 2011-04-25 20:54:12Z jilles $ + +f() { return 42; } +f +: | : & diff --git a/bin/1sh/tests/execution/bg3.0 b/bin/1sh/tests/execution/bg3.0 new file mode 100644 index 00000000..e218ecd0 --- /dev/null +++ b/bin/1sh/tests/execution/bg3.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/bg3.0 221027 2011-04-25 20:54:12Z jilles $ + +f() { return 42; } +f +(:) & diff --git a/bin/1sh/tests/execution/bg4.0 b/bin/1sh/tests/execution/bg4.0 new file mode 100644 index 00000000..ddb5891f --- /dev/null +++ b/bin/1sh/tests/execution/bg4.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/bg4.0 223283 2011-06-19 00:00:36Z jilles $ + +x='' +: ${x:=1} & +wait +exit ${x:-0} diff --git a/bin/1sh/tests/execution/bg5.0 b/bin/1sh/tests/execution/bg5.0 new file mode 100644 index 00000000..cb9883ad --- /dev/null +++ b/bin/1sh/tests/execution/bg5.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/bg5.0 258489 2013-11-22 21:50:13Z jilles $ +# A background command has an implicit </dev/null redirection. + +echo bad | ${SH} -c '{ cat & wait; }' diff --git a/bin/1sh/tests/execution/bg6.0 b/bin/1sh/tests/execution/bg6.0 new file mode 100644 index 00000000..2883d2d4 --- /dev/null +++ b/bin/1sh/tests/execution/bg6.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/bg6.0 258489 2013-11-22 21:50:13Z jilles $ +# The redirection overrides the </dev/null implicit in a background command. + +echo yes | ${SH} -c '{ cat & wait; } </dev/stdin' diff --git a/bin/1sh/tests/execution/bg6.0.stdout b/bin/1sh/tests/execution/bg6.0.stdout new file mode 100644 index 00000000..7cfab5b0 --- /dev/null +++ b/bin/1sh/tests/execution/bg6.0.stdout @@ -0,0 +1 @@ +yes diff --git a/bin/1sh/tests/execution/bg7.0 b/bin/1sh/tests/execution/bg7.0 new file mode 100644 index 00000000..f15c3c21 --- /dev/null +++ b/bin/1sh/tests/execution/bg7.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/bg7.0 258533 2013-11-24 22:45:49Z jilles $ +# The redirection does not apply to the background command, and therefore +# does not override the implicit </dev/null. + +echo bad | ${SH} -c '</dev/null; { cat & wait; }' diff --git a/bin/1sh/tests/execution/bg8.0 b/bin/1sh/tests/execution/bg8.0 new file mode 100644 index 00000000..43c1e3f0 --- /dev/null +++ b/bin/1sh/tests/execution/bg8.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/bg8.0 258533 2013-11-24 22:45:49Z jilles $ +# The redirection does not apply to the background command, and therefore +# does not override the implicit </dev/null. + +echo bad | ${SH} -c 'command eval \) </dev/null 2>/dev/null; { cat & wait; }' diff --git a/bin/1sh/tests/execution/bg9.0 b/bin/1sh/tests/execution/bg9.0 new file mode 100644 index 00000000..4fee9946 --- /dev/null +++ b/bin/1sh/tests/execution/bg9.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/bg9.0 258533 2013-11-24 22:45:49Z jilles $ +# The redirection does not apply to the background command, and therefore +# does not override the implicit </dev/null. + +echo bad | ${SH} -c 'command eval eval \\\) \</dev/null 2>/dev/null; { cat & wait; }' diff --git a/bin/1sh/tests/execution/fork1.0 b/bin/1sh/tests/execution/fork1.0 new file mode 100644 index 00000000..edd8f6ad --- /dev/null +++ b/bin/1sh/tests/execution/fork1.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/fork1.0 218850 2011-02-19 13:22:18Z jilles $ + +shname=${SH%% *} +shname=${shname##*/} + +result=$(${SH} -c 'ps -p $$ -o comm=') +test "$result" = "ps" || exit 1 + +result=$(${SH} -c 'ps -p $$ -o comm=; :') +test "$result" = "$shname" || exit 1 diff --git a/bin/1sh/tests/execution/fork2.0 b/bin/1sh/tests/execution/fork2.0 new file mode 100644 index 00000000..f85f7e0a --- /dev/null +++ b/bin/1sh/tests/execution/fork2.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/fork2.0 213738 2010-10-12 18:20:38Z obrien $ + +result=$(${SH} -c '(/bin/sleep 1)& sleep 0.1; ps -p $! -o comm=; kill $!') +test "$result" = sleep || exit 1 + +result=$(${SH} -c '{ trap "echo trapped" EXIT; (/usr/bin/true); } & wait') +test "$result" = trapped || exit 1 + +exit 0 diff --git a/bin/1sh/tests/execution/fork3.0 b/bin/1sh/tests/execution/fork3.0 new file mode 100644 index 00000000..101a16c6 --- /dev/null +++ b/bin/1sh/tests/execution/fork3.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/fork3.0 220978 2011-04-23 22:28:56Z jilles $ + +result=$(${SH} -c 'f() { ps -p $$ -o comm=; }; f') +test "$result" = "ps" diff --git a/bin/1sh/tests/execution/func1.0 b/bin/1sh/tests/execution/func1.0 new file mode 100644 index 00000000..25b4781f --- /dev/null +++ b/bin/1sh/tests/execution/func1.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/func1.0 213738 2010-10-12 18:20:38Z obrien $ + +MALLOC_OPTIONS=J ${SH} -c 'g() { g() { :; }; :; }; g' && +MALLOC_OPTIONS=J ${SH} -c 'g() { unset -f g; :; }; g' diff --git a/bin/1sh/tests/execution/func2.0 b/bin/1sh/tests/execution/func2.0 new file mode 100644 index 00000000..a1f3e0cc --- /dev/null +++ b/bin/1sh/tests/execution/func2.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/func2.0 211399 2010-08-16 17:18:08Z jilles $ +# The empty pairs of braces here are to test that this does not cause a crash. + +f() { } +f +hash -v f >/dev/null +f() { { }; } +f +hash -v f >/dev/null +f() { { } } +f +hash -v f >/dev/null diff --git a/bin/1sh/tests/execution/func3.0 b/bin/1sh/tests/execution/func3.0 new file mode 100644 index 00000000..a92c12f6 --- /dev/null +++ b/bin/1sh/tests/execution/func3.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/func3.0 216398 2010-12-12 21:18:16Z jilles $ + +# This may fail when parsing or when defining the function, or the definition +# may silently do nothing. In no event may the function be executed. + +${SH} -c 'unset() { echo overriding function executed, bad; }; v=1; unset v; exit "${v-0}"' 2>/dev/null +: diff --git a/bin/1sh/tests/execution/hash1.0 b/bin/1sh/tests/execution/hash1.0 new file mode 100644 index 00000000..e47b86c8 --- /dev/null +++ b/bin/1sh/tests/execution/hash1.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/hash1.0 218323 2011-02-05 14:01:46Z jilles $ + +T=`mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXXXX"` || exit +trap 'rm -rf "${T}"' 0 +PATH=$T:$PATH +ls -ld . >/dev/null +cat <<EOF >"$T/ls" +: +EOF +chmod 755 "$T/ls" +PATH=$PATH +ls -ld . diff --git a/bin/1sh/tests/execution/int-cmd1.0 b/bin/1sh/tests/execution/int-cmd1.0 new file mode 100644 index 00000000..6c415646 --- /dev/null +++ b/bin/1sh/tests/execution/int-cmd1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/int-cmd1.0 253271 2013-07-12 15:29:41Z jilles $ + +! echo echo bad | $SH -ic 'fi' 2>/dev/null diff --git a/bin/1sh/tests/execution/killed1.0 b/bin/1sh/tests/execution/killed1.0 new file mode 100644 index 00000000..c5b36ab6 --- /dev/null +++ b/bin/1sh/tests/execution/killed1.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/killed1.0 217557 2011-01-18 21:18:31Z jilles $ +# Sometimes the "Killed" message is not flushed soon enough and it +# is redirected along with the output of a builtin. +# Do not change the semicolon to a newline as it would hide the bug. + +exec 3>&1 +exec >/dev/null 2>&1 +${SH} -c 'kill -9 $$'; : >&3 2>&3 diff --git a/bin/1sh/tests/execution/killed2.0 b/bin/1sh/tests/execution/killed2.0 new file mode 100644 index 00000000..6d7a40e6 --- /dev/null +++ b/bin/1sh/tests/execution/killed2.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/killed2.0 218105 2011-01-30 22:57:52Z jilles $ +# Most shells print a message when a foreground job is killed by a signal. +# POSIX allows this, provided the message is sent to stderr, not stdout. +# Some trickery is needed to capture the message as redirecting stderr of +# the command itself does not affect it. The colon command ensures that +# the subshell forks for ${SH}. + +exec 3>&1 +r=`(${SH} -c 'kill $$'; :) 2>&1 >&3` +[ -n "$r" ] diff --git a/bin/1sh/tests/execution/not1.0 b/bin/1sh/tests/execution/not1.0 new file mode 100644 index 00000000..8e329c9b --- /dev/null +++ b/bin/1sh/tests/execution/not1.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/not1.0 249407 2013-04-12 15:19:35Z jilles $ + +f() { ! return $1; } +f 0 && ! f 1 diff --git a/bin/1sh/tests/execution/not2.0 b/bin/1sh/tests/execution/not2.0 new file mode 100644 index 00000000..0930f243 --- /dev/null +++ b/bin/1sh/tests/execution/not2.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/not2.0 249407 2013-04-12 15:19:35Z jilles $ + +while :; do + ! break + exit 3 +done diff --git a/bin/1sh/tests/execution/path1.0 b/bin/1sh/tests/execution/path1.0 new file mode 100644 index 00000000..63ab97a1 --- /dev/null +++ b/bin/1sh/tests/execution/path1.0 @@ -0,0 +1,15 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/path1.0 217206 2011-01-09 21:07:30Z jilles $ +# Some builtins should not be overridable via PATH. + +set -e +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) +trap 'rm -rf ${T}' 0 +echo '#!/bin/sh +echo bad' >"$T/cd" +chmod 755 "$T/cd" +cd /bin +oPATH=$PATH +PATH=$T:$PATH:%builtin +cd / +PATH=$oPATH +[ "$(pwd)" = / ] diff --git a/bin/1sh/tests/execution/pipefail1.0 b/bin/1sh/tests/execution/pipefail1.0 new file mode 100644 index 00000000..4835d8c0 --- /dev/null +++ b/bin/1sh/tests/execution/pipefail1.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/pipefail1.0 345487 2019-03-24 22:10:26Z jilles $ + +set -o pipefail +: && : | : && : | : | : && : | : | : | : diff --git a/bin/1sh/tests/execution/pipefail2.42 b/bin/1sh/tests/execution/pipefail2.42 new file mode 100644 index 00000000..1d9d2759 --- /dev/null +++ b/bin/1sh/tests/execution/pipefail2.42 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/pipefail2.42 345487 2019-03-24 22:10:26Z jilles $ + +set -o pipefail +(exit 42) | : diff --git a/bin/1sh/tests/execution/pipefail3.42 b/bin/1sh/tests/execution/pipefail3.42 new file mode 100644 index 00000000..17f49894 --- /dev/null +++ b/bin/1sh/tests/execution/pipefail3.42 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/pipefail3.42 345487 2019-03-24 22:10:26Z jilles $ + +set -o pipefail +: | (exit 42) diff --git a/bin/1sh/tests/execution/pipefail4.42 b/bin/1sh/tests/execution/pipefail4.42 new file mode 100644 index 00000000..a7c9b4ad --- /dev/null +++ b/bin/1sh/tests/execution/pipefail4.42 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/pipefail4.42 345487 2019-03-24 22:10:26Z jilles $ + +set -o pipefail +(exit 43) | (exit 42) diff --git a/bin/1sh/tests/execution/pipefail5.42 b/bin/1sh/tests/execution/pipefail5.42 new file mode 100644 index 00000000..7882d8a5 --- /dev/null +++ b/bin/1sh/tests/execution/pipefail5.42 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/pipefail5.42 345487 2019-03-24 22:10:26Z jilles $ + +set -o pipefail +(exit 42) | : & +wait %+ diff --git a/bin/1sh/tests/execution/pipefail6.42 b/bin/1sh/tests/execution/pipefail6.42 new file mode 100644 index 00000000..70d84d72 --- /dev/null +++ b/bin/1sh/tests/execution/pipefail6.42 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/pipefail6.42 345487 2019-03-24 22:10:26Z jilles $ + +set -o pipefail +(exit 42) | : & +set +o pipefail +wait %+ diff --git a/bin/1sh/tests/execution/pipefail7.0 b/bin/1sh/tests/execution/pipefail7.0 new file mode 100644 index 00000000..1a23e90d --- /dev/null +++ b/bin/1sh/tests/execution/pipefail7.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/pipefail7.0 345487 2019-03-24 22:10:26Z jilles $ + +(exit 42) | : & +set -o pipefail +wait %+ diff --git a/bin/1sh/tests/execution/redir1.0 b/bin/1sh/tests/execution/redir1.0 new file mode 100644 index 00000000..797e42b2 --- /dev/null +++ b/bin/1sh/tests/execution/redir1.0 @@ -0,0 +1,27 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/redir1.0 211408 2010-08-16 22:23:19Z jilles $ +trap ': $((brokenpipe+=1))' PIPE + +P=${TMPDIR:-/tmp} +cd $P +T=$(mktemp -d sh-test.XXXXXX) +cd $T + +brokenpipe=0 +mkfifo fifo1 fifo2 +read dummy >fifo2 <fifo1 & +{ + exec 4>fifo2 +} 3<fifo2 # Formerly, sh would keep fd 3 and a duplicate of it open. +echo dummy >fifo1 +if [ $brokenpipe -ne 0 ]; then + rc=3 +fi +wait +echo dummy >&4 2>/dev/null +if [ $brokenpipe -eq 1 ]; then + : ${rc:=0} +fi + +rm fifo1 fifo2 +rmdir ${P}/${T} +exit ${rc:-3} diff --git a/bin/1sh/tests/execution/redir2.0 b/bin/1sh/tests/execution/redir2.0 new file mode 100644 index 00000000..1e2a9c16 --- /dev/null +++ b/bin/1sh/tests/execution/redir2.0 @@ -0,0 +1,29 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/redir2.0 213738 2010-10-12 18:20:38Z obrien $ +trap ': $((brokenpipe+=1))' PIPE + +P=${TMPDIR:-/tmp} +cd $P +T=$(mktemp -d sh-test.XXXXXX) +cd $T + +brokenpipe=0 +mkfifo fifo1 fifo2 +{ + { + exec ${SH} -c 'exec <fifo1; read dummy' + } 7<&- # fifo2 should be kept open, but not passed to programs + true +} 7<fifo2 & + +exec 4>fifo2 +exec 3>fifo1 +echo dummy >&4 2>/dev/null +if [ $brokenpipe -eq 1 ]; then + : ${rc:=0} +fi +echo dummy >&3 +wait + +rm fifo1 fifo2 +rmdir ${P}/${T} +exit ${rc:-3} diff --git a/bin/1sh/tests/execution/redir3.0 b/bin/1sh/tests/execution/redir3.0 new file mode 100644 index 00000000..ddd9e1c3 --- /dev/null +++ b/bin/1sh/tests/execution/redir3.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/redir3.0 214289 2010-10-24 19:56:34Z jilles $ + +3>&- 3>&- diff --git a/bin/1sh/tests/execution/redir4.0 b/bin/1sh/tests/execution/redir4.0 new file mode 100644 index 00000000..02cd9a8c --- /dev/null +++ b/bin/1sh/tests/execution/redir4.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/redir4.0 214290 2010-10-24 20:09:49Z jilles $ + +{ echo bad 0>&3; } 2>/dev/null 3>/dev/null 3>&- +exit 0 diff --git a/bin/1sh/tests/execution/redir5.0 b/bin/1sh/tests/execution/redir5.0 new file mode 100644 index 00000000..f0247102 --- /dev/null +++ b/bin/1sh/tests/execution/redir5.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/redir5.0 218325 2011-02-05 15:02:19Z jilles $ + +{ (echo bad) >/dev/null; } </dev/null diff --git a/bin/1sh/tests/execution/redir6.0 b/bin/1sh/tests/execution/redir6.0 new file mode 100644 index 00000000..846869d4 --- /dev/null +++ b/bin/1sh/tests/execution/redir6.0 @@ -0,0 +1,21 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/redir6.0 220978 2011-04-23 22:28:56Z jilles $ + +failures=0 + +check() { + if [ "$2" != "$3" ]; then + echo "Failure at $1" >&2 + failures=$((failures + 1)) + fi +} + +check $LINENO "$(trap "echo bye" EXIT; : >/dev/null)" bye +check $LINENO "$(trap "echo bye" EXIT; { :; } >/dev/null)" bye +check $LINENO "$(trap "echo bye" EXIT; (:) >/dev/null)" bye +check $LINENO "$(trap "echo bye" EXIT; (: >/dev/null))" bye +check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; : >/dev/null')" bye +check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; { :; } >/dev/null')" bye +check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; (:) >/dev/null')" bye +check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; (: >/dev/null)')" bye + +exit $((failures > 0)) diff --git a/bin/1sh/tests/execution/redir7.0 b/bin/1sh/tests/execution/redir7.0 new file mode 100644 index 00000000..f47c9db8 --- /dev/null +++ b/bin/1sh/tests/execution/redir7.0 @@ -0,0 +1,21 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/redir7.0 220978 2011-04-23 22:28:56Z jilles $ + +failures=0 + +check() { + if [ "$2" != "$3" ]; then + echo "Failure at $1" >&2 + failures=$((failures + 1)) + fi +} + +check $LINENO "$(trap "echo bye" EXIT; f() { :; }; f >/dev/null)" bye +check $LINENO "$(trap "echo bye" EXIT; f() { :; }; { f; } >/dev/null)" bye +check $LINENO "$(trap "echo bye" EXIT; f() { :; }; (f) >/dev/null)" bye +check $LINENO "$(trap "echo bye" EXIT; f() { :; }; (f >/dev/null))" bye +check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; f() { :; }; f >/dev/null')" bye +check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; f() { :; }; { f; } >/dev/null')" bye +check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; f() { :; }; (f) >/dev/null')" bye +check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; f() { :; }; (f >/dev/null)')" bye + +exit $((failures > 0)) diff --git a/bin/1sh/tests/execution/set-C1.0 b/bin/1sh/tests/execution/set-C1.0 new file mode 100644 index 00000000..736eefa6 --- /dev/null +++ b/bin/1sh/tests/execution/set-C1.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/set-C1.0 308229 2016-11-02 22:33:37Z jilles $ + +T=$(mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXXXX") || exit +trap 'rm -rf "$T"' 0 + +set -C +echo . >"$T/a" && +[ -s "$T/a" ] && +{ ! true >"$T/a"; } 2>/dev/null && +[ -s "$T/a" ] && +ln -s /dev/null "$T/b" && +true >"$T/b" diff --git a/bin/1sh/tests/execution/set-n1.0 b/bin/1sh/tests/execution/set-n1.0 new file mode 100644 index 00000000..19afbda3 --- /dev/null +++ b/bin/1sh/tests/execution/set-n1.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/set-n1.0 222661 2011-06-03 21:17:42Z jilles $ + +v=$( ($SH -n <<'EOF' +for +EOF +) 2>&1 >/dev/null) +[ $? -ne 0 ] && [ -n "$v" ] diff --git a/bin/1sh/tests/execution/set-n2.0 b/bin/1sh/tests/execution/set-n2.0 new file mode 100644 index 00000000..a6d9a1e4 --- /dev/null +++ b/bin/1sh/tests/execution/set-n2.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/set-n2.0 222661 2011-06-03 21:17:42Z jilles $ + +$SH -n <<'EOF' +echo bad +EOF diff --git a/bin/1sh/tests/execution/set-n3.0 b/bin/1sh/tests/execution/set-n3.0 new file mode 100644 index 00000000..65214703 --- /dev/null +++ b/bin/1sh/tests/execution/set-n3.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/set-n3.0 222661 2011-06-03 21:17:42Z jilles $ + +v=$( ($SH -nc 'for') 2>&1 >/dev/null) +[ $? -ne 0 ] && [ -n "$v" ] diff --git a/bin/1sh/tests/execution/set-n4.0 b/bin/1sh/tests/execution/set-n4.0 new file mode 100644 index 00000000..a7584dc3 --- /dev/null +++ b/bin/1sh/tests/execution/set-n4.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/set-n4.0 222676 2011-06-04 11:28:42Z jilles $ + +$SH -nc 'echo bad' diff --git a/bin/1sh/tests/execution/set-x1.0 b/bin/1sh/tests/execution/set-x1.0 new file mode 100644 index 00000000..8c8d11e4 --- /dev/null +++ b/bin/1sh/tests/execution/set-x1.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/set-x1.0 222882 2011-06-08 21:58:19Z jilles $ + +key='must_contain_this' +{ r=`set -x; { : "$key"; } 2>&1 >/dev/null`; } 2>/dev/null +case $r in +*"$key"*) true ;; +*) false ;; +esac diff --git a/bin/1sh/tests/execution/set-x2.0 b/bin/1sh/tests/execution/set-x2.0 new file mode 100644 index 00000000..0c5073b5 --- /dev/null +++ b/bin/1sh/tests/execution/set-x2.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/set-x2.0 222882 2011-06-08 21:58:19Z jilles $ + +key='must contain this' +PS4="$key+ " +{ r=`set -x; { :; } 2>&1 >/dev/null`; } 2>/dev/null +case $r in +*"$key"*) true ;; +*) false ;; +esac diff --git a/bin/1sh/tests/execution/set-x3.0 b/bin/1sh/tests/execution/set-x3.0 new file mode 100644 index 00000000..195219b2 --- /dev/null +++ b/bin/1sh/tests/execution/set-x3.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/set-x3.0 222907 2011-06-09 23:12:23Z jilles $ + +key='must contain this' +PS4='$key+ ' +{ r=`set -x; { :; } 2>&1 >/dev/null`; } 2>/dev/null +case $r in +*"$key"*) true ;; +*) false ;; +esac diff --git a/bin/1sh/tests/execution/set-x4.0 b/bin/1sh/tests/execution/set-x4.0 new file mode 100644 index 00000000..fcc638ec --- /dev/null +++ b/bin/1sh/tests/execution/set-x4.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/set-x4.0 275766 2014-12-14 16:26:19Z jilles $ + +key=`printf '\r\t\001\200\300'` +r=`{ set -x; : "$key"; } 2>&1 >/dev/null` +case $r in +*[![:print:]]*) echo fail; exit 3 +esac diff --git a/bin/1sh/tests/execution/shellproc1.0 b/bin/1sh/tests/execution/shellproc1.0 new file mode 100644 index 00000000..2d1fd919 --- /dev/null +++ b/bin/1sh/tests/execution/shellproc1.0 @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/shellproc1.0 218205 2011-02-02 22:03:18Z jilles $ + +T=`mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXXXX"` || exit +trap 'rm -rf "${T}"' 0 +cat <<EOF >"$T/testshellproc" +printf 'this ' +echo is a test +EOF +chmod 755 "$T/testshellproc" +PATH=$T:$PATH +[ "`testshellproc`" = "this is a test" ] diff --git a/bin/1sh/tests/execution/subshell1.0 b/bin/1sh/tests/execution/subshell1.0 new file mode 100644 index 00000000..83819cc1 --- /dev/null +++ b/bin/1sh/tests/execution/subshell1.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/subshell1.0 245383 2013-01-13 19:39:13Z jilles $ + +(eval "cd / +v=$(printf %0100000d 1) +echo \${#v}") +echo end diff --git a/bin/1sh/tests/execution/subshell1.0.stdout b/bin/1sh/tests/execution/subshell1.0.stdout new file mode 100644 index 00000000..8c71af3c --- /dev/null +++ b/bin/1sh/tests/execution/subshell1.0.stdout @@ -0,0 +1,2 @@ +100000 +end diff --git a/bin/1sh/tests/execution/subshell2.0 b/bin/1sh/tests/execution/subshell2.0 new file mode 100644 index 00000000..e938ff82 --- /dev/null +++ b/bin/1sh/tests/execution/subshell2.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/subshell2.0 245383 2013-01-13 19:39:13Z jilles $ + +f() { + x=2 +} +( + x=1 + f + [ "$x" = 2 ] +) diff --git a/bin/1sh/tests/execution/subshell3.0 b/bin/1sh/tests/execution/subshell3.0 new file mode 100644 index 00000000..f703e9c1 --- /dev/null +++ b/bin/1sh/tests/execution/subshell3.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/subshell3.0 245383 2013-01-13 19:39:13Z jilles $ + +(false; exit) && exit 3 +exit 0 diff --git a/bin/1sh/tests/execution/subshell4.0 b/bin/1sh/tests/execution/subshell4.0 new file mode 100644 index 00000000..37ea313e --- /dev/null +++ b/bin/1sh/tests/execution/subshell4.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/subshell4.0 245383 2013-01-13 19:39:13Z jilles $ + +(eval "set v=1"; false) && echo bad; : diff --git a/bin/1sh/tests/execution/unknown1.0 b/bin/1sh/tests/execution/unknown1.0 new file mode 100644 index 00000000..c4258aa9 --- /dev/null +++ b/bin/1sh/tests/execution/unknown1.0 @@ -0,0 +1,29 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/unknown1.0 197820 2009-10-06 22:00:14Z jilles $ + +nosuchtool 2>/dev/null +[ $? -ne 127 ] && exit 1 +/var/empty/nosuchtool 2>/dev/null +[ $? -ne 127 ] && exit 1 +(nosuchtool) 2>/dev/null +[ $? -ne 127 ] && exit 1 +(/var/empty/nosuchtool) 2>/dev/null +[ $? -ne 127 ] && exit 1 +/ 2>/dev/null +[ $? -ne 126 ] && exit 1 +PATH=/usr bin 2>/dev/null +[ $? -ne 126 ] && exit 1 + +dummy=$(nosuchtool 2>/dev/null) +[ $? -ne 127 ] && exit 1 +dummy=$(/var/empty/nosuchtool 2>/dev/null) +[ $? -ne 127 ] && exit 1 +dummy=$( (nosuchtool) 2>/dev/null) +[ $? -ne 127 ] && exit 1 +dummy=$( (/var/empty/nosuchtool) 2>/dev/null) +[ $? -ne 127 ] && exit 1 +dummy=$(/ 2>/dev/null) +[ $? -ne 126 ] && exit 1 +dummy=$(PATH=/usr bin 2>/dev/null) +[ $? -ne 126 ] && exit 1 + +exit 0 diff --git a/bin/1sh/tests/execution/var-assign1.0 b/bin/1sh/tests/execution/var-assign1.0 new file mode 100644 index 00000000..6507c487 --- /dev/null +++ b/bin/1sh/tests/execution/var-assign1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/execution/var-assign1.0 212467 2010-09-11 14:15:50Z jilles $ + +[ "$(HOME=/etc HOME=/ cd && pwd)" = / ] diff --git a/bin/1sh/tests/expansion/Makefile b/bin/1sh/tests/expansion/Makefile new file mode 100644 index 00000000..a22f0db0 --- /dev/null +++ b/bin/1sh/tests/expansion/Makefile @@ -0,0 +1,112 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/Makefile 345117 2019-03-13 21:53:10Z jilles $ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T} + +.PATH: ${.CURDIR:H} +ATF_TESTS_SH= functional_test + +${PACKAGE}FILES+= arith1.0 +${PACKAGE}FILES+= arith2.0 +${PACKAGE}FILES+= arith3.0 +${PACKAGE}FILES+= arith4.0 +${PACKAGE}FILES+= arith5.0 +${PACKAGE}FILES+= arith6.0 +${PACKAGE}FILES+= arith7.0 +${PACKAGE}FILES+= arith8.0 +${PACKAGE}FILES+= arith9.0 +${PACKAGE}FILES+= arith10.0 +${PACKAGE}FILES+= arith11.0 +${PACKAGE}FILES+= arith12.0 +${PACKAGE}FILES+= arith13.0 +${PACKAGE}FILES+= arith14.0 +${PACKAGE}FILES+= arith15.0 +${PACKAGE}FILES+= arith16.0 +${PACKAGE}FILES+= arith17.0 +${PACKAGE}FILES+= assign1.0 +${PACKAGE}FILES+= cmdsubst1.0 +${PACKAGE}FILES+= cmdsubst2.0 +${PACKAGE}FILES+= cmdsubst3.0 +${PACKAGE}FILES+= cmdsubst4.0 +${PACKAGE}FILES+= cmdsubst5.0 +${PACKAGE}FILES+= cmdsubst6.0 +${PACKAGE}FILES+= cmdsubst7.0 +${PACKAGE}FILES+= cmdsubst8.0 +${PACKAGE}FILES+= cmdsubst9.0 +${PACKAGE}FILES+= cmdsubst10.0 +${PACKAGE}FILES+= cmdsubst11.0 +${PACKAGE}FILES+= cmdsubst12.0 +${PACKAGE}FILES+= cmdsubst13.0 +${PACKAGE}FILES+= cmdsubst14.0 +${PACKAGE}FILES+= cmdsubst15.0 +${PACKAGE}FILES+= cmdsubst16.0 +${PACKAGE}FILES+= cmdsubst17.0 +${PACKAGE}FILES+= cmdsubst18.0 +${PACKAGE}FILES+= cmdsubst19.0 +${PACKAGE}FILES+= cmdsubst20.0 +${PACKAGE}FILES+= cmdsubst21.0 +${PACKAGE}FILES+= cmdsubst22.0 +${PACKAGE}FILES+= cmdsubst23.0 +${PACKAGE}FILES+= cmdsubst24.0 +${PACKAGE}FILES+= cmdsubst25.0 +${PACKAGE}FILES+= cmdsubst26.0 +${PACKAGE}FILES+= export1.0 +${PACKAGE}FILES+= export2.0 +${PACKAGE}FILES+= export3.0 +${PACKAGE}FILES+= heredoc1.0 +${PACKAGE}FILES+= heredoc2.0 +${PACKAGE}FILES+= ifs1.0 +${PACKAGE}FILES+= ifs2.0 +${PACKAGE}FILES+= ifs3.0 +${PACKAGE}FILES+= ifs4.0 +${PACKAGE}FILES+= ifs5.0 +${PACKAGE}FILES+= ifs6.0 +${PACKAGE}FILES+= ifs7.0 +${PACKAGE}FILES+= length1.0 +${PACKAGE}FILES+= length2.0 +${PACKAGE}FILES+= length3.0 +${PACKAGE}FILES+= length4.0 +${PACKAGE}FILES+= length5.0 +${PACKAGE}FILES+= length6.0 +${PACKAGE}FILES+= length7.0 +${PACKAGE}FILES+= length8.0 +${PACKAGE}FILES+= local1.0 +${PACKAGE}FILES+= local2.0 +${PACKAGE}FILES+= pathname1.0 +${PACKAGE}FILES+= pathname2.0 +${PACKAGE}FILES+= pathname3.0 +${PACKAGE}FILES+= pathname4.0 +${PACKAGE}FILES+= pathname5.0 +${PACKAGE}FILES+= pathname6.0 +${PACKAGE}FILES+= plus-minus1.0 +${PACKAGE}FILES+= plus-minus2.0 +${PACKAGE}FILES+= plus-minus3.0 +${PACKAGE}FILES+= plus-minus4.0 +${PACKAGE}FILES+= plus-minus5.0 +${PACKAGE}FILES+= plus-minus6.0 +${PACKAGE}FILES+= plus-minus7.0 +${PACKAGE}FILES+= plus-minus8.0 +${PACKAGE}FILES+= plus-minus9.0 +${PACKAGE}FILES+= question1.0 +${PACKAGE}FILES+= question2.0 +${PACKAGE}FILES+= readonly1.0 +${PACKAGE}FILES+= redir1.0 +${PACKAGE}FILES+= set-u1.0 +${PACKAGE}FILES+= set-u2.0 +${PACKAGE}FILES+= set-u3.0 +${PACKAGE}FILES+= tilde1.0 +${PACKAGE}FILES+= tilde2.0 +${PACKAGE}FILES+= trim1.0 +${PACKAGE}FILES+= trim2.0 +${PACKAGE}FILES+= trim3.0 +${PACKAGE}FILES+= trim4.0 +${PACKAGE}FILES+= trim5.0 +${PACKAGE}FILES+= trim6.0 +${PACKAGE}FILES+= trim7.0 +${PACKAGE}FILES+= trim8.0 +${PACKAGE}FILES+= trim9.0 +${PACKAGE}FILES+= trim10.0 +${PACKAGE}FILES+= trim11.0 + +.include <bsd.test.mk> diff --git a/bin/1sh/tests/expansion/Makefile.depend b/bin/1sh/tests/expansion/Makefile.depend new file mode 100644 index 00000000..b1212738 --- /dev/null +++ b/bin/1sh/tests/expansion/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/Makefile.depend 296587 2016-03-09 22:46:01Z bdrewery $ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/1sh/tests/expansion/arith1.0 b/bin/1sh/tests/expansion/arith1.0 new file mode 100644 index 00000000..cd5a28df --- /dev/null +++ b/bin/1sh/tests/expansion/arith1.0 @@ -0,0 +1,30 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith1.0 201259 2009-12-30 15:59:40Z jilles $ + +failures=0 + +check() { + if [ $(($1)) != $2 ]; then + failures=$((failures+1)) + echo "For $1, expected $2 actual $(($1))" + fi +} + +check "0&&0" 0 +check "1&&0" 0 +check "0&&1" 0 +check "1&&1" 1 +check "2&&2" 1 +check "1&&2" 1 +check "1<<40&&1<<40" 1 +check "1<<40&&4" 1 + +check "0||0" 0 +check "1||0" 1 +check "0||1" 1 +check "1||1" 1 +check "2||2" 1 +check "1||2" 1 +check "1<<40||1<<40" 1 +check "1<<40||4" 1 + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/arith10.0 b/bin/1sh/tests/expansion/arith10.0 new file mode 100644 index 00000000..4e152463 --- /dev/null +++ b/bin/1sh/tests/expansion/arith10.0 @@ -0,0 +1,35 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith10.0 218469 2011-02-08 23:23:55Z jilles $ + +failures=0 + +check() { + if [ $(($1)) != $2 ]; then + failures=$((failures+1)) + echo "For $1, expected $2 actual $(($1))" + fi +} + +readonly ro=4 +rw=1 +check "0 && 0 / 0" 0 +check "1 || 0 / 0" 1 +check "0 && (ro = 2)" 0 +check "ro" 4 +check "1 || (ro = -1)" 1 +check "ro" 4 +check "0 && (rw += 1)" 0 +check "rw" 1 +check "1 || (rw += 1)" 1 +check "rw" 1 +check "0 ? 44 / 0 : 51" 51 +check "0 ? ro = 3 : 52" 52 +check "ro" 4 +check "0 ? rw += 1 : 52" 52 +check "rw" 1 +check "1 ? 68 : 30 / 0" 68 +check "2 ? 1 : (ro += 2)" 1 +check "ro" 4 +check "4 ? 1 : (rw += 1)" 1 +check "rw" 1 + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/arith11.0 b/bin/1sh/tests/expansion/arith11.0 new file mode 100644 index 00000000..6fc08175 --- /dev/null +++ b/bin/1sh/tests/expansion/arith11.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith11.0 218626 2011-02-12 23:44:05Z jilles $ +# Try to divide the smallest integer by -1. +# On amd64 this causes SIGFPE, so make sure the shell checks. + +# Calculate the minimum possible value, assuming two's complement and +# a certain interpretation of overflow when shifting left. +minint=1 +while [ $((minint <<= 1)) -gt 0 ]; do + : +done +v=$( eval ': $((minint / -1))' 2>&1 >/dev/null) +[ $? -ne 0 ] && [ -n "$v" ] diff --git a/bin/1sh/tests/expansion/arith12.0 b/bin/1sh/tests/expansion/arith12.0 new file mode 100644 index 00000000..69dac270 --- /dev/null +++ b/bin/1sh/tests/expansion/arith12.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith12.0 232839 2012-03-11 22:12:05Z jilles $ + +_x=4 y_=5 z_z=6 +[ "$((_x*100+y_*10+z_z))" = 456 ] diff --git a/bin/1sh/tests/expansion/arith13.0 b/bin/1sh/tests/expansion/arith13.0 new file mode 100644 index 00000000..2142f410 --- /dev/null +++ b/bin/1sh/tests/expansion/arith13.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith13.0 254806 2013-08-24 20:06:00Z jilles $ +# Pre-increment and pre-decrement in arithmetic expansion are not in POSIX. +# Require either an error or a correct implementation. + +! (eval 'x=4; [ $((++x)) != 5 ] || [ $x != 5 ]') 2>/dev/null && +! (eval 'x=2; [ $((--x)) != 1 ] || [ $x != 1 ]') 2>/dev/null diff --git a/bin/1sh/tests/expansion/arith14.0 b/bin/1sh/tests/expansion/arith14.0 new file mode 100644 index 00000000..c6f84701 --- /dev/null +++ b/bin/1sh/tests/expansion/arith14.0 @@ -0,0 +1,40 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith14.0 270029 2014-08-15 22:36:41Z jilles $ +# Check that <</>> use the low bits of the shift count. + +if [ $((1<<16<<16)) = 0 ]; then + width=32 +elif [ $((1<<32<<32)) = 0 ]; then + width=64 +elif [ $((1<<64<<64)) = 0 ]; then + width=128 +elif [ $((1<<64>>64)) = 1 ]; then + # Integers are wider than 128 bits; assume arbitrary precision. + # Nothing to test here. + exit 0 +else + echo "Cannot determine integer width" + exit 2 +fi + +twowidth=$((width * 2)) +j=43 k=$((1 << (width - 2))) r=0 + +i=0 +while [ $i -lt $twowidth ]; do + if [ "$((j << i))" != "$((j << (i + width)))" ]; then + echo "Problem with $j << $i" + r=2 + fi + i=$((i + 1)) +done + +i=0 +while [ $i -lt $twowidth ]; do + if [ "$((k >> i))" != "$((k >> (i + width)))" ]; then + echo "Problem with $k >> $i" + r=2 + fi + i=$((i + 1)) +done + +exit $r diff --git a/bin/1sh/tests/expansion/arith15.0 b/bin/1sh/tests/expansion/arith15.0 new file mode 100644 index 00000000..f9897908 --- /dev/null +++ b/bin/1sh/tests/expansion/arith15.0 @@ -0,0 +1,20 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith15.0 345117 2019-03-13 21:53:10Z jilles $ + +failures=0 + +check() { + if [ $(($1)) != $2 ]; then + failures=$((failures+1)) + echo "For $1, expected $2 actual $(($1))" + fi +} + +XXX=-9223372036854775808 +check "XXX" -9223372036854775808 +check "XXX - 1" 9223372036854775807 +check "$XXX - 1" 9223372036854775807 +check "$XXX - 2" 9223372036854775806 +check "0x8000000000000000 == 0x7fffffffffffffff" \ + 0 + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/arith16.0 b/bin/1sh/tests/expansion/arith16.0 new file mode 100644 index 00000000..619889f8 --- /dev/null +++ b/bin/1sh/tests/expansion/arith16.0 @@ -0,0 +1,26 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith16.0 345117 2019-03-13 21:53:10Z jilles $ + +failures=0 + +for x in \ + 0x10000000000000000 \ + -0x8000000000000001 \ + 0xfffffffffffffffffffffffffffffffff \ + -0xfffffffffffffffffffffffffffffffff \ + 02000000000000000000000 \ + 9223372036854775808 \ + 9223372036854775809 \ + -9223372036854775809 \ + 9999999999999999999999999 \ + -9999999999999999999999999 +do + msg=$({ + v=$((x)) || : + } 3>&1 >&2 2>&3 3>&-) + r=$? + if [ "$r" = 0 ] || [ -z "$msg" ]; then + printf 'Failed: %s\n' "$x" + : $((failures += 1)) + fi +done +exit $((failures > 0)) diff --git a/bin/1sh/tests/expansion/arith17.0 b/bin/1sh/tests/expansion/arith17.0 new file mode 100644 index 00000000..c306a67d --- /dev/null +++ b/bin/1sh/tests/expansion/arith17.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith17.0 345117 2019-03-13 21:53:10Z jilles $ + +[ $((9223372036854775809)) -gt 0 ] diff --git a/bin/1sh/tests/expansion/arith2.0 b/bin/1sh/tests/expansion/arith2.0 new file mode 100644 index 00000000..4e3daf6d --- /dev/null +++ b/bin/1sh/tests/expansion/arith2.0 @@ -0,0 +1,77 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith2.0 209652 2010-07-02 21:31:24Z jilles $ + +failures=0 + +check() { + if [ $(($1)) != $2 ]; then + failures=$((failures+1)) + echo "For $1, expected $2 actual $(($1))" + fi +} + +# variables +unset v +check "v=2" 2 +check "v" 2 +check "$(($v))" 2 +check "v+=1" 3 +check "v" 3 + +# constants +check "4611686018427387904" 4611686018427387904 +check "0x4000000000000000" 4611686018427387904 +check "0400000000000000000000" 4611686018427387904 +check "0x4Ab0000000000000" 5381801554707742720 +check "010" 8 + +# try out all operators +v=42 +check "!v" 0 +check "!!v" 1 +check "!0" 1 +check "~0" -1 +check "~(-1)" 0 +check "-0" 0 +check "-v" -42 +check "v*v" 1764 +check "v/2" 21 +check "v%10" 2 +check "v+v" 84 +check "v-4" 38 +check "v<<1" 84 +check "v>>1" 21 +check "v<43" 1 +check "v>42" 0 +check "v<=43" 1 +check "v>=43" 0 +check "v==41" 0 +check "v!=42" 0 +check "v&3" 2 +check "v^3" 41 +check "v|3" 43 +check "v>=40&&v<=44" 1 +check "v<40||v>44" 0 +check "(v=42)&&(v+=1)==43" 1 +check "v" 43 +check "(v=42)&&(v-=1)==41" 1 +check "v" 41 +check "(v=42)&&(v*=2)==84" 1 +check "v" 84 +check "(v=42)&&(v/=10)==4" 1 +check "v" 4 +check "(v=42)&&(v%=10)==2" 1 +check "v" 2 +check "(v=42)&&(v<<=1)==84" 1 +check "v" 84 +check "(v=42)&&(v>>=2)==10" 1 +check "v" 10 +check "(v=42)&&(v&=32)==32" 1 +check "v" 32 +check "(v=42)&&(v^=32)==10" 1 +check "v" 10 +check "(v=42)&&(v|=32)==42" 1 +check "v" 42 + +# missing: ternary + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/arith3.0 b/bin/1sh/tests/expansion/arith3.0 new file mode 100644 index 00000000..a8db53d3 --- /dev/null +++ b/bin/1sh/tests/expansion/arith3.0 @@ -0,0 +1,14 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith3.0 204017 2010-02-17 22:25:22Z jilles $ + +failures=0 + +check() { + if [ $(($1)) != $2 ]; then + failures=$((failures+1)) + echo "For $1, expected $2 actual $(($1))" + fi +} + +check "1 << 1 + 1 | 1" 5 + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/arith4.0 b/bin/1sh/tests/expansion/arith4.0 new file mode 100644 index 00000000..32a61d87 --- /dev/null +++ b/bin/1sh/tests/expansion/arith4.0 @@ -0,0 +1,20 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith4.0 206167 2010-04-04 16:29:48Z jilles $ + +failures=0 + +check() { + if [ $(($1)) != $2 ]; then + failures=$((failures+1)) + echo "For $1, expected $2 actual $(($1))" + fi +} + +check '20 / 2 / 2' 5 +check '20 - 2 - 2' 16 +unset a b c d +check "a = b = c = d = 1" 1 +check "a == 1 && b == 1 && c == 1 && d == 1" 1 +check "a += b += c += d" 4 +check "a == 4 && b == 3 && c == 2 && d == 1" 1 + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/arith5.0 b/bin/1sh/tests/expansion/arith5.0 new file mode 100644 index 00000000..c87d49d3 --- /dev/null +++ b/bin/1sh/tests/expansion/arith5.0 @@ -0,0 +1,17 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith5.0 206168 2010-04-04 16:48:33Z jilles $ + +failures=0 + +check() { + if [ "$2" != "$3" ]; then + failures=$((failures+1)) + echo "For $1, expected $3 actual $2" + fi +} + +unset a +check '$((1+${a:-$((7+2))}))' "$((1+${a:-$((7+2))}))" 10 +check '$((1+${a:=$((2+2))}))' "$((1+${a:=$((2+2))}))" 5 +check '$a' "$a" 4 + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/arith6.0 b/bin/1sh/tests/expansion/arith6.0 new file mode 100644 index 00000000..31d2a8cd --- /dev/null +++ b/bin/1sh/tests/expansion/arith6.0 @@ -0,0 +1,16 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith6.0 215550 2010-11-19 22:25:32Z jilles $ + +v1=1\ +\ 1 +v2=D +v3=C123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +f() { v4="$*"; } + +while [ ${#v2} -lt 1250 ]; do + eval $v2=$((3+${#v2})) $v3=$((4-${#v2})) + eval f $(($v2+ $v1 +$v3)) + if [ $v4 -ne 9 ]; then + echo bad: $v4 -ne 9 at ${#v2} + fi + v2=x$v2 + v3=y$v3 +done diff --git a/bin/1sh/tests/expansion/arith7.0 b/bin/1sh/tests/expansion/arith7.0 new file mode 100644 index 00000000..2df33fbd --- /dev/null +++ b/bin/1sh/tests/expansion/arith7.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith7.0 216395 2010-12-12 16:56:16Z jilles $ + +v=1+ +v=$v$v$v$v +v=$v$v$v$v +v=$v$v$v$v +v=$v$v$v$v +v=$v$v$v$v +[ "$(cat <<EOF +$(($v 1)) +EOF +)" = 1025 ] diff --git a/bin/1sh/tests/expansion/arith8.0 b/bin/1sh/tests/expansion/arith8.0 new file mode 100644 index 00000000..a9d9e7e7 --- /dev/null +++ b/bin/1sh/tests/expansion/arith8.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith8.0 216547 2010-12-18 23:03:51Z jilles $ + +v=$( (eval ': $((08))') 2>&1 >/dev/null) +[ $? -ne 0 ] && [ -n "$v" ] diff --git a/bin/1sh/tests/expansion/arith9.0 b/bin/1sh/tests/expansion/arith9.0 new file mode 100644 index 00000000..98c9859a --- /dev/null +++ b/bin/1sh/tests/expansion/arith9.0 @@ -0,0 +1,20 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/arith9.0 218469 2011-02-08 23:23:55Z jilles $ + +failures=0 + +check() { + if [ $(($1)) != $2 ]; then + failures=$((failures+1)) + echo "For $1, expected $2 actual $(($1))" + fi +} + +check "0 ? 44 : 51" 51 +check "1 ? 68 : 30" 68 +check "2 ? 1 : -5" 1 +check "0 ? 4 : 0 ? 5 : 6" 6 +check "0 ? 4 : 1 ? 5 : 6" 5 +check "1 ? 4 : 0 ? 5 : 6" 4 +check "1 ? 4 : 1 ? 5 : 6" 4 + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/assign1.0 b/bin/1sh/tests/expansion/assign1.0 new file mode 100644 index 00000000..25035f57 --- /dev/null +++ b/bin/1sh/tests/expansion/assign1.0 @@ -0,0 +1,37 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/assign1.0 204842 2010-03-07 18:43:29Z jilles $ + +e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}' +h='##' +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'v=; set -- ${v=a b} $v' '0|' +testcase 'unset v; set -- ${v=a b} $v' '4|a|b|a|b' +testcase 'v=; set -- ${v:=a b} $v' '4|a|b|a|b' +testcase 'v=; set -- "${v:=a b}" "$v"' '2|a b|a b' +# expect sensible behaviour, although it disagrees with POSIX +testcase 'v=; set -- ${v:=a\ b} $v' '4|a|b|a|b' +testcase 'v=; set -- ${v:=$p} $v' '2|/etc/|/etc/' +testcase 'v=; set -- "${v:=$p}" "$v"' '2|/et[c]/|/et[c]/' +testcase 'v=; set -- "${v:=a\ b}" "$v"' '2|a\ b|a\ b' +testcase 'v=; set -- ${v:="$p"} $v' '2|/etc/|/etc/' +# whether $p is quoted or not shouldn't really matter +testcase 'v=; set -- "${v:="$p"}" "$v"' '2|/et[c]/|/et[c]/' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/cmdsubst1.0 b/bin/1sh/tests/expansion/cmdsubst1.0 new file mode 100644 index 00000000..7b8768a7 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst1.0 @@ -0,0 +1,48 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst1.0 201366 2010-01-01 18:17:46Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +check '"$(echo abcde)" = "abcde"' +check '"$(echo abcde; :)" = "abcde"' + +check '"$(printf abcde)" = "abcde"' +check '"$(printf abcde; :)" = "abcde"' + +# regular +check '-n "$(umask)"' +check '-n "$(umask; :)"' +check '-n "$(umask 2>&1)"' +check '-n "$(umask 2>&1; :)"' + +# special +check '-n "$(times)"' +check '-n "$(times; :)"' +check '-n "$(times 2>&1)"' +check '-n "$(times 2>&1; :)"' + +# regular +check '".$(umask -@ 2>&1)." = ".umask: Illegal option -@."' +check '".$(umask -@ 2>&1; :)." = ".umask: Illegal option -@."' +check '".$({ umask -@; } 2>&1)." = ".umask: Illegal option -@."' + +# special +check '".$(shift xyz 2>&1)." = ".shift: Illegal number: xyz."' +check '".$(shift xyz 2>&1; :)." = ".shift: Illegal number: xyz."' +check '".$({ shift xyz; } 2>&1)." = ".shift: Illegal number: xyz."' + +v=1 +check '-z "$(v=2 :)"' +check '"$v" = 1' +check '-z "$(v=3)"' +check '"$v" = 1' +check '"$(v=4 eval echo \$v)" = 4' +check '"$v" = 1' + +exit $((failures > 0)) diff --git a/bin/1sh/tests/expansion/cmdsubst10.0 b/bin/1sh/tests/expansion/cmdsubst10.0 new file mode 100644 index 00000000..fe357a7d --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst10.0 @@ -0,0 +1,51 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst10.0 216826 2010-12-30 22:33:55Z jilles $ + +a1=$(alias) +: $(alias testalias=abcd) +a2=$(alias) +[ "$a1" = "$a2" ] || echo Error at line $LINENO + +alias testalias2=abcd +a1=$(alias) +: $(unalias testalias2) +a2=$(alias) +[ "$a1" = "$a2" ] || echo Error at line $LINENO + +[ "$(command -V pwd)" = "$(command -V pwd; exit $?)" ] || echo Error at line $LINENO + +v=1 +: $(export v=2) +[ "$v" = 1 ] || echo Error at line $LINENO + +rotest=1 +: $(readonly rotest=2) +[ "$rotest" = 1 ] || echo Error at line $LINENO + +set +u +: $(set -u) +case $- in +*u*) echo Error at line $LINENO ;; +esac +set +u + +set +u +: $(set -o nounset) +case $- in +*u*) echo Error at line $LINENO ;; +esac +set +u + +set +u +: $(command set -u) +case $- in +*u*) echo Error at line $LINENO ;; +esac +set +u + +umask 77 +u1=$(umask) +: $(umask 022) +u2=$(umask) +[ "$u1" = "$u2" ] || echo Error at line $LINENO + +dummy=$(exit 3); [ $? -eq 3 ] || echo Error at line $LINENO diff --git a/bin/1sh/tests/expansion/cmdsubst11.0 b/bin/1sh/tests/expansion/cmdsubst11.0 new file mode 100644 index 00000000..320395ef --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst11.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst11.0 223163 2011-06-16 21:50:28Z jilles $ + +# Not required by POSIX but useful for efficiency. + +[ $$ = $(eval '${SH} -c echo\ \$PPID') ] diff --git a/bin/1sh/tests/expansion/cmdsubst12.0 b/bin/1sh/tests/expansion/cmdsubst12.0 new file mode 100644 index 00000000..927d3748 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst12.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst12.0 230121 2012-01-14 23:10:18Z jilles $ + +f() { + echo x$(printf foo >&2)y +} +[ "$(f 2>&1)" = "fooxy" ] diff --git a/bin/1sh/tests/expansion/cmdsubst13.0 b/bin/1sh/tests/expansion/cmdsubst13.0 new file mode 100644 index 00000000..849a8823 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst13.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst13.0 230121 2012-01-14 23:10:18Z jilles $ + +x=1 y=2 +[ "$( + case $((x+=1)) in + ($((y+=1))) echo bad1 ;; + ($((y-1))) echo $x.$y ;; + ($((y=2))) echo bad2 ;; + (*) echo bad3 ;; + esac +)" = "2.3" ] || echo "Error at $LINENO" +[ "$x.$y" = "1.2" ] || echo "Error at $LINENO" diff --git a/bin/1sh/tests/expansion/cmdsubst14.0 b/bin/1sh/tests/expansion/cmdsubst14.0 new file mode 100644 index 00000000..4f133796 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst14.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst14.0 245381 2013-01-13 19:19:40Z jilles $ + +! v=`false + +` diff --git a/bin/1sh/tests/expansion/cmdsubst15.0 b/bin/1sh/tests/expansion/cmdsubst15.0 new file mode 100644 index 00000000..fbe97009 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst15.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst15.0 245381 2013-01-13 19:19:40Z jilles $ + +! v=`false; + +` diff --git a/bin/1sh/tests/expansion/cmdsubst16.0 b/bin/1sh/tests/expansion/cmdsubst16.0 new file mode 100644 index 00000000..f031d9fa --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst16.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst16.0 245392 2013-01-13 22:35:51Z jilles $ + +f() { return 3; } +f +[ `echo $?` = 3 ] diff --git a/bin/1sh/tests/expansion/cmdsubst17.0 b/bin/1sh/tests/expansion/cmdsubst17.0 new file mode 100644 index 00000000..ea274080 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst17.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst17.0 245422 2013-01-14 12:20:55Z jilles $ + +f() { return 3; } +f +[ `echo $?; :` = 3 ] diff --git a/bin/1sh/tests/expansion/cmdsubst18.0 b/bin/1sh/tests/expansion/cmdsubst18.0 new file mode 100644 index 00000000..d090925b --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst18.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst18.0 314637 2017-03-03 22:46:20Z jilles $ + +x=X +unset n +r=${x+$(echo a)}${x-$(echo b)}${n+$(echo c)}${n-$(echo d)}$(echo e) +[ "$r" = aXde ] diff --git a/bin/1sh/tests/expansion/cmdsubst19.0 b/bin/1sh/tests/expansion/cmdsubst19.0 new file mode 100644 index 00000000..abc76471 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst19.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst19.0 314637 2017-03-03 22:46:20Z jilles $ + +b=200 c=30 d=5 x=4 +r=$(echo a)$(($(echo b) + ${x+$(echo c)} + ${x-$(echo d)}))$(echo e) +[ "$r" = a234e ] diff --git a/bin/1sh/tests/expansion/cmdsubst2.0 b/bin/1sh/tests/expansion/cmdsubst2.0 new file mode 100644 index 00000000..cf533c34 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst2.0 @@ -0,0 +1,43 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst2.0 205105 2010-03-12 23:23:46Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +check '`echo /et[c]/` = "/etc/"' +check '`printf /var/empty%s /et[c]/` = "/var/empty/etc/"' +check '"`echo /et[c]/`" = "/etc/"' +check '`echo "/et[c]/"` = "/etc/"' +check '`printf /var/empty%s "/et[c]/"` = "/var/empty/et[c]/"' +check '`printf /var/empty/%s \"/et[c]/\"` = "/var/empty/\"/et[c]/\""' +check '"`echo \"/et[c]/\"`" = "/et[c]/"' +check '"`echo "/et[c]/"`" = "/et[c]/"' +check '`echo $$` = $$' +check '"`echo $$`" = $$' +check '`echo \$\$` = $$' +check '"`echo \$\$`" = $$' + +# Command substitutions consisting of a single builtin may be treated +# differently. +check '`:; echo /et[c]/` = "/etc/"' +check '`:; printf /var/empty%s /et[c]/` = "/var/empty/etc/"' +check '"`:; echo /et[c]/`" = "/etc/"' +check '`:; echo "/et[c]/"` = "/etc/"' +check '`:; printf /var/empty%s "/et[c]/"` = "/var/empty/et[c]/"' +check '`:; printf /var/empty/%s \"/et[c]/\"` = "/var/empty/\"/et[c]/\""' +check '"`:; echo \"/et[c]/\"`" = "/et[c]/"' +check '"`:; echo "/et[c]/"`" = "/et[c]/"' +check '`:; echo $$` = $$' +check '"`:; echo $$`" = $$' +check '`:; echo \$\$` = $$' +check '"`:; echo \$\$`" = $$' + +check '`set -f; echo /et[c]/` = "/etc/"' +check '"`set -f; echo /et[c]/`" = "/et[c]/"' + +exit $((failures > 0)) diff --git a/bin/1sh/tests/expansion/cmdsubst20.0 b/bin/1sh/tests/expansion/cmdsubst20.0 new file mode 100644 index 00000000..7b6bd1b1 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst20.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst20.0 314637 2017-03-03 22:46:20Z jilles $ + +set -T +trapped='' +trap "trapped=x$trapped" USR1 +[ "x$(kill -USR1 $$)y" = xy ] && [ "$trapped" = x ] diff --git a/bin/1sh/tests/expansion/cmdsubst21.0 b/bin/1sh/tests/expansion/cmdsubst21.0 new file mode 100644 index 00000000..dbed0646 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst21.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst21.0 314686 2017-03-04 22:58:34Z jilles $ + +set -T +trapped='' +trap "trapped=x$trapped" TERM +[ "x$($SH -c "kill $$")y" = xy ] && [ "$trapped" = x ] diff --git a/bin/1sh/tests/expansion/cmdsubst22.0 b/bin/1sh/tests/expansion/cmdsubst22.0 new file mode 100644 index 00000000..941d8161 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst22.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst22.0 314686 2017-03-04 22:58:34Z jilles $ + +set -T +trapped='' +trap "trapped=x$trapped" TERM +[ "x$(:; kill $$)y" = xy ] && [ "$trapped" = x ] diff --git a/bin/1sh/tests/expansion/cmdsubst23.0 b/bin/1sh/tests/expansion/cmdsubst23.0 new file mode 100644 index 00000000..02ed0c41 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst23.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst23.0 315005 2017-03-10 16:04:00Z jilles $ + +unset n +x=abcd +[ "X${n#$(echo a)}X${x#$(echo ab)}X$(echo abc)X" = XXcdXabcX ] diff --git a/bin/1sh/tests/expansion/cmdsubst24.0 b/bin/1sh/tests/expansion/cmdsubst24.0 new file mode 100644 index 00000000..a9ab8f99 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst24.0 @@ -0,0 +1,24 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst24.0 317347 2017-04-23 21:58:17Z jilles $ +# POSIX leaves the effect of NUL bytes in command substitution output +# unspecified but we have always discarded them. + +failures=0 + +check() { + if [ "$2" != "$3" ]; then + printf "Failed at line %s: got \"%s\" expected \"%s\"\n" "$1" "$2" "$3" + : $((failures += 1)) + fi +} + +fmt='\0a\0 \0b\0c d\0' +assign_builtin=$(printf "$fmt") +check "$LINENO" "$assign_builtin" "a bc d" +assign_pipeline=$(printf "$fmt" | cat) +check "$LINENO" "$assign_pipeline" "a bc d" +set -- $(printf "$fmt") $(printf "$fmt" | cat) "$(printf "$fmt")" "$(printf "$fmt" | cat)" +IFS=@ +splits="$*" +check "$LINENO" "$splits" "a@bc@d@a@bc@d@a bc d@a bc d" + +[ "$failures" = 0 ] diff --git a/bin/1sh/tests/expansion/cmdsubst25.0 b/bin/1sh/tests/expansion/cmdsubst25.0 new file mode 100644 index 00000000..d9e2b95b --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst25.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst25.0 317514 2017-04-27 18:52:18Z jilles $ + +IFS=' ' +set -- `printf '\n '` +IFS=: +[ "$*" = ' +' ] diff --git a/bin/1sh/tests/expansion/cmdsubst26.0 b/bin/1sh/tests/expansion/cmdsubst26.0 new file mode 100644 index 00000000..674bbe01 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst26.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst26.0 317514 2017-04-27 18:52:18Z jilles $ + +nl=' +' +v=$nl`printf '\n'` +[ "$v" = "$nl" ] diff --git a/bin/1sh/tests/expansion/cmdsubst3.0 b/bin/1sh/tests/expansion/cmdsubst3.0 new file mode 100644 index 00000000..4089210c --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst3.0 @@ -0,0 +1,23 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst3.0 218819 2011-02-18 20:37:09Z jilles $ + +unset LC_ALL +export LC_CTYPE=en_US.ISO8859-1 + +e= +for i in 0 1 2 3; do + for j in 0 1 2 3 4 5 6 7; do + for k in 0 1 2 3 4 5 6 7; do + case $i$j$k in + 000) continue ;; + esac + e="$e\n\\$i$j$k" + done + done +done +e1=$(printf "$e") +e2="$(printf "$e")" +[ "${#e1}" = 510 ] || echo length bad +[ "$e1" = "$e2" ] || echo e1 != e2 +[ "$e1" = "$(printf "$e")" ] || echo quoted bad +IFS= +[ "$e1" = $(printf "$e") ] || echo unquoted bad diff --git a/bin/1sh/tests/expansion/cmdsubst4.0 b/bin/1sh/tests/expansion/cmdsubst4.0 new file mode 100644 index 00000000..dd200c01 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst4.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst4.0 216747 2010-12-27 23:56:03Z jilles $ + +exec 2>/dev/null +! y=$(: </var/empty/nonexistent) diff --git a/bin/1sh/tests/expansion/cmdsubst5.0 b/bin/1sh/tests/expansion/cmdsubst5.0 new file mode 100644 index 00000000..b58f8ce4 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst5.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst5.0 216761 2010-12-28 13:28:24Z jilles $ + +unset v +exec 2>/dev/null +! y=$(: ${v?}) diff --git a/bin/1sh/tests/expansion/cmdsubst6.0 b/bin/1sh/tests/expansion/cmdsubst6.0 new file mode 100644 index 00000000..afe519f0 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst6.0 @@ -0,0 +1,53 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst6.0 216763 2010-12-28 14:58:08Z jilles $ +# This tests if the cmdsubst optimization is still used if possible. + +failures='' +ok='' + +testcase() { + code="$1" + + unset v + eval "pid=\$(dummy=$code echo \$(\$SH -c echo\ \\\$PPID))" + + if [ "$pid" = "$$" ]; then + ok=x$ok + else + failures=x$failures + echo "Failure for $code" + fi +} + +unset v +w=1 +testcase '$w' +testcase '1${w+1}' +testcase '1${w-1}' +testcase '1${v+1}' +testcase '1${v-1}' +testcase '1${w:+1}' +testcase '1${w:-1}' +testcase '1${v:+1}' +testcase '1${v:-1}' +testcase '${w?}' +testcase '${w:?}' +testcase '${w#x}' +testcase '${w##x}' +testcase '${w%x}' +testcase '${w%%x}' + +testcase '$((w))' +testcase '$(((w+4)*2/3))' +testcase '$((w==1))' +testcase '$((w>=0 && w<=5 && w!=2))' +testcase '$((${#w}))' +testcase '$((${#IFS}))' +testcase '$((${#w}>=1))' +testcase '$(($$))' +testcase '$(($#))' +testcase '$(($?))' + +testcase '$(: $((w=4)))' +testcase '$(: ${v=2})' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/cmdsubst7.0 b/bin/1sh/tests/expansion/cmdsubst7.0 new file mode 100644 index 00000000..91f991cb --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst7.0 @@ -0,0 +1,31 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst7.0 216778 2010-12-28 21:27:08Z jilles $ + +failures='' +ok='' + +testcase() { + code="$1" + + unset v + eval ": \$($code)" + + if [ "${v:+bad}" = "" ]; then + ok=x$ok + else + failures=x$failures + echo "Failure for $code" + fi +} + +testcase ': ${v=0}' +testcase ': ${v:=0}' +testcase ': $((v=1))' +testcase ': $((v+=1))' +w='v=1' +testcase ': $(($w))' +testcase ': $((${$+v=1}))' +testcase ': $((v${$+=1}))' +testcase ': $((v $(echo =) 1))' +testcase ': $(($(echo $w)))' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/cmdsubst8.0 b/bin/1sh/tests/expansion/cmdsubst8.0 new file mode 100644 index 00000000..b22473d9 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst8.0 @@ -0,0 +1,17 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst8.0 216819 2010-12-30 15:04:59Z jilles $ +# Not required by POSIX (although referenced in a non-normative section), +# but possibly useful. + +: hi there & +p=$! +q=$(jobs -l $p) + +# Change tabs to spaces. +set -f +set -- $q +r="$*" + +case $r in +*" $p "*) ;; +*) echo Pid missing; exit 3 ;; +esac diff --git a/bin/1sh/tests/expansion/cmdsubst9.0 b/bin/1sh/tests/expansion/cmdsubst9.0 new file mode 100644 index 00000000..e2d9fa67 --- /dev/null +++ b/bin/1sh/tests/expansion/cmdsubst9.0 @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/cmdsubst9.0 216819 2010-12-30 15:04:59Z jilles $ + +set -e + +cd / +dummy=$(cd /bin) +[ "$(pwd)" = / ] + +v=1 +dummy=$(eval v=2) +[ "$v" = 1 ] diff --git a/bin/1sh/tests/expansion/export1.0 b/bin/1sh/tests/expansion/export1.0 new file mode 100644 index 00000000..da65d309 --- /dev/null +++ b/bin/1sh/tests/expansion/export1.0 @@ -0,0 +1,13 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/export1.0 238430 2012-07-13 22:29:02Z jilles $ + +w='@ vv=6' + +v=0 vv=0 +export \v=$w +[ "$v" = "@" ] || echo "Expected @ got $v" +[ "$vv" = "6" ] || echo "Expected 6 got $vv" + +HOME=/known/value + +export \v=~ +[ "$v" = \~ ] || echo "Expected ~ got $v" diff --git a/bin/1sh/tests/expansion/export2.0 b/bin/1sh/tests/expansion/export2.0 new file mode 100644 index 00000000..41fe44d2 --- /dev/null +++ b/bin/1sh/tests/expansion/export2.0 @@ -0,0 +1,24 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/export2.0 238468 2012-07-15 10:19:43Z jilles $ + +w='@ @' +check() { + [ "$v" = "$w" ] || echo "Expected $w got $v" +} + +export v=$w +check + +HOME=/known/value +check() { + [ "$v" = ~ ] || echo "Expected $HOME got $v" +} + +export v=~ +check + +check() { + [ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v" +} + +export v=x:~ +check diff --git a/bin/1sh/tests/expansion/export3.0 b/bin/1sh/tests/expansion/export3.0 new file mode 100644 index 00000000..cc981eac --- /dev/null +++ b/bin/1sh/tests/expansion/export3.0 @@ -0,0 +1,30 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/export3.0 238468 2012-07-15 10:19:43Z jilles $ + +w='@ @' +check() { + [ "$v" = "$w" ] || echo "Expected $w got $v" +} + +command export v=$w +check +command command export v=$w +check + +HOME=/known/value +check() { + [ "$v" = ~ ] || echo "Expected $HOME got $v" +} + +command export v=~ +check +command command export v=~ +check + +check() { + [ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v" +} + +command export v=x:~ +check +command command export v=x:~ +check diff --git a/bin/1sh/tests/expansion/heredoc1.0 b/bin/1sh/tests/expansion/heredoc1.0 new file mode 100644 index 00000000..364e909c --- /dev/null +++ b/bin/1sh/tests/expansion/heredoc1.0 @@ -0,0 +1,25 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/heredoc1.0 222715 2011-06-05 12:46:26Z jilles $ + +f() { return $1; } + +[ `f 42; { cat; } <<EOF +$? +EOF +` = 42 ] || echo compound command bad + +[ `f 42; (cat) <<EOF +$? +EOF +` = 42 ] || echo subshell bad + +long=`printf %08192d 0` + +[ `f 42; { cat; } <<EOF +$long.$? +EOF +` = $long.42 ] || echo long compound command bad + +[ `f 42; (cat) <<EOF +$long.$? +EOF +` = $long.42 ] || echo long subshell bad diff --git a/bin/1sh/tests/expansion/heredoc2.0 b/bin/1sh/tests/expansion/heredoc2.0 new file mode 100644 index 00000000..937f7b11 --- /dev/null +++ b/bin/1sh/tests/expansion/heredoc2.0 @@ -0,0 +1,15 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/heredoc2.0 222716 2011-06-05 14:13:15Z jilles $ + +f() { return $1; } + +[ `f 42; cat <<EOF +$? +EOF +` = 42 ] || echo simple command bad + +long=`printf %08192d 0` + +[ `f 42; cat <<EOF +$long.$? +EOF +` = $long.42 ] || echo long simple command bad diff --git a/bin/1sh/tests/expansion/ifs1.0 b/bin/1sh/tests/expansion/ifs1.0 new file mode 100644 index 00000000..6245422f --- /dev/null +++ b/bin/1sh/tests/expansion/ifs1.0 @@ -0,0 +1,35 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/ifs1.0 194981 2009-06-25 17:36:08Z jilles $ + +c=: e= s=' ' +failures='' +ok='' + +check_result() { + if [ "x$2" = "x$3" ]; then + ok=x$ok + else + failures=x$failures + echo "For $1, expected $3 actual $2" + fi +} + +IFS=' +' +set -- a '' +set -- "$@" +check_result 'set -- "$@"' "($#)($1)($2)" "(2)(a)()" + +set -- a '' +set -- "$@"$e +check_result 'set -- "$@"$e' "($#)($1)($2)" "(2)(a)()" + +set -- a '' +set -- "$@"$s +check_result 'set -- "$@"$s' "($#)($1)($2)" "(2)(a)()" + +IFS="$c" +set -- a '' +set -- "$@"$c +check_result 'set -- "$@"$c' "($#)($1)($2)" "(2)(a)()" + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/ifs2.0 b/bin/1sh/tests/expansion/ifs2.0 new file mode 100644 index 00000000..98d87696 --- /dev/null +++ b/bin/1sh/tests/expansion/ifs2.0 @@ -0,0 +1,24 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/ifs2.0 211341 2010-08-15 17:14:05Z jilles $ + +failures=0 +i=1 +set -f +while [ "$i" -le 127 ]; do + # A different byte still in the range 1..127. + i2=$((i^2+(i==2))) + # Add a character to work around command substitution's removal of + # final newlines, then remove it again. + c=$(printf \\"$(printf %o@ "$i")") + c=${c%@} + c2=$(printf \\"$(printf %o@ "$i2")") + c2=${c2%@} + IFS=$c + set -- $c2$c$c2$c$c2 + if [ "$#" -ne 3 ] || [ "$1" != "$c2" ] || [ "$2" != "$c2" ] || + [ "$3" != "$c2" ]; then + echo "Bad results for separator $i (word $i2)" >&2 + : $((failures += 1)) + fi + i=$((i+1)) +done +exit $((failures > 0)) diff --git a/bin/1sh/tests/expansion/ifs3.0 b/bin/1sh/tests/expansion/ifs3.0 new file mode 100644 index 00000000..e43d92e3 --- /dev/null +++ b/bin/1sh/tests/expansion/ifs3.0 @@ -0,0 +1,21 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/ifs3.0 211622 2010-08-22 13:09:12Z jilles $ + +failures=0 +unset LC_ALL +export LC_CTYPE=en_US.ISO8859-1 +i=128 +set -f +while [ "$i" -le 255 ]; do + i2=$((i^2)) + c=$(printf \\"$(printf %o "$i")") + c2=$(printf \\"$(printf %o "$i2")") + IFS=$c + set -- $c2$c$c2$c$c2 + if [ "$#" -ne 3 ] || [ "$1" != "$c2" ] || [ "$2" != "$c2" ] || + [ "$3" != "$c2" ]; then + echo "Bad results for separator $i (word $i2)" >&2 + : $((failures += 1)) + fi + i=$((i+1)) +done +exit $((failures > 0)) diff --git a/bin/1sh/tests/expansion/ifs4.0 b/bin/1sh/tests/expansion/ifs4.0 new file mode 100644 index 00000000..8b9967b8 --- /dev/null +++ b/bin/1sh/tests/expansion/ifs4.0 @@ -0,0 +1,39 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/ifs4.0 222361 2011-05-27 15:56:13Z jilles $ + +c=: e= s=' ' +failures='' +ok='' + +check_result() { + if [ "x$2" = "x$3" ]; then + ok=x$ok + else + failures=x$failures + echo "For $1, expected $3 actual $2" + fi +} + +IFS=' +' +set -- a b '' c +set -- $@ +check_result 'set -- $@' "($#)($1)($2)($3)($4)" "(3)(a)(b)(c)()" + +IFS='' +set -- a b '' c +set -- $@ +check_result 'set -- $@' "($#)($1)($2)($3)($4)" "(3)(a)(b)(c)()" + +set -- a b '' c +set -- $* +check_result 'set -- $*' "($#)($1)($2)($3)($4)" "(3)(a)(b)(c)()" + +set -- a b '' c +set -- "$@" +check_result 'set -- "$@"' "($#)($1)($2)($3)($4)" "(4)(a)(b)()(c)" + +set -- a b '' c +set -- "$*" +check_result 'set -- "$*"' "($#)($1)($2)($3)($4)" "(1)(abc)()()()" + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/ifs5.0 b/bin/1sh/tests/expansion/ifs5.0 new file mode 100644 index 00000000..04db254a --- /dev/null +++ b/bin/1sh/tests/expansion/ifs5.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/ifs5.0 278806 2015-02-15 19:48:29Z jilles $ + +set -- $(echo a b c d) +[ "$#" = 4 ] diff --git a/bin/1sh/tests/expansion/ifs6.0 b/bin/1sh/tests/expansion/ifs6.0 new file mode 100644 index 00000000..e151c9c0 --- /dev/null +++ b/bin/1sh/tests/expansion/ifs6.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/ifs6.0 280920 2015-03-31 20:59:37Z jilles $ + +IFS=': ' +x=': :' +set -- $x +[ "$#|$1|$2|$3" = "2|||" ] diff --git a/bin/1sh/tests/expansion/ifs7.0 b/bin/1sh/tests/expansion/ifs7.0 new file mode 100644 index 00000000..f9e9ca49 --- /dev/null +++ b/bin/1sh/tests/expansion/ifs7.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/ifs7.0 280920 2015-03-31 20:59:37Z jilles $ + +IFS=2 +set -- $((123)) +[ "$#|$1|$2|$3" = "2|1|3|" ] diff --git a/bin/1sh/tests/expansion/length1.0 b/bin/1sh/tests/expansion/length1.0 new file mode 100644 index 00000000..9f3b9443 --- /dev/null +++ b/bin/1sh/tests/expansion/length1.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/length1.0 219611 2011-03-13 16:20:38Z jilles $ + +v=abcd +[ "${#v}" = 4 ] || echo '${#v} wrong' +v=$$ +[ "${#$}" = "${#v}" ] || echo '${#$} wrong' +[ "${#!}" = 0 ] || echo '${#!} wrong' +set -- 01 2 3 4 5 6 7 8 9 10 11 12 0013 +[ "${#1}" = 2 ] || echo '${#1} wrong' +[ "${#13}" = 4 ] || echo '${#13} wrong' +v=$0 +[ "${#0}" = "${#v}" ] || echo '${#0} wrong' diff --git a/bin/1sh/tests/expansion/length2.0 b/bin/1sh/tests/expansion/length2.0 new file mode 100644 index 00000000..0ff07bd6 --- /dev/null +++ b/bin/1sh/tests/expansion/length2.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/length2.0 219611 2011-03-13 16:20:38Z jilles $ + +v=$- +[ "${#-}" = "${#v}" ] || echo '${#-} wrong' diff --git a/bin/1sh/tests/expansion/length3.0 b/bin/1sh/tests/expansion/length3.0 new file mode 100644 index 00000000..4c4391cd --- /dev/null +++ b/bin/1sh/tests/expansion/length3.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/length3.0 219611 2011-03-13 16:20:38Z jilles $ + +set -- 1 2 3 4 5 6 7 8 9 10 11 12 13 +[ "$#" = 13 ] || echo '$# wrong' +[ "${#}" = 13 ] || echo '${#} wrong' +[ "${##}" = 2 ] || echo '${##} wrong' +set -- +[ "$#" = 0 ] || echo '$# wrong' +[ "${#}" = 0 ] || echo '${#} wrong' +[ "${##}" = 1 ] || echo '${##} wrong' diff --git a/bin/1sh/tests/expansion/length4.0 b/bin/1sh/tests/expansion/length4.0 new file mode 100644 index 00000000..4a39fea1 --- /dev/null +++ b/bin/1sh/tests/expansion/length4.0 @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/length4.0 220655 2011-04-15 15:26:05Z jilles $ + +# The construct ${#?} is ambiguous in POSIX.1-2008: it could be the length +# of $? or it could be $# giving an error in the (impossible) case that it +# is not set. +# We use the former interpretation; it seems more useful. + +: +[ "${#?}" = 1 ] || echo '${#?} wrong' +(exit 42) +[ "${#?}" = 2 ] || echo '${#?} wrong' diff --git a/bin/1sh/tests/expansion/length5.0 b/bin/1sh/tests/expansion/length5.0 new file mode 100644 index 00000000..dcc76831 --- /dev/null +++ b/bin/1sh/tests/expansion/length5.0 @@ -0,0 +1,27 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/length5.0 220656 2011-04-15 15:33:24Z jilles $ + +unset LC_ALL +LC_CTYPE=en_US.ISO8859-1 +export LC_CTYPE + +e= +for i in 0 1 2 3; do + for j in 0 1 2 3 4 5 6 7; do + for k in 0 1 2 3 4 5 6 7; do + case $i$j$k in + 000) continue ;; + esac + e="$e\\$i$j$k" + done + done +done +ee=`printf "$e"` +[ ${#ee} = 255 ] || echo bad 1 +[ "${#ee}" = 255 ] || echo bad 2 +[ $((${#ee})) = 255 ] || echo bad 3 +[ "$((${#ee}))" = 255 ] || echo bad 4 +set -- "$ee" +[ ${#1} = 255 ] || echo bad 5 +[ "${#1}" = 255 ] || echo bad 6 +[ $((${#1})) = 255 ] || echo bad 7 +[ "$((${#1}))" = 255 ] || echo bad 8 diff --git a/bin/1sh/tests/expansion/length6.0 b/bin/1sh/tests/expansion/length6.0 new file mode 100644 index 00000000..cf65ea4c --- /dev/null +++ b/bin/1sh/tests/expansion/length6.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/length6.0 220903 2011-04-20 22:24:54Z jilles $ + +x='!@#$%^&*()[]' +[ ${#x} = 12 ] || echo bad 1 +[ "${#x}" = 12 ] || echo bad 2 +IFS=2 +[ ${#x} = 1 ] || echo bad 3 +[ "${#x}" = 12 ] || echo bad 4 diff --git a/bin/1sh/tests/expansion/length7.0 b/bin/1sh/tests/expansion/length7.0 new file mode 100644 index 00000000..a89ed1c6 --- /dev/null +++ b/bin/1sh/tests/expansion/length7.0 @@ -0,0 +1,14 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/length7.0 221602 2011-05-07 14:32:16Z jilles $ + +unset LC_ALL +LC_CTYPE=en_US.UTF-8 +export LC_CTYPE + +# a umlaut +s=$(printf '\303\244') +# euro sign +s=$s$(printf '\342\202\254') +# some sort of 't' outside BMP +s=$s$(printf '\360\235\225\245') +set -- "$s" +[ ${#s} = 3 ] && [ ${#1} = 3 ] diff --git a/bin/1sh/tests/expansion/length8.0 b/bin/1sh/tests/expansion/length8.0 new file mode 100644 index 00000000..01e52283 --- /dev/null +++ b/bin/1sh/tests/expansion/length8.0 @@ -0,0 +1,14 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/length8.0 221602 2011-05-07 14:32:16Z jilles $ + +unset LC_ALL +LC_CTYPE=en_US.ISO8859-1 +export LC_CTYPE + +# a umlaut +s=$(printf '\303\244') +# euro sign +s=$s$(printf '\342\202\254') +# some sort of 't' outside BMP +s=$s$(printf '\360\235\225\245') +set -- "$s" +[ ${#s} = 9 ] && [ ${#1} = 9 ] diff --git a/bin/1sh/tests/expansion/local1.0 b/bin/1sh/tests/expansion/local1.0 new file mode 100644 index 00000000..722e376f --- /dev/null +++ b/bin/1sh/tests/expansion/local1.0 @@ -0,0 +1,28 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/local1.0 238468 2012-07-15 10:19:43Z jilles $ + +run_test() { + w='@ @' + check() { + [ "$v" = "$w" ] || echo "Expected $w got $v" + } + + local v=$w + check + + HOME=/known/value + check() { + [ "$v" = ~ ] || echo "Expected $HOME got $v" + } + + local v=~ + check + + check() { + [ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v" + } + + local v=x:~ + check +} + +run_test diff --git a/bin/1sh/tests/expansion/local2.0 b/bin/1sh/tests/expansion/local2.0 new file mode 100644 index 00000000..25d7dbf8 --- /dev/null +++ b/bin/1sh/tests/expansion/local2.0 @@ -0,0 +1,34 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/local2.0 238468 2012-07-15 10:19:43Z jilles $ + +run_test() { + w='@ @' + check() { + [ "$v" = "$w" ] || echo "Expected $w got $v" + } + + command local v=$w + check + command command local v=$w + check + + HOME=/known/value + check() { + [ "$v" = ~ ] || echo "Expected $HOME got $v" + } + + command local v=~ + check + command command local v=~ + check + + check() { + [ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v" + } + + command local v=x:~ + check + command command local v=x:~ + check +} + +run_test diff --git a/bin/1sh/tests/expansion/pathname1.0 b/bin/1sh/tests/expansion/pathname1.0 new file mode 100644 index 00000000..b1daef58 --- /dev/null +++ b/bin/1sh/tests/expansion/pathname1.0 @@ -0,0 +1,65 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/pathname1.0 302937 2016-07-16 13:26:18Z ache $ + +unset LC_ALL +LC_COLLATE=C +export LC_COLLATE + +failures=0 + +check() { + testcase=$1 + expect=$2 + eval "set -- $testcase" + actual="$*" + if [ "$actual" != "$expect" ]; then + failures=$((failures+1)) + printf '%s\n' "For $testcase, expected $expect actual $actual" + fi +} + +set -e +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) +trap 'rm -rf $T' 0 +cd -P $T + +mkdir testdir testdir2 'testdir/*' 'testdir/?' testdir/a testdir/b testdir2/b +mkdir testdir2/.c +touch testf 'testdir/*/1' 'testdir/?/1' testdir/a/1 testdir/b/1 testdir2/b/.a + +check '' '' +check 'testdir/b' 'testdir/b' +check 'testdir/c' 'testdir/c' +check '\*' '*' +check '\?' '?' +check '*' 'testdir testdir2 testf' +check '*""' 'testdir testdir2 testf' +check '""*' 'testdir testdir2 testf' +check '*/' 'testdir/ testdir2/' +check 'testdir*/a' 'testdir/a' +check 'testdir*/b' 'testdir/b testdir2/b' +check '*/.c' 'testdir2/.c' +check 'testdir2/*' 'testdir2/b' +check 'testdir2/b/*' 'testdir2/b/*' +check 'testdir/*' 'testdir/* testdir/? testdir/a testdir/b' +check 'testdir/*/1' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1' +check '"testdir/"*/1' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1' +check 'testdir/\*/*' 'testdir/*/1' +check 'testdir/\?/*' 'testdir/?/1' +check 'testdir/"?"/*' 'testdir/?/1' +check '"testdir"/"?"/*' 'testdir/?/1' +check '"testdir"/"?"*/*' 'testdir/?/1' +check '"testdir"/*"?"/*' 'testdir/?/1' +check '"testdir/?"*/*' 'testdir/?/1' +check 'testdir/\*/' 'testdir/*/' +check 'testdir/\?/' 'testdir/?/' +check 'testdir/"?"/' 'testdir/?/' +check '"testdir"/"?"/' 'testdir/?/' +check '"testdir"/"?"*/' 'testdir/?/' +check '"testdir"/*"?"/' 'testdir/?/' +check '"testdir/?"*/' 'testdir/?/' +check 'testdir/[*]/' 'testdir/*/' +check 'testdir/[?]/' 'testdir/?/' +check 'testdir/[*?]/' 'testdir/*/ testdir/?/' +check '[tz]estdir/[*]/' 'testdir/*/' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/pathname2.0 b/bin/1sh/tests/expansion/pathname2.0 new file mode 100644 index 00000000..e4ee5be5 --- /dev/null +++ b/bin/1sh/tests/expansion/pathname2.0 @@ -0,0 +1,35 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/pathname2.0 302937 2016-07-16 13:26:18Z ache $ + +unset LC_ALL +LC_COLLATE=C +export LC_COLLATE + +failures=0 + +check() { + testcase=$1 + expect=$2 + eval "set -- $testcase" + actual="$*" + if [ "$actual" != "$expect" ]; then + failures=$((failures+1)) + printf '%s\n' "For $testcase, expected $expect actual $actual" + fi +} + +set -e +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) +trap 'rm -rf $T' 0 +cd -P $T + +mkdir testdir testdir2 'testdir/*' 'testdir/?' testdir/a testdir/b testdir2/b +mkdir testdir2/.c +touch testf 'testdir/*/1' 'testdir/?/1' testdir/a/1 testdir/b/1 testdir2/b/.a + +check '*\/' 'testdir/ testdir2/' +check '"testdir/"*"/1"' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1' +check '"testdir/"*"/"*' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1' +check '"testdir/"*\/*' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1' +check '"testdir"*"/"*"/"*' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/pathname3.0 b/bin/1sh/tests/expansion/pathname3.0 new file mode 100644 index 00000000..f291eea5 --- /dev/null +++ b/bin/1sh/tests/expansion/pathname3.0 @@ -0,0 +1,29 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/pathname3.0 211155 2010-08-10 22:45:59Z jilles $ + +v=12345678 +v=$v$v$v$v +v=$v$v$v$v +v=$v$v$v$v +v=$v$v$v$v +v=$v$v$v$v +# 8192 bytes +v=${v##???} +[ /*/$v = "/*/$v" ] || exit 1 + +s=//// +s=$s$s$s$s +s=$s$s$s$s +s=$s$s$s$s +s=$s$s$s$s +# 1024 bytes +s=${s##??????????} +[ /var/empt[y]/$s/$v = "/var/empt[y]/$s/$v" ] || exit 2 +while [ ${#s} -lt 1034 ]; do + set -- /.${s}et[c] + [ ${#s} -gt 1018 ] || [ "$1" = /.${s}etc ] || exit 3 + set -- /.${s}et[c]/ + [ ${#s} -gt 1017 ] || [ "$1" = /.${s}etc/ ] || exit 4 + set -- /.${s}et[c]/. + [ ${#s} -gt 1016 ] || [ "$1" = /.${s}etc/. ] || exit 5 + s=$s/ +done diff --git a/bin/1sh/tests/expansion/pathname4.0 b/bin/1sh/tests/expansion/pathname4.0 new file mode 100644 index 00000000..e3cabf4c --- /dev/null +++ b/bin/1sh/tests/expansion/pathname4.0 @@ -0,0 +1,28 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/pathname4.0 211646 2010-08-22 21:18:21Z jilles $ + +failures=0 + +check() { + testcase=$1 + expect=$2 + eval "set -- $testcase" + actual="$*" + if [ "$actual" != "$expect" ]; then + failures=$((failures+1)) + printf '%s\n' "For $testcase, expected $expect actual $actual" + fi +} + +set -e +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) +trap 'rm -rf $T' 0 +cd -P $T + +mkdir !!a +touch !!a/fff + +chmod u-r . +check '!!a/ff*' '!!a/fff' +chmod u+r . + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/pathname5.0 b/bin/1sh/tests/expansion/pathname5.0 new file mode 100644 index 00000000..ad105468 --- /dev/null +++ b/bin/1sh/tests/expansion/pathname5.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/pathname5.0 278806 2015-02-15 19:48:29Z jilles $ + +[ `echo '/[e]tc'` = /etc ] diff --git a/bin/1sh/tests/expansion/pathname6.0 b/bin/1sh/tests/expansion/pathname6.0 new file mode 100644 index 00000000..2cf6f07d --- /dev/null +++ b/bin/1sh/tests/expansion/pathname6.0 @@ -0,0 +1,29 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/pathname6.0 302937 2016-07-16 13:26:18Z ache $ + +unset LC_ALL +LC_COLLATE=en_US.US-ASCII +export LC_COLLATE + +failures=0 + +check() { + testcase=$1 + expect=$2 + eval "set -- $testcase" + actual="$*" + if [ "$actual" != "$expect" ]; then + failures=$((failures+1)) + printf '%s\n' "For $testcase, expected $expect actual $actual" + fi +} + +set -e +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) +trap 'rm -rf $T' 0 +cd -P $T + +touch A B a b + +check '*' 'a A b B' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/expansion/plus-minus1.0 b/bin/1sh/tests/expansion/plus-minus1.0 new file mode 100644 index 00000000..41410e44 --- /dev/null +++ b/bin/1sh/tests/expansion/plus-minus1.0 @@ -0,0 +1,76 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/plus-minus1.0 216738 2010-12-27 15:57:41Z emaste $ + +e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}' +h='##' +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'set -- a b' '2|a|b' +testcase 'set --' '0|' +testcase 'set -- ${e}' '0|' +testcase 'set -- "${e}"' '1|' + +testcase 'set -- $p' '1|/etc/' +testcase 'set -- "$p"' '1|/et[c]/' +testcase 'set -- ${s+$p}' '1|/etc/' +testcase 'set -- "${s+$p}"' '1|/et[c]/' +testcase 'set -- ${s+"$p"}' '1|/et[c]/' +# Dquotes in dquotes is undefined for Bourne shell operators +#testcase 'set -- "${s+"$p"}"' '1|/et[c]/' +testcase 'set -- ${e:-$p}' '1|/etc/' +testcase 'set -- "${e:-$p}"' '1|/et[c]/' +testcase 'set -- ${e:-"$p"}' '1|/et[c]/' +# Dquotes in dquotes is undefined for Bourne shell operators +#testcase 'set -- "${e:-"$p"}"' '1|/et[c]/' +testcase 'set -- ${e:+"$e"}' '0|' +testcase 'set -- ${e:+$w"$e"}' '0|' +testcase 'set -- ${w:+"$w"}' '1|a b c' +testcase 'set -- ${w:+$w"$w"}' '3|a|b|ca b c' + +testcase 'set -- "${s+a b}"' '1|a b' +testcase 'set -- "${e:-a b}"' '1|a b' +testcase 'set -- ${e:-\}}' '1|}' +testcase 'set -- ${e:+{}}' '1|}' +testcase 'set -- "${e:+{}}"' '1|}' + +testcase 'set -- ${e+x}${e+x}' '1|xx' +testcase 'set -- "${e+x}"${e+x}' '1|xx' +testcase 'set -- ${e+x}"${e+x}"' '1|xx' +testcase 'set -- "${e+x}${e+x}"' '1|xx' +testcase 'set -- "${e+x}""${e+x}"' '1|xx' + +testcase 'set -- ${e:-${e:-$p}}' '1|/etc/' +testcase 'set -- "${e:-${e:-$p}}"' '1|/et[c]/' +testcase 'set -- ${e:-"${e:-$p}"}' '1|/et[c]/' +testcase 'set -- ${e:-${e:-"$p"}}' '1|/et[c]/' +testcase 'set -- ${e:-${e:-${e:-$w}}}' '3|a|b|c' +testcase 'set -- ${e:-${e:-${e:-"$w"}}}' '1|a b c' +testcase 'set -- ${e:-${e:-"${e:-$w}"}}' '1|a b c' +testcase 'set -- ${e:-"${e:-${e:-$w}}"}' '1|a b c' +testcase 'set -- "${e:-${e:-${e:-$w}}}"' '1|a b c' + +testcase 'shift $#; set -- ${1+"$@"}' '0|' +testcase 'set -- ""; set -- ${1+"$@"}' '1|' +testcase 'set -- "" a; set -- ${1+"$@"}' '2||a' +testcase 'set -- a ""; set -- ${1+"$@"}' '2|a|' +testcase 'set -- a b; set -- ${1+"$@"}' '2|a|b' +testcase 'set -- a\ b; set -- ${1+"$@"}' '1|a b' +testcase 'set -- " " ""; set -- ${1+"$@"}' '2| |' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/plus-minus2.0 b/bin/1sh/tests/expansion/plus-minus2.0 new file mode 100644 index 00000000..2920c9e5 --- /dev/null +++ b/bin/1sh/tests/expansion/plus-minus2.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/plus-minus2.0 206145 2010-04-03 20:55:56Z jilles $ + +e= +test "${e:-\}}" = '}' diff --git a/bin/1sh/tests/expansion/plus-minus3.0 b/bin/1sh/tests/expansion/plus-minus3.0 new file mode 100644 index 00000000..acbc04ae --- /dev/null +++ b/bin/1sh/tests/expansion/plus-minus3.0 @@ -0,0 +1,44 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/plus-minus3.0 206817 2010-04-18 22:13:45Z jilles $ + +e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}' +h='##' +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +# We follow original ash behaviour for quoted ${var+-=?} expansions: +# a double-quote in one switches back to unquoted state. +# This allows expanding a variable as a single word if it is set +# and substituting multiple words otherwise. +# It is also close to the Bourne and Korn shells. +# POSIX leaves this undefined, and various other shells treat +# such double-quotes as introducing a second level of quoting +# which does not do much except quoting close braces. + +testcase 'set -- "${p+"/et[c]/"}"' '1|/etc/' +testcase 'set -- "${p-"/et[c]/"}"' '1|/et[c]/' +testcase 'set -- "${p+"$p"}"' '1|/etc/' +testcase 'set -- "${p-"$p"}"' '1|/et[c]/' +testcase 'set -- "${p+"""/et[c]/"}"' '1|/etc/' +testcase 'set -- "${p-"""/et[c]/"}"' '1|/et[c]/' +testcase 'set -- "${p+"""$p"}"' '1|/etc/' +testcase 'set -- "${p-"""$p"}"' '1|/et[c]/' +testcase 'set -- "${p+"\@"}"' '1|@' +testcase 'set -- "${p+"'\''/et[c]/'\''"}"' '1|/et[c]/' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/plus-minus4.0 b/bin/1sh/tests/expansion/plus-minus4.0 new file mode 100644 index 00000000..980b1785 --- /dev/null +++ b/bin/1sh/tests/expansion/plus-minus4.0 @@ -0,0 +1,38 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/plus-minus4.0 211080 2010-08-08 17:03:23Z jilles $ + +# These may be a bit unclear in the POSIX spec or the proposed revisions, +# and conflict with bash's interpretation, but I think ksh93's interpretation +# makes most sense. In particular, it makes no sense to me that single-quotes +# must match but are not removed. + +e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}' +h='##' +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'set -- ${e:-'"'"'}'"'"'}' '1|}' +testcase "set -- \${e:-\\'}" "1|'" +testcase "set -- \${e:-\\'\\'}" "1|''" +testcase "set -- \"\${e:-'}\"" "1|'" +testcase "set -- \"\${e:-'}'}\"" "1|''}" +testcase "set -- \"\${e:-''}\"" "1|''" +testcase 'set -- ${e:-\a}' '1|a' +testcase 'set -- "${e:-\a}"' '1|\a' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/plus-minus5.0 b/bin/1sh/tests/expansion/plus-minus5.0 new file mode 100644 index 00000000..b8a57f5b --- /dev/null +++ b/bin/1sh/tests/expansion/plus-minus5.0 @@ -0,0 +1,31 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/plus-minus5.0 214492 2010-10-28 22:34:49Z jilles $ + +e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}' +h='##' +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'set -- ${e:-"{x}"}' '1|{x}' +testcase 'set -- "${e:-"{x}"}"' '1|{x}' +testcase 'set -- ${h+"{x}"}' '1|{x}' +testcase 'set -- "${h+"{x}"}"' '1|{x}' +testcase 'set -- ${h:-"{x}"}' '1|##' +testcase 'set -- "${h:-"{x}"}"' '1|##' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/plus-minus6.0 b/bin/1sh/tests/expansion/plus-minus6.0 new file mode 100644 index 00000000..df57df97 --- /dev/null +++ b/bin/1sh/tests/expansion/plus-minus6.0 @@ -0,0 +1,34 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/plus-minus6.0 214512 2010-10-29 13:42:18Z jilles $ + +failures=0 +unset LC_ALL +export LC_CTYPE=en_US.ISO8859-1 +nl=' +' +i=1 +set -f +while [ "$i" -le 255 ]; do + # A different byte still in the range 1..255. + i2=$((i^2+(i==2))) + # Add a character to work around command substitution's removal of + # final newlines, then remove it again. + c=$(printf \\"$(printf %o@ "$i")") + c=${c%@} + c2=$(printf \\"$(printf %o@ "$i2")") + c2=${c2%@} + case $c in + [\'$nl'$}();&|\"`']) c=M + esac + case $c2 in + [\'$nl'$}();&|\"`']) c2=N + esac + IFS=$c + command eval "set -- \${\$+$c2$c$c2$c$c2}" + if [ "$#" -ne 3 ] || [ "$1" != "$c2" ] || [ "$2" != "$c2" ] || + [ "$3" != "$c2" ]; then + echo "Bad results for separator $i (word $i2)" >&2 + : $((failures += 1)) + fi + i=$((i+1)) +done +exit $((failures > 0)) diff --git a/bin/1sh/tests/expansion/plus-minus7.0 b/bin/1sh/tests/expansion/plus-minus7.0 new file mode 100644 index 00000000..e6161700 --- /dev/null +++ b/bin/1sh/tests/expansion/plus-minus7.0 @@ -0,0 +1,26 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/plus-minus7.0 216738 2010-12-27 15:57:41Z emaste $ + +e= s='foo' +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'set -- ${s+a b}' '2|a|b' +testcase 'set -- ${e:-a b}' '2|a|b' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/plus-minus8.0 b/bin/1sh/tests/expansion/plus-minus8.0 new file mode 100644 index 00000000..49385cd7 --- /dev/null +++ b/bin/1sh/tests/expansion/plus-minus8.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/plus-minus8.0 219623 2011-03-13 20:02:39Z jilles $ + +set -- 1 2 3 4 5 6 7 8 9 10 11 12 13 +[ "${#+hi}" = hi ] || echo '${#+hi} wrong' +[ "${#-hi}" = 13 ] || echo '${#-hi} wrong' diff --git a/bin/1sh/tests/expansion/plus-minus9.0 b/bin/1sh/tests/expansion/plus-minus9.0 new file mode 100644 index 00000000..30f754a1 --- /dev/null +++ b/bin/1sh/tests/expansion/plus-minus9.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/plus-minus9.0 333927 2018-05-20 17:25:52Z jilles $ + +a=1 +b=${a+ +} +n=' +' +[ "$b" = "$n" ] diff --git a/bin/1sh/tests/expansion/question1.0 b/bin/1sh/tests/expansion/question1.0 new file mode 100644 index 00000000..8795be66 --- /dev/null +++ b/bin/1sh/tests/expansion/question1.0 @@ -0,0 +1,22 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/question1.0 213738 2010-10-12 18:20:38Z obrien $ + +x=a\ b +[ "$x" = "${x?}" ] || exit 1 +set -- ${x?} +{ [ "$#" = 2 ] && [ "$1" = a ] && [ "$2" = b ]; } || exit 1 +unset x +(echo ${x?abcdefg}) 2>&1 | grep -q abcdefg || exit 1 +${SH} -c 'unset foo; echo ${foo?}' 2>/dev/null && exit 1 +${SH} -c 'foo=; echo ${foo:?}' 2>/dev/null && exit 1 +${SH} -c 'foo=; echo ${foo?}' >/dev/null || exit 1 +${SH} -c 'foo=1; echo ${foo:?}' >/dev/null || exit 1 +${SH} -c 'echo ${!?}' 2>/dev/null && exit 1 +${SH} -c ':& echo ${!?}' >/dev/null || exit 1 +${SH} -c 'echo ${#?}' >/dev/null || exit 1 +${SH} -c 'echo ${*?}' 2>/dev/null && exit 1 +${SH} -c 'echo ${*?}' ${SH} x >/dev/null || exit 1 +${SH} -c 'echo ${1?}' 2>/dev/null && exit 1 +${SH} -c 'echo ${1?}' ${SH} x >/dev/null || exit 1 +${SH} -c 'echo ${2?}' ${SH} x 2>/dev/null && exit 1 +${SH} -c 'echo ${2?}' ${SH} x y >/dev/null || exit 1 +exit 0 diff --git a/bin/1sh/tests/expansion/question2.0 b/bin/1sh/tests/expansion/question2.0 new file mode 100644 index 00000000..abf5f99a --- /dev/null +++ b/bin/1sh/tests/expansion/question2.0 @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/question2.0 341767 2018-12-09 19:14:21Z jilles $ + +unset dummyvar +msg=`(: ${dummyvar?}) 2>&1` +r=$? +[ "$r" != 0 ] && case $msg in +*dummyvar?* | *?dummyvar*) : ;; +*) + printf 'Bad message: [%s]\n' "$msg" + exit 1 +esac diff --git a/bin/1sh/tests/expansion/readonly1.0 b/bin/1sh/tests/expansion/readonly1.0 new file mode 100644 index 00000000..7a8f7335 --- /dev/null +++ b/bin/1sh/tests/expansion/readonly1.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/readonly1.0 238468 2012-07-15 10:19:43Z jilles $ + +w='@ @' + +v=0 HOME=/known/value +readonly v=~:~/:$w +[ "$v" = "$HOME:$HOME/:$w" ] || echo "Expected $HOME/:$w got $v" diff --git a/bin/1sh/tests/expansion/redir1.0 b/bin/1sh/tests/expansion/redir1.0 new file mode 100644 index 00000000..fbbb2088 --- /dev/null +++ b/bin/1sh/tests/expansion/redir1.0 @@ -0,0 +1,26 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/redir1.0 273920 2014-10-31 22:28:10Z jilles $ + +bad=0 +for i in 0 1 2 3; do + for j in 0 1 2 3 4 5 6 7; do + for k in 0 1 2 3 4 5 6 7; do + case $i$j$k in + 000) continue ;; + esac + set -- "$(printf \\$i$j$k@)" + set -- "${1%@}" + ff= + for f in /dev/null /dev/zero /; do + if [ -e "$f" ] && [ ! -e "$f$1" ]; then + ff=$f + fi + done + [ -n "$ff" ] || continue + if { true <$ff$1; } 2>/dev/null; then + echo "Bad: $i$j$k ($ff)" >&2 + : $((bad += 1)) + fi + done + done +done +exit $((bad ? 2 : 0)) diff --git a/bin/1sh/tests/expansion/set-u1.0 b/bin/1sh/tests/expansion/set-u1.0 new file mode 100644 index 00000000..08ad6118 --- /dev/null +++ b/bin/1sh/tests/expansion/set-u1.0 @@ -0,0 +1,29 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/set-u1.0 213738 2010-10-12 18:20:38Z obrien $ + +${SH} -uc 'unset foo; echo $foo' 2>/dev/null && exit 1 +${SH} -uc 'foo=; echo $foo' >/dev/null || exit 1 +${SH} -uc 'foo=1; echo $foo' >/dev/null || exit 1 +# -/+/= are unaffected by set -u +${SH} -uc 'unset foo; echo ${foo-}' >/dev/null || exit 1 +${SH} -uc 'unset foo; echo ${foo+}' >/dev/null || exit 1 +${SH} -uc 'unset foo; echo ${foo=}' >/dev/null || exit 1 +# length/trimming are affected +${SH} -uc 'unset foo; echo ${#foo}' 2>/dev/null && exit 1 +${SH} -uc 'foo=; echo ${#foo}' >/dev/null || exit 1 +${SH} -uc 'unset foo; echo ${foo#?}' 2>/dev/null && exit 1 +${SH} -uc 'foo=1; echo ${foo#?}' >/dev/null || exit 1 +${SH} -uc 'unset foo; echo ${foo##?}' 2>/dev/null && exit 1 +${SH} -uc 'foo=1; echo ${foo##?}' >/dev/null || exit 1 +${SH} -uc 'unset foo; echo ${foo%?}' 2>/dev/null && exit 1 +${SH} -uc 'foo=1; echo ${foo%?}' >/dev/null || exit 1 +${SH} -uc 'unset foo; echo ${foo%%?}' 2>/dev/null && exit 1 +${SH} -uc 'foo=1; echo ${foo%%?}' >/dev/null || exit 1 + +${SH} -uc 'echo $!' 2>/dev/null && exit 1 +${SH} -uc ':& echo $!' >/dev/null || exit 1 +${SH} -uc 'echo $#' >/dev/null || exit 1 +${SH} -uc 'echo $1' 2>/dev/null && exit 1 +${SH} -uc 'echo $1' ${SH} x >/dev/null || exit 1 +${SH} -uc 'echo $2' ${SH} x 2>/dev/null && exit 1 +${SH} -uc 'echo $2' ${SH} x y >/dev/null || exit 1 +exit 0 diff --git a/bin/1sh/tests/expansion/set-u2.0 b/bin/1sh/tests/expansion/set-u2.0 new file mode 100644 index 00000000..fff4e09f --- /dev/null +++ b/bin/1sh/tests/expansion/set-u2.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/set-u2.0 198454 2009-10-24 21:20:04Z jilles $ + +set -u +: $* $@ "$@" "$*" +set -- x +: $* $@ "$@" "$*" +shift $# +: $* $@ "$@" "$*" +set -- y +set -- +: $* $@ "$@" "$*" +exit 0 diff --git a/bin/1sh/tests/expansion/set-u3.0 b/bin/1sh/tests/expansion/set-u3.0 new file mode 100644 index 00000000..c68a4240 --- /dev/null +++ b/bin/1sh/tests/expansion/set-u3.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/set-u3.0 221463 2011-05-04 22:12:22Z jilles $ + +set -u +unset x +v=$( (eval ': $((x))') 2>&1 >/dev/null) +[ $? -ne 0 ] && [ -n "$v" ] diff --git a/bin/1sh/tests/expansion/tilde1.0 b/bin/1sh/tests/expansion/tilde1.0 new file mode 100644 index 00000000..9623209a --- /dev/null +++ b/bin/1sh/tests/expansion/tilde1.0 @@ -0,0 +1,56 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/tilde1.0 206149 2010-04-03 21:56:24Z jilles $ + +HOME=/tmp +roothome=~root +if [ "$roothome" = "~root" ]; then + echo "~root is not expanded!" + exit 2 +fi + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'set -- ~' '1|/tmp' +testcase 'set -- ~/foo' '1|/tmp/foo' +testcase 'set -- x~' '1|x~' +testcase 'set -- ~root' "1|$roothome" +h=~ +testcase 'set -- "$h"' '1|/tmp' +ooIFS=$IFS +IFS=m +testcase 'set -- ~' '1|/tmp' +testcase 'set -- ~/foo' '1|/tmp/foo' +testcase 'set -- $h' '2|/t|p' +IFS=$ooIFS +t=\~ +testcase 'set -- $t' '1|~' +r=$(cat <<EOF +~ +EOF +) +testcase 'set -- $r' '1|~' +r=$(cat <<EOF +${t+~} +EOF +) +testcase 'set -- $r' '1|~' +r=$(cat <<EOF +${t+~/.} +EOF +) +testcase 'set -- $r' '1|~/.' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/tilde2.0 b/bin/1sh/tests/expansion/tilde2.0 new file mode 100644 index 00000000..6313b4dc --- /dev/null +++ b/bin/1sh/tests/expansion/tilde2.0 @@ -0,0 +1,90 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/tilde2.0 206150 2010-04-03 22:04:44Z jilles $ + +HOME=/tmp +roothome=~root +if [ "$roothome" = "~root" ]; then + echo "~root is not expanded!" + exit 2 +fi + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'set -- ${$+~}' '1|/tmp' +testcase 'set -- ${$+~/}' '1|/tmp/' +testcase 'set -- ${$+~/foo}' '1|/tmp/foo' +testcase 'set -- ${$+x~}' '1|x~' +testcase 'set -- ${$+~root}' "1|$roothome" +testcase 'set -- ${$+"~"}' '1|~' +testcase 'set -- ${$+"~/"}' '1|~/' +testcase 'set -- ${$+"~/foo"}' '1|~/foo' +testcase 'set -- ${$+"x~"}' '1|x~' +testcase 'set -- ${$+"~root"}' "1|~root" +testcase 'set -- "${$+~}"' '1|~' +testcase 'set -- "${$+~/}"' '1|~/' +testcase 'set -- "${$+~/foo}"' '1|~/foo' +testcase 'set -- "${$+x~}"' '1|x~' +testcase 'set -- "${$+~root}"' "1|~root" +testcase 'set -- ${HOME#~}' '0|' +h=~ +testcase 'set -- "$h"' '1|/tmp' +f=~/foo +testcase 'set -- "$f"' '1|/tmp/foo' +testcase 'set -- ${f#~}' '1|/foo' +testcase 'set -- ${f#~/}' '1|foo' + +ooIFS=$IFS +IFS=m +testcase 'set -- ${$+~}' '1|/tmp' +testcase 'set -- ${$+~/foo}' '1|/tmp/foo' +testcase 'set -- ${$+$h}' '2|/t|p' +testcase 'set -- ${HOME#~}' '0|' +IFS=$ooIFS + +t=\~ +testcase 'set -- ${$+$t}' '1|~' +r=$(cat <<EOF +${HOME#~} +EOF +) +testcase 'set -- $r' '0|' +r=$(cat <<EOF +${HOME#'~'} +EOF +) +testcase 'set -- $r' '1|/tmp' +r=$(cat <<EOF +${t#'~'} +EOF +) +testcase 'set -- $r' '0|' +r=$(cat <<EOF +${roothome#~root} +EOF +) +testcase 'set -- $r' '0|' +r=$(cat <<EOF +${f#~} +EOF +) +testcase 'set -- $r' '1|/foo' +r=$(cat <<EOF +${f#~/} +EOF +) +testcase 'set -- $r' '1|foo' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/trim1.0 b/bin/1sh/tests/expansion/trim1.0 new file mode 100644 index 00000000..13bc5b6d --- /dev/null +++ b/bin/1sh/tests/expansion/trim1.0 @@ -0,0 +1,85 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/trim1.0 206143 2010-04-03 20:14:10Z jilles $ + +e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}' +h='##' +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'set -- ${t%t}' '1|texttex' +testcase 'set -- "${t%t}"' '1|texttex' +testcase 'set -- ${t%e*}' '1|textt' +testcase 'set -- "${t%e*}"' '1|textt' +testcase 'set -- ${t%%e*}' '1|t' +testcase 'set -- "${t%%e*}"' '1|t' +testcase 'set -- ${t%%*}' '0|' +testcase 'set -- "${t%%*}"' '1|' +testcase 'set -- ${t#t}' '1|exttext' +testcase 'set -- "${t#t}"' '1|exttext' +testcase 'set -- ${t#*x}' '1|ttext' +testcase 'set -- "${t#*x}"' '1|ttext' +testcase 'set -- ${t##*x}' '1|t' +testcase 'set -- "${t##*x}"' '1|t' +testcase 'set -- ${t##*}' '0|' +testcase 'set -- "${t##*}"' '1|' +testcase 'set -- ${t%e$a}' '1|textt' + +set -f +testcase 'set -- ${s%[?]*}' '1|ast*que' +testcase 'set -- "${s%[?]*}"' '1|ast*que' +testcase 'set -- ${s%[*]*}' '1|ast' +testcase 'set -- "${s%[*]*}"' '1|ast' +set +f + +testcase 'set -- $b' '1|{{(#)}}' +testcase 'set -- ${b%\}}' '1|{{(#)}' +testcase 'set -- ${b#{}' '1|{(#)}}' +testcase 'set -- "${b#{}"' '1|{(#)}}' +# Parentheses are special in ksh, check that they can be escaped +testcase 'set -- ${b%\)*}' '1|{{(#' +testcase 'set -- ${b#{}' '1|{(#)}}' +testcase 'set -- $h' '1|##' +testcase 'set -- ${h#\#}' '1|#' +testcase 'set -- ${h###}' '1|#' +testcase 'set -- "${h###}"' '1|#' +testcase 'set -- ${h%#}' '1|#' +testcase 'set -- "${h%#}"' '1|#' + +set -f +testcase 'set -- ${s%"${s#?}"}' '1|a' +testcase 'set -- ${s%"${s#????}"}' '1|ast*' +testcase 'set -- ${s%"${s#????????}"}' '1|ast*que?' +testcase 'set -- ${s#"${s%?}"}' '1|n' +testcase 'set -- ${s#"${s%????}"}' '1|?non' +testcase 'set -- ${s#"${s%????????}"}' '1|*que?non' +set +f +testcase 'set -- "${s%"${s#?}"}"' '1|a' +testcase 'set -- "${s%"${s#????}"}"' '1|ast*' +testcase 'set -- "${s%"${s#????????}"}"' '1|ast*que?' +testcase 'set -- "${s#"${s%?}"}"' '1|n' +testcase 'set -- "${s#"${s%????}"}"' '1|?non' +testcase 'set -- "${s#"${s%????????}"}"' '1|*que?non' +testcase 'set -- ${p#${p}}' '1|/etc/' +testcase 'set -- "${p#${p}}"' '1|/et[c]/' +testcase 'set -- ${p#*[[]}' '1|c]/' +testcase 'set -- "${p#*[[]}"' '1|c]/' +testcase 'set -- ${p#*\[}' '1|c]/' +testcase 'set -- ${p#*"["}' '1|c]/' +testcase 'set -- "${p#*"["}"' '1|c]/' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/trim10.0 b/bin/1sh/tests/expansion/trim10.0 new file mode 100644 index 00000000..177bfe41 --- /dev/null +++ b/bin/1sh/tests/expansion/trim10.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/trim10.0 333927 2018-05-20 17:25:52Z jilles $ + +a='z +' +b=${a% +} +[ "$b" = z ] diff --git a/bin/1sh/tests/expansion/trim11.0 b/bin/1sh/tests/expansion/trim11.0 new file mode 100644 index 00000000..4dd05ee6 --- /dev/null +++ b/bin/1sh/tests/expansion/trim11.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/trim11.0 333927 2018-05-20 17:25:52Z jilles $ + +a='z +' +b="${a% +}" +[ "$b" = z ] diff --git a/bin/1sh/tests/expansion/trim2.0 b/bin/1sh/tests/expansion/trim2.0 new file mode 100644 index 00000000..4c0d3778 --- /dev/null +++ b/bin/1sh/tests/expansion/trim2.0 @@ -0,0 +1,55 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/trim2.0 206147 2010-04-03 21:07:50Z jilles $ + +e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}' +h='##' +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +set -f +testcase 'set -- $s' '1|ast*que?non' +testcase 'set -- ${s%\?*}' '1|ast*que' +testcase 'set -- "${s%\?*}"' '1|ast*que' +testcase 'set -- ${s%\**}' '1|ast' +testcase 'set -- "${s%\**}"' '1|ast' +testcase 'set -- ${s%"$q"*}' '1|ast*que' +testcase 'set -- "${s%"$q"*}"' '1|ast*que' +testcase 'set -- ${s%"$a"*}' '1|ast' +testcase 'set -- "${s%"$a"*}"' '1|ast' +testcase 'set -- ${s%"$q"$a}' '1|ast*que' +testcase 'set -- "${s%"$q"$a}"' '1|ast*que' +testcase 'set -- ${s%"$a"$a}' '1|ast' +testcase 'set -- "${s%"$a"$a}"' '1|ast' +set +f + +testcase 'set -- "${b%\}}"' '1|{{(#)}' +# Parentheses are special in ksh, check that they can be escaped +testcase 'set -- "${b%\)*}"' '1|{{(#' +testcase 'set -- "${h#\#}"' '1|#' + +testcase 'set -- ${p%"${p#?}"}' '1|/' +testcase 'set -- ${p%"${p#??????}"}' '1|/etc' +testcase 'set -- ${p%"${p#???????}"}' '1|/etc/' +testcase 'set -- "${p%"${p#?}"}"' '1|/' +testcase 'set -- "${p%"${p#??????}"}"' '1|/et[c]' +testcase 'set -- "${p%"${p#???????}"}"' '1|/et[c]/' +testcase 'set -- ${p#"${p}"}' '0|' +testcase 'set -- "${p#"${p}"}"' '1|' +testcase 'set -- "${p#*\[}"' '1|c]/' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/trim3.0 b/bin/1sh/tests/expansion/trim3.0 new file mode 100644 index 00000000..bc9cd5f0 --- /dev/null +++ b/bin/1sh/tests/expansion/trim3.0 @@ -0,0 +1,46 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/trim3.0 207127 2010-04-23 17:26:49Z jilles $ + +e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}' +h='##' c='\\\\' +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +# This doesn't make much sense, but it fails in dash so I'm adding it here: +testcase 'set -- "${w%${w#???}}"' '1|a b' + +testcase 'set -- ${p#/et[}' '1|c]/' +testcase 'set -- "${p#/et[}"' '1|c]/' +testcase 'set -- "${p%${p#????}}"' '1|/et[' + +testcase 'set -- ${b%'\'}\''}' '1|{{(#)}' + +testcase 'set -- ${c#\\}' '1|\\\' +testcase 'set -- ${c#\\\\}' '1|\\' +testcase 'set -- ${c#\\\\\\}' '1|\' +testcase 'set -- ${c#\\\\\\\\}' '0|' +testcase 'set -- "${c#\\}"' '1|\\\' +testcase 'set -- "${c#\\\\}"' '1|\\' +testcase 'set -- "${c#\\\\\\}"' '1|\' +testcase 'set -- "${c#\\\\\\\\}"' '1|' +testcase 'set -- "${c#"$c"}"' '1|' +testcase 'set -- ${c#"$c"}' '0|' +testcase 'set -- "${c%"$c"}"' '1|' +testcase 'set -- ${c%"$c"}' '0|' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/trim4.0 b/bin/1sh/tests/expansion/trim4.0 new file mode 100644 index 00000000..77986240 --- /dev/null +++ b/bin/1sh/tests/expansion/trim4.0 @@ -0,0 +1,15 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/trim4.0 213814 2010-10-13 23:29:09Z obrien $ + +v1=/homes/SOME_USER +v2= +v3=C123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + +# Trigger bug in VSTRIMRIGHT processing STADJUST() call in expand.c:subevalvar() +while [ ${#v2} -lt 2000 ]; do + v4="${v2} ${v1%/*} $v3" + if [ ${#v4} -ne $((${#v2} + ${#v3} + 8)) ]; then + echo bad: ${#v4} -ne $((${#v2} + ${#v3} + 8)) + fi + v2=x$v2 + v3=y$v3 +done diff --git a/bin/1sh/tests/expansion/trim5.0 b/bin/1sh/tests/expansion/trim5.0 new file mode 100644 index 00000000..cc3a477f --- /dev/null +++ b/bin/1sh/tests/expansion/trim5.0 @@ -0,0 +1,28 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/trim5.0 214490 2010-10-28 21:51:14Z jilles $ + +e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}' +h='##' +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'set -- "${b%'\'}\''}"' '1|{{(#)}' +testcase 'set -- ${b%"}"}' '1|{{(#)}' +testcase 'set -- "${b%"}"}"' '1|{{(#)}' + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/trim6.0 b/bin/1sh/tests/expansion/trim6.0 new file mode 100644 index 00000000..7a080018 --- /dev/null +++ b/bin/1sh/tests/expansion/trim6.0 @@ -0,0 +1,22 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/trim6.0 214524 2010-10-29 19:34:57Z jilles $ + +e= +for i in 0 1 2 3; do + for j in 0 1 2 3 4 5 6 7; do + for k in 0 1 2 3 4 5 6 7; do + case $i$j$k in + 000) continue ;; + esac + e="$e\\$i$j$k" + done + done +done +e=$(printf "$e") +v=@$e@$e@ +y=${v##*"$e"} +yq="${v##*"$e"}" +[ "$y" = @ ] || echo "error when unquoted in non-splitting context" +[ "$yq" = @ ] || echo "error when quoted in non-splitting context" +[ "${v##*"$e"}" = @ ] || echo "error when quoted in splitting context" +IFS= +[ ${v##*"$e"} = @ ] || echo "error when unquoted in splitting context" diff --git a/bin/1sh/tests/expansion/trim7.0 b/bin/1sh/tests/expansion/trim7.0 new file mode 100644 index 00000000..61ca3ab2 --- /dev/null +++ b/bin/1sh/tests/expansion/trim7.0 @@ -0,0 +1,16 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/trim7.0 219623 2011-03-13 20:02:39Z jilles $ + +set -- 1 2 3 4 5 6 7 8 9 10 11 12 13 +[ "${##1}" = 3 ] || echo '${##1} wrong' +[ "${###1}" = 3 ] || echo '${###1} wrong' +[ "${###}" = 13 ] || echo '${###} wrong' +[ "${#%3}" = 1 ] || echo '${#%3} wrong' +[ "${#%%3}" = 1 ] || echo '${#%%3} wrong' +[ "${#%%}" = 13 ] || echo '${#%%} wrong' +set -- +[ "${##0}" = "" ] || echo '${##0} wrong' +[ "${###0}" = "" ] || echo '${###0} wrong' +[ "${###}" = 0 ] || echo '${###} wrong' +[ "${#%0}" = "" ] || echo '${#%0} wrong' +[ "${#%%0}" = "" ] || echo '${#%%0} wrong' +[ "${#%%}" = 0 ] || echo '${#%%} wrong' diff --git a/bin/1sh/tests/expansion/trim8.0 b/bin/1sh/tests/expansion/trim8.0 new file mode 100644 index 00000000..7fd7d052 --- /dev/null +++ b/bin/1sh/tests/expansion/trim8.0 @@ -0,0 +1,75 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/trim8.0 221646 2011-05-08 11:32:20Z jilles $ + +unset LC_ALL +LC_CTYPE=en_US.UTF-8 +export LC_CTYPE + +c1=e +# a umlaut +c2=$(printf '\303\244') +# euro sign +c3=$(printf '\342\202\254') +# some sort of 't' outside BMP +c4=$(printf '\360\235\225\245') + +s=$c1$c2$c3$c4 + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'set -- "$s"' "1|$s" +testcase 'set -- "${s#$c2}"' "1|$s" +testcase 'set -- "${s#*}"' "1|$s" +testcase 'set -- "${s#$c1}"' "1|$c2$c3$c4" +testcase 'set -- "${s#$c1$c2}"' "1|$c3$c4" +testcase 'set -- "${s#$c1$c2$c3}"' "1|$c4" +testcase 'set -- "${s#$c1$c2$c3$c4}"' "1|" +testcase 'set -- "${s#?}"' "1|$c2$c3$c4" +testcase 'set -- "${s#??}"' "1|$c3$c4" +testcase 'set -- "${s#???}"' "1|$c4" +testcase 'set -- "${s#????}"' "1|" +testcase 'set -- "${s#*$c3}"' "1|$c4" +testcase 'set -- "${s%$c4}"' "1|$c1$c2$c3" +testcase 'set -- "${s%$c3$c4}"' "1|$c1$c2" +testcase 'set -- "${s%$c2$c3$c4}"' "1|$c1" +testcase 'set -- "${s%$c1$c2$c3$c4}"' "1|" +testcase 'set -- "${s%?}"' "1|$c1$c2$c3" +testcase 'set -- "${s%??}"' "1|$c1$c2" +testcase 'set -- "${s%???}"' "1|$c1" +testcase 'set -- "${s%????}"' "1|" +testcase 'set -- "${s%$c2*}"' "1|$c1" +testcase 'set -- "${s##$c2}"' "1|$s" +testcase 'set -- "${s##*}"' "1|" +testcase 'set -- "${s##$c1}"' "1|$c2$c3$c4" +testcase 'set -- "${s##$c1$c2}"' "1|$c3$c4" +testcase 'set -- "${s##$c1$c2$c3}"' "1|$c4" +testcase 'set -- "${s##$c1$c2$c3$c4}"' "1|" +testcase 'set -- "${s##?}"' "1|$c2$c3$c4" +testcase 'set -- "${s##??}"' "1|$c3$c4" +testcase 'set -- "${s##???}"' "1|$c4" +testcase 'set -- "${s##????}"' "1|" +testcase 'set -- "${s##*$c3}"' "1|$c4" +testcase 'set -- "${s%%$c4}"' "1|$c1$c2$c3" +testcase 'set -- "${s%%$c3$c4}"' "1|$c1$c2" +testcase 'set -- "${s%%$c2$c3$c4}"' "1|$c1" +testcase 'set -- "${s%%$c1$c2$c3$c4}"' "1|" +testcase 'set -- "${s%%?}"' "1|$c1$c2$c3" +testcase 'set -- "${s%%??}"' "1|$c1$c2" +testcase 'set -- "${s%%???}"' "1|$c1" +testcase 'set -- "${s%%????}"' "1|" +testcase 'set -- "${s%%$c2*}"' "1|$c1" + +test "x$failures" = x diff --git a/bin/1sh/tests/expansion/trim9.0 b/bin/1sh/tests/expansion/trim9.0 new file mode 100644 index 00000000..e83f3725 --- /dev/null +++ b/bin/1sh/tests/expansion/trim9.0 @@ -0,0 +1,61 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/expansion/trim9.0 292758 2015-12-26 22:27:48Z jilles $ + +# POSIX does not specify these but they occasionally occur in the wild. +# This just serves to keep working what currently works. + +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'shift $#; set -- "${*#Q}"' '1|' +testcase 'shift $#; set -- "${*##Q}"' '1|' +testcase 'shift $#; set -- "${*%Q}"' '1|' +testcase 'shift $#; set -- "${*%%Q}"' '1|' +testcase 'set -- Q R; set -- "${*#Q}"' '1| R' +testcase 'set -- Q R; set -- "${*##Q}"' '1| R' +testcase 'set -- Q R; set -- "${*%R}"' '1|Q ' +testcase 'set -- Q R; set -- "${*%%R}"' '1|Q ' +testcase 'set -- Q R; set -- "${*#S}"' '1|Q R' +testcase 'set -- Q R; set -- "${*##S}"' '1|Q R' +testcase 'set -- Q R; set -- "${*%S}"' '1|Q R' +testcase 'set -- Q R; set -- "${*%%S}"' '1|Q R' +testcase 'set -- Q R; set -- ${*#Q}' '1|R' +testcase 'set -- Q R; set -- ${*##Q}' '1|R' +testcase 'set -- Q R; set -- ${*%R}' '1|Q' +testcase 'set -- Q R; set -- ${*%%R}' '1|Q' +testcase 'set -- Q R; set -- ${*#S}' '2|Q|R' +testcase 'set -- Q R; set -- ${*##S}' '2|Q|R' +testcase 'set -- Q R; set -- ${*%S}' '2|Q|R' +testcase 'set -- Q R; set -- ${*%%S}' '2|Q|R' +testcase 'set -- Q R; set -- ${@#Q}' '1|R' +testcase 'set -- Q R; set -- ${@##Q}' '1|R' +testcase 'set -- Q R; set -- ${@%R}' '1|Q' +testcase 'set -- Q R; set -- ${@%%R}' '1|Q' +testcase 'set -- Q R; set -- ${@#S}' '2|Q|R' +testcase 'set -- Q R; set -- ${@##S}' '2|Q|R' +testcase 'set -- Q R; set -- ${@%S}' '2|Q|R' +testcase 'set -- Q R; set -- ${@%%S}' '2|Q|R' +testcase 'set -- Q R; set -- "${@#Q}"' '2||R' +testcase 'set -- Q R; set -- "${@%R}"' '2|Q|' +testcase 'set -- Q R; set -- "${@%%R}"' '2|Q|' +testcase 'set -- Q R; set -- "${@#S}"' '2|Q|R' +testcase 'set -- Q R; set -- "${@##S}"' '2|Q|R' +testcase 'set -- Q R; set -- "${@%S}"' '2|Q|R' +testcase 'set -- Q R; set -- "${@%%S}"' '2|Q|R' + +test "x$failures" = x diff --git a/bin/1sh/tests/functional_test.sh b/bin/1sh/tests/functional_test.sh new file mode 100755 index 00000000..19d1006a --- /dev/null +++ b/bin/1sh/tests/functional_test.sh @@ -0,0 +1,72 @@ +# +# Copyright 2014 EMC Corp. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT +# OWNER 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. +# +# $FreeBSD: releng/12.1/bin/sh/tests/functional_test.sh 270101 2014-08-17 14:26:12Z jilles $ + +SRCDIR=$(atf_get_srcdir) + +check() +{ + local tc=${1}; shift + + export SH=$(atf_config_get bin.sh.test_shell /bin/sh) + + local err_file="${SRCDIR}/${tc}.stderr" + [ -f "${err_file}" ] && err_flag="-e file:${err_file}" + local out_file="${SRCDIR}/${tc}.stdout" + [ -f "${out_file}" ] && out_flag="-o file:${out_file}" + + atf_check -s exit:${tc##*.} ${err_flag} ${out_flag} ${SH} "${SRCDIR}/${tc}" +} + +add_testcase() +{ + local tc=${1} + local tc_escaped word + + case "${tc%.*}" in + *-*) + local IFS="-" + for word in ${tc%.*}; do + tc_escaped="${tc_escaped:+${tc_escaped}_}${word}" + done + ;; + *) + tc_escaped=${tc%.*} + ;; + esac + + atf_test_case ${tc_escaped} + eval "${tc_escaped}_body() { check ${tc}; }" + atf_add_test_case ${tc_escaped} +} + +atf_init_test_cases() +{ + for path in $(find -Es "${SRCDIR}" -regex '.*\.[0-9]+$'); do + add_testcase ${path##*/} + done +} diff --git a/bin/1sh/tests/invocation/Makefile b/bin/1sh/tests/invocation/Makefile new file mode 100644 index 00000000..095fc46c --- /dev/null +++ b/bin/1sh/tests/invocation/Makefile @@ -0,0 +1,16 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/invocation/Makefile 322455 2017-08-13 14:36:10Z jilles $ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T} + +.PATH: ${.CURDIR:H} +ATF_TESTS_SH= functional_test + +${PACKAGE}FILES+= sh-ac1.0 +${PACKAGE}FILES+= sh-c-missing1.0 +${PACKAGE}FILES+= sh-c1.0 +${PACKAGE}FILES+= sh-ca1.0 +${PACKAGE}FILES+= sh-fca1.0 + +.include <bsd.test.mk> diff --git a/bin/1sh/tests/invocation/Makefile.depend b/bin/1sh/tests/invocation/Makefile.depend new file mode 100644 index 00000000..cbec5fcc --- /dev/null +++ b/bin/1sh/tests/invocation/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/invocation/Makefile.depend 325187 2017-10-31 00:04:07Z bdrewery $ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/1sh/tests/invocation/sh-ac1.0 b/bin/1sh/tests/invocation/sh-ac1.0 new file mode 100644 index 00000000..a0dd1cbb --- /dev/null +++ b/bin/1sh/tests/invocation/sh-ac1.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/invocation/sh-ac1.0 322438 2017-08-12 19:17:48Z jilles $ +# Test that attached options before c are processed + +case `${SH} -ac 'echo $-:$0' moo` in +*a*:moo) true ;; +*) false ;; +esac diff --git a/bin/1sh/tests/invocation/sh-c-missing1.0 b/bin/1sh/tests/invocation/sh-c-missing1.0 new file mode 100644 index 00000000..15bf9488 --- /dev/null +++ b/bin/1sh/tests/invocation/sh-c-missing1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/invocation/sh-c-missing1.0 322455 2017-08-13 14:36:10Z jilles $ + +! echo echo bad | ${SH} -c 2>/dev/null diff --git a/bin/1sh/tests/invocation/sh-c1.0 b/bin/1sh/tests/invocation/sh-c1.0 new file mode 100644 index 00000000..a24e1b44 --- /dev/null +++ b/bin/1sh/tests/invocation/sh-c1.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/invocation/sh-c1.0 322438 2017-08-12 19:17:48Z jilles $ +# Test that -c executes command_string with the given name and arg + +${SH} -c 'echo $0 $@' moo foo | grep -qx -- "moo foo" diff --git a/bin/1sh/tests/invocation/sh-ca1.0 b/bin/1sh/tests/invocation/sh-ca1.0 new file mode 100644 index 00000000..8913e71b --- /dev/null +++ b/bin/1sh/tests/invocation/sh-ca1.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/invocation/sh-ca1.0 322438 2017-08-12 19:17:48Z jilles $ +# Test that attached options after c are processed + +case `${SH} -ca 'echo $-:$0' moo` in +*a*:moo) true ;; +*) false ;; +esac diff --git a/bin/1sh/tests/invocation/sh-fca1.0 b/bin/1sh/tests/invocation/sh-fca1.0 new file mode 100644 index 00000000..c0a5082b --- /dev/null +++ b/bin/1sh/tests/invocation/sh-fca1.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/invocation/sh-fca1.0 322438 2017-08-12 19:17:48Z jilles $ +# Test that attached options before and after c are processed + +case `${SH} -fca 'echo $-:$-:$0:$@' foo -bar` in +*f*:*a*:foo:-bar) true ;; +*) false ;; +esac diff --git a/bin/1sh/tests/parameters/Makefile b/bin/1sh/tests/parameters/Makefile new file mode 100644 index 00000000..71ba0525 --- /dev/null +++ b/bin/1sh/tests/parameters/Makefile @@ -0,0 +1,29 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/Makefile 306843 2016-10-08 13:40:12Z jilles $ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T} + +.PATH: ${.CURDIR:H} +ATF_TESTS_SH= functional_test + +${PACKAGE}FILES+= env1.0 +${PACKAGE}FILES+= exitstatus1.0 +${PACKAGE}FILES+= ifs1.0 +${PACKAGE}FILES+= mail1.0 +${PACKAGE}FILES+= mail2.0 +${PACKAGE}FILES+= optind1.0 +${PACKAGE}FILES+= optind2.0 +${PACKAGE}FILES+= positional1.0 +${PACKAGE}FILES+= positional2.0 +${PACKAGE}FILES+= positional3.0 +${PACKAGE}FILES+= positional4.0 +${PACKAGE}FILES+= positional5.0 +${PACKAGE}FILES+= positional6.0 +${PACKAGE}FILES+= positional7.0 +${PACKAGE}FILES+= positional8.0 +${PACKAGE}FILES+= positional9.0 +${PACKAGE}FILES+= pwd1.0 +${PACKAGE}FILES+= pwd2.0 + +.include <bsd.test.mk> diff --git a/bin/1sh/tests/parameters/Makefile.depend b/bin/1sh/tests/parameters/Makefile.depend new file mode 100644 index 00000000..e37b229c --- /dev/null +++ b/bin/1sh/tests/parameters/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/Makefile.depend 296587 2016-03-09 22:46:01Z bdrewery $ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/1sh/tests/parameters/env1.0 b/bin/1sh/tests/parameters/env1.0 new file mode 100644 index 00000000..cd1c7436 --- /dev/null +++ b/bin/1sh/tests/parameters/env1.0 @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/env1.0 222957 2011-06-10 22:42:00Z jilles $ + +export key='must contain this' +unset x +r=$(ENV="\${x?\$key}" ${SH} -i +m 2>&1 >/dev/null <<\EOF +exit 0 +EOF +) && case $r in +*"$key"*) true ;; +*) false ;; +esac diff --git a/bin/1sh/tests/parameters/exitstatus1.0 b/bin/1sh/tests/parameters/exitstatus1.0 new file mode 100644 index 00000000..584b7958 --- /dev/null +++ b/bin/1sh/tests/parameters/exitstatus1.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/exitstatus1.0 185232 2008-11-23 20:27:03Z stefanf $ +f() { + [ $? = $1 ] || exit 1 +} + +true +f 0 +false +f 1 diff --git a/bin/1sh/tests/parameters/ifs1.0 b/bin/1sh/tests/parameters/ifs1.0 new file mode 100644 index 00000000..3cd37cd9 --- /dev/null +++ b/bin/1sh/tests/parameters/ifs1.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/ifs1.0 306843 2016-10-08 13:40:12Z jilles $ + +env IFS=_ ${SH} -c ' +rc=2 +nosuchtool_function() { + rc=0 +} +v=nosuchtool_function +$v && exit "$rc" +' diff --git a/bin/1sh/tests/parameters/mail1.0 b/bin/1sh/tests/parameters/mail1.0 new file mode 100644 index 00000000..01baf8fd --- /dev/null +++ b/bin/1sh/tests/parameters/mail1.0 @@ -0,0 +1,15 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/mail1.0 213738 2010-10-12 18:20:38Z obrien $ +# Test that a non-interactive shell does not access $MAIL. + +goodfile=/var/empty/sh-test-goodfile +mailfile=/var/empty/sh-test-mailfile +T=$(mktemp sh-test.XXXXXX) || exit +MAIL=$mailfile ktrace -i -f "$T" ${SH} -c "[ -s $goodfile ]" 2>/dev/null +if ! grep -q $goodfile "$T"; then + # ktrace problem + rc=0 +elif ! grep -q $mailfile "$T"; then + rc=0 +fi +rm "$T" +exit ${rc:-3} diff --git a/bin/1sh/tests/parameters/mail2.0 b/bin/1sh/tests/parameters/mail2.0 new file mode 100644 index 00000000..b6524daf --- /dev/null +++ b/bin/1sh/tests/parameters/mail2.0 @@ -0,0 +1,15 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/mail2.0 213738 2010-10-12 18:20:38Z obrien $ +# Test that an interactive shell accesses $MAIL. + +goodfile=/var/empty/sh-test-goodfile +mailfile=/var/empty/sh-test-mailfile +T=$(mktemp sh-test.XXXXXX) || exit +ENV=$goodfile MAIL=$mailfile ktrace -i -f "$T" ${SH} +m -i </dev/null >/dev/null 2>&1 +if ! grep -q $goodfile "$T"; then + # ktrace problem + rc=0 +elif grep -q $mailfile "$T"; then + rc=0 +fi +rm "$T" +exit ${rc:-3} diff --git a/bin/1sh/tests/parameters/optind1.0 b/bin/1sh/tests/parameters/optind1.0 new file mode 100644 index 00000000..947e6aad --- /dev/null +++ b/bin/1sh/tests/parameters/optind1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/optind1.0 227773 2011-11-20 21:48:50Z jilles $ + +unset OPTIND && [ -z "$OPTIND" ] diff --git a/bin/1sh/tests/parameters/optind2.0 b/bin/1sh/tests/parameters/optind2.0 new file mode 100644 index 00000000..3c552346 --- /dev/null +++ b/bin/1sh/tests/parameters/optind2.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/optind2.0 259846 2013-12-24 22:38:24Z jilles $ + +[ "$(OPTIND=42 ${SH} -c 'printf %s "$OPTIND"')" = 1 ] diff --git a/bin/1sh/tests/parameters/positional1.0 b/bin/1sh/tests/parameters/positional1.0 new file mode 100644 index 00000000..71ee1a20 --- /dev/null +++ b/bin/1sh/tests/parameters/positional1.0 @@ -0,0 +1,13 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/positional1.0 222158 2011-05-21 14:52:26Z jilles $ + +set -- a b c d e f g h i j +[ "$1" = a ] || echo "error at line $LINENO" +[ "${1}" = a ] || echo "error at line $LINENO" +[ "${1-foo}" = a ] || echo "error at line $LINENO" +[ "${1+foo}" = foo ] || echo "error at line $LINENO" +[ "$1+foo" = a+foo ] || echo "error at line $LINENO" +[ "$10" = a0 ] || echo "error at line $LINENO" +[ "$100" = a00 ] || echo "error at line $LINENO" +[ "${10}" = j ] || echo "error at line $LINENO" +[ "${10-foo}" = j ] || echo "error at line $LINENO" +[ "${100-foo}" = foo ] || echo "error at line $LINENO" diff --git a/bin/1sh/tests/parameters/positional2.0 b/bin/1sh/tests/parameters/positional2.0 new file mode 100644 index 00000000..94f4ec13 --- /dev/null +++ b/bin/1sh/tests/parameters/positional2.0 @@ -0,0 +1,65 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/positional2.0 228873 2011-12-25 13:24:48Z jilles $ + +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'set -- a b; set -- p$@q' '2|pa|bq' +testcase 'set -- a b; set -- $@q' '2|a|bq' +testcase 'set -- a b; set -- p$@' '2|pa|b' +testcase 'set -- a b; set -- p$@q' '2|pa|bq' +testcase 'set -- a b; set -- $@q' '2|a|bq' +testcase 'set -- a b; set -- p$@' '2|pa|b' +testcase 'set -- a b; set -- p$*q' '2|pa|bq' +testcase 'set -- a b; set -- $*q' '2|a|bq' +testcase 'set -- a b; set -- p$*' '2|pa|b' +testcase 'set -- a b; set -- p$*q' '2|pa|bq' +testcase 'set -- a b; set -- $*q' '2|a|bq' +testcase 'set -- a b; set -- p$*' '2|pa|b' +testcase 'set -- a b; set -- "p$@q"' '2|pa|bq' +testcase 'set -- a b; set -- "$@q"' '2|a|bq' +testcase 'set -- a b; set -- "p$@"' '2|pa|b' +testcase 'set -- a b; set -- p"$@"q' '2|pa|bq' +testcase 'set -- a b; set -- "$@"q' '2|a|bq' +testcase 'set -- a b; set -- p"$@"' '2|pa|b' +testcase 'set -- "" a b; set -- "p$@q"' '3|p|a|bq' +testcase 'set -- "" a b; set -- "$@q"' '3||a|bq' +testcase 'set -- "" a b; set -- "p$@"' '3|p|a|b' +testcase 'set -- "" a b; set -- p"$@"q' '3|p|a|bq' +testcase 'set -- "" a b; set -- "$@"q' '3||a|bq' +testcase 'set -- "" a b; set -- p"$@"' '3|p|a|b' +testcase 'set -- a; set -- p$@q' '1|paq' +testcase 'set -- a; set -- $@q' '1|aq' +testcase 'set -- a; set -- p$@' '1|pa' +testcase 'set -- a; set -- p$@q' '1|paq' +testcase 'set -- a; set -- $@q' '1|aq' +testcase 'set -- a; set -- p$@' '1|pa' +testcase 'set -- a; set -- p$*q' '1|paq' +testcase 'set -- a; set -- $*q' '1|aq' +testcase 'set -- a; set -- p$*' '1|pa' +testcase 'set -- a; set -- p$*q' '1|paq' +testcase 'set -- a; set -- $*q' '1|aq' +testcase 'set -- a; set -- p$*' '1|pa' +testcase 'set -- a; set -- "p$@q"' '1|paq' +testcase 'set -- a; set -- "$@q"' '1|aq' +testcase 'set -- a; set -- "p$@"' '1|pa' +testcase 'set -- a; set -- p"$@"q' '1|paq' +testcase 'set -- a; set -- "$@"q' '1|aq' +testcase 'set -- a; set -- p"$@"' '1|pa' + +test "x$failures" = x diff --git a/bin/1sh/tests/parameters/positional3.0 b/bin/1sh/tests/parameters/positional3.0 new file mode 100644 index 00000000..397fa19c --- /dev/null +++ b/bin/1sh/tests/parameters/positional3.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/positional3.0 268436 2014-07-08 22:04:44Z jilles $ + +r=$(${SH} -c 'echo ${01:+yes}${010:+yes}' '' a '' '' '' '' '' '' '' '' b) +[ "$r" = yesyes ] diff --git a/bin/1sh/tests/parameters/positional4.0 b/bin/1sh/tests/parameters/positional4.0 new file mode 100644 index 00000000..b2a47dce --- /dev/null +++ b/bin/1sh/tests/parameters/positional4.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/positional4.0 268568 2014-07-12 10:27:30Z jilles $ + +set -- "x$0" 2 3 4 5 6 7 8 9 "y$0" +[ "${01}.${010}" = "$1.${10}" ] diff --git a/bin/1sh/tests/parameters/positional5.0 b/bin/1sh/tests/parameters/positional5.0 new file mode 100644 index 00000000..ab918d2f --- /dev/null +++ b/bin/1sh/tests/parameters/positional5.0 @@ -0,0 +1,14 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/positional5.0 268576 2014-07-12 21:54:11Z jilles $ + +i=1 +r=0 +while [ $i -lt $((0x100000000)) ]; do + t= + eval t=\${$i-x} + case $t in + x) ;; + *) echo "Problem with \${$i}" >&2; r=1 ;; + esac + i=$((i + 0x10000000)) +done +exit $r diff --git a/bin/1sh/tests/parameters/positional6.0 b/bin/1sh/tests/parameters/positional6.0 new file mode 100644 index 00000000..e0b46aa2 --- /dev/null +++ b/bin/1sh/tests/parameters/positional6.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/positional6.0 273802 2014-10-28 22:14:31Z jilles $ + +IFS=? +set p r +v=pqrs +r=${v#"$*"} +[ "$r" = pqrs ] diff --git a/bin/1sh/tests/parameters/positional7.0 b/bin/1sh/tests/parameters/positional7.0 new file mode 100644 index 00000000..60a92bf6 --- /dev/null +++ b/bin/1sh/tests/parameters/positional7.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/positional7.0 273802 2014-10-28 22:14:31Z jilles $ + +set -- / '' +IFS=* +set -- "$*" +IFS=: +args="$*" +[ "$#:$args" = "1:/*" ] diff --git a/bin/1sh/tests/parameters/positional8.0 b/bin/1sh/tests/parameters/positional8.0 new file mode 100644 index 00000000..78cfefb2 --- /dev/null +++ b/bin/1sh/tests/parameters/positional8.0 @@ -0,0 +1,31 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/positional8.0 291025 2015-11-18 21:09:03Z jilles $ + +failures='' +ok='' + +testcase() { + code="$1" + expected="$2" + oIFS="$IFS" + eval "$code" + IFS='|' + result="$#|$*" + IFS="$oIFS" + if [ "x$result" = "x$expected" ]; then + ok=x$ok + else + failures=x$failures + echo "For $code, expected $expected actual $result" + fi +} + +testcase 'shift $#; set -- ""$*' '1|' +testcase 'shift $#; set -- $*""' '1|' +testcase 'shift $#; set -- ""$@' '1|' +testcase 'shift $#; set -- $@""' '1|' +testcase 'shift $#; set -- """$*"' '1|' +testcase 'shift $#; set -- "$*"""' '1|' +testcase 'shift $#; set -- """$@"' '1|' +testcase 'shift $#; set -- "$@"""' '1|' + +test "x$failures" = x diff --git a/bin/1sh/tests/parameters/positional9.0 b/bin/1sh/tests/parameters/positional9.0 new file mode 100644 index 00000000..3d1e667d --- /dev/null +++ b/bin/1sh/tests/parameters/positional9.0 @@ -0,0 +1,18 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/positional9.0 291903 2015-12-06 14:09:31Z jilles $ +# Although POSIX leaves the result of expanding ${#@} and ${#*} unspecified, +# make sure it is at least numeric. + +set -- bb cc ddd +set -f +lengths=${#*}${#@}"${#*}${#@}"$(echo ${#*}${#@}"${#*}${#@}") +IFS= +lengths=$lengths${#*}${#@}"${#*}${#@}"$(echo ${#*}${#@}"${#*}${#@}") +case $lengths in +*[!0-9]*) + printf 'bad: %s\n' "$lengths" + exit 3 ;; +????????????????*) ;; +*) + printf 'too short: %s\n' "$lengths" + exit 3 ;; +esac diff --git a/bin/1sh/tests/parameters/pwd1.0 b/bin/1sh/tests/parameters/pwd1.0 new file mode 100644 index 00000000..c88511af --- /dev/null +++ b/bin/1sh/tests/parameters/pwd1.0 @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/pwd1.0 213738 2010-10-12 18:20:38Z obrien $ +# Check that bogus PWD values are not accepted from the environment. + +cd / || exit 3 +failures=0 +[ "$(PWD=foo ${SH} -c 'pwd')" = / ] || : $((failures += 1)) +[ "$(PWD=/var/empty ${SH} -c 'pwd')" = / ] || : $((failures += 1)) +[ "$(PWD=/var/empty/foo ${SH} -c 'pwd')" = / ] || : $((failures += 1)) +[ "$(PWD=/bin/ls ${SH} -c 'pwd')" = / ] || : $((failures += 1)) + +exit $((failures != 0)) diff --git a/bin/1sh/tests/parameters/pwd2.0 b/bin/1sh/tests/parameters/pwd2.0 new file mode 100644 index 00000000..b03f9ace --- /dev/null +++ b/bin/1sh/tests/parameters/pwd2.0 @@ -0,0 +1,24 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parameters/pwd2.0 213738 2010-10-12 18:20:38Z obrien $ +# Check that PWD is exported and accepted from the environment. +set -e + +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) +trap 'rm -rf $T' 0 +cd -P $T +TP=$(pwd) +mkdir test1 +ln -s test1 link +cd link +[ "$PWD" = "$TP/link" ] +[ "$(pwd)" = "$TP/link" ] +[ "$(pwd -P)" = "$TP/test1" ] +[ "$(${SH} -c pwd)" = "$TP/link" ] +[ "$(${SH} -c pwd\ -P)" = "$TP/test1" ] +cd .. +[ "$(pwd)" = "$TP" ] +cd -P link +[ "$PWD" = "$TP/test1" ] +[ "$(pwd)" = "$TP/test1" ] +[ "$(pwd -P)" = "$TP/test1" ] +[ "$(${SH} -c pwd)" = "$TP/test1" ] +[ "$(${SH} -c pwd\ -P)" = "$TP/test1" ] diff --git a/bin/1sh/tests/parser/Makefile b/bin/1sh/tests/parser/Makefile new file mode 100644 index 00000000..44322a35 --- /dev/null +++ b/bin/1sh/tests/parser/Makefile @@ -0,0 +1,89 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/Makefile 333507 2018-05-11 21:56:01Z jilles $ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T} + +.PATH: ${.CURDIR:H} +ATF_TESTS_SH= functional_test + +${PACKAGE}FILES+= alias1.0 +${PACKAGE}FILES+= alias2.0 +${PACKAGE}FILES+= alias3.0 +${PACKAGE}FILES+= alias4.0 +${PACKAGE}FILES+= alias5.0 +${PACKAGE}FILES+= alias6.0 +${PACKAGE}FILES+= alias7.0 +${PACKAGE}FILES+= alias8.0 +${PACKAGE}FILES+= alias9.0 +${PACKAGE}FILES+= alias10.0 +${PACKAGE}FILES+= alias11.0 +${PACKAGE}FILES+= alias12.0 +${PACKAGE}FILES+= alias13.0 +${PACKAGE}FILES+= alias14.0 +${PACKAGE}FILES+= alias15.0 alias15.0.stdout +${PACKAGE}FILES+= alias16.0 +${PACKAGE}FILES+= alias17.0 +${PACKAGE}FILES+= alias18.0 +${PACKAGE}FILES+= and-pipe-not.0 +${PACKAGE}FILES+= case1.0 +${PACKAGE}FILES+= case2.0 +${PACKAGE}FILES+= comment1.0 +${PACKAGE}FILES+= comment2.42 +${PACKAGE}FILES+= dollar-quote1.0 +${PACKAGE}FILES+= dollar-quote2.0 +${PACKAGE}FILES+= dollar-quote3.0 +${PACKAGE}FILES+= dollar-quote4.0 +${PACKAGE}FILES+= dollar-quote5.0 +${PACKAGE}FILES+= dollar-quote6.0 +${PACKAGE}FILES+= dollar-quote7.0 +${PACKAGE}FILES+= dollar-quote8.0 +${PACKAGE}FILES+= dollar-quote9.0 +${PACKAGE}FILES+= dollar-quote10.0 +${PACKAGE}FILES+= dollar-quote11.0 +${PACKAGE}FILES+= dollar-quote12.0 +${PACKAGE}FILES+= dollar-quote13.0 +${PACKAGE}FILES+= empty-braces1.0 +${PACKAGE}FILES+= empty-cmd1.0 +${PACKAGE}FILES+= for1.0 +${PACKAGE}FILES+= for2.0 +${PACKAGE}FILES+= func1.0 +${PACKAGE}FILES+= func2.0 +${PACKAGE}FILES+= func3.0 +${PACKAGE}FILES+= heredoc1.0 +${PACKAGE}FILES+= heredoc2.0 +${PACKAGE}FILES+= heredoc3.0 +${PACKAGE}FILES+= heredoc4.0 +${PACKAGE}FILES+= heredoc5.0 +${PACKAGE}FILES+= heredoc6.0 +${PACKAGE}FILES+= heredoc7.0 +${PACKAGE}FILES+= heredoc8.0 +${PACKAGE}FILES+= heredoc9.0 +${PACKAGE}FILES+= heredoc10.0 +${PACKAGE}FILES+= heredoc11.0 +${PACKAGE}FILES+= heredoc12.0 +${PACKAGE}FILES+= heredoc13.0 +${PACKAGE}FILES+= line-cont1.0 +${PACKAGE}FILES+= line-cont2.0 +${PACKAGE}FILES+= line-cont3.0 +${PACKAGE}FILES+= line-cont4.0 +${PACKAGE}FILES+= line-cont5.0 +${PACKAGE}FILES+= line-cont6.0 +${PACKAGE}FILES+= line-cont7.0 +${PACKAGE}FILES+= line-cont8.0 +${PACKAGE}FILES+= line-cont9.0 +${PACKAGE}FILES+= line-cont10.0 +${PACKAGE}FILES+= line-cont11.0 +${PACKAGE}FILES+= line-cont12.0 +${PACKAGE}FILES+= no-space1.0 +${PACKAGE}FILES+= no-space2.0 +${PACKAGE}FILES+= nul1.0 +${PACKAGE}FILES+= only-redir1.0 +${PACKAGE}FILES+= only-redir2.0 +${PACKAGE}FILES+= only-redir3.0 +${PACKAGE}FILES+= only-redir4.0 +${PACKAGE}FILES+= pipe-not1.0 +${PACKAGE}FILES+= set-v1.0 set-v1.0.stderr +${PACKAGE}FILES+= var-assign1.0 + +.include <bsd.test.mk> diff --git a/bin/1sh/tests/parser/Makefile.depend b/bin/1sh/tests/parser/Makefile.depend new file mode 100644 index 00000000..93ab5889 --- /dev/null +++ b/bin/1sh/tests/parser/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/Makefile.depend 296587 2016-03-09 22:46:01Z bdrewery $ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/1sh/tests/parser/alias1.0 b/bin/1sh/tests/parser/alias1.0 new file mode 100644 index 00000000..2f20b05f --- /dev/null +++ b/bin/1sh/tests/parser/alias1.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias1.0 214280 2010-10-24 16:55:17Z jilles $ + +alias alias0=exit +eval 'alias0 0' +exit 1 diff --git a/bin/1sh/tests/parser/alias10.0 b/bin/1sh/tests/parser/alias10.0 new file mode 100644 index 00000000..bccf9d46 --- /dev/null +++ b/bin/1sh/tests/parser/alias10.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias10.0 243252 2012-11-18 23:15:22Z jilles $ + +# This test may start consuming memory indefinitely if it fails. +ulimit -t 5 2>/dev/null +ulimit -v 100000 2>/dev/null + +alias echo='echo' +alias echo='echo' +[ "`eval echo b`" = b ] diff --git a/bin/1sh/tests/parser/alias11.0 b/bin/1sh/tests/parser/alias11.0 new file mode 100644 index 00000000..1f8de0e3 --- /dev/null +++ b/bin/1sh/tests/parser/alias11.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias11.0 261141 2014-01-24 23:00:35Z jilles $ + +alias alias0=alias1 +alias alias1=exit +eval 'alias0 0' +exit 3 diff --git a/bin/1sh/tests/parser/alias12.0 b/bin/1sh/tests/parser/alias12.0 new file mode 100644 index 00000000..4763bf86 --- /dev/null +++ b/bin/1sh/tests/parser/alias12.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias12.0 261160 2014-01-25 14:59:08Z jilles $ + +unalias -a +alias alias0=command +alias true='echo bad' +eval 'alias0 true' diff --git a/bin/1sh/tests/parser/alias13.0 b/bin/1sh/tests/parser/alias13.0 new file mode 100644 index 00000000..ef370ee9 --- /dev/null +++ b/bin/1sh/tests/parser/alias13.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias13.0 261160 2014-01-25 14:59:08Z jilles $ + +unalias -a +alias command=command +alias true='echo bad' +eval 'command true' diff --git a/bin/1sh/tests/parser/alias14.0 b/bin/1sh/tests/parser/alias14.0 new file mode 100644 index 00000000..42a195ad --- /dev/null +++ b/bin/1sh/tests/parser/alias14.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias14.0 261192 2014-01-26 21:19:33Z jilles $ + +alias command='command ' +alias alias0=exit +eval 'command alias0 0' +exit 3 diff --git a/bin/1sh/tests/parser/alias15.0 b/bin/1sh/tests/parser/alias15.0 new file mode 100644 index 00000000..a070fd66 --- /dev/null +++ b/bin/1sh/tests/parser/alias15.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias15.0 261192 2014-01-26 21:19:33Z jilles $ + +f_echoanddo() { + printf '%s\n' "$*" + "$@" +} + +alias echoanddo='f_echoanddo ' +alias alias0='echo test2' +eval 'echoanddo echo test1' +eval 'echoanddo alias0' +exit 0 diff --git a/bin/1sh/tests/parser/alias15.0.stdout b/bin/1sh/tests/parser/alias15.0.stdout new file mode 100644 index 00000000..6dd179c0 --- /dev/null +++ b/bin/1sh/tests/parser/alias15.0.stdout @@ -0,0 +1,4 @@ +echo test1 +test1 +echo test2 +test2 diff --git a/bin/1sh/tests/parser/alias16.0 b/bin/1sh/tests/parser/alias16.0 new file mode 100644 index 00000000..a9dc4e60 --- /dev/null +++ b/bin/1sh/tests/parser/alias16.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias16.0 316646 2017-04-08 21:57:59Z jilles $ + +v=1 +alias a='unalias a +v=2' +eval a +[ "$v" = 2 ] diff --git a/bin/1sh/tests/parser/alias17.0 b/bin/1sh/tests/parser/alias17.0 new file mode 100644 index 00000000..bfd3d886 --- /dev/null +++ b/bin/1sh/tests/parser/alias17.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias17.0 317037 2017-04-16 21:42:43Z jilles $ + +v=1 +alias a='unalias -a +v=2' +eval a +[ "$v" = 2 ] diff --git a/bin/1sh/tests/parser/alias18.0 b/bin/1sh/tests/parser/alias18.0 new file mode 100644 index 00000000..1063b063 --- /dev/null +++ b/bin/1sh/tests/parser/alias18.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias18.0 317039 2017-04-16 22:10:02Z jilles $ + +v=1 +alias a='alias a=v=2 +v=3 +a' +eval a +[ "$v" = 2 ] diff --git a/bin/1sh/tests/parser/alias2.0 b/bin/1sh/tests/parser/alias2.0 new file mode 100644 index 00000000..8a966d7a --- /dev/null +++ b/bin/1sh/tests/parser/alias2.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias2.0 214280 2010-10-24 16:55:17Z jilles $ + +alias alias0=exit +x=alias0 +eval 'case $x in alias0) exit 0;; esac' +exit 1 diff --git a/bin/1sh/tests/parser/alias3.0 b/bin/1sh/tests/parser/alias3.0 new file mode 100644 index 00000000..3c6701c8 --- /dev/null +++ b/bin/1sh/tests/parser/alias3.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias3.0 214709 2010-11-02 23:44:29Z jilles $ + +alias alias0=exit +x=alias0 +eval 'case $x in "alias0") alias0 0;; esac' +exit 1 diff --git a/bin/1sh/tests/parser/alias4.0 b/bin/1sh/tests/parser/alias4.0 new file mode 100644 index 00000000..f2794aa6 --- /dev/null +++ b/bin/1sh/tests/parser/alias4.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias4.0 222165 2011-05-21 22:03:06Z jilles $ + +alias alias0=exit +eval 'x=1 alias0 0' +exit 1 diff --git a/bin/1sh/tests/parser/alias5.0 b/bin/1sh/tests/parser/alias5.0 new file mode 100644 index 00000000..b4c53500 --- /dev/null +++ b/bin/1sh/tests/parser/alias5.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias5.0 222165 2011-05-21 22:03:06Z jilles $ + +alias alias0=exit +eval '</dev/null alias0 0' +exit 1 diff --git a/bin/1sh/tests/parser/alias6.0 b/bin/1sh/tests/parser/alias6.0 new file mode 100644 index 00000000..7db847a3 --- /dev/null +++ b/bin/1sh/tests/parser/alias6.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias6.0 224104 2011-07-16 16:14:14Z jilles $ + +alias alias0='| cat >/dev/null' + +eval '{ echo bad; } alias0' +eval '(echo bad)alias0' diff --git a/bin/1sh/tests/parser/alias7.0 b/bin/1sh/tests/parser/alias7.0 new file mode 100644 index 00000000..2dbb19e6 --- /dev/null +++ b/bin/1sh/tests/parser/alias7.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias7.0 240825 2012-09-22 12:52:41Z jilles $ + +alias echo='echo a' +[ "`eval echo b`" = "a b" ] diff --git a/bin/1sh/tests/parser/alias8.0 b/bin/1sh/tests/parser/alias8.0 new file mode 100644 index 00000000..47d6b55a --- /dev/null +++ b/bin/1sh/tests/parser/alias8.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias8.0 240825 2012-09-22 12:52:41Z jilles $ + +alias echo='echo' +[ "`eval echo b`" = b ] diff --git a/bin/1sh/tests/parser/alias9.0 b/bin/1sh/tests/parser/alias9.0 new file mode 100644 index 00000000..06634702 --- /dev/null +++ b/bin/1sh/tests/parser/alias9.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/alias9.0 242721 2012-11-07 23:15:36Z jilles $ + +alias alias0=: +alias alias0=exit +eval 'alias0 0' +exit 1 diff --git a/bin/1sh/tests/parser/and-pipe-not.0 b/bin/1sh/tests/parser/and-pipe-not.0 new file mode 100644 index 00000000..bca8893e --- /dev/null +++ b/bin/1sh/tests/parser/and-pipe-not.0 @@ -0,0 +1,2 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/and-pipe-not.0 191010 2009-04-13 19:12:28Z stefanf $ +true && ! true | false diff --git a/bin/1sh/tests/parser/case1.0 b/bin/1sh/tests/parser/case1.0 new file mode 100644 index 00000000..521ca4a1 --- /dev/null +++ b/bin/1sh/tests/parser/case1.0 @@ -0,0 +1,14 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/case1.0 207824 2010-05-09 17:10:50Z jilles $ + +keywords='if then else elif fi while until for do done { } case esac ! in' + +# Keywords can be used unquoted in case statements, except the keyword +# esac as the first pattern of a '|' alternation without a starting '('. +# (POSIX doesn't seem to require (esac) to work.) +for k in $keywords; do + eval "case $k in (foo|$k) ;; *) echo bad ;; esac" + eval "case $k in ($k) ;; *) echo bad ;; esac" + eval "case $k in foo|$k) ;; *) echo bad ;; esac" + [ "$k" = esac ] && continue + eval "case $k in $k) ;; *) echo bad ;; esac" +done diff --git a/bin/1sh/tests/parser/case2.0 b/bin/1sh/tests/parser/case2.0 new file mode 100644 index 00000000..539ed0fe --- /dev/null +++ b/bin/1sh/tests/parser/case2.0 @@ -0,0 +1,32 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/case2.0 207824 2010-05-09 17:10:50Z jilles $ + +# Pretty much only ash derivatives can parse all of this. + +f1() { + x=$(case x in + (x|esac) ;; + (*) echo bad >&2 ;; + esac) +} +f1 +f2() { + x=$(case x in + (x|esac) ;; + (*) echo bad >&2 + esac) +} +f2 +f3() { + x=$(case x in + x|esac) ;; + *) echo bad >&2 ;; + esac) +} +f3 +f4() { + x=$(case x in + x|esac) ;; + *) echo bad >&2 + esac) +} +f4 diff --git a/bin/1sh/tests/parser/comment1.0 b/bin/1sh/tests/parser/comment1.0 new file mode 100644 index 00000000..aa475ac2 --- /dev/null +++ b/bin/1sh/tests/parser/comment1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/comment1.0 295818 2016-02-19 16:56:07Z jilles $ + +${SH} -c '#' diff --git a/bin/1sh/tests/parser/comment2.42 b/bin/1sh/tests/parser/comment2.42 new file mode 100644 index 00000000..a0b0d696 --- /dev/null +++ b/bin/1sh/tests/parser/comment2.42 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/comment2.42 295818 2016-02-19 16:56:07Z jilles $ + +${SH} -c '# +exit 42' diff --git a/bin/1sh/tests/parser/dollar-quote1.0 b/bin/1sh/tests/parser/dollar-quote1.0 new file mode 100644 index 00000000..12061417 --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote1.0 @@ -0,0 +1,12 @@ +# $FreeBSD$ + +set -e + +[ $'hi' = hi ] +[ $'hi +there' = 'hi +there' ] +[ $'\"\'\\\a\b\f\t\v' = "\"'\\$(printf "\a\b\f\t\v")" ] +[ $'hi\nthere' = 'hi +there' ] +[ $'a\rb' = "$(printf "a\rb")" ] diff --git a/bin/1sh/tests/parser/dollar-quote10.0 b/bin/1sh/tests/parser/dollar-quote10.0 new file mode 100644 index 00000000..fdcabbb3 --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote10.0 @@ -0,0 +1,10 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/dollar-quote10.0 221669 2011-05-08 17:40:10Z jilles $ + +# a umlaut +s=$(printf '\303\244') +# euro sign +s=$s$(printf '\342\202\254') + +# Start a new shell so the locale change is picked up. +ss="$(LC_ALL=en_US.UTF-8 ${SH} -c "printf %s \$'\u00e4\u20ac'")" +[ "$s" = "$ss" ] diff --git a/bin/1sh/tests/parser/dollar-quote11.0 b/bin/1sh/tests/parser/dollar-quote11.0 new file mode 100644 index 00000000..cab063f6 --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote11.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/dollar-quote11.0 221669 2011-05-08 17:40:10Z jilles $ + +# some sort of 't' outside BMP +s=$s$(printf '\360\235\225\245') + +# Start a new shell so the locale change is picked up. +ss="$(LC_ALL=en_US.UTF-8 ${SH} -c "printf %s \$'\U0001d565'")" +[ "$s" = "$ss" ] diff --git a/bin/1sh/tests/parser/dollar-quote12.0 b/bin/1sh/tests/parser/dollar-quote12.0 new file mode 100644 index 00000000..54f80b5a --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote12.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/dollar-quote12.0 286971 2015-08-20 21:31:36Z jilles $ + +# \u without any digits at all remains invalid. +# Our choice is a parse error. + +v=$( (eval ": \$'\u'") 2>&1 >/dev/null) +[ $? -ne 0 ] && [ -n "$v" ] diff --git a/bin/1sh/tests/parser/dollar-quote13.0 b/bin/1sh/tests/parser/dollar-quote13.0 new file mode 100644 index 00000000..436e1dc7 --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote13.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/dollar-quote13.0 286973 2015-08-20 22:05:55Z jilles $ + +# This Unicode escape sequence that has never been in range should either +# fail to expand or expand to a fallback. + +c=$(eval printf %s \$\'\\Uffffff41\' 2>/dev/null) +r=$(($? != 0)) +[ "$r.$c" = '1.' ] || [ "$r.$c" = '0.?' ] || [ "$r.$c" = $'0.\u2222' ] diff --git a/bin/1sh/tests/parser/dollar-quote2.0 b/bin/1sh/tests/parser/dollar-quote2.0 new file mode 100644 index 00000000..4617ed8d --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote2.0 @@ -0,0 +1,5 @@ +# $FreeBSD$ + +# This depends on the ASCII character set. + +[ $'\e' = "$(printf "\033")" ] diff --git a/bin/1sh/tests/parser/dollar-quote3.0 b/bin/1sh/tests/parser/dollar-quote3.0 new file mode 100644 index 00000000..a7e68527 --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote3.0 @@ -0,0 +1,22 @@ +# $FreeBSD$ + +unset LC_ALL +LC_CTYPE=en_US.ISO8859-1 +export LC_CTYPE + +e= +for i in 0 1 2 3; do + for j in 0 1 2 3 4 5 6 7; do + for k in 0 1 2 3 4 5 6 7; do + case $i$j$k in + 000) continue ;; + esac + e="$e\\$i$j$k" + done + done +done +ee=`printf "$e"` +[ "${#ee}" = 255 ] || echo length bad + +# Start a new shell so the locale change is picked up. +[ "$(${SH} -c "printf %s \$'$e'")" = "$ee" ] diff --git a/bin/1sh/tests/parser/dollar-quote4.0 b/bin/1sh/tests/parser/dollar-quote4.0 new file mode 100644 index 00000000..f620af5b --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote4.0 @@ -0,0 +1,19 @@ +# $FreeBSD$ + +unset LC_ALL +LC_CTYPE=en_US.ISO8859-1 +export LC_CTYPE + +e= +for i in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do + for j in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do + case $i$j in + 00) continue ;; + esac + e="$e\x$i$j" + done +done + +# Start a new shell so the locale change is picked up. +ee="$(${SH} -c "printf %s \$'$e'")" +[ "${#ee}" = 255 ] || echo length bad diff --git a/bin/1sh/tests/parser/dollar-quote5.0 b/bin/1sh/tests/parser/dollar-quote5.0 new file mode 100644 index 00000000..c2c44ca6 --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote5.0 @@ -0,0 +1,12 @@ +# $FreeBSD$ + +# This depends on the ASCII character set. + +set -e + +[ $'\ca\cb\cc\cd\ce\cf\cg\ch\ci\cj\ck\cl\cm\cn\co\cp\cq\cr\cs\ct\cu\cv\cw\cx\cy\cz' = $'\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032' ] +[ $'\cA\cB\cC\cD\cE\cF\cG\cH\cI\cJ\cK\cL\cM\cN\cO\cP\cQ\cR\cS\cT\cU\cV\cW\cX\cY\cZ' = $'\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032' ] +[ $'\c[' = $'\033' ] +[ $'\c]' = $'\035' ] +[ $'\c^' = $'\036' ] +[ $'\c_' = $'\037' ] diff --git a/bin/1sh/tests/parser/dollar-quote6.0 b/bin/1sh/tests/parser/dollar-quote6.0 new file mode 100644 index 00000000..a4b1e3f4 --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote6.0 @@ -0,0 +1,5 @@ +# $FreeBSD$ + +# This depends on the ASCII character set. + +[ $'\c\\' = $'\034' ] diff --git a/bin/1sh/tests/parser/dollar-quote7.0 b/bin/1sh/tests/parser/dollar-quote7.0 new file mode 100644 index 00000000..c866b1af --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote7.0 @@ -0,0 +1,6 @@ +# $FreeBSD$ + +set -e + +[ $'\u0024\u0040\u0060' = '$@`' ] +[ $'\U00000024\U00000040\U00000060' = '$@`' ] diff --git a/bin/1sh/tests/parser/dollar-quote8.0 b/bin/1sh/tests/parser/dollar-quote8.0 new file mode 100644 index 00000000..8f0b41a0 --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote8.0 @@ -0,0 +1,11 @@ +# $FreeBSD$ + +[ $'hello\0' = hello ] +[ $'hello\0world' = hello ] +[ $'hello\0'$'world' = helloworld ] +[ $'hello\000' = hello ] +[ $'hello\000world' = hello ] +[ $'hello\000'$'world' = helloworld ] +[ $'hello\x00' = hello ] +[ $'hello\x00world' = hello ] +[ $'hello\x00'$'world' = helloworld ] diff --git a/bin/1sh/tests/parser/dollar-quote9.0 b/bin/1sh/tests/parser/dollar-quote9.0 new file mode 100644 index 00000000..df64b7df --- /dev/null +++ b/bin/1sh/tests/parser/dollar-quote9.0 @@ -0,0 +1,8 @@ +# $FreeBSD$ + +# POSIX and C99 say D800-DFFF are undefined in a universal character name. +# We reject this but many other shells expand to something that looks like +# CESU-8. + +v=$( (eval ": \$'\uD800'") 2>&1 >/dev/null) +[ $? -ne 0 ] && [ -n "$v" ] diff --git a/bin/1sh/tests/parser/empty-braces1.0 b/bin/1sh/tests/parser/empty-braces1.0 new file mode 100644 index 00000000..5b3b379b --- /dev/null +++ b/bin/1sh/tests/parser/empty-braces1.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/empty-braces1.0 245382 2013-01-13 19:26:33Z jilles $ + +# Unfortunately, some scripts depend on the extension of allowing an empty +# pair of braces. + +{ } & +wait $! diff --git a/bin/1sh/tests/parser/empty-cmd1.0 b/bin/1sh/tests/parser/empty-cmd1.0 new file mode 100644 index 00000000..b2851e37 --- /dev/null +++ b/bin/1sh/tests/parser/empty-cmd1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/empty-cmd1.0 254843 2013-08-25 10:57:48Z jilles $ + +! (eval ': || f()') 2>/dev/null diff --git a/bin/1sh/tests/parser/for1.0 b/bin/1sh/tests/parser/for1.0 new file mode 100644 index 00000000..e2d36b15 --- /dev/null +++ b/bin/1sh/tests/parser/for1.0 @@ -0,0 +1,29 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/for1.0 218889 2011-02-20 14:18:58Z jilles $ + +nl=' +' +list=' a b c' +for s1 in "$nl" " "; do + for s2 in "$nl" ";" ";$nl"; do + for s3 in "$nl" " "; do + r='' + eval "for i${s1}in ${list}${s2}do${s3}r=\"\$r \$i\"; done" + [ "$r" = "$list" ] || exit 1 + done + done +done +set -- $list +for s2 in "$nl" " "; do + for s3 in "$nl" " "; do + r='' + eval "for i${s2}do${s3}r=\"\$r \$i\"; done" + [ "$r" = "$list" ] || exit 1 + done +done +for s1 in "$nl" " "; do + for s2 in "$nl" ";" ";$nl"; do + for s3 in "$nl" " "; do + eval "for i${s1}in${s2}do${s3}exit 1; done" + done + done +done diff --git a/bin/1sh/tests/parser/for2.0 b/bin/1sh/tests/parser/for2.0 new file mode 100644 index 00000000..fad4369c --- /dev/null +++ b/bin/1sh/tests/parser/for2.0 @@ -0,0 +1,15 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/for2.0 218889 2011-02-20 14:18:58Z jilles $ + +# Common extensions to the 'for' syntax. + +nl=' +' +list=' a b c' +set -- $list +for s2 in ";" ";$nl"; do + for s3 in "$nl" " "; do + r='' + eval "for i${s2}do${s3}r=\"\$r \$i\"; done" + [ "$r" = "$list" ] || exit 1 + done +done diff --git a/bin/1sh/tests/parser/func1.0 b/bin/1sh/tests/parser/func1.0 new file mode 100644 index 00000000..dd1d5487 --- /dev/null +++ b/bin/1sh/tests/parser/func1.0 @@ -0,0 +1,25 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/func1.0 214291 2010-10-24 20:45:13Z jilles $ +# POSIX does not require these bytes to work in function names, +# but making them all work seems a good goal. + +failures=0 +unset LC_ALL +export LC_CTYPE=en_US.ISO8859-1 +i=128 +set -f +while [ "$i" -le 255 ]; do + c=$(printf \\"$(printf %o "$i")") + ok=0 + eval "$c() { ok=1; }" + $c + ok1=$ok + ok=0 + "$c" + if [ "$ok" != 1 ] || [ "$ok1" != 1 ]; then + echo "Bad results for character $i" >&2 + : $((failures += 1)) + fi + unset -f $c + i=$((i+1)) +done +exit $((failures > 0)) diff --git a/bin/1sh/tests/parser/func2.0 b/bin/1sh/tests/parser/func2.0 new file mode 100644 index 00000000..7a71706a --- /dev/null +++ b/bin/1sh/tests/parser/func2.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/func2.0 222512 2011-05-30 21:49:59Z jilles $ + +f() { return 42; } +f() { return 3; } & +f +[ $? -eq 42 ] diff --git a/bin/1sh/tests/parser/func3.0 b/bin/1sh/tests/parser/func3.0 new file mode 100644 index 00000000..32646651 --- /dev/null +++ b/bin/1sh/tests/parser/func3.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/func3.0 222512 2011-05-30 21:49:59Z jilles $ + +name=/var/empty/nosuch +f() { true; } <$name +name=/dev/null +f diff --git a/bin/1sh/tests/parser/heredoc1.0 b/bin/1sh/tests/parser/heredoc1.0 new file mode 100644 index 00000000..dd7ad331 --- /dev/null +++ b/bin/1sh/tests/parser/heredoc1.0 @@ -0,0 +1,85 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc1.0 204836 2010-03-07 15:08:42Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +check '"$(cat <<EOF +hi +EOF +)" = hi' + +check '"$(cat <<EOF +${$+hi} +EOF +)" = hi' + +unset yy +check '"$(cat <<EOF +${yy-hi} +EOF +)" = hi' + +check '"$(cat <<EOF +${$+hi +there} +EOF +)" = "hi +there"' + +check '"$(cat <<EOF +$((1+1)) +EOF +)" = 2' + +check '"$(cat <<EOF +$(echo hi) +EOF +)" = hi' + +check '"$(cat <<EOF +`echo hi` +EOF +)" = hi' + +check '"$(cat <<\EOF +${$+hi} +EOF +)" = "\${\$+hi}"' + +check '"$(cat <<\EOF +$( +EOF +)" = \$\(' + +check '"$(cat <<\EOF +` +EOF +)" = \`' + +check '"$(cat <<EOF +" +EOF +)" = \"' + +check '"$(cat <<\EOF +" +EOF +)" = \"' + +check '"$(cat <<esac +'"'"' +esac +)" = "'"'"'"' + +check '"$(cat <<\) +'"'"' +) +)" = "'"'"'"' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/parser/heredoc10.0 b/bin/1sh/tests/parser/heredoc10.0 new file mode 100644 index 00000000..5ada3115 --- /dev/null +++ b/bin/1sh/tests/parser/heredoc10.0 @@ -0,0 +1,49 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc10.0 221887 2011-05-14 14:19:30Z jilles $ + +# It may be argued that +# x=$(cat <<EOF +# foo +# EOF) +# is a valid complete command that sets x to foo, because +# cat <<EOF +# foo +# EOF +# is a valid script even without the final newline. +# However, if the here-document is not within a new-style command substitution +# or there are other constructs nested inside the command substitution that +# need terminators, the delimiter at the start of a line followed by a close +# parenthesis is clearly a literal part of the here-document. + +# This file contains tests that may not work with simplistic $(...) parsers. +# The open parentheses in comments help mksh, but not zsh. + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +check '"$(cat <<EOF # ( +EOF ) +EOF +)" = "EOF )"' + +check '"$({ cat <<EOF # ( +EOF) +EOF +})" = "EOF)"' + +check '"$(if :; then cat <<EOF # ( +EOF) +EOF +fi)" = "EOF)"' + +check '"$( (cat <<EOF # ( +EOF) +EOF +))" = "EOF)"' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/parser/heredoc11.0 b/bin/1sh/tests/parser/heredoc11.0 new file mode 100644 index 00000000..ed255593 --- /dev/null +++ b/bin/1sh/tests/parser/heredoc11.0 @@ -0,0 +1,26 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc11.0 222134 2011-05-20 16:03:36Z jilles $ + +failures='' + +check() { + if eval "[ $* ]"; then + : + else + echo "Failed: $*" + failures=x$failures + fi +} + +check '`cat <<EOF +foo +EOF` = foo' + +check '"`cat <<EOF +foo +EOF`" = foo' + +check '`eval "cat <<EOF +foo +EOF"` = foo' + +test "x$failures" = x diff --git a/bin/1sh/tests/parser/heredoc12.0 b/bin/1sh/tests/parser/heredoc12.0 new file mode 100644 index 00000000..6a5eefdc --- /dev/null +++ b/bin/1sh/tests/parser/heredoc12.0 @@ -0,0 +1,47 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc12.0 271593 2014-09-14 16:46:30Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +longmark=`printf %01000d 4` +longmarkstripped=`printf %0999d 0` + +check '"$(cat <<'"$longmark +$longmark"' +echo yes)" = "yes"' + +check '"$(cat <<\'"$longmark +$longmark"' +echo yes)" = "yes"' + +check '"$(cat <<'"$longmark +yes +$longmark"' +)" = "yes"' + +check '"$(cat <<\'"$longmark +yes +$longmark"' +)" = "yes"' + +check '"$(cat <<'"$longmark +$longmarkstripped +$longmark. +$longmark"' +)" = "'"$longmarkstripped +$longmark."'"' + +check '"$(cat <<\'"$longmark +$longmarkstripped +$longmark. +$longmark"' +)" = "'"$longmarkstripped +$longmark."'"' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/parser/heredoc13.0 b/bin/1sh/tests/parser/heredoc13.0 new file mode 100644 index 00000000..705082bb --- /dev/null +++ b/bin/1sh/tests/parser/heredoc13.0 @@ -0,0 +1,21 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc13.0 287408 2015-09-02 19:49:55Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +check '"$(cat <<"" + +echo yes)" = "yes"' + +check '"$(cat <<"" +yes + +)" = "yes"' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/parser/heredoc2.0 b/bin/1sh/tests/parser/heredoc2.0 new file mode 100644 index 00000000..19314734 --- /dev/null +++ b/bin/1sh/tests/parser/heredoc2.0 @@ -0,0 +1,39 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc2.0 211405 2010-08-16 21:14:49Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +s='ast*que?non' sq=\' dq=\" + +check '"$(cat <<EOF +${s} +EOF +)" = "ast*que?non"' + +check '"$(cat <<EOF +${s+'$sq'x'$sq'} +EOF +)" = ${sq}x${sq}' + +check '"$(cat <<EOF +${s#ast} +EOF +)" = "*que?non"' + +check '"$(cat <<EOF +${s##"ast"} +EOF +)" = "*que?non"' + +check '"$(cat <<EOF +${s##'$sq'ast'$sq'} +EOF +)" = "*que?non"' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/parser/heredoc3.0 b/bin/1sh/tests/parser/heredoc3.0 new file mode 100644 index 00000000..f0c8d88a --- /dev/null +++ b/bin/1sh/tests/parser/heredoc3.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc3.0 207824 2010-05-09 17:10:50Z jilles $ + +# This may be expected to work, but pretty much only ash derivatives allow it. + +test "$(cat <<EOF)" = "hi there" +hi there +EOF diff --git a/bin/1sh/tests/parser/heredoc4.0 b/bin/1sh/tests/parser/heredoc4.0 new file mode 100644 index 00000000..1ea11a55 --- /dev/null +++ b/bin/1sh/tests/parser/heredoc4.0 @@ -0,0 +1,44 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc4.0 208655 2010-05-30 14:11:27Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +f() { + cat <<EOF && echo `echo bar` +foo +EOF +} +check '"`f`" = "foo +bar"' + +f() { + cat <<EOF && echo $(echo bar) +foo +EOF +} +check '"$(f)" = "foo +bar"' + +f() { + echo `echo bar` && cat <<EOF +foo +EOF +} +check '"`f`" = "bar +foo"' + +f() { + echo $(echo bar) && cat <<EOF +foo +EOF +} +check '"$(f)" = "bar +foo"' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/parser/heredoc5.0 b/bin/1sh/tests/parser/heredoc5.0 new file mode 100644 index 00000000..e261a2a3 --- /dev/null +++ b/bin/1sh/tests/parser/heredoc5.0 @@ -0,0 +1,56 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc5.0 208655 2010-05-30 14:11:27Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +f() { + cat <<EOF && echo `cat <<EOF +bar +EOF +` +foo +EOF +} +check '"`f`" = "foo +bar"' + +f() { + cat <<EOF && echo $(cat <<EOF +bar +EOF +) +foo +EOF +} +check '"$(f)" = "foo +bar"' + +f() { + echo `cat <<EOF +bar +EOF +` && cat <<EOF +foo +EOF +} +check '"`f`" = "bar +foo"' + +f() { + echo $(cat <<EOF +bar +EOF +) && cat <<EOF +foo +EOF +} +check '"$(f)" = "bar +foo"' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/parser/heredoc6.0 b/bin/1sh/tests/parser/heredoc6.0 new file mode 100644 index 00000000..9b791856 --- /dev/null +++ b/bin/1sh/tests/parser/heredoc6.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc6.0 208656 2010-05-30 14:20:32Z jilles $ + +r= +! command eval ": <<EOF; )" 2>/dev/null; command eval : hi \${r:=0} +exit ${r:-3} diff --git a/bin/1sh/tests/parser/heredoc7.0 b/bin/1sh/tests/parser/heredoc7.0 new file mode 100644 index 00000000..0296f663 --- /dev/null +++ b/bin/1sh/tests/parser/heredoc7.0 @@ -0,0 +1,19 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc7.0 210488 2010-07-25 22:25:52Z jilles $ + +# Some of these created malformed parse trees with null pointers for here +# documents, causing the here document writing process to segfault. +eval ': <<EOF' +eval ': <<EOF;' +eval '`: <<EOF`' +eval '`: <<EOF;`' +eval '`: <<EOF`;' +eval '`: <<EOF;`;' + +# Some of these created malformed parse trees with null pointers for here +# documents, causing sh to segfault. +eval ': <<\EOF' +eval ': <<\EOF;' +eval '`: <<\EOF`' +eval '`: <<\EOF;`' +eval '`: <<\EOF`;' +eval '`: <<\EOF;`;' diff --git a/bin/1sh/tests/parser/heredoc8.0 b/bin/1sh/tests/parser/heredoc8.0 new file mode 100644 index 00000000..1cb8c92c --- /dev/null +++ b/bin/1sh/tests/parser/heredoc8.0 @@ -0,0 +1,20 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc8.0 211405 2010-08-16 21:14:49Z jilles $ + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +s='ast*que?non' sq=\' dq=\" + +# This is possibly useful but differs from other shells. +check '"$(cat <<EOF +${s+"x"} +EOF +)" = ${dq}x${dq}' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/parser/heredoc9.0 b/bin/1sh/tests/parser/heredoc9.0 new file mode 100644 index 00000000..f2958c72 --- /dev/null +++ b/bin/1sh/tests/parser/heredoc9.0 @@ -0,0 +1,58 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/heredoc9.0 221887 2011-05-14 14:19:30Z jilles $ + +# It may be argued that +# x=$(cat <<EOF +# foo +# EOF) +# is a valid complete command that sets x to foo, because +# cat <<EOF +# foo +# EOF +# is a valid script even without the final newline. +# However, if the here-document is not within a new-style command substitution +# or there are other constructs nested inside the command substitution that +# need terminators, the delimiter at the start of a line followed by a close +# parenthesis is clearly a literal part of the here-document. + +# This file contains tests that also work with simplistic $(...) parsers. + +failures=0 + +check() { + if ! eval "[ $* ]"; then + echo "Failed: $*" + : $((failures += 1)) + fi +} + +check '`${SH} -c "cat <<EOF +EOF) +EOF +"` = "EOF)"' + +check '`${SH} -c "(cat <<EOF +EOF) +EOF +)"` = "EOF)"' + +check '"`cat <<EOF +EOF x +EOF +`" = "EOF x"' + +check '"`cat <<EOF +EOF ) +EOF +`" = "EOF )"' + +check '"`cat <<EOF +EOF) +EOF +`" = "EOF)"' + +check '"$(cat <<EOF +EOF x +EOF +)" = "EOF x"' + +exit $((failures != 0)) diff --git a/bin/1sh/tests/parser/line-cont1.0 b/bin/1sh/tests/parser/line-cont1.0 new file mode 100644 index 00000000..be1673ac --- /dev/null +++ b/bin/1sh/tests/parser/line-cont1.0 @@ -0,0 +1,16 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont1.0 273243 2014-10-17 21:52:57Z jilles $ + +i\ +f +t\ +r\ +u\ +e +t\ +h\ +e\ +n +: +\ +f\ +i diff --git a/bin/1sh/tests/parser/line-cont10.0 b/bin/1sh/tests/parser/line-cont10.0 new file mode 100644 index 00000000..d79f182b --- /dev/null +++ b/bin/1sh/tests/parser/line-cont10.0 @@ -0,0 +1,18 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont10.0 273276 2014-10-19 11:59:15Z jilles $ + +v=XaaaXbbbX +[ "${v\ +#\ +*\ +a}.${v\ +#\ +#\ +*\ +a}.${v\ +%\ +b\ +*}.${v\ +%\ +%\ +b\ +*}" = aaXbbbX.XbbbX.XaaaXbb.XaaaX ] diff --git a/bin/1sh/tests/parser/line-cont11.0 b/bin/1sh/tests/parser/line-cont11.0 new file mode 100644 index 00000000..053d81d3 --- /dev/null +++ b/bin/1sh/tests/parser/line-cont11.0 @@ -0,0 +1,23 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont11.0 273276 2014-10-19 11:59:15Z jilles $ + +T=$(mktemp "${TMPDIR:-/tmp}/sh-test.XXXXXXXX") || exit +trap 'rm -f -- "$T"' 0 +w='#A' +# A naive pgetc_linecont() would push back two characters here, which +# fails if a new buffer is read between the two characters. +c='${w#\#}' +c=$c$c$c$c +c=$c$c$c$c +c=$c$c$c$c +c=$c$c$c$c +c=$c$c$c$c +c=$c$c$c$c +printf 'v=%s\n' "$c" >"$T" +. "$T" +if [ "${#v}" != 4096 ]; then + echo "Length is bad (${#v})" + exit 3 +fi +case $v in +*[!A]*) echo "Content is bad"; exit 3 ;; +esac diff --git a/bin/1sh/tests/parser/line-cont12.0 b/bin/1sh/tests/parser/line-cont12.0 new file mode 100644 index 00000000..916cec7b --- /dev/null +++ b/bin/1sh/tests/parser/line-cont12.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont12.0 333507 2018-05-11 21:56:01Z jilles $ + +[ '\ +' = "\\ +" ] diff --git a/bin/1sh/tests/parser/line-cont2.0 b/bin/1sh/tests/parser/line-cont2.0 new file mode 100644 index 00000000..0f058e15 --- /dev/null +++ b/bin/1sh/tests/parser/line-cont2.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont2.0 273243 2014-10-17 21:52:57Z jilles $ + +[ "a\ +b" = ab ] diff --git a/bin/1sh/tests/parser/line-cont3.0 b/bin/1sh/tests/parser/line-cont3.0 new file mode 100644 index 00000000..196d4203 --- /dev/null +++ b/bin/1sh/tests/parser/line-cont3.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont3.0 273243 2014-10-17 21:52:57Z jilles $ + +v=`printf %s 'a\ +b'` +w="`printf %s 'c\ +d'`" +[ "$v$w" = abcd ] diff --git a/bin/1sh/tests/parser/line-cont4.0 b/bin/1sh/tests/parser/line-cont4.0 new file mode 100644 index 00000000..47fc8410 --- /dev/null +++ b/bin/1sh/tests/parser/line-cont4.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont4.0 273276 2014-10-19 11:59:15Z jilles $ + +v=abcd +[ "$\ +v.$\ +{v}.${\ +v}.${v\ +}" = abcd.abcd.abcd.abcd ] diff --git a/bin/1sh/tests/parser/line-cont5.0 b/bin/1sh/tests/parser/line-cont5.0 new file mode 100644 index 00000000..b0037c80 --- /dev/null +++ b/bin/1sh/tests/parser/line-cont5.0 @@ -0,0 +1,14 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont5.0 273276 2014-10-19 11:59:15Z jilles $ + +bad=1 +case x in +x\ +) ;\ +; *) exit 7 +esac &\ +& bad= &\ +& : >\ +>/dev/null + +false |\ +| [ -z "$bad" ] diff --git a/bin/1sh/tests/parser/line-cont6.0 b/bin/1sh/tests/parser/line-cont6.0 new file mode 100644 index 00000000..824ed1a5 --- /dev/null +++ b/bin/1sh/tests/parser/line-cont6.0 @@ -0,0 +1,23 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont6.0 273276 2014-10-19 11:59:15Z jilles $ + +v0\ +=abc + +v=$(cat <\ +<\ +E\ +O\ +F +${v0}d +EOF +) + +w=$(cat <\ +<\ +-\ +EOF + efgh +EOF +) + +[ "$v.$w" = "abcd.efgh" ] diff --git a/bin/1sh/tests/parser/line-cont7.0 b/bin/1sh/tests/parser/line-cont7.0 new file mode 100644 index 00000000..0e512c3f --- /dev/null +++ b/bin/1sh/tests/parser/line-cont7.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont7.0 273276 2014-10-19 11:59:15Z jilles $ + +[ "$(\ +( +1\ ++ 1)\ +)" = 2 ] diff --git a/bin/1sh/tests/parser/line-cont8.0 b/bin/1sh/tests/parser/line-cont8.0 new file mode 100644 index 00000000..ce4a9714 --- /dev/null +++ b/bin/1sh/tests/parser/line-cont8.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont8.0 273276 2014-10-19 11:59:15Z jilles $ + +set -- a b c d e f g h i j +[ "${1\ +0\ +}" = j ] diff --git a/bin/1sh/tests/parser/line-cont9.0 b/bin/1sh/tests/parser/line-cont9.0 new file mode 100644 index 00000000..92e14b95 --- /dev/null +++ b/bin/1sh/tests/parser/line-cont9.0 @@ -0,0 +1,6 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/line-cont9.0 273276 2014-10-19 11:59:15Z jilles $ + +[ "${$\ +:\ ++\ +xyz}" = xyz ] diff --git a/bin/1sh/tests/parser/no-space1.0 b/bin/1sh/tests/parser/no-space1.0 new file mode 100644 index 00000000..9546f811 --- /dev/null +++ b/bin/1sh/tests/parser/no-space1.0 @@ -0,0 +1,18 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/no-space1.0 218891 2011-02-20 17:28:58Z jilles $ + +# These are ugly but are required to work. + +set -e + +while(false)do(:)done +if(false)then(:)fi +if(false)then(:)else(:)fi +(:&&:)||: +until(:)do(:)done +case x in(x);;*)exit 1;(:)esac +case x in(x);;*)exit 1;;esac +for i do(:)done +{(:)} +f(){(:)} +:|: +(:)|(:) diff --git a/bin/1sh/tests/parser/no-space2.0 b/bin/1sh/tests/parser/no-space2.0 new file mode 100644 index 00000000..2448d9af --- /dev/null +++ b/bin/1sh/tests/parser/no-space2.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/no-space2.0 218891 2011-02-20 17:28:58Z jilles $ + +# This conflicts with ksh extended patterns but occurs in the wild. + +set -e + +!(false) diff --git a/bin/1sh/tests/parser/nul1.0 b/bin/1sh/tests/parser/nul1.0 new file mode 100644 index 00000000..6919b3a4 --- /dev/null +++ b/bin/1sh/tests/parser/nul1.0 @@ -0,0 +1,12 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/nul1.0 295825 2016-02-19 21:53:12Z jilles $ +# Although POSIX does not specify the effect of NUL bytes in scripts, +# we ignore them. + +{ + printf 'v=%03000d\0%02000d' 7 2 + dd if=/dev/zero bs=1000 count=1 status=none + printf '1 w=%03000d%02000d1\0\n' 7 2 + printf '\0l\0v\0=\0$\0{\0#\0v\0}\n' + printf '\0l\0w\0=\0\0$\0{\0#\0w}\0\0\0\n' + printf '[ "$lv.$lw.$v" = "5001.5001.$w" ]\n' +} | ${SH} diff --git a/bin/1sh/tests/parser/only-redir1.0 b/bin/1sh/tests/parser/only-redir1.0 new file mode 100644 index 00000000..3479c3bb --- /dev/null +++ b/bin/1sh/tests/parser/only-redir1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/only-redir1.0 210221 2010-07-18 12:45:31Z jilles $ +</dev/null & +wait $! diff --git a/bin/1sh/tests/parser/only-redir2.0 b/bin/1sh/tests/parser/only-redir2.0 new file mode 100644 index 00000000..b0b3d2cc --- /dev/null +++ b/bin/1sh/tests/parser/only-redir2.0 @@ -0,0 +1,2 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/only-redir2.0 254335 2013-08-14 19:34:13Z jilles $ +</dev/null | : diff --git a/bin/1sh/tests/parser/only-redir3.0 b/bin/1sh/tests/parser/only-redir3.0 new file mode 100644 index 00000000..972c9aef --- /dev/null +++ b/bin/1sh/tests/parser/only-redir3.0 @@ -0,0 +1,2 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/only-redir3.0 254335 2013-08-14 19:34:13Z jilles $ +case x in x) </dev/null ;; esac diff --git a/bin/1sh/tests/parser/only-redir4.0 b/bin/1sh/tests/parser/only-redir4.0 new file mode 100644 index 00000000..9e7cda83 --- /dev/null +++ b/bin/1sh/tests/parser/only-redir4.0 @@ -0,0 +1,2 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/only-redir4.0 254335 2013-08-14 19:34:13Z jilles $ +case x in x) </dev/null ;& esac diff --git a/bin/1sh/tests/parser/pipe-not1.0 b/bin/1sh/tests/parser/pipe-not1.0 new file mode 100644 index 00000000..de83b6b2 --- /dev/null +++ b/bin/1sh/tests/parser/pipe-not1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/pipe-not1.0 214281 2010-10-24 17:06:49Z jilles $ + +: | ! : | false diff --git a/bin/1sh/tests/parser/set-v1.0 b/bin/1sh/tests/parser/set-v1.0 new file mode 100644 index 00000000..eb985e6b --- /dev/null +++ b/bin/1sh/tests/parser/set-v1.0 @@ -0,0 +1,8 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/set-v1.0 295937 2016-02-23 22:44:01Z jilles $ + +${SH} <<\EOF +echo one >&2 +set -v +echo two >&2 +echo three >&2 +EOF diff --git a/bin/1sh/tests/parser/set-v1.0.stderr b/bin/1sh/tests/parser/set-v1.0.stderr new file mode 100644 index 00000000..d904fa5f --- /dev/null +++ b/bin/1sh/tests/parser/set-v1.0.stderr @@ -0,0 +1,5 @@ +one +echo two >&2 +two +echo three >&2 +three diff --git a/bin/1sh/tests/parser/var-assign1.0 b/bin/1sh/tests/parser/var-assign1.0 new file mode 100644 index 00000000..244ccfc9 --- /dev/null +++ b/bin/1sh/tests/parser/var-assign1.0 @@ -0,0 +1,19 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/parser/var-assign1.0 257920 2013-11-10 18:46:59Z jilles $ +# In a variable assignment, both the name and the equals sign must be entirely +# unquoted. Therefore, there is only one assignment below; the other words +# containing equals signs are command words. + +abc=0 +\abc=1 2>/dev/null +a\bc=2 2>/dev/null +abc\=3 2>/dev/null +a\bc\=4 2>/dev/null +'abc'=5 2>/dev/null +a'b'c=6 2>/dev/null +abc'='7 2>/dev/null +'abc=8' 2>/dev/null +"abc"=9 2>/dev/null +a"b"c=10 2>/dev/null +abc"="11 2>/dev/null +"abc=12" 2>/dev/null +[ "$abc" = 0 ] diff --git a/bin/1sh/tests/set-e/Makefile b/bin/1sh/tests/set-e/Makefile new file mode 100644 index 00000000..d938300e --- /dev/null +++ b/bin/1sh/tests/set-e/Makefile @@ -0,0 +1,46 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/Makefile 299094 2016-05-04 23:20:53Z ngie $ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T} + +.PATH: ${.CURDIR:H} +ATF_TESTS_SH= functional_test + +${PACKAGE}FILES+= and1.0 +${PACKAGE}FILES+= and2.1 +${PACKAGE}FILES+= and3.0 +${PACKAGE}FILES+= and4.0 +${PACKAGE}FILES+= background1.0 +${PACKAGE}FILES+= cmd1.0 +${PACKAGE}FILES+= cmd2.1 +${PACKAGE}FILES+= elif1.0 +${PACKAGE}FILES+= elif2.0 +${PACKAGE}FILES+= eval1.0 +${PACKAGE}FILES+= eval2.1 +${PACKAGE}FILES+= for1.0 +${PACKAGE}FILES+= func1.0 +${PACKAGE}FILES+= func2.1 +${PACKAGE}FILES+= if1.0 +${PACKAGE}FILES+= if2.0 +${PACKAGE}FILES+= if3.0 +${PACKAGE}FILES+= not1.0 +${PACKAGE}FILES+= not2.0 +${PACKAGE}FILES+= or1.0 +${PACKAGE}FILES+= or2.0 +${PACKAGE}FILES+= or3.1 +${PACKAGE}FILES+= pipe1.1 +${PACKAGE}FILES+= pipe2.0 +${PACKAGE}FILES+= return1.0 +${PACKAGE}FILES+= semi1.1 +${PACKAGE}FILES+= semi2.1 +${PACKAGE}FILES+= subshell1.0 +${PACKAGE}FILES+= subshell2.1 +${PACKAGE}FILES+= until1.0 +${PACKAGE}FILES+= until2.0 +${PACKAGE}FILES+= until3.0 +${PACKAGE}FILES+= while1.0 +${PACKAGE}FILES+= while2.0 +${PACKAGE}FILES+= while3.0 + +.include <bsd.test.mk> diff --git a/bin/1sh/tests/set-e/Makefile.depend b/bin/1sh/tests/set-e/Makefile.depend new file mode 100644 index 00000000..110a72e1 --- /dev/null +++ b/bin/1sh/tests/set-e/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/Makefile.depend 296587 2016-03-09 22:46:01Z bdrewery $ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/1sh/tests/set-e/and1.0 b/bin/1sh/tests/set-e/and1.0 new file mode 100644 index 00000000..dc5a0108 --- /dev/null +++ b/bin/1sh/tests/set-e/and1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/and1.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +true && true diff --git a/bin/1sh/tests/set-e/and2.1 b/bin/1sh/tests/set-e/and2.1 new file mode 100644 index 00000000..c964f3df --- /dev/null +++ b/bin/1sh/tests/set-e/and2.1 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/and2.1 149781 2005-09-04 11:59:59Z stefanf $ +set -e +true && false +exit 0 diff --git a/bin/1sh/tests/set-e/and3.0 b/bin/1sh/tests/set-e/and3.0 new file mode 100644 index 00000000..b3ef8b3d --- /dev/null +++ b/bin/1sh/tests/set-e/and3.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/and3.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +false && true +exit 0 diff --git a/bin/1sh/tests/set-e/and4.0 b/bin/1sh/tests/set-e/and4.0 new file mode 100644 index 00000000..356804dd --- /dev/null +++ b/bin/1sh/tests/set-e/and4.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/and4.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +false && false +exit 0 diff --git a/bin/1sh/tests/set-e/background1.0 b/bin/1sh/tests/set-e/background1.0 new file mode 100644 index 00000000..b7d70c7e --- /dev/null +++ b/bin/1sh/tests/set-e/background1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/background1.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +false & diff --git a/bin/1sh/tests/set-e/cmd1.0 b/bin/1sh/tests/set-e/cmd1.0 new file mode 100644 index 00000000..f88034f7 --- /dev/null +++ b/bin/1sh/tests/set-e/cmd1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/cmd1.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +true diff --git a/bin/1sh/tests/set-e/cmd2.1 b/bin/1sh/tests/set-e/cmd2.1 new file mode 100644 index 00000000..a8dbf64f --- /dev/null +++ b/bin/1sh/tests/set-e/cmd2.1 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/cmd2.1 149781 2005-09-04 11:59:59Z stefanf $ +set -e +false +exit 0 diff --git a/bin/1sh/tests/set-e/elif1.0 b/bin/1sh/tests/set-e/elif1.0 new file mode 100644 index 00000000..aa8ee945 --- /dev/null +++ b/bin/1sh/tests/set-e/elif1.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/elif1.0 211399 2010-08-16 17:18:08Z jilles $ +set -e +if false; then + : +elif false; then + : +fi diff --git a/bin/1sh/tests/set-e/elif2.0 b/bin/1sh/tests/set-e/elif2.0 new file mode 100644 index 00000000..f742f81b --- /dev/null +++ b/bin/1sh/tests/set-e/elif2.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/elif2.0 211399 2010-08-16 17:18:08Z jilles $ +set -e +if false; then + : +elif false; false; then + : +fi diff --git a/bin/1sh/tests/set-e/eval1.0 b/bin/1sh/tests/set-e/eval1.0 new file mode 100644 index 00000000..257ccd06 --- /dev/null +++ b/bin/1sh/tests/set-e/eval1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/eval1.0 193178 2009-05-31 17:23:27Z stefanf $ +set -e +eval false || true diff --git a/bin/1sh/tests/set-e/eval2.1 b/bin/1sh/tests/set-e/eval2.1 new file mode 100644 index 00000000..c6f161b9 --- /dev/null +++ b/bin/1sh/tests/set-e/eval2.1 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/eval2.1 193178 2009-05-31 17:23:27Z stefanf $ +set -e +eval false +exit 0 diff --git a/bin/1sh/tests/set-e/for1.0 b/bin/1sh/tests/set-e/for1.0 new file mode 100644 index 00000000..344d851c --- /dev/null +++ b/bin/1sh/tests/set-e/for1.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/for1.0 149791 2005-09-05 09:42:10Z stefanf $ +set -e +f() { + for i in a b c; do + false + true + done +} +f || true diff --git a/bin/1sh/tests/set-e/func1.0 b/bin/1sh/tests/set-e/func1.0 new file mode 100644 index 00000000..a3435785 --- /dev/null +++ b/bin/1sh/tests/set-e/func1.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/func1.0 149791 2005-09-05 09:42:10Z stefanf $ +set -e +f() { + false + true +} +f || true diff --git a/bin/1sh/tests/set-e/func2.1 b/bin/1sh/tests/set-e/func2.1 new file mode 100644 index 00000000..1ac1b46f --- /dev/null +++ b/bin/1sh/tests/set-e/func2.1 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/func2.1 149791 2005-09-05 09:42:10Z stefanf $ +set -e +f() { + false + exit 0 +} +f diff --git a/bin/1sh/tests/set-e/if1.0 b/bin/1sh/tests/set-e/if1.0 new file mode 100644 index 00000000..19cf155f --- /dev/null +++ b/bin/1sh/tests/set-e/if1.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/if1.0 211399 2010-08-16 17:18:08Z jilles $ +set -e +if false; then + : +fi diff --git a/bin/1sh/tests/set-e/if2.0 b/bin/1sh/tests/set-e/if2.0 new file mode 100644 index 00000000..52e6e596 --- /dev/null +++ b/bin/1sh/tests/set-e/if2.0 @@ -0,0 +1,7 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/if2.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +# PR 28852 +if true; then + false && true +fi +exit 0 diff --git a/bin/1sh/tests/set-e/if3.0 b/bin/1sh/tests/set-e/if3.0 new file mode 100644 index 00000000..61598039 --- /dev/null +++ b/bin/1sh/tests/set-e/if3.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/if3.0 211399 2010-08-16 17:18:08Z jilles $ +set -e +if false; false; then + : +fi diff --git a/bin/1sh/tests/set-e/not1.0 b/bin/1sh/tests/set-e/not1.0 new file mode 100644 index 00000000..5421ab4a --- /dev/null +++ b/bin/1sh/tests/set-e/not1.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/not1.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +! true +exit 0 diff --git a/bin/1sh/tests/set-e/not2.0 b/bin/1sh/tests/set-e/not2.0 new file mode 100644 index 00000000..146f4949 --- /dev/null +++ b/bin/1sh/tests/set-e/not2.0 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/not2.0 193178 2009-05-31 17:23:27Z stefanf $ +set -e +! false +! eval false diff --git a/bin/1sh/tests/set-e/or1.0 b/bin/1sh/tests/set-e/or1.0 new file mode 100644 index 00000000..bdf99f7a --- /dev/null +++ b/bin/1sh/tests/set-e/or1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/or1.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +true || false diff --git a/bin/1sh/tests/set-e/or2.0 b/bin/1sh/tests/set-e/or2.0 new file mode 100644 index 00000000..d96ae4a7 --- /dev/null +++ b/bin/1sh/tests/set-e/or2.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/or2.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +false || true diff --git a/bin/1sh/tests/set-e/or3.1 b/bin/1sh/tests/set-e/or3.1 new file mode 100644 index 00000000..3cde1bff --- /dev/null +++ b/bin/1sh/tests/set-e/or3.1 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/or3.1 149781 2005-09-04 11:59:59Z stefanf $ +set -e +false || false +exit 0 diff --git a/bin/1sh/tests/set-e/pipe1.1 b/bin/1sh/tests/set-e/pipe1.1 new file mode 100644 index 00000000..b3937ad4 --- /dev/null +++ b/bin/1sh/tests/set-e/pipe1.1 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/pipe1.1 149781 2005-09-04 11:59:59Z stefanf $ +set -e +true | false +exit 0 diff --git a/bin/1sh/tests/set-e/pipe2.0 b/bin/1sh/tests/set-e/pipe2.0 new file mode 100644 index 00000000..50d90306 --- /dev/null +++ b/bin/1sh/tests/set-e/pipe2.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/pipe2.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +false | true diff --git a/bin/1sh/tests/set-e/return1.0 b/bin/1sh/tests/set-e/return1.0 new file mode 100644 index 00000000..39439b51 --- /dev/null +++ b/bin/1sh/tests/set-e/return1.0 @@ -0,0 +1,11 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/return1.0 149788 2005-09-04 21:29:09Z stefanf $ +set -e + +# PR 77067, 85267 +f() { + return 1 + true +} + +f || true +exit 0 diff --git a/bin/1sh/tests/set-e/semi1.1 b/bin/1sh/tests/set-e/semi1.1 new file mode 100644 index 00000000..624d4915 --- /dev/null +++ b/bin/1sh/tests/set-e/semi1.1 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/semi1.1 149781 2005-09-04 11:59:59Z stefanf $ +set -e +false; true +exit 0 diff --git a/bin/1sh/tests/set-e/semi2.1 b/bin/1sh/tests/set-e/semi2.1 new file mode 100644 index 00000000..f6c8d468 --- /dev/null +++ b/bin/1sh/tests/set-e/semi2.1 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/semi2.1 149781 2005-09-04 11:59:59Z stefanf $ +set -e +true; false +exit 0 diff --git a/bin/1sh/tests/set-e/subshell1.0 b/bin/1sh/tests/set-e/subshell1.0 new file mode 100644 index 00000000..7975eac9 --- /dev/null +++ b/bin/1sh/tests/set-e/subshell1.0 @@ -0,0 +1,3 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/subshell1.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +(true) diff --git a/bin/1sh/tests/set-e/subshell2.1 b/bin/1sh/tests/set-e/subshell2.1 new file mode 100644 index 00000000..6b441cd5 --- /dev/null +++ b/bin/1sh/tests/set-e/subshell2.1 @@ -0,0 +1,4 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/subshell2.1 149781 2005-09-04 11:59:59Z stefanf $ +set -e +(false) +exit 0 diff --git a/bin/1sh/tests/set-e/until1.0 b/bin/1sh/tests/set-e/until1.0 new file mode 100644 index 00000000..281b4617 --- /dev/null +++ b/bin/1sh/tests/set-e/until1.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/until1.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +until false; do + break +done diff --git a/bin/1sh/tests/set-e/until2.0 b/bin/1sh/tests/set-e/until2.0 new file mode 100644 index 00000000..8b571d31 --- /dev/null +++ b/bin/1sh/tests/set-e/until2.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/until2.0 149781 2005-09-04 11:59:59Z stefanf $ +set -e +until false; false; do + break +done diff --git a/bin/1sh/tests/set-e/until3.0 b/bin/1sh/tests/set-e/until3.0 new file mode 100644 index 00000000..cc39584c --- /dev/null +++ b/bin/1sh/tests/set-e/until3.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/until3.0 149791 2005-09-05 09:42:10Z stefanf $ +set -e +f() { + until false; do + false + break + done +} +f || true diff --git a/bin/1sh/tests/set-e/while1.0 b/bin/1sh/tests/set-e/while1.0 new file mode 100644 index 00000000..a60532c8 --- /dev/null +++ b/bin/1sh/tests/set-e/while1.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/while1.0 211399 2010-08-16 17:18:08Z jilles $ +set -e +while false; do + : +done diff --git a/bin/1sh/tests/set-e/while2.0 b/bin/1sh/tests/set-e/while2.0 new file mode 100644 index 00000000..d0e4f71a --- /dev/null +++ b/bin/1sh/tests/set-e/while2.0 @@ -0,0 +1,5 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/while2.0 211399 2010-08-16 17:18:08Z jilles $ +set -e +while false; false; do + : +done diff --git a/bin/1sh/tests/set-e/while3.0 b/bin/1sh/tests/set-e/while3.0 new file mode 100644 index 00000000..5a1b2695 --- /dev/null +++ b/bin/1sh/tests/set-e/while3.0 @@ -0,0 +1,9 @@ +# $FreeBSD: releng/12.1/bin/sh/tests/set-e/while3.0 149791 2005-09-05 09:42:10Z stefanf $ +set -e +f() { + while true; do + false + break + done +} +f || true diff --git a/bin/1sh/trap.c b/bin/1sh/trap.c new file mode 100644 index 00000000..1622a319 --- /dev/null +++ b/bin/1sh/trap.c @@ -0,0 +1,553 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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[] = "@(#)trap.c 8.5 (Berkeley) 6/5/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/trap.c 326025 2017-11-20 19:49:47Z pfg $"); + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#include "main.h" +#include "nodes.h" /* for other headers */ +#include "eval.h" +#include "jobs.h" +#include "show.h" +#include "options.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "trap.h" +#include "mystring.h" +#include "builtins.h" +#include "myhistedit.h" + + +/* + * 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 permanently */ +#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + + +static char sigmode[NSIG]; /* current value of signal */ +volatile sig_atomic_t pendingsig; /* indicates some signal received */ +volatile sig_atomic_t pendingsig_waitcmd; /* indicates wait builtin should be interrupted */ +static int in_dotrap; /* do we execute in a trap handler? */ +static char *volatile trap[NSIG]; /* trap handler commands */ +static volatile sig_atomic_t gotsig[NSIG]; + /* indicates specified signal received */ +static int ignore_sigchld; /* Used while handling SIGCHLD traps. */ +static int last_trapsig; + +static int exiting; /* exitshell() has been called */ +static int exiting_exitstatus; /* value passed to exitshell() */ + +static int getsigaction(int, sig_t *); + + +/* + * Map a string to a signal number. + * + * Note: the signal number may exceed NSIG. + */ +static int +sigstring_to_signum(char *sig) +{ + + if (is_number(sig)) { + int signo; + + signo = atoi(sig); + return ((signo >= 0 && signo < NSIG) ? signo : (-1)); + } else if (strcasecmp(sig, "EXIT") == 0) { + return (0); + } else { + int n; + + if (strncasecmp(sig, "SIG", 3) == 0) + sig += 3; + for (n = 1; n < sys_nsig; n++) + if (sys_signame[n] && + strcasecmp(sys_signame[n], sig) == 0) + return (n); + } + return (-1); +} + + +/* + * Print a list of valid signal names. + */ +static void +printsignals(void) +{ + int n, outlen; + + outlen = 0; + for (n = 1; n < sys_nsig; n++) { + if (sys_signame[n]) { + out1fmt("%s", sys_signame[n]); + outlen += strlen(sys_signame[n]); + } else { + out1fmt("%d", n); + outlen += 3; /* good enough */ + } + ++outlen; + if (outlen > 71 || n == sys_nsig - 1) { + out1str("\n"); + outlen = 0; + } else { + out1c(' '); + } + } +} + + +/* + * The trap builtin. + */ +int +trapcmd(int argc __unused, char **argv) +{ + char *action; + int signo; + int errors = 0; + int i; + + while ((i = nextopt("l")) != '\0') { + switch (i) { + case 'l': + printsignals(); + return (0); + } + } + argv = argptr; + + if (*argv == NULL) { + for (signo = 0 ; signo < sys_nsig ; signo++) { + if (signo < NSIG && trap[signo] != NULL) { + out1str("trap -- "); + out1qstr(trap[signo]); + if (signo == 0) { + out1str(" EXIT\n"); + } else if (sys_signame[signo]) { + out1fmt(" %s\n", sys_signame[signo]); + } else { + out1fmt(" %d\n", signo); + } + } + } + return 0; + } + action = NULL; + if (*argv && !is_number(*argv)) { + if (strcmp(*argv, "-") == 0) + argv++; + else { + action = *argv; + argv++; + } + } + for (; *argv; argv++) { + if ((signo = sigstring_to_signum(*argv)) == -1) { + warning("bad signal %s", *argv); + errors = 1; + continue; + } + INTOFF; + if (action) + action = savestr(action); + if (trap[signo]) + ckfree(trap[signo]); + trap[signo] = action; + if (signo != 0) + setsignal(signo); + INTON; + } + return errors; +} + + +/* + * Clear traps on a fork. + */ +void +clear_traps(void) +{ + char *volatile *tp; + + for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + INTOFF; + ckfree(*tp); + *tp = NULL; + if (tp != &trap[0]) + setsignal(tp - trap); + INTON; + } + } +} + + +/* + * Check if we have any traps enabled. + */ +int +have_traps(void) +{ + char *volatile *tp; + + for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) { + if (*tp && **tp) /* trap not NULL or SIG_IGN */ + return 1; + } + return 0; +} + +/* + * Set the signal handler for the specified signal. The routine figures + * out what it should be set to. + */ +void +setsignal(int signo) +{ + int action; + sig_t sigact = SIG_DFL; + struct sigaction sa; + char *t; + + if ((t = trap[signo]) == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + else + action = S_IGN; + if (action == S_DFL) { + switch (signo) { + case SIGINT: + action = S_CATCH; + break; + case SIGQUIT: +#ifdef DEBUG + { + extern int debug; + + if (debug) + break; + } +#endif + action = S_CATCH; + break; + case SIGTERM: + if (rootshell && iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (rootshell && mflag) + action = S_IGN; + break; +#endif + } + } + + t = &sigmode[signo]; + if (*t == 0) { + /* + * current setting unknown + */ + if (!getsigaction(signo, &sigact)) { + /* + * 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 (sigact == SIG_IGN) { + if (mflag && (signo == SIGTSTP || + signo == SIGTTIN || signo == SIGTTOU)) { + *t = S_IGN; /* don't hard ignore these */ + } else + *t = S_HARD_IGN; + } else { + *t = S_RESET; /* force to be set */ + } + } + if (*t == S_HARD_IGN || *t == action) + return; + switch (action) { + case S_DFL: sigact = SIG_DFL; break; + case S_CATCH: sigact = onsig; break; + case S_IGN: sigact = SIG_IGN; break; + } + *t = action; + sa.sa_handler = sigact; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(signo, &sa, NULL); +} + + +/* + * Return the current setting for sig w/o changing it. + */ +static int +getsigaction(int signo, sig_t *sigact) +{ + struct sigaction sa; + + if (sigaction(signo, (struct sigaction *)0, &sa) == -1) + return 0; + *sigact = (sig_t) sa.sa_handler; + return 1; +} + + +/* + * Ignore a signal. + */ +void +ignoresig(int signo) +{ + + if (sigmode[signo] == 0) + setsignal(signo); + if (sigmode[signo] != S_IGN && sigmode[signo] != S_HARD_IGN) { + signal(signo, SIG_IGN); + sigmode[signo] = S_IGN; + } +} + + +int +issigchldtrapped(void) +{ + + return (trap[SIGCHLD] != NULL && *trap[SIGCHLD] != '\0'); +} + + +/* + * Signal handler. + */ +void +onsig(int signo) +{ + + if (signo == SIGINT && trap[SIGINT] == NULL) { + /* + * The !in_dotrap here is safe. The only way we can arrive + * here with in_dotrap set is that a trap handler set SIGINT to + * SIG_DFL and killed itself. + */ + if (suppressint && !in_dotrap) + SET_PENDING_INT; + else + onint(); + return; + } + + /* If we are currently in a wait builtin, prepare to break it */ + if (signo == SIGINT || signo == SIGQUIT) + pendingsig_waitcmd = signo; + + if (trap[signo] != NULL && trap[signo][0] != '\0' && + (signo != SIGCHLD || !ignore_sigchld)) { + gotsig[signo] = 1; + pendingsig = signo; + pendingsig_waitcmd = signo; + } +} + + +/* + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. + */ +void +dotrap(void) +{ + struct stackmark smark; + int i; + int savestatus, prev_evalskip, prev_skipcount; + + in_dotrap++; + for (;;) { + pendingsig = 0; + pendingsig_waitcmd = 0; + for (i = 1; i < NSIG; i++) { + if (gotsig[i]) { + gotsig[i] = 0; + if (trap[i]) { + /* + * Ignore SIGCHLD to avoid infinite + * recursion if the trap action does + * a fork. + */ + if (i == SIGCHLD) + ignore_sigchld++; + + /* + * Backup current evalskip + * state and reset it before + * executing a trap, so that the + * trap is not disturbed by an + * ongoing break/continue/return + * statement. + */ + prev_evalskip = evalskip; + prev_skipcount = skipcount; + evalskip = 0; + + last_trapsig = i; + savestatus = exitstatus; + setstackmark(&smark); + evalstring(stsavestr(trap[i]), 0); + popstackmark(&smark); + + /* + * If such a command was not + * already in progress, allow a + * break/continue/return in the + * trap action to have an effect + * outside of it. + */ + if (evalskip == 0 || + prev_evalskip != 0) { + evalskip = prev_evalskip; + skipcount = prev_skipcount; + exitstatus = savestatus; + } + + if (i == SIGCHLD) + ignore_sigchld--; + } + break; + } + } + if (i >= NSIG) + break; + } + in_dotrap--; +} + + +/* + * Controls whether the shell is interactive or not based on iflag. + */ +void +setinteractive(void) +{ + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); +} + + +/* + * Called to exit the shell. + */ +void +exitshell(int status) +{ + TRACE(("exitshell(%d) pid=%d\n", status, getpid())); + exiting = 1; + exiting_exitstatus = status; + exitshell_savedstatus(); +} + +void +exitshell_savedstatus(void) +{ + struct jmploc loc1, loc2; + char *p; + int sig = 0; + sigset_t sigs; + + if (!exiting) { + if (in_dotrap && last_trapsig) { + sig = last_trapsig; + exiting_exitstatus = sig + 128; + } else + exiting_exitstatus = oexitstatus; + } + exitstatus = oexitstatus = exiting_exitstatus; + if (!setjmp(loc1.loc)) { + handler = &loc1; + if ((p = trap[0]) != NULL && *p != '\0') { + /* + * Reset evalskip, or the trap on EXIT could be + * interrupted if the last command was a "return". + */ + evalskip = 0; + trap[0] = NULL; + FORCEINTON; + evalstring(p, 0); + } + } + if (!setjmp(loc2.loc)) { + handler = &loc2; /* probably unnecessary */ + FORCEINTON; + flushall(); +#if JOBS + setjobctl(0); +#endif + } + if (sig != 0 && sig != SIGSTOP && sig != SIGTSTP && sig != SIGTTIN && + sig != SIGTTOU) { + signal(sig, SIG_DFL); + sigemptyset(&sigs); + sigaddset(&sigs, sig); + sigprocmask(SIG_UNBLOCK, &sigs, NULL); + kill(getpid(), sig); + /* If the default action is to ignore, fall back to _exit(). */ + } + _exit(exiting_exitstatus); +} diff --git a/bin/1sh/trap.h b/bin/1sh/trap.h new file mode 100644 index 00000000..88076fc2 --- /dev/null +++ b/bin/1sh/trap.h @@ -0,0 +1,50 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)trap.h 8.3 (Berkeley) 6/5/95 + * $FreeBSD: releng/12.1/bin/sh/trap.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +extern volatile sig_atomic_t pendingsig; +extern volatile sig_atomic_t pendingsig_waitcmd; + +void clear_traps(void); +int have_traps(void); +void setsignal(int); +void ignoresig(int); +int issigchldtrapped(void); +void onsig(int); +void dotrap(void); +void setinteractive(void); +void exitshell(int) __dead2; +void exitshell_savedstatus(void) __dead2; diff --git a/bin/1sh/var.c b/bin/1sh/var.c new file mode 100644 index 00000000..2c217399 --- /dev/null +++ b/bin/1sh/var.c @@ -0,0 +1,971 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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 <sys/cdefs.h> +__FBSDID("$FreeBSD: releng/12.1/bin/sh/var.c 329221 2018-02-13 16:48:57Z bdrewery $"); + +#include <unistd.h> +#include <stdlib.h> +#include <paths.h> + +/* + * Shell variables. + */ + +#include <locale.h> +#include <langinfo.h> + +#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 + + +#ifndef VTABSIZE +#define VTABSIZE 39 +#endif + + +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 vps1; +struct var vps2; +struct var vps4; +static struct var voptind; +struct var vdisvfork; + +struct localvar *localvars; +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 }, + /* + * 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 *); +static void setvareq_const(const char *s, int flags); + +extern char **environ; + +/* + * 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; + } + fmtstr(ppid, sizeof(ppid), "%d", (int)getppid()); + setvarsafe("PPID", ppid, 0); + for (envp = environ ; *envp ; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } + setvareq_const("OPTIND=1", 0); + setvareq_const("IFS= \t\n", 0); +} + +/* + * 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; + size_t len; + size_t namelen; + size_t vallen; + 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", (int)namelen, name); + len = namelen + 2; /* 2 is space for '=' and '\0' */ + if (val == NULL) { + flags |= VUNSET; + vallen = 0; + } else { + vallen = strlen(val); + len += vallen; + } + INTOFF; + nameeq = ckmalloc(len); + memcpy(nameeq, name, namelen); + nameeq[namelen] = '='; + if (val) + memcpy(nameeq + namelen + 1, val, vallen + 1); + else + nameeq[namelen + 1] = '\0'; + setvareq(nameeq, flags); + INTON; +} + +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; + + INTOFF; + 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); + INTON; + + 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) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + error("%.*s: is read only", vp->name_len, vp->text); + } + if (flags & VNOSET) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + 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 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) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + INTOFF; + vp = ckmalloc(sizeof (*vp)); + vp->flags = flags; + vp->text = s; + vp->name_len = nlen; + vp->next = *vpp; + vp->func = NULL; + *vpp = vp; + if ((vp->flags & VEXPORT) && localevar(s)) { + change_env(s, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + INTON; +} + + +static void +setvareq_const(const char *s, int flags) +{ + setvareq(__DECONST(char *, s), flags | VTEXTFIXED); +} + + +/* + * Process a linked list of variable assignments. + */ + +void +listsetvar(struct arglist *list, int flags) +{ + int i; + + INTOFF; + for (i = 0; i < list->count; i++) + setvareq(savestr(list->args[i]), 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 var *v; + char *result; + int i; + + result = NULL; + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (varequal(cmdenviron->args[i], name)) + result = strchr(cmdenviron->args[i], '=') + 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) +{ + int act = 0; + char *loc, *locdef; + int i; + + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (localevar(cmdenviron->args[i])) { + 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) +{ + int i; + + INTOFF; + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (localevar(cmdenviron->args[i])) { + setlocale(LC_ALL, ""); + updatecharset(); + break; + } + } + 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; + + nextopt(""); + 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 optval); + memcpy(lvp->text, optval, sizeof optval); + 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; + int islocalevar; + + INTOFF; + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + if (vp == NULL) { /* $- saved */ + memcpy(optval, lvp->text, sizeof optval); + ckfree(lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + vp->flags &= ~VREADONLY; + (void)unsetvar(vp->text); + } else { + islocalevar = (vp->flags | lvp->flags) & VEXPORT && + localevar(lvp->text); + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + if (vp->func) + (*vp->func)(vp->text + vp->name_len + 1); + if (islocalevar) { + change_env(vp->text, vp->flags & VEXPORT && + (vp->flags & VUNSET) == 0); + setlocale(LC_ALL, ""); + updatecharset(); + } + } + ckfree(lvp); + } + INTON; +} + + +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; + + INTOFF; + for (ap = argptr; *ap ; ap++) { + if (flg_func) + ret |= unsetfunc(*ap); + if (flg_var) + ret |= unsetvar(*ap); + } + INTON; + return ret; +} + + +/* + * Unset the specified variable. + * Called with interrupts off. + */ + +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); + if (vp->text[vp->name_len + 1] != '\0') + setvar(s, "", 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); + } + return (0); +} + + + +/* + * Returns true if the two strings specify the same variable. 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 characters 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; +} diff --git a/bin/1sh/var.h b/bin/1sh/var.h new file mode 100644 index 00000000..f36bdc68 --- /dev/null +++ b/bin/1sh/var.h @@ -0,0 +1,132 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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. + * + * @(#)var.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/var.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* + * Shell variables. + */ + +/* 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 VNOLOCAL 0x100 /* ignore forcelocal */ + + +struct var { + struct var *next; /* next entry in hash list */ + int flags; /* flags are defined above */ + int name_len; /* length of name */ + 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 */ + char *text; /* saved text */ +}; + + +extern struct localvar *localvars; +extern int forcelocal; + +extern struct var vifs; +extern struct var vmail; +extern struct var vmpath; +extern struct var vpath; +extern struct var vps1; +extern struct var vps2; +extern struct var vps4; +extern struct var vdisvfork; +#ifndef NO_HISTORY +extern struct var vhistsize; +extern struct var vterm; +#endif + +extern int localeisutf8; +/* The parser uses the locale that was in effect at startup. */ +extern int initial_localeisutf8; + +/* + * 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) +#ifndef NO_HISTORY +#define histsizeval() (vhistsize.text + 9) +#define termval() (vterm.text + 5) +#endif + +#define mpathset() ((vmpath.flags & VUNSET) == 0) +#define disvforkset() ((vdisvfork.flags & VUNSET) == 0) + +void initvar(void); +void setvar(const char *, const char *, int); +void setvareq(char *, int); +struct arglist; +void listsetvar(struct arglist *, int); +char *lookupvar(const char *); +char *bltinlookup(const char *, int); +void bltinsetlocale(void); +void bltinunsetlocale(void); +void updatecharset(void); +void initcharset(void); +char **environment(void); +int showvarscmd(int, char **); +void mklocal(char *); +void poplocalvars(void); +int unsetvar(const char *); +int setvarsafe(const char *, const char *, int); |