use eyre::{format_err, Result}; use log::{error, info, warn}; use std::collections::BTreeSet as Set; use std::process::Stdio; use tokio::io::AsyncWriteExt; use tokio::process::Command; use tokio::sync::Mutex; use super::{retry_or_ignore, USED_DEVS}; use crate::blockdev::{is_uninitialized, uninitialize}; use crate::bootstrap::config::{CryptDev, DevFilter}; use crate::fs::walk_dir; use crate::input; pub async fn setup(devs: &[CryptDev]) { if devs.is_empty() { return; } let mut used_devs = USED_DEVS.lock().await; // CryptDev.name that have a least one assignment done let mut done = Set::new(); // dmcrypt devices opened here let mut done_crypt = Set::new(); retry_or_ignore(async || { let all_devs = walk_dir("/dev").await; for dev in devs { let mut mappings = find_dev(dev, &all_devs); mappings.retain(|(_, dev_path)| !used_devs.contains(dev_path)); if mappings.is_empty() && !dev.optional() && !done.contains(&dev.name) { return Err(format_err!("no device found for crypt dev {}", dev.name)); } for (crypt_dev, dev_path) in mappings { if done_crypt.contains(&crypt_dev) { continue; } info!("crypt dev {crypt_dev}: using {dev_path}"); crypt_open(&crypt_dev, &dev_path).await?; done_crypt.insert(crypt_dev); used_devs.insert(dev_path); done.insert(&dev.name); } } Ok(()) }) .await; } static PREV_PW: Mutex = Mutex::const_new(String::new()); async fn crypt_open(crypt_dev: &str, dev_path: &str) -> Result<()> { 'open_loop: loop { let mut prev_pw = PREV_PW.lock().await; let prompt = if prev_pw.is_empty() { format!("crypt password for {crypt_dev}? ") } else { format!("crypt password for {crypt_dev} (enter = reuse previous)? ") }; let mut pw = input::read_password(prompt).await; if pw.is_empty() { pw = prev_pw.clone(); } if pw.is_empty() { error!("empty password provided!"); continue; } *prev_pw = pw.clone(); if cryptsetup(&pw, ["open", dev_path, crypt_dev]).await? { return Ok(()); } error!("crypt open {crypt_dev} from {dev_path} failed"); if is_uninitialized(dev_path).await? { // we can format the device info!("{dev_path} looks uninitialized, it may be formatted"); match input::read_choice(["[f]ormat", "[r]etry", "[i]gnore"]).await { 'r' => continue 'open_loop, 'i' => return Ok(()), 'f' => { if !cryptsetup(&pw, ["luksFormat", dev_path]).await? { return Err(format_err!("cryptsetup luksFormat failed")); } if !cryptsetup(&pw, ["open", dev_path, crypt_dev]).await? { return Err(format_err!("open after format failed")); } if let Err(e) = uninitialize(&format!("/dev/mapper/{crypt_dev}")).await { warn!("uninitialize failed (ignored): {e}"); } return Ok(()); } _ => unreachable!(), } } else { // device looks initialized, don't allow format warn!("{dev_path} looks initialized, formatting not allowed from init"); match input::read_choice(["[r]etry", "[i]gnore"]).await { 'r' => continue 'open_loop, 'i' => return Ok(()), _ => unreachable!(), } } } } async fn cryptsetup(pw: &str, args: [&str; N]) -> Result { let mut child = Command::new("cryptsetup") .args(args) .arg("--key-file=-") .stdin(Stdio::piped()) .spawn()?; (child.stdin.as_mut().unwrap()) .write_all(pw.as_bytes()) .await?; Ok(child.wait().await?.success()) } fn find_dev(dev: &CryptDev, all_devs: &[String]) -> Vec<(String, String)> { let dev_name = &dev.name; match dev.filter { DevFilter::Dev(ref path) => (all_devs.iter()) .filter(|dev_path| dev_path == &path) .map(|dev_path| (dev.name.clone(), dev_path.clone())) .collect(), DevFilter::Prefix(ref prefix) => (all_devs.iter()) .filter_map(|path| { let suffix = path.strip_prefix(prefix)?; Some((format!("{dev_name}{suffix}"), path.clone())) }) .collect(), } }