aboutsummaryrefslogtreecommitdiff
path: root/bin/sh
diff options
context:
space:
mode:
Diffstat (limited to 'bin/sh')
-rw-r--r--bin/sh/Makefile7
-rw-r--r--bin/sh/dot.profile2
-rw-r--r--bin/sh/jobs.c9
-rw-r--r--bin/sh/miscbltin.c83
-rw-r--r--bin/sh/parser.c126
-rw-r--r--bin/sh/sh.16
-rw-r--r--bin/sh/tests/builtins/Makefile2
-rw-r--r--bin/sh/tests/builtins/read11.021
-rw-r--r--bin/sh/tests/builtins/read12.032
-rw-r--r--bin/sh/tests/builtins/wait11.06
-rw-r--r--bin/sh/tests/execution/Makefile1
-rw-r--r--bin/sh/tests/execution/bg14.09
-rw-r--r--bin/sh/tests/parser/Makefile6
-rw-r--r--bin/sh/tests/parser/ps1-expand1.07
-rw-r--r--bin/sh/tests/parser/ps1-expand2.07
-rw-r--r--bin/sh/tests/parser/ps1-expand3.08
-rw-r--r--bin/sh/tests/parser/ps1-expand4.08
-rw-r--r--bin/sh/tests/parser/ps1-expand5.08
-rw-r--r--bin/sh/tests/parser/ps2-expand1.012
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