Skip to content

Commit

Permalink
Merge pull request #260 from cgwalters/to-disk-loopback
Browse files Browse the repository at this point in the history
install: Support `to-disk --via-loopback`
  • Loading branch information
cgwalters authored Jan 16, 2024
2 parents 736c0e8 + 767b159 commit fe982a2
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 35 deletions.
52 changes: 51 additions & 1 deletion lib/src/blockdev.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::install::run_in_host_mountns;
use crate::task::Task;
use anyhow::{anyhow, Context, Result};
use camino::Utf8Path;
use camino::{Utf8Path, Utf8PathBuf};
use fn_error_context::context;
use nix::errno::Errno;
use once_cell::sync::Lazy;
Expand All @@ -10,6 +10,7 @@ use serde::Deserialize;
use std::collections::HashMap;
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::process::Command;

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -75,6 +76,55 @@ pub(crate) fn list() -> Result<Vec<Device>> {
list_impl(None)
}

pub(crate) struct LoopbackDevice {
pub(crate) dev: Option<Utf8PathBuf>,
}

impl LoopbackDevice {
// Create a new loopback block device targeting the provided file path.
pub(crate) fn new(path: &Path) -> Result<Self> {
let dev = Task::new("losetup", "losetup")
.args(["--show", "-P", "--find"])
.arg(path)
.quiet()
.read()?;
let dev = Utf8PathBuf::from(dev.trim());
Ok(Self { dev: Some(dev) })
}

// Access the path to the loopback block device.
pub(crate) fn path(&self) -> &Utf8Path {
// SAFETY: The option cannot be destructured until we are dropped
self.dev.as_deref().unwrap()
}

// Shared backend for our `close` and `drop` implementations.
fn impl_close(&mut self) -> Result<()> {
// SAFETY: This is the only place we take the option
let dev = if let Some(dev) = self.dev.take() {
dev
} else {
return Ok(());
};
Task::new("losetup", "losetup")
.args(["-d", dev.as_str()])
.quiet()
.run()
}

/// Consume this device, unmounting it.
pub(crate) fn close(mut self) -> Result<()> {
self.impl_close()
}
}

impl Drop for LoopbackDevice {
fn drop(&mut self) {
// Best effort to unmount if we're dropped without invoking `close`
let _ = self.impl_close();
}
}

pub(crate) fn udev_settle() -> Result<()> {
// There's a potential window after rereading the partition table where
// udevd hasn't yet received updates from the kernel, settle will return
Expand Down
28 changes: 25 additions & 3 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ pub(crate) struct InstallToDiskOpts {
#[clap(flatten)]
#[serde(flatten)]
pub(crate) config_opts: InstallConfigOpts,

/// Instead of targeting a block device, write to a file via loopback.
#[clap(long)]
#[serde(default)]
pub(crate) via_loopback: bool,
}

#[derive(ValueEnum, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -1039,13 +1044,26 @@ fn installation_complete() {

/// Implementation of the `bootc install to-disk` CLI command.
pub(crate) async fn install_to_disk(opts: InstallToDiskOpts) -> Result<()> {
let block_opts = opts.block_opts;
let mut block_opts = opts.block_opts;
let target_blockdev_meta = block_opts
.device
.metadata()
.with_context(|| format!("Querying {}", &block_opts.device))?;
if !target_blockdev_meta.file_type().is_block_device() {
anyhow::bail!("Not a block device: {}", block_opts.device);
let mut loopback = None;
if opts.via_loopback {
if !target_blockdev_meta.file_type().is_file() {
anyhow::bail!(
"Not a regular file (to be used via loopback): {}",
block_opts.device
);
}
let loopback_dev = crate::blockdev::LoopbackDevice::new(block_opts.device.as_std_path())?;
block_opts.device = loopback_dev.path().into();
loopback = Some(loopback_dev);
} else {
if !target_blockdev_meta.file_type().is_block_device() {
anyhow::bail!("Not a block device: {}", block_opts.device);
}
}
let state = prepare_install(opts.config_opts, opts.target_opts).await?;

Expand All @@ -1069,6 +1087,10 @@ pub(crate) async fn install_to_disk(opts: InstallToDiskOpts) -> Result<()> {
Task::new_and_run("Closing root LUKS device", "cryptsetup", ["close", luksdev])?;
}

if let Some(loopback_dev) = loopback {
loopback_dev.close()?;
}

installation_complete();

Ok(())
Expand Down
37 changes: 6 additions & 31 deletions lib/src/privtests.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,18 @@
use std::process::Command;

use anyhow::Result;
use camino::{Utf8Path, Utf8PathBuf};
use camino::Utf8Path;
use fn_error_context::context;
use rustix::fd::AsFd;
use xshell::{cmd, Shell};

use crate::spec::HostType;
use crate::blockdev::LoopbackDevice;

use super::cli::TestingOpts;
use super::spec::Host;

const IMGSIZE: u64 = 20 * 1024 * 1024 * 1024;

struct LoopbackDevice {
#[allow(dead_code)]
tmpf: tempfile::NamedTempFile,
dev: Utf8PathBuf,
}

impl LoopbackDevice {
fn new_temp(sh: &xshell::Shell) -> Result<Self> {
let mut tmpd = tempfile::NamedTempFile::new_in("/var/tmp")?;
rustix::fs::ftruncate(tmpd.as_file_mut().as_fd(), IMGSIZE)?;
let diskpath = tmpd.path();
let path = cmd!(sh, "losetup --find --show {diskpath}").read()?;
Ok(Self {
tmpf: tmpd,
dev: path.into(),
})
}
}

impl Drop for LoopbackDevice {
fn drop(&mut self) {
let _ = Command::new("losetup")
.args(["-d", self.dev.as_str()])
.status();
}
}

fn init_ostree(sh: &Shell, rootfs: &Utf8Path) -> Result<()> {
cmd!(sh, "ostree admin init-fs --modern {rootfs}").run()?;
Ok(())
Expand All @@ -49,8 +22,10 @@ fn init_ostree(sh: &Shell, rootfs: &Utf8Path) -> Result<()> {
fn run_bootc_status() -> Result<()> {
let sh = Shell::new()?;

let loopdev = LoopbackDevice::new_temp(&sh)?;
let devpath = &loopdev.dev;
let mut tmpdisk = tempfile::NamedTempFile::new_in("/var/tmp")?;
rustix::fs::ftruncate(tmpdisk.as_file_mut().as_fd(), IMGSIZE)?;
let loopdev = LoopbackDevice::new(tmpdisk.path())?;
let devpath = loopdev.path();
println!("Using {devpath:?}");

let td = tempfile::tempdir()?;
Expand Down

0 comments on commit fe982a2

Please sign in to comment.