xref: /freebsd/usr.sbin/bsdinstall/partedit/partedit.c (revision fd45b686f9d92f583366c75b22c04c7ee49709c0)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2011 Nathan Whitehorn
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/param.h>
30 
31 #include <bsddialog.h>
32 #include <err.h>
33 #include <errno.h>
34 #include <fstab.h>
35 #include <inttypes.h>
36 #include <libgeom.h>
37 #include <libutil.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sysexits.h>
42 
43 #include "diskmenu.h"
44 #include "partedit.h"
45 
46 struct pmetadata_head part_metadata;
47 static int sade_mode = 0;
48 
49 static int apply_changes(struct gmesh *mesh);
50 static void apply_workaround(struct gmesh *mesh);
51 static struct partedit_item *read_geom_mesh(struct gmesh *mesh, int *nitems);
52 static void add_geom_children(struct ggeom *gp, int recurse,
53     struct partedit_item **items, int *nitems);
54 static void init_fstab_metadata(void);
55 static void get_mount_points(struct partedit_item *items, int nitems);
56 static int validate_setup(void);
57 
58 static void
59 sigint_handler(int sig)
60 {
61 	struct gmesh mesh;
62 
63 	/* Revert all changes and exit dialog-mode cleanly on SIGINT */
64 	if (geom_gettree(&mesh) == 0) {
65 		gpart_revert_all(&mesh);
66 		geom_deletetree(&mesh);
67 	}
68 
69 	bsddialog_end();
70 
71 	exit(1);
72 }
73 
74 int
75 main(int argc, const char **argv)
76 {
77 	struct partition_metadata *md;
78 	const char *progname, *prompt;
79 	struct partedit_item *items = NULL;
80 	struct gmesh mesh;
81 	int i, op, nitems;
82 	int error;
83 	struct bsddialog_conf conf;
84 
85 	progname = getprogname();
86 	if (strcmp(progname, "sade") == 0)
87 		sade_mode = 1;
88 
89 	TAILQ_INIT(&part_metadata);
90 
91 	init_fstab_metadata();
92 
93 	if (bsddialog_init() == BSDDIALOG_ERROR)
94 		err(1, "%s", bsddialog_geterror());
95 	bsddialog_initconf(&conf);
96 	if (!sade_mode)
97 		bsddialog_backtitle(&conf, OSNAME " Installer");
98 	i = 0;
99 
100 	/* Revert changes on SIGINT */
101 	signal(SIGINT, sigint_handler);
102 
103 	if (strcmp(progname, "autopart") == 0) { /* Guided */
104 		prompt = "Please review the disk setup. When complete, press "
105 		    "the Finish button.";
106 		/* Experimental ZFS autopartition support */
107 		if (argc > 1 && strcmp(argv[1], "zfs") == 0) {
108 			part_wizard("zfs");
109 		} else {
110 			part_wizard("ufs");
111 		}
112 	} else if (strcmp(progname, "scriptedpart") == 0) {
113 		error = scripted_editor(argc, argv);
114 		prompt = NULL;
115 		if (error != 0) {
116 			bsddialog_end();
117 			return (error);
118 		}
119 	} else {
120 		prompt = "Create partitions for " OSNAME ", F1 for help.\n"
121 		    "No changes will be made until you select Finish.";
122 	}
123 
124 	/* Show the part editor either immediately, or to confirm wizard */
125 	while (prompt != NULL) {
126 		bsddialog_clear(0);
127 		if (!sade_mode)
128 			bsddialog_backtitle(&conf, "FreeBSD Installer");
129 
130 		error = geom_gettree(&mesh);
131 		if (error == 0)
132 			items = read_geom_mesh(&mesh, &nitems);
133 		if (error || items == NULL) {
134 			conf.title = "Error";
135 			bsddialog_msgbox(&conf, "No disks found. If you need "
136 			    "to install a kernel driver, choose Shell at the "
137 			    "installation menu.", 0, 0);
138 			break;
139 		}
140 
141 		get_mount_points(items, nitems);
142 
143 		if (i >= nitems)
144 			i = nitems - 1;
145 		op = diskmenu_show("Partition Editor", prompt, items, nitems,
146 		    &i);
147 
148 		switch (op) {
149 		case BUTTON_CREATE:
150 			gpart_create((struct gprovider *)(items[i].cookie),
151 			    NULL, NULL, NULL, NULL, 1);
152 			break;
153 		case BUTTON_DELETE:
154 			gpart_delete((struct gprovider *)(items[i].cookie));
155 			break;
156 		case BUTTON_MODIFY:
157 			gpart_edit((struct gprovider *)(items[i].cookie));
158 			break;
159 		case BUTTON_REVERT:
160 			gpart_revert_all(&mesh);
161 			while ((md = TAILQ_FIRST(&part_metadata)) != NULL) {
162 				if (md->fstab != NULL) {
163 					free(md->fstab->fs_spec);
164 					free(md->fstab->fs_file);
165 					free(md->fstab->fs_vfstype);
166 					free(md->fstab->fs_mntops);
167 					free(md->fstab->fs_type);
168 					free(md->fstab);
169 				}
170 				if (md->newfs != NULL)
171 					free(md->newfs);
172 				free(md->name);
173 
174 				TAILQ_REMOVE(&part_metadata, md, metadata);
175 				free(md);
176 			}
177 			init_fstab_metadata();
178 			break;
179 		case BUTTON_AUTO:
180 			part_wizard("ufs");
181 			break;
182 		}
183 
184 		error = 0;
185 		if (op == BUTTON_FINISH) {
186 			conf.button.ok_label = "Commit";
187 			conf.button.with_extra = true;
188 			conf.button.extra_label = "Revert & Exit";
189 			conf.button.cancel_label = "Back";
190 			conf.title = "Confirmation";
191 			op = bsddialog_yesno(&conf, "Your changes will now be "
192 			    "written to disk. If you have chosen to overwrite "
193 			    "existing data, it will be PERMANENTLY ERASED. Are "
194 			    "you sure you want to commit your changes?", 0, 0);
195 			conf.button.ok_label = NULL;
196 			conf.button.with_extra = false;
197 			conf.button.extra_label = NULL;
198 			conf.button.cancel_label = NULL;
199 
200 			if (op == BSDDIALOG_OK && validate_setup()) { /* Save */
201 				error = apply_changes(&mesh);
202 				if (!error)
203 					apply_workaround(&mesh);
204 				break;
205 			} else if (op == BSDDIALOG_EXTRA) { /* Quit */
206 				gpart_revert_all(&mesh);
207 				error =	-1;
208 				break;
209 			}
210 		}
211 
212 		geom_deletetree(&mesh);
213 		free(items);
214 	}
215 
216 	if (prompt == NULL) {
217 		error = geom_gettree(&mesh);
218 		if (error == 0) {
219 			if (validate_setup()) {
220 				error = apply_changes(&mesh);
221 			} else {
222 				gpart_revert_all(&mesh);
223 				error = -1;
224 			}
225 			geom_deletetree(&mesh);
226 		}
227 	}
228 
229 	bsddialog_end();
230 
231 	return (error);
232 }
233 
234 struct partition_metadata *
235 get_part_metadata(const char *name, int create)
236 {
237 	struct partition_metadata *md;
238 
239 	TAILQ_FOREACH(md, &part_metadata, metadata)
240 		if (md->name != NULL && strcmp(md->name, name) == 0)
241 			break;
242 
243 	if (md == NULL && create) {
244 		md = calloc(1, sizeof(*md));
245 		md->name = strdup(name);
246 		TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
247 	}
248 
249 	return (md);
250 }
251 
252 void
253 delete_part_metadata(const char *name)
254 {
255 	struct partition_metadata *md;
256 
257 	TAILQ_FOREACH(md, &part_metadata, metadata) {
258 		if (md->name != NULL && strcmp(md->name, name) == 0) {
259 			if (md->fstab != NULL) {
260 				free(md->fstab->fs_spec);
261 				free(md->fstab->fs_file);
262 				free(md->fstab->fs_vfstype);
263 				free(md->fstab->fs_mntops);
264 				free(md->fstab->fs_type);
265 				free(md->fstab);
266 			}
267 			if (md->newfs != NULL)
268 				free(md->newfs);
269 			free(md->name);
270 
271 			TAILQ_REMOVE(&part_metadata, md, metadata);
272 			free(md);
273 			break;
274 		}
275 	}
276 }
277 
278 static int
279 validate_setup(void)
280 {
281 	struct partition_metadata *md, *root = NULL;
282 	int button;
283 	struct bsddialog_conf conf;
284 
285 	TAILQ_FOREACH(md, &part_metadata, metadata) {
286 		if (md->fstab != NULL && strcmp(md->fstab->fs_file, "/") == 0)
287 			root = md;
288 
289 		/* XXX: Check for duplicate mountpoints */
290 	}
291 
292 	bsddialog_initconf(&conf);
293 
294 	if (root == NULL) {
295 		conf.title = "Error";
296 		bsddialog_msgbox(&conf, "No root partition was found. "
297 		    "The root " OSNAME " partition must have a mountpoint "
298 		    "of '/'.", 0, 0);
299 		return (false);
300 	}
301 
302 	/*
303 	 * Check for root partitions that we aren't formatting, which is
304 	 * usually a mistake
305 	 */
306 	if (root->newfs == NULL && !sade_mode) {
307 		conf.button.default_cancel = true;
308 		conf.title = "Warning";
309 		button = bsddialog_yesno(&conf, "The chosen root partition "
310 		    "has a preexisting filesystem. If it contains an existing "
311 		    OSNAME " system, please update it with freebsd-update "
312 		    "instead of installing a new system on it. The partition "
313 		    "can also be erased by pressing \"No\" and then deleting "
314 		    "and recreating it. Are you sure you want to proceed?",
315 		    0, 0);
316 		if (button == BSDDIALOG_CANCEL)
317 			return (false);
318 	}
319 
320 	return (true);
321 }
322 
323 static int
324 mountpoint_sorter(const void *xa, const void *xb)
325 {
326 	struct partition_metadata *a = *(struct partition_metadata **)xa;
327 	struct partition_metadata *b = *(struct partition_metadata **)xb;
328 
329 	if (a->fstab == NULL && b->fstab == NULL)
330 		return 0;
331 	if (a->fstab == NULL)
332 		return 1;
333 	if (b->fstab == NULL)
334 		return -1;
335 
336 	return strcmp(a->fstab->fs_file, b->fstab->fs_file);
337 }
338 
339 static int
340 apply_changes(struct gmesh *mesh)
341 {
342 	struct partition_metadata *md;
343 	char message[512];
344 	int i, nitems, error, *miniperc;
345 	const char **minilabel;
346 	const char *fstab_path;
347 	FILE *fstab;
348 	char *command;
349 	struct bsddialog_conf conf;
350 
351 	nitems = 1; /* Partition table changes */
352 	TAILQ_FOREACH(md, &part_metadata, metadata) {
353 		if (md->newfs != NULL)
354 			nitems++;
355 	}
356 	minilabel = calloc(nitems, sizeof(const char *));
357 	miniperc  = calloc(nitems, sizeof(int));
358 	minilabel[0] = "Writing partition tables";
359 	miniperc[0]  = BSDDIALOG_MG_INPROGRESS;
360 	i = 1;
361 	TAILQ_FOREACH(md, &part_metadata, metadata) {
362 		if (md->newfs != NULL) {
363 			char *item;
364 
365 			asprintf(&item, "Initializing %s", md->name);
366 			minilabel[i] = item;
367 			miniperc[i]  = BSDDIALOG_MG_PENDING;
368 			i++;
369 		}
370 	}
371 
372 	i = 0;
373 	bsddialog_initconf(&conf);
374 	conf.title = "Initializing";
375 	bsddialog_mixedgauge(&conf,
376 	    "Initializing file systems. Please wait.", 0, 0, i * 100 / nitems,
377 	    nitems, minilabel, miniperc);
378 	gpart_commit(mesh);
379 	miniperc[i] = BSDDIALOG_MG_COMPLETED;
380 	i++;
381 
382 	if (getenv("BSDINSTALL_LOG") == NULL)
383 		setenv("BSDINSTALL_LOG", "/dev/null", 1);
384 
385 	TAILQ_FOREACH(md, &part_metadata, metadata) {
386 		if (md->newfs != NULL) {
387 			miniperc[i] = BSDDIALOG_MG_INPROGRESS;
388 			bsddialog_mixedgauge(&conf,
389 			    "Initializing file systems. Please wait.", 0, 0,
390 			    i * 100 / nitems, nitems, minilabel, miniperc);
391 			asprintf(&command, "(echo %s; %s) >>%s 2>>%s",
392 			    md->newfs, md->newfs, getenv("BSDINSTALL_LOG"),
393 			    getenv("BSDINSTALL_LOG"));
394 			error = system(command);
395 			free(command);
396 			miniperc[i] = (error == 0) ?
397 			    BSDDIALOG_MG_COMPLETED : BSDDIALOG_MG_FAILED;
398 			i++;
399 		}
400 	}
401 	bsddialog_mixedgauge(&conf, "Initializing file systems. Please wait.",
402 	    0, 0, i * 100 / nitems, nitems, minilabel, miniperc);
403 
404 	for (i = 1; i < nitems; i++)
405 		free(__DECONST(char *, minilabel[i]));
406 
407 	free(minilabel);
408 	free(miniperc);
409 
410 	/* Sort filesystems for fstab so that mountpoints are ordered */
411 	{
412 		struct partition_metadata **tobesorted;
413 		struct partition_metadata *tmp;
414 		int nparts = 0;
415 		TAILQ_FOREACH(md, &part_metadata, metadata)
416 			nparts++;
417 		tobesorted = malloc(sizeof(struct partition_metadata *)*nparts);
418 		nparts = 0;
419 		TAILQ_FOREACH_SAFE(md, &part_metadata, metadata, tmp) {
420 			tobesorted[nparts++] = md;
421 			TAILQ_REMOVE(&part_metadata, md, metadata);
422 		}
423 		qsort(tobesorted, nparts, sizeof(tobesorted[0]),
424 		    mountpoint_sorter);
425 
426 		/* Now re-add everything */
427 		while (nparts-- > 0)
428 			TAILQ_INSERT_HEAD(&part_metadata,
429 			    tobesorted[nparts], metadata);
430 		free(tobesorted);
431 	}
432 
433 	if (getenv("PATH_FSTAB") != NULL)
434 		fstab_path = getenv("PATH_FSTAB");
435 	else
436 		fstab_path = "/etc/fstab";
437 	fstab = fopen(fstab_path, "w+");
438 	if (fstab == NULL) {
439 		snprintf(message, sizeof(message),
440 		    "Cannot open fstab file %s for writing (%s)\n",
441 		    getenv("PATH_FSTAB"), strerror(errno));
442 		conf.title = "Error";
443 		bsddialog_msgbox(&conf, message, 0, 0);
444 		return (-1);
445 	}
446 	fprintf(fstab, "# Device\tMountpoint\tFStype\tOptions\tDump\tPass#\n");
447 	TAILQ_FOREACH(md, &part_metadata, metadata) {
448 		if (md->fstab != NULL)
449 			fprintf(fstab, "%s\t%s\t\t%s\t%s\t%d\t%d\n",
450 			    md->fstab->fs_spec, md->fstab->fs_file,
451 			    md->fstab->fs_vfstype, md->fstab->fs_mntops,
452 			    md->fstab->fs_freq, md->fstab->fs_passno);
453 	}
454 	fclose(fstab);
455 
456 	return (0);
457 }
458 
459 static void
460 apply_workaround(struct gmesh *mesh)
461 {
462 	struct gclass *classp;
463 	struct ggeom *gp;
464 	struct gconfig *gc;
465 	const char *scheme = NULL, *modified = NULL;
466 	struct bsddialog_conf conf;
467 
468 	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
469 		if (strcmp(classp->lg_name, "PART") == 0)
470 			break;
471 	}
472 
473 	if (strcmp(classp->lg_name, "PART") != 0) {
474 		bsddialog_initconf(&conf);
475 		conf.title = "Error";
476 		bsddialog_msgbox(&conf, "gpart not found!", 0, 0);
477 		return;
478 	}
479 
480 	LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
481 		LIST_FOREACH(gc, &gp->lg_config, lg_config) {
482 			if (strcmp(gc->lg_name, "scheme") == 0) {
483 				scheme = gc->lg_val;
484 			} else if (strcmp(gc->lg_name, "modified") == 0) {
485 				modified = gc->lg_val;
486 			}
487 		}
488 
489 		if (scheme && strcmp(scheme, "GPT") == 0 &&
490 		    modified && strcmp(modified, "true") == 0) {
491 			if (getenv("WORKAROUND_LENOVO"))
492 				gpart_set_root(gp->lg_name, "lenovofix");
493 			if (getenv("WORKAROUND_GPTACTIVE"))
494 				gpart_set_root(gp->lg_name, "active");
495 		}
496 	}
497 }
498 
499 static struct partedit_item *
500 read_geom_mesh(struct gmesh *mesh, int *nitems)
501 {
502 	struct gclass *classp;
503 	struct ggeom *gp;
504 	struct partedit_item *items;
505 
506 	*nitems = 0;
507 	items = NULL;
508 
509 	/*
510 	 * Build the device table. First add all disks (and CDs).
511 	 */
512 
513 	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
514 		if (strcmp(classp->lg_name, "DISK") != 0 &&
515 		    strcmp(classp->lg_name, "MD") != 0)
516 			continue;
517 
518 		/* Now recurse into all children */
519 		LIST_FOREACH(gp, &classp->lg_geom, lg_geom)
520 			add_geom_children(gp, 0, &items, nitems);
521 	}
522 
523 	return (items);
524 }
525 
526 static void
527 add_geom_children(struct ggeom *gp, int recurse, struct partedit_item **items,
528     int *nitems)
529 {
530 	struct gconsumer *cp;
531 	struct gprovider *pp;
532 	struct gconfig *gc;
533 
534 	if (strcmp(gp->lg_class->lg_name, "PART") == 0 &&
535 	    !LIST_EMPTY(&gp->lg_config)) {
536 		LIST_FOREACH(gc, &gp->lg_config, lg_config) {
537 			if (strcmp(gc->lg_name, "scheme") == 0)
538 				(*items)[*nitems-1].type = gc->lg_val;
539 		}
540 	}
541 
542 	if (LIST_EMPTY(&gp->lg_provider))
543 		return;
544 
545 	LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
546 		if (strcmp(gp->lg_class->lg_name, "LABEL") == 0)
547 			continue;
548 
549 		/* Skip WORM media */
550 		if (strncmp(pp->lg_name, "cd", 2) == 0)
551 			continue;
552 
553 		*items = realloc(*items,
554 		    (*nitems+1)*sizeof(struct partedit_item));
555 		(*items)[*nitems].indentation = recurse;
556 		(*items)[*nitems].name = pp->lg_name;
557 		(*items)[*nitems].size = pp->lg_mediasize;
558 		(*items)[*nitems].mountpoint = NULL;
559 		(*items)[*nitems].type = "";
560 		(*items)[*nitems].cookie = pp;
561 
562 		LIST_FOREACH(gc, &pp->lg_config, lg_config) {
563 			if (strcmp(gc->lg_name, "type") == 0)
564 				(*items)[*nitems].type = gc->lg_val;
565 		}
566 
567 		/* Skip swap-backed MD devices */
568 		if (strcmp(gp->lg_class->lg_name, "MD") == 0 &&
569 		    strcmp((*items)[*nitems].type, "swap") == 0)
570 			continue;
571 
572 		(*nitems)++;
573 
574 		LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
575 			add_geom_children(cp->lg_geom, recurse+1, items,
576 			    nitems);
577 
578 		/* Only use first provider for acd */
579 		if (strcmp(gp->lg_class->lg_name, "ACD") == 0)
580 			break;
581 	}
582 }
583 
584 static void
585 init_fstab_metadata(void)
586 {
587 	struct fstab *fstab;
588 	struct partition_metadata *md;
589 
590 	setfsent();
591 	while ((fstab = getfsent()) != NULL) {
592 		md = calloc(1, sizeof(struct partition_metadata));
593 
594 		md->name = NULL;
595 		if (strncmp(fstab->fs_spec, "/dev/", 5) == 0)
596 			md->name = strdup(&fstab->fs_spec[5]);
597 
598 		md->fstab = malloc(sizeof(struct fstab));
599 		md->fstab->fs_spec = strdup(fstab->fs_spec);
600 		md->fstab->fs_file = strdup(fstab->fs_file);
601 		md->fstab->fs_vfstype = strdup(fstab->fs_vfstype);
602 		md->fstab->fs_mntops = strdup(fstab->fs_mntops);
603 		md->fstab->fs_type = strdup(fstab->fs_type);
604 		md->fstab->fs_freq = fstab->fs_freq;
605 		md->fstab->fs_passno = fstab->fs_passno;
606 
607 		md->newfs = NULL;
608 
609 		TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
610 	}
611 }
612 
613 static void
614 get_mount_points(struct partedit_item *items, int nitems)
615 {
616 	struct partition_metadata *md;
617 	int i;
618 
619 	for (i = 0; i < nitems; i++) {
620 		TAILQ_FOREACH(md, &part_metadata, metadata) {
621 			if (md->name != NULL && md->fstab != NULL &&
622 			    strcmp(md->name, items[i].name) == 0) {
623 				items[i].mountpoint = md->fstab->fs_file;
624 				break;
625 			}
626 		}
627 	}
628 }
629