1 /* 2 * Copyright (c) 2008 The DragonFly Project. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in 13 * the documentation and/or other materials provided with the 14 * distribution. 15 * 16 * Neither the name of the DragonFly Project nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 29 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 31 * OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 /* 35 * fn_subpart_hammer.c 36 * Installer Function : Create HAMMER Subpartitions. 37 */ 38 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 43 #ifdef ENABLE_NLS 44 #include <libintl.h> 45 #define _(String) gettext (String) 46 #else 47 #define _(String) (String) 48 #endif 49 50 #include "libaura/mem.h" 51 #include "libaura/buffer.h" 52 #include "libaura/dict.h" 53 #include "libaura/fspred.h" 54 55 #include "libdfui/dfui.h" 56 #include "libdfui/dump.h" 57 #include "libdfui/system.h" 58 59 #include "libinstaller/commands.h" 60 #include "libinstaller/diskutil.h" 61 #include "libinstaller/functions.h" 62 #include "libinstaller/uiutil.h" 63 64 #include "fn.h" 65 #include "flow.h" 66 #include "pathnames.h" 67 68 static int create_subpartitions(struct i_fn_args *); 69 static long default_capacity(struct storage *, const char *); 70 static int check_capacity(struct i_fn_args *); 71 static int check_subpartition_selections(struct dfui_response *, struct i_fn_args *); 72 static void save_subpartition_selections(struct dfui_response *, struct i_fn_args *); 73 static void populate_create_subpartitions_form(struct dfui_form *, struct i_fn_args *); 74 static int warn_subpartition_selections(struct i_fn_args *); 75 static struct dfui_form *make_create_subpartitions_form(struct i_fn_args *); 76 static int show_create_subpartitions_form(struct dfui_form *, struct i_fn_args *); 77 78 static const char *def_mountpt[] = {"/boot", "swap", "/", NULL}; 79 static int expert = 0; 80 81 /* 82 * Given a set of subpartitions-to-be in the selected slice, 83 * create them. 84 */ 85 static int 86 create_subpartitions(struct i_fn_args *a) 87 { 88 struct subpartition *sp; 89 struct commands *cmds; 90 int result = 0; 91 int num_partitions; 92 93 cmds = commands_new(); 94 if (!is_file("%sinstall.disklabel.%s", 95 a->tmp, 96 slice_get_device_name(storage_get_selected_slice(a->s)))) { 97 /* 98 * Get a copy of the 'virgin' disklabel. 99 * XXX It might make more sense for this to 100 * happen right after format_slice() instead. 101 */ 102 command_add(cmds, "%s%s -r %s >%sinstall.disklabel.%s", 103 a->os_root, cmd_name(a, "DISKLABEL64"), 104 slice_get_device_name(storage_get_selected_slice(a->s)), 105 a->tmp, 106 slice_get_device_name(storage_get_selected_slice(a->s))); 107 } 108 109 /* 110 * Weave together a new disklabel out the of the 'virgin' 111 * disklabel, and the user's subpartition choices. 112 */ 113 114 /* 115 * Take everything from the 'virgin' disklabel up until the 116 * '16 partitions' line. 117 */ 118 num_partitions = 16; 119 command_add(cmds, "%s%s '$2==\"partitions:\" || cut { cut = 1 } !cut { print $0 }' <%sinstall.disklabel.%s >%sinstall.disklabel", 120 a->os_root, cmd_name(a, "AWK"), 121 a->tmp, 122 slice_get_device_name(storage_get_selected_slice(a->s)), 123 a->tmp); 124 125 /* 126 * 16 partitions: 127 * # size offset fstype 128 * c: 16383969 0 unused # 7999.985MB 129 */ 130 131 command_add(cmds, "%s%s '%d partitions:' >>%sinstall.disklabel", 132 a->os_root, cmd_name(a, "ECHO"), num_partitions ,a->tmp); 133 command_add(cmds, "%s%s '%s' >>%sinstall.disklabel", 134 a->os_root, cmd_name(a, "ECHO"), 135 "# size offset fstype", 136 a->tmp); 137 138 #ifdef DEBUG 139 for (sp = slice_subpartition_first(storage_get_selected_slice(a->s)); 140 sp != NULL; sp = subpartition_next(sp)) { 141 command_add(cmds, "%s%s 'mountpoint: %s device: %s'", 142 a->os_root, cmd_name(a, "ECHO"), 143 subpartition_get_mountpoint(sp), 144 subpartition_get_device_name(sp)); 145 } 146 #endif 147 148 /* 149 * Write a line for each subpartition the user wants. 150 */ 151 for (sp = slice_subpartition_first(storage_get_selected_slice(a->s)); 152 sp != NULL; sp = subpartition_next(sp)) { 153 if (subpartition_is_mfsbacked(sp)) { 154 continue; 155 } 156 if (subpartition_is_swap(sp)) { 157 command_add(cmds, "%s%s ' %c:\t%s\t*\tswap' >>%sinstall.disklabel", 158 a->os_root, cmd_name(a, "ECHO"), 159 subpartition_get_letter(sp), 160 capacity_to_string(subpartition_get_capacity(sp)), 161 a->tmp); 162 } else if (strcmp(subpartition_get_mountpoint(sp), "/boot") == 0) { 163 command_add(cmds, "%s%s ' %c:\t%s\t0\t4.2BSD' >>%sinstall.disklabel", 164 a->os_root, cmd_name(a, "ECHO"), 165 subpartition_get_letter(sp), 166 capacity_to_string(subpartition_get_capacity(sp)), 167 a->tmp); 168 } else { 169 command_add(cmds, "%s%s ' %c:\t%s\t*\tHAMMER' >>%sinstall.disklabel", 170 a->os_root, cmd_name(a, "ECHO"), 171 subpartition_get_letter(sp), 172 capacity_to_string(subpartition_get_capacity(sp)), 173 a->tmp); 174 } 175 } 176 temp_file_add(a, "install.disklabel"); 177 178 /* 179 * Label the slice from the disklabel we just wove together. 180 */ 181 command_add(cmds, "%s%s -R -B -r %s %sinstall.disklabel", 182 a->os_root, cmd_name(a, "DISKLABEL64"), 183 slice_get_device_name(storage_get_selected_slice(a->s)), 184 a->tmp); 185 186 /* 187 * Create a snapshot of the disklabel we just created 188 * for debugging inspection in the log. 189 */ 190 command_add(cmds, "%s%s %s", 191 a->os_root, cmd_name(a, "DISKLABEL64"), 192 slice_get_device_name(storage_get_selected_slice(a->s))); 193 194 /* 195 * Create filesystems on the newly-created subpartitions. 196 */ 197 for (sp = slice_subpartition_first(storage_get_selected_slice(a->s)); 198 sp != NULL; sp = subpartition_next(sp)) { 199 if (subpartition_is_swap(sp) || subpartition_is_mfsbacked(sp)) 200 continue; 201 202 if (strcmp(subpartition_get_mountpoint(sp), "/boot") == 0) { 203 command_add(cmds, "%s%s %sdev/%s", 204 a->os_root, cmd_name(a, "NEWFS"), 205 a->os_root, 206 subpartition_get_device_name(sp)); 207 } else { 208 command_add(cmds, "%s%s -f -L ROOT %sdev/%s", 209 a->os_root, cmd_name(a, "NEWFS_HAMMER"), 210 a->os_root, 211 subpartition_get_device_name(sp)); 212 } 213 } 214 215 result = commands_execute(a, cmds); 216 commands_free(cmds); 217 return(result); 218 } 219 220 static long 221 default_capacity(struct storage *s, const char *mtpt) 222 { 223 unsigned long swap; 224 unsigned long capacity; 225 unsigned long mem; 226 227 capacity = slice_get_capacity(storage_get_selected_slice(s)); 228 mem = storage_get_memsize(s); 229 swap = 2 * mem; 230 if (mem > (capacity / 2) || capacity < 4096) 231 swap = mem; 232 if (mem > capacity) 233 swap = capacity / 2; 234 if (swap > 8192) 235 swap = 8192; 236 237 if (capacity < DISK_MIN) { 238 /* 239 * For the purposes of this installer: 240 * can't be done. Sorry. 241 */ 242 return(-1); 243 } else if (strcmp(mtpt, "/boot") == 0) { 244 return(256); 245 } else if (strcmp(mtpt, "swap") == 0) { 246 return(swap); 247 } else if (strcmp(mtpt, "/") == 0) { 248 return(-1); 249 } 250 /* shouldn't ever happen */ 251 return(-1); 252 } 253 254 static int 255 check_capacity(struct i_fn_args *a) 256 { 257 struct subpartition *sp; 258 unsigned long min_capacity[] = {256, 0, DISK_MIN - 256, 0}; 259 unsigned long total_capacity = 0; 260 int mtpt; 261 262 for (sp = slice_subpartition_first(storage_get_selected_slice(a->s)); 263 sp != NULL; sp = subpartition_next(sp)) { 264 if (subpartition_get_capacity(sp) == -1) 265 total_capacity++; 266 else 267 total_capacity += subpartition_get_capacity(sp); 268 for (mtpt = 0; def_mountpt[mtpt] != NULL; mtpt++) { 269 if (strcmp(subpartition_get_mountpoint(sp), def_mountpt[mtpt]) == 0 && 270 min_capacity[mtpt] > 0 && 271 subpartition_get_capacity(sp) < min_capacity[mtpt]) { 272 inform(a->c, _("WARNING: the %s subpartition should " 273 "be at least %dM in size or you will " 274 "risk running out of space during " 275 "the installation."), 276 subpartition_get_mountpoint(sp), min_capacity[mtpt]); 277 } 278 } 279 if (strcmp(subpartition_get_mountpoint(sp), "/") == 0 && 280 subpartition_get_capacity(sp) < HAMMER_MIN) { 281 inform(a->c, _("WARNING: HAMMER file systems" 282 "less than 50G are not recommended! You may" 283 "have to run 'hammer prune-everything' and 'hammer reblock'" 284 "quite often, even if using a nohistory mount.")); 285 } 286 } 287 288 if (total_capacity > slice_get_capacity(storage_get_selected_slice(a->s))) { 289 inform(a->c, _("The space allocated to all of your selected " 290 "subpartitions (%dM) exceeds the total " 291 "capacity of the selected primary partition " 292 "(%dM). Remove some subpartitions or choose " 293 "a smaller size for them and try again."), 294 total_capacity, slice_get_capacity(storage_get_selected_slice(a->s))); 295 return(0); 296 } 297 298 return(1); 299 } 300 301 static int 302 check_subpartition_selections(struct dfui_response *r, struct i_fn_args *a) 303 { 304 struct dfui_dataset *ds; 305 struct dfui_dataset *star_ds = NULL; 306 struct aura_dict *d; 307 const char *mountpoint, *capstring; 308 long capacity = 0; 309 int found_root = 0; 310 int valid = 1; 311 312 d = aura_dict_new(1, AURA_DICT_LIST); 313 314 if ((ds = dfui_response_dataset_get_first(r)) == NULL) { 315 inform(a->c, _("Please set up at least one subpartition.")); 316 valid = 0; 317 } 318 319 for (ds = dfui_response_dataset_get_first(r); valid && ds != NULL; 320 ds = dfui_dataset_get_next(ds)) { 321 #ifdef DEBUG 322 dfui_dataset_dump(ds); 323 #endif 324 mountpoint = dfui_dataset_get_value(ds, "mountpoint"); 325 capstring = dfui_dataset_get_value(ds, "capacity"); 326 327 if (aura_dict_exists(d, mountpoint, strlen(mountpoint) + 1)) { 328 inform(a->c, _("The same mount point cannot be specified " 329 "for two different subpartitions.")); 330 valid = 0; 331 } 332 333 if (strcmp(mountpoint, "/") == 0) 334 found_root = 1; 335 336 if (strcmp(capstring, "*") == 0) { 337 if (star_ds != NULL) { 338 inform(a->c, _("You cannot have more than one subpartition " 339 "with a '*' capacity (meaning 'use the remainder " 340 "of the primary partition'.)")); 341 valid = 0; 342 } else { 343 star_ds = ds; 344 } 345 } 346 347 if (!(!strcasecmp(mountpoint, "swap") || mountpoint[0] == '/')) { 348 inform(a->c, _("Mount point must be either 'swap', or it must " 349 "start with a '/'.")); 350 valid = 0; 351 } 352 353 if (strpbrk(mountpoint, " \\\"'`") != NULL) { 354 inform(a->c, _("Mount point may not contain the following " 355 "characters: blank space, backslash, or " 356 "single, double, or back quotes.")); 357 valid = 0; 358 } 359 360 if (strlen(capstring) == 0) { 361 inform(a->c, _("A capacity must be specified.")); 362 valid = 0; 363 } 364 365 if (!string_to_capacity(capstring, &capacity)) { 366 inform(a->c, _("Capacity must be either a '*' symbol to indicate " 367 "'use the rest of the primary partition', or it " 368 "must be a series of decimal digits ending with a " 369 "'M' (indicating megabytes) or a 'G' (indicating " 370 "gigabytes.)")); 371 valid = 0; 372 } 373 374 if ((strcasecmp(mountpoint, "swap") == 0) && (capacity > 8192)) { 375 inform(a->c, _("Swap capacity is limited to 8G.")); 376 valid = 0; 377 } 378 379 /* 380 * If we made it through that obstacle course, all is well. 381 */ 382 383 if (valid) 384 aura_dict_store(d, mountpoint, strlen(mountpoint) + 1, "", 1); 385 } 386 387 if (!found_root) { 388 inform(a->c, _("You must include a / (root) subpartition.")); 389 valid = 0; 390 } 391 392 if (aura_dict_size(d) > 16) { 393 inform(a->c, _("You cannot have more than 16 subpartitions " 394 "on a single primary partition. Remove some " 395 "and try again.")); 396 valid = 0; 397 } 398 399 aura_dict_free(d); 400 401 return(valid); 402 } 403 404 static void 405 save_subpartition_selections(struct dfui_response *r, struct i_fn_args *a) 406 { 407 struct dfui_dataset *ds; 408 const char *mountpoint, *capstring; 409 long capacity; 410 int valid = 1; 411 412 subpartitions_free(storage_get_selected_slice(a->s)); 413 414 for (ds = dfui_response_dataset_get_first(r); valid && ds != NULL; 415 ds = dfui_dataset_get_next(ds)) { 416 mountpoint = dfui_dataset_get_value(ds, "mountpoint"); 417 capstring = dfui_dataset_get_value(ds, "capacity"); 418 419 if (string_to_capacity(capstring, &capacity)) { 420 subpartition_new_hammer(storage_get_selected_slice(a->s), 421 mountpoint, capacity); 422 } 423 } 424 } 425 426 static void 427 populate_create_subpartitions_form(struct dfui_form *f, struct i_fn_args *a) 428 { 429 struct subpartition *sp; 430 struct dfui_dataset *ds; 431 int i; 432 long capacity; 433 434 if (slice_subpartition_first(storage_get_selected_slice(a->s)) != NULL) { 435 /* 436 * The user has already given us their subpartition 437 * preferences, so use them here. 438 */ 439 for (sp = slice_subpartition_first(storage_get_selected_slice(a->s)); 440 sp != NULL; sp = subpartition_next(sp)) { 441 ds = dfui_dataset_new(); 442 dfui_dataset_celldata_add(ds, "mountpoint", 443 subpartition_get_mountpoint(sp)); 444 dfui_dataset_celldata_add(ds, "capacity", 445 capacity_to_string(subpartition_get_capacity(sp))); 446 dfui_form_dataset_add(f, ds); 447 } 448 } else { 449 /* 450 * Otherwise, populate the form with datasets representing 451 * reasonably-calculated defaults. The defaults are chosen 452 * based on the slice's total capacity and the machine's 453 * total physical memory (for swap.) 454 */ 455 for (i = 0; def_mountpt[i] != NULL; i++) { 456 capacity = default_capacity(a->s, def_mountpt[i]); 457 ds = dfui_dataset_new(); 458 dfui_dataset_celldata_add(ds, "mountpoint", 459 def_mountpt[i]); 460 dfui_dataset_celldata_add(ds, "capacity", 461 capacity_to_string(capacity)); 462 dfui_form_dataset_add(f, ds); 463 } 464 } 465 } 466 467 static int 468 warn_subpartition_selections(struct i_fn_args *a) 469 { 470 int valid = 0; 471 472 if (subpartition_find(storage_get_selected_slice(a->s), "/boot") == NULL) { 473 inform(a->c, _("The /boot partition must not be omitted.")); 474 } else if (subpartition_find(storage_get_selected_slice(a->s), "/home") != NULL || 475 subpartition_find(storage_get_selected_slice(a->s), "/tmp") != NULL || 476 subpartition_find(storage_get_selected_slice(a->s), "/usr") != NULL || 477 subpartition_find(storage_get_selected_slice(a->s), "/usr/obj") != NULL || 478 subpartition_find(storage_get_selected_slice(a->s), "/var") != NULL || 479 subpartition_find(storage_get_selected_slice(a->s), "/var/crash") != NULL || 480 subpartition_find(storage_get_selected_slice(a->s), "/var/tmp") != NULL) { 481 inform(a->c, _("Pseudo filesystems will automatically be created " 482 "for /home, /tmp, /usr, /usr/obj, /var, /var/crash " 483 "and /var/tmp and must not be specified.")); 484 } else { 485 valid = check_capacity(a); 486 } 487 488 return(!valid); 489 } 490 491 static struct dfui_form * 492 make_create_subpartitions_form(struct i_fn_args *a) 493 { 494 struct dfui_form *f; 495 char msg_buf[1][1024]; 496 497 snprintf(msg_buf[0], sizeof(msg_buf[0]), 498 _("Subpartitions further divide a primary partition for " 499 "use with %s. Some reasons you may want " 500 "a set of subpartitions are:\n\n" 501 "- you want to restrict how much data can be written " 502 "to certain parts of the primary partition, to quell " 503 "denial-of-service attacks; and\n" 504 "- you want to speed up access to data on the disk." 505 ""), OPERATING_SYSTEM_NAME); 506 507 f = dfui_form_create( 508 "create_subpartitions", 509 _("Create Subpartitions"), 510 _("Set up the subpartitions (also known as just `partitions' " 511 "in BSD tradition) you want to have on this primary " 512 "partition. In most cases you should be fine with " 513 "the default settings.\n\n" 514 "For Capacity, use 'M' to indicate megabytes, 'G' to " 515 "indicate gigabytes, or a single '*' to indicate " 516 "'use the remaining space on the primary partition'."), 517 518 msg_buf[0], 519 520 "p", "special", "dfinstaller_create_subpartitions", 521 "p", "minimum_width","64", 522 523 "f", "mountpoint", _("Mountpoint"), "", "", 524 "f", "capacity", _("Capacity"), "", "", 525 526 "a", "ok", _("Accept and Create"), "", "", 527 "a", "cancel", 528 (disk_get_formatted(storage_get_selected_disk(a->s)) ? 529 _("Return to Select Disk") : 530 _("Return to Select Primary Partition")), "", "", 531 "p", "accelerator", "ESC", 532 533 NULL 534 ); 535 536 dfui_form_set_multiple(f, 1); 537 dfui_form_set_extensible(f, 1); 538 /* 539 * Remove ATM until HAMMER installer support is better 540 * dfui_form_set_extensible(f, 1); 541 */ 542 #if 0 543 if (expert) { 544 fi = dfui_form_field_add(f, "softupdates", 545 dfui_info_new(_("Softupdates"), "", "")); 546 dfui_field_property_set(fi, "control", "checkbox"); 547 548 fi = dfui_form_field_add(f, "mfsbacked", 549 dfui_info_new(_("MFS"), "", "")); 550 dfui_field_property_set(fi, "control", "checkbox"); 551 552 fi = dfui_form_field_add(f, "fsize", 553 dfui_info_new(_("Frag Sz"), "", "")); 554 555 fi = dfui_form_field_add(f, "bsize", 556 dfui_info_new(_("Block Sz"), "", "")); 557 558 dfui_form_action_add(f, "switch", 559 dfui_info_new(_("Switch to Normal Mode"), "", "")); 560 } else { 561 dfui_form_action_add(f, "switch", 562 dfui_info_new(_("Switch to Expert Mode"), "", "")); 563 } 564 #endif 565 return(f); 566 } 567 568 /* 569 * Returns: 570 * -1 = the form should be redisplayed 571 * 0 = failure, function is over 572 * 1 = success, function is over 573 */ 574 static int 575 show_create_subpartitions_form(struct dfui_form *f, struct i_fn_args *a) 576 { 577 struct dfui_dataset *ds; 578 struct dfui_response *r; 579 580 for (;;) { 581 if (dfui_form_dataset_get_first(f) == NULL) 582 populate_create_subpartitions_form(f, a); 583 584 if (!dfui_be_present(a->c, f, &r)) 585 abort_backend(); 586 587 if (strcmp(dfui_response_get_action_id(r), "cancel") == 0) { 588 dfui_response_free(r); 589 return(0); 590 } else if (strcmp(dfui_response_get_action_id(r), "switch") == 0) { 591 if (check_subpartition_selections(r, a)) { 592 save_subpartition_selections(r, a); 593 expert = expert ? 0 : 1; 594 dfui_response_free(r); 595 return(-1); 596 } 597 } else { 598 if (check_subpartition_selections(r, a)) { 599 save_subpartition_selections(r, a); 600 if (!warn_subpartition_selections(a)) { 601 if (!create_subpartitions(a)) { 602 inform(a->c, _("The subpartitions you chose were " 603 "not correctly created, and the " 604 "primary partition may " 605 "now be in an inconsistent state. " 606 "We recommend re-formatting it " 607 "before proceeding.")); 608 dfui_response_free(r); 609 return(0); 610 } else { 611 dfui_response_free(r); 612 return(1); 613 } 614 } 615 } 616 } 617 618 dfui_form_datasets_free(f); 619 /* dfui_form_datasets_add_from_response(f, r); */ 620 for (ds = dfui_response_dataset_get_first(r); ds != NULL; 621 ds = dfui_dataset_get_next(ds)) { 622 dfui_form_dataset_add(f, dfui_dataset_dup(ds)); 623 } 624 } 625 } 626 627 /* 628 * fn_create_subpartitions_hammer: let the user specify what subpartitions they 629 * want on the disk, how large each should be, and where it should be mounted. 630 */ 631 void 632 fn_create_subpartitions_hammer(struct i_fn_args *a) 633 { 634 struct dfui_form *f; 635 int done = 0; 636 637 a->result = 0; 638 while (!done) { 639 f = make_create_subpartitions_form(a); 640 switch (show_create_subpartitions_form(f, a)) { 641 case -1: 642 done = 0; 643 break; 644 case 0: 645 done = 1; 646 a->result = 0; 647 break; 648 case 1: 649 done = 1; 650 a->result = 1; 651 break; 652 } 653 dfui_form_free(f); 654 } 655 } 656