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
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		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
216struct partition_metadata *
217get_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
234void
235delete_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
260static int
261validate_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
301static int
302apply_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
388static struct partedit_item *
389read_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
415static void
416add_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
473static void
474init_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
502static void
503get_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