Skip to content

Commit 891e44a

Browse files
committed
Allow snapshot creation / move logic to function
Some times it's helpful to be able to create a snapshot from the bootloader, outside of the OS. On the snapshot screen, there's now a MOD+N option to create a (N)ew snapshot. It rides along the coat-tails of most of the snapshot cloning/duplication logic, as that has checks for everything we also care about. The MOD+S handler in zfsbootmenu.sh has grown quite large, with the creation of quite a few global variables. The bulk of this is now hidden inside snapshot_dispatcher(), which can use local variables for all of the space calculations.
1 parent d208103 commit 891e44a

File tree

3 files changed

+176
-73
lines changed

3 files changed

+176
-73
lines changed

90zfsbootmenu/zfsbootmenu-lib.sh

Lines changed: 165 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -571,13 +571,12 @@ draw_snapshots() {
571571

572572
sort_key="$( get_sort_key )"
573573

574-
header="$( header_wrap "[RETURN] duplicate" "[ESCAPE] back" "" \
575-
"[CTRL+X] clone and promote" "[CTRL+C] clone only" "" \
576-
"[CTRL+J] jump into chroot" "[CTRL+D] show diff" "" \
577-
"[CTRL+L] view logs" "[CTRL+H] help" )"
574+
header="$( header_wrap "[RETURN] duplicate" "[CTRL+C] clone only" "[CTRL+X] clone and promote" "" \
575+
"[CTRL+N] create new snapshot" "[CTRL+J] jump into chroot" "[CTRL+D] show diff" "" \
576+
"[CTRL+L] view logs" "[CTRL+H] help" "[ESCAPE] back" )"
578577
context="Note: for diff viewer, use tab to select/deselect up to two items"
579578

580-
expects="--expect=alt-x,alt-c,alt-j,alt-o"
579+
expects="--expect=alt-x,alt-c,alt-j,alt-o,alt-n"
581580

582581
if ! selected="$( zfs list -t snapshot -H -o name "${benv}" -S "${sort_key}" |
583582
HELP_SECTION=snapshot-management ${FUZZYSEL} \
@@ -901,6 +900,167 @@ clone_snapshot() {
901900
return 0
902901
}
903902

