extract: fs flags: use get/set to influence only specific flags, #9090, FreeBSD only.

This commit is contained in:
Thomas Waldmann 2025-10-31 00:22:40 +01:00
parent 3082c59e9a
commit 8a895f55ae
No known key found for this signature in database
GPG key ID: 243ACFA951F78E01
2 changed files with 69 additions and 0 deletions

View file

@ -36,6 +36,7 @@ elif is_freebsd: # pragma: freebsd only
from .freebsd import API_VERSION as OS_API_VERSION
from .freebsd import listxattr, getxattr, setxattr
from .freebsd import acl_get, acl_set
from .freebsd import set_flags
elif is_darwin: # pragma: darwin only
from .darwin import API_VERSION as OS_API_VERSION
from .darwin import listxattr, getxattr, setxattr

View file

@ -222,3 +222,71 @@ def acl_set(path, item, numeric_ids=False, fd=None):
if ret == 1:
_set_acl(path, ACL_TYPE_ACCESS, item, 'acl_access', numeric_ids, fd=fd)
_set_acl(path, ACL_TYPE_DEFAULT, item, 'acl_default', numeric_ids, fd=fd)
cdef extern from "sys/stat.h":
int chflags(const char *path, unsigned long flags)
int lchflags(const char *path, unsigned long flags)
int fchflags(int fd, unsigned long flags)
# ----------------------------
# BSD file flags (FreeBSD)
# ----------------------------
# Only influence flags that are known to be settable and leave system-managed/read-only flags untouched.
# We express the mask in terms of names to avoid hard failures if a constant does
# not exist on a given FreeBSD version; missing names simply contribute 0.
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-only (SF_*)
'SF_ARCHIVED',
'SF_IMMUTABLE',
'SF_APPEND',
'SF_NOUNLINK',
)
cdef unsigned long SETTABLE_FLAGS_MASK = 0
for _name in SETTABLE_FLAG_NAMES:
# getattr(..., 0) keeps this importable when flags are missing on some FreeBSD versions
SETTABLE_FLAGS_MASK |= <unsigned long> getattr(stat_mod, _name, 0)
def set_flags(path, bsd_flags, fd=None):
"""Set a subset of BSD file flags on FreeBSD without disturbing other bits.
Only flags that are known to be settable are influenced. All other flag bits are preserved as-is.
"""
# 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)
cdef unsigned long c_flags = <unsigned long> new_flags
if fd is not None:
if fchflags(fd, c_flags) == -1:
err = errno.errno
# Some filesystems may not support flags; ignore EOPNOTSUPP quietly.
if err != errno.EOPNOTSUPP:
# Keep error signature consistent with other platforms; st may not exist here.
raise OSError(err, os.strerror(err), path)
else:
path_bytes = os.fsencode(path)
if lchflags(path_bytes, c_flags) == -1:
err = errno.errno
if err != errno.EOPNOTSUPP:
raise OSError(err, os.strerror(err), os.fsdecode(path_bytes))