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