1/*	$OpenBSD: cmd.c,v 1.180 2024/03/01 17:48:03 krw Exp $	*/
2
3/*
4 * Copyright (c) 1997 Tobias Weingartner
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20#include <sys/disklabel.h>
21
22#include <err.h>
23#include <errno.h>
24#include <signal.h>
25#include <stdint.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <uuid.h>
30
31#include "part.h"
32#include "disk.h"
33#include "misc.h"
34#include "mbr.h"
35#include "gpt.h"
36#include "user.h"
37#include "cmd.h"
38
39int		 gedit(const int);
40int		 edit(const int, struct mbr *);
41int		 gsetpid(const int);
42int		 setpid(const int, struct mbr *);
43int		 parsepn(const char *);
44int		 parseflag(const char *, uint64_t *);
45
46int		 ask_num(const char *, int, int, int);
47int		 ask_pid(const int);
48const struct uuid *ask_uuid(const struct uuid *);
49
50extern const unsigned char	manpage[];
51extern const int		manpage_sz;
52
53int
54Xreinit(const char *args, struct mbr *mbr)
55{
56	int			dogpt;
57
58	dogpt = 0;
59
60	if (strcasecmp(args, "gpt") == 0)
61		dogpt = 1;
62	else if (strcasecmp(args, "mbr") == 0)
63		dogpt = 0;
64	else if (strlen(args) > 0) {
65		printf("Unrecognized modifier '%s'\n", args);
66		return CMD_CONT;
67	}
68
69	if (dogpt) {
70		GPT_init(GHANDGP);
71		GPT_print("s", TERSE);
72	} else {
73		MBR_init(mbr);
74		MBR_print(mbr, "s");
75	}
76
77	printf("Use 'write' to update disk.\n");
78
79	return CMD_DIRTY;
80}
81
82int
83Xswap(const char *args, struct mbr *mbr)
84{
85	char			 lbuf[LINEBUFSZ];
86	char			*from, *to;
87	int			 pf, pt;
88	struct prt		 pp;
89	struct gpt_partition	 gg;
90
91	if (strlcpy(lbuf, args, sizeof(lbuf)) >= sizeof(lbuf)) {
92		printf("argument string too long\n");
93		return CMD_CONT;
94	}
95
96	to = lbuf;
97	from = strsep(&to, WHITESPACE);
98
99	pt = parsepn(to);
100	if (pt == -1)
101		return CMD_CONT;
102
103	pf = parsepn(from);
104	if (pf == -1)
105		return CMD_CONT;
106
107	if (pt == pf) {
108		printf("%d same partition as %d, doing nothing.\n", pt, pf);
109		return CMD_CONT;
110	}
111
112	if (gh.gh_sig == GPTSIGNATURE) {
113		gg = gp[pt];
114		gp[pt] = gp[pf];
115		gp[pf] = gg;
116	} else {
117		pp = mbr->mbr_prt[pt];
118		mbr->mbr_prt[pt] = mbr->mbr_prt[pf];
119		mbr->mbr_prt[pf] = pp;
120	}
121
122	return CMD_DIRTY;
123}
124
125int
126gedit(const int pn)
127{
128	struct uuid		 oldtype;
129
130	oldtype = gp[pn].gp_type;
131
132	if (gsetpid(pn))
133		return -1;
134
135	if (uuid_is_nil(&gp[pn].gp_type, NULL)) {
136		if (uuid_is_nil(&oldtype, NULL) == 0) {
137			memset(&gp[pn], 0, sizeof(gp[pn]));
138			printf("Partition %d is disabled.\n", pn);
139		}
140		return 0;
141	}
142
143	if (GPT_get_lba_start(pn) == -1 ||
144	    GPT_get_lba_end(pn) == -1 ||
145	    GPT_get_name(pn) == -1) {
146		return -1;
147	}
148
149	return 0;
150}
151
152int
153parsepn(const char *pnstr)
154{
155	const char		*errstr;
156	int			 maxpn, pn;
157
158	if (pnstr == NULL) {
159		printf("no partition number\n");
160		return -1;
161	}
162
163	if (gh.gh_sig == GPTSIGNATURE)
164		maxpn = gh.gh_part_num - 1;
165	else
166		maxpn = NDOSPART - 1;
167
168	pn = strtonum(pnstr, 0, maxpn, &errstr);
169	if (errstr) {
170		printf("partition number is %s: %s\n", errstr, pnstr);
171		return -1;
172	}
173
174	return pn;
175}
176
177int
178parseflag(const char *flagstr, uint64_t *flagvalue)
179{
180	const char		*errstr;
181	char			*ep;
182	uint64_t		 val;
183
184	flagstr += strspn(flagstr, WHITESPACE);
185	if (flagstr[0] == '0' && (flagstr[1] == 'x' || flagstr[1] == 'X')) {
186		errno = 0;
187		val = strtoull(flagstr, &ep, 16);
188		if (errno || ep == flagstr || *ep != '\0' ||
189		    (gh.gh_sig != GPTSIGNATURE && val > 0xff)) {
190			printf("flag value is invalid: %s\n", flagstr);
191			return -1;
192		}
193		goto done;
194	}
195
196	if (gh.gh_sig == GPTSIGNATURE)
197		val = strtonum(flagstr, 0, INT64_MAX, &errstr);
198	else
199		val = strtonum(flagstr, 0, 0xff, &errstr);
200	if (errstr) {
201		printf("flag value is %s: %s\n", errstr, flagstr);
202		return -1;
203	}
204
205 done:
206	*flagvalue = val;
207	return 0;
208}
209
210int
211edit(const int pn, struct mbr *mbr)
212{
213	struct chs		 start, end;
214	struct prt		*pp;
215	uint64_t		 track;
216	unsigned char		 oldid;
217
218	pp = &mbr->mbr_prt[pn];
219	oldid = pp->prt_id;
220
221	if (setpid(pn, mbr))
222		return -1;
223
224	if (pp->prt_id == DOSPTYP_UNUSED) {
225		if (oldid != DOSPTYP_UNUSED) {
226			memset(pp, 0, sizeof(*pp));
227			printf("Partition %d is disabled.\n", pn);
228		}
229		return 0;
230	}
231
232	if (ask_yn("Do you wish to edit in CHS mode?")) {
233		PRT_lba_to_chs(pp, &start, &end);
234		start.chs_cyl = ask_num("BIOS Starting cylinder", start.chs_cyl,
235		    0, disk.dk_cylinders - 1);
236		start.chs_head = ask_num("BIOS Starting head", start.chs_head,
237		    0, disk.dk_heads - 1);
238		start.chs_sect = ask_num("BIOS Starting sector", start.chs_sect,
239		    1, disk.dk_sectors);
240
241		end.chs_cyl = ask_num("BIOS Ending cylinder", end.chs_cyl,
242		    start.chs_cyl, disk.dk_cylinders - 1);
243		end.chs_head = ask_num("BIOS Ending head", end.chs_head,
244		    (start.chs_cyl == end.chs_cyl) ? start.chs_head : 0,
245		    disk.dk_heads - 1);
246		end.chs_sect = ask_num("BIOS Ending sector", end.chs_sect,
247		    (start.chs_cyl == end.chs_cyl && start.chs_head ==
248		    end.chs_head) ? start.chs_sect : 1, disk.dk_sectors);
249
250		/* The ATA/ATAPI spec says LBA = (C �� HPC + H) �� SPT + (S ��� 1) */
251		track = start.chs_cyl * disk.dk_heads + start.chs_head;
252		pp->prt_bs = track * disk.dk_sectors + (start.chs_sect - 1);
253		track = end.chs_cyl * disk.dk_heads + end.chs_head;
254		pp->prt_ns = track * disk.dk_sectors + (end.chs_sect - 1) -
255		    pp->prt_bs + 1;
256	} else {
257		pp->prt_bs = getuint64("Partition offset", pp->prt_bs, 0,
258		    disk.dk_size - 1);
259		pp->prt_ns = getuint64("Partition size",   pp->prt_ns, 1,
260		    disk.dk_size - pp->prt_bs);
261	}
262
263	return 0;
264}
265
266int
267Xedit(const char *args, struct mbr *mbr)
268{
269	struct gpt_partition	 oldgg;
270	struct prt		 oldprt;
271	int			 pn;
272
273	pn = parsepn(args);
274	if (pn == -1)
275		return CMD_CONT;
276
277	if (gh.gh_sig == GPTSIGNATURE) {
278		oldgg = gp[pn];
279		if (gedit(pn))
280			gp[pn] = oldgg;
281		else if (memcmp(&gp[pn], &oldgg, sizeof(gp[pn])))
282			return CMD_DIRTY;
283	} else {
284		oldprt = mbr->mbr_prt[pn];
285		if (edit(pn, mbr))
286			mbr->mbr_prt[pn] = oldprt;
287		else if (memcmp(&mbr->mbr_prt[pn], &oldprt, sizeof(oldprt)))
288			return CMD_DIRTY;
289	}
290
291	return CMD_CONT;
292}
293
294int
295gsetpid(const int pn)
296{
297	int32_t			 is_nil;
298	uint32_t		 status;
299
300	GPT_print_parthdr(TERSE);
301	GPT_print_part(pn, "s", TERSE);
302
303	if (PRT_protected_uuid(&gp[pn].gp_type)) {
304		printf("can't edit partition type %s\n",
305		    PRT_uuid_to_desc(&gp[pn].gp_type));
306		return -1;
307	}
308
309	is_nil = uuid_is_nil(&gp[pn].gp_type, NULL);
310	gp[pn].gp_type = *ask_uuid(&gp[pn].gp_type);
311	if (PRT_protected_uuid(&gp[pn].gp_type) && is_nil == 0) {
312		printf("can't change partition type to %s\n",
313		    PRT_uuid_to_desc(&gp[pn].gp_type));
314		return -1;
315	}
316
317	if (uuid_is_nil(&gp[pn].gp_guid, NULL)) {
318		uuid_create(&gp[pn].gp_guid, &status);
319		if (status != uuid_s_ok) {
320			printf("could not create guid for partition\n");
321			return -1;
322		}
323	}
324
325	return 0;
326}
327
328int
329setpid(const int pn, struct mbr *mbr)
330{
331	struct prt		*pp;
332
333	pp = &mbr->mbr_prt[pn];
334
335	PRT_print_parthdr();
336	PRT_print_part(pn, pp, "s");
337
338	pp->prt_id = ask_pid(pp->prt_id);
339
340	return 0;
341}
342
343int
344Xsetpid(const char *args, struct mbr *mbr)
345{
346	struct gpt_partition	oldgg;
347	struct prt		oldprt;
348	int			pn;
349
350	pn = parsepn(args);
351	if (pn == -1)
352		return CMD_CONT;
353
354	if (gh.gh_sig == GPTSIGNATURE) {
355		oldgg = gp[pn];
356		if (gsetpid(pn))
357			gp[pn] = oldgg;
358		else if (memcmp(&gp[pn], &oldgg, sizeof(gp[pn])))
359			return CMD_DIRTY;
360	} else {
361		oldprt = mbr->mbr_prt[pn];
362		if (setpid(pn, mbr))
363			mbr->mbr_prt[pn] = oldprt;
364		else if (memcmp(&mbr->mbr_prt[pn], &oldprt, sizeof(oldprt)))
365			return CMD_DIRTY;
366	}
367
368	return CMD_CONT;
369}
370
371int
372Xselect(const char *args, struct mbr *mbr)
373{
374	static uint64_t		lba_firstembr = 0;
375	uint64_t		lba_self;
376	int			pn;
377
378	pn = parsepn(args);
379	if (pn == -1)
380		return CMD_CONT;
381
382	lba_self = mbr->mbr_prt[pn].prt_bs;
383
384	if ((mbr->mbr_prt[pn].prt_id != DOSPTYP_EXTEND) &&
385	    (mbr->mbr_prt[pn].prt_id != DOSPTYP_EXTENDL)) {
386		printf("Partition %d is not an extended partition.\n", pn);
387		return CMD_CONT;
388	}
389
390	if (lba_firstembr == 0)
391		lba_firstembr = lba_self;
392
393	if (lba_self == 0) {
394		printf("Loop to MBR (sector 0)! Not selected.\n");
395		return CMD_CONT;
396	} else {
397		printf("Selected extended partition %d\n", pn);
398		printf("New EMBR at offset %llu.\n", lba_self);
399	}
400
401	USER_edit(lba_self, lba_firstembr);
402
403	return CMD_CONT;
404}
405
406int
407Xprint(const char *args, struct mbr *mbr)
408{
409	if (gh.gh_sig == GPTSIGNATURE)
410		GPT_print(args, VERBOSE);
411	else if (MBR_valid_prt(mbr))
412		MBR_print(mbr, args);
413	else {
414		DISK_printgeometry("s");
415		printf("Offset: %d\tSignature: 0x%X.\tNo MBR or GPT.\n",
416		    DOSBBSECTOR, (int)mbr->mbr_signature);
417	}
418
419	return CMD_CONT;
420}
421
422int
423Xwrite(const char *args, struct mbr *mbr)
424{
425	unsigned int		i, n;
426
427	for (i = 0, n = 0; i < nitems(mbr->mbr_prt); i++)
428		if (mbr->mbr_prt[i].prt_id == DOSPTYP_OPENBSD)
429			n++;
430	if (n > 1) {
431		warnx("MBR contains more than one OpenBSD partition!");
432		if (ask_yn("Write MBR anyway?") == 0)
433			return CMD_CONT;
434	}
435
436	if (gh.gh_sig == GPTSIGNATURE) {
437		printf("Writing GPT.\n");
438		if (GPT_write() == -1) {
439			warnx("error writing GPT");
440			return CMD_CONT;
441		}
442	} else {
443		printf("Writing MBR at offset %llu.\n", mbr->mbr_lba_self);
444		if (MBR_write(mbr) == -1) {
445			warnx("error writing MBR");
446			return CMD_CONT;
447		}
448		GPT_zap_headers();
449	}
450
451	return CMD_CLEAN;
452}
453
454int
455Xquit(const char *args, struct mbr *mbr)
456{
457	return CMD_QUIT;
458}
459
460int
461Xabort(const char *args, struct mbr *mbr)
462{
463	exit(0);
464}
465
466int
467Xexit(const char *args, struct mbr *mbr)
468{
469	return CMD_EXIT;
470}
471
472int
473Xhelp(const char *args, struct mbr *mbr)
474{
475	USER_help(mbr);
476
477	return CMD_CONT;
478}
479
480int
481Xupdate(const char *args, struct mbr *mbr)
482{
483	memcpy(mbr->mbr_code, default_dmbr.dmbr_boot, sizeof(mbr->mbr_code));
484	mbr->mbr_signature = DOSMBR_SIGNATURE;
485	printf("Machine code updated.\n");
486	return CMD_DIRTY;
487}
488
489int
490Xflag(const char *args, struct mbr *mbr)
491{
492	char			 lbuf[LINEBUFSZ];
493	char			*part, *flag;
494	uint64_t		 val;
495	int			 i, pn;
496
497	if (strlcpy(lbuf, args, sizeof(lbuf)) >= sizeof(lbuf)) {
498		printf("argument string too long\n");
499		return CMD_CONT;
500	}
501
502	flag = lbuf;
503	part = strsep(&flag, WHITESPACE);
504
505	pn = parsepn(part);
506	if (pn == -1)
507		return CMD_CONT;
508
509	if (flag != NULL) {
510		if (parseflag(flag, &val) == -1)
511			return CMD_CONT;
512		if (gh.gh_sig == GPTSIGNATURE)
513			gp[pn].gp_attrs = val;
514		else
515			mbr->mbr_prt[pn].prt_flag = val;
516		printf("Partition %d flag value set to 0x%llx.\n", pn, val);
517	} else {
518		if (gh.gh_sig == GPTSIGNATURE) {
519			for (i = 0; i < gh.gh_part_num; i++) {
520				if (i == pn)
521					gp[i].gp_attrs = GPTPARTATTR_BOOTABLE;
522				else
523					gp[i].gp_attrs &= ~GPTPARTATTR_BOOTABLE;
524			}
525		} else {
526			for (i = 0; i < nitems(mbr->mbr_prt); i++) {
527				if (i == pn)
528					mbr->mbr_prt[i].prt_flag = DOSACTIVE;
529				else
530					mbr->mbr_prt[i].prt_flag = 0x00;
531			}
532		}
533		printf("Partition %d marked active.\n", pn);
534	}
535
536	return CMD_DIRTY;
537}
538
539int
540Xmanual(const char *args, struct mbr *mbr)
541{
542	char			*pager = "/usr/bin/less";
543	char			*p;
544	FILE			*f;
545	sig_t			 opipe;
546
547	opipe = signal(SIGPIPE, SIG_IGN);
548	if ((p = getenv("PAGER")) != NULL && (*p != '\0'))
549		pager = p;
550	if (asprintf(&p, "gunzip -qc|%s", pager) != -1) {
551		f = popen(p, "w");
552		if (f) {
553			fwrite(manpage, manpage_sz, 1, f);
554			pclose(f);
555		}
556		free(p);
557	}
558
559	signal(SIGPIPE, opipe);
560
561	return CMD_CONT;
562}
563
564int
565ask_num(const char *str, int dflt, int low, int high)
566{
567	char			 lbuf[LINEBUFSZ];
568	const char		*errstr;
569	int			 num;
570
571	if (dflt < low)
572		dflt = low;
573	else if (dflt > high)
574		dflt = high;
575
576	do {
577		printf("%s [%d - %d]: [%d] ", str, low, high, dflt);
578		string_from_line(lbuf, sizeof(lbuf), TRIMMED);
579
580		if (lbuf[0] == '\0') {
581			num = dflt;
582			errstr = NULL;
583		} else {
584			num = (int)strtonum(lbuf, low, high, &errstr);
585			if (errstr)
586				printf("%s is %s: %s.\n", str, errstr, lbuf);
587		}
588	} while (errstr);
589
590	return num;
591}
592
593int
594ask_pid(const int dflt)
595{
596	char			lbuf[LINEBUFSZ];
597	int			num;
598
599	for (;;) {
600		printf("Partition id ('0' to disable) [01 - FF]: [%02X] ", dflt);
601		printf("(? for help) ");
602		string_from_line(lbuf, sizeof(lbuf), TRIMMED);
603
604		if (strlen(lbuf) == 0)
605			return dflt;
606		if (strcmp(lbuf, "?") == 0) {
607			PRT_print_mbrmenu(lbuf, sizeof(lbuf));
608			if (strlen(lbuf) == 0)
609				continue;
610		}
611
612		num = hex_octet(lbuf);
613		if (num != -1)
614			return num;
615
616		printf("'%s' is not a valid partition id.\n", lbuf);
617	}
618}
619
620const struct uuid *
621ask_uuid(const struct uuid *olduuid)
622{
623	char			 lbuf[LINEBUFSZ];
624	static struct uuid	 uuid;
625	const char		*guid;
626	char			*dflt;
627	uint32_t		 status;
628
629	dflt = PRT_uuid_to_menudflt(olduuid);
630	if (dflt == NULL) {
631		if (asprintf(&dflt, "00") == -1) {
632			warn("asprintf()");
633			goto done;
634		}
635	}
636
637	for (;;) {
638		printf("Partition id ('0' to disable) [01 - FF, <uuid>]: [%s] ",
639		    dflt);
640		printf("(? for help) ");
641		string_from_line(lbuf, sizeof(lbuf), TRIMMED);
642
643		if (strcmp(lbuf, "?") == 0) {
644			PRT_print_gptmenu(lbuf, sizeof(lbuf));
645			if (strlen(lbuf) == 0)
646				continue;
647		} else if (strlen(lbuf) == 0) {
648			uuid = *olduuid;
649			goto done;
650		}
651
652		guid = PRT_menuid_to_guid(hex_octet(lbuf));
653		if (guid == NULL)
654			guid = lbuf;
655
656		uuid_from_string(guid, &uuid, &status);
657		if (status == uuid_s_ok)
658			goto done;
659
660		printf("'%s' has no associated UUID\n", lbuf);
661	}
662
663 done:
664	free(dflt);
665	return &uuid;
666}
667