diff options
Diffstat (limited to 'rust-src/mount/src')
-rw-r--r-- | rust-src/mount/src/filesystem.rs | 238 | ||||
-rw-r--r-- | rust-src/mount/src/key.rs | 23 | ||||
-rw-r--r-- | rust-src/mount/src/keyutils_wrapper.h | 1 | ||||
-rw-r--r-- | rust-src/mount/src/lib.rs | 181 | ||||
-rw-r--r-- | rust-src/mount/src/libbcachefs_wrapper.h | 4 | ||||
-rw-r--r-- | rust-src/mount/src/main.rs | 63 |
6 files changed, 252 insertions, 258 deletions
diff --git a/rust-src/mount/src/filesystem.rs b/rust-src/mount/src/filesystem.rs index 36af8c05..b1575c26 100644 --- a/rust-src/mount/src/filesystem.rs +++ b/rust-src/mount/src/filesystem.rs @@ -19,11 +19,103 @@ pub struct FileSystem { #[getset(get = "pub")] devices: Vec<PathBuf>, } +impl std::fmt::Debug for FileSystem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FileSystem") + .field("uuid", &self.uuid) + .field("encrypted", &self.encrypted) + .field("devices", &self.device_string()) + .finish() + } +} +use std::fmt; +impl std::fmt::Display for FileSystem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let devs = self.device_string(); + write!( + f, + "{:?}: locked?={lock} ({}) ", + self.uuid, + devs, + lock = self.encrypted + ) + } +} + +impl FileSystem { + pub(crate) fn new(sb: bcachefs::bch_sb_handle) -> Self { + Self { + uuid: sb.sb().uuid(), + encrypted: sb.sb().crypt().is_some(), + sb: sb, + devices: Vec::new(), + } + } + + pub fn device_string(&self) -> String { + use itertools::Itertools; + self.devices.iter().map(|d| d.display()).join(":") + } + + pub fn mount( + &self, + target: impl AsRef<std::path::Path>, + options: impl AsRef<str>, + ) -> anyhow::Result<()> { + tracing::info_span!("mount").in_scope(|| { + let src = self.device_string(); + let (data, mountflags) = parse_mount_options(options); + // let fstype = c_str!("bcachefs"); + + tracing::info!(msg="mounting bcachefs filesystem", target=%target.as_ref().display()); + mount_inner(src, target, "bcachefs", mountflags, data) + }) + } +} + +fn mount_inner( + src: String, + target: impl AsRef<std::path::Path>, + fstype: &str, + mountflags: u64, + data: Option<String>, +) -> anyhow::Result<()> { + use std::{ + ffi::{c_void, CString}, + os::{raw::c_char, unix::ffi::OsStrExt}, + }; + + // bind the CStrings to keep them alive + let src = CString::new(src)?; + let target = CString::new(target.as_ref().as_os_str().as_bytes())?; + let data = data.map(CString::new).transpose()?; + let fstype = CString::new(fstype)?; + + // convert to pointers for ffi + let src = src.as_c_str().to_bytes_with_nul().as_ptr() as *const c_char; + let target = target.as_c_str().to_bytes_with_nul().as_ptr() as *const c_char; + let data = data.as_ref().map_or(std::ptr::null(), |data| { + data.as_c_str().to_bytes_with_nul().as_ptr() as *const c_void + }); + let fstype = fstype.as_c_str().to_bytes_with_nul().as_ptr() as *const c_char; + + let ret = {let _entered = tracing::info_span!("libc::mount").entered(); + tracing::info!("mounting filesystem"); + // REQUIRES: CAP_SYS_ADMIN + unsafe { libc::mount(src, target, fstype, mountflags, data) } + }; + match ret { + 0 => Ok(()), + _ => Err(crate::ErrnoError(errno::errno()).into()), + } +} /// Parse a comma-separated mount options and split out mountflags and filesystem /// specific options. +#[tracing_attributes::instrument(skip(options))] fn parse_mount_options(options: impl AsRef<str>) -> (Option<String>, u64) { use either::Either::*; + tracing::debug!(msg="parsing mount options", options=?options.as_ref()); let (opts, flags) = options .as_ref() .split(",") @@ -63,112 +155,54 @@ fn parse_mount_options(options: impl AsRef<str>) -> (Option<String>, u64) { ) } -impl FileSystem { - pub(crate) fn new(sb: bcachefs::bch_sb_handle) -> Self { - Self { - uuid: sb.sb().uuid(), - encrypted: sb.sb().crypt().is_some(), - sb: sb, - devices: vec![], - } - } - - pub fn mount( - &self, - target: impl AsRef<std::path::Path>, - options: impl AsRef<str>, - ) -> anyhow::Result<()> { - use itertools::Itertools; - use std::ffi::c_void; - use std::os::raw::c_char; - use std::os::unix::ffi::OsStrExt; - let src = self.devices.iter().map(|d| d.display()).join(":"); - let (data, mountflags) = parse_mount_options(options); - let fstype = c_str!("bcachefs"); - - let src = std::ffi::CString::new(src)?; // bind the CString to keep it alive - let target = std::ffi::CString::new(target.as_ref().as_os_str().as_bytes())?; // ditto - let data = data.map(|data| std::ffi::CString::new(data)).transpose()?; // ditto - - let src = src.as_c_str().to_bytes_with_nul().as_ptr() as *const c_char; - let target = target.as_c_str().to_bytes_with_nul().as_ptr() as *const c_char; - let data = data.as_ref().map_or(std::ptr::null(), |data| { - data.as_c_str().to_bytes_with_nul().as_ptr() as *const c_void - }); - - let ret = unsafe { libc::mount(src, target, fstype, mountflags, data) }; - if ret == 0 { - Ok(()) - } else { - Err(crate::ErrnoError(errno::errno()).into()) - } - } -} - -use crate::bcachefs; +use bch_bindgen::bcachefs; use std::collections::HashMap; use uuid::Uuid; + +#[tracing_attributes::instrument] pub fn probe_filesystems() -> anyhow::Result<HashMap<Uuid, FileSystem>> { - use std::os::unix::ffi::OsStrExt; + tracing::trace!("enumerating udev devices"); let mut udev = udev::Enumerator::new()?; - let mut fss = HashMap::new(); - udev.match_subsystem("block")?; - - { - // Stop libbcachefs from spamming the output - let _gag = gag::Gag::stdout().unwrap(); - for dev in udev.scan_devices()? { - if let Some(p) = dev.devnode() { - let path = - std::ffi::CString::new(p.as_os_str().as_bytes()).unwrap(); - let result = unsafe { - let mut opts = std::mem::MaybeUninit::zeroed(); - let mut sb = std::mem::MaybeUninit::zeroed(); - let ret = bcachefs::bch2_read_super( - path.as_ptr(), - opts.as_mut_ptr(), - sb.as_mut_ptr(), - ); - if ret == -libc::EACCES { - Err(std::io::Error::new( - std::io::ErrorKind::PermissionDenied, - "no permission", - )) - } else if ret != 0 { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "failed to read super", - )) - } else { - Ok((opts.assume_init(), sb.assume_init())) - } - }; - match result { - Ok((_, sb)) => match fss.get_mut(&sb.sb().uuid()) { - None => { - let mut fs = FileSystem::new(sb); - fs.devices.push(p.to_owned()); - fss.insert(fs.uuid, fs); - } - Some(fs) => { - fs.devices.push(p.to_owned()); - } - }, - Err(e) if e.kind() - != std::io::ErrorKind::PermissionDenied => - { - () - } - e @ Err(_) => { - e?; - } - } - } - } - // Flush stdout so buffered output don't get printed after we remove the gag - unsafe { - libc::fflush(stdout); + + udev.match_subsystem("block")?; // find kernel block devices + + let mut fs_map = HashMap::new(); + let devresults = + udev.scan_devices()? + .into_iter() + .filter_map(|dev| dev.devnode().map(ToOwned::to_owned)); + + for pathbuf in devresults { + match get_super_block_uuid(&pathbuf)? { + + Ok((uuid_key, superblock)) => { + let fs = fs_map.entry(uuid_key).or_insert_with(|| { + tracing::info!(msg="found bcachefs pool", uuid=?uuid_key); + FileSystem::new(superblock) + }); + + fs.devices.push(pathbuf); + }, + + Err(e) => { tracing::debug!(inner2_error=?e);} } } - Ok(fss) + + + tracing::info!(msg = "found filesystems", count = fs_map.len()); + Ok(fs_map) +} + +// #[tracing_attributes::instrument(skip(dev, fs_map))] +fn get_super_block_uuid(path: &std::path::Path) -> std::io::Result<std::io::Result<(Uuid, bcachefs::bch_sb_handle)>> { + let sb = bch_bindgen::rs::read_super(&path)?; + let super_block = match sb { + Err(e) => { return Ok(Err(e)); } + Ok(sb) => sb, + }; + + let uuid = (&super_block).sb().uuid(); + tracing::debug!(found="bcachefs superblock", devnode=?path, ?uuid); + + Ok(Ok((uuid, super_block))) } diff --git a/rust-src/mount/src/key.rs b/rust-src/mount/src/key.rs index 6769f52c..91c92d1c 100644 --- a/rust-src/mount/src/key.rs +++ b/rust-src/mount/src/key.rs @@ -1,12 +1,11 @@ -use log::info; +use tracing::info; fn check_for_key(key_name: &std::ffi::CStr) -> anyhow::Result<bool> { - use crate::keyutils::{self, keyctl_search}; + use bch_bindgen::keyutils::{self, keyctl_search}; let key_name = key_name.to_bytes_with_nul().as_ptr() as *const _; let key_type = c_str!("logon"); - let key_id = - unsafe { keyctl_search(keyutils::KEY_SPEC_USER_KEYRING, key_type, key_name, 0) }; + let key_id = unsafe { keyctl_search(keyutils::KEY_SPEC_USER_KEYRING, key_type, key_name, 0) }; if key_id > 0 { info!("Key has became avaiable"); Ok(true) @@ -31,9 +30,9 @@ fn wait_for_key(uuid: &uuid::Uuid) -> anyhow::Result<()> { const BCH_KEY_MAGIC: &str = "bch**key"; use crate::filesystem::FileSystem; fn ask_for_key(fs: &FileSystem) -> anyhow::Result<()> { - use crate::bcachefs::{self, bch2_chacha_encrypt_key, bch_encrypted_key, bch_key}; use anyhow::anyhow; use byteorder::{LittleEndian, ReadBytesExt}; + use bch_bindgen::bcachefs::{self, bch2_chacha_encrypt_key, bch_encrypted_key, bch_key}; use std::os::raw::c_char; let key_name = std::ffi::CString::new(format!("bcachefs:{}", fs.uuid())).unwrap(); @@ -62,19 +61,18 @@ fn ask_for_key(fs: &FileSystem) -> anyhow::Result<()> { ) }; if ret != 0 { - Err(anyhow!("chache decryption failure")) + Err(anyhow!("chacha decryption failure")) } else if key.magic != bch_key_magic { Err(anyhow!("failed to verify the password")) } else { let key_type = c_str!("logon"); let ret = unsafe { - crate::keyutils::add_key( + bch_bindgen::keyutils::add_key( key_type, - key_name.as_c_str().to_bytes_with_nul() as *const _ - as *const c_char, + key_name.as_c_str().to_bytes_with_nul() as *const _ as *const c_char, &output as *const _ as *const _, std::mem::size_of::<bch_key>() as u64, - crate::keyutils::KEY_SPEC_USER_KEYRING, + bch_bindgen::keyutils::KEY_SPEC_USER_KEYRING, ) }; if ret == -1 { @@ -85,9 +83,12 @@ fn ask_for_key(fs: &FileSystem) -> anyhow::Result<()> { } } -pub(crate) fn prepare_key(fs: &FileSystem, password: crate::KeyLocation) -> anyhow::Result<()> { +#[tracing_attributes::instrument] +pub fn prepare_key(fs: &FileSystem, password: crate::KeyLocation) -> anyhow::Result<()> { use crate::KeyLocation::*; use anyhow::anyhow; + + tracing::info!(msg = "checking if key exists for filesystem"); match password { Fail => Err(anyhow!("no key available")), Wait => Ok(wait_for_key(fs.uuid())?), diff --git a/rust-src/mount/src/keyutils_wrapper.h b/rust-src/mount/src/keyutils_wrapper.h deleted file mode 100644 index 857cee2e..00000000 --- a/rust-src/mount/src/keyutils_wrapper.h +++ /dev/null @@ -1 +0,0 @@ -#include <keyutils.h> diff --git a/rust-src/mount/src/lib.rs b/rust-src/mount/src/lib.rs index 751eab38..4e918e13 100644 --- a/rust-src/mount/src/lib.rs +++ b/rust-src/mount/src/lib.rs @@ -1,12 +1,24 @@ -use structopt::StructOpt; use anyhow::anyhow; +use structopt::StructOpt; + +pub mod err { + pub enum GError { + Unknown{ + message: std::borrow::Cow<'static, String> + } + } + pub type GResult<T, E, OE> =::core::result::Result< ::core::result::Result<T, E>, OE>; + pub type Result<T, E> = GResult<T, E, GError>; +} #[macro_export] macro_rules! c_str { ($lit:expr) => { - unsafe { std::ffi::CStr::from_ptr(concat!($lit, "\0").as_ptr() as *const std::os::raw::c_char) - .to_bytes_with_nul() - .as_ptr() as *const std::os::raw::c_char } + unsafe { + std::ffi::CStr::from_ptr(concat!($lit, "\0").as_ptr() as *const std::os::raw::c_char) + .to_bytes_with_nul() + .as_ptr() as *const std::os::raw::c_char + } }; } @@ -20,171 +32,60 @@ impl std::fmt::Display for ErrnoError { impl std::error::Error for ErrnoError {} #[derive(Debug)] -pub(crate) enum KeyLocation { +pub enum KeyLocation { Fail, Wait, Ask, } -impl std::str::FromStr for KeyLocation { +#[derive(Debug)] +pub struct KeyLoc(pub Option<KeyLocation>); +impl std::ops::Deref for KeyLoc { + type Target = Option<KeyLocation>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::str::FromStr for KeyLoc { type Err = anyhow::Error; fn from_str(s: &str) -> anyhow::Result<Self> { - use anyhow::anyhow; + // use anyhow::anyhow; match s { - "fail" => Ok(Self::Fail), - "wait" => Ok(Self::Wait), - "ask" => Ok(Self::Ask), - _ => Err(anyhow!("invalid password option")) + "" => Ok(KeyLoc(None)), + "fail" => Ok(KeyLoc(Some(KeyLocation::Fail))), + "wait" => Ok(KeyLoc(Some(KeyLocation::Wait))), + "ask" => Ok(KeyLoc(Some(KeyLocation::Ask))), + _ => Err(anyhow!("invalid password option")), } } } #[derive(StructOpt, Debug)] /// Mount a bcachefs filesystem by its UUID. -struct Options { +pub struct Options { /// Where the password would be loaded from. /// /// Possible values are: /// "fail" - don't ask for password, fail if filesystem is encrypted; /// "wait" - wait for password to become available before mounting; /// "ask" - prompt the user for password; - #[structopt(short, long, default_value = "fail")] - key_location: KeyLocation, + #[structopt(short, long, default_value = "")] + pub key_location: KeyLoc, /// External UUID of the bcachefs filesystem - uuid: uuid::Uuid, + pub uuid: uuid::Uuid, /// Where the filesystem should be mounted. If not set, then the filesystem /// won't actually be mounted. But all steps preceeding mounting the /// filesystem (e.g. asking for passphrase) will still be performed. - mountpoint: Option<std::path::PathBuf>, + pub mountpoint: Option<std::path::PathBuf>, /// Mount options #[structopt(short, default_value = "")] - options: String, -} - -mod filesystem; -mod key; -mod keyutils { - #![allow(non_upper_case_globals)] - #![allow(non_camel_case_types)] - #![allow(non_snake_case)] - #![allow(unused)] - - include!(concat!(env!("OUT_DIR"), "/keyutils.rs")); + pub options: String, } -mod bcachefs { - #![allow(non_upper_case_globals)] - #![allow(non_camel_case_types)] - #![allow(non_snake_case)] - #![allow(unused)] - - include!(concat!(env!("OUT_DIR"), "/bcachefs.rs")); +pub mod filesystem; +pub mod key; - use bitfield::bitfield; - bitfield! { - pub struct bch_scrypt_flags(u64); - pub N, _: 15, 0; - pub R, _: 31, 16; - pub P, _: 47, 32; - } - bitfield! { - pub struct bch_crypt_flags(u64); - TYPE, _: 4, 0; - } - use memoffset::offset_of; - impl bch_sb_field_crypt { - pub fn scrypt_flags(&self) -> Option<bch_scrypt_flags> { - let t = bch_crypt_flags(self.flags); - if t.TYPE() != bch_kdf_types::BCH_KDF_SCRYPT as u64 { - None - } else { - Some(bch_scrypt_flags(self.kdf_flags)) - } - } - pub fn key(&self) -> &bch_encrypted_key { - &self.key - } - } - impl bch_sb { - pub fn crypt(&self) -> Option<&bch_sb_field_crypt> { - unsafe { - let ptr = bch2_sb_field_get( - self as *const _ as *mut _, - bch_sb_field_type::BCH_SB_FIELD_crypt, - ) as *const u8; - if ptr.is_null() { - None - } else { - let offset = offset_of!(bch_sb_field_crypt, field); - Some(&*((ptr.sub(offset)) as *const _)) - } - } - } - pub fn uuid(&self) -> uuid::Uuid { - uuid::Uuid::from_bytes(self.user_uuid.b) - } - - /// Get the nonce used to encrypt the superblock - pub fn nonce(&self) -> nonce { - use byteorder::{ReadBytesExt, LittleEndian}; - let mut internal_uuid = &self.uuid.b[..]; - let dword1 = internal_uuid.read_u32::<LittleEndian>().unwrap(); - let dword2 = internal_uuid.read_u32::<LittleEndian>().unwrap(); - nonce { d: [0, 0, dword1, dword2] } - } - } - impl bch_sb_handle { - pub fn sb(&self) -> &bch_sb { - unsafe { &*self.sb } - } - } -} - -fn main_inner() -> anyhow::Result<()> { - use itertools::Itertools; - use log::{info, trace}; - - env_logger::init(); - let opt = Options::from_args(); - trace!("{:?}", opt); - - let fss = filesystem::probe_filesystems()?; - info!("Found {} bcachefs filesystems: ", fss.len()); - for fs in fss.values() { - info!( - "{} ({}): {}", - fs.uuid(), - if fs.encrypted() { - "encrypted" - } else { - "unencrypted" - }, - fs.devices().iter().map(|d| d.display()).join(" ") - ); - } - - if let Some(fs) = fss.get(&opt.uuid) { - if fs.encrypted() { - info!("Making sure key is loaded for this filesystem"); - key::prepare_key(&fs, opt.key_location)?; - } - - if let Some(p) = opt.mountpoint { - fs.mount(&p, &opt.options) - } else { - Ok(()) - } - } else { - Err(anyhow!("Filesystem {} is not found", opt.uuid)) - } -} - -#[no_mangle] -pub extern "C" fn main() { - if let Err(e) = main_inner() { - println!("Error: {:?}", e); - } -} +// pub fn mnt_in_use() diff --git a/rust-src/mount/src/libbcachefs_wrapper.h b/rust-src/mount/src/libbcachefs_wrapper.h deleted file mode 100644 index 9d9754c1..00000000 --- a/rust-src/mount/src/libbcachefs_wrapper.h +++ /dev/null @@ -1,4 +0,0 @@ -#include "../libbcachefs/super-io.h" -#include "../libbcachefs/checksum.h" -#include "../libbcachefs/bcachefs_format.h" -#include "../crypto.h" diff --git a/rust-src/mount/src/main.rs b/rust-src/mount/src/main.rs new file mode 100644 index 00000000..92b69170 --- /dev/null +++ b/rust-src/mount/src/main.rs @@ -0,0 +1,63 @@ +fn main() { + // convert existing log statements to tracing events + // tracing_log::LogTracer::init().expect("logtracer init failed!"); + // format tracing log data to env_logger like stdout + tracing_subscriber::fmt::init(); + + if let Err(e) = crate::main_inner() { + tracing::error!(fatal_error = ?e); + } +} + + + +#[tracing_attributes::instrument("main")] +pub fn main_inner() -> anyhow::Result<()> { + use structopt::StructOpt; + use bcachefs_mount::{Options, filesystem, key}; + unsafe { + libc::setvbuf( + filesystem::stdout, + std::ptr::null_mut(), + libc::_IONBF, + 0, + ); + // libc::fflush(filesystem::stdout); + } + let opt = Options::from_args(); + + + tracing::trace!(?opt); + + let fss = filesystem::probe_filesystems()?; + let fs = fss + .get(&opt.uuid) + .ok_or_else(|| anyhow::anyhow!("filesystem was not found"))?; + + tracing::info!(msg="found filesystem", %fs); + if fs.encrypted() { + let key = opt + .key_location + .0 + .ok_or_else(|| anyhow::anyhow!("no keyoption specified for locked filesystem"))?; + + key::prepare_key(&fs, key)?; + } + + let mountpoint = opt + .mountpoint + .ok_or_else(|| anyhow::anyhow!("mountpoint option was not specified"))?; + + fs.mount(&mountpoint, &opt.options)?; + + Ok(()) +} + +#[cfg(test)] +mod test { + // use insta::assert_debug_snapshot; + // #[test] + // fn snapshot_testing() { + // insta::assert_debug_snapshot!(); + // } +} |