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