xref: /freebsd/usr.sbin/bsdinstall/partedit/partedit.c (revision cfd6422a5217410fbd66f7a7a8a64d9d85e61229)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 <dialog.h>
34 #include <dlg_keys.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <fstab.h>
38 #include <inttypes.h>
39 #include <libgeom.h>
40 #include <libutil.h>
41 #include <stdlib.h>
42 #include <sysexits.h>
43 
44 #include "diskeditor.h"
45 #include "partedit.h"
46 
47 struct pmetadata_head part_metadata;
48 int tmpdfd;
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 	geom_gettree(&mesh);
67 	gpart_revert_all(&mesh);
68 	geom_deletetree(&mesh);
69 
70 	end_dialog();
71 
72 	close(tmpdfd);
73 
74 	exit(1);
75 }
76 
77 int
78 main(int argc, const char **argv)
79 {
80 	struct partition_metadata *md;
81 	const char *progname, *prompt, *tmpdir;
82 	struct partedit_item *items = NULL;
83 	struct gmesh mesh;
84 	int i, op, nitems, nscroll;
85 	int error;
86 
87 	progname = getprogname();
88 	if (strcmp(progname, "sade") == 0)
89 		sade_mode = 1;
90 
91 	TAILQ_INIT(&part_metadata);
92 
93 	tmpdir = getenv("TMPDIR");
94 	if (tmpdir == NULL)
95 		tmpdir = "/tmp";
96 	tmpdfd = open(tmpdir, O_DIRECTORY);
97 	if (tmpdfd < 0)
98 		err(EX_OSERR, "%s", tmpdir);
99 	unlinkat(tmpdfd, "bsdinstall-esps", 0);
100 
101 	init_fstab_metadata();
102 
103 	init_dialog(stdin, stdout);
104 	if (!sade_mode)
105 		dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer");
106 	dialog_vars.item_help = TRUE;
107 	nscroll = i = 0;
108 
109 	/* Revert changes on SIGINT */
110 	signal(SIGINT, sigint_handler);
111 
112 	if (strcmp(progname, "autopart") == 0) { /* Guided */
113 		prompt = "Please review the disk setup. When complete, press "
114 		    "the Finish button.";
115 		/* Experimental ZFS autopartition support */
116 		if (argc > 1 && strcmp(argv[1], "zfs") == 0) {
117 			part_wizard("zfs");
118 		} else {
119 			part_wizard("ufs");
120 		}
121 	} else if (strcmp(progname, "scriptedpart") == 0) {
122 		error = scripted_editor(argc, argv);
123 		prompt = NULL;
124 		if (error != 0) {
125 			end_dialog();
126 			return (error);
127 		}
128 	} else {
129 		prompt = "Create partitions for FreeBSD. No changes will be "
130 		    "made until you select Finish.";
131 	}
132 
133 	/* Show the part editor either immediately, or to confirm wizard */
134 	while (prompt != NULL) {
135 		dlg_clear();
136 		dlg_put_backtitle();
137 
138 		error = geom_gettree(&mesh);
139 		if (error == 0)
140 			items = read_geom_mesh(&mesh, &nitems);
141 		if (error || items == NULL) {
142 			dialog_msgbox("Error", "No disks found. If you need to "
143 			    "install a kernel driver, choose Shell at the "
144 			    "installation menu.", 0, 0, TRUE);
145 			break;
146 		}
147 
148 		get_mount_points(items, nitems);
149 
150 		if (i >= nitems)
151 			i = nitems - 1;
152 		op = diskeditor_show("Partition Editor", prompt,
153 		    items, nitems, &i, &nscroll);
154 
155 		switch (op) {
156 		case 0: /* Create */
157 			gpart_create((struct gprovider *)(items[i].cookie),
158 			    NULL, NULL, NULL, NULL, 1);
159 			break;
160 		case 1: /* Delete */
161 			gpart_delete((struct gprovider *)(items[i].cookie));
162 			break;
163 		case 2: /* Modify */
164 			gpart_edit((struct gprovider *)(items[i].cookie));
165 			break;
166 		case 3: /* Revert */
167 			gpart_revert_all(&mesh);
168 			while ((md = TAILQ_FIRST(&part_metadata)) != NULL) {
169 				if (md->fstab != NULL) {
170 					free(md->fstab->fs_spec);
171 					free(md->fstab->fs_file);
172 					free(md->fstab->fs_vfstype);
173 					free(md->fstab->fs_mntops);
174 					free(md->fstab->fs_type);
175 					free(md->fstab);
176 				}
177 				if (md->newfs != NULL)
178 					free(md->newfs);
179 				free(md->name);
180 
181 				TAILQ_REMOVE(&part_metadata, md, metadata);
182 				free(md);
183 			}
184 			init_fstab_metadata();
185 			break;
186 		case 4: /* Auto */
187 			part_wizard("ufs");
188 			break;
189 		}
190 
191 		error = 0;
192 		if (op == 5) { /* Finished */
193 			dialog_vars.ok_label = __DECONST(char *, "Commit");
194 			dialog_vars.extra_label =
195 			    __DECONST(char *, "Revert & Exit");
196 			dialog_vars.extra_button = TRUE;
197 			dialog_vars.cancel_label = __DECONST(char *, "Back");
198 			op = dialog_yesno("Confirmation", "Your changes will "
199 			    "now be written to disk. If you have chosen to "
200 			    "overwrite existing data, it will be PERMANENTLY "
201 			    "ERASED. Are you sure you want to commit your "
202 			    "changes?", 0, 0);
203 			dialog_vars.ok_label = NULL;
204 			dialog_vars.extra_button = FALSE;
205 			dialog_vars.cancel_label = NULL;
206 
207 			if (op == 0 && validate_setup()) { /* Save */
208 				error = apply_changes(&mesh);
209 				if (!error)
210 					apply_workaround(&mesh);
211 				break;
212 			} else if (op == 3) { /* Quit */
213 				gpart_revert_all(&mesh);
214 				error =	-1;
215 				break;
216 			}
217 		}
218 
219 		geom_deletetree(&mesh);
220 		free(items);
221 	}
222 
223 	if (prompt == NULL) {
224 		error = geom_gettree(&mesh);
225 		if (validate_setup()) {
226 			error = apply_changes(&mesh);
227 		} else {
228 			gpart_revert_all(&mesh);
229 			error = -1;
230 		}
231 	}
232 
233 	geom_deletetree(&mesh);
234 	free(items);
235 	end_dialog();
236 	close(tmpdfd);
237 
238 	return (error);
239 }
240 
241 struct partition_metadata *
242 get_part_metadata(const char *name, int create)
243 {
244 	struct partition_metadata *md;
245 
246 	TAILQ_FOREACH(md, &part_metadata, metadata)
247 		if (md->name != NULL && strcmp(md->name, name) == 0)
248 			break;
249 
250 	if (md == NULL && create) {
251 		md = calloc(1, sizeof(*md));
252 		md->name = strdup(name);
253 		TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
254 	}
255 
256 	return (md);
257 }
258 
259 void
260 delete_part_metadata(const char *name)
261 {
262 	struct partition_metadata *md;
263 
264 	TAILQ_FOREACH(md, &part_metadata, metadata) {
265 		if (md->name != NULL && strcmp(md->name, name) == 0) {
266 			if (md->fstab != NULL) {
267 				free(md->fstab->fs_spec);
268 				free(md->fstab->fs_file);
269 				free(md->fstab->fs_vfstype);
270 				free(md->fstab->fs_mntops);
271 				free(md->fstab->fs_type);
272 				free(md->fstab);
273 			}
274 			if (md->newfs != NULL)
275 				free(md->newfs);
276 			free(md->name);
277 
278 			TAILQ_REMOVE(&part_metadata, md, metadata);
279 			free(md);
280 			break;
281 		}
282 	}
283 }
284 
285 static int
286 validate_setup(void)
287 {
288 	struct partition_metadata *md, *root = NULL;
289 	int cancel;
290 
291 	TAILQ_FOREACH(md, &part_metadata, metadata) {
292 		if (md->fstab != NULL && strcmp(md->fstab->fs_file, "/") == 0)
293 			root = md;
294 
295 		/* XXX: Check for duplicate mountpoints */
296 	}
297 
298 	if (root == NULL) {
299 		dialog_msgbox("Error", "No root partition was found. "
300 		    "The root FreeBSD partition must have a mountpoint of '/'.",
301 		0, 0, TRUE);
302 		return (FALSE);
303 	}
304 
305 	/*
306 	 * Check for root partitions that we aren't formatting, which is
307 	 * usually a mistake
308 	 */
309 	if (root->newfs == NULL && !sade_mode) {
310 		dialog_vars.defaultno = TRUE;
311 		cancel = dialog_yesno("Warning", "The chosen root partition "
312 		    "has a preexisting filesystem. If it contains an existing "
313 		    "FreeBSD 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 		dialog_vars.defaultno = FALSE;
319 		if (cancel)
320 			return (FALSE);
321 	}
322 
323 	return (TRUE);
324 }
325 
326 static int
327 apply_changes(struct gmesh *mesh)
328 {
329 	struct partition_metadata *md;
330 	char message[512];
331 	int i, nitems, error;
332 	const char **items;
333 	const char *fstab_path;
334 	FILE *fstab;
335 
336 	nitems = 1; /* Partition table changes */
337 	TAILQ_FOREACH(md, &part_metadata, metadata) {
338 		if (md->newfs != NULL)
339 			nitems++;
340 	}
341 	items = calloc(nitems * 2, sizeof(const char *));
342 	items[0] = "Writing partition tables";
343 	items[1] = "7"; /* In progress */
344 	i = 1;
345 	TAILQ_FOREACH(md, &part_metadata, metadata) {
346 		if (md->newfs != NULL) {
347 			char *item;
348 			item = malloc(255);
349 			sprintf(item, "Initializing %s", md->name);
350 			items[i*2] = item;
351 			items[i*2 + 1] = "Pending";
352 			i++;
353 		}
354 	}
355 
356 	i = 0;
357 	dialog_mixedgauge("Initializing",
358 	    "Initializing file systems. Please wait.", 0, 0, i*100/nitems,
359 	    nitems, __DECONST(char **, items));
360 	gpart_commit(mesh);
361 	items[i*2 + 1] = "3";
362 	i++;
363 
364 	if (getenv("BSDINSTALL_LOG") == NULL)
365 		setenv("BSDINSTALL_LOG", "/dev/null", 1);
366 
367 	TAILQ_FOREACH(md, &part_metadata, metadata) {
368 		if (md->newfs != NULL) {
369 			items[i*2 + 1] = "7"; /* In progress */
370 			dialog_mixedgauge("Initializing",
371 			    "Initializing file systems. Please wait.", 0, 0,
372 			    i*100/nitems, nitems, __DECONST(char **, items));
373 			sprintf(message, "(echo %s; %s) >>%s 2>>%s",
374 			    md->newfs, md->newfs, getenv("BSDINSTALL_LOG"),
375 			    getenv("BSDINSTALL_LOG"));
376 			error = system(message);
377 			items[i*2 + 1] = (error == 0) ? "3" : "1";
378 			i++;
379 		}
380 	}
381 	dialog_mixedgauge("Initializing",
382 	    "Initializing file systems. Please wait.", 0, 0,
383 	    i*100/nitems, nitems, __DECONST(char **, items));
384 
385 	for (i = 1; i < nitems; i++)
386 		free(__DECONST(char *, items[i*2]));
387 	free(items);
388 
389 	if (getenv("PATH_FSTAB") != NULL)
390 		fstab_path = getenv("PATH_FSTAB");
391 	else
392 		fstab_path = "/etc/fstab";
393 	fstab = fopen(fstab_path, "w+");
394 	if (fstab == NULL) {
395 		sprintf(message, "Cannot open fstab file %s for writing (%s)\n",
396 		    getenv("PATH_FSTAB"), strerror(errno));
397 		dialog_msgbox("Error", message, 0, 0, TRUE);
398 		return (-1);
399 	}
400 	fprintf(fstab, "# Device\tMountpoint\tFStype\tOptions\tDump\tPass#\n");
401 	TAILQ_FOREACH(md, &part_metadata, metadata) {
402 		if (md->fstab != NULL)
403 			fprintf(fstab, "%s\t%s\t\t%s\t%s\t%d\t%d\n",
404 			    md->fstab->fs_spec, md->fstab->fs_file,
405 			    md->fstab->fs_vfstype, md->fstab->fs_mntops,
406 			    md->fstab->fs_freq, md->fstab->fs_passno);
407 	}
408 	fclose(fstab);
409 
410 	return (0);
411 }
412 
413 static void
414 apply_workaround(struct gmesh *mesh)
415 {
416 	struct gclass *classp;
417 	struct ggeom *gp;
418 	struct gconfig *gc;
419 	const char *scheme = NULL, *modified = NULL;
420 
421 	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
422 		if (strcmp(classp->lg_name, "PART") == 0)
423 			break;
424 	}
425 
426 	if (strcmp(classp->lg_name, "PART") != 0) {
427 		dialog_msgbox("Error", "gpart not found!", 0, 0, TRUE);
428 		return;
429 	}
430 
431 	LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
432 		LIST_FOREACH(gc, &gp->lg_config, lg_config) {
433 			if (strcmp(gc->lg_name, "scheme") == 0) {
434 				scheme = gc->lg_val;
435 			} else if (strcmp(gc->lg_name, "modified") == 0) {
436 				modified = gc->lg_val;
437 			}
438 		}
439 
440 		if (scheme && strcmp(scheme, "GPT") == 0 &&
441 		    modified && strcmp(modified, "true") == 0) {
442 			if (getenv("WORKAROUND_LENOVO"))
443 				gpart_set_root(gp->lg_name, "lenovofix");
444 			if (getenv("WORKAROUND_GPTACTIVE"))
445 				gpart_set_root(gp->lg_name, "active");
446 		}
447 	}
448 }
449 
450 static struct partedit_item *
451 read_geom_mesh(struct gmesh *mesh, int *nitems)
452 {
453 	struct gclass *classp;
454 	struct ggeom *gp;
455 	struct partedit_item *items;
456 
457 	*nitems = 0;
458 	items = NULL;
459 
460 	/*
461 	 * Build the device table. First add all disks (and CDs).
462 	 */
463 
464 	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
465 		if (strcmp(classp->lg_name, "DISK") != 0 &&
466 		    strcmp(classp->lg_name, "MD") != 0)
467 			continue;
468 
469 		/* Now recurse into all children */
470 		LIST_FOREACH(gp, &classp->lg_geom, lg_geom)
471 			add_geom_children(gp, 0, &items, nitems);
472 	}
473 
474 	return (items);
475 }
476 
477 static void
478 add_geom_children(struct ggeom *gp, int recurse, struct partedit_item **items,
479     int *nitems)
480 {
481 	struct gconsumer *cp;
482 	struct gprovider *pp;
483 	struct gconfig *gc;
484 
485 	if (strcmp(gp->lg_class->lg_name, "PART") == 0 &&
486 	    !LIST_EMPTY(&gp->lg_config)) {
487 		LIST_FOREACH(gc, &gp->lg_config, lg_config) {
488 			if (strcmp(gc->lg_name, "scheme") == 0)
489 				(*items)[*nitems-1].type = gc->lg_val;
490 		}
491 	}
492 
493 	if (LIST_EMPTY(&gp->lg_provider))
494 		return;
495 
496 	LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
497 		if (strcmp(gp->lg_class->lg_name, "LABEL") == 0)
498 			continue;
499 
500 		/* Skip WORM media */
501 		if (strncmp(pp->lg_name, "cd", 2) == 0)
502 			continue;
503 
504 		*items = realloc(*items,
505 		    (*nitems+1)*sizeof(struct partedit_item));
506 		(*items)[*nitems].indentation = recurse;
507 		(*items)[*nitems].name = pp->lg_name;
508 		(*items)[*nitems].size = pp->lg_mediasize;
509 		(*items)[*nitems].mountpoint = NULL;
510 		(*items)[*nitems].type = "";
511 		(*items)[*nitems].cookie = pp;
512 
513 		LIST_FOREACH(gc, &pp->lg_config, lg_config) {
514 			if (strcmp(gc->lg_name, "type") == 0)
515 				(*items)[*nitems].type = gc->lg_val;
516 		}
517 
518 		/* Skip swap-backed MD devices */
519 		if (strcmp(gp->lg_class->lg_name, "MD") == 0 &&
520 		    strcmp((*items)[*nitems].type, "swap") == 0)
521 			continue;
522 
523 		(*nitems)++;
524 
525 		LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
526 			add_geom_children(cp->lg_geom, recurse+1, items,
527 			    nitems);
528 
529 		/* Only use first provider for acd */
530 		if (strcmp(gp->lg_class->lg_name, "ACD") == 0)
531 			break;
532 	}
533 }
534 
535 static void
536 init_fstab_metadata(void)
537 {
538 	struct fstab *fstab;
539 	struct partition_metadata *md;
540 
541 	setfsent();
542 	while ((fstab = getfsent()) != NULL) {
543 		md = calloc(1, sizeof(struct partition_metadata));
544 
545 		md->name = NULL;
546 		if (strncmp(fstab->fs_spec, "/dev/", 5) == 0)
547 			md->name = strdup(&fstab->fs_spec[5]);
548 
549 		md->fstab = malloc(sizeof(struct fstab));
550 		md->fstab->fs_spec = strdup(fstab->fs_spec);
551 		md->fstab->fs_file = strdup(fstab->fs_file);
552 		md->fstab->fs_vfstype = strdup(fstab->fs_vfstype);
553 		md->fstab->fs_mntops = strdup(fstab->fs_mntops);
554 		md->fstab->fs_type = strdup(fstab->fs_type);
555 		md->fstab->fs_freq = fstab->fs_freq;
556 		md->fstab->fs_passno = fstab->fs_passno;
557 
558 		md->newfs = NULL;
559 
560 		TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
561 	}
562 }
563 
564 static void
565 get_mount_points(struct partedit_item *items, int nitems)
566 {
567 	struct partition_metadata *md;
568 	int i;
569 
570 	for (i = 0; i < nitems; i++) {
571 		TAILQ_FOREACH(md, &part_metadata, metadata) {
572 			if (md->name != NULL && md->fstab != NULL &&
573 			    strcmp(md->name, items[i].name) == 0) {
574 				items[i].mountpoint = md->fstab->fs_file;
575 				break;
576 			}
577 		}
578 	}
579 }
580