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