diff options
Diffstat (limited to 'bin/sh')
| -rw-r--r-- | bin/sh/Makefile | 7 | ||||
| -rw-r--r-- | bin/sh/dot.profile | 2 | ||||
| -rw-r--r-- | bin/sh/jobs.c | 9 | ||||
| -rw-r--r-- | bin/sh/miscbltin.c | 83 | ||||
| -rw-r--r-- | bin/sh/parser.c | 126 | ||||
| -rw-r--r-- | bin/sh/sh.1 | 6 | ||||
| -rw-r--r-- | bin/sh/tests/builtins/Makefile | 2 | ||||
| -rw-r--r-- | bin/sh/tests/builtins/read11.0 | 21 | ||||
| -rw-r--r-- | bin/sh/tests/builtins/read12.0 | 32 | ||||
| -rw-r--r-- | bin/sh/tests/builtins/wait11.0 | 6 | ||||
| -rw-r--r-- | bin/sh/tests/execution/Makefile | 1 | ||||
| -rw-r--r-- | bin/sh/tests/execution/bg14.0 | 9 | ||||
| -rw-r--r-- | bin/sh/tests/parser/Makefile | 6 | ||||
| -rw-r--r-- | bin/sh/tests/parser/ps1-expand1.0 | 7 | ||||
| -rw-r--r-- | bin/sh/tests/parser/ps1-expand2.0 | 7 | ||||
| -rw-r--r-- | bin/sh/tests/parser/ps1-expand3.0 | 8 | ||||
| -rw-r--r-- | bin/sh/tests/parser/ps1-expand4.0 | 8 | ||||
| -rw-r--r-- | bin/sh/tests/parser/ps1-expand5.0 | 8 | ||||
| -rw-r--r-- | bin/sh/tests/parser/ps2-expand1.0 | 12 |
19 files changed, 318 insertions, 42 deletions
diff --git a/bin/sh/Makefile b/bin/sh/Makefile index 087dbf40c3bd..2b1eca8e4b31 100644 --- a/bin/sh/Makefile +++ b/bin/sh/Makefile @@ -69,11 +69,4 @@ token.h: mktokens HAS_TESTS= SUBDIR.${MK_TESTS}+= tests -beforeinstallconfig: - rm -f ${DESTDIR}/.profile - -LINKMODE=${CONFMODE} -afterinstallconfig: - ${INSTALL_LINK} ${TAG_ARGS} ${DESTDIR}/root/.profile ${DESTDIR}/.profile - .include <bsd.prog.mk> diff --git a/bin/sh/dot.profile b/bin/sh/dot.profile index d27a2ae2fdbe..cba9bcf18ad9 100644 --- a/bin/sh/dot.profile +++ b/bin/sh/dot.profile @@ -1,6 +1,4 @@ # -HOME=/root -export HOME PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:~/bin export PATH TERM=${TERM:-xterm} diff --git a/bin/sh/jobs.c b/bin/sh/jobs.c index 1328ae50edef..a4cd76473921 100644 --- a/bin/sh/jobs.c +++ b/bin/sh/jobs.c @@ -573,6 +573,7 @@ waitcmdloop(struct job *job) freejob(job); else { job->remembered = 0; + deljob(job); if (job == bgjob) bgjob = NULL; } @@ -599,7 +600,7 @@ waitcmdloop(struct job *job) break; } } - } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1); + } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, job) != -1); sig = pendingsig_waitcmd; pendingsig_waitcmd = 0; @@ -1077,6 +1078,7 @@ waitforjob(struct job *jp, int *signaled) #if JOBS int propagate_int = jp->jobctl && jp->foreground; #endif + int jobindex; int status; int st; @@ -1084,8 +1086,11 @@ waitforjob(struct job *jp, int *signaled) TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1)); while (jp->state == 0) if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG | - DOWAIT_SIG_TRAP : 0), jp) == -1) + DOWAIT_SIG_TRAP : 0), jp) == -1) { + jobindex = jp - jobtab; dotrap(); + jp = jobtab + jobindex; + } #if JOBS if (jp->jobctl) { if (ttyfd >= 0 && tcsetpgrp(ttyfd, rootpid) < 0) diff --git a/bin/sh/miscbltin.c b/bin/sh/miscbltin.c index 9d0280bb548a..bbf0aa5b8bde 100644 --- a/bin/sh/miscbltin.c +++ b/bin/sh/miscbltin.c @@ -40,11 +40,14 @@ #include <sys/stat.h> #include <sys/time.h> #include <sys/resource.h> -#include <unistd.h> + #include <errno.h> +#include <poll.h> +#include <signal.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> +#include <unistd.h> #include "shell.h" #include "options.h" @@ -162,17 +165,18 @@ readcmd(int argc __unused, char **argv __unused) int is_ifs; int saveall = 0; ptrdiff_t lastnonifs, lastnonifsws; - struct timeval tv; - char *tvptr; - fd_set ifds; + sigset_t set, oset; + intmax_t number, timeout; + struct timespec tnow, tend, tresid; + struct pollfd pfd; + char *endptr; ssize_t nread; int sig; struct fdctx fdctx; rflag = 0; prompt = NULL; - tv.tv_sec = -1; - tv.tv_usec = 0; + timeout = -1; while ((i = nextopt("erp:t:")) != '\0') { switch(i) { case 'p': @@ -184,22 +188,29 @@ readcmd(int argc __unused, char **argv __unused) rflag = 1; break; case 't': - tv.tv_sec = strtol(shoptarg, &tvptr, 0); - if (tvptr == shoptarg) - error("timeout value"); - switch(*tvptr) { - case 0: - case 's': - break; - case 'h': - tv.tv_sec *= 60; - /* FALLTHROUGH */ - case 'm': - tv.tv_sec *= 60; - break; - default: - error("timeout unit"); - } + timeout = 0; + do { + number = strtol(shoptarg, &endptr, 0); + if (number < 0 || endptr == shoptarg) + error("timeout value"); + switch (*endptr) { + case 's': + endptr++; + break; + case 'h': + number *= 60; + /* FALLTHROUGH */ + case 'm': + number *= 60; + endptr++; + break; + } + if (*endptr != '\0' && + !(*endptr >= '0' && *endptr <= '9')) + error("timeout unit"); + timeout += number; + shoptarg = endptr; + } while (*shoptarg != '\0'); break; } } @@ -212,13 +223,33 @@ readcmd(int argc __unused, char **argv __unused) if ((ifs = bltinlookup("IFS", 1)) == NULL) ifs = " \t\n"; - if (tv.tv_sec >= 0) { + if (timeout >= 0) { /* * Wait for something to become available. */ - FD_ZERO(&ifds); - FD_SET(0, &ifds); - status = select(1, &ifds, NULL, NULL, &tv); + pfd.fd = STDIN_FILENO; + pfd.events = POLLIN; + status = sig = 0; + sigfillset(&set); + sigprocmask(SIG_SETMASK, &set, &oset); + if (pendingsig) { + /* caught a signal already */ + status = -1; + } else if (timeout == 0) { + status = poll(&pfd, 1, 0); + } else { + clock_gettime(CLOCK_UPTIME, &tnow); + tend = tnow; + tend.tv_sec += timeout; + do { + timespecsub(&tend, &tnow, &tresid); + status = ppoll(&pfd, 1, &tresid, &oset); + if (status >= 0 || pendingsig != 0) + break; + clock_gettime(CLOCK_UPTIME, &tnow); + } while (timespeccmp(&tnow, &tend, <)); + } + sigprocmask(SIG_SETMASK, &oset, NULL); /* * If there's nothing ready, return an error. */ diff --git a/bin/sh/parser.c b/bin/sh/parser.c index 0c1b7a91c257..3e42d41caec4 100644 --- a/bin/sh/parser.c +++ b/bin/sh/parser.c @@ -55,6 +55,8 @@ #include "show.h" #include "eval.h" #include "exec.h" /* to check for special builtins */ +#include "main.h" +#include "jobs.h" #ifndef NO_HISTORY #include "myhistedit.h" #endif @@ -2050,7 +2052,129 @@ getprompt(void *unused __unused) * Format prompt string. */ for (i = 0; (i < PROMPTLEN - 1) && (*fmt != '\0'); i++, fmt++) { - if (*fmt != '\\') { + if (*fmt == '$') { + const char *varname_start, *varname_end, *value; + char varname[256]; + int namelen, braced = 0; + + fmt++; /* Skip the '$' */ + + /* Check for ${VAR} syntax */ + if (*fmt == '{') { + braced = 1; + fmt++; + } + + varname_start = fmt; + + /* Extract variable name */ + if (is_digit(*fmt)) { + /* Positional parameter: $0, $1, etc. */ + fmt++; + varname_end = fmt; + } else if (is_special(*fmt)) { + /* Special parameter: $?, $!, $$, etc. */ + fmt++; + varname_end = fmt; + } else if (is_name(*fmt)) { + /* Regular variable name */ + do + fmt++; + while (is_in_name(*fmt)); + varname_end = fmt; + } else { + /* + * Not a valid variable reference. + * Output literal '$'. + */ + ps[i] = '$'; + if (braced && i < PROMPTLEN - 2) + ps[++i] = '{'; + fmt = varname_start - 1; + continue; + } + + namelen = varname_end - varname_start; + if (namelen == 0 || namelen >= (int)sizeof(varname)) { + /* Invalid or too long, output literal */ + ps[i] = '$'; + fmt = varname_start - 1; + continue; + } + + /* Copy variable name */ + memcpy(varname, varname_start, namelen); + varname[namelen] = '\0'; + + /* Handle closing brace for ${VAR} */ + if (braced) { + if (*fmt == '}') { + fmt++; + } else { + /* Missing closing brace, treat as literal */ + ps[i] = '$'; + if (i < PROMPTLEN - 2) + ps[++i] = '{'; + fmt = varname_start - 1; + continue; + } + } + + /* Look up the variable */ + if (namelen == 1 && is_digit(*varname)) { + /* Positional parameters - check digits FIRST */ + int num = *varname - '0'; + if (num == 0) + value = arg0 ? arg0 : ""; + else if (num > 0 && num <= shellparam.nparam) + value = shellparam.p[num - 1]; + else + value = ""; + } else if (namelen == 1 && is_special(*varname)) { + /* Special parameters */ + char valbuf[20]; + int num; + + switch (*varname) { + case '$': + num = rootpid; + break; + case '?': + num = exitstatus; + break; + case '#': + num = shellparam.nparam; + break; + case '!': + num = backgndpidval(); + break; + default: + num = 0; + break; + } + snprintf(valbuf, sizeof(valbuf), "%d", num); + value = valbuf; + } else { + /* Regular variables */ + value = lookupvar(varname); + if (value == NULL) + value = ""; + } + + /* Copy value to output, respecting buffer size */ + while (*value != '\0' && i < PROMPTLEN - 1) { + ps[i++] = *value++; + } + + /* + * Adjust fmt and i for the loop increment. + * fmt will be incremented by the for loop, + * so position it one before where we want. + */ + fmt--; + i--; + continue; + } else if (*fmt != '\\') { ps[i] = *fmt; continue; } diff --git a/bin/sh/sh.1 b/bin/sh/sh.1 index 7ef22fa352bb..affb653cd3ae 100644 --- a/bin/sh/sh.1 +++ b/bin/sh/sh.1 @@ -31,7 +31,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 1, 2025 +.Dd November 17, 2025 .Dt SH 1 .Os .Sh NAME @@ -2544,6 +2544,10 @@ to explicitly specify seconds, minutes or hours. If none is supplied, .Ql s is assumed. +Multiple value-unit groups may be stringed together, in which case +they are added up, e.g.\& +.Ql 1h30m15s +which adds up to 5,415 seconds. .Pp The .Fl e diff --git a/bin/sh/tests/builtins/Makefile b/bin/sh/tests/builtins/Makefile index 7fdecb23c817..0246009cce81 100644 --- a/bin/sh/tests/builtins/Makefile +++ b/bin/sh/tests/builtins/Makefile @@ -143,6 +143,7 @@ ${PACKAGE}FILES+= read8.0 ${PACKAGE}FILES+= read9.0 ${PACKAGE}FILES+= read10.0 ${PACKAGE}FILES+= read11.0 +${PACKAGE}FILES+= read12.0 ${PACKAGE}FILES+= return1.0 ${PACKAGE}FILES+= return2.1 ${PACKAGE}FILES+= return3.1 @@ -188,5 +189,6 @@ ${PACKAGE}FILES+= wait7.0 ${PACKAGE}FILES+= wait8.0 ${PACKAGE}FILES+= wait9.127 ${PACKAGE}FILES+= wait10.0 +${PACKAGE}FILES+= wait11.0 .include <bsd.test.mk> diff --git a/bin/sh/tests/builtins/read11.0 b/bin/sh/tests/builtins/read11.0 index c75ed9c92a83..07bd3e70644c 100644 --- a/bin/sh/tests/builtins/read11.0 +++ b/bin/sh/tests/builtins/read11.0 @@ -1,3 +1,5 @@ +# Verify that `read -t 0 v` succeeds immediately if input is available +# and fails immediately if not set -e @@ -5,13 +7,26 @@ T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) trap 'rm -rf "$T"' 0 cd $T mkfifo fifo1 -# Open fifo1 for writing and then read block on a dummy fifo -{ mkfifo fifo2; read dummy <fifo2; } >fifo1 & +# Open fifo1 for writing +{ echo new_value; sleep 10; } >fifo1 & # Wait for the child to open fifo1 for writing exec 3<fifo1 + +v=original_value +r=0 +ts=$(date +%s%3N) +read -t 0 v <&3 || r=$? +te=$(date +%s%3N) +[ "$r" -eq 0 ] +[ $((te-ts)) -lt 250 ] +[ "$v" = "new_value" ] + v=original_value r=0 +ts=$(date +%s%3N) read -t 0 v <&3 || r=$? +te=$(date +%s%3N) kill -TERM "$!" || : -{ [ "$r" -gt 128 ] && [ "$(kill -l "$r")" = ALRM ]; } || exit +[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = ALRM ] +[ $((te-ts)) -lt 250 ] [ -z "$v" ] diff --git a/bin/sh/tests/builtins/read12.0 b/bin/sh/tests/builtins/read12.0 new file mode 100644 index 000000000000..4551555adfed --- /dev/null +++ b/bin/sh/tests/builtins/read12.0 @@ -0,0 +1,32 @@ +# Verify that `read -t 3 v` succeeds immediately if input is available +# and times out after 3 s if not + +set -e + +T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) +trap 'rm -rf "$T"' 0 +cd $T +mkfifo fifo1 +# Open fifo1 for writing +{ echo new_value; sleep 10; } >fifo1 & +# Wait for the child to open fifo1 for writing +exec 3<fifo1 + +v=original_value +r=0 +ts=$(date +%s%3N) +read -t 3 v <&3 || r=$? +te=$(date +%s%3N) +[ "$r" -eq 0 ] +[ $((te-ts)) -lt 250 ] +[ "$v" = "new_value" ] + +v=original_value +r=0 +ts=$(date +%s%3N) +read -t 3 v <&3 || r=$? +te=$(date +%s%3N) +kill -TERM "$!" || : +[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = ALRM ] +[ $((te-ts)) -gt 3000 ] && [ $((te-ts)) -lt 3250 ] +[ -z "$v" ] diff --git a/bin/sh/tests/builtins/wait11.0 b/bin/sh/tests/builtins/wait11.0 new file mode 100644 index 000000000000..d5fab26fb677 --- /dev/null +++ b/bin/sh/tests/builtins/wait11.0 @@ -0,0 +1,6 @@ +sleep 3 | sleep 2 & +sleep 3 & +kill %1 +wait %1 +r=$? +[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = TERM ] diff --git a/bin/sh/tests/execution/Makefile b/bin/sh/tests/execution/Makefile index 53cb97db9393..dde562a082cd 100644 --- a/bin/sh/tests/execution/Makefile +++ b/bin/sh/tests/execution/Makefile @@ -18,6 +18,7 @@ ${PACKAGE}FILES+= bg10.0 bg10.0.stdout ${PACKAGE}FILES+= bg11.0 ${PACKAGE}FILES+= bg12.0 ${PACKAGE}FILES+= bg13.0 +${PACKAGE}FILES+= bg14.0 ${PACKAGE}FILES+= env1.0 ${PACKAGE}FILES+= fork1.0 ${PACKAGE}FILES+= fork2.0 diff --git a/bin/sh/tests/execution/bg14.0 b/bin/sh/tests/execution/bg14.0 new file mode 100644 index 000000000000..e27f77e9b7b3 --- /dev/null +++ b/bin/sh/tests/execution/bg14.0 @@ -0,0 +1,9 @@ +T=`mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXXXX` +trap 'rm -rf "$T"' 0 +cd "$T" || exit 3 +mkfifo fifo1 || exit 3 +set -T +trap "for i in 1 2 3 4; do sleep 1 & done" USR1 +sleep 1 & +{ kill -USR1 "$$"; echo .; } >fifo1 & +(read dummy <fifo1) diff --git a/bin/sh/tests/parser/Makefile b/bin/sh/tests/parser/Makefile index afeb604710e4..c22af5414526 100644 --- a/bin/sh/tests/parser/Makefile +++ b/bin/sh/tests/parser/Makefile @@ -86,6 +86,12 @@ ${PACKAGE}FILES+= only-redir2.0 ${PACKAGE}FILES+= only-redir3.0 ${PACKAGE}FILES+= only-redir4.0 ${PACKAGE}FILES+= pipe-not1.0 +${PACKAGE}FILES+= ps1-expand1.0 +${PACKAGE}FILES+= ps1-expand2.0 +${PACKAGE}FILES+= ps1-expand3.0 +${PACKAGE}FILES+= ps1-expand4.0 +${PACKAGE}FILES+= ps1-expand5.0 +${PACKAGE}FILES+= ps2-expand1.0 ${PACKAGE}FILES+= set-v1.0 set-v1.0.stderr ${PACKAGE}FILES+= var-assign1.0 diff --git a/bin/sh/tests/parser/ps1-expand1.0 b/bin/sh/tests/parser/ps1-expand1.0 new file mode 100644 index 000000000000..351e6437a023 --- /dev/null +++ b/bin/sh/tests/parser/ps1-expand1.0 @@ -0,0 +1,7 @@ +# Test simple variable expansion in PS1 +testvar=abcdef +output=$(testvar=abcdef PS1='$testvar:' ENV=/dev/null ${SH} +m -i </dev/null 2>&1) +case $output in +*abcdef*) exit 0 ;; +*) echo "Expected 'abcdef' in prompt output"; exit 1 ;; +esac diff --git a/bin/sh/tests/parser/ps1-expand2.0 b/bin/sh/tests/parser/ps1-expand2.0 new file mode 100644 index 000000000000..ed31a7c17136 --- /dev/null +++ b/bin/sh/tests/parser/ps1-expand2.0 @@ -0,0 +1,7 @@ +# Test braced variable expansion in PS1 +testvar=xyz123 +output=$(testvar=xyz123 PS1='prefix-${testvar}-suffix:' ENV=/dev/null ${SH} +m -i </dev/null 2>&1) +case $output in +*xyz123*) exit 0 ;; +*) echo "Expected 'xyz123' in prompt output"; exit 1 ;; +esac diff --git a/bin/sh/tests/parser/ps1-expand3.0 b/bin/sh/tests/parser/ps1-expand3.0 new file mode 100644 index 000000000000..0b6270c300ff --- /dev/null +++ b/bin/sh/tests/parser/ps1-expand3.0 @@ -0,0 +1,8 @@ +# Test special parameter $$ (PID) in PS1 +output=$(PS1='pid:$$:' ENV=/dev/null ${SH} +m -i </dev/null 2>&1) +# Check that output contains "pid:" followed by a number (not literal $$) +case $output in +*pid:\$\$:*) echo "PID not expanded, got literal \$\$"; exit 1 ;; +*pid:[0-9]*) exit 0 ;; +*) echo "Expected PID after 'pid:' in output"; exit 1 ;; +esac diff --git a/bin/sh/tests/parser/ps1-expand4.0 b/bin/sh/tests/parser/ps1-expand4.0 new file mode 100644 index 000000000000..623c52707eec --- /dev/null +++ b/bin/sh/tests/parser/ps1-expand4.0 @@ -0,0 +1,8 @@ +# Test special parameter $? (exit status) in PS1 +output=$(PS1='status:$?:' ENV=/dev/null ${SH} +m -i </dev/null 2>&1) +# Should start with exit status 0 +case $output in +*status:\$?:*) echo "Exit status not expanded, got literal \$?"; exit 1 ;; +*status:0:*) exit 0 ;; +*) echo "Expected 'status:0:' in initial prompt"; exit 1 ;; +esac diff --git a/bin/sh/tests/parser/ps1-expand5.0 b/bin/sh/tests/parser/ps1-expand5.0 new file mode 100644 index 000000000000..73fe3ba5a3d5 --- /dev/null +++ b/bin/sh/tests/parser/ps1-expand5.0 @@ -0,0 +1,8 @@ +# Test positional parameter $0 in PS1 +output=$(PS1='shell:$0:' ENV=/dev/null ${SH} +m -i </dev/null 2>&1) +# $0 should contain the shell name/path +case $output in +*shell:\$0:*) echo "Positional parameter not expanded, got literal \$0"; exit 1 ;; +*shell:*sh*:*) exit 0 ;; +*) echo "Expected shell name after 'shell:' in output"; exit 1 ;; +esac diff --git a/bin/sh/tests/parser/ps2-expand1.0 b/bin/sh/tests/parser/ps2-expand1.0 new file mode 100644 index 000000000000..f0a3a77ded1c --- /dev/null +++ b/bin/sh/tests/parser/ps2-expand1.0 @@ -0,0 +1,12 @@ +# Test variable expansion in PS2 (continuation prompt) +testvar=continue +# Send incomplete command (backslash at end) to trigger PS2 +output=$(testvar=continue PS2='$testvar>' ENV=/dev/null ${SH} +m -i <<EOF 2>&1 +echo \\ +done +EOF +) +case $output in +*continue\>*) exit 0 ;; +*) echo "Expected 'continue>' in PS2 output"; exit 1 ;; +esac |
