mirror of
https://github.com/borgbackup/borg.git
synced 2026-06-09 17:00:11 -04:00
extract: fs flags: use get/set to influence only specific flags, #9039, macOS only.
preserve UF_COMPRESSED and SF_DATALESS when restoring flags,
get-modify-set in macOS set_flags, keeping system-managed read-only flags.
(cherry picked from commit 83571aa00d)
This commit is contained in:
parent
73b6193cc5
commit
10ce4f4fd3
3 changed files with 87 additions and 0 deletions
|
|
@ -40,3 +40,4 @@ elif is_darwin: # pragma: darwin only
|
|||
from .darwin import API_VERSION as OS_API_VERSION
|
||||
from .darwin import listxattr, getxattr, setxattr
|
||||
from .darwin import acl_get, acl_set
|
||||
from .darwin import set_flags
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
|
||||
from libc cimport errno
|
||||
from libc.stdint cimport uint32_t
|
||||
|
||||
from .posix import user2uid, group2gid
|
||||
|
|
@ -153,3 +154,65 @@ def acl_set(path, item, numeric_ids=False, fd=None):
|
|||
acl_set_link_np(path, ACL_TYPE_EXTENDED, acl)
|
||||
finally:
|
||||
acl_free(acl)
|
||||
|
||||
# macOS flags handling: only modify flags documented as settable; preserve all others, see #9090.
|
||||
# The man page states UF_COMPRESSED and SF_DATALESS are internal flags and must not be modified
|
||||
# from user space. We therefore only modify flags that are documented to be settable by owner or
|
||||
# super-user and preserve everything else (including unknown or future flags).
|
||||
|
||||
cdef extern from "sys/stat.h":
|
||||
int chflags(const char *path, uint32_t flags)
|
||||
int lchflags(const char *path, uint32_t flags)
|
||||
int fchflags(int fd, uint32_t flags)
|
||||
|
||||
# Known-good settable flags from macOS chflags(2). We intentionally do NOT include
|
||||
# internal flags like UF_COMPRESSED and SF_DATALESS. Resolved once at import time.
|
||||
# getattr(..., 0) keeps this importable on non-Darwin platforms or Python versions
|
||||
# missing some constants.
|
||||
import stat as stat_mod
|
||||
|
||||
SETTABLE_FLAG_NAMES = (
|
||||
# Owner-settable (UF_*)
|
||||
'UF_NODUMP',
|
||||
'UF_IMMUTABLE',
|
||||
'UF_APPEND',
|
||||
'UF_OPAQUE',
|
||||
'UF_NOUNLINK',
|
||||
'UF_HIDDEN',
|
||||
# Super-user-settable (SF_*)
|
||||
'SF_ARCHIVED',
|
||||
'SF_IMMUTABLE',
|
||||
'SF_APPEND',
|
||||
# SF_NOUNLINK exists on some BSDs; include defensively
|
||||
'SF_NOUNLINK',
|
||||
)
|
||||
|
||||
cdef uint32_t SETTABLE_FLAGS_MASK = 0
|
||||
for _name in SETTABLE_FLAG_NAMES:
|
||||
SETTABLE_FLAGS_MASK |= <uint32_t> getattr(stat_mod, _name, 0)
|
||||
|
||||
|
||||
def set_flags(path, bsd_flags, fd=None):
|
||||
"""Set BSD-style flags on macOS, preserving system-managed read-only flags."""
|
||||
# Determine current flags.
|
||||
try:
|
||||
if fd is not None:
|
||||
st = os.fstat(fd)
|
||||
else:
|
||||
st = os.lstat(path)
|
||||
current = st.st_flags
|
||||
except (OSError, AttributeError):
|
||||
# We can't determine the current flags, so better give up than corrupting anything.
|
||||
return
|
||||
|
||||
new_flags = (current & ~SETTABLE_FLAGS_MASK) | (bsd_flags & SETTABLE_FLAGS_MASK)
|
||||
|
||||
# Apply flags.
|
||||
cdef uint32_t c_flags = <uint32_t> new_flags
|
||||
if fd is not None:
|
||||
if fchflags(fd, c_flags) == -1:
|
||||
raise OSError(errno.errno, os.strerror(errno.errno), path)
|
||||
else:
|
||||
path_bytes = os.fsencode(path)
|
||||
if lchflags(path_bytes, c_flags) == -1:
|
||||
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path_bytes))
|
||||
|
|
|
|||
|
|
@ -1521,6 +1521,29 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|||
assert same_ts_ns(mtime_extracted, mtime_expected)
|
||||
# assert same_ts_ns(atime_extracted, atime_expected) # still broken, but not really important.
|
||||
|
||||
@pytest.mark.skipif(not is_darwin, reason='only for macOS')
|
||||
def test_extract_restores_append_flag(self):
|
||||
if not has_lchflags or not hasattr(stat, 'UF_APPEND'):
|
||||
pytest.skip('BSD flags or UF_APPEND not supported on this platform')
|
||||
# create a file and set the append flag on it
|
||||
self.create_regular_file('appendflag', size=1)
|
||||
src_path = os.path.join(self.input_path, 'appendflag')
|
||||
platform.set_flags(src_path, stat.UF_APPEND)
|
||||
# Verify the flag actually got set; otherwise skip (filesystem may not support it)
|
||||
st = os.lstat(src_path)
|
||||
if (platform.get_flags(src_path, st) & stat.UF_APPEND) == 0:
|
||||
pytest.skip('UF_APPEND not settable on this filesystem')
|
||||
# archive and extract
|
||||
self.cmd('init', '--encryption=repokey', self.repository_location)
|
||||
archive = self.repository_location + '::test'
|
||||
self.cmd('create', archive, 'input')
|
||||
with changedir('output'):
|
||||
self.cmd('extract', archive)
|
||||
out_path = os.path.join('input', 'appendflag')
|
||||
st2 = os.lstat(out_path)
|
||||
flags = platform.get_flags(out_path, st2)
|
||||
assert (flags & stat.UF_APPEND) == stat.UF_APPEND
|
||||
|
||||
def test_path_normalization(self):
|
||||
self.cmd('init', '--encryption=repokey', self.repository_location)
|
||||
self.create_regular_file('dir1/dir2/file', size=1024 * 80)
|
||||
|
|
|
|||
Loading…
Reference in a new issue