mount: When mounting a single device, make sure we mount the device specified

When mounting a multi device filesystem we have to scan for component
devices from the UUID, but we don't want to scan if it's a single device
filesystem; if the same filesystem is exposed via multiple device nodes
(e.g. dm faulty testing), the scan can pick up the wrong node.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2024-08-08 12:10:37 -04:00
parent c616061a93
commit b00ac20e76

View File

@ -2,7 +2,6 @@ use std::{
collections::HashMap,
env,
ffi::CString,
fs,
io::{stdout, IsTerminal},
path::{Path, PathBuf},
ptr, str,
@ -210,69 +209,6 @@ fn get_devices_by_uuid(
Ok(get_super_blocks(uuid, &devices))
}
#[allow(clippy::type_complexity)]
fn get_uuid_for_dev_node(
udev_bcachefs: &HashMap<String, Vec<String>>,
device: impl AsRef<Path>,
) -> 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, None))
}
/// Mount a bcachefs filesystem by its UUID.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// Path to passphrase file
///
/// This can be used to optionally specify a file to read the passphrase
/// from. An explictly specified key_location/unlock_policy overrides this
/// argument.
#[arg(short = 'f', long)]
passphrase_file: Option<PathBuf>,
/// Passphrase policy to use in case of an encrypted filesystem. If not
/// specified, the password will be searched for in the keyring. If not
/// found, the password will be prompted or read from stdin, depending on
/// whether the stdin is connected to a terminal or not.
#[arg(short = 'k', long = "key_location", value_enum)]
unlock_policy: Option<UnlockPolicy>,
/// Device, or UUID=\<UUID\>
dev: String,
/// 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<PathBuf>,
/// Mount options
#[arg(short, default_value = "")]
options: String,
// FIXME: would be nicer to have `--color[=WHEN]` like diff or ls?
/// Force color on/off. Autodetect tty is used to define default:
#[arg(short, long, action = clap::ArgAction::Set, default_value_t=stdout().is_terminal())]
colorize: bool,
/// Verbose mode
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
}
fn devs_str_sbs_from_uuid(
udev_info: &HashMap<String, Vec<String>>,
uuid: &str,
@ -294,28 +230,16 @@ fn devs_str_sbs_from_uuid(
fn devs_str_sbs_from_device(
udev_info: &HashMap<String, Vec<String>>,
device: impl AsRef<Path>,
device: &Path,
) -> anyhow::Result<(String, Vec<bch_sb_handle>)> {
let (uuid, sb_info) = get_uuid_for_dev_node(udev_info, device)?;
let dev_sb = read_super_silent(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())),
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())
}
}
@ -395,6 +319,47 @@ fn cmd_mount_inner(cli: &Cli) -> Result<()> {
}
}
/// Mount a bcachefs filesystem by its UUID.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// Path to passphrase file
///
/// This can be used to optionally specify a file to read the passphrase
/// from. An explictly specified key_location/unlock_policy overrides this
/// argument.
#[arg(short = 'f', long)]
passphrase_file: Option<PathBuf>,
/// Passphrase policy to use in case of an encrypted filesystem. If not
/// specified, the password will be searched for in the keyring. If not
/// found, the password will be prompted or read from stdin, depending on
/// whether the stdin is connected to a terminal or not.
#[arg(short = 'k', long = "key_location", value_enum)]
unlock_policy: Option<UnlockPolicy>,
/// Device, or UUID=\<UUID\>
dev: String,
/// 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<PathBuf>,
/// Mount options
#[arg(short, default_value = "")]
options: String,
// FIXME: would be nicer to have `--color[=WHEN]` like diff or ls?
/// Force color on/off. Autodetect tty is used to define default:
#[arg(short, long, action = clap::ArgAction::Set, default_value_t=stdout().is_terminal())]
colorize: bool,
/// Verbose mode
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
}
pub fn mount(mut argv: Vec<String>, symlink_cmd: Option<&str>) -> Result<()> {
// If the bcachefs tool is being called as "bcachefs mount dev ..." (as opposed to via a
// symlink like "/usr/sbin/mount.bcachefs dev ...", then we need to pop the 0th argument