[BACK]Return to cmd.c CVS log [TXT][DIR] Up to [local] / src / sbin / fdisk

File: [local] / src / sbin / fdisk / cmd.c (download)

Revision 1.180, Fri Mar 1 17:48:03 2024 UTC (3 months ago) by krw
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.179: +4 -2 lines

Allow fdisk(8) to add GPT partitions of protected types.

This makes it possible to provision virtual machine images that
need a "BIOS Boot" partition.

Report, original diff and testing by Christian Ludwig. Thanks!

ok miod@

/*	$OpenBSD: cmd.c,v 1.180 2024/03/01 17:48:03 krw Exp $	*/

/*
 * Copyright (c) 1997 Tobias Weingartner
 *
 * 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/types.h>
#include <sys/disklabel.h>

#include <err.h>
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uuid.h>

#include "part.h"
#include "disk.h"
#include "misc.h"
#include "mbr.h"
#include "gpt.h"
#include "user.h"
#include "cmd.h"

int		 gedit(const int);
int		 edit(const int, struct mbr *);
int		 gsetpid(const int);
int		 setpid(const int, struct mbr *);
int		 parsepn(const char *);
int		 parseflag(const char *, uint64_t *);

int		 ask_num(const char *, int, int, int);
int		 ask_pid(const int);
const struct uuid *ask_uuid(const struct uuid *);

extern const unsigned char	manpage[];
extern const int		manpage_sz;

int
Xreinit(const char *args, struct mbr *mbr)
{
	int			dogpt;

	dogpt = 0;

	if (strcasecmp(args, "gpt") == 0)
		dogpt = 1;
	else if (strcasecmp(args, "mbr") == 0)
		dogpt = 0;
	else if (strlen(args) > 0) {
		printf("Unrecognized modifier '%s'\n", args);
		return CMD_CONT;
	}

	if (dogpt) {
		GPT_init(GHANDGP);
		GPT_print("s", TERSE);
	} else {
		MBR_init(mbr);
		MBR_print(mbr, "s");
	}

	printf("Use 'write' to update disk.\n");

	return CMD_DIRTY;
}

int
Xswap(const char *args, struct mbr *mbr)
{
	char			 lbuf[LINEBUFSZ];
	char			*from, *to;
	int			 pf, pt;
	struct prt		 pp;
	struct gpt_partition	 gg;

	if (strlcpy(lbuf, args, sizeof(lbuf)) >= sizeof(lbuf)) {
		printf("argument string too long\n");
		return CMD_CONT;
	}

	to = lbuf;
	from = strsep(&to, WHITESPACE);

	pt = parsepn(to);
	if (pt == -1)
		return CMD_CONT;

	pf = parsepn(from);
	if (pf == -1)
		return CMD_CONT;

	if (pt == pf) {
		printf("%d same partition as %d, doing nothing.\n", pt, pf);
		return CMD_CONT;
	}

	if (gh.gh_sig == GPTSIGNATURE) {
		gg = gp[pt];
		gp[pt] = gp[pf];
		gp[pf] = gg;
	} else {
		pp = mbr->mbr_prt[pt];
		mbr->mbr_prt[pt] = mbr->mbr_prt[pf];
		mbr->mbr_prt[pf] = pp;
	}

	return CMD_DIRTY;
}

int
gedit(const int pn)
{
	struct uuid		 oldtype;

	oldtype = gp[pn].gp_type;

	if (gsetpid(pn))
		return -1;

	if (uuid_is_nil(&gp[pn].gp_type, NULL)) {
		if (uuid_is_nil(&oldtype, NULL) == 0) {
			memset(&gp[pn], 0, sizeof(gp[pn]));
			printf("Partition %d is disabled.\n", pn);
		}
		return 0;
	}

	if (GPT_get_lba_start(pn) == -1 ||
	    GPT_get_lba_end(pn) == -1 ||
	    GPT_get_name(pn) == -1) {
		return -1;
	}

	return 0;
}

int
parsepn(const char *pnstr)
{
	const char		*errstr;
	int			 maxpn, pn;

	if (pnstr == NULL) {
		printf("no partition number\n");
		return -1;
	}

	if (gh.gh_sig == GPTSIGNATURE)
		maxpn = gh.gh_part_num - 1;
	else
		maxpn = NDOSPART - 1;

	pn = strtonum(pnstr, 0, maxpn, &errstr);
	if (errstr) {
		printf("partition number is %s: %s\n", errstr, pnstr);
		return -1;
	}

	return pn;
}

int
parseflag(const char *flagstr, uint64_t *flagvalue)
{
	const char		*errstr;
	char			*ep;
	uint64_t		 val;

	flagstr += strspn(flagstr, WHITESPACE);
	if (flagstr[0] == '0' && (flagstr[1] == 'x' || flagstr[1] == 'X')) {
		errno = 0;
		val = strtoull(flagstr, &ep, 16);
		if (errno || ep == flagstr || *ep != '\0' ||
		    (gh.gh_sig != GPTSIGNATURE && val > 0xff)) {
			printf("flag value is invalid: %s\n", flagstr);
			return -1;
		}
		goto done;
	}

	if (gh.gh_sig == GPTSIGNATURE)
		val = strtonum(flagstr, 0, INT64_MAX, &errstr);
	else
		val = strtonum(flagstr, 0, 0xff, &errstr);
	if (errstr) {
		printf("flag value is %s: %s\n", errstr, flagstr);
		return -1;
	}

 done:
	*flagvalue = val;
	return 0;
}

int
edit(const int pn, struct mbr *mbr)
{
	struct chs		 start, end;
	struct prt		*pp;
	uint64_t		 track;
	unsigned char		 oldid;

	pp = &mbr->mbr_prt[pn];
	oldid = pp->prt_id;

	if (setpid(pn, mbr))
		return -1;

	if (pp->prt_id == DOSPTYP_UNUSED) {
		if (oldid != DOSPTYP_UNUSED) {
			memset(pp, 0, sizeof(*pp));
			printf("Partition %d is disabled.\n", pn);
		}
		return 0;
	}

	if (ask_yn("Do you wish to edit in CHS mode?")) {
		PRT_lba_to_chs(pp, &start, &end);
		start.chs_cyl = ask_num("BIOS Starting cylinder", start.chs_cyl,
		    0, disk.dk_cylinders - 1);
		start.chs_head = ask_num("BIOS Starting head", start.chs_head,
		    0, disk.dk_heads - 1);
		start.chs_sect = ask_num("BIOS Starting sector", start.chs_sect,
		    1, disk.dk_sectors);

		end.chs_cyl = ask_num("BIOS Ending cylinder", end.chs_cyl,
		    start.chs_cyl, disk.dk_cylinders - 1);
		end.chs_head = ask_num("BIOS Ending head", end.chs_head,
		    (start.chs_cyl == end.chs_cyl) ? start.chs_head : 0,
		    disk.dk_heads - 1);
		end.chs_sect = ask_num("BIOS Ending sector", end.chs_sect,
		    (start.chs_cyl == end.chs_cyl && start.chs_head ==
		    end.chs_head) ? start.chs_sect : 1, disk.dk_sectors);

		/* The ATA/ATAPI spec says LBA = (C × HPC + H) × SPT + (S − 1) */
		track = start.chs_cyl * disk.dk_heads + start.chs_head;
		pp->prt_bs = track * disk.dk_sectors + (start.chs_sect - 1);
		track = end.chs_cyl * disk.dk_heads + end.chs_head;
		pp->prt_ns = track * disk.dk_sectors + (end.chs_sect - 1) -
		    pp->prt_bs + 1;
	} else {
		pp->prt_bs = getuint64("Partition offset", pp->prt_bs, 0,
		    disk.dk_size - 1);
		pp->prt_ns = getuint64("Partition size",   pp->prt_ns, 1,
		    disk.dk_size - pp->prt_bs);
	}

	return 0;
}

