diff --git a/src/basic/chattr-util.c b/src/basic/chattr-util.c index c724e17685..10e59875ad 100644 --- a/src/basic/chattr-util.c +++ b/src/basic/chattr-util.c @@ -10,25 +10,31 @@ #include "fd-util.h" #include "macro.h" -int chattr_fd(int fd, unsigned value, unsigned mask, unsigned *previous) { +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; unsigned old_attr, new_attr; struct stat st; - assert(fd >= 0); + assert(path || fd >= 0); + + if (fd < 0) { + fd = fd_will_close = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + } if (fstat(fd, &st) < 0) return -errno; - /* Explicitly check whether this is a regular file or - * directory. If it is anything else (such as a device node or - * fifo), then the ioctl will not hit the file systems but - * possibly drivers, where the ioctl might have different - * effects. Notably, DRM is using the same ioctl() number. */ + /* Explicitly check whether this is a regular file or directory. If it is anything else (such + * as a device node or fifo), then the ioctl will not hit the file systems but possibly + * drivers, where the ioctl might have different effects. Notably, DRM is using the same + * ioctl() number. */ if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) return -ENOTTY; - if (mask == 0 && !previous) + if (mask == 0 && !ret_previous && !ret_final) return 0; if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) @@ -36,33 +42,55 @@ int chattr_fd(int fd, unsigned value, unsigned mask, unsigned *previous) { new_attr = (old_attr & ~mask) | (value & mask); if (new_attr == old_attr) { - if (previous) - *previous = old_attr; + if (ret_previous) + *ret_previous = old_attr; + if (ret_final) + *ret_final = old_attr; return 0; } - if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0) + 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; + } + + if (errno != EINVAL || !fallback) return -errno; - if (previous) - *previous = old_attr; + /* 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. */ - return 1; -} + unsigned current_attr = old_attr; + for (unsigned i = 0; i < sizeof(unsigned) * 8; i++) { + unsigned new_one, mask_one = 1u << i; -int chattr_path(const char *p, unsigned value, unsigned mask, unsigned *previous) { - _cleanup_close_ int fd = -1; + if (!FLAGS_SET(mask, mask_one)) + continue; - assert(p); + new_one = UPDATE_FLAG(current_attr, mask_one, FLAGS_SET(value, mask_one)); + if (new_one == current_attr) + continue; - if (mask == 0) - return 0; + if (ioctl(fd, FS_IOC_SETFLAGS, &new_one) < 0) { + if (errno != EINVAL) + return -errno; + continue; + } - fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; + if (ioctl(fd, FS_IOC_GETFLAGS, ¤t_attr) < 0) + return -errno; + } - return chattr_fd(fd, value, mask, previous); + if (ret_previous) + *ret_previous = old_attr; + if (ret_final) + *ret_final = current_attr; + + return current_attr == new_attr ? 1 : -ENOANO; /* -ENOANO indicates that some attributes cannot be set. */ } int read_attr_fd(int fd, unsigned *ret) { diff --git a/src/basic/chattr-util.h b/src/basic/chattr-util.h index 2fcdb64431..3f46367c82 100644 --- a/src/basic/chattr-util.h +++ b/src/basic/chattr-util.h @@ -2,6 +2,8 @@ #pragma once #include +#include +#include #include "missing_fs.h" @@ -32,8 +34,13 @@ FS_NOCOW_FL | \ FS_PROJINHERIT_FL) -int chattr_fd(int fd, unsigned value, unsigned mask, unsigned *previous); -int chattr_path(const char *p, unsigned value, unsigned mask, unsigned *previous); +int chattr_full(const char *path, int fd, unsigned value, unsigned mask, unsigned *ret_previous, unsigned *ret_final, bool fallback); +static inline int chattr_fd(int fd, unsigned value, unsigned mask, unsigned *previous) { + return chattr_full(NULL, fd, value, mask, previous, NULL, false); +} +static inline int chattr_path(const char *path, unsigned value, unsigned mask, unsigned *previous) { + return chattr_full(path, -1, value, mask, previous, NULL, false); +} int read_attr_fd(int fd, unsigned *ret); int read_attr_path(const char *p, unsigned *ret); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 9906c70eef..2bbacf0ccf 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -1308,11 +1308,15 @@ static int fd_set_attribute(Item *item, int fd, const char *path, const struct s if (procfs_fd < 0) return log_error_errno(procfs_fd, "Failed to re-open '%s': %m", path); - r = chattr_fd(procfs_fd, f, item->attribute_mask, NULL); - if (r < 0) - log_full_errno(IN_SET(r, -ENOTTY, -EOPNOTSUPP) ? LOG_DEBUG : LOG_WARNING, - r, - "Cannot set file attribute for '%s', value=0x%08x, mask=0x%08x, ignoring: %m", + unsigned previous, current; + r = chattr_full(NULL, procfs_fd, f, item->attribute_mask, &previous, ¤t, true); + if (r == -ENOANO) + log_warning("Cannot set file attributes for '%s', maybe due to incompatiblity in specified attributes, " + "previous=0x%08x, current=0x%08x, expected=0x%08x, ignoring.", + path, previous, current, (previous & ~item->attribute_mask) | (f & item->attribute_mask)); + else if (r < 0) + log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r, + "Cannot set file attributes for '%s', value=0x%08x, mask=0x%08x, ignoring: %m", path, item->attribute_value, item->attribute_mask); return 0;