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