diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bcachefs.rs | 1 | ||||
-rw-r--r-- | src/commands/mount.rs | 172 | ||||
-rw-r--r-- | src/device_scan.rs | 228 | ||||
-rw-r--r-- | src/rust_to_c.h | 17 |
4 files changed, 253 insertions, 165 deletions
diff --git a/src/bcachefs.rs b/src/bcachefs.rs index f49f9195..4fb0b14c 100644 --- a/src/bcachefs.rs +++ b/src/bcachefs.rs @@ -3,6 +3,7 @@ mod key; mod dump_stack; mod logging; mod wrappers; +mod device_scan; use std::{ ffi::{c_char, CString}, diff --git a/src/commands/mount.rs b/src/commands/mount.rs index 883a0d11..d0fd7168 100644 --- a/src/commands/mount.rs +++ b/src/commands/mount.rs @@ -1,6 +1,4 @@ use std::{ - collections::HashMap, - env, ffi::CString, io::{stdout, IsTerminal}, path::{Path, PathBuf}, @@ -8,11 +6,10 @@ use std::{ }; use anyhow::{ensure, Result}; -use bch_bindgen::{bcachefs, bcachefs::bch_sb_handle, opt_set, path_to_cstr}; -use bcachefs::bch_opts; +use bch_bindgen::{bcachefs, bcachefs::bch_sb_handle, path_to_cstr}; use clap::Parser; use log::{debug, error, info}; -use uuid::Uuid; +use crate::device_scan; use crate::{ key::{KeyHandle, Passphrase, UnlockPolicy}, @@ -121,141 +118,6 @@ fn parse_mountflag_options(options: impl AsRef<str>) -> (Option<String>, libc::c ) } -fn read_super_silent(path: impl AsRef<Path>, mut opts: bch_opts) -> anyhow::Result<bch_sb_handle> { - opt_set!(opts, noexcl, 1); - - bch_bindgen::sb_io::read_super_silent(path.as_ref(), opts) -} - -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")?; - - for m in udev - .scan_devices()? - .filter(udev::Device::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], opts: &bch_opts) -> Vec<(PathBuf, bch_sb_handle)> { - devices - .iter() - .filter_map(|dev| { - read_super_silent(PathBuf::from(dev), *opts) - .ok() - .map(|sb| (PathBuf::from(dev), sb)) - }) - .filter(|(_, sb)| sb.sb().uuid() == uuid) - .collect::<Vec<_>>() -} - -fn get_all_block_devnodes() -> anyhow::Result<Vec<String>> { - let mut udev = udev::Enumerator::new()?; - udev.match_subsystem("block")?; - - 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, - opts: &bch_opts -) -> 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()? - } - }; - - Ok(get_super_blocks(uuid, &devices, opts)) -} - -fn devs_str_sbs_from_uuid( - udev_info: &HashMap<String, Vec<String>>, - uuid: &str, - opts: &bch_opts -) -> 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(udev_info, uuid, opts))??; - - let devs_str = devs_sbs - .iter() - .map(|(dev, _)| dev.to_str().unwrap()) - .collect::<Vec<_>>() - .join(":"); - - let sbs: Vec<bch_sb_handle> = devs_sbs.iter().map(|(_, sb)| *sb).collect(); - - Ok((devs_str, sbs)) -} - -fn devs_str_sbs_from_device( - udev_info: &HashMap<String, Vec<String>>, - device: &Path, - opts: &bch_opts -) -> anyhow::Result<(String, Vec<bch_sb_handle>)> { - let dev_sb = read_super_silent(device, *opts)?; - - if dev_sb.sb().number_of_devices() == 1 { - Ok((device.as_os_str().to_str().unwrap().to_string(), vec![dev_sb])) - } else { - let uuid = dev_sb.sb().uuid(); - - devs_str_sbs_from_uuid(udev_info, &uuid.to_string(), opts) - } -} - /// If a user explicitly specifies `unlock_policy` or `passphrase_file` then use /// that without falling back to other mechanisms. If these options are not /// used, then search for the key or ask for it. @@ -278,40 +140,20 @@ fn cmd_mount_inner(cli: &Cli) -> Result<()> { let opts = bch_bindgen::opts::parse_mount_opts(None, optstr.as_deref(), true) .unwrap_or_default(); - // Grab the udev information once - let udev_info = udev_bcachefs_info()?; - - let (devices, mut sbs) = - if let Some(("UUID" | "OLD_BLKID_UUID", uuid)) = cli.dev.split_once('=') { - devs_str_sbs_from_uuid(&udev_info, uuid, &opts)? - } else if cli.dev.contains(':') { - // 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. - - let sbs = cli - .dev - .split(':') - .map(|path| read_super_silent(path, opts)) - .collect::<Result<Vec<_>>>()?; - - (cli.dev.clone(), sbs) - } else { - devs_str_sbs_from_device(&udev_info, Path::new(&cli.dev), &opts)? - }; + let mut sbs = device_scan::scan_sbs(&cli.dev, &opts)?; ensure!(!sbs.is_empty(), "No device(s) to mount specified"); - let first_sb = &sbs[0]; + let devices = device_scan::joined_device_str(&sbs); + + let first_sb = &sbs[0].1; if unsafe { bcachefs::bch2_sb_is_encrypted(first_sb.sb) } { handle_unlock(cli, first_sb)?; } for sb in &mut sbs { unsafe { - bch_bindgen::sb_io::bch2_free_super(sb); + bch_bindgen::sb_io::bch2_free_super(&mut sb.1); } } drop(sbs); diff --git a/src/device_scan.rs b/src/device_scan.rs new file mode 100644 index 00000000..a6b62290 --- /dev/null +++ b/src/device_scan.rs @@ -0,0 +1,228 @@ +use std::{ + ffi::{CStr, CString, c_char, c_int}, + collections::HashMap, + env, + path::{Path, PathBuf}, + str, +}; + +use anyhow::Result; +use bch_bindgen::{bcachefs, opt_set}; +use bcachefs::{ + bch_sb_handle, + sb_name, + sb_names, +}; +use bcachefs::bch_opts; +use uuid::Uuid; +use log::debug; + +fn read_super_silent(path: impl AsRef<Path>, mut opts: bch_opts) -> anyhow::Result<bch_sb_handle> { + opt_set!(opts, noexcl, 1); + + bch_bindgen::sb_io::read_super_silent(path.as_ref(), opts) +} + +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")?; + + for m in udev + .scan_devices()? + .filter(udev::Device::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], opts: &bch_opts) -> Vec<(PathBuf, bch_sb_handle)> { + devices + .iter() + .filter_map(|dev| { + read_super_silent(PathBuf::from(dev), *opts) + .ok() + .map(|sb| (PathBuf::from(dev), sb)) + }) + .filter(|(_, sb)| sb.sb().uuid() == uuid) + .collect::<Vec<_>>() +} + +fn get_all_block_devnodes() -> anyhow::Result<Vec<String>> { + let mut udev = udev::Enumerator::new()?; + udev.match_subsystem("block")?; + + 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, + opts: &bch_opts +) -> 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()? + } + }; + + Ok(get_super_blocks(uuid, &devices, opts)) +} + +fn devs_str_sbs_from_uuid( + udev_info: &HashMap<String, Vec<String>>, + uuid: &str, + opts: &bch_opts +) -> Result<Vec<(PathBuf, bch_sb_handle)>> { + debug!("enumerating devices with UUID {}", uuid); + + Uuid::parse_str(uuid).map(|uuid| get_devices_by_uuid(udev_info, uuid, opts))? +} + +fn devs_str_sbs_from_device( + udev_info: &HashMap<String, Vec<String>>, + device: &Path, + opts: &bch_opts +) -> anyhow::Result<Vec<(PathBuf, bch_sb_handle)>> { + let dev_sb = read_super_silent(device, *opts)?; + + if dev_sb.sb().number_of_devices() == 1 { + Ok(vec![(device.to_path_buf(), dev_sb)]) + } else { + let uuid = dev_sb.sb().uuid(); + + get_devices_by_uuid(udev_info, uuid, opts) + } +} + +pub fn scan_sbs(device: &String, opts: &bch_opts) -> Result<Vec<(PathBuf, bch_sb_handle)>> { + // Grab the udev information once + let udev_info = udev_bcachefs_info()?; + + if let Some(("UUID" | "OLD_BLKID_UUID", uuid)) = device.split_once('=') { + devs_str_sbs_from_uuid(&udev_info, uuid, opts) + } else if device.contains(':') { + // 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. + + let devices = device.split(':') + .map(PathBuf::from) + .collect::<Vec<_>>(); + let sbs = devices + .iter() + .map(|path| read_super_silent(path, *opts)) + .collect::<Result<Vec<_>>>()?; + + Ok(devices.iter().zip(sbs.iter()) + .map(|(x, y)| (PathBuf::from(x), y.clone())) + .collect::<Vec<_>>()) + } else { + devs_str_sbs_from_device(&udev_info, Path::new(device), opts) + } +} + +pub fn joined_device_str(sbs: &Vec<(PathBuf, bch_sb_handle)>) -> String { + sbs.iter() + .map(|sb| sb.0.clone().into_os_string().into_string().unwrap()) + .collect::<Vec<_>>() + .join(":") +} + +pub fn scan_devices(device: &String, opts: &bch_opts) -> Result<String> { + let mut sbs = scan_sbs(device, opts)?; + + for sb in &mut sbs { + unsafe { + bch_bindgen::sb_io::bch2_free_super(&mut sb.1); + } + } + + Ok(joined_device_str(&sbs)) +} + +#[no_mangle] +pub extern "C" fn bch2_scan_device_sbs(device: *const c_char, ret: *mut sb_names) -> c_int { + let device = unsafe { CStr::from_ptr(device) }; + let device = device.to_str().unwrap().to_string(); + + // how to initialize to default/empty? + let opts = bch_bindgen::opts::parse_mount_opts(None, None, true).unwrap_or_default(); + + let sbs = scan_sbs(&device, &opts).unwrap(); + + let mut sbs = sbs.iter() + .map(|(name, sb)| sb_name { + name: CString::new(name.clone().into_os_string().into_string().unwrap()).unwrap().into_raw(), + sb: *sb } ) + .collect::<Vec<sb_name>>(); + + unsafe { + (*ret).data = sbs.as_mut_ptr(); + (*ret).nr = sbs.len(); + (*ret).size = sbs.capacity(); + + std::mem::forget(sbs); + } + 0 +} + +#[no_mangle] +pub extern "C" fn bch2_scan_devices(device: *const c_char) -> *mut c_char { + let device = unsafe { CStr::from_ptr(device) }; + let device = device.to_str().unwrap().to_string(); + + // how to initialize to default/empty? + let opts = bch_bindgen::opts::parse_mount_opts(None, None, true).unwrap_or_default(); + + CString::new(scan_devices(&device, &opts).unwrap()).unwrap().into_raw() +} diff --git a/src/rust_to_c.h b/src/rust_to_c.h new file mode 100644 index 00000000..b64059c4 --- /dev/null +++ b/src/rust_to_c.h @@ -0,0 +1,17 @@ +#ifndef _BCACHEFS_TOOLS_RUST_TO_C_H +#define _BCACHEFS_TOOLS_RUST_TO_C_H + +#include "libbcachefs/super_types.h" +#include "libbcachefs/darray.h" + +struct sb_name { + const char *name; + struct bch_sb_handle sb; +}; +typedef DARRAY(struct sb_name) sb_names; + +int bch2_scan_device_sbs(char *, sb_names *ret); + +char *bch2_scan_devices(char *); + +#endif /* _BCACHEFS_TOOLS_RUST_TO_C_H */ |