-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathzfsUnlocker.hook
197 lines (164 loc) · 5.86 KB
/
zfsUnlocker.hook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#!/bin/bash
#
# Goal: Find and unlock datasets, particularly the root.
#
zfs_get_bootfs () {
for zfs_dataset in $(zpool list -H -o bootfs); do
case ${zfs_dataset} in
"" | "-")
# skip if unset
;;
"no pools available")
return 1
;;
*)
ZFS_DATASET=${zfs_dataset}
return 0
;;
esac
done
return 1
}
zfs_decrypt_fs() {
export dataset=$1
# check if 'zfs load-key' is available
zfs 2>&1 | grep load-key > /dev/null || return 0
# check if dataset is encrypted
[ "$(zfs get -H -o value encryption "${dataset}")" != "off" ] || return 0
# check if key is already loaded
[ "$(zfs get -H -o value keystatus "${dataset}")" != "available" ] || return 0
# get the encryption root
encryptionroot=$(zfs get -H -o value encryptionroot "${dataset}")
echo $encryptionroot > /tmp/encryptionroot # For any other modules to have a crack at it
echo -e "\tTrying modules..."
# Try zfsUnlocker's modules.d hooks in order
moduleBasepath="/etc/zfsUnlocker/modules.d"
moduleList="$(ls -1 ${moduleBasepath} | grep -v disabled)"
for module in $moduleList
do
[ "$(zfs get -H -o value keystatus "${encryptionroot}")" = "available" ] && break
echo "" # spacer
echo -e "\t[${module}]"
if [ -f ${moduleBasepath}/${module}/hook ]
then
${moduleBasepath}/${module}/hook hook
fi
done
# When out of options, loop indefinitely on a password prompt.
[ "$(zfs get -H -o value keystatus "${encryptionroot}")" != "available" ] && echo "Out of options, prompting indefinitely."
while [ "$(zfs get -H -o value keystatus "${encryptionroot}")" != "available" ]
do
read -t 30 -s -p "Enter unlock passphrase: " userinput
if [ -n "$userinput" ]
then
yes "${userinput}" | zfs load-key -a
unset userinput
sleep 2
fi
done
# If we make it to a mountable root, run the cleanup functions of our hook modules before we get there.
for module in $moduleList
do
if [ -f ${moduleBasepath}/${module}/hook ]
then
${moduleBasepath}/${module}/hook cleanup
fi
done
}
zfs_mount_handler () {
if [ "${ZFS_DATASET}" = "bootfs" ] ; then
if ! zfs_get_bootfs ; then
# Lets import everything and try again
zpool import ${ZPOOL_IMPORT_FLAGS} -N -a ${ZPOOL_FORCE}
if ! zfs_get_bootfs ; then
err "zfsUnlocker: Cannot find a dataset marked as bootfs."
exit 1
fi
fi
fi
local pool="${ZFS_DATASET%%/*}"
local rwopt_exp="${rwopt:-ro}"
if ! zpool list -H "${pool}" 2>1 > /dev/null ; then
if [ ! "${rwopt_exp}" = "rw" ]; then
msg "ZFS: Importing pool ${pool} readonly."
ZPOOL_IMPORT_FLAGS="${ZPOOL_IMPORT_FLAGS} -o readonly=on"
else
msg "ZFS: Importing pool ${pool}."
fi
if ! zpool import ${ZPOOL_IMPORT_FLAGS} -N "${pool}" ${ZPOOL_FORCE} ; then
err "ZFS: Unable to import pool ${pool}."
exit 1
fi
fi
local newrootDir="$1" # Work off desired mountdir as advised by /init to $mount_handler function.
local rootmnt=$(zfs get -H -o value mountpoint "${ZFS_DATASET}")
local zfs_datasets="$(zfs list -H -o name -t filesystem -r ${ZFS_DATASET})"
# Handle unlocking the root and any child datasets, but only mount the root now.
for dataset in ${zfs_datasets}; do
mountpoint=$(zfs get -H -o value mountpoint "${dataset}")
canmount=$(zfs get -H -o value canmount "${dataset}")
# Skip datasets where canmount=off and mountpoint=none or unset.
[ ${canmount} = "off" -o ${mountpoint} = "none" -o ${mountpoint} = "-" ] && continue
# Check for encryption on the dataset and fire the modules if so.
zfs_decrypt_fs "${dataset}"
# At this point stop processing datasets we don't intend to mount as the rootfs.
if [ ${mountpoint} = "legacy" ]
then
mountOpts="${opt}"
else
mountOpts="zfsutil,${rwopt_exp}"
fi
if [ "${dataset}" == "${ZFS_DATASET}" ] # If this is the root to be
then
mount -t zfs -o "${mountOpts}" "${dataset}" "${newrootDir}"
elif [ ${mountpoint} = "legacy" ]
then
[ ${mountpoint} = "legacy" ] && echo "Leaving legacy mount for host fstab to handle: ${dataset}"
continue
else
echo "Mounting $dataset under rootfs"
mount -t zfs -o "${mountOpts}" "${dataset}" "${newrootDir}${mountpoint}"
fi
done
}
run_hook() {
echo "[zfsUnlocker]"
[ ! -h /dev/fd ] && ln -s /proc/self/fd /dev/fd >/dev/null 2>&1
until grep -q zfs <(lsmod)
do
echo -ne "Modprobing ZFS..."
modprobe zfs
echo -ne '.'
count=$(( $count + 1 ))
[ $count -eq 5 ] && echo -ne "\rStuck trying to modprobe zfs... being patient"
[ $count -ge 60 ] && { echo -e "\nCouldn't modprobe ZFS. Waited too long for this to be a slow host. Missing zfs module in initramfs?" ; break ;}
sleep 1
done
case ${root} in
# root=zfs
"zfs")
ZFS_DATASET="${root}"
mount_handler="zfs_mount_handler"
;;
# root=ZFS=xxx
"ZFS="*)
ZFS_DATASET="${root#*[=]}"
mount_handler="zfs_mount_handler"
;;
esac
case ${zfs} in
"")
# skip if empty/unset
;;
auto|bootfs)
ZFS_DATASET="bootfs"
mount_handler="zfs_mount_handler"
local pool="[a-zA-Z][^ ]*"
;;
*)
ZFS_DATASET="${zfs}"
mount_handler="zfs_mount_handler"
local pool="${ZFS_DATASET%%/*}"
;;
esac
}