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