1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2011 Nathan Whitehorn
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 * $FreeBSD$
29 */
30
31#include <sys/param.h>
32
33#include <dialog.h>
34#include <dlg_keys.h>
35#include <errno.h>
36#include <fstab.h>
37#include <inttypes.h>
38#include <libgeom.h>
39#include <libutil.h>
40#include <stdlib.h>
41
42#include "diskeditor.h"
43#include "partedit.h"
44
45struct pmetadata_head part_metadata;
46static int sade_mode = 0;
47
48static int apply_changes(struct gmesh *mesh);
49static void apply_workaround(struct gmesh *mesh);
50static struct partedit_item *read_geom_mesh(struct gmesh *mesh, int *nitems);
51static void add_geom_children(struct ggeom *gp, int recurse,
52    struct partedit_item **items, int *nitems);
53static void init_fstab_metadata(void);
54static void get_mount_points(struct partedit_item *items, int nitems);
55static int validate_setup(void);
56
57static void
58sigint_handler(int sig)
59{
60	struct gmesh mesh;
61
62	/* Revert all changes and exit dialog-mode cleanly on SIGINT */
63	geom_gettree(&mesh);
64	gpart_revert_all(&mesh);
65	geom_deletetree(&mesh);
66
67	end_dialog();
68
69	exit(1);
70}
71
72int
73main(int argc, const char **argv)
74{
75	struct partition_metadata *md;
76	const char *progname, *prompt;
77	struct partedit_item *items = NULL;
78	struct gmesh mesh;
79	int i, op, nitems, nscroll;
80	int error;
81
82	progname = getprogname();
83	if (strcmp(progname, "sade") == 0)
84		sade_mode = 1;
85
86	TAILQ_INIT(&part_metadata);
87
88	init_fstab_metadata();
89
90	init_dialog(stdin, stdout);
91	if (!sade_mode)
92		dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer");
93	dialog_vars.item_help = TRUE;
94	nscroll = i = 0;
95
96	/* Revert changes on SIGINT */
97	signal(SIGINT, sigint_handler);
98
99	if (strcmp(progname, "autopart") == 0) { /* Guided */
100		prompt = "Please review the disk setup. When complete, press "
101		    "the Finish button.";
102		/* Experimental ZFS autopartition support */
103		if (argc > 1 && strcmp(argv[1], "zfs") == 0) {
104			part_wizard("zfs");
105		} else {
106			part_wizard("ufs");
107		}
108	} else if (strcmp(progname, "scriptedpart") == 0) {
109		error = scripted_editor(argc, argv);
110		prompt = NULL;
111		if (error != 0) {
112			end_dialog();
113			return (error);
114		}
115	} else {
116		prompt = "Create partitions for FreeBSD. No changes will be "
117		    "made until you select Finish.";
118	}
119
120	/* Show the part editor either immediately, or to confirm wizard */
121	while (prompt != NULL) {
122		dlg_clear();
123		dlg_put_backtitle();
124
125		error = geom_gettree(&mesh);
126		if (error == 0)
127			items = read_geom_mesh(&mesh, &nitems);
128		if (error || items == NULL) {
129			dialog_msgbox("Error", "No disks found. If you need to "
130			    "install a kernel driver, choose Shell at the "
131			    "installation menu.", 0, 0, TRUE);
132			break;
133		}
134
135		get_mount_points(items, nitems);
136
137		if (i >= nitems)
138			i = nitems - 1;
139		op = diskeditor_show("Partition Editor", prompt,
140		    items, nitems, &i, &nscroll);
141
142		switch (op) {
143		case 0: /* Create */
144			gpart_create((struct gprovider *)(items[i].cookie),
145			    NULL, NULL, NULL, NULL, 1);
146			break;
147		case 1: /* Delete */
148			gpart_delete((struct gprovider *)(items[i].cookie));
149			break;
150		case 2: /* Modify */
151			gpart_edit((struct gprovider *)(items[i].cookie));
152			break;
153		case 3: /* Revert */
154			gpart_revert_all(&mesh);
155			while ((md = TAILQ_FIRST(&part_metadata)) != NULL) {
156				if (md->fstab != NULL) {
157					free(md->fstab->fs_spec);
158					free(md->fstab->fs_file);
159					free(md->fstab->fs_vfstype);
160					free(md->fstab->fs_mntops);
161					free(md->fstab->fs_type);
162					free(md->fstab);
163				}
164				if (md->newfs != NULL)
165					free(md->newfs);
166				free(md->name);
167
168				TAILQ_REMOVE(&part_metadata, md, metadata);
169				free(md);
170			}
171			init_fstab_metadata();
172			break;
173		case 4: /* Auto */
174			part_wizard("ufs");
175			break;
176		}
177
178		error = 0;
179		if (op == 5) { /* Finished */
180			dialog_vars.ok_label = __DECONST(char *, "Commit");
181			dialog_vars.extra_label =
182			    __DECONST(char *, "Revert & Exit");
183			dialog_vars.extra_button = TRUE;
184			dialog_vars.cancel_label = __DECONST(char *, "Back");
185			op = dialog_yesno("Confirmation", "Your changes will "
186			    "now be written to disk. If you have chosen to "
187			    "overwrite existing data, it will be PERMANENTLY "
188			    "ERASED. Are you sure you want to commit your "
189			    "changes?", 0, 0);
190			dialog_vars.ok_label = NULL;
191			dialog_vars.extra_button = FALSE;
192			dialog_vars.cancel_label = NULL;
193
194			if (op == 0 && validate_setup()) { /* Save */
195				error = apply_changes(&mesh);
196				if (!error)
197					apply_workaround(&mesh);
198				break;
199			} else if (op == 3) { /* Quit */
200				gpart_revert_all(&mesh);
201				error =	-1;
202				break;
203			}
204		}
205
206		geom_deletetree(&mesh);
207		free(items);
208	}
209
210	if (prompt == NULL) {
211		error = geom_gettree(&mesh);
212		if (validate_setup()) {
213			error = apply_changes(&mesh);
214		} else {
215			gpart_revert_all(&mesh);
216			error = -1;
217		}
218	}
219
220	geom_deletetree(&mesh);
221	free(items);
222	end_dialog();
223
224	return (error);
225}
226
227struct partition_metadata *
228get_part_metadata(const char *name, int create)
229{
230	struct partition_metadata *md;
231
232	TAILQ_FOREACH(md, &part_metadata, metadata)
233		if (md->name != NULL && strcmp(md->name, name) == 0)
234			break;
235
236	if (md == NULL && create) {
237		md = calloc(1, sizeof(*md));
238		md->name = strdup(name);
239		TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
240	}
241
242	return (md);
243}
244
245void
246delete_part_metadata(const char *name)
247{
248	struct partition_metadata *md;
249
250	TAILQ_FOREACH(md, &part_metadata, metadata) {
251		if (md->name != NULL && strcmp(md->name, name) == 0) {
252			if (md->fstab != NULL) {
253				free(md->fstab->fs_spec);
254				free(md->fstab->fs_file);
255				free(md->fstab->fs_vfstype);
256				free(md->fstab->fs_mntops);
257				free(md->fstab->fs_type);
258				free(md->fstab);
259			}
260			if (md->newfs != NULL)
261				free(md->newfs);
262			free(md->name);
263
264			TAILQ_REMOVE(&part_metadata, md, metadata);
265			free(md);
266			break;
267		}
268	}
269}
270
271static int
272validate_setup(void)
273{
274	struct partition_metadata *md, *root = NULL;
275	int cancel;
276
277	TAILQ_FOREACH(md, &part_metadata, metadata) {
278		if (md->fstab != NULL && strcmp(md->fstab->fs_file, "/") == 0)
279			root = md;
280
281		/* XXX: Check for duplicate mountpoints */
282	}
283
284	if (root == NULL) {
285		dialog_msgbox("Error", "No root partition was found. "
286		    "The root FreeBSD partition must have a mountpoint of '/'.",
287		0, 0, TRUE);
288		return (FALSE);
289	}
290
291	/*
292	 * Check for root partitions that we aren't formatting, which is
293	 * usually a mistake
294	 */
295	if (root->newfs == NULL && !sade_mode) {
296		dialog_vars.defaultno = TRUE;
297		cancel = dialog_yesno("Warning", "The chosen root partition "
298		    "has a preexisting filesystem. If it contains an existing "
299		    "FreeBSD system, please update it with freebsd-update "
300		    "instead of installing a new system on it. The partition "
301		    "can also be erased by pressing \"No\" and then deleting "
302		    "and recreating it. Are you sure you want to proceed?",
303		    0, 0);
304		dialog_vars.defaultno = FALSE;
305		if (cancel)
306			return (FALSE);
307	}
308
309	return (TRUE);
310}
311
312static int
313apply_changes(struct gmesh *mesh)
314{
315	struct partition_metadata *md;
316	char message[512];
317	int i, nitems, error;
318	const char **items;
319	const char *fstab_path;
320	FILE *fstab;
321
322	nitems = 1; /* Partition table changes */
323	TAILQ_FOREACH(md, &part_metadata, metadata) {
324		if (md->newfs != NULL)
325			nitems++;
326	}
327	items = calloc(nitems * 2, sizeof(const char *));
328	items[0] = "Writing partition tables";
329	items[1] = "7"; /* In progress */
330	i = 1;
331	TAILQ_FOREACH(md, &part_metadata, metadata) {
332		if (md->newfs != NULL) {
333			char *item;
334			item = malloc(255);
335			sprintf(item, "Initializing %s", md->name);
336			items[i*2] = item;
337			items[i*2 + 1] = "Pending";
338			i++;
339		}
340	}
341
342	i = 0;
343	dialog_mixedgauge("Initializing",
344	    "Initializing file systems. Please wait.", 0, 0, i*100/nitems,
345	    nitems, __DECONST(char **, items));
346	gpart_commit(mesh);
347	items[i*2 + 1] = "3";
348	i++;
349
350	if (getenv("BSDINSTALL_LOG") == NULL)
351		setenv("BSDINSTALL_LOG", "/dev/null", 1);
352
353	TAILQ_FOREACH(md, &part_metadata, metadata) {
354		if (md->newfs != NULL) {
355			items[i*2 + 1] = "7"; /* In progress */
356			dialog_mixedgauge("Initializing",
357			    "Initializing file systems. Please wait.", 0, 0,
358			    i*100/nitems, nitems, __DECONST(char **, items));
359			sprintf(message, "(echo %s; %s) >>%s 2>>%s",
360			    md->newfs, md->newfs, getenv("BSDINSTALL_LOG"),
361			    getenv("BSDINSTALL_LOG"));
362			error = system(message);
363			items[i*2 + 1] = (error == 0) ? "3" : "1";
364			i++;
365		}
366	}
367	dialog_mixedgauge("Initializing",
368	    "Initializing file systems. Please wait.", 0, 0,
369	    i*100/nitems, nitems, __DECONST(char **, items));
370
371	for (i = 1; i < nitems; i++)
372		free(__DECONST(char *, items[i*2]));
373	free(items);
374
375	if (getenv("PATH_FSTAB") != NULL)
376		fstab_path = getenv("PATH_FSTAB");
377	else
378		fstab_path = "/etc/fstab";
379	fstab = fopen(fstab_path, "w+");
380	if (fstab == NULL) {
381		sprintf(message, "Cannot open fstab file %s for writing (%s)\n",
382		    getenv("PATH_FSTAB"), strerror(errno));
383		dialog_msgbox("Error", message, 0, 0, TRUE);
384		return (-1);
385	}
386	fprintf(fstab, "# Device\tMountpoint\tFStype\tOptions\tDump\tPass#\n");
387	TAILQ_FOREACH(md, &part_metadata, metadata) {
388		if (md->fstab != NULL)
389			fprintf(fstab, "%s\t%s\t\t%s\t%s\t%d\t%d\n",
390			    md->fstab->fs_spec, md->fstab->fs_file,
391			    md->fstab->fs_vfstype, md->fstab->fs_mntops,
392			    md->fstab->fs_freq, md->fstab->fs_passno);
393	}
394	fclose(fstab);
395
396	return (0);
397}
398
399static void
400apply_workaround(struct gmesh *mesh)
401{
402	struct gclass *classp;
403	struct ggeom *gp;
404	struct gconfig *gc;
405	const char *scheme = NULL, *modified = NULL;
406
407	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
408		if (strcmp(classp->lg_name, "PART") == 0)
409			break;
410	}
411
412	if (strcmp(classp->lg_name, "PART") != 0) {
413		dialog_msgbox("Error", "gpart not found!", 0, 0, TRUE);
414		return;
415	}
416
417	LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
418		LIST_FOREACH(gc, &gp->lg_config, lg_config) {
419			if (strcmp(gc->lg_name, "scheme") == 0) {
420				scheme = gc->lg_val;
421			} else if (strcmp(gc->lg_name, "modified") == 0) {
422				modified = gc->lg_val;
423			}
424		}
425
426		if (scheme && strcmp(scheme, "GPT") == 0 &&
427		    modified && strcmp(modified, "true") == 0) {
428			if (getenv("WORKAROUND_LENOVO"))
429				gpart_set_root(gp->lg_name, "lenovofix");
430			if (getenv("WORKAROUND_GPTACTIVE"))
431				gpart_set_root(gp->lg_name, "active");
432		}
433	}
434}
435
436static struct partedit_item *
437read_geom_mesh(struct gmesh *mesh, int *nitems)
438{
439	struct gclass *classp;
440	struct ggeom *gp;
441	struct partedit_item *items;
442
443	*nitems = 0;
444	items = NULL;
445
446	/*
447	 * Build the device table. First add all disks (and CDs).
448	 */
449
450	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
451		if (strcmp(classp->lg_name, "DISK") != 0 &&
452		    strcmp(classp->lg_name, "MD") != 0)
453			continue;
454
455		/* Now recurse into all children */
456		LIST_FOREACH(gp, &classp->lg_geom, lg_geom)
457			add_geom_children(gp, 0, &items, nitems);
458	}
459
460	return (items);
461}
462
463static void
464add_geom_children(struct ggeom *gp, int recurse, struct partedit_item **items,
465    int *nitems)
466{
467	struct gconsumer *cp;
468	struct gprovider *pp;
469	struct gconfig *gc;
470
471	if (strcmp(gp->lg_class->lg_name, "PART") == 0 &&
472	    !LIST_EMPTY(&gp->lg_config)) {
473		LIST_FOREACH(gc, &gp->lg_config, lg_config) {
474			if (strcmp(gc->lg_name, "scheme") == 0)
475				(*items)[*nitems-1].type = gc->lg_val;
476		}
477	}
478
479	if (LIST_EMPTY(&gp->lg_provider))
480		return;
481
482	LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
483		if (strcmp(gp->lg_class->lg_name, "LABEL") == 0)
484			continue;
485
486		/* Skip WORM media */
487		if (strncmp(pp->lg_name, "cd", 2) == 0)
488			continue;
489
490		*items = realloc(*items,
491		    (*nitems+1)*sizeof(struct partedit_item));
492		(*items)[*nitems].indentation = recurse;
493		(*items)[*nitems].name = pp->lg_name;
494		(*items)[*nitems].size = pp->lg_mediasize;
495		(*items)[*nitems].mountpoint = NULL;
496		(*items)[*nitems].type = "";
497		(*items)[*nitems].cookie = pp;
498
499		LIST_FOREACH(gc, &pp->lg_config, lg_config) {
500			if (strcmp(gc->lg_name, "type") == 0)
501				(*items)[*nitems].type = gc->lg_val;
502		}
503
504		/* Skip swap-backed MD devices */
505		if (strcmp(gp->lg_class->lg_name, "MD") == 0 &&
506		    strcmp((*items)[*nitems].type, "swap") == 0)
507			continue;
508
509		(*nitems)++;
510
511		LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
512			add_geom_children(cp->lg_geom, recurse+1, items,
513			    nitems);
514
515		/* Only use first provider for acd */
516		if (strcmp(gp->lg_class->lg_name, "ACD") == 0)
517			break;
518	}
519}
520
521static void
522init_fstab_metadata(void)
523{
524	struct fstab *fstab;
525	struct partition_metadata *md;
526
527	setfsent();
528	while ((fstab = getfsent()) != NULL) {
529		md = calloc(1, sizeof(struct partition_metadata));
530
531		md->name = NULL;
532		if (strncmp(fstab->fs_spec, "/dev/", 5) == 0)
533			md->name = strdup(&fstab->fs_spec[5]);
534
535		md->fstab = malloc(sizeof(struct fstab));
536		md->fstab->fs_spec = strdup(fstab->fs_spec);
537		md->fstab->fs_file = strdup(fstab->fs_file);
538		md->fstab->fs_vfstype = strdup(fstab->fs_vfstype);
539		md->fstab->fs_mntops = strdup(fstab->fs_mntops);
540		md->fstab->fs_type = strdup(fstab->fs_type);
541		md->fstab->fs_freq = fstab->fs_freq;
542		md->fstab->fs_passno = fstab->fs_passno;
543
544		md->newfs = NULL;
545
546		TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
547	}
548}
549
550static void
551get_mount_points(struct partedit_item *items, int nitems)
552{
553	struct partition_metadata *md;
554	int i;
555
556	for (i = 0; i < nitems; i++) {
557		TAILQ_FOREACH(md, &part_metadata, metadata) {
558			if (md->name != NULL && md->fstab != NULL &&
559			    strcmp(md->name, items[i].name) == 0) {
560				items[i].mountpoint = md->fstab->fs_file;
561				break;
562			}
563		}
564	}
565}
566