|
|
@@ -198,3 +198,67 @@ def _get_birthtime_ns(path, follow_symlinks=False):
|
|
|
if result != 0:
|
|
|
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
|
|
|
return stat_info.st_birthtimespec.tv_sec * 1_000_000_000 + stat_info.st_birthtimespec.tv_nsec
|
|
|
+
|
|
|
+
|
|
|
+# 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))
|