1 /* $OpenBSD: test-run-down.c,v 1.3 2021/10/22 05:03:57 anton Exp $ */
2
3 /*
4 * Copyright (c) 2019 Anton Lindqvist <anton@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <err.h>
20 #include <errno.h>
21 #include <stdlib.h>
22 #include <pthread.h>
23 #include <unistd.h>
24
25 #include "pipe.h"
26
27 struct context {
28 int c_pipe[2];
29 char *c_buf;
30 size_t c_bufsiz;
31 unsigned int c_nsyscalls;
32
33 pthread_cond_t c_cv;
34 pthread_mutex_t c_mtx;
35 };
36
37 static void ctx_setup(struct context *, size_t);
38 static void ctx_teardown(struct context *);
39 static void ctx_lock(struct context *);
40 static void ctx_unlock(struct context *);
41
42 static int test_run_down(size_t);
43
44 static void *write_thread(void *);
45
46 /*
47 * Verify delivery of SIGPIPE while trying to write on a pipe where the read end
48 * is gone.
49 *
50 * The writer thread first of writes BIG_PIPE_SIZE number of bytes and then
51 * tries to perform the same operation again. The ambition is to cause the
52 * writer thread to go to sleep since the pipe capacity is exhausted. The main
53 * thread closes the read end at this point causing the writer thread to wake up
54 * and punt.
55 */
56 int
test_run_down_write_big()57 test_run_down_write_big()
58 {
59
60 return test_run_down(BIG_PIPE_SIZE);
61 }
62
63 /*
64 * Verify delivery of SIGPIPE while trying to write on a pipe where the read end
65 * is gone.
66 *
67 * The writer thread writes a single byte continously while the main thread ends
68 * up closing the read end after at least a single byte has been written. The
69 * ambition is not to make the writer thread go to sleep but rather punt early
70 * while writing since the read end is gone.
71 */
72 int
test_run_down_write_small()73 test_run_down_write_small()
74 {
75
76 return test_run_down(1);
77 }
78
79 static void
ctx_setup(struct context * ctx,size_t bufsiz)80 ctx_setup(struct context *ctx, size_t bufsiz)
81 {
82 int error;
83
84 if (pipe(ctx->c_pipe) == -1)
85 err(1, "pipe");
86
87 ctx->c_buf = malloc(bufsiz);
88 if (ctx->c_buf == NULL)
89 err(1, NULL);
90 ctx->c_bufsiz = bufsiz;
91 ctx->c_nsyscalls = 0;
92
93 error = pthread_cond_init(&ctx->c_cv, NULL);
94 if (error)
95 errc(1, error, "pthread_cond_init");
96
97 error = pthread_mutex_init(&ctx->c_mtx, NULL);
98 if (error)
99 errc(1, error, "pthread_mutex_init");
100 }
101
102 static void
ctx_teardown(struct context * ctx)103 ctx_teardown(struct context *ctx)
104 {
105 int error;
106
107 if (ctx->c_pipe[0] != -1)
108 close(ctx->c_pipe[0]);
109 ctx->c_pipe[0] = -1;
110 if (ctx->c_pipe[1] != -1)
111 close(ctx->c_pipe[1]);
112 ctx->c_pipe[1] = -1;
113
114 free(ctx->c_buf);
115
116 error = pthread_cond_destroy(&ctx->c_cv);
117 if (error)
118 errc(1, error, "pthread_cond_destroy");
119
120 error = pthread_mutex_destroy(&ctx->c_mtx);
121 if (error)
122 errc(1, error, "pthread_mutex_destroy");
123 }
124
125 static void
ctx_lock(struct context * ctx)126 ctx_lock(struct context *ctx)
127 {
128 int error;
129
130 error = pthread_mutex_lock(&ctx->c_mtx);
131 if (error)
132 errc(1, error, "pthread_mutex_lock");
133 }
134
135 static void
ctx_unlock(struct context * ctx)136 ctx_unlock(struct context *ctx)
137 {
138 int error;
139
140 error = pthread_mutex_unlock(&ctx->c_mtx);
141 if (error)
142 errc(1, error, "pthread_mutex_unlock");
143 }
144
145 static int
test_run_down(size_t bufsiz)146 test_run_down(size_t bufsiz)
147 {
148 struct context ctx;
149 pthread_t th;
150 int error;
151
152 ctx_setup(&ctx, bufsiz);
153
154 error = pthread_create(&th, NULL, write_thread, &ctx);
155 if (error)
156 errc(1, error, "pthread_create");
157
158 ctx_lock(&ctx);
159 for (;;) {
160 if (ctx.c_nsyscalls > 0)
161 break;
162
163 error = pthread_cond_wait(&ctx.c_cv, &ctx.c_mtx);
164 if (error)
165 errc(1, error, "pthread_cond_wait");
166 }
167 ctx_unlock(&ctx);
168
169 /* Signal shutdown to the write thread. */
170 close(ctx.c_pipe[0]);
171 ctx.c_pipe[0] = -1;
172
173 error = pthread_join(th, NULL);
174 if (error)
175 errc(1, error, "pthread_join");
176
177 if (!gotsigpipe)
178 errx(1, "no SIGPIPE");
179
180 ctx_teardown(&ctx);
181
182 return 0;
183 }
184
185 static void *
write_thread(void * arg)186 write_thread(void *arg)
187 {
188 struct context *ctx = arg;
189 int error;
190
191 for (;;) {
192 ssize_t n;
193
194 n = write(ctx->c_pipe[1], ctx->c_buf, ctx->c_bufsiz);
195 if (n == -1) {
196 if (errno == EPIPE)
197 break;
198 err(1, "write");
199 }
200
201 ctx_lock(ctx);
202 ctx->c_nsyscalls++;
203 ctx_unlock(ctx);
204
205 error = pthread_cond_signal(&ctx->c_cv);
206 if (error)
207 errc(1, error, "pthread_cond_signal");
208 }
209
210 return NULL;
211 }
212