1/* A program to put stress on a POSIX system (stress).
2 *
3 * Copyright (C) 2001, 2002 Amos Waterland <awaterl@yahoo.com>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include <ctype.h>
21#include <errno.h>
22#include <libgen.h>
23#include <math.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <signal.h>
28#include <time.h>
29#include <unistd.h>
30#include <sys/wait.h>
31
32/* By default, print all messages of severity info and above.  */
33static int global_debug = 2;
34
35/* By default, just print warning for non-critical errors.  */
36static int global_ignore = 1;
37
38/* By default, retry on non-critical errors every 50ms.  */
39static int global_retry = 50000;
40
41/* By default, use this as backoff coefficient for good fork throughput.  */
42static int global_backoff = 3000;
43
44/* By default, do not timeout.  */
45static int global_timeout = 0;
46
47/* Name of this program */
48static char *global_progname = PACKAGE;
49
50/* By default, do not hang after allocating memory.  */
51static int global_vmhang = 0;
52
53/* Implemention of runtime-selectable severity message printing.  */
54#define dbg if (global_debug >= 3) \
55            fprintf (stdout, "%s: debug: (%d) ", global_progname, __LINE__), \
56            fprintf
57#define out if (global_debug >= 2) \
58            fprintf (stdout, "%s: info: ", global_progname), \
59            fprintf
60#define wrn if (global_debug >= 1) \
61            fprintf (stderr, "%s: warn: (%d) ", global_progname, __LINE__), \
62            fprintf
63#define err if (global_debug >= 0) \
64            fprintf (stderr, "%s: error: (%d) ", global_progname, __LINE__), \
65            fprintf
66
67/* Implementation of check for option argument correctness.  */
68#define assert_arg(A) \
69          if (++i == argc || ((arg = argv[i])[0] == '-' && \
70              !isdigit ((int)arg[1]) )) \
71            { \
72              err (stderr, "missing argument to option '%s'\n", A); \
73              exit (1); \
74            }
75
76/* Prototypes for utility functions.  */
77int usage(int status);
78int version(int status);
79long long atoll_s(const char *nptr);
80long long atoll_b(const char *nptr);
81
82/* Prototypes for the worker functions.  */
83int hogcpu(long long forks);
84int hogio(long long forks);
85int hogvm(long long forks, long long chunks, long long bytes);
86int hoghdd(long long forks, int clean, long long files, long long bytes);
87
88int main(int argc, char **argv)
89{
90	int i, pid, children = 0, retval = 0;
91	long starttime, stoptime, runtime;
92
93	/* Variables that indicate which options have been selected.  */
94	int do_dryrun = 0;
95	int do_timeout = 0;
96	int do_cpu = 0;		/* Default to 1 fork. */
97	long long do_cpu_forks = 1;
98	int do_io = 0;		/* Default to 1 fork. */
99	long long do_io_forks = 1;
100	int do_vm = 0;		/* Default to 1 fork, 1 chunk of 256MB.  */
101	long long do_vm_forks = 1;
102	long long do_vm_chunks = 1;
103	long long do_vm_bytes = 256 * 1024 * 1024;
104	int do_hdd = 0;		/* Default to 1 fork, clean, 1 file of 1GB.  */
105	long long do_hdd_forks = 1;
106	int do_hdd_clean = 0;
107	long long do_hdd_files = 1;
108	long long do_hdd_bytes = 1024 * 1024 * 1024;
109
110	/* Record our start time.  */
111	if ((starttime = time(NULL)) == -1) {
112		err(stderr, "failed to acquire current time\n");
113		exit(1);
114	}
115
116	/* SuSv3 does not define any error conditions for this function.  */
117	global_progname = basename(argv[0]);
118
119	/* For portability, parse command line options without getopt_long.  */
120	for (i = 1; i < argc; i++) {
121		char *arg = argv[i];
122
123		if (strcmp(arg, "--help") == 0 || strcmp(arg, "-?") == 0) {
124			usage(0);
125		} else if (strcmp(arg, "--version") == 0) {
126			version(0);
127		} else if (strcmp(arg, "--verbose") == 0
128			   || strcmp(arg, "-v") == 0) {
129			global_debug = 3;
130		} else if (strcmp(arg, "--quiet") == 0
131			   || strcmp(arg, "-q") == 0) {
132			global_debug = 0;
133		} else if (strcmp(arg, "--dry-run") == 0
134			   || strcmp(arg, "-n") == 0) {
135			do_dryrun = 1;
136		} else if (strcmp(arg, "--no-retry") == 0) {
137			global_ignore = 0;
138			dbg(stdout,
139			    "turning off ignore of non-critical errors");
140		} else if (strcmp(arg, "--retry-delay") == 0) {
141			assert_arg("--retry-delay");
142			global_retry = atoll(arg);
143			dbg(stdout, "setting retry delay to %dus\n",
144			    global_retry);
145		} else if (strcmp(arg, "--backoff") == 0) {
146			assert_arg("--backoff");
147			global_backoff = atoll(arg);
148			if (global_backoff < 0) {
149				err(stderr, "invalid backoff factor: %i\n",
150				    global_backoff);
151				exit(1);
152			}
153			dbg(stdout, "setting backoff coeffient to %dus\n",
154			    global_backoff);
155		} else if (strcmp(arg, "--timeout") == 0
156			   || strcmp(arg, "-t") == 0) {
157			do_timeout = 1;
158			assert_arg("--timeout");
159			global_timeout = atoll_s(arg);
160			dbg(stdout, "setting timeout to %ds\n", global_timeout);
161		} else if (strcmp(arg, "--cpu") == 0 || strcmp(arg, "-c") == 0) {
162			do_cpu = 1;
163			assert_arg("--cpu");
164			do_cpu_forks = atoll_b(arg);
165		} else if (strcmp(arg, "--io") == 0 || strcmp(arg, "-i") == 0) {
166			do_io = 1;
167			assert_arg("--io");
168			do_io_forks = atoll_b(arg);
169		} else if (strcmp(arg, "--vm") == 0 || strcmp(arg, "-m") == 0) {
170			do_vm = 1;
171			assert_arg("--vm");
172			do_vm_forks = atoll_b(arg);
173		} else if (strcmp(arg, "--vm-chunks") == 0) {
174			assert_arg("--vm-chunks");
175			do_vm_chunks = atoll_b(arg);
176		} else if (strcmp(arg, "--vm-bytes") == 0) {
177			assert_arg("--vm-bytes");
178			do_vm_bytes = atoll_b(arg);
179		} else if (strcmp(arg, "--vm-hang") == 0) {
180			global_vmhang = 1;
181		} else if (strcmp(arg, "--hdd") == 0 || strcmp(arg, "-d") == 0) {
182			do_hdd = 1;
183			assert_arg("--hdd");
184			do_hdd_forks = atoll_b(arg);
185		} else if (strcmp(arg, "--hdd-noclean") == 0) {
186			do_hdd_clean = 2;
187		} else if (strcmp(arg, "--hdd-files") == 0) {
188			assert_arg("--hdd-files");
189			do_hdd_files = atoll_b(arg);
190		} else if (strcmp(arg, "--hdd-bytes") == 0) {
191			assert_arg("--hdd-bytes");
192			do_hdd_bytes = atoll_b(arg);
193		} else {
194			err(stderr, "unrecognized option: %s\n", arg);
195			exit(1);
196		}
197	}
198
199	/* Hog CPU option.  */
200	if (do_cpu) {
201		out(stdout, "dispatching %lli hogcpu forks\n", do_cpu_forks);
202
203		switch (pid = fork()) {
204		case 0:	/* child */
205			if (do_dryrun)
206				exit(0);
207			exit(hogcpu(do_cpu_forks));
208		case -1:	/* error */
209			err(stderr, "hogcpu dispatcher fork failed\n");
210			exit(1);
211		default:	/* parent */
212			children++;
213			dbg(stdout, "--> hogcpu dispatcher forked (%i)\n", pid);
214		}
215	}
216
217	/* Hog I/O option.  */
218	if (do_io) {
219		out(stdout, "dispatching %lli hogio forks\n", do_io_forks);
220
221		switch (pid = fork()) {
222		case 0:	/* child */
223			if (do_dryrun)
224				exit(0);
225			exit(hogio(do_io_forks));
226		case -1:	/* error */
227			err(stderr, "hogio dispatcher fork failed\n");
228			exit(1);
229		default:	/* parent */
230			children++;
231			dbg(stdout, "--> hogio dispatcher forked (%i)\n", pid);
232		}
233	}
234
235	/* Hog VM option.  */
236	if (do_vm) {
237		out(stdout,
238		    "dispatching %lli hogvm forks, each %lli chunks of %lli bytes\n",
239		    do_vm_forks, do_vm_chunks, do_vm_bytes);
240
241		switch (pid = fork()) {
242		case 0:	/* child */
243			if (do_dryrun)
244				exit(0);
245			exit(hogvm(do_vm_forks, do_vm_chunks, do_vm_bytes));
246		case -1:	/* error */
247			err(stderr, "hogvm dispatcher fork failed\n");
248			exit(1);
249		default:	/* parent */
250			children++;
251			dbg(stdout, "--> hogvm dispatcher forked (%i)\n", pid);
252		}
253	}
254
255	/* Hog HDD option.  */
256	if (do_hdd) {
257		out(stdout, "dispatching %lli hoghdd forks, each %lli files of "
258		    "%lli bytes\n", do_hdd_forks, do_hdd_files, do_hdd_bytes);
259
260		switch (pid = fork()) {
261		case 0:	/* child */
262			if (do_dryrun)
263				exit(0);
264			exit(hoghdd
265			     (do_hdd_forks, do_hdd_clean, do_hdd_files,
266			      do_hdd_bytes));
267		case -1:	/* error */
268			err(stderr, "hoghdd dispatcher fork failed\n");
269			exit(1);
270		default:	/* parent */
271			children++;
272			dbg(stdout, "--> hoghdd dispatcher forked (%i)\n", pid);
273		}
274	}
275
276	/* We have no work to do, so bail out.  */
277	if (children == 0)
278		usage(0);
279
280	/* Wait for our children to exit.  */
281	while (children) {
282		int status, ret;
283
284		if ((pid = wait(&status)) > 0) {
285			if ((WIFEXITED(status)) != 0) {
286				if ((ret = WEXITSTATUS(status)) != 0) {
287					err(stderr,
288					    "dispatcher %i returned error %i\n",
289					    pid, ret);
290					retval += ret;
291				} else {
292					dbg(stdout,
293					    "<-- dispatcher return (%i)\n",
294					    pid);
295				}
296			} else {
297				err(stderr,
298				    "dispatcher did not exit normally\n");
299				++retval;
300			}
301
302			--children;
303		} else {
304			dbg(stdout, "wait() returned error: %s\n",
305			    strerror(errno));
306			err(stderr, "detected missing dispatcher children\n");
307			++retval;
308			break;
309		}
310	}
311
312	/* Record our stop time.  */
313	if ((stoptime = time(NULL)) == -1) {
314		err(stderr, "failed to acquire current time\n");
315		exit(1);
316	}
317
318	/* Calculate our runtime.  */
319	runtime = stoptime - starttime;
320
321	/* Print final status message.  */
322	if (retval) {
323		err(stderr, "failed run completed in %lis\n", runtime);
324	} else {
325		out(stdout, "successful run completed in %lis\n", runtime);
326	}
327
328	exit(retval);
329}
330
331int usage(int status)
332{
333	char *mesg =
334	    "`%s' imposes certain types of compute stress on your system\n\n"
335	    "Usage: %s [OPTION [ARG]] ...\n\n"
336	    " -?, --help            show this help statement\n"
337	    "     --version         show version statement\n"
338	    " -v, --verbose         be verbose\n"
339	    " -q, --quiet           be quiet\n"
340	    " -n, --dry-run         show what would have been done\n"
341	    "     --no-retry        exit rather than retry non-critical errors\n"
342	    "     --retry-delay n   wait n us before continuing past error\n"
343	    " -t, --timeout n       timeout after n seconds\n"
344	    "     --backoff n       wait for factor of n us before starting work\n"
345	    " -c, --cpu n           spawn n procs spinning on sqrt()\n"
346	    " -i, --io n            spawn n procs spinning on sync()\n"
347	    " -m, --vm n            spawn n procs spinning on malloc()\n"
348	    "     --vm-chunks c     malloc c chunks (default is 1)\n"
349	    "     --vm-bytes b      malloc chunks of b bytes (default is 256MB)\n"
350	    "     --vm-hang         hang in a sleep loop after memory allocated\n"
351	    " -d, --hdd n           spawn n procs spinning on write()\n"
352	    "     --hdd-noclean     do not unlink file to which random data written\n"
353	    "     --hdd-files f     write to f files (default is 1)\n"
354	    "     --hdd-bytes b     write b bytes (default is 1GB)\n\n"
355	    "Infinity is denoted with 0.  For -m, -d: n=0 means infinite redo,\n"
356	    "n<0 means redo abs(n) times. Valid suffixes are m,h,d,y for time;\n"
357	    "k,m,g for size.\n\n";
358
359	fprintf(stdout, mesg, global_progname, global_progname);
360
361	if (status <= 0)
362		exit(-1 * status);
363
364	return 0;
365}
366
367int version(int status)
368{
369	char *mesg = "%s %s\n";
370
371	fprintf(stdout, mesg, global_progname, VERSION);
372
373	if (status <= 0)
374		exit(-1 * status);
375
376	return 0;
377}
378
379/* Convert a string representation of a number with an optional size suffix
380 * to a long long.
381 */
382long long atoll_b(const char *nptr)
383{
384	int pos;
385	char suffix;
386	long long factor = 1;
387
388	if ((pos = strlen(nptr) - 1) < 0) {
389		err(stderr, "invalid string\n");
390		exit(1);
391	}
392
393	switch (suffix = nptr[pos]) {
394	case 'k':
395	case 'K':
396		factor = 1024;
397		break;
398	case 'm':
399	case 'M':
400		factor = 1024 * 1024;
401		break;
402	case 'g':
403	case 'G':
404		factor = 1024 * 1024 * 1024;
405		break;
406	default:
407		if (suffix < '0' || suffix > '9') {
408			err(stderr, "unrecognized suffix: %c\n", suffix);
409			exit(1);
410		}
411	}
412
413	factor = atoll(nptr) * factor;
414
415	return factor;
416}
417
418/* Convert a string representation of a number with an optional time suffix
419 * to a long long.
420 */
421long long atoll_s(const char *nptr)
422{
423	int pos;
424	char suffix;
425	long long factor = 1;
426
427	if ((pos = strlen(nptr) - 1) < 0) {
428		err(stderr, "invalid string\n");
429		exit(1);
430	}
431
432	switch (suffix = nptr[pos]) {
433	case 's':
434	case 'S':
435		factor = 1;
436		break;
437	case 'm':
438	case 'M':
439		factor = 60;
440		break;
441	case 'h':
442	case 'H':
443		factor = 60 * 60;
444		break;
445	case 'd':
446	case 'D':
447		factor = 60 * 60 * 24;
448		break;
449	case 'y':
450	case 'Y':
451		factor = 60 * 60 * 24 * 360;
452		break;
453	default:
454		if (suffix < '0' || suffix > '9') {
455			err(stderr, "unrecognized suffix: %c\n", suffix);
456			exit(1);
457		}
458	}
459
460	factor = atoll(nptr) * factor;
461
462	return factor;
463}
464
465int hogcpu(long long forks)
466{
467	long long i;
468	double d;
469	int pid, retval = 0;
470
471	/* Make local copies of global variables.  */
472	int ignore = global_ignore;
473	int retry = global_retry;
474	int timeout = global_timeout;
475	long backoff = global_backoff * forks;
476
477	dbg(stdout, "using backoff sleep of %lius for hogcpu\n", backoff);
478
479	for (i = 0; forks == 0 || i < forks; i++) {
480		switch (pid = fork()) {
481		case 0:	/* child */
482			alarm(timeout);
483
484			/* Use a backoff sleep to ensure we get good fork throughput.  */
485			usleep(backoff);
486
487			while (1)
488				d = sqrt(rand());
489
490			/* This case never falls through; alarm signal can cause exit.  */
491		case -1:	/* error */
492			if (ignore) {
493				++retval;
494				wrn(stderr,
495				    "hogcpu worker fork failed, continuing\n");
496				usleep(retry);
497				continue;
498			}
499
500			err(stderr, "hogcpu worker fork failed\n");
501			return 1;
502		default:	/* parent */
503			dbg(stdout, "--> hogcpu worker forked (%i)\n", pid);
504		}
505	}
506
507	/* Wait for our children to exit.  */
508	while (i) {
509		int status, ret;
510
511		if ((pid = wait(&status)) > 0) {
512			if ((WIFEXITED(status)) != 0) {
513				if ((ret = WEXITSTATUS(status)) != 0) {
514					err(stderr,
515					    "hogcpu worker %i exited %i\n", pid,
516					    ret);
517					retval += ret;
518				} else {
519					dbg(stdout,
520					    "<-- hogcpu worker exited (%i)\n",
521					    pid);
522				}
523			} else {
524				dbg(stdout,
525				    "<-- hogcpu worker signalled (%i)\n", pid);
526			}
527
528			--i;
529		} else {
530			dbg(stdout, "wait() returned error: %s\n",
531			    strerror(errno));
532			err(stderr,
533			    "detected missing hogcpu worker children\n");
534			++retval;
535			break;
536		}
537	}
538
539	return retval;
540}
541
542int hogio(long long forks)
543{
544	long long i;
545	int pid, retval = 0;
546
547	/* Make local copies of global variables.  */
548	int ignore = global_ignore;
549	int retry = global_retry;
550	int timeout = global_timeout;
551	long backoff = global_backoff * forks;
552
553	dbg(stdout, "using backoff sleep of %lius for hogio\n", backoff);
554
555	for (i = 0; forks == 0 || i < forks; i++) {
556		switch (pid = fork()) {
557		case 0:	/* child */
558			alarm(timeout);
559
560			/* Use a backoff sleep to ensure we get good fork throughput.  */
561			usleep(backoff);
562
563			while (1)
564				sync();
565
566			/* This case never falls through; alarm signal can cause exit.  */
567		case -1:	/* error */
568			if (ignore) {
569				++retval;
570				wrn(stderr,
571				    "hogio worker fork failed, continuing\n");
572				usleep(retry);
573				continue;
574			}
575
576			err(stderr, "hogio worker fork failed\n");
577			return 1;
578		default:	/* parent */
579			dbg(stdout, "--> hogio worker forked (%i)\n", pid);
580		}
581	}
582
583	/* Wait for our children to exit.  */
584	while (i) {
585		int status, ret;
586
587		if ((pid = wait(&status)) > 0) {
588			if ((WIFEXITED(status)) != 0) {
589				if ((ret = WEXITSTATUS(status)) != 0) {
590					err(stderr,
591					    "hogio worker %i exited %i\n", pid,
592					    ret);
593					retval += ret;
594				} else {
595					dbg(stdout,
596					    "<-- hogio worker exited (%i)\n",
597					    pid);
598				}
599			} else {
600				dbg(stdout, "<-- hogio worker signalled (%i)\n",
601				    pid);
602			}
603
604			--i;
605		} else {
606			dbg(stdout, "wait() returned error: %s\n",
607			    strerror(errno));
608			err(stderr, "detected missing hogio worker children\n");
609			++retval;
610			break;
611		}
612	}
613
614	return retval;
615}
616
617int hogvm(long long forks, long long chunks, long long bytes)
618{
619	long long i, j, k;
620	int pid, retval = 0;
621	char **ptr;
622
623	/* Make local copies of global variables.  */
624	int ignore = global_ignore;
625	int retry = global_retry;
626	int timeout = global_timeout;
627	long backoff = global_backoff * forks;
628
629	dbg(stdout, "using backoff sleep of %lius for hogvm\n", backoff);
630
631	if (bytes == 0) {
632		/* 512MB is guess at the largest value can than be malloced at once.  */
633		bytes = 512 * 1024 * 1024;
634	}
635
636	for (i = 0; forks == 0 || i < forks; i++) {
637		switch (pid = fork()) {
638		case 0:	/* child */
639			alarm(timeout);
640
641			/* Use a backoff sleep to ensure we get good fork throughput.  */
642			usleep(backoff);
643
644			while (1) {
645				ptr = (char **)malloc(chunks * 2);
646				for (j = 0; chunks == 0 || j < chunks; j++) {
647					if ((ptr[j] =
648					     (char *)malloc(bytes *
649							    sizeof(char)))) {
650						for (k = 0; k < bytes; k++)
651							ptr[j][k] = 'Z';	/* Ensure that COW happens.  */
652						dbg(stdout,
653						    "hogvm worker malloced %lli bytes\n",
654						    k);
655					} else if (ignore) {
656						++retval;
657						wrn(stderr,
658						    "hogvm malloc failed, continuing\n");
659						usleep(retry);
660						continue;
661					} else {
662						++retval;
663						err(stderr,
664						    "hogvm malloc failed\n");
665						break;
666					}
667				}
668				if (global_vmhang && retval == 0) {
669					dbg(stdout,
670					    "sleeping forever with allocated memory\n");
671					while (1)
672						sleep(1024);
673				}
674				if (retval == 0) {
675					dbg(stdout,
676					    "hogvm worker freeing memory and starting over\n");
677					for (j = 0; chunks == 0 || j < chunks;
678					     j++) {
679						free(ptr[j]);
680					}
681					free(ptr);
682					continue;
683				}
684
685				exit(retval);
686			}
687
688			/* This case never falls through; alarm signal can cause exit.  */
689		case -1:	/* error */
690			if (ignore) {
691				++retval;
692				wrn(stderr,
693				    "hogvm worker fork failed, continuing\n");
694				usleep(retry);
695				continue;
696			}
697
698			err(stderr, "hogvm worker fork failed\n");
699			return 1;
700		default:	/* parent */
701			dbg(stdout, "--> hogvm worker forked (%i)\n", pid);
702		}
703	}
704
705	/* Wait for our children to exit.  */
706	while (i) {
707		int status, ret;
708
709		if ((pid = wait(&status)) > 0) {
710			if ((WIFEXITED(status)) != 0) {
711				if ((ret = WEXITSTATUS(status)) != 0) {
712					err(stderr,
713					    "hogvm worker %i exited %i\n", pid,
714					    ret);
715					retval += ret;
716				} else {
717					dbg(stdout,
718					    "<-- hogvm worker exited (%i)\n",
719					    pid);
720				}
721			} else {
722				dbg(stdout, "<-- hogvm worker signalled (%i)\n",
723				    pid);
724			}
725
726			--i;
727		} else {
728			dbg(stdout, "wait() returned error: %s\n",
729			    strerror(errno));
730			err(stderr, "detected missing hogvm worker children\n");
731			++retval;
732			break;
733		}
734	}
735
736	return retval;
737}
738
739int hoghdd(long long forks, int clean, long long files, long long bytes)
740{
741	long long i, j;
742	int fd, pid, retval = 0;
743	int chunk = (1024 * 1024) - 1;	/* Minimize slow writing.  */
744	char buff[chunk];
745
746	/* Make local copies of global variables.  */
747	int ignore = global_ignore;
748	int retry = global_retry;
749	int timeout = global_timeout;
750	long backoff = global_backoff * forks;
751
752	/* Initialize buffer with some random ASCII data.  */
753	dbg(stdout, "seeding buffer with random data\n");
754	for (i = 0; i < chunk - 1; i++) {
755		j = rand();
756		j = (j < 0) ? -j : j;
757		j %= 95;
758		j += 32;
759		buff[i] = j;
760	}
761	buff[i] = '\n';
762
763	dbg(stdout, "using backoff sleep of %lius for hoghdd\n", backoff);
764
765	for (i = 0; forks == 0 || i < forks; i++) {
766		switch (pid = fork()) {
767		case 0:	/* child */
768			alarm(timeout);
769
770			/* Use a backoff sleep to ensure we get good fork throughput.  */
771			usleep(backoff);
772
773			while (1) {
774				for (i = 0; i < files; i++) {
775					char name[] = "./stress.XXXXXX";
776
777					if ((fd = mkstemp(name)) < 0) {
778						perror("mkstemp");
779						err(stderr, "mkstemp failed\n");
780						exit(1);
781					}
782
783					if (clean == 0) {
784						dbg(stdout, "unlinking %s\n",
785						    name);
786						if (unlink(name)) {
787							err(stderr,
788							    "unlink failed\n");
789							exit(1);
790						}
791					}
792
793					dbg(stdout, "fast writing to %s\n",
794					    name);
795					for (j = 0;
796					     bytes == 0 || j + chunk < bytes;
797					     j += chunk) {
798						if (write(fd, buff, chunk) !=
799						    chunk) {
800							err(stderr,
801							    "write failed\n");
802							exit(1);
803						}
804					}
805
806					dbg(stdout, "slow writing to %s\n",
807					    name);
808					for (; bytes == 0 || j < bytes - 1; j++) {
809						if (write(fd, "Z", 1) != 1) {
810							err(stderr,
811							    "write failed\n");
812							exit(1);
813						}
814					}
815					if (write(fd, "\n", 1) != 1) {
816						err(stderr, "write failed\n");
817						exit(1);
818					}
819					++j;
820
821					dbg(stdout,
822					    "closing %s after writing %lli bytes\n",
823					    name, j);
824					close(fd);
825
826					if (clean == 1) {
827						if (unlink(name)) {
828							err(stderr,
829							    "unlink failed\n");
830							exit(1);
831						}
832					}
833				}
834				if (retval == 0) {
835					dbg(stdout,
836					    "hoghdd worker starting over\n");
837					continue;
838				}
839
840				exit(retval);
841			}
842
843			/* This case never falls through; alarm signal can cause exit.  */
844		case -1:	/* error */
845			if (ignore) {
846				++retval;
847				wrn(stderr,
848				    "hoghdd worker fork failed, continuing\n");
849				usleep(retry);
850				continue;
851			}
852
853			err(stderr, "hoghdd worker fork failed\n");
854			return 1;
855		default:	/* parent */
856			dbg(stdout, "--> hoghdd worker forked (%i)\n", pid);
857		}
858	}
859
860	/* Wait for our children to exit.  */
861	while (i) {
862		int status, ret;
863
864		if ((pid = wait(&status)) > 0) {
865			if ((WIFEXITED(status)) != 0) {
866				if ((ret = WEXITSTATUS(status)) != 0) {
867					err(stderr,
868					    "hoghdd worker %i exited %i\n", pid,
869					    ret);
870					retval += ret;
871				} else {
872					dbg(stdout,
873					    "<-- hoghdd worker exited (%i)\n",
874					    pid);
875				}
876			} else {
877				dbg(stdout,
878				    "<-- hoghdd worker signalled (%i)\n", pid);
879			}
880
881			--i;
882		} else {
883			dbg(stdout, "wait() returned error: %s\n",
884			    strerror(errno));
885			err(stderr,
886			    "detected missing hoghdd worker children\n");
887			++retval;
888			break;
889		}
890	}
891
892	return retval;
893}
894