]>
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 | ||
9 | #include "chattr-util.h" | |
c1631ee1 | 10 | #include "errno-util.h" |
c8b3094d | 11 | #include "fd-util.h" |
cf91b915 | 12 | #include "fs-util.h" |
11c3a366 | 13 | #include "macro.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 | ||
c1631ee1 LP |
84 | if ((errno != EINVAL && !ERRNO_IS_NOT_SUPPORTED(errno)) || |
85 | !FLAGS_SET(flags, CHATTR_FALLBACK_BITWISE)) | |
c8b3094d LP |
86 | return -errno; |
87 | ||
459631a0 YW |
88 | /* When -EINVAL is returned, we assume that incompatible attributes are simultaneously |
89 | * specified. E.g., compress(c) and nocow(C) attributes cannot be set to files on btrfs. | |
c1631ee1 LP |
90 | * As a fallback, let's try to set attributes one by one. |
91 | * | |
92 | * Also, when we get EOPNOTSUPP (or a similar error code) we assume a flag might just not be | |
93 | * supported, and we can ignore it too */ | |
db9a4254 | 94 | |
459631a0 YW |
95 | unsigned current_attr = old_attr; |
96 | for (unsigned i = 0; i < sizeof(unsigned) * 8; i++) { | |
97 | unsigned new_one, mask_one = 1u << i; | |
c8b3094d | 98 | |
459631a0 YW |
99 | if (!FLAGS_SET(mask, mask_one)) |
100 | continue; | |
c8b3094d | 101 | |
459631a0 YW |
102 | new_one = UPDATE_FLAG(current_attr, mask_one, FLAGS_SET(value, mask_one)); |
103 | if (new_one == current_attr) | |
104 | continue; | |
c8b3094d | 105 | |
459631a0 | 106 | if (ioctl(fd, FS_IOC_SETFLAGS, &new_one) < 0) { |
c1631ee1 | 107 | if (errno != EINVAL && !ERRNO_IS_NOT_SUPPORTED(errno)) |
459631a0 | 108 | return -errno; |
c1631ee1 LP |
109 | |
110 | log_full_errno(FLAGS_SET(flags, CHATTR_WARN_UNSUPPORTED_FLAGS) ? LOG_WARNING : LOG_DEBUG, | |
111 | errno, | |
112 | "Unable to set file attribute 0x%x on %s, ignoring: %m", mask_one, strna(path)); | |
7c3b51c4 LB |
113 | |
114 | /* Ensures that we record whether only EOPNOTSUPP&friends are encountered, or if a more serious | |
115 | * error (thus worth logging at a different level, etc) was seen too. */ | |
116 | if (set_flags_errno == 0 || !ERRNO_IS_NOT_SUPPORTED(errno)) | |
117 | set_flags_errno = -errno; | |
118 | ||
459631a0 YW |
119 | continue; |
120 | } | |
c8b3094d | 121 | |
459631a0 YW |
122 | if (ioctl(fd, FS_IOC_GETFLAGS, ¤t_attr) < 0) |
123 | return -errno; | |
124 | } | |
125 | ||
126 | if (ret_previous) | |
127 | *ret_previous = old_attr; | |
128 | if (ret_final) | |
129 | *ret_final = current_attr; | |
c8b3094d | 130 | |
7c3b51c4 LB |
131 | /* -ENOANO indicates that some attributes cannot be set. ERRNO_IS_NOT_SUPPORTED indicates that all |
132 | * encountered failures were due to flags not supported by the FS, so return a specific error in | |
133 | * that case, so callers can handle it properly (e.g.: tmpfiles.d can use debug level logging). */ | |
134 | return current_attr == new_attr ? 1 : ERRNO_IS_NOT_SUPPORTED(set_flags_errno) ? set_flags_errno : -ENOANO; | |
c8b3094d LP |
135 | } |
136 | ||
137 | int read_attr_fd(int fd, unsigned *ret) { | |
138 | struct stat st; | |
139 | ||
140 | assert(fd >= 0); | |
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 | ||
151 | int read_attr_path(const char *p, unsigned *ret) { | |
254d1313 | 152 | _cleanup_close_ int fd = -EBADF; |
c8b3094d LP |
153 | |
154 | assert(p); | |
155 | assert(ret); | |
156 | ||
157 | fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); | |
158 | if (fd < 0) | |
159 | return -errno; | |
160 | ||
161 | return read_attr_fd(fd, ret); | |
162 | } |