blockdev-list: split per-device filtering into blockdev_list_one()
Subscribers to block-device uevents need to apply the same filter set
blockdev_list() uses over the bulk enumeration, one device at a time.
Pull the per-device body out as blockdev_list_one(), expose
blockdev_list_get_root_devnos() so callers can compute the IGNORE_ROOT
devnos once up-front, and introduce a 4-valued return code so callers
can distinguish:
MATCH_NO - fails a static filter (subsystem, ZRAM, IGNORE_ROOT,
REQUIRE_PARTITION_SCANNING, REQUIRE_LUKS)
MATCH_YES - fully matches
MATCH_FILTERED - passes static filters but fails a dynamic one
(IGNORE_EMPTY, IGNORE_READ_ONLY) - useful to surface
as "device just became invisible" transitions when
the kernel flips size/ro at runtime
MATCH_SKIPPED - soft error during filter evaluation; callers should
treat this like MATCH_FILTERED, but the distinct code
makes the soft failure visible rather than silently
swallowed inside blockdev_list_one()
*ret is initialized on every non-negative return (BLOCK_DEVICE_NULL for
MATCH_NO / MATCH_SKIPPED, fully populated for MATCH_YES / MATCH_FILTERED),
and untouched on negative return. blockdev_list() itself collapses
everything except MATCH_YES back into "skip", so existing callers see
no behavior change.
This commit is contained in:
+205
-110
@@ -87,20 +87,17 @@ static int blockdev_get_subsystem(sd_device *d, char **ret_subsystem) {
|
||||
return ret < 0 ? ret : -ENOENT;
|
||||
}
|
||||
|
||||
int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *ret_n_devices) {
|
||||
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
|
||||
int blockdev_list_get_root_devnos(BlockDevListFlags flags, dev_t *ret_root, dev_t *ret_whole_root) {
|
||||
int r;
|
||||
|
||||
assert(!!ret_devices == !!ret_n_devices);
|
||||
|
||||
/* If ret_devices/ret_n_devices are passed, returns a list of matching block devices, otherwise
|
||||
* prints the list to stdout */
|
||||
|
||||
BlockDevice *l = NULL;
|
||||
size_t n = 0;
|
||||
CLEANUP_ARRAY(l, n, block_device_array_free);
|
||||
/* Looks up the devno of the block device the OS is booted from, plus the whole-disk devno (e.g.
|
||||
* /dev/sda for /dev/sda3). Returns both as 0 if root can't be determined – which is fine, callers
|
||||
* pass these to blockdev_list_one() and devnum_set_and_equal() handles zero gracefully.
|
||||
*
|
||||
* Split out of blockdev_list() so subscribers can look them up once up-front instead of per uevent. */
|
||||
|
||||
dev_t root_devno = 0, whole_root_devno = 0;
|
||||
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ROOT)) {
|
||||
r = blockdev_get_root(LOG_DEBUG, &root_devno);
|
||||
if (r < 0)
|
||||
@@ -110,98 +107,137 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to get whole block device of root device, ignoring: %m");
|
||||
}
|
||||
|
||||
/* It's fine if root_devno/whole_root_devno are zero here as devnum_set_and_equal() will
|
||||
* happily take that into account – it is in fact its primary raison d'etre. */
|
||||
}
|
||||
|
||||
if (sd_device_enumerator_new(&e) < 0)
|
||||
return log_oom();
|
||||
if (ret_root)
|
||||
*ret_root = root_devno;
|
||||
if (ret_whole_root)
|
||||
*ret_whole_root = whole_root_devno;
|
||||
|
||||
r = sd_device_enumerator_add_match_subsystem(e, "block", /* match= */ true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add subsystem match: %m");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int blockdev_list_one(
|
||||
sd_device *dev,
|
||||
BlockDevListFlags flags,
|
||||
dev_t root_devno,
|
||||
dev_t whole_root_devno,
|
||||
BlockDevice *ret) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(dev);
|
||||
|
||||
/* Per-device counterpart to blockdev_list(). Applies the same filters and metadata collection,
|
||||
* and reports via a multi-valued return:
|
||||
*
|
||||
* MATCH_NO – cleanly fails a static filter (never interesting)
|
||||
* MATCH_YES – fully matches
|
||||
* MATCH_FILTERED – passes static filters but fails a dynamic one (IGNORE_EMPTY / IGNORE_READ_ONLY)
|
||||
* MATCH_SKIPPED – an unexpected error tripped during filter evaluation; the caller should
|
||||
* treat this like _FILTERED, but the distinct code makes the soft failure
|
||||
* visible rather than silently swallowed here.
|
||||
*
|
||||
* On each of the four success codes *ret is initialized: BLOCK_DEVICE_NULL for MATCH_NO and
|
||||
* MATCH_SKIPPED, fully populated for MATCH_YES / MATCH_FILTERED (with d.size / d.read_only
|
||||
* reflecting the current state so callers can tell which dynamic filter tripped). On negative
|
||||
* return *ret is untouched.
|
||||
*
|
||||
* blockdev_list() itself collapses everything except _YES back into "skip". */
|
||||
|
||||
const char *node;
|
||||
r = sd_device_get_devname(dev, &node);
|
||||
if (r < 0) {
|
||||
log_device_warning_errno(dev, r, "Failed to get device node of discovered block device, ignoring: %m");
|
||||
goto skipped;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ROOT) && root_devno != 0) {
|
||||
dev_t devno;
|
||||
|
||||
r = sd_device_get_devnum(dev, &devno);
|
||||
if (r < 0) {
|
||||
log_device_warning_errno(dev, r, "Failed to get major/minor of discovered block device, ignoring: %m");
|
||||
goto skipped;
|
||||
}
|
||||
|
||||
if (devnum_set_and_equal(devno, root_devno) ||
|
||||
devnum_set_and_equal(devno, whole_root_devno))
|
||||
goto no_match;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ZRAM)) {
|
||||
r = device_sysname_startswith(dev, "zram");
|
||||
if (r < 0) {
|
||||
log_device_warning_errno(dev, r, "Failed to check device name of discovered block device '%s', ignoring: %m", node);
|
||||
goto skipped;
|
||||
}
|
||||
if (r > 0)
|
||||
goto no_match;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING)) {
|
||||
r = blockdev_partscan_enabled(dev);
|
||||
if (r < 0) {
|
||||
log_device_warning_errno(dev, r, "Unable to determine whether '%s' supports partition scanning, skipping device: %m", node);
|
||||
goto skipped;
|
||||
}
|
||||
if (r == 0) {
|
||||
log_device_debug(dev, "Device '%s' does not support partition scanning, skipping.", node);
|
||||
goto no_match;
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_REQUIRE_LUKS)) {
|
||||
r = sd_device_enumerator_add_match_property(e, "ID_FS_TYPE", "crypto_LUKS");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add match for LUKS block devices: %m");
|
||||
const char *fstype;
|
||||
r = sd_device_get_property_value(dev, "ID_FS_TYPE", &fstype);
|
||||
if (r == -ENOENT)
|
||||
goto no_match; /* No detected filesystem → not a LUKS superblock. */
|
||||
if (r < 0) {
|
||||
log_device_warning_errno(dev, r, "Failed to acquire ID_FS_TYPE of device '%s', ignoring: %m", node);
|
||||
goto skipped;
|
||||
}
|
||||
if (!streq(fstype, "crypto_LUKS"))
|
||||
goto no_match;
|
||||
}
|
||||
|
||||
FOREACH_DEVICE(e, dev) {
|
||||
const char *node;
|
||||
bool dynamic_filtered = false;
|
||||
|
||||
r = sd_device_get_devname(dev, &node);
|
||||
if (r < 0) {
|
||||
log_device_warning_errno(dev, r, "Failed to get device node of discovered block device, ignoring: %m");
|
||||
continue;
|
||||
uint64_t size = UINT64_MAX;
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_EMPTY) || ret) {
|
||||
r = device_get_sysattr_u64(dev, "size", &size);
|
||||
if (r < 0)
|
||||
log_device_debug_errno(dev, r, "Failed to acquire size of device '%s', ignoring: %m", node);
|
||||
else
|
||||
/* the 'size' sysattr is always in multiples of 512, even on 4K sector block devices! */
|
||||
assert_se(MUL_ASSIGN_SAFE(&size, 512)); /* Overflow check for coverity */
|
||||
|
||||
if (size == 0 && FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_EMPTY)) {
|
||||
log_device_debug(dev, "Device '%s' has a zero size, assuming drive without a medium.", node);
|
||||
dynamic_filtered = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ROOT) && root_devno != 0) {
|
||||
dev_t devno;
|
||||
int ro = -1;
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_READ_ONLY) || FLAGS_SET(flags, BLOCKDEV_LIST_METADATA)) {
|
||||
r = device_get_sysattr_bool(dev, "ro");
|
||||
if (r < 0)
|
||||
log_device_debug_errno(dev, r, "Failed to acquire read-only flag of device '%s', ignoring: %m", node);
|
||||
else
|
||||
ro = r;
|
||||
|
||||
r = sd_device_get_devnum(dev, &devno);
|
||||
if (r < 0) {
|
||||
log_device_warning_errno(dev, r, "Failed to get major/minor of discovered block device, ignoring: %m");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (devnum_set_and_equal(devno, root_devno) ||
|
||||
devnum_set_and_equal(devno, whole_root_devno))
|
||||
continue;
|
||||
if (ro > 0 && FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_READ_ONLY)) {
|
||||
log_device_debug(dev, "Device '%s' is read-only.", node);
|
||||
dynamic_filtered = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ZRAM)) {
|
||||
r = device_sysname_startswith(dev, "zram");
|
||||
if (r < 0) {
|
||||
log_device_warning_errno(dev, r, "Failed to check device name of discovered block device '%s', ignoring: %m", node);
|
||||
continue;
|
||||
}
|
||||
if (r > 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING)) {
|
||||
r = blockdev_partscan_enabled(dev);
|
||||
if (r < 0) {
|
||||
log_device_warning_errno(dev, r, "Unable to determine whether '%s' supports partition scanning, skipping device: %m", node);
|
||||
continue;
|
||||
}
|
||||
if (r == 0) {
|
||||
log_device_debug(dev, "Device '%s' does not support partition scanning, skipping.", node);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t size = UINT64_MAX;
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_EMPTY) || ret_devices) {
|
||||
r = device_get_sysattr_u64(dev, "size", &size);
|
||||
if (r < 0)
|
||||
log_device_debug_errno(dev, r, "Failed to acquire size of device '%s', ignoring: %m", node);
|
||||
else
|
||||
/* the 'size' sysattr is always in multiples of 512, even on 4K sector block devices! */
|
||||
assert_se(MUL_ASSIGN_SAFE(&size, 512)); /* Overflow check for coverity */
|
||||
|
||||
if (size == 0 && FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_EMPTY)) {
|
||||
log_device_debug(dev, "Device '%s' has a zero size, assuming drive without a medium, skipping.", node);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int ro = -1;
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_READ_ONLY) || FLAGS_SET(flags, BLOCKDEV_LIST_METADATA)) {
|
||||
r = device_get_sysattr_bool(dev, "ro");
|
||||
if (r < 0)
|
||||
log_device_debug_errno(dev, r, "Failed to acquire read-only flag of device '%s', ignoring: %m", node);
|
||||
else
|
||||
ro = r;
|
||||
|
||||
if (ro > 0 && FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_READ_ONLY)) {
|
||||
log_device_debug(dev, "Device '%s' is read-only, skipping.", node);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!ret)
|
||||
return dynamic_filtered ? BLOCKDEV_LIST_MATCH_FILTERED : BLOCKDEV_LIST_MATCH_YES;
|
||||
|
||||
/* Wrap the metadata population in a nested scope so the cleanup-attributed locals don't
|
||||
* intersect the goto-target label below (jumping into a protected scope is a clang error). */
|
||||
{
|
||||
_cleanup_strv_free_ char **list = NULL;
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_SHOW_SYMLINKS)) {
|
||||
FOREACH_DEVICE_DEVLINK(dev, sl)
|
||||
@@ -218,36 +254,95 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re
|
||||
(void) blockdev_get_subsystem(dev, &subsystem);
|
||||
}
|
||||
|
||||
if (ret_devices) {
|
||||
uint64_t diskseq = UINT64_MAX;
|
||||
r = sd_device_get_diskseq(dev, &diskseq);
|
||||
if (r < 0)
|
||||
log_device_debug_errno(dev, r, "Failed to acquire diskseq of device '%s', ignoring: %m", node);
|
||||
uint64_t diskseq = UINT64_MAX;
|
||||
r = sd_device_get_diskseq(dev, &diskseq);
|
||||
if (r < 0)
|
||||
log_device_debug_errno(dev, r, "Failed to acquire diskseq of device '%s', ignoring: %m", node);
|
||||
|
||||
_cleanup_free_ char *m = strdup(node);
|
||||
if (!m)
|
||||
return log_oom();
|
||||
|
||||
*ret = (BlockDevice) {
|
||||
.node = TAKE_PTR(m),
|
||||
.symlinks = TAKE_PTR(list),
|
||||
.diskseq = diskseq,
|
||||
.size = size,
|
||||
.model = TAKE_PTR(model),
|
||||
.vendor = TAKE_PTR(vendor),
|
||||
.subsystem = TAKE_PTR(subsystem),
|
||||
.read_only = ro,
|
||||
};
|
||||
|
||||
return dynamic_filtered ? BLOCKDEV_LIST_MATCH_FILTERED : BLOCKDEV_LIST_MATCH_YES;
|
||||
}
|
||||
|
||||
no_match:
|
||||
if (ret)
|
||||
*ret = BLOCK_DEVICE_NULL;
|
||||
return BLOCKDEV_LIST_MATCH_NO;
|
||||
|
||||
skipped:
|
||||
if (ret)
|
||||
*ret = BLOCK_DEVICE_NULL;
|
||||
return BLOCKDEV_LIST_MATCH_SKIPPED;
|
||||
}
|
||||
|
||||
int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *ret_n_devices) {
|
||||
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
|
||||
int r;
|
||||
|
||||
assert(!!ret_devices == !!ret_n_devices);
|
||||
|
||||
/* If ret_devices/ret_n_devices are passed, returns a list of matching block devices, otherwise
|
||||
* prints the list to stdout */
|
||||
|
||||
BlockDevice *l = NULL;
|
||||
size_t n = 0;
|
||||
CLEANUP_ARRAY(l, n, block_device_array_free);
|
||||
|
||||
dev_t root_devno = 0, whole_root_devno = 0;
|
||||
(void) blockdev_list_get_root_devnos(flags, &root_devno, &whole_root_devno);
|
||||
|
||||
if (sd_device_enumerator_new(&e) < 0)
|
||||
return log_oom();
|
||||
|
||||
r = sd_device_enumerator_add_match_subsystem(e, "block", /* match= */ true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add subsystem match: %m");
|
||||
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_REQUIRE_LUKS)) {
|
||||
/* blockdev_list_one() enforces this filter authoritatively; the enumerator match is just
|
||||
* an optimization so we don't iterate non-LUKS devices here. */
|
||||
r = sd_device_enumerator_add_match_property(e, "ID_FS_TYPE", "crypto_LUKS");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add match for LUKS block devices: %m");
|
||||
}
|
||||
|
||||
FOREACH_DEVICE(e, dev) {
|
||||
_cleanup_(block_device_done) BlockDevice d = BLOCK_DEVICE_NULL;
|
||||
|
||||
r = blockdev_list_one(dev, flags, root_devno, whole_root_devno, ret_devices ? &d : NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r != BLOCKDEV_LIST_MATCH_YES)
|
||||
continue; /* MATCH_NO or MATCH_FILTERED – bulk enumeration treats both as "skip" */
|
||||
|
||||
if (ret_devices) {
|
||||
if (!GREEDY_REALLOC(l, n+1))
|
||||
return log_oom();
|
||||
|
||||
_cleanup_free_ char *m = strdup(node);
|
||||
if (!m)
|
||||
return log_oom();
|
||||
|
||||
l[n++] = (BlockDevice) {
|
||||
.node = TAKE_PTR(m),
|
||||
.symlinks = TAKE_PTR(list),
|
||||
.diskseq = diskseq,
|
||||
.size = size,
|
||||
.model = TAKE_PTR(model),
|
||||
.vendor = TAKE_PTR(vendor),
|
||||
.subsystem = TAKE_PTR(subsystem),
|
||||
.read_only = ro,
|
||||
};
|
||||
|
||||
l[n++] = TAKE_STRUCT(d);
|
||||
} else {
|
||||
const char *node;
|
||||
if (sd_device_get_devname(dev, &node) < 0)
|
||||
continue;
|
||||
|
||||
printf("%s\n", node);
|
||||
|
||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_SHOW_SYMLINKS))
|
||||
STRV_FOREACH(i, list)
|
||||
printf("%s%s%s%s\n", on_tty() ? " " : "", ansi_grey(), *i, ansi_normal());
|
||||
FOREACH_DEVICE_DEVLINK(dev, sl)
|
||||
printf("%s%s%s%s\n", on_tty() ? " " : "", ansi_grey(), sl, ansi_normal());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "basic-forward.h"
|
||||
#include "shared-forward.h"
|
||||
|
||||
typedef enum BlockDevListFlags {
|
||||
@@ -14,6 +15,12 @@ typedef enum BlockDevListFlags {
|
||||
BLOCKDEV_LIST_IGNORE_READ_ONLY = 1 << 7, /* Ignore read-only block devices */
|
||||
} BlockDevListFlags;
|
||||
|
||||
/* The "dynamic" filters – ones the kernel can flip at runtime via sysattrs on a live device.
|
||||
* Useful for callers that want to distinguish "device is hard-filtered (never interesting)" from
|
||||
* "device just transitioned in or out of the candidate set". */
|
||||
#define BLOCKDEV_LIST_DYNAMIC_FILTERS_MASK \
|
||||
(BLOCKDEV_LIST_IGNORE_EMPTY | BLOCKDEV_LIST_IGNORE_READ_ONLY)
|
||||
|
||||
typedef struct BlockDevice {
|
||||
char *node;
|
||||
char **symlinks;
|
||||
@@ -31,7 +38,29 @@ typedef struct BlockDevice {
|
||||
.read_only = -1, \
|
||||
}
|
||||
|
||||
/* Return values of blockdev_list_one(). Not its own type – returned as int. */
|
||||
enum {
|
||||
BLOCKDEV_LIST_MATCH_NO, /* Hard-filtered out by a static filter (subsystem, ZRAM,
|
||||
* IGNORE_ROOT, REQUIRE_PARTITION_SCANNING, REQUIRE_LUKS, …). */
|
||||
BLOCKDEV_LIST_MATCH_YES, /* Passes all filters. */
|
||||
BLOCKDEV_LIST_MATCH_FILTERED, /* Passes the static filters; fails at least one dynamic filter
|
||||
* (IGNORE_EMPTY, IGNORE_READ_ONLY). */
|
||||
BLOCKDEV_LIST_MATCH_SKIPPED, /* Filter evaluation hit an unexpected error (failed sysattr
|
||||
* read, missing devname, …). Callers should typically treat
|
||||
* this like _FILTERED (device not currently a candidate),
|
||||
* but distinguishing it makes the soft failure visible
|
||||
* rather than silently swallowed inside blockdev_list_one(). */
|
||||
};
|
||||
|
||||
void block_device_done(BlockDevice *d);
|
||||
void block_device_array_free(BlockDevice *d, size_t n_devices);
|
||||
|
||||
int blockdev_list_get_root_devnos(BlockDevListFlags flags, dev_t *ret_root, dev_t *ret_whole_root);
|
||||
int blockdev_list_one(
|
||||
sd_device *dev,
|
||||
BlockDevListFlags flags,
|
||||
dev_t root_devno,
|
||||
dev_t whole_root_devno,
|
||||
BlockDevice *ret);
|
||||
|
||||
int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *ret_n_devices);
|
||||
|
||||
Reference in New Issue
Block a user