]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
18c528e9 | 2 | |
ac83e5ae | 3 | #include <sys/file.h> |
f5947a5e | 4 | #include <unistd.h> |
18c528e9 LP |
5 | |
6 | #include "alloc-util.h" | |
7 | #include "blockdev-util.h" | |
8 | #include "btrfs-util.h" | |
7176f06c | 9 | #include "devnum-util.h" |
18c528e9 LP |
10 | #include "dirent-util.h" |
11 | #include "fd-util.h" | |
12 | #include "fileio.h" | |
f5947a5e | 13 | #include "missing_magic.h" |
3a47c40d | 14 | #include "parse-util.h" |
18c528e9 LP |
15 | |
16 | int block_get_whole_disk(dev_t d, dev_t *ret) { | |
17 | char p[SYS_BLOCK_PATH_MAX("/partition")]; | |
18 | _cleanup_free_ char *s = NULL; | |
3a47c40d | 19 | dev_t devt; |
18c528e9 LP |
20 | int r; |
21 | ||
22 | assert(ret); | |
23 | ||
51f14fa1 LP |
24 | if (major(d) == 0) |
25 | return -ENODEV; | |
26 | ||
18c528e9 LP |
27 | /* If it has a queue this is good enough for us */ |
28 | xsprintf_sys_block_path(p, "/queue", d); | |
29 | if (access(p, F_OK) >= 0) { | |
30 | *ret = d; | |
31 | return 0; | |
32 | } | |
6cba41ab LP |
33 | if (errno != ENOENT) |
34 | return -errno; | |
18c528e9 LP |
35 | |
36 | /* If it is a partition find the originating device */ | |
37 | xsprintf_sys_block_path(p, "/partition", d); | |
38 | if (access(p, F_OK) < 0) | |
d18b3009 | 39 | return -errno; |
18c528e9 LP |
40 | |
41 | /* Get parent dev_t */ | |
42 | xsprintf_sys_block_path(p, "/../dev", d); | |
43 | r = read_one_line_file(p, &s); | |
44 | if (r < 0) | |
45 | return r; | |
46 | ||
7176f06c | 47 | r = parse_devnum(s, &devt); |
3a47c40d LP |
48 | if (r < 0) |
49 | return r; | |
18c528e9 LP |
50 | |
51 | /* Only return this if it is really good enough for us. */ | |
3a47c40d | 52 | xsprintf_sys_block_path(p, "/queue", devt); |
18c528e9 | 53 | if (access(p, F_OK) < 0) |
d18b3009 | 54 | return -errno; |
18c528e9 | 55 | |
3a47c40d | 56 | *ret = devt; |
a0ea1dee | 57 | return 1; |
18c528e9 LP |
58 | } |
59 | ||
0bfef8b4 | 60 | int get_block_device_fd(int fd, dev_t *ret) { |
18c528e9 | 61 | struct stat st; |
db8728a6 | 62 | int r; |
18c528e9 | 63 | |
0bfef8b4 | 64 | assert(fd >= 0); |
db8728a6 | 65 | assert(ret); |
18c528e9 | 66 | |
db8728a6 LP |
67 | /* Gets the block device directly backing a file system. If the block device is encrypted, returns |
68 | * the device mapper block device. */ | |
69 | ||
db8728a6 | 70 | if (fstat(fd, &st)) |
18c528e9 LP |
71 | return -errno; |
72 | ||
73 | if (major(st.st_dev) != 0) { | |
db8728a6 | 74 | *ret = st.st_dev; |
18c528e9 LP |
75 | return 1; |
76 | } | |
77 | ||
db8728a6 LP |
78 | r = btrfs_get_block_device_fd(fd, ret); |
79 | if (r > 0) | |
80 | return 1; | |
81 | if (r != -ENOTTY) /* not btrfs */ | |
82 | return r; | |
18c528e9 | 83 | |
db8728a6 | 84 | *ret = 0; |
18c528e9 LP |
85 | return 0; |
86 | } | |
87 | ||
0bfef8b4 LP |
88 | int get_block_device(const char *path, dev_t *ret) { |
89 | _cleanup_close_ int fd = -1; | |
90 | ||
91 | assert(path); | |
92 | assert(ret); | |
93 | ||
94 | fd = open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC); | |
95 | if (fd < 0) | |
96 | return -errno; | |
97 | ||
98 | return get_block_device_fd(fd, ret); | |
99 | } | |
100 | ||
880f09bd | 101 | int block_get_originating(dev_t dt, dev_t *ret) { |
18c528e9 LP |
102 | _cleanup_closedir_ DIR *d = NULL; |
103 | _cleanup_free_ char *t = NULL; | |
104 | char p[SYS_BLOCK_PATH_MAX("/slaves")]; | |
c68fc351 | 105 | _cleanup_free_ char *first_found = NULL; |
880f09bd | 106 | const char *q; |
3a47c40d | 107 | dev_t devt; |
18c528e9 LP |
108 | int r; |
109 | ||
880f09bd LP |
110 | /* For the specified block device tries to chase it through the layers, in case LUKS-style DM stacking is used, |
111 | * trying to find the next underlying layer. */ | |
18c528e9 LP |
112 | |
113 | xsprintf_sys_block_path(p, "/slaves", dt); | |
114 | d = opendir(p); | |
880f09bd | 115 | if (!d) |
18c528e9 | 116 | return -errno; |
18c528e9 LP |
117 | |
118 | FOREACH_DIRENT_ALL(de, d, return -errno) { | |
119 | ||
120 | if (dot_or_dot_dot(de->d_name)) | |
121 | continue; | |
122 | ||
123 | if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN)) | |
124 | continue; | |
125 | ||
c68fc351 | 126 | if (first_found) { |
18c528e9 LP |
127 | _cleanup_free_ char *u = NULL, *v = NULL, *a = NULL, *b = NULL; |
128 | ||
c68fc351 LP |
129 | /* We found a device backed by multiple other devices. We don't really support |
130 | * automatic discovery on such setups, with the exception of dm-verity partitions. In | |
131 | * this case there are two backing devices: the data partition and the hash | |
132 | * partition. We are fine with such setups, however, only if both partitions are on | |
133 | * the same physical device. Hence, let's verify this by iterating over every node | |
134 | * in the 'slaves/' directory and comparing them with the first that gets returned by | |
135 | * readdir(), to ensure they all point to the same device. */ | |
18c528e9 | 136 | |
657ee2d8 | 137 | u = path_join(p, de->d_name, "../dev"); |
18c528e9 LP |
138 | if (!u) |
139 | return -ENOMEM; | |
140 | ||
c68fc351 | 141 | v = path_join(p, first_found, "../dev"); |
18c528e9 LP |
142 | if (!v) |
143 | return -ENOMEM; | |
144 | ||
145 | r = read_one_line_file(u, &a); | |
880f09bd LP |
146 | if (r < 0) |
147 | return log_debug_errno(r, "Failed to read %s: %m", u); | |
18c528e9 LP |
148 | |
149 | r = read_one_line_file(v, &b); | |
880f09bd LP |
150 | if (r < 0) |
151 | return log_debug_errno(r, "Failed to read %s: %m", v); | |
18c528e9 LP |
152 | |
153 | /* Check if the parent device is the same. If not, then the two backing devices are on | |
154 | * different physical devices, and we don't support that. */ | |
155 | if (!streq(a, b)) | |
880f09bd | 156 | return -ENOTUNIQ; |
c68fc351 LP |
157 | } else { |
158 | first_found = strdup(de->d_name); | |
159 | if (!first_found) | |
160 | return -ENOMEM; | |
18c528e9 | 161 | } |
18c528e9 LP |
162 | } |
163 | ||
c68fc351 | 164 | if (!first_found) |
880f09bd | 165 | return -ENOENT; |
18c528e9 | 166 | |
c68fc351 | 167 | q = strjoina(p, "/", first_found, "/dev"); |
18c528e9 LP |
168 | |
169 | r = read_one_line_file(q, &t); | |
18c528e9 LP |
170 | if (r < 0) |
171 | return r; | |
172 | ||
7176f06c | 173 | r = parse_devnum(t, &devt); |
3a47c40d | 174 | if (r < 0) |
18c528e9 LP |
175 | return -EINVAL; |
176 | ||
3a47c40d | 177 | if (major(devt) == 0) |
880f09bd | 178 | return -ENOENT; |
18c528e9 | 179 | |
3a47c40d | 180 | *ret = devt; |
18c528e9 | 181 | return 1; |
880f09bd LP |
182 | } |
183 | ||
bcf8fc26 | 184 | int get_block_device_harder_fd(int fd, dev_t *ret) { |
880f09bd LP |
185 | int r; |
186 | ||
bcf8fc26 | 187 | assert(fd >= 0); |
880f09bd LP |
188 | assert(ret); |
189 | ||
190 | /* Gets the backing block device for a file system, and handles LUKS encrypted file systems, looking for its | |
191 | * immediate parent, if there is one. */ | |
192 | ||
bcf8fc26 | 193 | r = get_block_device_fd(fd, ret); |
880f09bd LP |
194 | if (r <= 0) |
195 | return r; | |
196 | ||
197 | r = block_get_originating(*ret, ret); | |
198 | if (r < 0) | |
bcf8fc26 | 199 | log_debug_errno(r, "Failed to chase block device, ignoring: %m"); |
18c528e9 | 200 | |
18c528e9 LP |
201 | return 1; |
202 | } | |
ac83e5ae | 203 | |
bcf8fc26 LP |
204 | int get_block_device_harder(const char *path, dev_t *ret) { |
205 | _cleanup_close_ int fd = -1; | |
206 | ||
207 | assert(path); | |
208 | assert(ret); | |
209 | ||
210 | fd = open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC); | |
211 | if (fd < 0) | |
212 | return -errno; | |
213 | ||
214 | return get_block_device_harder_fd(fd, ret); | |
215 | } | |
216 | ||
ac83e5ae LP |
217 | int lock_whole_block_device(dev_t devt, int operation) { |
218 | _cleanup_free_ char *whole_node = NULL; | |
219 | _cleanup_close_ int lock_fd = -1; | |
220 | dev_t whole_devt; | |
221 | int r; | |
222 | ||
223 | /* Let's get a BSD file lock on the whole block device, as per: https://systemd.io/BLOCK_DEVICE_LOCKING */ | |
224 | ||
225 | r = block_get_whole_disk(devt, &whole_devt); | |
226 | if (r < 0) | |
227 | return r; | |
228 | ||
229 | r = device_path_make_major_minor(S_IFBLK, whole_devt, &whole_node); | |
230 | if (r < 0) | |
231 | return r; | |
232 | ||
233 | lock_fd = open(whole_node, O_RDONLY|O_CLOEXEC|O_NONBLOCK); | |
234 | if (lock_fd < 0) | |
235 | return -errno; | |
236 | ||
237 | if (flock(lock_fd, operation) < 0) | |
238 | return -errno; | |
239 | ||
240 | return TAKE_FD(lock_fd); | |
241 | } | |
e8467cd3 LP |
242 | |
243 | int blockdev_partscan_enabled(int fd) { | |
244 | _cleanup_free_ char *p = NULL, *buf = NULL; | |
245 | unsigned long long ull; | |
246 | struct stat st; | |
247 | int r; | |
248 | ||
249 | /* Checks if partition scanning is correctly enabled on the block device */ | |
250 | ||
251 | if (fstat(fd, &st) < 0) | |
252 | return -errno; | |
253 | ||
254 | if (!S_ISBLK(st.st_mode)) | |
255 | return -ENOTBLK; | |
256 | ||
257 | if (asprintf(&p, "/sys/dev/block/%u:%u/capability", major(st.st_rdev), minor(st.st_rdev)) < 0) | |
258 | return -ENOMEM; | |
259 | ||
260 | r = read_one_line_file(p, &buf); | |
261 | if (r == -ENOENT) /* If the capability file doesn't exist then we are most likely looking at a | |
262 | * partition block device, not the whole block device. And that means we have no | |
263 | * partition scanning on for it (we do for its parent, but not for the partition | |
264 | * itself). */ | |
265 | return false; | |
266 | if (r < 0) | |
267 | return r; | |
268 | ||
269 | r = safe_atollu_full(buf, 16, &ull); | |
270 | if (r < 0) | |
271 | return r; | |
272 | ||
273 | #ifndef GENHD_FL_NO_PART_SCAN | |
274 | #define GENHD_FL_NO_PART_SCAN (0x0200) | |
275 | #endif | |
276 | ||
277 | return !FLAGS_SET(ull, GENHD_FL_NO_PART_SCAN); | |
278 | } | |
b25a930f ZJS |
279 | |
280 | static int blockdev_is_encrypted(const char *sysfs_path, unsigned depth_left) { | |
281 | _cleanup_free_ char *p = NULL, *uuids = NULL; | |
282 | _cleanup_closedir_ DIR *d = NULL; | |
283 | int r, found_encrypted = false; | |
284 | ||
285 | assert(sysfs_path); | |
286 | ||
287 | if (depth_left == 0) | |
288 | return -EINVAL; | |
289 | ||
290 | p = path_join(sysfs_path, "dm/uuid"); | |
291 | if (!p) | |
292 | return -ENOMEM; | |
293 | ||
294 | r = read_one_line_file(p, &uuids); | |
295 | if (r != -ENOENT) { | |
296 | if (r < 0) | |
297 | return r; | |
298 | ||
299 | /* The DM device's uuid attribute is prefixed with "CRYPT-" if this is a dm-crypt device. */ | |
300 | if (startswith(uuids, "CRYPT-")) | |
301 | return true; | |
302 | } | |
303 | ||
304 | /* Not a dm-crypt device itself. But maybe it is on top of one? Follow the links in the "slaves/" | |
305 | * subdir. */ | |
306 | ||
307 | p = mfree(p); | |
308 | p = path_join(sysfs_path, "slaves"); | |
309 | if (!p) | |
310 | return -ENOMEM; | |
311 | ||
312 | d = opendir(p); | |
313 | if (!d) { | |
314 | if (errno == ENOENT) /* Doesn't have underlying devices */ | |
315 | return false; | |
316 | ||
317 | return -errno; | |
318 | } | |
319 | ||
320 | for (;;) { | |
321 | _cleanup_free_ char *q = NULL; | |
322 | struct dirent *de; | |
323 | ||
324 | errno = 0; | |
325 | de = readdir_no_dot(d); | |
326 | if (!de) { | |
327 | if (errno != 0) | |
328 | return -errno; | |
329 | ||
330 | break; /* No more underlying devices */ | |
331 | } | |
332 | ||
333 | q = path_join(p, de->d_name); | |
334 | if (!q) | |
335 | return -ENOMEM; | |
336 | ||
337 | r = blockdev_is_encrypted(q, depth_left - 1); | |
338 | if (r < 0) | |
339 | return r; | |
340 | if (r == 0) /* we found one that is not encrypted? then propagate that immediately */ | |
341 | return false; | |
342 | ||
343 | found_encrypted = true; | |
344 | } | |
345 | ||
346 | return found_encrypted; | |
347 | } | |
348 | ||
91358db9 LP |
349 | int fd_is_encrypted(int fd) { |
350 | char p[SYS_BLOCK_PATH_MAX(NULL)]; | |
351 | dev_t devt; | |
352 | int r; | |
353 | ||
354 | r = get_block_device_fd(fd, &devt); | |
355 | if (r < 0) | |
356 | return r; | |
357 | if (r == 0) /* doesn't have a block device */ | |
358 | return false; | |
359 | ||
360 | xsprintf_sys_block_path(p, NULL, devt); | |
361 | ||
362 | return blockdev_is_encrypted(p, 10 /* safety net: maximum recursion depth */); | |
363 | } | |
364 | ||
b25a930f ZJS |
365 | int path_is_encrypted(const char *path) { |
366 | char p[SYS_BLOCK_PATH_MAX(NULL)]; | |
367 | dev_t devt; | |
368 | int r; | |
369 | ||
370 | r = get_block_device(path, &devt); | |
371 | if (r < 0) | |
372 | return r; | |
373 | if (r == 0) /* doesn't have a block device */ | |
374 | return false; | |
375 | ||
376 | xsprintf_sys_block_path(p, NULL, devt); | |
377 | ||
378 | return blockdev_is_encrypted(p, 10 /* safety net: maximum recursion depth */); | |
379 | } | |
26aa4800 DDM |
380 | |
381 | int fd_get_whole_disk(int fd, bool backing, dev_t *ret) { | |
382 | dev_t devt; | |
383 | struct stat st; | |
384 | int r; | |
385 | ||
386 | assert(ret); | |
387 | ||
388 | if (fstat(fd, &st) < 0) | |
389 | return -errno; | |
390 | ||
391 | if (S_ISBLK(st.st_mode)) | |
392 | devt = st.st_rdev; | |
393 | else if (!backing) | |
394 | return -ENOTBLK; | |
395 | else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) | |
396 | return -ENOTBLK; | |
397 | else if (major(st.st_dev) != 0) | |
398 | devt = st.st_dev; | |
399 | else { | |
400 | _cleanup_close_ int regfd = -1; | |
401 | ||
402 | /* If major(st.st_dev) is zero, this might mean we are backed by btrfs, which needs special | |
403 | * handing, to get the backing device node. */ | |
404 | ||
405 | regfd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK); | |
406 | if (regfd < 0) | |
407 | return regfd; | |
408 | ||
409 | r = btrfs_get_block_device_fd(regfd, &devt); | |
410 | if (r == -ENOTTY) | |
411 | return -ENOTBLK; | |
412 | if (r < 0) | |
413 | return r; | |
414 | } | |
415 | ||
416 | return block_get_whole_disk(devt, ret); | |
417 | } | |
418 | ||
419 | int path_get_whole_disk(const char *path, bool backing, dev_t *ret) { | |
420 | _cleanup_close_ int fd = -1; | |
421 | ||
422 | fd = open(path, O_CLOEXEC|O_PATH); | |
423 | if (fd < 0) | |
424 | return -errno; | |
425 | ||
426 | return fd_get_whole_disk(fd, backing, ret); | |
427 | } |