xref: /illumos-gate/usr/src/test/libc-tests/tests/aio_cancel.c (revision 438283cf397cce47d80cc67b04bbfdfe73b0d142)
1*438283cfSBen Stoltz /*
2*438283cfSBen Stoltz  * This file and its contents are supplied under the terms of the
3*438283cfSBen Stoltz  * Common Development and Distribution License ("CDDL"), version 1.0.
4*438283cfSBen Stoltz  * You may only use this file in accordance with the terms of version
5*438283cfSBen Stoltz  * 1.0 of the CDDL.
6*438283cfSBen Stoltz  *
7*438283cfSBen Stoltz  * A full copy of the text of the CDDL should have accompanied this
8*438283cfSBen Stoltz  * source.  A copy of the CDDL is also available via the Internet at
9*438283cfSBen Stoltz  * http://www.illumos.org/license/CDDL.
10*438283cfSBen Stoltz  */
11*438283cfSBen Stoltz 
12*438283cfSBen Stoltz /*
13*438283cfSBen Stoltz  * Copyright 2026 Oxide Computer Company
14*438283cfSBen Stoltz  */
15*438283cfSBen Stoltz 
16*438283cfSBen Stoltz /*
17*438283cfSBen Stoltz  * Regression test for illumos#18362. A libc AIO worker handled only one
18*438283cfSBen Stoltz  * SIGAIOCANCEL over its lifetime. A later cancellation left the worker
19*438283cfSBen Stoltz  * blocked in read(2). The request did not complete and no notification
20*438283cfSBen Stoltz  * was sent. A process could only cancel one request per worker thread.
21*438283cfSBen Stoltz  *
22*438283cfSBen Stoltz  * The test sets the number of AIO worker threads and drives them through
23*438283cfSBen Stoltz  * their states. Counting threads blocked in read(2) confirms the state.
24*438283cfSBen Stoltz  *
25*438283cfSBen Stoltz  * Set the aio worker pool size via environment variable.
26*438283cfSBen Stoltz  * The test loops more times than there are worker threads.
27*438283cfSBen Stoltz  *   On each iteration:
28*438283cfSBen Stoltz  *   1) Get the count of threads blocked in read(2)
29*438283cfSBen Stoltz  *   2) Post the read to a pipe. No data is written, so the read blocks.
30*438283cfSBen Stoltz  *   3) Loop with timeout until one more thread is blocked in read(2)
31*438283cfSBen Stoltz  *   4) Call aio_cancel(3C)
32*438283cfSBen Stoltz  *
33*438283cfSBen Stoltz  * aio_error() does not report the failure. _aio_cancel_req() sets ECANCELED
34*438283cfSBen Stoltz  * before it sends the signal, so polling reports the request cancelled while
35*438283cfSBen Stoltz  * the worker is still in read(2). The test checks aio_error() for conformance
36*438283cfSBen Stoltz  * only.
37*438283cfSBen Stoltz  */
38*438283cfSBen Stoltz 
39*438283cfSBen Stoltz #include <sys/types.h>
40*438283cfSBen Stoltz #include <sys/lwp.h>
41*438283cfSBen Stoltz #include <sys/syscall.h>
42*438283cfSBen Stoltz #include <sys/time.h>
43*438283cfSBen Stoltz #include <aio.h>
44*438283cfSBen Stoltz #include <err.h>
45*438283cfSBen Stoltz #include <errno.h>
46*438283cfSBen Stoltz #include <libproc.h>
47*438283cfSBen Stoltz #include <limits.h>
48*438283cfSBen Stoltz #include <procfs.h>
49*438283cfSBen Stoltz #include <pthread.h>
50*438283cfSBen Stoltz #include <stdio.h>
51*438283cfSBen Stoltz #include <stdlib.h>
52*438283cfSBen Stoltz #include <string.h>
53*438283cfSBen Stoltz #include <time.h>
54*438283cfSBen Stoltz #include <unistd.h>
55*438283cfSBen Stoltz 
56*438283cfSBen Stoltz /*
57*438283cfSBen Stoltz  * The default worker pool size in aio.c is 4. Rather than duplicate that
58*438283cfSBen Stoltz  * private knowledge, this test sets the pool size to 2, a size sufficient
59*438283cfSBen Stoltz  * to demonstrate all of the interesting states before and after the fix.
60*438283cfSBen Stoltz  */
61*438283cfSBen Stoltz #define	AIO_WORKERS	2
62*438283cfSBen Stoltz 
63*438283cfSBen Stoltz #define	AIO_STR_(x)	#x
64*438283cfSBen Stoltz #define	AIO_STR(x)	AIO_STR_(x)
65*438283cfSBen Stoltz 
66*438283cfSBen Stoltz /*
67*438283cfSBen Stoltz  * Maximum wait for a worker to accept a request, which takes well under a
68*438283cfSBen Stoltz  * millisecond. With the defect present, a request queues behind a worker
69*438283cfSBen Stoltz  * blocked in read(2) instead of entering read(2) itself.
70*438283cfSBen Stoltz  */
71*438283cfSBen Stoltz #define	AIO_SETTLE_MS	1000
72*438283cfSBen Stoltz 
73*438283cfSBen Stoltz /*
74*438283cfSBen Stoltz  * Maximum wait for a notification, which arrives in about a millisecond. A
75*438283cfSBen Stoltz  * failing run waits this long for each cancellation that goes unreported.
76*438283cfSBen Stoltz  */
77*438283cfSBen Stoltz #define	AIO_DEADLINE_MS	5000
78*438283cfSBen Stoltz static const struct timespec aio_deadline = {
79*438283cfSBen Stoltz 	AIO_DEADLINE_MS / MILLISEC, 0
80*438283cfSBen Stoltz };
81*438283cfSBen Stoltz 
82*438283cfSBen Stoltz /*
83*438283cfSBen Stoltz  * The test polls for a worker blocked in read(2) every AIO_POLL_MS before
84*438283cfSBen Stoltz  * cancelling the request. AIO_SETTLE_TRIES polls sum to AIO_SETTLE_MS of
85*438283cfSBen Stoltz  * sleeping, so the wait is at least that long.
86*438283cfSBen Stoltz  */
87*438283cfSBen Stoltz #define	AIO_POLL_MS	100
88*438283cfSBen Stoltz #define	AIO_SETTLE_TRIES	(AIO_SETTLE_MS / AIO_POLL_MS)
89*438283cfSBen Stoltz static const struct timespec aio_poll = {
90*438283cfSBen Stoltz 	0, MSEC2NSEC(AIO_POLL_MS)
91*438283cfSBen Stoltz };
92*438283cfSBen Stoltz 
93*438283cfSBen Stoltz /* A pool this size makes a failing run take minutes. */
94*438283cfSBen Stoltz #define	AIO_WORKERS_WARN	10
95*438283cfSBen Stoltz 
96*438283cfSBen Stoltz #define	AIO_BUFSZ	64
97*438283cfSBen Stoltz 
98*438283cfSBen Stoltz static pthread_mutex_t	aio_lock = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
99*438283cfSBen Stoltz static pthread_cond_t	aio_cv = PTHREAD_COND_INITIALIZER;
100*438283cfSBen Stoltz static boolean_t	*aio_notified;
101*438283cfSBen Stoltz 
102*438283cfSBen Stoltz static void
103*438283cfSBen Stoltz aio_notify(union sigval sv)
104*438283cfSBen Stoltz {
105*438283cfSBen Stoltz 	pthread_mutex_enter_np(&aio_lock);
106*438283cfSBen Stoltz 	aio_notified[sv.sival_int] = B_TRUE;
107*438283cfSBen Stoltz 	(void) pthread_cond_broadcast(&aio_cv);
108*438283cfSBen Stoltz 	pthread_mutex_exit_np(&aio_lock);
109*438283cfSBen Stoltz }
110*438283cfSBen Stoltz 
111*438283cfSBen Stoltz /*
112*438283cfSBen Stoltz  * Wait for the notification of request i, up to AIO_DEADLINE_MS.
113*438283cfSBen Stoltz  *
114*438283cfSBen Stoltz  * One wait is enough. The cancellation we just issued is the only thing that
115*438283cfSBen Stoltz  * can complete this request, so either its notification arrives or nothing
116*438283cfSBen Stoltz  * ever will.
117*438283cfSBen Stoltz  */
118*438283cfSBen Stoltz static boolean_t
119*438283cfSBen Stoltz aio_await(uint_t i)
120*438283cfSBen Stoltz {
121*438283cfSBen Stoltz 	boolean_t notified;
122*438283cfSBen Stoltz 
123*438283cfSBen Stoltz 	pthread_mutex_enter_np(&aio_lock);
124*438283cfSBen Stoltz 	if (!aio_notified[i]) {
125*438283cfSBen Stoltz 		(void) pthread_cond_relclockwait_np(&aio_cv, &aio_lock,
126*438283cfSBen Stoltz 		    CLOCK_MONOTONIC, &aio_deadline);
127*438283cfSBen Stoltz 	}
128*438283cfSBen Stoltz 	notified = aio_notified[i];
129*438283cfSBen Stoltz 	pthread_mutex_exit_np(&aio_lock);
130*438283cfSBen Stoltz 
131*438283cfSBen Stoltz 	return (notified);
132*438283cfSBen Stoltz }
133*438283cfSBen Stoltz 
134*438283cfSBen Stoltz typedef struct {
135*438283cfSBen Stoltz 	int	air_fd;		/* the descriptor our requests read */
136*438283cfSBen Stoltz 	uint_t	air_readers;	/* threads blocked reading it */
137*438283cfSBen Stoltz } aio_readers_t;
138*438283cfSBen Stoltz 
139*438283cfSBen Stoltz /*
140*438283cfSBen Stoltz  * Count the LWPs blocked in read(2) on our pipe.
141*438283cfSBen Stoltz  *
142*438283cfSBen Stoltz  * Each such thread is an AIO worker whose request has neither completed nor
143*438283cfSBen Stoltz  * been cancelled.
144*438283cfSBen Stoltz  * The AIO workers use pread(2) first and then fall back to read(2) on
145*438283cfSBen Stoltz  * descriptors that are not seekable, which includes this test's pipe.
146*438283cfSBen Stoltz  *
147*438283cfSBen Stoltz  * Matching the descriptor keeps the count to threads doing this test's work.
148*438283cfSBen Stoltz  * libc does not mark its workers, so an unrelated thread blocked on some
149*438283cfSBen Stoltz  * other descriptor would otherwise be indistinguishable from one of them.
150*438283cfSBen Stoltz  */
151*438283cfSBen Stoltz static int
152*438283cfSBen Stoltz aio_count_reader(void *cd, const lwpstatus_t *lsp)
153*438283cfSBen Stoltz {
154*438283cfSBen Stoltz 	aio_readers_t *air = cd;
155*438283cfSBen Stoltz 
156*438283cfSBen Stoltz 	if (lsp->pr_lwpid == (id_t)_lwp_self() || lsp->pr_syscall != SYS_read)
157*438283cfSBen Stoltz 		return (0);
158*438283cfSBen Stoltz 
159*438283cfSBen Stoltz 	if (lsp->pr_nsysarg < 1) {
160*438283cfSBen Stoltz 		errx(EXIT_FAILURE, "TEST FAILED: read(2) on lwp %ld reports "
161*438283cfSBen Stoltz 		    "%d arguments", (long)lsp->pr_lwpid, lsp->pr_nsysarg);
162*438283cfSBen Stoltz 	}
163*438283cfSBen Stoltz 
164*438283cfSBen Stoltz 	if (lsp->pr_sysarg[0] == air->air_fd)
165*438283cfSBen Stoltz 		air->air_readers++;
166*438283cfSBen Stoltz 
167*438283cfSBen Stoltz 	return (0);
168*438283cfSBen Stoltz }
169*438283cfSBen Stoltz 
170*438283cfSBen Stoltz static uint_t
171*438283cfSBen Stoltz aio_readers(struct ps_prochandle *P, int fd)
172*438283cfSBen Stoltz {
173*438283cfSBen Stoltz 	aio_readers_t air = { .air_fd = fd, .air_readers = 0 };
174*438283cfSBen Stoltz 
175*438283cfSBen Stoltz 	if (Plwp_iter(P, aio_count_reader, &air) != 0) {
176*438283cfSBen Stoltz 		errx(EXIT_FAILURE, "TEST FAILED: could not iterate our own "
177*438283cfSBen Stoltz 		    "threads");
178*438283cfSBen Stoltz 	}
179*438283cfSBen Stoltz 
180*438283cfSBen Stoltz 	return (air.air_readers);
181*438283cfSBen Stoltz }
182*438283cfSBen Stoltz 
183*438283cfSBen Stoltz /*
184*438283cfSBen Stoltz  * Iterations to run against the worker pool in force.
185*438283cfSBen Stoltz  *
186*438283cfSBen Stoltz  * A caller may set _AIO_MIN_WORKERS to test other pool sizes. The count
187*438283cfSBen Stoltz  * follows the environment rather than AIO_WORKERS. More requests than
188*438283cfSBen Stoltz  * workers is what forces a worker to take a second cancellation.
189*438283cfSBen Stoltz  *
190*438283cfSBen Stoltz  * Inspection of aio.c and testing show that the known failure modes are
191*438283cfSBen Stoltz  * fully exercised at two iterations per worker thread plus one.
192*438283cfSBen Stoltz  *
193*438283cfSBen Stoltz  * A value libc would reject leaves the pool size unknown to us, since libc
194*438283cfSBen Stoltz  * substitutes its own default rather than failing. The test stops instead of
195*438283cfSBen Stoltz  * copying that default.
196*438283cfSBen Stoltz  */
197*438283cfSBen Stoltz static uint_t
198*438283cfSBen Stoltz aio_iterations(void)
199*438283cfSBen Stoltz {
200*438283cfSBen Stoltz 	const char	*workers = getenv("_AIO_MIN_WORKERS");
201*438283cfSBen Stoltz 	const char	*errstr;
202*438283cfSBen Stoltz 	long long	val;
203*438283cfSBen Stoltz 
204*438283cfSBen Stoltz 	if (workers == NULL)
205*438283cfSBen Stoltz 		errx(EXIT_FAILURE, "TEST FAILED: _AIO_MIN_WORKERS is unset");
206*438283cfSBen Stoltz 
207*438283cfSBen Stoltz 	val = strtonum(workers, 1, INT_MAX, &errstr);
208*438283cfSBen Stoltz 	if (errstr != NULL) {
209*438283cfSBen Stoltz 		errx(EXIT_FAILURE, "TEST FAILED: _AIO_MIN_WORKERS is %s, "
210*438283cfSBen Stoltz 		    "which is %s; the worker pool size is not known", workers,
211*438283cfSBen Stoltz 		    errstr);
212*438283cfSBen Stoltz 	}
213*438283cfSBen Stoltz 
214*438283cfSBen Stoltz 	if (val > AIO_WORKERS_WARN)
215*438283cfSBen Stoltz 		warnx("_AIO_MIN_WORKERS is %lld, so this run may take minutes",
216*438283cfSBen Stoltz 		    val);
217*438283cfSBen Stoltz 
218*438283cfSBen Stoltz 	return ((2 * (uint_t)val) + 1);
219*438283cfSBen Stoltz }
220*438283cfSBen Stoltz 
221*438283cfSBen Stoltz int
222*438283cfSBen Stoltz main(int argc, char **argv)
223*438283cfSBen Stoltz {
224*438283cfSBen Stoltz 	struct ps_prochandle	*P;
225*438283cfSBen Stoltz 	int	fds[2];
226*438283cfSBen Stoltz 	int	perr;
227*438283cfSBen Stoltz 	uint_t	stuck = 0;
228*438283cfSBen Stoltz 	uint_t	iterations;
229*438283cfSBen Stoltz 	char	buf[AIO_BUFSZ];
230*438283cfSBen Stoltz 
231*438283cfSBen Stoltz 	/*
232*438283cfSBen Stoltz 	 * Ensure that the number of AIO workers is known, so that our model of
233*438283cfSBen Stoltz 	 * the AIO behavior is correct.
234*438283cfSBen Stoltz 	 *
235*438283cfSBen Stoltz 	 * libc reads _AIO_MIN_WORKERS in its init section, so the pool size
236*438283cfSBen Stoltz 	 * must be set before this process starts. If the caller did not
237*438283cfSBen Stoltz 	 * choose one, set our own and exec ourselves again.
238*438283cfSBen Stoltz 	 */
239*438283cfSBen Stoltz 	if (getenv("_AIO_MIN_WORKERS") == NULL) {
240*438283cfSBen Stoltz 		if (setenv("_AIO_MIN_WORKERS", AIO_STR(AIO_WORKERS), 1) != 0)
241*438283cfSBen Stoltz 			err(EXIT_FAILURE, "TEST FAILED: setenv");
242*438283cfSBen Stoltz 		(void) execvp(argv[0], argv);
243*438283cfSBen Stoltz 		err(EXIT_FAILURE, "TEST FAILED: could not re-exec %s",
244*438283cfSBen Stoltz 		    argv[0]);
245*438283cfSBen Stoltz 	}
246*438283cfSBen Stoltz 
247*438283cfSBen Stoltz 	iterations = aio_iterations();
248*438283cfSBen Stoltz 
249*438283cfSBen Stoltz 	aio_notified = calloc(iterations, sizeof (*aio_notified));
250*438283cfSBen Stoltz 	if (aio_notified == NULL)
251*438283cfSBen Stoltz 		err(EXIT_FAILURE, "TEST FAILED: calloc");
252*438283cfSBen Stoltz 
253*438283cfSBen Stoltz 	/*
254*438283cfSBen Stoltz 	 * A read-only grab is the only kind permitted on the calling process,
255*438283cfSBen Stoltz 	 * and it is all Plwp_iter() needs.
256*438283cfSBen Stoltz 	 */
257*438283cfSBen Stoltz 	P = Pgrab(getpid(), PGRAB_RDONLY, &perr);
258*438283cfSBen Stoltz 	if (P == NULL) {
259*438283cfSBen Stoltz 		errx(EXIT_FAILURE, "TEST FAILED: could not grab ourselves: %s",
260*438283cfSBen Stoltz 		    Pgrab_error(perr));
261*438283cfSBen Stoltz 	}
262*438283cfSBen Stoltz 
263*438283cfSBen Stoltz 	if (pipe(fds) != 0)
264*438283cfSBen Stoltz 		err(EXIT_FAILURE, "TEST FAILED: could not create a pipe");
265*438283cfSBen Stoltz 
266*438283cfSBen Stoltz 	if (aio_readers(P, fds[0]) != 0) {
267*438283cfSBen Stoltz 		errx(EXIT_FAILURE, "TEST FAILED: a thread is reading the pipe "
268*438283cfSBen Stoltz 		    "before the first request");
269*438283cfSBen Stoltz 	}
270*438283cfSBen Stoltz 
271*438283cfSBen Stoltz 	for (uint_t i = 0; i < iterations; i++) {
272*438283cfSBen Stoltz 		struct aiocb	cb;
273*438283cfSBen Stoltz 		uint_t		before;
274*438283cfSBen Stoltz 		uint_t		try;
275*438283cfSBen Stoltz 		int		error;
276*438283cfSBen Stoltz 
277*438283cfSBen Stoltz 		(void) memset(&cb, 0, sizeof (cb));
278*438283cfSBen Stoltz 		cb.aio_fildes = fds[0];
279*438283cfSBen Stoltz 		cb.aio_buf = buf;
280*438283cfSBen Stoltz 		cb.aio_nbytes = sizeof (buf);
281*438283cfSBen Stoltz 		cb.aio_sigevent.sigev_notify = SIGEV_THREAD;
282*438283cfSBen Stoltz 		cb.aio_sigevent.sigev_notify_function = aio_notify;
283*438283cfSBen Stoltz 		cb.aio_sigevent.sigev_value.sival_int = i;
284*438283cfSBen Stoltz 
285*438283cfSBen Stoltz 		/* Get a baseline count for this iteration. */
286*438283cfSBen Stoltz 		before = aio_readers(P, fds[0]);
287*438283cfSBen Stoltz 
288*438283cfSBen Stoltz 		/*
289*438283cfSBen Stoltz 		 * This read will block indefinitely.
290*438283cfSBen Stoltz 		 *
291*438283cfSBen Stoltz 		 * aio.c adds a worker only when it finds neither an idle
292*438283cfSBen Stoltz 		 * worker nor an acquirable queue lock, which never happens
293*438283cfSBen Stoltz 		 * with one request in flight, so the pool stays at the size
294*438283cfSBen Stoltz 		 * requested above.
295*438283cfSBen Stoltz 		 */
296*438283cfSBen Stoltz 		if (aio_read(&cb) != 0)
297*438283cfSBen Stoltz 			err(EXIT_FAILURE, "TEST FAILED: aio_read %u", i);
298*438283cfSBen Stoltz 
299*438283cfSBen Stoltz 		/* Baseline + 1 means the request reached its own worker. */
300*438283cfSBen Stoltz 		for (try = 0; try < AIO_SETTLE_TRIES; try++) {
301*438283cfSBen Stoltz 			if (aio_readers(P, fds[0]) > before)
302*438283cfSBen Stoltz 				break;
303*438283cfSBen Stoltz 			(void) nanosleep(&aio_poll, NULL);
304*438283cfSBen Stoltz 		}
305*438283cfSBen Stoltz 		if (try == AIO_SETTLE_TRIES) {
306*438283cfSBen Stoltz 			errx(EXIT_FAILURE, "TEST FAILED: request %u reached no "
307*438283cfSBen Stoltz 			    "worker within %d ms, with %u already blocked in "
308*438283cfSBen Stoltz 			    "read(2)", i, AIO_SETTLE_MS, before);
309*438283cfSBen Stoltz 		}
310*438283cfSBen Stoltz 
311*438283cfSBen Stoltz 		if (aio_cancel(fds[0], &cb) != AIO_CANCELED) {
312*438283cfSBen Stoltz 			warnx("TEST FAILED: request %u was not cancelled", i);
313*438283cfSBen Stoltz 			stuck++;
314*438283cfSBen Stoltz 			continue;
315*438283cfSBen Stoltz 		}
316*438283cfSBen Stoltz 
317*438283cfSBen Stoltz 		if (!aio_await(i)) {
318*438283cfSBen Stoltz 			warnx("TEST FAILED: request %u was cancelled but "
319*438283cfSBen Stoltz 			    "never reported after %d ms", i, AIO_DEADLINE_MS);
320*438283cfSBen Stoltz 			stuck++;
321*438283cfSBen Stoltz 			continue;
322*438283cfSBen Stoltz 		}
323*438283cfSBen Stoltz 
324*438283cfSBen Stoltz 		/*
325*438283cfSBen Stoltz 		 * aio_error() returns -1 when the aiocb names no outstanding
326*438283cfSBen Stoltz 		 * request, which is a different failure from the request
327*438283cfSBen Stoltz 		 * reporting the wrong status.
328*438283cfSBen Stoltz 		 */
329*438283cfSBen Stoltz 		error = aio_error(&cb);
330*438283cfSBen Stoltz 		if (error == -1) {
331*438283cfSBen Stoltz 			warnx("TEST FAILED: aio_error on request %u: %s", i,
332*438283cfSBen Stoltz 			    strerror(errno));
333*438283cfSBen Stoltz 			stuck++;
334*438283cfSBen Stoltz 		} else if (error != ECANCELED) {
335*438283cfSBen Stoltz 			warnx("TEST FAILED: request %u reported %s, expected "
336*438283cfSBen Stoltz 			    "ECANCELED", i, strerror(error));
337*438283cfSBen Stoltz 			stuck++;
338*438283cfSBen Stoltz 		}
339*438283cfSBen Stoltz 	}
340*438283cfSBen Stoltz 
341*438283cfSBen Stoltz 	/*
342*438283cfSBen Stoltz 	 * Any unreported cancellation is a failure. A worker whose
343*438283cfSBen Stoltz 	 * cancellation was not delivered is still blocked in read(2), so
344*438283cfSBen Stoltz 	 * report that count as well.
345*438283cfSBen Stoltz 	 */
346*438283cfSBen Stoltz 	if (stuck != 0) {
347*438283cfSBen Stoltz 		errx(EXIT_FAILURE, "TEST FAILED: %u of %u cancellations were "
348*438283cfSBen Stoltz 		    "not reported, %u workers left blocked in read(2)", stuck,
349*438283cfSBen Stoltz 		    iterations, aio_readers(P, fds[0]));
350*438283cfSBen Stoltz 	}
351*438283cfSBen Stoltz 
352*438283cfSBen Stoltz 	Prelease(P, 0);
353*438283cfSBen Stoltz 	free(aio_notified);
354*438283cfSBen Stoltz 
355*438283cfSBen Stoltz 	(void) printf("TEST PASSED: %u cancellations were all reported\n",
356*438283cfSBen Stoltz 	    iterations);
357*438283cfSBen Stoltz 	return (EXIT_SUCCESS);
358*438283cfSBen Stoltz }
359