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