mirror of
https://projects.torsion.org/witten/borgmatic.git
synced 2025-04-10 15:37:33 +00:00
Fix a "no such file or directory" error in ZFS, Btrfs, and LVM hooks with nested directories that reside on separate devices/filesystems (#1048).
This commit is contained in:
parent
5cea1e1b72
commit
ab01e97a5e
3 changed files with 72 additions and 12 deletions
2
NEWS
2
NEWS
|
@ -22,6 +22,8 @@
|
|||
"working_directory" are used.
|
||||
* #1044: Fix an error in the systemd credential hook when the credential name contains a "."
|
||||
character.
|
||||
* #1048: Fix a "no such file or directory" error in ZFS, Btrfs, and LVM hooks with nested
|
||||
directories that reside on separate devices/filesystems.
|
||||
|
||||
1.9.14
|
||||
* #409: With the PagerDuty monitoring hook, send borgmatic logs to PagerDuty so they show up in the
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import os
|
||||
import pathlib
|
||||
|
||||
IS_A_HOOK = False
|
||||
|
@ -11,6 +12,10 @@ def get_contained_patterns(parent_directory, candidate_patterns):
|
|||
paths, but there's a parent directory (logical volume, dataset, subvolume, etc.) at /var, then
|
||||
/var is what we want to snapshot.
|
||||
|
||||
If a parent directory and a candidate pattern are on different devices, skip the pattern. That's
|
||||
because any snapshot of a parent directory won't actually include "contained" directories if
|
||||
they reside on separate devices.
|
||||
|
||||
For this function to work, a candidate pattern path can't have any globs or other non-literal
|
||||
characters in the initial portion of the path that matches the parent directory. For instance, a
|
||||
parent directory of /var would match a candidate pattern path of /var/log/*/data, but not a
|
||||
|
@ -27,6 +32,8 @@ def get_contained_patterns(parent_directory, candidate_patterns):
|
|||
if not candidate_patterns:
|
||||
return ()
|
||||
|
||||
parent_device = os.stat(parent_directory).st_dev
|
||||
|
||||
contained_patterns = tuple(
|
||||
candidate
|
||||
for candidate in candidate_patterns
|
||||
|
@ -35,6 +42,7 @@ def get_contained_patterns(parent_directory, candidate_patterns):
|
|||
pathlib.PurePath(parent_directory) == candidate_path
|
||||
or pathlib.PurePath(parent_directory) in candidate_path.parents
|
||||
)
|
||||
if candidate.device == parent_device
|
||||
)
|
||||
candidate_patterns -= set(contained_patterns)
|
||||
|
||||
|
|
|
@ -1,34 +1,84 @@
|
|||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.borg.pattern import Pattern
|
||||
from borgmatic.hooks.data_source import snapshot as module
|
||||
|
||||
|
||||
def test_get_contained_patterns_without_candidates_returns_empty():
|
||||
flexmock(module.os).should_receive('stat').and_return(flexmock(st_dev=flexmock()))
|
||||
|
||||
assert module.get_contained_patterns('/mnt', {}) == ()
|
||||
|
||||
|
||||
def test_get_contained_patterns_with_self_candidate_returns_self():
|
||||
candidates = {Pattern('/foo'), Pattern('/mnt'), Pattern('/bar')}
|
||||
device = flexmock()
|
||||
flexmock(module.os).should_receive('stat').and_return(flexmock(st_dev=device))
|
||||
candidates = {
|
||||
Pattern('/foo', device=device),
|
||||
Pattern('/mnt', device=device),
|
||||
Pattern('/bar', device=device),
|
||||
}
|
||||
|
||||
assert module.get_contained_patterns('/mnt', candidates) == (Pattern('/mnt'),)
|
||||
assert candidates == {Pattern('/foo'), Pattern('/bar')}
|
||||
assert module.get_contained_patterns('/mnt', candidates) == (Pattern('/mnt', device=device),)
|
||||
assert candidates == {Pattern('/foo', device=device), Pattern('/bar', device=device)}
|
||||
|
||||
|
||||
def test_get_contained_patterns_with_self_candidate_and_caret_prefix_returns_self():
|
||||
candidates = {Pattern('^/foo'), Pattern('^/mnt'), Pattern('^/bar')}
|
||||
device = flexmock()
|
||||
flexmock(module.os).should_receive('stat').and_return(flexmock(st_dev=device))
|
||||
candidates = {
|
||||
Pattern('^/foo', device=device),
|
||||
Pattern('^/mnt', device=device),
|
||||
Pattern('^/bar', device=device),
|
||||
}
|
||||
|
||||
assert module.get_contained_patterns('/mnt', candidates) == (Pattern('^/mnt'),)
|
||||
assert candidates == {Pattern('^/foo'), Pattern('^/bar')}
|
||||
assert module.get_contained_patterns('/mnt', candidates) == (Pattern('^/mnt', device=device),)
|
||||
assert candidates == {Pattern('^/foo', device=device), Pattern('^/bar', device=device)}
|
||||
|
||||
|
||||
def test_get_contained_patterns_with_child_candidate_returns_child():
|
||||
candidates = {Pattern('/foo'), Pattern('/mnt/subdir'), Pattern('/bar')}
|
||||
device = flexmock()
|
||||
flexmock(module.os).should_receive('stat').and_return(flexmock(st_dev=device))
|
||||
candidates = {
|
||||
Pattern('/foo', device=device),
|
||||
Pattern('/mnt/subdir', device=device),
|
||||
Pattern('/bar', device=device),
|
||||
}
|
||||
|
||||
assert module.get_contained_patterns('/mnt', candidates) == (Pattern('/mnt/subdir'),)
|
||||
assert candidates == {Pattern('/foo'), Pattern('/bar')}
|
||||
assert module.get_contained_patterns('/mnt', candidates) == (
|
||||
Pattern('/mnt/subdir', device=device),
|
||||
)
|
||||
assert candidates == {Pattern('/foo', device=device), Pattern('/bar', device=device)}
|
||||
|
||||
|
||||
def test_get_contained_patterns_with_grandchild_candidate_returns_child():
|
||||
candidates = {Pattern('/foo'), Pattern('/mnt/sub/dir'), Pattern('/bar')}
|
||||
device = flexmock()
|
||||
flexmock(module.os).should_receive('stat').and_return(flexmock(st_dev=device))
|
||||
candidates = {
|
||||
Pattern('/foo', device=device),
|
||||
Pattern('/mnt/sub/dir', device=device),
|
||||
Pattern('/bar', device=device),
|
||||
}
|
||||
|
||||
assert module.get_contained_patterns('/mnt', candidates) == (Pattern('/mnt/sub/dir'),)
|
||||
assert candidates == {Pattern('/foo'), Pattern('/bar')}
|
||||
assert module.get_contained_patterns('/mnt', candidates) == (
|
||||
Pattern('/mnt/sub/dir', device=device),
|
||||
)
|
||||
assert candidates == {Pattern('/foo', device=device), Pattern('/bar', device=device)}
|
||||
|
||||
|
||||
def test_get_contained_patterns_ignores_child_candidate_on_another_device():
|
||||
one_device = flexmock()
|
||||
another_device = flexmock()
|
||||
flexmock(module.os).should_receive('stat').and_return(flexmock(st_dev=one_device))
|
||||
candidates = {
|
||||
Pattern('/foo', device=one_device),
|
||||
Pattern('/mnt/subdir', device=another_device),
|
||||
Pattern('/bar', device=one_device),
|
||||
}
|
||||
|
||||
assert module.get_contained_patterns('/mnt', candidates) == ()
|
||||
assert candidates == {
|
||||
Pattern('/foo', device=one_device),
|
||||
Pattern('/mnt/subdir', device=another_device),
|
||||
Pattern('/bar', device=one_device),
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue