Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
cgwalters committed Jul 27, 2024
1 parent 23f9d0b commit cd63cfa
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 23 deletions.
9 changes: 9 additions & 0 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

use std::ffi::OsString;
use std::io::Seek;
use std::os::fd::AsFd;
use std::os::unix::process::CommandExt;
use std::process::Command;

use anyhow::{Context, Result};
use camino::Utf8PathBuf;
use cap_std_ext::cap_std;
use cap_std_ext::cap_std::fs::Dir;
use cap_std_ext::cap_tempfile::TempDir;
use clap::Parser;
use clap::ValueEnum;
use fn_error_context::context;
Expand All @@ -22,10 +24,12 @@ use ostree_ext::ostree;
use schemars::schema_for;

use crate::deploy::RequiredHostSpec;
use crate::imgstorage;
use crate::lints;
use crate::spec::Host;
use crate::spec::ImageReference;
use crate::utils::sigpolicy_from_opts;
use crate::utils::CommandRunExt;

include!(concat!(env!("OUT_DIR"), "/version.rs"));

Expand Down Expand Up @@ -777,6 +781,11 @@ impl Opt {
/// Internal (non-generic/monomorphized) primary CLI entrypoint
async fn run_from_opt(opt: Opt) -> Result<()> {
let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
let td = TempDir::new(cap_std::ambient_authority())?;
td.create_dir("sysroot")?;
td.create_dir("run")?;
let td = imgstorage::Storage::create(&td.open_dir("sysroot")?, &td.open_dir("run")?)?;
return Ok(());
match opt {
Opt::Upgrade(opts) => upgrade(opts).await,
Opt::Switch(opts) => switch(opts).await,
Expand Down
53 changes: 32 additions & 21 deletions lib/src/imgstorage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use std::io::{Read, Seek};
use std::os::unix::process::CommandExt;
use std::process::Command;
use std::sync::Arc;

use anyhow::{Context, Result};
use camino::Utf8Path;
Expand All @@ -18,7 +19,8 @@ use fn_error_context::context;
use std::os::fd::AsFd;
use tokio::process::Command as AsyncCommand;

use crate::utils::{AsyncCommandRunExt, CommandRunExt};
use crate::mount::bind_mount_fd;
use crate::utils::{rustix_err_context, AsyncCommandRunExt, CommandRunExt};

/// Global directory path which we use for podman to point
/// it at our storage.
Expand Down Expand Up @@ -66,6 +68,7 @@ async fn run_cmd_async(cmd: Command) -> Result<()> {
}

#[allow(unsafe_code)]
#[context("Binding storage roots")]
fn bind_storage_roots(cmd: &mut Command, storage_root: &Dir, run_root: &Dir) -> Result<()> {
// podman requires an absolute path, for two reasons right now:
// - It writes the file paths into `db.sql`, a sqlite database for unknown reasons
Expand All @@ -74,22 +77,18 @@ fn bind_storage_roots(cmd: &mut Command, storage_root: &Dir, run_root: &Dir) ->
// We create a new mount namespace, which also has the helpful side effect
// of automatically cleaning up the global bind mount that the storage stack
// creates.
let storage_root = storage_root.try_clone()?;
let run_root = run_root.try_clone()?;

let storage_root = Arc::new(storage_root.try_clone().context("Cloning storage root")?);
let run_root = Arc::new(run_root.try_clone().context("Cloning runroot")?);
// SAFETY: All the APIs we call here are safe to invoke between fork and exec.
unsafe {
cmd.pre_exec(move || {
use rustix::mount::mount_bind;
use rustix::process::fchdir;
use rustix::thread::unshare;

unshare(rustix::thread::UnshareFlags::NEWNS)?;
fchdir(storage_root.as_fd())?;
mount_bind(".", STORAGE_ALIAS_DIR)?;
fchdir(run_root.as_fd())?;
mount_bind(".", STORAGE_RUN_ALIAS_DIR)?;
rustix_err_context(unshare(rustix::thread::UnshareFlags::NEWNS), "unshare").unwrap();
bind_mount_fd(storage_root.as_fd(), STORAGE_ALIAS_DIR).unwrap();
bind_mount_fd(run_root.as_fd(), STORAGE_RUN_ALIAS_DIR).unwrap();
// And back to / just by default
rustix::process::chdir("/")?;
rustix_err_context(rustix::process::chdir("/"), "chdir").unwrap();
Ok(())
})
};
Expand Down Expand Up @@ -118,23 +117,38 @@ impl Storage {
Ok(r)
}

fn init_globals() -> Result<()> {
// Ensure our global storage alias dirs exist
for d in [STORAGE_ALIAS_DIR, STORAGE_RUN_ALIAS_DIR] {
std::fs::create_dir_all(d).with_context(|| format!("Creating {d}"))?;
}
Ok(())
}

#[context("Creating imgstorage")]
pub(crate) fn create(sysroot: &Dir, run: &Dir) -> Result<Self> {
Self::init_globals()?;
let subpath = Utf8Path::new(SUBPATH);
// SAFETY: We know there's a parent
let parent = subpath.parent().unwrap();
if !sysroot.try_exists(subpath)? {
if !sysroot
.try_exists(subpath)
.with_context(|| format!("Querying {subpath}"))?
{
let tmp = format!("{SUBPATH}.tmp");
sysroot.remove_all_optional(&tmp)?;
sysroot.create_dir_all(parent)?;
sysroot.remove_all_optional(&tmp).context("Removing tmp")?;
sysroot
.create_dir_all(parent)
.with_context(|| format!("Creating {parent}"))?;
sysroot.create_dir_all(&tmp).context("Creating tmpdir")?;
let storage_root = sysroot.open_dir(&tmp)?;
let storage_root = sysroot.open_dir(&tmp).context("Open tmp")?;
// There's no explicit API to initialize a containers-storage:
// root, simply passing a path will attempt to auto-create it.
// We run "podman images" in the new root.
new_podman_cmd_in(&storage_root, &run)?
.arg("images")
.run()?;
.run()
.context("Initializing images")?;
drop(storage_root);
sysroot
.rename(&tmp, sysroot, subpath)
Expand All @@ -145,10 +159,7 @@ impl Storage {

#[context("Opening imgstorage")]
pub(crate) fn open(sysroot: &Dir, run: &Dir) -> Result<Self> {
// Ensure our global storage alias dirs exist
for d in [STORAGE_ALIAS_DIR, STORAGE_RUN_ALIAS_DIR] {
std::fs::create_dir_all(d).with_context(|| format!("Creating {d}"))?;
}
Self::init_globals()?;
let storage_root = sysroot
.open_dir(SUBPATH)
.with_context(|| format!("Opening {SUBPATH}"))?;
Expand Down
23 changes: 22 additions & 1 deletion lib/src/mount.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
//! Helpers for interacting with mountpoints

use std::os::fd::{AsRawFd, BorrowedFd};

use anyhow::{anyhow, Context, Result};
use camino::Utf8Path;
use fn_error_context::context;
use serde::Deserialize;

use crate::task::Task;
use crate::{task::Task, utils::rustix_err_context};

#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
Expand Down Expand Up @@ -88,3 +90,22 @@ pub(crate) fn is_same_as_host(path: &Utf8Path) -> Result<bool> {
);
Ok(devstat.f_fsid == hostdevstat.f_fsid)
}

/// Bind mount using a directory file descriptor as a source to the target path.
/// This is useful for dealing with e.g. subprocess that expect a path and not a
/// file descriptor (and can't work via /proc/self/fd).
pub(crate) fn bind_mount_fd(
fd: BorrowedFd,
path: impl AsRef<std::path::Path>,
) -> std::io::Result<()> {
use rustix::fs::OFlags;
let fd = rustix::fs::openat(
fd,
".",
OFlags::CLOEXEC | OFlags::DIRECTORY | OFlags::NOFOLLOW,
rustix::fs::Mode::empty(),
)?;
let fd = format!("/proc/self/fd/{}", fd.as_raw_fd());
rustix_err_context(rustix::mount::mount_bind(&fd, path.as_ref()), "bind")?;
Ok(())
}
8 changes: 7 additions & 1 deletion lib/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub(crate) trait AsyncCommandRunExt {

impl AsyncCommandRunExt for tokio::process::Command {
/// Asynchronously execute the child, and return an error if the child exited unsuccessfully.
///
///
async fn run(&mut self) -> Result<()> {
project_status(self.status().await?)
}
Expand All @@ -59,6 +59,12 @@ pub(crate) fn origin_has_rpmostree_stuff(kf: &glib::KeyFile) -> bool {
false
}

/// Helper function to add some basic context to a rustix Result, which is just an errno
/// value. This also converts to a std::io::Result.
pub(crate) fn rustix_err_context(r: rustix::io::Result<()>, s: &str) -> std::io::Result<()> {
r.map_err(|e| std::io::Error::new(e.kind(), format!("{s}: {e}")))
}

// Access the file descriptor for a sysroot
#[allow(unsafe_code)]
pub(crate) fn sysroot_fd(sysroot: &ostree::Sysroot) -> BorrowedFd {
Expand Down

0 comments on commit cd63cfa

Please sign in to comment.