tor_interface/
arti_process.rs1use std::fs;
3#[cfg(unix)]
4use std::os::unix::fs::PermissionsExt;
5use std::fs::File;
6use std::io::{BufRead, BufReader, Write};
7use std::ops::Drop;
8use std::process::{Child, ChildStdout, Command, Stdio};
9use std::path::Path;
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(arti_bin_path: &Path, data_directory: &Path, stdout_lines: Weak<Mutex<Vec<String>>>) -> Result<Self, Error> {
58 if arti_bin_path.is_relative() {
60 return Err(Error::ArtiBinPathNotAbsolute(format!(
61 "{}",
62 arti_bin_path.display()
63 )));
64 }
65 if data_directory.is_relative() {
66 return Err(Error::ArtiDataDirectoryPathNotAbsolute(format!(
67 "{}",
68 data_directory.display()
69 )));
70 }
71
72 if !data_directory.exists() {
74 fs::create_dir_all(data_directory).map_err(Error::ArtiDataDirectoryCreationFailed)?;
75 } else if data_directory.is_file() {
76 return Err(Error::ArtiDataDirectoryPathExistsAsFile(format!(
77 "{}",
78 data_directory.display()
79 )));
80 }
81
82 #[cfg(unix)]
84 fs::set_permissions(
85 data_directory,
86 PermissionsExt::from_mode(0o700))
87 .map_err(Error::ArtiDataDirectorySetPermissionsFailed)?;
88
89 let arti_toml = data_directory.join("arti.toml");
91 let cache_dir_string = data_directory.join("cache").display().to_string().escape_default().to_string();
92 let state_dir_string = data_directory.join("state").display().to_string().escape_default().to_string();
93
94 let mut arti_toml_content = format!("\
95 [rpc]\n\
96 enable = true\n\n\
97 [rpc.listen.user-default]\n\
98 enable = false\n\n\
99 [rpc.listen.system-default]\n\
100 enable = false\n\n\
101 [storage]\n\
102 cache_dir = \"{cache_dir_string}\"\n\
103 state_dir = \"{state_dir_string}\"\n\n\
104 [storage.keystore]\n\
105 enabled = true\n\n\
106 [storage.keystore.primary]\n\
107 kind = \"ephemeral\"\n\n\
108 [storage.permissions]\n\
109 dangerously_trust_everyone = true\n\n\
110 ");
111
112 let connect_string = if cfg!(unix) {
113 let unix_rpc_toml_path = data_directory.join("rpc.toml");
115 let unix_rpc_toml_path_string = unix_rpc_toml_path.display().to_string().escape_default().to_string();
116
117 arti_toml_content.push_str(format!("\
118 [rpc.listen.unix-point]\n\
119 enable = true\n\
120 file = \"{unix_rpc_toml_path_string}\"\n\n\
121 ").as_str());
122
123 let socket_path = data_directory.join("rpc.socket").display().to_string().escape_default().to_string();
124
125 let unix_rpc_toml_content = format!("\
126 [connect]\n\
127 socket = \"unix:{socket_path}\"\n\
128 auth = \"none\"\n\
129 ");
130
131 let mut unix_rpc_toml_file =
132 File::create(&unix_rpc_toml_path).map_err(Error::RpcTomlFileCreationFailed)?;
133 unix_rpc_toml_file
134 .write_all(unix_rpc_toml_content.as_bytes())
135 .map_err(Error::RpcTomlFileWriteFailed)?;
136
137 unix_rpc_toml_path_string
138 } else {
139 let tcp_rpc_toml_path = data_directory.join("rpc.toml");
141 let tcp_rpc_toml_path_string = tcp_rpc_toml_path.display().to_string().escape_default().to_string();
142
143 arti_toml_content.push_str(format!("\
144 [rpc.listen.tcp-point]\n\
145 enable = true\n\
146 file = \"{tcp_rpc_toml_path_string}\"\n\n\
147 ").as_str());
148
149 let cookie_path_string = data_directory.join("rpc.cookie").display().to_string().escape_default().to_string();
150
151 const RPC_PORT: u16 = 18929;
152
153 let tcp_rpc_toml_content = format!("\
154 [connect]\n\
155 socket = \"inet:127.0.0.1:{RPC_PORT}\"\n\
156 auth = {{ cookie = {{ path = \"{cookie_path_string}\" }} }}\n\
157 ");
158
159 let mut tcp_rpc_toml_file =
160 File::create(&tcp_rpc_toml_path).map_err(Error::RpcTomlFileCreationFailed)?;
161 tcp_rpc_toml_file
162 .write_all(tcp_rpc_toml_content.as_bytes())
163 .map_err(Error::RpcTomlFileWriteFailed)?;
164
165 tcp_rpc_toml_path_string
166 };
167
168 let mut arti_toml_file =
169 File::create(&arti_toml).map_err(Error::ArtiTomlFileCreationFailed)?;
170 arti_toml_file
171 .write_all(arti_toml_content.as_bytes())
172 .map_err(Error::ArtiTomlFileWriteFailed)?;
173
174 let mut process = Command::new(arti_bin_path.as_os_str())
175 .stdout(Stdio::piped())
176 .stdin(Stdio::null())
177 .stderr(Stdio::null())
178 .current_dir(data_directory)
180 .arg("proxy")
182 .arg("--config")
184 .arg(arti_toml)
185 .spawn()
186 .map_err(Error::ArtiProcessStartFailed)?;
187
188 let stdout = BufReader::new(match process.stdout.take() {
190 Some(stdout) => stdout,
191 None => return Err(Error::ArtiProcessStdoutTakeFailed()),
192 });
193 std::thread::Builder::new()
194 .name("arti_stdout_reader".to_string())
195 .spawn(move || {
196 ArtiProcess::read_stdout_task(&stdout_lines, stdout);
197 })
198 .map_err(Error::ArtiStdoutReadThreadSpawnFailed)?;
199
200 Ok(ArtiProcess { process, connect_string })
201 }
202
203 pub fn connect_string(&self) -> &str {
204 self.connect_string.as_str()
205 }
206
207 fn read_stdout_task(
208 stdout_lines: &std::sync::Weak<Mutex<Vec<String>>>,
209 mut stdout: BufReader<ChildStdout>,
210 ) {
211 while let Some(stdout_lines) = stdout_lines.upgrade() {
212 let mut line = String::default();
213 if stdout.read_line(&mut line).is_ok() {
215 line.pop();
217 let mut stdout_lines = match stdout_lines.lock() {
219 Ok(stdout_lines) => stdout_lines,
220 Err(_) => unreachable!(),
221 };
222 stdout_lines.push(line);
223 }
224 }
225 }
226}
227
228impl Drop for ArtiProcess {
229 fn drop(&mut self) {
230 let _ = self.process.kill();
231 }
232}