152 lines
4.7 KiB
Rust
152 lines
4.7 KiB
Rust
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<String> = 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<const N: usize>(pw: &str, args: [&str; N]) -> Result<bool> {
|
|
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(),
|
|
}
|
|
}
|