1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
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
29#include <sys/param.h>
30
31#include <bsddialog.h>
32#include <err.h>
33#include <errno.h>
34#include <fstab.h>
35#include <inttypes.h>
36#include <libgeom.h>
37#include <libutil.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <sysexits.h>
42
43#include "diskmenu.h"
44#include "partedit.h"
45
46struct pmetadata_head part_metadata;
47static int sade_mode = 0;
48
49static int apply_changes(struct gmesh *mesh);
50static void apply_workaround(struct gmesh *mesh);
51static struct partedit_item *read_geom_mesh(struct gmesh *mesh, int *nitems);
52static void add_geom_children(struct ggeom *gp, int recurse,
53    struct partedit_item **items, int *nitems);
54static void init_fstab_metadata(void);
55static void get_mount_points(struct partedit_item *items, int nitems);
56static int validate_setup(void);
57
58static void
59sigint_handler(int sig)
60{
61	struct gmesh mesh;
62
63	/* Revert all changes and exit dialog-mode cleanly on SIGINT */
64	if (geom_gettree(&mesh) == 0) {
65		gpart_revert_all(&mesh);
66		geom_deletetree(&mesh);
67	}
68
69	bsddialog_end();
70
71	exit(1);
72}
73
74int
75main(int argc, const char **argv)
76{
77	struct partition_metadata *md;
78	const char *progname, *prompt;
79	struct partedit_item *items = NULL;
80	struct gmesh mesh;
81	int i, op, nitems;
82	int error;
83	struct bsddialog_conf conf;
84
85	progname = getprogname();
86	if (strcmp(progname, "sade") == 0)
87		sade_mode = 1;
88
89	TAILQ_INIT(&part_metadata);
90
91	init_fstab_metadata();
92
93	if (bsddialog_init() == BSDDIALOG_ERROR)
94		err(1, "%s", bsddialog_geterror());
95	bsddialog_initconf(&conf);
96	if (!sade_mode)
97		bsddialog_backtitle(&conf, OSNAME " Installer");
98	i = 0;
99
100	/* Revert changes on SIGINT */
101	signal(SIGINT, sigint_handler);
102
103	if (strcmp(progname, "autopart") == 0) { /* Guided */
104		prompt = "Please review the disk setup. When complete, press "
105		    "the Finish button.";
106		/* Experimental ZFS autopartition support */
107		if (argc > 1 && strcmp(argv[1], "zfs") == 0) {
108			part_wizard("zfs");
109		} else {
110			part_wizard("ufs");
111		}
112	} else if (strcmp(progname, "scriptedpart") == 0) {
113		error = scripted_editor(argc, argv);
114		prompt = NULL;
115		if (error != 0) {
116			bsddialog_end();
117			return (error);
118		}
119	} else {
120		prompt = "Create partitions for " OSNAME ", F1 for help.\n"
121		    "No changes will be made until you select Finish.";
122	}
123
124	/* Show the part editor either immediately, or to confirm wizard */
125	while (prompt != NULL) {
126		bsddialog_clear(0);
127		if (!sade_mode)
128			bsddialog_backtitle(&conf, OSNAME " Installer");
129
130		error = geom_gettree(&mesh);
131		if (error == 0)
132			items = read_geom_mesh(&mesh, &nitems);
133		if (error || items == NULL) {
134			conf.title = "Error";
135			bsddialog_msgbox(&conf, "No disks found. If you need "
136			    "to install a kernel driver, choose Shell at the "
137			    "installation menu.", 0, 0);
138			break;
139		}
140
141		get_mount_points(items, nitems);
142
143		if (i >= nitems)
144			i = nitems - 1;
145		op = diskmenu_show("Partition Editor", prompt, items, nitems,
146		    &i);
147
148		switch (op) {
149		case BUTTON_CREATE:
150			gpart_create((struct gprovider *)(items[i].cookie),
151			    NULL, NULL, NULL, NULL, 1);
152			break;
153		case BUTTON_DELETE:
154			gpart_delete((struct gprovider *)(items[i].cookie));
155			break;
156		case BUTTON_MODIFY:
157			gpart_edit((struct gprovider *)(items[i].cookie));
158			break;
159		case BUTTON_REVERT:
160			gpart_revert_all(&mesh);
161			while ((md = TAILQ_FIRST(&part_metadata)) != NULL) {
162				if (md->fstab != NULL) {
163					free(md->fstab->fs_spec);
164					free(md->fstab->fs_file);
165					free(md->fstab->fs_vfstype);
166					free(md->fstab->fs_mntops);
167					free(md->fstab->fs_type);
168					free(md->fstab);
169				}
170				if (md->newfs != NULL)
171					free(md->newfs);
172				free(md->name);
173
174				TAILQ_REMOVE(&part_metadata, md, metadata);
175				free(md);
176			}
177			init_fstab_metadata();
178			break;
179		case BUTTON_AUTO:
180			part_wizard("ufs");
181			break;
182		}
183
184		error = 0;
185		if (op == BUTTON_FINISH) {
186			conf.button.ok_label = "Commit";
187			conf.button.with_extra = true;
188			conf.button.extra_label = "Revert & Exit";
189			conf.button.cancel_label = "Back";
190			conf.title = "Confirmation";
191			op = bsddialog_yesno(&conf, "Your changes will now be "
192			    "written to disk. If you have chosen to overwrite "
193			    "existing data, it will be PERMANENTLY ERASED. Are "
194			    "you sure you want to commit your changes?", 0, 0);
195			conf.button.ok_label = NULL;
196			conf.button.with_extra = false;
197			conf.button.extra_label = NULL;
198			conf.button.cancel_label = NULL;
199
200			if (op == BSDDIALOG_OK && validate_setup()) { /* Save */
201				error = apply_changes(&mesh);
202				if (!error)
203					apply_workaround(&mesh);
204				break;
205			} else if (op == BSDDIALOG_EXTRA) { /* Quit */
206				gpart_revert_all(&mesh);
207				error =	-1;
208				break;
209			}
210		}
211
212		geom_deletetree(&mesh);
213		free(items);
214	}
215
216	if (prompt == NULL) {
217		error = geom_gettree(&mesh);
218		if (error == 0) {
219			if (validate_setup()) {
220				error = apply_changes(&mesh);
221			} else {
222				gpart_revert_all(&mesh);
223				error = -1;
224			}
225			geom_deletetree(&mesh);
226		}
227	}
228
229	bsddialog_end();
230
231	return (error);
232}
233
234struct partition_metadata *
235get_part_metadata(const char *name, int create)
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			break;
242
243	if (md == NULL && create) {
244		md = calloc(1, sizeof(*md));
245		md->name = strdup(name);
246		TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
247	}
248
249	return (md);
250}
251
252void
253delete_part_metadata(const char *name)
254{
255	struct partition_metadata *md;
256
257	TAILQ_FOREACH(md, &part_metadata, metadata) {
258		if (md->name != NULL && strcmp(md->name, name) == 0) {
259			if (md->fstab != NULL) {
260				free(md->fstab->fs_spec);
261				free(md->fstab->fs_file);
262				free(md->fstab->fs_vfstype);
263				free(md->fstab->fs_mntops);
264				free(md->fstab->fs_type);
265				free(md->fstab);
266			}
267			if (md->newfs != NULL)
268				free(md->newfs);
269			free(md->name);
270
271			TAILQ_REMOVE(&part_metadata, md, metadata);
272			free(md);
273			break;
274		}
275	}
276}
277
278static int
279validate_setup(void)
280{
281	struct partition_metadata *md, *root = NULL;
282	int button;
283	struct bsddialog_conf conf;
284
285	TAILQ_FOREACH(md, &part_metadata, metadata) {
286		if (md->fstab != NULL && strcmp(md->fstab->fs_file, "/") == 0)
287			root = md;
288
289		/* XXX: Check for duplicate mountpoints */
290	}
291
292	bsddialog_initconf(&conf);
293
294	if (root == NULL) {
295		conf.title = "Error";
296		bsddialog_msgbox(&conf, "No root partition was found. "
297		    "The root " OSNAME " partition must have a mountpoint "
298		    "of '/'.", 0, 0);
299		return (false);
300	}
301
302	/*
303	 * Check for root partitions that we aren't formatting, which is
304	 * usually a mistake
305	 */
306	if (root->newfs == NULL && !sade_mode) {
307		conf.button.default_cancel = true;
308		conf.title = "Warning";
309		button = bsddialog_yesno(&conf, "The chosen root partition "
310		    "has a preexisting filesystem. If it contains an existing "
311		    OSNAME " system, please update it with freebsd-update "
312		    "instead of installing a new system on it. The partition "
313		    "can also be erased by pressing \"No\" and then deleting "
314		    "and recreating it. Are you sure you want to proceed?",
315		    0, 0);
316		if (button == BSDDIALOG_CANCEL)
317			return (false);
318	}
319
320	return (true);
321}
322
323static int
324mountpoint_sorter(const void *xa, const void *xb)
325{
326	struct partition_metadata *a = *(struct partition_metadata **)xa;
327	struct partition_metadata *b = *(struct partition_metadata **)xb;
328
329	if (a->fstab == NULL && b->fstab == NULL)
330		return 0;
331	if (a->fstab == NULL)
332		return 1;
333	if (b->fstab == NULL)
334		return -1;
335
336	return strcmp(a->fstab->fs_file, b->fstab->fs_file);
337}
338
339static int
340apply_changes(struct gmesh *mesh)
341{
342	struct partition_metadata *md;
343	char message[512];
344	int i, nitems, error, *miniperc;
345	const char **minilabel;
346	const char *fstab_path;
347	FILE *fstab;
348	char *command;
349	struct bsddialog_conf conf;
350
351	nitems = 1; /* Partition table changes */
352	TAILQ_FOREACH(md, &part_metadata, metadata) {
353		if (md->newfs != NULL)
354			nitems++;
355	}
356	minilabel = calloc(nitems, sizeof(const char *));
357	miniperc  = calloc(nitems, sizeof(int));
358	minilabel[0] = "Writing partition tables";
359	miniperc[0]  = BSDDIALOG_MG_INPROGRESS;
360	i = 1;
361	TAILQ_FOREACH(md, &part_metadata, metadata) {
362		if (md->newfs != NULL) {
363			char *item;
364
365			asprintf(&item, "Initializing %s", md->name);
366			minilabel[i] = item;
367			miniperc[i]  = BSDDIALOG_MG_PENDING;
368			i++;
369		}
370	}
371
372	i = 0;
373	bsddialog_initconf(&conf);
374	conf.title = "Initializing";
375	bsddialog_mixedgauge(&conf,
376	    "Initializing file systems. Please wait.", 0, 0, i * 100 / nitems,
377	    nitems, minilabel, miniperc);
378	gpart_commit(mesh);
379	miniperc[i] = BSDDIALOG_MG_COMPLETED;
380	i++;
381
382	if (getenv("BSDINSTALL_LOG") == NULL)
383		setenv("BSDINSTALL_LOG", "/dev/null", 1);
384
385	TAILQ_FOREACH(md, &part_metadata, metadata) {
386		if (md->newfs != NULL) {
387			miniperc[i] = BSDDIALOG_MG_INPROGRESS;
388			bsddialog_mixedgauge(&conf,
389			    "Initializing file systems. Please wait.", 0, 0,
390			    i * 100 / nitems, nitems, minilabel, miniperc);
391			asprintf(&command, "(echo %s; %s) >>%s 2>>%s",
392			    md->newfs, md->newfs, getenv("BSDINSTALL_LOG"),
393			    getenv("BSDINSTALL_LOG"));
394			error = system(command);
395			free(command);
396			miniperc[i] = (error == 0) ?
397			    BSDDIALOG_MG_COMPLETED : BSDDIALOG_MG_FAILED;
398			i++;
399		}
400	}
401	bsddialog_mixedgauge(&conf, "Initializing file systems. Please wait.",
402	    0, 0, i * 100 / nitems, nitems, minilabel, miniperc);
403
404	for (i = 1; i < nitems; i++)
405		free(__DECONST(char *, minilabel[i]));
406
407	free(minilabel);
408	free(miniperc);
409
410	/* Sort filesystems for fstab so that mountpoints are ordered */
411	{
412		struct partition_metadata **tobesorted;
413		struct partition_metadata *tmp;
414		int nparts = 0;
415		TAILQ_FOREACH(md, &part_metadata, metadata)
416			nparts++;
417		tobesorted = malloc(sizeof(struct partition_metadata *)*nparts);
418		nparts = 0;
419		TAILQ_FOREACH_SAFE(md, &part_metadata, metadata, tmp) {
420			tobesorted[nparts++] = md;
421			TAILQ_REMOVE(&part_metadata, md, metadata);
422		}
423		qsort(tobesorted, nparts, sizeof(tobesorted[0]),
424		    mountpoint_sorter);
425
426		/* Now re-add everything */
427		while (nparts-- > 0)
428			TAILQ_INSERT_HEAD(&part_metadata,
429			    tobesorted[nparts], metadata);
430		free(tobesorted);
431	}
432
433	if (getenv("PATH_FSTAB") != NULL)
434		fstab_path = getenv("PATH_FSTAB");
435	else
436		fstab_path = "/etc/fstab";
437	fstab = fopen(fstab_path, "w+");
438	if (fstab == NULL) {
439		snprintf(message, sizeof(message),
440		    "Cannot open fstab file %s for writing (%s)\n",
441		    getenv("PATH_FSTAB"), strerror(errno));
442		conf.title = "Error";
443		bsddialog_msgbox(&conf, message, 0, 0);
444		return (-1);
445	}
446	fprintf(fstab, "# Device\tMountpoint\tFStype\tOptions\tDump\tPass#\n");
447	TAILQ_FOREACH(md, &part_metadata, metadata) {
448		if (md->fstab != NULL)
449			fprintf(fstab, "%s\t%s\t\t%s\t%s\t%d\t%d\n",
450			    md->fstab->fs_spec, md->fstab->fs_file,
451			    md->fstab->fs_vfstype, md->fstab->fs_mntops,
452			    md->fstab->fs_freq, md->fstab->fs_passno);
453	}
454	fclose(fstab);
455
456	return (0);
457}
458
459static void
460apply_workaround(struct gmesh *mesh)
461{
462	struct gclass *classp;
463	struct ggeom *gp;
464	struct gconfig *gc;
465	const char *scheme = NULL, *modified = NULL;
466	struct bsddialog_conf conf;
467
468	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
469		if (strcmp(classp->lg_name, "PART") == 0)
470			break;
471	}
472
473	if (strcmp(classp->lg_name, "PART") != 0) {
474		bsddialog_initconf(&conf);
475		conf.title = "Error";
476		bsddialog_msgbox(&conf, "gpart not found!", 0, 0);
477		return;
478	}
479
480	LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
481		LIST_FOREACH(gc, &gp->lg_config, lg_config) {
482			if (strcmp(gc->lg_name, "scheme") == 0) {
483				scheme = gc->lg_val;
484			} else if (strcmp(gc->lg_name, "modified") == 0) {
485				modified = gc->lg_val;
486			}
487		}
488
489		if (scheme && strcmp(scheme, "GPT") == 0 &&
490		    modified && strcmp(modified, "true") == 0) {
491			if (getenv("WORKAROUND_LENOVO"))
492				gpart_set_root(gp->lg_name, "lenovofix");
493			if (getenv("WORKAROUND_GPTACTIVE"))
494				gpart_set_root(gp->lg_name, "active");
495		}
496	}
497}
498
499static struct partedit_item *
500read_geom_mesh(struct gmesh *mesh, int *nitems)
501{
502	struct gclass *classp;
503	struct ggeom *gp;
504	struct partedit_item *items;
505
506	*nitems = 0;
507	items = NULL;
508
509	/*
510	 * Build the device table. First add all disks (and CDs).
511	 */
512
513	LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
514		if (strcmp(classp->lg_name, "DISK") != 0 &&
515		    strcmp(classp->lg_name, "MD") != 0)
516			continue;
517
518		/* Now recurse into all children */
519		LIST_FOREACH(gp, &classp->lg_geom, lg_geom)
520			add_geom_children(gp, 0, &items, nitems);
521	}
522
523	return (items);
524}
525
526static void
527add_geom_children(struct ggeom *gp, int recurse, struct partedit_item **items,
528    int *nitems)
529{
530	struct gconsumer *cp;
531	struct gprovider *pp;
532	struct gconfig *gc;
533
534	if (strcmp(gp->lg_class->lg_name, "PART") == 0 &&
535	    !LIST_EMPTY(&gp->lg_config)) {
536		LIST_FOREACH(gc, &gp->lg_config, lg_config) {
537			if (strcmp(gc->lg_name, "scheme") == 0)
538				(*items)[*nitems-1].type = gc->lg_val;
539		}
540	}
541
542	if (LIST_EMPTY(&gp->lg_provider))
543		return;
544
545	LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
546		if (strcmp(gp->lg_class->lg_name, "LABEL") == 0)
547			continue;
548
549		/* Skip WORM media */
550		if (strncmp(pp->lg_name, "cd", 2) == 0)
551			continue;
552
553		*items = realloc(*items,
554		    (*nitems+1)*sizeof(struct partedit_item));
555		(*items)[*nitems].indentation = recurse;
556		(*items)[*nitems].name = pp->lg_name;
557		(*items)[*nitems].size = pp->lg_mediasize;
558		(*items)[*nitems].mountpoint = NULL;
559		(*items)[*nitems].type = "";
560		(*items)[*nitems].cookie = pp;
561
562		LIST_FOREACH(gc, &pp->lg_config, lg_config) {
563			if (strcmp(gc->lg_name, "type") == 0)
564				(*items)[*nitems].type = gc->lg_val;
565		}
566
567		/* Skip swap-backed MD devices */
568		if (strcmp(gp->lg_class->lg_name, "MD") == 0 &&
569		    strcmp((*items)[*nitems].type, "swap") == 0)
570			continue;
571
572		(*nitems)++;
573
574		LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
575			add_geom_children(cp->lg_geom, recurse+1, items,
576			    nitems);
577
578		/* Only use first provider for acd */
579		if (strcmp(gp->lg_class->lg_name, "ACD") == 0)
580			break;
581	}
582}
583
584static void
585init_fstab_metadata(void)
586{
587	struct fstab *fstab;
588	struct partition_metadata *md;
589
590	setfsent();
591	while ((fstab = getfsent()) != NULL) {
592		md = calloc(1, sizeof(struct partition_metadata));
593
594		md->name = NULL;
595		if (strncmp(fstab->fs_spec, "/dev/", 5) == 0)
596			md->name = strdup(&fstab->fs_spec[5]);
597
598		md->fstab = malloc(sizeof(struct fstab));
599		md->fstab->fs_spec = strdup(fstab->fs_spec);
600		md->fstab->fs_file = strdup(fstab->fs_file);
601		md->fstab->fs_vfstype = strdup(fstab->fs_vfstype);
602		md->fstab->fs_mntops = strdup(fstab->fs_mntops);
603		md->fstab->fs_type = strdup(fstab->fs_type);
604		md->fstab->fs_freq = fstab->fs_freq;
605		md->fstab->fs_passno = fstab->fs_passno;
606
607		md->newfs = NULL;
608
609		TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
610	}
611}
612
613static void
614get_mount_points(struct partedit_item *items, int nitems)
615{
616	struct partition_metadata *md;
617	int i;
618
619	for (i = 0; i < nitems; i++) {
620		TAILQ_FOREACH(md, &part_metadata, metadata) {
621			if (md->name != NULL && md->fstab != NULL &&
622			    strcmp(md->name, items[i].name) == 0) {
623				items[i].mountpoint = md->fstab->fs_file;
624				break;
625			}
626		}
627	}
628}
629