partedit.c revision 285769
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 285769 2015-07-21 21:12:28Z allanjude $
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 void apply_workaround(struct gmesh *mesh);
48static struct partedit_item *read_geom_mesh(struct gmesh *mesh, int *nitems);
49static void add_geom_children(struct ggeom *gp, int recurse,
50    struct partedit_item **items, int *nitems);
51static void init_fstab_metadata(void);
52static void get_mount_points(struct partedit_item *items, int nitems);
53static int validate_setup(void);
54
55static void
56sigint_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
70int
71main(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
224struct partition_metadata *
225get_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
242void
243delete_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
268static int
269validate_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
309static int
310apply_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
396static void
397apply_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
433static struct partedit_item *
434read_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
460static void
461add_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
518static void
519init_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
547static void
548get_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