bsdlabel.c revision 127044
155714Skris/*
255714Skris * Copyright (c) 1994, 1995 Gordon W. Ross
355714Skris * Copyright (c) 1994 Theo de Raadt
455714Skris * All rights reserved.
555714Skris * Copyright (c) 1987, 1993
655714Skris *	The Regents of the University of California.  All rights reserved.
755714Skris *
8296341Sdelphij * This code is derived from software contributed to Berkeley by
955714Skris * Symmetric Computer Systems.
1055714Skris *
1155714Skris * Redistribution and use in source and binary forms, with or without
1255714Skris * modification, are permitted provided that the following conditions
1355714Skris * are met:
1455714Skris * 1. Redistributions of source code must retain the above copyright
15296341Sdelphij *    notice, this list of conditions and the following disclaimer.
1655714Skris * 2. Redistributions in binary form must reproduce the above copyright
1755714Skris *    notice, this list of conditions and the following disclaimer in the
1855714Skris *    documentation and/or other materials provided with the distribution.
1955714Skris * 3. All advertising materials mentioning features or use of this software
2055714Skris *    must display the following acknowledgement:
2155714Skris *	This product includes software developed by the University of
22296341Sdelphij *	California, Berkeley and its contributors.
2355714Skris *      This product includes software developed by Theo de Raadt.
2455714Skris * 4. Neither the name of the University nor the names of its contributors
2555714Skris *    may be used to endorse or promote products derived from this software
2655714Skris *    without specific prior written permission.
2755714Skris *
2855714Skris * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2955714Skris * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
3055714Skris * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
3155714Skris * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
3255714Skris * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3355714Skris * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
3455714Skris * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3555714Skris * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3655714Skris * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37296341Sdelphij * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3855714Skris * SUCH DAMAGE.
3955714Skris *
40296341Sdelphij *	from: $NetBSD: disksubr.c,v 1.13 2000/12/17 22:39:18 pk $
4155714Skris */
4255714Skris
4355714Skris#if 0
4455714Skris#ifndef lint
4555714Skrisstatic const char copyright[] =
4655714Skris"@(#) Copyright (c) 1987, 1993\n\
4755714Skris	The Regents of the University of California.  All rights reserved.\n";
4855714Skris#endif /* not lint */
4955714Skris
5055714Skris#ifndef lint
5155714Skrisstatic char sccsid[] = "@(#)disklabel.c	8.2 (Berkeley) 1/7/94";
52296341Sdelphij/* from static char sccsid[] = "@(#)disklabel.c	1.2 (Symmetric) 11/28/85"; */
5355714Skris#endif /* not lint */
5455714Skris#endif
5555714Skris#include <sys/cdefs.h>
5655714Skris__FBSDID("$FreeBSD: head/sbin/bsdlabel/bsdlabel.c 127044 2004-03-15 23:10:34Z jhb $");
5755714Skris
5855714Skris#include <sys/param.h>
5955714Skris#include <stdint.h>
6055714Skris#include <sys/file.h>
6155714Skris#include <sys/stat.h>
6255714Skris#include <sys/wait.h>
63296341Sdelphij#include <sys/disk.h>
64296341Sdelphij#define DKTYPENAMES
65296341Sdelphij#define FSTYPENAMES
6655714Skris#include <sys/disklabel.h>
67296341Sdelphij
68296341Sdelphij#include <unistd.h>
69296341Sdelphij#include <string.h>
70296341Sdelphij#include <stdio.h>
71296341Sdelphij#include <libgeom.h>
72296341Sdelphij#include <stdlib.h>
73296341Sdelphij#include <signal.h>
74296341Sdelphij#include <stdarg.h>
75296341Sdelphij#include <ctype.h>
76296341Sdelphij#include <err.h>
77296341Sdelphij#include <errno.h>
78296341Sdelphij
79296341Sdelphij#include "pathnames.h"
80296341Sdelphij
81296341Sdelphijstatic void	makelabel(const char *, struct disklabel *);
82296341Sdelphijstatic int	writelabel(void);
83296341Sdelphijstatic int readlabel(int flag);
84296341Sdelphijstatic void	display(FILE *, const struct disklabel *);
85296341Sdelphijstatic int edit(void);
86296341Sdelphijstatic int	editit(void);
87296341Sdelphijstatic void	fixlabel(struct disklabel *);
88296341Sdelphijstatic char	*skip(char *);
89296341Sdelphijstatic char	*word(char *);
90296341Sdelphijstatic int	getasciilabel(FILE *, struct disklabel *);
9155714Skrisstatic int	getasciipartspec(char *, struct disklabel *, int, int);
92296341Sdelphijstatic int	checklabel(struct disklabel *);
9355714Skrisstatic void	usage(void);
94296341Sdelphijstatic struct disklabel *getvirginlabel(void);
95
96#define	DEFEDITOR	_PATH_VI
97
98static char	*dkname;
99static char	*specname;
100static char	tmpfil[] = PATH_TMPFILE;
101
102static struct	disklabel lab;
103static u_char	bootarea[BBSIZE];
104static off_t	mediasize;
105static u_int	secsize;
106static char	blank[] = "";
107static char	unknown[] = "unknown";
108
109#define MAX_PART ('z')
110#define MAX_NUM_PARTS (1 + MAX_PART - 'a')
111static char    part_size_type[MAX_NUM_PARTS];
112static char    part_offset_type[MAX_NUM_PARTS];
113static int     part_set[MAX_NUM_PARTS];
114
115static int	installboot;	/* non-zero if we should install a boot program */
116static int	allfields;	/* present all fields in edit */
117static char const *xxboot;	/* primary boot */
118
119static off_t mbroffset;
120#ifndef LABELSECTOR
121#define LABELSECTOR -1
122#endif
123#ifndef LABELOFFSET
124#define LABELOFFSET -1
125#endif
126static int labelsoffset = LABELSECTOR;
127static int labeloffset = LABELOFFSET;
128static int bbsize = BBSIZE;
129static int alphacksum =
130#if defined(__alpha__)
131	1;
132#else
133	0;
134#endif
135
136enum	{
137	UNSPEC, EDIT, READ, RESTORE, WRITE, WRITEBOOT
138} op = UNSPEC;
139
140
141static int	disable_write;   /* set to disable writing to disk label */
142
143int
144main(int argc, char *argv[])
145{
146	FILE *t;
147	int ch, error = 0;
148	char const *name = 0;
149
150	while ((ch = getopt(argc, argv, "ABb:em:nRrs:w")) != -1)
151		switch (ch) {
152			case 'A':
153				allfields = 1;
154				break;
155			case 'B':
156				++installboot;
157				break;
158			case 'b':
159				xxboot = optarg;
160				break;
161			case 'm':
162				if (!strcmp(optarg, "i386") ||
163				    !strcmp(optarg, "amd64") ||
164				    !strcmp(optarg, "ia64") ||
165				    !strcmp(optarg, "pc98")) {
166					labelsoffset = 1;
167					labeloffset = 0;
168					bbsize = 8192;
169					alphacksum = 0;
170				} else if (!strcmp(optarg, "alpha")) {
171					labelsoffset = 0;
172					labeloffset = 64;
173					bbsize = 8192;
174					alphacksum = 1;
175				} else {
176					errx(1, "Unsupported architecture");
177				}
178				break;
179			case 'n':
180				disable_write = 1;
181				break;
182			case 'R':
183				if (op != UNSPEC)
184					usage();
185				op = RESTORE;
186				break;
187			case 'e':
188				if (op != UNSPEC)
189					usage();
190				op = EDIT;
191				break;
192			case 'r':
193				/*
194				 * We accept and ignode -r for compatibility with
195				 * historically disklabel usage.
196				 */
197				break;
198			case 'w':
199				if (op != UNSPEC)
200					usage();
201				op = WRITE;
202				break;
203			case '?':
204			default:
205				usage();
206		}
207	argc -= optind;
208	argv += optind;
209
210	if (argc < 1)
211		usage();
212	if (labelsoffset < 0 || labeloffset < 0)
213		errx(1, "a -m <architecture> option must be specified");
214
215	/* Figure out the names of the thing we're working on */
216	if (argv[0][0] != '/') {
217		dkname = argv[0];
218		asprintf(&specname, "%s%s", _PATH_DEV, argv[0]);
219	} else {
220		dkname = strrchr(argv[0], '/');
221		dkname++;
222		specname = argv[0];
223	}
224
225	if (installboot && op == UNSPEC)
226		op = WRITEBOOT;
227	else if (op == UNSPEC)
228		op = READ;
229
230	switch(op) {
231
232	case UNSPEC:
233		break;
234
235	case EDIT:
236		if (argc != 1)
237			usage();
238		readlabel(1);
239		fixlabel(&lab);
240		error = edit();
241		break;
242
243	case READ:
244		if (argc != 1)
245			usage();
246		readlabel(1);
247		display(stdout, NULL);
248		error = checklabel(NULL);
249		break;
250
251	case RESTORE:
252		if (argc != 2)
253			usage();
254		if (!(t = fopen(argv[1], "r")))
255			err(4, "fopen %s", argv[1]);
256		readlabel(0);
257		if (!getasciilabel(t, &lab))
258			exit(1);
259		error = writelabel();
260		break;
261
262	case WRITE:
263		if (argc == 2)
264			name = argv[1];
265		else if (argc == 1)
266			name = "auto";
267		else
268			usage();
269		readlabel(0);
270		makelabel(name, &lab);
271		fixlabel(&lab);
272		if (checklabel(NULL) == 0)
273			error = writelabel();
274		break;
275
276	case WRITEBOOT:
277
278		readlabel(1);
279		fixlabel(&lab);
280		if (argc == 2)
281			makelabel(argv[1], &lab);
282		if (checklabel(NULL) == 0)
283			error = writelabel();
284		break;
285	}
286	exit(error);
287}
288
289static void
290fixlabel(struct disklabel *lp)
291{
292	struct partition *dp;
293	int i;
294
295	for (i = 0; i < MAXPARTITIONS; i++) {
296		if (i == RAW_PART)
297			continue;
298		if (lp->d_partitions[i].p_size)
299			return;
300	}
301
302	dp = &lp->d_partitions[0];
303	dp->p_offset = BBSIZE / secsize;
304	dp->p_size = lp->d_secperunit - dp->p_offset;
305}
306
307/*
308 * Construct a prototype disklabel from /etc/disktab.
309 */
310static void
311makelabel(const char *type, struct disklabel *lp)
312{
313	struct disklabel *dp;
314
315	if (strcmp(type, "auto") == 0)
316		dp = getvirginlabel();
317	else
318		dp = getdiskbyname(type);
319	if (dp == NULL)
320		errx(1, "%s: unknown disk type", type);
321	*lp = *dp;
322	bzero(lp->d_packname, sizeof(lp->d_packname));
323}
324
325static void
326readboot(void)
327{
328	int fd, i;
329	struct stat st;
330	uint64_t *p;
331
332	if (xxboot == NULL)
333		xxboot = "/boot/boot";
334	fd = open(xxboot, O_RDONLY);
335	if (fd < 0)
336		err(1, "cannot open %s", xxboot);
337	fstat(fd, &st);
338	if (alphacksum && st.st_size <= BBSIZE - 512) {
339		i = read(fd, bootarea + 512, st.st_size);
340		if (i != st.st_size)
341			err(1, "read error %s", xxboot);
342
343		/*
344		 * Set the location and length so SRM can find the
345		 * boot blocks.
346		 */
347		p = (uint64_t *)bootarea;
348		p[60] = (st.st_size + secsize - 1) / secsize;
349		p[61] = 1;
350		p[62] = 0;
351		return;
352	} else if ((!alphacksum) && st.st_size <= BBSIZE) {
353		i = read(fd, bootarea, st.st_size);
354		if (i != st.st_size)
355			err(1, "read error %s", xxboot);
356		return;
357	}
358	errx(1, "boot code %s is wrong size", xxboot);
359}
360
361static int
362writelabel(void)
363{
364	uint64_t *p, sum;
365	int i, fd;
366	struct gctl_req *grq;
367	char const *errstr;
368	struct disklabel *lp = &lab;
369
370	if (disable_write) {
371		warnx("write to disk label supressed - label was as follows:");
372		display(stdout, NULL);
373		return (0);
374	}
375
376	lp->d_magic = DISKMAGIC;
377	lp->d_magic2 = DISKMAGIC;
378	lp->d_checksum = 0;
379	lp->d_checksum = dkcksum(lp);
380	if (installboot)
381		readboot();
382	for (i = 0; i < lab.d_npartitions; i++)
383		if (lab.d_partitions[i].p_size)
384			lab.d_partitions[i].p_offset += mbroffset;
385	bsd_disklabel_le_enc(bootarea + labeloffset + labelsoffset * secsize,
386	    lp);
387	if (alphacksum) {
388		/* Generate the bootblock checksum for the SRM console.  */
389		for (p = (uint64_t *)bootarea, i = 0, sum = 0; i < 63; i++)
390			sum += p[i];
391		p[63] = sum;
392	}
393
394	fd = open(specname, O_RDWR);
395	if (fd < 0) {
396		grq = gctl_get_handle();
397		gctl_ro_param(grq, "verb", -1, "write label");
398		gctl_ro_param(grq, "class", -1, "BSD");
399		gctl_ro_param(grq, "geom", -1, dkname);
400		gctl_ro_param(grq, "label", 148+16*8,
401			bootarea + labeloffset + labelsoffset * secsize);
402		errstr = gctl_issue(grq);
403		if (errstr != NULL) {
404			warnx("%s", errstr);
405			gctl_free(grq);
406			return(1);
407		}
408		gctl_free(grq);
409		if (installboot) {
410			grq = gctl_get_handle();
411			gctl_ro_param(grq, "verb", -1, "write bootcode");
412			gctl_ro_param(grq, "class", -1, "BSD");
413			gctl_ro_param(grq, "geom", -1, dkname);
414			gctl_ro_param(grq, "bootcode", BBSIZE, bootarea);
415			errstr = gctl_issue(grq);
416			if (errstr != NULL) {
417				warnx("%s", errstr);
418				gctl_free(grq);
419				return (1);
420			}
421			gctl_free(grq);
422		}
423	} else {
424		if (write(fd, bootarea, bbsize) != bbsize) {
425			warn("write %s", specname);
426			close (fd);
427			return (1);
428		}
429		close (fd);
430	}
431	return (0);
432}
433
434/*
435 * Fetch disklabel for disk.
436 * Use ioctl to get label unless -r flag is given.
437 */
438static int
439readlabel(int flag)
440{
441	int f, i;
442	int error;
443	struct gctl_req *grq;
444	char const *errstr;
445
446	f = open(specname, O_RDONLY);
447	if (f < 0)
448		err(1, specname);
449	/* New world order */
450	if ((ioctl(f, DIOCGMEDIASIZE, &mediasize) != 0) ||
451	    (ioctl(f, DIOCGSECTORSIZE, &secsize) != 0)) {
452		err(4, "cannot get disk geometry");
453	}
454	(void)lseek(f, (off_t)0, SEEK_SET);
455	if (read(f, bootarea, BBSIZE) != BBSIZE)
456		err(4, "%s read", specname);
457	close (f);
458	error = bsd_disklabel_le_dec(
459	    bootarea + (labeloffset + labelsoffset * secsize),
460	    &lab, MAXPARTITIONS);
461	if (flag && error)
462		errx(1, "%s: no valid label found", specname);
463
464	grq = gctl_get_handle();
465	gctl_ro_param(grq, "verb", -1, "read mbroffset");
466	gctl_ro_param(grq, "class", -1, "BSD");
467	gctl_ro_param(grq, "geom", -1, dkname);
468	gctl_rw_param(grq, "mbroffset", sizeof(mbroffset), &mbroffset);
469	errstr = gctl_issue(grq);
470	if (errstr != NULL) {
471		mbroffset = 0;
472		gctl_free(grq);
473		return (error);
474	}
475	mbroffset /= lab.d_secsize;
476	if (lab.d_partitions[RAW_PART].p_offset == mbroffset)
477		for (i = 0; i < lab.d_npartitions; i++)
478			if (lab.d_partitions[i].p_size)
479				lab.d_partitions[i].p_offset -= mbroffset;
480	return (error);
481}
482
483
484static void
485display(FILE *f, const struct disklabel *lp)
486{
487	int i, j;
488	const struct partition *pp;
489
490	if (lp == NULL)
491		lp = &lab;
492
493	fprintf(f, "# %s:\n", specname);
494	if (allfields) {
495		if (lp->d_type < DKMAXTYPES)
496			fprintf(f, "type: %s\n", dktypenames[lp->d_type]);
497		else
498			fprintf(f, "type: %u\n", lp->d_type);
499		fprintf(f, "disk: %.*s\n", (int)sizeof(lp->d_typename),
500			lp->d_typename);
501		fprintf(f, "label: %.*s\n", (int)sizeof(lp->d_packname),
502			lp->d_packname);
503		fprintf(f, "flags:");
504		if (lp->d_flags & D_REMOVABLE)
505			fprintf(f, " removeable");
506		if (lp->d_flags & D_ECC)
507			fprintf(f, " ecc");
508		if (lp->d_flags & D_BADSECT)
509			fprintf(f, " badsect");
510		fprintf(f, "\n");
511		fprintf(f, "bytes/sector: %lu\n", (u_long)lp->d_secsize);
512		fprintf(f, "sectors/track: %lu\n", (u_long)lp->d_nsectors);
513		fprintf(f, "tracks/cylinder: %lu\n", (u_long)lp->d_ntracks);
514		fprintf(f, "sectors/cylinder: %lu\n", (u_long)lp->d_secpercyl);
515		fprintf(f, "cylinders: %lu\n", (u_long)lp->d_ncylinders);
516		fprintf(f, "sectors/unit: %lu\n", (u_long)lp->d_secperunit);
517		fprintf(f, "rpm: %u\n", lp->d_rpm);
518		fprintf(f, "interleave: %u\n", lp->d_interleave);
519		fprintf(f, "trackskew: %u\n", lp->d_trackskew);
520		fprintf(f, "cylinderskew: %u\n", lp->d_cylskew);
521		fprintf(f, "headswitch: %lu\t\t# milliseconds\n",
522		    (u_long)lp->d_headswitch);
523		fprintf(f, "track-to-track seek: %ld\t# milliseconds\n",
524		    (u_long)lp->d_trkseek);
525		fprintf(f, "drivedata: ");
526		for (i = NDDATA - 1; i >= 0; i--)
527			if (lp->d_drivedata[i])
528				break;
529		if (i < 0)
530			i = 0;
531		for (j = 0; j <= i; j++)
532			fprintf(f, "%lu ", (u_long)lp->d_drivedata[j]);
533		fprintf(f, "\n\n");
534	}
535	fprintf(f, "%u partitions:\n", lp->d_npartitions);
536	fprintf(f,
537	    "#        size   offset    fstype   [fsize bsize bps/cpg]\n");
538	pp = lp->d_partitions;
539	for (i = 0; i < lp->d_npartitions; i++, pp++) {
540		if (pp->p_size) {
541			fprintf(f, "  %c: %8lu %8lu  ", 'a' + i,
542			   (u_long)pp->p_size, (u_long)pp->p_offset);
543			if (pp->p_fstype < FSMAXTYPES)
544				fprintf(f, "%8.8s", fstypenames[pp->p_fstype]);
545			else
546				fprintf(f, "%8d", pp->p_fstype);
547			switch (pp->p_fstype) {
548
549			case FS_UNUSED:				/* XXX */
550				fprintf(f, "    %5lu %5lu %5.5s ",
551				    (u_long)pp->p_fsize,
552				    (u_long)(pp->p_fsize * pp->p_frag), "");
553				break;
554
555			case FS_BSDFFS:
556				fprintf(f, "    %5lu %5lu %5u ",
557				    (u_long)pp->p_fsize,
558				    (u_long)(pp->p_fsize * pp->p_frag),
559				    pp->p_cpg);
560				break;
561
562			case FS_BSDLFS:
563				fprintf(f, "    %5lu %5lu %5d",
564				    (u_long)pp->p_fsize,
565				    (u_long)(pp->p_fsize * pp->p_frag),
566				    pp->p_cpg);
567				break;
568
569			default:
570				fprintf(f, "%20.20s", "");
571				break;
572			}
573			if (i == RAW_PART) {
574				fprintf(f, "  # \"raw\" part, don't edit");
575			}
576			fprintf(f, "\n");
577		}
578	}
579	fflush(f);
580}
581
582static int
583edit(void)
584{
585	int c, fd;
586	struct disklabel label;
587	FILE *fp;
588
589	if ((fd = mkstemp(tmpfil)) == -1 ||
590	    (fp = fdopen(fd, "w")) == NULL) {
591		warnx("can't create %s", tmpfil);
592		return (1);
593	}
594	display(fp, NULL);
595	fclose(fp);
596	for (;;) {
597		if (!editit())
598			break;
599		fp = fopen(tmpfil, "r");
600		if (fp == NULL) {
601			warnx("can't reopen %s for reading", tmpfil);
602			break;
603		}
604		bzero((char *)&label, sizeof(label));
605		c = getasciilabel(fp, &label);
606		fclose(fp);
607		if (c) {
608			lab = label;
609			if (writelabel() == 0) {
610				(void) unlink(tmpfil);
611				return (0);
612			}
613		}
614		printf("re-edit the label? [y]: ");
615		fflush(stdout);
616		c = getchar();
617		if (c != EOF && c != (int)'\n')
618			while (getchar() != (int)'\n')
619				;
620		if  (c == (int)'n')
621			break;
622	}
623	(void) unlink(tmpfil);
624	return (1);
625}
626
627static int
628editit(void)
629{
630	int pid, xpid;
631	int locstat, omask;
632	const char *ed;
633
634	omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
635	while ((pid = fork()) < 0) {
636		if (errno == EPROCLIM) {
637			warnx("you have too many processes");
638			return(0);
639		}
640		if (errno != EAGAIN) {
641			warn("fork");
642			return(0);
643		}
644		sleep(1);
645	}
646	if (pid == 0) {
647		sigsetmask(omask);
648		setgid(getgid());
649		setuid(getuid());
650		if ((ed = getenv("EDITOR")) == (char *)0)
651			ed = DEFEDITOR;
652		execlp(ed, ed, tmpfil, (char *)0);
653		err(1, "%s", ed);
654	}
655	while ((xpid = wait(&locstat)) >= 0)
656		if (xpid == pid)
657			break;
658	sigsetmask(omask);
659	return(!locstat);
660}
661
662static char *
663skip(char *cp)
664{
665
666	while (*cp != '\0' && isspace(*cp))
667		cp++;
668	if (*cp == '\0' || *cp == '#')
669		return (NULL);
670	return (cp);
671}
672
673static char *
674word(char *cp)
675{
676	char c;
677
678	while (*cp != '\0' && !isspace(*cp) && *cp != '#')
679		cp++;
680	if ((c = *cp) != '\0') {
681		*cp++ = '\0';
682		if (c != '#')
683			return (skip(cp));
684	}
685	return (NULL);
686}
687
688/*
689 * Read an ascii label in from fd f,
690 * in the same format as that put out by display(),
691 * and fill in lp.
692 */
693static int
694getasciilabel(FILE *f, struct disklabel *lp)
695{
696	char *cp;
697	const char **cpp;
698	u_int part;
699	char *tp, line[BUFSIZ];
700	u_long v;
701	int lineno = 0, errors = 0;
702	int i;
703
704	makelabel("auto", lp);
705	bzero(&part_set, sizeof(part_set));
706	bzero(&part_size_type, sizeof(part_size_type));
707	bzero(&part_offset_type, sizeof(part_offset_type));
708	lp->d_bbsize = BBSIZE;				/* XXX */
709	lp->d_sbsize = 0;				/* XXX */
710	while (fgets(line, sizeof(line) - 1, f)) {
711		lineno++;
712		if ((cp = index(line,'\n')) != 0)
713			*cp = '\0';
714		cp = skip(line);
715		if (cp == NULL)
716			continue;
717		tp = index(cp, ':');
718		if (tp == NULL) {
719			fprintf(stderr, "line %d: syntax error\n", lineno);
720			errors++;
721			continue;
722		}
723		*tp++ = '\0', tp = skip(tp);
724		if (!strcmp(cp, "type")) {
725			if (tp == NULL)
726				tp = unknown;
727			cpp = dktypenames;
728			for (; cpp < &dktypenames[DKMAXTYPES]; cpp++)
729				if (*cpp && !strcmp(*cpp, tp)) {
730					lp->d_type = cpp - dktypenames;
731					break;
732				}
733			if (cpp < &dktypenames[DKMAXTYPES])
734				continue;
735			v = strtoul(tp, NULL, 10);
736			if (v >= DKMAXTYPES)
737				fprintf(stderr, "line %d:%s %lu\n", lineno,
738				    "Warning, unknown disk type", v);
739			lp->d_type = v;
740			continue;
741		}
742		if (!strcmp(cp, "flags")) {
743			for (v = 0; (cp = tp) && *cp != '\0';) {
744				tp = word(cp);
745				if (!strcmp(cp, "removeable"))
746					v |= D_REMOVABLE;
747				else if (!strcmp(cp, "ecc"))
748					v |= D_ECC;
749				else if (!strcmp(cp, "badsect"))
750					v |= D_BADSECT;
751				else {
752					fprintf(stderr,
753					    "line %d: %s: bad flag\n",
754					    lineno, cp);
755					errors++;
756				}
757			}
758			lp->d_flags = v;
759			continue;
760		}
761		if (!strcmp(cp, "drivedata")) {
762			for (i = 0; (cp = tp) && *cp != '\0' && i < NDDATA;) {
763				lp->d_drivedata[i++] = strtoul(cp, NULL, 10);
764				tp = word(cp);
765			}
766			continue;
767		}
768		if (sscanf(cp, "%lu partitions", &v) == 1) {
769			if (v == 0 || v > MAXPARTITIONS) {
770				fprintf(stderr,
771				    "line %d: bad # of partitions\n", lineno);
772				lp->d_npartitions = MAXPARTITIONS;
773				errors++;
774			} else
775				lp->d_npartitions = v;
776			continue;
777		}
778		if (tp == NULL)
779			tp = blank;
780		if (!strcmp(cp, "disk")) {
781			strncpy(lp->d_typename, tp, sizeof (lp->d_typename));
782			continue;
783		}
784		if (!strcmp(cp, "label")) {
785			strncpy(lp->d_packname, tp, sizeof (lp->d_packname));
786			continue;
787		}
788		if (!strcmp(cp, "bytes/sector")) {
789			v = strtoul(tp, NULL, 10);
790			if (v == 0 || (v % DEV_BSIZE) != 0) {
791				fprintf(stderr,
792				    "line %d: %s: bad sector size\n",
793				    lineno, tp);
794				errors++;
795			} else
796				lp->d_secsize = v;
797			continue;
798		}
799		if (!strcmp(cp, "sectors/track")) {
800			v = strtoul(tp, NULL, 10);
801#if (ULONG_MAX != 0xffffffffUL)
802			if (v == 0 || v > 0xffffffff) {
803#else
804			if (v == 0) {
805#endif
806				fprintf(stderr, "line %d: %s: bad %s\n",
807				    lineno, tp, cp);
808				errors++;
809			} else
810				lp->d_nsectors = v;
811			continue;
812		}
813		if (!strcmp(cp, "sectors/cylinder")) {
814			v = strtoul(tp, NULL, 10);
815			if (v == 0) {
816				fprintf(stderr, "line %d: %s: bad %s\n",
817				    lineno, tp, cp);
818				errors++;
819			} else
820				lp->d_secpercyl = v;
821			continue;
822		}
823		if (!strcmp(cp, "tracks/cylinder")) {
824			v = strtoul(tp, NULL, 10);
825			if (v == 0) {
826				fprintf(stderr, "line %d: %s: bad %s\n",
827				    lineno, tp, cp);
828				errors++;
829			} else
830				lp->d_ntracks = v;
831			continue;
832		}
833		if (!strcmp(cp, "cylinders")) {
834			v = strtoul(tp, NULL, 10);
835			if (v == 0) {
836				fprintf(stderr, "line %d: %s: bad %s\n",
837				    lineno, tp, cp);
838				errors++;
839			} else
840				lp->d_ncylinders = v;
841			continue;
842		}
843		if (!strcmp(cp, "sectors/unit")) {
844			v = strtoul(tp, NULL, 10);
845			if (v == 0) {
846				fprintf(stderr, "line %d: %s: bad %s\n",
847				    lineno, tp, cp);
848				errors++;
849			} else
850				lp->d_secperunit = v;
851			continue;
852		}
853		if (!strcmp(cp, "rpm")) {
854			v = strtoul(tp, NULL, 10);
855			if (v == 0 || v > USHRT_MAX) {
856				fprintf(stderr, "line %d: %s: bad %s\n",
857				    lineno, tp, cp);
858				errors++;
859			} else
860				lp->d_rpm = v;
861			continue;
862		}
863		if (!strcmp(cp, "interleave")) {
864			v = strtoul(tp, NULL, 10);
865			if (v == 0 || v > USHRT_MAX) {
866				fprintf(stderr, "line %d: %s: bad %s\n",
867				    lineno, tp, cp);
868				errors++;
869			} else
870				lp->d_interleave = v;
871			continue;
872		}
873		if (!strcmp(cp, "trackskew")) {
874			v = strtoul(tp, NULL, 10);
875			if (v > USHRT_MAX) {
876				fprintf(stderr, "line %d: %s: bad %s\n",
877				    lineno, tp, cp);
878				errors++;
879			} else
880				lp->d_trackskew = v;
881			continue;
882		}
883		if (!strcmp(cp, "cylinderskew")) {
884			v = strtoul(tp, NULL, 10);
885			if (v > USHRT_MAX) {
886				fprintf(stderr, "line %d: %s: bad %s\n",
887				    lineno, tp, cp);
888				errors++;
889			} else
890				lp->d_cylskew = v;
891			continue;
892		}
893		if (!strcmp(cp, "headswitch")) {
894			v = strtoul(tp, NULL, 10);
895			lp->d_headswitch = v;
896			continue;
897		}
898		if (!strcmp(cp, "track-to-track seek")) {
899			v = strtoul(tp, NULL, 10);
900			lp->d_trkseek = v;
901			continue;
902		}
903		/* the ':' was removed above */
904		if (*cp < 'a' || *cp > MAX_PART || cp[1] != '\0') {
905			fprintf(stderr,
906			    "line %d: %s: Unknown disklabel field\n", lineno,
907			    cp);
908			errors++;
909			continue;
910		}
911
912		/* Process a partition specification line. */
913		part = *cp - 'a';
914		if (part >= lp->d_npartitions) {
915			fprintf(stderr,
916			    "line %d: partition name out of range a-%c: %s\n",
917			    lineno, 'a' + lp->d_npartitions - 1, cp);
918			errors++;
919			continue;
920		}
921		part_set[part] = 1;
922
923		if (getasciipartspec(tp, lp, part, lineno) != 0) {
924			errors++;
925			break;
926		}
927	}
928	errors += checklabel(lp);
929	return (errors == 0);
930}
931
932#define NXTNUM(n) do { \
933	if (tp == NULL) { \
934		fprintf(stderr, "line %d: too few numeric fields\n", lineno); \
935		return (1); \
936	} else { \
937		cp = tp, tp = word(cp); \
938		(n) = strtoul(cp, NULL, 10); \
939	} \
940} while (0)
941
942/* retain 1 character following number */
943#define NXTWORD(w,n) do { \
944	if (tp == NULL) { \
945		fprintf(stderr, "line %d: too few numeric fields\n", lineno); \
946		return (1); \
947	} else { \
948	        char *tmp; \
949		cp = tp, tp = word(cp); \
950	        (n) = strtoul(cp, &tmp, 10); \
951		if (tmp) (w) = *tmp; \
952	} \
953} while (0)
954
955/*
956 * Read a partition line into partition `part' in the specified disklabel.
957 * Return 0 on success, 1 on failure.
958 */
959static int
960getasciipartspec(char *tp, struct disklabel *lp, int part, int lineno)
961{
962	struct partition *pp;
963	char *cp;
964	const char **cpp;
965	u_long v;
966
967	pp = &lp->d_partitions[part];
968	cp = NULL;
969
970	v = 0;
971	NXTWORD(part_size_type[part],v);
972	if (v == 0 && part_size_type[part] != '*') {
973		fprintf(stderr,
974		    "line %d: %s: bad partition size\n", lineno, cp);
975		return (1);
976	}
977	pp->p_size = v;
978
979	v = 0;
980	NXTWORD(part_offset_type[part],v);
981	if (v == 0 && part_offset_type[part] != '*' &&
982	    part_offset_type[part] != '\0') {
983		fprintf(stderr,
984		    "line %d: %s: bad partition offset\n", lineno, cp);
985		return (1);
986	}
987	pp->p_offset = v;
988	if (tp == NULL) {
989		fprintf(stderr, "line %d: missing file system type\n", lineno);
990		return (1);
991	}
992	cp = tp, tp = word(cp);
993	for (cpp = fstypenames; cpp < &fstypenames[FSMAXTYPES]; cpp++)
994		if (*cpp && !strcmp(*cpp, cp))
995			break;
996	if (*cpp != NULL) {
997		pp->p_fstype = cpp - fstypenames;
998	} else {
999		if (isdigit(*cp))
1000			v = strtoul(cp, NULL, 10);
1001		else
1002			v = FSMAXTYPES;
1003		if (v >= FSMAXTYPES) {
1004			fprintf(stderr,
1005			    "line %d: Warning, unknown file system type %s\n",
1006			    lineno, cp);
1007			v = FS_UNUSED;
1008		}
1009		pp->p_fstype = v;
1010	}
1011
1012	switch (pp->p_fstype) {
1013	case FS_UNUSED:
1014	case FS_BSDFFS:
1015	case FS_BSDLFS:
1016		/* accept defaults for fsize/frag/cpg */
1017		if (tp) {
1018			NXTNUM(pp->p_fsize);
1019			if (pp->p_fsize == 0)
1020				break;
1021			NXTNUM(v);
1022			pp->p_frag = v / pp->p_fsize;
1023			if (tp != NULL)
1024				NXTNUM(pp->p_cpg);
1025		}
1026		/* else default to 0's */
1027		break;
1028	default:
1029		break;
1030	}
1031	return (0);
1032}
1033
1034/*
1035 * Check disklabel for errors and fill in
1036 * derived fields according to supplied values.
1037 */
1038static int
1039checklabel(struct disklabel *lp)
1040{
1041	struct partition *pp;
1042	int i, errors = 0;
1043	char part;
1044	u_long total_size, total_percent, current_offset;
1045	int seen_default_offset;
1046	int hog_part;
1047	int j;
1048	struct partition *pp2;
1049
1050	if (lp == NULL)
1051		lp = &lab;
1052
1053	if (allfields) {
1054
1055		if (lp->d_secsize == 0) {
1056			fprintf(stderr, "sector size 0\n");
1057			return (1);
1058		}
1059		if (lp->d_nsectors == 0) {
1060			fprintf(stderr, "sectors/track 0\n");
1061			return (1);
1062		}
1063		if (lp->d_ntracks == 0) {
1064			fprintf(stderr, "tracks/cylinder 0\n");
1065			return (1);
1066		}
1067		if  (lp->d_ncylinders == 0) {
1068			fprintf(stderr, "cylinders/unit 0\n");
1069			errors++;
1070		}
1071		if (lp->d_rpm == 0)
1072			warnx("revolutions/minute 0");
1073		if (lp->d_secpercyl == 0)
1074			lp->d_secpercyl = lp->d_nsectors * lp->d_ntracks;
1075		if (lp->d_secperunit == 0)
1076			lp->d_secperunit = lp->d_secpercyl * lp->d_ncylinders;
1077		if (lp->d_bbsize == 0) {
1078			fprintf(stderr, "boot block size 0\n");
1079			errors++;
1080		} else if (lp->d_bbsize % lp->d_secsize)
1081			warnx("boot block size %% sector-size != 0");
1082		if (lp->d_npartitions > MAXPARTITIONS)
1083			warnx("number of partitions (%lu) > MAXPARTITIONS (%d)",
1084			    (u_long)lp->d_npartitions, MAXPARTITIONS);
1085	} else {
1086		struct disklabel *vl;
1087
1088		vl = getvirginlabel();
1089		lp->d_secsize = vl->d_secsize;
1090		lp->d_nsectors = vl->d_nsectors;
1091		lp->d_ntracks = vl->d_ntracks;
1092		lp->d_ncylinders = vl->d_ncylinders;
1093		lp->d_rpm = vl->d_rpm;
1094		lp->d_interleave = vl->d_interleave;
1095		lp->d_secpercyl = vl->d_secpercyl;
1096		lp->d_secperunit = vl->d_secperunit;
1097		lp->d_bbsize = vl->d_bbsize;
1098		lp->d_npartitions = vl->d_npartitions;
1099	}
1100
1101
1102	/* first allocate space to the partitions, then offsets */
1103	total_size = 0; /* in sectors */
1104	total_percent = 0; /* in percent */
1105	hog_part = -1;
1106	/* find all fixed partitions */
1107	for (i = 0; i < lp->d_npartitions; i++) {
1108		pp = &lp->d_partitions[i];
1109		if (part_set[i]) {
1110			if (part_size_type[i] == '*') {
1111				if (i == RAW_PART) {
1112					pp->p_size = lp->d_secperunit;
1113				} else {
1114					if (hog_part != -1)
1115						warnx("Too many '*' partitions (%c and %c)",
1116						    hog_part + 'a',i + 'a');
1117					else
1118						hog_part = i;
1119				}
1120			} else {
1121				off_t size;
1122
1123				size = pp->p_size;
1124				switch (part_size_type[i]) {
1125				case '%':
1126					total_percent += size;
1127					break;
1128				case 'k':
1129				case 'K':
1130					size *= 1024ULL;
1131					break;
1132				case 'm':
1133				case 'M':
1134					size *= 1024ULL * 1024ULL;
1135					break;
1136				case 'g':
1137				case 'G':
1138					size *= 1024ULL * 1024ULL * 1024ULL;
1139					break;
1140				case '\0':
1141					break;
1142				default:
1143					warnx("unknown size specifier '%c' (K/M/G are valid)",part_size_type[i]);
1144					break;
1145				}
1146				/* don't count %'s yet */
1147				if (part_size_type[i] != '%') {
1148					/*
1149					 * for all not in sectors, convert to
1150					 * sectors
1151					 */
1152					if (part_size_type[i] != '\0') {
1153						if (size % lp->d_secsize != 0)
1154							warnx("partition %c not an integer number of sectors",
1155							    i + 'a');
1156						size /= lp->d_secsize;
1157						pp->p_size = size;
1158					}
1159					/* else already in sectors */
1160					if (i != RAW_PART)
1161						total_size += size;
1162				}
1163			}
1164		}
1165	}
1166	/* handle % partitions - note %'s don't need to add up to 100! */
1167	if (total_percent != 0) {
1168		long free_space = lp->d_secperunit - total_size;
1169		if (total_percent > 100) {
1170			fprintf(stderr,"total percentage %lu is greater than 100\n",
1171			    total_percent);
1172			errors++;
1173		}
1174
1175		if (free_space > 0) {
1176			for (i = 0; i < lp->d_npartitions; i++) {
1177				pp = &lp->d_partitions[i];
1178				if (part_set[i] && part_size_type[i] == '%') {
1179					/* careful of overflows! and integer roundoff */
1180					pp->p_size = ((double)pp->p_size/100) * free_space;
1181					total_size += pp->p_size;
1182
1183					/* FIX we can lose a sector or so due to roundoff per
1184					   partition.  A more complex algorithm could avoid that */
1185				}
1186			}
1187		} else {
1188			fprintf(stderr,
1189			    "%ld sectors available to give to '*' and '%%' partitions\n",
1190			    free_space);
1191			errors++;
1192			/* fix?  set all % partitions to size 0? */
1193		}
1194	}
1195	/* give anything remaining to the hog partition */
1196	if (hog_part != -1) {
1197		lp->d_partitions[hog_part].p_size = lp->d_secperunit - total_size;
1198		total_size = lp->d_secperunit;
1199	}
1200
1201	/* Now set the offsets for each partition */
1202	current_offset = 0; /* in sectors */
1203	seen_default_offset = 0;
1204	for (i = 0; i < lp->d_npartitions; i++) {
1205		part = 'a' + i;
1206		pp = &lp->d_partitions[i];
1207		if (part_set[i]) {
1208			if (part_offset_type[i] == '*') {
1209				if (i == RAW_PART) {
1210					pp->p_offset = 0;
1211				} else {
1212					pp->p_offset = current_offset;
1213					seen_default_offset = 1;
1214				}
1215			} else {
1216				/* allow them to be out of order for old-style tables */
1217				if (pp->p_offset < current_offset &&
1218				    seen_default_offset && i != RAW_PART &&
1219				    pp->p_fstype != FS_VINUM) {
1220					fprintf(stderr,
1221"Offset %ld for partition %c overlaps previous partition which ends at %lu\n",
1222					    (long)pp->p_offset,i+'a',current_offset);
1223					fprintf(stderr,
1224"Labels with any *'s for offset must be in ascending order by sector\n");
1225					errors++;
1226				} else if (pp->p_offset != current_offset &&
1227				    i != RAW_PART && seen_default_offset) {
1228					/*
1229					 * this may give unneeded warnings if
1230					 * partitions are out-of-order
1231					 */
1232					warnx(
1233"Offset %ld for partition %c doesn't match expected value %ld",
1234					    (long)pp->p_offset, i + 'a', current_offset);
1235				}
1236			}
1237			if (i != RAW_PART)
1238				current_offset = pp->p_offset + pp->p_size;
1239		}
1240	}
1241
1242	for (i = 0; i < lp->d_npartitions; i++) {
1243		part = 'a' + i;
1244		pp = &lp->d_partitions[i];
1245		if (pp->p_size == 0 && pp->p_offset != 0)
1246			warnx("partition %c: size 0, but offset %lu",
1247			    part, (u_long)pp->p_offset);
1248#ifdef notdef
1249		if (pp->p_size % lp->d_secpercyl)
1250			warnx("partition %c: size %% cylinder-size != 0",
1251			    part);
1252		if (pp->p_offset % lp->d_secpercyl)
1253			warnx("partition %c: offset %% cylinder-size != 0",
1254			    part);
1255#endif
1256		if (pp->p_offset > lp->d_secperunit) {
1257			fprintf(stderr,
1258			    "partition %c: offset past end of unit\n", part);
1259			errors++;
1260		}
1261		if (pp->p_offset + pp->p_size > lp->d_secperunit) {
1262			fprintf(stderr,
1263			"partition %c: partition extends past end of unit\n",
1264			    part);
1265			errors++;
1266		}
1267		if (i == RAW_PART) {
1268			if (pp->p_fstype != FS_UNUSED)
1269				warnx("partition %c is not marked as unused!",part);
1270			if (pp->p_offset != 0)
1271				warnx("partition %c doesn't start at 0!",part);
1272			if (pp->p_size != lp->d_secperunit)
1273				warnx("partition %c doesn't cover the whole unit!",part);
1274
1275			if ((pp->p_fstype != FS_UNUSED) || (pp->p_offset != 0) ||
1276			    (pp->p_size != lp->d_secperunit)) {
1277				warnx("An incorrect partition %c may cause problems for "
1278				    "standard system utilities",part);
1279			}
1280		}
1281
1282		/* check for overlaps */
1283		/* this will check for all possible overlaps once and only once */
1284		for (j = 0; j < i; j++) {
1285			pp2 = &lp->d_partitions[j];
1286			if (j != RAW_PART && i != RAW_PART &&
1287			    pp->p_fstype != FS_VINUM &&
1288			    pp2->p_fstype != FS_VINUM &&
1289			    part_set[i] && part_set[j]) {
1290				if (pp2->p_offset < pp->p_offset + pp->p_size &&
1291				    (pp2->p_offset + pp2->p_size > pp->p_offset ||
1292					pp2->p_offset >= pp->p_offset)) {
1293					fprintf(stderr,"partitions %c and %c overlap!\n",
1294					    j + 'a', i + 'a');
1295					errors++;
1296				}
1297			}
1298		}
1299	}
1300	for (; i < MAXPARTITIONS; i++) {
1301		part = 'a' + i;
1302		pp = &lp->d_partitions[i];
1303		if (pp->p_size || pp->p_offset)
1304			warnx("unused partition %c: size %d offset %lu",
1305			    'a' + i, pp->p_size, (u_long)pp->p_offset);
1306	}
1307	return (errors);
1308}
1309
1310/*
1311 * When operating on a "virgin" disk, try getting an initial label
1312 * from the associated device driver.  This might work for all device
1313 * drivers that are able to fetch some initial device parameters
1314 * without even having access to a (BSD) disklabel, like SCSI disks,
1315 * most IDE drives, or vn devices.
1316 *
1317 * The device name must be given in its "canonical" form.
1318 */
1319static struct disklabel *
1320getvirginlabel(void)
1321{
1322	static struct disklabel loclab;
1323	struct partition *dp;
1324	int f;
1325	u_int u;
1326
1327	if ((f = open(specname, O_RDONLY)) == -1) {
1328		warn("cannot open %s", specname);
1329		return (NULL);
1330	}
1331
1332	/* New world order */
1333	if ((ioctl(f, DIOCGMEDIASIZE, &mediasize) != 0) ||
1334	    (ioctl(f, DIOCGSECTORSIZE, &secsize) != 0)) {
1335		close (f);
1336		return (NULL);
1337	}
1338	memset(&loclab, 0, sizeof loclab);
1339	loclab.d_magic = DISKMAGIC;
1340	loclab.d_magic2 = DISKMAGIC;
1341	loclab.d_secsize = secsize;
1342	loclab.d_secperunit = mediasize / secsize;
1343
1344	/*
1345	 * Nobody in these enligthened days uses the CHS geometry for
1346	 * anything, but nontheless try to get it right.  If we fail
1347	 * to get any good ideas from the device, construct something
1348	 * which is IBM-PC friendly.
1349	 */
1350	if (ioctl(f, DIOCGFWSECTORS, &u) == 0)
1351		loclab.d_nsectors = u;
1352	else
1353		loclab.d_nsectors = 63;
1354	if (ioctl(f, DIOCGFWHEADS, &u) == 0)
1355		loclab.d_ntracks = u;
1356	else if (loclab.d_secperunit <= 63*1*1024)
1357		loclab.d_ntracks = 1;
1358	else if (loclab.d_secperunit <= 63*16*1024)
1359		loclab.d_ntracks = 16;
1360	else
1361		loclab.d_ntracks = 255;
1362	loclab.d_secpercyl = loclab.d_ntracks * loclab.d_nsectors;
1363	loclab.d_ncylinders = loclab.d_secperunit / loclab.d_secpercyl;
1364	loclab.d_npartitions = MAXPARTITIONS;
1365
1366	/* Various (unneeded) compat stuff */
1367	loclab.d_rpm = 3600;
1368	loclab.d_bbsize = BBSIZE;
1369	loclab.d_interleave = 1;
1370	strncpy(loclab.d_typename, "amnesiac",
1371	    sizeof(loclab.d_typename));
1372
1373	dp = &loclab.d_partitions[RAW_PART];
1374	dp->p_size = loclab.d_secperunit;
1375	loclab.d_checksum = dkcksum(&loclab);
1376	close (f);
1377	return (&loclab);
1378}
1379
1380static void
1381usage(void)
1382{
1383
1384	fprintf(stderr,
1385	"%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
1386	"usage: bsdlabel disk",
1387	"\t\t(to read label)",
1388	"	bsdlabel -w [-n] [-m machine] disk [type]",
1389	"\t\t(to write label with existing boot program)",
1390	"	bsdlabel -e [-n] [-m machine] disk",
1391	"\t\t(to edit label)",
1392	"	bsdlabel -R [-n] [-m machine] disk protofile",
1393	"\t\t(to restore label with existing boot program)",
1394	"	bsdlabel -B [-b boot] [-m machine] disk",
1395	"\t\t(to install boot program with existing on-disk label)",
1396	"	bsdlabel -w -B [-n] [-b boot] [-m machine] disk [type]",
1397	"\t\t(to write label and install boot program)",
1398	"	bsdlabel -R -B [-n] [-b boot] [-m machine] disk protofile",
1399		"\t\t(to restore label and install boot program)"
1400	);
1401	exit(1);
1402}
1403