rustutils_chroot/
lib.rs

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/// Run command with root directory set to a different directory.
12#[derive(Parser, Clone, Debug)]
13#[clap(author, version, about, long_about = None)]
14pub struct Chroot {
15    /// Specify supplementary groups.
16    #[clap(long)]
17    groups: Vec<String>,
18    /// Specify user and group to use.
19    #[clap(long)]
20    userspec: Option<UserSpec>,
21    /// Do not change working directory to `/`.
22    #[clap(long)]
23    skip_chdir: bool,
24    /// New root directory to run command in.
25    newroot: PathBuf,
26    /// Command to run.
27    ///
28    /// If no command is given, runs `$SHELL -i`.
29    #[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    /// Change root directory.
71    fn change_root(&self) -> Result<(), Box<dyn Error>> {
72        nix::unistd::chroot(&self.newroot)?;
73        Ok(())
74    }
75
76    /// Change current directory to the new root.
77    fn change_directory(&self) -> Result<(), Box<dyn Error>> {
78        std::env::set_current_dir(&Path::new("/"))?;
79        Ok(())
80    }
81
82    /// Determine command to run.
83    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}