Build the underlying [`Command`] with the mount-namespace setup and chroot installed as a `pre_exec` hook.
(self, args: impl IntoIterator<Item = S>)
| 70 | /// Build the underlying [`Command`] with the mount-namespace |
| 71 | /// setup and chroot installed as a `pre_exec` hook. |
| 72 | fn build_command<S: AsRef<OsStr>>(self, args: impl IntoIterator<Item = S>) -> Result<Command> { |
| 73 | let mut args_iter = args.into_iter(); |
| 74 | let program = args_iter |
| 75 | .next() |
| 76 | .context("ChrootCmd requires the program as the first arg")?; |
| 77 | |
| 78 | // mount() requires its target directories to exist. |
| 79 | let proc_target = self.chroot_path.join("proc"); |
| 80 | let dev_target = self.chroot_path.join("dev"); |
| 81 | let sys_target = self.chroot_path.join("sys"); |
| 82 | let run_target = self.chroot_path.join("run"); |
| 83 | for p in [&proc_target, &dev_target, &sys_target, &run_target] { |
| 84 | create_dir_all(p).with_context(|| format!("Creating {p}"))?; |
| 85 | } |
| 86 | |
| 87 | // Convert paths to CStrings up front so the pre_exec closure |
| 88 | // below stays allocation-free. |
| 89 | let proc_target = CString::new(proc_target.as_str())?; |
| 90 | let dev_target = CString::new(dev_target.as_str())?; |
| 91 | let sys_target = CString::new(sys_target.as_str())?; |
| 92 | let run_target = CString::new(run_target.as_str())?; |
| 93 | |
| 94 | let user_binds: Vec<(CString, CString)> = self |
| 95 | .bind_mounts |
| 96 | .iter() |
| 97 | .map(|(src, tgt)| -> Result<_> { |
| 98 | let tgt_in_chroot = self.chroot_path.join(tgt.trim_start_matches('/')); |
| 99 | create_dir_all(&tgt_in_chroot) |
| 100 | .with_context(|| format!("Creating bind target {tgt_in_chroot}"))?; |
| 101 | Ok((CString::new(*src)?, CString::new(tgt_in_chroot.as_str())?)) |
| 102 | }) |
| 103 | .collect::<Result<_>>()?; |
| 104 | |
| 105 | let chroot_cstr = CString::new(self.chroot_path.as_str())?; |
| 106 | |
| 107 | let mut cmd = Command::new(program); |
| 108 | cmd.args(args_iter); |
| 109 | cmd.env_clear().envs(self.env_vars.iter().copied()); |
| 110 | |
| 111 | // SAFETY: All operations below are safe to invoke between |
| 112 | // fork and exec — only rustix-wrapped syscalls and iteration |
| 113 | // over CStrings allocated above. |
| 114 | #[allow(unsafe_code)] |
| 115 | unsafe { |
| 116 | cmd.pre_exec(move || { |
| 117 | unshare_unsafe(UnshareFlags::NEWNS)?; |
| 118 | |
| 119 | // Recursively mark every mount in our new namespace as |
| 120 | // PRIVATE. This both prevents the mounts we add below |
| 121 | // from leaking back to the host, and ensures that those |
| 122 | // mounts inherit PRIVATE propagation from their parent. |
| 123 | mount_change( |
| 124 | c"/", |
| 125 | MountPropagationFlags::PRIVATE | MountPropagationFlags::REC, |
| 126 | )?; |
| 127 | |
| 128 | // Bind-mount the chroot target onto itself so that `/` |
| 129 | // appears as a real mount point after chroot. Without |