From: Dirk Gouders <dirk@gouders.net>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Jiri Slaby <jslaby@suse.com>, Al Viro <viro@zeniv.linux.org.uk>
Cc: linux-kernel@vger.kernel.org, linux-newbie@vger.kernel.org
Subject: pty: childs don't always react on close(2)
Date: Fri, 05 Dec 2025 22:37:44 +0100 [thread overview]
Message-ID: <gho6oczk2f.fsf@gouders.net> (raw)
[-- Attachment #1: Type: text/plain, Size: 1405 bytes --]
Hi,
chances are high, that I am doing a stupid mistake, because experienced
programmers already would have noticed problems with ptys.
But if you have the time, perhaps you could help me undestand the
following problem:
I am working on a program (a terminal pager [1]) that works with
ptys to allow users to have several manual pages open and reload them
when terminals are resized.
I now noticed, that a close on a pty file descriptor doesn't cause the
child process to exit and a waitpid(2) hangs forever or gets interrupted
(when used without WNOHANG) -- it seems the child doesn't get noticed
about the close(2) on the other end.
Curious is that I notice this only if more than one ptys are in use --
with a single one everything is OK (see attached sample program and test
it with only one pty).
I would be very glad if you could tell me if I am correct to expect that
if I close(2) a pty file descriptor the other end should notice that and
I could assume a waitpid(2) to succeed.
And perhaps, you could tell me which mistake I am doing.
I prepared a small test-case that reproduces the problem, at least here.
If that program is modified to work with just one child, everything
works as expected, when the second child is used, the waitpid() hangs...
Apologies, if this is not the right place and if I should have asked
somewhere else.
Regards,
Dirk
[1] https://github.com/dgouders/lsp
[-- Attachment #2: PTY test program --]
[-- Type: text/plain, Size: 2030 bytes --]
/*
* Test closing file descriptors opened via forkpty() when not all data has been
* read. A following waitpid() blocks, when we opened two childs and try to
* close the file descriptor and then waitpid() for that child...
*/
#include <stdlib.h>
#include <stdio.h>
#include <pty.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdint.h>
#define READ_SIZE 4096
/*
* Set PAGER variables and start a man(1) process.
*/
void do_child(void);
void do_child()
{
char *e_argv[3] = {"man", "groff_ms", NULL};
putenv("PAGER=cat");
putenv("MANPAGER=cat");
execvp("man", e_argv);
}
int main()
{
unsigned char read_buffer[READ_SIZE];
int pty_fd;
int pty_fd1;
int wstatus;
pid_t child_pid;
pid_t child_pid1;
pid_t ret_pid;
ssize_t ret;
/*
* Start a child to send us a manual page.
*/
child_pid = forkpty(&pty_fd, NULL, NULL, NULL);
if (child_pid == -1) {
fprintf(stderr, "forkpty(): %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
if (child_pid == 0)
do_child();
printf("child_pid = %jd\n", (intmax_t) child_pid);
memset(read_buffer, '\0', READ_SIZE);
ret = read(pty_fd, read_buffer, READ_SIZE);
if (ret == -1) {
fprintf(stderr, "read(): %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
printf("%s\n", read_buffer);
printf("%ld bytes read.\n", ret);
/*
* Start another child to send us a manual page.
*/
child_pid1 = forkpty(&pty_fd1, NULL, NULL, NULL);
if (child_pid1 == -1) {
fprintf(stderr, "forkpty(): %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
if (child_pid1 == 0)
do_child();
printf("child_pid1 = %jd\n", (intmax_t) child_pid1);
memset(read_buffer, '\0', READ_SIZE);
ret = read(pty_fd1, read_buffer, READ_SIZE);
if (ret == -1) {
fprintf(stderr, "read(): %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
printf("%s\n", read_buffer);
printf("%ld bytes read.\n", ret);
close(pty_fd);
ret_pid = waitpid(child_pid, &wstatus, 0);
printf("ret_pid = %jd\n", (intmax_t) ret_pid);
exit(EXIT_SUCCESS);
}
next reply other threads:[~2025-12-05 21:38 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-12-05 21:37 Dirk Gouders [this message]
2025-12-05 21:48 ` pty: childs don't always react on close(2) Al Viro
2025-12-05 22:20 ` Dirk Gouders
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=gho6oczk2f.fsf@gouders.net \
--to=dirk@gouders.net \
--cc=gregkh@linuxfoundation.org \
--cc=jslaby@suse.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-newbie@vger.kernel.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox