1use clap::Parser;
2use rustutils_runnable::Runnable;
3use std::error::Error;
4use std::ffi::OsString;
5use std::path::{Path, PathBuf};
6use std::process::Command;
7use std::os::unix::process::CommandExt;
8
9pub const DEFAULT_SHELL: &str = "/bin/sh";
10
11#[derive(Parser, Clone, Debug)]
13#[clap(author, version, about, long_about = None)]
14pub struct Chroot {
15 #[clap(long)]
17 groups: Vec<String>,
18 #[clap(long)]
20 userspec: Option<UserSpec>,
21 #[clap(long)]
23 skip_chdir: bool,
24 newroot: PathBuf,
26 #[clap(last = true)]
30 command: Vec<OsString>,
31}
32
33#[derive(Debug, Clone)]
34pub struct UserSpec {
35 pub user: String,
36 pub group: String,
37}
38
39impl std::str::FromStr for UserSpec {
40 type Err = String;
41 fn from_str(spec: &str) -> Result<Self, Self::Err> {
42 let mut parts = spec.split(":");
43 let user = parts.next().ok_or("Missing user".to_string())?.to_string();
44 let group = parts.next().ok_or("Missing group".to_string())?.to_string();
45 Ok(UserSpec {
46 user,
47 group,
48 })
49 }
50}
51
52impl Chroot {
53 pub fn run(&self) -> Result<(), Box<dyn Error>> {
54 if !self.groups.is_empty() || self.userspec.is_some() {
55 unimplemented!()
56 }
57
58 self.change_root()?;
59
60 if !self.skip_chdir {
61 self.change_directory()?;
62 }
63
64 let mut command = self.command();
65 command.exec();
66
67 Ok(())
68 }
69
70 fn change_root(&self) -> Result<(), Box<dyn Error>> {
72 nix::unistd::chroot(&self.newroot)?;
73 Ok(())
74 }
75
76 fn change_directory(&self) -> Result<(), Box<dyn Error>> {
78 std::env::set_current_dir(&Path::new("/"))?;
79 Ok(())
80 }
81
82 pub fn command(&self) -> Command {
84 if self.command.is_empty() {
85 let shell = std::env::var_os("SHELL")
86 .unwrap_or_else(|| DEFAULT_SHELL.into());
87 let mut command = Command::new(shell);
88 command.arg("-i");
89 command
90 } else {
91 let mut command = Command::new(&self.command[0]);
92 command.args(self.command.iter().skip(1));
93 command
94 }
95 }
96}
97
98impl Runnable for Chroot {
99 fn run(&self) -> Result<(), Box<dyn Error>> {
100 self.run()?;
101 Ok(())
102 }
103}