Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions tests/test-pty.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,24 @@
int passes = 0, fails = 0;

/* A master with no hangup support blocks these reads forever; the alarm turns
* that into a visible failure instead of a wedged run.
* that into a visible failure instead of a wedged run. The message says why the
* run stopped: the log otherwise just ends after the last completed check, with
* nothing to tell a timeout apart from a crash.
*
* On stdout, not stderr: test-matrix.sh's run_elfuse discards stderr, so a
* message sent there would be missing from the one lane most likely to hit a
* timeout unattended. Writing the descriptor directly rather than through
* printf keeps the handler to async-signal-safe calls, and stdout is
* line-buffered (see main), so both guarded reads sit at a line boundary and
* the raw write cannot land inside a partly-built line.
*/
static void hup_on_alarm(int sig)
{
(void) sig;
static const char msg[] =
"\ntest-pty: TIMEOUT waiting on a hung-up master read\n";
ssize_t ignored = write(STDOUT_FILENO, msg, sizeof(msg) - 1);
(void) ignored;
_exit(2);
}

Expand Down Expand Up @@ -123,6 +136,13 @@ static int count_pts_entries(void)

int main(void)
{
/* Line-buffered so the results printed before a wedged read survive the
* alarm's _exit(2). CI reads this through a pipe, where the default block
* buffering would discard every line still in the buffer and leave nothing
* to say which check stopped making progress.
*/
setvbuf(stdout, NULL, _IOLBF, 0);

printf("test-pty: PTY ioctl + /dev/pts/N path support\n");

/* Regression guard for the pty_keepalive_table BSS-zero collision: any
Expand Down Expand Up @@ -804,6 +824,17 @@ int main(void)
EXPECT_TRUE(hr > 0 && (hp.revents & POLLHUP),
"no POLLHUP after the last slave closed");

/* Guard both reads, as the readv case below does. The second
* one is the wedge: on a tree without the support nothing ever
* fails it -- elfuse's keepalive slave holds the host pty open,
* so the host has no reason to -- and the run stalls where it
* should report. The drain above reads the same hung-up master
* with no deadline of its own, so it belongs in the same window
* rather than in one of its own.
*/
signal(SIGALRM, hup_on_alarm);
alarm(10);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alarm stays armed while the drain verdict prints. If the drain read burns most of the window, SIGALRM can fire after that read already returned, and it can land between TEST()'s label (printed without a newline, so still buffered) and the newline EXPECT_TRUE adds. The handler comment claims both guarded reads sit at a line boundary; that holds for the reads, not for the gap between them, and the TIMEOUT line would then name a read that did not block. Clearing at line 850 and re-arming just before the EIO read costs two lines.


char hbuf[16];
ssize_t drained = put == (ssize_t) (sizeof(bye) - 1)
? read(hup_master, hbuf, sizeof(hbuf))
Expand All @@ -814,10 +845,13 @@ int main(void)
"pending slave output was lost");

errno = 0;
ssize_t hret = read(hup_master, hbuf, sizeof(hbuf));
int herr = errno;
alarm(0);

TEST("read reports EIO once drained");
EXPECT_TRUE(
read(hup_master, hbuf, sizeof(hbuf)) < 0 && errno == EIO,
"read did not report the hangup as EIO");
EXPECT_TRUE(hret < 0 && herr == EIO,
"read did not report the hangup as EIO");
}
close(hup_master);
}
Expand Down
Loading