int
Xedit(const char *args, struct mbr *mbr)
{
	struct gpt_partition	 oldgg;
	struct prt		 oldprt;
	int			 pn;

	pn = parsepn(args);
	if (pn == -1)
		return CMD_CONT;

	if (gh.gh_sig == GPTSIGNATURE) {
		oldgg = gp[pn];
		if (gedit(pn))
			gp[pn] = oldgg;
		else if (memcmp(&gp[pn], &oldgg, sizeof(gp[pn])))
			return CMD_DIRTY;
	} else {
		oldprt = mbr->mbr_prt[pn];
		if (edit(pn, mbr))
			mbr->mbr_prt[pn] = oldprt;
		else if (memcmp(&mbr->mbr_prt[pn], &oldprt, sizeof(oldprt)))
			return CMD_DIRTY;
	}

	return CMD_CONT;
}

int
gsetpid(const int pn)
{
	int32_t			 is_nil;
	uint32_t		 status;

	GPT_print_parthdr(TERSE);
	GPT_print_part(pn, "s", TERSE);

	if (PRT_protected_uuid(&gp[pn].gp_type)) {
		printf("can't edit partition type %s\n",
		    PRT_uuid_to_desc(&gp[pn].gp_type));
		return -1;
	}

	is_nil = uuid_is_nil(&gp[pn].gp_type, NULL);
	gp[pn].gp_type = *ask_uuid(&gp[pn].gp_type);
	if (PRT_protected_uuid(&gp[pn].gp_type) && is_nil == 0) {
		printf("can't change partition type to %s\n",
		    PRT_uuid_to_desc(&gp[pn].gp_type));
		return -1;
	}

	if (uuid_is_nil(&gp[pn].gp_guid, NULL)) {
		uuid_create(&gp[pn].gp_guid, &status);
		if (status != uuid_s_ok) {
			printf("could not create guid for partition\n");
			return -1;
		}
	}

	return 0;
}

