|
|
@@ -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))
|