1use crate::config::{BootParams, SecurityProfile};
4use crate::error::AgentdResult;
5use crate::{network, rlimit, tls};
6
7pub fn init(
21 mut params: BootParams,
22 before_user_mounts: impl FnOnce() -> AgentdResult<()>,
23) -> AgentdResult<()> {
24 rlimit::apply_baseline(¶ms.rlimits)?;
25 linux::mount_filesystems()?;
26 linux::mount_runtime()?;
27 if let Some(spec) = ¶ms.block_root {
28 linux::mount_block_root(spec)?;
29 }
30 before_user_mounts()?;
31 if params.security_profile == SecurityProfile::Restricted {
32 force_restricted_mount_flags(&mut params);
33 }
34 linux::apply_dir_mounts(¶ms.dir_mounts)?;
35 linux::apply_file_mounts(¶ms.file_mounts)?;
36 linux::apply_disk_mounts(¶ms.disk_mounts)?;
37 network::apply_hostname(
38 params.hostname.as_deref(),
39 params.host_alias.as_deref(),
40 params.net_ipv4.as_ref().map(|v4| v4.gateway),
41 params.net_ipv6.as_ref().map(|v6| v6.gateway),
42 )?;
43 linux::apply_tmpfs_mounts(¶ms.tmpfs)?;
44 linux::ensure_standard_tmp_permissions()?;
45 network::apply_network_config(params.network())?;
46 tls::install_ca_cert()?;
47 tls::install_host_cas()?;
48 linux::ensure_scripts_path_in_profile()?;
49 linux::create_run_dir()?;
50 Ok(())
51}
52
53fn force_restricted_mount_flags(params: &mut BootParams) {
54 for spec in &mut params.dir_mounts {
55 spec.nosuid = true;
56 spec.nodev = true;
57 }
58 for spec in &mut params.file_mounts {
59 spec.nosuid = true;
60 spec.nodev = true;
61 }
62 for spec in &mut params.disk_mounts {
63 spec.nosuid = true;
64 spec.nodev = true;
65 }
66 for spec in &mut params.tmpfs {
67 spec.nosuid = true;
68 spec.nodev = true;
69 }
70}
71
72fn ensure_scripts_profile_block(profile: &str) -> String {
73 const START_MARKER: &str = "# >>> microsandbox scripts path >>>";
74 const END_MARKER: &str = "# <<< microsandbox scripts path <<<";
75 const BLOCK: &str = "# >>> microsandbox scripts path >>>\ncase \":$PATH:\" in\n *:/.msb/scripts:*) ;;\n *) export PATH=\"/.msb/scripts:$PATH\" ;;\nesac\n# <<< microsandbox scripts path <<<\n";
76
77 if profile.contains(START_MARKER) && profile.contains(END_MARKER) {
78 return profile.to_string();
79 }
80
81 let mut updated = profile.to_string();
82 if !updated.is_empty() && !updated.ends_with('\n') {
83 updated.push('\n');
84 }
85 updated.push_str(BLOCK);
86 updated
87}
88
89mod linux {
94 use std::os::unix::fs::{self as unix_fs, PermissionsExt};
95 use std::path::Path;
96 use std::{fs, thread, time::Duration};
97
98 use nix::mount::{self, MntFlags, MsFlags};
99 use nix::sys::stat::Mode;
100 use nix::unistd;
101
102 use crate::config::{
103 BlockRootSpec, BlockRootUpper, DirMountSpec, DiskMountSpec, FileMountSpec, TmpfsSpec,
104 };
105 use crate::error::{AgentdError, AgentdResult};
106
107 const UPPER_METRICS_PATH: &str = "/sys/kernel/msb_metrics/upper_path";
108 const UPPER_METRICS_REGISTER_ATTEMPTS: usize = 100;
109 const UPPER_METRICS_REGISTER_RETRY: Duration = Duration::from_millis(10);
110
111 pub fn mount_filesystems() -> AgentdResult<()> {
113 mkdir_ignore_exists("/dev")?;
115 mount_ignore_busy(
116 Some("devtmpfs"),
117 "/dev",
118 Some("devtmpfs"),
119 MsFlags::MS_RELATIME,
120 None::<&str>,
121 )?;
122
123 let nodev_noexec_nosuid =
125 MsFlags::MS_NODEV | MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_RELATIME;
126
127 mkdir_ignore_exists("/proc")?;
128 mount_ignore_busy(
129 Some("proc"),
130 "/proc",
131 Some("proc"),
132 nodev_noexec_nosuid,
133 None::<&str>,
134 )?;
135
136 mkdir_ignore_exists("/sys")?;
138 mount_ignore_busy(
139 Some("sysfs"),
140 "/sys",
141 Some("sysfs"),
142 nodev_noexec_nosuid,
143 None::<&str>,
144 )?;
145
146 mkdir_ignore_exists("/sys/fs/cgroup")?;
148 mount_ignore_busy(
149 Some("cgroup2"),
150 "/sys/fs/cgroup",
151 Some("cgroup2"),
152 nodev_noexec_nosuid,
153 None::<&str>,
154 )?;
155
156 let noexec_nosuid = MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_RELATIME;
158
159 mkdir_ignore_exists("/dev/pts")?;
160 mount_ignore_busy(
161 Some("devpts"),
162 "/dev/pts",
163 Some("devpts"),
164 noexec_nosuid,
165 None::<&str>,
166 )?;
167
168 mkdir_ignore_exists("/dev/shm")?;
170 mount_ignore_busy(
171 Some("tmpfs"),
172 "/dev/shm",
173 Some("tmpfs"),
174 noexec_nosuid,
175 None::<&str>,
176 )?;
177
178 if !Path::new("/dev/fd").exists() {
180 unix_fs::symlink("/proc/self/fd", "/dev/fd")
181 .map_err(|e| AgentdError::Init(format!("failed to symlink /dev/fd: {e}")))?;
182 }
183
184 Ok(())
185 }
186
187 pub fn mount_runtime() -> AgentdResult<()> {
189 mkdir_ignore_exists(microsandbox_protocol::RUNTIME_MOUNT_POINT)?;
190 mount_ignore_busy(
191 Some(microsandbox_protocol::RUNTIME_FS_TAG),
192 microsandbox_protocol::RUNTIME_MOUNT_POINT,
193 Some("virtiofs"),
194 MsFlags::empty(),
195 None::<&str>,
196 )?;
197 Ok(())
198 }
199
200 pub fn mount_block_root(spec: &BlockRootSpec) -> AgentdResult<()> {
204 mkdir_ignore_exists("/newroot")?;
205
206 match spec {
207 BlockRootSpec::DiskImage { device, fstype } => {
208 mount_disk_image(device, fstype.as_deref())?;
209 }
210 BlockRootSpec::OciErofs { lower, upper } => {
211 mount_oci_erofs(lower, upper)?;
212 }
213 }
214
215 pivot_to_newroot()?;
216
217 Ok(())
218 }
219
220 fn mount_disk_image(device: &str, fstype: Option<&str>) -> AgentdResult<()> {
222 if let Some(fstype) = fstype {
223 mount::mount(
224 Some(device),
225 "/newroot",
226 Some(fstype),
227 MsFlags::empty(),
228 None::<&str>,
229 )
230 .map_err(|e| {
231 AgentdError::Init(format!(
232 "failed to mount {device} at /newroot as {fstype}: {e}"
233 ))
234 })?;
235 } else {
236 let fstypes = read_proc_filesystems()?;
237 try_mount_any(device, "/newroot", MsFlags::empty(), &fstypes)?;
238 }
239 Ok(())
240 }
241
242 fn mount_oci_erofs(lower_device: &str, upper: &BlockRootUpper) -> AgentdResult<()> {
244 let lower_dir = "/.msb/rootfs/lower";
246 mkdir_ignore_exists("/.msb/rootfs")?;
247 mkdir_ignore_exists("/.msb/rootfs/lower")?;
248 mount::mount(
249 Some(lower_device),
250 lower_dir,
251 Some("erofs"),
252 MsFlags::MS_RDONLY,
253 None::<&str>,
254 )
255 .map_err(|e| AgentdError::Init(format!("mount {lower_device} at {lower_dir}: {e}")))?;
256
257 let upperfs_dir = "/.msb/rootfs/upperfs";
260 mkdir_ignore_exists("/.msb/rootfs/upperfs")?;
261 match upper {
262 BlockRootUpper::Device { device, fstype } => {
263 mount::mount(
264 Some(device.as_str()),
265 upperfs_dir,
266 Some(fstype.as_str()),
267 MsFlags::empty(),
268 None::<&str>,
269 )
270 .map_err(|e| AgentdError::Init(format!("mount {device} at {upperfs_dir}: {e}")))?;
271 }
272 BlockRootUpper::Tmpfs { size_mib } => {
273 let data = size_mib
274 .map(|mib| format!("size={},mode=755", u64::from(mib) * 1024 * 1024))
275 .unwrap_or_else(|| "mode=755".to_owned());
276 mount::mount(
277 Some("tmpfs"),
278 upperfs_dir,
279 Some("tmpfs"),
280 MsFlags::MS_RELATIME,
281 Some(data.as_str()),
282 )
283 .map_err(|e| {
284 AgentdError::Init(format!("mount tmpfs upper at {upperfs_dir}: {e}"))
285 })?;
286 }
287 }
288 register_upper_metrics(upperfs_dir);
289 crate::teardown::register_upper_fs(upperfs_dir);
291
292 let upper_dir = format!("{upperfs_dir}/upper");
294 let work_dir = format!("{upperfs_dir}/work");
295 fs::create_dir_all(&upper_dir)
296 .map_err(|e| AgentdError::Init(format!("mkdir {upper_dir}: {e}")))?;
297 fs::create_dir_all(&work_dir)
298 .map_err(|e| AgentdError::Init(format!("mkdir {work_dir}: {e}")))?;
299
300 let mount_data = format!("lowerdir={lower_dir},upperdir={upper_dir},workdir={work_dir}");
302
303 mount::mount(
304 Some("overlay"),
305 "/newroot",
306 Some("overlay"),
307 MsFlags::empty(),
308 Some(mount_data.as_str()),
309 )
310 .map_err(|e| AgentdError::Init(format!("mount overlay at /newroot: {e}")))?;
311
312 Ok(())
313 }
314
315 fn register_upper_metrics(upperfs_dir: &str) {
316 for attempt in 0..UPPER_METRICS_REGISTER_ATTEMPTS {
317 match fs::write(UPPER_METRICS_PATH, upperfs_dir) {
318 Ok(()) => return,
319 Err(err)
320 if err.kind() == std::io::ErrorKind::NotFound
321 && attempt + 1 < UPPER_METRICS_REGISTER_ATTEMPTS =>
322 {
323 thread::sleep(UPPER_METRICS_REGISTER_RETRY);
324 }
325 Err(err) if err.kind() == std::io::ErrorKind::NotFound => return,
326 Err(err) => {
327 eprintln!("agentd: upper metrics registration failed: {err}");
328 return;
329 }
330 }
331 }
332 }
333
334 fn pivot_to_newroot() -> AgentdResult<()> {
336 let msb_target = "/newroot/.msb";
337 mkdir_ignore_exists(msb_target)?;
338 mount::mount(
339 Some(microsandbox_protocol::RUNTIME_MOUNT_POINT),
340 msb_target,
341 None::<&str>,
342 MsFlags::MS_BIND,
343 None::<&str>,
344 )
345 .map_err(|e| AgentdError::Init(format!("failed to bind-mount /.msb into /newroot: {e}")))?;
346
347 unistd::chdir("/newroot")
348 .map_err(|e| AgentdError::Init(format!("failed to chdir /newroot: {e}")))?;
349
350 mount::mount(Some("."), "/", None::<&str>, MsFlags::MS_MOVE, None::<&str>)
351 .map_err(|e| AgentdError::Init(format!("failed to MS_MOVE /newroot to /: {e}")))?;
352
353 unistd::chroot(".").map_err(|e| AgentdError::Init(format!("failed to chroot: {e}")))?;
354
355 unistd::chdir("/")
356 .map_err(|e| AgentdError::Init(format!("failed to chdir / after chroot: {e}")))?;
357
358 mount_filesystems()?;
359
360 Ok(())
361 }
362
363 fn read_proc_filesystems() -> AgentdResult<Vec<String>> {
366 let content = fs::read_to_string("/proc/filesystems")
367 .map_err(|e| AgentdError::Init(format!("failed to read /proc/filesystems: {e}")))?;
368 Ok(content
369 .lines()
370 .filter_map(|line| {
371 if line.starts_with("nodev") {
372 return None;
373 }
374 let fstype = line.trim();
375 if fstype.is_empty() {
376 None
377 } else {
378 Some(fstype.to_string())
379 }
380 })
381 .collect())
382 }
383
384 fn try_mount_any(
389 device: &str,
390 target: &str,
391 flags: MsFlags,
392 fstypes: &[String],
393 ) -> AgentdResult<()> {
394 for fstype in fstypes {
395 if mount::mount(
396 Some(device),
397 target,
398 Some(fstype.as_str()),
399 flags,
400 None::<&str>,
401 )
402 .is_ok()
403 {
404 return Ok(());
405 }
406 }
407 Err(AgentdError::Init(format!(
408 "failed to mount {device} at {target}: no supported filesystem found"
409 )))
410 }
411
412 fn disk_mount_data(fstype: &str, readonly: bool) -> Option<&'static str> {
414 if readonly && fstype == "ext4" {
415 Some("noload")
419 } else {
420 None
421 }
422 }
423
424 fn try_mount_disk_any(
427 device: &str,
428 target: &str,
429 flags: MsFlags,
430 readonly: bool,
431 fstypes: &[String],
432 ) -> AgentdResult<()> {
433 for fstype in fstypes {
434 let data = disk_mount_data(fstype, readonly);
435 if mount::mount(Some(device), target, Some(fstype.as_str()), flags, data).is_ok() {
436 return Ok(());
437 }
438 }
439 Err(AgentdError::Init(format!(
440 "disk mount: failed to mount {device} at {target}: no supported filesystem found"
441 )))
442 }
443
444 pub fn apply_dir_mounts(specs: &[DirMountSpec]) -> AgentdResult<()> {
446 for spec in specs {
447 mount_dir(spec)?;
448 }
449 Ok(())
450 }
451
452 fn mount_dir(spec: &DirMountSpec) -> AgentdResult<()> {
454 let path = spec.guest_path.as_str();
455
456 fs::create_dir_all(path)
458 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
459
460 let mut flags = MsFlags::MS_RELATIME;
461 if spec.nosuid {
462 flags |= MsFlags::MS_NOSUID;
463 }
464 if spec.nodev {
465 flags |= MsFlags::MS_NODEV;
466 }
467 if spec.noexec {
468 flags |= MsFlags::MS_NOEXEC;
469 }
470 if spec.readonly {
471 flags |= MsFlags::MS_RDONLY;
472 }
473
474 mount::mount(
475 Some(spec.tag.as_str()),
476 path,
477 Some("virtiofs"),
478 flags,
479 None::<&str>,
480 )
481 .map_err(|e| {
482 AgentdError::Init(format!(
483 "failed to mount virtiofs tag '{}' at {path}: {e}",
484 spec.tag
485 ))
486 })?;
487
488 Ok(())
489 }
490
491 pub fn apply_file_mounts(specs: &[FileMountSpec]) -> AgentdResult<()> {
493 if specs.is_empty() {
494 return Ok(());
495 }
496
497 fs::create_dir_all(microsandbox_protocol::FILE_MOUNTS_DIR).map_err(|e| {
499 AgentdError::Init(format!(
500 "failed to create file mounts dir {}: {e}",
501 microsandbox_protocol::FILE_MOUNTS_DIR
502 ))
503 })?;
504
505 for spec in specs {
506 mount_file(spec)?;
507 }
508
509 let _ = fs::remove_dir(microsandbox_protocol::FILE_MOUNTS_DIR);
512
513 Ok(())
514 }
515
516 fn mount_file(spec: &FileMountSpec) -> AgentdResult<()> {
518 let staging_path = format!("{}/{}", microsandbox_protocol::FILE_MOUNTS_DIR, spec.tag);
519
520 fs::create_dir_all(&staging_path).map_err(|e| {
522 AgentdError::Init(format!("failed to create staging dir {staging_path}: {e}"))
523 })?;
524
525 let mut flags = MsFlags::MS_RELATIME;
527 if spec.nosuid {
528 flags |= MsFlags::MS_NOSUID;
529 }
530 if spec.nodev {
531 flags |= MsFlags::MS_NODEV;
532 }
533 if spec.noexec {
534 flags |= MsFlags::MS_NOEXEC;
535 }
536 if spec.readonly {
537 flags |= MsFlags::MS_RDONLY;
538 }
539
540 mount::mount(
541 Some(spec.tag.as_str()),
542 staging_path.as_str(),
543 Some("virtiofs"),
544 flags,
545 None::<&str>,
546 )
547 .map_err(|e| {
548 AgentdError::Init(format!(
549 "failed to mount virtiofs tag '{}' at {staging_path}: {e}",
550 spec.tag
551 ))
552 })?;
553
554 let bind_result = (|| {
555 let guest = Path::new(&spec.guest_path);
557 if let Some(parent) = guest.parent() {
558 fs::create_dir_all(parent).map_err(|e| {
559 AgentdError::Init(format!(
560 "failed to create parent dirs for {}: {e}",
561 spec.guest_path
562 ))
563 })?;
564 }
565
566 fs::OpenOptions::new()
568 .create(true)
569 .truncate(false)
570 .write(true)
571 .open(&spec.guest_path)
572 .map_err(|e| {
573 AgentdError::Init(format!(
574 "failed to create bind target {}: {e}",
575 spec.guest_path
576 ))
577 })?;
578
579 let source_path = format!("{staging_path}/{}", spec.filename);
581 mount::mount(
582 Some(source_path.as_str()),
583 spec.guest_path.as_str(),
584 None::<&str>,
585 MsFlags::MS_BIND,
586 None::<&str>,
587 )
588 .map_err(|e| {
589 AgentdError::Init(format!(
590 "failed to bind mount {source_path} to {}: {e}",
591 spec.guest_path
592 ))
593 })?;
594
595 let mut remount_flags = MsFlags::MS_BIND | MsFlags::MS_REMOUNT;
597 if spec.nosuid {
598 remount_flags |= MsFlags::MS_NOSUID;
599 }
600 if spec.nodev {
601 remount_flags |= MsFlags::MS_NODEV;
602 }
603 if spec.noexec {
604 remount_flags |= MsFlags::MS_NOEXEC;
605 }
606 if spec.readonly {
607 remount_flags |= MsFlags::MS_RDONLY;
608 }
609 mount::mount(
610 None::<&str>,
611 spec.guest_path.as_str(),
612 None::<&str>,
613 remount_flags,
614 None::<&str>,
615 )
616 .map_err(|e| {
617 AgentdError::Init(format!(
618 "failed to remount {} with volume flags: {e}",
619 spec.guest_path
620 ))
621 })?;
622
623 Ok(())
624 })();
625
626 let cleanup_result = cleanup_file_mount_staging(&staging_path);
627 match (bind_result, cleanup_result) {
628 (Ok(()), Ok(())) => Ok(()),
629 (Err(err), Ok(())) => Err(err),
630 (Ok(()), Err(err)) => Err(err),
631 (Err(err), Err(cleanup_err)) => Err(AgentdError::Init(format!(
632 "{err}; additionally failed to cleanup file mount staging {staging_path}: {cleanup_err}"
633 ))),
634 }
635 }
636
637 fn cleanup_file_mount_staging(staging_path: &str) -> AgentdResult<()> {
638 mount::umount2(staging_path, MntFlags::MNT_DETACH).map_err(|e| {
641 AgentdError::Init(format!(
642 "failed to unmount file mount staging {staging_path}: {e}"
643 ))
644 })?;
645 fs::remove_dir(staging_path).map_err(|e| {
646 AgentdError::Init(format!(
647 "failed to remove file mount staging {staging_path}: {e}"
648 ))
649 })?;
650 Ok(())
651 }
652
653 pub fn apply_disk_mounts(specs: &[DiskMountSpec]) -> AgentdResult<()> {
655 if specs.is_empty() {
656 return Ok(());
657 }
658 let fstypes = if specs.iter().any(|spec| spec.fstype.is_none()) {
661 Some(read_proc_filesystems()?)
662 } else {
663 None
664 };
665 for spec in specs {
666 mount_disk(spec, fstypes.as_deref())?;
667 }
668 Ok(())
669 }
670
671 fn resolve_disk_device(id: &str) -> AgentdResult<String> {
679 use std::{thread::sleep, time::Duration};
680 const RETRIES: u32 = 20;
681 const INTERVAL: Duration = Duration::from_millis(10);
682
683 let by_id = format!("/dev/disk/by-id/virtio-{id}");
684 for attempt in 0..RETRIES {
685 if Path::new(&by_id).exists() {
686 return Ok(by_id);
687 }
688 if let Some(dev) = scan_block_serial(id) {
689 return Ok(dev);
690 }
691 if attempt + 1 < RETRIES {
694 sleep(INTERVAL);
695 }
696 }
697 Err(AgentdError::Init(format!(
698 "disk mount: no block device found for id '{id}' \
699 (checked /dev/disk/by-id/virtio-{id} and /sys/block/*/serial)"
700 )))
701 }
702
703 fn scan_block_serial(id: &str) -> Option<String> {
705 let entries = fs::read_dir("/sys/block").ok()?;
706 for entry in entries.flatten() {
707 let name = entry.file_name();
708 let Some(name_str) = name.to_str() else {
709 continue;
710 };
711 if !name_str.starts_with("vd") {
712 continue;
713 }
714 let serial_path = entry.path().join("serial");
715 let Ok(serial) = fs::read_to_string(&serial_path) else {
716 continue;
717 };
718 if serial.trim() == id {
719 return Some(format!("/dev/{name_str}"));
720 }
721 }
722 None
723 }
724
725 fn mount_disk(spec: &DiskMountSpec, fstypes: Option<&[String]>) -> AgentdResult<()> {
726 let path = spec.guest_path.as_str();
727 fs::create_dir_all(path)
728 .map_err(|e| AgentdError::Init(format!("disk mount: create dir {path}: {e}")))?;
729
730 let device = resolve_disk_device(&spec.id)?;
731
732 let mut flags = MsFlags::MS_RELATIME;
733 if spec.nosuid {
734 flags |= MsFlags::MS_NOSUID;
735 }
736 if spec.nodev {
737 flags |= MsFlags::MS_NODEV;
738 }
739 if spec.noexec {
740 flags |= MsFlags::MS_NOEXEC;
741 }
742 if spec.readonly {
743 flags |= MsFlags::MS_RDONLY;
744 }
745
746 if let Some(fstype) = spec.fstype.as_deref() {
747 let data = disk_mount_data(fstype, spec.readonly);
748 mount::mount(Some(device.as_str()), path, Some(fstype), flags, data).map_err(|e| {
749 AgentdError::Init(format!(
750 "disk mount: failed to mount {device} at {path} as {fstype}: {e}"
751 ))
752 })?;
753 } else {
754 let fstypes = fstypes.ok_or_else(|| {
755 AgentdError::Init("disk mount: missing filesystem autodetect list".into())
756 })?;
757 try_mount_disk_any(&device, path, flags, spec.readonly, fstypes)?;
758 }
759
760 Ok(())
761 }
762
763 pub fn apply_tmpfs_mounts(specs: &[TmpfsSpec]) -> AgentdResult<()> {
765 for spec in specs {
766 mount_tmpfs(spec)?;
767 }
768 Ok(())
769 }
770
771 pub fn ensure_standard_tmp_permissions() -> AgentdResult<()> {
773 ensure_directory_mode("/tmp", 0o1777)?;
774 ensure_directory_mode("/var/tmp", 0o1777)?;
775 Ok(())
776 }
777
778 fn mount_tmpfs(spec: &TmpfsSpec) -> AgentdResult<()> {
780 let path = spec.path.as_str();
781
782 let mode = spec
784 .mode
785 .unwrap_or(if path == "/tmp" || path == "/var/tmp" {
786 0o1777
787 } else {
788 0o755
789 });
790
791 fs::create_dir_all(path)
793 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
794
795 let mut flags = MsFlags::MS_RELATIME;
796 if spec.nosuid {
797 flags |= MsFlags::MS_NOSUID;
798 }
799 if spec.nodev {
800 flags |= MsFlags::MS_NODEV;
801 }
802 if spec.noexec {
803 flags |= MsFlags::MS_NOEXEC;
804 }
805 if spec.readonly {
806 flags |= MsFlags::MS_RDONLY;
807 }
808
809 let mut data = String::new();
811 if let Some(mib) = spec.size_mib {
812 data.push_str(&format!("size={}", u64::from(mib) * 1024 * 1024));
813 }
814 if !data.is_empty() {
815 data.push(',');
816 }
817 data.push_str(&format!("mode={mode:o}"));
818
819 mount::mount(
820 Some("tmpfs"),
821 path,
822 Some("tmpfs"),
823 flags,
824 Some(data.as_str()),
825 )
826 .map_err(|e| AgentdError::Init(format!("failed to mount tmpfs at {path}: {e}")))?;
827
828 Ok(())
829 }
830
831 pub fn create_run_dir() -> AgentdResult<()> {
838 mkdir_ignore_exists("/run")?;
839 mkdir_ignore_exists("/run/microsandbox")?;
840 Ok(())
841 }
842
843 pub fn ensure_scripts_path_in_profile() -> AgentdResult<()> {
845 let profile_path = Path::new("/etc/profile");
846 let existing = match fs::read_to_string(profile_path) {
847 Ok(contents) => contents,
848 Err(err) if err.kind() == std::io::ErrorKind::NotFound => String::new(),
849 Err(err) => {
850 return Err(AgentdError::Init(format!(
851 "failed to read {}: {err}",
852 profile_path.display()
853 )));
854 }
855 };
856
857 let updated = super::ensure_scripts_profile_block(&existing);
858 if updated != existing {
859 if let Some(parent) = profile_path.parent() {
860 fs::create_dir_all(parent).map_err(|err| {
861 AgentdError::Init(format!("failed to create {}: {err}", parent.display()))
862 })?;
863 }
864 fs::write(profile_path, updated).map_err(|err| {
865 AgentdError::Init(format!("failed to write {}: {err}", profile_path.display()))
866 })?;
867 }
868
869 Ok(())
870 }
871
872 fn mkdir_ignore_exists(path: &str) -> AgentdResult<()> {
874 match unistd::mkdir(path, Mode::from_bits_truncate(0o755)) {
875 Ok(()) => Ok(()),
876 Err(nix::Error::EEXIST) => Ok(()),
877 Err(e) => Err(e.into()),
878 }
879 }
880
881 fn ensure_directory_mode(path: &str, mode: u32) -> AgentdResult<()> {
882 fs::create_dir_all(path)
883 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
884
885 let metadata = fs::metadata(path)
886 .map_err(|e| AgentdError::Init(format!("failed to stat {path}: {e}")))?;
887 if !metadata.is_dir() {
888 return Err(AgentdError::Init(format!(
889 "expected directory at {path}, found non-directory"
890 )));
891 }
892
893 let current_mode = metadata.permissions().mode() & 0o7777;
894 if current_mode != mode {
895 fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|e| {
896 AgentdError::Init(format!("failed to chmod {path} to {mode:o}: {e}"))
897 })?;
898 }
899
900 Ok(())
901 }
902
903 fn mount_ignore_busy(
905 source: Option<&str>,
906 target: &str,
907 fstype: Option<&str>,
908 flags: MsFlags,
909 data: Option<&str>,
910 ) -> AgentdResult<()> {
911 match mount::mount(source, target, fstype, flags, data) {
912 Ok(()) => Ok(()),
913 Err(nix::Error::EBUSY) => Ok(()),
914 Err(e) => Err(AgentdError::Init(format!("failed to mount {target}: {e}"))),
915 }
916 }
917}
918
919#[cfg(test)]
924mod tests {
925 use super::*;
926
927 #[test]
928 fn test_ensure_scripts_profile_block_appends_block() {
929 let updated = ensure_scripts_profile_block("export PATH=/usr/bin:/bin\n");
930 assert!(updated.contains("# >>> microsandbox scripts path >>>"));
931 assert!(updated.contains("export PATH=\"/.msb/scripts:$PATH\""));
932 }
933
934 #[test]
935 fn test_ensure_scripts_profile_block_adds_newline_when_missing() {
936 let updated = ensure_scripts_profile_block("export PATH=/usr/bin:/bin");
937 assert!(updated.contains("/usr/bin:/bin\n# >>> microsandbox scripts path >>>"));
938 }
939
940 #[test]
941 fn test_ensure_scripts_profile_block_is_idempotent() {
942 let profile = ensure_scripts_profile_block("");
943 let updated = ensure_scripts_profile_block(&profile);
944 assert_eq!(profile, updated);
945 }
946}