Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F162920348
D51839.id160149.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
D51839.id160149.diff
View Options
diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist
--- a/etc/mtree/BSD.tests.dist
+++ b/etc/mtree/BSD.tests.dist
@@ -1197,6 +1197,8 @@
..
rs
..
+ script
+ ..
sdiff
..
sed
diff --git a/usr.bin/script/Makefile b/usr.bin/script/Makefile
--- a/usr.bin/script/Makefile
+++ b/usr.bin/script/Makefile
@@ -1,5 +1,10 @@
+.include <src.opts.mk>
+
PROG= script
LIBADD= util
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
.include <bsd.prog.mk>
diff --git a/usr.bin/script/script.c b/usr.bin/script/script.c
--- a/usr.bin/script/script.c
+++ b/usr.bin/script/script.c
@@ -47,6 +47,7 @@
#include <libutil.h>
#include <paths.h>
#include <signal.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -62,8 +63,15 @@
uint32_t scr_direction; /* 'i', 'o', etc (also indicates endianness) */
};
+enum elm_type {
+ ET_DATA,
+ ET_EOF,
+ ET_CLOSE,
+};
+
struct buf_elm {
TAILQ_ENTRY(buf_elm) link;
+ enum elm_type etype;
size_t rpos;
size_t len;
char ibuf[];
@@ -74,7 +82,7 @@
static int child;
static const char *fname;
static char *fmfname;
-static int fflg, qflg, ttyflg;
+static int fflg, kflg, qflg, ttyflg;
static int usesleep, rawout, showexit;
static TAILQ_HEAD(, buf_elm) obuf_list = TAILQ_HEAD_INITIALIZER(obuf_list);
static volatile sig_atomic_t doresize;
@@ -97,6 +105,66 @@
static void usage(void) __dead2;
static void resizeit(int);
+/*
+ * Writes the enqueued buf_elm out to the pty. Returns true if the element has
+ * been completely consumed, false if it has not.
+ */
+static bool
+write_input(struct buf_elm *be, struct termios *stt)
+{
+ ssize_t cc;
+
+ switch (be->etype) {
+ case ET_DATA:
+ cc = write(master, be->ibuf + be->rpos, be->len);
+ if (cc == -1) {
+ if (errno == EWOULDBLOCK ||
+ errno == EINTR)
+ break;
+ warn("write master");
+ done(1);
+ } else if (cc == 0)
+ return (false); /* retry later ? */
+
+ if (kflg && tcgetattr(master, stt) >= 0 &&
+ ((stt->c_lflag & ECHO) == 0))
+ (void)fwrite(be->ibuf + be->rpos, 1, cc, fscript);
+
+ be->len -= cc;
+ if (be->len != 0) {
+ be->rpos += cc;
+ return (false);
+ }
+
+ break;
+ case ET_EOF:
+ (void)write(master, &stt->c_cc[VEOF], 1);
+ break;
+ case ET_CLOSE:
+ (void)close(master);
+ master = -1;
+ break;
+ }
+
+ return (true);
+}
+
+static void
+enqueue_input(enum elm_type etype, const char *buf, size_t bufsz)
+{
+ struct buf_elm *be;
+
+ assert(etype != ET_DATA || buf != NULL);
+ be = malloc(sizeof(*be) + bufsz);
+ if (be == NULL)
+ err(1, "malloc");
+ be->etype = etype;
+ be->rpos = 0;
+ be->len = bufsz;
+ memcpy(be->ibuf, buf, bufsz);
+ TAILQ_INSERT_TAIL(&obuf_list, be, link);
+}
+
int
main(int argc, char *argv[])
{
@@ -110,7 +178,7 @@
fd_set rfd, wfd;
struct buf_elm *be;
ssize_t cc;
- int aflg, Fflg, kflg, pflg, wflg, ch, k, n, fcm;
+ int aflg, Fflg, pflg, wflg, ch, k, n, fcm;
int flushtime, readstdin;
int fm_fd, fm_log;
@@ -295,11 +363,16 @@
start = tvec = time(0);
readstdin = 1;
- for (;;) {
+
+ /*
+ * Keep looping until we end up closing the pty or we have run out of
+ * obuf_list and cannot possibly read any more.
+ */
+ while (master >= 0 && (!TAILQ_EMPTY(&obuf_list) || readstdin >= 0)) {
FD_ZERO(&rfd);
FD_ZERO(&wfd);
FD_SET(master, &rfd);
- if (readstdin)
+ if (readstdin > 0)
FD_SET(STDIN_FILENO, &rfd);
if (!TAILQ_EMPTY(&obuf_list))
FD_SET(master, &wfd);
@@ -327,51 +400,42 @@
if (n > 0 && FD_ISSET(STDIN_FILENO, &rfd)) {
cc = read(STDIN_FILENO, ibuf, BUFSIZ);
- if (cc < 0)
- break;
+ if (cc < 0) {
+ /*
+ * readstdin == 0 signals that we have nothing
+ * to read, but we may try to read stdin after
+ * some timeout if it's a tty -- negative values
+ * of readstdin will avoid it.
+ */
+ readstdin = -1;
+ enqueue_input(ET_CLOSE, NULL, 0);
+ }
if (cc == 0) {
if (tcgetattr(master, &stt) == 0 &&
(stt.c_lflag & ICANON) != 0) {
- (void)write(master, &stt.c_cc[VEOF], 1);
+ enqueue_input(ET_EOF, NULL, 0);
}
readstdin = 0;
}
if (cc > 0) {
if (rawout)
record(fscript, ibuf, cc, 'i');
- be = malloc(sizeof(*be) + cc);
- be->rpos = 0;
- be->len = cc;
- memcpy(be->ibuf, ibuf, cc);
- TAILQ_INSERT_TAIL(&obuf_list, be, link);
+ enqueue_input(ET_DATA, ibuf, cc);
}
+
+ n--;
}
if (n > 0 && FD_ISSET(master, &wfd)) {
while ((be = TAILQ_FIRST(&obuf_list)) != NULL) {
- cc = write(master, be->ibuf + be->rpos,
- be->len);
- if (cc == -1) {
- if (errno == EWOULDBLOCK ||
- errno == EINTR)
- break;
- warn("write master");
- done(1);
- }
- if (cc == 0)
- break; /* retry later ? */
- if (kflg && tcgetattr(master, &stt) >= 0 &&
- ((stt.c_lflag & ECHO) == 0)) {
- (void)fwrite(be->ibuf + be->rpos,
- 1, cc, fscript);
- }
- be->len -= cc;
- if (be->len == 0) {
- TAILQ_REMOVE(&obuf_list, be, link);
- free(be);
- } else {
- be->rpos += cc;
- }
+ if (!write_input(be, &stt))
+ break;
+
+ /* Item is consumed. */
+ TAILQ_REMOVE(&obuf_list, be, link);
+ free(be);
}
+
+ n--;
}
if (n > 0 && FD_ISSET(master, &rfd)) {
cc = read(master, obuf, sizeof(obuf));
@@ -382,6 +446,8 @@
record(fscript, obuf, cc, 'o');
else
(void)fwrite(obuf, 1, cc, fscript);
+
+ n--;
}
tvec = time(0);
if (tvec - start >= flushtime) {
@@ -391,6 +457,10 @@
if (Fflg)
fflush(fscript);
}
+
+ if (master < 0)
+ assert(TAILQ_EMPTY(&obuf_list));
+
finish();
done(0);
}
@@ -470,7 +540,8 @@
}
}
(void)fclose(fscript);
- (void)close(master);
+ if (master >= 0)
+ (void)close(master);
exit(eno);
}
diff --git a/usr.bin/script/tests/Makefile b/usr.bin/script/tests/Makefile
new file mode 100644
--- /dev/null
+++ b/usr.bin/script/tests/Makefile
@@ -0,0 +1,6 @@
+PACKAGE= tests
+
+ATF_TESTS_SH+= script_test
+${PACKAGE}FILES+= echo_loop.sh
+
+.include <bsd.test.mk>
diff --git a/usr.bin/script/tests/echo_loop.sh b/usr.bin/script/tests/echo_loop.sh
new file mode 100755
--- /dev/null
+++ b/usr.bin/script/tests/echo_loop.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+#
+# Copyright (C) 2025 Kyle Evans <kevans@FreeBSD.org>
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+# porch(1) will race script(1) putting the tty into raw mode and flushing it at
+# the same time, and we lose our input almost every time. Providing a sentinel
+# that naturally occurs *after* the tty is configured to avoid that is the main
+# point of this script.
+echo "Proceed"
+while read line; do
+ echo "$line"
+done
+echo "Finished"
diff --git a/usr.bin/script/tests/script_test.sh b/usr.bin/script/tests/script_test.sh
new file mode 100644
--- /dev/null
+++ b/usr.bin/script/tests/script_test.sh
@@ -0,0 +1,119 @@
+#
+# Copyright (c) 2025 Kyle Evans <kevans@FreeBSD.org>
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+echo_loop="$(atf_get_srcdir)/echo_loop.sh"
+
+atf_test_case interactive_basic
+interactive_basic_head()
+{
+ atf_set "descr" "Test basic interactive use"
+ atf_set "require.progs" "porch"
+}
+interactive_basic_body()
+{
+ cat <<EOF > basic.porch
+match "Proceed"
+write "sample\r"
+match "sample"
+write "^D"
+eof()
+EOF
+
+ atf_check porch -f basic.porch script echo.script sh "$echo_loop"
+ atf_check grep -q "Script started" echo.script
+ atf_check grep -q "Command:" echo.script
+ atf_check grep -q "sample" echo.script
+ atf_check grep -q "exit status" echo.script
+ atf_check grep -q "Script done" echo.script
+ atf_check -o match:"1" -x 'grep -o "sample" echo.script | uniq -c'
+
+ # Check -k for input recording while we're here.
+ atf_check porch -f basic.porch script -k echo.script sh "$echo_loop"
+ atf_check -o match:"2" -x 'grep -o "sample" echo.script | uniq -c'
+}
+
+atf_test_case noninteractive_cmd
+noninteractive_cmd_head()
+{
+ atf_set "descr" "Test non-interactive usage of the command form"
+}
+noninteractive_cmd_body()
+{
+ echo "sample" > input
+ atf_check -o not-empty script cat.script cat < input
+
+ atf_check grep -q "Script started" cat.script
+ atf_check grep -q "Command:" cat.script
+ atf_check grep -q "sample" cat.script
+ atf_check grep -q "exit status" cat.script
+ atf_check grep -q "Script done" cat.script
+
+ # We should see the sample string twice: once for echo, once from cat.
+ # One will have VEOF following it, the other not (thus grep -o).
+ atf_check -o match:"2" -x 'grep -o "sample" cat.script | uniq -c'
+}
+
+atf_test_case noninteractive_cmd_append
+noninteractive_cmd_append_head()
+{
+ atf_set "descr" "Test append (-a) using the noninteractive command form"
+}
+noninteractive_cmd_append_body()
+{
+ echo "sample" > input
+ atf_check -o not-empty script cat.script cat < input
+ atf_check -o not-empty script -a cat.script cat < input
+
+ atf_check -o match:"4" -x 'grep -o "sample" cat.script | uniq -c'
+}
+
+atf_test_case noninteractive_cmd_error
+noninteractive_cmd_error_head()
+{
+ atf_set "descr" \
+ "Test non-interactive usage of the command form encountering an error"
+}
+noninteractive_cmd_error_body()
+{
+ # Check both that the error is propagated, and that we can observe it
+ # in the script file.
+ atf_check -o not-empty -s exit:9 script _.script /bin/sh -c 'exit 9'
+ atf_check grep -q 'exit status: 9' _.script
+}
+
+atf_test_case quiet
+quiet_head()
+{
+ atf_set "descr" "Test the quiet form of output"
+}
+quiet_body()
+{
+ echo "sample" > input
+
+ # Largely lifted from the noninteractive_cmd test; just the framing is
+ # stripped off.
+ atf_check -o not-empty script -q cat.script cat < input
+
+ atf_check -s not-exit:0 grep -q "Script started" cat.script
+ atf_check -s not-exit:0 grep -q "Command:" cat.script
+ atf_check grep -q "sample" cat.script
+ atf_check -s not-exit:0 grep -q "exit status" cat.script
+ atf_check -s not-exit:0 grep -q "Script done" cat.script
+
+ # We should see the sample string twice: once for echo, once from cat.
+ # One will have VEOF following it, the other not (thus grep -o).
+ atf_check -o match:"2" -x 'grep -o "sample" cat.script | uniq -c'
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case interactive_basic
+ atf_add_test_case noninteractive_cmd
+ atf_add_test_case noninteractive_cmd_append
+ atf_add_test_case noninteractive_cmd_error
+ atf_add_test_case quiet
+}
+
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Jul 19, 7:45 AM (8 h, 14 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
35226591
Default Alt Text
D51839.id160149.diff (10 KB)
Attached To
Mode
D51839: script: close our side of the pty if reading stdin throws an error
Attached
Detach File
Event Timeline
Log In to Comment