aboutsummaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rw-r--r--bin/date/date.181
-rw-r--r--bin/date/date.c148
-rwxr-xr-xbin/date/tests/format_string_test.sh2
-rw-r--r--bin/pwait/pwait.16
-rw-r--r--bin/pwait/pwait.c98
-rw-r--r--bin/pwait/tests/pwait_test.sh38
-rw-r--r--bin/rm/rm.c4
-rw-r--r--bin/sh/parser.c126
-rw-r--r--bin/sh/tests/builtins/read11.04
-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
16 files changed, 455 insertions, 108 deletions
diff --git a/bin/date/date.1 b/bin/date/date.1
index b86a660a924d..374a687fcbdc 100644
--- a/bin/date/date.1
+++ b/bin/date/date.1
@@ -29,7 +29,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd September 1, 2025
+.Dd November 10, 2025
.Dt DATE 1
.Os
.Sh NAME
@@ -143,7 +143,8 @@ values are
.Cm minutes ,
.Cm seconds ,
and
-.Cm ns No Pq for nanoseconds .
+.Cm ns
+.Pq for nanoseconds .
The date and time is formatted to the specified precision.
When
.Ar FMT
@@ -172,7 +173,7 @@ Obsolete flag, accepted and ignored for compatibility.
.It Fl R
Use RFC 2822 date and time output format.
This is equivalent to using
-.Dq Li %a, %d %b %Y \&%T %z
+.Ql %a, %d %b %Y \&%T %z
as
.Ar output_fmt
while
@@ -185,7 +186,7 @@ Print the date and time represented by
.Ar seconds ,
where
.Ar seconds
-is the number of seconds since the Epoch
+is the number of seconds since the Unix Epoch
(00:00:00 UTC, January 1, 1970;
see
.Xr time 3 ) ,
@@ -194,9 +195,7 @@ and can be specified in decimal, octal, or hex.
Print the date and time of the last modification of
.Ar filename .
.It Fl u
-Display or set the date in
-.Tn UTC
-(Coordinated Universal) time.
+Display or set the date in UTC (Coordinated Universal) time.
By default
.Nm
displays the time in the time zone described by
@@ -322,20 +321,43 @@ Refer to the examples below for further details.
.Pp
An operand with a leading plus
.Pq Sq +
-sign signals a user-defined format string
+sign specifies a user-defined format string
which specifies the format in which to display the date and time.
The format string may contain any of the conversion specifications
described in the
.Xr strftime 3
-manual page and
-.Ql %N
-for nanoseconds, as well as any arbitrary text.
+manual page, as well as any arbitrary text.
+.Pp
+The following extensions to the regular
+.Xr strftime 3
+syntax are supported:
+.Bl -tag -width "xxxx"
+.It Cm \&% Ns Ar n Ns Cm N
+Replaced by the
+.Ar n Ns
+-digit fractional part of the number of seconds since the Unix Epoch.
+If
+.Ar n
+is omitted or zero, a default value of 9 is used, resulting in a
+number with nanosecond resolution (hence the choice of the letter
+.Sq N
+for this conversion).
+Note that the underlying clock may not necessarily support nanosecond
+resolution.
+.It Cm \&%-N
+As above, but automatically choose the precision based on the reported
+resolution of the underlying clock.
+If the
+.Fl r
+option was specified, the default precision of 9 digits is used.
+.El
+.Pp
A newline
.Pq Ql \en
character is always output after the characters specified by
the format string.
The format string for the default display is
-.Dq +%+ .
+.Dq %+ .
.Pp
If an operand does not have a leading plus sign, it is interpreted as
a value for setting the system's notion of the current date and time.
@@ -449,6 +471,13 @@ The
utility exits 0 on success, 1 if unable to set the date, and 2
if able to set the local date, but unable to set it globally.
.Sh EXAMPLES
+The command
+.Pp
+.Dl "date +%s.%3N"
+.Pp
+will print the time elapsed since the Unix Epoch with millisecond
+precision.
+.Pp
The command:
.Pp
.Dl "date ""+DATE: %Y-%m-%d%nTIME: %H:%M:%S"""
@@ -468,7 +497,7 @@ will display:
.Dl "Sun Jan 4 04:15:24 GMT 1998"
.Pp
where it is currently
-.Li "Mon Aug 4 04:15:24 BST 1997" .
+.Ql "Mon Aug 4 04:15:24 BST 1997" .
.Pp
The command:
.Pp
@@ -493,29 +522,31 @@ will display the last Friday of the month:
.Dl "Fri Aug 29 04:31:11 BST 1997"
.Pp
where it is currently
-.Li "Mon Aug 4 04:31:11 BST 1997" .
+.Ql "Mon Aug 4 04:31:11 BST 1997" .
.Pp
The command:
.Pp
.Dl "date 8506131627"
.Pp
sets the date to
-.Dq Li "June 13, 1985, 4:27 PM" .
+.Ql "June 13, 1985, 4:27 PM" .
.Pp
.Dl "date ""+%Y%m%d%H%M.%S"""
.Pp
may be used on one machine to print out the date
suitable for setting on another.
-.Qq ( Li "+%m%d%H%M%Y.%S"
-for use on
-.Tn Linux . )
+.Po Use
+.Ql "+%m%d%H%M%Y.%S"
+with GNU date on
+Linux .
+.Pc
.Pp
The command:
.Pp
.Dl "date 1432"
.Pp
sets the time to
-.Li "2:32 PM" ,
+.Ql "2:32 PM" ,
without modifying the date.
.Pp
The command
@@ -591,10 +622,10 @@ flag is compatible with
.St -iso8601 .
.Pp
The
-.Ql %N
+.Ql \&%N
conversion specification for nanoseconds is a non-standard extension.
It is compatible with GNU date's
-.Ql %N .
+.Ql \&%N .
.Sh HISTORY
A
.Nm
@@ -615,6 +646,12 @@ flag was added in
.Fx 12.0 .
.Pp
The
-.Ql %N
+.Ql \&%N
conversion specification was added in
.Fx 14.1 .
+Support for the
+.Ql \&% Ns Ar n Ns Cm N
+and
+.Ql \&%-N
+variants was added in
+.Fx 15.1 .
diff --git a/bin/date/date.c b/bin/date/date.c
index 01797084c0d6..9a40ac43d58b 100644
--- a/bin/date/date.c
+++ b/bin/date/date.c
@@ -36,6 +36,7 @@
#include <ctype.h>
#include <err.h>
#include <errno.h>
+#include <inttypes.h>
#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
@@ -55,10 +56,10 @@ static void badformat(void);
static void iso8601_usage(const char *) __dead2;
static void multipleformats(void);
static void printdate(const char *);
-static void printisodate(struct tm *, long);
+static void printisodate(struct tm *, long, long);
static void setthetime(const char *, const char *, int, struct timespec *);
static size_t strftime_ns(char * __restrict, size_t, const char * __restrict,
- const struct tm * __restrict, long);
+ const struct tm * __restrict, long, long);
static void usage(void) __dead2;
static const struct iso8601_fmt {
@@ -78,26 +79,24 @@ static const char *rfc2822_format = "%a, %d %b %Y %T %z";
int
main(int argc, char *argv[])
{
- struct timespec ts;
+ struct timespec ts = { 0, 0 }, tres = { 0, 1 };
int ch, rflag;
bool Iflag, jflag, Rflag;
const char *format;
char buf[1024];
- char *fmt, *outzone = NULL;
- char *tmp;
+ char *end, *fmt, *outzone = NULL;
struct vary *v;
const struct vary *badv;
struct tm *lt;
struct stat sb;
size_t i;
+ intmax_t number;
v = NULL;
fmt = NULL;
(void) setlocale(LC_TIME, "");
rflag = 0;
Iflag = jflag = Rflag = 0;
- ts.tv_sec = 0;
- ts.tv_nsec = 0;
while ((ch = getopt(argc, argv, "f:I::jnRr:uv:z:")) != -1)
switch((char)ch) {
case 'f':
@@ -131,13 +130,15 @@ main(int argc, char *argv[])
break;
case 'r': /* user specified seconds */
rflag = 1;
- ts.tv_sec = strtoq(optarg, &tmp, 0);
- if (*tmp != 0) {
- if (stat(optarg, &sb) == 0) {
- ts.tv_sec = sb.st_mtim.tv_sec;
- ts.tv_nsec = sb.st_mtim.tv_nsec;
- } else
- usage();
+ number = strtoimax(optarg, &end, 0);
+ if (end > optarg && *end == '\0') {
+ ts.tv_sec = number;
+ ts.tv_nsec = 0;
+ } else if (stat(optarg, &sb) == 0) {
+ ts.tv_sec = sb.st_mtim.tv_sec;
+ ts.tv_nsec = sb.st_mtim.tv_nsec;
+ } else {
+ usage();
}
break;
case 'u': /* do everything in UTC */
@@ -155,8 +156,12 @@ main(int argc, char *argv[])
argc -= optind;
argv += optind;
- if (!rflag && clock_gettime(CLOCK_REALTIME, &ts) == -1)
- err(1, "clock_gettime");
+ if (!rflag) {
+ if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
+ err(1, "clock_gettime");
+ if (clock_getres(CLOCK_REALTIME, &tres) == -1)
+ err(1, "clock_getres");
+ }
format = "%+";
@@ -191,14 +196,14 @@ main(int argc, char *argv[])
badv = vary_apply(v, lt);
if (badv) {
fprintf(stderr, "%s: Cannot apply date adjustment\n",
- badv->arg);
+ badv->arg);
vary_destroy(v);
usage();
}
vary_destroy(v);
if (Iflag)
- printisodate(lt, ts.tv_nsec);
+ printisodate(lt, ts.tv_nsec, tres.tv_nsec);
if (format == rfc2822_format)
/*
@@ -208,7 +213,8 @@ main(int argc, char *argv[])
setlocale(LC_TIME, "C");
- (void)strftime_ns(buf, sizeof(buf), format, lt, ts.tv_nsec);
+ (void)strftime_ns(buf, sizeof(buf), format, lt,
+ ts.tv_nsec, tres.tv_nsec);
printdate(buf);
}
@@ -222,7 +228,7 @@ printdate(const char *buf)
}
static void
-printisodate(struct tm *lt, long nsec)
+printisodate(struct tm *lt, long nsec, long res)
{
const struct iso8601_fmt *it;
char fmtbuf[64], buf[64], tzbuf[8];
@@ -231,10 +237,10 @@ printisodate(struct tm *lt, long nsec)
for (it = iso8601_fmts; it <= iso8601_selected; it++)
strlcat(fmtbuf, it->format_string, sizeof(fmtbuf));
- (void)strftime_ns(buf, sizeof(buf), fmtbuf, lt, nsec);
+ (void)strftime_ns(buf, sizeof(buf), fmtbuf, lt, nsec, res);
if (iso8601_selected > iso8601_fmts) {
- (void)strftime_ns(tzbuf, sizeof(tzbuf), "%z", lt, nsec);
+ (void)strftime_ns(tzbuf, sizeof(tzbuf), "%z", lt, nsec, res);
memmove(&tzbuf[4], &tzbuf[3], 3);
tzbuf[3] = ':';
strlcat(buf, tzbuf, sizeof(buf));
@@ -370,16 +376,17 @@ setthetime(const char *fmt, const char *p, int jflag, struct timespec *ts)
*/
static size_t
strftime_ns(char * __restrict s, size_t maxsize, const char * __restrict format,
- const struct tm * __restrict t, long nsec)
+ const struct tm * __restrict t, long nsec, long res)
{
- size_t prefixlen;
size_t ret;
char *newformat;
char *oldformat;
const char *prefix;
const char *suffix;
const char *tok;
- bool seen_percent;
+ long number;
+ int i, len, prefixlen, width, zeroes;
+ bool seen_percent, seen_dash, seen_width;
seen_percent = false;
if ((newformat = strdup(format)) == NULL)
@@ -392,36 +399,85 @@ strftime_ns(char * __restrict s, size_t maxsize, const char * __restrict format,
* If the previous token was a percent sign,
* then there are two percent tokens in a row.
*/
- if (seen_percent)
+ if (seen_percent) {
seen_percent = false;
- else
+ } else {
seen_percent = true;
+ seen_dash = seen_width = false;
+ prefixlen = tok - newformat;
+ width = 0;
+ }
break;
case 'N':
- if (seen_percent) {
- oldformat = newformat;
- prefix = oldformat;
- prefixlen = tok - oldformat - 1;
- suffix = tok + 1;
+ if (!seen_percent)
+ break;
+ oldformat = newformat;
+ prefix = oldformat;
+ suffix = tok + 1;
+ /*
+ * Prepare the number we are about to print. If
+ * the requested width is less than 9, we need to
+ * cut off the least significant digits. If it is
+ * more than 9, we will have to append zeroes.
+ */
+ if (seen_dash) {
/*
- * Construct a new format string from the
- * prefix (i.e., the part of the old format
- * from its beginning to the currently handled
- * "%N" conversion specification), the
- * nanoseconds, and the suffix (i.e., the part
- * of the old format from the next token to the
- * end).
+ * Calculate number of singificant digits
+ * based on res which is the clock's
+ * resolution in nanoseconds.
*/
- if (asprintf(&newformat, "%.*s%.9ld%s",
- (int)prefixlen, prefix, nsec,
- suffix) < 0) {
- err(1, "asprintf");
- }
- free(oldformat);
- tok = newformat + prefixlen + 9;
+ for (width = 9, number = res;
+ width > 0 && number > 0;
+ width--, number /= 10)
+ /* nothing */;
+ }
+ number = nsec;
+ zeroes = 0;
+ if (width == 0) {
+ width = 9;
+ } else if (width > 9) {
+ zeroes = width - 9;
+ width = 9;
+ } else {
+ for (i = 0; i < 9 - width; i++)
+ number /= 10;
}
+ /*
+ * Construct a new format string from the prefix
+ * (i.e., the part of the old format from its
+ * beginning to the currently handled "%N"
+ * conversion specification), the nanoseconds, and
+ * the suffix (i.e., the part of the old format
+ * from the next token to the end).
+ */
+ asprintf(&newformat, "%.*s%.*ld%.*d%n%s", prefixlen,
+ prefix, width, number, zeroes, 0, &len, suffix);
+ if (newformat == NULL)
+ err(1, "asprintf");
+ free(oldformat);
+ tok = newformat + len - 1;
seen_percent = false;
break;
+ case '-':
+ if (seen_percent) {
+ if (seen_dash || seen_width) {
+ seen_percent = false;
+ break;
+ }
+ seen_dash = true;
+ }
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (seen_percent) {
+ if (seen_dash) {
+ seen_percent = false;
+ break;
+ }
+ width = width * 10 + *tok - '0';
+ seen_width = true;
+ }
+ break;
default:
seen_percent = false;
break;
diff --git a/bin/date/tests/format_string_test.sh b/bin/date/tests/format_string_test.sh
index c2fe2111373f..5f199a3b5fd6 100755
--- a/bin/date/tests/format_string_test.sh
+++ b/bin/date/tests/format_string_test.sh
@@ -132,6 +132,8 @@ atf_init_test_cases()
format_string_test M M 04 20
format_string_test m m 02 11
format_string_test N N 000000000 000000000
+ format_string_test 3N 3N 000 000
+ format_string_test 12N 12N 000000000000 000000000000
format_string_test p p AM PM
format_string_test R R 07:04 21:20
format_string_test r r "07:04:03 AM" "09:20:00 PM"
diff --git a/bin/pwait/pwait.1 b/bin/pwait/pwait.1
index 83ac8bcef317..d92b829b1d6a 100644
--- a/bin/pwait/pwait.1
+++ b/bin/pwait/pwait.1
@@ -30,7 +30,7 @@
.\" USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
.\" OF SUCH DAMAGE.
.\"
-.Dd January 21, 2021
+.Dd October 22, 2025
.Dt PWAIT 1
.Os
.Sh NAME
@@ -39,7 +39,7 @@
.Sh SYNOPSIS
.Nm
.Op Fl t Ar duration
-.Op Fl ov
+.Op Fl opv
.Ar pid
\&...
.Sh DESCRIPTION
@@ -51,6 +51,8 @@ The following option is available:
.Bl -tag -width indent
.It Fl o
Exit when any of the given processes has terminated.
+.It Fl p
+On exit, print a list of processes that have not terminated.
.It Fl t Ar duration
If any process is still running after
.Ar duration ,
diff --git a/bin/pwait/pwait.c b/bin/pwait/pwait.c
index 27f4c8e9858d..59bf0eb93ced 100644
--- a/bin/pwait/pwait.c
+++ b/bin/pwait/pwait.c
@@ -33,7 +33,9 @@
#include <sys/types.h>
#include <sys/event.h>
+#include <sys/sysctl.h>
#include <sys/time.h>
+#include <sys/tree.h>
#include <sys/wait.h>
#include <err.h>
@@ -46,10 +48,25 @@
#include <sysexits.h>
#include <unistd.h>
+struct pid {
+ RB_ENTRY(pid) entry;
+ pid_t pid;
+};
+
+static int
+pidcmp(const struct pid *a, const struct pid *b)
+{
+ return (a->pid > b->pid ? 1 : a->pid < b->pid ? -1 : 0);
+}
+
+RB_HEAD(pidtree, pid);
+static struct pidtree pids = RB_INITIALIZER(&pids);
+RB_GENERATE_STATIC(pidtree, pid, entry, pidcmp);
+
static void
usage(void)
{
- fprintf(stderr, "usage: pwait [-t timeout] [-ov] pid ...\n");
+ fprintf(stderr, "usage: pwait [-t timeout] [-opv] pid ...\n");
exit(EX_USAGE);
}
@@ -61,22 +78,28 @@ main(int argc, char *argv[])
{
struct itimerval itv;
struct kevent *e;
+ struct pid k, *p;
char *end, *s;
double timeout;
+ size_t sz;
long pid;
pid_t mypid;
- int i, kq, n, nleft, opt, status;
- bool oflag, tflag, verbose;
+ int i, kq, n, ndone, nleft, opt, pid_max, ret, status;
+ bool oflag, pflag, tflag, verbose;
oflag = false;
+ pflag = false;
tflag = false;
verbose = false;
memset(&itv, 0, sizeof(itv));
- while ((opt = getopt(argc, argv, "ot:v")) != -1) {
+ while ((opt = getopt(argc, argv, "opt:v")) != -1) {
switch (opt) {
case 'o':
- oflag = 1;
+ oflag = true;
+ break;
+ case 'p':
+ pflag = true;
break;
case 't':
tflag = true;
@@ -128,16 +151,17 @@ main(int argc, char *argv[])
usage();
}
- kq = kqueue();
- if (kq == -1) {
+ if ((kq = kqueue()) < 0)
err(EX_OSERR, "kqueue");
- }
- e = malloc((argc + tflag) * sizeof(struct kevent));
- if (e == NULL) {
+ sz = sizeof(pid_max);
+ if (sysctlbyname("kern.pid_max", &pid_max, &sz, NULL, 0) != 0) {
+ pid_max = 99999;
+ }
+ if ((e = malloc((argc + tflag) * sizeof(*e))) == NULL) {
err(EX_OSERR, "malloc");
}
- nleft = 0;
+ ndone = nleft = 0;
mypid = getpid();
for (n = 0; n < argc; n++) {
s = argv[n];
@@ -147,7 +171,7 @@ main(int argc, char *argv[])
}
errno = 0;
pid = strtol(s, &end, 10);
- if (pid < 0 || *end != '\0' || errno != 0) {
+ if (pid < 0 || pid > pid_max || *end != '\0' || errno != 0) {
warnx("%s: bad process id", s);
continue;
}
@@ -155,27 +179,29 @@ main(int argc, char *argv[])
warnx("%s: skipping my own pid", s);
continue;
}
- for (i = 0; i < nleft; i++) {
- if (e[i].ident == (uintptr_t)pid) {
- break;
- }
+ if ((p = malloc(sizeof(*p))) == NULL) {
+ err(EX_OSERR, NULL);
}
- if (i < nleft) {
+ p->pid = pid;
+ if (RB_INSERT(pidtree, &pids, p) != NULL) {
/* Duplicate. */
+ free(p);
continue;
}
EV_SET(e + nleft, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
if (kevent(kq, e + nleft, 1, NULL, 0, NULL) == -1) {
+ if (errno != ESRCH)
+ err(EX_OSERR, "kevent()");
warn("%ld", pid);
- if (oflag) {
- exit(EX_OK);
- }
+ RB_REMOVE(pidtree, &pids, p);
+ free(p);
+ ndone++;
} else {
nleft++;
}
}
- if (nleft > 0 && tflag) {
+ if ((ndone == 0 || !oflag) && nleft > 0 && tflag) {
/*
* Explicitly detect SIGALRM so that an exit status of 124
* can be returned rather than 142.
@@ -190,7 +216,8 @@ main(int argc, char *argv[])
err(EX_OSERR, "setitimer");
}
}
- while (nleft > 0) {
+ ret = EX_OK;
+ while ((ndone == 0 || !oflag) && ret == EX_OK && nleft > 0) {
n = kevent(kq, NULL, 0, e, nleft + tflag, NULL);
if (n == -1) {
err(EX_OSERR, "kevent");
@@ -200,29 +227,34 @@ main(int argc, char *argv[])
if (verbose) {
printf("timeout\n");
}
- exit(124);
+ ret = 124;
}
+ pid = e[i].ident;
if (verbose) {
status = e[i].data;
if (WIFEXITED(status)) {
printf("%ld: exited with status %d.\n",
- (long)e[i].ident,
- WEXITSTATUS(status));
+ pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("%ld: killed by signal %d.\n",
- (long)e[i].ident,
- WTERMSIG(status));
+ pid, WTERMSIG(status));
} else {
- printf("%ld: terminated.\n",
- (long)e[i].ident);
+ printf("%ld: terminated.\n", pid);
}
}
- if (oflag) {
- exit(EX_OK);
+ k.pid = pid;
+ if ((p = RB_FIND(pidtree, &pids, &k)) != NULL) {
+ RB_REMOVE(pidtree, &pids, p);
+ free(p);
+ ndone++;
}
--nleft;
}
}
-
- exit(EX_OK);
+ if (pflag) {
+ RB_FOREACH(p, pidtree, &pids) {
+ printf("%d\n", p->pid);
+ }
+ }
+ exit(ret);
}
diff --git a/bin/pwait/tests/pwait_test.sh b/bin/pwait/tests/pwait_test.sh
index 66bdd6981704..d31ca21cff93 100644
--- a/bin/pwait/tests/pwait_test.sh
+++ b/bin/pwait/tests/pwait_test.sh
@@ -310,6 +310,43 @@ or_flag_cleanup()
wait $p2 $p4 $p6 >/dev/null 2>&1
}
+atf_test_case print
+print_head()
+{
+ atf_set "descr" "Test the -p flag"
+}
+
+print_body()
+{
+ sleep 1 &
+ p1=$!
+
+ sleep 5 &
+ p5=$!
+
+ sleep 10 &
+ p10=$!
+
+ atf_check \
+ -o inline:"$p5\n$p10\n" \
+ -s exit:124 \
+ pwait -t 2 -p $p10 $p5 $p1 $p5 $p10
+
+ atf_check \
+ -e inline:"kill: $p1: No such process\n" \
+ -s exit:1 \
+ kill -0 $p1
+
+ atf_check kill -0 $p5
+ atf_check kill -0 $p10
+}
+
+print_cleanup()
+{
+ kill $p1 $p5 $p10 >/dev/null 2>&1
+ wait $p1 $p5 $p10 >/dev/null 2>&1
+}
+
atf_init_test_cases()
{
atf_add_test_case basic
@@ -318,4 +355,5 @@ atf_init_test_cases()
atf_add_test_case timeout_no_timeout
atf_add_test_case timeout_many
atf_add_test_case or_flag
+ atf_add_test_case print
}
diff --git a/bin/rm/rm.c b/bin/rm/rm.c
index 16bbf7403fd4..2c41d7380cea 100644
--- a/bin/rm/rm.c
+++ b/bin/rm/rm.c
@@ -184,7 +184,7 @@ rm_tree(char **argv)
flags = FTS_PHYSICAL;
if (!needstat)
flags |= FTS_NOSTAT;
- if (Wflag)
+ if (Wflag || fflag)
flags |= FTS_WHITEOUT;
if (xflag)
flags |= FTS_XDEV;
@@ -273,7 +273,7 @@ rm_tree(char **argv)
case FTS_W:
rval = undelete(p->fts_accpath);
- if (rval == 0 && (fflag && errno == ENOENT)) {
+ if (rval == 0 || (fflag && errno == ENOENT)) {
if (vflag)
(void)printf("%s\n",
p->fts_path);
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/tests/builtins/read11.0 b/bin/sh/tests/builtins/read11.0
index c75ed9c92a83..5bae80318b15 100644
--- a/bin/sh/tests/builtins/read11.0
+++ b/bin/sh/tests/builtins/read11.0
@@ -5,8 +5,8 @@ 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
+{ sleep 10; } >fifo1 &
# Wait for the child to open fifo1 for writing
exec 3<fifo1
v=original_value
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