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-to-disk: Check for mounts before install #805

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

djach7
Copy link
Contributor

@djach7 djach7 commented Sep 25, 2024

Works to improve bootc install to-disk flow by checking for mounts on the device to be installed to. If mounts are present then install will abort.

Resolves #720

Copy link
Collaborator

@cgwalters cgwalters left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this up!

.dockerignore Outdated Show resolved Hide resolved
@@ -37,6 +38,9 @@ pub(crate) struct Device {
// Filesystem-related properties
pub(crate) label: Option<String>,
pub(crate) fstype: Option<String>,

// VFS properties
pub(crate) mountpoints: Option<Vec<String>>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below

@@ -50,6 +54,30 @@ impl Device {
self.children.as_ref().map_or(false, |v| !v.is_empty())
}

// Find first mounted device along with mountpoints
pub(crate) fn find_first_mounted(&self) -> Option<(&Device, String)> {
let output = Command::new("findmnt")
Copy link
Collaborator

@cgwalters cgwalters Sep 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we already have code in mount.rs that's wrapping findmnt. An overall challenge here is because the install code is in a mount namespace, we won't see the device as mounted to start when we scan the block device.

That's why the mountpoints property that we can get from lsblk will be empty.

I think what we want here is a new api in mount.rs like this:

pub(crate) fn is_mounted_in_pid_mountns(device: &Device, pid: rustix::process::Pid) -> Result<bool>

Then we traverse the the device and run findmnt -N <pid> --source <device> or so.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the bootc install to-disk code is in a mount namespace, which results in the device mountpoints being hidden from findmnt?

For your suggestion, what process am I getting the pid from? Is that from the disk, since I'm feeding it into findmnt as the task id then?

Just trying to wrap my head around all this stuff, I appreciate the quick feedback a ton!

Copy link
Collaborator

@cgwalters cgwalters Sep 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For your suggestion, what process am I getting the pid from?

Pid 1

So the bootc install to-disk code is in a mount namespace, which results in the device mountpoints being hidden from findmnt?

Yep. Try playing with this from a root shell:

$ unshare -m
$ findmnt /tmp
$ mount --bind /var/tmp /tmp
$ findmnt /tmp
$ findmnt -N 1 /tmp

Note the second one (looking in the root mountns) didn't see the bind. Press ctrl-d to exit the unshare bash shell which will also tear down the mount.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have a better idea of it now, Joseph helped me walk through it yesterday too. My basic understanding is that bootc install to-disk can't see the same pids (and hence the mountpoints) that the podman container can see, so I need to bridge the gap and allow install to-disk to see the mountpoints podman can. Then I can run findmnt with that specified pid and check for the mountpoints off of it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll experiment with those commands you listed too, thank you

@djach7 djach7 force-pushed the installToDisk branch 3 times, most recently from 738eb54 to eece03c Compare October 3, 2024 18:20
@djach7
Copy link
Contributor Author

djach7 commented Oct 3, 2024

This should be working now, I made changes to parse the findmnt output for the device and bail if found. @cgwalters for the findmnt command I hardcoded -N 1, is there any issue with that? I was attempting to pass it a pid (you can see that attempt in the wip commit if you're curious) but the pid being passed was unable to see the device I was attempting to mount to.

.output()
.expect("Failed to execute findmnt");

let mounts = String::from_utf8(output.stdout).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While findmnt giving non-utf8 output is a "shouldn't happen" case, it's equally easy for us to use ? here to propagate it as an error instead of aborting, so let's do that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding this code to mount.rs instead? We could aim to share code with run_findmnt...at least in terms of parsing JSON instead of text.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pushed up a refactor into mount.rs, it now shares some code with the other findmnt functions in there. Is that more in line with what you were thinking?

lib/src/blockdev.rs Outdated Show resolved Hide resolved
lib/src/blockdev.rs Outdated Show resolved Hide resolved
@cgwalters
Copy link
Collaborator

Thanks for working on this BTW, I think it's getting close!

Signed-off-by: djach7 <djachimo@redhat.com>
@github-actions github-actions bot added the area/install Issues related to `bootc install` label Oct 17, 2024
}

// Recursively check a given filesystem to see if it contains an already mounted source
pub(crate) fn is_source_mounted(path: &String, mounted_fs: &Filesystem) -> Result<bool> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, will resolve when pushed

}

// Check if a specified device contains an already mounted filesystem
pub(crate) fn is_mounted_in_pid1_mountns(path: String) -> Result<bool> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this take &str

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, will resolve when pushed

Comment on lines +73 to +79
for fs in o.filesystems.iter() {
if is_source_mounted(&path, fs)? {
return Ok(true);
}
}

Ok(false)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all fine but with the below change could be condensed to something like
Ok(o.filesystems.iter().any(|fs| is_source_mounted(path)))

Or:

let mounted = o.filesystems.iter().any(|fs| is_source_mounted(path));
Ok(mounted)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, will resolve when pushed

findmnt_filesystem(&["--source"], &(format!("UUID={uuid}")))
}

// Check if a specified device contains an already mounted filesystem
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"already mounted filesystem in the root mount namespace" or so.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, will resolve when pushed


// Recursively check a given filesystem to see if it contains an already mounted source
pub(crate) fn is_source_mounted(path: &String, mounted_fs: &Filesystem) -> Result<bool> {
if mounted_fs.source.contains(path) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit uncertain about source.contains(path) vs mounted_fs.source.as_str() == path here...I guess you're probably doing that as a way to handle e.g. /dev/vda3 being mounted, but we're trying to match vs a whole device like /dev/vda?

Just thinking through this...what is probably cleaner actually is to do a set-intersection; on the calling side loop through all children of the device.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow what you're saying, my understanding is that we want to see if /dev/vda (the device) is mounted or if any partition on it is mounted. That's what I wrote it for, so it will kickback the mounting error if any part of the device is mounted.

Are you saying that this should only fail if the device alone is mounted? As in it shouldn't be checking the partitions of the device?

That's the reasoning behind the source.contains(path), I can't be sure what number partition will be mounted but I can work off it having the same naming stem.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what I mean is doing the check like this:

diff --git a/lib/src/install/baseline.rs b/lib/src/install/baseline.rs
index f7c1c35..b8eb8bd 100644
--- a/lib/src/install/baseline.rs
+++ b/lib/src/install/baseline.rs
@@ -165,7 +165,13 @@ pub(crate) fn install_create_rootfs(
     // Handle wiping any existing data
     if opts.wipe {
         let dev = &opts.device;
+        if crate::mount::is_mounted_in_pid1_mountns(device.path()) {
+            anyhow::bail!("Device {} is mounted", device.path());
+        }
         for child in device.children.iter().flatten() {
+            if crate::mount::is_mounted_in_pid1_mountns(child.path()) {
+                anyhow::bail!("Device {} is mounted", child.path());
+            }
             let child = child.path();
             println!("Wiping {child}");
             crate::blockdev::wipefs(Utf8Path::new(&child))?;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe cleaner actually, make is_mounted_in_pid1_mountns take a &Device and have it iterate over children.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/install Issues related to `bootc install` do-not-merge/work-in-progress
Projects
None yet
Development

Successfully merging this pull request may close these issues.

install to-disk: Check for mounts
2 participants