Răsfoiți Sursa

Merge pull request #7270 from ThomasWaldmann/user-group-uid-gid

make user/group/uid/gid optional
TW 2 ani în urmă
părinte
comite
1df7c244c4

+ 31 - 24
src/borg/archive.py

@@ -410,15 +410,15 @@ def get_item_uid_gid(item, *, numeric, uid_forced=None, gid_forced=None, uid_def
         uid = uid_forced
         uid = uid_forced
     else:
     else:
         uid = None if numeric else user2uid(item.get("user"))
         uid = None if numeric else user2uid(item.get("user"))
-        uid = item.uid if uid is None else uid
-        if uid < 0:
+        uid = item.get("uid") if uid is None else uid
+        if uid is None or uid < 0:
             uid = uid_default
             uid = uid_default
     if gid_forced is not None:
     if gid_forced is not None:
         gid = gid_forced
         gid = gid_forced
     else:
     else:
         gid = None if numeric else group2gid(item.get("group"))
         gid = None if numeric else group2gid(item.get("group"))
-        gid = item.gid if gid is None else gid
-        if gid < 0:
+        gid = item.get("gid") if gid is None else gid
+        if gid is None or gid < 0:
             gid = gid_default
             gid = gid_default
     return uid, gid
     return uid, gid
 
 
@@ -928,9 +928,12 @@ Duration: {0.duration}
         Does not access the repository.
         Does not access the repository.
         """
         """
         backup_io.op = "attrs"
         backup_io.op = "attrs"
-        uid, gid = get_item_uid_gid(item, numeric=self.numeric_ids)
-        # This code is a bit of a mess due to os specific differences
+        # This code is a bit of a mess due to OS specific differences.
         if not is_win32:
         if not is_win32:
+            # by using uid_default = -1 and gid_default = -1, they will not be restored if
+            # the archived item has no information about them.
+            uid, gid = get_item_uid_gid(item, numeric=self.numeric_ids, uid_default=-1, gid_default=-1)
+            # if uid and/or gid is -1, chown will keep it as is and not change it.
             try:
             try:
                 if fd:
                 if fd:
                     os.fchown(fd, uid, gid)
                     os.fchown(fd, uid, gid)
@@ -1399,28 +1402,32 @@ class FilesystemObjectProcessors:
             item.update(self.metadata_collector.stat_attrs(st, path))  # can't use FD here?
             item.update(self.metadata_collector.stat_attrs(st, path))  # can't use FD here?
             return status
             return status
 
 
-    def process_pipe(self, *, path, cache, fd, mode, user, group):
+    def process_pipe(self, *, path, cache, fd, mode, user=None, group=None):
         status = "i"  # stdin (or other pipe)
         status = "i"  # stdin (or other pipe)
         self.print_file_status(status, path)
         self.print_file_status(status, path)
         status = None  # we already printed the status
         status = None  # we already printed the status
-        uid = user2uid(user)
-        if uid is None:
-            raise Error("no such user: %s" % user)
-        gid = group2gid(group)
-        if gid is None:
-            raise Error("no such group: %s" % group)
+        if user is not None:
+            uid = user2uid(user)
+            if uid is None:
+                raise Error("no such user: %s" % user)
+        else:
+            uid = None
+        if group is not None:
+            gid = group2gid(group)
+            if gid is None:
+                raise Error("no such group: %s" % group)
+        else:
+            gid = None
         t = int(time.time()) * 1000000000
         t = int(time.time()) * 1000000000
-        item = Item(
-            path=path,
-            mode=mode & 0o107777 | 0o100000,  # forcing regular file mode
-            uid=uid,
-            user=user,
-            gid=gid,
-            group=group,
-            mtime=t,
-            atime=t,
-            ctime=t,
-        )
+        item = Item(path=path, mode=mode & 0o107777 | 0o100000, mtime=t, atime=t, ctime=t)  # forcing regular file mode
+        if user is not None:
+            item.user = user
+        if group is not None:
+            item.group = group
+        if uid is not None:
+            item.uid = uid
+        if gid is not None:
+            item.gid = gid
         self.process_file_chunks(item, cache, self.stats, self.show_progress, backup_io_iter(self.chunker.chunkify(fd)))
         self.process_file_chunks(item, cache, self.stats, self.show_progress, backup_io_iter(self.chunker.chunkify(fd)))
         item.get_size(memorize=True)
         item.get_size(memorize=True)
         self.stats.nfiles += 1
         self.stats.nfiles += 1

+ 4 - 5
src/borg/archiver/create_cmd.py

@@ -30,7 +30,6 @@ from ..manifest import Manifest
 from ..patterns import PatternMatcher
 from ..patterns import PatternMatcher
 from ..platform import is_win32
 from ..platform import is_win32
 from ..platform import get_flags
 from ..platform import get_flags
-from ..platform import uid2user, gid2group
 
 
 from ..logger import create_logger
 from ..logger import create_logger
 
 
@@ -718,15 +717,15 @@ class CreateMixIn:
             "--stdin-user",
             "--stdin-user",
             metavar="USER",
             metavar="USER",
             dest="stdin_user",
             dest="stdin_user",
-            default=uid2user(0),
-            help="set user USER in archive for stdin data (default: %(default)r)",
+            default=None,
+            help="set user USER in archive for stdin data (default: do not store user/uid)",
         )
         )
         subparser.add_argument(
         subparser.add_argument(
             "--stdin-group",
             "--stdin-group",
             metavar="GROUP",
             metavar="GROUP",
             dest="stdin_group",
             dest="stdin_group",
-            default=gid2group(0),
-            help="set group GROUP in archive for stdin data (default: %(default)r)",
+            default=None,
+            help="set group GROUP in archive for stdin data (default: do not store group/gid)",
         )
         )
         subparser.add_argument(
         subparser.add_argument(
             "--stdin-mode",
             "--stdin-mode",

+ 2 - 2
src/borg/archiver/tar_cmds.py

@@ -132,8 +132,8 @@ class TarMixIn:
             tarinfo.name = item.path
             tarinfo.name = item.path
             tarinfo.mtime = item.mtime / 1e9
             tarinfo.mtime = item.mtime / 1e9
             tarinfo.mode = stat.S_IMODE(item.mode)
             tarinfo.mode = stat.S_IMODE(item.mode)
-            tarinfo.uid = item.uid
-            tarinfo.gid = item.gid
+            tarinfo.uid = item.get("uid", 0)
+            tarinfo.gid = item.get("gid", 0)
             tarinfo.uname = item.get("user", "")
             tarinfo.uname = item.get("user", "")
             tarinfo.gname = item.get("group", "")
             tarinfo.gname = item.get("group", "")
             # The linkname in tar has 2 uses:
             # The linkname in tar has 2 uses:

+ 4 - 4
src/borg/helpers/parseformat.py

@@ -893,10 +893,10 @@ class ItemFormatter(BaseFormatter):
         item_data["type"] = item_type
         item_data["type"] = item_type
         item_data["mode"] = mode
         item_data["mode"] = mode
 
 
-        item_data.update(text_to_json("user", item.get("user", str(item.uid))))
-        item_data.update(text_to_json("group", item.get("group", str(item.gid))))
-        item_data["uid"] = item.uid
-        item_data["gid"] = item.gid
+        item_data["uid"] = item.get("uid")  # int or None
+        item_data["gid"] = item.get("gid")  # int or None
+        item_data.update(text_to_json("user", item.get("user", str(item_data["uid"]))))
+        item_data.update(text_to_json("group", item.get("group", str(item_data["gid"]))))
 
 
         if self.json_lines:
         if self.json_lines:
             item_data["healthy"] = "chunks_healthy" not in item
             item_data["healthy"] = "chunks_healthy" not in item

+ 37 - 6
src/borg/testsuite/archive.py

@@ -1,4 +1,5 @@
 import json
 import json
+import os
 from collections import OrderedDict
 from collections import OrderedDict
 from datetime import datetime, timezone
 from datetime import datetime, timezone
 from io import StringIO
 from io import StringIO
@@ -295,18 +296,22 @@ def test_backup_io_iter():
 
 
 def test_get_item_uid_gid():
 def test_get_item_uid_gid():
     # test requires that:
     # test requires that:
-    # - a name for user 0 and group 0 exists, usually root:root or root:wheel.
+    # - a user/group name for the current process' real uid/gid exists.
     # - a system user/group udoesnotexist:gdoesnotexist does NOT exist.
     # - a system user/group udoesnotexist:gdoesnotexist does NOT exist.
 
 
-    user0, group0 = uid2user(0), gid2group(0)
+    try:
+        puid, pgid = os.getuid(), os.getgid()  # UNIX only
+    except AttributeError:
+        puid, pgid = 0, 0
+    puser, pgroup = uid2user(puid), gid2group(pgid)
 
 
     # this is intentionally a "strange" item, with not matching ids/names.
     # this is intentionally a "strange" item, with not matching ids/names.
-    item = Item(path="filename", uid=1, gid=2, user=user0, group=group0)
+    item = Item(path="filename", uid=1, gid=2, user=puser, group=pgroup)
 
 
     uid, gid = get_item_uid_gid(item, numeric=False)
     uid, gid = get_item_uid_gid(item, numeric=False)
     # these are found via a name-to-id lookup
     # these are found via a name-to-id lookup
-    assert uid == 0
-    assert gid == 0
+    assert uid == puid
+    assert gid == pgid
 
 
     uid, gid = get_item_uid_gid(item, numeric=True)
     uid, gid = get_item_uid_gid(item, numeric=True)
     # these are directly taken from the item.uid and .gid
     # these are directly taken from the item.uid and .gid
@@ -319,7 +324,7 @@ def test_get_item_uid_gid():
     assert gid == 4
     assert gid == 4
 
 
     # item metadata broken, has negative ids.
     # item metadata broken, has negative ids.
-    item = Item(path="filename", uid=-1, gid=-2, user=user0, group=group0)
+    item = Item(path="filename", uid=-1, gid=-2, user=puser, group=pgroup)
 
 
     uid, gid = get_item_uid_gid(item, numeric=True)
     uid, gid = get_item_uid_gid(item, numeric=True)
     # use the uid/gid defaults (which both default to 0).
     # use the uid/gid defaults (which both default to 0).
@@ -356,3 +361,29 @@ def test_get_item_uid_gid():
     # because item uid/gid seems valid, do not use the given uid/gid defaults
     # because item uid/gid seems valid, do not use the given uid/gid defaults
     assert uid == 9
     assert uid == 9
     assert gid == 10
     assert gid == 10
+
+    # item metadata only has uid/gid, but no user/group.
+    item = Item(path="filename", uid=13, gid=14)
+
+    uid, gid = get_item_uid_gid(item, numeric=False)
+    # it'll check user/group first, but as there is nothing in the item, falls back to uid/gid.
+    assert uid == 13
+    assert gid == 14
+
+    uid, gid = get_item_uid_gid(item, numeric=True)
+    # does not check user/group, directly returns uid/gid.
+    assert uid == 13
+    assert gid == 14
+
+    # item metadata has no uid/gid/user/group.
+    item = Item(path="filename")
+
+    uid, gid = get_item_uid_gid(item, numeric=False, uid_default=15)
+    # as there is nothing, it'll fall back to uid_default/gid_default.
+    assert uid == 15
+    assert gid == 0
+
+    uid, gid = get_item_uid_gid(item, numeric=True, gid_default=16)
+    # as there is nothing, it'll fall back to uid_default/gid_default.
+    assert uid == 0
+    assert gid == 16

+ 0 - 4
src/borg/testsuite/archiver/create_cmd.py

@@ -160,8 +160,6 @@ class ArchiverTestCase(ArchiverTestCaseBase):
         input_data = b"\x00foo\n\nbar\n   \n"
         input_data = b"\x00foo\n\nbar\n   \n"
         self.cmd(f"--repo={self.repository_location}", "create", "test", "-", input=input_data)
         self.cmd(f"--repo={self.repository_location}", "create", "test", "-", input=input_data)
         item = json.loads(self.cmd(f"--repo={self.repository_location}", "list", "test", "--json-lines"))
         item = json.loads(self.cmd(f"--repo={self.repository_location}", "list", "test", "--json-lines"))
-        assert item["uid"] == 0
-        assert item["gid"] == 0
         assert item["size"] == len(input_data)
         assert item["size"] == len(input_data)
         assert item["path"] == "stdin"
         assert item["path"] == "stdin"
         extracted_data = self.cmd(
         extracted_data = self.cmd(
@@ -185,8 +183,6 @@ class ArchiverTestCase(ArchiverTestCaseBase):
             input_data,
             input_data,
         )
         )
         item = json.loads(self.cmd(f"--repo={self.repository_location}", "list", "test", "--json-lines"))
         item = json.loads(self.cmd(f"--repo={self.repository_location}", "list", "test", "--json-lines"))
-        assert item["uid"] == 0
-        assert item["gid"] == 0
         assert item["size"] == len(input_data) + 1  # `echo` adds newline
         assert item["size"] == len(input_data) + 1  # `echo` adds newline
         assert item["path"] == name
         assert item["path"] == name
         extracted_data = self.cmd(f"--repo={self.repository_location}", "extract", "test", "--stdout")
         extracted_data = self.cmd(f"--repo={self.repository_location}", "extract", "test", "--stdout")