From 9214197a2cd18796553f1d2cce6faf5ad7576a95 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Thu, 16 Oct 2025 21:26:49 +0200 Subject: [PATCH 1/3] set_flags: use get/set to only influence specific flags, fixes #9039 Linux platform only. --- src/borg/platform/linux.pyx | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/borg/platform/linux.pyx b/src/borg/platform/linux.pyx index 38d67b875..e849dbba3 100644 --- a/src/borg/platform/linux.pyx +++ b/src/borg/platform/linux.pyx @@ -128,6 +128,8 @@ BSD_TO_LINUX_FLAGS = { stat.UF_APPEND: FS_APPEND_FL, stat.UF_COMPRESSED: FS_COMPR_FL, } +# must be a bitwise OR of all values in BSD_TO_LINUX_FLAGS. +LINUX_MASK = FS_NODUMP_FL | FS_IMMUTABLE_FL | FS_APPEND_FL | FS_COMPR_FL def set_flags(path, bsd_flags, fd=None): @@ -136,17 +138,31 @@ def set_flags(path, bsd_flags, fd=None): if stat.S_ISBLK(st.st_mode) or stat.S_ISCHR(st.st_mode) or stat.S_ISLNK(st.st_mode): # See comment in get_flags(). return - cdef int flags = 0 + cdef int flags + cdef int mask = LINUX_MASK # 1 at positions we want to influence + cdef int new_flags = 0 for bsd_flag, linux_flag in BSD_TO_LINUX_FLAGS.items(): if bsd_flags & bsd_flag: - flags |= linux_flag + new_flags |= linux_flag + open_fd = fd is None if open_fd: fd = os.open(path, os.O_RDONLY|os.O_NONBLOCK|os.O_NOFOLLOW) try: + # Get current flags. If this fails, fall back to 0 so we can still attempt to set. + if ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1: + flags = 0 + + # Replace only the bits we actually want to influence, keep others. + # We can't just set all flags to the archived value, because we might + # reset flags that are not controllable from userspace, see #9039. + flags = (flags & ~mask) | (new_flags & mask) + if ioctl(fd, FS_IOC_SETFLAGS, &flags) == -1: error_number = errno.errno - if error_number != errno.EOPNOTSUPP: + # Usually we would only catch EOPNOTSUPP here, but Linux Kernel 6.17 + # has a bug where it returns ENOTTY instead of EOPNOTSUPP. + if error_number not in (errno.EOPNOTSUPP, errno.ENOTTY): raise OSError(error_number, strerror(error_number).decode(), path) finally: if open_fd: From 9c600a95715ec22a5dd6cfba9bb1bee8238fc938 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Fri, 17 Oct 2025 00:20:05 +0200 Subject: [PATCH 2/3] set_flags: better give up than corrupt Thanks to Earnestly for the feedback on IRC. --- src/borg/platform/linux.pyx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/borg/platform/linux.pyx b/src/borg/platform/linux.pyx index e849dbba3..d7680ccd6 100644 --- a/src/borg/platform/linux.pyx +++ b/src/borg/platform/linux.pyx @@ -149,9 +149,12 @@ def set_flags(path, bsd_flags, fd=None): if open_fd: fd = os.open(path, os.O_RDONLY|os.O_NONBLOCK|os.O_NOFOLLOW) try: - # Get current flags. If this fails, fall back to 0 so we can still attempt to set. + # Get current flags. if ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1: - flags = 0 + # If this fails, give up because it is either not supported by the fs + # or maybe not permitted? If we can't determine the current flags, + # we better not risk corrupting them by setflags, see the comment below. + return # give up silently # Replace only the bits we actually want to influence, keep others. # We can't just set all flags to the archived value, because we might From 56dda841623f90556b37798e85f9371ebe4a3de2 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Fri, 17 Oct 2025 02:41:53 +0200 Subject: [PATCH 3/3] set_flags: remove compression flag This flag needs to be set BEFORE writing to the file. But "borg extract" sets the flags last (to support IMMUTABLE), thus the compression flag would not work as expected. --- src/borg/platform/linux.pyx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/borg/platform/linux.pyx b/src/borg/platform/linux.pyx index d7680ccd6..1a8b7942c 100644 --- a/src/borg/platform/linux.pyx +++ b/src/borg/platform/linux.pyx @@ -126,10 +126,9 @@ BSD_TO_LINUX_FLAGS = { stat.UF_NODUMP: FS_NODUMP_FL, stat.UF_IMMUTABLE: FS_IMMUTABLE_FL, stat.UF_APPEND: FS_APPEND_FL, - stat.UF_COMPRESSED: FS_COMPR_FL, } # must be a bitwise OR of all values in BSD_TO_LINUX_FLAGS. -LINUX_MASK = FS_NODUMP_FL | FS_IMMUTABLE_FL | FS_APPEND_FL | FS_COMPR_FL +LINUX_MASK = FS_NODUMP_FL | FS_IMMUTABLE_FL | FS_APPEND_FL def set_flags(path, bsd_flags, fd=None):