From 07cad228ae4d1567aeb6d13d2af83d0a28d13b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Wed, 14 Dec 2022 02:06:05 +0100 Subject: [PATCH 01/59] parser: Invalid redirections are run-time, not syntax errors This fixes a long-standing bug where echo 'echo >&a' | sh errors out with sh: 2: Syntax error: Bad fd number despite the error being on line 1 This patch makes the error sh: 1: Bad fd number: a as expected Adapted-from: https://github.com/hvdijk/gwsh/commit/d279523041c1c380d64b6dec7760feba20bbf6b5 Signed-off-by: Herbert Xu --- src/parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser.c b/src/parser.c index a552c47..8a06b9e 100644 --- a/src/parser.c +++ b/src/parser.c @@ -615,7 +615,7 @@ void fixredir(union node *n, const char *text, int err) else { if (err) - synerror("Bad fd number"); + sh_error("Bad fd number: %s", text); else n->ndup.vname = makename(); } From 8dbbff647de1e3a1510866fa1ec983c3b88f432c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Wed, 14 Dec 2022 03:51:13 +0100 Subject: [PATCH 02/59] builtin: Don't early-exit when first hash -r is found This fixes incorrectly-accepted "hash -rv" being equivalent to hash -r (well, hash -r[literally anything] being equivalent to hash -r) Also remove -v from the manual, it doesn't appear to have ever existed Link: https://bugs.debian.org/819829 Signed-off-by: Herbert Xu --- src/dash.1 | 6 ++---- src/exec.c | 8 +++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/dash.1 b/src/dash.1 index ff02237..3e09090 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -1441,7 +1441,8 @@ cmd \-a \-c arg file file cmd \-carg -a file file cmd \-a \-carg \-\- file file .Ed -.It hash Fl rv Ar command ... +.It hash Op Ar command ... +.It hash Fl r The shell maintains a hash table which remembers the locations of commands. With no arguments whatsoever, @@ -1457,9 +1458,6 @@ With arguments, the .Ic hash command removes the specified commands from the hash table (unless they are functions) and then locates them. -With the -.Fl v -option, hash prints the locations of the commands as it finds them. The .Fl r option causes the hash command to delete all the entries in the hash table diff --git a/src/exec.c b/src/exec.c index 87354d4..d7a1f53 100644 --- a/src/exec.c +++ b/src/exec.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #ifdef HAVE_PATHS_H #include @@ -271,11 +272,16 @@ hashcmd(int argc, char **argv) int c; struct cmdentry entry; char *name; + bool clear; - while ((c = nextopt("r")) != '\0') { + clear = false; + while ((c = nextopt("r")) != '\0') + clear = true; + if(clear) { clearcmdentry(); return 0; } + if (*argptr == NULL) { for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { From 5c55b53f2b028228837f712b3c3e13363c3310e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Wed, 14 Dec 2022 17:52:04 +0100 Subject: [PATCH 03/59] man: Fix getopts documentation The explicit arguments were missing, also exchange expr subst for arithmetic and fix the spacing around Bell Labs Signed-off-by: Herbert Xu --- src/dash.1 | 13 +++++++++---- src/options.c | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/dash.1 b/src/dash.1 index 3e09090..1683d43 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -1342,13 +1342,12 @@ The number of previous commands that are accessible. .El .It fg Op Ar job Move the specified job or the current job to the foreground. -.It getopts Ar optstring var +.It getopts Ar optstring var Op Ar arg ... The .Tn POSIX .Ic getopts command, not to be confused with the -.Em Bell Labs --derived +.Em Bell Labs Ns -derived .Xr getopt 1 . .Pp The first argument should be a series of letters, each of which may be @@ -1386,6 +1385,12 @@ then .Ev OPTARG will be unset. .Pp +By default, the variables +.Va $1 , ... , $n +are inspected; if +.Ar arg Ns s +are specified, they'll be parsed instead. +.Pp .Va optstring is a string of recognized option letters (see .Xr getopt 3 ) . @@ -1430,7 +1435,7 @@ do \\?) echo $USAGE; exit 1;; esac done -shift `expr $OPTIND - 1` +shift $((OPTIND - 1)) .Ed .Pp This code will accept any of the following as equivalent: diff --git a/src/options.c b/src/options.c index a46c23b..3158498 100644 --- a/src/options.c +++ b/src/options.c @@ -410,7 +410,7 @@ getoptscmd(int argc, char **argv) char **optbase; if (argc < 3) - sh_error("Usage: getopts optstring var [arg]"); + sh_error("Usage: getopts optstring var [arg...]"); else if (argc == 3) { optbase = shellparam.p; if ((unsigned)shellparam.optind > shellparam.nparam + 1) { From d483fa794db46b4e09708b8c98b93d53bee6ed64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Thu, 15 Dec 2022 01:22:18 +0100 Subject: [PATCH 04/59] man: Document jobs builtin Link: https://bugs.debian.org/558607 Signed-off-by: Herbert Xu --- src/dash.1 | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/dash.1 b/src/dash.1 index 1683d43..6a9f673 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -1467,6 +1467,20 @@ The .Fl r option causes the hash command to delete all the entries in the hash table except for functions. +.It jobs Oo Fl lp Oc Op Ar job ... +Display the status of all, or just the specified, +.Ar job Ns s : +.Bl -tag -compact -offset 5n -width "By default" +.It By default +display the job number, currency +.Pq Sy +- +status, if any, the job state, and its shell command. +.It Fl l +also output the PID of the group leader, and just the PID and shell commands +of other members of the job. +.It Fl p +Display only leader PIDs, one per line. +.El .It pwd Op Fl LP builtin command remembers what the current directory is rather than recomputing it each time. From 76d4cc6797251be200dfa25a2bcdfbaed5f6a47a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Thu, 15 Dec 2022 01:22:54 +0100 Subject: [PATCH 05/59] man: Note chdir being the same builtin as cd Signed-off-by: Herbert Xu --- src/dash.1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dash.1 b/src/dash.1 index 6a9f673..89a5a12 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -1143,8 +1143,8 @@ Do not execute the command but search for the command and print the absolute pathname of utilities, the name for builtins or the expansion of aliases. .El -.It cd Ar - -.It Xo cd Op Fl LP +.It cd|chdir Ar - +.It Xo cd|chdir Op Fl LP .Op Ar directory .Xc Switch to the specified directory (default From 447bcdca000f218d56c6a6c5ef610e5d61a917cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Thu, 15 Dec 2022 01:23:01 +0100 Subject: [PATCH 06/59] man: Document kill builtin The manual now contains all built-ins Signed-off-by: Herbert Xu --- src/dash.1 | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/dash.1 b/src/dash.1 index 89a5a12..ce75ce3 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -1481,6 +1481,33 @@ of other members of the job. .It Fl p Display only leader PIDs, one per line. .El +.It kill Oo Fl s Ar sigspec | Fl Ns Ar signum | Fl Ns Ar sigspec Oc Op Ar pid | job ... +Equivalent to +.Xr kill 1 , +but a +.Ar job +spec may also be specified. +Signals can be either case-insensitive names without +.Dv SIG +prefixes or decimal numbers; the default is +.Dv TERM . +.It kill Fl l Op Ar signum | exitstatus +List available signal names without the +.Dv SIG +prefix +.Pq Ar sigspec Ns s . +If +.Ar signum +specified, display just the +.Ar sigspec +for that signal. +If +.Ar exitstatus +specified +.Pq > Sy 128 , +display just the +.Ar sigspec +that caused it. .It pwd Op Fl LP builtin command remembers what the current directory is rather than recomputing it each time. From f1a72d429d3c0cf8449a5e8389f7991776d32c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Thu, 15 Dec 2022 01:25:54 +0100 Subject: [PATCH 07/59] man: Reword to avoid confusion v/v printf Ar argument[s]/arguments The current wording says that given printf a b c d a is the format, c and d are processed as noted, but b is unspecified Signed-off-by: Herbert Xu --- src/dash.1 | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/dash.1 b/src/dash.1 index ce75ce3..98bb701 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -1572,30 +1572,26 @@ With the option specified the output will be formatted suitably for non-interactive use. .\".Pp .It Xo printf Ar format -.Op Ar arguments ... +.Oo Ar value Oc Ns ... .Xc .Ic printf -formats and prints its arguments, after the first, under control -of the -.Ar format . -The -.Ar format -is a character string which contains three types of objects: plain characters, +formats and prints its arguments according to +.Ar format , +a character string which contains three types of objects: plain characters, which are simply copied to standard output, character escape sequences which are converted and copied to the standard output, and format specifications, each of which causes printing of the next successive -.Ar argument . +.Ar value . .Pp -The -.Ar arguments -after the first are treated as strings if the corresponding format is +Each +.Ar value +is treated as a string if the corresponding format specification is either .Cm b , -.Cm c +.Cm c , or .Cm s ; -otherwise it is evaluated as a C constant, with the following extensions: -.Pp +otherwise it is evaluated as a C constant, with the following additions: .Bl -bullet -offset indent -compact .It A leading plus or minus sign is allowed. @@ -1605,8 +1601,9 @@ If the leading character is a single or double quote, the value is the code of the next character. .El .Pp -The format string is reused as often as necessary to satisfy the -.Ar arguments . +The format string is reused as often as necessary until all +.Ar value Ns s +are consumed. Any extra format specifications are evaluated with zero or the null string. .Pp From fc61c2f1b0172c14ce7f6df65be755b54cee1875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Thu, 15 Dec 2022 01:25:59 +0100 Subject: [PATCH 08/59] man: printf 'X, X is a byte under dash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple issues: * the encoding is not always ASCII * what ASCII code is assigned to я * dash isn't internationalised (this is nonconformant but out of scope), and uses the next /byte/; in a UTF-8 locale: $ printf %d\\n \'ą 196 $ printf %d\\n \'я 196 this is in contrast to POSIX (and bash), which says: > If the leading character is a single-quote or double-quote, > the value shall be the numeric value in the underlying codeset > of the character following the single-quote or double-quote. (i.e. mbrtowc(&val, argv[n], ...)) Signed-off-by: Herbert Xu --- src/dash.1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/dash.1 b/src/dash.1 index 98bb701..d077e1d 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -1596,9 +1596,7 @@ otherwise it is evaluated as a C constant, with the following additions: .It A leading plus or minus sign is allowed. .It -If the leading character is a single or double quote, the value is the -.Tn ASCII -code of the next character. +If the leading character is a single or double quote, the value of the next byte. .El .Pp The format string is reused as often as necessary until all From 2beac674c4862a7129eca3a8cb7d27cee7ff667b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Thu, 15 Dec 2022 21:51:19 +0100 Subject: [PATCH 09/59] man: Document false built-in Only true was documented, add false just below it (out of order, but so is true, and the grouping makes much more sense). Signed-off-by: Herbert Xu --- src/dash.1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dash.1 b/src/dash.1 index d077e1d..f143a4a 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -1095,6 +1095,8 @@ etc). .It : .It true A null command that returns a 0 (true) exit value. +.It false +A null command that returns a 1 (false) exit value. .It \&. file The commands in the specified file are read and executed by the shell. .It alias Op Ar name Ns Op Ar "=string ..." From 4bdefd16c6ea4b5b7c2b4dc2fccf5226401e13b7 Mon Sep 17 00:00:00 2001 From: Vincent Lefevre Date: Fri, 16 Dec 2022 18:20:19 +0100 Subject: [PATCH 10/59] builtin: Actually accept ulimit -r The original commit that added it supposes this works, but it only adds it to the ulimit -a listing and the manual, but doesn't allow it as an option. Fixes: 46abc8c6d8a5 ("[BUILTIN] Add support for ulimit -r") Link: https://bugs.debian.org/975326 Signed-off-by: Herbert Xu --- src/miscbltin.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/miscbltin.c b/src/miscbltin.c index 5ccbbcb..e553f9e 100644 --- a/src/miscbltin.c +++ b/src/miscbltin.c @@ -440,6 +440,9 @@ ulimitcmd(int argc, char **argv) #endif #ifdef RLIMIT_LOCKS "w" +#endif +#ifdef RLIMIT_RTPRIO + "r" #endif )) != '\0') switch (optc) { From c3b97c70d8ffec83122caf2bfd0489380610217c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Sat, 17 Dec 2022 16:17:17 +0100 Subject: [PATCH 11/59] man: Document ulimit -w And fix the synopsis. Fixes: 05c1076ba2d1 ("Initial import.)" Link: https://bugs.debian.org/850202 Signed-off-by: Herbert Xu --- src/dash.1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dash.1 b/src/dash.1 index f143a4a..d3893bc 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -2162,7 +2162,7 @@ printed; for commands and tracked aliases the complete pathname of the command is printed. .It ulimit Xo .Op Fl H \*(Ba Fl S -.Op Fl a \*(Ba Fl tfdscmlpnv Op Ar value +.Op Fl a \*(Ba Fl tfdscmlpnvwr Op Ar value .Xc Inquire about or set the hard or soft limits on processes or set new limits. @@ -2215,6 +2215,8 @@ show or set the limit on the number files a process can have open at once .It Fl v show or set the limit on the total virtual memory that can be in use by a process (in kilobytes) +.It Fl w +show or set the limit on the total number of locks held by a process .It Fl r show or set the limit on the real-time scheduling priority of a process .El From 91a375576d37bb4db1eca48e6bf5bac0db6cc3fa Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Tue, 3 Jan 2023 13:32:41 +0800 Subject: [PATCH 12/59] input: Eat rest of line upon reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Interactively, sh_error() doesn't terminate, so echo "|$(printf %10000s)echo bug" | sh -i would read the first 8KiB, see that it's invalid, then jump back to the parser, which would then read and execute the rest of the line as-if it were the next line. The fix for this is to explicitly consume the rest of the invalid line, so that the next line observed is /actually/ the next line. This is difficult to trigger accidentally right now, since we consume the entire icanon line buffer at once (provided it's <8k, which it ~always is interactively), so we always observe one line at a time, but the next patch would make even "| echo bug" blow up. Reported-by: наб Signed-off-by: Herbert Xu --- src/input.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/input.c b/src/input.c index ec075f5..cff15b5 100644 --- a/src/input.c +++ b/src/input.c @@ -77,6 +77,7 @@ INCLUDE INCLUDE INCLUDE "input.h" INCLUDE "error.h" +INCLUDE "syntax.h" INIT { basepf.nextc = basepf.buf = basebuf; @@ -85,9 +86,11 @@ INIT { RESET { /* clear input buffer */ - basepf.lleft = basepf.nleft = 0; - basepf.unget = 0; popallfiles(); + basepf.unget = 0; + while (basepf.lastc[0] != '\n' && + basepf.lastc[0] != PEOF) + pgetc(); } FORKRESET { From 5f094d08c5bcee876191404a4f3dd2d075571215 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Tue, 3 Jan 2023 14:15:58 +0800 Subject: [PATCH 13/59] input: Read standard input byte-wise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POSIX Issue 7, XCU, sh, STDIN says: When the shell is using standard input and it invokes a command that also uses standard input, the shell shall ensure that the standard input file pointer points directly after the command it has read when the command begins execution. It shall not read ahead in such a manner that any characters intended to be read by the invoked command are consumed by the shell (whether interpreted by the shell or not) or that characters that are not read by the invoked command are not seen by the shell. I.e. sh < Signed-off-by: Herbert Xu --- src/input.c | 47 +++++++++++++++++++++++++++++++++++++---------- src/input.h | 6 ++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/input.c b/src/input.c index cff15b5..8691617 100644 --- a/src/input.c +++ b/src/input.c @@ -162,6 +162,17 @@ int pgetc(void) return __pgetc(); } +static int stdin_clear_nonblock(void) +{ + int flags = fcntl(0, F_GETFL, 0); + + if (flags >= 0) { + flags &=~ O_NONBLOCK; + flags = fcntl(0, F_SETFL, flags); + } + + return flags; +} static int preadfd(void) @@ -198,22 +209,38 @@ retry: } else #endif + if (parsefile->fd) nr = read(parsefile->fd, buf, IBUFSIZ - 1); + else { + unsigned len = IBUFSIZ - 1; + nr = 0; + + do { + int err; + + err = read(0, buf, 1); + if (err <= 0) { + if (nr) + break; + + nr = err; + if (errno != EWOULDBLOCK) + break; + if (stdin_clear_nonblock() < 0) + break; + + out2str("sh: turning off NDELAY mode\n"); + goto retry; + } + + nr++; + } while (!IS_DEFINED_SMALL && *buf++ != '\n' && --len); + } 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) { - out2str("sh: turning off NDELAY mode\n"); - goto retry; - } - } - } } return nr; } diff --git a/src/input.h b/src/input.h index 8c39f33..8830b66 100644 --- a/src/input.h +++ b/src/input.h @@ -34,6 +34,12 @@ * @(#)input.h 8.2 (Berkeley) 5/4/95 */ +#ifdef SMALL +#define IS_DEFINED_SMALL 1 +#else +#define IS_DEFINED_SMALL 0 +#endif + /* PEOF (the end of file marker) is defined in syntax.h */ enum { From 44ae22beedf8a3d68bbfa1d065ad677182372de2 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Tue, 3 Jan 2023 17:08:37 +0800 Subject: [PATCH 14/59] input: Disable lleft in SMALL mode Counting lleft is only necessary if history support is enabled. Therefore it can be safely disabled if SMALL is defined. Signed-off-by: Herbert Xu --- src/input.c | 67 +++++++++++++++++++++++------------------------- src/input.h | 18 +++++++++++++ src/myhistedit.h | 18 +++++++++++++ 3 files changed, 68 insertions(+), 35 deletions(-) diff --git a/src/input.c b/src/input.c index 8691617..7b37ae2 100644 --- a/src/input.c +++ b/src/input.c @@ -54,9 +54,7 @@ #include "alias.h" #include "parser.h" #include "main.h" -#ifndef SMALL #include "myhistedit.h" -#endif #define IBUFSIZ (BUFSIZ + 1) @@ -256,12 +254,10 @@ retry: static int preadbuffer(void) { - char *q; - int more; -#ifndef SMALL int something; -#endif char savec; + int more; + char *q; if (unlikely(parsefile->strpush)) { popstring(); @@ -271,11 +267,11 @@ static int preadbuffer(void) return PEOF; flushall(); - more = parsefile->lleft; + more = input_get_lleft(parsefile); if (more <= 0) { again: if ((more = preadfd()) <= 0) { - parsefile->lleft = parsefile->nleft = 0; + input_set_lleft(parsefile, parsefile->nleft = 0); return PEOF; } } @@ -283,37 +279,38 @@ again: q = parsefile->nextc; /* delete nul characters */ -#ifndef SMALL something = 0; -#endif for (;;) { int c; more--; c = *q; - if (!c) + if (!c) { memmove(q, q + 1, more); - else { - q++; - - if (c == '\n') { - parsefile->nleft = q - parsefile->nextc - 1; - break; - } - -#ifndef SMALL - switch (c) { - default: - something = 1; - /* fall through */ - case '\t': - case ' ': - break; - } -#endif + goto check; } + q++; + + if (IS_DEFINED_SMALL) + goto check; + + switch (c) { + case '\n': + parsefile->nleft = q - parsefile->nextc - 1; + goto check; + + default: + something = 1; + /* fall through */ + + case '\t': + case ' ': + break; + } + +check: if (more <= 0) { parsefile->nleft = q - parsefile->nextc - 1; if (parsefile->nleft < 0) @@ -321,12 +318,12 @@ again: break; } } - parsefile->lleft = more; + input_set_lleft(parsefile, more); - savec = *q; + if (!IS_DEFINED_SMALL) + savec = *q; *q = '\0'; -#ifndef SMALL if (parsefile->fd == 0 && hist && something) { HistEvent he; INTOFF; @@ -334,7 +331,6 @@ again: parsefile->nextc); INTON; } -#endif if (vflag) { out2str(parsefile->nextc); @@ -343,7 +339,8 @@ again: #endif } - *q = savec; + if (!IS_DEFINED_SMALL) + *q = savec; return (signed char)*parsefile->nextc++; } @@ -458,7 +455,7 @@ setinputfd(int fd, int push) parsefile->fd = fd; if (parsefile->buf == NULL) parsefile->buf = ckmalloc(IBUFSIZ); - parsefile->lleft = parsefile->nleft = 0; + input_set_lleft(parsefile, parsefile->nleft = 0); plinno = 1; } diff --git a/src/input.h b/src/input.h index 8830b66..1ff5773 100644 --- a/src/input.h +++ b/src/input.h @@ -76,7 +76,9 @@ struct parsefile { int linno; /* current line */ int fd; /* file descriptor (or -1 if string) */ int nleft; /* number of chars left in this line */ +#ifndef SMALL int lleft; /* number of chars left in this buffer */ +#endif char *nextc; /* next char in buffer */ char *buf; /* input buffer */ struct strpush *strpush; /* for pushing strings at this level */ @@ -110,3 +112,19 @@ void setinputstring(char *); void popfile(void); void unwindfiles(struct parsefile *); void popallfiles(void); + +static inline int input_get_lleft(struct parsefile *pf) +{ +#ifdef SMALL + return 0; +#else + return pf->lleft; +#endif +} + +static inline void input_set_lleft(struct parsefile *pf, int len) +{ +#ifndef SMALL + pf->lleft = len; +#endif +} diff --git a/src/myhistedit.h b/src/myhistedit.h index 22e5c43..1736f62 100644 --- a/src/myhistedit.h +++ b/src/myhistedit.h @@ -31,9 +31,27 @@ * @(#)myhistedit.h 8.2 (Berkeley) 5/4/95 */ +#ifdef SMALL +typedef void History; +typedef void EditLine; +typedef int HistEvent; + +enum { + H_APPEND, + H_ENTER, +}; + +#define hist NULL + +static inline void history(History *h, HistEvent *he, int action, char *p) +{ +} +#else #include extern History *hist; +#endif + extern EditLine *el; extern int displayhist; From b4ecd84eb4048522648bc16920d3615cb243a6bf Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Tue, 3 Jan 2023 17:51:18 +0800 Subject: [PATCH 15/59] var: Do not add 1 to return value of strchrnul MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a variable like OPTIND is unset dash may call the action function with a bogus pointer because it tries to add one to the return value of strchrnul unconditionally. Use strchr and nullstr instead. Link: https://bugs.debian.org/985478 Reported-by: наб Signed-off-by: Herbert Xu --- src/var.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/var.c b/src/var.c index ef9c2bd..b70d72c 100644 --- a/src/var.c +++ b/src/var.c @@ -154,6 +154,10 @@ RESET { } #endif +static char *varnull(const char *s) +{ + return (strchr(s, '=') ?: nullstr - 1) + 1; +} /* * This routine initializes the builtin variables. It is called when the @@ -266,7 +270,7 @@ struct var *setvareq(char *s, int flags) goto out; if (vp->func && (flags & VNOFUNC) == 0) - (*vp->func)(strchrnul(s, '=') + 1); + (*vp->func)(varnull(s)); if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) ckfree(vp->text); @@ -531,7 +535,7 @@ poplocalvars(void) unsetvar(vp->text); } else { if (vp->func) - (*vp->func)(strchrnul(lvp->text, '=') + 1); + (*vp->func)(varnull(lvp->text)); if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) ckfree(vp->text); vp->flags = lvp->flags; From 54485578e01017534dae30731f7682abadb38a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Wed, 4 Jan 2023 12:33:45 +0100 Subject: [PATCH 16/59] builtin: Ignore first -- in getopts per POSIX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue 7, XCU, getopts, OPTIONS reads "None.", and getopts isn't a special built-in listed in sexion 2.14 ‒ this means that XCU, 1. Introduction, 1.4 Utility Description Defaults, OPTIONS, Default Behavior applies: Default Behavior: When this section is listed as "None.", it means that the implementation need not support any options. Standard utilities that do not accept options, but that do accept operands, shall recognize "--" as a first argument to be discarded. Test with: getopts -- d: a Correct output is no output, exit 1 Wrong output errors out with d: being an invalid argument name Signed-off-by: Herbert Xu --- src/options.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/options.c b/src/options.c index 3158498..2d4bd3b 100644 --- a/src/options.c +++ b/src/options.c @@ -409,6 +409,9 @@ getoptscmd(int argc, char **argv) { char **optbase; + nextopt(nullstr); + argc -= argptr - argv - 1; + argv = argptr - 1; if (argc < 3) sh_error("Usage: getopts optstring var [arg...]"); else if (argc == 3) { From ba57b84b305dd16f9d3e0d798835a7e9e15454ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Wed, 4 Jan 2023 12:35:13 +0100 Subject: [PATCH 17/59] builtin: Ignore first -- in type for consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This appears to be the only remaining built-in that doesn't use nextopt() to parse its arguments (and isn't forbidden from doing so) ‒ users expect to be able to do this, and it's nice to be consistent here. Test with: type -- ls -- Correct output lists ls=/bin/ls, then --=ENOENT Wrong output lists --=ENOENT, ls=/bin/ls, --=ENOENT Fixes: https://bugs.debian.org/870317 Signed-off-by: Herbert Xu --- src/exec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/exec.c b/src/exec.c index d7a1f53..83cba94 100644 --- a/src/exec.c +++ b/src/exec.c @@ -766,11 +766,11 @@ unsetfunc(const char *name) int typecmd(int argc, char **argv) { - int i; int err = 0; - for (i = 1; i < argc; i++) { - err |= describe_command(out1, argv[i], NULL, 1); + nextopt(nullstr); + while (*argptr) { + err |= describe_command(out1, *argptr++, NULL, 1); } return err; } From de23304e72857733e65355275cddf1415a22c558 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Thu, 5 Jan 2023 15:12:52 +0800 Subject: [PATCH 18/59] input: Check for int_pending while clearing input If we receive SIGINT while clearing a partially read line from stdin we should bail out instead of continuing. Signed-off-by: Herbert Xu Thanks, Signed-off-by: Herbert Xu --- src/input.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/input.c b/src/input.c index 7b37ae2..4c86a75 100644 --- a/src/input.c +++ b/src/input.c @@ -87,7 +87,8 @@ RESET { popallfiles(); basepf.unget = 0; while (basepf.lastc[0] != '\n' && - basepf.lastc[0] != PEOF) + basepf.lastc[0] != PEOF && + !int_pending()) pgetc(); } From 1a0cc2a4f52aefb6ad00eb5b242f39d530460063 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Thu, 5 Jan 2023 15:26:10 +0800 Subject: [PATCH 19/59] input: Only skip blank lines on PS1 Blank line should not be skipped if they're found on PS2. Reported-by: Harald van Dijk Signed-off-by: Herbert Xu --- src/input.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/input.c b/src/input.c index 4c86a75..38969a7 100644 --- a/src/input.c +++ b/src/input.c @@ -255,6 +255,7 @@ retry: static int preadbuffer(void) { + int first = whichprompt == 1; int something; char savec; int more; @@ -280,7 +281,7 @@ again: q = parsefile->nextc; /* delete nul characters */ - something = 0; + something = !first; for (;;) { int c; @@ -328,7 +329,7 @@ check: if (parsefile->fd == 0 && hist && something) { HistEvent he; INTOFF; - history(hist, &he, whichprompt == 1? H_ENTER : H_APPEND, + history(hist, &he, first ? H_ENTER : H_APPEND, parsefile->nextc); INTON; } From dd73362d08d5aa1596cb4ca5b271a08bb4e123c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Thu, 5 Jan 2023 13:43:21 +0100 Subject: [PATCH 20/59] redir: Use F_DUPFD_CLOEXEC instead of F_DUPFD+F_SETFD if available This saves a syscall on every source file open, &c.; F_DUPFD_CLOEXEC is a mandatory part of POSIX since Issue 7 (Austin Group Interpretation 1003.1-2001 #171). Signed-off-by: Herbert Xu --- configure.ac | 11 +++++++++++ src/redir.c | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 52aa429..5524650 100644 --- a/configure.ac +++ b/configure.ac @@ -177,6 +177,17 @@ if test "$have_st_mtim" = "yes"; then [Define if your `struct stat' has `st_mtim']) fi +dnl F_DUPFD_CLOEXEC is a mandatory part of POSIX since Issue 7 +AC_MSG_CHECKING(for F_DUPFD_CLOEXEC) +AC_COMPILE_IFELSE( +[AC_LANG_PROGRAM([#include +#include ], +[return fcntl(0, F_DUPFD_CLOEXEC, 0)])], +have_dupfd_cloexec=1, have_dupfd_cloexec=0) +AC_MSG_RESULT($(expr yes \& $have_dupfd_cloexec \| no)) +AC_DEFINE_UNQUOTED([HAVE_F_DUPFD_CLOEXEC], [$have_dupfd_cloexec], + [Define to 1 your system supports F_DUPFD_CLOEXEC]) + AC_ARG_WITH(libedit, AS_HELP_STRING(--with-libedit, [Compile with libedit support])) use_libedit= if test "$with_libedit" = "yes"; then diff --git a/src/redir.c b/src/redir.c index 5a5835c..631ddc9 100644 --- a/src/redir.c +++ b/src/redir.c @@ -446,13 +446,18 @@ savefd(int from, int ofd) int newfd; int err; +#if HAVE_F_DUPFD_CLOEXEC + newfd = fcntl(from, F_DUPFD_CLOEXEC, 10); +#else newfd = fcntl(from, F_DUPFD, 10); +#endif + err = newfd < 0 ? errno : 0; if (err != EBADF) { close(ofd); if (err) sh_error("%d: %s", from, strerror(err)); - else + else if(!HAVE_F_DUPFD_CLOEXEC) fcntl(newfd, F_SETFD, FD_CLOEXEC); } From 4ec545e8dc98a3f461cf56bed03adafa81c64aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Thu, 5 Jan 2023 13:49:47 +0100 Subject: [PATCH 21/59] alias: Quote name in printalias This ensures even something like alias 'a|b|c=d' is output by alias as 'a|b|c'='d' instead of a|b|c='d' which is both "suitable for reinput to the shell" per POSIX and doesn't execute the aliases as code. Signed-off-by: Herbert Xu --- src/alias.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alias.c b/src/alias.c index daeacbb..1375cdd 100644 --- a/src/alias.c +++ b/src/alias.c @@ -197,7 +197,7 @@ freealias(struct alias *ap) { void printalias(const struct alias *ap) { - out1fmt("%s=%s\n", ap->name, single_quote(ap->val)); + out1fmt("%s=%s\n", single_quote(ap->name), single_quote(ap->val)); } STATIC struct alias ** From d89761b0e1652e212e9354fd3c96f977de873a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Thu, 5 Jan 2023 14:42:04 +0100 Subject: [PATCH 22/59] parser: Don't keep alloca()ing in a loop for substitutions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When encountering printf %010000d | tr 0 \` | sh -n printf %09999d | tr 0 \` | sh -n you want no output and "Syntax error: EOF in backquote substitution", respectively; instead, current dash segfaults. This is because the alloca for the save buffer is run, naturally, in the same function, so first it allocates one byte, then two, then ..., then appx. 4000 (for me, depends on the binary), then it segfaults on the memcpy (it's even worse, since due to alignment, it usually allocates much more for the early stuff). Nevertheless, the stack frame grows unboundedly, until we completely destroy the stack. Instead of squirreling the out block away, then letting subsequent allocations override the original, mark it used, and just re-copy it to the top of the dash stack. This increases peak memory usage somewhat (in the most pathological case ‒ the above but with three nines ‒ from 23.26 to 173.7KiB according to massif, in parsing a regular program (ratrun from ratrun 0c) from 28.68 to 29.19; a simpler program (ibid., rat) stays at 5.422; parsing libtoolize, debootstrap, and dkms (the biggest shell programs in my /[s]bin by size + by `/$( count) likewise stay the same at 12.02, 41.48, and 6.438) but it's barely measurable outside of truly pathological conditions that were a step away from a segfault previously. Link: https://bugs.debian.org/966156 Signed-off-by: Herbert Xu --- src/parser.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/parser.c b/src/parser.c index 8a06b9e..f5f76d5 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1360,12 +1360,9 @@ parsebackq: { struct heredoc *saveheredoclist; int uninitialized_var(saveprompt); - str = NULL; + str = stackblock(); savelen = out - (char *)stackblock(); - if (savelen > 0) { - str = alloca(savelen); - memcpy(str, stackblock(), savelen); - } + grabstackblock(savelen); if (oldstyle) { /* We must read until the closing backquote, giving special treatment to some slashes, and then push the string and @@ -1445,12 +1442,8 @@ done: /* Ignore any pushed back tokens left from the backquote parsing. */ if (oldstyle) tokpushback = 0; - out = growstackto(savelen + 1); - if (str) { - memcpy(out, str, savelen); - STADJUST(savelen, out); - } - USTPUTC(CTLBACKQ, out); + out = stnputs(str, savelen, stackblock()); + STPUTC(CTLBACKQ, out); if (oldstyle) goto parsebackq_oldreturn; else From f96ec8765cf37eb0c222a563de2f767ebfbf56db Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Fri, 6 Jan 2023 11:15:55 +0800 Subject: [PATCH 23/59] parser: Print CTLBACKQ early in parsesub As we are allowed to perform 4 USTPUTC's we can save a growstackstr call by adding the CTLBACKQ before we save the string. Signed-off-by: Herbert Xu --- src/parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser.c b/src/parser.c index f5f76d5..299c260 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1360,6 +1360,7 @@ parsebackq: { struct heredoc *saveheredoclist; int uninitialized_var(saveprompt); + USTPUTC(CTLBACKQ, out); str = stackblock(); savelen = out - (char *)stackblock(); grabstackblock(savelen); @@ -1443,7 +1444,6 @@ done: if (oldstyle) tokpushback = 0; out = stnputs(str, savelen, stackblock()); - STPUTC(CTLBACKQ, out); if (oldstyle) goto parsebackq_oldreturn; else From b00288fd28c1b39d0f1531b6e6d86de59de4be8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Mon, 9 Jan 2023 01:15:43 +0100 Subject: [PATCH 24/59] alias: fix name quoting in printalias single_quote() over-writes the stack string, so just output the name separately first. Reported-by: Harald van Dijk Fixes: 4ec545e8dc98 ("alias: Quote name in printalias") Signed-off-by: Herbert Xu --- src/alias.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/alias.c b/src/alias.c index 1375cdd..fcad43b 100644 --- a/src/alias.c +++ b/src/alias.c @@ -197,7 +197,8 @@ freealias(struct alias *ap) { void printalias(const struct alias *ap) { - out1fmt("%s=%s\n", single_quote(ap->name), single_quote(ap->val)); + out1str(single_quote(ap->name)); + out1fmt("=%s\n", single_quote(ap->val)); } STATIC struct alias ** From 088f265909f5b85e49302d8c4d624b977ce2bd3c Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 2 Oct 2021 16:37:32 -0400 Subject: [PATCH 25/59] mail: Fix chkmail loop break condition padvance_magic() returns -1 when there are no more paths left, not zero. Fixes: 4f7527f8e492 ("exec: Do not allocate stack string in padvance") Signed-off-by: Herbert Xu --- src/mail.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mail.c b/src/mail.c index 8eacb2d..49cd5fa 100644 --- a/src/mail.c +++ b/src/mail.c @@ -80,7 +80,7 @@ chkmail(void) int len; len = padvance_magic(&mpath, nullstr, 2); - if (!len) + if (len < 0) break; p = stackblock(); if (*p == '\0') From 6347b9fc52d742f36a0276cdea06cd9ad1f02c77 Mon Sep 17 00:00:00 2001 From: Chris Novakovic Date: Fri, 22 Apr 2022 22:10:13 +0100 Subject: [PATCH 26/59] jobs: Implement pipefail option With the pipefail option set, a pipeline's exit status is the exit status of the rightmost command that failed, or zero if all commands succeeded. This is planned for inclusion in the next revision of POSIX [1], although the details are yet to be finalised. The semantics of this implementation are the same as those proposed in [2], which have also been adopted by the BSD shells. [1] https://www.austingroupbugs.net/view.php?id=789 [2] https://www.austingroupbugs.net/view.php?id=789#c4115 Signed-off-by: Herbert Xu --- src/dash.1 | 31 ++++++++++++++++++++++++------- src/jobs.c | 9 ++++++++- src/options.c | 2 ++ src/options.h | 5 +++-- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/dash.1 b/src/dash.1 index d3893bc..31a9d31 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -553,13 +553,17 @@ by redirection operators that are part of the command. If the pipeline is not in the background (discussed later), the shell waits for all commands to complete. .Pp -If the reserved word ! does not precede the pipeline, the exit status is -the exit status of the last command specified in the pipeline. -Otherwise, the exit status is the logical NOT of the exit status of the -last command. -That is, if the last command returns zero, the exit status -is 1; if the last command returns greater than zero, the exit status is -zero. +If the +.Em pipefail +option was enabled when the shell began execution of the pipeline, the +pipeline's exit status is the exit status of the last command specified in +the pipeline that exited with non-zero status, or zero if all commands in +the pipeline exited with a status of zero. If the +.Em pipefail +option was not enabled, the pipeline's exit status is the exit status of +the last command specified in the pipeline; the exit statuses of any other +commands are not used. If the reserved word ! precedes the pipeline, its +exit status is the logical NOT of the exit status described above. .Pp Because pipeline assignment of standard input or standard output or both takes place before redirection, it can be modified by redirection. @@ -1832,6 +1836,19 @@ if the option is +o, the settings are printed in a format suitable for reinput to the shell to affect the same option settings. .Pp +In addition to the option names listed in the +.Sx Argument List Processing +section, the following options may be specified as arguments +to -o or +o: +.Bl -tag -width pipefail +.It Em pipefail +Derive the exit status of a pipeline from the exit statuses of all +of the commands in the pipeline, not just the last command, as +described in the +.Sx Pipelines +section. +.El +.Pp The third use of the set command is to set the values of the shell's positional parameters to the specified args. To change the positional diff --git a/src/jobs.c b/src/jobs.c index f3b9ffc..78c7bc6 100644 --- a/src/jobs.c +++ b/src/jobs.c @@ -1526,8 +1526,15 @@ STATIC int getstatus(struct job *job) { int status; int retval; + struct procstat *ps; + + ps = job->ps + job->nprocs - 1; + status = ps->status; + if (pipefail) { + while (status == 0 && --ps >= job->ps) + status = ps->status; + } - status = job->ps[job->nprocs - 1].status; retval = WEXITSTATUS(status); if (!WIFEXITED(status)) { #if JOBS diff --git a/src/options.c b/src/options.c index 2d4bd3b..e192191 100644 --- a/src/options.c +++ b/src/options.c @@ -80,6 +80,7 @@ static const char *const optnames[NOPTS] = { "notify", "nounset", "nolog", + "pipefail", "debug", }; @@ -101,6 +102,7 @@ const char optletters[NOPTS] = { 'u', 0, 0, + 0, }; char optlist[NOPTS]; diff --git a/src/options.h b/src/options.h index 975fe33..f421316 100644 --- a/src/options.h +++ b/src/options.h @@ -60,9 +60,10 @@ struct shparam { #define bflag optlist[13] #define uflag optlist[14] #define nolog optlist[15] -#define debug optlist[16] +#define pipefail optlist[16] +#define debug optlist[17] -#define NOPTS 17 +#define NOPTS 18 extern const char optletters[NOPTS]; extern char optlist[NOPTS]; From e85e972e718b08c72579d24eb39e16563889cc29 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Tue, 10 May 2022 09:16:55 +0100 Subject: [PATCH 27/59] var: move hashvar() calls into findvar() The first argument to findvar() is always obtained by a call to hashvar(), the return value of which is otherwise unused. Signed-off-by: Ron Yorston Signed-off-by: Herbert Xu --- src/var.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/var.c b/src/var.c index b70d72c..21e0abf 100644 --- a/src/var.c +++ b/src/var.c @@ -107,7 +107,7 @@ STATIC struct var *vartab[VTABSIZE]; STATIC struct var **hashvar(const char *); STATIC int vpcmp(const void *, const void *); -STATIC struct var **findvar(struct var **, const char *); +STATIC struct var **findvar(const char *); /* * Initialize the varable symbol tables and import the environment @@ -251,9 +251,8 @@ struct var *setvareq(char *s, int flags) { struct var *vp, **vpp; - vpp = hashvar(s); flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1)); - vpp = findvar(vpp, s); + vpp = findvar(s); vp = *vpp; if (vp) { if (vp->flags & VREADONLY) { @@ -315,7 +314,7 @@ lookupvar(const char *name) { struct var *v; - if ((v = *findvar(hashvar(name), name)) && !(v->flags & VUNSET)) { + if ((v = *findvar(name)) && !(v->flags & VUNSET)) { #ifdef WITH_LINENO if (v == &vlineno && v->text == linenovar) { fmtstr(linenovar+7, sizeof(linenovar)-7, "%d", lineno); @@ -422,7 +421,7 @@ exportcmd(int argc, char **argv) if ((p = strchr(name, '=')) != NULL) { p++; } else { - if ((vp = *findvar(hashvar(name), name))) { + if ((vp = *findvar(name))) { vp->flags |= flag; continue; } @@ -466,7 +465,6 @@ localcmd(int argc, char **argv) void mklocal(char *name, int flags) { struct localvar *lvp; - struct var **vpp; struct var *vp; INTOFF; @@ -479,8 +477,7 @@ void mklocal(char *name, int flags) } else { char *eq; - vpp = hashvar(name); - vp = *findvar(vpp, name); + vp = *findvar(name); eq = strchr(name, '='); if (vp == NULL) { if (eq) @@ -667,9 +664,11 @@ vpcmp(const void *a, const void *b) } STATIC struct var ** -findvar(struct var **vpp, const char *name) +findvar(const char *name) { - for (; *vpp; vpp = &(*vpp)->next) { + struct var **vpp; + + for (vpp = hashvar(name); *vpp; vpp = &(*vpp)->next) { if (varequal((*vpp)->text, name)) { break; } From 933e016f29ffd4863b9b2857d240716f7b2728b5 Mon Sep 17 00:00:00 2001 From: Forest Date: Mon, 16 May 2022 16:02:24 -0700 Subject: [PATCH 28/59] man: Fix swapped stdin/stdout for redirection operators The Redirections section incorrectly claimed that <& replaces stdout and >& replaces stdin. Swapped them to make it read correctly. Ref: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_05 Both errors were followed by extra text that looked like remains of a mostly-deleted sentence. Removed those. Fixes: 6adc14a0d4e4 ("man: Clarify two redirection mechanisms") Signed-off-by: Herbert Xu --- src/dash.1 | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/dash.1 b/src/dash.1 index 31a9d31..1f155ba 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -402,13 +402,11 @@ Append standard output (or n) to file. .It [n] Ns \*[Lt] file Redirect standard input (or n) from file. .It [n1] Ns \*[Lt]& Ns n2 -Copy file descriptor n2 as stdout (or fd n1). -fd n2. +Copy file descriptor n2 as stdin (or fd n1). .It [n] Ns \*[Lt]&- Close standard input (or n). .It [n1] Ns \*[Gt]& Ns n2 -Copy file descriptor n2 as stdin (or fd n1). -fd n2. +Copy file descriptor n2 as stdout (or fd n1). .It [n] Ns \*[Gt]&- Close standard output (or n). .It [n] Ns \*[Lt]\*[Gt] file From 96b972aa3d15317cc1e853543a918b2b727e4566 Mon Sep 17 00:00:00 2001 From: Subhaditya Nath Date: Mon, 6 Feb 2023 20:38:47 +0530 Subject: [PATCH 29/59] options: Fix getopts handling of colon in optstr Putting a colon at the beginning of optstring to silence errors doesn't mean that the colon is a valid option. Before this patch, dash treated -: as a valid option if the optstring started with a colon. This patch fixes that problem. Test: getopts :a opt -: echo $opt$OPTARG Correct output - ?: Invalid output - : Signed-off-by: Herbert Xu --- src/options.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/options.c b/src/options.c index e192191..8101cf5 100644 --- a/src/options.c +++ b/src/options.c @@ -467,7 +467,7 @@ atend: } c = *p++; - for (q = optstr; *q != c; ) { + for (q = optstr[0] == ':' ? optstr + 1 : optstr; *q != c; ) { if (*q == '\0') { if (optstr[0] == ':') { s[0] = c; From b9c069b0cc372821942aecd04829030f5710baac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Tue, 7 Feb 2023 20:33:25 +0100 Subject: [PATCH 30/59] histedit: Disallow fc -s first last The POSIX SYNOPSIS (and our manual which steals it verbatim) says: fc -s [old=new] [first] and, indeed, we only use the first non-= argument instead of enforcing the usage, which is confusing. bash: 2025 ls 2026 id $ fc -s ls=who 2025 2026 who nabijaczleweli pts/2 2023-02-07 17:36 (192.168.1.109) nabijaczleweli pts/3 2023-02-07 17:38 (192.168.1.109) nabijaczleweli pts/4 2023-02-07 16:58 (192.168.1.109) nabijaczleweli pts/5 2023-02-07 17:45 (192.168.1.109) ksh93: 240 id 241 ls $ fc -s ls=who 241 240 ksh: hist: -e - requires single argument yash: 2 ls 3 id $ fc -s ls=who 2 3 fc: too many operands are specified zsh: 2 id 3 ls tarta% fc -s ls=who 3 2 fc: bad option: -s dash (before): 1 ls 2 id $ fc -s ls=who 1 2 who nabijaczleweli pts/2 2023-02-07 17:36 (192.168.1.109) nabijaczleweli pts/3 2023-02-07 17:38 (192.168.1.109) nabijaczleweli pts/4 2023-02-07 16:58 (192.168.1.109) nabijaczleweli pts/5 2023-02-07 17:45 (192.168.1.109) dash (after): 1 ls 2 id $ fc -s ls=who 1 2 src/dash: 3: fc: -s takes one history argument Adapted-from: NetBSD src bin/sh/histedit.c rev 1.38 by aymeric@ Signed-off-by: Herbert Xu --- src/histedit.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/histedit.c b/src/histedit.c index f5c90ab..fc87283 100644 --- a/src/histedit.c +++ b/src/histedit.c @@ -296,6 +296,13 @@ histcmd(int argc, char **argv) *repl++ = '\0'; argc--, argv++; } + + /* + * If -s is specified, accept only one operand + */ + if (sflg && argc >= 2) + sh_error("too many args"); + /* * determine [first] and [last] */ From f0d4a2eeaf3cd151a37453ff1cd94fcc7a7b10af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Tue, 7 Feb 2023 20:33:30 +0100 Subject: [PATCH 31/59] histedit: Fix fs -s infinite loop $ id 1 uid=1(daemon) gid=1(daemon) groups=1(daemon) $ fc -s 2 fc -s 2 fc -s 2 fc -s 2 fc -s 2 src/dash: 1: fc: called recursively too many times and I'm happy to call this "behaving exactly as I expected when I was typing it in", so removing the XXX. Adapted-from: NetBSD src bin/sh/histedit.c rev 1.38 by aymeric@ Signed-off-by: Herbert Xu --- src/histedit.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/histedit.c b/src/histedit.c index fc87283..28956ec 100644 --- a/src/histedit.c +++ b/src/histedit.c @@ -382,12 +382,10 @@ histcmd(int argc, char **argv) evalstring(s, 0); if (displayhist && hist) { - /* - * XXX what about recursive and - * relative histnums. - */ history(hist, &he, H_ENTER, s); } + + break; } else fputs(s, efp); } From 56f6355249bae99a09102b1f812f2f365923dc24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Tue, 7 Feb 2023 20:33:34 +0100 Subject: [PATCH 32/59] histedit: Only parse old=new for fc -s Before (erroneously replaced): $ a=b set ... $ fc a=b 8 , b=b set After (used as search string): $ fc a=b 8 , a=b set Reported-by: Harald van Dijk Reported-in: https://marc.info/?l=dash&m=154707728009743&w=2 Signed-off-by: Herbert Xu --- src/histedit.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/histedit.c b/src/histedit.c index 28956ec..24631ca 100644 --- a/src/histedit.c +++ b/src/histedit.c @@ -288,21 +288,18 @@ histcmd(int argc, char **argv) } /* - * If executing, parse [old=new] now + * If -s is specified, accept [old=new] first only */ - if (lflg == 0 && argc > 0 && - ((repl = strchr(argv[0], '=')) != NULL)) { - pat = argv[0]; - *repl++ = '\0'; - argc--, argv++; + if (sflg) { + if (argc > 0 && ((repl = strchr(argv[0], '=')) != NULL)) { + pat = argv[0]; + *repl++ = '\0'; + argc--, argv++; + } + if (argc >= 2) + sh_error("too many args"); } - /* - * If -s is specified, accept only one operand - */ - if (sflg && argc >= 2) - sh_error("too many args"); - /* * determine [first] and [last] */ From 7884dcced4c04c036b5227207e63a0d06f19e9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Tue, 7 Feb 2023 20:33:42 +0100 Subject: [PATCH 33/59] histedit: Fix "fc -3" breakage on glibc Before: $ echo a a $ echo b b $ fc -2 -1 src/dash: 3: fc: unknown option: -2 $ fc -- -2 -1 16 ,p echo b fc -2 -1 After: $ echo a a $ echo b b $ fc -2 -1 6 ,p echo a echo b Reported-by: Harald van Dijk Reported-in: https://marc.info/?l=dash&m=154707728009743&w=2 Signed-off-by: Herbert Xu --- src/histedit.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/histedit.c b/src/histedit.c index 24631ca..fcd8af0 100644 --- a/src/histedit.c +++ b/src/histedit.c @@ -220,7 +220,7 @@ histcmd(int argc, char **argv) #else optreset = 1; optind = 1; /* initialize getopt */ #endif - while (not_fcnumber(argv[optind]) && + while (not_fcnumber(argv[optind ?: 1]) && (ch = getopt(argc, argv, ":e:lnrs")) != -1) switch ((char)ch) { case 'e': @@ -246,6 +246,7 @@ histcmd(int argc, char **argv) sh_error("unknown option: -%c", optopt); /* NOTREACHED */ } + optind = optind ?: 1; argc -= optind, argv += optind; /* From 459e51a2ae9d720b12e44fa725a6014a4d784d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Tue, 7 Feb 2023 20:33:47 +0100 Subject: [PATCH 34/59] histedit: Don't include the current fc in out-of-range last POSIX states: When a range of commands is used, it shall not be an error to specify first or last values that are not in the history list; fc shall substitute the value representing the oldest or newest command in the list, as appropriate. For example, if there are only ten commands in the history list, numbered 1 to 10: fc -l fc 1 99 shall list and edit, respectively, all ten commands. Which would seem to imply that the current fc shouldn't be included (well, in the POSIX model, no non--l fc enters the history, so that reinforces that). zsh, bash, mksh, yash all agree with this; oddly, ksh includes it. Before: $ 1 src/dash: 1: 1: not found $ 2 src/dash: 2: 2: not found $ 3 src/dash: 3: 3: not found $ 4 src/dash: 4: 4: not found $ 5 src/dash: 5: 5: not found $ 6 src/dash: 6: 6: not found $ fc 1 999 21 ,p 1 2 3 4 5 6 fc 1 999 After: $ fc 1 9999 12 ,p 1 2 3 4 5 6 Reported-by: Harald van Dijk Reported-in: https://marc.info/?l=dash&m=154707728009743&w=2 Signed-off-by: Herbert Xu --- src/histedit.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/histedit.c b/src/histedit.c index fcd8af0..07dd1b4 100644 --- a/src/histedit.c +++ b/src/histedit.c @@ -479,6 +479,8 @@ str_to_event(const char *str, int last) */ retval = history(hist, &he, last ? H_FIRST : H_LAST); + if (retval != -1 && last) + retval = history(hist, &he, H_NEXT); } } if (retval == -1) From 9b9f39396a3b468237d6f361b36e9394ce6b3b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Tue, 7 Feb 2023 20:33:38 +0100 Subject: [PATCH 35/59] histedit: Don't require argument for fc This is already handled correctly (per POSIX) below: When the synopsis form with -s is used: If first is omitted, the previous command shall be used. For the synopsis forms without -s: If first and last are both omitted, the previous 16 commands shall be listed or the previous single command shall be edited (based on the -l option). Test log: $ ls autogen.sh ChangeLog ... $ id uid=1000(nabijaczleweli) gid=100(users) groups=100(users) $ who nabijaczleweli pts/2 2023-02-07 18:36 (192.168.1.109) $ fc 4 , who q nabijaczleweli pts/2 2023-02-07 18:36 (192.168.1.109) $ fc -l 1 ls 2 id 3 who 4 fc $ fc -s fc -l 1 ls 2 id 3 who 4 fc 5 fc -l Reported-by: Harald van Dijk Reported-in: https://marc.info/?l=dash&m=154707728009743&w=2 Signed-off-by: Herbert Xu --- src/histedit.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/histedit.c b/src/histedit.c index 07dd1b4..7692776 100644 --- a/src/histedit.c +++ b/src/histedit.c @@ -212,9 +212,6 @@ histcmd(int argc, char **argv) if (hist == NULL) sh_error("history not active"); - if (argc == 1) - sh_error("missing history argument"); - #ifdef __GLIBC__ optind = 0; #else From b41b0d41228fe82991a63f475e0bef701f539db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Fri, 10 Feb 2023 12:24:49 +0100 Subject: [PATCH 36/59] shell: Prototype all function definitions for C23 compat With this patch, you're just left with the histedit.c warning. Signed-off-by: Herbert Xu --- src/exec.c | 13 +++---------- src/jobs.c | 5 +---- src/mksignames.c | 4 +--- src/nodes.c.pat | 20 +++++--------------- src/options.c | 4 +--- src/redir.c | 12 +++--------- 6 files changed, 14 insertions(+), 44 deletions(-) diff --git a/src/exec.c b/src/exec.c index 83cba94..4b777d2 100644 --- a/src/exec.c +++ b/src/exec.c @@ -775,12 +775,8 @@ typecmd(int argc, char **argv) return err; } -STATIC int -describe_command(out, command, path, verbose) - struct output *out; - char *command; - const char *path; - int verbose; +static int describe_command(struct output *out, char *command, + const char *path, int verbose) { struct cmdentry entry; struct tblentry *cmdp; @@ -881,10 +877,7 @@ out: return 0; } -int -commandcmd(argc, argv) - int argc; - char **argv; +int commandcmd(int argc, char **argv) { char *cmd; int c; diff --git a/src/jobs.c b/src/jobs.c index 78c7bc6..e16aeca 100644 --- a/src/jobs.c +++ b/src/jobs.c @@ -243,10 +243,7 @@ close: #endif -int -killcmd(argc, argv) - int argc; - char **argv; +int killcmd(int argc, char **argv) { extern char *signal_names[]; int signo = -1; diff --git a/src/mksignames.c b/src/mksignames.c index a832eab..192728b 100644 --- a/src/mksignames.c +++ b/src/mksignames.c @@ -360,9 +360,7 @@ initialize_signames () } } -void -write_signames (stream) - FILE *stream; +void write_signames(FILE *stream) { register int i; diff --git a/src/nodes.c.pat b/src/nodes.c.pat index 9125bc7..636456c 100644 --- a/src/nodes.c.pat +++ b/src/nodes.c.pat @@ -87,18 +87,14 @@ copyfunc(union node *n) -STATIC void -calcsize(n) - union node *n; +static void calcsize(union node *n) { %CALCSIZE } -STATIC void -sizenodelist(lp) - struct nodelist *lp; +static void sizenodelist(struct nodelist *lp) { while (lp) { funcblocksize += SHELL_ALIGN(sizeof(struct nodelist)); @@ -109,9 +105,7 @@ sizenodelist(lp) -STATIC union node * -copynode(n) - union node *n; +static union node *copynode(union node *n) { union node *new; @@ -120,9 +114,7 @@ copynode(n) } -STATIC struct nodelist * -copynodelist(lp) - struct nodelist *lp; +static struct nodelist *copynodelist(struct nodelist *lp) { struct nodelist *start; struct nodelist **lpp; @@ -142,9 +134,7 @@ copynodelist(lp) -STATIC char * -nodesavestr(s) - char *s; +static char *nodesavestr(char *s) { char *rtn = funcstring; diff --git a/src/options.c b/src/options.c index 8101cf5..f157321 100644 --- a/src/options.c +++ b/src/options.c @@ -391,9 +391,7 @@ setcmd(int argc, char **argv) } -void -getoptsreset(value) - const char *value; +void getoptsreset(const char *value) { shellparam.optind = number(value) ?: 1; shellparam.optoff = -1; diff --git a/src/redir.c b/src/redir.c index 631ddc9..d74602c 100644 --- a/src/redir.c +++ b/src/redir.c @@ -281,18 +281,12 @@ ecreate: } -STATIC void #ifdef notyet -dupredirect(redir, f, memory) +static void dupredirect(union node *redir, int f, char memory[10]) #else -dupredirect(redir, f) +static void dupredirect(union node *redir, int f) #endif - union node *redir; - int f; -#ifdef notyet - char memory[10]; -#endif - { +{ int fd = redir->nfile.fd; int err = 0; From b23ae6de6c987e1448b65c3ecaa7659a28e8374c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 31 Mar 2023 14:51:57 +0200 Subject: [PATCH 37/59] jobs: drop unused node parameter in makejob() CC: dash@vger.kernel.org CC: Herbert Xu Signed-off-by: Denys Vlasenko Signed-off-by: Herbert Xu --- src/eval.c | 6 +++--- src/jobs.c | 7 +++---- src/jobs.h | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/eval.c b/src/eval.c index fa43b68..978a174 100644 --- a/src/eval.c +++ b/src/eval.c @@ -494,7 +494,7 @@ evalsubshell(union node *n, int flags) forkreset(); goto nofork; } - jp = makejob(n, 1); + jp = makejob(1); if (forkshell(jp, n, backgnd) == 0) { flags |= EV_EXIT; if (backgnd) @@ -571,7 +571,7 @@ evalpipe(union node *n, int flags) pipelen++; flags |= EV_EXIT; INTOFF; - jp = makejob(n, pipelen); + jp = makejob(pipelen); prevfd = -1; for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { prehash(lp->n); @@ -637,7 +637,7 @@ evalbackcmd(union node *n, struct backcmd *result) if (pipe(pip) < 0) sh_error("Pipe call failed"); - jp = makejob(n, 1); + jp = makejob(1); if (forkshell(jp, n, FORK_NOJOB) == 0) { FORCEINTON; close(pip[0]); diff --git a/src/jobs.c b/src/jobs.c index e16aeca..a0f4d47 100644 --- a/src/jobs.c +++ b/src/jobs.c @@ -745,8 +745,7 @@ err: * Called with interrupts off. */ -struct job * -makejob(union node *node, int nprocs) +struct job *makejob(int nprocs) { int i; struct job *jp; @@ -777,7 +776,7 @@ makejob(union node *node, int nprocs) if (nprocs > 1) { jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); } - TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs, + TRACE(("makejob(%d) returns %%%d\n", nprocs, jobno(jp))); return jp; } @@ -960,7 +959,7 @@ struct job *vforkexec(union node *n, char **argv, const char *path, int idx) struct job *jp; int pid; - jp = makejob(n, 1); + jp = makejob(1); sigblockall(NULL); vforked++; diff --git a/src/jobs.h b/src/jobs.h index 6ac6c56..2832d64 100644 --- a/src/jobs.h +++ b/src/jobs.h @@ -102,7 +102,7 @@ int jobscmd(int, char **); struct output; void showjobs(struct output *, int); int waitcmd(int, char **); -struct job *makejob(union node *, int); +struct job *makejob(int); int forkshell(struct job *, union node *, int); struct job *vforkexec(union node *n, char **argv, const char *path, int idx); int waitforjob(struct job *); From 14ac8fbcaa8faf2f4faf1e4d98c0168eaddf6a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Sat, 22 Apr 2023 17:16:21 +0200 Subject: [PATCH 38/59] man: document what happens when IFS= (and when it's not) A question I just got from a user was "how do I make while read -r l; do ...; done < f not strip the initial tabs?". Turns out, the manual is silent on this, and POSIX just about implies this behaviour. (Indeed, our read is almost verbatim POSIX, and both defer to Field Splitting, but our Field Splitting isn't nearly as detailed, and thank god.) Even POSIX spends just one line describing this pivotal behaviour (Issue 8 Draft 2.1 line 75044-75045: "2. If the value of IFS is null, field splitting shall have no effect, except that if the input is empty the result shall be zero fields.)," and when I first encountered this it was also quite surprising to me. Spell it out explicitly: IFS= means that input is preserved, and the default value means whitespace is stripped from the front. Drive it home with an example because it's esoteric (and I know from that user that they first tried searching for read in the manual, but it was not very helpful). Reported-by: rozbrajaczpoziomow Signed-off-by: Herbert Xu --- src/dash.1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/dash.1 b/src/dash.1 index 1f155ba..1b14662 100644 --- a/src/dash.1 +++ b/src/dash.1 @@ -1023,6 +1023,19 @@ The shell treats each character of the .Ev IFS as a delimiter and uses the delimiters to split the results of parameter expansion and command substitution into fields. +.Pp +If +.Ev IFS +is empty, field splitting yields no fields if the input string was empty, +and one string with the unchanged value of the input otherwise. +For example, with the default +.Ev IFS , +.Dq Ic read Fl r Ev l +will remove any initial whitespace, +but +.Dq Ev IFS Ns = Ic read Fl r Ev l +will leave the entire line in +.Ev l . .Ss Pathname Expansion (File Name Generation) Unless the .Fl f From 419f334520c251fb9cdeac380312d38521dfca5c Mon Sep 17 00:00:00 2001 From: Fabrice Fontaine Date: Fri, 16 Feb 2024 17:33:19 +0100 Subject: [PATCH 39/59] configure.ac: drop -Wl,--fatal-warnings Drop -Wl,--fatal-warnings with --enable-static to avoid the following static build failure: configure:4778: checking for strtod configure:4778: /home/autobuild/autobuild/instance-8/output-1/host/bin/powerpc-buildroot-linux-uclibcspe-gcc -o conftest -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -mabi=spe -mfloat-gprs=single -Wa,-me500 -Os -g0 -static -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -static -Wl,--fatal-warnings conftest.c >&5 /home/autobuild/autobuild/instance-8/output-1/host/lib/gcc/powerpc-buildroot-linux-uclibcspe/8.4.0/../../../../powerpc-buildroot-linux-uclibcspe/bin/ld: warning: conftest has a LOAD segment with RWX permissions collect2: error: ld returned 1 exit status [...] In file included from arith_yylex.c:44: system.h:74:22: error: static declaration of 'strtod' follows non-static declaration static inline double strtod(const char *nptr, char **endptr) ^~~~~~ Fixes: - http://autobuild.buildroot.org/results/a54fdc7d1b94beb47203373ae35b08d9cea8d42c Signed-off-by: Fabrice Fontaine Signed-off-by: Herbert Xu --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 5524650..6993364 100644 --- a/configure.ac +++ b/configure.ac @@ -34,7 +34,7 @@ fi AC_ARG_ENABLE(static, AS_HELP_STRING(--enable-static, \ [Build statical linked program])) if test "$enable_static" = "yes"; then - export LDFLAGS="-static -Wl,--fatal-warnings" + export LDFLAGS="-static" fi AC_ARG_ENABLE(fnmatch, AS_HELP_STRING(--disable-fnmatch, \ From d489f2e2e98268894a38a1c84da559e74020c47b Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Fri, 5 Apr 2024 17:55:46 +0800 Subject: [PATCH 40/59] exec: Check executable bit when searching path Andrej Shadura wrote: > > Here's an old bug from 2017, but it was brought to my attention in some > recent discussion about which "which" is which. There's also a patch in > one of the follow-ups, but I'm afraid I don't know enough about that > part of code to judge the consequences of it being applied: > > https://bugs.debian.org/874264 > > -------- Forwarded Message -------- > Subject: dash: 'command -v' mistakenly returns a shell script whose > executable is not set > Date: Mon, 04 Sep 2017 10:45:48 -0400 > From: Norman Ramsey > To: Debian Bug Tracking System > > Package: dash > Version: 0.5.8-2.4 > Severity: normal > > Dear Maintainer, > > > I tracked a build bug in s-nail to a problem with dash. Symptom: > building s-nail tries to run /home/nr/bin/clang, a script whose > executable bit is not set. We tracked the problem to the result of > running `command -v clang` with /bin/sh: > > nr@homedog ~/n/s-nail> /bin/sh -c 'command -v clang' > /home/nr/bin/clang > nr@homedog ~/n/s-nail> ls -l /home/nr/bin/clang > -rw-rw-r-- 1 nr nr 1009 Aug 29 2011 /home/nr/bin/clang > nr@homedog ~/n/s-nail> ls -l /bin/sh > lrwxrwxrwx 1 root root 4 Jan 24 2017 /bin/sh -> dash > nr@homedog ~/n/s-nail> ksh -c 'command -v clang' > /usr/bin/clang > nr@homedog ~/n/s-nail> bash -c 'command -v clang' > /usr/bin/clang > nr@homedog ~/n/s-nail> sh -c 'command -v clang' > /home/nr/bin/clang > nr@homedog ~/n/s-nail> dash -c 'command -v clang' > /home/nr/bin/clang > nr@homedog ~/n/s-nail> fish -c 'command -v clang' > /usr/bin/clang > > When I run `command -v clang` I expect it to answer /usr/bin/clang. > > -- System Information: > Debian Release: 9.1 > APT prefers stable > APT policy: (990, 'stable'), (500, 'stable'), (1, 'experimental') > Architecture: i386 (x86_64) > Foreign Architectures: amd64 > > Kernel: Linux 4.9.0-3-amd64 (SMP w/4 CPU cores) > Locale: LANG=C, LC_CTYPE=C (charmap=UTF-8) (ignored: LC_ALL set to > en_US.utf8), LANGUAGE=C (charmap=UTF-8) (ignored: LC_ALL set to en_US.utf8) > Shell: /bin/sh linked to /bin/dash > Init: systemd (via /run/systemd/system) > > Versions of packages dash depends on: > ii debianutils 4.8.1.1 > ii dpkg 1.18.24 > ii libc6 2.24-11+deb9u1 > > dash recommends no packages. > > dash suggests no packages. > > -- debconf information: > * dash/sh: true This is inherited from NetBSD. There is even a commented-out block of code that tried to fix this. Anyway, we now have faccessat so we can simply use it. Reported-by: Norman Ramsey Reported-by: Nicola Lamacchia Signed-off-by: Herbert Xu --- src/bltin/test.c | 10 +++------- src/exec.c | 38 +++++++++++++++++++++----------------- src/exec.h | 5 +++++ 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/bltin/test.c b/src/bltin/test.c index c7fc479..fd8a43b 100644 --- a/src/bltin/test.c +++ b/src/bltin/test.c @@ -18,6 +18,7 @@ #include #include #include "bltin.h" +#include "../exec.h" /* test(1) accepts the following grammar: oexpr ::= aexpr | aexpr "-o" oexpr ; @@ -148,11 +149,6 @@ static int isoperand(char **); static int newerf(const char *, const char *); static int olderf(const char *, const char *); static int equalf(const char *, const char *); -#ifdef HAVE_FACCESSAT -static int test_file_access(const char *, int); -#else -static int test_access(const struct stat64 *, int); -#endif #ifdef HAVE_FACCESSAT # ifdef HAVE_TRADITIONAL_FACCESSAT @@ -527,7 +523,7 @@ static int has_exec_bit_set(const char *path) return st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH); } -static int test_file_access(const char *path, int mode) +int test_file_access(const char *path, int mode) { if (faccessat_confused_about_superuser() && mode == X_OK && geteuid() == 0 && !has_exec_bit_set(path)) @@ -657,7 +653,7 @@ static int test_file_access(const char *path, int mode) * (euid==uid&&egid==gid), but uses st_mode for '-x' iff running as root. * i.e. it does strictly conform to 1003.1-2001 (and presumably 1003.2b). */ -static int test_access(const struct stat64 *sp, int stmode) +int test_access(const struct stat64 *sp, int stmode) { gid_t *groups; register int n; diff --git a/src/exec.c b/src/exec.c index 4b777d2..6fe0fed 100644 --- a/src/exec.c +++ b/src/exec.c @@ -325,7 +325,22 @@ printentry(struct tblentry *cmdp) out1fmt(snlfmt, cmdp->rehash ? "*" : nullstr); } +static int test_exec(const char *fullname, struct stat64 *statb) +{ + if (!S_ISREG(statb->st_mode)) + return 0; + if ((statb->st_mode & 0111) != 0111 && +#ifdef HAVE_FACCESSAT + !test_file_access(fullname, X_OK) +#else + !test_access(statb, X_OK) +#endif + ) + return 0; + + return 1; +} /* * Resolve a command name. If you change this routine, you may have to @@ -354,9 +369,12 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) if (errno == EINTR) continue; #endif +absfail: entry->cmdtype = CMDUNKNOWN; return; } + if (!test_exec(name, &statb)) + goto absfail; } entry->cmdtype = CMDNORMAL; return; @@ -451,9 +469,6 @@ loop: e = errno; goto loop; } - e = EACCES; /* if we fail, this will be the error */ - if (!S_ISREG(statb.st_mode)) - continue; if (lpathopt) { /* this is a %func directory */ stalloc(len); readcmdfile(fullname); @@ -464,20 +479,9 @@ loop: stunalloc(fullname); goto success; } -#ifdef notdef - /* XXX this code stops root executing stuff, and is buggy - if you need a group from the group list. */ - 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 + e = EACCES; /* if we fail, this will be the error */ + if (!test_exec(fullname, &statb)) + continue; TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); if (!updatetbl) { entry->cmdtype = CMDNORMAL; diff --git a/src/exec.h b/src/exec.h index 423b07e..8707d36 100644 --- a/src/exec.h +++ b/src/exec.h @@ -62,6 +62,8 @@ union node; extern const char *pathopt; /* set by padvance */ +struct stat64; + void shellexec(char **, const char *, int) __attribute__((__noreturn__)); int padvance_magic(const char **path, const char *name, int magic); @@ -78,6 +80,9 @@ void unsetfunc(const char *); int typecmd(int, char **); int commandcmd(int, char **); +int test_file_access(const char *path, int mode); +int test_access(const struct stat64 *sp, int stmode); + static inline int padvance(const char **path, const char *name) { return padvance_magic(path, name, 1); From 74085cc28deb9d95867ad7c350efd11ea722a552 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Sun, 7 Apr 2024 17:04:37 +0800 Subject: [PATCH 41/59] jobs: Allow monitor mode without a tty in non-interactive mode When a tty is unavailable, or the shell is in the background, job control could still be used for the purpose of setting process groups. This is based on work by Jilles Tjoelker from FreeBSD and Steffen Nurpmeso. Reported-by: Steffen Nurpmeso Reported-by: Ganael Laplanche Signed-off-by: Herbert Xu --- src/jobs.c | 55 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src/jobs.c b/src/jobs.c index a0f4d47..2a2fe22 100644 --- a/src/jobs.c +++ b/src/jobs.c @@ -187,11 +187,21 @@ set_curjob(struct job *jp, unsigned mode) int jobctl; +static void xxtcsetpgrp(pid_t pgrp) +{ + int fd = ttyfd; + + if (fd < 0) + return; + + xtcsetpgrp(fd, pgrp); +} + void setjobctl(int on) { + int pgrp = -1; int fd; - int pgrp; if (on == jobctl || rootshell == 0) return; @@ -207,36 +217,43 @@ setjobctl(int on) fd = savefd(fd, ofd); do { /* while we are in the background */ if ((pgrp = tcgetpgrp(fd)) < 0) { +close: + close(fd); + fd = -1; out: + if (!iflag) + break; sh_warnx("can't access tty; job control turned off"); mflag = on = 0; - goto close; + return; } if (pgrp == getpgrp()) break; + if (!iflag) + goto close; killpg(0, SIGTTIN); } while (1); initialpgrp = pgrp; - - setsignal(SIGTSTP); - setsignal(SIGTTOU); - setsignal(SIGTTIN); pgrp = rootpid; - setpgid(0, pgrp); - xtcsetpgrp(fd, pgrp); } else { /* turning job control off */ fd = ttyfd; pgrp = initialpgrp; - xtcsetpgrp(fd, pgrp); - setpgid(0, pgrp); - setsignal(SIGTSTP); - setsignal(SIGTTOU); - setsignal(SIGTTIN); -close: - close(fd); - fd = -1; } + + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + if (fd >= 0) { + setpgid(0, pgrp); + xtcsetpgrp(fd, pgrp); + + if (!on) { + close(fd); + fd = -1; + } + } + ttyfd = fd; jobctl = on; } @@ -391,7 +408,7 @@ restartjob(struct job *jp, int mode) jp->state = JOBRUNNING; pgid = jp->ps->pid; if (mode == FORK_FG) - xtcsetpgrp(ttyfd, pgid); + xxtcsetpgrp(pgid); killpg(pgid, SIGCONT); ps = jp->ps; i = jp->nprocs; @@ -874,7 +891,7 @@ static void forkchild(struct job *jp, union node *n, int mode) /* This can fail because we are doing it in the parent also */ (void)setpgid(0, pgrp); if (mode == FORK_FG) - xtcsetpgrp(ttyfd, pgrp); + xxtcsetpgrp(pgrp); setsignal(SIGTSTP); setsignal(SIGTTOU); } else @@ -1014,7 +1031,7 @@ waitforjob(struct job *jp) st = getstatus(jp); #if JOBS if (jp->jobctl) { - xtcsetpgrp(ttyfd, rootpid); + xxtcsetpgrp(rootpid); /* * This is truly gross. * If we're doing job control, then we did a TIOCSPGRP which From 865f44f3fdbc97e21dd279cba46376984cb1e059 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Mon, 8 Apr 2024 12:55:03 +0800 Subject: [PATCH 42/59] alias: Fix out-of-bound access Check for empty string before searching for equal sign starting at n+1 in aliascmd. Reported-by: Harald van Dijk Signed-off-by: Herbert Xu --- src/alias.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/alias.c b/src/alias.c index fcad43b..cee07e9 100644 --- a/src/alias.c +++ b/src/alias.c @@ -143,7 +143,8 @@ aliascmd(int argc, char **argv) return (0); } while ((n = *++argv) != NULL) { - if ((v = strchr(n+1, '=')) == NULL) { /* n+1: funny ksh stuff */ + /* n + 1: funny ksh stuff (from 44lite) */ + if (!*n || !(v = strchr(n + 1, '='))) { if ((ap = *__lookupalias(n)) == NULL) { outfmt(out2, "%s: %s not found\n", "alias", n); ret = 1; From 1c8cf3e96d3ff221dbcf3f8447fd197cdca18939 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Thu, 11 Apr 2024 15:43:52 +0800 Subject: [PATCH 43/59] var: Fix unexporting of local variables using unset Local variables and other variables with the flag VSTRFIXED set could not be unexported using the unset command. Fix this by adding a special case in setvareq for them. Reported-by: Christoph Anton Mitterer Fixes: e3c9a7dd7097 ("[VAR] Move unsetvar functionality into setvareq") Signed-off-by: Herbert Xu --- src/var.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/var.c b/src/var.c index 21e0abf..6f85be3 100644 --- a/src/var.c +++ b/src/var.c @@ -255,6 +255,8 @@ struct var *setvareq(char *s, int flags) vpp = findvar(s); vp = *vpp; if (vp) { + unsigned bits; + if (vp->flags & VREADONLY) { const char *n; @@ -274,8 +276,11 @@ struct var *setvareq(char *s, int flags) if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) ckfree(vp->text); - if (((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) | - (vp->flags & VSTRFIXED)) == VUNSET) { + if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) != VUNSET) + bits = ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); + else if ((vp->flags & VSTRFIXED)) + bits = VSTRFIXED; + else { *vpp = vp->next; ckfree(vp); out_free: @@ -284,7 +289,7 @@ out_free: goto out; } - flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); + flags |= vp->flags & bits; } else { if (flags & VNOSET) goto out; From 177072c2e718d2fa9758be9925b8558aedbc0227 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Thu, 11 Apr 2024 15:46:28 +0800 Subject: [PATCH 44/59] var: Remove unused VNOSET The bit VNOSET is no longer used. Remove it. Signed-off-by: Herbert Xu --- src/var.c | 5 ----- src/var.h | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/var.c b/src/var.c index 6f85be3..895eabc 100644 --- a/src/var.c +++ b/src/var.c @@ -267,9 +267,6 @@ struct var *setvareq(char *s, int flags) n); } - if (flags & VNOSET) - goto out; - if (vp->func && (flags & VNOFUNC) == 0) (*vp->func)(varnull(s)); @@ -291,8 +288,6 @@ out_free: flags |= vp->flags & bits; } else { - if (flags & VNOSET) - goto out; if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET) goto out_free; /* not found */ diff --git a/src/var.h b/src/var.h index aa7575a..953a694 100644 --- a/src/var.h +++ b/src/var.h @@ -48,7 +48,7 @@ #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 VNOSET 0x80 do not set variable - just readonly test */ #define VNOSAVE 0x100 /* when text is on the heap before setvareq */ From c8db655b3c7056f20362d400a1b3fd2910900c76 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Fri, 12 Apr 2024 17:51:25 +0800 Subject: [PATCH 45/59] alias: Disallow non-CWORD characters Alias names containing control characters may match words from the parser that shouldn't be matched. Disallow such characters from appearing in an alias name. Reported-by: Harald van Dijk Signed-off-by: Herbert Xu --- src/alias.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/alias.c b/src/alias.c index cee07e9..3cd3413 100644 --- a/src/alias.c +++ b/src/alias.c @@ -41,6 +41,7 @@ #include "mystring.h" #include "alias.h" #include "options.h" /* XXX for argptr (should remove?) */ +#include "syntax.h" #define ATABSIZE 39 @@ -55,6 +56,11 @@ void setalias(const char *name, const char *val) { struct alias *ap, **app; + const char *p; + + for (p = name; *p; p++) + if (BASESYNTAX[(signed char)*p] != CWORD) + sh_error("Invalid alias name: %s", name); app = __lookupalias(name); ap = *app; From 21847204559bf9a720f3863e19e8f046fdce0bf1 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Sun, 14 Apr 2024 11:08:02 +0800 Subject: [PATCH 46/59] expand: Fix here-document file descriptor leak Swap the order of here-document expansion and pipe creation as otherwise the pipe file descriptors will become accessible in the expanded text. Fixes: f4ee8c859c3d ("[EXPAND] Expand here-documents in the...") Signed-off-by: Herbert Xu --- src/redir.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/redir.c b/src/redir.c index d74602c..bcc81b4 100644 --- a/src/redir.c +++ b/src/redir.c @@ -335,15 +335,15 @@ openhere(union node *redir) int pip[2]; size_t len = 0; - if (pipe(pip) < 0) - sh_error("Pipe call failed"); - p = redir->nhere.doc->narg.text; if (redir->type == NXHERE) { expandarg(redir->nhere.doc, NULL, EXP_QUOTED); p = stackblock(); } + if (pipe(pip) < 0) + sh_error("Pipe call failed"); + len = strlen(p); if (len <= PIPESIZE) { xwrite(pip[1], p, len); From 7a11b3e330a36a2c33607ac77ceca656038b3798 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Mon, 15 Apr 2024 16:47:33 +0800 Subject: [PATCH 47/59] parser: Extend coverage of CHKEOFMARK Extend the coverage of CHKEOFMARK to cover parameter expansion, arithmetic expansion, and command substitution. For command substitution, use the reconstruction from commandtext as the here-document marker. Reported-by: Harald van Dijk Signed-off-by: Herbert Xu --- src/jobs.c | 10 +++- src/jobs.h | 1 + src/parser.c | 144 ++++++++++++++++++++++++++++++++------------------- 3 files changed, 99 insertions(+), 56 deletions(-) diff --git a/src/jobs.c b/src/jobs.c index 2a2fe22..ac22ae5 100644 --- a/src/jobs.c +++ b/src/jobs.c @@ -1242,13 +1242,19 @@ commandtext(union node *n) { char *name; - STARTSTACKSTR(cmdnextc); - cmdtxt(n); + STARTSTACKSTR(name); + commandtextcont(n, name); name = stackblock(); TRACE(("commandtext: name %p, end %p\n", name, cmdnextc)); return savestr(name); } +char *commandtextcont(union node *n, char *next) +{ + cmdnextc = next; + cmdtxt(n); + return cmdnextc; +} STATIC void cmdtxt(union node *n) diff --git a/src/jobs.h b/src/jobs.h index 2832d64..a58d2a2 100644 --- a/src/jobs.h +++ b/src/jobs.h @@ -107,6 +107,7 @@ int forkshell(struct job *, union node *, int); struct job *vforkexec(union node *n, char **argv, const char *path, int idx); int waitforjob(struct job *); int stoppedjobs(void); +char *commandtextcont(union node *n, char *next); #if ! JOBS #define setjobctl(on) ((void)(on)) /* do nothing */ diff --git a/src/parser.c b/src/parser.c index 299c260..e3168de 100644 --- a/src/parser.c +++ b/src/parser.c @@ -46,6 +46,7 @@ #include "syntax.h" #include "options.h" #include "input.h" +#include "jobs.h" #include "output.h" #include "var.h" #include "error.h" @@ -628,9 +629,10 @@ parsefname(void) union node *n = redirnode; if (n->type == NHERE) - checkkwd = CHKEOFMARK; + checkkwd |= CHKEOFMARK; if (readtoken() != TWORD) synexpect(-1); + checkkwd &= ~CHKEOFMARK; if (n->type == NHERE) { struct heredoc *here = heredoc; struct heredoc *p; @@ -901,6 +903,7 @@ readtoken1(int firstc, char const *syntax, char *eofmark, int striptabs) /* syntax stack */ struct synstack synbase = { .syntax = syntax }; struct synstack *synstack = &synbase; + int chkeofmark = checkkwd & CHKEOFMARK; if (syntax == DQSYNTAX) synstack->dblquote = 1; @@ -1010,39 +1013,35 @@ toggledq: synstack_pop(&synstack); else if (synstack->dqvarnest > 0) synstack->dqvarnest--; - USTPUTC(CTLENDVAR, out); - } else { - USTPUTC(c, out); + if (!chkeofmark) + c = CTLENDVAR; } + USTPUTC(c, out); break; case CLP: /* '(' in arithmetic */ synstack->parenlevel++; USTPUTC(c, out); break; case CRP: /* ')' in arithmetic */ - if (synstack->parenlevel > 0) { - USTPUTC(c, out); + if (synstack->parenlevel > 0) --synstack->parenlevel; + else if (pgetc_eatbnl() == ')') { + synstack_pop(&synstack); + if (chkeofmark) + USTPUTC(c, out); + else + c = CTLENDARI; } else { - if (pgetc_eatbnl() == ')') { - USTPUTC(CTLENDARI, out); - synstack_pop(&synstack); - } else { - /* - * unbalanced parens - * (don't 2nd guess - no error) - */ - pungetc(); - USTPUTC(')', out); - } + /* + * unbalanced parens + * (don't 2nd guess - no error) + */ + pungetc(); } + USTPUTC(c, out); break; case CBQUOTE: /* '`' */ - if (checkkwd & CHKEOFMARK) { - USTPUTC('`', out); - break; - } - + USTPUTC('`', out); PARSEBACKQOLD(); break; case CEOF: @@ -1218,13 +1217,16 @@ parsesub: { static const char types[] = "}-+?="; c = pgetc_eatbnl(); - if ( - (checkkwd & CHKEOFMARK) || - (c != '(' && c != '{' && !is_name(c) && !is_special(c)) - ) { + if (c != '(' && c != '{' && !is_name(c) && !is_special(c)) { USTPUTC('$', out); pungetc(); - } else if (c == '(') { /* $(command) or $((arith)) */ + goto parsesub_return; + } + + USTPUTC('$', out); + + if (c == '(') { /* $(command) or $((arith)) */ + USTPUTC(c, out); if (pgetc_eatbnl() == '(') { PARSEARITH(); } else { @@ -1234,11 +1236,15 @@ parsesub: { } else { const char *newsyn = synstack->syntax; - USTPUTC(CTLVAR, out); typeloc = out - (char *)stackblock(); - STADJUST(1, out); + if (!chkeofmark) { + out[-1] = CTLVAR; + STADJUST(1, out); + } subtype = VSNORMAL; if (likely(c == '{')) { + if (chkeofmark) + USTPUTC('{', out); c = pgetc_eatbnl(); subtype = 0; } @@ -1262,8 +1268,11 @@ varname: if (!subtype && cc == '#') { subtype = VSLENGTH; - if (c == '_' || isalnum(c)) + if (c == '_' || isalnum(c)) { + if (chkeofmark) + USTPUTC('#', out); goto varname; + } cc = c; c = pgetc_eatbnl(); @@ -1272,7 +1281,8 @@ varname: subtype = 0; c = cc; cc = '#'; - } + } else if (chkeofmark) + USTPUTC('#', out); } if (!is_special(cc)) { @@ -1288,10 +1298,15 @@ varname: if (subtype == 0) { int cc = c; + if (chkeofmark) + STPUTC(c, out); + switch (c) { case ':': subtype = VSNUL; c = pgetc_eatbnl(); + if (chkeofmark) + STPUTC(c, out); /*FALLTHROUGH*/ default: p = strchr(types, c); @@ -1304,9 +1319,11 @@ varname: subtype = c == '#' ? VSTRIMLEFT : VSTRIMRIGHT; c = pgetc_eatbnl(); - if (c == cc) + if (c == cc) { + if (chkeofmark) + STPUTC(c, out); subtype++; - else + } else pungetc(); newsyn = BASESYNTAX; @@ -1333,13 +1350,15 @@ badsub: synstack->dblquote = newsyn != BASESYNTAX; } - *((char *)stackblock() + typeloc) = subtype | VSBIT; if (subtype != VSNORMAL) { synstack->varnest++; if (synstack->dblquote) synstack->dqvarnest++; } - STPUTC('=', out); + if (!chkeofmark) { + *((char *)stackblock() + typeloc) = subtype | VSBIT; + STPUTC('=', out); + } } goto parsesub_return; } @@ -1353,14 +1372,19 @@ badsub: */ parsebackq: { - struct nodelist **nlpp; - union node *n; - char *str; - size_t savelen; - struct heredoc *saveheredoclist; int uninitialized_var(saveprompt); + struct heredoc *saveheredoclist; + struct nodelist **nlpp; + size_t psavelen; + size_t savelen; + union node *n; + char *pstr; + char *str; - USTPUTC(CTLBACKQ, out); + if (!chkeofmark) { + STADJUST(oldstyle - 1, out); + out[-1] = CTLBACKQ; + } str = stackblock(); savelen = out - (char *)stackblock(); grabstackblock(savelen); @@ -1370,9 +1394,6 @@ parsebackq: { reread it as input, interpreting it normally. */ char *pout; int pc; - size_t psavelen; - char *pstr; - STARTSTACKSTR(pout); for (;;) { @@ -1405,10 +1426,8 @@ parsebackq: { done: STPUTC('\0', pout); psavelen = pout - (char *)stackblock(); - if (psavelen > 0) { - pstr = grabstackstr(pout); - setinputstring(pstr); - } + pstr = grabstackstr(pout); + setinputstring(pstr); } nlpp = &bqlist; while (*nlpp) @@ -1440,14 +1459,26 @@ done: (*nlpp)->n = n; /* Start reading from old file again. */ popfile(); - /* Ignore any pushed back tokens left from the backquote parsing. */ - if (oldstyle) - tokpushback = 0; + out = stnputs(str, savelen, stackblock()); - if (oldstyle) + + if (oldstyle) { + /* Ignore any pushed back tokens left from the backquote + * parsing. + */ + tokpushback = 0; + if (chkeofmark) { + pstr[psavelen - 1] = '`'; + out = stnputs(pstr, psavelen, out); + } goto parsebackq_oldreturn; - else + } else { + if (chkeofmark) { + out = commandtextcont(n, out); + USTPUTC(')', out); + } goto parsebackq_newreturn; + } } /* @@ -1459,7 +1490,12 @@ parsearith: { synstack->prev ?: alloca(sizeof(*synstack)), ARISYNTAX); synstack->dblquote = 1; - USTPUTC(CTLARI, out); + if (chkeofmark) + USTPUTC(c, out); + else { + STADJUST(-1, out); + out[-1] = CTLARI; + } goto parsearith_return; } From 94b1e82588289a477933f7075db7f098d4755fad Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Sat, 20 Apr 2024 08:36:04 +0800 Subject: [PATCH 48/59] trap: Preserve parent traps for trap-only command substitution Traps are reset when a subshell is started. When a subshell is started for command substitution with a simple command whose first word is "trap", preserve the parent trap text so that they can be printed. Signed-off-by: Herbert Xu --- src/eval.c | 4 ++-- src/init.h | 4 +++- src/jobs.c | 2 +- src/mkinit.c | 5 +++-- src/trap.c | 57 +++++++++++++++++++++++++++++++++++++++------------- 5 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/eval.c b/src/eval.c index 978a174..f65f55f 100644 --- a/src/eval.c +++ b/src/eval.c @@ -491,11 +491,11 @@ evalsubshell(union node *n, int flags) expredir(n->nredir.redirect); INTOFF; if (!backgnd && flags & EV_EXIT && !have_traps()) { - forkreset(); + forkreset(NULL); goto nofork; } jp = makejob(1); - if (forkshell(jp, n, backgnd) == 0) { + if (forkshell(jp, n->nredir.n, backgnd) == 0) { flags |= EV_EXIT; if (backgnd) flags &=~ EV_TESTED; diff --git a/src/init.h b/src/init.h index d56fb28..4f98b5d 100644 --- a/src/init.h +++ b/src/init.h @@ -34,7 +34,9 @@ * @(#)init.h 8.2 (Berkeley) 5/4/95 */ +union node; + void init(void); void exitreset(void); -void forkreset(void); +void forkreset(union node *); void reset(void); diff --git a/src/jobs.c b/src/jobs.c index ac22ae5..24dcae7 100644 --- a/src/jobs.c +++ b/src/jobs.c @@ -872,7 +872,7 @@ static void forkchild(struct job *jp, union node *n, int mode) if (!lvforked) { shlvl++; - forkreset(); + forkreset(mode == FORK_NOJOB ? n : NULL); #if JOBS /* do job control only in root shell */ diff --git a/src/mkinit.c b/src/mkinit.c index 9025862..870b64d 100644 --- a/src/mkinit.c +++ b/src/mkinit.c @@ -91,6 +91,7 @@ struct event { char *name; /* name of event (e.g. INIT) */ char *routine; /* name of routine called on event */ char *comment; /* comment describing routine */ + char *args; /* arguments to routine */ struct text code; /* code for handling event */ }; @@ -128,7 +129,7 @@ char reset[] = "\ struct event event[] = { {"INIT", "init", init}, {"EXITRESET", "exitreset", exitreset}, - {"FORKRESET", "forkreset", forkreset}, + {"FORKRESET", "forkreset", forkreset, "union node *n"}, {"RESET", "reset", reset}, {NULL, NULL} }; @@ -388,7 +389,7 @@ output(void) for (ep = event ; ep->name ; ep++) { fputs("\n\n\n", fp); fputs(ep->comment, fp); - fprintf(fp, "\nvoid\n%s() {\n", ep->routine); + fprintf(fp, "\nvoid\n%s(%s) {\n", ep->routine, ep->args ?: ""); writetext(&ep->code, fp); fprintf(fp, "}\n"); } diff --git a/src/trap.c b/src/trap.c index cd84814..75501d7 100644 --- a/src/trap.c +++ b/src/trap.c @@ -66,7 +66,9 @@ /* trap handler commands */ -MKINIT char *trap[NSIG]; +static char *trap[NSIG]; +/* traps have not been fully cleared */ +static int ptrap; /* number of non-null traps */ int trapcnt; /* current value of signal */ @@ -81,6 +83,7 @@ volatile sig_atomic_t gotsigchld; extern char *signal_names[]; static int decode_signum(const char *); +MKINIT void clear_traps(union node *); #ifdef mkinit INCLUDE "memalloc.h" @@ -92,19 +95,7 @@ INIT { } FORKRESET { - char **tp; - - INTOFF; - for (tp = trap ; tp < &trap[NSIG] ; tp++) { - if (*tp && **tp) { /* trap not NULL or SIG_IGN */ - ckfree(*tp); - *tp = NULL; - if (tp != &trap[0]) - setsignal(tp - trap); - } - } - trapcnt = 0; - INTON; + clear_traps(n); } #endif @@ -133,6 +124,8 @@ trapcmd(int argc, char **argv) } return 0; } + if (ptrap) + clear_traps(NULL); if (!ap[1] || decode_signum(*ap) >= 0) action = NULL; else @@ -168,6 +161,40 @@ trapcmd(int argc, char **argv) +/* + * Clear traps on a fork. + */ + +void clear_traps(union node *n) +{ + int simplecmd; + char **tp; + + simplecmd = n && n->type == NCMD && n->ncmd.args && + equal(n->ncmd.args->narg.text, "trap"); + + INTOFF; + for (tp = trap ; tp < &trap[NSIG] ; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + char *otp = *tp; + + *tp = NULL; + if (tp != &trap[0]) + setsignal(tp - trap); + + if (simplecmd) + *tp = otp; + else + ckfree(*tp); + } + } + trapcnt = 0; + ptrap = simplecmd; + INTON; +} + + + /* * Set the signal handler for the specified signal. The routine figures * out what it should be set to. @@ -390,6 +417,8 @@ exitshell(void) handler = &loc; if ((p = trap[0])) { trap[0] = NULL; + if (ptrap) + goto out; evalskip = 0; evalstring(p, 0); evalskip = SKIPFUNCDEF; From 9881d00e939e75e5348aebe6046ff80d3b7edb17 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Mon, 15 Apr 2024 20:01:33 +0800 Subject: [PATCH 49/59] jobs: Preserve parent jobs for simple commands Do not free parent shell jobs if a simple command with the first word being "jobs" is executed as a command substitution. Signed-off-by: Herbert Xu --- src/jobs.c | 6 ++++++ src/parser.c | 6 ++++++ src/parser.h | 3 +++ src/trap.c | 5 +++-- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/jobs.c b/src/jobs.c index 24dcae7..840e37c 100644 --- a/src/jobs.c +++ b/src/jobs.c @@ -53,6 +53,7 @@ #include #undef CEOF /* syntax.h redefines this */ #endif +#include "builtins.h" #include "exec.h" #include "eval.h" #include "init.h" @@ -913,6 +914,11 @@ static void forkchild(struct job *jp, union node *n, int mode) if (lvforked) return; + freejob(jp); + + if (issimplecmd(n, JOBSCMD->name)) + return; + for (jp = curjob; jp; jp = jp->prev_job) freejob(jp); } diff --git a/src/parser.c b/src/parser.c index e3168de..27611f0 100644 --- a/src/parser.c +++ b/src/parser.c @@ -133,6 +133,12 @@ int isassignment(const char *p) return *q == '='; } +int issimplecmd(union node *n, const char *name) +{ + return n && n->type == NCMD && n->ncmd.args && + equal(n->ncmd.args->narg.text, name); +} + static inline int realeofmark(const char *eofmark) { return eofmark && eofmark != FAKEEOFMARK; diff --git a/src/parser.h b/src/parser.h index 729c15c..433573d 100644 --- a/src/parser.h +++ b/src/parser.h @@ -36,6 +36,8 @@ #include "token.h" +union node; + /* control characters in argument strings */ #define CTL_FIRST -127 /* first 'special' character */ #define CTLESC -127 /* escape next character */ @@ -85,6 +87,7 @@ extern int checkkwd; int isassignment(const char *p); +int issimplecmd(union node *n, const char *name); union node *parsecmd(int); void fixredir(union node *, const char *, int); const char *getprompt(void *); diff --git a/src/trap.c b/src/trap.c index 75501d7..f871656 100644 --- a/src/trap.c +++ b/src/trap.c @@ -37,6 +37,7 @@ #include #include +#include "builtins.h" #include "shell.h" #include "main.h" #include "nodes.h" /* for other headers */ @@ -47,6 +48,7 @@ #include "options.h" #include "syntax.h" #include "output.h" +#include "parser.h" #include "memalloc.h" #include "error.h" #include "trap.h" @@ -170,8 +172,7 @@ void clear_traps(union node *n) int simplecmd; char **tp; - simplecmd = n && n->type == NCMD && n->ncmd.args && - equal(n->ncmd.args->narg.text, "trap"); + simplecmd = issimplecmd(n, TRAPCMD->name); INTOFF; for (tp = trap ; tp < &trap[NSIG] ; tp++) { From 509f5b0dcd710804fb6c66750c7c7fd2d30a3ec9 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Sun, 21 Apr 2024 08:33:53 +0800 Subject: [PATCH 50/59] redir: Use memfd_create instead of pipe Use memfd_create(2) instead of pipe(2). With pipe(2), a fork is required if the amount of data to be written exceeds the pipe size. This is not the case with memfd_create. Signed-off-by: Herbert Xu --- configure.ac | 2 +- src/eval.c | 3 +-- src/redir.c | 61 +++++++++++++++++++++++++++++++++++----------------- src/redir.h | 1 + src/system.h | 7 ++++++ 5 files changed, 51 insertions(+), 23 deletions(-) diff --git a/configure.ac b/configure.ac index 6993364..cb55c3f 100644 --- a/configure.ac +++ b/configure.ac @@ -87,7 +87,7 @@ AC_CHECK_DECL([PRIdMAX],, dnl Checks for library functions. AC_CHECK_FUNCS(bsearch faccessat getpwnam getrlimit isalpha killpg \ - mempcpy \ + memfd_create mempcpy \ sigsetmask stpcpy strchrnul strsignal strtod strtoimax \ strtoumax sysconf) diff --git a/src/eval.c b/src/eval.c index f65f55f..d169eb8 100644 --- a/src/eval.c +++ b/src/eval.c @@ -635,8 +635,7 @@ evalbackcmd(union node *n, struct backcmd *result) goto out; } - if (pipe(pip) < 0) - sh_error("Pipe call failed"); + sh_pipe(pip, 0); jp = makejob(1); if (forkshell(jp, n, FORK_NOJOB) == 0) { FORCEINTON; diff --git a/src/redir.c b/src/redir.c index bcc81b4..bf5207d 100644 --- a/src/redir.c +++ b/src/redir.c @@ -32,6 +32,7 @@ * SUCH DAMAGE. */ +#include #include #include #include /* PIPE_BUF */ @@ -280,6 +281,21 @@ ecreate: sh_open_fail(fname, O_CREAT, EEXIST); } +static int sh_dup2(int ofd, int nfd, int cfd) +{ + if (nfd < 0) { + nfd = dup(ofd); + if (nfd >= 0) + cfd = -1; + } else + nfd = dup2(ofd, nfd); + if (likely(cfd >= 0)) + close(cfd); + if (nfd < 0) + sh_error("%d: %s", ofd, strerror(errno)); + + return nfd; +} #ifdef notyet static void dupredirect(union node *redir, int f, char memory[10]) @@ -288,7 +304,6 @@ static void dupredirect(union node *redir, int f) #endif { int fd = redir->nfile.fd; - int err = 0; #ifdef notyet memory[fd] = 0; @@ -301,26 +316,31 @@ static void dupredirect(union node *redir, int f) memory[fd] = 1; else #endif - if (dup2(f, fd) < 0) { - err = errno; - goto err; - } + sh_dup2(f, fd, -1); return; } f = fd; - } else if (dup2(f, fd) < 0) - err = errno; + } else + sh_dup2(f, fd, f); close(f); - if (err < 0) - goto err; - - return; - -err: - sh_error("%d: %s", f, strerror(err)); } +int sh_pipe(int pip[2], int memfd) +{ + if (memfd) { + pip[0] = memfd_create("dash", 0); + if (pip[0] >= 0) { + pip[1] = sh_dup2(pip[0], -1, pip[0]); + return 1; + } + } + + if (pipe(pip) < 0) + sh_error("Pipe call failed"); + + return 0; +} /* * Handle here documents. Normally we fork off a process to write the @@ -331,9 +351,10 @@ err: STATIC int openhere(union node *redir) { - char *p; - int pip[2]; size_t len = 0; + int pip[2]; + int memfd; + char *p; p = redir->nhere.doc->narg.text; if (redir->type == NXHERE) { @@ -341,12 +362,12 @@ openhere(union node *redir) p = stackblock(); } - if (pipe(pip) < 0) - sh_error("Pipe call failed"); - len = strlen(p); - if (len <= PIPESIZE) { + memfd = sh_pipe(pip, len > PIPESIZE); + + if (memfd || len <= PIPESIZE) { xwrite(pip[1], p, len); + lseek(pip[1], 0, SEEK_SET); goto out; } diff --git a/src/redir.h b/src/redir.h index 16f5c20..0be5f1a 100644 --- a/src/redir.h +++ b/src/redir.h @@ -50,4 +50,5 @@ int redirectsafe(union node *, int); void unwindredir(struct redirtab *stop); struct redirtab *pushredir(union node *redir); int sh_open(const char *pathname, int flags, int mayfail); +int sh_pipe(int pip[2], int memfd); diff --git a/src/system.h b/src/system.h index 007952c..371c64b 100644 --- a/src/system.h +++ b/src/system.h @@ -54,6 +54,13 @@ static inline void sigclearmask(void) #endif } +#ifndef HAVE_MEMFD_CREATE +static inline int memfd_create(const char *name, unsigned int flags) +{ + return -1; +} +#endif + #ifndef HAVE_MEMPCPY void *mempcpy(void *, const void *, size_t); #endif From f9af463600b1025e7e14914c9fcde972a2be991e Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Sat, 27 Apr 2024 17:11:02 +0800 Subject: [PATCH 51/59] input: Fix history line reading regression When a newline is encountered with history support, terminate the loop immediately. Fixes: 44ae22beedf8 ("input: Disable lleft in SMALL mode") Signed-off-by: Herbert Xu --- src/input.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/input.c b/src/input.c index 38969a7..fb9858f 100644 --- a/src/input.c +++ b/src/input.c @@ -301,7 +301,7 @@ again: switch (c) { case '\n': parsefile->nleft = q - parsefile->nextc - 1; - goto check; + goto done; default: something = 1; @@ -320,6 +320,7 @@ check: break; } } +done: input_set_lleft(parsefile, more); if (!IS_DEFINED_SMALL) From 13fc32156f7ea9b683ff8d3c983db233129d04e8 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Sat, 27 Apr 2024 17:11:31 +0800 Subject: [PATCH 52/59] main: Fix profiling on longjmp exit paths Ensure that longjmp exit paths also write the profiling file. Signed-off-by: Herbert Xu --- src/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index 5c49fdc..7beb280 100644 --- a/src/main.c +++ b/src/main.c @@ -112,7 +112,7 @@ main(int argc, char **argv) s = state; if (e == EXEND || e == EXEXIT || s == 0 || iflag == 0 || shlvl) - exitshell(); + goto exit; reset(); @@ -175,6 +175,7 @@ state3: state4: /* XXX ??? - why isn't this before the "if" statement */ cmdloop(1); } +exit: #if PROFILE monitor(0); #endif From 43ffd41e7c8233af79cfb74ac416de290711459b Mon Sep 17 00:00:00 2001 From: Harald van Dijk Date: Sun, 28 Apr 2024 01:38:34 +0100 Subject: [PATCH 53/59] alias: Simplify alias storage Rather than storing the alias name and value separately, we can reduce simplify code and reduce code size by storing them in name=value form. This allows us to re-use some code from var.c to handle hashing and comparisons, so long as we update that to account for aliases' special handling of a leading = character. This is okay to do for variables as well, as for variables the leading character is guaranteed to not be =. Signed-off-by: Herbert Xu --- src/alias.c | 40 ++++++++++++++-------------------------- src/input.c | 4 ++-- src/var.c | 27 +++++++++++---------------- src/var.h | 15 +++++++++++++++ 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/src/alias.c b/src/alias.c index 3cd3413..bacf10f 100644 --- a/src/alias.c +++ b/src/alias.c @@ -42,6 +42,7 @@ #include "alias.h" #include "options.h" /* XXX for argptr (should remove?) */ #include "syntax.h" +#include "var.h" #define ATABSIZE 39 @@ -56,30 +57,31 @@ void setalias(const char *name, const char *val) { struct alias *ap, **app; - const char *p; + const char *p = name; + size_t namelen; - for (p = name; *p; p++) + do { if (BASESYNTAX[(signed char)*p] != CWORD) sh_error("Invalid alias name: %s", name); + } while (*++p != '='); app = __lookupalias(name); ap = *app; INTOFF; if (ap) { - if (!(ap->flag & ALIASINUSE)) { - ckfree(ap->val); - } - ap->val = savestr(val); + if (!(ap->flag & ALIASINUSE)) + ckfree(ap->name); ap->flag &= ~ALIASDEAD; } else { /* not found */ ap = ckmalloc(sizeof (struct alias)); - ap->name = savestr(name); - ap->val = savestr(val); ap->flag = 0; ap->next = 0; *app = ap; } + namelen = val - name; + ap->name = savestr(name); + ap->val = ap->name + namelen; INTON; } @@ -157,8 +159,7 @@ aliascmd(int argc, char **argv) } else printalias(ap); } else { - *v++ = '\0'; - setalias(n, v); + setalias(n, v + 1); } } @@ -197,36 +198,23 @@ freealias(struct alias *ap) { next = ap->next; ckfree(ap->name); - ckfree(ap->val); ckfree(ap); return next; } void printalias(const struct alias *ap) { - out1str(single_quote(ap->name)); - out1fmt("=%s\n", single_quote(ap->val)); + out1fmt("%s\n", single_quote(ap->name)); } STATIC struct alias ** __lookupalias(const char *name) { - unsigned int hashval; struct alias **app; - const char *p; - unsigned int ch; - p = name; - - ch = (unsigned char)*p; - hashval = ch << 4; - while (ch) { - hashval += ch; - ch = (unsigned char)*++p; - } - app = &atab[hashval % ATABSIZE]; + app = &atab[hashval(name) % ATABSIZE]; for (; *app; app = &(*app)->next) { - if (equal(name, (*app)->name)) { + if (varequal(name, (*app)->name)) { break; } } diff --git a/src/input.c b/src/input.c index fb9858f..c0c7410 100644 --- a/src/input.c +++ b/src/input.c @@ -387,7 +387,7 @@ pushstring(char *s, void *ap) sp->ap = (struct alias *)ap; if (ap) { ((struct alias *)ap)->flag |= ALIASINUSE; - sp->string = s; + sp->string = ((struct alias *)ap)->name; } parsefile->nextc = s; parsefile->nleft = len; @@ -406,7 +406,7 @@ static void popstring(void) parsefile->nextc[-1] == '\t') { checkkwd |= CHKALIAS; } - if (sp->string != sp->ap->val) { + if (sp->string != sp->ap->name) { ckfree(sp->string); } } diff --git a/src/var.c b/src/var.c index 895eabc..35ea7c6 100644 --- a/src/var.c +++ b/src/var.c @@ -622,12 +622,7 @@ void unsetvar(const char *s) STATIC struct var ** hashvar(const char *p) { - unsigned int hashval; - - hashval = ((unsigned char) *p) << 4; - while (*p && *p != '=') - hashval += (unsigned char) *p++; - return &vartab[hashval % VTABSIZE]; + return &vartab[hashval(p) % VTABSIZE]; } @@ -641,19 +636,19 @@ hashvar(const char *p) int varcmp(const char *p, const char *q) { - int c, d; - - while ((c = *p) == (d = *q)) { - if (!c || c == '=') - goto out; + int c = *p, d = *q; + while (c == d) { + if (!c) + break; p++; q++; + c = *p; + d = *q; + if (c == '=') + c = '\0'; + if (d == '=') + d = '\0'; } - if (c == '=') - c = 0; - if (d == '=') - d = 0; -out: return c - d; } diff --git a/src/var.h b/src/var.h index 953a694..f6fb320 100644 --- a/src/var.h +++ b/src/var.h @@ -153,6 +153,21 @@ int unsetcmd(int, char **); void unsetvar(const char *); int varcmp(const char *, const char *); +static inline unsigned int hashval(const char *p) +{ + unsigned int hashval; + + hashval = ((unsigned char) *p) << 4; + while (*p) { + hashval += (unsigned char) *p++; + if (*p == '=') + break; + } + + return hashval; +} + + static inline int varequal(const char *a, const char *b) { return !varcmp(a, b); } From 1f1e555aba99808a82cb5090b5ef980714dea09c Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Wed, 1 May 2024 17:12:27 +0800 Subject: [PATCH 54/59] expand: Fix naked backslah leakage Naked backslashes in patterns may incorrectly unquote subsequent wild characters that are themselves quoted. Fix this by adding an extra backslash when necessary. Test case: a="\\*bc"; b="\\"; c="*"; echo "<${a##$b"$c"}>" Old result: <> New result: Signed-off-by: Herbert Xu --- src/expand.c | 10 ++++++++-- src/mystring.c | 1 + src/mystring.h | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/expand.c b/src/expand.c index 2ed02d6..0db2b29 100644 --- a/src/expand.c +++ b/src/expand.c @@ -1658,6 +1658,7 @@ _rmescapes(char *str, int flag) char *p, *q, *r; int notescaped; int globbing; + int inquotes; p = strpbrk(str, cqchars); if (!p) { @@ -1692,16 +1693,17 @@ _rmescapes(char *str, int flag) q = mempcpy(q, str, len); } } + inquotes = 0; notescaped = globbing; while (*p) { if (*p == (char)CTLQUOTEMARK) { p++; - notescaped = globbing; + inquotes ^= globbing; continue; } if (*p == '\\') { /* naked back slash */ - notescaped = 0; + notescaped ^= globbing; goto copy; } if (FNMATCH_IS_ENABLED && *p == '^') @@ -1711,6 +1713,10 @@ _rmescapes(char *str, int flag) add_escape: if (notescaped) *q++ = '\\'; + else if (inquotes) { + *q++ = '\\'; + *q++ = '\\'; + } } notescaped = globbing; copy: diff --git a/src/mystring.c b/src/mystring.c index f651521..5eace6c 100644 --- a/src/mystring.c +++ b/src/mystring.c @@ -63,6 +63,7 @@ const char snlfmt[] = "%s\n"; const char dolatstr[] = { CTLQUOTEMARK, CTLVAR, VSNORMAL | VSBIT, '@', '=', CTLQUOTEMARK, '\0' }; const char cqchars[] = { + '\\', #ifdef HAVE_FNMATCH '^', #endif diff --git a/src/mystring.h b/src/mystring.h index 564b911..d0ec9dd 100644 --- a/src/mystring.h +++ b/src/mystring.h @@ -48,7 +48,7 @@ extern const char spcstr[]; extern const char dolatstr[]; #define DOLATSTRLEN 6 extern const char cqchars[]; -#define qchars (cqchars + FNMATCH_IS_ENABLED) +#define qchars (cqchars + FNMATCH_IS_ENABLED + 1) extern const char illnum[]; extern const char homestr[]; From 32fdc38848381389450aa736a82f7285ed61fc68 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Thu, 2 May 2024 18:22:20 +0800 Subject: [PATCH 55/59] input: Fix potential out-of-bounds read in popstring For an empty alias, the check on the last character of the alias in popstring may read a bogus byte. Fix this by checking whether the alias is empty or not before reading the last byte. Signed-off-by: Herbert Xu --- src/input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input.c b/src/input.c index c0c7410..1c598b2 100644 --- a/src/input.c +++ b/src/input.c @@ -401,7 +401,7 @@ static void popstring(void) struct strpush *sp = parsefile->strpush; INTOFF; - if (sp->ap) { + if (sp->ap && parsefile->nextc > sp->string) { if (parsefile->nextc[-1] == ' ' || parsefile->nextc[-1] == '\t') { checkkwd |= CHKALIAS; From 245a47b9a406b21f46d1aeb4a52329bbace6ef34 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Sat, 4 May 2024 13:18:02 +0800 Subject: [PATCH 56/59] mystring: Add a few more uses of snlfmt Use snlfmt in a few more places. Signed-off-by: Herbert Xu --- src/alias.c | 2 +- src/jobs.c | 2 +- src/miscbltin.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/alias.c b/src/alias.c index bacf10f..a443e05 100644 --- a/src/alias.c +++ b/src/alias.c @@ -204,7 +204,7 @@ freealias(struct alias *ap) { void printalias(const struct alias *ap) { - out1fmt("%s\n", single_quote(ap->name)); + out1fmt(snlfmt, single_quote(ap->name)); } STATIC struct alias ** diff --git a/src/jobs.c b/src/jobs.c index 840e37c..4cf6b8c 100644 --- a/src/jobs.c +++ b/src/jobs.c @@ -345,7 +345,7 @@ usage: pid = **argv == '-' ? -number(*argv + 1) : number(*argv); if (kill(pid, signo) != 0) { - sh_warnx("%s\n", strerror(errno)); + sh_warnx(snlfmt, strerror(errno)); i = 1; } } while (*++argv); diff --git a/src/miscbltin.c b/src/miscbltin.c index e553f9e..8a0ddf4 100644 --- a/src/miscbltin.c +++ b/src/miscbltin.c @@ -237,7 +237,7 @@ umaskcmd(int argc, char **argv) *ap++ = ','; } ap[-1] = '\0'; - out1fmt("%s\n", buf); + out1fmt(snlfmt, buf); } else { out1fmt("%.4o\n", mask); } From 257f6b36b8505dca9d073a2c5d4903c9c86faae9 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Sat, 4 May 2024 13:30:33 +0800 Subject: [PATCH 57/59] alias: Mark printalias as noinline The function printalias is not any critical path and inlining it makes no sense. Signed-off-by: Herbert Xu --- src/alias.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/alias.c b/src/alias.c index a443e05..1b9b979 100644 --- a/src/alias.c +++ b/src/alias.c @@ -202,8 +202,7 @@ freealias(struct alias *ap) { return next; } -void -printalias(const struct alias *ap) { +void __attribute__((noinline)) printalias(const struct alias *ap) { out1fmt(snlfmt, single_quote(ap->name)); } From ea5e24281aaaead307b08bd3f646a138c19ea6bc Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Sat, 4 May 2024 15:21:22 +0800 Subject: [PATCH 58/59] redir: Fix double close in dupredirect For a redirection like "> /dev/null" dupredirect will close the newly opened file descriptor twice in a row because sh_dup2 also closes the new file descriptor. Remove the extra close in dupredirect. Fixes: 509f5b0dcd71 ("redir: Use memfd_create instead of pipe") Signed-off-by: Herbert Xu --- src/redir.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/redir.c b/src/redir.c index bf5207d..2505d49 100644 --- a/src/redir.c +++ b/src/redir.c @@ -319,11 +319,9 @@ static void dupredirect(union node *redir, int f) sh_dup2(f, fd, -1); return; } - f = fd; + close(fd); } else sh_dup2(f, fd, f); - - close(f); } int sh_pipe(int pip[2], int memfd) From f47009f9a76ef953e1fd298f7cd5cb082233cf1a Mon Sep 17 00:00:00 2001 From: Martijn Dekker Date: Sun, 12 May 2024 22:02:51 +0100 Subject: [PATCH 59/59] redir: Fix non-Linux build As of 509f5b0d, the build fails on Darwin/macOS, which does not have memfd_create(2). The build fails on redir.c because the fallback for memfd_create, which is defined in system.h, is not included. Presumably it is the same on other systems without memfd_create. Fixes: 509f5b0dcd71 ("redir: Use memfd_create instead of pipe") Signed-off-by: Herbert Xu --- src/redir.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/redir.c b/src/redir.c index 2505d49..c57d745 100644 --- a/src/redir.c +++ b/src/redir.c @@ -56,6 +56,7 @@ #include "output.h" #include "memalloc.h" #include "error.h" +#include "system.h" #include "trap.h"