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 crate::bootc_composefs::boot::{get_esp_partition, get_sysroot_parent_dev, mount_esp};
40use crate::bootc_composefs::status::{ComposefsCmdline, composefs_booted, get_bootloader};
41use crate::lsm;
42use crate::podstorage::CStorage;
43use crate::spec::{Bootloader, ImageStatus};
44use crate::utils::{deployment_fd, open_dir_remount_rw};
45
46pub type ComposefsRepository =
48 composefs::repository::Repository<composefs::fsverity::Sha512HashValue>;
49pub type ComposefsFilesystem = composefs::tree::FileSystem<composefs::fsverity::Sha512HashValue>;
50
51pub const SYSROOT: &str = "sysroot";
53
54pub const COMPOSEFS: &str = "composefs";
56
57pub(crate) const COMPOSEFS_MODE: Mode = Mode::from_raw_mode(0o700);
60
61pub(crate) fn ensure_composefs_dir(physical_root: &Dir) -> Result<()> {
64 let mut db = DirBuilder::new();
65 db.mode(COMPOSEFS_MODE.as_raw_mode());
66 physical_root
67 .ensure_dir_with(COMPOSEFS, &db)
68 .context("Creating composefs directory")?;
69 physical_root
72 .set_permissions(
73 COMPOSEFS,
74 Permissions::from_mode(COMPOSEFS_MODE.as_raw_mode()),
75 )
76 .context("Setting composefs directory permissions")?;
77 Ok(())
78}
79
80pub(crate) const BOOTC_ROOT: &str = "ostree/bootc";
83
84pub(crate) struct BootedStorage {
89 pub(crate) storage: Storage,
90}
91
92impl Deref for BootedStorage {
93 type Target = Storage;
94
95 fn deref(&self) -> &Self::Target {
96 &self.storage
97 }
98}
99
100pub struct BootedOstree<'a> {
102 pub(crate) sysroot: &'a SysrootLock,
103 pub(crate) deployment: ostree::Deployment,
104}
105
106impl<'a> BootedOstree<'a> {
107 pub(crate) fn repo(&self) -> ostree::Repo {
109 self.sysroot.repo()
110 }
111
112 pub(crate) fn stateroot(&self) -> ostree::glib::GString {
114 self.deployment.osname()
115 }
116}
117
118#[allow(dead_code)]
120pub struct BootedComposefs {
121 pub repo: Arc<ComposefsRepository>,
122 pub cmdline: &'static ComposefsCmdline,
123}
124
125pub(crate) enum Environment {
129 OstreeBooted,
131 ComposefsBooted(ComposefsCmdline),
133 Container,
135 Other,
137}
138
139impl Environment {
140 pub(crate) fn detect() -> Result<Self> {
142 if ostree_ext::container_utils::running_in_container() {
143 return Ok(Self::Container);
144 }
145
146 if let Some(cmdline) = composefs_booted()? {
147 return Ok(Self::ComposefsBooted(cmdline.clone()));
148 }
149
150 if ostree_booted()? {
151 return Ok(Self::OstreeBooted);
152 }
153
154 Ok(Self::Other)
155 }
156
157 pub(crate) fn needs_mount_namespace(&self) -> bool {
160 matches!(self, Self::OstreeBooted | Self::ComposefsBooted(_))
161 }
162}
163
164pub(crate) enum BootedStorageKind<'a> {
167 Ostree(BootedOstree<'a>),
168 Composefs(BootedComposefs),
169}
170
171fn get_physical_root_and_run() -> Result<(Dir, Dir)> {
173 let physical_root = {
174 let d = Dir::open_ambient_dir("/sysroot", cap_std::ambient_authority())
175 .context("Opening /sysroot")?;
176 open_dir_remount_rw(&d, ".".into())?
177 };
178 let run =
179 Dir::open_ambient_dir("/run", cap_std::ambient_authority()).context("Opening /run")?;
180 Ok((physical_root, run))
181}
182
183impl BootedStorage {
184 pub(crate) async fn new(env: Environment) -> Result<Option<Self>> {
189 let r = match &env {
190 Environment::ComposefsBooted(cmdline) => {
191 let (physical_root, run) = get_physical_root_and_run()?;
192 let mut composefs = ComposefsRepository::open_path(&physical_root, COMPOSEFS)?;
193 if cmdline.insecure {
194 composefs.set_insecure(true);
195 }
196 let composefs = Arc::new(composefs);
197
198 let parent = get_sysroot_parent_dev(&physical_root)?;
201 let (esp_part, ..) = get_esp_partition(&parent)?;
202 let esp_mount = mount_esp(&esp_part)?;
203
204 let boot_dir = match get_bootloader()? {
205 Bootloader::Grub => physical_root.open_dir("boot").context("Opening boot")?,
206 Bootloader::Systemd => esp_mount.fd.try_clone().context("Cloning fd")?,
208 };
209
210 let storage = Storage {
211 physical_root,
212 physical_root_path: Utf8PathBuf::from("/sysroot"),
213 run,
214 boot_dir: Some(boot_dir),
215 esp: Some(esp_mount),
216 ostree: Default::default(),
217 composefs: OnceCell::from(composefs),
218 imgstore: Default::default(),
219 };
220
221 Some(Self { storage })
222 }
223 Environment::OstreeBooted => {
224 let (physical_root, run) = get_physical_root_and_run()?;
230
231 let sysroot = ostree::Sysroot::new_default();
232 sysroot.set_mount_namespace_in_use();
233 let sysroot = ostree_ext::sysroot::SysrootLock::new_from_sysroot(&sysroot).await?;
234 sysroot.load(gio::Cancellable::NONE)?;
235
236 let storage = Storage {
237 physical_root,
238 physical_root_path: Utf8PathBuf::from("/sysroot"),
239 run,
240 boot_dir: None,
241 esp: None,
242 ostree: OnceCell::from(sysroot),
243 composefs: Default::default(),
244 imgstore: Default::default(),
245 };
246
247 Some(Self { storage })
248 }
249 Environment::Container | Environment::Other => None,
251 };
252 Ok(r)
253 }
254
255 pub(crate) fn kind(&self) -> Result<BootedStorageKind<'_>> {
260 if let Some(cmdline) = composefs_booted()? {
261 let repo = self.composefs.get().unwrap();
263 Ok(BootedStorageKind::Composefs(BootedComposefs {
264 repo: Arc::clone(repo),
265 cmdline,
266 }))
267 } else {
268 let sysroot = self.ostree.get().unwrap();
270 let deployment = sysroot.require_booted_deployment()?;
271 Ok(BootedStorageKind::Ostree(BootedOstree {
272 sysroot,
273 deployment,
274 }))
275 }
276 }
277}
278
279pub(crate) struct Storage {
282 pub physical_root: Dir,
284
285 pub physical_root_path: Utf8PathBuf,
288
289 pub boot_dir: Option<Dir>,
293
294 pub esp: Option<TempMount>,
296
297 run: Dir,
299
300 ostree: OnceCell<SysrootLock>,
302 composefs: OnceCell<Arc<ComposefsRepository>>,
304 imgstore: OnceCell<CStorage>,
306}
307
308#[derive(Default)]
313pub(crate) struct CachedImageStatus {
314 pub image: Option<ImageStatus>,
315 pub cached_update: Option<ImageStatus>,
316}
317
318impl Storage {
319 pub fn new_ostree(sysroot: SysrootLock, run: &Dir) -> Result<Self> {
324 let run = run.try_clone()?;
325
326 let ostree_sysroot_dir = crate::utils::sysroot_dir(&sysroot)?;
336 let (physical_root, physical_root_path) = if sysroot.is_booted() {
337 (
338 ostree_sysroot_dir.open_dir(SYSROOT)?,
339 Utf8PathBuf::from("/sysroot"),
340 )
341 } else {
342 let path = sysroot.path();
344 let path_str = path.parse_name().to_string();
345 let path = Utf8PathBuf::from(path_str);
346 (ostree_sysroot_dir, path)
347 };
348
349 let ostree_cell = OnceCell::new();
350 let _ = ostree_cell.set(sysroot);
351
352 Ok(Self {
353 physical_root,
354 physical_root_path,
355 run,
356 boot_dir: None,
357 esp: None,
358 ostree: ostree_cell,
359 composefs: Default::default(),
360 imgstore: Default::default(),
361 })
362 }
363
364 pub(crate) fn require_boot_dir(&self) -> Result<&Dir> {
366 self.boot_dir
367 .as_ref()
368 .ok_or_else(|| anyhow::anyhow!("Boot dir not found"))
369 }
370
371 pub(crate) fn get_ostree(&self) -> Result<&SysrootLock> {
373 self.ostree
374 .get()
375 .ok_or_else(|| anyhow::anyhow!("OSTree storage not initialized"))
376 }
377
378 pub(crate) fn get_ostree_cloned(&self) -> Result<ostree::Sysroot> {
383 let r = self.get_ostree()?;
384 Ok((*r).clone())
385 }
386
387 pub(crate) fn get_ensure_imgstore(&self) -> Result<&CStorage> {
389 if let Some(imgstore) = self.imgstore.get() {
390 return Ok(imgstore);
391 }
392 let ostree = self.get_ostree()?;
393 let sysroot_dir = crate::utils::sysroot_dir(ostree)?;
394
395 let sepolicy = if ostree.booted_deployment().is_none() {
396 tracing::trace!("falling back to container root's selinux policy");
399 let container_root = Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
400 lsm::new_sepolicy_at(&container_root)?
401 } else {
402 tracing::trace!("loading sepolicy from booted ostree deployment");
405 let dep = ostree.booted_deployment().unwrap();
406 let dep_fs = deployment_fd(ostree, &dep)?;
407 lsm::new_sepolicy_at(&dep_fs)?
408 };
409
410 tracing::trace!("sepolicy in get_ensure_imgstore: {sepolicy:?}");
411
412 let imgstore = CStorage::create(&sysroot_dir, &self.run, sepolicy.as_ref())?;
413 Ok(self.imgstore.get_or_init(|| imgstore))
414 }
415
416 pub(crate) fn get_ensure_composefs(&self) -> Result<Arc<ComposefsRepository>> {
421 if let Some(composefs) = self.composefs.get() {
422 return Ok(Arc::clone(composefs));
423 }
424
425 ensure_composefs_dir(&self.physical_root)?;
426
427 let ostree = self.get_ostree()?;
430 let ostree_repo = &ostree.repo();
431 let ostree_verity = ostree_ext::fsverity::is_verity_enabled(ostree_repo)?;
432 let mut composefs =
433 ComposefsRepository::open_path(self.physical_root.open_dir(COMPOSEFS)?, ".")?;
434 if !ostree_verity.enabled {
435 tracing::debug!("Setting insecure mode for composefs repo");
436 composefs.set_insecure(true);
437 }
438 let composefs = Arc::new(composefs);
439 let r = Arc::clone(self.composefs.get_or_init(|| composefs));
440 Ok(r)
441 }
442
443 #[context("Updating storage root mtime")]
445 pub(crate) fn update_mtime(&self) -> Result<()> {
446 let ostree = self.get_ostree()?;
447 let sysroot_dir = crate::utils::sysroot_dir(ostree).context("Reopen sysroot directory")?;
448
449 sysroot_dir
450 .update_timestamps(std::path::Path::new(BOOTC_ROOT))
451 .context("update_timestamps")
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 const PERMS: Mode = Mode::from_raw_mode(0o777);
463
464 #[test]
465 fn test_ensure_composefs_dir_mode() -> Result<()> {
466 use cap_std_ext::cap_primitives::fs::PermissionsExt as _;
467
468 let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
469
470 let assert_mode = || -> Result<()> {
471 let perms = td.metadata(COMPOSEFS)?.permissions();
472 let mode = Mode::from_raw_mode(perms.mode());
473 assert_eq!(mode & PERMS, COMPOSEFS_MODE);
474 Ok(())
475 };
476
477 ensure_composefs_dir(&td)?;
478 assert_mode()?;
479
480 ensure_composefs_dir(&td)?;
482 assert_mode()?;
483
484 Ok(())
485 }
486
487 #[test]
488 fn test_ensure_composefs_dir_fixes_existing() -> Result<()> {
489 use cap_std_ext::cap_primitives::fs::PermissionsExt as _;
490
491 let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
492
493 let mut db = DirBuilder::new();
495 db.mode(0o755);
496 td.create_dir_with(COMPOSEFS, &db)?;
497
498 let perms = td.metadata(COMPOSEFS)?.permissions();
500 let mode = Mode::from_raw_mode(perms.mode());
501 assert_eq!(mode & PERMS, Mode::from_raw_mode(0o755));
502
503 ensure_composefs_dir(&td)?;
505
506 let perms = td.metadata(COMPOSEFS)?.permissions();
507 let mode = Mode::from_raw_mode(perms.mode());
508 assert_eq!(mode & PERMS, COMPOSEFS_MODE);
509
510 Ok(())
511 }
512}