Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
PATCH: improve checkptycmd
- X-seq: zsh-workers 54783
- From: Mikael Magnusson <mikachu@xxxxxxxxx>
- To: zsh-workers@xxxxxxx
- Subject: PATCH: improve checkptycmd
- Date: Mon, 15 Jun 2026 18:41:57 +0200
- Archived-at: <https://zsh.org/workers/54783>
- List-id: <zsh-workers.zsh.org>
With the previous (and now fallback) version of checkptycmd(), doing
this:
zpty foo 'while read; do echo hi; done'
zpty (or zpty -t foo)
would hang forever, which seems counterintuitive to the purpose of
listing processes (or testing if a process is running).
---
I'm not sure if it's really worth keeping the fallback version at all,
considering the tests (E01) already fail if none of these functions are
available. If we do keep it, would it be worth adding some zpty -c and
zpty -rc commands, so that you can read the readahead byte, if a
subshell already output it? Eg,
zpty foo 'while read; do echo hi; done'
zpty -w foo hi
zpty -t foo
zpty -rt foo | rev
zpty -rt foo # this outputs h again
In theory, the script writer can keep track of the situations where
they've used zpty -r in a subshell so they can clear the buffer in the
parent shell, but it seems like a lot of effort for what is surely a
theoretical scenario in the year 2026?
Src/Modules/zpty.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++
Test/V08zpty.ztst | 12 +++++++++
2 files changed, 73 insertions(+)
diff --git a/Src/Modules/zpty.c b/Src/Modules/zpty.c
index 6e201e76bb..0ee3abadec 100644
--- a/Src/Modules/zpty.c
+++ b/Src/Modules/zpty.c
@@ -37,6 +37,13 @@
#endif
#endif
+#ifdef HAVE_POLL_H
+# include <poll.h>
+#endif
+#if defined(HAVE_POLL) && !defined(POLLIN)
+# undef HAVE_POLL
+#endif
+
/* The number of bytes we normally read when given no pattern and the
* upper bound on the number of bytes we read (even if we are give a
* pattern). */
@@ -529,6 +536,59 @@ deleteallptycmds(void)
/**** a better process handling would be nice */
+#ifdef HAVE_POLL
+static void
+checkptycmd(Ptycmd cmd)
+{
+ struct pollfd pfd;
+
+ if (cmd->fin)
+ return;
+ pfd.fd = cmd->fd;
+ pfd.events = POLLIN;
+ pfd.revents = 0;
+ if (poll(&pfd, 1, 0) <= 0)
+ return; /* no events: slave still open, not finished */
+ if (pfd.revents & POLLIN)
+ return; /* data available, not finished */
+ /* POLLHUP without POLLIN: slave closed, no data */
+ cmd->fin = 1;
+ zclose(cmd->fd);
+}
+#elif defined(FIONREAD)
+static void
+checkptycmd(Ptycmd cmd)
+{
+ int val = 0, select_ret = 0;
+
+ if (cmd->fin)
+ return;
+#ifdef HAVE_SELECT
+ {
+ fd_set fds;
+ struct timeval tv;
+ int ret;
+
+ FD_ZERO(&fds);
+ FD_SET(cmd->fd, &fds);
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ ret = select(cmd->fd + 1, (SELECT_ARG_2_T) &fds, NULL, NULL, &tv);
+ if (ret > 0 && FD_ISSET(cmd->fd, &fds)) {
+ /* either there are bytes, or the process exited */
+ select_ret = 1;
+ }
+ }
+#endif
+ if (ioctl(cmd->fd, FIONREAD, (char *) &val) == 0 && val > 0)
+ return; /* data available, not finished */
+ /* No data (or ioctl failed): check if process is dead */
+ if (select_ret || kill(cmd->pid, 0) < 0) {
+ cmd->fin = 1;
+ zclose(cmd->fd);
+ }
+}
+#else
static void
checkptycmd(Ptycmd cmd)
{
@@ -545,6 +605,7 @@ checkptycmd(Ptycmd cmd)
}
cmd->read = (int) c;
}
+#endif
static int
ptyread(char *nam, Ptycmd cmd, char **args, int noblock, int mustmatch)
diff --git a/Test/V08zpty.ztst b/Test/V08zpty.ztst
index 057db2e18c..cad6bb9bdc 100644
--- a/Test/V08zpty.ztst
+++ b/Test/V08zpty.ztst
@@ -25,3 +25,15 @@
zpty -d cat
0:zpty with a process that does not set up the terminal: write via stdin
>a line of text
+
+ zpty loop 'while read; do echo hello; done'
+ zpty -w loop hi
+ zpty -rt loop | tr -d $'\r'
+ zpty -w loop hi
+ zpty -t loop
+ zpty -rt loop | tr -d $'\r'
+ zpty -rt loop
+ zpty -d loop
+0:zpty doesn't duplicate data
+>hello
+>hello
--
2.38.1
Messages sorted by:
Reverse Date,
Date,
Thread,
Author