savecore.c revision 97340
1214455Srpaulo/*-
2214455Srpaulo * Copyright (c) 2002 Poul-Henning Kamp
3214455Srpaulo * Copyright (c) 2002 Networks Associates Technology, Inc.
4214455Srpaulo * All rights reserved.
5214455Srpaulo *
6214455Srpaulo * This software was developed for the FreeBSD Project by Poul-Henning Kamp
7214455Srpaulo * and NAI Labs, the Security Research Division of Network Associates, Inc.
8214455Srpaulo * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
9214455Srpaulo * DARPA CHATS research program.
10214455Srpaulo *
11214455Srpaulo * Redistribution and use in source and binary forms, with or without
12214455Srpaulo * modification, are permitted provided that the following conditions
13214455Srpaulo * are met:
14214455Srpaulo * 1. Redistributions of source code must retain the above copyright
15214455Srpaulo *    notice, this list of conditions and the following disclaimer.
16214455Srpaulo * 2. Redistributions in binary form must reproduce the above copyright
17214455Srpaulo *    notice, this list of conditions and the following disclaimer in the
18214455Srpaulo *    documentation and/or other materials provided with the distribution.
19214455Srpaulo * 3. The names of the authors may not be used to endorse or promote
20214455Srpaulo *    products derived from this software without specific prior written
21214455Srpaulo *    permission.
22214455Srpaulo *
23214455Srpaulo * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
24214455Srpaulo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25214455Srpaulo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26214455Srpaulo * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
27214455Srpaulo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28214455Srpaulo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29214455Srpaulo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30214455Srpaulo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31214455Srpaulo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32214455Srpaulo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33214455Srpaulo * SUCH DAMAGE.
34214455Srpaulo *
35214455Srpaulo * Copyright (c) 1986, 1992, 1993
36214455Srpaulo *	The Regents of the University of California.  All rights reserved.
37214455Srpaulo *
38214455Srpaulo * Redistribution and use in source and binary forms, with or without
39214455Srpaulo * modification, are permitted provided that the following conditions
40214455Srpaulo * are met:
41214455Srpaulo * 1. Redistributions of source code must retain the above copyright
42214455Srpaulo *    notice, this list of conditions and the following disclaimer.
43214455Srpaulo * 2. Redistributions in binary form must reproduce the above copyright
44214455Srpaulo *    notice, this list of conditions and the following disclaimer in the
45214455Srpaulo *    documentation and/or other materials provided with the distribution.
46214455Srpaulo * 3. All advertising materials mentioning features or use of this software
47214455Srpaulo *    must display the following acknowledgement:
48214455Srpaulo *	This product includes software developed by the University of
49214455Srpaulo *	California, Berkeley and its contributors.
50214455Srpaulo * 4. Neither the name of the University nor the names of its contributors
51214455Srpaulo *    may be used to endorse or promote products derived from this software
52214455Srpaulo *    without specific prior written permission.
53214455Srpaulo *
54214455Srpaulo * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
55214455Srpaulo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56214455Srpaulo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
57214455Srpaulo * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
58214455Srpaulo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
59214455Srpaulo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
60214455Srpaulo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
61214455Srpaulo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
62214455Srpaulo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
63214455Srpaulo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
64214455Srpaulo * SUCH DAMAGE.
65214455Srpaulo */
66214455Srpaulo
67214455Srpaulo#include <sys/cdefs.h>
68214455Srpaulo__FBSDID("$FreeBSD: head/sbin/savecore/savecore.c 97340 2002-05-27 07:54:43Z marcel $");
69214455Srpaulo
70214455Srpaulo#include <sys/param.h>
71214455Srpaulo#include <sys/disk.h>
72214455Srpaulo#include <sys/kerneldump.h>
73214455Srpaulo#include <sys/param.h>
74214455Srpaulo#include <sys/mount.h>
75251129Sdelphij#include <sys/stat.h>
76251129Sdelphij#include <errno.h>
77251129Sdelphij#include <fcntl.h>
78251129Sdelphij#include <fstab.h>
79251129Sdelphij#include <paths.h>
80251129Sdelphij#include <stdarg.h>
81251129Sdelphij#include <stdio.h>
82251129Sdelphij#include <stdlib.h>
83251129Sdelphij#include <string.h>
84251129Sdelphij#include <syslog.h>
85251129Sdelphij#include <time.h>
86251129Sdelphij#include <unistd.h>
87214455Srpaulo
88251129Sdelphijint compress, clear, force, keep, verbose;	/* flags */
89214455Srpauloint nfound, nsaved, nerr;			/* statistics */
90251129Sdelphij
91251129Sdelphijextern FILE *zopen(const char *, const char *);
92251129Sdelphij
93214455Srpaulostatic void
94214455Srpauloprintheader(FILE *f, const struct kerneldumpheader *h, const char *device,
95251129Sdelphij    int bounds)
96251129Sdelphij{
97251129Sdelphij	uint64_t dumplen;
98251129Sdelphij	time_t t;
99251129Sdelphij
100251129Sdelphij	fprintf(f, "Good dump found on device %s\n", device);
101251129Sdelphij	fprintf(f, "  Architecture: %s\n", h->architecture);
102251129Sdelphij	fprintf(f, "  Architecture version: %d\n",
103251129Sdelphij	    dtoh32(h->architectureversion));
104251129Sdelphij	dumplen = dtoh64(h->dumplength);
105251129Sdelphij	fprintf(f, "  Dump length: %lldB (%lld MB)\n", (long long)dumplen,
106251129Sdelphij	    (long long)(dumplen >> 20));
107251129Sdelphij	fprintf(f, "  Blocksize: %d\n", dtoh32(h->blocksize));
108251129Sdelphij	t = dtoh64(h->dumptime);
109251129Sdelphij	fprintf(f, "  Dumptime: %s", ctime(&t));
110251129Sdelphij	fprintf(f, "  Hostname: %s\n", h->hostname);
111251129Sdelphij	fprintf(f, "  Versionstring: %s", h->versionstring);
112251129Sdelphij	fprintf(f, "  Panicstring: %s\n", h->panicstring);
113251129Sdelphij	fprintf(f, "  Bounds: %d\n", bounds);
114251129Sdelphij	fflush(f);
115251129Sdelphij}
116251129Sdelphij
117251129Sdelphijstatic int
118251129Sdelphijgetbounds(void) {
119251129Sdelphij	FILE *fp;
120251129Sdelphij	char buf[6];
121251129Sdelphij	int ret;
122251129Sdelphij
123251129Sdelphij	ret = 0;
124251129Sdelphij
125251129Sdelphij	if ((fp = fopen("bounds", "r")) == NULL) {
126251129Sdelphij		syslog(LOG_WARNING, "unable to open bounds file, using 0");
127214455Srpaulo		goto newfile;
128214455Srpaulo	}
129214455Srpaulo
130214455Srpaulo	if (fgets(buf, sizeof buf, fp) == NULL) {
131214455Srpaulo		syslog(LOG_WARNING, "unable to read from bounds, using 0");
132214455Srpaulo		fclose(fp);
133214455Srpaulo		goto newfile;
134214455Srpaulo	}
135214455Srpaulo
136214455Srpaulo	errno = 0;
137214455Srpaulo	ret = (int)strtol(buf, NULL, 10);
138214455Srpaulo	if (ret == 0 && (errno == EINVAL || errno == ERANGE))
139214455Srpaulo		syslog(LOG_WARNING, "invalid value found in bounds, using 0");
140214455Srpaulo
141214455Srpaulonewfile:
142214455Srpaulo
143214455Srpaulo	if ((fp = fopen("bounds", "w")) == NULL) {
144214455Srpaulo		syslog(LOG_WARNING, "unable to write to bounds file: %m");
145214455Srpaulo		goto done;
146214455Srpaulo	}
147214455Srpaulo
148214455Srpaulo	if (verbose)
149214455Srpaulo		printf("bounds number: %d\n", ret);
150214455Srpaulo
151214455Srpaulo	fprintf(fp, "%d\n", (ret + 1));
152214455Srpaulo	fclose(fp);
153214455Srpaulo
154214455Srpaulodone:
155214455Srpaulo	return (ret);
156214455Srpaulo}
157214455Srpaulo
158214455Srpaulo/*
159214455Srpaulo * Check that sufficient space is available on the disk that holds the
160214455Srpaulo * save directory.
161214455Srpaulo */
162214455Srpaulostatic int
163214455Srpaulocheck_space(char *savedir, off_t dumpsize)
164214455Srpaulo{
165214455Srpaulo	FILE *fp;
166214455Srpaulo	off_t minfree, spacefree, totfree, needed;
167214455Srpaulo	struct statfs fsbuf;
168214455Srpaulo	char buf[100], path[MAXPATHLEN];
169214455Srpaulo
170214455Srpaulo	if (statfs(savedir, &fsbuf) < 0) {
171214455Srpaulo		syslog(LOG_ERR, "%s: %m", savedir);
172214455Srpaulo		exit(1);
173214455Srpaulo	}
174214455Srpaulo 	spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
175214455Srpaulo	totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024;
176214455Srpaulo
177214455Srpaulo	(void)snprintf(path, sizeof(path), "%s/minfree", savedir);
178214455Srpaulo	if ((fp = fopen(path, "r")) == NULL)
179214455Srpaulo		minfree = 0;
180214455Srpaulo	else {
181214455Srpaulo		if (fgets(buf, sizeof(buf), fp) == NULL)
182214455Srpaulo			minfree = 0;
183214455Srpaulo		else
184214455Srpaulo			minfree = atoi(buf);
185214455Srpaulo		(void)fclose(fp);
186214455Srpaulo	}
187214455Srpaulo
188214455Srpaulo	needed = dumpsize / 1024 + 2;	/* 2 for info file */
189214455Srpaulo 	if (((minfree > 0) ? spacefree : totfree) - needed < minfree) {
190214455Srpaulo		syslog(LOG_WARNING,
191214455Srpaulo	"no dump, not enough free space on device (%lld available, need %lld)",
192214455Srpaulo		    (long long)(minfree > 0 ? spacefree : totfree),
193214455Srpaulo		    (long long)needed);
194214455Srpaulo		return (0);
195214455Srpaulo	}
196214455Srpaulo	if (spacefree - needed < 0)
197214455Srpaulo		syslog(LOG_WARNING,
198214455Srpaulo		    "dump performed, but free space threshold crossed");
199214455Srpaulo	return (1);
200214455Srpaulo}
201214455Srpaulo
202214455Srpaulo#define BLOCKSIZE (1<<12)
203214455Srpaulo#define BLOCKMASK (~(BLOCKSIZE-1))
204214455Srpaulo
205214455Srpaulostatic void
206214455SrpauloDoFile(char *savedir, const char *device)
207214455Srpaulo{
208214455Srpaulo	static char *buf = NULL;
209214455Srpaulo	struct kerneldumpheader kdhf, kdhl;
210214455Srpaulo	off_t mediasize, dumpsize, firsthd, lasthd, dmpcnt;
211214455Srpaulo	FILE *info, *fp;
212214455Srpaulo	int fd, fdinfo, error, wl;
213214455Srpaulo	int nr, nw, hs, he;
214214455Srpaulo	int bounds;
215214455Srpaulo	u_int sectorsize;
216214455Srpaulo	mode_t oumask;
217214455Srpaulo
218214455Srpaulo	dmpcnt = 0;
219214455Srpaulo	mediasize = 0;
220214455Srpaulo
221214455Srpaulo	/*
222214455Srpaulo	 * XXX On ia64 something breaks when the buffer is put on the
223214455Srpaulo	 * stack. When the buffer is roughly larger than 128K the read()
224214455Srpaulo	 * below simply fails with errno=14 (EFAULT). We work around
225214455Srpaulo	 * this by doing a on-time allocation...
226214455Srpaulo	 */
227214455Srpaulo	if (buf == NULL) {
228214455Srpaulo		buf = malloc(1024 * 1024);
229214455Srpaulo		if (buf == NULL) {
230214455Srpaulo			syslog(LOG_ERR, "%m");
231214455Srpaulo			return;
232214455Srpaulo		}
233214455Srpaulo	}
234214455Srpaulo
235214455Srpaulo	if (verbose)
236214455Srpaulo		printf("checking for kernel dump on device %s\n", device);
237214455Srpaulo
238214455Srpaulo	fd = open(device, O_RDWR);
239214455Srpaulo	if (fd < 0) {
240214455Srpaulo		syslog(LOG_ERR, "%s: %m", device);
241214455Srpaulo		return;
242214455Srpaulo	}
243214455Srpaulo
244214455Srpaulo	error = ioctl(fd, DIOCGMEDIASIZE, &mediasize);
245214455Srpaulo	if (!error)
246214455Srpaulo		error = ioctl(fd, DIOCGSECTORSIZE, &sectorsize);
247214455Srpaulo	if (error) {
248214455Srpaulo		syslog(LOG_ERR,
249214455Srpaulo		    "couldn't find media and/or sector size of %s: %m", device);
250214455Srpaulo		goto closefd;
251214455Srpaulo	}
252214455Srpaulo
253214455Srpaulo	if (verbose) {
254214455Srpaulo		printf("mediasize = %lld\n", (long long)mediasize);
255214455Srpaulo		printf("sectorsize = %u\n", sectorsize);
256214455Srpaulo	}
257214455Srpaulo
258214455Srpaulo	lasthd = mediasize - sectorsize;
259214455Srpaulo	lseek(fd, lasthd, SEEK_SET);
260214455Srpaulo	error = read(fd, &kdhl, sizeof kdhl);
261214455Srpaulo	if (error != sizeof kdhl) {
262214455Srpaulo		syslog(LOG_ERR,
263214455Srpaulo		    "error reading last dump header at offset %lld in %s: %m",
264214455Srpaulo		    (long long)lasthd, device);
265214455Srpaulo		goto closefd;
266214455Srpaulo	}
267214455Srpaulo	if (memcmp(kdhl.magic, KERNELDUMPMAGIC, sizeof kdhl.magic)) {
268214455Srpaulo		if (verbose)
269214455Srpaulo			printf("magic mismatch on last dump header on %s\n",
270214455Srpaulo			    device);
271214455Srpaulo
272214455Srpaulo		if (force == 0)
273214455Srpaulo			goto closefd;
274214455Srpaulo
275214455Srpaulo		if (memcmp(kdhl.magic, KERNELDUMPMAGIC_CLEARED,
276214455Srpaulo			    sizeof kdhl.magic) == 0) {
277214455Srpaulo			if (verbose)
278214455Srpaulo				printf("forcing magic on %s\n", device);
279214455Srpaulo			memcpy(kdhl.magic, KERNELDUMPMAGIC,
280214455Srpaulo			    sizeof kdhl.magic);
281214455Srpaulo		} else {
282214455Srpaulo			syslog(LOG_ERR, "unable to force dump - bad magic");
283214455Srpaulo			goto closefd;
284214455Srpaulo		}
285214455Srpaulo	}
286214455Srpaulo	if (dtoh32(kdhl.version) != KERNELDUMPVERSION) {
287214455Srpaulo		syslog(LOG_ERR,
288214455Srpaulo		    "unknown version (%d) in last dump header on %s",
289214455Srpaulo		    dtoh32(kdhl.version), device);
290214455Srpaulo		goto closefd;
291214455Srpaulo	}
292214455Srpaulo
293214455Srpaulo	nfound++;
294214455Srpaulo	if (clear)
295214455Srpaulo		goto nuke;
296214455Srpaulo
297214455Srpaulo	if (kerneldump_parity(&kdhl)) {
298214455Srpaulo		syslog(LOG_ERR,
299214455Srpaulo		    "parity error on last dump header on %s", device);
300214455Srpaulo		nerr++;
301214455Srpaulo		goto closefd;
302214455Srpaulo	}
303214455Srpaulo	dumpsize = dtoh64(kdhl.dumplength);
304214455Srpaulo	firsthd = lasthd - dumpsize - sizeof kdhf;
305214455Srpaulo	lseek(fd, firsthd, SEEK_SET);
306214455Srpaulo	error = read(fd, &kdhf, sizeof kdhf);
307214455Srpaulo	if (error != sizeof kdhf) {
308214455Srpaulo		syslog(LOG_ERR,
309214455Srpaulo		    "error reading first dump header at offset %lld in %s: %m",
310214455Srpaulo		    (long long)firsthd, device);
311214455Srpaulo		nerr++;
312		goto closefd;
313	}
314	if (memcmp(&kdhl, &kdhf, sizeof kdhl)) {
315		syslog(LOG_ERR,
316		    "first and last dump headers disagree on %s", device);
317		nerr++;
318		goto closefd;
319	}
320
321	if (kdhl.panicstring[0])
322		syslog(LOG_ALERT, "reboot after panic: %s", kdhl.panicstring);
323	else
324		syslog(LOG_ALERT, "reboot");
325
326	if (verbose)
327		printf("Checking for available free space\n");
328	if (!check_space(savedir, dumpsize)) {
329		nerr++;
330		goto closefd;
331	}
332
333	bounds = getbounds();
334
335	sprintf(buf, "info.%d", bounds);
336
337	/*
338	 * Create or overwrite any existing files.
339	 */
340	fdinfo = open(buf, O_WRONLY | O_CREAT | O_TRUNC, 0600);
341	if (fdinfo < 0) {
342		syslog(LOG_ERR, "%s: %m", buf);
343		nerr++;
344		goto closefd;
345	}
346	oumask = umask(S_IRWXG|S_IRWXO); /* Restrict access to the core file.*/
347	if (compress) {
348		sprintf(buf, "vmcore.%d.gz", bounds);
349		fp = zopen(buf, "w");
350	} else {
351		sprintf(buf, "vmcore.%d", bounds);
352		fp = fopen(buf, "w");
353	}
354	if (fp == NULL) {
355		syslog(LOG_ERR, "%s: %m", buf);
356		close(fdinfo);
357		nerr++;
358		goto closefd;
359	}
360	(void)umask(oumask);
361
362	info = fdopen(fdinfo, "w");
363
364	if (verbose)
365		printheader(stdout, &kdhl, device, bounds);
366
367	printheader(info, &kdhl, device, bounds);
368	fclose(info);
369
370	syslog(LOG_NOTICE, "writing %score to %s",
371	    compress ? "compressed " : "", buf);
372
373	while (dumpsize > 0) {
374		wl = sizeof(buf);
375		if (wl > dumpsize)
376			wl = dumpsize;
377		nr = read(fd, buf, wl);
378		if (nr != wl) {
379			if (nr == 0)
380				syslog(LOG_WARNING,
381				    "WARNING: EOF on dump device");
382			else
383				syslog(LOG_ERR, "read error on %s: %m", device);
384			nerr++;
385			goto closeall;
386		}
387		if (compress) {
388			nw = fwrite(buf, 1, wl, fp);
389		} else {
390			for (nw = 0; nw < nr; nw = he) {
391			    /* find a contiguous block of zeroes */
392			    for (hs = nw; hs < nr; hs += BLOCKSIZE) {
393				for (he = hs; he < nr && buf[he] == 0; ++he)
394				    /* nothing */ ;
395				/* is the hole long enough to matter? */
396				if (he >= hs + BLOCKSIZE)
397				    break;
398			    }
399
400			    /* back down to a block boundary */
401			    he &= BLOCKMASK;
402
403			    /*
404			     * 1) Don't go beyond the end of the buffer.
405			     * 2) If the end of the buffer is less than
406			     *    BLOCKSIZE bytes away, we're at the end
407			     *    of the file, so just grab what's left.
408			     */
409			    if (hs + BLOCKSIZE > nr)
410				hs = he = nr;
411
412			    /*
413			     * At this point, we have a partial ordering:
414			     *     nw <= hs <= he <= nr
415			     * If hs > nw, buf[nw..hs] contains non-zero data.
416			     * If he > hs, buf[hs..he] is all zeroes.
417			     */
418			    if (hs > nw)
419				if (fwrite(buf + nw, hs - nw, 1, fp) != 1)
420				    break;
421			    if (he > hs)
422				if (fseek(fp, he - hs, SEEK_CUR) == -1)
423				    break;
424			}
425		}
426		if (nw != wl) {
427			syslog(LOG_ERR,
428			    "write error on vmcore.%d file: %m", bounds);
429			syslog(LOG_WARNING,
430			    "WARNING: vmcore may be incomplete");
431			nerr++;
432			goto closeall;
433		}
434		if (verbose) {
435			dmpcnt += wl;
436			printf("%llu\r", (unsigned long long)dmpcnt);
437			fflush(stdout);
438		}
439		dumpsize -= wl;
440	}
441	if (verbose)
442		printf("\n");
443
444	if (fclose(fp) < 0) {
445		syslog(LOG_ERR, "error on vmcore.%d: %m", bounds);
446		nerr++;
447		goto closeall;
448	}
449	nsaved++;
450
451	if (verbose)
452		printf("dump saved\n");
453
454nuke:
455	if (clear || !keep) {
456		if (verbose)
457			printf("clearing dump header\n");
458		memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof kdhl.magic);
459		lseek(fd, lasthd, SEEK_SET);
460		error = write(fd, &kdhl, sizeof kdhl);
461		if (error != sizeof kdhl)
462			syslog(LOG_ERR,
463			    "error while clearing the dump header: %m");
464	}
465	close(fd);
466	return;
467
468closeall:
469	fclose(fp);
470
471closefd:
472	close(fd);
473}
474
475static void
476usage(void)
477{
478	fprintf(stderr, "usage: savecore [-cfkv] [directory [device...]]\n");
479	exit (1);
480}
481
482int
483main(int argc, char **argv)
484{
485	int i, ch, error;
486	struct fstab *fsp;
487	char *savedir;
488
489	openlog("savecore", LOG_PERROR, LOG_DAEMON);
490
491	savedir = strdup(".");
492	if (savedir == NULL) {
493		syslog(LOG_ERR, "Cannot allocate memory");
494		exit(1);
495	}
496	while ((ch = getopt(argc, argv, "cdfkN:vz")) != -1)
497		switch(ch) {
498		case 'c':
499			clear = 1;
500			break;
501		case 'k':
502			keep = 1;
503			break;
504		case 'v':
505			verbose = 1;
506			break;
507		case 'f':
508			force = 1;
509			break;
510		case 'z':
511			compress = 1;
512			break;
513		case 'd':	/* Obsolete */
514		case 'N':
515		case '?':
516		default:
517			usage();
518		}
519	argc -= optind;
520	argv += optind;
521	if (argc >= 1) {
522		error = chdir(argv[0]);
523		if (error) {
524			syslog(LOG_ERR, "chdir(%s): %m", argv[0]);
525			exit(1);
526		}
527		savedir = argv[0];
528		argc--;
529		argv++;
530	}
531	if (argc == 0) {
532		for (;;) {
533			fsp = getfsent();
534			if (fsp == NULL)
535				break;
536			if (strcmp(fsp->fs_vfstype, "swap") &&
537			    strcmp(fsp->fs_vfstype, "dump"))
538				continue;
539			DoFile(savedir, fsp->fs_spec);
540		}
541	} else {
542		for (i = 0; i < argc; i++)
543			DoFile(savedir, argv[i]);
544	}
545
546	/* Emit minimal output. */
547	if (nfound == 0)
548		syslog(LOG_WARNING, "no dumps found");
549	else if (nsaved == 0) {
550		if (nerr != 0)
551			syslog(LOG_WARNING, "unsaved dumps found but not saved");
552		else
553			syslog(LOG_WARNING, "no unsaved dumps found");
554	}
555
556	return (0);
557}
558