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