partedit.c revision 273831
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: stable/10/usr.sbin/bsdinstall/partedit/partedit.c 273831 2014-10-29 16:48:18Z nwhitehorn $
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
43struct pmetadata_head part_metadata;
44static int sade_mode = 0;
45
46static int apply_changes(struct gmesh *mesh);
47static struct partedit_item *read_geom_mesh(struct gmesh *mesh, int *nitems);
48static void add_geom_children(struct ggeom *gp, int recurse,
49    struct partedit_item **items, int *nitems);
50static void init_fstab_metadata(void);
51static void get_mount_points(struct partedit_item *items, int nitems);
52static int validate_setup(void);
53
54static void
55sigint_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
69int
70main(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
221struct partition_metadata *
222get_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
239void
240delete_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
265static int
266validate_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
306static int
307apply_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
393static struct partedit_item *
394read_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
420static void
421add_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
478static void
479init_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
507static void
508get_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