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