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 crate::teardown::register_upper_fs(upperfs_dir);
277
278 let upper_dir = format!("{upperfs_dir}/upper");
280 let work_dir = format!("{upperfs_dir}/work");
281 fs::create_dir_all(&upper_dir)
282 .map_err(|e| AgentdError::Init(format!("mkdir {upper_dir}: {e}")))?;
283 fs::create_dir_all(&work_dir)
284 .map_err(|e| AgentdError::Init(format!("mkdir {work_dir}: {e}")))?;
285
286 let mount_data = format!("lowerdir={lower_dir},upperdir={upper_dir},workdir={work_dir}");
288
289 mount::mount(
290 Some("overlay"),
291 "/newroot",
292 Some("overlay"),
293 MsFlags::empty(),
294 Some(mount_data.as_str()),
295 )
296 .map_err(|e| AgentdError::Init(format!("mount overlay at /newroot: {e}")))?;
297
298 Ok(())
299 }
300
301 fn register_upper_metrics(upperfs_dir: &str) {
302 for attempt in 0..UPPER_METRICS_REGISTER_ATTEMPTS {
303 match fs::write(UPPER_METRICS_PATH, upperfs_dir) {
304 Ok(()) => return,
305 Err(err)
306 if err.kind() == std::io::ErrorKind::NotFound
307 && attempt + 1 < UPPER_METRICS_REGISTER_ATTEMPTS =>
308 {
309 thread::sleep(UPPER_METRICS_REGISTER_RETRY);
310 }
311 Err(err) if err.kind() == std::io::ErrorKind::NotFound => return,
312 Err(err) => {
313 eprintln!("agentd: upper metrics registration failed: {err}");
314 return;
315 }
316 }
317 }
318 }
319
320 fn pivot_to_newroot() -> AgentdResult<()> {
322 let msb_target = "/newroot/.msb";
323 mkdir_ignore_exists(msb_target)?;
324 mount::mount(
325 Some(microsandbox_protocol::RUNTIME_MOUNT_POINT),
326 msb_target,
327 None::<&str>,
328 MsFlags::MS_BIND,
329 None::<&str>,
330 )
331 .map_err(|e| AgentdError::Init(format!("failed to bind-mount /.msb into /newroot: {e}")))?;
332
333 unistd::chdir("/newroot")
334 .map_err(|e| AgentdError::Init(format!("failed to chdir /newroot: {e}")))?;
335
336 mount::mount(Some("."), "/", None::<&str>, MsFlags::MS_MOVE, None::<&str>)
337 .map_err(|e| AgentdError::Init(format!("failed to MS_MOVE /newroot to /: {e}")))?;
338
339 unistd::chroot(".").map_err(|e| AgentdError::Init(format!("failed to chroot: {e}")))?;
340
341 unistd::chdir("/")
342 .map_err(|e| AgentdError::Init(format!("failed to chdir / after chroot: {e}")))?;
343
344 mount_filesystems()?;
345
346 Ok(())
347 }
348
349 fn read_proc_filesystems() -> AgentdResult<Vec<String>> {
352 let content = fs::read_to_string("/proc/filesystems")
353 .map_err(|e| AgentdError::Init(format!("failed to read /proc/filesystems: {e}")))?;
354 Ok(content
355 .lines()
356 .filter_map(|line| {
357 if line.starts_with("nodev") {
358 return None;
359 }
360 let fstype = line.trim();
361 if fstype.is_empty() {
362 None
363 } else {
364 Some(fstype.to_string())
365 }
366 })
367 .collect())
368 }
369
370 fn try_mount_any(
375 device: &str,
376 target: &str,
377 flags: MsFlags,
378 fstypes: &[String],
379 ) -> AgentdResult<()> {
380 for fstype in fstypes {
381 if mount::mount(
382 Some(device),
383 target,
384 Some(fstype.as_str()),
385 flags,
386 None::<&str>,
387 )
388 .is_ok()
389 {
390 return Ok(());
391 }
392 }
393 Err(AgentdError::Init(format!(
394 "failed to mount {device} at {target}: no supported filesystem found"
395 )))
396 }
397
398 fn disk_mount_data(fstype: &str, readonly: bool) -> Option<&'static str> {
400 if readonly && fstype == "ext4" {
401 Some("noload")
405 } else {
406 None
407 }
408 }
409
410 fn try_mount_disk_any(
413 device: &str,
414 target: &str,
415 flags: MsFlags,
416 readonly: bool,
417 fstypes: &[String],
418 ) -> AgentdResult<()> {
419 for fstype in fstypes {
420 let data = disk_mount_data(fstype, readonly);
421 if mount::mount(Some(device), target, Some(fstype.as_str()), flags, data).is_ok() {
422 return Ok(());
423 }
424 }
425 Err(AgentdError::Init(format!(
426 "disk mount: failed to mount {device} at {target}: no supported filesystem found"
427 )))
428 }
429
430 pub fn apply_dir_mounts(specs: &[DirMountSpec]) -> AgentdResult<()> {
432 for spec in specs {
433 mount_dir(spec)?;
434 }
435 Ok(())
436 }
437
438 fn mount_dir(spec: &DirMountSpec) -> AgentdResult<()> {
440 let path = spec.guest_path.as_str();
441
442 fs::create_dir_all(path)
444 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
445
446 let mut flags = MsFlags::MS_RELATIME;
447 if spec.nosuid {
448 flags |= MsFlags::MS_NOSUID;
449 }
450 if spec.nodev {
451 flags |= MsFlags::MS_NODEV;
452 }
453 if spec.noexec {
454 flags |= MsFlags::MS_NOEXEC;
455 }
456 if spec.readonly {
457 flags |= MsFlags::MS_RDONLY;
458 }
459
460 mount::mount(
461 Some(spec.tag.as_str()),
462 path,
463 Some("virtiofs"),
464 flags,
465 None::<&str>,
466 )
467 .map_err(|e| {
468 AgentdError::Init(format!(
469 "failed to mount virtiofs tag '{}' at {path}: {e}",
470 spec.tag
471 ))
472 })?;
473
474 Ok(())
475 }
476
477 pub fn apply_file_mounts(specs: &[FileMountSpec]) -> AgentdResult<()> {
479 if specs.is_empty() {
480 return Ok(());
481 }
482
483 fs::create_dir_all(microsandbox_protocol::FILE_MOUNTS_DIR).map_err(|e| {
485 AgentdError::Init(format!(
486 "failed to create file mounts dir {}: {e}",
487 microsandbox_protocol::FILE_MOUNTS_DIR
488 ))
489 })?;
490
491 for spec in specs {
492 mount_file(spec)?;
493 }
494
495 let _ = fs::remove_dir(microsandbox_protocol::FILE_MOUNTS_DIR);
498
499 Ok(())
500 }
501
502 fn mount_file(spec: &FileMountSpec) -> AgentdResult<()> {
504 let staging_path = format!("{}/{}", microsandbox_protocol::FILE_MOUNTS_DIR, spec.tag);
505
506 fs::create_dir_all(&staging_path).map_err(|e| {
508 AgentdError::Init(format!("failed to create staging dir {staging_path}: {e}"))
509 })?;
510
511 let mut flags = MsFlags::MS_RELATIME;
513 if spec.nosuid {
514 flags |= MsFlags::MS_NOSUID;
515 }
516 if spec.nodev {
517 flags |= MsFlags::MS_NODEV;
518 }
519 if spec.noexec {
520 flags |= MsFlags::MS_NOEXEC;
521 }
522 if spec.readonly {
523 flags |= MsFlags::MS_RDONLY;
524 }
525
526 mount::mount(
527 Some(spec.tag.as_str()),
528 staging_path.as_str(),
529 Some("virtiofs"),
530 flags,
531 None::<&str>,
532 )
533 .map_err(|e| {
534 AgentdError::Init(format!(
535 "failed to mount virtiofs tag '{}' at {staging_path}: {e}",
536 spec.tag
537 ))
538 })?;
539
540 let bind_result = (|| {
541 let guest = Path::new(&spec.guest_path);
543 if let Some(parent) = guest.parent() {
544 fs::create_dir_all(parent).map_err(|e| {
545 AgentdError::Init(format!(
546 "failed to create parent dirs for {}: {e}",
547 spec.guest_path
548 ))
549 })?;
550 }
551
552 fs::OpenOptions::new()
554 .create(true)
555 .truncate(false)
556 .write(true)
557 .open(&spec.guest_path)
558 .map_err(|e| {
559 AgentdError::Init(format!(
560 "failed to create bind target {}: {e}",
561 spec.guest_path
562 ))
563 })?;
564
565 let source_path = format!("{staging_path}/{}", spec.filename);
567 mount::mount(
568 Some(source_path.as_str()),
569 spec.guest_path.as_str(),
570 None::<&str>,
571 MsFlags::MS_BIND,
572 None::<&str>,
573 )
574 .map_err(|e| {
575 AgentdError::Init(format!(
576 "failed to bind mount {source_path} to {}: {e}",
577 spec.guest_path
578 ))
579 })?;
580
581 let mut remount_flags = MsFlags::MS_BIND | MsFlags::MS_REMOUNT;
583 if spec.nosuid {
584 remount_flags |= MsFlags::MS_NOSUID;
585 }
586 if spec.nodev {
587 remount_flags |= MsFlags::MS_NODEV;
588 }
589 if spec.noexec {
590 remount_flags |= MsFlags::MS_NOEXEC;
591 }
592 if spec.readonly {
593 remount_flags |= MsFlags::MS_RDONLY;
594 }
595 mount::mount(
596 None::<&str>,
597 spec.guest_path.as_str(),
598 None::<&str>,
599 remount_flags,
600 None::<&str>,
601 )
602 .map_err(|e| {
603 AgentdError::Init(format!(
604 "failed to remount {} with volume flags: {e}",
605 spec.guest_path
606 ))
607 })?;
608
609 Ok(())
610 })();
611
612 let cleanup_result = cleanup_file_mount_staging(&staging_path);
613 match (bind_result, cleanup_result) {
614 (Ok(()), Ok(())) => Ok(()),
615 (Err(err), Ok(())) => Err(err),
616 (Ok(()), Err(err)) => Err(err),
617 (Err(err), Err(cleanup_err)) => Err(AgentdError::Init(format!(
618 "{err}; additionally failed to cleanup file mount staging {staging_path}: {cleanup_err}"
619 ))),
620 }
621 }
622
623 fn cleanup_file_mount_staging(staging_path: &str) -> AgentdResult<()> {
624 mount::umount2(staging_path, MntFlags::MNT_DETACH).map_err(|e| {
627 AgentdError::Init(format!(
628 "failed to unmount file mount staging {staging_path}: {e}"
629 ))
630 })?;
631 fs::remove_dir(staging_path).map_err(|e| {
632 AgentdError::Init(format!(
633 "failed to remove file mount staging {staging_path}: {e}"
634 ))
635 })?;
636 Ok(())
637 }
638
639 pub fn apply_disk_mounts(specs: &[DiskMountSpec]) -> AgentdResult<()> {
641 if specs.is_empty() {
642 return Ok(());
643 }
644 let fstypes = if specs.iter().any(|spec| spec.fstype.is_none()) {
647 Some(read_proc_filesystems()?)
648 } else {
649 None
650 };
651 for spec in specs {
652 mount_disk(spec, fstypes.as_deref())?;
653 }
654 Ok(())
655 }
656
657 fn resolve_disk_device(id: &str) -> AgentdResult<String> {
665 use std::{thread::sleep, time::Duration};
666 const RETRIES: u32 = 20;
667 const INTERVAL: Duration = Duration::from_millis(10);
668
669 let by_id = format!("/dev/disk/by-id/virtio-{id}");
670 for attempt in 0..RETRIES {
671 if Path::new(&by_id).exists() {
672 return Ok(by_id);
673 }
674 if let Some(dev) = scan_block_serial(id) {
675 return Ok(dev);
676 }
677 if attempt + 1 < RETRIES {
680 sleep(INTERVAL);
681 }
682 }
683 Err(AgentdError::Init(format!(
684 "disk mount: no block device found for id '{id}' \
685 (checked /dev/disk/by-id/virtio-{id} and /sys/block/*/serial)"
686 )))
687 }
688
689 fn scan_block_serial(id: &str) -> Option<String> {
691 let entries = fs::read_dir("/sys/block").ok()?;
692 for entry in entries.flatten() {
693 let name = entry.file_name();
694 let Some(name_str) = name.to_str() else {
695 continue;
696 };
697 if !name_str.starts_with("vd") {
698 continue;
699 }
700 let serial_path = entry.path().join("serial");
701 let Ok(serial) = fs::read_to_string(&serial_path) else {
702 continue;
703 };
704 if serial.trim() == id {
705 return Some(format!("/dev/{name_str}"));
706 }
707 }
708 None
709 }
710
711 fn mount_disk(spec: &DiskMountSpec, fstypes: Option<&[String]>) -> AgentdResult<()> {
712 let path = spec.guest_path.as_str();
713 fs::create_dir_all(path)
714 .map_err(|e| AgentdError::Init(format!("disk mount: create dir {path}: {e}")))?;
715
716 let device = resolve_disk_device(&spec.id)?;
717
718 let mut flags = MsFlags::MS_RELATIME;
719 if spec.nosuid {
720 flags |= MsFlags::MS_NOSUID;
721 }
722 if spec.nodev {
723 flags |= MsFlags::MS_NODEV;
724 }
725 if spec.noexec {
726 flags |= MsFlags::MS_NOEXEC;
727 }
728 if spec.readonly {
729 flags |= MsFlags::MS_RDONLY;
730 }
731
732 if let Some(fstype) = spec.fstype.as_deref() {
733 let data = disk_mount_data(fstype, spec.readonly);
734 mount::mount(Some(device.as_str()), path, Some(fstype), flags, data).map_err(|e| {
735 AgentdError::Init(format!(
736 "disk mount: failed to mount {device} at {path} as {fstype}: {e}"
737 ))
738 })?;
739 } else {
740 let fstypes = fstypes.ok_or_else(|| {
741 AgentdError::Init("disk mount: missing filesystem autodetect list".into())
742 })?;
743 try_mount_disk_any(&device, path, flags, spec.readonly, fstypes)?;
744 }
745
746 Ok(())
747 }
748
749 pub fn apply_tmpfs_mounts(specs: &[TmpfsSpec]) -> AgentdResult<()> {
751 for spec in specs {
752 mount_tmpfs(spec)?;
753 }
754 Ok(())
755 }
756
757 pub fn ensure_standard_tmp_permissions() -> AgentdResult<()> {
759 ensure_directory_mode("/tmp", 0o1777)?;
760 ensure_directory_mode("/var/tmp", 0o1777)?;
761 Ok(())
762 }
763
764 fn mount_tmpfs(spec: &TmpfsSpec) -> AgentdResult<()> {
766 let path = spec.path.as_str();
767
768 let mode = spec
770 .mode
771 .unwrap_or(if path == "/tmp" || path == "/var/tmp" {
772 0o1777
773 } else {
774 0o755
775 });
776
777 fs::create_dir_all(path)
779 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
780
781 let mut flags = MsFlags::MS_RELATIME;
782 if spec.nosuid {
783 flags |= MsFlags::MS_NOSUID;
784 }
785 if spec.nodev {
786 flags |= MsFlags::MS_NODEV;
787 }
788 if spec.noexec {
789 flags |= MsFlags::MS_NOEXEC;
790 }
791 if spec.readonly {
792 flags |= MsFlags::MS_RDONLY;
793 }
794
795 let mut data = String::new();
797 if let Some(mib) = spec.size_mib {
798 data.push_str(&format!("size={}", u64::from(mib) * 1024 * 1024));
799 }
800 if !data.is_empty() {
801 data.push(',');
802 }
803 data.push_str(&format!("mode={mode:o}"));
804
805 mount::mount(
806 Some("tmpfs"),
807 path,
808 Some("tmpfs"),
809 flags,
810 Some(data.as_str()),
811 )
812 .map_err(|e| AgentdError::Init(format!("failed to mount tmpfs at {path}: {e}")))?;
813
814 Ok(())
815 }
816
817 pub fn create_run_dir() -> AgentdResult<()> {
824 mkdir_ignore_exists("/run")?;
825 mkdir_ignore_exists("/run/microsandbox")?;
826 Ok(())
827 }
828
829 pub fn ensure_scripts_path_in_profile() -> AgentdResult<()> {
831 let profile_path = Path::new("/etc/profile");
832 let existing = match fs::read_to_string(profile_path) {
833 Ok(contents) => contents,
834 Err(err) if err.kind() == std::io::ErrorKind::NotFound => String::new(),
835 Err(err) => {
836 return Err(AgentdError::Init(format!(
837 "failed to read {}: {err}",
838 profile_path.display()
839 )));
840 }
841 };
842
843 let updated = super::ensure_scripts_profile_block(&existing);
844 if updated != existing {
845 if let Some(parent) = profile_path.parent() {
846 fs::create_dir_all(parent).map_err(|err| {
847 AgentdError::Init(format!("failed to create {}: {err}", parent.display()))
848 })?;
849 }
850 fs::write(profile_path, updated).map_err(|err| {
851 AgentdError::Init(format!("failed to write {}: {err}", profile_path.display()))
852 })?;
853 }
854
855 Ok(())
856 }
857
858 fn mkdir_ignore_exists(path: &str) -> AgentdResult<()> {
860 match unistd::mkdir(path, Mode::from_bits_truncate(0o755)) {
861 Ok(()) => Ok(()),
862 Err(nix::Error::EEXIST) => Ok(()),
863 Err(e) => Err(e.into()),
864 }
865 }
866
867 fn ensure_directory_mode(path: &str, mode: u32) -> AgentdResult<()> {
868 fs::create_dir_all(path)
869 .map_err(|e| AgentdError::Init(format!("failed to create directory {path}: {e}")))?;
870
871 let metadata = fs::metadata(path)
872 .map_err(|e| AgentdError::Init(format!("failed to stat {path}: {e}")))?;
873 if !metadata.is_dir() {
874 return Err(AgentdError::Init(format!(
875 "expected directory at {path}, found non-directory"
876 )));
877 }
878
879 let current_mode = metadata.permissions().mode() & 0o7777;
880 if current_mode != mode {
881 fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|e| {
882 AgentdError::Init(format!("failed to chmod {path} to {mode:o}: {e}"))
883 })?;
884 }
885
886 Ok(())
887 }
888
889 fn mount_ignore_busy(
891 source: Option<&str>,
892 target: &str,
893 fstype: Option<&str>,
894 flags: MsFlags,
895 data: Option<&str>,
896 ) -> AgentdResult<()> {
897 match mount::mount(source, target, fstype, flags, data) {
898 Ok(()) => Ok(()),
899 Err(nix::Error::EBUSY) => Ok(()),
900 Err(e) => Err(AgentdError::Init(format!("failed to mount {target}: {e}"))),
901 }
902 }
903}
904
905#[cfg(test)]
910mod tests {
911 use super::*;
912
913 #[test]
914 fn test_ensure_scripts_profile_block_appends_block() {
915 let updated = ensure_scripts_profile_block("export PATH=/usr/bin:/bin\n");
916 assert!(updated.contains("# >>> microsandbox scripts path >>>"));
917 assert!(updated.contains("export PATH=\"/.msb/scripts:$PATH\""));
918 }
919
920 #[test]
921 fn test_ensure_scripts_profile_block_adds_newline_when_missing() {
922 let updated = ensure_scripts_profile_block("export PATH=/usr/bin:/bin");
923 assert!(updated.contains("/usr/bin:/bin\n# >>> microsandbox scripts path >>>"));
924 }
925
926 #[test]
927 fn test_ensure_scripts_profile_block_is_idempotent() {
928 let profile = ensure_scripts_profile_block("");
929 let updated = ensure_scripts_profile_block(&profile);
930 assert_eq!(profile, updated);
931 }
932}