1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "chattr-util.h"
9 #include "errno-util.h"
13 #include "recurse-dir.h"
14 #include "string-util.h"
21 unsigned *ret_previous
,
23 ChattrApplyFlags flags
) {
25 _cleanup_close_
int fd
= -EBADF
;
26 unsigned old_attr
, new_attr
;
27 int set_flags_errno
= 0;
30 assert(dir_fd
>= 0 || dir_fd
== AT_FDCWD
);
32 fd
= xopenat(dir_fd
, path
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
|O_NOFOLLOW
);
36 if (fstat(fd
, &st
) < 0)
39 /* Explicitly check whether this is a regular file or directory. If it is anything else (such
40 * as a device node or fifo), then the ioctl will not hit the file systems but possibly
41 * drivers, where the ioctl might have different effects. Notably, DRM is using the same
44 if (!S_ISDIR(st
.st_mode
) && !S_ISREG(st
.st_mode
))
47 if (mask
== 0 && !ret_previous
&& !ret_final
)
50 if (ioctl(fd
, FS_IOC_GETFLAGS
, &old_attr
) < 0)
53 new_attr
= (old_attr
& ~mask
) | (value
& mask
);
54 if (new_attr
== old_attr
) {
56 *ret_previous
= old_attr
;
58 *ret_final
= old_attr
;
62 if (ioctl(fd
, FS_IOC_SETFLAGS
, &new_attr
) >= 0) {
65 /* Some filesystems (BTRFS) silently fail when a flag cannot be set. Let's make sure our
66 * changes actually went through by querying the flags again and verifying they're equal to
67 * the flags we tried to configure. */
69 if (ioctl(fd
, FS_IOC_GETFLAGS
, &attr
) < 0)
72 if (new_attr
== attr
) {
74 *ret_previous
= old_attr
;
76 *ret_final
= new_attr
;
80 /* Trigger the fallback logic. */
84 if (!ERRNO_IS_IOCTL_NOT_SUPPORTED(errno
) || !FLAGS_SET(flags
, CHATTR_FALLBACK_BITWISE
))
87 /* When -EINVAL is returned, incompatible attributes might be simultaneously specified. E.g.,
88 * compress(c) and nocow(C) attributes cannot be set to files on btrfs. As a fallback, let's try to
89 * set attributes one by one.
91 * Alternatively, when we get EINVAL or EOPNOTSUPP (or a similar error code) we assume a flag might
92 * just not be supported, and we can ignore it too */
94 unsigned current_attr
= old_attr
;
96 BIT_FOREACH(i
, mask
) {
97 unsigned new_one
, mask_one
= 1u << i
;
99 new_one
= UPDATE_FLAG(current_attr
, mask_one
, FLAGS_SET(value
, mask_one
));
100 if (new_one
== current_attr
)
103 if (ioctl(fd
, FS_IOC_SETFLAGS
, &new_one
) < 0) {
104 if (!ERRNO_IS_IOCTL_NOT_SUPPORTED(errno
))
107 log_full_errno(FLAGS_SET(flags
, CHATTR_WARN_UNSUPPORTED_FLAGS
) ? LOG_WARNING
: LOG_DEBUG
,
109 "Unable to set file attribute 0x%x on %s, ignoring: %m", mask_one
, strna(path
));
111 /* Ensures that we record whether only EOPNOTSUPP&friends are encountered, or if a more serious
112 * error (thus worth logging at a different level, etc) was seen too. */
113 if (set_flags_errno
== 0 || !ERRNO_IS_IOCTL_NOT_SUPPORTED(errno
))
114 set_flags_errno
= -errno
;
119 if (ioctl(fd
, FS_IOC_GETFLAGS
, ¤t_attr
) < 0)
124 *ret_previous
= old_attr
;
126 *ret_final
= current_attr
;
128 /* -ENOANO indicates that some attributes cannot be set. ERRNO_IS_IOCTL_NOT_SUPPORTED indicates that
129 * all encountered failures were due to flags not supported by the FS, so return a specific error in
130 * that case, so callers can handle it properly (e.g.: tmpfiles.d can use debug level logging). */
131 return current_attr
== new_attr
? 1 : ERRNO_IS_IOCTL_NOT_SUPPORTED(set_flags_errno
) ? set_flags_errno
: -ENOANO
;
134 int read_attr_fd(int fd
, unsigned *ret
) {
140 if (fstat(fd
, &st
) < 0)
143 if (!S_ISDIR(st
.st_mode
) && !S_ISREG(st
.st_mode
))
146 _cleanup_close_
int fd_close
= -EBADF
;
147 fd
= fd_reopen_condition(fd
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
, O_PATH
, &fd_close
); /* drop O_PATH if it is set */
151 return RET_NERRNO(ioctl(fd
, FS_IOC_GETFLAGS
, ret
));
154 int read_attr_at(int dir_fd
, const char *path
, unsigned *ret
) {
155 _cleanup_close_
int fd_close
= -EBADF
;
158 assert(dir_fd
>= 0 || dir_fd
== AT_FDCWD
);
161 if (isempty(path
) && dir_fd
!= AT_FDCWD
)
164 fd_close
= xopenat(dir_fd
, path
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
|O_NOFOLLOW
);
171 return read_attr_fd(fd
, ret
);
174 int read_fs_xattr_fd(int fd
, uint32_t *ret_xflags
, uint32_t *ret_projid
) {
175 struct fsxattr attrs
;
176 _cleanup_close_
int fd_reopened
= -EBADF
;
180 fd
= fd_reopen_condition(fd
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
, O_PATH
, &fd_reopened
);
184 if (ioctl(fd
, FS_IOC_FSGETXATTR
, &attrs
) < 0)
188 *ret_xflags
= attrs
.fsx_xflags
;
191 *ret_projid
= attrs
.fsx_projid
;
196 int set_proj_id(int fd
, uint32_t proj_id
) {
197 struct fsxattr attrs
;
198 _cleanup_close_
int fd_reopened
= -EBADF
;
202 fd
= fd_reopen_condition(fd
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
, O_PATH
, &fd_reopened
);
206 if (ioctl(fd
, FS_IOC_FSGETXATTR
, &attrs
) < 0)
210 if (fstat(fd
, &statbuf
) < 0)
213 if (attrs
.fsx_projid
== proj_id
&& (!S_ISDIR(statbuf
.st_mode
) || FLAGS_SET(attrs
.fsx_xflags
, FS_XFLAG_PROJINHERIT
)))
216 attrs
.fsx_projid
= proj_id
;
217 if (S_ISDIR(statbuf
.st_mode
))
218 attrs
.fsx_xflags
|= FS_XFLAG_PROJINHERIT
;
220 return RET_NERRNO(ioctl(fd
, FS_IOC_FSSETXATTR
, &attrs
));
223 static int set_proj_id_cb(
224 RecurseDirEvent event
,
228 const struct dirent
*de
,
229 const struct statx
*sx
,
232 if (!IN_SET(event
, RECURSE_DIR_ENTER
, RECURSE_DIR_ENTRY
))
233 return RECURSE_DIR_CONTINUE
;
235 if (de
&& !IN_SET(de
->d_type
, DT_DIR
, DT_REG
))
236 return RECURSE_DIR_CONTINUE
;
238 return set_proj_id(inode_fd
, PTR_TO_UINT32(userdata
));
241 int set_proj_id_recursive(int fd
, uint32_t proj_id
) {
242 return recurse_dir_at(
245 /* statx_mask = */ 0,
246 /* n_depth_max = */ UINT_MAX
,
247 RECURSE_DIR_ENSURE_TYPE
|RECURSE_DIR_TOPLEVEL
|RECURSE_DIR_INODE_FD
,
249 UINT32_TO_PTR(proj_id
));