Files
initrd/src/cmd/init/dmcrypt.rs
Mikaël Cluseau 73cafb7385 introduce rust
2025-06-26 14:29:17 +02:00

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(),
}
}