int
setpid(const int pn, struct mbr *mbr)
{
	struct prt		*pp;

	pp = &mbr->mbr_prt[pn];

	PRT_print_parthdr();
	PRT_print_part(pn, pp, "s");

	pp->prt_id = ask_pid(pp->prt_id);

	return 0;
}

int
Xsetpid(const char *args, struct mbr *mbr)
{
	struct gpt_partition	oldgg;
	struct prt		oldprt;
	int			pn;

	pn = parsepn(args);
	if (pn == -1)
		return CMD_CONT;

	if (gh.gh_sig == GPTSIGNATURE) {
		oldgg = gp[pn];
		if (gsetpid(pn))
			gp[pn] = oldgg;
		else if (memcmp(&gp[pn], &oldgg, sizeof(gp[pn])))
			return CMD_DIRTY;
	} else {
		oldprt = mbr->mbr_prt[pn];
		if (setpid(pn, mbr))
			mbr->mbr_prt[pn] = oldprt;
		else if (memcmp(&mbr->mbr_prt[pn], &oldprt, sizeof(oldprt)))
			return CMD_DIRTY;
	}

	return CMD_CONT;
}

int
Xselect(const char *args, struct mbr *mbr)
{
	static uint64_t		lba_firstembr = 0;
	uint64_t		lba_self;
	int			pn;

	pn = parsepn(args);
	if (pn == -1)
		return CMD_CONT;

	lba_self = mbr->mbr_prt[pn].prt_bs;

	if ((mbr->mbr_prt[pn].prt_id != DOSPTYP_EXTEND) &&
	    (mbr->mbr_prt[pn].prt_id != DOSPTYP_EXTENDL)) {
		printf("Partition %d is not an extended partition.\n", pn);
		return CMD_CONT;
	}

	if (lba_firstembr == 0)
		lba_firstembr = lba_self;

	if (lba_self == 0) {
		printf("Loop to MBR (sector 0)! Not selected.\n");
		return CMD_CONT;
	} else {
		printf("Selected extended partition %d\n", pn);
		printf("New EMBR at offset %llu.\n", lba_self);
	}

	USER_edit(lba_self, lba_firstembr);

	return CMD_CONT;
}

int
Xprint(const char *args, struct mbr *mbr)
{
	if (gh.gh_sig == GPTSIGNATURE)
		GPT_print(args, VERBOSE);
	else if (MBR_valid_prt(mbr))
		MBR_print(mbr, args);
	else {
		DISK_printgeometry("s");
		printf("Offset: %d\tSignature: 0x%X.\tNo MBR or GPT.\n",
		    DOSBBSECTOR, (int)mbr->mbr_signature);
	}

	return CMD_CONT;
}

int
Xwrite(const char *args, struct mbr *mbr)
{
	unsigned int		i, n;

	for (i = 0, n = 0; i < nitems(mbr->mbr_prt); i++)
		if (mbr->mbr_prt[i].prt_id == DOSPTYP_OPENBSD)
			n++;
	if (n > 1) {
		warnx("MBR contains more than one OpenBSD partition!");
		if (ask_yn("Write MBR anyway?") == 0)
			return CMD_CONT;
	}

	if (gh.gh_sig == GPTSIGNATURE) {
		printf("Writing GPT.\n");
		if (GPT_write() == -1) {
			warnx("error writing GPT");
			return CMD_CONT;
		}
	} else {
		printf("Writing MBR at offset %llu.\n", mbr->mbr_lba_self);
		if (MBR_write(mbr) == -1) {
			warnx("error writing MBR");
			return CMD_CONT;
		}
		GPT_zap_headers();
	}

	return CMD_CLEAN;
}

int
Xquit(const char *args, struct mbr *mbr)
{
	return CMD_QUIT;
}

int
Xabort(const char *args, struct mbr *mbr)
{
	exit(0);
}

int
Xexit(const char *args, struct mbr *mbr)
{
	return CMD_EXIT;
}

int
Xhelp(const char *args, struct mbr *mbr)
{
	USER_help(mbr);

	return CMD_CONT;
}

int
Xupdate(const char *args, struct mbr *mbr)
{
	memcpy(mbr->mbr_code, default_dmbr.dmbr_boot, sizeof(mbr->mbr_code));
	mbr->mbr_signature = DOSMBR_SIGNATURE;
	printf("Machine code updated.\n");
	return CMD_DIRTY;
}

