Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions src/borg/testsuite/archiver/diff_cmd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pathlib import Path
import stat
import time
import pytest

from ...constants import * # NOQA
from .. import are_symlinks_supported, are_hardlinks_supported
Expand Down Expand Up @@ -318,3 +319,83 @@ def test_sort_option(archivers, request):
outputs = output.splitlines()
assert len(outputs) == len(expected)
assert all(x in line for x, line in zip(expected, outputs))


@pytest.mark.skipif(not are_hardlinks_supported(), reason="hardlinks not supported")
def test_hard_link_deletion_and_replacement(archivers, request):
archiver = request.getfixturevalue(archivers)

path_a = os.path.join(archiver.input_path, "a")
path_b = os.path.join(archiver.input_path, "b")
os.mkdir(path_a)
os.mkdir(path_b)
hl_a = os.path.join(path_a, "hardlink")
hl_b = os.path.join(path_b, "hardlink")
create_regular_file(archiver.input_path, hl_a, contents=b"123456")
os.link(hl_a, hl_b)

cmd(archiver, "repo-create", RK_ENCRYPTION)
cmd(archiver, "create", "test0", "input")
os.unlink(hl_a) # Don't duplicate warning message- one is enough.
cmd(archiver, "create", "test1", "input")

# Moral equivalent of test_multiple_link_exclusion in borg v1.x... see #8344
# Borg v2 doesn't have this issue comparing hard-links, so we'll defer to
# POSIX behavior:
# https://pubs.opengroup.org/onlinepubs/9799919799/functions/unlink.html
# Upon successful completion, unlink() shall mark for update the last data modification
# and last file status change timestamps of the parent directory. Also, if the
# file's link count is not 0, the last file status change timestamp of the
# file shall be marked for update.
output = cmd(
archiver, "diff", "--pattern=+ fm:input/b", "--pattern=! **/", "test0", "test1", exit_code=EXIT_SUCCESS
)
lines = output.splitlines()
# Directory was excluded.
assert_line_not_exists(lines, "input/a$")
# Remaining hardlink
assert_line_exists(lines, "ctime:.*input/b/hardlink")
assert_line_not_exists(lines, ".*mtime:.*input/b/hardlink")
# Deleted hardlink was excluded
assert_line_not_exists(lines, "input/a/hardlink$")

# Now try again, except with no patterns!
output = cmd(archiver, "diff", "test0", "test1", exit_code=EXIT_SUCCESS)
lines = output.splitlines()
# Directory... preferably, let's not care about order differences are presented.
assert_line_exists(lines, "[cm]time:.*[cm]time:.*input/a")
# Remaining hardlink
assert_line_exists(lines, "ctime:.*input/b/hardlink")
assert_line_not_exists(lines, ".*mtime:.*input/b/hardlink")
# Deleted hardlink
assert_line_exists(lines, "removed:.*input/a/hardlink")

# Now recreate the unlinked file as a different entity with identical
# contents.
create_regular_file(archiver.input_path, hl_a, contents=b"123456")
cmd(archiver, "create", "test2", "input")

# Compare test0 and test2.
output = cmd(archiver, "diff", "test0", "test2", exit_code=EXIT_SUCCESS)
lines = output.splitlines()
# Adding a file changes c/mtime.
assert_line_exists(lines, "[cm]time:.*[cm]time:.*input/a$")
# Different c/mtime but no apparent changes (i.e. perms) or content
# modifications should be a hint that something hard-link related is going on.
assert_line_exists(lines, "[cm]time:.*[cm]time:.*input/a/hardlink")
assert_line_not_exists(lines, "modified.*B.*input/a/hardlink")
assert_line_not_exists(lines, "[.* -> .*].*input/dir_replaced_with_file")
# The hard-link count went down, but file content isn't modified.
assert_line_exists(lines, "ctime:.*input/b/hardlink")
assert_line_not_exists(lines, ".*mtime:.*input/b/hardlink")
assert_line_not_exists(lines, "modified.*B.*input/b/hardlink")

# Finally, compare test1 and test2.
output = cmd(archiver, "diff", "test1", "test2", exit_code=EXIT_SUCCESS)
lines = output.splitlines()
# Same situation applies as previous diff for a.
assert_line_exists(lines, "[cm]time:.*[cm]time:.*input/a$")
# From test1 to test2's POV, the a/hardlink file is a fresh new file.
assert_line_exists(lines, "added.*B.*input/a/hardlink")
# But the b/hardlink file was not modified at all.
assert_line_not_exists(lines, ".*input/b/hardlink")
Loading