mirror of https://github.com/borgbackup/borg.git
mount: implement --numeric-owner (default: False!), fixes #2377
this is different default behaviour than in borg < 1.2: default (numeric_owner=False) is to use the user/group name from the archive, look up the local uid / gid and then use that for the FUSE fs. when --numeric-owner is given (numeric_owner=True), then the uid/gid from the archive is directly used (as it was the default behaviour in borg < 1.2). this was implemented like this (changing the default behaviour) to make borg mount and borg extract behave more similar considering usage of user/group numeric archived ids or archived names mapped to corresponding numeric local system ids. also, both now use the same function to get the uid/gid from the item. fuse: - add user and group name entries to default_dir - also: set internal_dict(!) of new Item with data from Item.as_dict()
This commit is contained in:
parent
1ef7519823
commit
bbccdbd81c
|
@ -367,6 +367,24 @@ class CacheChunkBuffer(ChunkBuffer):
|
|||
return id_
|
||||
|
||||
|
||||
def get_item_uid_gid(item, *, numeric, uid_forced=None, gid_forced=None, uid_default=0, gid_default=0):
|
||||
if uid_forced is not None:
|
||||
uid = uid_forced
|
||||
else:
|
||||
uid = None if numeric else user2uid(item.user)
|
||||
uid = item.uid if uid is None else uid
|
||||
if uid < 0:
|
||||
uid = uid_default
|
||||
if gid_forced is not None:
|
||||
gid = gid_forced
|
||||
else:
|
||||
gid = None if numeric else group2gid(item.group)
|
||||
gid = item.gid if gid is None else gid
|
||||
if gid < 0:
|
||||
gid = gid_default
|
||||
return uid, gid
|
||||
|
||||
|
||||
class Archive:
|
||||
|
||||
class DoesNotExist(Error):
|
||||
|
@ -809,12 +827,7 @@ Utilization of max. archive size: {csize_max:.0%}
|
|||
Does not access the repository.
|
||||
"""
|
||||
backup_io.op = 'attrs'
|
||||
uid = gid = None
|
||||
if not self.numeric_owner:
|
||||
uid = user2uid(item.user)
|
||||
gid = group2gid(item.group)
|
||||
uid = item.uid if uid is None else uid
|
||||
gid = item.gid if gid is None else gid
|
||||
uid, gid = get_item_uid_gid(item, numeric=self.numeric_owner)
|
||||
# This code is a bit of a mess due to os specific differences
|
||||
if not is_win32:
|
||||
try:
|
||||
|
|
|
@ -2822,6 +2822,8 @@ class Archiver:
|
|||
help='stay in foreground, do not daemonize')
|
||||
parser.add_argument('-o', dest='options', type=str,
|
||||
help='Extra mount options')
|
||||
parser.add_argument('--numeric-owner', dest='numeric_owner', action='store_true',
|
||||
help='use numeric user and group identifiers from archive(s)')
|
||||
define_archive_filters_group(parser)
|
||||
parser.add_argument('paths', metavar='PATH', nargs='*', type=str,
|
||||
help='paths to extract; patterns are supported')
|
||||
|
|
|
@ -33,12 +33,13 @@ logger = create_logger()
|
|||
|
||||
from .crypto.low_level import blake2b_128
|
||||
from .archiver import Archiver
|
||||
from .archive import Archive
|
||||
from .archive import Archive, get_item_uid_gid
|
||||
from .hashindex import FuseVersionsIndex
|
||||
from .helpers import daemonize, daemonizing, hardlinkable, signal_handler, format_file_size
|
||||
from .helpers import msgpack
|
||||
from .item import Item
|
||||
from .lrucache import LRUCache
|
||||
from .platform import uid2user, gid2group
|
||||
from .remote import RemoteRepository
|
||||
|
||||
|
||||
|
@ -240,6 +241,7 @@ class FuseBackend(object):
|
|||
def __init__(self, key, manifest, repository, args, decrypted_repository):
|
||||
self.repository_uncached = repository
|
||||
self._args = args
|
||||
self.numeric_owner = args.numeric_owner
|
||||
self._manifest = manifest
|
||||
self.key = key
|
||||
# Maps inode numbers to Item instances. This is used for synthetic inodes, i.e. file-system objects that are
|
||||
|
@ -311,7 +313,7 @@ class FuseBackend(object):
|
|||
"""
|
||||
ino = self._allocate_inode()
|
||||
if mtime is not None:
|
||||
self._items[ino] = Item(**self.default_dir.as_dict())
|
||||
self._items[ino] = Item(internal_dict=self.default_dir.as_dict())
|
||||
self._items[ino].mtime = mtime
|
||||
else:
|
||||
self._items[ino] = self.default_dir
|
||||
|
@ -530,8 +532,13 @@ class FuseOperations(llfuse.Operations, FuseBackend):
|
|||
self.umask = pop_option(options, 'umask', 0, 0, int, int_base=8) # umask is octal, e.g. 222 or 0222
|
||||
dir_uid = self.uid_forced if self.uid_forced is not None else self.default_uid
|
||||
dir_gid = self.gid_forced if self.gid_forced is not None else self.default_gid
|
||||
dir_user = uid2user(dir_uid)
|
||||
dir_group = gid2group(dir_gid)
|
||||
assert isinstance(dir_user, str)
|
||||
assert isinstance(dir_group, str)
|
||||
dir_mode = 0o40755 & ~self.umask
|
||||
self.default_dir = Item(mode=dir_mode, mtime=int(time.time() * 1e9), uid=dir_uid, gid=dir_gid)
|
||||
self.default_dir = Item(mode=dir_mode, mtime=int(time.time() * 1e9),
|
||||
user=dir_user, group=dir_group, uid=dir_uid, gid=dir_gid)
|
||||
self._create_filesystem()
|
||||
llfuse.init(self, mountpoint, options)
|
||||
if not foreground:
|
||||
|
@ -581,8 +588,9 @@ class FuseOperations(llfuse.Operations, FuseBackend):
|
|||
entry.attr_timeout = 300
|
||||
entry.st_mode = item.mode & ~self.umask
|
||||
entry.st_nlink = item.get('nlink', 1)
|
||||
entry.st_uid = self.uid_forced if self.uid_forced is not None else item.uid if item.uid >= 0 else self.default_uid
|
||||
entry.st_gid = self.gid_forced if self.gid_forced is not None else item.gid if item.gid >= 0 else self.default_gid
|
||||
entry.st_uid, entry.st_gid = get_item_uid_gid(item, numeric=self.numeric_owner,
|
||||
uid_default=self.default_uid, gid_default=self.default_gid,
|
||||
uid_forced=self.uid_forced, gid_forced=self.gid_forced)
|
||||
entry.st_rdev = item.get('rdev', 0)
|
||||
entry.st_size = item.get_size()
|
||||
entry.st_blksize = 512
|
||||
|
|
|
@ -8,10 +8,11 @@ import pytest
|
|||
from . import BaseTestCase
|
||||
from ..crypto.key import PlaintextKey
|
||||
from ..archive import Archive, CacheChunkBuffer, RobustUnpacker, valid_msgpacked_dict, ITEM_KEYS, Statistics
|
||||
from ..archive import BackupOSError, backup_io, backup_io_iter
|
||||
from ..archive import BackupOSError, backup_io, backup_io_iter, get_item_uid_gid
|
||||
from ..helpers import Manifest
|
||||
from ..helpers import msgpack
|
||||
from ..item import Item, ArchiveItem
|
||||
from ..platform import uid2user, gid2group
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
|
@ -249,3 +250,68 @@ def test_backup_io_iter():
|
|||
normal_iterator = Iterator(StopIteration)
|
||||
for _ in backup_io_iter(normal_iterator):
|
||||
assert False, 'StopIteration handled incorrectly'
|
||||
|
||||
|
||||
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 system user/group udoesnotexist:gdoesnotexist does NOT exist.
|
||||
|
||||
user0, group0 = uid2user(0), gid2group(0)
|
||||
|
||||
# this is intentionally a "strange" item, with not matching ids/names.
|
||||
item = Item(path='filename', uid=1, gid=2, user=user0, group=group0)
|
||||
|
||||
uid, gid = get_item_uid_gid(item, numeric=False)
|
||||
# these are found via a name-to-id lookup
|
||||
assert uid == 0
|
||||
assert gid == 0
|
||||
|
||||
uid, gid = get_item_uid_gid(item, numeric=True)
|
||||
# these are directly taken from the item.uid and .gid
|
||||
assert uid == 1
|
||||
assert gid == 2
|
||||
|
||||
uid, gid = get_item_uid_gid(item, numeric=False, uid_forced=3, gid_forced=4)
|
||||
# these are enforced (not from item metadata)
|
||||
assert uid == 3
|
||||
assert gid == 4
|
||||
|
||||
# item metadata broken, has negative ids.
|
||||
item = Item(path='filename', uid=-1, gid=-2, user=user0, group=group0)
|
||||
|
||||
uid, gid = get_item_uid_gid(item, numeric=True)
|
||||
# use the uid/gid defaults (which both default to 0).
|
||||
assert uid == 0
|
||||
assert gid == 0
|
||||
|
||||
uid, gid = get_item_uid_gid(item, numeric=True, uid_default=5, gid_default=6)
|
||||
# use the uid/gid defaults (as given).
|
||||
assert uid == 5
|
||||
assert gid == 6
|
||||
|
||||
# item metadata broken, has negative ids and non-existing user/group names.
|
||||
item = Item(path='filename', uid=-3, gid=-4, user='udoesnotexist', group='gdoesnotexist')
|
||||
|
||||
uid, gid = get_item_uid_gid(item, numeric=False)
|
||||
# use the uid/gid defaults (which both default to 0).
|
||||
assert uid == 0
|
||||
assert gid == 0
|
||||
|
||||
uid, gid = get_item_uid_gid(item, numeric=True, uid_default=7, gid_default=8)
|
||||
# use the uid/gid defaults (as given).
|
||||
assert uid == 7
|
||||
assert gid == 8
|
||||
|
||||
# item metadata has valid uid/gid, but non-existing user/group names.
|
||||
item = Item(path='filename', uid=9, gid=10, user='udoesnotexist', group='gdoesnotexist')
|
||||
|
||||
uid, gid = get_item_uid_gid(item, numeric=False)
|
||||
# because user/group name does not exist here, use valid numeric ids from item metadata.
|
||||
assert uid == 9
|
||||
assert gid == 10
|
||||
|
||||
uid, gid = get_item_uid_gid(item, numeric=False, uid_default=11, gid_default=12)
|
||||
# because item uid/gid seems valid, do not use the given uid/gid defaults
|
||||
assert uid == 9
|
||||
assert gid == 10
|
||||
|
|
Loading…
Reference in New Issue