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::{Dir, DirBuilder, DirBuilderExt as _};
28use cap_std_ext::dirext::CapStdExtDirExt;
29use fn_error_context::context;
30
31use ostree_ext::container_utils::ostree_booted;
32use ostree_ext::prelude::FileExt;
33use ostree_ext::sysroot::SysrootLock;
34use ostree_ext::{gio, ostree};
35use rustix::fs::Mode;
36
37use crate::bootc_composefs::boot::{get_esp_partition, get_sysroot_parent_dev, mount_esp};
38use crate::bootc_composefs::status::{ComposefsCmdline, composefs_booted, get_bootloader};
39use crate::lsm;
40use crate::podstorage::CStorage;
41use crate::spec::{Bootloader, ImageStatus};
42use crate::utils::{deployment_fd, open_dir_remount_rw};
43
44pub type ComposefsRepository =
46 composefs::repository::Repository<composefs::fsverity::Sha512HashValue>;
47pub type ComposefsFilesystem = composefs::tree::FileSystem<composefs::fsverity::Sha512HashValue>;
48
49pub const SYSROOT: &str = "sysroot";
51
52pub const COMPOSEFS: &str = "composefs";
54#[allow(dead_code)]
55pub const COMPOSEFS_MODE: Mode = Mode::from_raw_mode(0o700);
56
57pub(crate) const BOOTC_ROOT: &str = "ostree/bootc";
60
61pub(crate) struct BootedStorage {
66 pub(crate) storage: Storage,
67}
68
69impl Deref for BootedStorage {
70 type Target = Storage;
71
72 fn deref(&self) -> &Self::Target {
73 &self.storage
74 }
75}
76
77pub struct BootedOstree<'a> {
79 pub(crate) sysroot: &'a SysrootLock,
80 pub(crate) deployment: ostree::Deployment,
81}
82
83impl<'a> BootedOstree<'a> {
84 pub(crate) fn repo(&self) -> ostree::Repo {
86 self.sysroot.repo()
87 }
88
89 pub(crate) fn stateroot(&self) -> ostree::glib::GString {
91 self.deployment.osname()
92 }
93}
94
95#[allow(dead_code)]
97pub struct BootedComposefs {
98 pub repo: Arc<ComposefsRepository>,
99 pub cmdline: &'static ComposefsCmdline,
100}
101
102pub(crate) enum Environment {
106 OstreeBooted,
108 ComposefsBooted(ComposefsCmdline),
110 Container,
112 Other,
114}
115
116impl Environment {
117 pub(crate) fn detect() -> Result<Self> {
119 if ostree_ext::container_utils::running_in_container() {
120 return Ok(Self::Container);
121 }
122
123 if let Some(cmdline) = composefs_booted()? {
124 return Ok(Self::ComposefsBooted(cmdline.clone()));
125 }
126
127 if ostree_booted()? {
128 return Ok(Self::OstreeBooted);
129 }
130
131 Ok(Self::Other)
132 }
133
134 pub(crate) fn needs_mount_namespace(&self) -> bool {
137 matches!(self, Self::OstreeBooted | Self::ComposefsBooted(_))
138 }
139}
140
141pub(crate) enum BootedStorageKind<'a> {
144 Ostree(BootedOstree<'a>),
145 Composefs(BootedComposefs),
146}
147
148fn get_physical_root_and_run() -> Result<(Dir, Dir)> {
150 let physical_root = {
151 let d = Dir::open_ambient_dir("/sysroot", cap_std::ambient_authority())
152 .context("Opening /sysroot")?;
153 open_dir_remount_rw(&d, ".".into())?
154 };
155 let run =
156 Dir::open_ambient_dir("/run", cap_std::ambient_authority()).context("Opening /run")?;
157 Ok((physical_root, run))
158}
159
160impl BootedStorage {
161 pub(crate) async fn new(env: Environment) -> Result<Option<Self>> {
166 let r = match &env {
167 Environment::ComposefsBooted(cmdline) => {
168 let (physical_root, run) = get_physical_root_and_run()?;
169 let mut composefs = ComposefsRepository::open_path(&physical_root, COMPOSEFS)?;
170 if cmdline.insecure {
171 composefs.set_insecure(true);
172 }
173 let composefs = Arc::new(composefs);
174
175 let parent = get_sysroot_parent_dev(&physical_root)?;
178 let (esp_part, ..) = get_esp_partition(&parent)?;
179 let esp_mount = mount_esp(&esp_part)?;
180
181 let boot_dir = match get_bootloader()? {
182 Bootloader::Grub => physical_root.open_dir("boot").context("Opening boot")?,
183 Bootloader::Systemd => esp_mount.fd.try_clone().context("Cloning fd")?,
185 };
186
187 let storage = Storage {
188 physical_root,
189 physical_root_path: Utf8PathBuf::from("/sysroot"),
190 run,
191 boot_dir: Some(boot_dir),
192 esp: Some(esp_mount),
193 ostree: Default::default(),
194 composefs: OnceCell::from(composefs),
195 imgstore: Default::default(),
196 };
197
198 Some(Self { storage })
199 }
200 Environment::OstreeBooted => {
201 let (physical_root, run) = get_physical_root_and_run()?;
207
208 let sysroot = ostree::Sysroot::new_default();
209 sysroot.set_mount_namespace_in_use();
210 let sysroot = ostree_ext::sysroot::SysrootLock::new_from_sysroot(&sysroot).await?;
211 sysroot.load(gio::Cancellable::NONE)?;
212
213 let storage = Storage {
214 physical_root,
215 physical_root_path: Utf8PathBuf::from("/sysroot"),
216 run,
217 boot_dir: None,
218 esp: None,
219 ostree: OnceCell::from(sysroot),
220 composefs: Default::default(),
221 imgstore: Default::default(),
222 };
223
224 Some(Self { storage })
225 }
226 Environment::Container | Environment::Other => None,
228 };
229 Ok(r)
230 }
231
232 pub(crate) fn kind(&self) -> Result<BootedStorageKind<'_>> {
237 if let Some(cmdline) = composefs_booted()? {
238 let repo = self.composefs.get().unwrap();
240 Ok(BootedStorageKind::Composefs(BootedComposefs {
241 repo: Arc::clone(repo),
242 cmdline,
243 }))
244 } else {
245 let sysroot = self.ostree.get().unwrap();
247 let deployment = sysroot.require_booted_deployment()?;
248 Ok(BootedStorageKind::Ostree(BootedOstree {
249 sysroot,
250 deployment,
251 }))
252 }
253 }
254}
255
256pub(crate) struct Storage {
259 pub physical_root: Dir,
261
262 pub physical_root_path: Utf8PathBuf,
265
266 pub boot_dir: Option<Dir>,
270
271 pub esp: Option<TempMount>,
273
274 run: Dir,
276
277 ostree: OnceCell<SysrootLock>,
279 composefs: OnceCell<Arc<ComposefsRepository>>,
281 imgstore: OnceCell<CStorage>,
283}
284
285#[derive(Default)]
290pub(crate) struct CachedImageStatus {
291 pub image: Option<ImageStatus>,
292 pub cached_update: Option<ImageStatus>,
293}
294
295impl Storage {
296 pub fn new_ostree(sysroot: SysrootLock, run: &Dir) -> Result<Self> {
301 let run = run.try_clone()?;
302
303 let ostree_sysroot_dir = crate::utils::sysroot_dir(&sysroot)?;
313 let (physical_root, physical_root_path) = if sysroot.is_booted() {
314 (
315 ostree_sysroot_dir.open_dir(SYSROOT)?,
316 Utf8PathBuf::from("/sysroot"),
317 )
318 } else {
319 let path = sysroot.path();
321 let path_str = path.parse_name().to_string();
322 let path = Utf8PathBuf::from(path_str);
323 (ostree_sysroot_dir, path)
324 };
325
326 let ostree_cell = OnceCell::new();
327 let _ = ostree_cell.set(sysroot);
328
329 Ok(Self {
330 physical_root,
331 physical_root_path,
332 run,
333 boot_dir: None,
334 esp: None,
335 ostree: ostree_cell,
336 composefs: Default::default(),
337 imgstore: Default::default(),
338 })
339 }
340
341 pub(crate) fn require_boot_dir(&self) -> Result<&Dir> {
343 self.boot_dir
344 .as_ref()
345 .ok_or_else(|| anyhow::anyhow!("Boot dir not found"))
346 }
347
348 pub(crate) fn get_ostree(&self) -> Result<&SysrootLock> {
350 self.ostree
351 .get()
352 .ok_or_else(|| anyhow::anyhow!("OSTree storage not initialized"))
353 }
354
355 pub(crate) fn get_ostree_cloned(&self) -> Result<ostree::Sysroot> {
360 let r = self.get_ostree()?;
361 Ok((*r).clone())
362 }
363
364 pub(crate) fn get_ensure_imgstore(&self) -> Result<&CStorage> {
366 if let Some(imgstore) = self.imgstore.get() {
367 return Ok(imgstore);
368 }
369 let ostree = self.get_ostree()?;
370 let sysroot_dir = crate::utils::sysroot_dir(ostree)?;
371
372 let sepolicy = if ostree.booted_deployment().is_none() {
373 tracing::trace!("falling back to container root's selinux policy");
376 let container_root = Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
377 lsm::new_sepolicy_at(&container_root)?
378 } else {
379 tracing::trace!("loading sepolicy from booted ostree deployment");
382 let dep = ostree.booted_deployment().unwrap();
383 let dep_fs = deployment_fd(ostree, &dep)?;
384 lsm::new_sepolicy_at(&dep_fs)?
385 };
386
387 tracing::trace!("sepolicy in get_ensure_imgstore: {sepolicy:?}");
388
389 let imgstore = CStorage::create(&sysroot_dir, &self.run, sepolicy.as_ref())?;
390 Ok(self.imgstore.get_or_init(|| imgstore))
391 }
392
393 pub(crate) fn get_ensure_composefs(&self) -> Result<Arc<ComposefsRepository>> {
398 if let Some(composefs) = self.composefs.get() {
399 return Ok(Arc::clone(composefs));
400 }
401
402 let mut db = DirBuilder::new();
403 db.mode(COMPOSEFS_MODE.as_raw_mode());
404 self.physical_root.ensure_dir_with(COMPOSEFS, &db)?;
405
406 let ostree = self.get_ostree()?;
409 let ostree_repo = &ostree.repo();
410 let ostree_verity = ostree_ext::fsverity::is_verity_enabled(ostree_repo)?;
411 let mut composefs =
412 ComposefsRepository::open_path(self.physical_root.open_dir(COMPOSEFS)?, ".")?;
413 if !ostree_verity.enabled {
414 tracing::debug!("Setting insecure mode for composefs repo");
415 composefs.set_insecure(true);
416 }
417 let composefs = Arc::new(composefs);
418 let r = Arc::clone(self.composefs.get_or_init(|| composefs));
419 Ok(r)
420 }
421
422 #[context("Updating storage root mtime")]
424 pub(crate) fn update_mtime(&self) -> Result<()> {
425 let ostree = self.get_ostree()?;
426 let sysroot_dir = crate::utils::sysroot_dir(ostree).context("Reopen sysroot directory")?;
427
428 sysroot_dir
429 .update_timestamps(std::path::Path::new(BOOTC_ROOT))
430 .context("update_timestamps")
431 }
432}