]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <fcntl.h> | |
4 | #include <sys/ioctl.h> | |
5 | #include <sys/stat.h> | |
6 | ||
7 | #include "bitfield.h" | |
8 | #include "chattr-util.h" | |
9 | #include "errno-util.h" | |
10 | #include "fd-util.h" | |
11 | #include "fs-util.h" | |
12 | #include "log.h" | |
13 | #include "recurse-dir.h" | |
14 | #include "string-util.h" | |
15 | ||
16 | int chattr_full( | |
17 | int dir_fd, | |
18 | const char *path, | |
19 | unsigned value, | |
20 | unsigned mask, | |
21 | unsigned *ret_previous, | |
22 | unsigned *ret_final, | |
23 | ChattrApplyFlags flags) { | |
24 | ||
25 | _cleanup_close_ int fd = -EBADF; | |
26 | unsigned old_attr, new_attr; | |
27 | int set_flags_errno = 0; | |
28 | struct stat st; | |
29 | ||
30 | assert(dir_fd >= 0 || dir_fd == AT_FDCWD); | |
31 | ||
32 | fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); | |
33 | if (fd < 0) | |
34 | return fd; | |
35 | ||
36 | if (fstat(fd, &st) < 0) | |
37 | return -errno; | |
38 | ||
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 | |
42 | * ioctl() number. */ | |
43 | ||
44 | if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) | |
45 | return -ENOTTY; | |
46 | ||
47 | if (mask == 0 && !ret_previous && !ret_final) | |
48 | return 0; | |
49 | ||
50 | if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) | |
51 | return -errno; | |
52 | ||
53 | new_attr = (old_attr & ~mask) | (value & mask); | |
54 | if (new_attr == old_attr) { | |
55 | if (ret_previous) | |
56 | *ret_previous = old_attr; | |
57 | if (ret_final) | |
58 | *ret_final = old_attr; | |
59 | return 0; | |
60 | } | |
61 | ||
62 | if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) >= 0) { | |
63 | unsigned attr; | |
64 | ||
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. */ | |
68 | ||
69 | if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0) | |
70 | return -errno; | |
71 | ||
72 | if (new_attr == attr) { | |
73 | if (ret_previous) | |
74 | *ret_previous = old_attr; | |
75 | if (ret_final) | |
76 | *ret_final = new_attr; | |
77 | return 1; | |
78 | } | |
79 | ||
80 | /* Trigger the fallback logic. */ | |
81 | errno = EINVAL; | |
82 | } | |
83 | ||
84 | if (!ERRNO_IS_IOCTL_NOT_SUPPORTED(errno) || !FLAGS_SET(flags, CHATTR_FALLBACK_BITWISE)) | |
85 | return -errno; | |
86 | ||
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. | |
90 | * | |
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 */ | |
93 | ||
94 | unsigned current_attr = old_attr; | |
95 | ||
96 | BIT_FOREACH(i, mask) { | |
97 | unsigned new_one, mask_one = 1u << i; | |
98 | ||
99 | new_one = UPDATE_FLAG(current_attr, mask_one, FLAGS_SET(value, mask_one)); | |
100 | if (new_one == current_attr) | |
101 | continue; | |
102 | ||
103 | if (ioctl(fd, FS_IOC_SETFLAGS, &new_one) < 0) { | |
104 | if (!ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) | |
105 | return -errno; | |
106 | ||
107 | log_full_errno(FLAGS_SET(flags, CHATTR_WARN_UNSUPPORTED_FLAGS) ? LOG_WARNING : LOG_DEBUG, | |
108 | errno, | |
109 | "Unable to set file attribute 0x%x on %s, ignoring: %m", mask_one, strna(path)); | |
110 | ||
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; | |
115 | ||
116 | continue; | |
117 | } | |
118 | ||
119 | if (ioctl(fd, FS_IOC_GETFLAGS, ¤t_attr) < 0) | |
120 | return -errno; | |
121 | } | |
122 | ||
123 | if (ret_previous) | |
124 | *ret_previous = old_attr; | |
125 | if (ret_final) | |
126 | *ret_final = current_attr; | |
127 | ||
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; | |
132 | } | |
133 | ||
134 | int read_attr_fd(int fd, unsigned *ret) { | |
135 | struct stat st; | |
136 | ||
137 | assert(fd >= 0); | |
138 | assert(ret); | |
139 | ||
140 | if (fstat(fd, &st) < 0) | |
141 | return -errno; | |
142 | ||
143 | if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) | |
144 | return -ENOTTY; | |
145 | ||
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 */ | |
148 | if (fd < 0) | |
149 | return fd; | |
150 | ||
151 | return RET_NERRNO(ioctl(fd, FS_IOC_GETFLAGS, ret)); | |
152 | } | |
153 | ||
154 | int read_attr_at(int dir_fd, const char *path, unsigned *ret) { | |
155 | _cleanup_close_ int fd_close = -EBADF; | |
156 | int fd; | |
157 | ||
158 | assert(dir_fd >= 0 || dir_fd == AT_FDCWD); | |
159 | assert(ret); | |
160 | ||
161 | if (isempty(path) && dir_fd != AT_FDCWD) | |
162 | fd = dir_fd; | |
163 | else { | |
164 | fd_close = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); | |
165 | if (fd_close < 0) | |
166 | return fd_close; | |
167 | ||
168 | fd = fd_close; | |
169 | } | |
170 | ||
171 | return read_attr_fd(fd, ret); | |
172 | } | |
173 | ||
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; | |
177 | ||
178 | assert(fd >= 0); | |
179 | ||
180 | fd = fd_reopen_condition(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY, O_PATH, &fd_reopened); | |
181 | if (fd < 0) | |
182 | return fd; | |
183 | ||
184 | if (ioctl(fd, FS_IOC_FSGETXATTR, &attrs) < 0) | |
185 | return -errno; | |
186 | ||
187 | if (ret_xflags) | |
188 | *ret_xflags = attrs.fsx_xflags; | |
189 | ||
190 | if (ret_projid) | |
191 | *ret_projid = attrs.fsx_projid; | |
192 | ||
193 | return 0; | |
194 | } | |
195 | ||
196 | int set_proj_id(int fd, uint32_t proj_id) { | |
197 | struct fsxattr attrs; | |
198 | _cleanup_close_ int fd_reopened = -EBADF; | |
199 | ||
200 | assert(fd >= 0); | |
201 | ||
202 | fd = fd_reopen_condition(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY, O_PATH, &fd_reopened); | |
203 | if (fd < 0) | |
204 | return fd; | |
205 | ||
206 | if (ioctl(fd, FS_IOC_FSGETXATTR, &attrs) < 0) | |
207 | return -errno; | |
208 | ||
209 | struct stat statbuf; | |
210 | if (fstat(fd, &statbuf) < 0) | |
211 | return -errno; | |
212 | ||
213 | if (attrs.fsx_projid == proj_id && (!S_ISDIR(statbuf.st_mode) || FLAGS_SET(attrs.fsx_xflags, FS_XFLAG_PROJINHERIT))) | |
214 | return 0; | |
215 | ||
216 | attrs.fsx_projid = proj_id; | |
217 | if (S_ISDIR(statbuf.st_mode)) | |
218 | attrs.fsx_xflags |= FS_XFLAG_PROJINHERIT; | |
219 | ||
220 | return RET_NERRNO(ioctl(fd, FS_IOC_FSSETXATTR, &attrs)); | |
221 | } | |
222 | ||
223 | static int set_proj_id_cb( | |
224 | RecurseDirEvent event, | |
225 | const char *path, | |
226 | int dir_fd, | |
227 | int inode_fd, | |
228 | const struct dirent *de, | |
229 | const struct statx *sx, | |
230 | void *userdata) { | |
231 | ||
232 | if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY)) | |
233 | return RECURSE_DIR_CONTINUE; | |
234 | ||
235 | if (de && !IN_SET(de->d_type, DT_DIR, DT_REG)) | |
236 | return RECURSE_DIR_CONTINUE; | |
237 | ||
238 | return set_proj_id(inode_fd, PTR_TO_UINT32(userdata)); | |
239 | } | |
240 | ||
241 | int set_proj_id_recursive(int fd, uint32_t proj_id) { | |
242 | return recurse_dir_at( | |
243 | fd, | |
244 | /* path = */ NULL, | |
245 | /* statx_mask = */ 0, | |
246 | /* n_depth_max = */ UINT_MAX, | |
247 | RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_TOPLEVEL|RECURSE_DIR_INODE_FD, | |
248 | set_proj_id_cb, | |
249 | UINT32_TO_PTR(proj_id)); | |
250 | } |