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