]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
c8b3094d | 2 | |
11c3a366 | 3 | #include <fcntl.h> |
c8b3094d LP |
4 | #include <sys/ioctl.h> |
5 | #include <sys/stat.h> | |
c8b3094d | 6 | |
5bab5e4a | 7 | #include "bitfield.h" |
c8b3094d | 8 | #include "chattr-util.h" |
c1631ee1 | 9 | #include "errno-util.h" |
c8b3094d | 10 | #include "fd-util.h" |
cf91b915 | 11 | #include "fs-util.h" |
93a1f792 | 12 | #include "log.h" |
652ba6e0 | 13 | #include "recurse-dir.h" |
c1631ee1 LP |
14 | #include "string-util.h" |
15 | ||
cf91b915 DDM |
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) { | |
c8b3094d | 24 | |
cf91b915 | 25 | _cleanup_close_ int fd = -EBADF; |
c8b3094d | 26 | unsigned old_attr, new_attr; |
7c3b51c4 | 27 | int set_flags_errno = 0; |
c8b3094d LP |
28 | struct stat st; |
29 | ||
cf91b915 | 30 | assert(dir_fd >= 0 || dir_fd == AT_FDCWD); |
459631a0 | 31 | |
e40b11be | 32 | fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); |
cf91b915 | 33 | if (fd < 0) |
59a4e172 | 34 | return fd; |
c8b3094d LP |
35 | |
36 | if (fstat(fd, &st) < 0) | |
37 | return -errno; | |
38 | ||
459631a0 YW |
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. */ | |
c8b3094d LP |
43 | |
44 | if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) | |
45 | return -ENOTTY; | |
46 | ||
459631a0 | 47 | if (mask == 0 && !ret_previous && !ret_final) |
c8b3094d LP |
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); | |
db9a4254 | 54 | if (new_attr == old_attr) { |
459631a0 YW |
55 | if (ret_previous) |
56 | *ret_previous = old_attr; | |
57 | if (ret_final) | |
58 | *ret_final = old_attr; | |
c8b3094d | 59 | return 0; |
db9a4254 | 60 | } |
c8b3094d | 61 | |
459631a0 | 62 | if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) >= 0) { |
5a980196 DDM |
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; | |
459631a0 YW |
82 | } |
83 | ||
f1ee656d | 84 | if (!ERRNO_IS_IOCTL_NOT_SUPPORTED(errno) || !FLAGS_SET(flags, CHATTR_FALLBACK_BITWISE)) |
c8b3094d LP |
85 | return -errno; |
86 | ||
f1ee656d DDM |
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. | |
c1631ee1 | 90 | * |
f1ee656d DDM |
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 */ | |
db9a4254 | 93 | |
459631a0 | 94 | unsigned current_attr = old_attr; |
c8b3094d | 95 | |
5bab5e4a MY |
96 | BIT_FOREACH(i, mask) { |
97 | unsigned new_one, mask_one = 1u << i; | |
c8b3094d | 98 | |
459631a0 YW |
99 | new_one = UPDATE_FLAG(current_attr, mask_one, FLAGS_SET(value, mask_one)); |
100 | if (new_one == current_attr) | |
101 | continue; | |
c8b3094d | 102 | |
459631a0 | 103 | if (ioctl(fd, FS_IOC_SETFLAGS, &new_one) < 0) { |
9a7b20b6 | 104 | if (!ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) |
459631a0 | 105 | return -errno; |
c1631ee1 LP |
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)); | |
7c3b51c4 LB |
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. */ | |
f1ee656d | 113 | if (set_flags_errno == 0 || !ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) |
7c3b51c4 LB |
114 | set_flags_errno = -errno; |
115 | ||
459631a0 YW |
116 | continue; |
117 | } | |
c8b3094d | 118 | |
459631a0 YW |
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; | |
c8b3094d | 127 | |
f1ee656d DDM |
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 | |
7c3b51c4 | 130 | * that case, so callers can handle it properly (e.g.: tmpfiles.d can use debug level logging). */ |
f1ee656d | 131 | return current_attr == new_attr ? 1 : ERRNO_IS_IOCTL_NOT_SUPPORTED(set_flags_errno) ? set_flags_errno : -ENOANO; |
c8b3094d LP |
132 | } |
133 | ||
134 | int read_attr_fd(int fd, unsigned *ret) { | |
135 | struct stat st; | |
136 | ||
137 | assert(fd >= 0); | |
26f58977 | 138 | assert(ret); |
c8b3094d LP |
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 | ||
e837c257 LP |
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 | ||
7c248223 | 151 | return RET_NERRNO(ioctl(fd, FS_IOC_GETFLAGS, ret)); |
c8b3094d LP |
152 | } |
153 | ||
5e496845 | 154 | int read_attr_at(int dir_fd, const char *path, unsigned *ret) { |
07862c9f DDM |
155 | _cleanup_close_ int fd_close = -EBADF; |
156 | int fd; | |
c8b3094d | 157 | |
5e496845 | 158 | assert(dir_fd >= 0 || dir_fd == AT_FDCWD); |
c8b3094d LP |
159 | assert(ret); |
160 | ||
e837c257 LP |
161 | if (isempty(path) && dir_fd != AT_FDCWD) |
162 | fd = dir_fd; | |
163 | else { | |
07862c9f DDM |
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 | } | |
c8b3094d LP |
170 | |
171 | return read_attr_fd(fd, ret); | |
172 | } | |
652ba6e0 AB |
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 | } |