Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

install: Support to-disk --via-loopback #260

Merged
merged 1 commit into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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