From ac950facc08dbaf8bdda7431d434a16f4d02b222 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 12 Feb 2026 09:01:19 -0600 Subject: Fix snapshot rotation failure on remote filesystems Snapshots with read-only permissions (from chmod -w at creation) and internal dirs with restrictive modes (e.g. /etc at 555) prevented rm -rf from deleting old snapshots during rotation. This caused cascading mv failures and silent rotation breakage. - Add chmod -R u+w before rm -rf of oldest snapshot - Clean up orphan snapshots beyond retention count - Add error checking on rm, mv, and cp -al operations --- rsyncshot | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/rsyncshot b/rsyncshot index 5b5820b..8194d20 100755 --- a/rsyncshot +++ b/rsyncshot @@ -940,9 +940,24 @@ fi log "Rotating snapshots..." # --- Delete oldest snapshot if it exceeds retention count --- +# Restore write permission recursively first — snapshots contain directories +# preserved with their original permissions (e.g. /etc dirs with mode 555), +# and chmod -w (applied at creation) removes owner write on the top level. +# Without u+w, rm -rf fails on remote filesystems where we run as a normal user. if run_cmd "[ -d '$BASE_PATH/$TYPE.$MAX' ]"; then log "Deleting oldest snapshot: $TYPE.$MAX" - run_cmd "$RM -rf '$BASE_PATH/$TYPE.$MAX'" + run_cmd "chmod -R u+w '$BASE_PATH/$TYPE.$MAX'" + if ! run_cmd "$RM -rf '$BASE_PATH/$TYPE.$MAX'"; then + error "Snapshot rotation failed: could not delete $TYPE.$MAX" + fi +fi + +# --- Clean up orphan snapshot beyond retention (from previous failures) --- +BEYOND=$((MAX+1)) +if run_cmd "[ -d '$BASE_PATH/$TYPE.$BEYOND' ]"; then + log "Cleaning up orphaned snapshot: $TYPE.$BEYOND" + run_cmd "chmod -R u+w '$BASE_PATH/$TYPE.$BEYOND'" + run_cmd "$RM -rf '$BASE_PATH/$TYPE.$BEYOND'" fi # --- Rotate existing snapshots (newest to oldest to avoid overwriting) --- @@ -950,7 +965,9 @@ for (( start=$((MAX)); start>=0; start-- )); do end=$((start+1)) if run_cmd "[ -d '$BASE_PATH/$TYPE.$start' ]"; then log "Rotating: $TYPE.$start -> $TYPE.$end" - run_cmd "$MV '$BASE_PATH/$TYPE.$start' '$BASE_PATH/$TYPE.$end'" + if ! run_cmd "$MV '$BASE_PATH/$TYPE.$start' '$BASE_PATH/$TYPE.$end'"; then + error "Snapshot rotation failed: could not move $TYPE.$start to $TYPE.$end" + fi fi done @@ -962,7 +979,9 @@ run_cmd "touch '$BASE_PATH/latest'" # This is instant and uses no additional disk space for unchanged files. # Only files that differ between snapshots consume extra space. log "Creating new snapshot: $TYPE.0" -run_cmd "$CP -al '$BASE_PATH/latest' '$BASE_PATH/$TYPE.0'" +if ! run_cmd "$CP -al '$BASE_PATH/latest' '$BASE_PATH/$TYPE.0'"; then + error "Snapshot creation failed: could not hard-link latest to $TYPE.0" +fi # --- Make snapshot read-only to prevent accidental modification --- run_cmd "chmod -w '$BASE_PATH/$TYPE.0'" -- cgit v1.2.3