tor_interface/
arti_process.rs1use std::fs;
3use std::fs::File;
4use std::io::{BufRead, BufReader, Write};
5use std::ops::Drop;
6#[cfg(unix)]
7use std::os::unix::fs::PermissionsExt;
8use std::path::Path;
9use std::process::{Child, ChildStdout, Command, Stdio};
10use std::sync::{Mutex, Weak};
11
12#[derive(thiserror::Error, Debug)]
13pub enum Error {
14 #[error("provided arti bin path '{0}' must be an absolute path")]
15 ArtiBinPathNotAbsolute(String),
16
17 #[error("provided data directory '{0}' must be an absolute path")]
18 ArtiDataDirectoryPathNotAbsolute(String),
19
20 #[error("failed to create data directory: {0}")]
21 ArtiDataDirectoryCreationFailed(#[source] std::io::Error),
22
23 #[error("file exists in provided data directory path '{0}'")]
24 ArtiDataDirectoryPathExistsAsFile(String),
25
26 #[error("unable to set permissions for data directory: {0}")]
27 ArtiDataDirectorySetPermissionsFailed(#[source] std::io::Error),
28
29 #[error("failed to create arti.toml file: {0}")]
30 ArtiTomlFileCreationFailed(#[source] std::io::Error),
31
32 #[error("failed to write arti.toml file: {0}")]
33 ArtiTomlFileWriteFailed(#[source] std::io::Error),
34
35 #[error("failed to create rpc.toml file: {0}")]
36 RpcTomlFileCreationFailed(#[source] std::io::Error),
37
38 #[error("failed to write rpc.toml file: {0}")]
39 RpcTomlFileWriteFailed(#[source] std::io::Error),
40
41 #[error("failed to start arti process: {0}")]
42 ArtiProcessStartFailed(#[source] std::io::Error),
43
44 #[error("unable to take arti process stdout")]
45 ArtiProcessStdoutTakeFailed(),
46
47 #[error("failed to spawn arti process stdout read thread: {0}")]
48 ArtiStdoutReadThreadSpawnFailed(#[source] std::io::Error),
49}
50
51pub(crate) struct ArtiProcess {
52 process: Child,
53 connect_string: String,
54}
55
56impl ArtiProcess {
57 pub fn new(
58 arti_bin_path: &Path,
59 data_directory: &Path,
60 stdout_lines: Weak<Mutex<Vec<String>>>,
61 ) -> Result<Self, Error> {
62 if arti_bin_path.is_relative() {
64 return Err(Error::ArtiBinPathNotAbsolute(format!(
65 "{}",
66 arti_bin_path.display()
67 )));
68 }
69 if data_directory.is_relative() {
70 return Err(Error::ArtiDataDirectoryPathNotAbsolute(format!(
71 "{}",
72 data_directory.display()
73 )));
74 }
75
76 if !data_directory.exists() {
78 fs::create_dir_all(data_directory).map_err(Error::ArtiDataDirectoryCreationFailed)?;
79 } else if data_directory.is_file() {
80 return Err(Error::ArtiDataDirectoryPathExistsAsFile(format!(
81 "{}",
82 data_directory.display()
83 )));
84 }
85
86 #[cfg(unix)]
88 fs::set_permissions(data_directory, PermissionsExt::from_mode(0o700))
89 .map_err(Error::ArtiDataDirectorySetPermissionsFailed)?;
90
91 let arti_toml = data_directory.join("arti.toml");
93 let cache_dir_string = data_directory
94 .join("cache")
95 .display()
96 .to_string()
97 .escape_default()
98 .to_string();
99 let state_dir_string = data_directory
100 .join("state")
101 .display()
102 .to_string()
103 .escape_default()
104 .to_string();
105
106 let mut arti_toml_content = format!(
107 "\
108 [rpc]\n\
109 enable = true\n\n\
110 [rpc.listen.user-default]\n\
111 enable = false\n\n\
112 [rpc.listen.system-default]\n\
113 enable = false\n\n\
114 [storage]\n\
115 cache_dir = \"{cache_dir_string}\"\n\
116 state_dir = \"{state_dir_string}\"\n\n\
117 [storage.keystore]\n\
118 enabled = true\n\n\
119 [storage.keystore.primary]\n\
120 kind = \"ephemeral\"\n\n\
121 [storage.permissions]\n\
122 dangerously_trust_everyone = true\n\n\
123 "
124 );
125
126 let connect_string = if cfg!(unix) {
127 let unix_rpc_toml_path = data_directory.join("rpc.toml");
129 let unix_rpc_toml_path_string = unix_rpc_toml_path
130 .display()
131 .to_string()
132 .escape_default()
133 .to_string();
134
135 arti_toml_content.push_str(
136 format!(
137 "\
138 [rpc.listen.unix-point]\n\
139 enable = true\n\
140 file = \"{unix_rpc_toml_path_string}\"\n\n\
141 "
142 )
143 .as_str(),
144 );
145
146 let socket_path = data_directory
147 .join("rpc.socket")
148 .display()
149 .to_string()
150 .escape_default()
151 .to_string();
152
153 let unix_rpc_toml_content = format!(
154 "\
155 [connect]\n\
156 socket = \"unix:{socket_path}\"\n\
157 auth = \"none\"\n\
158 "
159 );
160
161 let mut unix_rpc_toml_file =
162 File::create(&unix_rpc_toml_path).map_err(Error::RpcTomlFileCreationFailed)?;
163 unix_rpc_toml_file
164 .write_all(unix_rpc_toml_content.as_bytes())
165 .map_err(Error::RpcTomlFileWriteFailed)?;
166
167 unix_rpc_toml_path_string
168 } else {
169 let tcp_rpc_toml_path = data_directory.join("rpc.toml");
171 let tcp_rpc_toml_path_string = tcp_rpc_toml_path
172 .display()
173 .to_string()
174 .escape_default()
175 .to_string();
176
177 arti_toml_content.push_str(
178 format!(
179 "\
180 [rpc.listen.tcp-point]\n\
181 enable = true\n\
182 file = \"{tcp_rpc_toml_path_string}\"\n\n\
183 "
184 )
185 .as_str(),
186 );
187
188 let cookie_path_string = data_directory
189 .join("rpc.cookie")
190 .display()
191 .to_string()
192 .escape_default()
193 .to_string();
194
195 const RPC_PORT: u16 = 18929;
196
197 let tcp_rpc_toml_content = format!(
198 "\
199 [connect]\n\
200 socket = \"inet:127.0.0.1:{RPC_PORT}\"\n\
201 auth = {{ cookie = {{ path = \"{cookie_path_string}\" }} }}\n\
202 "
203 );
204
205 let mut tcp_rpc_toml_file =
206 File::create(&tcp_rpc_toml_path).map_err(Error::RpcTomlFileCreationFailed)?;
207 tcp_rpc_toml_file
208 .write_all(tcp_rpc_toml_content.as_bytes())
209 .map_err(Error::RpcTomlFileWriteFailed)?;
210
211 tcp_rpc_toml_path_string
212 };
213
214 let mut arti_toml_file =
215 File::create(&arti_toml).map_err(Error::ArtiTomlFileCreationFailed)?;
216 arti_toml_file
217 .write_all(arti_toml_content.as_bytes())
218 .map_err(Error::ArtiTomlFileWriteFailed)?;
219
220 let mut process = Command::new(arti_bin_path.as_os_str())
221 .stdout(Stdio::piped())
222 .stdin(Stdio::null())
223 .stderr(Stdio::null())
224 .current_dir(data_directory)
226 .arg("proxy")
228 .arg("--config")
230 .arg(arti_toml)
231 .spawn()
232 .map_err(Error::ArtiProcessStartFailed)?;
233
234 let stdout = BufReader::new(match process.stdout.take() {
236 Some(stdout) => stdout,
237 None => return Err(Error::ArtiProcessStdoutTakeFailed()),
238 });
239 std::thread::Builder::new()
240 .name("arti_stdout_reader".to_string())
241 .spawn(move || {
242 ArtiProcess::read_stdout_task(&stdout_lines, stdout);
243 })
244 .map_err(Error::ArtiStdoutReadThreadSpawnFailed)?;
245
246 Ok(ArtiProcess {
247 process,
248 connect_string,
249 })
250 }
251
252 pub fn connect_string(&self) -> &str {
253 self.connect_string.as_str()
254 }
255
256 fn read_stdout_task(
257 stdout_lines: &std::sync::Weak<Mutex<Vec<String>>>,
258 mut stdout: BufReader<ChildStdout>,
259 ) {
260 while let Some(stdout_lines) = stdout_lines.upgrade() {
261 let mut line = String::default();
262 if stdout.read_line(&mut line).is_ok() {
264 line.pop();
266 let mut stdout_lines = match stdout_lines.lock() {
268 Ok(stdout_lines) => stdout_lines,
269 Err(_) => unreachable!(),
270 };
271 stdout_lines.push(line);
272 }
273 }
274 }
275}
276
277impl Drop for ArtiProcess {
278 fn drop(&mut self) {
279 let _ = self.process.kill();
280 }
281}