diff --git a/src/args.rs b/src/args.rs index 6c62330..4a83712 100644 --- a/src/args.rs +++ b/src/args.rs @@ -2,6 +2,8 @@ use byte_unit::Byte; use std::path::PathBuf; use structopt::StructOpt; +/// Parse size argument as bytes +/// e.g. 10GB, 10GiB, etc. fn parse_bytes(src: &str) -> Result { Byte::from_str(src).map_err(|_| "Invalid image size") } diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..4d29ff4 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,17 @@ +pub const BOOT_PARTITION_INDEX: u8 = 1; +pub const ROOT_PARTITION_INDEX: u8 = 3; + +pub static JOURNALD_CONF: &str = " +[Journal] +Storage=volatile +SystemMaxUse=16M +"; + +pub const BASE_PACKAGES: [&str; 6] = [ + "base", + "grub", + "efibootmgr", + "intel-ucode", + "networkmanager", + "broadcom-wl", +]; diff --git a/src/main.rs b/src/main.rs index a01cdc7..a9ad9fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod args; +mod constants; mod error; mod initcpio; mod presets; @@ -6,65 +7,71 @@ mod process; mod storage; mod tool; -use crate::args::*; -use crate::error::*; -use crate::process::CommandExt; -use crate::storage::*; -use crate::tool::Tool; +use args::Command; use byte_unit::Byte; use console::style; use dialoguer::{theme::ColorfulTheme, Select}; +use error::Error; +use error::ErrorKind; use failure::{Fail, ResultExt}; use log::{debug, error, info, log_enabled, Level, LevelFilter}; -use pretty_env_logger; +use process::CommandExt; use std::collections::HashSet; use std::fs; use std::io::Write; -use std::os::unix::{fs::PermissionsExt, process::CommandExt as UnixCommandExt}; +use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use std::process::{exit, Command as ProcessCommand}; use std::thread; use std::time::Duration; +use storage::EncryptedDevice; +use storage::{BlockDevice, Filesystem, FilesystemType, LoopDevice}; use structopt::StructOpt; use tempfile::tempdir; +use tool::Tool; -const BOOT_PARTITION_INDEX: u8 = 1; -const ROOT_PARTITION_INDEX: u8 = 3; +fn main() { + // Get struct of args using structopt + let app = args::App::from_args(); -static JOURNALD_CONF: &'static str = " -[Journal] -Storage=volatile -SystemMaxUse=16M -"; + // Set up logging + let mut builder = pretty_env_logger::formatted_timed_builder(); + let log_level = if app.verbose { + LevelFilter::Debug + } else { + LevelFilter::Info + }; + builder.filter_level(log_level); + builder.init(); -fn mount<'a>( - mount_path: &Path, - boot_filesystem: &'a Filesystem, - root_filesystem: &'a Filesystem, -) -> Result, Error> { - let mut mount_stack = MountStack::new(); - debug!( - "Root partition: {}", - root_filesystem.block().path().display() - ); + // Match command from arguments and run relevant code + let result = match app.cmd { + Command::Create(command) => create(command), + Command::Chroot(command) => tool::chroot(command), + Command::Qemu(command) => tool::qemu(command), + }; - info!("Mounting filesystems to {}", mount_path.display()); - mount_stack - .mount(&root_filesystem, mount_path.into(), None) - .context(ErrorKind::Mounting)?; - - let boot_point = mount_path.join("boot"); - if !boot_point.exists() { - fs::create_dir(&boot_point).context(ErrorKind::CreateBoot)?; + // Check if command return an Error + // Print all causes to stderr if so + match result { + Ok(()) => { + exit(0); + } + Err(error) => { + error!("{}", error); + for cause in (&error as &dyn Fail).iter_causes() { + error!("Caused by: {}", cause); + } + exit(1); + } } - - mount_stack - .mount(&boot_filesystem, boot_point, None) - .context(ErrorKind::Mounting)?; - - Ok(mount_stack) } +/// Remove swap entry from fstab and any commented lines +/// Returns an owned String +/// +/// # Arguments +/// * `fstab` - A string slice holding the contents of the fstab file fn fix_fstab(fstab: &str) -> String { fstab .lines() @@ -73,6 +80,7 @@ fn fix_fstab(fstab: &str) -> String { .join("\n") } +/// Creates a file at the path provided, and mounts it to a loop device fn create_image(path: &Path, size: Byte, overwrite: bool) -> Result { { let mut options = fs::OpenOptions::new(); @@ -92,11 +100,12 @@ fn create_image(path: &Path, size: Byte, overwrite: bool) -> Result Result { - let devices = get_storage_devices(allow_non_removable)?; + let devices = storage::get_storage_devices(allow_non_removable)?; if devices.is_empty() { - Err(ErrorKind::NoRemovableDevices)? + return Err(ErrorKind::NoRemovableDevices.into()); } if allow_non_removable { @@ -118,9 +127,10 @@ fn select_block_device(allow_non_removable: bool) -> Result { Ok(PathBuf::from("/dev").join(&devices[selection].name)) } -#[allow(clippy::cognitive_complexity)] -fn create(command: CreateCommand) -> Result<(), Error> { - let presets = presets::Presets::load(&command.presets)?; +/// Creates the installation +#[allow(clippy::cognitive_complexity)] // TODO: Split steps into functions and remove this +fn create(command: args::CreateCommand) -> Result<(), Error> { + let presets = presets::PresetsCollection::load(&command.presets)?; let sgdisk = Tool::find("sgdisk")?; let pacstrap = Tool::find("pacstrap")?; @@ -185,10 +195,10 @@ fn create(command: CreateCommand) -> Result<(), Error> { thread::sleep(Duration::from_millis(1000)); info!("Formatting filesystems"); - let boot_partition = storage_device.get_partition(BOOT_PARTITION_INDEX)?; + let boot_partition = storage_device.get_partition(constants::BOOT_PARTITION_INDEX)?; let boot_filesystem = Filesystem::format(&boot_partition, FilesystemType::Vfat, &mkfat)?; - let root_partition_base = storage_device.get_partition(ROOT_PARTITION_INDEX)?; + let root_partition_base = storage_device.get_partition(constants::ROOT_PARTITION_INDEX)?; let encrypted_root = if let Some(cryptsetup) = &cryptsetup { info!("Encrypting the root filesystem"); EncryptedDevice::prepare(&cryptsetup, &root_partition_base)?; @@ -209,7 +219,7 @@ fn create(command: CreateCommand) -> Result<(), Error> { let root_filesystem = Filesystem::format(root_partition, FilesystemType::Ext4, &mkext4)?; - let mount_stack = mount(mount_point.path(), &boot_filesystem, &root_filesystem)?; + let mount_stack = tool::mount(mount_point.path(), &boot_filesystem, &root_filesystem)?; if log_enabled!(Level::Debug) { debug!("lsblk:"); @@ -223,17 +233,10 @@ fn create(command: CreateCommand) -> Result<(), Error> { .ok(); } - let mut packages: HashSet = [ - "base", - "grub", - "efibootmgr", - "intel-ucode", - "networkmanager", - "broadcom-wl", - ] - .iter() - .map(|s| String::from(*s)) - .collect(); + let mut packages: HashSet = constants::BASE_PACKAGES + .iter() + .map(|s| String::from(*s)) + .collect(); packages.extend(presets.packages); @@ -292,7 +295,7 @@ fn create(command: CreateCommand) -> Result<(), Error> { info!("Configuring journald"); fs::write( mount_point.path().join("etc/systemd/journald.conf"), - JOURNALD_CONF, + constants::JOURNALD_CONF, ) .context(ErrorKind::PostInstallation)?; @@ -378,123 +381,3 @@ fn create(command: CreateCommand) -> Result<(), Error> { Ok(()) } - -fn chroot(command: ChrootCommand) -> Result<(), Error> { - let arch_chroot = Tool::find("arch-chroot")?; - let cryptsetup; - - let loop_device: Option; - 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)?; - let boot_filesystem = Filesystem::from_partition(&boot_partition, FilesystemType::Vfat); - - let root_partition_base = storage_device.get_partition(ROOT_PARTITION_INDEX)?; - let encrypted_root = if is_encrypted_device(&root_partition_base)? { - cryptsetup = Some(Tool::find("cryptsetup")?); - Some(EncryptedDevice::open( - cryptsetup.as_ref().unwrap(), - &root_partition_base, - "alma_root".into(), - )?) - } else { - None - }; - - let root_partition = if let Some(e) = encrypted_root.as_ref() { - e as &dyn BlockDevice - } else { - &root_partition_base as &dyn BlockDevice - }; - let root_filesystem = Filesystem::from_partition(root_partition, FilesystemType::Ext4); - - let mount_stack = mount(mount_point.path(), &boot_filesystem, &root_filesystem)?; - - arch_chroot - .execute() - .arg(mount_point.path()) - .args(&command.command) - .run(ErrorKind::Interactive)?; - - info!("Unmounting filesystems"); - mount_stack.umount()?; - - Ok(()) -} - -fn qemu(command: QemuCommand) -> Result<(), Error> { - let qemu = Tool::find("qemu-system-x86_64")?; - - let mut run = qemu.execute(); - run.args(&[ - "-m", - "4G", - "-netdev", - "user,id=user.0", - "-device", - "virtio-net-pci,netdev=user.0", - "-device", - "qemu-xhci,id=xhci", - "-device", - "usb-tablet,bus=xhci.0", - "-drive", - ]) - .arg(format!( - "file={},if=virtio,format=raw", - command.block_device.display() - )) - .args(command.args); - - if PathBuf::from("/dev/kvm").exists() { - debug!("KVM is enabled"); - run.args(&["-enable-kvm", "-cpu", "host"]); - } - - let err = run.exec(); - - Err(err).context(ErrorKind::Qemu)? -} - -fn main() { - let app = App::from_args(); - - let mut builder = pretty_env_logger::formatted_timed_builder(); - let log_level = if app.verbose { - LevelFilter::Debug - } else { - LevelFilter::Info - }; - builder.filter_level(log_level); - builder.init(); - - let result = match app.cmd { - Command::Create(command) => create(command), - Command::Chroot(command) => chroot(command), - Command::Qemu(command) => qemu(command), - }; - - match result { - Ok(()) => { - exit(0); - } - Err(error) => { - error!("{}", error); - for cause in (&error as &dyn Fail).iter_causes() { - error!("Caused by: {}", cause); - } - exit(1); - } - } -} diff --git a/src/presets.rs b/src/presets.rs index 01b4b15..248ba27 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -5,10 +5,8 @@ use std::collections::HashSet; use std::env; use std::fs; use std::path::{Path, PathBuf}; -use toml; #[derive(Deserialize)] - struct Preset { packages: Option>, script: Option, @@ -24,12 +22,12 @@ impl Preset { } } -pub struct Presets { +pub struct PresetsCollection { pub packages: HashSet, pub scripts: Vec, } -impl Presets { +impl PresetsCollection { pub fn load(list: &[PathBuf]) -> Result { let mut packages = HashSet::new(); let mut scripts = Vec::new(); @@ -59,7 +57,7 @@ impl Presets { .collect(); if !missing_envrionments.is_empty() { - Err(ErrorKind::MissingEnvironmentVariables(missing_envrionments))? + return Err(ErrorKind::MissingEnvironmentVariables(missing_envrionments).into()); } Ok(Self { packages, scripts }) diff --git a/src/process.rs b/src/process.rs index 51eafd6..e7a1c24 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,4 +1,4 @@ -use super::error::*; +use super::error::{Error, ErrorKind}; use failure::{Fail, ResultExt}; use log::error; use std::process::{Command, ExitStatus}; diff --git a/src/storage/crypt.rs b/src/storage/crypt.rs index d9e23e8..ef84a41 100644 --- a/src/storage/crypt.rs +++ b/src/storage/crypt.rs @@ -9,8 +9,8 @@ use std::io::Read; use std::marker::PhantomData; use std::path::{Path, PathBuf}; -static LUKS_MAGIC_1: &'static [u8] = &[0x4c, 0x55, 0x4b, 0x53, 0xba, 0xbe]; -static LUKS_MAGIC_2: &'static [u8] = &[0x53, 0x4b, 0x55, 0x4c, 0xba, 0xbe]; +static LUKS_MAGIC_1: &[u8] = &[0x4c, 0x55, 0x4b, 0x53, 0xba, 0xbe]; +static LUKS_MAGIC_2: &[u8] = &[0x53, 0x4b, 0x55, 0x4c, 0xba, 0xbe]; #[derive(Debug)] pub struct EncryptedDevice<'t, 'o> { diff --git a/src/storage/loop_device.rs b/src/storage/loop_device.rs index 7156ce0..5445fcb 100644 --- a/src/storage/loop_device.rs +++ b/src/storage/loop_device.rs @@ -21,9 +21,7 @@ impl LoopDevice { .context(ErrorKind::Image)?; if !output.status.success() { - Err(ErrorKind::Losetup( - String::from_utf8(output.stderr).unwrap(), - ))? + return Err(ErrorKind::Losetup(String::from_utf8(output.stderr).unwrap()).into()); } let path = PathBuf::from(String::from_utf8(output.stdout).unwrap().trim()); diff --git a/src/storage/markers.rs b/src/storage/markers.rs index 8e18a7c..a2b72be 100644 --- a/src/storage/markers.rs +++ b/src/storage/markers.rs @@ -1,5 +1,5 @@ use std::path::Path; - +// Marker traits pub trait BlockDevice: std::fmt::Debug { fn path(&self) -> &Path; } diff --git a/src/storage/mount_stack.rs b/src/storage/mount_stack.rs index c77e0e2..6dbabcd 100644 --- a/src/storage/mount_stack.rs +++ b/src/storage/mount_stack.rs @@ -2,7 +2,6 @@ use super::Filesystem; use crate::error::{Error, ErrorKind}; use failure::Fail; use log::{debug, warn}; -use nix; use nix::mount::{mount, umount, MsFlags}; use std::marker::PhantomData; use std::path::PathBuf; @@ -20,7 +19,6 @@ impl<'a> MountStack<'a> { } } - #[must_use] pub fn mount( &mut self, filesystem: &'a Filesystem, diff --git a/src/storage/storage_device.rs b/src/storage/storage_device.rs index 8f70002..17b1ccf 100644 --- a/src/storage/storage_device.rs +++ b/src/storage/storage_device.rs @@ -31,8 +31,11 @@ impl<'a> StorageDevice<'a> { path, origin: PhantomData, }; - if !allow_non_removable && (!(_self.is_removable_device()? || _self.is_loop_device())) { - return Err(ErrorKind::DangerousDevice)?; + + // If we only allow removable/loop devices, and the device is neither removable or a loop + // device then throw a DangerousDevice error + if !(allow_non_removable || _self.is_removable_device()? || _self.is_loop_device()) { + return Err(ErrorKind::DangerousDevice.into()); } Ok(_self) diff --git a/src/tool/chroot.rs b/src/tool/chroot.rs new file mode 100644 index 0000000..3202449 --- /dev/null +++ b/src/tool/chroot.rs @@ -0,0 +1,70 @@ +use super::mount; +use super::Tool; +use crate::args; +use crate::constants::{BOOT_PARTITION_INDEX, ROOT_PARTITION_INDEX}; +use crate::error::{Error, ErrorKind}; +use crate::process::CommandExt; +use crate::storage; +use crate::storage::{is_encrypted_device, EncryptedDevice}; +use crate::storage::{BlockDevice, Filesystem, FilesystemType, LoopDevice}; +use log::info; + +use failure::ResultExt; +use tempfile::tempdir; + +/// Use arch-chroot to chroot to the given device +/// Also handles encrypted root partitions (detected by checking for the LUKS magic header) +pub fn chroot(command: args::ChrootCommand) -> Result<(), Error> { + let arch_chroot = Tool::find("arch-chroot")?; + let cryptsetup; + + let loop_device: Option; + 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)?; + let boot_filesystem = Filesystem::from_partition(&boot_partition, FilesystemType::Vfat); + + let root_partition_base = storage_device.get_partition(ROOT_PARTITION_INDEX)?; + let encrypted_root = if is_encrypted_device(&root_partition_base)? { + cryptsetup = Some(Tool::find("cryptsetup")?); + Some(EncryptedDevice::open( + cryptsetup.as_ref().unwrap(), + &root_partition_base, + "alma_root".into(), + )?) + } else { + None + }; + + let root_partition = if let Some(e) = encrypted_root.as_ref() { + e as &dyn BlockDevice + } else { + &root_partition_base as &dyn BlockDevice + }; + let root_filesystem = Filesystem::from_partition(root_partition, FilesystemType::Ext4); + + let mount_stack = mount(mount_point.path(), &boot_filesystem, &root_filesystem)?; + + arch_chroot + .execute() + .arg(mount_point.path()) + .args(&command.command) + .run(ErrorKind::Interactive)?; + + info!("Unmounting filesystems"); + mount_stack.umount()?; + + Ok(()) +} diff --git a/src/tool.rs b/src/tool/mod.rs similarity index 76% rename from src/tool.rs rename to src/tool/mod.rs index 1e5bed7..a57c9da 100644 --- a/src/tool.rs +++ b/src/tool/mod.rs @@ -1,4 +1,12 @@ -use super::error::*; +mod chroot; +mod mount; +mod qemu; + +pub use chroot::chroot; +pub use mount::mount; +pub use qemu::qemu; + +use crate::error::*; use failure::ResultExt; use std::path::PathBuf; use std::process::Command; diff --git a/src/tool/mount.rs b/src/tool/mount.rs new file mode 100644 index 0000000..5b188c3 --- /dev/null +++ b/src/tool/mount.rs @@ -0,0 +1,37 @@ +use crate::error::{Error, ErrorKind}; +use crate::storage::{Filesystem, MountStack}; +use failure::ResultExt; +use log::{debug, info}; +use std::fs; +use std::path::Path; + +/// Mounts root filesystem to given mount_path +/// Mounts boot filesystem to mount_path/boot +/// Note we mount with noatime to reduce disk writes by not recording file access times +pub fn mount<'a>( + mount_path: &Path, + boot_filesystem: &'a Filesystem, + root_filesystem: &'a Filesystem, +) -> Result, Error> { + let mut mount_stack = MountStack::new(); + debug!( + "Root partition: {}", + root_filesystem.block().path().display() + ); + + info!("Mounting filesystems to {}", mount_path.display()); + mount_stack + .mount(&root_filesystem, mount_path.into(), None) + .context(ErrorKind::Mounting)?; + + let boot_point = mount_path.join("boot"); + if !boot_point.exists() { + fs::create_dir(&boot_point).context(ErrorKind::CreateBoot)?; + } + + mount_stack + .mount(&boot_filesystem, boot_point, None) + .context(ErrorKind::Mounting)?; + + Ok(mount_stack) +} diff --git a/src/tool/qemu.rs b/src/tool/qemu.rs new file mode 100644 index 0000000..d0e8409 --- /dev/null +++ b/src/tool/qemu.rs @@ -0,0 +1,43 @@ +use super::Tool; +use crate::args; +use crate::error; +use log::debug; + +use failure::ResultExt; +use std::os::unix::process::CommandExt as UnixCommandExt; +use std::path::PathBuf; + +/// Loads given block device in qemu +/// Uses kvm if it is enabled +pub fn qemu(command: args::QemuCommand) -> Result<(), error::Error> { + let qemu = Tool::find("qemu-system-x86_64")?; + + let mut run = qemu.execute(); + run.args(&[ + "-m", + "4G", + "-netdev", + "user,id=user.0", + "-device", + "virtio-net-pci,netdev=user.0", + "-device", + "qemu-xhci,id=xhci", + "-device", + "usb-tablet,bus=xhci.0", + "-drive", + ]) + .arg(format!( + "file={},if=virtio,format=raw", + command.block_device.display() + )) + .args(command.args); + + if PathBuf::from("/dev/kvm").exists() { + debug!("KVM is enabled"); + run.args(&["-enable-kvm", "-cpu", "host"]); + } + + let err = run.exec(); + + Err(err).context(error::ErrorKind::Qemu)? +}