1use std::cell::OnceCell;
20use std::ops::Deref;
21use std::sync::Arc;
22
23use anyhow::{Context, Result};
24use bootc_mount::tempmount::TempMount;
25use camino::Utf8PathBuf;
26use cap_std_ext::cap_std;
27use cap_std_ext::cap_std::fs::{
28 Dir, DirBuilder, DirBuilderExt as _, Permissions, PermissionsExt as _,
29};
30use cap_std_ext::dirext::CapStdExtDirExt;
31use fn_error_context::context;
32
33use ostree_ext::container_utils::ostree_booted;
34use ostree_ext::prelude::FileExt;
35use ostree_ext::sysroot::SysrootLock;
36use ostree_ext::{gio, ostree};
37use rustix::fs::Mode;
38
39use cfsctl::composefs;
40use composefs::fsverity::Sha512HashValue;
41
42use crate::bootc_composefs::boot::{EFI_LINUX, mount_esp};
43use crate::bootc_composefs::status::{ComposefsCmdline, composefs_booted, get_bootloader};
44use crate::lsm;
45use crate::podstorage::CStorage;
46use crate::spec::{Bootloader, ImageStatus};
47use crate::utils::{deployment_fd, open_dir_remount_rw};
48
49pub type ComposefsRepository = composefs::repository::Repository<Sha512HashValue>;
51pub type ComposefsFilesystem = composefs::tree::FileSystem<Sha512HashValue>;
53
54pub const SYSROOT: &str = "sysroot";
56
57pub const COMPOSEFS: &str = "composefs";
59
60pub(crate) const COMPOSEFS_MODE: Mode = Mode::from_raw_mode(0o700);
63
64pub(crate) fn ensure_composefs_dir(physical_root: &Dir) -> Result<()> {
67 let mut db = DirBuilder::new();
68 db.mode(COMPOSEFS_MODE.as_raw_mode());
69 physical_root
70 .ensure_dir_with(COMPOSEFS, &db)
71 .context("Creating composefs directory")?;
72 physical_root
75 .set_permissions(
76 COMPOSEFS,
77 Permissions::from_mode(COMPOSEFS_MODE.as_raw_mode()),
78 )
79 .context("Setting composefs directory permissions")?;
80 Ok(())
81}
82
83pub(crate) const BOOTC_ROOT: &str = "ostree/bootc";
86
87pub(crate) struct BootedStorage {
92 pub(crate) storage: Storage,
93}
94
95impl Deref for BootedStorage {
96 type Target = Storage;
97
98 fn deref(&self) -> &Self::Target {
99 &self.storage
100 }
101}
102
103pub struct BootedOstree<'a> {
105 pub(crate) sysroot: &'a SysrootLock,
106 pub(crate) deployment: ostree::Deployment,
107}
108
109impl<'a> BootedOstree<'a> {
110 pub(crate) fn repo(&self) -> ostree::Repo {
112 self.sysroot.repo()
113 }
114
115 pub(crate) fn stateroot(&self) -> ostree::glib::GString {
117 self.deployment.osname()
118 }
119}
120
121#[allow(dead_code)]
123pub struct BootedComposefs {
124 pub repo: Arc<ComposefsRepository>,
125 pub cmdline: &'static ComposefsCmdline,
126}
127
128pub(crate) enum Environment {
132 OstreeBooted,
134 ComposefsBooted(ComposefsCmdline),
136 Container,
138 Other,
140}
141
142impl Environment {
143 pub(crate) fn detect() -> Result<Self> {
145 if ostree_ext::container_utils::running_in_container() {
146 return Ok(Self::Container);
147 }
148
149 if let Some(cmdline) = composefs_booted()? {
150 return Ok(Self::ComposefsBooted(cmdline.clone()));
151 }
152
153 if ostree_booted()? {
154 return Ok(Self::OstreeBooted);
155 }
156
157 Ok(Self::Other)
158 }
159
160 pub(crate) fn needs_mount_namespace(&self) -> bool {
163 matches!(self, Self::OstreeBooted | Self::ComposefsBooted(_))
164 }
165}
166
167pub(crate) enum BootedStorageKind<'a> {
170 Ostree(BootedOstree<'a>),
171 Composefs(BootedComposefs),
172}
173
174fn get_physical_root_and_run() -> Result<(Dir, Dir)> {
176 let physical_root = {
177 let d = Dir::open_ambient_dir("/sysroot", cap_std::ambient_authority())
178 .context("Opening /sysroot")?;
179 open_dir_remount_rw(&d, ".".into())?
180 };
181 let run =
182 Dir::open_ambient_dir("/run", cap_std::ambient_authority()).context("Opening /run")?;
183 Ok((physical_root, run))
184}
185
186impl BootedStorage {
187 pub(crate) async fn new(env: Environment) -> Result<Option<Self>> {
192 let r = match &env {
193 Environment::ComposefsBooted(cmdline) => {
194 let (physical_root, run) = get_physical_root_and_run()?;
195 let mut composefs = ComposefsRepository::open_path(&physical_root, COMPOSEFS)?;
196 if cmdline.allow_missing_fsverity {
197 composefs.set_insecure(true);
198 }
199 let composefs = Arc::new(composefs);
200
201 let root_dev =
203 bootc_blockdev::list_dev_by_dir(&physical_root)?.require_single_root()?;
204 let esp_dev = root_dev.find_partition_of_esp()?;
205 let esp_mount = mount_esp(&esp_dev.path())?;
206
207 let boot_dir = match get_bootloader()? {
208 Bootloader::Grub => physical_root.open_dir("boot").context("Opening boot")?,
209 Bootloader::Systemd => esp_mount.fd.try_clone().context("Cloning fd")?,
211 Bootloader::None => unreachable!("Checked at install time"),
212 };
213
214 let storage = Storage {
215 physical_root,
216 physical_root_path: Utf8PathBuf::from("/sysroot"),
217 run,
218 boot_dir: Some(boot_dir),
219 esp: Some(esp_mount),
220 ostree: Default::default(),
221 composefs: OnceCell::from(composefs),
222 imgstore: Default::default(),
223 };
224
225 Some(Self { storage })
226 }
227 Environment::OstreeBooted => {
228 let (physical_root, run) = get_physical_root_and_run()?;
234
235 let sysroot = ostree::Sysroot::new_default();
236 sysroot.set_mount_namespace_in_use();
237 let sysroot = ostree_ext::sysroot::SysrootLock::new_from_sysroot(&sysroot).await?;
238 sysroot.load(gio::Cancellable::NONE)?;
239
240 let storage = Storage {
241 physical_root,
242 physical_root_path: Utf8PathBuf::from("/sysroot"),
243 run,
244 boot_dir: None,
245 esp: None,
246 ostree: OnceCell::from(sysroot),
247 composefs: Default::default(),
248 imgstore: Default::default(),
249 };
250
251 Some(Self { storage })
252 }
253 Environment::Container | Environment::Other => None,
255 };
256 Ok(r)
257 }
258
259 pub(crate) fn kind(&self) -> Result<BootedStorageKind<'_>> {
264 if let Some(cmdline) = composefs_booted()? {
265 let repo = self.composefs.get().unwrap();
267 Ok(BootedStorageKind::Composefs(BootedComposefs {
268 repo: Arc::clone(repo),
269 cmdline,
270 }))
271 } else {
272 let sysroot = self.ostree.get().unwrap();
274 let deployment = sysroot.require_booted_deployment()?;
275 Ok(BootedStorageKind::Ostree(BootedOstree {
276 sysroot,
277 deployment,
278 }))
279 }
280 }
281}
282
283pub(crate) struct Storage {
286 pub physical_root: Dir,
288
289 pub physical_root_path: Utf8PathBuf,
292
293 pub boot_dir: Option<Dir>,
297
298 pub esp: Option<TempMount>,
300
301 run: Dir,
303
304 ostree: OnceCell<SysrootLock>,
306 composefs: OnceCell<Arc<ComposefsRepository>>,
308 imgstore: OnceCell<CStorage>,
310}
311
312#[derive(Default)]
317pub(crate) struct CachedImageStatus {
318 pub image: Option<ImageStatus>,
319 pub cached_update: Option<ImageStatus>,
320}
321
322impl Storage {
323 pub fn new_ostree(sysroot: SysrootLock, run: &Dir) -> Result<Self> {
328 let run = run.try_clone()?;
329
330 let ostree_sysroot_dir = crate::utils::sysroot_dir(&sysroot)?;
340 let (physical_root, physical_root_path) = if sysroot.is_booted() {
341 (
342 ostree_sysroot_dir.open_dir(SYSROOT)?,
343 Utf8PathBuf::from("/sysroot"),
344 )
345 } else {
346 let path = sysroot.path();
348 let path_str = path.parse_name().to_string();
349 let path = Utf8PathBuf::from(path_str);
350 (ostree_sysroot_dir, path)
351 };
352
353 let ostree_cell = OnceCell::new();
354 let _ = ostree_cell.set(sysroot);
355
356 Ok(Self {
357 physical_root,
358 physical_root_path,
359 run,
360 boot_dir: None,
361 esp: None,
362 ostree: ostree_cell,
363 composefs: Default::default(),
364 imgstore: Default::default(),
365 })
366 }
367
368 pub(crate) fn require_boot_dir(&self) -> Result<&Dir> {
370 self.boot_dir
371 .as_ref()
372 .ok_or_else(|| anyhow::anyhow!("Boot dir not found"))
373 }
374
375 pub(crate) fn require_esp(&self) -> Result<&TempMount> {
377 self.esp
378 .as_ref()
379 .ok_or_else(|| anyhow::anyhow!("ESP not found"))
380 }
381
382 pub(crate) fn bls_boot_binaries_dir(&self) -> Result<Dir> {
385 let boot_dir = self.require_boot_dir()?;
386
387 let boot_dir = match get_bootloader()? {
390 Bootloader::Grub => boot_dir.try_clone()?,
391 Bootloader::Systemd => {
392 let boot_dir = boot_dir
393 .open_dir(EFI_LINUX)
394 .with_context(|| format!("Opening {EFI_LINUX}"))?;
395
396 boot_dir
397 }
398 Bootloader::None => anyhow::bail!("Unknown bootloader"),
399 };
400
401 Ok(boot_dir)
402 }
403
404 pub(crate) fn get_ostree(&self) -> Result<&SysrootLock> {
406 self.ostree
407 .get()
408 .ok_or_else(|| anyhow::anyhow!("OSTree storage not initialized"))
409 }
410
411 pub(crate) fn get_ostree_cloned(&self) -> Result<ostree::Sysroot> {
416 let r = self.get_ostree()?;
417 Ok((*r).clone())
418 }
419
420 pub(crate) fn get_ensure_imgstore(&self) -> Result<&CStorage> {
422 if let Some(imgstore) = self.imgstore.get() {
423 return Ok(imgstore);
424 }
425 let ostree = self.get_ostree()?;
426 let sysroot_dir = crate::utils::sysroot_dir(ostree)?;
427
428 let sepolicy = if ostree.booted_deployment().is_none() {
429 tracing::trace!("falling back to container root's selinux policy");
432 let container_root = Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
433 lsm::new_sepolicy_at(&container_root)?
434 } else {
435 tracing::trace!("loading sepolicy from booted ostree deployment");
438 let dep = ostree.booted_deployment().unwrap();
439 let dep_fs = deployment_fd(ostree, &dep)?;
440 lsm::new_sepolicy_at(&dep_fs)?
441 };
442
443 tracing::trace!("sepolicy in get_ensure_imgstore: {sepolicy:?}");
444
445 let imgstore = CStorage::create(&sysroot_dir, &self.run, sepolicy.as_ref())?;
446 Ok(self.imgstore.get_or_init(|| imgstore))
447 }
448
449 pub(crate) fn ensure_imgstore_labeled(&self) -> Result<()> {
452 if let Some(imgstore) = self.imgstore.get() {
453 imgstore.ensure_labeled()?;
454 }
455 Ok(())
456 }
457
458 pub(crate) fn get_ensure_composefs(&self) -> Result<Arc<ComposefsRepository>> {
463 if let Some(composefs) = self.composefs.get() {
464 return Ok(Arc::clone(composefs));
465 }
466
467 ensure_composefs_dir(&self.physical_root)?;
468
469 let ostree = self.get_ostree()?;
472 let ostree_repo = &ostree.repo();
473 let ostree_verity = ostree_ext::fsverity::is_verity_enabled(ostree_repo)?;
474 let mut composefs =
475 ComposefsRepository::open_path(self.physical_root.open_dir(COMPOSEFS)?, ".")?;
476 if !ostree_verity.enabled {
477 tracing::debug!("Setting insecure mode for composefs repo");
478 composefs.set_insecure(true);
479 }
480 let composefs = Arc::new(composefs);
481 let r = Arc::clone(self.composefs.get_or_init(|| composefs));
482 Ok(r)
483 }
484
485 #[context("Updating storage root mtime")]
487 pub(crate) fn update_mtime(&self) -> Result<()> {
488 let ostree = self.get_ostree()?;
489 let sysroot_dir = crate::utils::sysroot_dir(ostree).context("Reopen sysroot directory")?;
490
491 sysroot_dir
492 .update_timestamps(std::path::Path::new(BOOTC_ROOT))
493 .context("update_timestamps")
494 }
495}
496
497#[cfg(test)]
498mod tests {
499 use super::*;
500
501 const PERMS: Mode = Mode::from_raw_mode(0o777);
505
506 #[test]
507 fn test_ensure_composefs_dir_mode() -> Result<()> {
508 use cap_std_ext::cap_primitives::fs::PermissionsExt as _;
509
510 let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
511
512 let assert_mode = || -> Result<()> {
513 let perms = td.metadata(COMPOSEFS)?.permissions();
514 let mode = Mode::from_raw_mode(perms.mode());
515 assert_eq!(mode & PERMS, COMPOSEFS_MODE);
516 Ok(())
517 };
518
519 ensure_composefs_dir(&td)?;
520 assert_mode()?;
521
522 ensure_composefs_dir(&td)?;
524 assert_mode()?;
525
526 Ok(())
527 }
528
529 #[test]
530 fn test_ensure_composefs_dir_fixes_existing() -> Result<()> {
531 use cap_std_ext::cap_primitives::fs::PermissionsExt as _;
532
533 let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
534
535 let mut db = DirBuilder::new();
537 db.mode(0o755);
538 td.create_dir_with(COMPOSEFS, &db)?;
539
540 let perms = td.metadata(COMPOSEFS)?.permissions();
542 let mode = Mode::from_raw_mode(perms.mode());
543 assert_eq!(mode & PERMS, Mode::from_raw_mode(0o755));
544
545 ensure_composefs_dir(&td)?;
547
548 let perms = td.metadata(COMPOSEFS)?.permissions();
549 let mode = Mode::from_raw_mode(perms.mode());
550 assert_eq!(mode & PERMS, COMPOSEFS_MODE);
551
552 Ok(())
553 }
554}