Edit

IABSD.fr/src/sbin/disklabel/editor.c

Branch :

  • Show log

    Commit

  • Author : deraadt
    Date : 2026-03-15 15:16:12
    Hash : 4dc0ad4d
    Message : auto-partitioning of /usr/obj has become a bit small on at least amd64, where there's no room for the entire build and a few manual debugging library builds. grow it for future system installs ok otto

  • sbin/disklabel/editor.c
  • /*	$OpenBSD: editor.c,v 1.428 2026/03/15 15:16:12 deraadt Exp $	*/
    
    /*
     * Copyright (c) 1997-2000 Todd C. Miller <millert@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/param.h>	/* MAXBSIZE DEV_BSIZE */
    #include <sys/types.h>
    #include <sys/signal.h>
    #include <sys/stat.h>
    #include <sys/ioctl.h>
    #include <sys/dkio.h>
    #include <sys/sysctl.h>
    #define	DKTYPENAMES
    #include <sys/disklabel.h>
    
    #include <ufs/ffs/fs.h>
    
    #include <ctype.h>
    #include <err.h>
    #include <errno.h>
    #include <string.h>
    #include <libgen.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <limits.h>
    
    #include "extern.h"
    #include "pathnames.h"
    
    #define	ROUNDUP(_s, _a)		((((_s) + (_a) - 1) / (_a)) * (_a))
    #define	ROUNDDOWN(_s, _a)	(((_s) / (_a)) * (_a))
    #define	CHUNKSZ(_c)		((_c)->stop - (_c)->start)
    
    /* flags for getuint64() */
    #define	DO_CONVERSIONS	0x00000001
    #define	DO_ROUNDING	0x00000002
    
    /* flags for alignpartition() */
    #define	ROUND_OFFSET_UP		0x00000001
    #define	ROUND_OFFSET_DOWN	0x00000002
    #define	ROUND_SIZE_UP		0x00000004
    #define	ROUND_SIZE_DOWN		0x00000008
    #define	ROUND_SIZE_OVERLAP	0x00000010
    
    /* Special return values for getnumber and getuint64() */
    #define	CMD_ABORTED	(ULLONG_MAX - 1)
    #define	CMD_BADVALUE	(ULLONG_MAX)
    
    /* structure to describe a portion of a disk */
    struct diskchunk {
    	u_int64_t start;
    	u_int64_t stop;
    };
    
    /* used when sorting mountpoints in mpsave() */
    struct mountinfo {
    	char *mountpoint;
    	int partno;
    };
    
    /* used when allocating all space according to recommendations */
    
    struct space_allocation {
    	u_int64_t	minsz;	/* starts as blocks, xlated to sectors. */
    	u_int64_t	maxsz;	/* starts as blocks, xlated to sectors. */
    	int		rate;	/* % of extra space to use */
    	char	       *mp;
    };
    
    /*
     * NOTE! Changing partition sizes in the space_allocation tables
     *       requires corresponding updates to the *.ok files in
     *	 /usr/src/regress/sbin/disklabel.
     */
    
    /* entries for swap and var are changed by editor_allocspace() */
    struct space_allocation alloc_big[] = {
    	{  MEG(150),         GIG(1),   5, "/"		},
    	{   MEG(80),       MEG(256),  10, "swap"	},
    	{  MEG(120),         GIG(4),   8, "/tmp"	},
    	{   MEG(80),         GIG(4),  13, "/var"	},
    	{ MEG(1500),        GIG(30),  10, "/usr"	},
    	{  MEG(384),         GIG(1),   3, "/usr/X11R6"	},
    	{    GIG(1),        GIG(20),  15, "/usr/local"	},
    	{    GIG(2),         GIG(5),   2, "/usr/src"	},
    	{    GIG(8),        GIG(10),   4, "/usr/obj"	},
    	{    GIG(1),       GIG(300),  30, "/home"	}
    	/* Anything beyond this leave for the user to decide */
    };
    
    struct space_allocation alloc_medium[] = {
    	{  MEG(800),         GIG(2),   5, "/"		},
    	{   MEG(80),       MEG(256),  10, "swap"	},
    	{ MEG(1300),         GIG(3),  78, "/usr"	},
    	{  MEG(256),         GIG(2),   7, "/home"	}
    };
    
    struct space_allocation alloc_small[] = {
    	{  MEG(700),         GIG(4),  95, "/"		},
    	{    MEG(1),       MEG(256),   5, "swap"	}
    };
    
    struct space_allocation alloc_stupid[] = {
    	{    MEG(1),      MEG(2048), 100, "/"		}
    };
    
    #ifndef nitems
    #define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
    #endif
    
    struct alloc_table {
    	struct space_allocation *table;
    	int sz;
    };
    
    struct alloc_table alloc_table_default[] = {
    	{ alloc_big,	nitems(alloc_big) },
    	{ alloc_medium,	nitems(alloc_medium) },
    	{ alloc_small,	nitems(alloc_small) },
    	{ alloc_stupid,	nitems(alloc_stupid) }
    };
    struct alloc_table *alloc_table = alloc_table_default;
    int alloc_table_nitems = 4;
    
    void	edit_packname(struct disklabel *);
    void	editor_resize(struct disklabel *, const char *);
    void	editor_add(struct disklabel *, const char *);
    void	editor_change(struct disklabel *, const char *);
    u_int64_t editor_countfree(const struct disklabel *);
    void	editor_delete(struct disklabel *, const char *);
    void	editor_help(void);
    void	editor_modify(struct disklabel *, const char *);
    void	editor_name(const struct disklabel *, const char *);
    char	*getstring(const char *, const char *, const char *);
    u_int64_t getuint64(const struct disklabel *, char *, char *, u_int64_t,
        u_int64_t, int *);
    u_int64_t getnumber(const char *, const char *, u_int32_t, u_int32_t);
    int	getpartno(const struct disklabel *, const char *, const char *);
    int	has_overlap(struct disklabel *);
    int	partition_cmp(const void *, const void *);
    const struct partition **sort_partitions(const struct disklabel *, int);
    void	find_bounds(const struct disklabel *);
    void	set_bounds(struct disklabel *);
    void	set_duid(struct disklabel *);
    int	set_fragblock(struct disklabel *, int);
    const struct diskchunk *free_chunks(const struct disklabel *, int);
    int	micmp(const void *, const void *);
    int	mpequal(char **, char **);
    int	get_fstype(struct disklabel *, int);
    int	get_mp(const struct disklabel *, int);
    int	get_offset(struct disklabel *, int);
    int	get_size(struct disklabel *, int);
    void	zero_partitions(struct disklabel *);
    u_int64_t max_partition_size(const struct disklabel *, int);
    void	display_edit(const struct disklabel *, char);
    void	psize(u_int64_t sz, char unit, const struct disklabel *lp);
    char	*get_token(char **);
    int	apply_unit(double, u_char, u_int64_t *);
    int	parse_sizespec(const char *, double *, char **);
    int	parse_sizerange(char *, u_int64_t *, u_int64_t *);
    int	parse_pct(char *, int *);
    int	alignpartition(struct disklabel *, int, u_int64_t, u_int64_t, int);
    int	allocate_space(struct disklabel *, const struct alloc_table *);
    void	allocate_physmemincr(struct space_allocation *);
    int	allocate_partition(struct disklabel *, struct space_allocation *);
    const struct diskchunk *allocate_diskchunk(const struct disklabel *,
        const struct space_allocation *);
    
    static u_int64_t starting_sector;
    static u_int64_t ending_sector;
    static int resizeok;
    
    /*
     * Simple partition editor.
     */
    int
    editor(int f)
    {
    	struct disklabel origlabel, lastlabel, tmplabel, newlab = lab;
    	struct partition *pp;
    	FILE *fp;
    	char buf[BUFSIZ], *cmd, *arg;
    	char **omountpoints = NULL;
    	char **origmountpoints = NULL, **tmpmountpoints = NULL;
    	int i, error = 0;
    
    	/* Alloc and init mount point info */
    	if (!(omountpoints = calloc(MAXPARTITIONS, sizeof(char *))) ||
    	    !(origmountpoints = calloc(MAXPARTITIONS, sizeof(char *))) ||
    	    !(tmpmountpoints = calloc(MAXPARTITIONS, sizeof(char *))))
    		err(1, NULL);
    
    	/* How big is the OpenBSD portion of the disk?  */
    	find_bounds(&newlab);
    
    	/* Make sure there is no partition overlap. */
    	if (has_overlap(&newlab))
    		errx(1, "can't run when there is partition overlap.");
    
    	/* If we don't have a 'c' partition, create one. */
    	pp = &newlab.d_partitions[RAW_PART];
    	if (newlab.d_npartitions <= RAW_PART || DL_GETPSIZE(pp) == 0) {
    		puts("No 'c' partition found, adding one that spans the disk.");
    		if (newlab.d_npartitions <= RAW_PART)
    			newlab.d_npartitions = RAW_PART + 1;
    		DL_SETPOFFSET(pp, 0);
    		DL_SETPSIZE(pp, DL_GETDSIZE(&newlab));
    		pp->p_fstype = FS_UNUSED;
    		pp->p_fragblock = pp->p_cpg = 0;
    	}
    
    #ifdef SUN_CYLCHECK
    	if ((newlab.d_flags & D_VENDOR) && !quiet) {
    		puts("This platform requires that partition offsets/sizes "
    		    "be on cylinder boundaries.\n"
    		    "Partition offsets/sizes will be rounded to the "
    		    "nearest cylinder automatically.");
    	}
    #endif
    
    	/* Save the (U|u)ndo labels and mountpoints. */
    	mpcopy(origmountpoints, mountpoints);
    	origlabel = newlab;
    	lastlabel = newlab;
    
    	puts("Label editor (enter '?' for help at any prompt)");
    	for (;;) {
    		fprintf(stdout, "%s%s> ", dkname,
    		    (memcmp(&lab, &newlab, sizeof(newlab)) == 0) ? "" : "*");
    		if (fgets(buf, sizeof(buf), stdin) == NULL) {
    			putchar('\n');
    			buf[0] = 'q';
    			buf[1] = '\0';
    		}
    		if ((cmd = strtok(buf, " \t\r\n")) == NULL)
    			continue;
    		arg = strtok(NULL, " \t\r\n");
    
    		if ((*cmd != 'u') && (*cmd != 'U')) {
    			/*
    			 * Save undo info in case the command tries to make
    			 * changes but decides not to.
    			 */
    			tmplabel = lastlabel;
    			lastlabel = newlab;
    			mpcopy(tmpmountpoints, omountpoints);
    			mpcopy(omountpoints, mountpoints);
    		}
    
    		switch (*cmd) {
    		case '?':
    		case 'h':
    			editor_help();
    			break;
    
    		case 'A':
    			if (ioctl(f, DIOCGPDINFO, &newlab) == -1) {
    				warn("DIOCGPDINFO");
    				newlab = lastlabel;
    			} else {
    				int oquiet = quiet;
    				aflag = 1;
    				quiet = 0;
    				editor_allocspace(&newlab);
    				quiet = oquiet;
    			}
    			break;
    		case 'a':
    			editor_add(&newlab, arg);
    			break;
    
    		case 'b':
    			set_bounds(&newlab);
    			break;
    
    		case 'c':
    			editor_change(&newlab, arg);
    			break;
    
    		case 'D':
    			if (ioctl(f, DIOCGPDINFO, &newlab) == -1)
    				warn("DIOCGPDINFO");
    			else {
    				dflag = 1;
    				for (i = 0; i < MAXPARTITIONS; i++) {
    					free(mountpoints[i]);
    					mountpoints[i] = NULL;
    				}
    			}
    			break;
    
    		case 'd':
    			editor_delete(&newlab, arg);
    			break;
    
    		case 'e':
    			edit_packname(&newlab);
    			break;
    
    		case 'i':
    			set_duid(&newlab);
    			break;
    
    		case 'm':
    			editor_modify(&newlab, arg);
    			break;
    
    		case 'n':
    			if (!fstabfile) {
    				fputs("This option is not valid when run "
    				    "without the -F or -f flags.\n", stderr);
    				break;
    			}
    			editor_name(&newlab, arg);
    			break;
    
    		case 'p':
    			display_edit(&newlab, arg ? *arg : 0);
    			break;
    
    		case 'l':
    			display(stdout, &newlab, arg ? *arg : 0, 0);
    			break;
    
    		case 'M': {
    			sig_t opipe = signal(SIGPIPE, SIG_IGN);
    			char *pager, *comm = NULL;
    			extern const u_char manpage[];
    			extern const int manpage_sz;
    
    			if ((pager = getenv("PAGER")) == NULL || *pager == '\0')
    				pager = _PATH_LESS;
    
    			if (asprintf(&comm, "gunzip -qc|%s", pager) != -1 &&
    			    (fp = popen(comm, "w")) != NULL) {
    				(void) fwrite(manpage, manpage_sz, 1, fp);
    				pclose(fp);
    			} else
    				warn("unable to execute %s", pager);
    
    			free(comm);
    			(void)signal(SIGPIPE, opipe);
    			break;
    		}
    
    		case 'q':
    			if (donothing) {
    				puts("In no change mode, not writing label.");
    				goto done;
    			}
    
    			/*
    			 * If we haven't changed the original label, and it
    			 * wasn't a default label or an auto-allocated label,
    			 * there is no need to do anything before exiting. Note
    			 * that 'w' will reset dflag and aflag to allow 'q' to
    			 * exit without further questions.
    			 */
    			if (!dflag && !aflag &&
    			    memcmp(&lab, &newlab, sizeof(newlab)) == 0) {
    				puts("No label changes.");
    				/* Save mountpoint info. */
    				mpsave(&newlab);
    				goto done;
    			}
    			do {
    				arg = getstring("Write new label?",
    				    "Write the modified label to disk?",
    				    "y");
    			} while (arg && tolower((unsigned char)*arg) != 'y' &&
    			    tolower((unsigned char)*arg) != 'n');
    			if (arg && tolower((unsigned char)*arg) == 'y') {
    				if (writelabel(f, &newlab) == 0) {
    					newlab = lab; /* lab now has UID info */
    					goto done;
    				}
    				warnx("unable to write label");
    			}
    			error = 1;
    			goto done;
    			/* NOTREACHED */
    			break;
    
    		case 'R':
    			if (aflag && resizeok)
    				editor_resize(&newlab, arg);
    			else
    				fputs("Resize only implemented for auto "
    				    "allocated labels\n", stderr);
    			has_overlap(&newlab);
    			break;
    
    		case 'r': {
    			const struct diskchunk *chunk;
    			uint64_t total = 0;
    			/* Display free space. */
    			chunk = free_chunks(&newlab, -1);
    			for (; chunk->start != 0 || chunk->stop != 0; chunk++) {
    				total += CHUNKSZ(chunk);
    				fprintf(stderr, "Free sectors: %16llu - %16llu "
    				    "(%16llu)\n",
    				    chunk->start, chunk->stop - 1,
    				    CHUNKSZ(chunk));
    			}
    			fprintf(stderr, "Total free sectors: %llu.\n", total);
    			break;
    		}
    
    		case 's':
    			if (arg == NULL) {
    				arg = getstring("Filename",
    				    "Name of the file to save label into.",
    				    NULL);
    				if (arg == NULL || *arg == '\0')
    					break;
    			}
    			if ((fp = fopen(arg, "w")) == NULL) {
    				warn("cannot open %s", arg);
    			} else {
    				display(fp, &newlab, 0, 1);
    				(void)fclose(fp);
    			}
    			break;
    
    		case 'U':
    			/*
    			 * If we allow 'U' repeatedly, information would be
    			 * lost. This way multiple 'U's followed by 'u' will
    			 * undo the 'U's.
    			 */
    			if (memcmp(&newlab, &origlabel, sizeof(newlab)) ||
    			    !mpequal(mountpoints, origmountpoints)) {
    				tmplabel = newlab;
    				newlab = origlabel;
    				lastlabel = tmplabel;
    				mpcopy(tmpmountpoints, mountpoints);
    				mpcopy(mountpoints, origmountpoints);
    				mpcopy(omountpoints, tmpmountpoints);
    			}
    			puts("Original label and mount points restored.");
    			break;
    
    		case 'u':
    			tmplabel = newlab;
    			newlab = lastlabel;
    			lastlabel = tmplabel;
    			mpcopy(tmpmountpoints, mountpoints);
    			mpcopy(mountpoints, omountpoints);
    			mpcopy(omountpoints, tmpmountpoints);
    			puts("Last change undone.");
    			break;
    
    		case 'w':
    			if (donothing)  {
    				puts("In no change mode, not writing label.");
    				break;
    			}
    
    			/* Write label to disk. */
    			if (writelabel(f, &newlab) != 0)
    				warnx("unable to write label");
    			else {
    				dflag = aflag = 0;
    				newlab = lab; /* lab now has UID info */
    			}
    			break;
    
    		case 'x':
    			goto done;
    			break;
    
    		case 'z':
    			zero_partitions(&newlab);
    			break;
    
    		case '\n':
    			break;
    
    		default:
    			printf("Unknown option: %c ('?' for help)\n", *cmd);
    			break;
    		}
    
    		/*
    		 * If no changes were made to label or mountpoints, then
    		 * restore undo info.
    		 */
    		if (memcmp(&newlab, &lastlabel, sizeof(newlab)) == 0 &&
    		    (mpequal(mountpoints, omountpoints))) {
    			lastlabel = tmplabel;
    			mpcopy(omountpoints, tmpmountpoints);
    		}
    	}
    done:
    	mpfree(omountpoints, DISCARD);
    	mpfree(origmountpoints, DISCARD);
    	mpfree(tmpmountpoints, DISCARD);
    	return error;
    }
    
    /*
     * Allocate all disk space according to standard recommendations for a
     * root disk.
     */
    int
    editor_allocspace(struct disklabel *lp_org)
    {
    	struct disklabel label;
    	struct partition *pp;
    	u_int64_t pstart, pend;
    	int i;
    
    	/* How big is the OpenBSD portion of the disk?  */
    	find_bounds(lp_org);
    
    	resizeok = 1;
    	for (i = 0;  i < MAXPARTITIONS; i++) {
    		if (i == RAW_PART)
    			continue;
    		pp = &lp_org->d_partitions[i];
    		if (DL_GETPSIZE(pp) == 0 || pp->p_fstype == FS_UNUSED)
    			continue;
    		pstart = DL_GETPOFFSET(pp);
    		pend = pstart + DL_GETPSIZE(pp);
    		if (((pstart >= starting_sector && pstart < ending_sector) ||
    		    (pend > starting_sector && pend <= ending_sector)))
    			resizeok = 0; /* Part of OBSD area is in use! */
    	}
    
    	for (i = 0; i < alloc_table_nitems; i++) {
    		memcpy(&label, lp_org, sizeof(label));
    		if (allocate_space(&label, &alloc_table[i]) == 0) {
    			memcpy(lp_org, &label, sizeof(struct disklabel));
    			return 0;
    		}
    	}
    
    	return 1;
    }
    
    const struct diskchunk *
    allocate_diskchunk(const struct disklabel *lp,
        const struct space_allocation *sa)
    {
    	const struct diskchunk *chunk;
    	static struct diskchunk largest;
    	uint64_t maxstop;
    
    	largest.start = largest.stop = 0;
    
    	chunk = free_chunks(lp, -1);
    	for (; chunk->start != 0 || chunk->stop != 0; chunk++) {
    		if (CHUNKSZ(chunk) > CHUNKSZ(&largest))
    			largest = *chunk;
    	}
    	maxstop = largest.start + DL_BLKTOSEC(lp, sa->maxsz);
    	if (maxstop > largest.stop)
    		maxstop = largest.stop;
    #ifdef SUN_CYLCHECK
    	if (lp->d_flags & D_VENDOR) {
    		largest.start = ROUNDUP(largest.start, lp->d_secpercyl);
    		maxstop = ROUNDUP(maxstop, lp->d_secpercyl);
    		if (maxstop > largest.stop)
    			maxstop -= lp->d_secpercyl;
    		if (largest.start >= maxstop)
    			largest.start = largest.stop = maxstop = 0;
    	}
    #endif
    	if (maxstop < largest.stop)
    		largest.stop = maxstop;
    	if (CHUNKSZ(&largest) < DL_BLKTOSEC(lp, sa->minsz))
    		return NULL;
    
    	return &largest;
    }
    
    int
    allocate_partition(struct disklabel *lp, struct space_allocation *sa)
    {
    	const struct diskchunk *chunk;
    	struct partition *pp;
    	unsigned int partno;
    
    	for (partno = 0; partno < MAXPARTITIONS; partno++) {
    		if (partno == RAW_PART)
    			continue;
    		pp = &lp->d_partitions[partno];
    		if (DL_GETPSIZE(pp) == 0 || pp->p_fstype == FS_UNUSED)
    			break;
    	}
    	if (partno >= MAXPARTITIONS)
    		return 1;		/* No free partition. */
    
    	/* Find appropriate chunk of free space. */
    	chunk = allocate_diskchunk(lp, sa);
    	if (chunk == NULL)
    		return 1;
    
    	if (strcasecmp(sa->mp, "raid") == 0)
    		pp->p_fstype = FS_RAID;
    	else if (strcasecmp(sa->mp, "swap") == 0)
    		pp->p_fstype = FS_SWAP;
    	else if (sa->mp[0] == '/')
    		pp->p_fstype = FS_BSDFFS;
    	else
    		return 1;
    
    	DL_SETPSIZE(pp, chunk->stop - chunk->start);
    	DL_SETPOFFSET(pp, chunk->start);
    
    	if (pp->p_fstype == FS_BSDFFS && DL_GETPSIZE(pp) > 0) {
    		mountpoints[partno] = strdup(sa->mp);
    		if (mountpoints[partno] == NULL)
    			err(1, NULL);
    		if (set_fragblock(lp, partno))
    			return 1;
    	}
    
    	return 0;
    }
    
    void
    allocate_physmemincr(struct space_allocation *sa)
    {
    	u_int64_t memblks;
    	extern int64_t physmem;
    
    	if (physmem == 0)
    		return;
    
    	memblks = physmem / DEV_BSIZE;
    	if (strcasecmp(sa->mp, "swap") == 0) {
    		if (memblks < MEG(256))
    			sa->minsz = sa->maxsz = 2 * memblks;
    		else
    			sa->maxsz += memblks;
    	} else if (strcasecmp(sa->mp, "/var") == 0) {
    		sa->maxsz += 2 * memblks;
    	}
    }
    
    int
    allocate_space(struct disklabel *lp, const struct alloc_table *alloc_table)
    {
    	struct space_allocation sa[MAXPARTITIONS];
    	u_int64_t maxsz, xtrablks;
    	int i;
    
    	xtrablks = DL_SECTOBLK(lp, editor_countfree(lp));
    	memset(sa, 0, sizeof(sa));
    	for (i = 0; i < alloc_table->sz; i++) {
    		sa[i] = alloc_table->table[i];
    		if (alloc_table->table == alloc_big)
    			allocate_physmemincr(&sa[i]);
    		if (xtrablks < sa[i].minsz)
    			return 1;	/* Too few free blocks. */
    		xtrablks -= sa[i].minsz;
    	}
    	sa[alloc_table->sz - 1].rate = 100; /* Last allocation is greedy. */
    
    	for (i = lp->d_npartitions; i < MAXPARTITIONS; i++) {
    		if (i == RAW_PART)
    			continue;
    		memset(&lp->d_partitions[i], 0, sizeof(lp->d_partitions[i]));
    	}
    	lp->d_npartitions = MAXPARTITIONS;
    
    	mpfree(mountpoints, KEEP);
    	for (i = 0; i < alloc_table->sz; i++) {
    		if (sa[i].rate == 100)
    			maxsz = sa[i].minsz + xtrablks;
    		else
    			maxsz = sa[i].minsz + (xtrablks / 100) * sa[i].rate;
    		if (maxsz < sa[i].maxsz)
    			sa[i].maxsz = maxsz;
    		if (allocate_partition(lp, &sa[i])) {
    			mpfree(mountpoints, KEEP);
    			return 1;
    		}
    	}
    
    	return 0;
    }
    
    /*
     * Resize a partition, moving all subsequent partitions
     */
    void
    editor_resize(struct disklabel *lp, const char *p)
    {
    	struct disklabel label;
    	struct partition *pp, *prev;
    	u_int64_t ui, sz, off;
    	int partno, i, flags, shrunk;
    
    	label = *lp;
    
    	if ((partno = getpartno(&label, p, "resize")) == -1)
    		return;
    
    	pp = &label.d_partitions[partno];
    	sz = DL_GETPSIZE(pp);
    	if (pp->p_fstype != FS_BSDFFS && pp->p_fstype != FS_SWAP) {
    		fputs("Cannot resize spoofed partition\n", stderr);
    		return;
    	}
    	flags = DO_CONVERSIONS;
    	ui = getuint64(lp, "[+|-]new size (with unit)",
    	    "new size or amount to grow (+) or shrink (-) partition including "
    	    "unit", sz, sz + editor_countfree(lp), &flags);
    
    	if (ui == CMD_ABORTED)
    		return;
    	else if (ui == CMD_BADVALUE)
    		return;
    	else if (ui == 0) {
    		fputs("The size must be > 0 sectors\n", stderr);
    		return;
    	}
    
    #ifdef SUN_CYLCHECK
    	if (lp->d_flags & D_VENDOR)
    		ui = ROUNDUP(ui, lp->d_secpercyl);
    #endif
    	if (DL_GETPOFFSET(pp) + ui > ending_sector) {
    		fputs("Amount too big\n", stderr);
    		return;
    	}
    
    	DL_SETPSIZE(pp, ui);
    	pp->p_fragblock = 0;
    	if (set_fragblock(&label, partno) == 1)
    		return;
    
    	/*
    	 * Pack partitions above the resized partition, leaving unused
    	 * partitions alone.
    	 */
    	shrunk = -1;
    	prev = pp;
    	for (i = partno + 1; i < MAXPARTITIONS; i++) {
    		if (i == RAW_PART)
    			continue;
    		pp = &label.d_partitions[i];
    		if (pp->p_fstype != FS_BSDFFS && pp->p_fstype != FS_SWAP)
    			continue;
    		sz = DL_GETPSIZE(pp);
    		if (sz == 0)
    			continue;
    
    		off = DL_GETPOFFSET(prev) + DL_GETPSIZE(prev);
    
    		if (off < ending_sector) {
    			DL_SETPOFFSET(pp, off);
    			if (off + DL_GETPSIZE(pp) > ending_sector) {
    				DL_SETPSIZE(pp, ending_sector - off);
    				pp->p_fragblock = 0;
    				if (set_fragblock(&label, i) == 1)
    					return;
    				shrunk = i;
    			}
    		} else {
    			fputs("Amount too big\n", stderr);
    			return;
    		}
    		prev = pp;
    	}
    
    	if (shrunk != -1)
    		fprintf(stderr, "Partition %c shrunk to %llu sectors to make "
    		    "room\n", DL_PARTNUM2NAME(shrunk),
    		    DL_GETPSIZE(&label.d_partitions[shrunk]));
    	*lp = label;
    }
    
    /*
     * Add a new partition.
     */
    void
    editor_add(struct disklabel *lp, const char *p)
    {
    	struct partition *pp;
    	const struct diskchunk *chunk;
    	int partno;
    	u_int64_t new_offset, new_size;
    
    	chunk = free_chunks(lp, -1);
    	new_size = new_offset = 0;
    	for (; chunk->start != 0 || chunk->stop != 0; chunk++) {
    		if (CHUNKSZ(chunk) > new_size) {
    			new_size = CHUNKSZ(chunk);
    			new_offset = chunk->start;
    		}
    	}
    
    #ifdef SUN_CYLCHECK
    	if ((lp->d_flags & D_VENDOR) && new_size < lp->d_secpercyl) {
    		fputs("No space left, you need to shrink a partition "
    		    "(need at least one full cylinder)\n",
    		    stderr);
    		return;
    	}
    #endif
    	if (new_size == 0) {
    		fputs("No space left, you need to shrink a partition\n",
    		    stderr);
    		return;
    	}
    
    	if ((partno = getpartno(lp, p, "add")) == -1)
    		return;
    	pp = &lp->d_partitions[partno];
    	memset(pp, 0, sizeof(*pp));
    
    	/*
    	 * Increase d_npartitions if necessary. Ensure all new partitions are
    	 * zero'ed to avoid inadvertent overlaps.
    	 */
    	for(; lp->d_npartitions <= partno; lp->d_npartitions++)
    		memset(&lp->d_partitions[lp->d_npartitions], 0, sizeof(*pp));
    
    	DL_SETPSIZE(pp, new_size);
    	DL_SETPOFFSET(pp, new_offset);
    	pp->p_fstype = partno == 1 ? FS_SWAP : FS_BSDFFS;
    
    	if (get_offset(lp, partno) == 0 &&
    	    get_size(lp, partno) == 0 &&
    	    get_fstype(lp, partno) == 0 &&
    	    get_mp(lp, partno) == 0 &&
    	    set_fragblock(lp, partno) == 0)
    		return;
    
    	/* Bailed out at some point, so effectively delete the partition. */
    	memset(pp, 0, sizeof(*pp));
    }
    
    /*
     * Set the mountpoint of an existing partition ('name').
     */
    void
    editor_name(const struct disklabel *lp, const char *p)
    {
    	int partno;
    
    	if ((partno = getpartno(lp, p, "name")) == -1)
    		return;
    
    	get_mp(lp, partno);
    }
    
    /*
     * Change an existing partition.
     */
    void
    editor_modify(struct disklabel *lp, const char *p)
    {
    	struct partition opp, *pp;
    	int partno;
    
    	if ((partno = getpartno(lp, p, "modify")) == -1)
    		return;
    
    	pp = &lp->d_partitions[partno];
    	opp = *pp;
    
    	if (get_offset(lp, partno) == 0 &&
    	    get_size(lp, partno) == 0   &&
    	    get_fstype(lp, partno) == 0 &&
    	    get_mp(lp, partno) == 0 &&
    	    set_fragblock(lp, partno) == 0)
    		return;
    
    	/* Bailed out at some point, so undo any changes. */
    	*pp = opp;
    }
    
    /*
     * Delete an existing partition.
     */
    void
    editor_delete(struct disklabel *lp, const char *p)
    {
    	struct partition *pp;
    	int partno;
    
    	if ((partno = getpartno(lp, p, "delete")) == -1)
    		return;
    	if (partno == lp->d_npartitions) {
    		zero_partitions(lp);
    		return;
    	}
    	pp = &lp->d_partitions[partno];
    
    	/* Really delete it (as opposed to just setting to "unused") */
    	memset(pp, 0, sizeof(*pp));
    	free(mountpoints[partno]);
    	mountpoints[partno] = NULL;
    }
    
    /*
     * Change the size of an existing partition.
     */
    void
    editor_change(struct disklabel *lp, const char *p)
    {
    	struct partition *pp;
    	int partno;
    
    	if ((partno = getpartno(lp, p, "change size")) == -1)
    		return;
    
    	pp = &lp->d_partitions[partno];
    	printf("Partition %c is currently %llu sectors in size, and can have "
    	    "a maximum\nsize of %llu sectors.\n",
    	    DL_PARTNUM2NAME(partno), DL_GETPSIZE(pp), max_partition_size(lp, partno));
    
    	/* Get new size */
    	get_size(lp, partno);
    }
    
    /*
     * Sort the partitions based on starting offset.
     * This assumes there can be no overlap.
     */
    int
    partition_cmp(const void *e1, const void *e2)
    {
    	struct partition *p1 = *(struct partition **)e1;
    	struct partition *p2 = *(struct partition **)e2;
    	u_int64_t o1 = DL_GETPOFFSET(p1);
    	u_int64_t o2 = DL_GETPOFFSET(p2);
    
    	if (o1 < o2)
    		return -1;
    	else if (o1 > o2)
    		return 1;
    	else
    		return 0;
    }
    
    char *
    getstring(const char *prompt, const char *helpstring, const char *oval)
    {
    	static char buf[BUFSIZ];
    	int n;
    
    	buf[0] = '\0';
    	do {
    		printf("%s: [%s] ", prompt, oval ? oval : "");
    		if (fgets(buf, sizeof(buf), stdin) == NULL) {
    			buf[0] = '\0';
    			if (feof(stdin)) {
    				clearerr(stdin);
    				putchar('\n');
    				fputs("Command aborted\n", stderr);
    				return NULL;
    			}
    		}
    		n = strlen(buf);
    		if (n > 0 && buf[n-1] == '\n')
    			buf[--n] = '\0';
    		if (buf[0] == '?')
    			puts(helpstring);
    		else if (oval != NULL && buf[0] == '\0')
    			strlcpy(buf, oval, sizeof(buf));
    	} while (buf[0] == '?');
    
    	return &buf[0];
    }
    
    int
    getpartno(const struct disklabel *lp, const char *p, const char *action)
    {
    	char buf[2] = { '\0', '\0'};
    	const char *promptfmt = "partition to %s";
    	const char *helpfmt = "Partition must be between %s (excluding 'c')%s.\n";
    	const struct partition *pp;
    	char *help = NULL, *prompt = NULL, *partitionnames;
    	int add, delete, inuse, partno;
    
    	partitionnames = (MAXPARTITIONS == 16) ? "'a'-'p'" : "'a'-'z' or 'A'-'Z'";
    
    	add = strcmp("add", action) == 0;
    	delete = strcmp("delete", action) == 0;
    
    	if (p == NULL) {
    		if (asprintf(&prompt, promptfmt, action) == -1 ||
    		    asprintf(&help, helpfmt, partitionnames, delete ? ", or '*'" : "")
    		    == -1) {
    			fprintf(stderr, "Unable to build prompt or help\n");
    			goto done;
    		}
    		if (add) {
    			/* Default to first unused partition. */
    			for (partno = 0; partno < MAXPARTITIONS; partno++) {
    				if (partno == RAW_PART)
    					continue;
    				pp = &lp->d_partitions[partno];
    				if (partno >= lp->d_npartitions ||
    				    DL_GETPSIZE(pp) == 0 ||
    				    pp->p_fstype == FS_UNUSED) {
    					buf[0] = DL_PARTNUM2NAME(partno);
    					p = buf;
    					break;
    				}
    			}
    		}
    		p = getstring(prompt, help, p);
    		free(prompt);
    		free(help);
    		if (p == NULL || *p == '\0')
    			goto done;
    	}
    
    	if (delete && strlen(p) == 1 && *p == '*')
    		return lp->d_npartitions;
    
    	partno = DL_PARTNAME2NUM(*p);
    	if (strlen(p) > 1 || partno == -1 || *p == 'c') {
    		fprintf(stderr, helpfmt, partitionnames, delete ? ", or '*'" : "");
    		goto done;
    	}
    
    	pp = &lp->d_partitions[partno];
    	inuse = partno < lp->d_npartitions && DL_GETPSIZE(pp) > 0 &&
    	    pp->p_fstype != FS_UNUSED;
    
    	if (((add || delete) && !inuse) || (!add && inuse))
    		return partno;
    
    	fprintf(stderr, "Partition '%c' is %sin use.\n", *p,
    	    inuse ? "" : "not ");
    
     done:
    	return -1;
    }
    
    /*
     * Returns
     * 0 .. CMD_ABORTED - 1	==> valid value
     * CMD_BADVALUE		==> invalid value
     * CMD_ABORTED		==> ^D on input
     */
    u_int64_t
    getnumber(const char *prompt, const char *helpstring, u_int32_t oval,
        u_int32_t maxval)
    {
    	char buf[BUFSIZ], *p;
    	int rslt;
    	long long rval;
    	const char *errstr;
    
    	rslt = snprintf(buf, sizeof(buf), "%u", oval);
    	if (rslt < 0 || (unsigned int)rslt >= sizeof(buf))
    		return CMD_BADVALUE;
    
    	p = getstring(prompt, helpstring, buf);
    	if (p == NULL)
    		return CMD_ABORTED;
    	if (strlen(p) == 0)
    		return oval;
    
    	rval = strtonum(p, 0, maxval, &errstr);
    	if (errstr != NULL) {
    		printf("%s must be between 0 and %u\n", prompt, maxval);
    		return CMD_BADVALUE;
    	}
    
    	return rval;
    }
    
    /*
     * Returns
     * 0 .. CMD_ABORTED - 1	==> valid value
     * CMD_BADVALUE		==> invalid value
     * CMD_ABORTED		==> ^D on input
     */
    u_int64_t
    getuint64(const struct disklabel *lp, char *prompt, char *helpstring,
        u_int64_t oval, u_int64_t maxval, int *flags)
    {
    	char buf[21], *p, operator = '\0';
    	char *unit = NULL;
    	u_int64_t rval = oval;
    	double d;
    	int rslt;
    
    	rslt = snprintf(buf, sizeof(buf), "%llu", oval);
    	if (rslt < 0 || (unsigned int)rslt >= sizeof(buf))
    		goto invalid;
    
    	p = getstring(prompt, helpstring, buf);
    	if (p == NULL)
    		return CMD_ABORTED;
    	else if (p[0] == '\0')
    		rval = oval;
    	else if (p[0] == '*' && p[1] == '\0')
    		rval = maxval;
    	else {
    		if (*p == '+' || *p == '-')
    			operator = *p++;
    		if (parse_sizespec(p, &d, &unit) == -1)
    			goto invalid;
    		if (unit == NULL)
    			rval = d;
    		else if (flags != NULL && (*flags & DO_CONVERSIONS) == 0)
    			goto invalid;
    		else {
    			switch (tolower((unsigned char)*unit)) {
    			case 'b':
    				rval = d / lp->d_secsize;
    				break;
    			case 'c':
    				rval = d * lp->d_secpercyl;
    				break;
    			case '%':
    				rval = DL_GETDSIZE(lp) * (d / 100.0);
    				break;
    			case '&':
    				rval = maxval * (d / 100.0);
    				break;
    			default:
    				if (apply_unit(d, *unit, &rval) == -1)
    					goto invalid;
    				rval = DL_BLKTOSEC(lp, rval);
    				break;
    			}
    		}
    
    		/* Range check then apply [+-] operator */
    		if (operator == '+') {
    			if (CMD_ABORTED - oval > rval)
    				rval += oval;
    			else {
    				goto invalid;
    			}
    		} else if (operator == '-') {
    			if (oval >= rval)
    				rval = oval - rval;
    			else {
    				goto invalid;
    			}
    		}
    	}
    
    	if (flags != NULL) {
    		if (unit != NULL)
    			*flags |= DO_ROUNDING;
    #ifdef SUN_CYLCHECK
    		if (lp->d_flags & D_VENDOR)
    			*flags |= DO_ROUNDING;
    #endif
    	}
    	return rval;
    
    invalid:
    	fputs("Invalid entry\n", stderr);
    	return CMD_BADVALUE;
    }
    
    /*
     * Check for partition overlap in lp and prompt the user to resolve the overlap
     * if any is found.  Returns 1 if unable to resolve, else 0.
     */
    int
    has_overlap(struct disklabel *lp)
    {
    	const struct partition **spp;
    	int i, p1, p2;
    	char *line = NULL;
    	size_t linesize = 0;
    	ssize_t linelen;
    
    	for (;;) {
    		spp = sort_partitions(lp, -1);
    		for (i = 0; spp[i+1] != NULL; i++) {
    			if (DL_GETPOFFSET(spp[i]) + DL_GETPSIZE(spp[i]) >
    			    DL_GETPOFFSET(spp[i+1]))
    				break;
    		}
    		if (spp[i+1] == NULL) {
    			free(line);
    			return 0;
    		}
    
    		p1 = DL_PARTNUM2NAME(spp[i] - lp->d_partitions);
    		p2 = DL_PARTNUM2NAME(spp[i+1] - lp->d_partitions);
    		printf("\nError, partitions %c and %c overlap:\n", p1, p2);
    		printf("#    %16.16s %16.16s  fstype [fsize bsize    cpg]\n",
    		    "size", "offset");
    		display_partition(stdout, lp, DL_PARTNAME2NUM(p1), 0);
    		display_partition(stdout, lp, DL_PARTNAME2NUM(p2), 0);
    
    		for (;;) {
    			printf("Disable which one? (%c %c) ", p1, p2);
    			linelen = getline(&line, &linesize, stdin);
    			if (linelen == -1)
    				goto done;
    			if (linelen == 2 && (line[0] == p1 || line[0] == p2))
    				break;
    		}
    		lp->d_partitions[DL_PARTNAME2NUM(line[0])].p_fstype = FS_UNUSED;
    	}
    
    done:
    	putchar('\n');
    	free(line);
    	return 1;
    }
    
    void
    edit_packname(struct disklabel *lp)
    {
    	char *p;
    	struct disklabel oldlabel = *lp;
    
    	printf("Changing label description for %s:\n", specname);
    
    	/* pack/label id */
    	p = getstring("label name",
    	    "15 char string that describes this label, usually the disk name.",
    	    lp->d_packname);
    	if (p == NULL) {
    		*lp = oldlabel;		/* undo damage */
    		return;
    	}
    	strncpy(lp->d_packname, p, sizeof(lp->d_packname));	/* checked */
    }
    
    const struct partition **
    sort_partitions(const struct disklabel *lp, int ignore)
    {
    	static const struct partition *spp[MAXPARTITIONS+2];
    	int i, npartitions;
    
    	memset(spp, 0, sizeof(spp));
    
    	for (npartitions = 0, i = 0; i < lp->d_npartitions; i++) {
    		if (i != ignore && lp->d_partitions[i].p_fstype != FS_UNUSED &&
    		    DL_GETPSIZE(&lp->d_partitions[i]) != 0)
    			spp[npartitions++] = &lp->d_partitions[i];
    	}
    
    	/*
    	 * Sort the partitions based on starting offset.
    	 * This is safe because we guarantee no overlap.
    	 */
    	if (npartitions > 1)
    		if (mergesort((void *)spp, npartitions, sizeof(spp[0]),
    		    partition_cmp))
    			err(4, "failed to sort partition table");
    
    	return spp;
    }
    
    /*
     * Get beginning and ending sectors of the OpenBSD portion of the disk
     * from the user.
     */
    void
    set_bounds(struct disklabel *lp)
    {
    	u_int64_t ui, start_temp;
    
    	/* Starting sector */
    	for (;;) {
    		ui = getuint64(lp, "Starting sector",
    		    "The start of the OpenBSD portion of the disk.",
    		    starting_sector, DL_GETDSIZE(lp), NULL);
    		if (ui == CMD_ABORTED)
    			return;
    		else if (ui == CMD_BADVALUE)
    			;	/* Try again. */
    		else if (ui >= DL_GETDSIZE(lp))
    			fprintf(stderr, "starting sector must be < %llu\n",
    			    DL_GETDSIZE(lp));
    		else
    			break;
    	}
    	start_temp = ui;
    
    	/* Size */
    	for (;;) {
    		ui = getuint64(lp, "Size ('*' for entire disk)",
    		    "The size of the OpenBSD portion of the disk ('*' for the "
    		    "entire disk).", ending_sector - starting_sector,
    		    DL_GETDSIZE(lp) - start_temp, NULL);
    		if (ui == CMD_ABORTED)
    			return;
    		else if (ui == CMD_BADVALUE)
    			;	/* Try again. */
    		else if (ui > DL_GETDSIZE(lp) - start_temp)
    			fprintf(stderr, "size must be <= %llu\n",
    			    DL_GETDSIZE(lp) - start_temp);
    		else
    			break;
    	}
    	ending_sector = start_temp + ui;
    	DL_SETBEND(lp, ending_sector);
    	starting_sector = start_temp;
    	DL_SETBSTART(lp, starting_sector);
    }
    
    /*
     * Allow user to interactively change disklabel UID.
     */
    void
    set_duid(struct disklabel *lp)
    {
    	char *s;
    	int i;
    
    	printf("The disklabel UID is currently: "
    	    "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx\n",
    	    lp->d_uid[0], lp->d_uid[1], lp->d_uid[2], lp->d_uid[3],
    	    lp->d_uid[4], lp->d_uid[5], lp->d_uid[6], lp->d_uid[7]);
    
    	do {
    		s = getstring("duid", "The disklabel UID, given as a 16 "
    		    "character hexadecimal string.", NULL);
    		if (s == NULL || strlen(s) == 0) {
    			fputs("Command aborted\n", stderr);
    			return;
    		}
    		i = duid_parse(lp, s);
    		if (i != 0)
    			fputs("Invalid UID entered.\n", stderr);
    	} while (i != 0);
    }
    
    /*
     * Return a list of the "chunks" of free space available
     */
    const struct diskchunk *
    free_chunks(const struct disklabel *lp, int partno)
    {
    	const struct partition **spp;
    	static struct diskchunk chunks[MAXPARTITIONS + 2];
    	u_int64_t start, stop;
    	int i, numchunks;
    
    	/* Sort the in-use partitions based on offset */
    	spp = sort_partitions(lp, partno);
    
    	/* If there are no partitions, it's all free. */
    	if (spp[0] == NULL) {
    		chunks[0].start = starting_sector;
    		chunks[0].stop = ending_sector;
    		chunks[1].start = chunks[1].stop = 0;
    		return chunks;
    	}
    
    	/* Find chunks of free space */
    	numchunks = 0;
    	if (DL_GETPOFFSET(spp[0]) > starting_sector) {
    		chunks[0].start = starting_sector;
    		chunks[0].stop = DL_GETPOFFSET(spp[0]);
    		numchunks++;
    	}
    	for (i = 0; spp[i] != NULL; i++) {
    		start = DL_GETPOFFSET(spp[i]) + DL_GETPSIZE(spp[i]);
    		if (start < starting_sector)
    			start = starting_sector;
    		else if (start > ending_sector)
    			start = ending_sector;
    		if (spp[i + 1] != NULL)
    			stop = DL_GETPOFFSET(spp[i+1]);
    		else
    			stop = ending_sector;
    		if (stop < starting_sector)
    			stop = starting_sector;
    		else if (stop > ending_sector)
    			stop = ending_sector;
    		if (start < stop) {
    			chunks[numchunks].start = start;
    			chunks[numchunks].stop = stop;
    			numchunks++;
    		}
    	}
    
    	/* Terminate and return */
    	chunks[numchunks].start = chunks[numchunks].stop = 0;
    	return chunks;
    }
    
    void
    find_bounds(const struct disklabel *lp)
    {
    	starting_sector = DL_GETBSTART(lp);
    	ending_sector = DL_GETBEND(lp);
    
    	if (ending_sector) {
    		if (verbose)
    			printf("Treating sectors %llu-%llu as the OpenBSD"
    			    " portion of the disk.\nYou can use the 'b'"
    			    " command to change this.\n\n", starting_sector,
    			    ending_sector);
    	}
    }
    
    /*
     * Calculate free space.
     */
    u_int64_t
    editor_countfree(const struct disklabel *lp)
    {
    	const struct diskchunk *chunk;
    	u_int64_t freesectors = 0;
    
    	chunk = free_chunks(lp, -1);
    
    	for (; chunk->start != 0 || chunk->stop != 0; chunk++)
    		freesectors += CHUNKSZ(chunk);
    
    	return freesectors;
    }
    
    void
    editor_help(void)
    {
    	puts("Available commands:");
    	puts(
    " ? | h    - show help                 n [part] - set mount point\n"
    " A        - auto partition all space  p [unit] - print partitions\n"
    " a [part] - add partition             q        - quit & save changes\n"
    " b        - set OpenBSD boundaries    R [part] - resize auto allocated partition\n"
    " c [part] - change partition size     r        - display free space\n"
    " D        - reset label to default    s [path] - save label to file\n"
    " d [part] - delete partition          U        - undo all changes\n"
    " e        - edit label description    u        - undo last change\n"
    " i        - modify disklabel UID      w        - write label to disk\n"
    " l [unit] - print disk label header   x        - exit & lose changes\n"
    " M        - disklabel(8) man page     z        - delete all partitions\n"
    " m [part] - modify partition\n"
    "\n"
    "Suffixes can be used to indicate units other than sectors:\n"
    " 'b' (bytes), 'k' (kilobytes), 'm' (megabytes), 'g' (gigabytes) 't' (terabytes)\n"
    " 'c' (cylinders), '%' (% of total disk), '&' (% of free space).\n"
    "Values in non-sector units are truncated to the nearest cylinder boundary.");
    
    }
    
    void
    mpcopy(char **to, char **from)
    {
    	int i;
    
    	for (i = 0; i < MAXPARTITIONS; i++) {
    		free(to[i]);
    		to[i] = NULL;
    		if (from[i] != NULL) {
    			to[i] = strdup(from[i]);
    			if (to[i] == NULL)
    				err(1, NULL);
    		}
    	}
    }
    
    int
    mpequal(char **mp1, char **mp2)
    {
    	int i;
    
    	for (i = 0; i < MAXPARTITIONS; i++) {
    		if (mp1[i] == NULL && mp2[i] == NULL)
    			continue;
    
    		if ((mp1[i] != NULL && mp2[i] == NULL) ||
    		    (mp1[i] == NULL && mp2[i] != NULL) ||
    		    (strcmp(mp1[i], mp2[i]) != 0))
    			return 0;
    	}
    	return 1;
    }
    
    void
    mpsave(const struct disklabel *lp)
    {
    	int i, j;
    	char bdev[PATH_MAX], *p;
    	struct mountinfo mi[MAXPARTITIONS];
    	FILE *fp;
    	u_int8_t fstype;
    
    	if (!fstabfile)
    		return;
    
    	memset(&mi, 0, sizeof(mi));
    
    	for (i = 0; i < MAXPARTITIONS; i++) {
    		fstype = lp->d_partitions[i].p_fstype;
    		if (mountpoints[i] != NULL || fstype == FS_SWAP) {
    			mi[i].mountpoint = mountpoints[i];
    			mi[i].partno = i;
    		}
    	}
    
    	/* Convert specname to bdev */
    	if (uidflag) {
    		snprintf(bdev, sizeof(bdev),
    		    "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx.%c",
    		    lab.d_uid[0], lab.d_uid[1], lab.d_uid[2], lab.d_uid[3],
    		    lab.d_uid[4], lab.d_uid[5], lab.d_uid[6], lab.d_uid[7],
    		    specname[strlen(specname)-1]);
    	} else if (strncmp(_PATH_DEV, specname, sizeof(_PATH_DEV) - 1) == 0 &&
    	    specname[sizeof(_PATH_DEV) - 1] == 'r') {
    		snprintf(bdev, sizeof(bdev), "%s%s", _PATH_DEV,
    		    &specname[sizeof(_PATH_DEV)]);
    	} else {
    		if ((p = strrchr(specname, '/')) == NULL || *(++p) != 'r')
    			return;
    		*p = '\0';
    		snprintf(bdev, sizeof(bdev), "%s%s", specname, p + 1);
    		*p = 'r';
    	}
    	bdev[strlen(bdev) - 1] = '\0';
    
    	/* Sort mountpoints so we don't try to mount /usr/local before /usr */
    	qsort((void *)mi, MAXPARTITIONS, sizeof(struct mountinfo), micmp);
    
    	if ((fp = fopen(fstabfile, "w"))) {
    		for (i = 0; i < MAXPARTITIONS; i++) {
    			j =  mi[i].partno;
    			fstype = lp->d_partitions[j].p_fstype;
    			if (fstype == FS_RAID)
    				continue;
    			if (fstype == FS_SWAP) {
    				fprintf(fp, "%s%c none swap sw\n", bdev,
    				    DL_PARTNUM2NAME(j));
    			} else if (mi[i].mountpoint) {
    				fprintf(fp, "%s%c %s %s rw 1 %d\n", bdev,
    				    DL_PARTNUM2NAME(j), mi[i].mountpoint,
    				    fstypesnames[fstype], j == 0 ? 1 : 2);
    			}
    		}
    		fclose(fp);
    	}
    }
    
    void
    mpfree(char **mp, int action)
    {
    	int part;
    
    	if (mp == NULL)
    		return;
    
    	for (part = 0; part < MAXPARTITIONS; part++) {
    		free(mp[part]);
    		mp[part] = NULL;
    	}
    
    	if (action == DISCARD) {
    		free(mp);
    		mp = NULL;
    	}
    }
    
    int
    get_offset(struct disklabel *lp, int partno)
    {
    	struct partition opp, *pp = &lp->d_partitions[partno];
    	u_int64_t ui, offsetalign;
    	int flags;
    
    	flags = DO_CONVERSIONS;
    	ui = getuint64(lp, "offset",
    	    "Starting sector for this partition.",
    	    DL_GETPOFFSET(pp),
    	    DL_GETPOFFSET(pp), &flags);
    
    	if (ui == CMD_ABORTED || ui == CMD_BADVALUE)
    		return 1;
    #ifdef SUN_AAT0
    	if (partno == 0 && ui != 0) {
    		fprintf(stderr, "This architecture requires that "
    		    "partition 'a' start at sector 0.\n");
    		return 1;
    	}
    #endif
    	opp = *pp;
    	DL_SETPOFFSET(pp, ui);
    	offsetalign = 1;
    	if ((flags & DO_ROUNDING) != 0 && pp->p_fstype == FS_BSDFFS)
    		offsetalign = lp->d_secpercyl;
    
    	if (alignpartition(lp, partno, offsetalign, 1, ROUND_OFFSET_UP) == 1) {
    		*pp = opp;
    		return 1;
    	}
    
    	return 0;
    }
    
    int
    get_size(struct disklabel *lp, int partno)
    {
    	struct partition opp, *pp = &lp->d_partitions[partno];
    	u_int64_t maxsize, ui, sizealign;
    	int flags;
    
    	maxsize = max_partition_size(lp, partno);
    	flags = DO_CONVERSIONS;
    	ui = getuint64(lp, "size", "Size of the partition. "
    	    "You may also say +/- amount for a relative change.",
    	    DL_GETPSIZE(pp), maxsize, &flags);
    
    	if (ui == CMD_ABORTED || ui == CMD_BADVALUE)
    		return 1;
    
    	opp = *pp;
    	DL_SETPSIZE(pp, ui);
    	sizealign = 1;
    	if ((flags & DO_ROUNDING) != 0 && (pp->p_fstype == FS_SWAP ||
    	    pp->p_fstype == FS_BSDFFS))
    		sizealign = lp->d_secpercyl;
    
    	if (alignpartition(lp, partno, 1, sizealign, ROUND_SIZE_UP) == 1) {
    		*pp = opp;
    		return 1;
    	}
    
    	return 0;
    }
    
    int
    set_fragblock(struct disklabel *lp, int partno)
    {
    	struct partition opp, *pp = &lp->d_partitions[partno];
    	u_int64_t bytes, offsetalign, sizealign;
    	u_int32_t frag, fsize;
    
    	if (pp->p_fstype != FS_BSDFFS)
    		return 0;
    
    	if (pp->p_cpg == 0)
    		pp->p_cpg = 1;
    
    	fsize = DISKLABELV1_FFS_FSIZE(pp->p_fragblock);
    	frag = DISKLABELV1_FFS_FRAG(pp->p_fragblock);
    	if (fsize == 0) {
    		fsize = 2048;
    		frag = 8;
    		bytes = DL_GETPSIZE(pp) * lp->d_secsize;
    		if (bytes > 128ULL * 1024 * 1024 * 1024)
    			fsize *= 2;
    		if (bytes > 512ULL * 1024 * 1024 * 1024)
    			fsize *= 2;
    		if (fsize < lp->d_secsize)
    			fsize = lp->d_secsize;
    		if (fsize > MAXBSIZE / frag)
    			fsize = MAXBSIZE / frag;
    		pp->p_fragblock = DISKLABELV1_FFS_FRAGBLOCK(fsize, frag);
    	}
    #ifdef SUN_CYLCHECK
    	return 0;
    #endif
    	opp = *pp;
    	sizealign = (DISKLABELV1_FFS_FRAG(pp->p_fragblock) *
    	    DISKLABELV1_FFS_FSIZE(pp->p_fragblock)) / lp->d_secsize;
    	offsetalign = 1;
    	if (DL_GETPOFFSET(pp) != starting_sector)
    		offsetalign = sizealign;
    
    	if (alignpartition(lp, partno, offsetalign, sizealign, ROUND_OFFSET_UP |
    	    ROUND_SIZE_DOWN | ROUND_SIZE_OVERLAP) == 1) {
    		*pp = opp;
    		return 1;
    	}
    
    	return 0;
    }
    
    int
    get_fstype(struct disklabel *lp, int partno)
    {
    	char *p;
    	u_int64_t ui;
    	struct partition *pp = &lp->d_partitions[partno];
    
    	if (pp->p_fstype < FSMAXTYPES) {
    		p = getstring("FS type",
    		    "Filesystem type (usually 4.2BSD or swap)",
    		    fstypenames[pp->p_fstype]);
    		if (p == NULL) {
    			return 1;
    		}
    		for (ui = 0; ui < FSMAXTYPES; ui++) {
    			if (!strcasecmp(p, fstypenames[ui])) {
    				pp->p_fstype = ui;
    				break;
    			}
    		}
    		if (ui >= FSMAXTYPES) {
    			printf("Unrecognized filesystem type '%s', treating "
    			    "as 'unknown'\n", p);
    			pp->p_fstype = FS_OTHER;
    		}
    	} else {
    		for (;;) {
    			ui = getnumber("FS type (decimal)",
    			    "Filesystem type as a decimal number; usually 7 "
    			    "(4.2BSD) or 1 (swap).",
    			    pp->p_fstype, UINT8_MAX);
    			if (ui == CMD_ABORTED)
    				return 1;
    			else if (ui == CMD_BADVALUE)
    				;	/* Try again. */
    			else
    				break;
    		}
    		pp->p_fstype = ui;
    	}
    	return 0;
    }
    
    int
    get_mp(const struct disklabel *lp, int partno)
    {
    	const struct partition *pp = &lp->d_partitions[partno];
    	char *p;
    	int i;
    
    	if (fstabfile == NULL ||
    	    pp->p_fstype == FS_UNUSED ||
    	    pp->p_fstype == FS_SWAP ||
    	    pp->p_fstype == FS_BOOT ||
    	    pp->p_fstype == FS_OTHER ||
    	    pp->p_fstype == FS_RAID) {
    		/* No fstabfile, no names. Not all fstypes can be named */
    		return 0;
    	}
    
    	for (;;) {
    		p = getstring("mount point",
    		    "Where to mount this filesystem (ie: / /var /usr)",
    		    mountpoints[partno] ? mountpoints[partno] : "none");
    		if (p == NULL)
    			return 1;
    		if (strcasecmp(p, "none") == 0) {
    			free(mountpoints[partno]);
    			mountpoints[partno] = NULL;
    			break;
    		}
    		for (i = 0; i < MAXPARTITIONS; i++)
    			if (mountpoints[i] != NULL && i != partno &&
    			    strcmp(p, mountpoints[i]) == 0)
    				break;
    		if (i < MAXPARTITIONS) {
    			fprintf(stderr, "'%c' already being mounted at "
    			    "'%s'\n", DL_PARTNUM2NAME(i), p);
    			break;
    		}
    		if (*p == '/') {
    			/* XXX - might as well realloc */
    			free(mountpoints[partno]);
    			if ((mountpoints[partno] = strdup(p)) == NULL)
    				err(1, NULL);
    			break;
    		}
    		fputs("Mount points must start with '/'\n", stderr);
    	}
    
    	return 0;
    }
    
    int
    micmp(const void *a1, const void *a2)
    {
    	struct mountinfo *mi1 = (struct mountinfo *)a1;
    	struct mountinfo *mi2 = (struct mountinfo *)a2;
    
    	/* We want all the NULLs at the end... */
    	if (mi1->mountpoint == NULL && mi2->mountpoint == NULL)
    		return 0;
    	else if (mi1->mountpoint == NULL)
    		return 1;
    	else if (mi2->mountpoint == NULL)
    		return -1;
    	else
    		return strcmp(mi1->mountpoint, mi2->mountpoint);
    }
    
    void
    zero_partitions(struct disklabel *lp)
    {
    	memset(lp->d_partitions, 0, sizeof(lp->d_partitions));
    	DL_SETPSIZE(&lp->d_partitions[RAW_PART], DL_GETDSIZE(lp));
    
    	mpfree(mountpoints, KEEP);
    }
    
    u_int64_t
    max_partition_size(const struct disklabel *lp, int partno)
    {
    	const struct diskchunk *chunk;
    	u_int64_t maxsize = 0, offset;
    
    	chunk = free_chunks(lp, partno);
    
    	offset = DL_GETPOFFSET(&lp->d_partitions[partno]);
    	for (; chunk->start != 0 || chunk->stop != 0; chunk++) {
    		if (offset < chunk->start || offset >= chunk->stop)
    			continue;
    		maxsize = chunk->stop - offset;
    		break;
    	}
    	return maxsize;
    }
    
    void
    psize(u_int64_t sz, char unit, const struct disklabel *lp)
    {
    	double d = scale(sz, unit, lp);
    	if (d < 0)
    		printf("%llu", sz);
    	else
    		printf("%.*f%c", unit == 'B' ? 0 : 1, d, unit);
    }
    
    void
    display_edit(const struct disklabel *lp, char unit)
    {
    	u_int64_t fr;
    	int i;
    
    	fr = editor_countfree(lp);
    	unit = canonical_unit(lp, unit);
    
    	printf("OpenBSD area: ");
    	psize(starting_sector, 0, lp);
    	printf("-");
    	psize(ending_sector, 0, lp);
    	printf("; size: ");
    	psize(ending_sector - starting_sector, unit, lp);
    	printf("; free: ");
    	psize(fr, unit, lp);
    
    	printf("\n#    %16.16s %16.16s  fstype [fsize bsize   cpg]\n",
    	    "size", "offset");
    	for (i = 0; i < lp->d_npartitions; i++)
    		display_partition(stdout, lp, i, unit);
    }
    
    void
    parse_autotable(char *filename)
    {
    	FILE	*cfile;
    	size_t	 linesize = 0;
    	char	*line = NULL, *buf, *t;
    	uint	 idx = 0, pctsum = 0;
    	struct space_allocation *sa;
    
    	if (strcmp(filename, "-") == 0)
    		cfile = stdin;
    	else if ((cfile = fopen(filename, "r")) == NULL)
    		err(1, "%s", filename);
    	if ((alloc_table = calloc(1, sizeof(struct alloc_table))) == NULL)
    		err(1, NULL);
    	alloc_table_nitems = 1;
    
    	while (getline(&line, &linesize, cfile) != -1) {
    		if ((alloc_table[0].table = reallocarray(alloc_table[0].table,
    		    idx + 1, sizeof(*sa))) == NULL)
    			err(1, NULL);
    		sa = &(alloc_table[0].table[idx]);
    		memset(sa, 0, sizeof(*sa));
    		idx++;
    
    		buf = line;
    		if ((sa->mp = get_token(&buf)) == NULL ||
    		    (sa->mp[0] != '/' && strcasecmp(sa->mp, "swap") &&
    		    strcasecmp(sa->mp, "raid")))
    			errx(1, "%s: parse error on line %u", filename, idx);
    		if ((t = get_token(&buf)) == NULL ||
    		    parse_sizerange(t, &sa->minsz, &sa->maxsz) == -1)
    			errx(1, "%s: parse error on line %u", filename, idx);
    		if ((t = get_token(&buf)) != NULL &&
    		    parse_pct(t, &sa->rate) == -1)
    			errx(1, "%s: parse error on line %u", filename, idx);
    		if (sa->minsz > sa->maxsz)
    			errx(1, "%s: min size > max size on line %u", filename,
    			    idx);
    		pctsum += sa->rate;
    	}
    	if (pctsum > 100)
    		errx(1, "%s: sum of extra space allocation > 100%%", filename);
    	alloc_table[0].sz = idx;
    	free(line);
    	fclose(cfile);
    }
    
    char *
    get_token(char **s)
    {
    	char	*p, *r;
    	size_t	 tlen = 0;
    
    	p = *s;
    	while (**s != '\0' && !isspace((u_char)**s)) {
    		(*s)++;
    		tlen++;
    	}
    	if (tlen == 0)
    		return NULL;
    
    	/* eat whitespace */
    	while (isspace((u_char)**s))
    		(*s)++;
    
    	if ((r = strndup(p, tlen)) == NULL)
    		err(1, NULL);
    	return r;
    }
    
    int
    apply_unit(double val, u_char unit, u_int64_t *n)
    {
    	u_int64_t factor = 1;
    
    	switch (tolower(unit)) {
    	case 't':
    		factor *= 1024;
    		/* FALLTHROUGH */
    	case 'g':
    		factor *= 1024;
    		/* FALLTHROUGH */
    	case 'm':
    		factor *= 1024;
    		/* FALLTHROUGH */
    	case 'k':
    		factor *= 1024;
    		break;
    	default:
    		return -1;
    	}
    
    	val *= factor / DEV_BSIZE;
    	if (val > (double)ULLONG_MAX)
    		return -1;
    	*n = val;
    	return 0;
    }
    
    int
    parse_sizespec(const char *buf, double *val, char **unit)
    {
    	errno = 0;
    	*val = strtod(buf, unit);
    	if (errno == ERANGE || *val < 0 || *val > (double)ULLONG_MAX)
    		return -1;	/* too big/small */
    	if (*val == 0 && *unit == buf)
    		return -1;	/* No conversion performed. */
    	if (*unit != NULL && *unit[0] == '\0')
    		*unit = NULL;
    	return 0;
    }
    
    int
    parse_sizerange(char *buf, u_int64_t *min, u_int64_t *max)
    {
    	char	*p, *unit1 = NULL, *unit2 = NULL;
    	double	 val1 = 0, val2 = 0;
    
    	if (strcmp(buf, "*") == 0) {
    		*min = 0;
    		*max = UINT64_MAX;
    		goto done;
    	}
    
    	if ((p = strchr(buf, '-')) != NULL) {
    		p[0] = '\0';
    		p++;
    	}
    	*max = 0;
    	if (parse_sizespec(buf, &val1, &unit1) == -1)
    		return -1;
    	if (p != NULL && p[0] != '\0') {
    		if (p[0] == '*')
    			*max = UINT64_MAX;
    		else
    			if (parse_sizespec(p, &val2, &unit2) == -1)
    				return -1;
    	}
    	if (unit1 == NULL && (unit1 = unit2) == NULL)
    		return -1;
    	if (apply_unit(val1, unit1[0], min) == -1)
    		return -1;
    	if (val2 > 0) {
    		if (apply_unit(val2, unit2[0], max) == -1)
    			return -1;
    	} else
    		if (*max == 0)
    			*max = *min;
     done:
    	free(buf);
    	return 0;
    }
    
    int
    parse_pct(char *buf, int *n)
    {
    	const char	*errstr;
    
    	if (buf[strlen(buf) - 1] == '%')
    		buf[strlen(buf) - 1] = '\0';
    	*n = strtonum(buf, 0, 100, &errstr);
    	if (errstr) {
    		warnx("parse percent %s: %s", buf, errstr);
    		return -1;
    	}
    	free(buf);
    	return 0;
    }
    
    int
    alignpartition(struct disklabel *lp, int partno, u_int64_t startalign,
        u_int64_t stopalign, int flags)
    {
    	struct partition *pp = &lp->d_partitions[partno];
    	const struct diskchunk *chunk;
    	u_int64_t start, stop, maxstop;
    
    	start = DL_GETPOFFSET(pp);
    	if ((flags & ROUND_OFFSET_UP) == ROUND_OFFSET_UP)
    		start = ROUNDUP(start, startalign);
    	else if ((flags & ROUND_OFFSET_DOWN) == ROUND_OFFSET_DOWN)
    		start = ROUNDDOWN(start, startalign);
    
    	/* Find the chunk that contains 'start'. */
    	chunk = free_chunks(lp, partno);
    	for (; chunk->start != 0 || chunk->stop != 0; chunk++) {
    		if (start >= chunk->start && start < chunk->stop)
    			break;
    	}
    	if (chunk->stop == 0) {
    		fprintf(stderr, "'%c' aligned offset %llu lies outside "
    		    "the OpenBSD bounds or inside another partition\n",
    		    DL_PARTNUM2NAME(partno), start);
    		return 1;
    	}
    
    	/* Calculate the new 'stop' sector, the sector after the partition. */
    	if ((flags & ROUND_SIZE_OVERLAP) == 0)
    		maxstop = ROUNDDOWN(chunk->stop, stopalign);
    	else
    		maxstop = ROUNDDOWN(ending_sector, stopalign);
    
    	stop = DL_GETPOFFSET(pp) + DL_GETPSIZE(pp);
    	if ((flags & ROUND_SIZE_UP) == ROUND_SIZE_UP)
    		stop = ROUNDUP(stop, stopalign);
    	else if ((flags & ROUND_SIZE_DOWN) == ROUND_SIZE_DOWN)
    		stop = ROUNDDOWN(stop, stopalign);
    	if (stop > maxstop)
    		stop = maxstop;
    
    	if (stop <= start) {
    		fprintf(stderr, "not enough space\n");
    		return 1;
    	}
    
    	if (start != DL_GETPOFFSET(pp))
    		DL_SETPOFFSET(pp, start);
    	if (stop != DL_GETPOFFSET(pp) + DL_GETPSIZE(pp))
    		DL_SETPSIZE(pp, stop - start);
    
    	return 0;
    }