#!/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/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" }