Skip to content

create: back up automounted directories, fixes #6652 (1.4-maint) - #10575

Open
ThomasWaldmann wants to merge 1 commit into
borgbackup:1.4-maintfrom
ThomasWaldmann:automount-6652-14
Open

ThomasWaldmann wants to merge 1 commit into
borgbackup:1.4-maintfrom
ThomasWaldmann:automount-6652-14

Conversation

@ThomasWaldmann

Copy link
Copy Markdown
Member

Backport of #10509 to 1.4-maint, fixes #6652.

stat() / lstat() do not trigger an automount (on Linux, they act as if AT_NO_AUTOMOUNT was given), but opening the directory does. borg create stats a directory, then opens it and compares with fstat(): for an automount point that is not mounted yet, these differ, so borg considered it a race condition ("file type or inode changed") and skipped the whole tree. This affects autofs (indirect and direct maps), systemd .automount units and ZFS .zfs/snapshot/* on Linux.

Fix (same as on master)

  • platform.on_different_mounts(fd1, fd2): Linux compares the mount ids (from /proc/self/fdinfo/<fd>), other platforms compare st_dev; None if undeterminable (handled like "no").
  • archive.is_automount(): accepts the change only if it is a directory before and after and the opened directory is on a different mount than its parent directory. Everything else still raises BackupRaceConditionError.
  • In _rec_walk, we then use the fstat() result and redo the skip_inodes and --one-file-system decisions.
  • Recursion roots that are directories are opened before taking their stat (stat_dir_mounted), so the mount happens early and --one-file-system uses the device number of the mounted filesystem.

Differences to the master PR

  • Adapted to the monolithic archiver.py (no stat_root() on 1.4, so stat_dir_mounted() is called directly for the recursion roots; symlinked recursion roots are not followed on 1.4, so no follow_symlinks variant).
  • on_different_mounts is imported once from .base in platform/__init__.py and overridden for Linux, matching how 1.4 structures that module.
  • The borg create test is an ArchiverTestCase method (skipped for the binary, as it patches objects).

Testing

Full test suite on macOS (Python 3.14): 1471 passed, 382 skipped. The Linux mount-id code path is identical to master, where it was tested manually on Debian 13 with autofs, systemd automount and ZFS snapshots.

🤖 Generated with Claude Code

stat() does not trigger an automount (autofs, systemd automount units,
ZFS .zfs/snapshot/*), but opening the directory does. borg create then
saw a different inode for fstat() than for stat(), considered that a
race condition and skipped the whole tree.

When the directory is a directory before and after and the opened
directory is on a different mount than its parent, accept it, use the
fstat() result and redo the skip_inodes and --one-file-system decisions.
platform.on_different_mounts() compares the mount ids on Linux (st_dev
is not good enough there, e.g. for btrfs subvolumes) and st_dev elsewhere.

Recursion roots are opened before taking their stat, so --one-file-system
uses the device number of the mounted filesystem (this also fixes
incomplete -x backups of a ZFS snapshot directory given as a recursion
root).

Backport of borgbackup#10509.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.87097% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.14%. Comparing base (e81a51f) to head (21923d0).
⚠️ Report is 12 commits behind head on 1.4-maint.

Files with missing lines Patch % Lines
src/borg/archiver.py 77.77% 3 Missing and 1 partial ⚠️
src/borg/platform/base.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           1.4-maint   #10575      +/-   ##
=============================================
+ Coverage      82.11%   82.14%   +0.02%     
=============================================
  Files             38       38              
  Lines          11485    11514      +29     
  Branches        1810     1816       +6     
=============================================
+ Hits            9431     9458      +27     
- Misses          1468     1469       +1     
- Partials         586      587       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant