diff --git a/src/borg/archive.py b/src/borg/archive.py index 8a7564b6f..6d8e4e04d 100644 --- a/src/borg/archive.py +++ b/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 else: 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 if gid_forced is not None: gid = gid_forced else: 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 return uid, gid @@ -928,9 +928,12 @@ def restore_attrs(self, path, item, symlink=False, fd=None): Does not access the repository. """ 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: + # 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: if fd: os.fchown(fd, uid, gid) @@ -1399,28 +1402,32 @@ def process_symlink(self, *, path, parent_fd, name, st): item.update(self.metadata_collector.stat_attrs(st, path)) # can't use FD here? 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) self.print_file_status(status, path) 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 - 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))) item.get_size(memorize=True) self.stats.nfiles += 1 diff --git a/src/borg/archiver/create_cmd.py b/src/borg/archiver/create_cmd.py index 0cb51fa42..4f8208ab5 100644 --- a/src/borg/archiver/create_cmd.py +++ b/src/borg/archiver/create_cmd.py @@ -30,7 +30,6 @@ from ..patterns import PatternMatcher from ..platform import is_win32 from ..platform import get_flags -from ..platform import uid2user, gid2group from ..logger import create_logger @@ -718,15 +717,15 @@ def build_parser_create(self, subparsers, common_parser, mid_common_parser): "--stdin-user", metavar="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( "--stdin-group", metavar="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( "--stdin-mode", diff --git a/src/borg/archiver/tar_cmds.py b/src/borg/archiver/tar_cmds.py index 2ee5a0712..c273ffde7 100644 --- a/src/borg/archiver/tar_cmds.py +++ b/src/borg/archiver/tar_cmds.py @@ -132,8 +132,8 @@ def item_to_tarinfo(item, original_path): tarinfo.name = item.path tarinfo.mtime = item.mtime / 1e9 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.gname = item.get("group", "") # The linkname in tar has 2 uses: diff --git a/src/borg/helpers/parseformat.py b/src/borg/helpers/parseformat.py index f1142871d..94d005e4f 100644 --- a/src/borg/helpers/parseformat.py +++ b/src/borg/helpers/parseformat.py @@ -893,10 +893,10 @@ def get_item_data(self, item): item_data["type"] = item_type 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: item_data["healthy"] = "chunks_healthy" not in item diff --git a/src/borg/testsuite/archive.py b/src/borg/testsuite/archive.py index 3c518ffd3..c796b6af3 100644 --- a/src/borg/testsuite/archive.py +++ b/src/borg/testsuite/archive.py @@ -1,4 +1,5 @@ import json +import os from collections import OrderedDict from datetime import datetime, timezone from io import StringIO @@ -295,18 +296,22 @@ def __next__(self): def test_get_item_uid_gid(): # 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. - 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. - 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) # 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) # these are directly taken from the item.uid and .gid @@ -319,7 +324,7 @@ def test_get_item_uid_gid(): assert gid == 4 # 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) # 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 assert uid == 9 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 diff --git a/src/borg/testsuite/archiver/create_cmd.py b/src/borg/testsuite/archiver/create_cmd.py index 76e9a0209..8b9f1d4d1 100644 --- a/src/borg/testsuite/archiver/create_cmd.py +++ b/src/borg/testsuite/archiver/create_cmd.py @@ -160,8 +160,6 @@ def test_create_stdin(self): input_data = b"\x00foo\n\nbar\n \n" 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")) - assert item["uid"] == 0 - assert item["gid"] == 0 assert item["size"] == len(input_data) assert item["path"] == "stdin" extracted_data = self.cmd( @@ -185,8 +183,6 @@ def test_create_content_from_command(self): input_data, ) 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["path"] == name extracted_data = self.cmd(f"--repo={self.repository_location}", "extract", "test", "--stdout")