/src/systemd/src/shared/reread-partition-table.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <linux/fs.h> |
4 | | #include <sys/file.h> |
5 | | #include <sys/ioctl.h> |
6 | | |
7 | | #include "sd-device.h" |
8 | | |
9 | | #include "alloc-util.h" |
10 | | #include "blkid-util.h" |
11 | | #include "blockdev-util.h" |
12 | | #include "device-private.h" |
13 | | #include "device-util.h" |
14 | | #include "errno-util.h" |
15 | | #include "fd-util.h" |
16 | | #include "log.h" |
17 | | #include "reread-partition-table.h" |
18 | | #include "set.h" |
19 | | #include "string-util.h" |
20 | | |
21 | 0 | static int trigger_partitions(sd_device *dev, bool blkrrpart_success) { |
22 | 0 | int ret = 0, r; |
23 | |
|
24 | 0 | assert(dev); |
25 | | |
26 | | /* search for partitions */ |
27 | 0 | _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; |
28 | 0 | r = partition_enumerator_new(dev, &e); |
29 | 0 | if (r < 0) |
30 | 0 | return log_device_debug_errno(dev, r, "Failed to initialize partition enumerator: %m"); |
31 | | |
32 | | /* We have partitions and re-read the table, the kernel already sent out a "change" |
33 | | * event for the disk, and "remove/add" for all partitions. */ |
34 | 0 | if (blkrrpart_success && sd_device_enumerator_get_device_first(e)) |
35 | 0 | return 0; |
36 | | |
37 | | /* We have partitions but re-reading the partition table did not work, synthesize |
38 | | * "change" for the disk and all partitions. */ |
39 | 0 | r = sd_device_trigger(dev, SD_DEVICE_CHANGE); |
40 | 0 | if (r < 0) |
41 | 0 | RET_GATHER(ret, log_device_debug_errno(dev, r, "Failed to trigger 'change' uevent, proceeding: %m")); |
42 | |
|
43 | 0 | FOREACH_DEVICE(e, d) { |
44 | 0 | r = sd_device_trigger(d, SD_DEVICE_CHANGE); |
45 | 0 | if (r < 0) |
46 | 0 | RET_GATHER(ret, log_device_debug_errno(d, r, "Failed to trigger 'change' uevent, proceeding: %m")); |
47 | 0 | } |
48 | |
|
49 | 0 | return ret; |
50 | 0 | } |
51 | | |
52 | 0 | static int fallback_ioctl(sd_device *d, int fd, RereadPartitionTableFlags flags) { |
53 | 0 | int r; |
54 | |
|
55 | 0 | assert(d); |
56 | 0 | assert(fd >= 0); |
57 | |
|
58 | 0 | r = RET_NERRNO(ioctl(fd, BLKRRPART, 0)); |
59 | 0 | if (r < 0) |
60 | 0 | log_device_debug_errno(d, r, "Failed to reread partition table via BLKRRPART: %m"); |
61 | 0 | else |
62 | 0 | log_device_debug(d, "Successfully reread partition table via BLKRRPART."); |
63 | |
|
64 | 0 | if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) |
65 | 0 | RET_GATHER(r, trigger_partitions(d, r >= 0)); |
66 | |
|
67 | 0 | return r; |
68 | 0 | } |
69 | | |
70 | | #if HAVE_BLKID |
71 | | static int process_partition( |
72 | | sd_device *d, |
73 | | int fd, |
74 | | blkid_partition pp, |
75 | | sd_device_enumerator *e, |
76 | | Set **partnos, |
77 | | RereadPartitionTableFlags flags, |
78 | | bool *changed) { |
79 | | |
80 | | int r; |
81 | | |
82 | | assert(d); |
83 | | assert(fd >= 0); |
84 | | assert(pp); |
85 | | assert(e); |
86 | | assert(partnos); |
87 | | assert(changed); |
88 | | |
89 | | const char *node; |
90 | | r = sd_device_get_devname(d, &node); |
91 | | if (r < 0) |
92 | | return log_device_debug_errno(d, r, "Failed to acquire device node path: %m"); |
93 | | |
94 | | errno = 0; |
95 | | int nr = sym_blkid_partition_get_partno(pp); |
96 | | if (nr < 0) |
97 | | return log_debug_errno(errno_or_else(EIO), "Failed to read partition number of partition: %m"); |
98 | | |
99 | | log_device_debug(d, "Processing partition %i...", nr); |
100 | | |
101 | | errno = 0; |
102 | | blkid_loff_t start = sym_blkid_partition_get_start(pp); |
103 | | if (start < 0) |
104 | | return log_debug_errno(errno_or_else(EIO), "Failed to read partition start offset of partition %i: %m", nr); |
105 | | assert((uint64_t) start < UINT64_MAX / 512U); |
106 | | |
107 | | errno = 0; |
108 | | blkid_loff_t size = sym_blkid_partition_get_size(pp); |
109 | | if (size < 0) |
110 | | return log_debug_errno(errno_or_else(EIO), "Failed to read partition size of partition %i: %m", nr); |
111 | | assert((uint64_t) size < UINT64_MAX / 512U); |
112 | | |
113 | | if (set_ensure_put(partnos, /* hash_ops= */ NULL, UINT_TO_PTR(nr)) < 0) |
114 | | return log_oom_debug(); |
115 | | |
116 | | _cleanup_free_ char *subnode = NULL; |
117 | | r = partition_node_of(node, nr, &subnode); |
118 | | if (r < 0) |
119 | | return log_device_debug_errno(d, r, "Failed to determine partition node %i for '%s': %m", nr, node); |
120 | | |
121 | | _cleanup_(sd_device_unrefp) sd_device *partition = NULL; |
122 | | r = sd_device_new_from_devname(&partition, subnode); |
123 | | if (r < 0) { |
124 | | if (r != -ENODEV) |
125 | | return log_device_debug_errno(d, r, "Failed to acquire device '%s': %m", subnode); |
126 | | } else { |
127 | | uint64_t start_kernel; |
128 | | r = device_get_sysattr_u64(partition, "start", &start_kernel); |
129 | | if (r < 0) |
130 | | return log_device_debug_errno(partition, r, "Failed to get start of kernel partition device '%s': %m", subnode); |
131 | | |
132 | | uint64_t size_kernel; |
133 | | r = device_get_sysattr_u64(partition, "size", &size_kernel); |
134 | | if (r < 0) |
135 | | return log_device_debug_errno(partition, r, "Failed to get size of kernel partition device '%s': %m", subnode); |
136 | | |
137 | | if (start_kernel == (uint64_t) start && size_kernel == (uint64_t) size) { |
138 | | log_device_debug(partition, "Kernel partition device '%s' already matches partition table, not modifying.", subnode); |
139 | | |
140 | | if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) { |
141 | | if (!*changed) { |
142 | | /* Make sure to synthesize a change event on the main device, before we issue the first one on a partition device */ |
143 | | r = sd_device_trigger(d, SD_DEVICE_CHANGE); |
144 | | if (r < 0) |
145 | | return log_device_debug_errno(d, r, "Failed to issue 'change' uevent on device '%s': %m", node); |
146 | | |
147 | | log_device_debug(d, "Successfully issued 'change' uevent on device '%s'.", node); |
148 | | *changed = true; |
149 | | } |
150 | | |
151 | | r = sd_device_trigger(partition, SD_DEVICE_CHANGE); |
152 | | if (r < 0) |
153 | | return log_device_debug_errno(partition, r, "Failed to issue 'change' uevent on partition '%s': %m", subnode); |
154 | | |
155 | | log_device_debug(partition, "Successfully issued 'change' uevent on partition '%s'.", subnode); |
156 | | } |
157 | | |
158 | | return 0; |
159 | | } |
160 | | |
161 | | if (start_kernel == (uint64_t) start) { |
162 | | /* If the start offsize doesn't change we can just resize the partition */ |
163 | | log_device_debug(partition, "Resizing partition %i...", nr); |
164 | | |
165 | | r = block_device_resize_partition(fd, nr, (uint64_t) start * 512U, (uint64_t) size * 512U); |
166 | | if (r < 0) |
167 | | return log_device_debug_errno(partition, r, "Failed to resize kernel partition device '%s' to partition table values: %m", subnode); |
168 | | |
169 | | log_device_debug(partition, "Successfully resized kernel partition device '%s' to match partition table.", subnode); |
170 | | *changed = true; |
171 | | return 1; |
172 | | } |
173 | | |
174 | | /* If the start offset changed we need to remove and recreate the partition */ |
175 | | log_device_debug(partition, "Removing and recreating partition %i...", nr); |
176 | | |
177 | | /* NB: when logging below we use the parent device now, after all the partition device ceased |
178 | | * existing by now, most likely. Let's explicitly get rid of the obsolete device object now, |
179 | | * just to make a point. */ |
180 | | partition = sd_device_unref(partition); |
181 | | |
182 | | r = block_device_remove_partition(fd, subnode, nr); |
183 | | if (r < 0) |
184 | | return log_device_debug_errno(d, r, "Failed to remove kernel partition device '%s' in order to recreate it: %m", subnode); |
185 | | |
186 | | /* And now add it the partition anew */ |
187 | | log_device_debug(d, "Successfully removed kernel partition device '%s' in order to recreate it.", subnode); |
188 | | } |
189 | | |
190 | | log_device_debug(d, "Adding partition %i...", nr); |
191 | | |
192 | | r = block_device_add_partition(fd, subnode, nr, (uint64_t) start * 512U, (uint64_t) size * 512U); |
193 | | if (r < 0) |
194 | | return log_device_debug_errno(d, r, "Failed to add kernel partition device %i to partition table values: %m", nr); |
195 | | |
196 | | log_device_debug(d, "Successfully added kernel partition device %i to match partition table.", nr); |
197 | | *changed = true; |
198 | | return 1; |
199 | | } |
200 | | |
201 | | static int remove_partitions(sd_device *d, int fd, sd_device_enumerator *e, Set *partnos, bool *changed) { |
202 | | int r; |
203 | | |
204 | | assert(d); |
205 | | assert(fd >= 0); |
206 | | assert(e); |
207 | | assert(changed); |
208 | | |
209 | | /* Removes all partitions of the specified device that we didn't find in the partition table (as |
210 | | * listed in the specified Set object) */ |
211 | | |
212 | | int ret = 0; |
213 | | FOREACH_DEVICE(e, partition) { |
214 | | const char *devname; |
215 | | |
216 | | r = sd_device_get_devname(partition, &devname); |
217 | | if (r < 0) |
218 | | return log_device_debug_errno(partition, r, "Failed to get name of partition: %m"); |
219 | | |
220 | | unsigned nr; |
221 | | r = device_get_property_uint(partition, "PARTN", &nr); |
222 | | if (r < 0) |
223 | | return log_device_debug_errno(partition, r, "Failed to read partition number property: %m"); |
224 | | if (set_contains(partnos, UINT_TO_PTR(nr))) { |
225 | | log_device_debug(partition, "Found kernel partition device %u in partition table, leaving around.", nr); |
226 | | continue; |
227 | | } |
228 | | |
229 | | log_device_debug(partition, "Kernel knows partition %u which we didn't find, removing.", nr); |
230 | | |
231 | | r = block_device_remove_partition(fd, devname, (int) nr); |
232 | | if (r < 0) /* NB: when logging we use the parent device below, after all the partition device ceased existing by now, most likely */ |
233 | | RET_GATHER(ret, log_device_debug_errno(d, r, "Failed to remove kernel partition device '%s' that vanished from partition table: %m", devname)); |
234 | | else { |
235 | | log_device_debug(d, "Removed partition %u from kernel.", nr); |
236 | | *changed = true; |
237 | | } |
238 | | } |
239 | | |
240 | | return ret; |
241 | | } |
242 | | #endif |
243 | | |
244 | 0 | static int reread_partition_table_full(sd_device *dev, int fd, RereadPartitionTableFlags flags) { |
245 | 0 | int r; |
246 | |
|
247 | 0 | assert(dev); |
248 | 0 | assert(fd >= 0); |
249 | |
|
250 | 0 | const char *p; |
251 | 0 | r = sd_device_get_devname(dev, &p); |
252 | 0 | if (r < 0) |
253 | 0 | return log_device_debug_errno(dev, r, "Failed to get block device name: %m"); |
254 | | |
255 | 0 | _cleanup_close_ int lock_fd = -EBADF; |
256 | 0 | if (FLAGS_SET(flags, REREADPT_BSD_LOCK)) { |
257 | 0 | lock_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY); |
258 | 0 | if (lock_fd < 0) |
259 | 0 | return log_device_debug_errno(dev, lock_fd, "Failed to open lock fd for block device '%s': %m", p); |
260 | | |
261 | 0 | if (flock(lock_fd, LOCK_EX|LOCK_NB) < 0) { |
262 | 0 | r = log_device_debug_errno(dev, errno, "Failed to take BSD lock on block device '%s': %m", p); |
263 | |
|
264 | 0 | if (r == -EAGAIN && FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) { |
265 | 0 | log_device_debug(dev, "Giving up rereading partition table of '%s'. Triggering change events for the device and its partitions.", p); |
266 | 0 | (void) trigger_partitions(dev, /* blkrrpart_success= */ false); |
267 | 0 | } |
268 | |
|
269 | 0 | return r; |
270 | 0 | } |
271 | 0 | } |
272 | | |
273 | 0 | r = blockdev_partscan_enabled(dev); |
274 | 0 | if (r < 0) |
275 | 0 | return log_device_debug_errno(dev, r, "Failed to test if block device '%s' knows partition scanning: %m", p); |
276 | 0 | if (r == 0) { |
277 | | /* No partition scanning? Generate a uevent at least, if that's requested */ |
278 | 0 | if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) { |
279 | 0 | r = sd_device_trigger(dev, SD_DEVICE_CHANGE); |
280 | 0 | if (r < 0) |
281 | 0 | return log_device_debug_errno(dev, r, "Failed to trigger 'change' uevent, proceeding: %m"); |
282 | | |
283 | 0 | return 0; |
284 | 0 | } |
285 | | |
286 | 0 | return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ENOTTY), "Block device '%s' does not support partition scanning.", p); |
287 | 0 | } |
288 | | |
289 | | #if HAVE_BLKID |
290 | | r = dlopen_libblkid(); |
291 | | if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { |
292 | | log_device_debug(dev, "We don't have libblkid, falling back to BLKRRPART on '%s'.", p); |
293 | | return fallback_ioctl(dev, fd, flags); |
294 | | } |
295 | | if (r < 0) |
296 | | return log_device_debug_errno(dev, r, "Failed to load libblkid: %m"); |
297 | | |
298 | | _cleanup_(blkid_free_probep) blkid_probe b = sym_blkid_new_probe(); |
299 | | if (!b) |
300 | | return log_oom_debug(); |
301 | | |
302 | | errno = 0; |
303 | | r = sym_blkid_probe_set_device(b, fd, /* off= */ 0, /* size= */ 0); |
304 | | if (r != 0) |
305 | | return log_device_debug_errno(dev, errno_or_else(ENOMEM), "Failed to open block device '%s': %m", p); |
306 | | |
307 | | (void) sym_blkid_probe_enable_partitions(b, 1); |
308 | | (void) sym_blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); |
309 | | |
310 | | errno = 0; |
311 | | r = sym_blkid_do_safeprobe(b); |
312 | | if (r == _BLKID_SAFEPROBE_ERROR) |
313 | | return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to probe for partition table of '%s': %m", p); |
314 | | if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND)) { |
315 | | log_device_debug(dev, "Didn't find partition table on block device '%s', falling back to BLKRRPART.", p); |
316 | | return fallback_ioctl(dev, fd, flags); |
317 | | } |
318 | | |
319 | | assert(r == _BLKID_SAFEPROBE_FOUND); |
320 | | |
321 | | const char *pttype = NULL; |
322 | | (void) sym_blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); |
323 | | if (!streq_ptr(pttype, "gpt")) { |
324 | | log_device_debug(dev, "Didn't find a GPT partition table on '%s', falling back to BLKRRPART.", p); |
325 | | return fallback_ioctl(dev, fd, flags); |
326 | | } |
327 | | |
328 | | errno = 0; |
329 | | blkid_partlist pl = sym_blkid_probe_get_partitions(b); |
330 | | if (!pl) |
331 | | return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to read partition table of '%s': %m", p); |
332 | | |
333 | | errno = 0; |
334 | | int n_partitions = sym_blkid_partlist_numof_partitions(pl); |
335 | | if (n_partitions < 0) |
336 | | return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to acquire number of entries in partition table of '%s': %m", p); |
337 | | |
338 | | _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; |
339 | | r = partition_enumerator_new(dev, &e); |
340 | | if (r < 0) |
341 | | return log_device_debug_errno(dev, r, "Failed to enumerate kernel partition devices: %m"); |
342 | | |
343 | | log_device_debug(dev, "Updating/adding kernel partition devices..."); |
344 | | |
345 | | _cleanup_(set_freep) Set *found_partnos = NULL; |
346 | | bool changed = false; |
347 | | int ret = 0; |
348 | | for (int i = 0; i < n_partitions; i++) { |
349 | | errno = 0; |
350 | | blkid_partition pp = sym_blkid_partlist_get_partition(pl, i); |
351 | | if (!pp) |
352 | | return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to get partition data of partition %i of partition table of '%s': %m", i, p); |
353 | | |
354 | | RET_GATHER(ret, process_partition(dev, fd, pp, e, &found_partnos, flags, &changed)); |
355 | | } |
356 | | |
357 | | /* Only delete unrecognized partitions if everything else worked */ |
358 | | if (ret < 0) |
359 | | return ret; |
360 | | |
361 | | log_device_debug(dev, "Removing old kernel partition devices..."); |
362 | | |
363 | | r = remove_partitions(dev, fd, e, found_partnos, &changed); |
364 | | if (r < 0) |
365 | | return r; |
366 | | |
367 | | if (changed) |
368 | | return 1; |
369 | | |
370 | | if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) { |
371 | | /* No change? Then trigger an event manually if we were told to */ |
372 | | r = sd_device_trigger(dev, SD_DEVICE_CHANGE); |
373 | | if (r < 0) |
374 | | return log_device_debug_errno(dev, r, "Failed to issue 'change' uevent on device '%s': %m", p); |
375 | | } |
376 | | |
377 | | return 0; |
378 | | #else |
379 | 0 | log_device_debug(dev, "We don't have libblkid, falling back to BLKRRPART on '%s'.", p); |
380 | 0 | return fallback_ioctl(dev, fd, flags); |
381 | 0 | #endif |
382 | 0 | } |
383 | | |
384 | 0 | int reread_partition_table(sd_device *dev, RereadPartitionTableFlags flags) { |
385 | 0 | assert(dev); |
386 | |
|
387 | 0 | _cleanup_close_ int fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); |
388 | 0 | if (fd < 0) |
389 | 0 | return log_debug_errno(fd, "Failed to open block device: %m"); |
390 | | |
391 | 0 | return reread_partition_table_full(dev, fd, flags); |
392 | 0 | } |
393 | | |
394 | 0 | int reread_partition_table_fd(int fd, RereadPartitionTableFlags flags) { |
395 | 0 | int r; |
396 | |
|
397 | 0 | _cleanup_(sd_device_unrefp) sd_device *dev = NULL; |
398 | 0 | r = block_device_new_from_fd(fd, /* flags= */ 0, &dev); |
399 | 0 | if (r < 0) |
400 | 0 | return log_debug_errno(r, "Failed to get block device object: %m"); |
401 | | |
402 | 0 | return reread_partition_table_full(dev, fd, flags); |
403 | 0 | } |