Skip to content

Commit b10a1fe

Browse files
committed
cli: Make sysroot lock automatically check root + setup mountns
Previous to this change, we were always pairing together two functions: - `prepare_for_write()` - `get_locked_sysroot()` Make the latter automatically call the former (and make it private to the cli code). This will avoid bugs like dcfc752 where we forgot to call the first one and didn't end up with an unshared mountns. While we're here, drop an unnecessary `async` from these functions, and just in case make `prepare_for_write()` idempotent. Signed-off-by: Colin Walters <[email protected]>
1 parent 377bfd6 commit b10a1fe

File tree

3 files changed

+19
-9
lines changed

3 files changed

+19
-9
lines changed

lib/src/cli.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ pub(crate) enum Opt {
324324
/// TODO use https://github.com/ostreedev/ostree/pull/2779 once
325325
/// we can depend on a new enough ostree
326326
#[context("Ensuring mountns")]
327-
pub(crate) async fn ensure_self_unshared_mount_namespace() -> Result<()> {
327+
pub(crate) fn ensure_self_unshared_mount_namespace() -> Result<()> {
328328
let uid = rustix::process::getuid();
329329
if !uid.is_root() {
330330
tracing::debug!("Not root, assuming no need to unshare");
@@ -354,6 +354,7 @@ pub(crate) async fn ensure_self_unshared_mount_namespace() -> Result<()> {
354354
/// TODO drain this and the above into SysrootLock
355355
#[context("Acquiring sysroot")]
356356
pub(crate) async fn get_locked_sysroot() -> Result<ostree_ext::sysroot::SysrootLock> {
357+
prepare_for_write()?;
357358
let sysroot = ostree::Sysroot::new_default();
358359
sysroot.set_mount_namespace_in_use();
359360
let sysroot = ostree_ext::sysroot::SysrootLock::new_from_sysroot(&sysroot).await?;
@@ -375,8 +376,21 @@ pub(crate) fn require_root() -> Result<()> {
375376
}
376377

377378
/// A few process changes that need to be made for writing.
379+
/// IMPORTANT: This may end up re-executing the current process,
380+
/// so anything that happens before this should be idempotent.
378381
#[context("Preparing for write")]
379-
pub(crate) async fn prepare_for_write() -> Result<()> {
382+
fn prepare_for_write() -> Result<()> {
383+
use std::sync::atomic::{AtomicBool, Ordering};
384+
385+
// This is intending to give "at most once" semantics to this
386+
// function. We should never invoke this from multiple threads
387+
// at the same time, but verifying "on main thread" is messy.
388+
// Yes, using SeqCst is likely overkill, but there is nothing perf
389+
// sensitive about this.
390+
static ENTERED: AtomicBool = AtomicBool::new(false);
391+
if ENTERED.load(Ordering::SeqCst) {
392+
return Ok(());
393+
}
380394
if ostree_ext::container_utils::is_ostree_container()? {
381395
anyhow::bail!(
382396
"Detected container (ostree base); this command requires a booted host system."
@@ -389,17 +403,17 @@ pub(crate) async fn prepare_for_write() -> Result<()> {
389403
anyhow::bail!("This command requires an ostree-booted host system");
390404
}
391405
crate::cli::require_root()?;
392-
ensure_self_unshared_mount_namespace().await?;
406+
ensure_self_unshared_mount_namespace()?;
393407
if crate::lsm::selinux_enabled()? && !crate::lsm::selinux_ensure_install()? {
394408
tracing::warn!("Do not have install_t capabilities");
395409
}
410+
ENTERED.store(true, Ordering::SeqCst);
396411
Ok(())
397412
}
398413

399414
/// Implementation of the `bootc upgrade` CLI command.
400415
#[context("Upgrading")]
401416
async fn upgrade(opts: UpgradeOpts) -> Result<()> {
402-
prepare_for_write().await?;
403417
let sysroot = &get_locked_sysroot().await?;
404418
let repo = &sysroot.repo();
405419
let (booted_deployment, _deployments, host) =
@@ -539,7 +553,6 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
539553
return Ok(());
540554
}
541555

542-
prepare_for_write().await?;
543556
let cancellable = gio::Cancellable::NONE;
544557

545558
let sysroot = &get_locked_sysroot().await?;
@@ -585,15 +598,13 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
585598
/// Implementation of the `bootc rollback` CLI command.
586599
#[context("Rollback")]
587600
async fn rollback(_opts: RollbackOpts) -> Result<()> {
588-
prepare_for_write().await?;
589601
let sysroot = &get_locked_sysroot().await?;
590602
crate::deploy::rollback(sysroot).await
591603
}
592604

593605
/// Implementation of the `bootc edit` CLI command.
594606
#[context("Editing spec")]
595607
async fn edit(opts: EditOpts) -> Result<()> {
596-
prepare_for_write().await?;
597608
let sysroot = &get_locked_sysroot().await?;
598609
let (booted_deployment, _deployments, host) =
599610
crate::status::get_status_require_booted(sysroot)?;

lib/src/install.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ async fn prepare_install(
11381138
// Even though we require running in a container, the mounts we create should be specific
11391139
// to this process, so let's enter a private mountns to avoid leaking them.
11401140
if !external_source && std::env::var_os("BOOTC_SKIP_UNSHARE").is_none() {
1141-
super::cli::ensure_self_unshared_mount_namespace().await?;
1141+
super::cli::ensure_self_unshared_mount_namespace()?;
11421142
}
11431143

11441144
setup_sys_mount("efivarfs", EFIVARFS)?;

lib/src/status.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,6 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
305305
let host = if !Utf8Path::new("/run/ostree-booted").try_exists()? {
306306
Default::default()
307307
} else {
308-
crate::cli::prepare_for_write().await?;
309308
let sysroot = super::cli::get_locked_sysroot().await?;
310309
let booted_deployment = sysroot.booted_deployment();
311310
let (_deployments, host) = get_status(&sysroot, booted_deployment.as_ref())?;

0 commit comments

Comments
 (0)