]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
c8b3094d | 2 | |
11c3a366 TA |
3 | #include <errno.h> |
4 | #include <fcntl.h> | |
c8b3094d LP |
5 | #include <sys/ioctl.h> |
6 | #include <sys/stat.h> | |
7 | #include <linux/fs.h> | |
8 | ||
5bab5e4a | 9 | #include "bitfield.h" |
c8b3094d | 10 | #include "chattr-util.h" |
c1631ee1 | 11 | #include "errno-util.h" |
c8b3094d | 12 | #include "fd-util.h" |
cf91b915 | 13 | #include "fs-util.h" |
11c3a366 | 14 | #include "macro.h" |
c1631ee1 LP |
15 | #include "string-util.h" |
16 | ||
cf91b915 DDM |
17 | int chattr_full( |
18 | int dir_fd, | |
19 | const char *path, | |
20 | unsigned value, | |
21 | unsigned mask, | |
22 | unsigned *ret_previous, | |
23 | unsigned *ret_final, | |
24 | ChattrApplyFlags flags) { | |
c8b3094d | 25 | |
cf91b915 | 26 | _cleanup_close_ int fd = -EBADF; |
c8b3094d | 27 | unsigned old_attr, new_attr; |
7c3b51c4 | 28 | int set_flags_errno = 0; |
c8b3094d LP |
29 | struct stat st; |
30 | ||
cf91b915 | 31 | assert(dir_fd >= 0 || dir_fd == AT_FDCWD); |
459631a0 | 32 | |
e40b11be | 33 | fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); |
cf91b915 | 34 | if (fd < 0) |
59a4e172 | 35 | return fd; |
c8b3094d LP |
36 | |
37 | if (fstat(fd, &st) < 0) | |
38 | return -errno; | |
39 | ||
459631a0 YW |
40 | /* Explicitly check whether this is a regular file or directory. If it is anything else (such |
41 | * as a device node or fifo), then the ioctl will not hit the file systems but possibly | |
42 | * drivers, where the ioctl might have different effects. Notably, DRM is using the same | |
43 | * ioctl() number. */ | |
c8b3094d LP |
44 | |
45 | if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) | |
46 | return -ENOTTY; | |
47 | ||
459631a0 | 48 | if (mask == 0 && !ret_previous && !ret_final) |
c8b3094d LP |
49 | return 0; |
50 | ||
51 | if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) | |
52 | return -errno; | |
53 | ||
54 | new_attr = (old_attr & ~mask) | (value & mask); | |
db9a4254 | 55 | if (new_attr == old_attr) { |
459631a0 YW |
56 | if (ret_previous) |
57 | *ret_previous = old_attr; | |
58 | if (ret_final) | |
59 | *ret_final = old_attr; | |
c8b3094d | 60 | return 0; |
db9a4254 | 61 | } |
c8b3094d | 62 | |
459631a0 | 63 | if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) >= 0) { |
5a980196 DDM |
64 | unsigned attr; |
65 | ||
66 | /* Some filesystems (BTRFS) silently fail when a flag cannot be set. Let's make sure our | |
67 | * changes actually went through by querying the flags again and verifying they're equal to | |
68 | * the flags we tried to configure. */ | |
69 | ||
70 | if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0) | |
71 | return -errno; | |
72 | ||
73 | if (new_attr == attr) { | |
74 | if (ret_previous) | |
75 | *ret_previous = old_attr; | |
76 | if (ret_final) | |
77 | *ret_final = new_attr; | |
78 | return 1; | |
79 | } | |
80 | ||
81 | /* Trigger the fallback logic. */ | |
82 | errno = EINVAL; | |
459631a0 YW |
83 | } |
84 | ||
c1631ee1 LP |
85 | if ((errno != EINVAL && !ERRNO_IS_NOT_SUPPORTED(errno)) || |
86 | !FLAGS_SET(flags, CHATTR_FALLBACK_BITWISE)) | |
c8b3094d LP |
87 | return -errno; |
88 | ||
459631a0 YW |
89 | /* When -EINVAL is returned, we assume that incompatible attributes are simultaneously |
90 | * specified. E.g., compress(c) and nocow(C) attributes cannot be set to files on btrfs. | |
c1631ee1 LP |
91 | * As a fallback, let's try to set attributes one by one. |
92 | * | |
93 | * Also, when we get EOPNOTSUPP (or a similar error code) we assume a flag might just not be | |
94 | * supported, and we can ignore it too */ | |
db9a4254 | 95 | |
459631a0 | 96 | unsigned current_attr = old_attr; |
c8b3094d | 97 | |
5bab5e4a MY |
98 | BIT_FOREACH(i, mask) { |
99 | unsigned new_one, mask_one = 1u << i; | |
c8b3094d | 100 | |
459631a0 YW |
101 | new_one = UPDATE_FLAG(current_attr, mask_one, FLAGS_SET(value, mask_one)); |
102 | if (new_one == current_attr) | |
103 | continue; | |
c8b3094d | 104 | |
459631a0 | 105 | if (ioctl(fd, FS_IOC_SETFLAGS, &new_one) < 0) { |
c1631ee1 | 106 | if (errno != EINVAL && !ERRNO_IS_NOT_SUPPORTED(errno)) |
459631a0 | 107 | return -errno; |
c1631ee1 LP |
108 | |
109 | log_full_errno(FLAGS_SET(flags, CHATTR_WARN_UNSUPPORTED_FLAGS) ? LOG_WARNING : LOG_DEBUG, | |
110 | errno, | |
111 | "Unable to set file attribute 0x%x on %s, ignoring: %m", mask_one, strna(path)); | |
7c3b51c4 LB |
112 | |
113 | /* Ensures that we record whether only EOPNOTSUPP&friends are encountered, or if a more serious | |
114 | * error (thus worth logging at a different level, etc) was seen too. */ | |
115 | if (set_flags_errno == 0 || !ERRNO_IS_NOT_SUPPORTED(errno)) | |
116 | set_flags_errno = -errno; | |
117 | ||
459631a0 YW |
118 | continue; |
119 | } | |
c8b3094d | 120 | |
459631a0 YW |
121 | if (ioctl(fd, FS_IOC_GETFLAGS, ¤t_attr) < 0) |
122 | return -errno; | |
123 | } | |
124 | ||
125 | if (ret_previous) | |
126 | *ret_previous = old_attr; | |
127 | if (ret_final) | |
128 | *ret_final = current_attr; | |
c8b3094d | 129 | |
7c3b51c4 LB |
130 | /* -ENOANO indicates that some attributes cannot be set. ERRNO_IS_NOT_SUPPORTED indicates that all |
131 | * encountered failures were due to flags not supported by the FS, so return a specific error in | |
132 | * that case, so callers can handle it properly (e.g.: tmpfiles.d can use debug level logging). */ | |
133 | return current_attr == new_attr ? 1 : ERRNO_IS_NOT_SUPPORTED(set_flags_errno) ? set_flags_errno : -ENOANO; | |
c8b3094d LP |
134 | } |
135 | ||
136 | int read_attr_fd(int fd, unsigned *ret) { | |
137 | struct stat st; | |
138 | ||
139 | assert(fd >= 0); | |
26f58977 | 140 | assert(ret); |
c8b3094d LP |
141 | |
142 | if (fstat(fd, &st) < 0) | |
143 | return -errno; | |
144 | ||
145 | if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) | |
146 | return -ENOTTY; | |
147 | ||
7c248223 | 148 | return RET_NERRNO(ioctl(fd, FS_IOC_GETFLAGS, ret)); |
c8b3094d LP |
149 | } |
150 | ||
5e496845 | 151 | int read_attr_at(int dir_fd, const char *path, unsigned *ret) { |
07862c9f DDM |
152 | _cleanup_close_ int fd_close = -EBADF; |
153 | int fd; | |
c8b3094d | 154 | |
5e496845 | 155 | assert(dir_fd >= 0 || dir_fd == AT_FDCWD); |
c8b3094d LP |
156 | assert(ret); |
157 | ||
07862c9f | 158 | if (isempty(path)) { |
26f58977 | 159 | fd = fd_reopen_condition(dir_fd, O_RDONLY|O_CLOEXEC|O_NOCTTY, O_PATH, &fd_close); /* drop O_PATH if it is set */ |
07862c9f DDM |
160 | if (fd < 0) |
161 | return fd; | |
162 | } else { | |
163 | fd_close = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); | |
164 | if (fd_close < 0) | |
165 | return fd_close; | |
166 | ||
167 | fd = fd_close; | |
168 | } | |
c8b3094d LP |
169 | |
170 | return read_attr_fd(fd, ret); | |
171 | } |