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 boot, root, 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 230 /* 231 * Try to get 768M for /boot, but if space is tight go down to 128M 232 * in 128M steps. 233 * For swap, start with 2*mem but take less if space is tight 234 * (minimum is 384). 235 * Rest goes to / (which is at least 75% slice size). 236 */ 237 238 root = capacity / 4 * 3; 239 swap = 2 * mem; 240 if (swap > 8192) 241 swap = 8192; 242 boot = 768; 243 while (boot + root > capacity - 384) 244 boot -= 128; 245 if (boot + root + swap > capacity) 246 swap = capacity - boot - root; 247 248 if (capacity < DISK_MIN) 249 return(-1); 250 else if (strcmp(mtpt, "/boot") == 0) 251 return(boot); 252 else if (strcmp(mtpt, "swap") == 0) 253 return(swap); 254 else if (strcmp(mtpt, "/") == 0) 255 return(-1); 256 257 /* shouldn't ever happen */ 258 return(-1); 259 } 260 261 static int 262 check_capacity(struct i_fn_args *a) 263 { 264 struct subpartition *sp; 265 unsigned long min_capacity[] = {128, 0, DISK_MIN - 128, 0}; 266 unsigned long total_capacity = 0; 267 unsigned long remaining_capacity; 268 int mtpt, warn_smallpart = 0; 269 270 remaining_capacity = slice_get_capacity(storage_get_selected_slice(a->s)); 271 for (sp = slice_subpartition_first(storage_get_selected_slice(a->s)); 272 sp != NULL; sp = subpartition_next(sp)) { 273 if (subpartition_get_capacity(sp) != -1) 274 remaining_capacity -= subpartition_get_capacity(sp); 275 } 276 277 for (sp = slice_subpartition_first(storage_get_selected_slice(a->s)); 278 sp != NULL; sp = subpartition_next(sp)) { 279 if (subpartition_get_capacity(sp) == -1) 280 total_capacity++; 281 else 282 total_capacity += subpartition_get_capacity(sp); 283 for (mtpt = 0; def_mountpt[mtpt] != NULL; mtpt++) { 284 if (strcmp(subpartition_get_mountpoint(sp), def_mountpt[mtpt]) == 0 && 285 min_capacity[mtpt] > 0 && 286 subpartition_get_capacity(sp) < min_capacity[mtpt]) { 287 inform(a->c, _("WARNING: the %s subpartition should " 288 "be at least %dM in size or you will " 289 "risk running out of space during " 290 "the installation."), 291 subpartition_get_mountpoint(sp), min_capacity[mtpt]); 292 } 293 } 294 if (strcmp(subpartition_get_mountpoint(sp), "/boot") != 0 && 295 strcmp(subpartition_get_mountpoint(sp), "swap") != 0) { 296 if ((subpartition_get_capacity(sp) == -1 && 297 remaining_capacity < HAMMER_MIN) || 298 (subpartition_get_capacity(sp) < HAMMER_MIN)) 299 warn_smallpart++; 300 } 301 } 302 303 if (total_capacity > slice_get_capacity(storage_get_selected_slice(a->s))) { 304 inform(a->c, _("The space allocated to all of your selected " 305 "subpartitions (%dM) exceeds the total " 306 "capacity of the selected primary partition " 307 "(%dM). Remove some subpartitions or choose " 308 "a smaller size for them and try again."), 309 total_capacity, slice_get_capacity(storage_get_selected_slice(a->s))); 310 return(0); 311 } 312 313 if (warn_smallpart) 314 return (confirm_dangerous_action(a->c, 315 _("WARNING: HAMMER filesystems less than 50GB are " 316 "not recommended!\n" 317 "You may have to run 'hammer prune-everything' and " 318 "'hammer reblock'\n" 319 "quite often, even if using a nohistory mount."))); 320 321 return(1); 322 } 323 324 static int 325 check_subpartition_selections(struct dfui_response *r, struct i_fn_args *a) 326 { 327 struct dfui_dataset *ds; 328 struct dfui_dataset *star_ds = NULL; 329 struct aura_dict *d; 330 const char *mountpoint, *capstring; 331 long capacity = 0; 332 int found_root = 0; 333 int valid = 1; 334 335 d = aura_dict_new(1, AURA_DICT_LIST); 336 337 if ((ds = dfui_response_dataset_get_first(r)) == NULL) { 338 inform(a->c, _("Please set up at least one subpartition.")); 339 valid = 0; 340 } 341 342 for (ds = dfui_response_dataset_get_first(r); valid && ds != NULL; 343 ds = dfui_dataset_get_next(ds)) { 344 #ifdef DEBUG 345 dfui_dataset_dump(ds); 346 #endif 347 mountpoint = dfui_dataset_get_value(ds, "mountpoint"); 348 capstring = dfui_dataset_get_value(ds, "capacity"); 349 350 if (aura_dict_exists(d, mountpoint, strlen(mountpoint) + 1)) { 351 inform(a->c, _("The same mount point cannot be specified " 352 "for two different subpartitions.")); 353 valid = 0; 354 } 355 356 if (strcmp(mountpoint, "/") == 0) 357 found_root = 1; 358 359 if (strcmp(capstring, "*") == 0) { 360 if (star_ds != NULL) { 361 inform(a->c, _("You cannot have more than one subpartition " 362 "with a '*' capacity (meaning 'use the remainder " 363 "of the primary partition'.)")); 364 valid = 0; 365 } else { 366 star_ds = ds; 367 } 368 } 369 370 if (!(!strcasecmp(mountpoint, "swap") || mountpoint[0] == '/')) { 371 inform(a->c, _("Mount point must be either 'swap', or it must " 372 "start with a '/'.")); 373 valid = 0; 374 } 375 376 if (strpbrk(mountpoint, " \\\"'`") != NULL) { 377 inform(a->c, _("Mount point may not contain the following " 378 "characters: blank space, backslash, or " 379 "single, double, or back quotes.")); 380 valid = 0; 381 } 382 383 if (strlen(capstring) == 0) { 384 inform(a->c, _("A capacity must be specified.")); 385 valid = 0; 386 } 387 388 if (!string_to_capacity(capstring, &capacity)) { 389 inform(a->c, _("Capacity must be either a '*' symbol to indicate " 390 "'use the rest of the primary partition', or it " 391 "must be a series of decimal digits ending with a " 392 "'M' (indicating megabytes) or a 'G' (indicating " 393 "gigabytes.)")); 394 valid = 0; 395 } 396 397 if ((strcasecmp(mountpoint, "swap") == 0) && (capacity > 8192)) { 398 inform(a->c, _("Swap capacity is limited to 8G.")); 399 valid = 0; 400 } 401 402 /* 403 * If we made it through that obstacle course, all is well. 404 */ 405 406 if (valid) 407 aura_dict_store(d, mountpoint, strlen(mountpoint) + 1, "", 1); 408 } 409 410 if (!found_root) { 411 inform(a->c, _("You must include a / (root) subpartition.")); 412 valid = 0; 413 } 414 415 if (aura_dict_size(d) > 16) { 416 inform(a->c, _("You cannot have more than 16 subpartitions " 417 "on a single primary partition. Remove some " 418 "and try again.")); 419 valid = 0; 420 } 421 422 aura_dict_free(d); 423 424 return(valid); 425 } 426 427 static void 428 save_subpartition_selections(struct dfui_response *r, struct i_fn_args *a) 429 { 430 struct dfui_dataset *ds; 431 const char *mountpoint, *capstring; 432 long capacity; 433 int valid = 1; 434 435 subpartitions_free(storage_get_selected_slice(a->s)); 436 437 for (ds = dfui_response_dataset_get_first(r); valid && ds != NULL; 438 ds = dfui_dataset_get_next(ds)) { 439 mountpoint = dfui_dataset_get_value(ds, "mountpoint"); 440 capstring = dfui_dataset_get_value(ds, "capacity"); 441 442 if (string_to_capacity(capstring, &capacity)) { 443 subpartition_new_hammer(storage_get_selected_slice(a->s), 444 mountpoint, capacity); 445 } 446 } 447 } 448 449 static void 450 populate_create_subpartitions_form(struct dfui_form *f, struct i_fn_args *a) 451 { 452 struct subpartition *sp; 453 struct dfui_dataset *ds; 454 int i; 455 long capacity; 456 457 if (slice_subpartition_first(storage_get_selected_slice(a->s)) != NULL) { 458 /* 459 * The user has already given us their subpartition 460 * preferences, so use them here. 461 */ 462 for (sp = slice_subpartition_first(storage_get_selected_slice(a->s)); 463 sp != NULL; sp = subpartition_next(sp)) { 464 ds = dfui_dataset_new(); 465 dfui_dataset_celldata_add(ds, "mountpoint", 466 subpartition_get_mountpoint(sp)); 467 dfui_dataset_celldata_add(ds, "capacity", 468 capacity_to_string(subpartition_get_capacity(sp))); 469 dfui_form_dataset_add(f, ds); 470 } 471 } else { 472 /* 473 * Otherwise, populate the form with datasets representing 474 * reasonably-calculated defaults. The defaults are chosen 475 * based on the slice's total capacity and the machine's 476 * total physical memory (for swap.) 477 */ 478 for (i = 0; def_mountpt[i] != NULL; i++) { 479 capacity = default_capacity(a->s, def_mountpt[i]); 480 ds = dfui_dataset_new(); 481 dfui_dataset_celldata_add(ds, "mountpoint", 482 def_mountpt[i]); 483 dfui_dataset_celldata_add(ds, "capacity", 484 capacity_to_string(capacity)); 485 dfui_form_dataset_add(f, ds); 486 } 487 } 488 } 489 490 static int 491 warn_subpartition_selections(struct i_fn_args *a) 492 { 493 int valid = 0; 494 495 if (subpartition_find(storage_get_selected_slice(a->s), "/boot") == NULL) { 496 inform(a->c, _("The /boot partition must not be omitted.")); 497 } else if (subpartition_find(storage_get_selected_slice(a->s), "/home") != NULL || 498 subpartition_find(storage_get_selected_slice(a->s), "/tmp") != NULL || 499 subpartition_find(storage_get_selected_slice(a->s), "/usr") != NULL || 500 subpartition_find(storage_get_selected_slice(a->s), "/usr/obj") != NULL || 501 subpartition_find(storage_get_selected_slice(a->s), "/var") != NULL || 502 subpartition_find(storage_get_selected_slice(a->s), "/var/crash") != NULL || 503 subpartition_find(storage_get_selected_slice(a->s), "/var/tmp") != NULL) { 504 inform(a->c, _("Pseudo filesystems will automatically be created " 505 "for /home, /tmp, /usr, /usr/obj, /var, /var/crash " 506 "and /var/tmp and must not be specified.")); 507 } else { 508 valid = check_capacity(a); 509 } 510 511 return(!valid); 512 } 513 514 static struct dfui_form * 515 make_create_subpartitions_form(struct i_fn_args *a) 516 { 517 struct dfui_form *f; 518 char msg_buf[1][1024]; 519 520 snprintf(msg_buf[0], sizeof(msg_buf[0]), 521 _("Subpartitions further divide a primary partition for " 522 "use with %s. Some reasons you may want " 523 "a set of subpartitions are:\n\n" 524 "- you want to restrict how much data can be written " 525 "to certain parts of the primary partition, to quell " 526 "denial-of-service attacks; and\n" 527 "- you want to speed up access to data on the disk." 528 ""), OPERATING_SYSTEM_NAME); 529 530 f = dfui_form_create( 531 "create_subpartitions", 532 _("Create Subpartitions"), 533 _("Set up the subpartitions (also known as just `partitions' " 534 "in BSD tradition) you want to have on this primary " 535 "partition. In most cases you should be fine with " 536 "the default settings.\n\n" 537 "For Capacity, use 'M' to indicate megabytes, 'G' to " 538 "indicate gigabytes, or a single '*' to indicate " 539 "'use the remaining space on the primary partition'."), 540 541 msg_buf[0], 542 543 "p", "special", "dfinstaller_create_subpartitions", 544 "p", "minimum_width","64", 545 546 "f", "mountpoint", _("Mountpoint"), "", "", 547 "f", "capacity", _("Capacity"), "", "", 548 549 "a", "ok", _("Accept and Create"), "", "", 550 "a", "cancel", 551 (disk_get_formatted(storage_get_selected_disk(a->s)) ? 552 _("Return to Select Disk") : 553 _("Return to Select Primary Partition")), "", "", 554 "p", "accelerator", "ESC", 555 556 NULL 557 ); 558 559 dfui_form_set_multiple(f, 1); 560 dfui_form_set_extensible(f, 1); 561 /* 562 * Remove ATM until HAMMER installer support is better 563 * dfui_form_set_extensible(f, 1); 564 */ 565 #if 0 566 if (expert) { 567 fi = dfui_form_field_add(f, "softupdates", 568 dfui_info_new(_("Softupdates"), "", "")); 569 dfui_field_property_set(fi, "control", "checkbox"); 570 571 fi = dfui_form_field_add(f, "mfsbacked", 572 dfui_info_new(_("MFS"), "", "")); 573 dfui_field_property_set(fi, "control", "checkbox"); 574 575 fi = dfui_form_field_add(f, "fsize", 576 dfui_info_new(_("Frag Sz"), "", "")); 577 578 fi = dfui_form_field_add(f, "bsize", 579 dfui_info_new(_("Block Sz"), "", "")); 580 581 dfui_form_action_add(f, "switch", 582 dfui_info_new(_("Switch to Normal Mode"), "", "")); 583 } else { 584 dfui_form_action_add(f, "switch", 585 dfui_info_new(_("Switch to Expert Mode"), "", "")); 586 } 587 #endif 588 return(f); 589 } 590 591 /* 592 * Returns: 593 * -1 = the form should be redisplayed 594 * 0 = failure, function is over 595 * 1 = success, function is over 596 */ 597 static int 598 show_create_subpartitions_form(struct dfui_form *f, struct i_fn_args *a) 599 { 600 struct dfui_dataset *ds; 601 struct dfui_response *r; 602 603 for (;;) { 604 if (dfui_form_dataset_get_first(f) == NULL) 605 populate_create_subpartitions_form(f, a); 606 607 if (!dfui_be_present(a->c, f, &r)) 608 abort_backend(); 609 610 if (strcmp(dfui_response_get_action_id(r), "cancel") == 0) { 611 dfui_response_free(r); 612 return(0); 613 } else if (strcmp(dfui_response_get_action_id(r), "switch") == 0) { 614 if (check_subpartition_selections(r, a)) { 615 save_subpartition_selections(r, a); 616 expert = expert ? 0 : 1; 617 dfui_response_free(r); 618 return(-1); 619 } 620 } else { 621 if (check_subpartition_selections(r, a)) { 622 save_subpartition_selections(r, a); 623 if (!warn_subpartition_selections(a)) { 624 if (!create_subpartitions(a)) { 625 inform(a->c, _("The subpartitions you chose were " 626 "not correctly created, and the " 627 "primary partition may " 628 "now be in an inconsistent state. " 629 "We recommend re-formatting it " 630 "before proceeding.")); 631 dfui_response_free(r); 632 return(0); 633 } else { 634 dfui_response_free(r); 635 return(1); 636 } 637 } 638 } 639 } 640 641 dfui_form_datasets_free(f); 642 /* dfui_form_datasets_add_from_response(f, r); */ 643 for (ds = dfui_response_dataset_get_first(r); ds != NULL; 644 ds = dfui_dataset_get_next(ds)) { 645 dfui_form_dataset_add(f, dfui_dataset_dup(ds)); 646 } 647 } 648 } 649 650 /* 651 * fn_create_subpartitions_hammer: let the user specify what subpartitions they 652 * want on the disk, how large each should be, and where it should be mounted. 653 */ 654 void 655 fn_create_subpartitions_hammer(struct i_fn_args *a) 656 { 657 struct dfui_form *f; 658 int done = 0; 659 660 a->result = 0; 661 while (!done) { 662 f = make_create_subpartitions_form(a); 663 switch (show_create_subpartitions_form(f, a)) { 664 case -1: 665 done = 0; 666 break; 667 case 0: 668 done = 1; 669 a->result = 0; 670 break; 671 case 1: 672 done = 1; 673 a->result = 1; 674 break; 675 } 676 dfui_form_free(f); 677 } 678 } 679