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