Ver código fonte

Merge pull request #8467 from ThomasWaldmann/tar-improvements

import-tar/export-tar improvements
TW 8 meses atrás
pai
commit
65d0ce66c1

+ 11 - 0
src/borg/archive.py

@@ -1487,6 +1487,17 @@ class TarfileObjectProcessors:
                     if name in ph:
                         ns = s_to_ns(ph[name])
                         setattr(item, name, ns)
+                xattrs = StableDict()
+                for key, value in ph.items():
+                    if key.startswith(SCHILY_XATTR):
+                        key = key.removeprefix(SCHILY_XATTR)
+                        # the tarfile code gives us str keys and str values,
+                        # but we need bytes keys and bytes values.
+                        bkey = key.encode("utf-8", errors="surrogateescape")
+                        bvalue = value.encode("utf-8", errors="surrogateescape")
+                        xattrs[bkey] = bvalue
+                if xattrs:
+                    item.xattrs = xattrs
         yield item, status
         # if we get here, "with"-block worked ok without error/exception, the item was processed ok...
         self.add_item(item, stats=self.stats)

+ 9 - 1
src/borg/archiver/tar_cmds.py

@@ -211,6 +211,13 @@ class TarMixIn:
                 if hasattr(item, name):
                     ns = getattr(item, name)
                     ph[name] = str(ns / 1e9)
+            if hasattr(item, "xattrs"):
+                for bkey, bvalue in item.xattrs.items():
+                    # we have bytes key and bytes value, but the tarfile code
+                    # expects str key and str value.
+                    key = SCHILY_XATTR + bkey.decode("utf-8", errors="surrogateescape")
+                    value = bvalue.decode("utf-8", errors="surrogateescape")
+                    ph[key] = value
             if format == "BORG":  # BORG format additions
                 ph["BORG.item.version"] = "1"
                 # BORG.item.meta - just serialize all metadata we have:
@@ -355,6 +362,7 @@ class TarMixIn:
         | BORG         | BORG specific, like PAX   | all as supported by borg   |
         +--------------+---------------------------+----------------------------+
         | PAX          | POSIX.1-2001 (pax) format | GNU + atime/ctime/mtime ns |
+        |              |                           | + xattrs                   |
         +--------------+---------------------------+----------------------------+
         | GNU          | GNU tar format            | mtime s, no atime/ctime,   |
         |              |                           | no ACLs/xattrs/bsdflags    |
@@ -396,7 +404,7 @@ class TarMixIn:
             "--tar-format",
             metavar="FMT",
             dest="tar_format",
-            default="GNU",
+            default="PAX",
             choices=("BORG", "PAX", "GNU"),
             action=Highlander,
             help="select tar format: BORG, PAX or GNU",

+ 3 - 0
src/borg/constants.py

@@ -121,6 +121,9 @@ TIME_DIFFERS1_NS = 20000000
 # similar to above, but for bigger granularity / clock differences
 TIME_DIFFERS2_NS = 3000000000
 
+# tar related
+SCHILY_XATTR = "SCHILY.xattr."  # xattr key prefix in tar PAX headers
+
 # return codes returned by borg command
 EXIT_SUCCESS = 0  # everything done, no problems
 EXIT_WARNING = 1  # reached normal end of operation, but there were issues (generic warning)

+ 22 - 1
src/borg/testsuite/archiver/tar_cmds_test.py

@@ -4,9 +4,11 @@ import subprocess
 
 import pytest
 
+from ... import xattr
 from ...constants import *  # NOQA
 from .. import changedir
-from . import assert_dirs_equal, _extract_hardlinks_setup, cmd, create_test_files, requires_hardlinks, RK_ENCRYPTION
+from . import assert_dirs_equal, _extract_hardlinks_setup, cmd, requires_hardlinks, RK_ENCRYPTION
+from . import create_test_files, create_regular_file
 from . import generate_archiver_tests
 
 pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary")  # NOQA
@@ -219,3 +221,22 @@ def test_roundtrip_pax_borg(archivers, request):
     with changedir(archiver.output_path):
         cmd(archiver, "extract", "dst")
     assert_dirs_equal("input", "output/input")
+
+
+def test_roundtrip_pax_xattrs(archivers, request):
+    archiver = request.getfixturevalue(archivers)
+    if not xattr.is_enabled(archiver.input_path):
+        pytest.skip("xattrs not supported")
+    create_regular_file(archiver.input_path, "file")
+    original_path = os.path.join(archiver.input_path, "file")
+    xa_key, xa_value = b"user.xattrtest", b"not valid utf-8: \xff"
+    xattr.setxattr(original_path.encode(), xa_key, xa_value)
+    cmd(archiver, "repo-create", "--encryption=none")
+    cmd(archiver, "create", "src", "input")
+    cmd(archiver, "export-tar", "src", "xattrs.tar", "--tar-format=PAX")
+    cmd(archiver, "import-tar", "dst", "xattrs.tar")
+    with changedir(archiver.output_path):
+        cmd(archiver, "extract", "dst")
+        extracted_path = os.path.abspath("input/file")
+        xa_value_extracted = xattr.getxattr(extracted_path.encode(), xa_key)
+    assert xa_value_extracted == xa_value