]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - usr.sbin/bsdinstall/scripts/zfsboot
MFC r256540, r256544:
[FreeBSD/stable/10.git] / usr.sbin / bsdinstall / scripts / zfsboot
1 #!/bin/sh
2 #-
3 # Copyright (c) 2013 Allan Jude
4 # Copyright (c) 2013 Devin Teske
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
9 # are met:
10 # 1. Redistributions of source code must retain the above copyright
11 #    notice, this list of conditions and the following disclaimer.
12 # 2. Redistributions in binary form must reproduce the above copyright
13 #    notice, this list of conditions and the following disclaimer in the
14 #    documentation and/or other materials provided with the distribution.
15 #
16 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 # SUCH DAMAGE.
27 #
28 # $FreeBSD$
29 #
30 ############################################################ INCLUDES
31
32 BSDCFG_SHARE="/usr/share/bsdconfig"
33 . $BSDCFG_SHARE/common.subr || exit 1
34 f_dprintf "%s: loading includes..." "$0"
35 f_include $BSDCFG_SHARE/device.subr
36 f_include $BSDCFG_SHARE/dialog.subr
37 f_include $BSDCFG_SHARE/password/password.subr
38 f_include $BSDCFG_SHARE/variable.subr
39
40 ############################################################ CONFIGURATION
41
42 #
43 # Default name of the boot-pool
44 #
45 : ${ZFSBOOT_POOL_NAME:=zroot}
46
47 #
48 # Default name for the boot environment parent dataset
49 #
50 : ${ZFSBOOT_BEROOT_NAME:=bootenv}
51
52 #
53 # Default name for the primany boot environment
54 #
55 : ${ZFSBOOT_BOOTFS_NAME:=default}
56
57 #
58 # Default Virtual Device (vdev) type to create
59 #
60 : ${ZFSBOOT_VDEV_TYPE:=stripe}
61
62 #
63 # Should we use gnop(8) to configure a transparent mapping to 4K sectors?
64 #
65 : ${ZFSBOOT_GNOP_4K_FORCE_ALIGN:=1}
66
67 #
68 # Should we use geli(8) to encrypt the drives?
69 #
70 : ${ZFSBOOT_GELI_ENCRYPTION:=}
71
72 #
73 # Default name the unencrypted pool when using geli(8) to encrypt the drives
74 #
75 : ${ZFSBOOT_GELI_POOL_NAME:=bootpool}
76
77 #
78 # Default size for the unencrypted boot pool when using geli(8)
79 #
80 : ${ZFSBOOT_GELI_BOOT_SIZE:=2g}
81
82 #
83 # Default path to the geli(8) keyfile used in drive encryption
84 #
85 : ${ZFSBOOT_GELI_KEY_FILE:=/boot/encryption.key}
86
87 #
88 # Default disks to use (always empty unless being scripted)
89 #
90 : ${ZFSBOOT_DISKS:=}
91
92 #
93 # Default partitioning scheme to use on disks
94 #
95 : ${ZFSBOOT_PARTITION_SCHEME:=GPT}
96
97 #
98 # How much swap to put on each block device in the boot zpool
99 # NOTE: Value passed to gpart(8); which supports SI unit suffixes.
100 #
101 : ${ZFSBOOT_SWAP_SIZE:=2g}
102
103 #
104 # Default ZFS layout for root zpool
105 #
106 # NOTE: Requires /tmp, /var/tmp, /$ZFSBOOT_BOOTFS_NAME/$ZFSBOOT_BOOTFS_NAME
107 # NOTE: Anything after pound/hash character [#] is ignored as a comment.
108 #
109 f_isset ZFSBOOT_DATASETS || ZFSBOOT_DATASETS="
110         # DATASET       OPTIONS (comma or space separated; or both)
111
112         # Boot Environment [BE] root and default boot dataset
113         /$ZFSBOOT_BEROOT_NAME                           mountpoint=none
114         /$ZFSBOOT_BEROOT_NAME/$ZFSBOOT_BOOTFS_NAME      mountpoint=/
115
116         # Compress /tmp, allow exec but not setuid
117         /tmp            mountpoint=/tmp,compression=lz4,exec=on,setuid=off
118
119         # Don't mount /usr so that 'base' files go to the BEROOT
120         /usr            mountpoint=/usr,canmount=off
121
122         /usr/local # local files (i.e. from packages) separate from base system
123
124         # Home directories separated so they are common to all BEs
125         /usr/home       setuid=off
126
127         # Ports tree
128         /usr/ports              compression=lz4,setuid=off
129         /usr/ports/distfiles    compression=off,exec=off,setuid=off
130         /usr/ports/packages     compression=off,exec=off,setuid=off
131
132         # Source tree (compressed)
133         /usr/src        compression=lz4,exec=off,setuid=off
134         /usr/obj        # Object files
135
136         # Create /var and friends
137         /var            mountpoint=/var
138         /var/crash      compression=lz4,exec=off,setuid=off
139         /var/db         exec=off,setuid=off
140         /var/db/pkg     compression=lz4,exec=off,setuid=off
141         /var/empty      exec=off,setuid=off
142         /var/log        compression=lz4,exec=off,setuid=off
143         /var/mail       compression=lz4,exec=off,setuid=off
144         /var/run        exec=off,setuid=off
145         /var/tmp        compression=lz4,exec=on,setuid=off
146 " # END-QUOTE
147
148 ############################################################ GLOBALS
149
150 #
151 # Strings that should be moved to an i18n file and loaded with f_include_lang()
152 #
153 hline_alnum_arrows_punc_tab_enter="Use alnum, arrows, punctuation, TAB or ENTER"
154 hline_arrows_space_tab_enter="Use arrows, SPACE, TAB or ENTER"
155 hline_arrows_tab_enter="Press arrows, TAB or ENTER"
156 msg_back="Back"
157 msg_cancel="Cancel"
158 msg_change="Change Selection"
159 msg_configure_options="Configure Options:"
160 msg_create="Install"
161 msg_create_desc="Proceed with Installation"
162 msg_create_help="Create ZFS boot pool with displayed options"
163 msg_detailed_disk_info="gpart(8) show %s:\n%s\n\ncamcontrol(8) inquiry %s:\n%s\n\n\ncamcontrol(8) identify %s:\n%s\n"
164 msg_disk_info="Disk Info"
165 msg_disk_info_help="Get detailed information on disk device(s)"
166 msg_disks_to_use="Disks To Use"
167 msg_disks_to_use_help="Choose which disks to use for the Virtual Device (Required)"
168 msg_force_4k_sectors="Force 4K Sectors?"
169 msg_force_4k_sectors_help="Use gnop(8) to configure forced 4K sector alignment"
170 msg_freebsd_installer="FreeBSD Installer"
171 msg_geli_encryption="Encrypt Disks?"
172 msg_geli_encryption_help="Use geli(8) to encrypt all data partitions"
173 msg_geli_password="Enter a strong passphrase, used to protect your encryption keys. You will be required to enter this passphrase each time the system is booted"
174 msg_geli_setup="Initializing encryption on the selected disks, this will take several seconds per disk"
175 msg_invalid_virtual_device_type="Invalid Virtual Device type \`%s'"
176 msg_invalid_virtual_device_type_help="Select another Virtual Device type or Cancel to\nreturn to the ZFS menu. From there you can select\nmore disks or rescan for additional devices."
177 msg_last_chance_are_you_sure="Last Chance! Are you sure you want to destroy the current contents of the following disks:\n%s"
178 msg_last_chance_are_you_sure_color="\\\\ZrLast Chance!\\\\ZR Are you \\\\Z1sure\\\\Zn you want to \\\\Zr\\\\Z1destroy\\\\Zn the current contents of the following disks:\n%s"
179 msg_mirror_desc="Mirror - n-Way Mirroring"
180 msg_mirror_help="[2+ Disks] Mirroring provides the best performance, but the least storage"
181 msg_no="NO"
182 msg_no_disks_present_to_configure="No disk(s) present to configure"
183 msg_no_disks_selected="No disks selected."
184 msg_not_enough_disks_selected="Not enough disks selected. (%u < %u wanted)"
185 msg_ok="OK"
186 msg_partition_scheme="Partition Scheme"
187 msg_partition_scheme_help="Toggle between GPT and MBR partitioning schemes"
188 msg_please_enter_a_name_for_your_zpool="Please enter a name for your zpool:"
189 msg_please_enter_amount_of_swap_space="Please enter amount of swap space (SI-Unit suffixes\nrecommended; e.g., \`2g' for 2 Gigabytes):"
190 msg_please_select_one_or_more_disks="Please select one or more disks to create a zpool:"
191 msg_pool_name="Pool Name"
192 msg_pool_name_cannot_be_empty="Pool name cannot be empty."
193 msg_pool_name_help="Customize the name of the zpool to be created (Required)"
194 msg_processing_selection="Processing selection..."
195 msg_raidz1_desc="RAID-Z1 - Single Redundant RAID"
196 msg_raidz1_help="[3+ Disks] Withstand failure of 1 disk. Recommended for: 3, 5 or 9 disks"
197 msg_raidz2_desc="RAID-Z2 - Double Redundant RAID"
198 msg_raidz2_help="[4+ Disks] Withstand failure of 2 disks. Recommended for: 4, 6 or 10 disks"
199 msg_raidz3_desc="RAID-Z3 - Triple Redundant RAID"
200 msg_raidz3_help="[5+ Disks] Withstand failure of 3 disks. Recommended for: 5, 7 or 11 disks"
201 msg_rescan_devices="Rescan Devices"
202 msg_rescan_devices_help="Scan for device changes"
203 msg_select="Select"
204 msg_select_a_disk_device="Select a disk device"
205 msg_select_virtual_device_type="Select Virtual Device type:"
206 msg_stripe_desc="Stripe - No Redundancy"
207 msg_stripe_help="[1+ Disks] Striping provides maximum storage but no redundancy"
208 msg_swap_size="Swap Size"
209 msg_swap_size_help="Customize how much swap space is allocated to each selected disk"
210 msg_these_disks_are_too_small="These disks are too small given the amount of requested\nswap (%s) and/or GELI (%s) partitions, which would take\n50%% or more (not recommended) of each of the following\nselected disk devices:\n\n  %s\n\nRecommend changing partition size(s) and/or selecting a\ndifferent set of devices."
211 msg_yes="YES"
212 msg_zfs_configuration="ZFS Configuration"
213 msg_zfs_vdev_type="ZFS VDev Type"
214 msg_zfs_vdev_type_help="Select type of ZFS Virtual Device to create"
215
216 ############################################################ FUNCTIONS
217
218 # dialog_menu_main
219 #
220 # Display the dialog(1)-based application main menu.
221 #
222 dialog_menu_main()
223 {
224         local title="$DIALOG_TITLE"
225         local btitle="$DIALOG_BACKTITLE"
226         local prompt="$msg_configure_options"
227         local force4k="$msg_no"
228         local usegeli="$msg_no"
229         [ "$ZFSBOOT_GNOP_4K_FORCE_ALIGN" ] && force4k="$msg_yes"
230         [ "$ZFSBOOT_GELI_ENCRYPTION" ] && usegeli="$msg_yes"
231         local menu_list="
232                 '>>> $msg_create'         '$msg_create_desc'
233                                           '$msg_create_help'
234                 '- $msg_rescan_devices'   '*'
235                                           '$msg_rescan_devices_help'
236                 '- $msg_disk_info'        '*'
237                                           '$msg_disk_info_help'
238                 '1 $msg_pool_name'        '$ZFSBOOT_POOL_NAME'
239                                           '$msg_pool_name_help'
240                 '2 $msg_disks_to_use'     '$ZFSBOOT_DISKS'
241                                           '$msg_disks_to_use_help'
242                 '3 $msg_zfs_vdev_type'    '$ZFSBOOT_VDEV_TYPE'
243                                           '$msg_zfs_vdev_type_help'
244                 '4 $msg_force_4k_sectors' '$force4k'
245                                           '$msg_force_4k_sectors_help'
246                 '5 $msg_geli_encryption'  '$usegeli'
247                                           '$msg_geli_encryption_help'
248                 '6 $msg_partition_scheme' '$ZFSBOOT_PARTITION_SCHEME'
249                                           '$msg_partition_scheme_help'
250                 '7 $msg_swap_size'        '$ZFSBOOT_SWAP_SIZE'
251                                           '$msg_swap_size_help'
252         " # END-QUOTE
253         local defaultitem= # Calculated below
254         local hline="$hline_alnum_arrows_punc_tab_enter"
255
256         local height width rows
257         eval f_dialog_menu_with_help_size height width rows \
258                 \"\$title\" \"\$btitle\" \"\$prompt\" \"\$hline\" $menu_list
259
260         # Obtain default-item from previously stored selection
261         f_dialog_default_fetch defaultitem
262
263         local menu_choice
264         menu_choice=$( eval $DIALOG \
265                 --title \"\$title\"              \
266                 --backtitle \"\$btitle\"         \
267                 --hline \"\$hline\"              \
268                 --item-help                      \
269                 --ok-label \"\$msg_select\"      \
270                 --cancel-label \"\$msg_cancel\"  \
271                 --default-item \"\$defaultitem\" \
272                 --menu \"\$prompt\"              \
273                 $height $width $rows             \
274                 $menu_list                       \
275                 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
276         )
277         local retval=$?
278         f_dialog_data_sanitize menu_choice
279         f_dialog_menutag_store "$menu_choice"
280
281         # Only update default-item on success
282         [ $retval -eq $DIALOG_OK ] && f_dialog_default_store "$menu_choice"
283
284         return $retval
285 }
286
287 # dialog_edit_disks
288 #
289 # Edit the list of disks to be used by the ZFS boot pool.
290 #
291 dialog_edit_disks()
292 {
293         local title="$DIALOG_TITLE"
294         local btitle="$DIALOG_BACKTITLE"
295         local prompt="$msg_please_select_one_or_more_disks"
296         local check_list= # Calculated below
297         local hline="$hline_arrows_space_tab_enter"
298         local dev vardev disks=
299
300         #
301         # Get a [new] list of disk devices
302         #
303         f_device_find "" $DEVICE_TYPE_DISK disks
304         if [ ! "$disks" ]; then
305                 f_show_msg "$msg_no_disks_present_to_configure"
306                 return $FAILURE
307         fi
308
309         # Lets sort the disks array to be more user friendly
310         disks=$( echo "$disks" | tr ' ' '\n' | sort | tr '\n' ' ' )
311
312         #
313         # Loop through the list of selected disks and create temporary local
314         # variables mapping their status onto an up-to-date list of disks.
315         #
316         for dev in $ZFSBOOT_DISKS; do
317                 f_str2varname "$dev" vardev
318                 local _${vardev}_status=on
319         done
320
321         #
322         # Create the checklist menu of discovered disk devices
323         #
324         local on_off
325         for dev in $disks; do
326                 local desc=
327                 device_$dev get desc desc
328                 f_shell_escape "$desc" desc
329                 f_str2varname "$dev" vardev
330                 f_getvar _${vardev}_status:-off on_off
331                 check_list="$check_list '$dev' '$desc' $on_off"
332         done
333
334         #
335         # Prompt the user to check some disks
336         #
337         local height width rows
338         eval f_dialog_checklist_size height width rows \
339                 \"\$title\" \"\$btitle\" \"\$prompt\" \"\$hline\" $check_list
340         disks=$( eval $DIALOG \
341                 --title \"\$DIALOG_TITLE\"         \
342                 --backtitle \"\$DIALOG_BACKTITLE\" \
343                 --hline \"\$hline\"                \
344                 --ok-label \"\$msg_ok\"            \
345                 --cancel-label \"\$msg_cancel\"    \
346                 --checklist \"\$prompt\"           \
347                 $height $width $rows               \
348                 $check_list                        \
349                 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
350         ) || return $?
351                 # Exit if user either pressed ESC or chose Cancel/No
352         f_dialog_data_sanitize disks
353
354         ZFSBOOT_DISKS="$disks"
355
356         return $DIALOG_OK
357 }
358
359 # dialog_menu_vdev
360 #
361 # Prompt the user to select a a Virtual Device type.
362 #
363 dialog_menu_vdev()
364 {
365         local title="$DIALOG_TITLE"
366         local btitle="$DIALOG_BACKTITLE"
367         local prompt="$msg_select_virtual_device_type"
368         
369         # Make sure [potentially scripted] selections are real
370         real_disks=
371         for disk in $ZFSBOOT_DISKS; do
372                 f_struct device_$disk && real_disks="$real_disks $disk"
373         done
374         # Make sure we have at least one real disk selected
375         ndisks=$( set -- $real_disks; echo $# )
376
377         local menu_list="
378                 'stripe' '$msg_stripe_desc' '$msg_stripe_help'
379                 'mirror' '$msg_mirror_desc' '$msg_mirror_help'
380                 'raidz1' '$msg_raidz1_desc' '$msg_raidz1_help'
381                 'raidz2' '$msg_raidz2_desc' '$msg_raidz2_help'
382                 'raidz3' '$msg_raidz3_desc' '$msg_raidz3_help'
383         " # END-QUOTE
384
385         local defaultitem="$ZFSBOOT_VDEV_TYPE"
386         local hline="$hline_arrows_tab_enter"
387         local error_msg revalidate_choice
388
389         local mheight mwidth mrows
390         eval f_dialog_menu_size mheight mwidth mrows \
391                 \"\$title\" \"\$btitle\" \"\$prompt\" \"\$hline\" $menu_list
392         local iheight iwidth
393         f_dialog_infobox_size iheight iwidth \
394                 "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$msg_processing_selection"
395
396         local menu_choice
397         menu_choice=$( eval $DIALOG \
398                 --title \"\$title\"              \
399                 --backtitle \"\$btitle\"         \
400                 --hline \"\$hline\"              \
401                 --ok-label \"\$msg_ok\"          \
402                 --cancel-label \"\$msg_cancel\"  \
403                 --item-help                      \
404                 --default-item \"\$defaultitem\" \
405                 --menu \"\$prompt\"              \
406                 $mheight $mwidth $mrows          \
407                 $menu_list                       \
408                 --and-widget                     \
409                 ${USE_XDIALOG:+--no-buttons}     \
410                 --infobox \"\$msg_processing_selection\" \
411                 $iheight $iwidth                 \
412                 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
413         ) || return $FAILURE
414         f_dialog_data_sanitize menu_choice
415         sleep 0.5 # Give time to read `--and-widget --info-box'
416
417         # Make sure we have enough disks for the desired vdev type
418         case "$menu_choice" in
419         stripe) want_disks=1 ;;
420         mirror) want_disks=2 ;;
421         raidz1) want_disks=3 ;;
422         raidz2) want_disks=4 ;;
423         raidz3) want_disks=5 ;;
424         *)
425                 f_show_msg "$msg_invalid_virtual_device_type" \
426                            "$menu_choice"
427                 continue
428         esac
429         if [ $ndisks -lt $want_disks ]; then
430                 msg_yes="$msg_change" msg_no="$msg_cancel" f_yesno \
431                         "%s: $msg_not_enough_disks_selected\n%s" \
432                         "$menu_choice" $ndisks $want_disks \
433                         "$msg_invalid_virtual_device_type_help" ||
434                         return $FAILURE
435                 dialog_menu_vdev
436         else
437                 ZFSBOOT_VDEV_TYPE="$menu_choice"
438         fi
439 }
440
441 # zfs_create_diskpart $disk $index
442 #
443 # For each block device to be used in the zpool, rather than just create the
444 # zpool with the raw block devices (e.g., da0, da1, etc.) we create partitions
445 # so we can have some real swap. This also provides wiggle room incase your
446 # replacement drivers do not have the exact same sector counts.
447 #
448 # NOTE: The MBR layout is more complicated (GPT is preferred).
449 #
450 zfs_create_diskpart()
451 {
452         local disk="$1" index="$2"
453         local funcname=zfs_create_diskpart
454         local disksize partsize
455
456         # Check arguments
457         [ "$disk" -a "$index" ] || return $FAILURE
458
459         #
460         # Destroy whatever partition layout is currently on disk.
461         # NOTE: `-F' required to destroy if partitions still exist.
462         # NOTE: Failure is ok here, blank disk will have nothing to destroy.
463         #
464         f_quietly gpart destroy -F $disk
465         f_quietly zpool labelclear -f /dev/$disk # Kill it with fire
466
467         # Make doubly-sure backup GPT is destroyed
468         f_quietly gpart create -s gpt $disk || return $FAILURE
469         f_quietly gpart destroy -F $disk || return $FAILURE
470
471         # Calculate partition size given desired amount of swap
472         device_$disk get capacity disksize || return $FAILURE
473         partsize=$(( $disksize - $swapsize ))
474
475         #
476         # Lay down the desired type of partition scheme
477         #
478         local setsize mbrindex
479         case "$ZFSBOOT_PARTITION_SCHEME" in
480         ""|GPT)
481                 #
482                 # 1. Create GPT layout using labels
483                 #
484                 gpart create -s gpt $disk || return $FAILURE
485
486                 #
487                 # 2. Add small freebsd-boot partition labeled `boot#'
488                 #
489                 gpart add -l gptboot$index -t freebsd-boot -s 512k $disk ||
490                         return $FAILURE
491                 gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 $disk ||
492                         return $FAILURE
493
494                 # zpool will use the `zfs#' GPT labels
495                 bootpart=p2 targetpart=p2
496
497                 # Change things around if we are using GELI
498                 if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
499                         bootpart=p2 targetpart=p3
500                         partsize=$(( $partsize - $gelisize ))
501                         gpart add -l boot$index -t freebsd-zfs \
502                                 -s ${gelisize}b -a 1m $disk || return $FAILURE
503                         # Pedantically nuke any old labels, stop geli
504                         f_quietly zpool labelclear -f /dev/$disk$bootpart
505                         f_quietly geli detach -f /dev/$disk$targetpart
506                 fi
507
508                 #
509                 # 3. Add freebsd-zfs partition labeled `zfs#' for zpool
510                 # NOTE: Using above calculated partsize to leave room for swap.
511                 #
512                 [ $swapsize -gt 0 ] && setsize="-s ${partsize}b"
513                 gpart add -l zfs$index -t freebsd-zfs $setsize -a 1m $disk ||
514                         return $FAILURE
515                 f_quietly zpool labelclear -f /dev/$disk$targetpart # Pedantic
516
517                 #
518                 # 4. Add freebsd-swap partition labeled `swap#'
519                 #
520                 if [ $swapsize -gt 0 ]; then
521                         gpart add -l swap$index -t freebsd-swap -a 1m $disk ||
522                                 return $FAILURE
523                         # Update fstab(5)
524                         printf "$fstab_fmt" \
525                                 /dev/gpt/swap$index none swap sw 0 0 \
526                                 >> $BSDINSTALL_TMPETC/fstab || return $FAILURE
527                 fi
528                 ;;
529
530         MBR)
531                 #
532                 # 1. Create MBR layout (no labels)
533                 #
534                 gpart create -s mbr $disk || return $FAILURE
535                 gpart bootcode -b /boot/boot0 $disk || return $FAILURE
536
537                 #
538                 # 2. Add freebsd slice with all available space
539                 #
540                 gpart add -t freebsd $disk || return $FAILURE
541                 gpart set -a active -i 1 $disk || return $FAILURE
542                 f_quietly zpool labelclear -f /dev/${disk}s1 # Pedantic
543                 f_quietly gpart destroy -F ${disk}s1 # Pedantic
544
545                 #
546                 # 3. Write BSD sceme to the freebsd slice
547                 #
548                 gpart create -s BSD ${disk}s1 || return $FAILURE
549
550                 # zpool will use s1a (no labels)
551                 bootpart=s1a targetpart=s1a mbrindex=1
552
553                 # Change things around if we are using GELI
554                 if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
555                         bootpart=s1a targetpart=s1d
556                         partsize=$(( $partsize - $gelisize ))
557                         mbrindex=4 # If this is s1a then make the zpool s1d
558                         gpart add -t freebsd-zfs -i 1 -s ${gelisize}b \
559                                 ${disk}s1 || return $FAILURE
560                         # Pedantically nuke any old labels, stop geli
561                         f_quietly zpool labelclear -f /dev/$disk$bootpart
562                         f_quietly geli detach -f /dev/$disk$targetpart
563                 fi
564
565                 #
566                 # 4. Partition the BSD slice for ZFS
567                 # NOTE: Using above calculated partsize to leave room for swap.
568                 #
569                 [ $swapsize -gt 0 ] && setsize="-s ${partsize}b"
570                 gpart add -t freebsd-zfs -i $mbrindex $setsize ${disk}s1 || 
571                         return $FAILURE
572                 f_quietly zpool labelclear -f /dev/$disk$targetpart # Pedantic
573
574                 #
575                 # 5. Add freebsd-swap partition
576                 #
577                 if [ $swapsize -gt 0 ]; then
578                         gpart add -t freebsd-swap -i 2 ${disk}s1 || 
579                                 return $FAILURE
580                         # Update fstab(5)
581                         printf "$fstab_fmt" /dev/${disk}s1b none swap sw 0 0 \
582                                 >> $BSDINSTALL_TMPETC/fstab || return $FAILURE
583                 fi
584                 ;;
585
586         *)
587                 printf "%s: %s is an unsupported partition scheme" \
588                        "$funcname" "$ZFSBOOT_PARTITION_SCHEME" >&2
589                 return $FAILURE
590
591         esac # $ZFSBOOT_PARTITION_SCHEME
592
593         return $SUCCESS
594 }
595
596 # zfs_create_boot $poolname $vdev_type $real_disks ...
597 #
598 # Creates boot pool and dataset layout. Returns error if something goes wrong.
599 # Errors are printed to stderr for collection and display.
600 #
601 zfs_create_boot()
602 {
603         local poolname="$1" vdev_type="$2"
604         local fstab_fmt="%s\t\t%s\t%s\t%s\t\t%s\t%s\n"
605         local funcname=zfs_create_boot
606         local bootpart targetpart
607
608         shift 2 # name vdev_type
609
610         # We may need this later
611         local realdisks=$*
612
613         # Pedantic checks; should never be seen
614         if [ ! "$poolname" ]; then
615                 echo "$funcname: NULL poolname" >&2
616                 return $FAILURE
617         fi
618         if [ $# -lt 1 ]; then
619                 echo "$funcname: missing disk arguments" >&2
620                 return $FAILURE
621         fi
622
623         # Initialize fstab(5)
624         printf "$fstab_fmt" \
625                 "# Device" Mountpoint FStype Options Dump "Pass#" \
626                 >> $BSDINSTALL_TMPETC/fstab || return $FAILURE
627
628         # Expand SI units in desired sizes
629         local swapsize gelisize
630         f_expand_number "$ZFSBOOT_SWAP_SIZE" swapsize || return $FAILURE
631         f_expand_number "$ZFSBOOT_GELI_BOOT_SIZE" gelisize || return $FAILURE
632
633         # Prepare the disks
634         local n=0
635         for disk in $*; do
636                 zfs_create_diskpart $disk $n || return $FAILURE
637                 n=$(( $n + 1 ))
638         done
639
640         # MBR boot loader hack part 1
641         # We have to do this early because geli gets in the way later
642         if [ "$ZFSBOOT_PARTITION_SCHEME" = "MBR" ]; then
643                 for disk in $realdisks; do
644                         dd if=/boot/zfsboot of=/dev/${disk}s1 count=1 ||
645                                 return $FAILURE
646                 done
647         fi
648
649         # Forced 4k alignment support provided by Geom NOP (see gnop(8))
650         local unenc_list=
651         if [ "$ZFSBOOT_GNOP_4K_FORCE_ALIGN" ]; then
652                 local new_list=
653                 for disk in $*; do
654                         if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
655                                 # We don't gnop the encrypted partition 
656                                 # because geli will do this for us
657                                 # gnop the unencrypted disk
658                                 gnop create -S 4096 $disk$bootpart ||
659                                         return $FAILURE
660                                 unenc_list="$unenc_list $disk$bootpart.nop"
661                         else
662                                 gnop create -S 4096 $disk$targetpart ||
663                                         return $FAILURE
664                                 new_list="$new_list $disk$targetpart.nop"
665                         fi
666                 done
667                 set -- $new_list
668         else
669                 local new_list=
670                 for disk in $*; do
671                         new_list="$new_list $disk$targetpart"
672                         [ "$ZFSBOOT_GELI_ENCRYPTION" ] &&
673                                 unenc_list="$unenc_list $disk$bootpart"
674                 done
675                 set -- $new_list
676         fi
677
678         #
679         # If encryption is enabled, we need to create the GEOMs
680         #
681         if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
682                 local bootvdev=
683                 local geli_pool="$BSDINSTALL_CHROOT/$ZFSBOOT_GELI_POOL_NAME"
684                 local key="$ZFSBOOT_GELI_KEY_FILE"
685
686                 # Create the parent directories for our unencrypted pool
687                 f_quietly umount /mnt
688                 mount -t tmpfs none $BSDINSTALL_CHROOT || return $FAILURE
689
690                 # Create mirror across the unencrypted partition on all disks
691                 [ $( set -- $unenc_list; echo $# ) -gt 1 ] && bootvdev=mirror
692
693                 zpool create -o altroot=$BSDINSTALL_CHROOT \
694                         -m "/$ZFSBOOT_GELI_POOL_NAME" -f \
695                         "$ZFSBOOT_GELI_POOL_NAME" $bootvdev $unenc_list || 
696                         return $FAILURE
697                 mkdir -p $geli_pool/boot || return $FAILURE
698
699                 # Generate an encryption key using random(4)
700                 dd if=/dev/random of="$geli_pool/$key" bs=4096 count=1 ||
701                         return $FAILURE
702
703                 # Create the geli(8) GEOMS
704                 local geli_list
705                 msg_enter_new_password="$msg_geli_password" \
706                         f_dialog_input_password || return $FAILURE
707                 f_dialog_info "$msg_geli_setup" \
708                         2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
709                 for disk in $realdisks; do
710                         echo "$pw_password" | geli init -b -B \
711                                 "$geli_pool/boot/$disk$targetpart.eli" \
712                                 -e AES-XTS -J - -K "$geli_pool/$key" -l 256 \
713                                 -s 4096 $disk$targetpart || return $FAILURE
714                         echo "$pw_password" | geli attach -j - \
715                                 -k "$geli_pool/$key" $disk$targetpart || 
716                                 return $FAILURE
717                         geli_list="$geli_list $disk$targetpart.eli"
718                 done
719                 set -- $geli_list
720                 zfs unmount "$ZFSBOOT_GELI_POOL_NAME" || return $FAILURE
721                 f_quietly umount /mnt # done with tmpfs
722         fi
723
724         #
725         # Create the ZFS pool with desired type and disk devices
726         #
727         zpool create -o altroot=$BSDINSTALL_CHROOT -m none -f \
728                 "$poolname" $vdev_type $* || return $FAILURE
729
730         # Customize the zpool a bit...
731         zfs set checksum=fletcher4      "$poolname" || return $FAILURE
732         zfs set atime=off               "$poolname" || return $FAILURE
733
734         #
735         # Create ZFS dataset layout within the new boot pool
736         #
737         echo "$ZFSBOOT_DATASETS" | while read dataset options; do
738                 # Skip blank lines and comments
739                 case "$dataset" in "#"*|"") continue; esac
740                 # Remove potential inline comments in options
741                 options="${options%%#*}"
742                 # Replace tabs with spaces
743                 f_replaceall "$options" "       " " " options
744                 # Reduce contiguous runs of space to one single space
745                 oldoptions=
746                 while [ "$oldoptions" != "$options" ]; do
747                         oldoptions="$options"
748                         f_replaceall "$options" "  " " " options
749                 done
750                 # Replace both commas and spaces with ` -o '
751                 f_replaceall "$options" "[ ,]" " -o " options
752                 # Create the dataset with desired options
753                 zfs create ${options:+-o $options} "$poolname$dataset" ||
754                         return $FAILURE
755         done
756
757         # Touch up permissions on the tmp directories
758         chmod 1777 $BSDINSTALL_CHROOT/tmp || return $FAILURE
759         chmod 1777 $BSDINSTALL_CHROOT/var/tmp || return $FAILURE
760
761         # Create symlink(s)
762         [ "$ZFSBOOT_GELI_ENCRYPTION" ] &&
763                 { ln -s $ZFSBOOT_GELI_POOL_NAME/boot $BSDINSTALL_CHROOT/boot ||
764                         return $FAILURE; }
765
766         # Set bootfs property
767         zpool set bootfs="$poolname/$ZFSBOOT_BEROOT_NAME/$ZFSBOOT_BOOTFS_NAME" \
768                 "$poolname" || return $FAILURE
769
770         # Export the pool(s)
771         zpool export "$poolname" || return $FAILURE
772         [ "$ZFSBOOT_GELI_ENCRYPTION" ] &&
773                 { zpool export "$ZFSBOOT_GELI_POOL_NAME" || return $FAILURE; }
774
775         # Destroy the gnop devices (if enabled)
776         for disk in ${ZFSBOOT_GNOP_4K_FORCE_ALIGN:+$realdisks}; do
777                 if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
778                         f_quietly gnop destroy $disk$bootpart.nop
779                 else
780                         f_quietly gnop destroy $disk$targetpart.nop
781                 fi
782         done
783
784         # MBR boot loader hack part 2
785         if [ "$ZFSBOOT_PARTITION_SCHEME" = "MBR" ]; then
786                 # Stick the ZFS boot loader in the "convienient hole" after 
787                 # the ZFS internal metadata
788                 for disk in $realdisks; do
789                         dd if=/boot/zfsboot of=/dev/$disk$bootpart \
790                                 skip=1 seek=1024 || return $FAILURE
791                 done
792         fi
793
794         # Re-import the ZFS pool(s)
795         zpool import -o altroot=$BSDINSTALL_CHROOT $poolname || return $FAILURE
796         [ "$ZFSBOOT_GELI_ENCRYPTION" ] &&
797                 { zpool import -o altroot=$BSDINSTALL_CHROOT \
798                         "$ZFSBOOT_GELI_POOL_NAME" || return $FAILURE; }
799
800         # While this is apparently not needed, it seems to help MBR
801         mkdir -p $BSDINSTALL_CHROOT/boot/zfs || return $FAILURE
802         zpool set cachefile=$BSDINSTALL_CHROOT/boot/zfs/zpool.cache \
803                 "$poolname" || return $FAILURE
804
805         # Last, but not least... required lines for rc.conf(5)/loader.conf(5)
806         # NOTE: We later concatenate these into their destination
807         echo 'zfs_enable="YES"' > $BSDINSTALL_TMPETC/rc.conf.zfs ||
808                 return $FAILURE
809         echo 'zfs_load="YES"' > $BSDINSTALL_TMPBOOT/loader.conf.zfs ||
810                 return $FAILURE
811
812         # We're all done unless we should go on to do encryption
813         [ "$ZFSBOOT_GELI_ENCRYPTION" ] || return $SUCCESS
814
815         # Some additional GELI requirements for loader.conf(5)
816         echo 'zpool_cache_load="YES"' \
817                 >> $BSDINSTALL_TMPBOOT/loader.conf.zfs || return $FAILURE
818         echo 'zpool_cache_type="/boot/zfs/zpool.cache"' \
819                 >> $BSDINSTALL_TMPBOOT/loader.conf.zfs || return $FAILURE
820         echo 'zpool_cache_name="/boot/zfs/zpool.cache"' \
821                 >> $BSDINSTALL_TMPBOOT/loader.conf.zfs || return $FAILURE
822
823         #
824         # Configure geli(8)-based encryption
825         #
826         echo 'aesni_load="YES"' \
827                 > $BSDINSTALL_TMPBOOT/loader.conf.aesni || return $FAILURE
828         echo 'geom_eli_load="YES"' \
829                 > $BSDINSTALL_TMPBOOT/loader.conf.geli || return $FAILURE
830         printf 'vfs.root.mountfrom="zfs:%s/%s/%s"\n' "$poolname" \
831                 "$ZFSBOOT_BEROOT_NAME" "$ZFSBOOT_BOOTFS_NAME" \
832                 > $BSDINSTALL_TMPBOOT/loader.conf.root || return $FAILURE
833         for disk in $realdisks; do
834                 printf 'geli_%s_keyfile0_load="YES"\n' \
835                         "$disk$targetpart" \
836                         > $BSDINSTALL_TMPBOOT/loader.conf.$disk$targetpart ||
837                         return $FAILURE
838                 printf 'geli_%s_keyfile0_type="%s:geli_keyfile0"\n' \
839                         "$disk$targetpart" "$disk$targetpart" \
840                         >> $BSDINSTALL_TMPBOOT/loader.conf.$disk$targetpart ||
841                         return $FAILURE
842                 printf 'geli_%s_keyfile0_name="%s"\n' \
843                         "$disk$targetpart" "$ZFSBOOT_GELI_KEY_FILE" \
844                         >> $BSDINSTALL_TMPBOOT/loader.conf.$disk$targetpart ||
845                         return $FAILURE
846         done
847
848         return $SUCCESS
849 }
850
851 # dialog_menu_diskinfo
852 #
853 # Prompt the user to select a disk and then provide detailed info on it.
854 #
855 dialog_menu_diskinfo()
856 {
857         local disk
858
859         #
860         # Break from loop when user cancels disk selection
861         #
862         while :; do
863                 disk=$( msg_cancel="$msg_back" f_device_menu \
864                         "$DIALOG_TITLE" "$msg_select_a_disk_device" "" \
865                         $DEVICE_TYPE_DISK 2>&1 ) || break
866
867                 # Show gpart(8) `show' and camcontrol(8) `inquiry' data
868                 f_show_msg "$msg_detailed_disk_info" \
869                         "$disk" "$( gpart show $disk 2> /dev/null )" \
870                         "$disk" "$( camcontrol inquiry $disk 2> /dev/null )" \
871                         "$disk" "$( camcontrol identify $disk 2> /dev/null )"
872         done
873
874         return $SUCCESS
875 }
876
877 ############################################################ MAIN
878
879 #
880 # Initialize
881 #
882 f_dialog_title "$msg_zfs_configuration"
883 f_dialog_backtitle "$msg_freebsd_installer"
884
885 # User may have specifically requested ZFS-related operations be interactive
886 ! f_interactive && f_zfsinteractive && unset $VAR_NONINTERACTIVE
887
888 #
889 # Loop over the main menu until we've accomplished what we came here to do
890 #
891 while :; do
892         if ! f_interactive; then
893                 retval=$DIALOG_OK
894                 mtag=">>> $msg_create"
895         else
896                 dialog_menu_main
897                 retval=$?
898                 f_dialog_menutag_fetch mtag
899         fi
900
901         f_dprintf "retval=%u mtag=[%s]" $reval "$mtag"
902         [ $retval -eq $DIALOG_OK ] || f_die
903
904         case "$mtag" in
905         ">>> $msg_create")
906                 #
907                 # First, validate the user's selections
908                 #
909
910                 # Make sure they gave us a name for the pool
911                 if [ ! "$ZFSBOOT_POOL_NAME" ]; then
912                         f_show_msg "$msg_pool_name_cannot_be_empty"
913                         f_interactive || f_die
914                         continue
915                 fi
916                 # Make sure [potentially scripted] selections are real
917                 real_disks=
918                 for disk in $ZFSBOOT_DISKS; do
919                         f_struct device_$disk && real_disks="$real_disks $disk"
920                 done
921                 # Make sure we have at least one real disk selected
922                 ndisks=$( set -- $real_disks; echo $# )
923                 if [ $ndisks -lt 1 ]; then
924                         f_show_msg "$msg_no_disks_selected"
925                         f_interactive || f_die
926                         continue
927                 fi
928                 # Make sure we have enough disks for the desired vdev type
929                 case "$ZFSBOOT_VDEV_TYPE" in
930                 stripe) want_disks=1 ;;
931                 mirror) want_disks=2 ;;
932                 raidz1) want_disks=3 ;;
933                 raidz2) want_disks=4 ;;
934                 raidz3) want_disks=5 ;;
935                 *)
936                         f_show_msg "$msg_invalid_virtual_device_type" \
937                                    "$ZFSBOOT_VDEV_TYPE"
938                         f_interactive || f_die
939                         continue
940                 esac
941                 if [ $ndisks -lt $want_disks ]; then
942                         f_show_msg "%s: $msg_not_enough_disks_selected" \
943                                    "$ZFSBOOT_VDEV_TYPE" "$want_disks"
944                         f_interactive || f_die
945                         continue
946                 fi
947                 # Make sure each disk will be at least 50% ZFS
948                 if f_expand_number "$ZFSBOOT_SWAP_SIZE" swapsize &&
949                    f_expand_number "$ZFSBOOT_GELI_BOOT_SIZE" gelisize
950                 then
951                         minsize=$swapsize teeny_disks=
952                         [ "$ZFSBOOT_GELI_ENCRYPTION" ] &&
953                                 minsize=$(( $minsize + $gelisize ))
954                         for disk in $real_disks; do
955                                 device_$disk get capacity disksize || continue
956                                 disksize=$(( $disksize - $minsize ))
957                                 [ $disksize -lt $minsize ] &&
958                                         teeny_disks="$teeny_disks $disk"
959                         done
960                         if [ "$teeny_disks" ]; then
961                                 f_show_msg "$msg_these_disks_are_too_small" \
962                                            "$ZFSBOOT_SWAP_SIZE" \
963                                            "$ZFSBOOT_GELI_BOOT_SIZE" \
964                                            "$teeny_disks"
965                                 f_interactive || f_die
966                                 continue
967                         fi
968                 fi
969
970                 #
971                 # Last Chance!
972                 #
973                 if [ ! "$USE_XDIALOG" ]; then
974                         f_interactive && DIALOG="$DIALOG --colors" f_noyes \
975                                 "$msg_last_chance_are_you_sure_color" \
976                                 "$ZFSBOOT_DISKS" || continue
977                 else
978                         f_interactive && f_noyes \
979                                 "$msg_last_chance_are_you_sure" \
980                                 "$ZFSBOOT_DISKS" || continue
981                 fi
982
983                 #
984                 # Let's do this
985                 #
986
987                 vdev_type="$ZFSBOOT_VDEV_TYPE"
988
989                 # Blank the vdev type for the default layout
990                 [ "$vdev_type" = "stripe" ] && vdev_type=
991
992                 if ! error=$( zfs_create_boot "$ZFSBOOT_POOL_NAME" \
993                         "$vdev_type" $real_disks 2>&1 )
994                 then
995                         f_dialog_msgbox "$error"
996                         f_interactive || f_die
997                         continue
998                 fi
999
1000                 break # to success
1001                 ;;
1002         "- $msg_rescan_devices") f_device_rescan ;;
1003         "- $msg_disk_info") dialog_menu_diskinfo ;;
1004         ?" $msg_pool_name")
1005                 # Prompt the user to input/change the name for the new pool
1006                 f_dialog_input input \
1007                         "$msg_please_enter_a_name_for_your_zpool" \
1008                         "$ZFSBOOT_POOL_NAME" &&
1009                         ZFSBOOT_POOL_NAME="$input"
1010                 ;;
1011         ?" $msg_disks_to_use") dialog_edit_disks ;;
1012         ?" $msg_zfs_vdev_type") dialog_menu_vdev ;;
1013         ?" $msg_force_4k_sectors")
1014                 # Toggle the variable referenced both by the menu and later
1015                 if [ "$ZFSBOOT_GNOP_4K_FORCE_ALIGN" ]; then
1016                         ZFSBOOT_GNOP_4K_FORCE_ALIGN=
1017                 else
1018                         ZFSBOOT_GNOP_4K_FORCE_ALIGN=1
1019                 fi
1020                 ;;
1021         ?" $msg_geli_encryption")
1022                 # Toggle the variable referenced both by the menu and later
1023                 if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
1024                         ZFSBOOT_GELI_ENCRYPTION=
1025                 else
1026                         ZFSBOOT_GELI_ENCRYPTION=1
1027                 fi
1028                 ;;
1029         ?" $msg_partition_scheme")
1030                 # Toggle between GPT and MBR
1031                 if [ "$ZFSBOOT_PARTITION_SCHEME" = GPT ]; then
1032                         ZFSBOOT_PARTITION_SCHEME=MBR
1033                 else
1034                         ZFSBOOT_PARTITION_SCHEME=GPT
1035                 fi
1036                 ;;
1037         ?" $msg_swap_size")
1038                 # Prompt the user to input/change the swap size for each disk
1039                 f_dialog_input input \
1040                         "$msg_please_enter_amount_of_swap_space" \
1041                         "$ZFSBOOT_SWAP_SIZE" &&
1042                         ZFSBOOT_SWAP_SIZE="$input"
1043                 ;;
1044         esac
1045 done
1046
1047 return $SUCCESS
1048
1049 ################################################################################
1050 # END
1051 ################################################################################