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