]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
18c528e9 | 2 | |
d25697f5 | 3 | #include <linux/blkpg.h> |
ac83e5ae | 4 | #include <sys/file.h> |
d25697f5 | 5 | #include <sys/ioctl.h> |
86f9b69a | 6 | #include <sys/mount.h> |
f5947a5e | 7 | #include <unistd.h> |
18c528e9 | 8 | |
35d40302 DDM |
9 | #include "sd-device.h" |
10 | ||
18c528e9 LP |
11 | #include "alloc-util.h" |
12 | #include "blockdev-util.h" | |
13 | #include "btrfs-util.h" | |
35d40302 | 14 | #include "device-util.h" |
7176f06c | 15 | #include "devnum-util.h" |
18c528e9 | 16 | #include "dirent-util.h" |
d25697f5 | 17 | #include "errno-util.h" |
18c528e9 LP |
18 | #include "fd-util.h" |
19 | #include "fileio.h" | |
c56be2c2 | 20 | #include "fs-util.h" |
f5947a5e | 21 | #include "missing_magic.h" |
3a47c40d | 22 | #include "parse-util.h" |
18c528e9 | 23 | |
fd690a4b | 24 | static int fd_get_devnum(int fd, BlockDeviceLookupFlag flags, dev_t *ret) { |
7e4a0387 YW |
25 | struct stat st; |
26 | dev_t devnum; | |
27 | int r; | |
28 | ||
29 | assert(fd >= 0); | |
30 | assert(ret); | |
31 | ||
32 | if (fstat(fd, &st) < 0) | |
33 | return -errno; | |
34 | ||
35 | if (S_ISBLK(st.st_mode)) | |
36 | devnum = st.st_rdev; | |
fd690a4b | 37 | else if (!FLAGS_SET(flags, BLOCK_DEVICE_LOOKUP_BACKING)) |
7e4a0387 YW |
38 | return -ENOTBLK; |
39 | else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) | |
40 | return -ENOTBLK; | |
41 | else if (major(st.st_dev) != 0) | |
42 | devnum = st.st_dev; | |
43 | else { | |
7e4a0387 YW |
44 | /* If major(st.st_dev) is zero, this might mean we are backed by btrfs, which needs special |
45 | * handing, to get the backing device node. */ | |
46 | ||
698bc186 | 47 | r = btrfs_get_block_device_fd(fd, &devnum); |
7e4a0387 YW |
48 | if (r == -ENOTTY) /* not btrfs */ |
49 | return -ENOTBLK; | |
50 | if (r < 0) | |
51 | return r; | |
52 | } | |
53 | ||
54 | *ret = devnum; | |
55 | return 0; | |
56 | } | |
57 | ||
2f9d2317 | 58 | int block_device_is_whole_disk(sd_device *dev) { |
c07186ec YW |
59 | const char *s; |
60 | int r; | |
61 | ||
62 | assert(dev); | |
63 | ||
64 | r = sd_device_get_subsystem(dev, &s); | |
65 | if (r < 0) | |
66 | return r; | |
67 | ||
68 | if (!streq(s, "block")) | |
69 | return -ENOTBLK; | |
70 | ||
71 | r = sd_device_get_devtype(dev, &s); | |
72 | if (r < 0) | |
73 | return r; | |
74 | ||
75 | return streq(s, "disk"); | |
76 | } | |
77 | ||
55a437f8 YW |
78 | int block_device_get_whole_disk(sd_device *dev, sd_device **ret) { |
79 | int r; | |
80 | ||
81 | assert(dev); | |
82 | assert(ret); | |
83 | ||
84 | /* Do not unref returned sd_device object. */ | |
85 | ||
86 | r = block_device_is_whole_disk(dev); | |
87 | if (r < 0) | |
88 | return r; | |
89 | if (r == 0) { | |
90 | r = sd_device_get_parent(dev, &dev); | |
91 | if (r == -ENOENT) /* Already removed? Let's return a recognizable error. */ | |
92 | return -ENODEV; | |
93 | if (r < 0) | |
94 | return r; | |
95 | ||
96 | r = block_device_is_whole_disk(dev); | |
97 | if (r < 0) | |
98 | return r; | |
99 | if (r == 0) | |
100 | return -ENXIO; | |
101 | } | |
102 | ||
103 | *ret = dev; | |
104 | return 0; | |
105 | } | |
106 | ||
50ab93eb | 107 | int block_device_get_originating(sd_device *dev, sd_device **ret) { |
612fc70f YW |
108 | _cleanup_(sd_device_unrefp) sd_device *first_found = NULL; |
109 | const char *suffix; | |
612fc70f YW |
110 | dev_t devnum = 0; /* avoid false maybe-uninitialized warning */ |
111 | ||
112 | /* For the specified block device tries to chase it through the layers, in case LUKS-style DM | |
113 | * stacking is used, trying to find the next underlying layer. */ | |
114 | ||
115 | assert(dev); | |
116 | assert(ret); | |
117 | ||
118 | FOREACH_DEVICE_CHILD_WITH_SUFFIX(dev, child, suffix) { | |
119 | sd_device *child_whole_disk; | |
120 | dev_t n; | |
121 | ||
122 | if (!path_startswith(suffix, "slaves")) | |
123 | continue; | |
124 | ||
125 | if (block_device_get_whole_disk(child, &child_whole_disk) < 0) | |
126 | continue; | |
127 | ||
128 | if (sd_device_get_devnum(child_whole_disk, &n) < 0) | |
129 | continue; | |
130 | ||
131 | if (!first_found) { | |
132 | first_found = sd_device_ref(child); | |
133 | devnum = n; | |
134 | continue; | |
135 | } | |
136 | ||
137 | /* We found a device backed by multiple other devices. We don't really support automatic | |
138 | * discovery on such setups, with the exception of dm-verity partitions. In this case there | |
139 | * are two backing devices: the data partition and the hash partition. We are fine with such | |
140 | * setups, however, only if both partitions are on the same physical device. Hence, let's | |
141 | * verify this by iterating over every node in the 'slaves/' directory and comparing them with | |
142 | * the first that gets returned by readdir(), to ensure they all point to the same device. */ | |
143 | if (n != devnum) | |
144 | return -ENOTUNIQ; | |
145 | } | |
146 | ||
147 | if (!first_found) | |
148 | return -ENOENT; | |
149 | ||
150 | *ret = TAKE_PTR(first_found); | |
151 | return 1; /* found */ | |
152 | } | |
153 | ||
fd690a4b YW |
154 | int block_device_new_from_fd(int fd, BlockDeviceLookupFlag flags, sd_device **ret) { |
155 | _cleanup_(sd_device_unrefp) sd_device *dev = NULL; | |
156 | dev_t devnum; | |
157 | int r; | |
158 | ||
159 | assert(fd >= 0); | |
160 | assert(ret); | |
161 | ||
162 | r = fd_get_devnum(fd, flags, &devnum); | |
163 | if (r < 0) | |
164 | return r; | |
165 | ||
166 | r = sd_device_new_from_devnum(&dev, 'b', devnum); | |
167 | if (r < 0) | |
168 | return r; | |
169 | ||
170 | if (FLAGS_SET(flags, BLOCK_DEVICE_LOOKUP_ORIGINATING)) { | |
171 | _cleanup_(sd_device_unrefp) sd_device *dev_origin = NULL; | |
172 | sd_device *dev_whole_disk; | |
173 | ||
174 | r = block_device_get_whole_disk(dev, &dev_whole_disk); | |
175 | if (r < 0) | |
176 | return r; | |
177 | ||
178 | r = block_device_get_originating(dev_whole_disk, &dev_origin); | |
179 | if (r < 0 && r != -ENOENT) | |
180 | return r; | |
181 | if (r > 0) | |
182 | device_unref_and_replace(dev, dev_origin); | |
183 | } | |
184 | ||
185 | if (FLAGS_SET(flags, BLOCK_DEVICE_LOOKUP_WHOLE_DISK)) { | |
186 | sd_device *dev_whole_disk; | |
187 | ||
188 | r = block_device_get_whole_disk(dev, &dev_whole_disk); | |
189 | if (r < 0) | |
190 | return r; | |
191 | ||
192 | *ret = sd_device_ref(dev_whole_disk); | |
193 | return 0; | |
194 | } | |
195 | ||
196 | *ret = sd_device_ref(dev); | |
197 | return 0; | |
198 | } | |
199 | ||
200 | int block_device_new_from_path(const char *path, BlockDeviceLookupFlag flags, sd_device **ret) { | |
254d1313 | 201 | _cleanup_close_ int fd = -EBADF; |
fd690a4b YW |
202 | |
203 | assert(path); | |
204 | assert(ret); | |
205 | ||
206 | fd = open(path, O_CLOEXEC|O_PATH); | |
207 | if (fd < 0) | |
208 | return -errno; | |
209 | ||
210 | return block_device_new_from_fd(fd, flags, ret); | |
211 | } | |
212 | ||
18c528e9 LP |
213 | int block_get_whole_disk(dev_t d, dev_t *ret) { |
214 | char p[SYS_BLOCK_PATH_MAX("/partition")]; | |
215 | _cleanup_free_ char *s = NULL; | |
3a47c40d | 216 | dev_t devt; |
18c528e9 LP |
217 | int r; |
218 | ||
219 | assert(ret); | |
220 | ||
51f14fa1 LP |
221 | if (major(d) == 0) |
222 | return -ENODEV; | |
223 | ||
18c528e9 LP |
224 | /* If it has a queue this is good enough for us */ |
225 | xsprintf_sys_block_path(p, "/queue", d); | |
226 | if (access(p, F_OK) >= 0) { | |
227 | *ret = d; | |
228 | return 0; | |
229 | } | |
6cba41ab LP |
230 | if (errno != ENOENT) |
231 | return -errno; | |
18c528e9 LP |
232 | |
233 | /* If it is a partition find the originating device */ | |
234 | xsprintf_sys_block_path(p, "/partition", d); | |
235 | if (access(p, F_OK) < 0) | |
d18b3009 | 236 | return -errno; |
18c528e9 LP |
237 | |
238 | /* Get parent dev_t */ | |
239 | xsprintf_sys_block_path(p, "/../dev", d); | |
240 | r = read_one_line_file(p, &s); | |
241 | if (r < 0) | |
242 | return r; | |
243 | ||
7176f06c | 244 | r = parse_devnum(s, &devt); |
3a47c40d LP |
245 | if (r < 0) |
246 | return r; | |
18c528e9 LP |
247 | |
248 | /* Only return this if it is really good enough for us. */ | |
3a47c40d | 249 | xsprintf_sys_block_path(p, "/queue", devt); |
18c528e9 | 250 | if (access(p, F_OK) < 0) |
d18b3009 | 251 | return -errno; |
18c528e9 | 252 | |
3a47c40d | 253 | *ret = devt; |
a0ea1dee | 254 | return 1; |
18c528e9 LP |
255 | } |
256 | ||
0bfef8b4 | 257 | int get_block_device_fd(int fd, dev_t *ret) { |
18c528e9 | 258 | struct stat st; |
db8728a6 | 259 | int r; |
18c528e9 | 260 | |
0bfef8b4 | 261 | assert(fd >= 0); |
db8728a6 | 262 | assert(ret); |
18c528e9 | 263 | |
db8728a6 LP |
264 | /* Gets the block device directly backing a file system. If the block device is encrypted, returns |
265 | * the device mapper block device. */ | |
266 | ||
db8728a6 | 267 | if (fstat(fd, &st)) |
18c528e9 LP |
268 | return -errno; |
269 | ||
270 | if (major(st.st_dev) != 0) { | |
db8728a6 | 271 | *ret = st.st_dev; |
18c528e9 LP |
272 | return 1; |
273 | } | |
274 | ||
698bc186 | 275 | r = btrfs_get_block_device_fd(fd, ret); |
db8728a6 LP |
276 | if (r > 0) |
277 | return 1; | |
278 | if (r != -ENOTTY) /* not btrfs */ | |
279 | return r; | |
18c528e9 | 280 | |
db8728a6 | 281 | *ret = 0; |
18c528e9 LP |
282 | return 0; |
283 | } | |
284 | ||
0bfef8b4 | 285 | int get_block_device(const char *path, dev_t *ret) { |
254d1313 | 286 | _cleanup_close_ int fd = -EBADF; |
0bfef8b4 LP |
287 | |
288 | assert(path); | |
289 | assert(ret); | |
290 | ||
291 | fd = open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC); | |
292 | if (fd < 0) | |
293 | return -errno; | |
294 | ||
295 | return get_block_device_fd(fd, ret); | |
296 | } | |
297 | ||
880f09bd | 298 | int block_get_originating(dev_t dt, dev_t *ret) { |
612fc70f | 299 | _cleanup_(sd_device_unrefp) sd_device *dev = NULL, *origin = NULL; |
18c528e9 LP |
300 | int r; |
301 | ||
612fc70f | 302 | assert(ret); |
18c528e9 | 303 | |
612fc70f | 304 | r = sd_device_new_from_devnum(&dev, 'b', dt); |
18c528e9 LP |
305 | if (r < 0) |
306 | return r; | |
307 | ||
612fc70f | 308 | r = block_device_get_originating(dev, &origin); |
3a47c40d | 309 | if (r < 0) |
612fc70f | 310 | return r; |
18c528e9 | 311 | |
612fc70f | 312 | return sd_device_get_devnum(origin, ret); |
880f09bd LP |
313 | } |
314 | ||
bcf8fc26 | 315 | int get_block_device_harder_fd(int fd, dev_t *ret) { |
880f09bd LP |
316 | int r; |
317 | ||
bcf8fc26 | 318 | assert(fd >= 0); |
880f09bd LP |
319 | assert(ret); |
320 | ||
321 | /* Gets the backing block device for a file system, and handles LUKS encrypted file systems, looking for its | |
322 | * immediate parent, if there is one. */ | |
323 | ||
bcf8fc26 | 324 | r = get_block_device_fd(fd, ret); |
880f09bd LP |
325 | if (r <= 0) |
326 | return r; | |
327 | ||
328 | r = block_get_originating(*ret, ret); | |
329 | if (r < 0) | |
bcf8fc26 | 330 | log_debug_errno(r, "Failed to chase block device, ignoring: %m"); |
18c528e9 | 331 | |
18c528e9 LP |
332 | return 1; |
333 | } | |
ac83e5ae | 334 | |
bcf8fc26 | 335 | int get_block_device_harder(const char *path, dev_t *ret) { |
254d1313 | 336 | _cleanup_close_ int fd = -EBADF; |
bcf8fc26 LP |
337 | |
338 | assert(path); | |
339 | assert(ret); | |
340 | ||
341 | fd = open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC); | |
342 | if (fd < 0) | |
343 | return -errno; | |
344 | ||
345 | return get_block_device_harder_fd(fd, ret); | |
346 | } | |
347 | ||
ac83e5ae | 348 | int lock_whole_block_device(dev_t devt, int operation) { |
254d1313 | 349 | _cleanup_close_ int lock_fd = -EBADF; |
ac83e5ae LP |
350 | dev_t whole_devt; |
351 | int r; | |
352 | ||
353 | /* Let's get a BSD file lock on the whole block device, as per: https://systemd.io/BLOCK_DEVICE_LOCKING */ | |
354 | ||
355 | r = block_get_whole_disk(devt, &whole_devt); | |
356 | if (r < 0) | |
357 | return r; | |
358 | ||
ca822829 | 359 | lock_fd = r = device_open_from_devnum(S_IFBLK, whole_devt, O_RDONLY|O_CLOEXEC|O_NONBLOCK, NULL); |
ac83e5ae LP |
360 | if (r < 0) |
361 | return r; | |
362 | ||
ac83e5ae LP |
363 | if (flock(lock_fd, operation) < 0) |
364 | return -errno; | |
365 | ||
366 | return TAKE_FD(lock_fd); | |
367 | } | |
e8467cd3 LP |
368 | |
369 | int blockdev_partscan_enabled(int fd) { | |
370 | _cleanup_free_ char *p = NULL, *buf = NULL; | |
371 | unsigned long long ull; | |
372 | struct stat st; | |
373 | int r; | |
374 | ||
375 | /* Checks if partition scanning is correctly enabled on the block device */ | |
376 | ||
377 | if (fstat(fd, &st) < 0) | |
378 | return -errno; | |
379 | ||
380 | if (!S_ISBLK(st.st_mode)) | |
381 | return -ENOTBLK; | |
382 | ||
383 | if (asprintf(&p, "/sys/dev/block/%u:%u/capability", major(st.st_rdev), minor(st.st_rdev)) < 0) | |
384 | return -ENOMEM; | |
385 | ||
386 | r = read_one_line_file(p, &buf); | |
387 | if (r == -ENOENT) /* If the capability file doesn't exist then we are most likely looking at a | |
388 | * partition block device, not the whole block device. And that means we have no | |
389 | * partition scanning on for it (we do for its parent, but not for the partition | |
390 | * itself). */ | |
391 | return false; | |
392 | if (r < 0) | |
393 | return r; | |
394 | ||
395 | r = safe_atollu_full(buf, 16, &ull); | |
396 | if (r < 0) | |
397 | return r; | |
398 | ||
399 | #ifndef GENHD_FL_NO_PART_SCAN | |
400 | #define GENHD_FL_NO_PART_SCAN (0x0200) | |
401 | #endif | |
402 | ||
403 | return !FLAGS_SET(ull, GENHD_FL_NO_PART_SCAN); | |
404 | } | |
b25a930f ZJS |
405 | |
406 | static int blockdev_is_encrypted(const char *sysfs_path, unsigned depth_left) { | |
407 | _cleanup_free_ char *p = NULL, *uuids = NULL; | |
408 | _cleanup_closedir_ DIR *d = NULL; | |
409 | int r, found_encrypted = false; | |
410 | ||
411 | assert(sysfs_path); | |
412 | ||
413 | if (depth_left == 0) | |
414 | return -EINVAL; | |
415 | ||
416 | p = path_join(sysfs_path, "dm/uuid"); | |
417 | if (!p) | |
418 | return -ENOMEM; | |
419 | ||
420 | r = read_one_line_file(p, &uuids); | |
421 | if (r != -ENOENT) { | |
422 | if (r < 0) | |
423 | return r; | |
424 | ||
425 | /* The DM device's uuid attribute is prefixed with "CRYPT-" if this is a dm-crypt device. */ | |
426 | if (startswith(uuids, "CRYPT-")) | |
427 | return true; | |
428 | } | |
429 | ||
430 | /* Not a dm-crypt device itself. But maybe it is on top of one? Follow the links in the "slaves/" | |
431 | * subdir. */ | |
432 | ||
433 | p = mfree(p); | |
434 | p = path_join(sysfs_path, "slaves"); | |
435 | if (!p) | |
436 | return -ENOMEM; | |
437 | ||
438 | d = opendir(p); | |
439 | if (!d) { | |
440 | if (errno == ENOENT) /* Doesn't have underlying devices */ | |
441 | return false; | |
442 | ||
443 | return -errno; | |
444 | } | |
445 | ||
446 | for (;;) { | |
447 | _cleanup_free_ char *q = NULL; | |
448 | struct dirent *de; | |
449 | ||
450 | errno = 0; | |
451 | de = readdir_no_dot(d); | |
452 | if (!de) { | |
453 | if (errno != 0) | |
454 | return -errno; | |
455 | ||
456 | break; /* No more underlying devices */ | |
457 | } | |
458 | ||
459 | q = path_join(p, de->d_name); | |
460 | if (!q) | |
461 | return -ENOMEM; | |
462 | ||
463 | r = blockdev_is_encrypted(q, depth_left - 1); | |
464 | if (r < 0) | |
465 | return r; | |
466 | if (r == 0) /* we found one that is not encrypted? then propagate that immediately */ | |
467 | return false; | |
468 | ||
469 | found_encrypted = true; | |
470 | } | |
471 | ||
472 | return found_encrypted; | |
473 | } | |
474 | ||
91358db9 LP |
475 | int fd_is_encrypted(int fd) { |
476 | char p[SYS_BLOCK_PATH_MAX(NULL)]; | |
477 | dev_t devt; | |
478 | int r; | |
479 | ||
480 | r = get_block_device_fd(fd, &devt); | |
481 | if (r < 0) | |
482 | return r; | |
483 | if (r == 0) /* doesn't have a block device */ | |
484 | return false; | |
485 | ||
486 | xsprintf_sys_block_path(p, NULL, devt); | |
487 | ||
488 | return blockdev_is_encrypted(p, 10 /* safety net: maximum recursion depth */); | |
489 | } | |
490 | ||
b25a930f ZJS |
491 | int path_is_encrypted(const char *path) { |
492 | char p[SYS_BLOCK_PATH_MAX(NULL)]; | |
493 | dev_t devt; | |
494 | int r; | |
495 | ||
496 | r = get_block_device(path, &devt); | |
497 | if (r < 0) | |
498 | return r; | |
499 | if (r == 0) /* doesn't have a block device */ | |
500 | return false; | |
501 | ||
502 | xsprintf_sys_block_path(p, NULL, devt); | |
503 | ||
504 | return blockdev_is_encrypted(p, 10 /* safety net: maximum recursion depth */); | |
505 | } | |
26aa4800 DDM |
506 | |
507 | int fd_get_whole_disk(int fd, bool backing, dev_t *ret) { | |
508 | dev_t devt; | |
26aa4800 DDM |
509 | int r; |
510 | ||
7e4a0387 | 511 | assert(fd >= 0); |
26aa4800 DDM |
512 | assert(ret); |
513 | ||
fd690a4b | 514 | r = fd_get_devnum(fd, backing ? BLOCK_DEVICE_LOOKUP_BACKING : 0, &devt); |
7e4a0387 YW |
515 | if (r < 0) |
516 | return r; | |
26aa4800 DDM |
517 | |
518 | return block_get_whole_disk(devt, ret); | |
519 | } | |
520 | ||
521 | int path_get_whole_disk(const char *path, bool backing, dev_t *ret) { | |
254d1313 | 522 | _cleanup_close_ int fd = -EBADF; |
26aa4800 DDM |
523 | |
524 | fd = open(path, O_CLOEXEC|O_PATH); | |
525 | if (fd < 0) | |
526 | return -errno; | |
527 | ||
528 | return fd_get_whole_disk(fd, backing, ret); | |
529 | } | |
d25697f5 | 530 | |
91e1ce1a LP |
531 | int block_device_add_partition( |
532 | int fd, | |
533 | const char *name, | |
534 | int nr, | |
535 | uint64_t start, | |
536 | uint64_t size) { | |
537 | ||
d25697f5 DDM |
538 | assert(fd >= 0); |
539 | assert(name); | |
540 | assert(nr > 0); | |
541 | ||
542 | struct blkpg_partition bp = { | |
543 | .pno = nr, | |
544 | .start = start, | |
545 | .length = size, | |
546 | }; | |
547 | ||
548 | struct blkpg_ioctl_arg ba = { | |
549 | .op = BLKPG_ADD_PARTITION, | |
550 | .data = &bp, | |
551 | .datalen = sizeof(bp), | |
552 | }; | |
553 | ||
554 | if (strlen(name) >= sizeof(bp.devname)) | |
555 | return -EINVAL; | |
556 | ||
557 | strcpy(bp.devname, name); | |
558 | ||
559 | return RET_NERRNO(ioctl(fd, BLKPG, &ba)); | |
560 | } | |
561 | ||
91e1ce1a LP |
562 | int block_device_remove_partition( |
563 | int fd, | |
564 | const char *name, | |
565 | int nr) { | |
566 | ||
d25697f5 DDM |
567 | assert(fd >= 0); |
568 | assert(name); | |
569 | assert(nr > 0); | |
570 | ||
571 | struct blkpg_partition bp = { | |
572 | .pno = nr, | |
573 | }; | |
574 | ||
575 | struct blkpg_ioctl_arg ba = { | |
576 | .op = BLKPG_DEL_PARTITION, | |
577 | .data = &bp, | |
578 | .datalen = sizeof(bp), | |
579 | }; | |
580 | ||
581 | if (strlen(name) >= sizeof(bp.devname)) | |
582 | return -EINVAL; | |
583 | ||
584 | strcpy(bp.devname, name); | |
585 | ||
586 | return RET_NERRNO(ioctl(fd, BLKPG, &ba)); | |
587 | } | |
35d40302 | 588 | |
91e1ce1a LP |
589 | int block_device_resize_partition( |
590 | int fd, | |
591 | int nr, | |
592 | uint64_t start, | |
593 | uint64_t size) { | |
594 | ||
595 | assert(fd >= 0); | |
596 | assert(nr > 0); | |
597 | ||
598 | struct blkpg_partition bp = { | |
599 | .pno = nr, | |
600 | .start = start, | |
601 | .length = size, | |
602 | }; | |
603 | ||
604 | struct blkpg_ioctl_arg ba = { | |
605 | .op = BLKPG_RESIZE_PARTITION, | |
606 | .data = &bp, | |
607 | .datalen = sizeof(bp), | |
608 | }; | |
609 | ||
610 | return RET_NERRNO(ioctl(fd, BLKPG, &ba)); | |
611 | } | |
612 | ||
9409174e | 613 | int partition_enumerator_new(sd_device *dev, sd_device_enumerator **ret) { |
af15ee03 YW |
614 | _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; |
615 | const char *s; | |
616 | int r; | |
617 | ||
618 | assert(dev); | |
619 | assert(ret); | |
620 | ||
c07186ec YW |
621 | /* Refuse invocation on partition block device, insist on "whole" device */ |
622 | r = block_device_is_whole_disk(dev); | |
af15ee03 YW |
623 | if (r < 0) |
624 | return r; | |
c07186ec | 625 | if (r == 0) |
5a27af52 | 626 | return -ENXIO; /* return a recognizable error */ |
af15ee03 YW |
627 | |
628 | r = sd_device_enumerator_new(&e); | |
629 | if (r < 0) | |
630 | return r; | |
631 | ||
632 | r = sd_device_enumerator_allow_uninitialized(e); | |
633 | if (r < 0) | |
634 | return r; | |
635 | ||
636 | r = sd_device_enumerator_add_match_parent(e, dev); | |
637 | if (r < 0) | |
638 | return r; | |
639 | ||
e0993236 YW |
640 | r = sd_device_get_sysname(dev, &s); |
641 | if (r < 0) | |
642 | return r; | |
643 | ||
644 | /* Also add sysname check for safety. Hopefully, this also improves performance. */ | |
645 | s = strjoina(s, "*"); | |
646 | r = sd_device_enumerator_add_match_sysname(e, s); | |
647 | if (r < 0) | |
648 | return r; | |
649 | ||
af15ee03 YW |
650 | r = sd_device_enumerator_add_match_subsystem(e, "block", /* match = */ true); |
651 | if (r < 0) | |
652 | return r; | |
653 | ||
654 | r = sd_device_enumerator_add_match_property(e, "DEVTYPE", "partition"); | |
655 | if (r < 0) | |
656 | return r; | |
657 | ||
658 | *ret = TAKE_PTR(e); | |
659 | return 0; | |
660 | } | |
661 | ||
46c3a288 | 662 | int block_device_remove_all_partitions(sd_device *dev, int fd) { |
35d40302 | 663 | _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; |
46c3a288 | 664 | _cleanup_(sd_device_unrefp) sd_device *dev_unref = NULL; |
254d1313 | 665 | _cleanup_close_ int fd_close = -EBADF; |
833106b8 | 666 | bool has_partitions = false; |
35d40302 DDM |
667 | int r, k = 0; |
668 | ||
46c3a288 | 669 | assert(dev || fd >= 0); |
35d40302 | 670 | |
46c3a288 | 671 | if (!dev) { |
fd690a4b | 672 | r = block_device_new_from_fd(fd, 0, &dev_unref); |
46c3a288 YW |
673 | if (r < 0) |
674 | return r; | |
675 | ||
676 | dev = dev_unref; | |
677 | } | |
35d40302 | 678 | |
af15ee03 | 679 | r = partition_enumerator_new(dev, &e); |
35d40302 DDM |
680 | if (r < 0) |
681 | return r; | |
682 | ||
46c3a288 YW |
683 | if (fd < 0) { |
684 | fd_close = sd_device_open(dev, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_RDONLY); | |
685 | if (fd_close < 0) | |
686 | return fd_close; | |
687 | ||
688 | fd = fd_close; | |
689 | } | |
690 | ||
35d40302 DDM |
691 | FOREACH_DEVICE(e, part) { |
692 | const char *v, *devname; | |
693 | int nr; | |
694 | ||
833106b8 YW |
695 | has_partitions = true; |
696 | ||
35d40302 DDM |
697 | r = sd_device_get_devname(part, &devname); |
698 | if (r < 0) | |
699 | return r; | |
700 | ||
701 | r = sd_device_get_property_value(part, "PARTN", &v); | |
702 | if (r < 0) | |
703 | return r; | |
704 | ||
705 | r = safe_atoi(v, &nr); | |
706 | if (r < 0) | |
707 | return r; | |
708 | ||
5228c58e DDM |
709 | r = btrfs_forget_device(devname); |
710 | if (r < 0 && r != -ENOENT) | |
711 | log_debug_errno(r, "Failed to forget btrfs device %s, ignoring: %m", devname); | |
712 | ||
35d40302 DDM |
713 | r = block_device_remove_partition(fd, devname, nr); |
714 | if (r == -ENODEV) { | |
715 | log_debug("Kernel removed partition %s before us, ignoring", devname); | |
716 | continue; | |
717 | } | |
718 | if (r < 0) { | |
719 | log_debug_errno(r, "Failed to remove partition %s: %m", devname); | |
833106b8 | 720 | k = k < 0 ? k : r; |
35d40302 DDM |
721 | continue; |
722 | } | |
723 | ||
724 | log_debug("Removed partition %s", devname); | |
725 | } | |
726 | ||
833106b8 | 727 | return k < 0 ? k : has_partitions; |
35d40302 | 728 | } |
aa0295f1 YW |
729 | |
730 | int block_device_has_partitions(sd_device *dev) { | |
731 | _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; | |
732 | int r; | |
733 | ||
734 | assert(dev); | |
735 | ||
736 | /* Checks if the specified device currently has partitions. */ | |
737 | ||
738 | r = partition_enumerator_new(dev, &e); | |
739 | if (r < 0) | |
740 | return r; | |
741 | ||
742 | return !!sd_device_enumerator_get_device_first(e); | |
743 | } | |
86f9b69a YW |
744 | |
745 | int blockdev_reread_partition_table(sd_device *dev) { | |
254d1313 | 746 | _cleanup_close_ int fd = -EBADF; |
86f9b69a YW |
747 | |
748 | assert(dev); | |
749 | ||
750 | /* Try to re-read the partition table. This only succeeds if none of the devices is busy. */ | |
751 | ||
752 | fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); | |
753 | if (fd < 0) | |
754 | return fd; | |
755 | ||
756 | if (flock(fd, LOCK_EX|LOCK_NB) < 0) | |
757 | return -errno; | |
758 | ||
759 | if (ioctl(fd, BLKRRPART, 0) < 0) | |
760 | return -errno; | |
761 | ||
762 | return 0; | |
763 | } | |
65046b92 LP |
764 | |
765 | int blockdev_get_sector_size(int fd, uint32_t *ret) { | |
766 | int ssz = 0; | |
767 | ||
768 | assert(fd >= 0); | |
769 | assert(ret); | |
770 | ||
771 | if (ioctl(fd, BLKSSZGET, &ssz) < 0) | |
772 | return -errno; | |
773 | if (ssz <= 0) /* make sure the field is initialized */ | |
774 | return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Block device reported invalid sector size %i.", ssz); | |
775 | ||
776 | *ret = ssz; | |
777 | return 0; | |
778 | } | |
c56be2c2 | 779 | |
01db9c85 LP |
780 | int blockdev_get_device_size(int fd, uint64_t *ret) { |
781 | uint64_t sz = 0; | |
782 | ||
783 | assert(fd >= 0); | |
784 | assert(ret); | |
785 | ||
786 | /* This is just a type-safe wrapper around BLKGETSIZE64 that gets us around having to include messy linux/fs.h in various clients */ | |
787 | ||
788 | if (ioctl(fd, BLKGETSIZE64, &sz) < 0) | |
789 | return -errno; | |
790 | ||
791 | *ret = sz; | |
792 | return 0; | |
793 | } | |
794 | ||
c56be2c2 LP |
795 | int blockdev_get_root(int level, dev_t *ret) { |
796 | _cleanup_free_ char *p = NULL; | |
797 | dev_t devno; | |
798 | int r; | |
799 | ||
800 | /* Returns the device node backing the root file system. Traces through | |
801 | * dm-crypt/dm-verity/... Returns > 0 and the devno of the device on success. If there's no block | |
802 | * device (or multiple) returns 0 and a devno of 0. Failure otherwise. | |
803 | * | |
804 | * If the root mount has been replaced by some form of volatile file system (overlayfs), the original | |
805 | * root block device node is symlinked in /run/systemd/volatile-root. Let's read that here. */ | |
806 | r = readlink_malloc("/run/systemd/volatile-root", &p); | |
807 | if (r == -ENOENT) { /* volatile-root not found */ | |
808 | r = get_block_device_harder("/", &devno); | |
809 | if (r == -EUCLEAN) | |
810 | return btrfs_log_dev_root(level, r, "root file system"); | |
811 | if (r < 0) | |
812 | return log_full_errno(level, r, "Failed to determine block device of root file system: %m"); | |
813 | if (r == 0) { /* Not backed by a single block device. (Could be NFS or so, or could be multi-device RAID or so) */ | |
814 | r = get_block_device_harder("/usr", &devno); | |
815 | if (r == -EUCLEAN) | |
816 | return btrfs_log_dev_root(level, r, "/usr"); | |
817 | if (r < 0) | |
818 | return log_full_errno(level, r, "Failed to determine block device of /usr/ file system: %m"); | |
819 | if (r == 0) { /* /usr/ not backed by single block device, either. */ | |
820 | log_debug("Neither root nor /usr/ file system are on a (single) block device."); | |
821 | ||
822 | if (ret) | |
823 | *ret = 0; | |
824 | ||
825 | return 0; | |
826 | } | |
827 | } | |
828 | } else if (r < 0) | |
829 | return log_full_errno(level, r, "Failed to read symlink /run/systemd/volatile-root: %m"); | |
830 | else { | |
831 | mode_t m; | |
832 | r = device_path_parse_major_minor(p, &m, &devno); | |
833 | if (r < 0) | |
834 | return log_full_errno(level, r, "Failed to parse major/minor device node: %m"); | |
835 | if (!S_ISBLK(m)) | |
836 | return log_full_errno(level, SYNTHETIC_ERRNO(ENOTBLK), "Volatile root device is of wrong type."); | |
837 | } | |
838 | ||
839 | if (ret) | |
840 | *ret = devno; | |
841 | ||
842 | return 1; | |
843 | } |