Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mount: Leverage udev db #254

Merged
merged 4 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bch_bindgen/src/bcachefs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ impl bch_sb {
uuid::Uuid::from_bytes(self.user_uuid.b)
}

pub fn number_of_devices(&self) -> u8 {
self.nr_devices
}

/// Get the nonce used to encrypt the superblock
pub fn nonce(&self) -> nonce {
use byteorder::{LittleEndian, ReadBytesExt};
Expand Down
173 changes: 137 additions & 36 deletions src/commands/mount.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use bch_bindgen::{path_to_cstr, bcachefs, bcachefs::bch_sb_handle, opt_set};
use log::{info, debug, error, LevelFilter};
use std::collections::HashMap;
use clap::Parser;
use uuid::Uuid;
use std::io::{stdout, IsTerminal};
use std::path::PathBuf;
use std::fs;
use std::{fs, str, env};
use crate::key;
use crate::key::UnlockPolicy;
use std::ffi::{CString, c_char, c_void};
Expand Down Expand Up @@ -107,41 +108,120 @@ fn read_super_silent(path: &std::path::PathBuf) -> anyhow::Result<bch_sb_handle>
bch_bindgen::sb_io::read_super_silent(&path, opts)
}

fn get_devices_by_uuid(uuid: Uuid) -> anyhow::Result<Vec<(PathBuf, bch_sb_handle)>> {
debug!("enumerating udev devices");
fn device_property_map(dev: &udev::Device) -> HashMap<String, String> {
let rc: HashMap<_, _> = dev
.properties()
.map(|i| {
(
String::from(i.name().to_string_lossy()),
String::from(i.value().to_string_lossy()),
)
})
.collect();
rc
}

fn udev_bcachefs_info() -> anyhow::Result<HashMap<String, Vec<String>>> {
let mut info = HashMap::new();

if env::var("BCACHEFS_BLOCK_SCAN").is_ok() {
debug!("Checking all block devices for bcachefs super block!");
return Ok(info);
}

let mut udev = udev::Enumerator::new()?;

debug!("Walking udev db!");

udev.match_subsystem("block")?;
udev.match_property("ID_FS_TYPE", "bcachefs")?;

let devs = udev
for m in udev
.scan_devices()?
.into_iter()
.filter_map(|dev| dev.devnode().map(ToOwned::to_owned))
.map(|dev| (dev.clone(), read_super_silent(&dev)))
.filter_map(|(dev, sb)| sb.ok().map(|sb| (dev, sb)))
.filter(|dev| dev.is_initialized())
.map(|dev| device_property_map(&dev))
.filter(|m| m.contains_key("ID_FS_UUID") && m.contains_key("DEVNAME"))
{
let fs_uuid = m["ID_FS_UUID"].clone();
let dev_node = m["DEVNAME"].clone();
info.insert(dev_node.clone(), vec![fs_uuid.clone()]);
info.entry(fs_uuid).or_insert(vec![]).push(dev_node.clone());
}

Ok(info)
}

fn get_super_blocks(
uuid: Uuid,
devices: &[String],
) -> anyhow::Result<Vec<(PathBuf, bch_sb_handle)>> {
Ok(devices
.iter()
.filter_map(|dev| {
read_super_silent(&PathBuf::from(dev))
.ok()
.map(|sb| (PathBuf::from(dev), sb))
})
.filter(|(_, sb)| sb.sb().uuid() == uuid)
.collect();
Ok(devs)
.collect::<Vec<_>>())
}

fn get_uuid_for_dev_node(device: &std::path::PathBuf) -> anyhow::Result<Option<Uuid>> {
fn get_all_block_devnodes() -> anyhow::Result<Vec<String>> {
let mut udev = udev::Enumerator::new()?;
let canonical = fs::canonicalize(device)?;

udev.match_subsystem("block")?;

for dev in udev.scan_devices()?.into_iter() {
if let Some(devnode) = dev.devnode() {
if devnode == canonical {
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()));
}
let devices = udev
.scan_devices()?
.filter_map(|dev| {
if dev.is_initialized() {
dev.devnode().map(|dn| dn.to_string_lossy().into_owned())
} else {
None
}
})
.collect::<Vec<_>>();
Ok(devices)
}

fn get_devices_by_uuid(
udev_bcachefs: &HashMap<String, Vec<String>>,
uuid: Uuid,
) -> anyhow::Result<Vec<(PathBuf, bch_sb_handle)>> {
let devices = {
if !udev_bcachefs.is_empty() {
let uuid_string = uuid.hyphenated().to_string();
if let Some(devices) = udev_bcachefs.get(&uuid_string) {
devices.clone()
} else {
Vec::new()
}
} else {
get_all_block_devnodes()?
}
};

get_super_blocks(uuid, &devices)
}

fn get_uuid_for_dev_node(
udev_bcachefs: &HashMap<String, Vec<String>>,
device: &std::path::PathBuf,
) -> anyhow::Result<(Option<Uuid>, Option<(PathBuf, bch_sb_handle)>)> {
let canonical = fs::canonicalize(device)?;

if !udev_bcachefs.is_empty() {
let dev_node_str = canonical.into_os_string().into_string().unwrap();

if udev_bcachefs.contains_key(&dev_node_str) && udev_bcachefs[&dev_node_str].len() == 1 {
let uuid_str = udev_bcachefs[&dev_node_str][0].clone();
return Ok((Some(Uuid::parse_str(&uuid_str)?), None));
}
} else {
return read_super_silent(&canonical).map_or(Ok((None, None)), |sb| {
Ok((Some(sb.sb().uuid()), Some((canonical, sb))))
});
}
Ok(None)
Ok((None, None))
}

/// Mount a bcachefs filesystem by its UUID.
Expand Down Expand Up @@ -186,11 +266,13 @@ pub struct Cli {
verbose: u8,
}

fn devs_str_sbs_from_uuid(uuid: String) -> anyhow::Result<(String, Vec<bch_sb_handle>)> {
fn devs_str_sbs_from_uuid(
udev_info: &HashMap<String, Vec<String>>,
uuid: String,
) -> anyhow::Result<(String, Vec<bch_sb_handle>)> {
debug!("enumerating devices with UUID {}", uuid);

let devs_sbs = Uuid::parse_str(&uuid)
.map(|uuid| get_devices_by_uuid(uuid))??;
let devs_sbs = Uuid::parse_str(&uuid).map(|uuid| get_devices_by_uuid(udev_info, uuid))??;

let devs_str = devs_sbs
.iter()
Expand All @@ -201,26 +283,45 @@ fn devs_str_sbs_from_uuid(uuid: String) -> anyhow::Result<(String, Vec<bch_sb_ha
let sbs: Vec<bch_sb_handle> = devs_sbs.iter().map(|(_, sb)| *sb).collect();

Ok((devs_str, sbs))

}

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 devs_str_sbs_from_device(
udev_info: &HashMap<String, Vec<String>>,
device: &std::path::PathBuf,
) -> anyhow::Result<(String, Vec<bch_sb_handle>)> {
let (uuid, sb_info) = get_uuid_for_dev_node(udev_info, device)?;

match (uuid, sb_info) {
(Some(uuid), Some((path, sb))) => {
// If we have a super block, it implies we aren't using udev db. If we only need
// 1 device to mount, we'll simply return it as we're done, else we'll use the uuid
// to walk through all the block devices.
debug!(
"number of devices in this FS = {}",
sb.sb().number_of_devices()
);
if sb.sb().number_of_devices() == 1 {
let dev = path.into_os_string().into_string().unwrap();
Ok((dev, vec![sb]))
} else {
devs_str_sbs_from_uuid(udev_info, uuid.to_string())
}
}
(Some(uuid), None) => devs_str_sbs_from_uuid(udev_info, uuid.to_string()),
_ => Ok((String::new(), Vec::new())),
}
}

fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> {
// Grab the udev information once
let udev_info = udev_bcachefs_info()?;

let (devices, block_devices_to_mount) = if opt.dev.starts_with("UUID=") {
let uuid = opt.dev.replacen("UUID=", "", 1);
devs_str_sbs_from_uuid(uuid)?
devs_str_sbs_from_uuid(&udev_info, uuid)?
} else if opt.dev.starts_with("OLD_BLKID_UUID=") {
let uuid = opt.dev.replacen("OLD_BLKID_UUID=", "", 1);
devs_str_sbs_from_uuid(uuid)?
devs_str_sbs_from_uuid(&udev_info, uuid)?
} else {
// 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
Expand All @@ -236,7 +337,7 @@ fn cmd_mount_inner(opt: Cli) -> anyhow::Result<()> {

(opt.dev, block_devices_to_mount)
} else {
devs_str_sbs_from_device(&PathBuf::from(opt.dev))?
devs_str_sbs_from_device(&udev_info, &PathBuf::from(opt.dev))?
}
};

Expand Down
Loading