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 = bootc_blockdev::list_dev_by_dir(&physical_root)?;
203 let esp_dev = root_dev.find_first_colocated_esp()?;
204 let esp_mount = mount_esp(&esp_dev.path())?;
205
206 let boot_dir = match get_bootloader()? {
207 Bootloader::Grub => physical_root.open_dir("boot").context("Opening boot")?,
208 Bootloader::Systemd => esp_mount.fd.try_clone().context("Cloning fd")?,
210 Bootloader::None => unreachable!("Checked at install time"),
211 };
212
213 let storage = Storage {
214 physical_root,
215 physical_root_path: Utf8PathBuf::from("/sysroot"),
216 run,
217 boot_dir: Some(boot_dir),
218 esp: Some(esp_mount),
219 ostree: Default::default(),
220 composefs: OnceCell::from(composefs),
221 imgstore: Default::default(),
222 };
223
224 Some(Self { storage })
225 }
226 Environment::OstreeBooted => {
227 let (physical_root, run) = get_physical_root_and_run()?;
233
234 let sysroot = ostree::Sysroot::new_default();
235 sysroot.set_mount_namespace_in_use();
236 let sysroot = ostree_ext::sysroot::SysrootLock::new_from_sysroot(&sysroot).await?;
237 sysroot.load(gio::Cancellable::NONE)?;
238
239 let storage = Storage {
240 physical_root,
241 physical_root_path: Utf8PathBuf::from("/sysroot"),
242 run,
243 boot_dir: None,
244 esp: None,
245 ostree: OnceCell::from(sysroot),
246 composefs: Default::default(),
247 imgstore: Default::default(),
248 };
249
250 Some(Self { storage })
251 }
252 Environment::Container | Environment::Other => None,
254 };
255 Ok(r)
256 }
257
258 pub(crate) fn kind(&self) -> Result<BootedStorageKind<'_>> {
263 if let Some(cmdline) = composefs_booted()? {
264 let repo = self.composefs.get().unwrap();
266 Ok(BootedStorageKind::Composefs(BootedComposefs {
267 repo: Arc::clone(repo),
268 cmdline,
269 }))
270 } else {
271 let sysroot = self.ostree.get().unwrap();
273 let deployment = sysroot.require_booted_deployment()?;
274 Ok(BootedStorageKind::Ostree(BootedOstree {
275 sysroot,
276 deployment,
277 }))
278 }
279 }
280}
281
282pub(crate) struct Storage {
285 pub physical_root: Dir,
287
288 pub physical_root_path: Utf8PathBuf,
291
292 pub boot_dir: Option<Dir>,
296
297 pub esp: Option<TempMount>,
299
300 run: Dir,
302
303 ostree: OnceCell<SysrootLock>,
305 composefs: OnceCell<Arc<ComposefsRepository>>,
307 imgstore: OnceCell<CStorage>,
309}
310
311#[derive(Default)]
316pub(crate) struct CachedImageStatus {
317 pub image: Option<ImageStatus>,
318 pub cached_update: Option<ImageStatus>,
319}
320
321impl Storage {
322 pub fn new_ostree(sysroot: SysrootLock, run: &Dir) -> Result<Self> {
327 let run = run.try_clone()?;
328
329 let ostree_sysroot_dir = crate::utils::sysroot_dir(&sysroot)?;
339 let (physical_root, physical_root_path) = if sysroot.is_booted() {
340 (
341 ostree_sysroot_dir.open_dir(SYSROOT)?,
342 Utf8PathBuf::from("/sysroot"),
343 )
344 } else {
345 let path = sysroot.path();
347 let path_str = path.parse_name().to_string();
348 let path = Utf8PathBuf::from(path_str);
349 (ostree_sysroot_dir, path)
350 };
351
352 let ostree_cell = OnceCell::new();
353 let _ = ostree_cell.set(sysroot);
354
355 Ok(Self {
356 physical_root,
357 physical_root_path,
358 run,
359 boot_dir: None,
360 esp: None,
361 ostree: ostree_cell,
362 composefs: Default::default(),
363 imgstore: Default::default(),
364 })
365 }
366
367 pub(crate) fn require_boot_dir(&self) -> Result<&Dir> {
369 self.boot_dir
370 .as_ref()
371 .ok_or_else(|| anyhow::anyhow!("Boot dir not found"))
372 }
373
374 pub(crate) fn require_esp(&self) -> Result<&TempMount> {
376 self.esp
377 .as_ref()
378 .ok_or_else(|| anyhow::anyhow!("ESP not found"))
379 }
380
381 pub(crate) fn bls_boot_binaries_dir(&self) -> Result<Dir> {
384 let boot_dir = self.require_boot_dir()?;
385
386 let boot_dir = match get_bootloader()? {
389 Bootloader::Grub => boot_dir.try_clone()?,
390 Bootloader::Systemd => {
391 let boot_dir = boot_dir
392 .open_dir(EFI_LINUX)
393 .with_context(|| format!("Opening {EFI_LINUX}"))?;
394
395 boot_dir
396 }
397 Bootloader::None => anyhow::bail!("Unknown bootloader"),
398 };
399
400 Ok(boot_dir)
401 }
402
403 pub(crate) fn get_ostree(&self) -> Result<&SysrootLock> {
405 self.ostree
406 .get()
407 .ok_or_else(|| anyhow::anyhow!("OSTree storage not initialized"))
408 }
409
410 pub(crate) fn get_ostree_cloned(&self) -> Result<ostree::Sysroot> {
415 let r = self.get_ostree()?;
416 Ok((*r).clone())
417 }
418
419 pub(crate) fn get_ensure_imgstore(&self) -> Result<&CStorage> {
421 if let Some(imgstore) = self.imgstore.get() {
422 return Ok(imgstore);
423 }
424 let ostree = self.get_ostree()?;
425 let sysroot_dir = crate::utils::sysroot_dir(ostree)?;
426
427 let sepolicy = if ostree.booted_deployment().is_none() {
428 tracing::trace!("falling back to container root's selinux policy");
431 let container_root = Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
432 lsm::new_sepolicy_at(&container_root)?
433 } else {
434 tracing::trace!("loading sepolicy from booted ostree deployment");
437 let dep = ostree.booted_deployment().unwrap();
438 let dep_fs = deployment_fd(ostree, &dep)?;
439 lsm::new_sepolicy_at(&dep_fs)?
440 };
441
442 tracing::trace!("sepolicy in get_ensure_imgstore: {sepolicy:?}");
443
444 let imgstore = CStorage::create(&sysroot_dir, &self.run, sepolicy.as_ref())?;
445 Ok(self.imgstore.get_or_init(|| imgstore))
446 }
447
448 pub(crate) fn ensure_imgstore_labeled(&self) -> Result<()> {
451 if let Some(imgstore) = self.imgstore.get() {
452 imgstore.ensure_labeled()?;
453 }
454 Ok(())
455 }
456
457 pub(crate) fn get_ensure_composefs(&self) -> Result<Arc<ComposefsRepository>> {
462 if let Some(composefs) = self.composefs.get() {
463 return Ok(Arc::clone(composefs));
464 }
465
466 ensure_composefs_dir(&self.physical_root)?;
467
468 let ostree = self.get_ostree()?;
471 let ostree_repo = &ostree.repo();
472 let ostree_verity = ostree_ext::fsverity::is_verity_enabled(ostree_repo)?;
473 let mut composefs =
474 ComposefsRepository::open_path(self.physical_root.open_dir(COMPOSEFS)?, ".")?;
475 if !ostree_verity.enabled {
476 tracing::debug!("Setting insecure mode for composefs repo");
477 composefs.set_insecure(true);
478 }
479 let composefs = Arc::new(composefs);
480 let r = Arc::clone(self.composefs.get_or_init(|| composefs));
481 Ok(r)
482 }
483
484 #[context("Updating storage root mtime")]
486 pub(crate) fn update_mtime(&self) -> Result<()> {
487 let ostree = self.get_ostree()?;
488 let sysroot_dir = crate::utils::sysroot_dir(ostree).context("Reopen sysroot directory")?;
489
490 sysroot_dir
491 .update_timestamps(std::path::Path::new(BOOTC_ROOT))
492 .context("update_timestamps")
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499
500 const PERMS: Mode = Mode::from_raw_mode(0o777);
504
505 #[test]
506 fn test_ensure_composefs_dir_mode() -> Result<()> {
507 use cap_std_ext::cap_primitives::fs::PermissionsExt as _;
508
509 let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
510
511 let assert_mode = || -> Result<()> {
512 let perms = td.metadata(COMPOSEFS)?.permissions();
513 let mode = Mode::from_raw_mode(perms.mode());
514 assert_eq!(mode & PERMS, COMPOSEFS_MODE);
515 Ok(())
516 };
517
518 ensure_composefs_dir(&td)?;
519 assert_mode()?;
520
521 ensure_composefs_dir(&td)?;
523 assert_mode()?;
524
525 Ok(())
526 }
527
528 #[test]
529 fn test_ensure_composefs_dir_fixes_existing() -> Result<()> {
530 use cap_std_ext::cap_primitives::fs::PermissionsExt as _;
531
532 let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
533
534 let mut db = DirBuilder::new();
536 db.mode(0o755);
537 td.create_dir_with(COMPOSEFS, &db)?;
538
539 let perms = td.metadata(COMPOSEFS)?.permissions();
541 let mode = Mode::from_raw_mode(perms.mode());
542 assert_eq!(mode & PERMS, Mode::from_raw_mode(0o755));
543
544 ensure_composefs_dir(&td)?;
546
547 let perms = td.metadata(COMPOSEFS)?.permissions();
548 let mode = Mode::from_raw_mode(perms.mode());
549 assert_eq!(mode & PERMS, COMPOSEFS_MODE);
550
551 Ok(())
552 }
553}