mount: Handle multi-device with 1 device node

Instead of requiring the user to supply all the device nodes for a
multi-device FS, allow them to specifiy 1 of them.  We then fetch
the UUID for the FS and then find all the disks on the system that
match this UUID.

This allows me to bring up a bcachefs FS in /etc/fstab by using a
UUID.  This works because it appears the mount command looks up
the UUID, finds an entry in /dev/disk/by-uuid and then passes that
found device node to mount.bcachefs which fails with
`insufficient_devices_to_start` as bcachefs is expecting a list of
devices with a ":" between them.  This behavior is preserved if a
user specifies a list of all the needed device nodes to bring up
the FS.

Signed-off-by: Tony Asleson <tasleson@redhat.com>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Tony Asleson 2024-02-26 16:07:54 -06:00 committed by Kent Overstreet
parent c28af3c0cc
commit 89b322abb3

View File

@ -123,6 +123,24 @@ fn get_devices_by_uuid(uuid: Uuid) -> anyhow::Result<Vec<(PathBuf, bch_sb_handle
Ok(devs) Ok(devs)
} }
fn get_uuid_for_dev_node(device: &std::path::PathBuf) -> anyhow::Result<Option<Uuid>> {
let mut udev = udev::Enumerator::new()?;
udev.match_subsystem("block")?;
for dev in udev.scan_devices()?.into_iter() {
if let Some(devnode) = dev.devnode() {
if devnode == device {
let devnode_owned = devnode.to_owned();
let sb_result = read_super_silent(&devnode_owned);
if let Ok(sb) = sb_result {
return Ok(Some(sb.sb().uuid()));
}
}
}
}
Ok(None)
}
/// Mount a bcachefs filesystem by its UUID. /// Mount a bcachefs filesystem by its UUID.
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
@ -183,6 +201,16 @@ fn devs_str_sbs_from_uuid(uuid: String) -> anyhow::Result<(String, Vec<bch_sb_ha
} }
fn devs_str_sbs_from_device(device: &std::path::PathBuf) -> anyhow::Result<(String, Vec<bch_sb_handle>)> {
let uuid = get_uuid_for_dev_node(device)?;
if let Some(bcache_fs_uuid) = uuid {
devs_str_sbs_from_uuid(bcache_fs_uuid.to_string())
} else {
Ok((String::new(), Vec::new()))
}
}
fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> { fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> {
let (devices, block_devices_to_mount) = if opt.dev.starts_with("UUID=") { let (devices, block_devices_to_mount) = if opt.dev.starts_with("UUID=") {
let uuid = opt.dev.replacen("UUID=", "", 1); let uuid = opt.dev.replacen("UUID=", "", 1);
@ -191,14 +219,22 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> {
let uuid = opt.dev.replacen("OLD_BLKID_UUID=", "", 1); let uuid = opt.dev.replacen("OLD_BLKID_UUID=", "", 1);
devs_str_sbs_from_uuid(uuid)? devs_str_sbs_from_uuid(uuid)?
} else { } else {
let mut block_devices_to_mount = Vec::new(); // If the device string contains ":" we will assume the user knows the entire list.
// If they supply a single device it could be either the FS only has 1 device or it's
// only 1 of a number of devices which are part of the FS. This appears to be the case
// when we get called during fstab mount processing and the fstab specifies a UUID.
if opt.dev.contains(":") {
let mut block_devices_to_mount = Vec::new();
for dev in opt.dev.split(':') { for dev in opt.dev.split(':') {
let dev = PathBuf::from(dev); let dev = PathBuf::from(dev);
block_devices_to_mount.push(read_super_silent(&dev)?); block_devices_to_mount.push(read_super_silent(&dev)?);
}
(opt.dev, block_devices_to_mount)
} else {
devs_str_sbs_from_device(&PathBuf::from(opt.dev))?
} }
(opt.dev, block_devices_to_mount)
}; };
if block_devices_to_mount.len() == 0 { if block_devices_to_mount.len() == 0 {