Skip to content

Commit 7497c43

Browse files
committed
reinstall: Add warnings about mounts
This uses findmnt to locate filesystem mounts that are on the same source as the root mount. If any are found, the user is warned these filesystems will persist unmounted in the bootc system. The user must hit <enter> to proceed. This does the same for logical volumes in the same group as root. It also adds a generic warning to help the user understand what will happen after rebooting into the bootc system. Signed-off-by: ckyrouac <ckyrouac@redhat.com>
1 parent 36fceed commit 7497c43

File tree

7 files changed

+169
-2
lines changed

7 files changed

+169
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mount/src/mount.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub struct Filesystem {
4545
pub children: Option<Vec<Filesystem>>,
4646
}
4747

48-
#[derive(Deserialize, Debug)]
48+
#[derive(Deserialize, Debug, Default)]
4949
pub struct Findmnt {
5050
pub filesystems: Vec<Filesystem>,
5151
}

system-reinstall-bootc/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ platforms = ["*-unknown-linux-gnu"]
1616

1717
[dependencies]
1818
anyhow = { workspace = true }
19+
bootc-mount = { path = "../mount" }
1920
bootc-utils = { path = "../utils" }
2021
clap = { workspace = true, features = ["derive"] }
2122
crossterm = "0.29.0"

system-reinstall-bootc/src/btrfs.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use anyhow::Result;
2+
use bootc_mount::Filesystem;
3+
4+
pub(crate) fn check_root_siblings() -> Result<Vec<String>> {
5+
let mounts = bootc_mount::run_findmnt(&[], None)?;
6+
let problem_filesystems: Vec<String> = mounts
7+
.filesystems
8+
.iter()
9+
.filter(|fs| fs.target == "/")
10+
.flat_map(|root| {
11+
let children: Vec<&Filesystem> = root
12+
.children
13+
.iter()
14+
.flatten()
15+
.filter(|child| child.source == root.source)
16+
.collect();
17+
children
18+
})
19+
.map(|zs| {
20+
format!(
21+
"Type: {}, Mount Point: {}, Source: {}",
22+
zs.fstype, zs.target, zs.source
23+
)
24+
})
25+
.collect();
26+
Ok(problem_filesystems)
27+
}

system-reinstall-bootc/src/lvm.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use std::process::{Command, Stdio};
2+
3+
use anyhow::Result;
4+
use bootc_mount::run_findmnt;
5+
use bootc_utils::CommandRunExt;
6+
use serde::Deserialize;
7+
8+
#[derive(Debug, Deserialize)]
9+
pub(crate) struct Lvs {
10+
report: Vec<LvsReport>,
11+
}
12+
13+
#[derive(Debug, Deserialize)]
14+
pub(crate) struct LvsReport {
15+
lv: Vec<LogicalVolume>,
16+
}
17+
18+
#[derive(Debug, Deserialize, Clone)]
19+
pub(crate) struct LogicalVolume {
20+
lv_name: String,
21+
lv_size: String,
22+
lv_path: String,
23+
vg_name: String,
24+
}
25+
26+
pub(crate) fn binary_available(binary: &str) -> bool {
27+
Command::new("which")
28+
.arg(binary)
29+
.stdout(Stdio::null())
30+
.stderr(Stdio::null())
31+
.status()
32+
.map(|status| status.success())
33+
.unwrap_or(false)
34+
}
35+
36+
pub(crate) fn parse_volumes(group: Option<String>) -> Result<Vec<LogicalVolume>> {
37+
if !binary_available("lvs") {
38+
tracing::debug!("lvs binary not found. Skipping logical volume check.");
39+
return Ok(Vec::<LogicalVolume>::new());
40+
}
41+
42+
let mut cmd = Command::new("lvs");
43+
cmd.args([
44+
"--reportformat=json",
45+
"-o",
46+
"lv_name,lv_size,lv_path,vg_name",
47+
]);
48+
49+
if let Some(group) = group {
50+
cmd.arg(group);
51+
}
52+
53+
let output: Lvs = cmd.run_and_parse_json()?;
54+
55+
Ok(output
56+
.report
57+
.iter()
58+
.flat_map(|r| r.lv.iter().cloned())
59+
.collect())
60+
}
61+
62+
pub(crate) fn check_root_siblings() -> Result<Vec<String>> {
63+
let all_volumes = parse_volumes(None)?;
64+
65+
// first look for a lv mounted to '/'
66+
// then gather all the sibling lvs in the vg along with their mount points
67+
let siblings: Vec<String> = all_volumes
68+
.iter()
69+
.filter(|lv| {
70+
let mount = run_findmnt(&["-S", &lv.lv_path], None).unwrap_or_default();
71+
if let Some(fs) = mount.filesystems.first() {
72+
&fs.target == "/"
73+
} else {
74+
false
75+
}
76+
})
77+
.flat_map(|root_lv| {
78+
tracing::warn!("inside flat_map");
79+
parse_volumes(Some(root_lv.vg_name.clone())).unwrap_or_default()
80+
})
81+
.filter_map(|lv| {
82+
tracing::warn!("inside map");
83+
let mount = run_findmnt(&["-S", &lv.lv_path], None).unwrap();
84+
let mount_path = if let Some(fs) = mount.filesystems.first() {
85+
&fs.target
86+
} else {
87+
""
88+
};
89+
90+
if mount_path == "/" {
91+
None
92+
} else {
93+
format!(
94+
"Type: LVM, Mount Point: {}, LV: {}, VG: {}, Size: {}",
95+
mount_path, lv.lv_name, lv.vg_name, lv.lv_size
96+
)
97+
.into()
98+
}
99+
})
100+
.collect();
101+
102+
Ok(siblings)
103+
}

