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