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