system-reinstall-bootc/src/main.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use anyhow::{ensure, Context, Result};
44
use bootc_utils::CommandRunExt;
55
use rustix::process::getuid;
66

7+
mod btrfs;
78
mod config;
9+
mod lvm;
810
mod podman;
911
mod prompt;
1012
pub(crate) mod users;
@@ -40,6 +42,8 @@ fn run() -> Result<()> {
4042

4143
prompt::get_ssh_keys(ssh_key_file_path)?;
4244

45+
prompt::mount_warning()?;
46+
4347
let mut reinstall_podman_command =
4448
podman::reinstall_command(&config.bootc_image, ssh_key_file_path);
4549

@@ -48,6 +52,9 @@ fn run() -> Result<()> {
4852
println!();
4953
println!("{}", reinstall_podman_command.to_string_pretty());
5054

55+
println!();
56+
println!("After reboot, the current root will be available in the /sysroot directory. Existing mounts will not be automatically mounted by the bootc system unless they are defined in the bootc image. Some automatic cleanup of the previous root will be performed.");
57+
5158
prompt::temporary_developer_protection_prompt()?;
5259

5360
reinstall_podman_command

system-reinstall-bootc/src/prompt.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{prompt, users::get_all_users_keys};
1+
use crate::{btrfs, lvm, prompt, users::get_all_users_keys};
22
use anyhow::{ensure, Context, Result};
33

44
use crossterm::event::{self, Event};
@@ -92,6 +92,34 @@ pub(crate) fn ask_yes_no(prompt: &str, default: bool) -> Result<bool> {
9292
.context("prompting")
9393
}
9494

95+
pub(crate) fn press_enter() {
96+
println!();
97+
println!("Press <enter> to continue.");
98+
99+
loop {
100+
if let Event::Key(_) = event::read().unwrap() {
101+
break;
102+
}
103+
}
104+
}
105+
106+
pub(crate) fn mount_warning() -> Result<()> {
107+
let mut mounts = btrfs::check_root_siblings()?;
108+
mounts.extend(lvm::check_root_siblings()?);
109+
110+
if !mounts.is_empty() {
111+
println!();
112+
println!("NOTICE: the following mounts are left unchanged by this tool and will not be automatically mounted unless specified in the bootc image. Consult the bootc documentation to determine the appropriate action for your system.");
113+
println!();
114+
for m in mounts {
115+
println!("{m}");
116+
}
117+
press_enter();
118+
}
119+
120+
Ok(())
121+
}
122+
95123
/// Gather authorized keys for all user's of the host system
96124
/// prompt the user to select which users's keys will be imported
97125
/// into the target system's root user's authorized_keys file

0 commit comments

Comments
 (0)