Line data Source code
1 : /**
2 : * @file test_watch_sigpipe.c
3 : * @brief Unit tests for FEAT-17: SIGPIPE / EPIPE handling in the watch loop.
4 : *
5 : * Verifies that:
6 : * 1. signal(SIGPIPE, SIG_IGN) prevents the process from being killed when
7 : * writing to a broken pipe — write(2) returns -1 with errno==EPIPE.
8 : * 2. A helper that mimics the watch-loop write path returns a "stop" signal
9 : * (rather than crashing) when the downstream pipe fd is closed.
10 : */
11 :
12 : #include "test_helpers.h"
13 :
14 : #include <errno.h>
15 : #include <signal.h>
16 : #include <stdio.h>
17 : #include <string.h>
18 : #include <unistd.h>
19 :
20 : /* ------------------------------------------------------------------ */
21 : /* Helpers */
22 : /* ------------------------------------------------------------------ */
23 :
24 : /**
25 : * Mimics the per-message write path used in cmd_watch():
26 : * printf() → fflush(stdout), redirected to a FILE* backed by @p fd.
27 : *
28 : * Returns 0 on success, 1 if EPIPE was detected (clean stop requested).
29 : */
30 2 : static int watch_write_line(int fd, const char *line) {
31 2 : FILE *f = fdopen(dup(fd), "w");
32 2 : if (!f) return 1; /* cannot wrap fd */
33 2 : int ret = 0;
34 2 : if (fputs(line, f) == EOF || fflush(f) != 0) {
35 1 : if (errno == EPIPE) ret = 1;
36 : }
37 2 : fclose(f);
38 2 : return ret;
39 : }
40 :
41 : /* ------------------------------------------------------------------ */
42 : /* Tests */
43 : /* ------------------------------------------------------------------ */
44 :
45 : /**
46 : * After signal(SIGPIPE, SIG_IGN), writing to the write-end of a pipe
47 : * whose read-end is already closed must NOT kill the process; instead
48 : * write(2) / fflush must return an error with errno == EPIPE.
49 : */
50 1 : static void test_sigpipe_ignored_write_returns_epipe(void) {
51 : /* Install SIG_IGN — mirrors what cmd_watch() now does. */
52 1 : signal(SIGPIPE, SIG_IGN);
53 :
54 : int fds[2];
55 1 : ASSERT(pipe(fds) == 0, "pipe(): must succeed");
56 :
57 : /* Close the read end — any write to fds[1] will now yield EPIPE. */
58 1 : close(fds[0]);
59 :
60 : /* Write to the broken pipe write end directly. */
61 1 : errno = 0;
62 1 : ssize_t n = write(fds[1], "hello\n", 6);
63 1 : int saved_errno = errno;
64 :
65 1 : close(fds[1]);
66 :
67 1 : ASSERT(n == -1, "write to broken pipe: must return -1");
68 1 : ASSERT(saved_errno == EPIPE, "write to broken pipe: errno must be EPIPE");
69 : }
70 :
71 : /**
72 : * The watch-loop write helper must return 1 (stop) when the downstream
73 : * pipe is closed, and must not crash.
74 : */
75 1 : static void test_watch_write_detects_epipe(void) {
76 1 : signal(SIGPIPE, SIG_IGN);
77 :
78 : int fds[2];
79 1 : ASSERT(pipe(fds) == 0, "pipe(): must succeed");
80 :
81 : /* Close the read end to simulate 'head' exiting. */
82 1 : close(fds[0]);
83 :
84 1 : int stop = watch_write_line(fds[1], "test line\n");
85 :
86 1 : close(fds[1]);
87 :
88 1 : ASSERT(stop == 1, "watch_write_line: must return 1 (stop) on EPIPE");
89 : }
90 :
91 : /**
92 : * When the pipe is intact the helper must return 0 (keep running).
93 : */
94 1 : static void test_watch_write_succeeds_on_open_pipe(void) {
95 1 : signal(SIGPIPE, SIG_IGN);
96 :
97 : int fds[2];
98 1 : ASSERT(pipe(fds) == 0, "pipe(): must succeed");
99 :
100 1 : int stop = watch_write_line(fds[1], "test line\n");
101 :
102 : /* Drain so the kernel buffer does not fill up; result intentionally ignored. */
103 : char buf[64];
104 1 : ssize_t _n = read(fds[0], buf, sizeof(buf));
105 : (void)_n;
106 :
107 1 : close(fds[0]);
108 1 : close(fds[1]);
109 :
110 1 : ASSERT(stop == 0, "watch_write_line: must return 0 on open pipe");
111 : }
112 :
113 : /* ------------------------------------------------------------------ */
114 : /* Suite entry point */
115 : /* ------------------------------------------------------------------ */
116 :
117 1 : void run_watch_sigpipe_tests(void) {
118 1 : RUN_TEST(test_sigpipe_ignored_write_returns_epipe);
119 1 : RUN_TEST(test_watch_write_detects_epipe);
120 1 : RUN_TEST(test_watch_write_succeeds_on_open_pipe);
121 1 : }
|