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 *, int);
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[7]  = {"/", "swap", "/var", "/tmp", "/usr", "/home", 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 {
163 			command_add(cmds, "%s%s '  %c:\t%s\t%s\tHAMMER' >>%sinstall.disklabel",
164 			    a->os_root, cmd_name(a, "ECHO"),
165 			    subpartition_get_letter(sp),
166 			    capacity_to_string(subpartition_get_capacity(sp)),
167 			    subpartition_get_letter(sp) == 'a' ? "0" : "*",
168 			    a->tmp);
169 		}
170 	}
171 	temp_file_add(a, "install.disklabel");
172 
173 	/*
174 	 * Label the slice from the disklabel we just wove together.
175 	 */
176 	command_add(cmds, "%s%s -R -B -r %s %sinstall.disklabel",
177 	    a->os_root, cmd_name(a, "DISKLABEL64"),
178 	    slice_get_device_name(storage_get_selected_slice(a->s)),
179 	    a->tmp);
180 
181 	/*
182 	 * Create a snapshot of the disklabel we just created
183 	 * for debugging inspection in the log.
184 	 */
185 	command_add(cmds, "%s%s %s",
186 	    a->os_root, cmd_name(a, "DISKLABEL64"),
187 	    slice_get_device_name(storage_get_selected_slice(a->s)));
188 
189 	/*
190 	 * Create filesystems on the newly-created subpartitions.
191 	 */
192 	for (sp = slice_subpartition_first(storage_get_selected_slice(a->s));
193 	     sp != NULL; sp = subpartition_next(sp)) {
194 		if (subpartition_is_swap(sp) || subpartition_is_mfsbacked(sp))
195 			continue;
196 
197 		/*
198 		 * Ensure that all the needed device nodes exist.
199 		 */
200 		command_add_ensure_dev(a, cmds,
201 		    disk_get_device_name(storage_get_selected_disk(a->s)));
202 		command_add_ensure_dev(a, cmds,
203 		    slice_get_device_name(storage_get_selected_slice(a->s)));
204 		command_add_ensure_dev(a, cmds,
205 		    subpartition_get_device_name(sp));
206 
207 		command_add(cmds, "%s%s -f -L ROOT %sdev/%s",
208 		    a->os_root, cmd_name(a, "NEWFS_HAMMER"),
209 		    a->os_root,
210 		    subpartition_get_device_name(sp));
211 	}
212 
213 	result = commands_execute(a, cmds);
214 	commands_free(cmds);
215 	return(result);
216 }
217 
218 static long
219 default_capacity(struct storage *s, int mtpt)
220 {
221 	unsigned long swap;
222 	unsigned long capacity;
223 #if 0
224 	if (mtpt == MTPT_HOME)
225 		return(-1);
226 #endif
227 	capacity = slice_get_capacity(storage_get_selected_slice(s));
228 	swap = 2 * storage_get_memsize(s);
229 	if (storage_get_memsize(s) > (capacity / 2) || capacity < 4096)
230 		swap = storage_get_memsize(s);
231 	if (storage_get_memsize(s) > capacity)
232 		swap = capacity / 2;
233 	if (swap > 8192)
234 		swap = 8192;
235 
236 	if (capacity < DISK_MIN) {
237 		/*
238 		 * For the purposes of this installer:
239 		 * can't be done.  Sorry.
240 		 */
241 		return(-1);
242 	} else if (capacity < 1024) {
243 		switch (mtpt) {
244 		case MTPT_ROOT:	return(-1);
245 		case MTPT_SWAP: return(swap);
246 		case MTPT_VAR:	return(256);
247 		case MTPT_TMP:	return(256);
248 		case MTPT_USR:	return(3072);
249 		}
250 	} else {
251 		switch (mtpt) {
252 		case MTPT_ROOT:	return(-1);
253 		case MTPT_SWAP: return(swap);
254 		case MTPT_VAR:	return(256);
255 		case MTPT_TMP:	return(256);
256 		case MTPT_USR:	return(8192);
257 		}
258 	}
259 	/* shouldn't ever happen */
260 	return(-1);
261 }
262 
263 static int
264 check_capacity(struct i_fn_args *a)
265 {
266 	struct subpartition *sp;
267 	unsigned long min_capacity[7] = {128, 128,0, 8, 0, 174, 0};
268 	unsigned long total_capacity = 0;
269 	int mtpt;
270 
271 	if (subpartition_find(storage_get_selected_slice(a->s), "/usr") == NULL)
272 		min_capacity[MTPT_ROOT] += min_capacity[MTPT_USR];
273 
274 	for (sp = slice_subpartition_first(storage_get_selected_slice(a->s));
275 	     sp != NULL; sp = subpartition_next(sp)) {
276 		if (subpartition_get_capacity(sp) == -1)
277 			total_capacity++;
278 		else
279 			total_capacity += subpartition_get_capacity(sp);
280 		for (mtpt = 0; def_mountpt[mtpt] != NULL; mtpt++) {
281 			if (strcmp(subpartition_get_mountpoint(sp), def_mountpt[mtpt]) == 0 &&
282 			    min_capacity[mtpt] > 0 &&
283 			    subpartition_get_capacity(sp) < min_capacity[mtpt]) {
284 				inform(a->c, _("WARNING: the %s subpartition should "
285 				    "be at least %dM in size or you will "
286 				    "risk running out of space during "
287 				    "the installation."),
288 				    subpartition_get_mountpoint(sp), min_capacity[mtpt]);
289 			}
290 		}
291 		if (strcmp(subpartition_get_mountpoint(sp), "/") == 0 &&
292 		    subpartition_get_capacity(sp) < HAMMER_MIN) {
293 			inform(a->c, _("WARNING: HAMMER file systems"
294 			"less than 50G are not recommended!  You may"
295 			"have to run 'hammer prune-everything' and 'hammer reblock'"
296 			"quite often, even if using a nohistory mount."));
297 		}
298 	}
299 
300 	if (total_capacity > slice_get_capacity(storage_get_selected_slice(a->s))) {
301 		inform(a->c, _("The space allocated to all of your selected "
302 		    "subpartitions (%dM) exceeds the total "
303 		    "capacity of the selected primary partition "
304 		    "(%dM). Remove some subpartitions or choose "
305 		    "a smaller size for them and try again."),
306 		    total_capacity, slice_get_capacity(storage_get_selected_slice(a->s)));
307 		return(0);
308 	}
309 
310 	return(1);
311 }
312 
313 static int
314 check_subpartition_selections(struct dfui_response *r, struct i_fn_args *a)
315 {
316 	struct dfui_dataset *ds;
317 	struct dfui_dataset *star_ds = NULL;
318 	struct aura_dict *d;
319 	const char *mountpoint, *capstring;
320 	long capacity = 0;
321 	int found_root = 0;
322 	int valid = 1;
323 
324 	d = aura_dict_new(1, AURA_DICT_LIST);
325 
326 	if ((ds = dfui_response_dataset_get_first(r)) == NULL) {
327 		inform(a->c, _("Please set up at least one subpartition."));
328 		valid = 0;
329 	}
330 
331 	for (ds = dfui_response_dataset_get_first(r); valid && ds != NULL;
332 	    ds = dfui_dataset_get_next(ds)) {
333 #ifdef DEBUG
334 		dfui_dataset_dump(ds);
335 #endif
336 		mountpoint = dfui_dataset_get_value(ds, "mountpoint");
337 		capstring = dfui_dataset_get_value(ds, "capacity");
338 
339 		if (aura_dict_exists(d, mountpoint, strlen(mountpoint) + 1)) {
340 			inform(a->c, _("The same mount point cannot be specified "
341 			    "for two different subpartitions."));
342 			valid = 0;
343 		}
344 
345 		if (strcmp(mountpoint, "/") == 0)
346 			found_root = 1;
347 
348 		if (strcmp(capstring, "*") == 0) {
349 			if (star_ds != NULL) {
350 				inform(a->c, _("You cannot have more than one subpartition "
351 				    "with a '*' capacity (meaning 'use the remainder "
352 				    "of the primary partition'.)"));
353 				valid = 0;
354 			} else {
355 				star_ds = ds;
356 			}
357 		}
358 
359 		if (!(!strcasecmp(mountpoint, "swap") || mountpoint[0] == '/')) {
360 			inform(a->c, _("Mount point must be either 'swap', or it must "
361 			    "start with a '/'."));
362 			valid = 0;
363 		}
364 
365 		if (strpbrk(mountpoint, " \\\"'`") != NULL) {
366 			inform(a->c, _("Mount point may not contain the following "
367 			    "characters: blank space, backslash, or "
368 			    "single, double, or back quotes."));
369 			valid = 0;
370 		}
371 
372 		if (strlen(capstring) == 0) {
373 			inform(a->c, _("A capacity must be specified."));
374 			valid = 0;
375 		}
376 
377 		if (!string_to_capacity(capstring, &capacity)) {
378 			inform(a->c, _("Capacity must be either a '*' symbol to indicate "
379 			    "'use the rest of the primary partition', or it "
380 			    "must be a series of decimal digits ending with a "
381 			    "'M' (indicating megabytes) or a 'G' (indicating "
382 			    "gigabytes.)"));
383 			valid = 0;
384 		}
385 
386 		if ((strcasecmp(mountpoint, "swap") == 0) && (capacity > 8192)) {
387 			inform(a->c, _("Swap capacity is limited to 8G."));
388 			valid = 0;
389 		}
390 
391 		/*
392 		 * If we made it through that obstacle course, all is well.
393 		 */
394 
395 		if (valid)
396 			aura_dict_store(d, mountpoint, strlen(mountpoint) + 1, "", 1);
397 	}
398 
399 	if (!found_root) {
400 		inform(a->c, _("You must include a / (root) subpartition."));
401 		valid = 0;
402 	}
403 
404 	if (aura_dict_size(d) > 16) {
405 		inform(a->c, _("You cannot have more than 16 subpartitions "
406 		    "on a single primary partition.  Remove some "
407 		    "and try again."));
408 		valid = 0;
409 	}
410 
411 	aura_dict_free(d);
412 
413 	return(valid);
414 }
415 
416 static void
417 save_subpartition_selections(struct dfui_response *r, struct i_fn_args *a)
418 {
419 	struct dfui_dataset *ds;
420 	const char *mountpoint, *capstring;
421 	long capacity;
422 	int valid = 1;
423 
424 	subpartitions_free(storage_get_selected_slice(a->s));
425 
426 	for (ds = dfui_response_dataset_get_first(r); valid && ds != NULL;
427 	    ds = dfui_dataset_get_next(ds)) {
428 		mountpoint = dfui_dataset_get_value(ds, "mountpoint");
429 		capstring = dfui_dataset_get_value(ds, "capacity");
430 
431 		if (string_to_capacity(capstring, &capacity)) {
432 			subpartition_new_hammer(storage_get_selected_slice(a->s),
433 			     mountpoint, capacity);
434 		}
435 	}
436 }
437 
438 static void
439 populate_create_subpartitions_form(struct dfui_form *f, struct i_fn_args *a)
440 {
441 	struct subpartition *sp;
442 	struct dfui_dataset *ds;
443 	int mtpt;
444 	long capacity;
445 
446 	if (slice_subpartition_first(storage_get_selected_slice(a->s)) != NULL) {
447 		/*
448 		 * The user has already given us their subpartition
449 		 * preferences, so use them here.
450 		 */
451 		for (sp = slice_subpartition_first(storage_get_selected_slice(a->s));
452 		     sp != NULL; sp = subpartition_next(sp)) {
453 			ds = dfui_dataset_new();
454 			dfui_dataset_celldata_add(ds, "mountpoint",
455 			    subpartition_get_mountpoint(sp));
456 			dfui_dataset_celldata_add(ds, "capacity",
457 			    capacity_to_string(subpartition_get_capacity(sp)));
458 			dfui_form_dataset_add(f, ds);
459 		}
460 	} else {
461 		/*
462 		 * Otherwise, populate the form with datasets representing
463 		 * reasonably-calculated defaults.  The defaults are chosen
464 		 * based on the slice's total capacity and the machine's
465 		 * total physical memory (for swap.)
466 		 */
467 		for (mtpt = 0; def_mountpt[mtpt] != NULL; mtpt++) {
468 			/* XXX matthias. skip them for now */
469 			if (mtpt != MTPT_ROOT && mtpt != MTPT_SWAP)
470 				continue;
471 			capacity = default_capacity(a->s, mtpt);
472 			ds = dfui_dataset_new();
473 			dfui_dataset_celldata_add(ds, "mountpoint",
474 			    def_mountpt[mtpt]);
475 			dfui_dataset_celldata_add(ds, "capacity",
476 			    capacity_to_string(capacity));
477 			dfui_form_dataset_add(f, ds);
478 		}
479 	}
480 }
481 
482 static int
483 warn_subpartition_selections(struct i_fn_args *a)
484 {
485 	int valid = 0;
486 	struct aura_buffer *omit, *consequences;
487 
488 	omit = aura_buffer_new(2048);
489 	consequences = aura_buffer_new(2048);
490 
491 	valid = check_capacity(a);
492 	/* XXX matthias
493 	 *
494 	 * Should we add an information here, that we created /usr, /var, /home
495 	 * and /tmp as PFS?
496 	 */
497 #if 0
498 	if (subpartition_find(storage_get_selected_slice(a->s), "/var") == NULL) {
499 		aura_buffer_cat(omit, "/var ");
500 		aura_buffer_cat(consequences, _("/var will be a plain dir in /\n"));
501 	}
502 	if (subpartition_find(storage_get_selected_slice(a->s), "/usr") == NULL) {
503 		aura_buffer_cat(omit, "/usr ");
504 		aura_buffer_cat(consequences, _("/usr will be a plain dir in /\n"));
505 	}
506         if (subpartition_find(storage_get_selected_slice(a->s), "/tmp") == NULL) {
507                 aura_buffer_cat(omit, "/tmp ");
508 		aura_buffer_cat(consequences, _("/tmp will be symlinked to /var/tmp\n"));
509 	}
510         if (subpartition_find(storage_get_selected_slice(a->s), "/home") == NULL) {
511                 aura_buffer_cat(omit, "/home ");
512 		aura_buffer_cat(consequences, _("/home will be symlinked to /usr/home\n"));
513 	}
514 
515 	if (valid && aura_buffer_len(omit) > 0) {
516 		switch (dfui_be_present_dialog(a->c, _("Really omit?"),
517 		    _("Omit Subpartition(s)|Return to Create Subpartitions"),
518 		    _("You have elected to not have the following "
519 		    "subpartition(s):\n\n%s\n\n"
520 		    "The ramifications of these subpartition(s) being "
521 		    "missing will be:\n\n%s\n"
522 		    "Is this really what you want to do?"),
523 		    aura_buffer_buf(omit), aura_buffer_buf(consequences))) {
524 		case 1:
525 			valid = 1;
526 			break;
527 		case 2:
528 			valid = 0;
529 			break;
530 		default:
531 			abort_backend();
532 		}
533 	}
534 #endif
535 	aura_buffer_free(omit);
536 	aura_buffer_free(consequences);
537 
538 	return(!valid);
539 }
540 
541 static struct dfui_form *
542 make_create_subpartitions_form(struct i_fn_args *a)
543 {
544 	struct dfui_form *f;
545 	char msg_buf[1][1024];
546 
547 	snprintf(msg_buf[0], sizeof(msg_buf[0]),
548 	    _("Subpartitions further divide a primary partition for "
549 	    "use with %s.  Some reasons you may want "
550 	    "a set of subpartitions are:\n\n"
551 	    "- you want to restrict how much data can be written "
552 	    "to certain parts of the primary partition, to quell "
553 	    "denial-of-service attacks; and\n"
554 	    "- you want to speed up access to data on the disk."
555 	    ""), OPERATING_SYSTEM_NAME);
556 
557 	f = dfui_form_create(
558 	    "create_subpartitions",
559 	    _("Create Subpartitions"),
560 	    _("Set up the subpartitions (also known as just `partitions' "
561 	    "in BSD tradition) you want to have on this primary "
562 	    "partition.\n\nIMPORTANT: "
563 	    "You have chosen HAMMER as your file system. This means you will "
564 	    "not need to create separate subpartitions for /home, /usr, /var and "
565 	    "/tmp. The installer will create them automatically as pseudo-"
566 	    "filesystems (PFS) for you. In most cases you should be fine with "
567 	    "the default settings.\n\n"
568 	    "For Capacity, use 'M' to indicate megabytes, 'G' to "
569 	    "indicate gigabytes, or a single '*' to indicate "
570 	    "'use the remaining space on the primary partition'."),
571 
572 	    msg_buf[0],
573 
574 	    "p", "special", "dfinstaller_create_subpartitions",
575 	    "p", "minimum_width","64",
576 
577 	    "f", "mountpoint", _("Mountpoint"), "", "",
578 	    "f", "capacity", _("Capacity"), "", "",
579 
580 	    "a", "ok", _("Accept and Create"), "", "",
581 	    "a", "cancel",
582 	    (disk_get_formatted(storage_get_selected_disk(a->s)) ?
583 	    _("Return to Select Disk") :
584 	    _("Return to Select Primary Partition")), "", "",
585 	    "p", "accelerator", "ESC",
586 
587 	    NULL
588 	);
589 
590 	dfui_form_set_multiple(f, 1);
591 	dfui_form_set_extensible(f, 1);
592 	/*
593 	 * Remove ATM until HAMMER installer support is better
594 	 * dfui_form_set_extensible(f, 1);
595 	 */
596 #if 0
597 	if (expert) {
598 		fi = dfui_form_field_add(f, "softupdates",
599 		    dfui_info_new(_("Softupdates"), "", ""));
600 		dfui_field_property_set(fi, "control", "checkbox");
601 
602 		fi = dfui_form_field_add(f, "mfsbacked",
603 		    dfui_info_new(_("MFS"), "", ""));
604 		dfui_field_property_set(fi, "control", "checkbox");
605 
606 		fi = dfui_form_field_add(f, "fsize",
607 		    dfui_info_new(_("Frag Sz"), "", ""));
608 
609 		fi = dfui_form_field_add(f, "bsize",
610 		    dfui_info_new(_("Block Sz"), "", ""));
611 
612 		dfui_form_action_add(f, "switch",
613 		    dfui_info_new(_("Switch to Normal Mode"), "", ""));
614 	} else {
615 		dfui_form_action_add(f, "switch",
616 		    dfui_info_new(_("Switch to Expert Mode"), "", ""));
617 	}
618 #endif
619 	return(f);
620 }
621 
622 /*
623  * Returns:
624  *	-1 = the form should be redisplayed
625  *	 0 = failure, function is over
626  *	 1 = success, function is over
627  */
628 static int
629 show_create_subpartitions_form(struct dfui_form *f, struct i_fn_args *a)
630 {
631 	struct dfui_dataset *ds;
632 	struct dfui_response *r;
633 
634 	for (;;) {
635 		if (dfui_form_dataset_get_first(f) == NULL)
636 			populate_create_subpartitions_form(f, a);
637 
638 		if (!dfui_be_present(a->c, f, &r))
639 			abort_backend();
640 
641 		if (strcmp(dfui_response_get_action_id(r), "cancel") == 0) {
642 			dfui_response_free(r);
643 			return(0);
644 		} else if (strcmp(dfui_response_get_action_id(r), "switch") == 0) {
645 			if (check_subpartition_selections(r, a)) {
646 				save_subpartition_selections(r, a);
647 				expert = expert ? 0 : 1;
648 				dfui_response_free(r);
649 				return(-1);
650 			}
651 		} else {
652 			if (check_subpartition_selections(r, a)) {
653 				save_subpartition_selections(r, a);
654 				if (!warn_subpartition_selections(a)) {
655 					if (!create_subpartitions(a)) {
656 						inform(a->c, _("The subpartitions you chose were "
657 							"not correctly created, and the "
658 							"primary partition may "
659 							"now be in an inconsistent state. "
660 							"We recommend re-formatting it "
661 							"before proceeding."));
662 						dfui_response_free(r);
663 						return(0);
664 					} else {
665 						dfui_response_free(r);
666 						return(1);
667 					}
668 				}
669 			}
670 		}
671 
672 		dfui_form_datasets_free(f);
673 		/* dfui_form_datasets_add_from_response(f, r); */
674 		for (ds = dfui_response_dataset_get_first(r); ds != NULL;
675 		    ds = dfui_dataset_get_next(ds)) {
676 			dfui_form_dataset_add(f, dfui_dataset_dup(ds));
677 		}
678 	}
679 }
680 
681 /*
682  * fn_create_subpartitions_hammer: let the user specify what subpartitions they
683  * want on the disk, how large each should be, and where it should be mounted.
684  */
685 void
686 fn_create_subpartitions_hammer(struct i_fn_args *a)
687 {
688 	struct dfui_form *f;
689 	int done = 0;
690 
691 	a->result = 0;
692 	while (!done) {
693 		f = make_create_subpartitions_form(a);
694 		switch (show_create_subpartitions_form(f, a)) {
695 		case -1:
696 			done = 0;
697 			break;
698 		case 0:
699 			done = 1;
700 			a->result = 0;
701 			break;
702 		case 1:
703 			done = 1;
704 			a->result = 1;
705 			break;
706 		}
707 		dfui_form_free(f);
708 	}
709 }
710