Files
platform-core/scripts/lib/k3s-local-recovery.sh
T

2039 lines
92 KiB
Bash

#!/usr/bin/env bash
# Source-only helpers for the non-secret local recovery hardware contract.
# The contract is parsed as data; it must never be sourced as shell code.
if [[ "${K3SLR_KEEPASS_ENTRY+x}" == x ]]; then
[[ "$K3SLR_KEEPASS_ENTRY" == 'K3s Recovery LUKS' ]] || return 1
readonly K3SLR_KEEPASS_ENTRY
else
readonly K3SLR_KEEPASS_ENTRY='K3s Recovery LUKS'
fi
if [[ "${K3SLR_KEEPASS_HEADER_ATTACHMENT+x}" == x ]]; then
[[ "$K3SLR_KEEPASS_HEADER_ATTACHMENT" == 'luks-header-backup.bin' ]] || return 1
readonly K3SLR_KEEPASS_HEADER_ATTACHMENT
else
readonly K3SLR_KEEPASS_HEADER_ATTACHMENT='luks-header-backup.bin'
fi
if [[ "${_K3SLR_INT64_MAX+x}" == x ]]; then
[[ "$_K3SLR_INT64_MAX" == 9223372036854775807 ]] || return 1
readonly _K3SLR_INT64_MAX
else
readonly _K3SLR_INT64_MAX=9223372036854775807
fi
if [[ "${_K3SLR_REQUIRED_MINIMUM_FREE_BYTES+x}" == x ]]; then
[[ "$_K3SLR_REQUIRED_MINIMUM_FREE_BYTES" == 10737418240 ]] || return 1
readonly _K3SLR_REQUIRED_MINIMUM_FREE_BYTES
else
readonly _K3SLR_REQUIRED_MINIMUM_FREE_BYTES=10737418240
fi
if [[ "${_K3SLR_KEEPASSXC_VERSION+x}" == x ]]; then
[[ "$_K3SLR_KEEPASSXC_VERSION" == '2.7.6+dfsg.1-1build3' ]] || return 1
readonly _K3SLR_KEEPASSXC_VERSION
else
readonly _K3SLR_KEEPASSXC_VERSION='2.7.6+dfsg.1-1build3'
fi
if [[ "${_K3SLR_CRYPTSETUP_VERSION+x}" == x ]]; then
[[ "$_K3SLR_CRYPTSETUP_VERSION" == '2:2.7.0-1ubuntu4.2' ]] || return 1
readonly _K3SLR_CRYPTSETUP_VERSION
else
readonly _K3SLR_CRYPTSETUP_VERSION='2:2.7.0-1ubuntu4.2'
fi
_k3slr_uint() {
local value="${1-}"
[[ "$value" =~ ^(0|[1-9][0-9]*)$ ]] || return 1
(( ${#value} < 19 )) && return 0
(( ${#value} == 19 )) &&
[[ "$value" < '9223372036854775807' || "$value" == '9223372036854775807' ]] && return 0
return 1
}
_k3slr_contract_value_is_valid() {
local key="$1" value="$2"
case "$key" in
K3SLR_SCHEMA_VERSION)
[[ "$value" == 1 ]]
;;
K3SLR_RECOVERY_DISK_BY_ID|K3SLR_K3S_DISK_BY_ID)
[[ "$value" =~ ^/dev/disk/by-id/wwn-0x[0-9a-f]{16}$ ]]
;;
K3SLR_RECOVERY_PARTITION_BY_ID|K3SLR_K3S_PARTITION_BY_ID)
[[ "$value" =~ ^/dev/disk/by-id/wwn-0x[0-9a-f]{16}-part[1-9][0-9]*$ ]]
;;
K3SLR_RECOVERY_FS_UUID)
[[ "$value" =~ ^[0-9A-F]{16}$ ]]
;;
K3SLR_K3S_FS_UUID|K3SLR_RECOVERY_PARTUUID|K3SLR_K3S_PARTUUID)
[[ "$value" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]
;;
K3SLR_RECOVERY_MODEL|K3SLR_K3S_MODEL)
[[ "$value" =~ ^[A-Za-z0-9._-]+$ ]]
;;
K3SLR_RECOVERY_SERIAL|K3SLR_K3S_SERIAL)
[[ "$value" =~ ^[A-Za-z0-9]+$ ]]
;;
K3SLR_RECOVERY_WWN|K3SLR_K3S_WWN)
[[ "$value" =~ ^0x[0-9a-f]{16}$ ]]
;;
K3SLR_OWNER_UID|K3SLR_OWNER_GID)
_k3slr_uint "$value"
;;
K3SLR_OUTER_MOUNT|K3SLR_INNER_MOUNT)
[[ "$value" =~ ^/[A-Za-z0-9._/-]+$ && "$value" != *'//'* && "$value" != *'/./'* && "$value" != *'/../'* && "$value" != */. && "$value" != */.. && "$value" != */ ]]
;;
K3SLR_ROOT_RELATIVE|K3SLR_DATABASE_RELATIVE|K3SLR_CONTAINER_RELATIVE|K3SLR_RUNTIME_METADATA_RELATIVE)
[[ "$value" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]*$ && "$value" != *'//'* && "$value" != *'/./'* && "$value" != *'/../'* && "$value" != */. && "$value" != */.. && "$value" != */ ]]
;;
K3SLR_MAPPING_NAME|K3SLR_PROOF_MAPPING_NAME)
[[ "$value" =~ ^[a-z][a-z0-9-]*$ ]]
;;
K3SLR_INNER_LABEL)
[[ "$value" =~ ^[A-Z][A-Z0-9_]*$ ]]
;;
K3SLR_CONTAINER_SIZE_BYTES|K3SLR_MINIMUM_FREE_BYTES)
_k3slr_uint "$value" && [[ "$value" != 0 ]]
;;
K3SLR_OUTER_MIN_REMAINING_PERCENT)
_k3slr_uint "$value" && [[ "$value" != 0 ]] && (( 10#$value <= 99 ))
;;
*)
return 1
;;
esac
}
_k3slr_parse_contract() {
local file="$1" result_name="$2" line key value
local -n result="$result_name"
local -A allowed=(
[K3SLR_SCHEMA_VERSION]=1
[K3SLR_RECOVERY_DISK_BY_ID]=1 [K3SLR_RECOVERY_PARTITION_BY_ID]=1
[K3SLR_RECOVERY_FS_UUID]=1 [K3SLR_RECOVERY_PARTUUID]=1
[K3SLR_RECOVERY_MODEL]=1 [K3SLR_RECOVERY_SERIAL]=1 [K3SLR_RECOVERY_WWN]=1
[K3SLR_K3S_DISK_BY_ID]=1 [K3SLR_K3S_PARTITION_BY_ID]=1
[K3SLR_K3S_FS_UUID]=1 [K3SLR_K3S_PARTUUID]=1
[K3SLR_K3S_MODEL]=1 [K3SLR_K3S_SERIAL]=1 [K3SLR_K3S_WWN]=1
[K3SLR_OWNER_UID]=1 [K3SLR_OWNER_GID]=1
[K3SLR_OUTER_MOUNT]=1 [K3SLR_INNER_MOUNT]=1
[K3SLR_ROOT_RELATIVE]=1 [K3SLR_DATABASE_RELATIVE]=1
[K3SLR_CONTAINER_RELATIVE]=1 [K3SLR_RUNTIME_METADATA_RELATIVE]=1
[K3SLR_MAPPING_NAME]=1 [K3SLR_PROOF_MAPPING_NAME]=1 [K3SLR_INNER_LABEL]=1
[K3SLR_CONTAINER_SIZE_BYTES]=1 [K3SLR_MINIMUM_FREE_BYTES]=1
[K3SLR_OUTER_MIN_REMAINING_PERCENT]=1
)
local -A seen=()
[[ -f "$file" && ! -L "$file" && -r "$file" ]] || return 1
result=()
while IFS= read -r line || [[ -n "$line" ]]; do
[[ "$line" != *$'\r'* && "$line" =~ ^([A-Z][A-Z0-9_]*)=(.*)$ ]] || return 1
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
[[ "${allowed[$key]+present}" == present && "${seen[$key]+present}" != present ]] || return 1
_k3slr_contract_value_is_valid "$key" "$value" || return 1
seen["$key"]=1
result["$key"]="$value"
done <"$file"
for key in "${!allowed[@]}"; do
[[ "${seen[$key]+present}" == present ]] || return 1
done
}
_k3slr_config_value() {
local file="$1" key="$2"
local -A values=()
_k3slr_parse_contract "$file" values || return 1
[[ "${values[$key]+present}" == present ]] || return 1
printf '%s\n' "${values[$key]}"
}
_k3slr_load_contract() {
local file="$1" key
local -A values=()
_k3slr_parse_contract "$file" values || return 1
for key in "${!values[@]}"; do
printf -v "$key" '%s' "${values[$key]}"
done
}
_k3slr_required_bytes() {
local phase_bytes="$1" used_bytes="$2" padded maximum_used
_k3slr_uint "$phase_bytes" || return 1
_k3slr_uint "$used_bytes" || return 1
(( phase_bytes <= 1844674407370955160 )) || return 1
padded=$(((phase_bytes * 5 + 3) / 4))
(( padded <= (_K3SLR_INT64_MAX - _K3SLR_REQUIRED_MINIMUM_FREE_BYTES) / 2 )) || return 1
maximum_used=$((_K3SLR_INT64_MAX - 2 * padded - _K3SLR_REQUIRED_MINIMUM_FREE_BYTES))
(( used_bytes <= maximum_used )) || return 1
printf '%s\n' "$((used_bytes + 2 * padded + _K3SLR_REQUIRED_MINIMUM_FREE_BYTES))"
}
_k3slr_inner_capacity_fits() {
local total_bytes="$1" used_bytes="$2" phase_bytes="$3" required
_k3slr_uint "$total_bytes" || return 1
required="$(_k3slr_required_bytes "$phase_bytes" "$used_bytes")" || return 1
(( total_bytes >= required ))
}
_k3slr_outer_capacity_fits() {
local total_bytes="$1" free_bytes="$2" container_bytes="$3"
local minimum_free_bytes="${K3SLR_MINIMUM_FREE_BYTES:-}"
local remaining_percent="${K3SLR_OUTER_MIN_REMAINING_PERCENT:-}"
local percent_reserve required
_k3slr_uint "$total_bytes" || return 1
_k3slr_uint "$free_bytes" || return 1
_k3slr_uint "$container_bytes" || return 1
_k3slr_uint "$minimum_free_bytes" || return 1
_k3slr_uint "$remaining_percent" || return 1
[[ "$container_bytes" != 0 && "$minimum_free_bytes" != 0 && "$remaining_percent" != 0 ]] || return 1
(( remaining_percent <= 99 && free_bytes <= total_bytes )) || return 1
(( total_bytes <= (_K3SLR_INT64_MAX - 99) / remaining_percent )) || return 1
percent_reserve=$(((total_bytes * remaining_percent + 99) / 100))
(( container_bytes <= (_K3SLR_INT64_MAX - minimum_free_bytes) / 2 )) || return 1
required=$((minimum_free_bytes + 2 * container_bytes))
(( required <= _K3SLR_INT64_MAX - percent_reserve )) || return 1
required=$((required + percent_reserve))
(( free_bytes >= required ))
}
_k3slr_xtrace_is_disabled() {
[[ "$-" != *x* ]]
}
_k3slr_parse_lifecycle_cli() {
local destination_name="${1-}" mode
shift || return 1
_k3slr_output_name_is_safe "$destination_name" _k3slr_cli_ || return 1
case "$#" in
0) mode=dry-run ;;
1)
[[ "$1" == --execute ]] || return 1
mode=execute
;;
*) return 1 ;;
esac
printf -v "$destination_name" '%s' "$mode"
}
_k3slr_tty_capability() {
local tty_fd
(( $# == 0 )) || return 1
[[ -t 0 ]] || return 1
exec {tty_fd}<>/dev/tty || return 1
if [[ ! -t "$tty_fd" ]]; then
exec {tty_fd}>&-
return 1
fi
exec {tty_fd}>&-
}
_k3slr_require_execute_tty() {
(( $# == 0 )) || return 1
_k3slr_tty_capability
}
_k3slr_open_close_capability_gate() {
local keepass_state='' cryptsetup_state='' binary
local -a required_binaries=(
/usr/bin/keepassxc-cli
/usr/sbin/cryptsetup
/usr/sbin/losetup
/usr/sbin/blkid
/usr/sbin/smartctl
/usr/bin/mount
/usr/bin/umount
/usr/bin/findmnt
/usr/bin/lsblk
/usr/bin/readlink
/usr/bin/stat
/usr/bin/ntfsinfo
/usr/bin/ntfs-3g.probe
/usr/bin/fuser
/usr/bin/sync
/usr/bin/cmp
/usr/bin/cat
/usr/bin/sha256sum
/usr/bin/mawk
/usr/bin/od
/usr/bin/bash
/usr/bin/du
/usr/bin/test
/usr/bin/dd
/usr/bin/id
/usr/bin/ps
/usr/bin/sleep
/usr/bin/mkdir
/usr/bin/dpkg-query
/usr/bin/mktemp
/usr/bin/rm
/usr/bin/rmdir
/usr/bin/sudo
)
local keepass_rc=0 cryptsetup_rc=0
_k3slr_query_package_state keepass_state keepassxc "$_K3SLR_KEEPASSXC_VERSION" || keepass_rc=$?
_k3slr_query_package_state cryptsetup_state cryptsetup-bin "$_K3SLR_CRYPTSETUP_VERSION" || cryptsetup_rc=$?
(( keepass_rc == 0 && cryptsetup_rc == 0 )) || return 1
[[ "$keepass_state" == installed && "$cryptsetup_state" == installed ]] || return 1
for binary in "${required_binaries[@]}"; do
_k3slr_trusted_root_executable "$binary" || return 1
done
}
_k3slr_lifecycle_step() {
(( $# == 1 ))
}
_k3slr_lifecycle_action() {
local lifecycle="${1-}" action="${2-}"
case "$lifecycle" in
prepare)
(( $# == 3 )) || return 1
;;
open|close)
(( $# == 2 )) || return 1
;;
*) return 1 ;;
esac
case "$lifecycle/$action" in
prepare/sudo)
[[ "${3-}" == _k3slr_prepare_invocation ]] || return 1
_k3slr_command /usr/bin/sudo --validate
;;
prepare/*|open/*|close/*)
return 1
;;
*)
return 1
;;
esac
}
_k3slr_lifecycle_dispatch() {
local lifecycle="${1-}" header_provider="${2-}" step
local -a steps=() action_arguments=()
(( $# == 2 )) || return 1
case "$lifecycle" in
prepare)
local -A _k3slr_prepare_invocation=()
steps=(
sudo
context
device-validator
estimate-phase
capacity-preflight
confirm-prepare
install-packages
feasibility
revalidate-device
mount-outer
revalidate-capacity
create-layout
create-kdbx
create-container
validate-allocation
create-loop
luks-format
luks-open
mkfs-ext4
mount-inner
write-marker-metadata
header-proof
close-inner
close-mapping
detach-loop
unmount-outer
closed-validator
)
;;
open)
steps=(
sudo
context
confirm-open
'device-validator(--expect-device-ready)'
revalidate-context-device
mount-outer
validate-layout-kdbx-container
validate-allocation-and-ntfs-attributes
validate-outer-metadata
collision-check
create-loop
luks-open
mount-inner
validate-inner-metadata-marker-capacity
'open-validator(--expect-open)'
)
;;
close)
steps=(
sudo
context
confirm-close
'open-validator(--expect-open)'
validate-metadata-and-full-lineage
busy-process-query-pass-1
busy-process-query-pass-2
sync-inner-filesystem
unmount-inner
close-main-mapping
detach-original-loop
unmount-outer
'closed-validator(--expect-closed)'
)
;;
*) return 1 ;;
esac
for step in "${steps[@]}"; do
_k3slr_lifecycle_step "$step" || return 1
action_arguments=("$lifecycle" "$step")
if [[ "$lifecycle" == prepare ]]; then
action_arguments+=(_k3slr_prepare_invocation)
fi
_k3slr_lifecycle_action "${action_arguments[@]}" || return 1
done
}
_k3slr_lifecycle_main() {
local lifecycle="${1-}" execution_mode="${2-}"
(( $# == 2 )) || return 1
[[ "$lifecycle" == prepare || "$lifecycle" == open || "$lifecycle" == close ]] || return 1
[[ -n "${K3SLR_WRAPPER_CONTRACT:-}" ]] || return 1
_k3slr_load_contract "$K3SLR_WRAPPER_CONTRACT" || return 1
case "$execution_mode" in
dry-run)
_k3slr_prepackage_device_preflight || return 1
printf 'DRY RUN: %s lifecycle; no changes made.\n' "$lifecycle"
;;
execute)
if [[ "$lifecycle" == prepare ]]; then
declare -F _k3slr_prove_header_restore >/dev/null || return 1
else
_k3slr_open_close_capability_gate || return 1
fi
_k3slr_require_execute_tty || return 1
_k3slr_lifecycle_dispatch "$lifecycle" _k3slr_prove_header_restore
;;
*) return 1 ;;
esac
}
_k3slr_command() {
"$@"
}
_k3slr_output_name_is_safe() {
[[ "$1" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ && "$1" != "$2"* ]]
}
_k3slr_capture_directory_is_safe() {
[[ "${1-}" =~ ^/tmp/k3slr-capture\.[A-Za-z0-9]{8,}$ ]]
}
_k3slr_remove_capture_directory() {
local _k3slr_process_capture_directory="${1-}" _k3slr_process_capture_rc=0
_k3slr_capture_directory_is_safe "$_k3slr_process_capture_directory" || return 1
_k3slr_command /usr/bin/rm -- \
"${_k3slr_process_capture_directory}/stdout" \
"${_k3slr_process_capture_directory}/stderr" >/dev/null 2>&1 || _k3slr_process_capture_rc=1
_k3slr_command /usr/bin/rmdir -- "$_k3slr_process_capture_directory" \
>/dev/null 2>&1 || _k3slr_process_capture_rc=1
return "$_k3slr_process_capture_rc"
}
_k3slr_capture_process() {
local _k3slr_process_capture_stdout_name="${1-}"
local _k3slr_process_capture_stderr_name="${2-}"
local _k3slr_process_capture_rc_name="${3-}"
local _k3slr_process_capture_directory='' _k3slr_process_capture_stdout=''
local _k3slr_process_capture_stderr='' _k3slr_process_capture_status=0
shift 3 || return 1
(( $# > 0 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_process_capture_stdout_name" _k3slr_process_capture_ || return 1
_k3slr_output_name_is_safe "$_k3slr_process_capture_stderr_name" _k3slr_process_capture_ || return 1
_k3slr_output_name_is_safe "$_k3slr_process_capture_rc_name" _k3slr_process_capture_ || return 1
[[ "$_k3slr_process_capture_stdout_name" != "$_k3slr_process_capture_stderr_name" &&
"$_k3slr_process_capture_stdout_name" != "$_k3slr_process_capture_rc_name" &&
"$_k3slr_process_capture_stderr_name" != "$_k3slr_process_capture_rc_name" ]] || return 1
_k3slr_capture_one_line _k3slr_process_capture_directory \
/usr/bin/mktemp --directory --tmpdir=/tmp 'k3slr-capture.XXXXXXXX' || return 1
_k3slr_capture_directory_is_safe "$_k3slr_process_capture_directory" || return 1
if _k3slr_command "$@" \
>"${_k3slr_process_capture_directory}/stdout" \
2>"${_k3slr_process_capture_directory}/stderr"; then
_k3slr_process_capture_status=0
else
_k3slr_process_capture_status=$?
fi
if IFS= read -r -d '' _k3slr_process_capture_stdout \
<"${_k3slr_process_capture_directory}/stdout"; then
_k3slr_remove_capture_directory "$_k3slr_process_capture_directory" || :
return 1
fi
if IFS= read -r -d '' _k3slr_process_capture_stderr \
<"${_k3slr_process_capture_directory}/stderr"; then
_k3slr_remove_capture_directory "$_k3slr_process_capture_directory" || :
return 1
fi
_k3slr_remove_capture_directory "$_k3slr_process_capture_directory" || return 1
printf -v "$_k3slr_process_capture_stdout_name" '%s' "$_k3slr_process_capture_stdout"
printf -v "$_k3slr_process_capture_stderr_name" '%s' "$_k3slr_process_capture_stderr"
printf -v "$_k3slr_process_capture_rc_name" '%s' "$_k3slr_process_capture_status"
}
_k3slr_parse_current_context_record() {
local _k3slr_context_record="${1-}" _k3slr_context_destination="${2-}"
(( $# == 2 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_context_destination" _k3slr_context_ || return 1
[[ "$_k3slr_context_record" == $'default\n' ]] || return 1
printf -v "$_k3slr_context_destination" '%s' default
}
_k3slr_current_context() {
local _k3slr_context_destination="${1-}" _k3slr_context_stdout=''
local _k3slr_context_stderr='' _k3slr_context_rc=''
(( $# == 1 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_context_destination" _k3slr_context_ || return 1
_k3slr_capture_process _k3slr_context_stdout _k3slr_context_stderr _k3slr_context_rc \
/usr/local/bin/k3s kubectl config current-context || return 1
[[ "$_k3slr_context_rc" == 0 && -z "$_k3slr_context_stderr" ]] || return 1
_k3slr_parse_current_context_record "$_k3slr_context_stdout" "$_k3slr_context_destination"
}
_k3slr_parse_package_query_record() {
local _k3slr_package_destination="${1-}" _k3slr_package_name="${2-}"
local _k3slr_package_expected="${3-}" _k3slr_package_rc="${4-}"
local _k3slr_package_stdout="${5-}" _k3slr_package_stderr="${6-}"
local _k3slr_package_state=malformed _k3slr_package_prefix _k3slr_package_version
(( $# == 6 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_package_destination" _k3slr_package_ || return 1
case "${_k3slr_package_name}|${_k3slr_package_expected}" in
"keepassxc|${_K3SLR_KEEPASSXC_VERSION}"|"cryptsetup-bin|${_K3SLR_CRYPTSETUP_VERSION}") ;;
*) return 1 ;;
esac
_k3slr_uint "$_k3slr_package_rc" || return 1
(( _k3slr_package_rc <= 255 )) || return 1
if [[ "$_k3slr_package_rc" == 0 && -z "$_k3slr_package_stderr" ]]; then
if [[ "$_k3slr_package_stdout" == "install ok installed|${_k3slr_package_expected}"$'\n' ]]; then
_k3slr_package_state=installed
else
_k3slr_package_prefix='install ok installed|'
if [[ "$_k3slr_package_stdout" == "$_k3slr_package_prefix"*$'\n' ]]; then
_k3slr_package_version="${_k3slr_package_stdout#"$_k3slr_package_prefix"}"
_k3slr_package_version="${_k3slr_package_version%$'\n'}"
if [[ -n "$_k3slr_package_version" && "$_k3slr_package_version" != *$'\n'* &&
"$_k3slr_package_version" != *$'\r'* &&
"$_k3slr_package_stdout" == "${_k3slr_package_prefix}${_k3slr_package_version}"$'\n' ]]; then
_k3slr_package_state=wrong-version
fi
fi
fi
elif [[ "$_k3slr_package_rc" == 1 && -z "$_k3slr_package_stdout" &&
"$_k3slr_package_stderr" == "dpkg-query: no packages found matching ${_k3slr_package_name}"$'\n' ]]; then
_k3slr_package_state=absent
fi
printf -v "$_k3slr_package_destination" '%s' "$_k3slr_package_state"
}
_k3slr_query_package_state() {
local _k3slr_package_destination="${1-}" _k3slr_package_name="${2-}"
local _k3slr_package_expected="${3-}" _k3slr_package_stdout=''
local _k3slr_package_stderr='' _k3slr_package_rc='' _k3slr_query_result=''
(( $# == 3 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_package_destination" _k3slr_package_ || return 1
_k3slr_capture_process _k3slr_package_stdout _k3slr_package_stderr _k3slr_package_rc \
/usr/bin/dpkg-query --show '--showformat=${Status}|${Version}\n' "$_k3slr_package_name" || return 1
_k3slr_parse_package_query_record _k3slr_query_result "$_k3slr_package_name" \
"$_k3slr_package_expected" "$_k3slr_package_rc" \
"$_k3slr_package_stdout" "$_k3slr_package_stderr" || return 1
printf -v "$_k3slr_package_destination" '%s' "$_k3slr_query_result"
}
_k3slr_parse_phase_bytes_record() {
local _k3slr_phase_record="${1-}" _k3slr_phase_destination="${2-}"
local _k3slr_phase_value
(( $# == 2 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_phase_destination" _k3slr_phase_ || return 1
[[ "$_k3slr_phase_record" == phase_bytes=*$'\n' ]] || return 1
_k3slr_phase_value="${_k3slr_phase_record#phase_bytes=}"
_k3slr_phase_value="${_k3slr_phase_value%$'\n'}"
[[ "$_k3slr_phase_record" == "phase_bytes=${_k3slr_phase_value}"$'\n' ]] || return 1
_k3slr_uint "$_k3slr_phase_value" || return 1
printf -v "$_k3slr_phase_destination" '%s' "$_k3slr_phase_value"
}
_k3slr_max_phase_bytes() {
local _k3slr_phase_destination="${1-}" _k3slr_phase_value _k3slr_phase_max=0
(( $# == 4 )) || return 1
shift
_k3slr_output_name_is_safe "$_k3slr_phase_destination" _k3slr_phase_ || return 1
for _k3slr_phase_value in "$@"; do
_k3slr_uint "$_k3slr_phase_value" || return 1
if (( _k3slr_phase_value > _k3slr_phase_max )); then
_k3slr_phase_max="$_k3slr_phase_value"
fi
done
printf -v "$_k3slr_phase_destination" '%s' "$_k3slr_phase_max"
}
_k3slr_parse_statfs_record() {
local _k3slr_statfs_record="${1-}" _k3slr_statfs_total_name="${2-}"
local _k3slr_statfs_free_name="${3-}" _k3slr_statfs_used_name="${4-}"
local _k3slr_statfs_block_size _k3slr_statfs_blocks _k3slr_statfs_available
local _k3slr_statfs_total _k3slr_statfs_free _k3slr_statfs_used
(( $# == 4 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_statfs_total_name" _k3slr_statfs_ || return 1
_k3slr_output_name_is_safe "$_k3slr_statfs_free_name" _k3slr_statfs_ || return 1
_k3slr_output_name_is_safe "$_k3slr_statfs_used_name" _k3slr_statfs_ || return 1
[[ "$_k3slr_statfs_total_name" != "$_k3slr_statfs_free_name" &&
"$_k3slr_statfs_total_name" != "$_k3slr_statfs_used_name" &&
"$_k3slr_statfs_free_name" != "$_k3slr_statfs_used_name" ]] || return 1
[[ "$_k3slr_statfs_record" =~ ^(0|[1-9][0-9]*)\|(0|[1-9][0-9]*)\|(0|[1-9][0-9]*)$'\n'$ ]] || return 1
_k3slr_statfs_block_size="${BASH_REMATCH[1]}"
_k3slr_statfs_blocks="${BASH_REMATCH[2]}"
_k3slr_statfs_available="${BASH_REMATCH[3]}"
_k3slr_uint "$_k3slr_statfs_block_size" || return 1
_k3slr_uint "$_k3slr_statfs_blocks" || return 1
_k3slr_uint "$_k3slr_statfs_available" || return 1
(( _k3slr_statfs_block_size > 0 && _k3slr_statfs_blocks > 0 &&
_k3slr_statfs_available <= _k3slr_statfs_blocks )) || return 1
(( _k3slr_statfs_blocks <= _K3SLR_INT64_MAX / _k3slr_statfs_block_size )) || return 1
_k3slr_statfs_total=$((_k3slr_statfs_block_size * _k3slr_statfs_blocks))
_k3slr_statfs_free=$((_k3slr_statfs_block_size * _k3slr_statfs_available))
_k3slr_statfs_used=$((_k3slr_statfs_total - _k3slr_statfs_free))
printf -v "$_k3slr_statfs_total_name" '%s' "$_k3slr_statfs_total"
printf -v "$_k3slr_statfs_free_name" '%s' "$_k3slr_statfs_free"
printf -v "$_k3slr_statfs_used_name" '%s' "$_k3slr_statfs_used"
}
_k3slr_conservative_inner_capacity_fits() {
local _k3slr_inner_container="${1-}" _k3slr_inner_phase="${2-}"
local _k3slr_inner_used
(( $# == 2 )) || return 1
_k3slr_uint "$_k3slr_inner_container" || return 1
_k3slr_uint "$_k3slr_inner_phase" || return 1
(( _k3slr_inner_container > 0 )) || return 1
_k3slr_inner_used=$((_k3slr_inner_container / 2 + _k3slr_inner_container % 2))
_k3slr_inner_capacity_fits "$_k3slr_inner_container" "$_k3slr_inner_used" "$_k3slr_inner_phase"
}
_k3slr_marker_sha256_is_valid() {
[[ "${1-}" =~ ^[0-9a-f]{64}$ ]]
}
_k3slr_encoded_uuid_is_valid() {
local _k3slr_uuid_hex="${1-}" _k3slr_uuid_kind="${2-}"
local _k3slr_uuid_index _k3slr_uuid_byte
(( $# == 2 && ${#_k3slr_uuid_hex} == 72 )) || return 1
[[ "$_k3slr_uuid_kind" == canonical || "$_k3slr_uuid_kind" == v4 ]] || return 1
for (( _k3slr_uuid_index=0; _k3slr_uuid_index<36; _k3slr_uuid_index++ )); do
_k3slr_uuid_byte="${_k3slr_uuid_hex:_k3slr_uuid_index * 2:2}"
case "$_k3slr_uuid_index" in
8|13|18|23)
[[ "$_k3slr_uuid_byte" == 2d ]] || return 1
;;
14)
if [[ "$_k3slr_uuid_kind" == v4 ]]; then
[[ "$_k3slr_uuid_byte" == 34 ]] || return 1
else
[[ "$_k3slr_uuid_byte" =~ ^(3[0-9]|6[1-6])$ ]] || return 1
fi
;;
19)
if [[ "$_k3slr_uuid_kind" == v4 ]]; then
[[ "$_k3slr_uuid_byte" =~ ^(38|39|61|62)$ ]] || return 1
else
[[ "$_k3slr_uuid_byte" =~ ^(3[0-9]|6[1-6])$ ]] || return 1
fi
;;
*)
[[ "$_k3slr_uuid_byte" =~ ^(3[0-9]|6[1-6])$ ]] || return 1
;;
esac
done
}
_k3slr_marker_hex_is_valid() {
local _k3slr_marker_hex_record="${1-}" _k3slr_marker_hex
(( $# == 1 )) || return 1
[[ "$_k3slr_marker_hex_record" =~ ^[0-9a-f]+$'\n'$ ]] || return 1
_k3slr_marker_hex="${_k3slr_marker_hex_record%$'\n'}"
[[ ${#_k3slr_marker_hex} == 74 && "${_k3slr_marker_hex:72:2}" == 0a ]] || return 1
_k3slr_encoded_uuid_is_valid "${_k3slr_marker_hex:0:72}" v4
}
_k3slr_parse_runtime_metadata_hex() {
local _k3slr_metadata_hex_record="${1-}" _k3slr_metadata_hash_name="${2-}"
local _k3slr_metadata_hex _k3slr_metadata_prefix _k3slr_metadata_remainder
local _k3slr_metadata_uuid_hex _k3slr_metadata_tail_prefix _k3slr_metadata_hash_hex
local _k3slr_metadata_contract_hex='' _k3slr_metadata_label_hex=''
local _k3slr_metadata_hash='' _k3slr_metadata_character _k3slr_metadata_byte
local _k3slr_metadata_index
(( $# == 2 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_metadata_hash_name" _k3slr_metadata_ || return 1
[[ -n "${K3SLR_CONTAINER_SIZE_BYTES:-}" && -n "${K3SLR_INNER_LABEL:-}" ]] || return 1
_k3slr_uint "$K3SLR_CONTAINER_SIZE_BYTES" || return 1
[[ "$K3SLR_INNER_LABEL" =~ ^[A-Z][A-Z0-9_]*$ ]] || return 1
[[ "$_k3slr_metadata_hex_record" =~ ^[0-9a-f]+$'\n'$ ]] || return 1
_k3slr_metadata_hex="${_k3slr_metadata_hex_record%$'\n'}"
(( ${#_k3slr_metadata_hex} <= 4096 && ${#_k3slr_metadata_hex} % 2 == 0 )) || return 1
for (( _k3slr_metadata_index=0;
_k3slr_metadata_index<${#K3SLR_CONTAINER_SIZE_BYTES};
_k3slr_metadata_index++ )); do
_k3slr_metadata_character="${K3SLR_CONTAINER_SIZE_BYTES:_k3slr_metadata_index:1}"
printf -v _k3slr_metadata_byte '%02x' "'$_k3slr_metadata_character"
_k3slr_metadata_contract_hex+="$_k3slr_metadata_byte"
done
for (( _k3slr_metadata_index=0;
_k3slr_metadata_index<${#K3SLR_INNER_LABEL};
_k3slr_metadata_index++ )); do
_k3slr_metadata_character="${K3SLR_INNER_LABEL:_k3slr_metadata_index:1}"
printf -v _k3slr_metadata_byte '%02x' "'$_k3slr_metadata_character"
_k3slr_metadata_label_hex+="$_k3slr_metadata_byte"
done
_k3slr_metadata_prefix='736368656d613d6b33736c722d72756e74696d652d76310a6c756b735f757569643d'
[[ "$_k3slr_metadata_hex" == "$_k3slr_metadata_prefix"* ]] || return 1
_k3slr_metadata_remainder="${_k3slr_metadata_hex#"$_k3slr_metadata_prefix"}"
(( ${#_k3slr_metadata_remainder} >= 72 )) || return 1
_k3slr_metadata_uuid_hex="${_k3slr_metadata_remainder:0:72}"
_k3slr_encoded_uuid_is_valid "$_k3slr_metadata_uuid_hex" canonical || return 1
_k3slr_metadata_remainder="${_k3slr_metadata_remainder:72}"
_k3slr_metadata_tail_prefix="0a636f6e7461696e65725f62797465733d${_k3slr_metadata_contract_hex}0a696e6e65725f6c6162656c3d${_k3slr_metadata_label_hex}0a6d61726b65725f7368613235363d"
[[ "$_k3slr_metadata_remainder" == "$_k3slr_metadata_tail_prefix"* ]] || return 1
_k3slr_metadata_hash_hex="${_k3slr_metadata_remainder#"$_k3slr_metadata_tail_prefix"}"
[[ ${#_k3slr_metadata_hash_hex} == 130 &&
"${_k3slr_metadata_hash_hex:128:2}" == 0a ]] || return 1
_k3slr_metadata_hash_hex="${_k3slr_metadata_hash_hex:0:128}"
for (( _k3slr_metadata_index=0; _k3slr_metadata_index<128; _k3slr_metadata_index+=2 )); do
_k3slr_metadata_byte="${_k3slr_metadata_hash_hex:_k3slr_metadata_index:2}"
case "$_k3slr_metadata_byte" in
30) _k3slr_metadata_hash+=0 ;; 31) _k3slr_metadata_hash+=1 ;;
32) _k3slr_metadata_hash+=2 ;; 33) _k3slr_metadata_hash+=3 ;;
34) _k3slr_metadata_hash+=4 ;; 35) _k3slr_metadata_hash+=5 ;;
36) _k3slr_metadata_hash+=6 ;; 37) _k3slr_metadata_hash+=7 ;;
38) _k3slr_metadata_hash+=8 ;; 39) _k3slr_metadata_hash+=9 ;;
61) _k3slr_metadata_hash+=a ;; 62) _k3slr_metadata_hash+=b ;;
63) _k3slr_metadata_hash+=c ;; 64) _k3slr_metadata_hash+=d ;;
65) _k3slr_metadata_hash+=e ;; 66) _k3slr_metadata_hash+=f ;;
*) return 1 ;;
esac
done
_k3slr_marker_sha256_is_valid "$_k3slr_metadata_hash" || return 1
printf -v "$_k3slr_metadata_hash_name" '%s' "$_k3slr_metadata_hash"
}
_k3slr_normalize_od_hex_record() {
local _k3slr_od_record="${1-}" _k3slr_od_destination="${2-}"
local _k3slr_od_remaining _k3slr_od_line _k3slr_od_token
local _k3slr_od_hex='' _k3slr_od_bytes=0
local -a _k3slr_od_tokens=()
(( $# == 2 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_od_destination" _k3slr_od_ || return 1
[[ -n "$_k3slr_od_record" && "$_k3slr_od_record" == *$'\n' &&
"$_k3slr_od_record" != *$'\r'* ]] || return 1
_k3slr_od_remaining="$_k3slr_od_record"
while [[ -n "$_k3slr_od_remaining" ]]; do
[[ "$_k3slr_od_remaining" == *$'\n'* ]] || return 1
_k3slr_od_line="${_k3slr_od_remaining%%$'\n'*}"
_k3slr_od_remaining="${_k3slr_od_remaining#*$'\n'}"
read -r -a _k3slr_od_tokens <<<"$_k3slr_od_line"
for _k3slr_od_token in "${_k3slr_od_tokens[@]}"; do
[[ "$_k3slr_od_token" =~ ^[0-9a-f]{2}$ ]] || return 1
_k3slr_od_bytes=$((_k3slr_od_bytes + 1))
(( _k3slr_od_bytes <= 65536 )) || return 1
_k3slr_od_hex+="$_k3slr_od_token"
done
done
(( _k3slr_od_bytes > 0 )) || return 1
printf -v "$_k3slr_od_destination" '%s\n' "$_k3slr_od_hex"
}
_k3slr_parse_unmounted_ntfs_capacity_hex() {
local _k3slr_ntfs_record="${1-}" _k3slr_ntfs_total_name="${2-}"
local _k3slr_ntfs_free_name="${3-}" _k3slr_ntfs_hex _k3slr_ntfs_text=''
local _k3slr_ntfs_byte _k3slr_ntfs_character _k3slr_ntfs_remaining _k3slr_ntfs_line
local _k3slr_ntfs_trimmed _k3slr_ntfs_cluster_size='' _k3slr_ntfs_volume=''
local _k3slr_ntfs_allocated='' _k3slr_ntfs_free_clusters=''
local _k3slr_ntfs_allocated_percent='' _k3slr_ntfs_free_percent=''
local _k3slr_ntfs_integer _k3slr_ntfs_fraction _k3slr_ntfs_allocated_tenths
local _k3slr_ntfs_free_tenths _k3slr_ntfs_total _k3slr_ntfs_free
local _k3slr_ntfs_left _k3slr_ntfs_right _k3slr_ntfs_difference
local _k3slr_ntfs_index
(( $# == 3 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_ntfs_total_name" _k3slr_ntfs_ || return 1
_k3slr_output_name_is_safe "$_k3slr_ntfs_free_name" _k3slr_ntfs_ || return 1
[[ "$_k3slr_ntfs_total_name" != "$_k3slr_ntfs_free_name" ]] || return 1
[[ "$_k3slr_ntfs_record" =~ ^[0-9a-f]+$'\n'$ ]] || return 1
_k3slr_ntfs_hex="${_k3slr_ntfs_record%$'\n'}"
(( ${#_k3slr_ntfs_hex} > 0 && ${#_k3slr_ntfs_hex} <= 131072 &&
${#_k3slr_ntfs_hex} % 2 == 0 )) || return 1
[[ "${_k3slr_ntfs_hex: -2}" == 0a ]] || return 1
for (( _k3slr_ntfs_index=0;
_k3slr_ntfs_index<${#_k3slr_ntfs_hex};
_k3slr_ntfs_index+=2 )); do
_k3slr_ntfs_byte="${_k3slr_ntfs_hex:_k3slr_ntfs_index:2}"
[[ "$_k3slr_ntfs_byte" != 00 && "$_k3slr_ntfs_byte" != 0d ]] || return 1
if [[ "$_k3slr_ntfs_byte" == 09 ]]; then
_k3slr_ntfs_character=$'\t'
elif [[ "$_k3slr_ntfs_byte" == 0a ]]; then
_k3slr_ntfs_character=$'\n'
elif (( 16#$_k3slr_ntfs_byte >= 16#20 && 16#$_k3slr_ntfs_byte <= 16#7e )); then
printf -v _k3slr_ntfs_character '%b' "\\x${_k3slr_ntfs_byte}"
else
return 1
fi
_k3slr_ntfs_text+="$_k3slr_ntfs_character"
done
[[ "$_k3slr_ntfs_text" == *$'\n' ]] || return 1
_k3slr_ntfs_remaining="$_k3slr_ntfs_text"
while [[ -n "$_k3slr_ntfs_remaining" ]]; do
[[ "$_k3slr_ntfs_remaining" == *$'\n'* ]] || return 1
_k3slr_ntfs_line="${_k3slr_ntfs_remaining%%$'\n'*}"
_k3slr_ntfs_remaining="${_k3slr_ntfs_remaining#*$'\n'}"
_k3slr_ntfs_trimmed="$_k3slr_ntfs_line"
while [[ "$_k3slr_ntfs_trimmed" == ' '* || "$_k3slr_ntfs_trimmed" == $'\t'* ]]; do
_k3slr_ntfs_trimmed="${_k3slr_ntfs_trimmed:1}"
done
if [[ "$_k3slr_ntfs_trimmed" =~ ^Cluster\ Size:\ (0|[1-9][0-9]*)$ ]]; then
[[ -z "$_k3slr_ntfs_cluster_size" ]] || return 1
_k3slr_ntfs_cluster_size="${BASH_REMATCH[1]}"
elif [[ "$_k3slr_ntfs_trimmed" =~ ^Volume\ Size\ in\ Clusters:\ (0|[1-9][0-9]*)$ ]]; then
[[ -z "$_k3slr_ntfs_volume" ]] || return 1
_k3slr_ntfs_volume="${BASH_REMATCH[1]}"
elif [[ "$_k3slr_ntfs_trimmed" =~ ^Allocated\ clusters\ (0|[1-9][0-9]*)\ \(((0|[1-9][0-9]{0,2})\.([0-9]))%\)$ ]]; then
[[ -z "$_k3slr_ntfs_allocated" ]] || return 1
_k3slr_ntfs_allocated="${BASH_REMATCH[1]}"
_k3slr_ntfs_allocated_percent="${BASH_REMATCH[2]}"
elif [[ "$_k3slr_ntfs_trimmed" =~ ^Free\ Clusters:\ (0|[1-9][0-9]*)\ \(((0|[1-9][0-9]{0,2})\.([0-9]))%\)$ ]]; then
[[ -z "$_k3slr_ntfs_free_clusters" ]] || return 1
_k3slr_ntfs_free_clusters="${BASH_REMATCH[1]}"
_k3slr_ntfs_free_percent="${BASH_REMATCH[2]}"
elif [[ "$_k3slr_ntfs_trimmed" == 'Cluster Size:'* ||
"$_k3slr_ntfs_trimmed" == 'Volume Size in Clusters:'* ||
"$_k3slr_ntfs_trimmed" == 'Allocated clusters'* ||
"$_k3slr_ntfs_trimmed" == 'Free Clusters:'* ]]; then
return 1
fi
done
[[ -n "$_k3slr_ntfs_cluster_size" && -n "$_k3slr_ntfs_volume" &&
-n "$_k3slr_ntfs_allocated" && -n "$_k3slr_ntfs_free_clusters" ]] || return 1
_k3slr_uint "$_k3slr_ntfs_cluster_size" || return 1
_k3slr_uint "$_k3slr_ntfs_volume" || return 1
_k3slr_uint "$_k3slr_ntfs_allocated" || return 1
_k3slr_uint "$_k3slr_ntfs_free_clusters" || return 1
(( _k3slr_ntfs_cluster_size > 0 && _k3slr_ntfs_volume > 0 )) || return 1
(( _k3slr_ntfs_allocated <= _K3SLR_INT64_MAX - _k3slr_ntfs_free_clusters &&
_k3slr_ntfs_allocated + _k3slr_ntfs_free_clusters == _k3slr_ntfs_volume )) || return 1
(( _k3slr_ntfs_volume <= _K3SLR_INT64_MAX / _k3slr_ntfs_cluster_size )) || return 1
_k3slr_ntfs_total=$((_k3slr_ntfs_cluster_size * _k3slr_ntfs_volume))
_k3slr_ntfs_free=$((_k3slr_ntfs_cluster_size * _k3slr_ntfs_free_clusters))
_k3slr_ntfs_integer="${_k3slr_ntfs_allocated_percent%.*}"
_k3slr_ntfs_fraction="${_k3slr_ntfs_allocated_percent#*.}"
_k3slr_ntfs_allocated_tenths=$((10#$_k3slr_ntfs_integer * 10 + 10#$_k3slr_ntfs_fraction))
_k3slr_ntfs_integer="${_k3slr_ntfs_free_percent%.*}"
_k3slr_ntfs_fraction="${_k3slr_ntfs_free_percent#*.}"
_k3slr_ntfs_free_tenths=$((10#$_k3slr_ntfs_integer * 10 + 10#$_k3slr_ntfs_fraction))
(( _k3slr_ntfs_allocated_tenths <= 1000 && _k3slr_ntfs_free_tenths <= 1000 )) || return 1
(( _k3slr_ntfs_volume <= _K3SLR_INT64_MAX / 1000 &&
_k3slr_ntfs_allocated <= _K3SLR_INT64_MAX / 1000 &&
_k3slr_ntfs_free_clusters <= _K3SLR_INT64_MAX / 1000 )) || return 1
_k3slr_ntfs_left=$((_k3slr_ntfs_allocated_tenths * _k3slr_ntfs_volume))
_k3slr_ntfs_right=$((_k3slr_ntfs_allocated * 1000))
if (( _k3slr_ntfs_left >= _k3slr_ntfs_right )); then
_k3slr_ntfs_difference=$((_k3slr_ntfs_left - _k3slr_ntfs_right))
else
_k3slr_ntfs_difference=$((_k3slr_ntfs_right - _k3slr_ntfs_left))
fi
(( _k3slr_ntfs_difference <= _K3SLR_INT64_MAX / 2 &&
2 * _k3slr_ntfs_difference <= _k3slr_ntfs_volume )) || return 1
_k3slr_ntfs_left=$((_k3slr_ntfs_free_tenths * _k3slr_ntfs_volume))
_k3slr_ntfs_right=$((_k3slr_ntfs_free_clusters * 1000))
if (( _k3slr_ntfs_left >= _k3slr_ntfs_right )); then
_k3slr_ntfs_difference=$((_k3slr_ntfs_left - _k3slr_ntfs_right))
else
_k3slr_ntfs_difference=$((_k3slr_ntfs_right - _k3slr_ntfs_left))
fi
(( _k3slr_ntfs_difference <= _K3SLR_INT64_MAX / 2 &&
2 * _k3slr_ntfs_difference <= _k3slr_ntfs_volume )) || return 1
printf -v "$_k3slr_ntfs_total_name" '%s' "$_k3slr_ntfs_total"
printf -v "$_k3slr_ntfs_free_name" '%s' "$_k3slr_ntfs_free"
}
_k3slr_parse_prepackage_snapshot_fields() {
local _k3slr_snapshot_record="${1-}" _k3slr_snapshot_result_name="${2-}"
local _k3slr_snapshot_remaining _k3slr_snapshot_line _k3slr_snapshot_key _k3slr_snapshot_value
local -A _k3slr_snapshot_allowed=(
[canonical_partition]=1 [partition_major_minor]=1
[canonical_parent_disk]=1 [disk_major_minor]=1
[filesystem_uuid]=1 [partuuid]=1 [filesystem_type]=1
[model]=1 [serial]=1 [wwn]=1
[canonical_k3s_partition]=1 [k3s_partition_major_minor]=1
[canonical_k3s_disk]=1 [k3s_disk_major_minor]=1
[k3s_filesystem_uuid]=1 [k3s_partuuid]=1 [k3s_filesystem_type]=1
[k3s_model]=1 [k3s_serial]=1 [k3s_wwn]=1
[smart_health]=1 [smart_reallocated]=1 [smart_pending]=1 [smart_uncorrectable]=1
[outer_mount]=1 [inner_mount]=1 [source_mounts]=1 [loops]=1
[mapper_directory]=1 [mapping_path]=1 [proof_mapping_path]=1 [mapping_names]=1
[ntfs_probe]=1
)
(( $# == 2 )) || return 1
[[ "$_k3slr_snapshot_result_name" =~ ^[A-Za-z_][A-Za-z0-9_]*$ &&
"$_k3slr_snapshot_result_name" != _k3slr_snapshot_* ]] || return 1
local -n _k3slr_snapshot_result="$_k3slr_snapshot_result_name"
[[ -n "$_k3slr_snapshot_record" && "$_k3slr_snapshot_record" == *$'\n' &&
"$_k3slr_snapshot_record" != *$'\r'* ]] || return 1
_k3slr_snapshot_result=()
_k3slr_snapshot_remaining="$_k3slr_snapshot_record"
while [[ -n "$_k3slr_snapshot_remaining" ]]; do
[[ "$_k3slr_snapshot_remaining" == *$'\n'* ]] || return 1
_k3slr_snapshot_line="${_k3slr_snapshot_remaining%%$'\n'*}"
_k3slr_snapshot_remaining="${_k3slr_snapshot_remaining#*$'\n'}"
[[ "$_k3slr_snapshot_line" =~ ^([a-z][a-z0-9_]*)=([^[:space:]]+)$ ]] || return 1
_k3slr_snapshot_key="${BASH_REMATCH[1]}"
_k3slr_snapshot_value="${BASH_REMATCH[2]}"
[[ "${_k3slr_snapshot_allowed[$_k3slr_snapshot_key]+present}" == present &&
"${_k3slr_snapshot_result[$_k3slr_snapshot_key]+present}" != present ]] || return 1
_k3slr_snapshot_result["$_k3slr_snapshot_key"]="$_k3slr_snapshot_value"
done
for _k3slr_snapshot_key in "${!_k3slr_snapshot_allowed[@]}"; do
[[ "${_k3slr_snapshot_result[$_k3slr_snapshot_key]+present}" == present ]] || return 1
done
}
_k3slr_validate_prepackage_snapshot() {
local _k3slr_snapshot_record="${1-}"
local -A snapshot_fields=()
(( $# == 1 )) || return 1
_k3slr_parse_prepackage_snapshot_fields "$_k3slr_snapshot_record" snapshot_fields || return 1
[[ "${snapshot_fields[canonical_partition]}" =~ ^/dev/[A-Za-z0-9._/-]+$ &&
"${snapshot_fields[canonical_parent_disk]}" =~ ^/dev/[A-Za-z0-9._/-]+$ &&
"${snapshot_fields[canonical_k3s_partition]}" =~ ^/dev/[A-Za-z0-9._/-]+$ &&
"${snapshot_fields[canonical_k3s_disk]}" =~ ^/dev/[A-Za-z0-9._/-]+$ ]] || return 1
[[ "${snapshot_fields[partition_major_minor]}" =~ ^[0-9]+:[0-9]+$ &&
"${snapshot_fields[disk_major_minor]}" =~ ^[0-9]+:[0-9]+$ &&
"${snapshot_fields[k3s_partition_major_minor]}" =~ ^[0-9]+:[0-9]+$ &&
"${snapshot_fields[k3s_disk_major_minor]}" =~ ^[0-9]+:[0-9]+$ ]] || return 1
[[ "${snapshot_fields[canonical_parent_disk]}" != "${snapshot_fields[canonical_k3s_disk]}" &&
"${snapshot_fields[disk_major_minor]}" != "${snapshot_fields[k3s_disk_major_minor]}" ]] || return 1
[[ "${snapshot_fields[filesystem_uuid]}" == "$K3SLR_RECOVERY_FS_UUID" &&
"${snapshot_fields[partuuid]}" == "$K3SLR_RECOVERY_PARTUUID" &&
"${snapshot_fields[filesystem_type]}" == ntfs &&
"${snapshot_fields[model]}" == "$K3SLR_RECOVERY_MODEL" &&
"${snapshot_fields[serial]}" == "$K3SLR_RECOVERY_SERIAL" &&
"${snapshot_fields[wwn]}" == "$K3SLR_RECOVERY_WWN" ]] || return 1
[[ "${snapshot_fields[k3s_filesystem_uuid]}" == "$K3SLR_K3S_FS_UUID" &&
"${snapshot_fields[k3s_partuuid]}" == "$K3SLR_K3S_PARTUUID" &&
"${snapshot_fields[k3s_filesystem_type]}" == ext4 &&
"${snapshot_fields[k3s_model]}" == "$K3SLR_K3S_MODEL" &&
"${snapshot_fields[k3s_serial]}" == "$K3SLR_K3S_SERIAL" &&
"${snapshot_fields[k3s_wwn]}" == "$K3SLR_K3S_WWN" ]] || return 1
[[ "${snapshot_fields[smart_health]}" == PASSED &&
"${snapshot_fields[smart_reallocated]}" == 0 &&
"${snapshot_fields[smart_pending]}" == 0 &&
"${snapshot_fields[smart_uncorrectable]}" == 0 &&
"${snapshot_fields[outer_mount]}" == absent &&
"${snapshot_fields[inner_mount]}" == absent &&
"${snapshot_fields[source_mounts]}" == absent &&
"${snapshot_fields[loops]}" == absent &&
"${snapshot_fields[mapper_directory]}" == physical-root-safe &&
"${snapshot_fields[mapping_path]}" == absent &&
"${snapshot_fields[proof_mapping_path]}" == absent &&
"${snapshot_fields[mapping_names]}" == absent &&
"${snapshot_fields[ntfs_probe]}" == pass ]] || return 1
}
_k3slr_prepackage_snapshot_identity() {
local _k3slr_snapshot_record="${1-}" _k3slr_snapshot_identity_name="${2-}"
local _k3slr_snapshot_identity
local -A snapshot_fields=()
(( $# == 2 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_snapshot_identity_name" _k3slr_snapshot_ || return 1
_k3slr_validate_prepackage_snapshot "$_k3slr_snapshot_record" || return 1
_k3slr_parse_prepackage_snapshot_fields "$_k3slr_snapshot_record" snapshot_fields || return 1
printf -v _k3slr_snapshot_identity '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' \
"${snapshot_fields[canonical_partition]}" "${snapshot_fields[partition_major_minor]}" \
"${snapshot_fields[canonical_parent_disk]}" "${snapshot_fields[disk_major_minor]}" \
"${snapshot_fields[filesystem_uuid]}" "${snapshot_fields[partuuid]}" \
"${snapshot_fields[filesystem_type]}" "${snapshot_fields[model]}" \
"${snapshot_fields[serial]}" "${snapshot_fields[wwn]}" \
"${snapshot_fields[k3s_disk_major_minor]}"
printf -v "$_k3slr_snapshot_identity_name" '%s' "$_k3slr_snapshot_identity"
}
_k3slr_prepackage_device_preflight() {
local _k3slr_preflight_before='' _k3slr_preflight_after=''
(( $# == 0 )) || return 1
_k3slr_collect_prepackage_snapshot _k3slr_preflight_before || return 1
_k3slr_validate_prepackage_snapshot "$_k3slr_preflight_before" || return 1
_k3slr_collect_prepackage_snapshot _k3slr_preflight_after || return 1
_k3slr_validate_prepackage_snapshot "$_k3slr_preflight_after" || return 1
[[ "$_k3slr_preflight_before" == "$_k3slr_preflight_after" ]]
}
_k3slr_collect_unmounted_ntfs_capacity() {
local _k3slr_capacity_hex_name="${1-}" _k3slr_capacity_identity_name="${2-}"
local _k3slr_capacity_before='' _k3slr_capacity_after=''
local _k3slr_capacity_hex='' _k3slr_capacity_identity=''
local -A snapshot_fields=()
(( $# == 2 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_capacity_hex_name" _k3slr_capacity_ || return 1
_k3slr_output_name_is_safe "$_k3slr_capacity_identity_name" _k3slr_capacity_ || return 1
[[ "$_k3slr_capacity_hex_name" != "$_k3slr_capacity_identity_name" ]] || return 1
_k3slr_collect_prepackage_snapshot _k3slr_capacity_before || return 1
_k3slr_validate_prepackage_snapshot "$_k3slr_capacity_before" || return 1
_k3slr_parse_prepackage_snapshot_fields "$_k3slr_capacity_before" snapshot_fields || return 1
_k3slr_capture_ntfsinfo_hex _k3slr_capacity_hex "${snapshot_fields[canonical_partition]}" || return 1
_k3slr_collect_prepackage_snapshot _k3slr_capacity_after || return 1
_k3slr_validate_prepackage_snapshot "$_k3slr_capacity_after" || return 1
[[ "$_k3slr_capacity_before" == "$_k3slr_capacity_after" ]] || return 1
_k3slr_prepackage_snapshot_identity "$_k3slr_capacity_before" _k3slr_capacity_identity || return 1
printf -v "$_k3slr_capacity_hex_name" '%s' "$_k3slr_capacity_hex"
printf -v "$_k3slr_capacity_identity_name" '%s' "$_k3slr_capacity_identity"
}
_k3slr_capture_exact_one_line_record() {
local _k3slr_record_destination="${1-}" _k3slr_record_stdout=''
local _k3slr_record_stderr='' _k3slr_record_rc='' _k3slr_record_value
shift || return 1
(( $# > 0 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_record_destination" _k3slr_record_ || return 1
_k3slr_capture_process _k3slr_record_stdout _k3slr_record_stderr _k3slr_record_rc "$@" || return 1
[[ "$_k3slr_record_rc" == 0 && -z "$_k3slr_record_stderr" &&
"$_k3slr_record_stdout" == *$'\n' ]] || return 1
_k3slr_record_value="${_k3slr_record_stdout%$'\n'}"
[[ -n "$_k3slr_record_value" && "$_k3slr_record_value" != *$'\n'* &&
"$_k3slr_record_value" != *$'\r'* &&
"$_k3slr_record_stdout" == "${_k3slr_record_value}"$'\n' ]] || return 1
printf -v "$_k3slr_record_destination" '%s' "$_k3slr_record_value"
}
_k3slr_capture_absent_record() {
local _k3slr_absent_stdout='' _k3slr_absent_stderr='' _k3slr_absent_rc=''
(( $# > 0 )) || return 1
_k3slr_capture_process _k3slr_absent_stdout _k3slr_absent_stderr _k3slr_absent_rc "$@" || return 1
[[ "$_k3slr_absent_rc" == 1 && -z "$_k3slr_absent_stdout" && -z "$_k3slr_absent_stderr" ]]
}
_k3slr_capture_empty_success() {
local _k3slr_empty_stdout='' _k3slr_empty_stderr='' _k3slr_empty_rc=''
(( $# > 0 )) || return 1
_k3slr_capture_process _k3slr_empty_stdout _k3slr_empty_stderr _k3slr_empty_rc "$@" || return 1
[[ "$_k3slr_empty_rc" == 0 && -z "$_k3slr_empty_stdout" && -z "$_k3slr_empty_stderr" ]]
}
_k3slr_capture_raw_success() {
local _k3slr_raw_destination="${1-}" _k3slr_raw_stdout=''
local _k3slr_raw_stderr='' _k3slr_raw_rc=''
shift || return 1
(( $# > 0 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_raw_destination" _k3slr_raw_ || return 1
_k3slr_capture_process _k3slr_raw_stdout _k3slr_raw_stderr _k3slr_raw_rc "$@" || return 1
[[ "$_k3slr_raw_rc" == 0 && -z "$_k3slr_raw_stderr" ]] || return 1
printf -v "$_k3slr_raw_destination" '%s' "$_k3slr_raw_stdout"
}
_k3slr_parse_smart_evidence() {
local _k3slr_smart_record="${1-}" _k3slr_smart_destination="${2-}"
local _k3slr_smart_remaining _k3slr_smart_line _k3slr_smart_id _k3slr_smart_raw
local _k3slr_smart_health='' _k3slr_smart_reallocated=''
local _k3slr_smart_pending='' _k3slr_smart_uncorrectable=''
local -a _k3slr_smart_fields=()
(( $# == 2 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_smart_destination" _k3slr_smart_ || return 1
[[ -n "$_k3slr_smart_record" && "$_k3slr_smart_record" == *$'\n' &&
"$_k3slr_smart_record" != *$'\r'* ]] || return 1
_k3slr_smart_remaining="$_k3slr_smart_record"
while [[ -n "$_k3slr_smart_remaining" ]]; do
_k3slr_smart_line="${_k3slr_smart_remaining%%$'\n'*}"
_k3slr_smart_remaining="${_k3slr_smart_remaining#*$'\n'}"
if [[ "$_k3slr_smart_line" =~ ^SMART[[:space:]]overall-health[[:space:]]self-assessment[[:space:]]test[[:space:]]result:[[:space:]]*PASSED[[:space:]]*$ ]]; then
[[ -z "$_k3slr_smart_health" ]] || return 1
_k3slr_smart_health=PASSED
continue
fi
[[ "$_k3slr_smart_line" =~ ^[[:space:]]*(5|197|198)[[:space:]] ]] || continue
_k3slr_smart_id="${BASH_REMATCH[1]}"
read -r -a _k3slr_smart_fields <<<"$_k3slr_smart_line"
(( ${#_k3slr_smart_fields[@]} >= 2 )) || return 1
_k3slr_smart_raw="${_k3slr_smart_fields[${#_k3slr_smart_fields[@]} - 1]}"
_k3slr_uint "$_k3slr_smart_raw" || return 1
case "$_k3slr_smart_id" in
5)
[[ -z "$_k3slr_smart_reallocated" ]] || return 1
_k3slr_smart_reallocated="$_k3slr_smart_raw"
;;
197)
[[ -z "$_k3slr_smart_pending" ]] || return 1
_k3slr_smart_pending="$_k3slr_smart_raw"
;;
198)
[[ -z "$_k3slr_smart_uncorrectable" ]] || return 1
_k3slr_smart_uncorrectable="$_k3slr_smart_raw"
;;
esac
done
[[ "$_k3slr_smart_health" == PASSED && "$_k3slr_smart_reallocated" == 0 &&
"$_k3slr_smart_pending" == 0 && "$_k3slr_smart_uncorrectable" == 0 ]] || return 1
printf -v "$_k3slr_smart_destination" '%s' 'PASSED|0|0|0'
}
_k3slr_all_loops_exclude_backing_device() {
local _k3slr_loops_record="${1-}" _k3slr_loops_backing_major_minor="${2-}"
local _k3slr_loops_remaining _k3slr_loops_line
local -a _k3slr_loops_fields=()
(( $# == 2 )) || return 1
[[ "$_k3slr_loops_backing_major_minor" =~ ^[0-9]+:[0-9]+$ ]] || return 1
[[ "$_k3slr_loops_record" != *$'\r'* ]] || return 1
[[ -z "$_k3slr_loops_record" || "$_k3slr_loops_record" == *$'\n' ]] || return 1
_k3slr_loops_remaining="$_k3slr_loops_record"
while [[ -n "$_k3slr_loops_remaining" ]]; do
_k3slr_loops_line="${_k3slr_loops_remaining%%$'\n'*}"
_k3slr_loops_remaining="${_k3slr_loops_remaining#*$'\n'}"
read -r -a _k3slr_loops_fields <<<"$_k3slr_loops_line"
(( ${#_k3slr_loops_fields[@]} == 7 )) || return 1
[[ "${_k3slr_loops_fields[0]}" =~ ^/dev/loop[0-9]+$ &&
"${_k3slr_loops_fields[1]}" =~ ^/[^[:space:]]+$ &&
"${_k3slr_loops_fields[2]}" =~ ^(0|[1-9][0-9]*)$ &&
"${_k3slr_loops_fields[3]}" =~ ^[0-9]+:[0-9]+$ &&
"${_k3slr_loops_fields[4]}" =~ ^[0-9]+:[0-9]+$ &&
"${_k3slr_loops_fields[5]}" =~ ^(0|[1-9][0-9]*)$ &&
"${_k3slr_loops_fields[6]}" =~ ^(0|[1-9][0-9]*)$ ]] || return 1
[[ "${_k3slr_loops_fields[3]}" != "$_k3slr_loops_backing_major_minor" ]] || return 1
done
}
_k3slr_mapper_name_scan_is_clear() {
local _k3slr_names_record="${1-}" _k3slr_names_remaining _k3slr_names_line
local -a _k3slr_names_fields=()
(( $# == 1 )) || return 1
[[ -n "$_k3slr_names_record" && "$_k3slr_names_record" == *$'\n' &&
"$_k3slr_names_record" != *$'\r'* ]] || return 1
_k3slr_names_remaining="$_k3slr_names_record"
while [[ -n "$_k3slr_names_remaining" ]]; do
_k3slr_names_line="${_k3slr_names_remaining%%$'\n'*}"
_k3slr_names_remaining="${_k3slr_names_remaining#*$'\n'}"
read -r -a _k3slr_names_fields <<<"$_k3slr_names_line"
(( ${#_k3slr_names_fields[@]} == 2 )) || return 1
[[ "${_k3slr_names_fields[0]}" =~ ^[A-Za-z0-9._/-]+$ &&
"${_k3slr_names_fields[1]}" =~ ^[a-z][a-z0-9_-]*$ ]] || return 1
[[ "${_k3slr_names_fields[0]}" != "$K3SLR_MAPPING_NAME" &&
"${_k3slr_names_fields[0]}" != "$K3SLR_PROOF_MAPPING_NAME" ]] || return 1
done
}
_k3slr_mapper_directory_is_safe() {
local _k3slr_mapper_record="${1-}" _k3slr_mapper_mode _k3slr_mapper_group _k3slr_mapper_other
(( $# == 1 )) || return 1
[[ "$_k3slr_mapper_record" =~ ^directory\|0\|0\|([0-7]{3,4})$'\n'$ ]] || return 1
_k3slr_mapper_mode="${BASH_REMATCH[1]}"
_k3slr_mapper_group="${_k3slr_mapper_mode: -2:1}"
_k3slr_mapper_other="${_k3slr_mapper_mode: -1}"
(( (10#$_k3slr_mapper_group & 2) == 0 && (10#$_k3slr_mapper_other & 2) == 0 ))
}
_k3slr_collect_prepackage_snapshot() {
local _k3slr_system_destination="${1-}"
local _k3slr_system_recovery_partition _k3slr_system_recovery_disk
local _k3slr_system_k3s_partition _k3slr_system_k3s_disk
local _k3slr_system_recovery_parent _k3slr_system_partition_mm
local _k3slr_system_recovery_model _k3slr_system_recovery_serial _k3slr_system_recovery_wwn
local _k3slr_system_recovery_mm _k3slr_system_k3s_parent _k3slr_system_k3s_partition_mm
local _k3slr_system_k3s_model _k3slr_system_k3s_serial _k3slr_system_k3s_wwn _k3slr_system_k3s_mm
local _k3slr_system_recovery_uuid _k3slr_system_recovery_partuuid _k3slr_system_recovery_type
local _k3slr_system_k3s_uuid _k3slr_system_k3s_partuuid _k3slr_system_k3s_type
local _k3slr_system_smart_raw _k3slr_system_smart _k3slr_system_loops
local _k3slr_system_mapper_metadata _k3slr_system_names _k3slr_system_snapshot
(( $# == 1 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_system_destination" _k3slr_system_ || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_partition \
/usr/bin/readlink -f -- "$K3SLR_RECOVERY_PARTITION_BY_ID" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_disk \
/usr/bin/readlink -f -- "$K3SLR_RECOVERY_DISK_BY_ID" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_partition \
/usr/bin/readlink -f -- "$K3SLR_K3S_PARTITION_BY_ID" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_disk \
/usr/bin/readlink -f -- "$K3SLR_K3S_DISK_BY_ID" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_parent \
/usr/bin/lsblk --noheadings --paths --output PKNAME -- "$_k3slr_system_recovery_partition" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_partition_mm \
/usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- "$_k3slr_system_recovery_partition" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_model \
/usr/bin/lsblk --noheadings --paths --output MODEL -- "$_k3slr_system_recovery_disk" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_serial \
/usr/bin/lsblk --noheadings --paths --output SERIAL -- "$_k3slr_system_recovery_disk" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_wwn \
/usr/bin/lsblk --noheadings --paths --output WWN -- "$_k3slr_system_recovery_disk" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_mm \
/usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- "$_k3slr_system_recovery_disk" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_parent \
/usr/bin/lsblk --noheadings --paths --output PKNAME -- "$_k3slr_system_k3s_partition" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_partition_mm \
/usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- "$_k3slr_system_k3s_partition" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_model \
/usr/bin/lsblk --noheadings --paths --output MODEL -- "$_k3slr_system_k3s_disk" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_serial \
/usr/bin/lsblk --noheadings --paths --output SERIAL -- "$_k3slr_system_k3s_disk" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_wwn \
/usr/bin/lsblk --noheadings --paths --output WWN -- "$_k3slr_system_k3s_disk" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_mm \
/usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- "$_k3slr_system_k3s_disk" || return 1
[[ "$_k3slr_system_recovery_parent" == "$_k3slr_system_recovery_disk" &&
"$_k3slr_system_k3s_parent" == "$_k3slr_system_k3s_disk" ]] || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_uuid \
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag UUID -- "$_k3slr_system_recovery_partition" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_partuuid \
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag PARTUUID -- "$_k3slr_system_recovery_partition" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_type \
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag TYPE -- "$_k3slr_system_recovery_partition" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_uuid \
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag UUID -- "$_k3slr_system_k3s_partition" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_partuuid \
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag PARTUUID -- "$_k3slr_system_k3s_partition" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_type \
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag TYPE -- "$_k3slr_system_k3s_partition" || return 1
_k3slr_capture_raw_success _k3slr_system_smart_raw \
/usr/bin/sudo --non-interactive -- /usr/sbin/smartctl -H -A "$_k3slr_system_recovery_disk" || return 1
_k3slr_parse_smart_evidence "$_k3slr_system_smart_raw" _k3slr_system_smart || return 1
_k3slr_capture_absent_record /usr/bin/sudo --non-interactive -- /usr/bin/findmnt \
--noheadings --raw --output SOURCE,FSTYPE,OPTIONS,ID,MAJ:MIN --mountpoint "$K3SLR_OUTER_MOUNT" || return 1
_k3slr_capture_absent_record /usr/bin/sudo --non-interactive -- /usr/bin/findmnt \
--noheadings --raw --output SOURCE,FSTYPE,OPTIONS,ID,MAJ:MIN --mountpoint "$K3SLR_INNER_MOUNT" || return 1
_k3slr_capture_absent_record /usr/bin/sudo --non-interactive -- /usr/bin/findmnt \
--noheadings --raw --output TARGET --source "$_k3slr_system_recovery_partition" || return 1
_k3slr_capture_raw_success _k3slr_system_loops /usr/bin/sudo --non-interactive -- \
/usr/sbin/losetup --list --noheadings --raw \
--output NAME,BACK-FILE,BACK-INO,BACK-MAJ:MIN,MAJ:MIN,OFFSET,SIZELIMIT || return 1
_k3slr_all_loops_exclude_backing_device "$_k3slr_system_loops" "$_k3slr_system_partition_mm" || return 1
_k3slr_capture_exact_one_line_record _k3slr_system_mapper_metadata \
/usr/bin/stat '--format=%F|%u|%g|%a' -- /dev/mapper || return 1
_k3slr_mapper_directory_is_safe "${_k3slr_system_mapper_metadata}"$'\n' || return 1
_k3slr_capture_empty_success /usr/bin/test ! -L /dev/mapper || return 1
_k3slr_capture_empty_success /usr/bin/test ! -e "/dev/mapper/${K3SLR_MAPPING_NAME}" || return 1
_k3slr_capture_empty_success /usr/bin/test ! -L "/dev/mapper/${K3SLR_MAPPING_NAME}" || return 1
_k3slr_capture_empty_success /usr/bin/test ! -e "/dev/mapper/${K3SLR_PROOF_MAPPING_NAME}" || return 1
_k3slr_capture_empty_success /usr/bin/test ! -L "/dev/mapper/${K3SLR_PROOF_MAPPING_NAME}" || return 1
_k3slr_capture_raw_success _k3slr_system_names \
/usr/bin/lsblk --noheadings --raw --output NAME,TYPE || return 1
_k3slr_mapper_name_scan_is_clear "$_k3slr_system_names" || return 1
_k3slr_capture_empty_success /usr/bin/sudo --non-interactive -- \
/usr/bin/ntfs-3g.probe --readwrite "$_k3slr_system_recovery_partition" || return 1
printf -v _k3slr_system_snapshot \
'canonical_partition=%s\npartition_major_minor=%s\ncanonical_parent_disk=%s\ndisk_major_minor=%s\nfilesystem_uuid=%s\npartuuid=%s\nfilesystem_type=%s\nmodel=%s\nserial=%s\nwwn=%s\ncanonical_k3s_partition=%s\nk3s_partition_major_minor=%s\ncanonical_k3s_disk=%s\nk3s_disk_major_minor=%s\nk3s_filesystem_uuid=%s\nk3s_partuuid=%s\nk3s_filesystem_type=%s\nk3s_model=%s\nk3s_serial=%s\nk3s_wwn=%s\nsmart_health=PASSED\nsmart_reallocated=0\nsmart_pending=0\nsmart_uncorrectable=0\nouter_mount=absent\ninner_mount=absent\nsource_mounts=absent\nloops=absent\nmapper_directory=physical-root-safe\nmapping_path=absent\nproof_mapping_path=absent\nmapping_names=absent\nntfs_probe=pass\n' \
"$_k3slr_system_recovery_partition" "$_k3slr_system_partition_mm" \
"$_k3slr_system_recovery_disk" "$_k3slr_system_recovery_mm" \
"$_k3slr_system_recovery_uuid" "$_k3slr_system_recovery_partuuid" \
"$_k3slr_system_recovery_type" "$_k3slr_system_recovery_model" \
"$_k3slr_system_recovery_serial" "$_k3slr_system_recovery_wwn" \
"$_k3slr_system_k3s_partition" "$_k3slr_system_k3s_partition_mm" \
"$_k3slr_system_k3s_disk" "$_k3slr_system_k3s_mm" \
"$_k3slr_system_k3s_uuid" "$_k3slr_system_k3s_partuuid" \
"$_k3slr_system_k3s_type" "$_k3slr_system_k3s_model" \
"$_k3slr_system_k3s_serial" "$_k3slr_system_k3s_wwn"
_k3slr_validate_prepackage_snapshot "$_k3slr_system_snapshot" || return 1
printf -v "$_k3slr_system_destination" '%s' "$_k3slr_system_snapshot"
}
_k3slr_capture_shell_function() {
local _k3slr_function_capture_stdout_name="${1-}"
local _k3slr_function_capture_stderr_name="${2-}"
local _k3slr_function_capture_rc_name="${3-}"
local _k3slr_function_capture_directory='' _k3slr_function_capture_stdout=''
local _k3slr_function_capture_stderr='' _k3slr_function_capture_status=0
shift 3 || return 1
(( $# > 0 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_function_capture_stdout_name" _k3slr_function_capture_ || return 1
_k3slr_output_name_is_safe "$_k3slr_function_capture_stderr_name" _k3slr_function_capture_ || return 1
_k3slr_output_name_is_safe "$_k3slr_function_capture_rc_name" _k3slr_function_capture_ || return 1
[[ "$_k3slr_function_capture_stdout_name" != "$_k3slr_function_capture_stderr_name" &&
"$_k3slr_function_capture_stdout_name" != "$_k3slr_function_capture_rc_name" &&
"$_k3slr_function_capture_stderr_name" != "$_k3slr_function_capture_rc_name" ]] || return 1
declare -F "$1" >/dev/null || return 1
_k3slr_capture_one_line _k3slr_function_capture_directory \
/usr/bin/mktemp --directory --tmpdir=/tmp 'k3slr-capture.XXXXXXXX' || return 1
_k3slr_capture_directory_is_safe "$_k3slr_function_capture_directory" || return 1
if "$@" >"${_k3slr_function_capture_directory}/stdout" \
2>"${_k3slr_function_capture_directory}/stderr"; then
_k3slr_function_capture_status=0
else
_k3slr_function_capture_status=$?
fi
if IFS= read -r -d '' _k3slr_function_capture_stdout \
<"${_k3slr_function_capture_directory}/stdout"; then
_k3slr_remove_capture_directory "$_k3slr_function_capture_directory" || :
return 1
fi
if IFS= read -r -d '' _k3slr_function_capture_stderr \
<"${_k3slr_function_capture_directory}/stderr"; then
_k3slr_remove_capture_directory "$_k3slr_function_capture_directory" || :
return 1
fi
_k3slr_remove_capture_directory "$_k3slr_function_capture_directory" || return 1
printf -v "$_k3slr_function_capture_stdout_name" '%s' "$_k3slr_function_capture_stdout"
printf -v "$_k3slr_function_capture_stderr_name" '%s' "$_k3slr_function_capture_stderr"
printf -v "$_k3slr_function_capture_rc_name" '%s' "$_k3slr_function_capture_status"
}
_k3slr_run_ntfsinfo_hex_pipeline() {
local _k3slr_pipeline_partition="${1-}" _k3slr_pipeline_program
local -a _k3slr_pipeline_statuses=()
(( $# == 1 )) || return 1
[[ "$_k3slr_pipeline_partition" =~ ^/dev/[A-Za-z0-9._/-]+$ ]] || return 1
_k3slr_pipeline_program=$'{\n for (i = 1; i <= NF; i++) {\n if ($i !~ /^[0-9a-f]{2}$/ || ++bytes > 65536) {\n bad = 1\n exit\n }\n hex = hex $i\n }\n}\nEND {\n if (bad || bytes == 0) exit 1\n print hex\n}'
_k3slr_command /usr/bin/sudo --non-interactive -- \
/usr/bin/ntfsinfo --mft --notime "$_k3slr_pipeline_partition" |
_k3slr_command /usr/bin/od -An -v -tx1 |
_k3slr_command /usr/bin/mawk "$_k3slr_pipeline_program"
_k3slr_pipeline_statuses=("${PIPESTATUS[@]}")
(( ${#_k3slr_pipeline_statuses[@]} == 3 &&
_k3slr_pipeline_statuses[0] == 0 &&
_k3slr_pipeline_statuses[1] == 0 &&
_k3slr_pipeline_statuses[2] == 0 ))
}
_k3slr_capture_ntfsinfo_hex() {
local _k3slr_ntfs_capture_destination="${1-}" _k3slr_ntfs_capture_partition="${2-}"
local _k3slr_ntfs_capture_stdout='' _k3slr_ntfs_capture_stderr='' _k3slr_ntfs_capture_rc=''
(( $# == 2 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_ntfs_capture_destination" _k3slr_ntfs_capture_ || return 1
_k3slr_capture_shell_function _k3slr_ntfs_capture_stdout _k3slr_ntfs_capture_stderr \
_k3slr_ntfs_capture_rc _k3slr_run_ntfsinfo_hex_pipeline "$_k3slr_ntfs_capture_partition" || return 1
[[ "$_k3slr_ntfs_capture_rc" == 0 && -z "$_k3slr_ntfs_capture_stderr" &&
"$_k3slr_ntfs_capture_stdout" =~ ^[0-9a-f]+$'\n'$ ]] || return 1
(( ${#_k3slr_ntfs_capture_stdout} > 1 && ${#_k3slr_ntfs_capture_stdout} <= 131073 &&
(${#_k3slr_ntfs_capture_stdout} - 1) % 2 == 0 )) || return 1
printf -v "$_k3slr_ntfs_capture_destination" '%s' "$_k3slr_ntfs_capture_stdout"
}
_k3slr_capture_one_line() {
local _k3slr_capture_framed _k3slr_capture_record _k3slr_capture_value
_k3slr_output_name_is_safe "$1" _k3slr_capture_ || return 1
_k3slr_capture_framed="$(
_k3slr_command "${@:2}"
_k3slr_capture_command_rc=$?
printf '\034%s' "$_k3slr_capture_command_rc"
)" || return 1
[[ "$_k3slr_capture_framed" == *$'\0340' ]] || return 1
_k3slr_capture_record="${_k3slr_capture_framed%$'\0340'}"
[[ "$_k3slr_capture_record" == *$'\n' ]] || return 1
_k3slr_capture_value="${_k3slr_capture_record%$'\n'}"
[[ -n "$_k3slr_capture_value" && "$_k3slr_capture_value" != *$'\n'* &&
"$_k3slr_capture_value" != *$'\r'* && "$_k3slr_capture_value" != *$'\034'* ]] || return 1
printf -v "$1" '%s' "$_k3slr_capture_value"
}
_k3slr_trusted_root_executable() {
local binary="$1" metadata uid mode kind group_digit other_digit
[[ "$binary" == /* ]] || return 1
_k3slr_command /usr/bin/test -f "$binary" >/dev/null 2>&1 || return 1
_k3slr_command /usr/bin/test ! -L "$binary" >/dev/null 2>&1 || return 1
_k3slr_command /usr/bin/test -x "$binary" >/dev/null 2>&1 || return 1
_k3slr_capture_one_line metadata /usr/bin/stat --format='%u|%a|%F' -- "$binary" 2>/dev/null || return 1
IFS='|' read -r uid mode kind <<<"$metadata"
[[ "$uid" == 0 && "$mode" =~ ^[0-7]{3,4}$ && "$kind" == 'regular file' ]] || return 1
group_digit="${mode: -2:1}"
other_digit="${mode: -1}"
(( (10#$group_digit & 2) == 0 && (10#$other_digit & 2) == 0 ))
}
_k3slr_pin_keepass_database() {
local database="$1" destination_name="$2" expected current canonical metadata uid
local component index=0 lineage=''
local -a components=()
[[ -n "${K3SLR_OUTER_MOUNT:-}" && -n "${K3SLR_DATABASE_RELATIVE:-}" ]] || return 1
expected="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}"
[[ "$database" == "$expected" ]] || return 1
_k3slr_capture_one_line uid /usr/bin/id -u || return 1
[[ "$uid" =~ ^[0-9]+$ ]] || return 1
current="$K3SLR_OUTER_MOUNT"
IFS='/' read -r -a components <<<"$K3SLR_DATABASE_RELATIVE"
for component in '' "${components[@]}"; do
if (( index > 0 )); then current="${current}/${component}"; fi
_k3slr_command /usr/bin/test ! -L "$current" >/dev/null 2>&1 || return 1
_k3slr_capture_one_line canonical /usr/bin/readlink -e -- "$current" 2>/dev/null || return 1
[[ "$canonical" == "$current" ]] || return 1
_k3slr_capture_one_line metadata /usr/bin/stat --format='%d:%i|%F|%u|%a' -- "$current" 2>/dev/null || return 1
if (( index < ${#components[@]} )); then
[[ "$metadata" == *'|directory|'* ]] || return 1
else
[[ "$current" == "$database" && "$metadata" == *'|regular file|'* &&
"$metadata" == *"|${uid}|600" ]] || return 1
fi
lineage+="${metadata%|*|*},"
index=$((index + 1))
done
printf -v "$destination_name" '%s' "${lineage%,}"
}
_k3slr_keepass_database_lineage_matches() {
local database="$1" expected_snapshot="$2" current_snapshot=''
_k3slr_pin_keepass_database "$database" current_snapshot || return 1
[[ "$current_snapshot" == "$expected_snapshot" ]]
}
_k3slr_parse_keepass_attachment_listing() {
local _k3slr_attachment_record="${1-}" _k3slr_attachment_name="${2-}"
local _k3slr_attachment_destination="${3-}" _k3slr_attachment_prefix=''
local _k3slr_attachment_size=''
(( $# == 3 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_attachment_destination" _k3slr_attachment_ || return 1
[[ "$_k3slr_attachment_name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,254}$ ]] || return 1
(( ${#_k3slr_attachment_record} >= 1 && ${#_k3slr_attachment_record} <= 8192 )) || return 1
[[ "$_k3slr_attachment_record" != *$'\r'* &&
"$_k3slr_attachment_record" == *$'\n' ]] || return 1
if [[ "$_k3slr_attachment_record" == "${K3SLR_KEEPASS_ENTRY}"$'\n\nNo attachments present.\n' ]]; then
printf -v "$_k3slr_attachment_destination" '%s' absent
return 0
fi
_k3slr_attachment_prefix="${K3SLR_KEEPASS_ENTRY}"$'\n\nAttachments:\n '"${_k3slr_attachment_name} ("
[[ "$_k3slr_attachment_record" == "$_k3slr_attachment_prefix"* ]] || return 1
_k3slr_attachment_size="${_k3slr_attachment_record#"$_k3slr_attachment_prefix"}"
[[ "$_k3slr_attachment_size" == *$'\n' ]] || return 1
_k3slr_attachment_size="${_k3slr_attachment_size%$'\n'}"
[[ "$_k3slr_attachment_size" =~ ^[0-9]+\.[0-9]\ (B|KiB|MiB|GiB)\)$ ]] || return 1
printf -v "$_k3slr_attachment_destination" '%s' present
}
_k3slr_capture_keepass_attachment_listing() {
local _k3slr_keepass_capture_destination="${1-}"
local _k3slr_keepass_capture_stdout_path="${2-}"
local _k3slr_keepass_capture_stderr_path="${3-}"
local _k3slr_keepass_capture_stdout_fd='' _k3slr_keepass_capture_stderr_fd=''
local _k3slr_keepass_capture_size='' _k3slr_keepass_capture_record=''
local _k3slr_keepass_capture_command_rc=0 _k3slr_keepass_capture_cleanup_rc=0
local _k3slr_keepass_capture_had_noclobber=false
shift 3 || return 1
(( $# > 0 )) || return 1
_k3slr_output_name_is_safe "$_k3slr_keepass_capture_destination" \
_k3slr_keepass_capture_ || return 1
declare -F "$1" >/dev/null || return 1
[[ "$_k3slr_keepass_capture_stdout_path" =~ ^/[A-Za-z0-9._/-]+$ &&
"$_k3slr_keepass_capture_stderr_path" =~ ^/[A-Za-z0-9._/-]+$ &&
"$_k3slr_keepass_capture_stdout_path" != *'//'* &&
"$_k3slr_keepass_capture_stderr_path" != *'//'* &&
"$_k3slr_keepass_capture_stdout_path" != *'/../'* &&
"$_k3slr_keepass_capture_stderr_path" != *'/../'* &&
"$_k3slr_keepass_capture_stdout_path" != "$_k3slr_keepass_capture_stderr_path" &&
! -e "$_k3slr_keepass_capture_stdout_path" && ! -L "$_k3slr_keepass_capture_stdout_path" &&
! -e "$_k3slr_keepass_capture_stderr_path" && ! -L "$_k3slr_keepass_capture_stderr_path" ]] || return 1
[[ -o noclobber ]] && _k3slr_keepass_capture_had_noclobber=true
set -o noclobber
if ! exec {_k3slr_keepass_capture_stdout_fd}>"$_k3slr_keepass_capture_stdout_path"; then
"$_k3slr_keepass_capture_had_noclobber" || set +o noclobber
return 1
fi
if ! exec {_k3slr_keepass_capture_stderr_fd}>"$_k3slr_keepass_capture_stderr_path"; then
exec {_k3slr_keepass_capture_stdout_fd}>&-
"$_k3slr_keepass_capture_had_noclobber" || set +o noclobber
_k3slr_command /usr/bin/rm -- "$_k3slr_keepass_capture_stdout_path" >/dev/null 2>&1 || :
return 1
fi
"$_k3slr_keepass_capture_had_noclobber" || set +o noclobber
if "$@" >&"$_k3slr_keepass_capture_stdout_fd" 2>&"$_k3slr_keepass_capture_stderr_fd"; then
_k3slr_keepass_capture_command_rc=0
else
_k3slr_keepass_capture_command_rc=$?
fi
exec {_k3slr_keepass_capture_stdout_fd}>&-
exec {_k3slr_keepass_capture_stderr_fd}>&-
if (( _k3slr_keepass_capture_command_rc == 0 )) &&
[[ -f "$_k3slr_keepass_capture_stdout_path" && ! -L "$_k3slr_keepass_capture_stdout_path" &&
-f "$_k3slr_keepass_capture_stderr_path" && ! -L "$_k3slr_keepass_capture_stderr_path" &&
! -s "$_k3slr_keepass_capture_stderr_path" ]] &&
_k3slr_capture_one_line _k3slr_keepass_capture_size /usr/bin/stat \
--format='%s' -- "$_k3slr_keepass_capture_stdout_path" &&
_k3slr_uint "$_k3slr_keepass_capture_size" &&
(( 10#$_k3slr_keepass_capture_size >= 1 && 10#$_k3slr_keepass_capture_size <= 8192 )); then
if IFS= read -r -d '' _k3slr_keepass_capture_record \
<"$_k3slr_keepass_capture_stdout_path"; then
_k3slr_keepass_capture_command_rc=1
fi
else
_k3slr_keepass_capture_command_rc=1
fi
_k3slr_command /usr/bin/rm -- \
"$_k3slr_keepass_capture_stdout_path" \
"$_k3slr_keepass_capture_stderr_path" >/dev/null 2>&1 ||
_k3slr_keepass_capture_cleanup_rc=1
(( _k3slr_keepass_capture_command_rc == 0 && _k3slr_keepass_capture_cleanup_rc == 0 )) || return 1
printf -v "$_k3slr_keepass_capture_destination" '%s' "$_k3slr_keepass_capture_record"
}
_k3slr_keepass_password_stdout() {
local database="${1-}" entry="${2-}"
(( $# == 2 )) || return 1
_k3slr_xtrace_is_disabled || return 1
[[ "$entry" == "$K3SLR_KEEPASS_ENTRY" ]] || return 1
_k3slr_command /usr/bin/test -t 0 >/dev/null 2>&1 || return 1
_k3slr_command /usr/bin/keepassxc-cli show --show-protected \
--attributes Password "$database" "$entry"
}
_k3slr_validated_keepass_stdout() {
local database="$1" entry="$2" stage_fd='' stage_pid='' stage_rc
exec {stage_fd}< <(
set -o pipefail
_k3slr_keepass_password_stdout "$database" "$entry" |
_k3slr_command /usr/bin/od -An -v -tx1 |
_k3slr_command /usr/bin/mawk '
function digit(c) { return index("0123456789abcdef", c) - 1 }
function byte_value(h) { return 16 * digit(substr(h, 1, 1)) + digit(substr(h, 2, 1)) }
{
for (i=1; i<=NF; i++) {
n++; byte=$i
if (n <= 40) {
if (byte ~ /^3[0-9]$/) numeric=1
else if (byte ~ /^(4[1-9a-f]|5[0-9a])$/) uppercase=1
else if (byte ~ /^(6[1-9a-f]|7[0-9a])$/) lowercase=1
else invalid=1
if (!invalid) value=value sprintf("%c", byte_value(byte))
} else if (n == 41) {
if (byte != "0a") invalid=1
} else invalid=1
}
}
END {
if (n == 41 && !invalid && lowercase && uppercase && numeric) printf "%s\n", value
else exit 1
}
'
)
stage_pid=$!
_k3slr_lifecycle_boundary validator-stage-published "$stage_pid"
if wait "$stage_pid"; then stage_rc=0; else stage_rc=$?; fi
if (( stage_rc == 0 )); then
_k3slr_command /usr/bin/dd iflag=fullblock bs=41 count=1 status=none <&"$stage_fd" || stage_rc=$?
fi
exec {stage_fd}<&-
return "$stage_rc"
}
_k3slr_cached_sudo_is_available() {
_k3slr_trusted_root_executable /usr/bin/sudo || return 1
_k3slr_command /usr/bin/sudo --non-interactive --validate >/dev/null 2>&1
}
_k3slr_loop_device_is_valid() {
local loop_device="$1" canonical kind
[[ "$loop_device" =~ ^/dev/loop(0|[1-9][0-9]*)$ ]] || return 1
_k3slr_command /usr/bin/test -b "$loop_device" >/dev/null 2>&1 || return 1
_k3slr_command /usr/bin/test ! -L "$loop_device" >/dev/null 2>&1 || return 1
_k3slr_capture_one_line canonical /usr/bin/readlink -e -- "$loop_device" 2>/dev/null || return 1
[[ "$canonical" == "$loop_device" ]] || return 1
_k3slr_capture_one_line kind /usr/bin/stat --format='%F' -- "$loop_device" 2>/dev/null || return 1
[[ "$kind" == 'block special file' ]]
}
_k3slr_mapping_name_is_allowed() {
local mapping_name="$1"
[[ -n "${K3SLR_MAPPING_NAME:-}" && -n "${K3SLR_PROOF_MAPPING_NAME:-}" ]] || return 1
[[ "$mapping_name" == "$K3SLR_MAPPING_NAME" || "$mapping_name" == "$K3SLR_PROOF_MAPPING_NAME" ]]
}
_k3slr_cryptsetup_format_stdin() {
local loop_device="${1-}"
(( $# == 1 )) || return 1
_k3slr_xtrace_is_disabled || return 1
_k3slr_command /usr/bin/sudo --non-interactive -- /usr/sbin/cryptsetup luksFormat \
--batch-mode --type luks2 --key-file=- "$loop_device"
}
_k3slr_cryptsetup_open_stdin() {
local loop_device="${1-}" mapping_name="${2-}"
(( $# == 2 )) || return 1
_k3slr_xtrace_is_disabled || return 1
_k3slr_command /usr/bin/sudo --non-interactive -- /usr/sbin/cryptsetup open \
--type luks2 --key-file=- "$loop_device" "$mapping_name"
}
_k3slr_cryptsetup_open_readonly_stdin() {
local loop_device="${1-}" mapping_name="${2-}"
(( $# == 2 )) || return 1
_k3slr_xtrace_is_disabled || return 1
_k3slr_command /usr/bin/sudo --non-interactive -- /usr/sbin/cryptsetup open \
--readonly --type luks2 --key-file=- "$loop_device" "$mapping_name"
}
_k3slr_password_pipe_preflight() {
local destination_name="$1" database="$2" entry="$3" loop_device="$4" mapping_name="${5-}"
local snapshot='' binary
_k3slr_xtrace_is_disabled || return 1
_k3slr_command /usr/bin/test -t 0 >/dev/null 2>&1 || return 1
[[ "$entry" == "$K3SLR_KEEPASS_ENTRY" ]] || return 1
for binary in /usr/bin/test /usr/bin/stat /usr/bin/readlink /usr/bin/id \
/usr/bin/od /usr/bin/mawk /usr/bin/dd /usr/bin/ps /usr/bin/sleep /usr/bin/sudo \
/usr/bin/keepassxc-cli /usr/sbin/cryptsetup; do
_k3slr_trusted_root_executable "$binary" || return 1
done
_k3slr_pin_keepass_database "$database" snapshot || return 1
_k3slr_loop_device_is_valid "$loop_device" || return 1
if [[ -n "$mapping_name" ]]; then _k3slr_mapping_name_is_allowed "$mapping_name" || return 1; fi
_k3slr_cached_sudo_is_available || return 1
printf -v "$destination_name" '%s' "$snapshot"
}
_k3slr_process_group_for_pid() {
local _k3slr_group_capture='' _k3slr_group_resolved=''
_k3slr_output_name_is_safe "$1" _k3slr_group_ || return 1
[[ "$2" =~ ^[1-9][0-9]*$ ]] || return 1
_k3slr_capture_one_line _k3slr_group_capture /usr/bin/ps -o pgid= -p "$2" || return 1
_k3slr_group_resolved="${_k3slr_group_capture//[[:space:]]/}"
[[ "$_k3slr_group_resolved" =~ ^[1-9][0-9]*$ ]] || return 1
printf -v "$1" '%s' "$_k3slr_group_resolved"
}
_k3slr_read_exact_record() {
local _k3slr_exact_value='' _k3slr_exact_trailing='' _k3slr_exact_fd=''
_k3slr_output_name_is_safe "$1" _k3slr_exact_ || return 1
exec {_k3slr_exact_fd}<"$2" || return 1
if ! IFS= read -r _k3slr_exact_value <&"$_k3slr_exact_fd"; then
exec {_k3slr_exact_fd}<&-
return 1
fi
if IFS= read -r _k3slr_exact_trailing <&"$_k3slr_exact_fd" || [[ -n "$_k3slr_exact_trailing" ]]; then
exec {_k3slr_exact_fd}<&-
return 1
fi
exec {_k3slr_exact_fd}<&-
[[ -n "$_k3slr_exact_value" && "$_k3slr_exact_value" != *$'\r'* &&
"$_k3slr_exact_value" != *$'\n'* ]] || return 1
printf -v "$1" '%s' "$_k3slr_exact_value"
}
_k3slr_proc_stat_record() {
local _k3slr_stat_value=''
_k3slr_output_name_is_safe "$1" _k3slr_stat_ || return 1
[[ "$2" =~ ^[1-9][0-9]*$ ]] || return 1
_k3slr_read_exact_record _k3slr_stat_value "/proc/${2}/stat" || return 1
printf -v "$1" '%s' "$_k3slr_stat_value"
}
_k3slr_parse_proc_stat_record() {
local _k3slr_parse_suffix='' _k3slr_parse_state='' _k3slr_parse_parent=''
local _k3slr_parse_group='' _k3slr_parse_start='' _k3slr_parse_prefix=''
local _k3slr_parse_after_comm=''
local -a _k3slr_parse_fields=()
[[ "$1" != "$2" ]] &&
_k3slr_output_name_is_safe "$1" _k3slr_parse_ &&
_k3slr_output_name_is_safe "$2" _k3slr_parse_ || return 1
[[ "$3" =~ ^[1-9][0-9]*$ ]] || return 1
[[ -n "$4" && "$4" != *$'\r'* && "$4" != *$'\n'* ]] || return 1
_k3slr_parse_prefix="${4%)*}"
_k3slr_parse_after_comm="${4##*)}"
[[ "$_k3slr_parse_prefix" != "$4" &&
"$_k3slr_parse_prefix" == "${3} ("* &&
"$_k3slr_parse_after_comm" == ' '* ]] || return 1
_k3slr_parse_suffix="${_k3slr_parse_after_comm:1}"
[[ -n "$_k3slr_parse_suffix" && "$_k3slr_parse_suffix" != [[:space:]]* ]] || return 1
read -r -a _k3slr_parse_fields <<<"$_k3slr_parse_suffix"
(( ${#_k3slr_parse_fields[@]} >= 20 )) || return 1
_k3slr_parse_state="${_k3slr_parse_fields[0]}"
_k3slr_parse_parent="${_k3slr_parse_fields[1]}"
_k3slr_parse_group="${_k3slr_parse_fields[2]}"
_k3slr_parse_start="${_k3slr_parse_fields[19]}"
[[ "$_k3slr_parse_state" =~ ^[RSDZTWtXxKPI]$ &&
"$_k3slr_parse_parent" =~ ^(0|[1-9][0-9]*)$ &&
"$_k3slr_parse_group" =~ ^[1-9][0-9]*$ &&
"$_k3slr_parse_start" =~ ^[1-9][0-9]*$ ]] || return 1
printf -v "$1" '%s' "${3}|${_k3slr_parse_start}|${_k3slr_parse_parent}|${_k3slr_parse_group}"
printf -v "$2" '%s' "$_k3slr_parse_state"
}
_k3slr_process_record() {
local _k3slr_record_value='' _k3slr_record_identity='' _k3slr_record_state=''
[[ "$1" != "$2" ]] &&
_k3slr_output_name_is_safe "$1" _k3slr_record_ &&
_k3slr_output_name_is_safe "$2" _k3slr_record_ || return 1
_k3slr_proc_stat_record _k3slr_record_value "$3" || return 1
_k3slr_parse_proc_stat_record _k3slr_record_identity _k3slr_record_state \
"$3" "$_k3slr_record_value" || return 1
printf -v "$1" '%s' "$_k3slr_record_identity"
printf -v "$2" '%s' "$_k3slr_record_state"
}
_k3slr_process_identity() {
local _k3slr_identity_value='' _k3slr_identity_state=''
_k3slr_output_name_is_safe "$1" _k3slr_identity_ || return 1
_k3slr_process_record _k3slr_identity_value _k3slr_identity_state "$2" || return 1
printf -v "$1" '%s' "$_k3slr_identity_value"
}
_k3slr_wait_for_stopped_group() {
local _k3slr_stopped_current_identity='' _k3slr_stopped_state='' _k3slr_stopped_attempt
local _k3slr_stopped_group_id='' _k3slr_stopped_identity_pid='' _k3slr_stopped_start_time=''
local _k3slr_stopped_parent_pid='' _k3slr_stopped_identity_group=''
[[ "$1" != "$2" ]] &&
_k3slr_output_name_is_safe "$1" _k3slr_stopped_ &&
_k3slr_output_name_is_safe "$2" _k3slr_stopped_ || return 1
for ((_k3slr_stopped_attempt=0; _k3slr_stopped_attempt<200; _k3slr_stopped_attempt++)); do
if _k3slr_process_record _k3slr_stopped_current_identity _k3slr_stopped_state "$3"; then
IFS='|' read -r _k3slr_stopped_identity_pid _k3slr_stopped_start_time \
_k3slr_stopped_parent_pid _k3slr_stopped_identity_group <<<"$_k3slr_stopped_current_identity"
if [[ "$_k3slr_stopped_state" =~ ^[Tt]$ && "$_k3slr_stopped_parent_pid" == "$BASHPID" ]]; then
printf -v "$2" '%s' "$_k3slr_stopped_current_identity"
_k3slr_process_group_for_pid _k3slr_stopped_group_id "$3" || return 1
[[ "$_k3slr_stopped_group_id" == "$_k3slr_stopped_identity_group" ]] || return 1
printf -v "$1" '%s' "$_k3slr_stopped_group_id"
return 0
fi
fi
_k3slr_command /usr/bin/sleep 0.01 || return 1
done
return 1
}
_k3slr_group_exists() {
local group_id="$1"
[[ "$group_id" =~ ^[1-9][0-9]*$ ]] || return 2
kill -0 -- "-${group_id}" >/dev/null 2>&1
}
_k3slr_wait_for_group_exit() {
local group_id="$1" maximum_attempts="${2:-100}" attempt
[[ "$maximum_attempts" =~ ^[1-9][0-9]*$ ]] || return 1
for ((attempt=0; attempt<maximum_attempts; attempt++)); do
_k3slr_group_exists "$group_id" || return 0
_k3slr_command /usr/bin/sleep 0.01 || return 1
done
return 1
}
_k3slr_group_authority_is_current() {
local child_pid="$1" group_id="$2" expected_identity="$3"
local current_identity='' state='' caller_group='' observed_group=''
local identity_pid='' start_time='' parent_pid='' identity_group=''
_k3slr_process_record current_identity state "$child_pid" || return 1
[[ "$current_identity" == "$expected_identity" ]] || return 1
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
[[ "$identity_pid" == "$child_pid" && "$parent_pid" == "$BASHPID" &&
"$identity_group" == "$group_id" ]] || return 1
_k3slr_process_group_for_pid caller_group "$BASHPID" 2>/dev/null || return 1
_k3slr_process_group_for_pid observed_group "$child_pid" 2>/dev/null || return 1
[[ "$observed_group" == "$group_id" && "$group_id" != "$caller_group" ]]
}
_k3slr_signal_pid() {
local signal_name="$1" child_pid="$2" expected_identity="$3" expected_parent="$4" expected_group="${5-}"
local current_identity='' state='' identity_pid='' start_time='' parent_pid='' identity_group=''
local caller_group='' observed_group=''
[[ "$signal_name" =~ ^(TERM|CONT|KILL)$ ]] || return 1
_k3slr_process_record current_identity state "$child_pid" || return 1
[[ "$current_identity" == "$expected_identity" ]] || return 1
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
[[ "$identity_pid" == "$child_pid" && "$parent_pid" == "$expected_parent" ]] || return 1
if [[ -n "$expected_group" ]]; then
[[ "$identity_group" == "$expected_group" ]] || return 1
_k3slr_process_group_for_pid caller_group "$BASHPID" 2>/dev/null || return 1
_k3slr_process_group_for_pid observed_group "$child_pid" 2>/dev/null || return 1
[[ "$observed_group" == "$expected_group" && "$expected_group" != "$caller_group" ]] || return 1
fi
kill -"$signal_name" "$child_pid" >/dev/null 2>&1
}
_k3slr_signal_group() {
local signal_name="$1" child_pid="$2" group_id="$3" expected_identity="$4"
[[ "$signal_name" =~ ^(TERM|CONT|KILL)$ ]] || return 1
_k3slr_group_authority_is_current "$child_pid" "$group_id" "$expected_identity" || return 1
kill -"$signal_name" -- "-${group_id}" >/dev/null 2>&1
}
_k3slr_signal_published_pid() {
local signal_name="$1" child_pid="$2"
[[ "$signal_name" == KILL && "$child_pid" =~ ^[1-9][0-9]*$ ]] || return 1
kill -KILL "$child_pid" >/dev/null 2>&1
}
_k3slr_wait_child() {
wait "$1" >/dev/null 2>&1
}
_k3slr_terminate_published_child() {
local child_pid="$1" wait_rc
[[ "$child_pid" =~ ^[1-9][0-9]*$ ]] || return 1
_k3slr_signal_published_pid KILL "$child_pid" || {
kill -0 "$child_pid" >/dev/null 2>&1 && return 1
}
if _k3slr_wait_child "$child_pid"; then wait_rc=0; else wait_rc=$?; fi
(( wait_rc != 127 )) || return 1
! kill -0 "$child_pid" >/dev/null 2>&1
}
_k3slr_continue_owned_child() {
local child_pid="$1" group_id="$2" expected_identity="$3"
_k3slr_signal_pid CONT "$child_pid" "$expected_identity" "$BASHPID" "$group_id"
}
_k3slr_terminate_direct_stopped_child() {
local child_pid="$1" expected_identity="$2" current_identity='' state='' wait_rc
_k3slr_process_record current_identity state "$child_pid" || return 1
local identity_pid='' start_time='' parent_pid='' identity_group=''
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
[[ "$current_identity" == "$expected_identity" && "$state" =~ ^[Tt]$ &&
"$identity_pid" == "$child_pid" && "$parent_pid" == "$BASHPID" ]] || return 1
_k3slr_signal_pid TERM "$child_pid" "$expected_identity" "$BASHPID" || return 1
if kill -0 "$child_pid" >/dev/null 2>&1; then
_k3slr_process_record current_identity state "$child_pid" || return 1
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
[[ "$current_identity" == "$expected_identity" && "$identity_pid" == "$child_pid" &&
"$parent_pid" == "$BASHPID" ]] || return 1
_k3slr_signal_pid CONT "$child_pid" "$expected_identity" "$BASHPID" || return 1
fi
for ((wait_rc=0; wait_rc<100; wait_rc++)); do
if ! kill -0 "$child_pid" >/dev/null 2>&1; then break; fi
_k3slr_command /usr/bin/sleep 0.01 || return 1
done
if kill -0 "$child_pid" >/dev/null 2>&1; then
_k3slr_process_record current_identity state "$child_pid" || return 1
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
[[ "$current_identity" == "$expected_identity" && "$identity_pid" == "$child_pid" &&
"$parent_pid" == "$BASHPID" ]] || return 1
_k3slr_signal_pid KILL "$child_pid" "$expected_identity" "$BASHPID" || return 1
fi
if _k3slr_wait_child "$child_pid"; then wait_rc=0; else wait_rc=$?; fi
(( wait_rc != 127 )) || return 1
! _k3slr_process_identity current_identity "$child_pid" 2>/dev/null
}
_k3slr_terminate_and_reap() {
local child_pid="$1" group_id="${2-}" expected_identity="${3-}"
local caller_group='' observed_group='' current_identity='' state='' wait_rc
local identity_pid='' start_time='' parent_pid='' identity_group=''
[[ "$child_pid" =~ ^[1-9][0-9]*$ &&
( -z "$group_id" || "$group_id" =~ ^[1-9][0-9]*$ ) &&
-n "$expected_identity" ]] || return 1
_k3slr_process_record current_identity state "$child_pid" || return 1
[[ "$current_identity" == "$expected_identity" ]] || return 1
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
[[ "$identity_pid" == "$child_pid" && "$parent_pid" == "$BASHPID" ]] || return 1
if [[ -z "$group_id" ]] ||
! _k3slr_process_group_for_pid caller_group "$BASHPID" 2>/dev/null ||
! _k3slr_process_group_for_pid observed_group "$child_pid" 2>/dev/null ||
[[ "$observed_group" != "$identity_group" || "$observed_group" != "$group_id" ||
"$group_id" == "$caller_group" ]]; then
_k3slr_terminate_direct_stopped_child "$child_pid" "$expected_identity"
return
fi
_k3slr_signal_group TERM "$child_pid" "$group_id" "$expected_identity" || {
_k3slr_group_exists "$group_id" && return 1
}
if _k3slr_group_exists "$group_id"; then
if _k3slr_group_authority_is_current "$child_pid" "$group_id" "$expected_identity"; then
_k3slr_signal_group CONT "$child_pid" "$group_id" "$expected_identity" || {
_k3slr_group_exists "$group_id" && return 1
}
else
_k3slr_wait_for_group_exit "$group_id" || return 1
fi
fi
if ! _k3slr_wait_for_group_exit "$group_id" 25; then
if _k3slr_group_authority_is_current "$child_pid" "$group_id" "$expected_identity"; then
_k3slr_signal_group KILL "$child_pid" "$group_id" "$expected_identity" || {
_k3slr_group_exists "$group_id" && return 1
}
else
_k3slr_wait_for_group_exit "$group_id" || return 1
fi
fi
if _k3slr_wait_child "$child_pid"; then wait_rc=0; else wait_rc=$?; fi
(( wait_rc != 127 )) || return 1
_k3slr_wait_for_group_exit "$group_id"
}
_k3slr_lifecycle_boundary() {
:
}
_k3slr_run_password_pipe() (
local operation="$1" database="$2" entry="$3" loop_device="$4" mapping_name="${5-}"
local database_snapshot='' producer_pid='' producer_pgid='' producer_source_fd='' pipe_fd='' tty_fd=''
local producer_identity='' stopped_identity='' current_identity='' caller_pgid='' monitor_enabled=false producer_rc consumer_rc
local lifecycle_state='empty' pending_signal='' pending_status=''
_k3slr_password_pipe_preflight database_snapshot "$database" "$entry" "$loop_device" "$mapping_name" || return 1
_k3slr_process_group_for_pid caller_pgid "$BASHPID" || return 1
_k3slr_password_pipe_cleanup() {
local cleanup_rc=0
if "$monitor_enabled"; then set +m; monitor_enabled=false; fi
if [[ -n "$tty_fd" ]]; then exec {tty_fd}<&- 2>/dev/null || true; tty_fd=''; fi
if [[ -n "$pipe_fd" ]]; then exec {pipe_fd}<&- 2>/dev/null || true; pipe_fd=''; fi
if [[ -n "$producer_source_fd" ]]; then exec {producer_source_fd}<&- 2>/dev/null || true; producer_source_fd=''; fi
if [[ -n "$producer_pid" ]]; then
if [[ "$lifecycle_state" == published ]]; then
_k3slr_terminate_published_child "$producer_pid" || cleanup_rc=$?
elif [[ -n "$producer_identity" ]]; then
_k3slr_terminate_and_reap "$producer_pid" "$producer_pgid" "$producer_identity" || cleanup_rc=$?
else
cleanup_rc=1
fi
if (( cleanup_rc == 0 )); then
producer_pid=''
producer_pgid=''
producer_identity=''
lifecycle_state='reaped'
fi
fi
return "$cleanup_rc"
}
_k3slr_password_pipe_signal() {
if [[ "$1" == TERM || "$pending_signal" != TERM ]]; then
pending_signal="$1"
pending_status="$2"
fi
}
_k3slr_password_pipe_pending_checkpoint() {
local signal_status cleanup_rc=0
[[ -n "$pending_signal" ]] || return 0
signal_status="$pending_status"
if [[ -n "$producer_pid" ]]; then
[[ -n "$producer_identity" ]] || return 1
_k3slr_password_pipe_cleanup || cleanup_rc=$?
fi
(( cleanup_rc == 0 )) || return 1
return "$signal_status"
}
_k3slr_password_pipe_exit_cleanup() {
local original_status="$1" cleanup_rc=0
trap - EXIT
_k3slr_password_pipe_cleanup || cleanup_rc=$?
(( cleanup_rc == 0 )) || exit 1
exit "$original_status"
}
trap '_k3slr_password_pipe_signal INT 130' INT
trap '_k3slr_password_pipe_signal TERM 143' TERM
trap '_k3slr_password_pipe_exit_cleanup "$?"' EXIT
exec {tty_fd}<&0 || return 1
set -m
monitor_enabled=true
lifecycle_state='launching'
_k3slr_lifecycle_boundary coproc-launch-before
_k3slr_password_pipe_pending_checkpoint || return $?
coproc K3SLR_PASSWORD_PRODUCER {
producer_cancel=''
trap '[[ "$producer_cancel" == TERM ]] || producer_cancel=INT' INT
trap 'producer_cancel=TERM' TERM
kill -STOP "$BASHPID"
producer_worker_rc=143
if [[ -z "$producer_cancel" ]]; then
if _k3slr_validated_keepass_stdout "$database" "$entry" <&"$tty_fd"; then
producer_worker_rc=0
else
producer_worker_rc=$?
fi
fi
while [[ -n "$producer_cancel" ]]; do
_k3slr_command /usr/bin/sleep 0.01 || :
done
exit "$producer_worker_rc"
}
producer_pid="$K3SLR_PASSWORD_PRODUCER_PID"
lifecycle_state='published'
_k3slr_lifecycle_boundary pid-published "$producer_pid"
if ! _k3slr_process_identity producer_identity "$producer_pid"; then
_k3slr_password_pipe_cleanup || return 1
if [[ -n "$pending_signal" ]]; then return "$pending_status"; fi
return 1
fi
if ! _k3slr_wait_for_stopped_group producer_pgid stopped_identity "$producer_pid" ||
[[ "$stopped_identity" != "$producer_identity" ]]; then
_k3slr_password_pipe_cleanup || return 1
if [[ -n "$pending_signal" ]]; then return "$pending_status"; fi
return 1
fi
lifecycle_state='stopped_pinned'
_k3slr_lifecycle_boundary stop-query-complete "$producer_pid"
_k3slr_password_pipe_pending_checkpoint || return $?
if [[ "$producer_pgid" == "$caller_pgid" ]]; then
_k3slr_password_pipe_cleanup || return 1
return 1
fi
set +m
monitor_enabled=false
_k3slr_lifecycle_boundary cont-before
_k3slr_password_pipe_pending_checkpoint || return $?
_k3slr_continue_owned_child "$producer_pid" "$producer_pgid" "$producer_identity" || return 1
lifecycle_state='continued'
_k3slr_lifecycle_boundary cont-after
_k3slr_password_pipe_pending_checkpoint || return $?
producer_source_fd="${K3SLR_PASSWORD_PRODUCER[0]}"
exec {pipe_fd}<&"$producer_source_fd" || return 1
exec {producer_source_fd}<&-
exec {tty_fd}<&-
tty_fd=''
lifecycle_state='waiting'
if _k3slr_wait_child "$producer_pid"; then producer_rc=0; else producer_rc=$?; fi
_k3slr_lifecycle_boundary wait-reaped-before-clear
if _k3slr_process_identity current_identity "$producer_pid" 2>/dev/null &&
[[ "$current_identity" == "$producer_identity" ]]; then
_k3slr_password_pipe_cleanup || return 1
fi
lifecycle_state='reaped' producer_pid='' producer_pgid='' producer_identity=''
_k3slr_lifecycle_boundary state-cleared
if [[ -n "$pending_signal" ]]; then
return "$pending_status"
fi
(( producer_rc == 0 )) || return "$producer_rc"
_k3slr_keepass_database_lineage_matches "$database" "$database_snapshot" || return 1
_k3slr_lifecycle_boundary pre-consumer
_k3slr_password_pipe_pending_checkpoint || return $?
if [[ "$operation" == format ]]; then
if _k3slr_cryptsetup_format_stdin "$loop_device" <&"$pipe_fd"; then consumer_rc=0; else consumer_rc=$?; fi
elif [[ "$operation" == open ]]; then
if _k3slr_cryptsetup_open_stdin "$loop_device" "$mapping_name" <&"$pipe_fd"; then consumer_rc=0; else consumer_rc=$?; fi
elif [[ "$operation" == open-readonly ]]; then
if _k3slr_cryptsetup_open_readonly_stdin "$loop_device" "$mapping_name" <&"$pipe_fd"; then consumer_rc=0; else consumer_rc=$?; fi
else
return 1
fi
exec {pipe_fd}<&-
pipe_fd=''
return "$consumer_rc"
)
_k3slr_luks_format_from_keepass() {
(( $# == 3 )) || return 1
_k3slr_run_password_pipe format "$1" "$2" "$3"
}
_k3slr_luks_open_from_keepass() {
(( $# == 4 )) || return 1
_k3slr_run_password_pipe open "$1" "$2" "$3" "$4"
}
_k3slr_luks_open_readonly_from_keepass() {
(( $# == 4 )) || return 1
_k3slr_run_password_pipe open-readonly "$1" "$2" "$3" "$4"
}