tartarus_api/config.rs
1#[cfg(feature = "config_file")]
2pub mod file;
3pub mod overrides;
4
5use crate::{
6 error::Result,
7 sandbox::{Sandbox, SandboxType},
8};
9use std::{
10 fmt::Debug,
11 fs::File,
12 io::{BufReader, BufWriter, Read, Write},
13 path::{Path, PathBuf},
14};
15
16/// The main configuration struct for the sandbox.
17///
18/// If at least one override or passthrough home directory is specified, the sandbox will use a
19/// temporary directory for the home directory instead of the user's actual one. The original home
20/// directory can still be accessed via absolute paths (and will be read-only by default like any
21/// other directory not explicitly specified as writable).
22#[derive(Debug)]
23pub struct SandboxConfig {
24 /// Whether network access is allowed for the sandbox.
25 pub allow_network_access: bool,
26
27 /// The directories to mounted as writable for the sandbox.
28 pub writable_dirs: Option<Vec<PathBuf>>,
29
30 /// Directories to recursively copy into the sandbox under a temporary directory rather than
31 /// mapping the real ones.
32 ///
33 /// Overriden home directories are mounted as writable for the sandbox.
34 pub override_home_dirs: Option<Vec<OverrideHomeDir>>,
35
36 /// Directories to directly mount from the user's home directory under the override home
37 /// directory.
38 ///
39 /// Passthrough directories are mounted as writable for the sandbox.
40 pub passthrough_home_dirs: Option<Vec<PathBuf>>,
41}
42
43impl Default for SandboxConfig {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49impl SandboxConfig {
50 /// Creates a new configuration with no network access, no writable directories, and no fake
51 /// home directory
52 pub const fn new() -> Self {
53 Self {
54 allow_network_access: false,
55 writable_dirs: None,
56 override_home_dirs: None,
57 passthrough_home_dirs: None,
58 }
59 }
60
61 /// Enables network access for the sandbox.
62 pub const fn with_network_access(&mut self) -> &mut Self {
63 self.allow_network_access = true;
64 self
65 }
66
67 /// Marks a directory as writable for the sandbox.
68 pub fn with_writable_dir(&mut self, dir: impl Into<PathBuf>) -> &mut Self {
69 self.writable_dirs.get_or_insert_default().push(dir.into());
70 self
71 }
72
73 /// Marks a subdirectory of the user's home as an override.
74 pub fn with_override_home_dir(&mut self, override_home_dir: OverrideHomeDir) -> &mut Self {
75 self.override_home_dirs
76 .get_or_insert_default()
77 .push(override_home_dir);
78 self
79 }
80
81 /// Marks a subdirectory of the user's home as passthrough.
82 pub fn with_passthrough_home_dir(
83 &mut self,
84 segments: impl IntoIterator<Item = impl AsRef<Path>>,
85 ) -> &mut Self {
86 self.passthrough_home_dirs
87 .get_or_insert_default()
88 .push(PathBuf::from_iter(segments));
89 self
90 }
91
92 /// Builds a sandbox with the given type.
93 pub const fn build_sandbox(&mut self, sandbox_type: SandboxType) -> Sandbox {
94 Sandbox {
95 sandbox_type,
96 config: Self {
97 allow_network_access: self.allow_network_access,
98 writable_dirs: self.writable_dirs.take(),
99 override_home_dirs: self.override_home_dirs.take(),
100 passthrough_home_dirs: self.passthrough_home_dirs.take(),
101 },
102 }
103 }
104
105 /// Returns whether the sandbox should configure a fake home directory.
106 pub(crate) fn needs_fake_home(&self) -> bool {
107 self.override_home_dirs
108 .as_ref()
109 .is_some_and(|v| !v.is_empty()) ||
110 self.passthrough_home_dirs
111 .as_ref()
112 .is_some_and(|v| !v.is_empty())
113 }
114}
115
116/// A directory under the home directory to recursively copy into the sandbox under a temporary
117/// directory rather than mapping the real one.
118#[derive(Debug)]
119pub struct OverrideHomeDir {
120 /// The path of the directory to copy from the host filesystem. These paths are interpreted as
121 /// relative paths below the user's home directory.
122 pub subpath: PathBuf,
123
124 /// Arbitrary extra settings to apply to files mapped to the sandbox
125 pub overrides: Option<Vec<OverrideFile<Box<dyn Override>>>>,
126}
127
128impl OverrideHomeDir {
129 /// Creates a new override home directory for the given path.
130 pub fn new(path_segments: impl IntoIterator<Item = impl AsRef<Path>>) -> Self {
131 Self {
132 subpath: PathBuf::from_iter(path_segments),
133 overrides: None,
134 }
135 }
136
137 /// Adds an override for the given path with the given behavior.
138 pub fn with_override(
139 &mut self,
140 path: impl Into<PathBuf>,
141 behavior: impl Override + 'static,
142 ) -> &mut Self {
143 self.overrides.get_or_insert_default().push(OverrideFile {
144 path: path.into(),
145 behavior: Box::new(behavior),
146 });
147 self
148 }
149
150 /// Helper function to get an owned [`OverrideHomeDir`] instance from a mutable reference to
151 /// facilitate builder-style chaining.
152 #[must_use]
153 pub fn take(&mut self) -> Self {
154 Self {
155 subpath: std::mem::take(&mut self.subpath),
156 overrides: self.overrides.take(),
157 }
158 }
159}
160
161/// Specifies how to override a file from the user's home directory in the sandbox.
162#[derive(Debug)]
163pub struct OverrideFile<T> {
164 pub path: PathBuf,
165 pub behavior: T,
166}
167
168/// Provides lazy access to the file being overridden in the sandbox and a sink for the contents of
169/// the file to override with in the sandbox.
170#[derive(Debug)]
171pub struct FileContents {
172 pub(crate) original: BufReader<File>,
173 pub(crate) output: BufWriter<File>,
174}
175
176impl FileContents {
177 /// Returns a [`Read`] instance for reading the original file contents.
178 pub fn read(&mut self) -> impl Read {
179 &mut self.original
180 }
181
182 /// Returns a [`Write`] instance for writing the sandboxed file contents.
183 pub fn write(&mut self) -> impl Write {
184 &mut self.output
185 }
186}
187
188/// A trait providing a way to define how a file should be overridden in the sandbox.
189///
190/// This can be used to inject extra configurations to the sandbox that you don't want to live in
191/// your standard configurations (bypassing permission checks, allowing arbitrary tool usage, etc.)
192pub trait Override: Debug {
193 /// Defines how the original file should be processed and mapped to the sandbox.
194 ///
195 /// # Arguments
196 ///
197 /// - `contents`: Provides access to the original file contents for reading and the sandboxed
198 /// output file for writing.
199 ///
200 /// # Errors
201 ///
202 /// Returns an error if the setting could not be applied.
203 fn apply(&self, contents: FileContents) -> Result<()>;
204}