903+
# arg1: filesystem name
904+
# arg2: snapshot name
905+
# prints: nothing
906+
# returns: 0 on success
907+
908+
create_snapshot() {
909+
local selected target pool
910+
911+
selected="${1}"
912+
if [ -z "$selected" ]; then
913+
zerror "selected is undefined"
914+
return 1
915+
fi
916+
zdebug "selected: ${selected}"
917+
918+
target="${2}"
919+
if [ -z "$target" ]; then
920+
zerror "target is undefined"
921+
return 1
922+
fi
923+
zdebug "target: ${target}"
924+
925+
pool="${selected%%/*}"
926+
if ! set_rw_pool "${pool}" ; then
927+
zerror "unable to set pool ${pool} read/write"
928+
return 1
929+
fi
930+
931+
load_key "${selected}"
932+
933+
zdebug "creating snapshot ${selected}@${target}"
934+
if ! output="$( zfs snapshot "${selected}@${target}" )" ; then
935+
zdebug "unable to create snapshot: ${output}"
936+
return 1
937+
fi
938+
939+
return 0
940+
}
941+
942+
# arg1: selected snapshot
943+
# arg2: subkey
944+
# prints: snapshot/filesystem creation prompt
945+
# returns: nothing
946+
947+
snapshot_dispatcher() {
948+
local selected subkey
949+
local parent_ds avail_space_exact be_size_exact leftover_space avail_space be_size
950+
local prompt header check_base pre_populated user_input valid_name clone_target
951+
952+
selected="${1}"
953+
if [ -z "$selected" ]; then
954+
zerror "selected is undefined"
955+
return 1
956+
fi
957+
zdebug "selected: ${selected}"
958+
959+
subkey="${2}"
960+
if [ -z "$subkey" ]; then
961+
zerror "subkey is undefined"
962+
return 1
963+
fi
964+
zdebug "subkey: ${subkey}"
965+
966+
parent_ds="${selected%/*}"
967+
parent_ds="${selected%@*}"
968+
969+
if [ -z "${parent_ds}" ]; then
970+
zerror "unable to determine parent dataset for ${selected}"
971+
return 1
972+
fi
973+
zdebug "parent_ds: ${parent_ds}"
974+
975+
# Do space calculations; bail early
976+
case "${subkey}" in
977+
"enter")
978+
avail_space_exact="$( zfs list -p -H -o available "${parent_ds}" )"
979+
be_size_exact="$( zfs list -p -H -o refer "${selected}" )"
980+
leftover_space=$(( avail_space_exact - be_size_exact ))
981+
if [ "${leftover_space}" -le 0 ]; then
982+
avail_space="$( zfs list -H -o available "${parent_ds}" )"
983+
be_size="$( zfs list -H -o refer "${selected}" )"
984+
zerror "Insufficient space for duplication, ${parent_ds}' has ${avail_space} free but needs ${be_size}"
985+
color=red delay=10 timed_prompt "Insufficient space for duplication" \
986+
"'${parent_ds}' has ${avail_space} free but needs ${be_size}"
987+
return 1
988+
fi
989+
;;
990+
esac
991+
992+
# Set prompt, header, existing check prefix
993+
case "${subkey}" in
994+
"enter"|"mod-x"|"mod-c")
995+
prompt="\nNew boot environment name (CTRL-C or leave blank to abort)"
996+
header="$( center_string "${selected}" )"
997+
check_base="${parent_ds}/"
998+
999+
pre_populated="${selected##*/}"
1000+
pre_populated="${pre_populated%%@*}_NEW"
1001+
;;
1002+
"mod-n")
1003+
prompt="\nNew snapshot name (CTRL-C or leave blank to abort)"
1004+
header="$( center_string "${selected%%@*}" )"
1005+
check_base="${selected%%@*}@"
1006+
1007+
pre_populated="$( printf "%(%Y-%m-%d-%H%M%S)T" )"
1008+
;;
1009+
esac
1010+
1011+
tput clear
1012+
tput cnorm
1013+
colorize green "${header}"
1014+
1015+
while true; do
1016+
echo -e "${prompt}"
1017+
user_input="$( /libexec/zfsbootmenu-input "${pre_populated}" )"
1018+
1019+
[ -n "${user_input}" ] || return
1020+
1021+
valid_name=$( echo "${user_input}" | tr -c -d 'a-zA-Z0-9-_.:' )
1022+
if [[ "${user_input}" != "${valid_name}" ]]; then
1023+
echo "${user_input} is invalid, ${valid_name} can be used"
1024+
pre_populated="${valid_name}"
1025+
elif zfs list -H -o name "${check_base}${user_input}" >/dev/null 2>&1; then
1026+
echo "${check_base}${user_input} already exists, please use another name"
1027+
pre_populated="${user_input}"
1028+
else
1029+
break
1030+
fi
1031+
done
1032+
1033+
[ -n "${user_input}" ] || return
1034+
1035+
# Print what we're doing for anything but snapshot creation
1036+
case "${subkey}" in
1037+
"enter"|"mod-x"|"mod-c")
1038+
clone_target="${parent_ds}/${user_input}"
1039+
be_size="$( zfs list -H -o refer "${selected}" )"
1040+
echo -e "\nCreating ${clone_target} from ${selected} (${be_size})"
1041+
;;
1042+
esac
1043+
1044+
# Finally, dispatch to one of the snapshot handler functions
1045+
case "${subkey}" in
1046+
"enter")
1047+
duplicate_snapshot "${selected}" "${clone_target}"
1048+
;;
1049+
"mod-x")
1050+
PROMOTE=1 clone_snapshot "${selected}" "${clone_target}"
1051+
;;
1052+
"mod-c")
1053+
clone_snapshot "${selected}" "${clone_target}"
1054+
;;
1055+
"mod-n")
1056+
create_snapshot "${selected%%@*}" "${user_input}"
1057+
# shellcheck disable=SC2034
1058+
BE_SELECTED=1
1059+
;;
1060+
esac
1061+
}
1062+
1063+
9041064
# arg1: ZFS filesystem
9051065
# arg2: default kernel path (omit to unset default)
9061066
# prints: nothing

90zfsbootmenu/zfsbootmenu.sh

Lines changed: 5 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,6 @@ while true; do
225225
selected_snap="${selected_snap%,*}"
226226
zdebug "selected snapshot: ${selected_snap}"
227227

