Skip to content

Commit fe982a2

Browse files
authored
Merge pull request #260 from cgwalters/to-disk-loopback
install: Support `to-disk --via-loopback`
2 parents 736c0e8 + 767b159 commit fe982a2

File tree

3 files changed

+82
-35
lines changed

3 files changed

+82
-35
lines changed

lib/src/blockdev.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::install::run_in_host_mountns;
22
use crate::task::Task;
33
use anyhow::{anyhow, Context, Result};
4-
use camino::Utf8Path;
4+
use camino::{Utf8Path, Utf8PathBuf};
55
use fn_error_context::context;
66
use nix::errno::Errno;
77
use once_cell::sync::Lazy;
@@ -10,6 +10,7 @@ use serde::Deserialize;
1010
use std::collections::HashMap;
1111
use std::fs::File;
1212
use std::os::unix::io::AsRawFd;
13+
use std::path::Path;
1314
use std::process::Command;
1415

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

79+
pub(crate) struct LoopbackDevice {
80+
pub(crate) dev: Option<Utf8PathBuf>,
81+
}
82+
83+
impl LoopbackDevice {
84+
// Create a new loopback block device targeting the provided file path.
85+
pub(crate) fn new(path: &Path) -> Result<Self> {
86+
let dev = Task::new("losetup", "losetup")
87+
.args(["--show", "-P", "--find"])
88+
.arg(path)
89+
.quiet()
90+
.read()?;
91+
let dev = Utf8PathBuf::from(dev.trim());
92+
Ok(Self { dev: Some(dev) })
93+
}
94+
95+
// Access the path to the loopback block device.
96+
pub(crate) fn path(&self) -> &Utf8Path {
97+
// SAFETY: The option cannot be destructured until we are dropped
98+
self.dev.as_deref().unwrap()
99+
}
100+
101+
// Shared backend for our `close` and `drop` implementations.
102+
fn impl_close(&mut self) -> Result<()> {
103+
// SAFETY: This is the only place we take the option
104+
let dev = if let Some(dev) = self.dev.take() {
105+
dev
106+
} else {
107+
return Ok(());
108+
};
109+
Task::new("losetup", "losetup")
110+
.args(["-d", dev.as_str()])
111+
.quiet()
112+
.run()
113+
}
114+
115+
/// Consume this device, unmounting it.
116+
pub(crate) fn close(mut self) -> Result<()> {
117+
self.impl_close()
118+
}
119+
}
120+
121+
impl Drop for LoopbackDevice {
122+
fn drop(&mut self) {
123+
// Best effort to unmount if we're dropped without invoking `close`
124+
let _ = self.impl_close();
125+
}
126+
}
127+
78128
pub(crate) fn udev_settle() -> Result<()> {
79129
// There's a potential window after rereading the partition table where
80130
// udevd hasn't yet received updates from the kernel, settle will return

lib/src/install.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ pub(crate) struct InstallToDiskOpts {
144144
#[clap(flatten)]
145145
#[serde(flatten)]
146146
pub(crate) config_opts: InstallConfigOpts,
147+
148+
/// Instead of targeting a block device, write to a file via loopback.
149+
#[clap(long)]
150+
#[serde(default)]
151+
pub(crate) via_loopback: bool,
147152
}
148153

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

10401045
/// Implementation of the `bootc install to-disk` CLI command.
10411046
pub(crate) async fn install_to_disk(opts: InstallToDiskOpts) -> Result<()> {
1042-
let block_opts = opts.block_opts;
1047+
let mut block_opts = opts.block_opts;
10431048
let target_blockdev_meta = block_opts
10441049
.device
10451050
.metadata()
10461051
.with_context(|| format!("Querying {}", &block_opts.device))?;
1047-
if !target_blockdev_meta.file_type().is_block_device() {
1048-
anyhow::bail!("Not a block device: {}", block_opts.device);
1052+
let mut loopback = None;
1053+
if opts.via_loopback {
1054+
if !target_blockdev_meta.file_type().is_file() {
1055+
anyhow::bail!(
1056+
"Not a regular file (to be used via loopback): {}",
1057+
block_opts.device
1058+
);
1059+
}
1060+
let loopback_dev = crate::blockdev::LoopbackDevice::new(block_opts.device.as_std_path())?;
1061+
block_opts.device = loopback_dev.path().into();
1062+
loopback = Some(loopback_dev);
1063+
} else {
1064+
if !target_blockdev_meta.file_type().is_block_device() {
1065+
anyhow::bail!("Not a block device: {}", block_opts.device);
1066+
}
10491067
}
10501068
let state = prepare_install(opts.config_opts, opts.target_opts).await?;
10511069

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

1090+
if let Some(loopback_dev) = loopback {
1091+
loopback_dev.close()?;
1092+
}
1093+
10721094
installation_complete();
10731095

10741096
Ok(())

lib/src/privtests.rs

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,18 @@
11
use std::process::Command;
22

33
use anyhow::Result;
4-
use camino::{Utf8Path, Utf8PathBuf};
4+
use camino::Utf8Path;
55
use fn_error_context::context;
66
use rustix::fd::AsFd;
77
use xshell::{cmd, Shell};
88

9-
use crate::spec::HostType;
9+
use crate::blockdev::LoopbackDevice;
1010

1111
use super::cli::TestingOpts;
1212
use super::spec::Host;
1313

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

16-
struct LoopbackDevice {
17-
#[allow(dead_code)]
18-
tmpf: tempfile::NamedTempFile,
19-
dev: Utf8PathBuf,
20-
}
21-
22-
impl LoopbackDevice {
23-
fn new_temp(sh: &xshell::Shell) -> Result<Self> {
24-
let mut tmpd = tempfile::NamedTempFile::new_in("/var/tmp")?;
25-
rustix::fs::ftruncate(tmpd.as_file_mut().as_fd(), IMGSIZE)?;
26-
let diskpath = tmpd.path();
27-
let path = cmd!(sh, "losetup --find --show {diskpath}").read()?;
28-
Ok(Self {
29-
tmpf: tmpd,
30-
dev: path.into(),
31-
})
32-
}
33-
}
34-
35-
impl Drop for LoopbackDevice {
36-
fn drop(&mut self) {
37-
let _ = Command::new("losetup")
38-
.args(["-d", self.dev.as_str()])
39-
.status();
40-
}
41-
}
42-
4316
fn init_ostree(sh: &Shell, rootfs: &Utf8Path) -> Result<()> {
4417
cmd!(sh, "ostree admin init-fs --modern {rootfs}").run()?;
4518
Ok(())
@@ -49,8 +22,10 @@ fn init_ostree(sh: &Shell, rootfs: &Utf8Path) -> Result<()> {
4922
fn run_bootc_status() -> Result<()> {
5023
let sh = Shell::new()?;
5124

52-
let loopdev = LoopbackDevice::new_temp(&sh)?;
53-
let devpath = &loopdev.dev;
25+
let mut tmpdisk = tempfile::NamedTempFile::new_in("/var/tmp")?;
26+
rustix::fs::ftruncate(tmpdisk.as_file_mut().as_fd(), IMGSIZE)?;
27+
let loopdev = LoopbackDevice::new(tmpdisk.path())?;
28+
let devpath = loopdev.path();
5429
println!("Using {devpath:?}");
5530

5631
let td = tempfile::tempdir()?;

0 commit comments

Comments
 (0)