Coverage Report

Created: 2026-03-12 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}