/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2011 Nathan Whitehorn
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/param.h>
#include <bsddialog.h>
#include <err.h>
#include <errno.h>
#include <fstab.h>
#include <inttypes.h>
#include <libgeom.h>
#include <libutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include "diskmenu.h"
#include "partedit.h"
struct pmetadata_head part_metadata;
static int sade_mode = 0;
static int apply_changes(struct gmesh *mesh);
static void apply_workaround(struct gmesh *mesh);
static struct partedit_item *read_geom_mesh(struct gmesh *mesh, int *nitems);
static void add_geom_children(struct ggeom *gp, int recurse,
struct partedit_item **items, int *nitems);
static void init_fstab_metadata(void);
static void get_mount_points(struct partedit_item *items, int nitems);
static int validate_setup(void);
static void
sigint_handler(int sig)
{
struct gmesh mesh;
/* Revert all changes and exit dialog-mode cleanly on SIGINT */
if (geom_gettree(&mesh) == 0) {
gpart_revert_all(&mesh);
geom_deletetree(&mesh);
}
bsddialog_end();
exit(1);
}
int
main(int argc, const char **argv)
{
struct partition_metadata *md;
const char *progname, *prompt;
struct partedit_item *items = NULL;
struct gmesh mesh;
int i, op, nitems;
int error;
struct bsddialog_conf conf;
progname = getprogname();
if (strcmp(progname, "sade") == 0)
sade_mode = 1;
TAILQ_INIT(&part_metadata);
init_fstab_metadata();
if (bsddialog_init() == BSDDIALOG_ERROR)
err(1, "%s", bsddialog_geterror());
bsddialog_initconf(&conf);
if (!sade_mode)
bsddialog_backtitle(&conf, OSNAME " Installer");
i = 0;
/* Revert changes on SIGINT */
signal(SIGINT, sigint_handler);
if (strcmp(progname, "autopart") == 0) { /* Guided */
prompt = "Please review the disk setup. When complete, press "
"the Finish button.";
/* Experimental ZFS autopartition support */
if (argc > 1 && strcmp(argv[1], "zfs") == 0) {
part_wizard("zfs");
} else {
part_wizard("ufs");
}
} else if (strcmp(progname, "scriptedpart") == 0) {
error = scripted_editor(argc, argv);
prompt = NULL;
if (error != 0) {
bsddialog_end();
return (error);
}
} else {
prompt = "Create partitions for " OSNAME ", F1 for help.\n"
"No changes will be made until you select Finish.";
}
/* Show the part editor either immediately, or to confirm wizard */
while (prompt != NULL) {
bsddialog_clearterminal();
if (!sade_mode)
bsddialog_backtitle(&conf, "FreeBSD Installer");
error = geom_gettree(&mesh);
if (error == 0)
items = read_geom_mesh(&mesh, &nitems);
if (error || items == NULL) {
conf.title = "Error";
bsddialog_msgbox(&conf, "No disks found. If you need "
"to install a kernel driver, choose Shell at the "
"installation menu.", 0, 0);
break;
}
get_mount_points(items, nitems);
if (i >= nitems)
i = nitems - 1;
op = diskmenu_show("Partition Editor", prompt, items, nitems,
&i);
switch (op) {
case BUTTON_CREATE:
gpart_create((struct gprovider *)(items[i].cookie),
NULL, NULL, NULL, NULL, 1);
break;
case BUTTON_DELETE:
gpart_delete((struct gprovider *)(items[i].cookie));
break;
case BUTTON_MODIFY:
gpart_edit((struct gprovider *)(items[i].cookie));
break;
case BUTTON_REVERT:
gpart_revert_all(&mesh);
while ((md = TAILQ_FIRST(&part_metadata)) != NULL) {
if (md->fstab != NULL) {
free(md->fstab->fs_spec);
free(md->fstab->fs_file);
free(md->fstab->fs_vfstype);
free(md->fstab->fs_mntops);
free(md->fstab->fs_type);
free(md->fstab);
}
if (md->newfs != NULL)
free(md->newfs);
free(md->name);
TAILQ_REMOVE(&part_metadata, md, metadata);
free(md);
}
init_fstab_metadata();
break;
case BUTTON_AUTO:
part_wizard("ufs");
break;
}
error = 0;
if (op == BUTTON_FINISH) {
conf.button.ok_label = "Commit";
conf.button.with_extra = true;
conf.button.extra_label = "Revert & Exit";
conf.button.cancel_label = "Back";
conf.title = "Confirmation";
op = bsddialog_yesno(&conf, "Your changes will now be "
"written to disk. If you have chosen to overwrite "
"existing data, it will be PERMANENTLY ERASED. Are "
"you sure you want to commit your changes?", 0, 0);
conf.button.ok_label = NULL;
conf.button.with_extra = false;
conf.button.extra_label = NULL;
conf.button.cancel_label = NULL;
if (op == BSDDIALOG_OK && validate_setup()) { /* Save */
error = apply_changes(&mesh);
if (!error)
apply_workaround(&mesh);
break;
} else if (op == BSDDIALOG_EXTRA) { /* Quit */
gpart_revert_all(&mesh);
error = -1;
break;
}
}
geom_deletetree(&mesh);
free(items);
}
if (prompt == NULL) {
error = geom_gettree(&mesh);
if (error == 0) {
if (validate_setup()) {
error = apply_changes(&mesh);
} else {
gpart_revert_all(&mesh);
error = -1;
}
geom_deletetree(&mesh);
}
}
bsddialog_end();
return (error);
}
struct partition_metadata *
get_part_metadata(const char *name, int create)
{
struct partition_metadata *md;
TAILQ_FOREACH(md, &part_metadata, metadata)
if (md->name != NULL && strcmp(md->name, name) == 0)
break;
if (md == NULL && create) {
md = calloc(1, sizeof(*md));
md->name = strdup(name);
TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
}
return (md);
}
void
delete_part_metadata(const char *name)
{
struct partition_metadata *md;
TAILQ_FOREACH(md, &part_metadata, metadata) {
if (md->name != NULL && strcmp(md->name, name) == 0) {
if (md->fstab != NULL) {
free(md->fstab->fs_spec);
free(md->fstab->fs_file);
free(md->fstab->fs_vfstype);
free(md->fstab->fs_mntops);
free(md->fstab->fs_type);
free(md->fstab);
}
if (md->newfs != NULL)
free(md->newfs);
free(md->name);
TAILQ_REMOVE(&part_metadata, md, metadata);
free(md);
break;
}
}
}
static int
validate_setup(void)
{
struct partition_metadata *md, *root = NULL;
int button;
struct bsddialog_conf conf;
TAILQ_FOREACH(md, &part_metadata, metadata) {
if (md->fstab != NULL && strcmp(md->fstab->fs_file, "/") == 0)
root = md;
/* XXX: Check for duplicate mountpoints */
}
bsddialog_initconf(&conf);
if (root == NULL) {
conf.title = "Error";
bsddialog_msgbox(&conf, "No root partition was found. "
"The root " OSNAME " partition must have a mountpoint "
"of '/'.", 0, 0);
return (false);
}
/*
* Check for root partitions that we aren't formatting, which is
* usually a mistake
*/
if (root->newfs == NULL && !sade_mode) {
conf.button.default_cancel = true;
conf.title = "Warning";
button = bsddialog_yesno(&conf, "The chosen root partition "
"has a preexisting filesystem. If it contains an existing "
OSNAME " system, please update it with freebsd-update "
"instead of installing a new system on it. The partition "
"can also be erased by pressing \"No\" and then deleting "
"and recreating it. Are you sure you want to proceed?",
0, 0);
if (button == BSDDIALOG_CANCEL)
return (false);
}
return (true);
}
static int
mountpoint_sorter(const void *xa, const void *xb)
{
struct partition_metadata *a = *(struct partition_metadata **)xa;
struct partition_metadata *b = *(struct partition_metadata **)xb;
if (a->fstab == NULL && b->fstab == NULL)
return 0;
if (a->fstab == NULL)
return 1;
if (b->fstab == NULL)
return -1;
return strcmp(a->fstab->fs_file, b->fstab->fs_file);
}
static int
apply_changes(struct gmesh *mesh)
{
struct partition_metadata *md;
char message[512];
int i, nitems, error, *miniperc;
const char **minilabel;
const char *fstab_path;
FILE *fstab;
struct bsddialog_conf conf;
nitems = 1; /* Partition table changes */
TAILQ_FOREACH(md, &part_metadata, metadata) {
if (md->newfs != NULL)
nitems++;
}
minilabel = calloc(nitems, sizeof(const char *));
miniperc = calloc(nitems, sizeof(int));
minilabel[0] = "Writing partition tables";
miniperc[0] = BSDDIALOG_MG_INPROGRESS;
i = 1;
TAILQ_FOREACH(md, &part_metadata, metadata) {
if (md->newfs != NULL) {
char *item;
item = malloc(255);
sprintf(item, "Initializing %s", md->name);
minilabel[i] = item;
miniperc[i] = BSDDIALOG_MG_PENDING;
i++;
}
}
i = 0;
bsddialog_initconf(&conf);
conf.title = "Initializing";
bsddialog_mixedgauge(&conf,
"Initializing file systems. Please wait.", 0, 0, i * 100 / nitems,
nitems, minilabel, miniperc);
gpart_commit(mesh);
miniperc[i] = BSDDIALOG_MG_COMPLETED;
i++;
if (getenv("BSDINSTALL_LOG") == NULL)
setenv("BSDINSTALL_LOG", "/dev/null", 1);
TAILQ_FOREACH(md, &part_metadata, metadata) {
if (md->newfs != NULL) {
miniperc[i] = BSDDIALOG_MG_INPROGRESS;
bsddialog_mixedgauge(&conf,
"Initializing file systems. Please wait.", 0, 0,
i * 100 / nitems, nitems, minilabel, miniperc);
sprintf(message, "(echo %s; %s) >>%s 2>>%s",
md->newfs, md->newfs, getenv("BSDINSTALL_LOG"),
getenv("BSDINSTALL_LOG"));
error = system(message);
miniperc[i] = (error == 0) ?
BSDDIALOG_MG_COMPLETED : BSDDIALOG_MG_FAILED;
i++;
}
}
bsddialog_mixedgauge(&conf, "Initializing file systems. Please wait.",
0, 0, i * 100 / nitems, nitems, minilabel, miniperc);
for (i = 1; i < nitems; i++)
free(__DECONST(char *, minilabel[i]));
free(minilabel);
free(miniperc);
/* Sort filesystems for fstab so that mountpoints are ordered */
{
struct partition_metadata **tobesorted;
struct partition_metadata *tmp;
int nparts = 0;
TAILQ_FOREACH(md, &part_metadata, metadata)
nparts++;
tobesorted = malloc(sizeof(struct partition_metadata *)*nparts);
nparts = 0;
TAILQ_FOREACH_SAFE(md, &part_metadata, metadata, tmp) {
tobesorted[nparts++] = md;
TAILQ_REMOVE(&part_metadata, md, metadata);
}
qsort(tobesorted, nparts, sizeof(tobesorted[0]),
mountpoint_sorter);
/* Now re-add everything */
while (nparts-- > 0)
TAILQ_INSERT_HEAD(&part_metadata,
tobesorted[nparts], metadata);
free(tobesorted);
}
if (getenv("PATH_FSTAB") != NULL)
fstab_path = getenv("PATH_FSTAB");
else
fstab_path = "/etc/fstab";
fstab = fopen(fstab_path, "w+");
if (fstab == NULL) {
sprintf(message, "Cannot open fstab file %s for writing (%s)\n",
getenv("PATH_FSTAB"), strerror(errno));
conf.title = "Error";
bsddialog_msgbox(&conf, message, 0, 0);
return (-1);
}
fprintf(fstab, "# Device\tMountpoint\tFStype\tOptions\tDump\tPass#\n");
TAILQ_FOREACH(md, &part_metadata, metadata) {
if (md->fstab != NULL)
fprintf(fstab, "%s\t%s\t\t%s\t%s\t%d\t%d\n",
md->fstab->fs_spec, md->fstab->fs_file,
md->fstab->fs_vfstype, md->fstab->fs_mntops,
md->fstab->fs_freq, md->fstab->fs_passno);
}
fclose(fstab);
return (0);
}
static void
apply_workaround(struct gmesh *mesh)
{
struct gclass *classp;
struct ggeom *gp;
struct gconfig *gc;
const char *scheme = NULL, *modified = NULL;
struct bsddialog_conf conf;
LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
if (strcmp(classp->lg_name, "PART") == 0)
break;
}
if (strcmp(classp->lg_name, "PART") != 0) {
bsddialog_initconf(&conf);
conf.title = "Error";
bsddialog_msgbox(&conf, "gpart not found!", 0, 0);
return;
}
LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
LIST_FOREACH(gc, &gp->lg_config, lg_config) {
if (strcmp(gc->lg_name, "scheme") == 0) {
scheme = gc->lg_val;
} else if (strcmp(gc->lg_name, "modified") == 0) {
modified = gc->lg_val;
}
}
if (scheme && strcmp(scheme, "GPT") == 0 &&
modified && strcmp(modified, "true") == 0) {
if (getenv("WORKAROUND_LENOVO"))
gpart_set_root(gp->lg_name, "lenovofix");
if (getenv("WORKAROUND_GPTACTIVE"))
gpart_set_root(gp->lg_name, "active");
}
}
}
static struct partedit_item *
read_geom_mesh(struct gmesh *mesh, int *nitems)
{
struct gclass *classp;
struct ggeom *gp;
struct partedit_item *items;
*nitems = 0;
items = NULL;
/*
* Build the device table. First add all disks (and CDs).
*/
LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
if (strcmp(classp->lg_name, "DISK") != 0 &&
strcmp(classp->lg_name, "MD") != 0)
continue;
/* Now recurse into all children */
LIST_FOREACH(gp, &classp->lg_geom, lg_geom)
add_geom_children(gp, 0, &items, nitems);
}
return (items);
}
static void
add_geom_children(struct ggeom *gp, int recurse, struct partedit_item **items,
int *nitems)
{
struct gconsumer *cp;
struct gprovider *pp;
struct gconfig *gc;
if (strcmp(gp->lg_class->lg_name, "PART") == 0 &&
!LIST_EMPTY(&gp->lg_config)) {
LIST_FOREACH(gc, &gp->lg_config, lg_config) {
if (strcmp(gc->lg_name, "scheme") == 0)
(*items)[*nitems-1].type = gc->lg_val;
}
}
if (LIST_EMPTY(&gp->lg_provider))
return;
LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
if (strcmp(gp->lg_class->lg_name, "LABEL") == 0)
continue;
/* Skip WORM media */
if (strncmp(pp->lg_name, "cd", 2) == 0)
continue;
*items = realloc(*items,
(*nitems+1)*sizeof(struct partedit_item));
(*items)[*nitems].indentation = recurse;
(*items)[*nitems].name = pp->lg_name;
(*items)[*nitems].size = pp->lg_mediasize;
(*items)[*nitems].mountpoint = NULL;
(*items)[*nitems].type = "";
(*items)[*nitems].cookie = pp;
LIST_FOREACH(gc, &pp->lg_config, lg_config) {
if (strcmp(gc->lg_name, "type") == 0)
(*items)[*nitems].type = gc->lg_val;
}
/* Skip swap-backed MD devices */
if (strcmp(gp->lg_class->lg_name, "MD") == 0 &&
strcmp((*items)[*nitems].type, "swap") == 0)
continue;
(*nitems)++;
LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
add_geom_children(cp->lg_geom, recurse+1, items,
nitems);
/* Only use first provider for acd */
if (strcmp(gp->lg_class->lg_name, "ACD") == 0)
break;
}
}
static void
init_fstab_metadata(void)
{
struct fstab *fstab;
struct partition_metadata *md;
setfsent();
while ((fstab = getfsent()) != NULL) {
md = calloc(1, sizeof(struct partition_metadata));
md->name = NULL;
if (strncmp(fstab->fs_spec, "/dev/", 5) == 0)
md->name = strdup(&fstab->fs_spec[5]);
md->fstab = malloc(sizeof(struct fstab));
md->fstab->fs_spec = strdup(fstab->fs_spec);
md->fstab->fs_file = strdup(fstab->fs_file);
md->fstab->fs_vfstype = strdup(fstab->fs_vfstype);
md->fstab->fs_mntops = strdup(fstab->fs_mntops);
md->fstab->fs_type = strdup(fstab->fs_type);
md->fstab->fs_freq = fstab->fs_freq;
md->fstab->fs_passno = fstab->fs_passno;
md->newfs = NULL;
TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
}
}
static void
get_mount_points(struct partedit_item *items, int nitems)
{
struct partition_metadata *md;
int i;
for (i = 0; i < nitems; i++) {
TAILQ_FOREACH(md, &part_metadata, metadata) {
if (md->name != NULL && md->fstab != NULL &&
strcmp(md->name, items[i].name) == 0) {
items[i].mountpoint = md->fstab->fs_file;
break;
}
}
}
}