int
Xflag(const char *args, struct mbr *mbr)
{
	char			 lbuf[LINEBUFSZ];
	char			*part, *flag;
	uint64_t		 val;
	int			 i, pn;

	if (strlcpy(lbuf, args, sizeof(lbuf)) >= sizeof(lbuf)) {
		printf("argument string too long\n");
		return CMD_CONT;
	}

	flag = lbuf;
	part = strsep(&flag, WHITESPACE);

	pn = parsepn(part);
	if (pn == -1)
		return CMD_CONT;

	if (flag != NULL) {
		if (parseflag(flag, &val) == -1)
			return CMD_CONT;
		if (gh.gh_sig == GPTSIGNATURE)
			gp[pn].gp_attrs = val;
		else
			mbr->mbr_prt[pn].prt_flag = val;
		printf("Partition %d flag value set to 0x%llx.\n", pn, val);
	} else {
		if (gh.gh_sig == GPTSIGNATURE) {
			for (i = 0; i < gh.gh_part_num; i++) {
				if (i == pn)
					gp[i].gp_attrs = GPTPARTATTR_BOOTABLE;
				else
					gp[i].gp_attrs &= ~GPTPARTATTR_BOOTABLE;
			}
		} else {
			for (i = 0; i < nitems(mbr->mbr_prt); i++) {
				if (i == pn)
					mbr->mbr_prt[i].prt_flag = DOSACTIVE;
				else
					mbr->mbr_prt[i].prt_flag = 0x00;
			}
		}
		printf("Partition %d marked active.\n", pn);
	}

	return CMD_DIRTY;
}

int
Xmanual(const char *args, struct mbr *mbr)
{
	char			*pager = "/usr/bin/less";
	char			*p;
	FILE			*f;
	sig_t			 opipe;

	opipe = signal(SIGPIPE, SIG_IGN);
	if ((p = getenv("PAGER")) != NULL && (*p != '\0'))
		pager = p;
	if (asprintf(&p, "gunzip -qc|%s", pager) != -1) {
		f = popen(p, "w");
		if (f) {
			fwrite(manpage, manpage_sz, 1, f);
			pclose(f);
		}
		free(p);
	}

	signal(SIGPIPE, opipe);

	return CMD_CONT;
}

int
ask_num(const char *str, int dflt, int low, int high)
{
	char			 lbuf[LINEBUFSZ];
	const char		*errstr;
	int			 num;

	if (dflt < low)
		dflt = low;
	else if (dflt > high)
		dflt = high;

	do {
		printf("%s [%d - %d]: [%d] ", str, low, high, dflt);
		string_from_line(lbuf, sizeof(lbuf), TRIMMED);

		if (lbuf[0] == '\0') {
			num = dflt;
			errstr = NULL;
		} else {
			num = (int)strtonum(lbuf, low, high, &errstr);
			if (errstr)
				printf("%s is %s: %s.\n", str, errstr, lbuf);
		}
	} while (errstr);

	return num;
}

int
ask_pid(const int dflt)
{
	char			lbuf[LINEBUFSZ];
	int			num;

	for (;;) {
		printf("Partition id ('0' to disable) [01 - FF]: [%02X] ", dflt);
		printf("(? for help) ");
		string_from_line(lbuf, sizeof(lbuf), TRIMMED);

		if (strlen(lbuf) == 0)
			return dflt;
		if (strcmp(lbuf, "?") == 0) {
			PRT_print_mbrmenu(lbuf, sizeof(lbuf));
			if (strlen(lbuf) == 0)
				continue;
		}

		num = hex_octet(lbuf);
		if (num != -1)
			return num;

		printf("'%s' is not a valid partition id.\n", lbuf);
	}
}

const struct uuid *
ask_uuid(const struct uuid *olduuid)
{
	char			 lbuf[LINEBUFSZ];
	static struct uuid	 uuid;
	const char		*guid;
	char			*dflt;
	uint32_t		 status;

	dflt = PRT_uuid_to_menudflt(olduuid);
	if (dflt == NULL) {
		if (asprintf(&dflt, "00") == -1) {
			warn("asprintf()");
			goto done;
		}
	}

	for (;;) {
		printf("Partition id ('0' to disable) [01 - FF, <uuid>]: [%s] ",
		    dflt);
		printf("(? for help) ");
		string_from_line(lbuf, sizeof(lbuf), TRIMMED);

		if (strcmp(lbuf, "?") == 0) {
			PRT_print_gptmenu(lbuf, sizeof(lbuf));
			if (strlen(lbuf) == 0)
				continue;
		} else if (strlen(lbuf) == 0) {
			uuid = *olduuid;
			goto done;
		}

		guid = PRT_menuid_to_guid(hex_octet(lbuf));
		if (guid == NULL)
			guid = lbuf;

		uuid_from_string(guid, &uuid, &status);
		if (status == uuid_s_ok)
			goto done;

		printf("'%s' has no associated UUID\n", lbuf);
	}

 done:
	free(dflt);
	return &uuid;
}