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 = (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}")) } }