]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/chattr-util.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / basic / chattr-util.c
index 10e59875ad28667a561395a7e87b9a9caa8c0cbb..3c66a3e0c83bcddd293121cb74719a426f3e48bf 100644 (file)
@@ -7,12 +7,22 @@
 #include <linux/fs.h>
 
 #include "chattr-util.h"
+#include "errno-util.h"
 #include "fd-util.h"
 #include "macro.h"
+#include "string-util.h"
 
-int chattr_full(const char *path, int fd, unsigned value, unsigned mask, unsigned *ret_previous, unsigned *ret_final, bool fallback) {
-        _cleanup_close_ int fd_will_close = -1;
+int chattr_full(const char *path,
+                int fd,
+                unsigned value,
+                unsigned mask,
+                unsigned *ret_previous,
+                unsigned *ret_final,
+                ChattrApplyFlags flags) {
+
+        _cleanup_close_ int fd_will_close = -EBADF;
         unsigned old_attr, new_attr;
+        int set_flags_errno = 0;
         struct stat st;
 
         assert(path || fd >= 0);
@@ -50,19 +60,37 @@ int chattr_full(const char *path, int fd, unsigned value, unsigned mask, unsigne
         }
 
         if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) >= 0) {
-                if (ret_previous)
-                        *ret_previous = old_attr;
-                if (ret_final)
-                        *ret_final = new_attr;
-                return 1;
+                unsigned attr;
+
+                /* Some filesystems (BTRFS) silently fail when a flag cannot be set. Let's make sure our
+                 * changes actually went through by querying the flags again and verifying they're equal to
+                 * the flags we tried to configure. */
+
+                if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0)
+                        return -errno;
+
+                if (new_attr == attr) {
+                        if (ret_previous)
+                                *ret_previous = old_attr;
+                        if (ret_final)
+                                *ret_final = new_attr;
+                        return 1;
+                }
+
+                /* Trigger the fallback logic. */
+                errno = EINVAL;
         }
 
-        if (errno != EINVAL || !fallback)
+        if ((errno != EINVAL && !ERRNO_IS_NOT_SUPPORTED(errno)) ||
+            !FLAGS_SET(flags, CHATTR_FALLBACK_BITWISE))
                 return -errno;
 
         /* When -EINVAL is returned, we assume that incompatible attributes are simultaneously
          * specified. E.g., compress(c) and nocow(C) attributes cannot be set to files on btrfs.
-         * As a fallback, let's try to set attributes one by one. */
+         * As a fallback, let's try to set attributes one by one.
+         *
+         * Also, when we get EOPNOTSUPP (or a similar error code) we assume a flag might just not be
+         * supported, and we can ignore it too */
 
         unsigned current_attr = old_attr;
         for (unsigned i = 0; i < sizeof(unsigned) * 8; i++) {
@@ -76,8 +104,18 @@ int chattr_full(const char *path, int fd, unsigned value, unsigned mask, unsigne
                         continue;
 
                 if (ioctl(fd, FS_IOC_SETFLAGS, &new_one) < 0) {
-                        if (errno != EINVAL)
+                        if (errno != EINVAL && !ERRNO_IS_NOT_SUPPORTED(errno))
                                 return -errno;
+
+                        log_full_errno(FLAGS_SET(flags, CHATTR_WARN_UNSUPPORTED_FLAGS) ? LOG_WARNING : LOG_DEBUG,
+                                       errno,
+                                       "Unable to set file attribute 0x%x on %s, ignoring: %m", mask_one, strna(path));
+
+                        /* Ensures that we record whether only EOPNOTSUPP&friends are encountered, or if a more serious
+                         * error (thus worth logging at a different level, etc) was seen too. */
+                        if (set_flags_errno == 0 || !ERRNO_IS_NOT_SUPPORTED(errno))
+                                set_flags_errno = -errno;
+
                         continue;
                 }
 
@@ -90,7 +128,10 @@ int chattr_full(const char *path, int fd, unsigned value, unsigned mask, unsigne
         if (ret_final)
                 *ret_final = current_attr;
 
-        return current_attr == new_attr ? 1 : -ENOANO; /* -ENOANO indicates that some attributes cannot be set. */
+        /* -ENOANO indicates that some attributes cannot be set. ERRNO_IS_NOT_SUPPORTED indicates that all
+         * encountered failures were due to flags not supported by the FS, so return a specific error in
+         * that case, so callers can handle it properly (e.g.: tmpfiles.d can use debug level logging). */
+        return current_attr == new_attr ? 1 : ERRNO_IS_NOT_SUPPORTED(set_flags_errno) ? set_flags_errno : -ENOANO;
 }
 
 int read_attr_fd(int fd, unsigned *ret) {
@@ -104,14 +145,11 @@ int read_attr_fd(int fd, unsigned *ret) {
         if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode))
                 return -ENOTTY;
 
-        if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0)
-                return -errno;
-
-        return 0;
+        return RET_NERRNO(ioctl(fd, FS_IOC_GETFLAGS, ret));
 }
 
 int read_attr_path(const char *p, unsigned *ret) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         assert(p);
         assert(ret);