diff --git a/Cargo.lock b/Cargo.lock index a1e1499..8a66db6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,7 @@ name = "alma" version = "0.9.0" dependencies = [ "byte-unit 3.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "console 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)", "dialoguer 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 1ea32df..0b5cd97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ nix = "0.15.0" env_logger = "0.6.2" pretty_env_logger = "0.3.0" dialoguer = "0.4.0" +console = "0.7.7" diff --git a/src/args.rs b/src/args.rs index 9e0821f..25b3538 100644 --- a/src/args.rs +++ b/src/args.rs @@ -60,9 +60,16 @@ pub struct CreateCommand { )] pub image: Option, - /// Overwrite existing image files. Use with caution + /// Overwrite existing image files. Use with caution! #[structopt(long = "overwrite")] pub overwrite: bool, + + /// Allow installation on non-removable devices. Use with extreme caution! + /// + /// If no device is specified in the command line, the device selection menu will + /// show non-removable devices + #[structopt(long = "allow-non-removable")] + pub allow_non_removable: bool, } #[derive(StructOpt)] @@ -71,6 +78,10 @@ pub struct ChrootCommand { #[structopt(parse(from_os_str))] pub block_device: PathBuf, + /// Allow installation on non-removable devices. Use with extreme caution! + #[structopt(long = "allow-non-removable")] + pub allow_non_removable: bool, + /// Optional command to run #[structopt()] pub command: Vec, diff --git a/src/error.rs b/src/error.rs index 1f56291..f497440 100644 --- a/src/error.rs +++ b/src/error.rs @@ -92,8 +92,8 @@ pub enum ErrorKind { #[fail(display = "Error setting up a loop device: {}", _0)] Losetup(String), - #[fail(display = "Error querying removeable devices")] - RemoveableDevicesQuery, + #[fail(display = "Error querying storage devices")] + StorageDevicesQuery, #[fail(display = "There are no removable devices")] NoRemovableDevices, diff --git a/src/main.rs b/src/main.rs index 5130514..3a3d98a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use crate::process::CommandExt; use crate::storage::*; use crate::tool::Tool; use byte_unit::Byte; +use console::style; use dialoguer::{theme::ColorfulTheme, Select}; use failure::{Fail, ResultExt}; use log::{debug, error, info, log_enabled, Level, LevelFilter}; @@ -91,13 +92,22 @@ fn create_image(path: &Path, size: Byte, overwrite: bool) -> Result Result { - let devices = get_removable_devices()?; +fn select_block_device(allow_non_removable: bool) -> Result { + let devices = get_storage_devices(allow_non_removable)?; if devices.is_empty() { Err(ErrorKind::NoRemovableDevices)? } + if allow_non_removable { + println!( + "{}\n", + style("Showing non-removable devices. Make sure you select the correct device.") + .red() + .bold() + ); + } + let selection = Select::with_theme(&ColorfulTheme::default()) .with_prompt("Select a removable device") .default(0) @@ -132,7 +142,7 @@ fn create(command: CreateCommand) -> Result<(), Error> { let storage_device_path = if let Some(path) = command.path { path } else { - select_block_device()? + select_block_device(command.allow_non_removable)? }; let image_loop = if let Some(size) = command.image { @@ -149,6 +159,7 @@ fn create(command: CreateCommand) -> Result<(), Error> { loop_dev.path() }) .unwrap_or(&storage_device_path), + command.allow_non_removable, )?; let mount_point = tempdir().context(ErrorKind::TmpDirError)?; @@ -373,13 +384,18 @@ fn chroot(command: ChrootCommand) -> Result<(), Error> { let mut cryptsetup; let mut loop_device: Option; - let storage_device = match storage::StorageDevice::from_path(&command.block_device) { - Ok(b) => b, - Err(_) => { - loop_device = Some(LoopDevice::create(&command.block_device)?); - storage::StorageDevice::from_path(loop_device.as_ref().unwrap().path())? - } - }; + let storage_device = + match storage::StorageDevice::from_path(&command.block_device, command.allow_non_removable) + { + Ok(b) => b, + Err(_) => { + loop_device = Some(LoopDevice::create(&command.block_device)?); + storage::StorageDevice::from_path( + loop_device.as_ref().unwrap().path(), + command.allow_non_removable, + )? + } + }; let mount_point = tempdir().context(ErrorKind::TmpDirError)?; let boot_partition = storage_device.get_partition(BOOT_PARTITION_INDEX)?; diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 041fd9f..586b9c7 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -12,5 +12,5 @@ pub use filesystem::{Filesystem, FilesystemType}; pub use loop_device::LoopDevice; pub use markers::BlockDevice; pub use mount_stack::MountStack; -pub use removeable_devices::get_removable_devices; +pub use removeable_devices::get_storage_devices; pub use storage_device::StorageDevice; diff --git a/src/storage/removeable_devices.rs b/src/storage/removeable_devices.rs index 8ca34c3..a327a95 100644 --- a/src/storage/removeable_devices.rs +++ b/src/storage/removeable_devices.rs @@ -27,22 +27,24 @@ fn trimmed(source: String) -> String { String::from(source.trim_end()) } -pub fn get_removable_devices() -> Result, Error> { +pub fn get_storage_devices(allow_non_removable: bool) -> Result, Error> { let mut result = Vec::new(); - for entry in fs::read_dir("/sys/block").context(ErrorKind::RemoveableDevicesQuery)? { - let entry = entry.context(ErrorKind::RemoveableDevicesQuery)?; + for entry in fs::read_dir("/sys/block").context(ErrorKind::StorageDevicesQuery)? { + let entry = entry.context(ErrorKind::StorageDevicesQuery)?; - let removable = fs::read_to_string(entry.path().join("removable")) - .context(ErrorKind::RemoveableDevicesQuery)?; + let removable = allow_non_removable + || fs::read_to_string(entry.path().join("removable")) + .map(|v| v == "1\n") + .context(ErrorKind::StorageDevicesQuery)?; - if removable != "1\n" { + if !removable { continue; } let model = fs::read_to_string(entry.path().join("device/model")) .map(trimmed) - .context(ErrorKind::RemoveableDevicesQuery)?; + .context(ErrorKind::StorageDevicesQuery)?; if model == "CD-ROM" { continue; @@ -58,10 +60,10 @@ pub fn get_removable_devices() -> Result, Error> { model, vendor: fs::read_to_string(entry.path().join("device/vendor")) .map(trimmed) - .context(ErrorKind::RemoveableDevicesQuery)?, + .context(ErrorKind::StorageDevicesQuery)?, size: Byte::from_bytes( fs::read_to_string(entry.path().join("size")) - .context(ErrorKind::RemoveableDevicesQuery)? + .context(ErrorKind::StorageDevicesQuery)? .trim() .parse::() .unwrap() diff --git a/src/storage/storage_device.rs b/src/storage/storage_device.rs index da7b035..0727894 100644 --- a/src/storage/storage_device.rs +++ b/src/storage/storage_device.rs @@ -15,7 +15,7 @@ pub struct StorageDevice<'a> { } impl<'a> StorageDevice<'a> { - pub fn from_path(path: &'a Path) -> Result { + pub fn from_path(path: &'a Path, allow_non_removable: bool) -> Result { debug!("path: {:?}", path); let path = path.canonicalize().context(ErrorKind::DeviceQuery)?; let device_name = path @@ -31,7 +31,7 @@ impl<'a> StorageDevice<'a> { path, origin: PhantomData, }; - if !(_self.is_removable_device()? || _self.is_loop_device()) { + if !allow_non_removable && (!(_self.is_removable_device()? || _self.is_loop_device())) { return Err(ErrorKind::DangerousDevice)?; }