228-
# Parent of the selected dataset, must be nonempty
229-
parent_ds="${selected_snap%/*}"
230-
[ -n "$parent_ds" ] || continue
231-
232-
tput clear
233-
tput cnorm
234-
235228
case "${subkey}" in
236229
"mod-j")
237230
zfs_chroot "${selected_snap}"
@@ -243,67 +236,11 @@ while true; do
243236
BE_SELECTED=1
244237
continue
245238
;;
246-
# Check available space early in the process
247-
"enter")
248-
avail_space_exact="$( zfs list -p -H -o available "${parent_ds}" )"
249-
be_size_exact="$( zfs list -p -H -o refer "${selected_snap}" )"
250-
leftover_space=$(( avail_space_exact - be_size_exact ))
251-
if [ "${leftover_space}" -le 0 ]; then
252-
avail_space="$( zfs list -H -o available "${parent_ds}" )"
253-
be_size="$( zfs list -H -o refer "${selected_snap}" )"
254-
zerror "Insufficient space for duplication, ${parent_ds}' has ${avail_space} free but needs ${be_size}"
255-
color=red delay=10 timed_prompt "Insufficient space for duplication" \
256-
"'${parent_ds}' has ${avail_space} free but needs ${be_size}"
257-
continue
258-
fi
239+
*)
240+
snapshot_dispatcher "${selected_snap}" "${subkey}"
241+
continue
259242
;;
260243
esac
261-
262-
# Strip parent datasets
263-
pre_populated="${selected_snap##*/}"
264-
# Strip snapshot name and append NEW
265-
pre_populated="${pre_populated%%@*}_NEW"
266-
267-
header="$( center_string "${selected_snap}" )"
268-
colorize green "${header}"
269-
270-
while true; do
271-
echo -e "\nNew boot environment name (leave blank to abort)"
272-
new_be="$( /libexec/zfsbootmenu-input "${pre_populated}" )"
273-
274-
[ -n "${new_be}" ] || break
275-
276-
valid_name=$( echo "${new_be}" | tr -c -d 'a-zA-Z0-9-_.:' )
277-
# If the entered name is invalid, set the prompt to the valid form of the name
278-
if [[ "${new_be}" != "${valid_name}" ]]; then
279-
echo "${new_be} is invalid, ${valid_name} can be used"
280-
pre_populated="${valid_name}"
281-
elif zfs list -H -o name "${parent_ds}/${new_be}" >/dev/null 2>&1; then
282-
echo "${new_be} already exists, please use another name"
283-
pre_populated="${new_be}"
284-
else
285-
break
286-
fi
287-
done
288-
289-
# Must have a nonempty name for the new BE
290-
[ -n "${new_be}" ] || continue
291-
292-
clone_target="${parent_ds}/${new_be}"
293-
be_size="$( zfs list -H -o refer "${selected_snap}" )"
294-
echo -e "\nCreating ${clone_target} from ${selected_snap} (${be_size})"
295-
296-
case "${subkey}" in
297-
"enter")
298-
duplicate_snapshot "${selected_snap}" "${clone_target}"
299-
;;
300-
"mod-x")
301-
PROMOTE=1 clone_snapshot "${selected_snap}" "${clone_target}"
302-
;;
303-
"mod-c")
304-
clone_snapshot "${selected_snap}" "${clone_target}"
305-
;;
306-
esac
307244
;;
308245
"mod-r")
309246
tput cnorm
@@ -346,9 +283,9 @@ while true; do
346283
;;
347284
"mod-j")
348285
zfs_chroot "${selected_be}"
349-
;;
286+
;;
350287
"mod-o")
351288
change_sort
352-
;;
289+
;;
353290
esac
354291
done

pod/online/snapshot-management.pod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ This creates a boot environment from a snapshot with out modifying snapshot inhe
3737

3838
The operation will fail gracefully if the pool can not be set I<read/write>.
3939

40+
=item I<[MOD+N]> B<snapshot creation>
41+
42+
This creates a new snapshot of the currently selected boot environment. A new snapshot is useful if you need to repair a boot environment from a chroot, to allow for easy roll-back of the changes.
43+
44+
The operation will fail gracefully if the pool can not be set I<read/write>.
45+
4046
=item I<[MOD+D]> B<diff>
4147

4248
Compare the differences between snapshots and filesystems. A single snapshot can be selected and a diff will be generated between that and the current state of the filesystem. Two snapshots can be selected (and deselected) with the tab key and a diff will be generated between them.

0 commit comments

Comments
 (0)