213 lines
5.7 KiB
Rust
213 lines
5.7 KiB
Rust
![]() |
use eyre::{format_err, Result};
|
|||
|
use log::{error, info, warn};
|
|||
|
use tokio::process::Command;
|
|||
|
|
|||
|
use super::{exec, retry, retry_or_ignore, USED_DEVS};
|
|||
|
use crate::bootstrap::config::{Config, Filesystem, LvSize, LvmLV, LvmVG, TAKE_ALL};
|
|||
|
use crate::fs::walk_dir;
|
|||
|
use crate::{blockdev, lvm};
|
|||
|
|
|||
|
pub async fn setup(cfg: &Config) {
|
|||
|
if cfg.lvm.is_empty() {
|
|||
|
info!("no LVM VG configured");
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
exec("pvscan", &[]).await;
|
|||
|
exec("vgscan", &["--mknodes"]).await;
|
|||
|
|
|||
|
for vg in &cfg.lvm {
|
|||
|
retry_or_ignore(async || setup_vg(vg).await).await
|
|||
|
}
|
|||
|
|
|||
|
let lvs = retry(lvm::lvs).await;
|
|||
|
|
|||
|
for vg in &cfg.lvm {
|
|||
|
let vg_name = vg.name.as_str();
|
|||
|
|
|||
|
for lv in &vg.lvs {
|
|||
|
let lv_name = lv.name.as_str();
|
|||
|
|
|||
|
if (lvs.iter()).any(|lv| lv.equal_name(vg_name, lv_name)) {
|
|||
|
info!("LVM LV {vg_name}/{lv_name} exists");
|
|||
|
} else {
|
|||
|
retry_or_ignore(async || setup_lv(&vg, &lv).await).await;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
exec("vgchange", &["--sysinit", "-a", "ly"]).await;
|
|||
|
|
|||
|
for vg in &cfg.lvm {
|
|||
|
for lv in &vg.lvs {
|
|||
|
retry_or_ignore(async || format_lv(&vg, &lv).await).await;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
async fn setup_vg(vg: &LvmVG) -> Result<()> {
|
|||
|
let vg_name = vg.name.as_str();
|
|||
|
|
|||
|
let pvs = retry(lvm::pvs).await;
|
|||
|
|
|||
|
let mut dev_done = pvs.iter().filter(|pv| pv.vg_name == vg.name).count();
|
|||
|
let dev_needed = vg.pvs.n;
|
|||
|
macro_rules! missing_count {
|
|||
|
() => {
|
|||
|
(dev_needed as usize) - dev_done
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
if dev_needed == TAKE_ALL {
|
|||
|
if dev_done == 0 {
|
|||
|
info!("setting up LVM VG {vg_name} using all matching devices");
|
|||
|
} else {
|
|||
|
// in "take all" mode, don't extend as existing vg at boot
|
|||
|
info!("LVM VG {vg_name} exists");
|
|||
|
return Ok(());
|
|||
|
}
|
|||
|
} else if dev_done >= (dev_needed as usize) {
|
|||
|
info!("LVM VG {vg_name} exists with enough devices");
|
|||
|
return Ok(()); // already set up
|
|||
|
} else {
|
|||
|
info!("setting up LVM VG {vg_name} ({dev_done}/{dev_needed} devices configured)");
|
|||
|
}
|
|||
|
|
|||
|
let regexps: Vec<regex::Regex> = (vg.pvs.regexps.iter())
|
|||
|
.filter_map(|re_str| {
|
|||
|
(re_str.parse())
|
|||
|
.inspect_err(|e| error!("invalid regex ignored: {re_str:?}: {e}"))
|
|||
|
.ok()
|
|||
|
})
|
|||
|
.collect();
|
|||
|
|
|||
|
let mut used_devs = USED_DEVS.lock().await;
|
|||
|
|
|||
|
let matching_devs = (walk_dir("/dev").await.into_iter())
|
|||
|
.filter(|path| !used_devs.contains(path.as_str()))
|
|||
|
.filter(|path| regexps.iter().any(|re| re.is_match(path)));
|
|||
|
|
|||
|
let devs: Vec<_> = if dev_needed == TAKE_ALL {
|
|||
|
matching_devs.collect()
|
|||
|
} else {
|
|||
|
matching_devs.take(missing_count!()).collect()
|
|||
|
};
|
|||
|
|
|||
|
let cmd = if dev_done == 0 {
|
|||
|
"vgcreate"
|
|||
|
} else {
|
|||
|
"vgextend"
|
|||
|
};
|
|||
|
let status = (Command::new(cmd).arg(vg_name).args(&devs))
|
|||
|
.status()
|
|||
|
.await?;
|
|||
|
if !status.success() {
|
|||
|
return Err(format_err!("{cmd} failed: {status}"));
|
|||
|
}
|
|||
|
|
|||
|
dev_done += devs.len();
|
|||
|
used_devs.extend(devs);
|
|||
|
|
|||
|
if dev_needed != TAKE_ALL && dev_done < (dev_needed as usize) {
|
|||
|
return Err(format_err!(
|
|||
|
"LVM VG {vg_name} needs {} more device(s)",
|
|||
|
missing_count!()
|
|||
|
));
|
|||
|
}
|
|||
|
|
|||
|
Ok(())
|
|||
|
}
|
|||
|
|
|||
|
async fn setup_lv(vg: &LvmVG, lv: &LvmLV) -> Result<()> {
|
|||
|
let name = format!("{}/{}", vg.name, lv.name);
|
|||
|
info!("creating LV {name}");
|
|||
|
|
|||
|
let mut cmd = Command::new("lvcreate");
|
|||
|
cmd.arg(&vg.name);
|
|||
|
cmd.args(&["--name", &lv.name]);
|
|||
|
|
|||
|
match &lv.size {
|
|||
|
LvSize::Size(sz) => cmd.args(&["-L", sz]),
|
|||
|
LvSize::Extents(sz) => cmd.args(&["-l", sz]),
|
|||
|
};
|
|||
|
|
|||
|
let raid = lv.raid.as_ref().unwrap_or(&vg.defaults.raid);
|
|||
|
|
|||
|
if let Some(mirrors) = raid.mirrors {
|
|||
|
cmd.args(&["--mirrors", &mirrors.to_string()]);
|
|||
|
}
|
|||
|
if let Some(stripes) = raid.stripes {
|
|||
|
cmd.args(&["--stripes", &stripes.to_string()]);
|
|||
|
}
|
|||
|
|
|||
|
let status = cmd.status().await?;
|
|||
|
if !status.success() {
|
|||
|
return Err(format_err!("lvcreate failed: {status}"));
|
|||
|
}
|
|||
|
|
|||
|
if let Err(e) = blockdev::uninitialize(&format!("/dev/{name}")).await {
|
|||
|
warn!("uninitialize failed (ignored): {e}");
|
|||
|
}
|
|||
|
|
|||
|
Ok(())
|
|||
|
}
|
|||
|
|
|||
|
async fn format_lv(vg: &LvmVG, lv: &LvmLV) -> Result<()> {
|
|||
|
let name = &format!("{}/{}", vg.name, lv.name);
|
|||
|
let dev = &format!("/dev/{name}");
|
|||
|
|
|||
|
if !blockdev::is_uninitialized(&dev).await? {
|
|||
|
info!("{dev} looks initialized");
|
|||
|
return Ok(());
|
|||
|
}
|
|||
|
|
|||
|
let fs = lv.fs.as_ref().unwrap_or(&vg.defaults.fs);
|
|||
|
info!("initializing {} filesystem on {dev}", fs.fstype());
|
|||
|
|
|||
|
let mkfs = format!("mkfs.{}", fs.fstype());
|
|||
|
|
|||
|
let mut cmd = Command::new(&mkfs);
|
|||
|
|
|||
|
// filesystem specific flags
|
|||
|
match fs {
|
|||
|
Filesystem::Ext4 => {
|
|||
|
cmd.arg("-F");
|
|||
|
}
|
|||
|
Filesystem::Btrfs | Filesystem::Xfs => {
|
|||
|
cmd.arg("-f");
|
|||
|
}
|
|||
|
&Filesystem::Other(_) => {}
|
|||
|
}
|
|||
|
|
|||
|
cmd.arg(dev);
|
|||
|
|
|||
|
let mut child = match cmd.spawn() {
|
|||
|
Ok(v) => v,
|
|||
|
Err(e) => {
|
|||
|
// try simple fixes
|
|||
|
match fs {
|
|||
|
Filesystem::Xfs => install_package("xfsprogs").await?,
|
|||
|
Filesystem::Btrfs => install_package("btrs-progs").await?,
|
|||
|
_ => Err(format_err!("{mkfs} failed: {e}"))?,
|
|||
|
}
|
|||
|
cmd.spawn().map_err(|e| format_err!("{mkfs} failed: {e}"))?
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
let status = child.wait().await?;
|
|||
|
if !status.success() {
|
|||
|
return Err(format_err!("{mkfs} failed: {status}"));
|
|||
|
}
|
|||
|
|
|||
|
Ok(())
|
|||
|
}
|
|||
|
|
|||
|
async fn install_package(pkg: &str) -> Result<()> {
|
|||
|
let status = Command::new("apk").arg("add").arg(pkg).status().await?;
|
|||
|
if status.success() {
|
|||
|
Ok(())
|
|||
|
} else {
|
|||
|
Err(format_err!("failed to install package {pkg}: {status}"))
|
|||
|
}
|
|||
|
}
|