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::{BlockRootSpec, DirMountSpec, DiskMountSpec, FileMountSpec, TmpfsSpec};
103 use crate::error::{AgentdError, AgentdResult};
104
105 const UPPER_METRICS_PATH: &str = "/sys/kernel/msb_metrics/upper_path";
106 const UPPER_METRICS_REGISTER_ATTEMPTS: usize = 100;
107 const UPPER_METRICS_REGISTER_RETRY: Duration = Duration::from_millis(10);
108
109 pub fn mount_filesystems() -> AgentdResult<()> {
111 mkdir_ignore_exists("/dev")?;
113 mount_ignore_busy(
114 Some("devtmpfs"),
115 "/dev",
116 Some("devtmpfs"),
117 MsFlags::MS_RELATIME,
118 None::<&str>,
119 )?;
120
121 let nodev_noexec_nosuid =
123 MsFlags::MS_NODEV | MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_RELATIME;
124
125 mkdir_ignore_exists("/proc")?;
126 mount_ignore_busy(
127 Some("proc"),
128 "/proc",
129 Some("proc"),
130 nodev_noexec_nosuid,
131 None::<&str>,
132 )?;
133
134 mkdir_ignore_exists("/sys")?;
136 mount_ignore_busy(
137 Some("sysfs"),
138 "/sys",
139 Some("sysfs"),
140 nodev_noexec_nosuid,
141 None::<&str>,
142 )?;
143
144 mkdir_ignore_exists("/sys/fs/cgroup")?;
146 mount_ignore_busy(
147 Some("cgroup2"),
148 "/sys/fs/cgroup",
149 Some("cgroup2"),
150 nodev_noexec_nosuid,
151 None::<&str>,
152 )?;
153
154 let noexec_nosuid = MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_RELATIME;
156
157 mkdir_ignore_exists("/dev/pts")?;
158 mount_ignore_busy(
159 Some("devpts"),
160 "/dev/pts",
161 Some("devpts"),
162 noexec_nosuid,
163 None::<&str>,
164 )?;
165
166 mkdir_ignore_exists("/dev/shm")?;
168 mount_ignore_busy(
169 Some("tmpfs"),
170 "/dev/shm",
171 Some("tmpfs"),
172 noexec_nosuid,
173 None::<&str>,
174 )?;
175
176 if !Path::new("/dev/fd").exists() {
178 unix_fs::symlink("/proc/self/fd", "/dev/fd")
179 .map_err(|e| AgentdError::Init(format!("failed to symlink /dev/fd: {e}")))?;
180 }
181
182 Ok(())
183 }
184
185 pub fn mount_runtime() -> AgentdResult<()> {
187 mkdir_ignore_exists(microsandbox_protocol::RUNTIME_MOUNT_POINT)?;
188 mount_ignore_busy(
189 Some(microsandbox_protocol::RUNTIME_FS_TAG),
190 microsandbox_protocol::RUNTIME_MOUNT_POINT,
191 Some("virtiofs"),
192 MsFlags::empty(),
193 None::<&str>,
194 )?;
195 Ok(())
196 }
197
198 pub fn mount_block_root(spec: &BlockRootSpec) -> AgentdResult<()> {
202 mkdir_ignore_exists("/newroot")?;
203
204 match spec {
205 BlockRootSpec::DiskImage { device, fstype } => {
206 mount_disk_image(device, fstype.as_deref())?;
207 }
208 BlockRootSpec::OciErofs {
209 lower,
210 upper,
211 upper_fstype,
212 } => {
213 mount_oci_erofs(lower, upper, upper_fstype)?;
214 }
215 }
216
217 pivot_to_newroot()?;
218
219 Ok(())
220 }
221
222 fn mount_disk_image(device: &str, fstype: Option<&str>) -> AgentdResult<()> {
224 if let Some(fstype) = fstype {
225 mount::mount(
226 Some(device),
227 "/newroot",
228 Some(fstype),
229 MsFlags::empty(),
230 None::<&str>,
231 )
232 .map_err(|e| {
233 AgentdError::Init(format!(
234 "failed to mount {device} at /newroot as {fstype}: {e}"
235 ))
236 })?;
237 } else {
238 let fstypes = read_proc_filesystems()?;
239 try_mount_any(device, "/newroot", MsFlags::empty(), &fstypes)?;
240 }
241 Ok(())
242 }
243
244 fn mount_oci_erofs(
246 lower_device: &str,
247 upper_device: &str,
248 upper_fstype: &str,
249 ) -> AgentdResult<()> {
250 let lower_dir = "/.msb/rootfs/lower";
252 mkdir_ignore_exists("/.msb/rootfs")?;
253 mkdir_ignore_exists("/.msb/rootfs/lower")?;
254 mount::mount(
255 Some(lower_device),
256 lower_dir,
257 Some("erofs"),
258 MsFlags::MS_RDONLY,
259 None::<&str>,
260 )
261 .map_err(|e| AgentdError::Init(format!("mount {lower_device} at {lower_dir}: {e}")))?;
262
263 let upperfs_dir = "/.msb/rootfs/upperfs";
265 mkdir_ignore_exists("/.msb/rootfs/upperfs")?;
266 mount::mount(
267 Some(upper_device),
268 upperfs_dir,
269 Some(upper_fstype),
270 MsFlags::empty(),
271 None::<&str>,
272 )
273 .map_err(|e| AgentdError::Init(format!("mount {upper_device} at {upperfs_dir}: {e}")))?;
274 register_upper_metrics(upperfs_dir);
275
276 let upper_dir = format!("{upperfs_dir}/upper");
278 let work_dir = format!("{upperfs_dir}/work");
279 fs::create_dir_all(&upper_dir)
280 .map_err(|e| AgentdError::Init(format!("mkdir {upper_dir}: {e}")))?;
281 fs::create_dir_all(&work_dir)
282 .map_err(|e| AgentdError::Init(format!("mkdir {work_dir}: {e}")))?;
283
284 let mount_data = format!("lowerdir={lower_dir},upperdir={upper_dir},workdir={work_dir}");
286
287 mount::mount(
288 Some("overlay"),
289 "/newroot",
290 Some("overlay"),
291 MsFlags::empty(),
292 Some(mount_data.as_str()),
293 )
294 .map_err(|e| AgentdError::Init(format!("mount overlay at /newroot: {e}")))?;
295
296 Ok(())
297 }
298
299 fn register_upper_metrics(upperfs_dir: &str) {
300 for attempt in 0..UPPER_METRICS_REGISTER_ATTEMPTS {
301 match fs::write(UPPER_METRICS_PATH, upperfs_dir) {
302 Ok(()) => return,
303 Err(err)
304 if err.kind() == std::io::ErrorKind::NotFound
305 && attempt + 1 < UPPER_METRICS_REGISTER_ATTEMPTS =>
306 {
307 thread::sleep(UPPER_METRICS_REGISTER_RETRY);
308 }
309 Err(err) if err.kind() == std::io::ErrorKind::NotFound => return,
310 Err(err) => {
311 eprintln!("agentd: upper metrics registration failed: {err}");
312 return;
313 }
314 }
315 }
316 }
317
318 fn pivot_to_newroot() -> AgentdResult<()> {
320 let msb_target = "/newroot/.msb";
321 mkdir_ignore_exists(msb_target)?;
322 mount::mount(
323 Some(microsandbox_protocol::RUNTIME_MOUNT_POINT),
324 msb_target,
325 None::<&str>,
326 MsFlags::MS_BIND,
327 None::<&str>,
328 )
329 .map_err(|e| AgentdError::Init(format!("failed to bind-mount /.msb into /newroot: {e}")))?;
330
331 unistd::chdir("/newroot")
332 .map_err(|e| AgentdError::Init(format!("failed to chdir /newroot: {e}")))?;
333
334 mount::mount(Some("."), "/", None::<&str>, MsFlags::MS_MOVE, None::<&str>)
335 .map_err(|e| AgentdError::Init(format!("failed to MS_MOVE /newroot to /: {e}")))?;
336
337 unistd::chroot(".").map_err(|e| AgentdError::Init(format!("failed to chroot: {e}")))?;
338
339 unistd::chdir("/")
340 .map_err(|e| AgentdError::Init(format!("failed to chdir / after chroot: {e}")))?;
341
342 mount_filesystems()?;
343
344 Ok(())
345 }
346
347 fn read_proc_filesystems() -> AgentdResult<Vec<String>> {
350 let content = fs::read_to_string("/proc/filesystems")
351 .map_err(|e| AgentdError::Init(format!("failed to read /proc/filesystems: {e}")))?;
352 Ok(content
353 .lines()
354 .filter_map(|line| {
355 if line.starts_with("nodev") {
356 return None;
357 }
358 let fstype = line.trim();
359 if fstype.is_empty() {
360 None
361 } else {
362 Some(fstype.to_string())
363 }
364 })
365 .collect())
366 }
367
368 fn try_mount_any(
373 device: &str,
374 target: &str,
375 flags: MsFlags,
376 fstypes: &[String],
377 ) -> AgentdResult<()> {
378 for fstype in fstypes {
379 if mount::mount(
380 Some(device),
381 target,
382 Some(fstype.as_str()),
383 flags,
384 None::<&str>,
385 )
386 .is_ok()
387 {
388 return Ok(());
389 }
390 }
391 Err(AgentdError::Init(format!(
392 "failed to mount {device} at {target}: no supported filesystem found"
393 )))
394 }
395
396 fn disk_mount_data(fstype: &str, readonly: bool) -> Option<&'static str> {
398 if readonly && fstype == "ext4" {
399 Some("noload")
403 } else {
404 None
405 }
406 }
407
408 fn try_mount_disk_any(
411 device: &str,
412 target: &str,
413 flags: MsFlags,
414 readonly: bool,
415 fstypes: &[String],
416 ) -> AgentdResult<()> {
417 for fstype in fstypes {
418 let data = disk_mount_data(fstype, readonly);
419 if mount::mount(Some(device), target, Some(fstype.as_str()), flags, data).is_ok() {
420 return Ok(());
421 }
422 }
423 Err(AgentdError::Init(format!(
424 "disk mount: failed to mount {device} at {target}: no supported filesystem found"
425 )))
426 }
427
428 pub fn apply_dir_mounts(specs: &[DirMountSpec]) -> AgentdResult<()> {
430 for spec in specs {
431 mount_dir(spec)?;
432 }
433 Ok(())
434 }
435
436 fn mount_dir(spec: &DirMountSpec) -> AgentdResult<()> {
438 let path = spec.guest_path.as_str();
439
440 fs::create_dir_all(path)
442 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
443
444 let mut flags = MsFlags::MS_RELATIME;
445 if spec.nosuid {
446 flags |= MsFlags::MS_NOSUID;
447 }
448 if spec.nodev {
449 flags |= MsFlags::MS_NODEV;
450 }
451 if spec.noexec {
452 flags |= MsFlags::MS_NOEXEC;
453 }
454 if spec.readonly {
455 flags |= MsFlags::MS_RDONLY;
456 }
457
458 mount::mount(
459 Some(spec.tag.as_str()),
460 path,
461 Some("virtiofs"),
462 flags,
463 None::<&str>,
464 )
465 .map_err(|e| {
466 AgentdError::Init(format!(
467 "failed to mount virtiofs tag '{}' at {path}: {e}",
468 spec.tag
469 ))
470 })?;
471
472 Ok(())
473 }
474
475 pub fn apply_file_mounts(specs: &[FileMountSpec]) -> AgentdResult<()> {
477 if specs.is_empty() {
478 return Ok(());
479 }
480
481 fs::create_dir_all(microsandbox_protocol::FILE_MOUNTS_DIR).map_err(|e| {
483 AgentdError::Init(format!(
484 "failed to create file mounts dir {}: {e}",
485 microsandbox_protocol::FILE_MOUNTS_DIR
486 ))
487 })?;
488
489 for spec in specs {
490 mount_file(spec)?;
491 }
492
493 let _ = fs::remove_dir(microsandbox_protocol::FILE_MOUNTS_DIR);
496
497 Ok(())
498 }
499
500 fn mount_file(spec: &FileMountSpec) -> AgentdResult<()> {
502 let staging_path = format!("{}/{}", microsandbox_protocol::FILE_MOUNTS_DIR, spec.tag);
503
504 fs::create_dir_all(&staging_path).map_err(|e| {
506 AgentdError::Init(format!("failed to create staging dir {staging_path}: {e}"))
507 })?;
508
509 let mut flags = MsFlags::MS_RELATIME;
511 if spec.nosuid {
512 flags |= MsFlags::MS_NOSUID;
513 }
514 if spec.nodev {
515 flags |= MsFlags::MS_NODEV;
516 }
517 if spec.noexec {
518 flags |= MsFlags::MS_NOEXEC;
519 }
520 if spec.readonly {
521 flags |= MsFlags::MS_RDONLY;
522 }
523
524 mount::mount(
525 Some(spec.tag.as_str()),
526 staging_path.as_str(),
527 Some("virtiofs"),
528 flags,
529 None::<&str>,
530 )
531 .map_err(|e| {
532 AgentdError::Init(format!(
533 "failed to mount virtiofs tag '{}' at {staging_path}: {e}",
534 spec.tag
535 ))
536 })?;
537
538 let bind_result = (|| {
539 let guest = Path::new(&spec.guest_path);
541 if let Some(parent) = guest.parent() {
542 fs::create_dir_all(parent).map_err(|e| {
543 AgentdError::Init(format!(
544 "failed to create parent dirs for {}: {e}",
545 spec.guest_path
546 ))
547 })?;
548 }
549
550 fs::OpenOptions::new()
552 .create(true)
553 .truncate(false)
554 .write(true)
555 .open(&spec.guest_path)
556 .map_err(|e| {
557 AgentdError::Init(format!(
558 "failed to create bind target {}: {e}",
559 spec.guest_path
560 ))
561 })?;
562
563 let source_path = format!("{staging_path}/{}", spec.filename);
565 mount::mount(
566 Some(source_path.as_str()),
567 spec.guest_path.as_str(),
568 None::<&str>,
569 MsFlags::MS_BIND,
570 None::<&str>,
571 )
572 .map_err(|e| {
573 AgentdError::Init(format!(
574 "failed to bind mount {source_path} to {}: {e}",
575 spec.guest_path
576 ))
577 })?;
578
579 let mut remount_flags = MsFlags::MS_BIND | MsFlags::MS_REMOUNT;
581 if spec.nosuid {
582 remount_flags |= MsFlags::MS_NOSUID;
583 }
584 if spec.nodev {
585 remount_flags |= MsFlags::MS_NODEV;
586 }
587 if spec.noexec {
588 remount_flags |= MsFlags::MS_NOEXEC;
589 }
590 if spec.readonly {
591 remount_flags |= MsFlags::MS_RDONLY;
592 }
593 mount::mount(
594 None::<&str>,
595 spec.guest_path.as_str(),
596 None::<&str>,
597 remount_flags,
598 None::<&str>,
599 )
600 .map_err(|e| {
601 AgentdError::Init(format!(
602 "failed to remount {} with volume flags: {e}",
603 spec.guest_path
604 ))
605 })?;
606
607 Ok(())
608 })();
609
610 let cleanup_result = cleanup_file_mount_staging(&staging_path);
611 match (bind_result, cleanup_result) {
612 (Ok(()), Ok(())) => Ok(()),
613 (Err(err), Ok(())) => Err(err),
614 (Ok(()), Err(err)) => Err(err),
615 (Err(err), Err(cleanup_err)) => Err(AgentdError::Init(format!(
616 "{err}; additionally failed to cleanup file mount staging {staging_path}: {cleanup_err}"
617 ))),
618 }
619 }
620
621 fn cleanup_file_mount_staging(staging_path: &str) -> AgentdResult<()> {
622 mount::umount2(staging_path, MntFlags::MNT_DETACH).map_err(|e| {
625 AgentdError::Init(format!(
626 "failed to unmount file mount staging {staging_path}: {e}"
627 ))
628 })?;
629 fs::remove_dir(staging_path).map_err(|e| {
630 AgentdError::Init(format!(
631 "failed to remove file mount staging {staging_path}: {e}"
632 ))
633 })?;
634 Ok(())
635 }
636
637 pub fn apply_disk_mounts(specs: &[DiskMountSpec]) -> AgentdResult<()> {
639 if specs.is_empty() {
640 return Ok(());
641 }
642 let fstypes = if specs.iter().any(|spec| spec.fstype.is_none()) {
645 Some(read_proc_filesystems()?)
646 } else {
647 None
648 };
649 for spec in specs {
650 mount_disk(spec, fstypes.as_deref())?;
651 }
652 Ok(())
653 }
654
655 fn resolve_disk_device(id: &str) -> AgentdResult<String> {
663 use std::{thread::sleep, time::Duration};
664 const RETRIES: u32 = 20;
665 const INTERVAL: Duration = Duration::from_millis(10);
666
667 let by_id = format!("/dev/disk/by-id/virtio-{id}");
668 for attempt in 0..RETRIES {
669 if Path::new(&by_id).exists() {
670 return Ok(by_id);
671 }
672 if let Some(dev) = scan_block_serial(id) {
673 return Ok(dev);
674 }
675 if attempt + 1 < RETRIES {
678 sleep(INTERVAL);
679 }
680 }
681 Err(AgentdError::Init(format!(
682 "disk mount: no block device found for id '{id}' \
683 (checked /dev/disk/by-id/virtio-{id} and /sys/block/*/serial)"
684 )))
685 }
686
687 fn scan_block_serial(id: &str) -> Option<String> {
689 let entries = fs::read_dir("/sys/block").ok()?;
690 for entry in entries.flatten() {
691 let name = entry.file_name();
692 let Some(name_str) = name.to_str() else {
693 continue;
694 };
695 if !name_str.starts_with("vd") {
696 continue;
697 }
698 let serial_path = entry.path().join("serial");
699 let Ok(serial) = fs::read_to_string(&serial_path) else {
700 continue;
701 };
702 if serial.trim() == id {
703 return Some(format!("/dev/{name_str}"));
704 }
705 }
706 None
707 }
708
709 fn mount_disk(spec: &DiskMountSpec, fstypes: Option<&[String]>) -> AgentdResult<()> {
710 let path = spec.guest_path.as_str();
711 fs::create_dir_all(path)
712 .map_err(|e| AgentdError::Init(format!("disk mount: create dir {path}: {e}")))?;
713
714 let device = resolve_disk_device(&spec.id)?;
715
716 let mut flags = MsFlags::MS_RELATIME;
717 if spec.nosuid {
718 flags |= MsFlags::MS_NOSUID;
719 }
720 if spec.nodev {
721 flags |= MsFlags::MS_NODEV;
722 }
723 if spec.noexec {
724 flags |= MsFlags::MS_NOEXEC;
725 }
726 if spec.readonly {
727 flags |= MsFlags::MS_RDONLY;
728 }
729
730 if let Some(fstype) = spec.fstype.as_deref() {
731 let data = disk_mount_data(fstype, spec.readonly);
732 mount::mount(Some(device.as_str()), path, Some(fstype), flags, data).map_err(|e| {
733 AgentdError::Init(format!(
734 "disk mount: failed to mount {device} at {path} as {fstype}: {e}"
735 ))
736 })?;
737 } else {
738 let fstypes = fstypes.ok_or_else(|| {
739 AgentdError::Init("disk mount: missing filesystem autodetect list".into())
740 })?;
741 try_mount_disk_any(&device, path, flags, spec.readonly, fstypes)?;
742 }
743
744 Ok(())
745 }
746
747 pub fn apply_tmpfs_mounts(specs: &[TmpfsSpec]) -> AgentdResult<()> {
749 for spec in specs {
750 mount_tmpfs(spec)?;
751 }
752 Ok(())
753 }
754
755 pub fn ensure_standard_tmp_permissions() -> AgentdResult<()> {
757 ensure_directory_mode("/tmp", 0o1777)?;
758 ensure_directory_mode("/var/tmp", 0o1777)?;
759 Ok(())
760 }
761
762 fn mount_tmpfs(spec: &TmpfsSpec) -> AgentdResult<()> {
764 let path = spec.path.as_str();
765
766 let mode = spec
768 .mode
769 .unwrap_or(if path == "/tmp" || path == "/var/tmp" {
770 0o1777
771 } else {
772 0o755
773 });
774
775 fs::create_dir_all(path)
777 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
778
779 let mut flags = MsFlags::MS_RELATIME;
780 if spec.nosuid {
781 flags |= MsFlags::MS_NOSUID;
782 }
783 if spec.nodev {
784 flags |= MsFlags::MS_NODEV;
785 }
786 if spec.noexec {
787 flags |= MsFlags::MS_NOEXEC;
788 }
789 if spec.readonly {
790 flags |= MsFlags::MS_RDONLY;
791 }
792
793 let mut data = String::new();
795 if let Some(mib) = spec.size_mib {
796 data.push_str(&format!("size={}", u64::from(mib) * 1024 * 1024));
797 }
798 if !data.is_empty() {
799 data.push(',');
800 }
801 data.push_str(&format!("mode={mode:o}"));
802
803 mount::mount(
804 Some("tmpfs"),
805 path,
806 Some("tmpfs"),
807 flags,
808 Some(data.as_str()),
809 )
810 .map_err(|e| AgentdError::Init(format!("failed to mount tmpfs at {path}: {e}")))?;
811
812 Ok(())
813 }
814
815 pub fn create_run_dir() -> AgentdResult<()> {
822 mkdir_ignore_exists("/run")?;
823 mkdir_ignore_exists("/run/microsandbox")?;
824 Ok(())
825 }
826
827 pub fn ensure_scripts_path_in_profile() -> AgentdResult<()> {
829 let profile_path = Path::new("/etc/profile");
830 let existing = match fs::read_to_string(profile_path) {
831 Ok(contents) => contents,
832 Err(err) if err.kind() == std::io::ErrorKind::NotFound => String::new(),
833 Err(err) => {
834 return Err(AgentdError::Init(format!(
835 "failed to read {}: {err}",
836 profile_path.display()
837 )));
838 }
839 };
840
841 let updated = super::ensure_scripts_profile_block(&existing);
842 if updated != existing {
843 if let Some(parent) = profile_path.parent() {
844 fs::create_dir_all(parent).map_err(|err| {
845 AgentdError::Init(format!("failed to create {}: {err}", parent.display()))
846 })?;
847 }
848 fs::write(profile_path, updated).map_err(|err| {
849 AgentdError::Init(format!("failed to write {}: {err}", profile_path.display()))
850 })?;
851 }
852
853 Ok(())
854 }
855
856 fn mkdir_ignore_exists(path: &str) -> AgentdResult<()> {
858 match unistd::mkdir(path, Mode::from_bits_truncate(0o755)) {
859 Ok(()) => Ok(()),
860 Err(nix::Error::EEXIST) => Ok(()),
861 Err(e) => Err(e.into()),
862 }
863 }
864
865 fn ensure_directory_mode(path: &str, mode: u32) -> AgentdResult<()> {
866 fs::create_dir_all(path)
867 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
868
869 let metadata = fs::metadata(path)
870 .map_err(|e| AgentdError::Init(format!("failed to stat {path}: {e}")))?;
871 if !metadata.is_dir() {
872 return Err(AgentdError::Init(format!(
873 "expected directory at {path}, found non-directory"
874 )));
875 }
876
877 let current_mode = metadata.permissions().mode() & 0o7777;
878 if current_mode != mode {
879 fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|e| {
880 AgentdError::Init(format!("failed to chmod {path} to {mode:o}: {e}"))
881 })?;
882 }
883
884 Ok(())
885 }
886
887 fn mount_ignore_busy(
889 source: Option<&str>,
890 target: &str,
891 fstype: Option<&str>,
892 flags: MsFlags,
893 data: Option<&str>,
894 ) -> AgentdResult<()> {
895 match mount::mount(source, target, fstype, flags, data) {
896 Ok(()) => Ok(()),
897 Err(nix::Error::EBUSY) => Ok(()),
898 Err(e) => Err(AgentdError::Init(format!("failed to mount {target}: {e}"))),
899 }
900 }
901}
902
903#[cfg(test)]
908mod tests {
909 use super::*;
910
911 #[test]
912 fn test_ensure_scripts_profile_block_appends_block() {
913 let updated = ensure_scripts_profile_block("export PATH=/usr/bin:/bin\n");
914 assert!(updated.contains("# >>> microsandbox scripts path >>>"));
915 assert!(updated.contains("export PATH=\"/.msb/scripts:$PATH\""));
916 }
917
918 #[test]
919 fn test_ensure_scripts_profile_block_adds_newline_when_missing() {
920 let updated = ensure_scripts_profile_block("export PATH=/usr/bin:/bin");
921 assert!(updated.contains("/usr/bin:/bin\n# >>> microsandbox scripts path >>>"));
922 }
923
924 #[test]
925 fn test_ensure_scripts_profile_block_is_idempotent() {
926 let profile = ensure_scripts_profile_block("");
927 let updated = ensure_scripts_profile_block(&profile);
928 assert_eq!(profile, updated);
929 }
930}