1use libc::user_regs_struct;
29use nix::sys::ptrace::{self, AddressType, Options};
30use nix::sys::signal::Signal;
31use nix::sys::wait::{self, WaitStatus};
32use nix::unistd::Pid;
33use std::io::Read;
34use std::mem;
35use std::sync::mpsc::{channel, Receiver, Sender};
36use std::thread::{self, JoinHandle};
37use thiserror::Error;
38
39#[derive(Debug, Error)]
44pub enum Error {
45 #[error("failed to access process ({pid})")]
46 ProcAccessFailed { pid: Pid },
47 #[error(transparent)]
48 Nix(#[from] nix::Error),
49 #[error(transparent)]
50 Recv(#[from] std::sync::mpsc::RecvError),
51 #[error(transparent)]
52 Send(#[from] std::sync::mpsc::SendError<Vec<u8>>),
53}
54
55#[cfg(target_arch = "x86_64")]
60type Word = u64;
61
62#[cfg(target_arch = "x86")]
63type Word = u32;
64
65#[cfg(target_arch = "x86_64")]
66const WORD_BYTES: u64 = 8;
67
68#[cfg(target_arch = "x86")]
69const WORD_BYTES: u32 = 4;
70
71#[cfg(target_arch = "x86_64")]
73#[derive(Clone, Copy, Debug)]
74struct UserRegs(user_regs_struct);
75
76#[cfg(target_arch = "x86")]
78#[derive(Clone, Copy, Debug)]
79struct UserRegs(user_regs_struct);
80
81#[cfg(target_arch = "x86_64")]
82#[allow(dead_code)]
83impl UserRegs {
84 fn syscall(self) -> Word {
85 self.0.orig_rax
86 }
87
88 fn ret(self) -> Word {
89 self.0.rax
90 }
91
92 fn arg1(self) -> Word {
93 self.0.rdi
94 }
95
96 fn arg2(self) -> Word {
97 self.0.rsi
98 }
99
100 fn arg3(self) -> Word {
101 self.0.rdx
102 }
103
104 fn arg4(self) -> Word {
105 self.0.r10
106 }
107
108 fn arg5(self) -> Word {
109 self.0.r8
110 }
111
112 fn arg6(self) -> Word {
113 self.0.r9
114 }
115}
116
117#[cfg(target_arch = "x86")]
118#[allow(dead_code)]
119impl UserRegs {
120 fn syscall(self) -> Word {
121 self.0.orig_eax
122 }
123
124 fn ret(self) -> Word {
125 self.0.eax
126 }
127
128 fn arg1(self) -> Word {
129 self.0.ebx
130 }
131
132 fn arg2(self) -> Word {
133 self.0.ecx
134 }
135
136 fn arg3(self) -> Word {
137 self.0.edx
138 }
139
140 fn arg4(self) -> Word {
141 self.0.esi
142 }
143
144 fn arg5(self) -> Word {
145 self.0.edi
146 }
147
148 fn arg6(self) -> Word {
149 self.0.ebp
150 }
151}
152
153enum ProcReaderMsg {
158 Stop,
159 Redirect,
160}
161
162pub struct ProcReader {
164 ctl: Sender<ProcReaderMsg>,
165 buf: Receiver<Vec<u8>>,
166 err: Receiver<Error>,
167 child: Option<JoinHandle<()>>,
168 rest: Vec<u8>,
169}
170
171#[derive(PartialEq)]
172enum StdType {
173 Any,
174 Out,
175 Err,
176}
177
178impl ProcReader {
179 pub fn from_stdany(pid: u32) -> Self {
181 let pid = Pid::from_raw(pid as i32);
182 ProcReader::new(pid, StdType::Any)
183 }
184
185 pub fn from_stdout(pid: u32) -> Self {
187 let pid = Pid::from_raw(pid as i32);
188 ProcReader::new(pid, StdType::Out)
189 }
190
191 pub fn from_stderr(pid: u32) -> Self {
193 let pid = Pid::from_raw(pid as i32);
194 ProcReader::new(pid, StdType::Err)
195 }
196
197 pub fn with_redirect(self) -> Self {
199 let _ = self.ctl.send(ProcReaderMsg::Redirect);
200 self
201 }
202
203 fn new(pid: Pid, typ: StdType) -> Self {
204 let (ctl_tx, ctl_rx) = channel();
205 let (buf_tx, buf_rx) = channel();
206 let (err_tx, err_rx) = channel();
207
208 let child = thread::spawn(
209 move || match ProcReader::collect(pid, typ, ctl_rx, buf_tx) {
210 Err(x) => {
211 let _ = err_tx.send(x);
212 }
213 _ => (),
214 },
215 );
216
217 ProcReader {
218 ctl: ctl_tx,
219 buf: buf_rx,
220 err: err_rx,
221 child: Some(child),
222 rest: Vec::new(),
223 }
224 }
225
226 fn collect(
227 pid: Pid,
228 typ: StdType,
229 ctl_rx: Receiver<ProcReaderMsg>,
230 buf_tx: Sender<Vec<u8>>,
231 ) -> Result<(), Error> {
232 ptrace::attach(pid).map_err(|_| Error::ProcAccessFailed { pid })?;
233 ProcReader::set_tracesysgood(pid).map_err(|_| Error::ProcAccessFailed { pid })?;
234
235 let mut pids = Vec::new();
237 pids.push(pid);
238
239 let mut fd = [0; 1024];
241 fd[1] = 1;
242 fd[2] = 2;
243 let mut fds = Vec::new();
244 fds.push(fd.clone());
245
246 let mut enable_redirect = false;
247 let mut is_syscall_before = false;
248 let mut prev_orig_rax = 0;
249
250 loop {
251 let mut pid = *pids.last().unwrap();
252 match wait::waitpid(pid, None).map_err(|_| Error::ProcAccessFailed { pid })? {
253 WaitStatus::PtraceSyscall(_) => {
254 let regs =
255 ProcReader::get_regs(pid).map_err(|_| Error::ProcAccessFailed { pid })?;
256
257 is_syscall_before = if prev_orig_rax == regs.syscall() {
258 !is_syscall_before
259 } else {
260 true
261 };
262 prev_orig_rax = regs.syscall();
263
264 if !is_syscall_before && enable_redirect {
265 fd = ProcReader::update_fd(fd, regs);
266 }
267
268 let sys_clone = regs.syscall() == libc::SYS_clone as Word;
269 let sys_fork = regs.syscall() == libc::SYS_fork as Word;
270 let sys_vfork = regs.syscall() == libc::SYS_vfork as Word;
271
272 if (sys_clone || sys_fork || sys_vfork) && !is_syscall_before {
273 pid = Pid::from_raw(regs.ret() as i32);
274 pids.push(pid);
275 fds.push(fd.clone());
276 continue;
277 }
278
279 if regs.syscall() == libc::SYS_write as Word && is_syscall_before {
280 let out = ProcReader::peek_bytes(pid, regs.arg2(), regs.arg3());
281 let out_typ = regs.arg1();
282
283 let send_stdout = fd[out_typ as usize] == 1
284 && (typ == StdType::Any || typ == StdType::Out);
285 let send_stderr = fd[out_typ as usize] == 2
286 && (typ == StdType::Any || typ == StdType::Err);
287
288 if send_stdout || send_stderr {
289 buf_tx.send(out)?;
290 }
291 }
292 }
293 WaitStatus::Exited(_, _) => {
294 pids.pop();
295 if pids.is_empty() {
296 break;
297 } else {
298 pid = *pids.last().unwrap();
299 fd = fds.pop().unwrap();
300 }
301 }
302 _ => (),
303 }
304
305 match ctl_rx.try_recv() {
306 Ok(ProcReaderMsg::Stop) => {
307 ptrace::detach(pid, None).map_err(|_| Error::ProcAccessFailed { pid })?;
308 break;
309 }
310 Ok(ProcReaderMsg::Redirect) => {
311 enable_redirect = true;
312 ptrace::syscall(pid, None).map_err(|_| Error::ProcAccessFailed { pid })?;
313 }
314 _ => {
315 ptrace::syscall(pid, None).map_err(|_| Error::ProcAccessFailed { pid })?;
316 }
317 }
318 }
319 Ok(())
320 }
321
322 fn set_tracesysgood(pid: Pid) -> Result<(), Error> {
323 loop {
324 match wait::waitpid(pid, None).map_err(|_| Error::ProcAccessFailed { pid })? {
325 WaitStatus::Stopped(_, Signal::SIGSTOP) => {
327 let opt = Options::PTRACE_O_TRACESYSGOOD
330 | Options::PTRACE_O_TRACECLONE
331 | Options::PTRACE_O_TRACEFORK
332 | Options::PTRACE_O_TRACEVFORK;
333 ptrace::setoptions(pid, opt).map_err(|_| Error::ProcAccessFailed { pid })?;
334 ptrace::syscall(pid, None).map_err(|_| Error::ProcAccessFailed { pid })?;
335 break;
336 }
337 _ => {
338 ptrace::syscall(pid, None).map_err(|_| Error::ProcAccessFailed { pid })?;
339 }
340 }
341 }
342
343 Ok(())
344 }
345
346 fn get_regs(pid: Pid) -> Result<UserRegs, Error> {
347 let regs = ptrace::getregs(pid).map(|x| UserRegs(x))?;
348 Ok(regs)
349 }
350
351 fn peek_bytes(pid: Pid, addr: Word, size: Word) -> Vec<u8> {
352 let mut vec = (0..(size + WORD_BYTES - 1) / WORD_BYTES)
353 .filter_map(|i| {
354 ptrace::read(pid, (addr + WORD_BYTES * i) as AddressType)
355 .map(|l| unsafe { mem::transmute(l) })
356 .ok()
357 })
358 .collect::<Vec<[u8; WORD_BYTES as usize]>>()
359 .concat();
360 vec.truncate(size as usize);
361 vec
362 }
363
364 fn update_fd(mut fd: [Word; 1024], regs: UserRegs) -> [Word; 1024] {
365 if regs.syscall() == libc::SYS_dup2 as Word {
367 let src = regs.arg1();
368 let dst = regs.arg2();
369 fd[dst as usize] = fd[src as usize];
370 }
371
372 if regs.syscall() == libc::SYS_fcntl as Word {
374 if regs.arg2() == libc::F_DUPFD as Word {
375 let src = regs.arg1();
376 let dst = regs.ret();
377 fd[dst as usize] = fd[src as usize];
378 }
379 }
380 fd
381 }
382}
383
384impl Read for ProcReader {
385 fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, std::io::Error> {
386 let err = match self.err.try_recv() {
387 Ok(x) => Some(x),
388 _ => None,
389 };
390
391 loop {
392 match self.buf.try_recv() {
393 Ok(mut x) => self.rest.append(&mut x),
394 Err(_) => break,
395 }
396 }
397
398 let len = if buf.len() >= self.rest.len() {
399 let len = self.rest.len();
400 self.rest.resize(buf.len(), 0);
401 buf.copy_from_slice(&self.rest);
402 self.rest.clear();
403 len
404 } else {
405 let len = buf.len();
406 let rest = self.rest.split_off(len);
407 buf.copy_from_slice(&self.rest);
408 self.rest = rest;
409 len
410 };
411
412 if len != 0 {
413 Ok(len)
414 } else if let Some(err) = err {
415 Err(std::io::Error::new(
416 std::io::ErrorKind::Other,
417 format!("{}", err),
418 ))
419 } else {
420 Ok(len)
421 }
422 }
423}
424
425impl Drop for ProcReader {
426 fn drop(&mut self) {
427 if self.child.is_some() {
428 let _ = self.ctl.send(ProcReaderMsg::Stop);
429 let _ = self.child.take().unwrap().join();
430 }
431 }
432}
433
434#[cfg(test)]
439mod tests {
440 use super::*;
441 use std::io::BufReader;
442 use std::process::Command;
443 use std::thread;
444 use std::time::Duration;
445
446 static SCRIPT: &'static str = r#"
447 sleep 1;
448 print "aaa\n";
449 sleep 1;
450 print "bbb\n";
451 sleep 1;
452 print "ccc\n";
453 "#;
454
455 static SCRIPT_WITH_ERR: &'static str = r#"
456 sleep 1;
457 print "aaa\n";
458 warn "eee\n";
459 "#;
460
461 static SCRIPT_REDIRECT: &'static str = r#"
462 sleep 1;
463 echo 'aaa';
464 echo 'bbb' > /dev/null 1>&2;
465 perl -e 'warn "ccc\n"' 2>&1;
466 perl -e 'warn "ddd\n"';
467 "#;
468
469 #[test]
470 fn test_bufreader() {
471 let child = Command::new("perl").arg("-e").arg(SCRIPT).spawn().unwrap();
472 let mut reader = BufReader::new(ProcReader::from_stdout(child.id()));
473
474 thread::sleep(Duration::from_secs(4));
475
476 let mut line = String::new();
477 let _ = reader.read_to_string(&mut line);
478 assert_eq!("aaa\nbbb\nccc\n", line);
479 }
480
481 #[test]
482 fn test_short_array() {
483 let child = Command::new("perl").arg("-e").arg(SCRIPT).spawn().unwrap();
484 let mut reader = ProcReader::from_stdout(child.id());
485
486 thread::sleep(Duration::from_secs(4));
487
488 let mut buf = [0; 10];
489 let _ = reader.read_exact(&mut buf);
490 assert_eq!("aaa\nbbb\ncc", String::from_utf8(buf.to_vec()).unwrap());
491 }
492
493 #[test]
494 fn test_kill() {
495 let mut child = Command::new("perl").arg("-e").arg(SCRIPT).spawn().unwrap();
496 let mut reader = ProcReader::from_stdout(child.id());
497 let _ = child.kill();
498
499 thread::sleep(Duration::from_secs(4));
500
501 let mut buf = [0; 10];
502 let ret = reader.read_exact(&mut buf);
503 assert_eq!(
504 &format!("{:?}", ret)[0..60],
505 "Err(Custom { kind: Other, error: \"failed to access process ("
506 );
507 }
508
509 #[test]
510 fn test_kill2() {
511 let mut child = Command::new("perl").arg("-e").arg(SCRIPT).spawn().unwrap();
512 let mut reader = ProcReader::from_stdout(child.id());
513
514 thread::sleep(Duration::from_secs(2));
515 let _ = child.kill();
516 thread::sleep(Duration::from_secs(2));
517
518 let mut buf = [0; 10];
519 let ret = reader.read_exact(&mut buf);
520 assert_eq!(
521 &format!("{:?}", ret)[0..60],
522 "Err(Error { kind: UnexpectedEof, message: \"failed to fill wh"
523 );
524 }
525
526 #[test]
527 fn test_stderr() {
528 let child = Command::new("perl")
529 .arg("-e")
530 .arg(SCRIPT_WITH_ERR)
531 .spawn()
532 .unwrap();
533 let mut reader = BufReader::new(ProcReader::from_stderr(child.id()));
534
535 thread::sleep(Duration::from_secs(2));
536
537 let mut line = String::new();
538 let _ = reader.read_to_string(&mut line);
539 assert_eq!("eee\n", line);
540 }
541
542 #[test]
543 fn test_both() {
544 let child = Command::new("perl")
545 .arg("-e")
546 .arg(SCRIPT_WITH_ERR)
547 .spawn()
548 .unwrap();
549 let mut reader = BufReader::new(ProcReader::from_stdany(child.id()));
550
551 thread::sleep(Duration::from_secs(2));
552
553 let mut line = String::new();
554 let _ = reader.read_to_string(&mut line);
555 assert_eq!("aaa\neee\n", line);
556 }
557
558 #[test]
559 fn test_stdout_without_redirect() {
560 let child = Command::new("sh")
561 .arg("-c")
562 .arg(SCRIPT_REDIRECT)
563 .spawn()
564 .unwrap();
565 let mut reader = BufReader::new(ProcReader::from_stdout(child.id()));
566
567 thread::sleep(Duration::from_secs(4));
568
569 let mut line = String::new();
570 let _ = reader.read_to_string(&mut line);
571 assert_eq!("aaa\nbbb\n", line);
572 }
573
574 #[test]
575 #[cfg(target_arch = "x86_64")]
576 fn test_stdout_with_redirect() {
577 let child = Command::new("sh")
578 .arg("-c")
579 .arg(SCRIPT_REDIRECT)
580 .spawn()
581 .unwrap();
582 let mut reader = BufReader::new(ProcReader::from_stdout(child.id()).with_redirect());
583
584 thread::sleep(Duration::from_secs(4));
585
586 let mut line = String::new();
587 let _ = reader.read_to_string(&mut line);
588 assert_eq!("aaa\nccc\n", line);
589 }
590
591 #[test]
592 fn test_stderr_without_redirect() {
593 let child = Command::new("sh")
594 .arg("-c")
595 .arg(SCRIPT_REDIRECT)
596 .spawn()
597 .unwrap();
598 let mut reader = BufReader::new(ProcReader::from_stderr(child.id()));
599
600 thread::sleep(Duration::from_secs(4));
601
602 let mut line = String::new();
603 let _ = reader.read_to_string(&mut line);
604 assert_eq!("ccc\nddd\n", line);
605 }
606
607 #[test]
608 #[cfg(target_arch = "x86_64")]
609 fn test_stderr_with_redirect() {
610 let child = Command::new("sh")
611 .arg("-c")
612 .arg(SCRIPT_REDIRECT)
613 .spawn()
614 .unwrap();
615 let mut reader = BufReader::new(ProcReader::from_stderr(child.id()).with_redirect());
616
617 thread::sleep(Duration::from_secs(4));
618
619 let mut line = String::new();
620 let _ = reader.read_to_string(&mut line);
621 assert_eq!("bbb\nddd\n", line);
622 }
623}