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