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 46 struct pmetadata_head part_metadata; 47 static int sade_mode = 0; 48 49 static int apply_changes(struct gmesh *mesh); 50 static void apply_workaround(struct gmesh *mesh); 51 static struct partedit_item *read_geom_mesh(struct gmesh *mesh, int *nitems); 52 static void add_geom_children(struct ggeom *gp, int recurse, 53 struct partedit_item **items, int *nitems); 54 static void init_fstab_metadata(void); 55 static void get_mount_points(struct partedit_item *items, int nitems); 56 static int validate_setup(void); 57 58 static void 59 sigint_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 74 int 75 main(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, "FreeBSD 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 234 struct partition_metadata * 235 get_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 252 void 253 delete_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 278 static int 279 validate_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 323 static int 324 mountpoint_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 339 static int 340 apply_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 459 static void 460 apply_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 499 static struct partedit_item * 500 read_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 526 static void 527 add_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 584 static void 585 init_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 613 static void 614 get_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