diff options
Diffstat (limited to 'bin')
| -rw-r--r-- | bin/date/date.1 | 81 | ||||
| -rw-r--r-- | bin/date/date.c | 148 | ||||
| -rwxr-xr-x | bin/date/tests/format_string_test.sh | 2 | ||||
| -rw-r--r-- | bin/pwait/pwait.1 | 6 | ||||
| -rw-r--r-- | bin/pwait/pwait.c | 98 | ||||
| -rw-r--r-- | bin/pwait/tests/pwait_test.sh | 38 | ||||
| -rw-r--r-- | bin/rm/rm.c | 4 | ||||
| -rw-r--r-- | bin/sh/parser.c | 126 | ||||
| -rw-r--r-- | bin/sh/tests/builtins/read11.0 | 4 | ||||
| -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 |
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 |
