]> git.lizzy.rs Git - rust.git/blob - src/tools/remote-test-server/src/main.rs
Don't build remote-test-server with the stage0 toolchain
[rust.git] / src / tools / remote-test-server / src / main.rs
1 //! This is a small server which is intended to run inside of an emulator or
2 //! on a remote test device. This server pairs with the `remote-test-client`
3 //! program in this repository. The `remote-test-client` connects to this
4 //! server over a TCP socket and performs work such as:
5 //!
6 //! 1. Pushing shared libraries to the server
7 //! 2. Running tests through the server
8 //!
9 //! The server supports running tests concurrently and also supports tests
10 //! themselves having support libraries. All data over the TCP sockets is in a
11 //! basically custom format suiting our needs.
12
13 #[cfg(not(windows))]
14 use std::fs::Permissions;
15 #[cfg(not(windows))]
16 use std::os::unix::prelude::*;
17
18 use std::cmp;
19 use std::env;
20 use std::fs::{self, File};
21 use std::io::prelude::*;
22 use std::io::{self, BufReader};
23 use std::net::{TcpListener, TcpStream};
24 use std::path::{Path, PathBuf};
25 use std::process::{Command, ExitStatus, Stdio};
26 use std::str;
27 use std::sync::atomic::{AtomicUsize, Ordering};
28 use std::sync::{Arc, Mutex};
29 use std::thread;
30
31 macro_rules! t {
32     ($e:expr) => {
33         match $e {
34             Ok(e) => e,
35             Err(e) => panic!("{} failed with {}", stringify!($e), e),
36         }
37     };
38 }
39
40 static TEST: AtomicUsize = AtomicUsize::new(0);
41
42 #[derive(Copy, Clone)]
43 struct Config {
44     pub remote: bool,
45     pub verbose: bool,
46 }
47
48 impl Config {
49     pub fn default() -> Config {
50         Config { remote: false, verbose: false }
51     }
52
53     pub fn parse_args() -> Config {
54         let mut config = Config::default();
55
56         let args = env::args().skip(1);
57         for argument in args {
58             match &argument[..] {
59                 "remote" => {
60                     config.remote = true;
61                 }
62                 "verbose" | "-v" => {
63                     config.verbose = true;
64                 }
65                 arg => panic!("unknown argument: {}", arg),
66             }
67         }
68
69         config
70     }
71 }
72
73 fn print_verbose(s: &str, conf: Config) {
74     if conf.verbose {
75         println!("{}", s);
76     }
77 }
78
79 fn main() {
80     println!("starting test server");
81
82     let config = Config::parse_args();
83
84     let bind_addr = if cfg!(target_os = "android") || cfg!(windows) || config.remote {
85         "0.0.0.0:12345"
86     } else {
87         "10.0.2.15:12345"
88     };
89
90     let listener = t!(TcpListener::bind(bind_addr));
91     let (work, tmp): (PathBuf, PathBuf) = if cfg!(target_os = "android") {
92         ("/data/tmp/work".into(), "/data/tmp/work/tmp".into())
93     } else {
94         let mut work_dir = env::temp_dir();
95         work_dir.push("work");
96         let mut tmp_dir = work_dir.clone();
97         tmp_dir.push("tmp");
98         (work_dir, tmp_dir)
99     };
100     println!("listening on {}!", bind_addr);
101
102     t!(fs::create_dir_all(&work));
103     t!(fs::create_dir_all(&tmp));
104
105     let lock = Arc::new(Mutex::new(()));
106
107     for socket in listener.incoming() {
108         let mut socket = t!(socket);
109         let mut buf = [0; 4];
110         if socket.read_exact(&mut buf).is_err() {
111             continue;
112         }
113         if &buf[..] == b"ping" {
114             print_verbose("Received ping", config);
115             t!(socket.write_all(b"pong"));
116         } else if &buf[..] == b"push" {
117             handle_push(socket, &work, config);
118         } else if &buf[..] == b"run " {
119             let lock = lock.clone();
120             let work = work.clone();
121             let tmp = tmp.clone();
122             thread::spawn(move || handle_run(socket, &work, &tmp, &lock, config));
123         } else {
124             panic!("unknown command {:?}", buf);
125         }
126     }
127 }
128
129 fn handle_push(socket: TcpStream, work: &Path, config: Config) {
130     let mut reader = BufReader::new(socket);
131     let dst = recv(&work, &mut reader);
132     print_verbose(&format!("push {:#?}", dst), config);
133
134     let mut socket = reader.into_inner();
135     t!(socket.write_all(b"ack "));
136 }
137
138 struct RemoveOnDrop<'a> {
139     inner: &'a Path,
140 }
141
142 impl Drop for RemoveOnDrop<'_> {
143     fn drop(&mut self) {
144         t!(fs::remove_dir_all(self.inner));
145     }
146 }
147
148 fn handle_run(socket: TcpStream, work: &Path, tmp: &Path, lock: &Mutex<()>, config: Config) {
149     let mut arg = Vec::new();
150     let mut reader = BufReader::new(socket);
151
152     // Allocate ourselves a directory that we'll delete when we're done to save
153     // space.
154     let n = TEST.fetch_add(1, Ordering::SeqCst);
155     let path = work.join(format!("test{}", n));
156     t!(fs::create_dir(&path));
157     let _a = RemoveOnDrop { inner: &path };
158
159     // First up we'll get a list of arguments delimited with 0 bytes. An empty
160     // argument means that we're done.
161     let mut args = Vec::new();
162     while t!(reader.read_until(0, &mut arg)) > 1 {
163         args.push(t!(str::from_utf8(&arg[..arg.len() - 1])).to_string());
164         arg.truncate(0);
165     }
166
167     // Next we'll get a bunch of env vars in pairs delimited by 0s as well
168     let mut env = Vec::new();
169     arg.truncate(0);
170     while t!(reader.read_until(0, &mut arg)) > 1 {
171         let key_len = arg.len() - 1;
172         let val_len = t!(reader.read_until(0, &mut arg)) - 1;
173         {
174             let key = &arg[..key_len];
175             let val = &arg[key_len + 1..][..val_len];
176             let key = t!(str::from_utf8(key)).to_string();
177             let val = t!(str::from_utf8(val)).to_string();
178             env.push((key, val));
179         }
180         arg.truncate(0);
181     }
182
183     // The section of code from here down to where we drop the lock is going to
184     // be a critical section for us. On Linux you can't execute a file which is
185     // open somewhere for writing, as you'll receive the error "text file busy".
186     // Now here we never have the text file open for writing when we spawn it,
187     // so why do we still need a critical section?
188     //
189     // Process spawning first involves a `fork` on Unix, which clones all file
190     // descriptors into the child process. This means that it's possible for us
191     // to open the file for writing (as we're downloading it), then some other
192     // thread forks, then we close the file and try to exec. At that point the
193     // other thread created a child process with the file open for writing, and
194     // we attempt to execute it, so we get an error.
195     //
196     // This race is resolve by ensuring that only one thread can write the file
197     // and spawn a child process at once. Kinda an unfortunate solution, but we
198     // don't have many other choices with this sort of setup!
199     //
200     // In any case the lock is acquired here, before we start writing any files.
201     // It's then dropped just after we spawn the child. That way we don't lock
202     // the execution of the child, just the creation of its files.
203     let lock = lock.lock();
204
205     // Next there's a list of dynamic libraries preceded by their filenames.
206     while t!(reader.fill_buf())[0] != 0 {
207         recv(&path, &mut reader);
208     }
209     assert_eq!(t!(reader.read(&mut [0])), 1);
210
211     // Finally we'll get the binary. The other end will tell us how big the
212     // binary is and then we'll download it all to the exe path we calculated
213     // earlier.
214     let exe = recv(&path, &mut reader);
215     print_verbose(&format!("run {:#?}", exe), config);
216
217     let mut cmd = Command::new(&exe);
218     cmd.args(args);
219     cmd.envs(env);
220
221     // Support libraries were uploaded to `work` earlier, so make sure that's
222     // in `LD_LIBRARY_PATH`. Also include our own current dir which may have
223     // had some libs uploaded.
224     if cfg!(windows) {
225         // On windows, libraries are just searched in the executable directory,
226         // system directories, PWD, and PATH, in that order. PATH is the only one
227         // we can change for this.
228         cmd.env(
229             "PATH",
230             env::join_paths(
231                 std::iter::once(work.to_owned())
232                     .chain(std::iter::once(path.clone()))
233                     .chain(env::split_paths(&env::var_os("PATH").unwrap())),
234             )
235             .unwrap(),
236         );
237     } else {
238         cmd.env("LD_LIBRARY_PATH", format!("{}:{}", work.display(), path.display()));
239     }
240
241     // Some tests assume RUST_TEST_TMPDIR exists
242     cmd.env("RUST_TEST_TMPDIR", tmp.to_owned());
243
244     // Spawn the child and ferry over stdout/stderr to the socket in a framed
245     // fashion (poor man's style)
246     let mut child =
247         t!(cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn());
248     drop(lock);
249     let mut stdout = child.stdout.take().unwrap();
250     let mut stderr = child.stderr.take().unwrap();
251     let socket = Arc::new(Mutex::new(reader.into_inner()));
252     let socket2 = socket.clone();
253     let thread = thread::spawn(move || my_copy(&mut stdout, 0, &*socket2));
254     my_copy(&mut stderr, 1, &*socket);
255     thread.join().unwrap();
256
257     // Finally send over the exit status.
258     let status = t!(child.wait());
259
260     let (which, code) = get_status_code(&status);
261
262     t!(socket.lock().unwrap().write_all(&[
263         which,
264         (code >> 24) as u8,
265         (code >> 16) as u8,
266         (code >> 8) as u8,
267         (code >> 0) as u8,
268     ]));
269 }
270
271 #[cfg(not(windows))]
272 fn get_status_code(status: &ExitStatus) -> (u8, i32) {
273     match status.code() {
274         Some(n) => (0, n),
275         None => (1, status.signal().unwrap()),
276     }
277 }
278
279 #[cfg(windows)]
280 fn get_status_code(status: &ExitStatus) -> (u8, i32) {
281     (0, status.code().unwrap())
282 }
283
284 fn recv<B: BufRead>(dir: &Path, io: &mut B) -> PathBuf {
285     let mut filename = Vec::new();
286     t!(io.read_until(0, &mut filename));
287
288     // We've got some tests with *really* long names. We try to name the test
289     // executable the same on the target as it is on the host to aid with
290     // debugging, but the targets we're emulating are often more restrictive
291     // than the hosts as well.
292     //
293     // To ensure we can run a maximum number of tests without modifications we
294     // just arbitrarily truncate the filename to 50 bytes. That should
295     // hopefully allow us to still identify what's running while staying under
296     // the filesystem limits.
297     let len = cmp::min(filename.len() - 1, 50);
298     let dst = dir.join(t!(str::from_utf8(&filename[..len])));
299     let amt = read_u32(io) as u64;
300     t!(io::copy(&mut io.take(amt), &mut t!(File::create(&dst))));
301     set_permissions(&dst);
302     dst
303 }
304
305 #[cfg(not(windows))]
306 fn set_permissions(path: &Path) {
307     t!(fs::set_permissions(&path, Permissions::from_mode(0o755)));
308 }
309 #[cfg(windows)]
310 fn set_permissions(_path: &Path) {}
311
312 fn my_copy(src: &mut dyn Read, which: u8, dst: &Mutex<dyn Write>) {
313     let mut b = [0; 1024];
314     loop {
315         let n = t!(src.read(&mut b));
316         let mut dst = dst.lock().unwrap();
317         t!(dst.write_all(&[
318             which,
319             (n >> 24) as u8,
320             (n >> 16) as u8,
321             (n >> 8) as u8,
322             (n >> 0) as u8,
323         ]));
324         if n > 0 {
325             t!(dst.write_all(&b[..n]));
326         } else {
327             break;
328         }
329     }
330 }
331
332 fn read_u32(r: &mut dyn Read) -> u32 {
333     let mut len = [0; 4];
334     t!(r.read_exact(&mut len));
335     ((len[0] as u32) << 24)
336         | ((len[1] as u32) << 16)
337         | ((len[2] as u32) << 8)
338         | ((len[3] as u32) << 0)
339 }