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