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