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