installer: Raise the absolute minimum disk size to 2GB.
[dragonfly.git] / usr.sbin / installer / dfuibe_installer / fn_subpart_hammer.c
CommitLineData
7eae318c
MS
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
68static int create_subpartitions(struct i_fn_args *);
69static long default_capacity(struct storage *, int);
70static int check_capacity(struct i_fn_args *);
71static int check_subpartition_selections(struct dfui_response *, struct i_fn_args *);
72static void save_subpartition_selections(struct dfui_response *, struct i_fn_args *);
73static void populate_create_subpartitions_form(struct dfui_form *, struct i_fn_args *);
74static int warn_subpartition_selections(struct i_fn_args *);
75static struct dfui_form *make_create_subpartitions_form(struct i_fn_args *);
76static int show_create_subpartitions_form(struct dfui_form *, struct i_fn_args *);
77
6cd7259e 78static const char *def_mountpt[] = {"/", "swap", "/var", "/tmp", "/usr", "/home", NULL};
7eae318c
MS
79static int expert = 0;
80
81/*
82 * Given a set of subpartitions-to-be in the selected slice,
83 * create them.
84 */
85static int
86create_subpartitions(struct i_fn_args *a)
87{
88 struct subpartition *sp;
89 struct commands *cmds;
90 int result = 0;
7eae318c
MS
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
218static long
219default_capacity(struct storage *s, int mtpt)
220{
221 unsigned long swap;
222 unsigned long capacity;
6cd7259e
SW
223 unsigned long mem;
224
7eae318c 225 capacity = slice_get_capacity(storage_get_selected_slice(s));
6cd7259e
SW
226 mem = storage_get_memsize(s);
227 swap = 2 * mem;
228 if (mem > (capacity / 2) || capacity < 4096)
229 swap = mem;
230 if (mem > capacity)
7eae318c 231 swap = capacity / 2;
4b220078
SW
232 if (swap > 8192)
233 swap = 8192;
7eae318c
MS
234
235 if (capacity < DISK_MIN) {
236 /*
237 * For the purposes of this installer:
238 * can't be done. Sorry.
239 */
240 return(-1);
7eae318c
MS
241 } else {
242 switch (mtpt) {
243 case MTPT_ROOT: return(-1);
244 case MTPT_SWAP: return(swap);
7eae318c
MS
245 }
246 }
247 /* shouldn't ever happen */
248 return(-1);
249}
250
251static int
252check_capacity(struct i_fn_args *a)
253{
254 struct subpartition *sp;
6cd7259e 255 unsigned long min_capacity[] = {DISK_MIN, 0, 0, 0, 0, 0, 0};
7eae318c
MS
256 unsigned long total_capacity = 0;
257 int mtpt;
258
259 if (subpartition_find(storage_get_selected_slice(a->s), "/usr") == NULL)
260 min_capacity[MTPT_ROOT] += min_capacity[MTPT_USR];
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
301static int
302check_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;
7eae318c
MS
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");
66764e14 326
7eae318c
MS
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
4b220078
SW
374 if ((strcasecmp(mountpoint, "swap") == 0) && (capacity > 8192)) {
375 inform(a->c, _("Swap capacity is limited to 8G."));
376 valid = 0;
377 }
378
7eae318c
MS
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
404static void
405save_subpartition_selections(struct dfui_response *r, struct i_fn_args *a)
406{
407 struct dfui_dataset *ds;
7eae318c
MS
408 const char *mountpoint, *capstring;
409 long capacity;
7eae318c
MS
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");
66764e14 418
7eae318c
MS
419 if (string_to_capacity(capstring, &capacity)) {
420 subpartition_new_hammer(storage_get_selected_slice(a->s),
421 mountpoint, capacity);
422 }
423 }
424}
425
426static void
427populate_create_subpartitions_form(struct dfui_form *f, struct i_fn_args *a)
428{
429 struct subpartition *sp;
430 struct dfui_dataset *ds;
7eae318c
MS
431 int mtpt;
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 (mtpt = 0; def_mountpt[mtpt] != NULL; mtpt++) {
6cd7259e 456 /* XXX skip all except / and swap for now */
7eae318c
MS
457 if (mtpt != MTPT_ROOT && mtpt != MTPT_SWAP)
458 continue;
459 capacity = default_capacity(a->s, mtpt);
460 ds = dfui_dataset_new();
461 dfui_dataset_celldata_add(ds, "mountpoint",
462 def_mountpt[mtpt]);
463 dfui_dataset_celldata_add(ds, "capacity",
464 capacity_to_string(capacity));
465 dfui_form_dataset_add(f, ds);
466 }
467 }
468}
469
470static int
471warn_subpartition_selections(struct i_fn_args *a)
472{
473 int valid = 0;
7eae318c
MS
474
475 valid = check_capacity(a);
7eae318c
MS
476
477 return(!valid);
478}
479
480static struct dfui_form *
481make_create_subpartitions_form(struct i_fn_args *a)
482{
7eae318c
MS
483 struct dfui_form *f;
484 char msg_buf[1][1024];
485
486 snprintf(msg_buf[0], sizeof(msg_buf[0]),
487 _("Subpartitions further divide a primary partition for "
488 "use with %s. Some reasons you may want "
489 "a set of subpartitions are:\n\n"
490 "- you want to restrict how much data can be written "
491 "to certain parts of the primary partition, to quell "
492 "denial-of-service attacks; and\n"
493 "- you want to speed up access to data on the disk."
494 ""), OPERATING_SYSTEM_NAME);
495
496 f = dfui_form_create(
497 "create_subpartitions",
498 _("Create Subpartitions"),
499 _("Set up the subpartitions (also known as just `partitions' "
500 "in BSD tradition) you want to have on this primary "
d4ff6419
MS
501 "partition.\n\nIMPORTANT: "
502 "You have chosen HAMMER as your file system. This means you will "
503 "not need to create separate subpartitions for /home, /usr, /var and "
504 "/tmp. The installer will create them automatically as pseudo-"
505 "filesystems (PFS) for you. In most cases you should be fine with "
506 "the default settings.\n\n"
7eae318c
MS
507 "For Capacity, use 'M' to indicate megabytes, 'G' to "
508 "indicate gigabytes, or a single '*' to indicate "
509 "'use the remaining space on the primary partition'."),
510
511 msg_buf[0],
512
513 "p", "special", "dfinstaller_create_subpartitions",
514 "p", "minimum_width","64",
515
516 "f", "mountpoint", _("Mountpoint"), "", "",
517 "f", "capacity", _("Capacity"), "", "",
518
519 "a", "ok", _("Accept and Create"), "", "",
520 "a", "cancel",
521 (disk_get_formatted(storage_get_selected_disk(a->s)) ?
522 _("Return to Select Disk") :
523 _("Return to Select Primary Partition")), "", "",
524 "p", "accelerator", "ESC",
525
526 NULL
527 );
528
529 dfui_form_set_multiple(f, 1);
530 dfui_form_set_extensible(f, 1);
531 /*
532 * Remove ATM until HAMMER installer support is better
533 * dfui_form_set_extensible(f, 1);
534 */
535#if 0
536 if (expert) {
537 fi = dfui_form_field_add(f, "softupdates",
538 dfui_info_new(_("Softupdates"), "", ""));
539 dfui_field_property_set(fi, "control", "checkbox");
540
541 fi = dfui_form_field_add(f, "mfsbacked",
542 dfui_info_new(_("MFS"), "", ""));
543 dfui_field_property_set(fi, "control", "checkbox");
544
545 fi = dfui_form_field_add(f, "fsize",
546 dfui_info_new(_("Frag Sz"), "", ""));
547
548 fi = dfui_form_field_add(f, "bsize",
549 dfui_info_new(_("Block Sz"), "", ""));
550
551 dfui_form_action_add(f, "switch",
552 dfui_info_new(_("Switch to Normal Mode"), "", ""));
553 } else {
554 dfui_form_action_add(f, "switch",
555 dfui_info_new(_("Switch to Expert Mode"), "", ""));
556 }
557#endif
558 return(f);
559}
560
561/*
562 * Returns:
563 * -1 = the form should be redisplayed
564 * 0 = failure, function is over
565 * 1 = success, function is over
566 */
567static int
568show_create_subpartitions_form(struct dfui_form *f, struct i_fn_args *a)
569{
570 struct dfui_dataset *ds;
571 struct dfui_response *r;
572
573 for (;;) {
574 if (dfui_form_dataset_get_first(f) == NULL)
575 populate_create_subpartitions_form(f, a);
576
577 if (!dfui_be_present(a->c, f, &r))
578 abort_backend();
579
580 if (strcmp(dfui_response_get_action_id(r), "cancel") == 0) {
581 dfui_response_free(r);
582 return(0);
583 } else if (strcmp(dfui_response_get_action_id(r), "switch") == 0) {
584 if (check_subpartition_selections(r, a)) {
585 save_subpartition_selections(r, a);
586 expert = expert ? 0 : 1;
587 dfui_response_free(r);
588 return(-1);
589 }
590 } else {
591 if (check_subpartition_selections(r, a)) {
592 save_subpartition_selections(r, a);
593 if (!warn_subpartition_selections(a)) {
594 if (!create_subpartitions(a)) {
595 inform(a->c, _("The subpartitions you chose were "
596 "not correctly created, and the "
597 "primary partition may "
598 "now be in an inconsistent state. "
599 "We recommend re-formatting it "
600 "before proceeding."));
601 dfui_response_free(r);
602 return(0);
603 } else {
604 dfui_response_free(r);
605 return(1);
606 }
607 }
608 }
609 }
610
611 dfui_form_datasets_free(f);
612 /* dfui_form_datasets_add_from_response(f, r); */
613 for (ds = dfui_response_dataset_get_first(r); ds != NULL;
614 ds = dfui_dataset_get_next(ds)) {
615 dfui_form_dataset_add(f, dfui_dataset_dup(ds));
616 }
617 }
618}
619
620/*
621 * fn_create_subpartitions_hammer: let the user specify what subpartitions they
622 * want on the disk, how large each should be, and where it should be mounted.
623 */
624void
625fn_create_subpartitions_hammer(struct i_fn_args *a)
626{
627 struct dfui_form *f;
628 int done = 0;
629
630 a->result = 0;
631 while (!done) {
632 f = make_create_subpartitions_form(a);
633 switch (show_create_subpartitions_form(f, a)) {
634 case -1:
635 done = 0;
636 break;
637 case 0:
638 done = 1;
639 a->result = 0;
640 break;
641 case 1:
642 done = 1;
643 a->result = 1;
644 break;
645 }
646 dfui_form_free(f);
647 }
648}