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