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 fn disk_mount_data(fstype: &str, readonly: bool) -> Option<&'static str> {
374 if readonly && fstype == "ext4" {
375 Some("noload")
379 } else {
380 None
381 }
382 }
383
384 fn try_mount_disk_any(
387 device: &str,
388 target: &str,
389 flags: MsFlags,
390 readonly: bool,
391 fstypes: &[String],
392 ) -> AgentdResult<()> {
393 for fstype in fstypes {
394 let data = disk_mount_data(fstype, readonly);
395 if mount::mount(Some(device), target, Some(fstype.as_str()), flags, data).is_ok() {
396 return Ok(());
397 }
398 }
399 Err(AgentdError::Init(format!(
400 "disk mount: failed to mount {device} at {target}: no supported filesystem found"
401 )))
402 }
403
404 pub fn apply_dir_mounts(specs: &[DirMountSpec]) -> AgentdResult<()> {
406 for spec in specs {
407 mount_dir(spec)?;
408 }
409 Ok(())
410 }
411
412 fn mount_dir(spec: &DirMountSpec) -> AgentdResult<()> {
414 let path = spec.guest_path.as_str();
415
416 fs::create_dir_all(path)
418 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
419
420 let mut flags = MsFlags::MS_RELATIME;
421 if spec.nosuid {
422 flags |= MsFlags::MS_NOSUID;
423 }
424 if spec.nodev {
425 flags |= MsFlags::MS_NODEV;
426 }
427 if spec.noexec {
428 flags |= MsFlags::MS_NOEXEC;
429 }
430 if spec.readonly {
431 flags |= MsFlags::MS_RDONLY;
432 }
433
434 mount::mount(
435 Some(spec.tag.as_str()),
436 path,
437 Some("virtiofs"),
438 flags,
439 None::<&str>,
440 )
441 .map_err(|e| {
442 AgentdError::Init(format!(
443 "failed to mount virtiofs tag '{}' at {path}: {e}",
444 spec.tag
445 ))
446 })?;
447
448 Ok(())
449 }
450
451 pub fn apply_file_mounts(specs: &[FileMountSpec]) -> AgentdResult<()> {
453 if specs.is_empty() {
454 return Ok(());
455 }
456
457 fs::create_dir_all(microsandbox_protocol::FILE_MOUNTS_DIR).map_err(|e| {
459 AgentdError::Init(format!(
460 "failed to create file mounts dir {}: {e}",
461 microsandbox_protocol::FILE_MOUNTS_DIR
462 ))
463 })?;
464
465 for spec in specs {
466 mount_file(spec)?;
467 }
468
469 let _ = fs::remove_dir(microsandbox_protocol::FILE_MOUNTS_DIR);
472
473 Ok(())
474 }
475
476 fn mount_file(spec: &FileMountSpec) -> AgentdResult<()> {
478 let staging_path = format!("{}/{}", microsandbox_protocol::FILE_MOUNTS_DIR, spec.tag);
479
480 fs::create_dir_all(&staging_path).map_err(|e| {
482 AgentdError::Init(format!("failed to create staging dir {staging_path}: {e}"))
483 })?;
484
485 let mut flags = MsFlags::MS_RELATIME;
487 if spec.nosuid {
488 flags |= MsFlags::MS_NOSUID;
489 }
490 if spec.nodev {
491 flags |= MsFlags::MS_NODEV;
492 }
493 if spec.noexec {
494 flags |= MsFlags::MS_NOEXEC;
495 }
496 if spec.readonly {
497 flags |= MsFlags::MS_RDONLY;
498 }
499
500 mount::mount(
501 Some(spec.tag.as_str()),
502 staging_path.as_str(),
503 Some("virtiofs"),
504 flags,
505 None::<&str>,
506 )
507 .map_err(|e| {
508 AgentdError::Init(format!(
509 "failed to mount virtiofs tag '{}' at {staging_path}: {e}",
510 spec.tag
511 ))
512 })?;
513
514 let bind_result = (|| {
515 let guest = Path::new(&spec.guest_path);
517 if let Some(parent) = guest.parent() {
518 fs::create_dir_all(parent).map_err(|e| {
519 AgentdError::Init(format!(
520 "failed to create parent dirs for {}: {e}",
521 spec.guest_path
522 ))
523 })?;
524 }
525
526 fs::OpenOptions::new()
528 .create(true)
529 .truncate(false)
530 .write(true)
531 .open(&spec.guest_path)
532 .map_err(|e| {
533 AgentdError::Init(format!(
534 "failed to create bind target {}: {e}",
535 spec.guest_path
536 ))
537 })?;
538
539 let source_path = format!("{staging_path}/{}", spec.filename);
541 mount::mount(
542 Some(source_path.as_str()),
543 spec.guest_path.as_str(),
544 None::<&str>,
545 MsFlags::MS_BIND,
546 None::<&str>,
547 )
548 .map_err(|e| {
549 AgentdError::Init(format!(
550 "failed to bind mount {source_path} to {}: {e}",
551 spec.guest_path
552 ))
553 })?;
554
555 let mut remount_flags = MsFlags::MS_BIND | MsFlags::MS_REMOUNT;
557 if spec.nosuid {
558 remount_flags |= MsFlags::MS_NOSUID;
559 }
560 if spec.nodev {
561 remount_flags |= MsFlags::MS_NODEV;
562 }
563 if spec.noexec {
564 remount_flags |= MsFlags::MS_NOEXEC;
565 }
566 if spec.readonly {
567 remount_flags |= MsFlags::MS_RDONLY;
568 }
569 mount::mount(
570 None::<&str>,
571 spec.guest_path.as_str(),
572 None::<&str>,
573 remount_flags,
574 None::<&str>,
575 )
576 .map_err(|e| {
577 AgentdError::Init(format!(
578 "failed to remount {} with volume flags: {e}",
579 spec.guest_path
580 ))
581 })?;
582
583 Ok(())
584 })();
585
586 let cleanup_result = cleanup_file_mount_staging(&staging_path);
587 match (bind_result, cleanup_result) {
588 (Ok(()), Ok(())) => Ok(()),
589 (Err(err), Ok(())) => Err(err),
590 (Ok(()), Err(err)) => Err(err),
591 (Err(err), Err(cleanup_err)) => Err(AgentdError::Init(format!(
592 "{err}; additionally failed to cleanup file mount staging {staging_path}: {cleanup_err}"
593 ))),
594 }
595 }
596
597 fn cleanup_file_mount_staging(staging_path: &str) -> AgentdResult<()> {
598 mount::umount2(staging_path, MntFlags::MNT_DETACH).map_err(|e| {
601 AgentdError::Init(format!(
602 "failed to unmount file mount staging {staging_path}: {e}"
603 ))
604 })?;
605 fs::remove_dir(staging_path).map_err(|e| {
606 AgentdError::Init(format!(
607 "failed to remove file mount staging {staging_path}: {e}"
608 ))
609 })?;
610 Ok(())
611 }
612
613 pub fn apply_disk_mounts(specs: &[DiskMountSpec]) -> AgentdResult<()> {
615 if specs.is_empty() {
616 return Ok(());
617 }
618 let fstypes = if specs.iter().any(|spec| spec.fstype.is_none()) {
621 Some(read_proc_filesystems()?)
622 } else {
623 None
624 };
625 for spec in specs {
626 mount_disk(spec, fstypes.as_deref())?;
627 }
628 Ok(())
629 }
630
631 fn resolve_disk_device(id: &str) -> AgentdResult<String> {
639 use std::{thread::sleep, time::Duration};
640 const RETRIES: u32 = 20;
641 const INTERVAL: Duration = Duration::from_millis(10);
642
643 let by_id = format!("/dev/disk/by-id/virtio-{id}");
644 for attempt in 0..RETRIES {
645 if Path::new(&by_id).exists() {
646 return Ok(by_id);
647 }
648 if let Some(dev) = scan_block_serial(id) {
649 return Ok(dev);
650 }
651 if attempt + 1 < RETRIES {
654 sleep(INTERVAL);
655 }
656 }
657 Err(AgentdError::Init(format!(
658 "disk mount: no block device found for id '{id}' \
659 (checked /dev/disk/by-id/virtio-{id} and /sys/block/*/serial)"
660 )))
661 }
662
663 fn scan_block_serial(id: &str) -> Option<String> {
665 let entries = fs::read_dir("/sys/block").ok()?;
666 for entry in entries.flatten() {
667 let name = entry.file_name();
668 let Some(name_str) = name.to_str() else {
669 continue;
670 };
671 if !name_str.starts_with("vd") {
672 continue;
673 }
674 let serial_path = entry.path().join("serial");
675 let Ok(serial) = fs::read_to_string(&serial_path) else {
676 continue;
677 };
678 if serial.trim() == id {
679 return Some(format!("/dev/{name_str}"));
680 }
681 }
682 None
683 }
684
685 fn mount_disk(spec: &DiskMountSpec, fstypes: Option<&[String]>) -> AgentdResult<()> {
686 let path = spec.guest_path.as_str();
687 fs::create_dir_all(path)
688 .map_err(|e| AgentdError::Init(format!("disk mount: create dir {path}: {e}")))?;
689
690 let device = resolve_disk_device(&spec.id)?;
691
692 let mut flags = MsFlags::MS_RELATIME;
693 if spec.nosuid {
694 flags |= MsFlags::MS_NOSUID;
695 }
696 if spec.nodev {
697 flags |= MsFlags::MS_NODEV;
698 }
699 if spec.noexec {
700 flags |= MsFlags::MS_NOEXEC;
701 }
702 if spec.readonly {
703 flags |= MsFlags::MS_RDONLY;
704 }
705
706 if let Some(fstype) = spec.fstype.as_deref() {
707 let data = disk_mount_data(fstype, spec.readonly);
708 mount::mount(Some(device.as_str()), path, Some(fstype), flags, data).map_err(|e| {
709 AgentdError::Init(format!(
710 "disk mount: failed to mount {device} at {path} as {fstype}: {e}"
711 ))
712 })?;
713 } else {
714 let fstypes = fstypes.ok_or_else(|| {
715 AgentdError::Init("disk mount: missing filesystem autodetect list".into())
716 })?;
717 try_mount_disk_any(&device, path, flags, spec.readonly, fstypes)?;
718 }
719
720 Ok(())
721 }
722
723 pub fn apply_tmpfs_mounts(specs: &[TmpfsSpec]) -> AgentdResult<()> {
725 for spec in specs {
726 mount_tmpfs(spec)?;
727 }
728 Ok(())
729 }
730
731 pub fn ensure_standard_tmp_permissions() -> AgentdResult<()> {
733 ensure_directory_mode("/tmp", 0o1777)?;
734 ensure_directory_mode("/var/tmp", 0o1777)?;
735 Ok(())
736 }
737
738 fn mount_tmpfs(spec: &TmpfsSpec) -> AgentdResult<()> {
740 let path = spec.path.as_str();
741
742 let mode = spec
744 .mode
745 .unwrap_or(if path == "/tmp" || path == "/var/tmp" {
746 0o1777
747 } else {
748 0o755
749 });
750
751 fs::create_dir_all(path)
753 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
754
755 let mut flags = MsFlags::MS_RELATIME;
756 if spec.nosuid {
757 flags |= MsFlags::MS_NOSUID;
758 }
759 if spec.nodev {
760 flags |= MsFlags::MS_NODEV;
761 }
762 if spec.noexec {
763 flags |= MsFlags::MS_NOEXEC;
764 }
765 if spec.readonly {
766 flags |= MsFlags::MS_RDONLY;
767 }
768
769 let mut data = String::new();
771 if let Some(mib) = spec.size_mib {
772 data.push_str(&format!("size={}", u64::from(mib) * 1024 * 1024));
773 }
774 if !data.is_empty() {
775 data.push(',');
776 }
777 data.push_str(&format!("mode={mode:o}"));
778
779 mount::mount(
780 Some("tmpfs"),
781 path,
782 Some("tmpfs"),
783 flags,
784 Some(data.as_str()),
785 )
786 .map_err(|e| AgentdError::Init(format!("failed to mount tmpfs at {path}: {e}")))?;
787
788 Ok(())
789 }
790
791 pub fn create_run_dir() -> AgentdResult<()> {
798 mkdir_ignore_exists("/run")?;
799 mkdir_ignore_exists("/run/microsandbox")?;
800 Ok(())
801 }
802
803 pub fn ensure_scripts_path_in_profile() -> AgentdResult<()> {
805 let profile_path = Path::new("/etc/profile");
806 let existing = match fs::read_to_string(profile_path) {
807 Ok(contents) => contents,
808 Err(err) if err.kind() == std::io::ErrorKind::NotFound => String::new(),
809 Err(err) => {
810 return Err(AgentdError::Init(format!(
811 "failed to read {}: {err}",
812 profile_path.display()
813 )));
814 }
815 };
816
817 let updated = super::ensure_scripts_profile_block(&existing);
818 if updated != existing {
819 if let Some(parent) = profile_path.parent() {
820 fs::create_dir_all(parent).map_err(|err| {
821 AgentdError::Init(format!("failed to create {}: {err}", parent.display()))
822 })?;
823 }
824 fs::write(profile_path, updated).map_err(|err| {
825 AgentdError::Init(format!("failed to write {}: {err}", profile_path.display()))
826 })?;
827 }
828
829 Ok(())
830 }
831
832 fn mkdir_ignore_exists(path: &str) -> AgentdResult<()> {
834 match unistd::mkdir(path, Mode::from_bits_truncate(0o755)) {
835 Ok(()) => Ok(()),
836 Err(nix::Error::EEXIST) => Ok(()),
837 Err(e) => Err(e.into()),
838 }
839 }
840
841 fn ensure_directory_mode(path: &str, mode: u32) -> AgentdResult<()> {
842 fs::create_dir_all(path)
843 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
844
845 let metadata = fs::metadata(path)
846 .map_err(|e| AgentdError::Init(format!("failed to stat {path}: {e}")))?;
847 if !metadata.is_dir() {
848 return Err(AgentdError::Init(format!(
849 "expected directory at {path}, found non-directory"
850 )));
851 }
852
853 let current_mode = metadata.permissions().mode() & 0o7777;
854 if current_mode != mode {
855 fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|e| {
856 AgentdError::Init(format!("failed to chmod {path} to {mode:o}: {e}"))
857 })?;
858 }
859
860 Ok(())
861 }
862
863 fn mount_ignore_busy(
865 source: Option<&str>,
866 target: &str,
867 fstype: Option<&str>,
868 flags: MsFlags,
869 data: Option<&str>,
870 ) -> AgentdResult<()> {
871 match mount::mount(source, target, fstype, flags, data) {
872 Ok(()) => Ok(()),
873 Err(nix::Error::EBUSY) => Ok(()),
874 Err(e) => Err(AgentdError::Init(format!("failed to mount {target}: {e}"))),
875 }
876 }
877}
878
879#[cfg(test)]
884mod tests {
885 use super::*;
886
887 #[test]
888 fn test_ensure_scripts_profile_block_appends_block() {
889 let updated = ensure_scripts_profile_block("export PATH=/usr/bin:/bin\n");
890 assert!(updated.contains("# >>> microsandbox scripts path >>>"));
891 assert!(updated.contains("export PATH=\"/.msb/scripts:$PATH\""));
892 }
893
894 #[test]
895 fn test_ensure_scripts_profile_block_adds_newline_when_missing() {
896 let updated = ensure_scripts_profile_block("export PATH=/usr/bin:/bin");
897 assert!(updated.contains("/usr/bin:/bin\n# >>> microsandbox scripts path >>>"));
898 }
899
900 #[test]
901 fn test_ensure_scripts_profile_block_is_idempotent() {
902 let profile = ensure_scripts_profile_block("");
903 let updated = ensure_scripts_profile_block(&profile);
904 assert_eq!(profile, updated);
905 }
906}