fix: flush and unmount even when the sync is interrupted
Build and publish container / build (pull_request) Canceled after 2m10s
Build and publish container / build (pull_request) Canceled after 2m10s
rsync itself is safe under interruption. It writes to a hidden temporary file and renames it into place only once complete, and by default deletes any partial file when interrupted -- verified both in the manual and by killing a transfer and inspecting what was left, which was nothing. --partial is deliberately absent and there is now a test asserting it stays that way. An unclean kill can leave a hidden .track.mp3.XXXXXX behind; it is unplayable, it is not in the source, and the next run's --delete removes it. The script was not safe. Ctrl-C killed it before the sync and the unmount, leaving a journal-less FAT filesystem holding dirty buffers -- which is the exact corruption the script exists to prevent, arrived at by the most likely route a person would take. INT and TERM are now trapped. Both the normal path and the interrupt path call the same finish function, so the flush and the unmount cannot drift apart, and an interrupted run exits 130 rather than pretending to have succeeded. The test is structural rather than timed. Reproducing a mid-transfer signal needs a payload large enough to be slow, and a test that depends on winning a race is a test that fails in CI for reasons that have nothing to do with the code. The behaviour was verified by hand: SIGTERM mid-transfer gave exit 130, the flush ran, and the destination held no short files and no leftover temporaries.
This commit is contained in:
+33
-13
@@ -204,21 +204,41 @@ else
|
||||
printf 'sync-to-ipod: %s files to copy\n' "$total" >&2
|
||||
fi
|
||||
|
||||
# Flushing and unmounting is the whole reason this is a script, so it has to
|
||||
# happen on the way out whichever way that is. Ctrl-C during a transfer would
|
||||
# otherwise leave a FAT filesystem with dirty buffers and no journal, which is
|
||||
# the corruption this exists to avoid.
|
||||
finish() {
|
||||
sync
|
||||
if $unmount; then
|
||||
device=$(findmnt -no SOURCE --target "$destination" 2>/dev/null || true)
|
||||
if [ -n "$device" ]; then
|
||||
printf 'sync-to-ipod: unmounting %s\n' "$device" >&2
|
||||
if command -v udisksctl >/dev/null 2>&1; then
|
||||
udisksctl unmount -b "$device" || umount -- "$destination" || true
|
||||
else
|
||||
umount -- "$destination" || true
|
||||
fi
|
||||
printf 'sync-to-ipod: safe to disconnect\n' >&2
|
||||
fi
|
||||
else
|
||||
printf 'sync-to-ipod: still mounted; unmount before disconnecting\n' >&2
|
||||
fi
|
||||
}
|
||||
|
||||
interrupted() {
|
||||
trap - INT TERM
|
||||
printf '\nsync-to-ipod: interrupted -- rsync leaves no partial files, but the\n' >&2
|
||||
printf 'sync-to-ipod: filesystem still needs flushing before you pull anything\n' >&2
|
||||
finish
|
||||
exit 130
|
||||
}
|
||||
|
||||
trap interrupted INT TERM
|
||||
|
||||
rsync "${options[@]}" --out-format='%l %n' "$mirror/" "$destination/" |
|
||||
python3 "$here/rsync_progress.py" --total "$total" --bytes "$total_bytes"
|
||||
status=${PIPESTATUS[0]}
|
||||
[ "$status" -eq 0 ] || die "rsync exited $status"
|
||||
|
||||
sync
|
||||
if $unmount; then
|
||||
device=$(findmnt -no SOURCE --target "$destination")
|
||||
printf 'sync-to-ipod: unmounting %s\n' "$device" >&2
|
||||
if command -v udisksctl >/dev/null 2>&1; then
|
||||
udisksctl unmount -b "$device"
|
||||
else
|
||||
umount -- "$destination"
|
||||
fi
|
||||
printf 'sync-to-ipod: safe to disconnect\n' >&2
|
||||
else
|
||||
printf 'sync-to-ipod: still mounted; unmount before disconnecting\n' >&2
|
||||
fi
|
||||
finish
|
||||
|
||||
Reference in New Issue
Block a user