diff --git a/CHANGELOG.md b/CHANGELOG.md index a4fd0a4..34419f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* bkctld: new inc-lock and inc-unlock command + ### Changed ### Deprecated diff --git a/bkctld b/bkctld index 07d399a..01dae84 100755 --- a/bkctld +++ b/bkctld @@ -124,6 +124,38 @@ case "${subcommand}" in "${LIBDIR}/bkctld-${subcommand}" "${jail_name}" fi ;; + "inc-lock" | "inc-unlock") + if [ "${subcommand}" = "inc-lock" ]; then + lock_status=on + elif [ "${subcommand}" = "inc-unlock" ]; then + lock_status=off + else + show_help + exit 1 + fi + pattern="${2:-}" + if [ "${pattern}" = "all" ]; then + target_path_pattern=$(incs_path "*/*") + elif [ -n "${pattern}" ]; then + # Check if pattern is a jail name + if jails_with_incs_list | grep --silent --fixed-strings --line-regexp "${pattern}"; then + debug "${pattern} is a jail, change pattern to ${pattern}/*" + # then change attern to all subdirectories + target_path_pattern=$(incs_path "${pattern}/*") + else + # or use it as is + target_path_pattern=$(incs_path "${pattern}") + fi + else + show_help + exit 1 + fi + + for target_path in ${target_path_pattern}; do + "${LIBDIR}/bkctld-inc-lock" "${lock_status}" "${target_path}" + done + echo "finish" + ;; *) show_help exit 1 diff --git a/lib/bkctld-inc-lock b/lib/bkctld-inc-lock new file mode 100755 index 0000000..6e69196 --- /dev/null +++ b/lib/bkctld-inc-lock @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Description: Lock or unlock dated copies (incs) on BTRFS formatted volumes +# Usage: inc- +# + +# shellcheck source=./includes +LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes" + +lock_status="${1:?}" +target_path="${2:?}" + +lock_target() { + target="${1:?}" + if is_btrfs "${target}"; then + btrfs property set -ts "${target}" ro true + info "Lock ${target}: done". + else + info "Lock ${target}: not BTRFS, nothing done". + fi +} +unlock_target() { + target="${1:?}" + if is_btrfs "${target}"; then + btrfs property set -ts "${target}" ro false + info "Unlock ${target}: done." + else + info "Unlock ${target}: not BTRFS, nothing done." + fi +} + +# this directory test must be quoted,beacause of the probable globbing +if [ -d ${target_path} ]; then + if [ "${lock_status}" = "on" ]; then + lock_target "${target_path}" + elif [ "${lock_status}" = "off" ]; then + unlock_target "${target_path}" + else + error "Unknown lock status \`${lock_status}'." + exit 1 + fi +else + error "\`${target_path}': no such file or directory." + exit 1 +fi diff --git a/test/incs.bats b/test/incs.bats index 8f17400..973242d 100644 --- a/test/incs.bats +++ b/test/incs.bats @@ -110,11 +110,11 @@ load test_helper /usr/lib/bkctld/bkctld-inc # no inc should be kept echo '' > "${CONFDIR}/${JAILNAME}.d/incs_policy" - + # The inc directory is present run test -d "${INCSPATH}" assert_success - + /usr/lib/bkctld/bkctld-rm # The inc directory is absent @@ -122,4 +122,109 @@ load test_helper assert_failure } +@test "BTRFS based inc can be unlock with complex pattern" { + if is_btrfs "/backup"; then + # Prepare an inc older than the policy + recent_inc_path="${INCSPATH}/${INC_NAME}" + older_inc_name=$(date -d -1month +"%Y-%m-%d-%H") + older_inc_path="${INCSPATH}/${older_inc_name}" + + # Create the inc, rename it to make it older, then run 'rm' + /usr/lib/bkctld/bkctld-inc + mv "${recent_inc_path}" "${older_inc_path}" + # run 'inc' again to remake today's inc + /usr/lib/bkctld/bkctld-inc + + # inc should be locked + run touch "${older_inc_path}/var/log/lastlog" + assert_failure + + # unlock inc + pattern="${JAILNAME}/$(date -d -1month +"%Y-%m-*")" + bkctld inc-unlock "${pattern}" + + # inc should be unlocked + run touch "${older_inc_path}/var/log/lastlog" + assert_success + + # other inc should still be locked + run touch "${recent_inc_path}/var/log/lastlog" + assert_failure + else + # On an ext4 filesystem it's always true + run true + assert_success + fi +} + +@test "BTRFS based inc can be unlock with jail name" { + if is_btrfs "/backup"; then + # Prepare an inc older than the policy + recent_inc_path="${INCSPATH}/${INC_NAME}" + older_inc_name=$(date -d -1month +"%Y-%m-%d-%H") + older_inc_path="${INCSPATH}/${older_inc_name}" + + # Create the inc, rename it to make it older, then run 'rm' + /usr/lib/bkctld/bkctld-inc + mv "${recent_inc_path}" "${older_inc_path}" + # run 'inc' again to remake today's inc + /usr/lib/bkctld/bkctld-inc + + # incs should be locked + run touch "${recent_inc_path}/var/log/lastlog" + assert_failure + run touch "${older_inc_path}/var/log/lastlog" + assert_failure + + # unlock incs + pattern="${JAILNAME}" + bkctld inc-unlock "${pattern}" + + # incs should be unlocked + run touch "${recent_inc_path}/var/log/lastlog" + assert_success + run touch "${older_inc_path}/var/log/lastlog" + assert_success + else + # On an ext4 filesystem it's always true + run true + assert_success + fi +} + +@test "BTRFS based inc can be unlock with 'all' pattern" { + if is_btrfs "/backup"; then + # Prepare an inc older than the policy + recent_inc_path="${INCSPATH}/${INC_NAME}" + older_inc_name=$(date -d -1month +"%Y-%m-%d-%H") + older_inc_path="${INCSPATH}/${older_inc_name}" + + # Create the inc, rename it to make it older, then run 'rm' + /usr/lib/bkctld/bkctld-inc + mv "${recent_inc_path}" "${older_inc_path}" + # run 'inc' again to remake today's inc + /usr/lib/bkctld/bkctld-inc + + # incs should be locked + run touch "${recent_inc_path}/var/log/lastlog" + assert_failure + run touch "${older_inc_path}/var/log/lastlog" + assert_failure + + # unlock incs + pattern="all" + bkctld inc-unlock "${pattern}" + + # incs should be unlocked + run touch "${recent_inc_path}/var/log/lastlog" + assert_success + run touch "${older_inc_path}/var/log/lastlog" + assert_success + else + # On an ext4 filesystem it's always true + run true + assert_success + fi +} + # TODO: add many tests for incs (creation and removal)