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_clearterminal(); 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 struct bsddialog_conf conf; 349 350 nitems = 1; /* Partition table changes */ 351 TAILQ_FOREACH(md, &part_metadata, metadata) { 352 if (md->newfs != NULL) 353 nitems++; 354 } 355 minilabel = calloc(nitems, sizeof(const char *)); 356 miniperc = calloc(nitems, sizeof(int)); 357 minilabel[0] = "Writing partition tables"; 358 miniperc[0] = BSDDIALOG_MG_INPROGRESS; 359 i = 1; 360 TAILQ_FOREACH(md, &part_metadata, metadata) { 361 if (md->newfs != NULL) { 362 char *item; 363 item = malloc(255); 364 sprintf(item, "Initializing %s", md->name); 365 minilabel[i] = item; 366 miniperc[i] = BSDDIALOG_MG_PENDING; 367 i++; 368 } 369 } 370 371 i = 0; 372 bsddialog_initconf(&conf); 373 conf.title = "Initializing"; 374 bsddialog_mixedgauge(&conf, 375 "Initializing file systems. Please wait.", 0, 0, i * 100 / nitems, 376 nitems, minilabel, miniperc); 377 gpart_commit(mesh); 378 miniperc[i] = BSDDIALOG_MG_COMPLETED; 379 i++; 380 381 if (getenv("BSDINSTALL_LOG") == NULL) 382 setenv("BSDINSTALL_LOG", "/dev/null", 1); 383 384 TAILQ_FOREACH(md, &part_metadata, metadata) { 385 if (md->newfs != NULL) { 386 miniperc[i] = BSDDIALOG_MG_INPROGRESS; 387 bsddialog_mixedgauge(&conf, 388 "Initializing file systems. Please wait.", 0, 0, 389 i * 100 / nitems, nitems, minilabel, miniperc); 390 sprintf(message, "(echo %s; %s) >>%s 2>>%s", 391 md->newfs, md->newfs, getenv("BSDINSTALL_LOG"), 392 getenv("BSDINSTALL_LOG")); 393 error = system(message); 394 miniperc[i] = (error == 0) ? 395 BSDDIALOG_MG_COMPLETED : BSDDIALOG_MG_FAILED; 396 i++; 397 } 398 } 399 bsddialog_mixedgauge(&conf, "Initializing file systems. Please wait.", 400 0, 0, i * 100 / nitems, nitems, minilabel, miniperc); 401 402 for (i = 1; i < nitems; i++) 403 free(__DECONST(char *, minilabel[i])); 404 405 free(minilabel); 406 free(miniperc); 407 408 /* Sort filesystems for fstab so that mountpoints are ordered */ 409 { 410 struct partition_metadata **tobesorted; 411 struct partition_metadata *tmp; 412 int nparts = 0; 413 TAILQ_FOREACH(md, &part_metadata, metadata) 414 nparts++; 415 tobesorted = malloc(sizeof(struct partition_metadata *)*nparts); 416 nparts = 0; 417 TAILQ_FOREACH_SAFE(md, &part_metadata, metadata, tmp) { 418 tobesorted[nparts++] = md; 419 TAILQ_REMOVE(&part_metadata, md, metadata); 420 } 421 qsort(tobesorted, nparts, sizeof(tobesorted[0]), 422 mountpoint_sorter); 423 424 /* Now re-add everything */ 425 while (nparts-- > 0) 426 TAILQ_INSERT_HEAD(&part_metadata, 427 tobesorted[nparts], metadata); 428 free(tobesorted); 429 } 430 431 if (getenv("PATH_FSTAB") != NULL) 432 fstab_path = getenv("PATH_FSTAB"); 433 else 434 fstab_path = "/etc/fstab"; 435 fstab = fopen(fstab_path, "w+"); 436 if (fstab == NULL) { 437 sprintf(message, "Cannot open fstab file %s for writing (%s)\n", 438 getenv("PATH_FSTAB"), strerror(errno)); 439 conf.title = "Error"; 440 bsddialog_msgbox(&conf, message, 0, 0); 441 return (-1); 442 } 443 fprintf(fstab, "# Device\tMountpoint\tFStype\tOptions\tDump\tPass#\n"); 444 TAILQ_FOREACH(md, &part_metadata, metadata) { 445 if (md->fstab != NULL) 446 fprintf(fstab, "%s\t%s\t\t%s\t%s\t%d\t%d\n", 447 md->fstab->fs_spec, md->fstab->fs_file, 448 md->fstab->fs_vfstype, md->fstab->fs_mntops, 449 md->fstab->fs_freq, md->fstab->fs_passno); 450 } 451 fclose(fstab); 452 453 return (0); 454 } 455 456 static void 457 apply_workaround(struct gmesh *mesh) 458 { 459 struct gclass *classp; 460 struct ggeom *gp; 461 struct gconfig *gc; 462 const char *scheme = NULL, *modified = NULL; 463 struct bsddialog_conf conf; 464 465 LIST_FOREACH(classp, &mesh->lg_class, lg_class) { 466 if (strcmp(classp->lg_name, "PART") == 0) 467 break; 468 } 469 470 if (strcmp(classp->lg_name, "PART") != 0) { 471 bsddialog_initconf(&conf); 472 conf.title = "Error"; 473 bsddialog_msgbox(&conf, "gpart not found!", 0, 0); 474 return; 475 } 476 477 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { 478 LIST_FOREACH(gc, &gp->lg_config, lg_config) { 479 if (strcmp(gc->lg_name, "scheme") == 0) { 480 scheme = gc->lg_val; 481 } else if (strcmp(gc->lg_name, "modified") == 0) { 482 modified = gc->lg_val; 483 } 484 } 485 486 if (scheme && strcmp(scheme, "GPT") == 0 && 487 modified && strcmp(modified, "true") == 0) { 488 if (getenv("WORKAROUND_LENOVO")) 489 gpart_set_root(gp->lg_name, "lenovofix"); 490 if (getenv("WORKAROUND_GPTACTIVE")) 491 gpart_set_root(gp->lg_name, "active"); 492 } 493 } 494 } 495 496 static struct partedit_item * 497 read_geom_mesh(struct gmesh *mesh, int *nitems) 498 { 499 struct gclass *classp; 500 struct ggeom *gp; 501 struct partedit_item *items; 502 503 *nitems = 0; 504 items = NULL; 505 506 /* 507 * Build the device table. First add all disks (and CDs). 508 */ 509 510 LIST_FOREACH(classp, &mesh->lg_class, lg_class) { 511 if (strcmp(classp->lg_name, "DISK") != 0 && 512 strcmp(classp->lg_name, "MD") != 0) 513 continue; 514 515 /* Now recurse into all children */ 516 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) 517 add_geom_children(gp, 0, &items, nitems); 518 } 519 520 return (items); 521 } 522 523 static void 524 add_geom_children(struct ggeom *gp, int recurse, struct partedit_item **items, 525 int *nitems) 526 { 527 struct gconsumer *cp; 528 struct gprovider *pp; 529 struct gconfig *gc; 530 531 if (strcmp(gp->lg_class->lg_name, "PART") == 0 && 532 !LIST_EMPTY(&gp->lg_config)) { 533 LIST_FOREACH(gc, &gp->lg_config, lg_config) { 534 if (strcmp(gc->lg_name, "scheme") == 0) 535 (*items)[*nitems-1].type = gc->lg_val; 536 } 537 } 538 539 if (LIST_EMPTY(&gp->lg_provider)) 540 return; 541 542 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { 543 if (strcmp(gp->lg_class->lg_name, "LABEL") == 0) 544 continue; 545 546 /* Skip WORM media */ 547 if (strncmp(pp->lg_name, "cd", 2) == 0) 548 continue; 549 550 *items = realloc(*items, 551 (*nitems+1)*sizeof(struct partedit_item)); 552 (*items)[*nitems].indentation = recurse; 553 (*items)[*nitems].name = pp->lg_name; 554 (*items)[*nitems].size = pp->lg_mediasize; 555 (*items)[*nitems].mountpoint = NULL; 556 (*items)[*nitems].type = ""; 557 (*items)[*nitems].cookie = pp; 558 559 LIST_FOREACH(gc, &pp->lg_config, lg_config) { 560 if (strcmp(gc->lg_name, "type") == 0) 561 (*items)[*nitems].type = gc->lg_val; 562 } 563 564 /* Skip swap-backed MD devices */ 565 if (strcmp(gp->lg_class->lg_name, "MD") == 0 && 566 strcmp((*items)[*nitems].type, "swap") == 0) 567 continue; 568 569 (*nitems)++; 570 571 LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers) 572 add_geom_children(cp->lg_geom, recurse+1, items, 573 nitems); 574 575 /* Only use first provider for acd */ 576 if (strcmp(gp->lg_class->lg_name, "ACD") == 0) 577 break; 578 } 579 } 580 581 static void 582 init_fstab_metadata(void) 583 { 584 struct fstab *fstab; 585 struct partition_metadata *md; 586 587 setfsent(); 588 while ((fstab = getfsent()) != NULL) { 589 md = calloc(1, sizeof(struct partition_metadata)); 590 591 md->name = NULL; 592 if (strncmp(fstab->fs_spec, "/dev/", 5) == 0) 593 md->name = strdup(&fstab->fs_spec[5]); 594 595 md->fstab = malloc(sizeof(struct fstab)); 596 md->fstab->fs_spec = strdup(fstab->fs_spec); 597 md->fstab->fs_file = strdup(fstab->fs_file); 598 md->fstab->fs_vfstype = strdup(fstab->fs_vfstype); 599 md->fstab->fs_mntops = strdup(fstab->fs_mntops); 600 md->fstab->fs_type = strdup(fstab->fs_type); 601 md->fstab->fs_freq = fstab->fs_freq; 602 md->fstab->fs_passno = fstab->fs_passno; 603 604 md->newfs = NULL; 605 606 TAILQ_INSERT_TAIL(&part_metadata, md, metadata); 607 } 608 } 609 610 static void 611 get_mount_points(struct partedit_item *items, int nitems) 612 { 613 struct partition_metadata *md; 614 int i; 615 616 for (i = 0; i < nitems; i++) { 617 TAILQ_FOREACH(md, &part_metadata, metadata) { 618 if (md->name != NULL && md->fstab != NULL && 619 strcmp(md->name, items[i].name) == 0) { 620 items[i].mountpoint = md->fstab->fs_file; 621 break; 622 } 623 } 624 } 625 } 626