r2pipe/
r2pipe.rs

1//! Provides functionality to connect with radare2.
2//!
3//! Please check crate level documentation for more details and example.
4
5use crate::dlfcn;
6use crate::{Error, Result};
7
8use std::env;
9use std::fs::File;
10use std::io::prelude::*;
11use std::io::BufReader;
12use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
13use std::path::Path;
14use std::process;
15use std::process::Command;
16use std::process::Stdio;
17use std::str;
18use std::sync::mpsc;
19use std::sync::Arc;
20use std::thread;
21
22use serde_json::Value;
23
24/// File descriptors to the parent r2 process.
25pub struct R2PipeLang {
26    read: BufReader<File>,
27    write: File,
28}
29
30/// Stores descriptors to the spawned r2 process.
31pub struct R2PipeSpawn {
32    read: BufReader<process::ChildStdout>,
33    write: process::ChildStdin,
34    child: Option<process::Child>,
35}
36
37/// Stores the socket address of the r2 process.
38pub struct R2PipeTcp {
39    socket_addr: SocketAddr,
40}
41
42pub struct R2PipeHttp {
43    host: String,
44}
45
46/// Stores thread metadata
47/// It stores both a sending and receiving end to the thread, allowing convenient interaction
48/// So we can send commands using R2PipeThread::send() and fetch outputs using R2PipeThread::recv()
49pub struct R2PipeThread {
50    r2recv: mpsc::Receiver<String>,
51    r2send: mpsc::Sender<String>,
52    pub id: u16,
53    pub handle: thread::JoinHandle<Result<()>>,
54}
55
56#[derive(Default, Clone)]
57pub struct R2PipeSpawnOptions {
58    pub exepath: String,
59    pub args: Vec<&'static str>,
60}
61
62/// Provides abstraction between the three invocation methods.
63pub struct R2Pipe(Box<dyn Pipe>);
64pub trait Pipe {
65    fn cmd(&mut self, cmd: &str) -> Result<String>;
66    fn cmdj(&mut self, cmd: &str) -> Result<Value> {
67        let result = self.cmd(cmd)?;
68        if result.is_empty() {
69            return Err(Error::EmptyResponse);
70        }
71        Ok(serde_json::from_str(&result)?)
72    }
73    /// Escape the command before executing, valid only as of r2 v.5.8.0 "icebucket"
74    fn call(&mut self, cmd: &str) -> Result<String> {
75        self.cmd(&format!("\"\"{}", cmd))
76    }
77    /// Escape the command before executing and convert it to a json value,
78    /// valid only as of r2 v.5.8.0 "icebucket"
79    fn callj(&mut self, cmd: &str) -> Result<Value> {
80        self.cmdj(&format!("\"\"{}", cmd))
81    }
82    fn close(&mut self) {}
83}
84fn getenv(k: &str) -> Option<i32> {
85    match env::var(k) {
86        Ok(val) => val.parse::<i32>().ok(),
87        Err(_) => None,
88    }
89}
90
91fn process_result(res: Vec<u8>) -> Result<String> {
92    let len = res.len();
93    if len == 0 {
94        Err(Error::EmptyResponse)
95    } else {
96        Ok(str::from_utf8(&res[..len - 1])?.to_string())
97    }
98}
99
100#[macro_export]
101macro_rules! open_pipe {
102    () => {
103        R2Pipe::open(),
104    };
105        ($x: expr) => {
106            match $x {
107                Some(path) => R2Pipe::load_native(&path.clone()).or_else(|_| R2Pipe::spawn(path, None)),
108                None => R2Pipe::open(),
109            }
110        };
111        ($x: expr, $y: expr) => {
112            match $x $y {
113                Some(path, opts) => R2Pipe::spawn(path, opts),
114                (None, None) => R2Pipe::open(),
115            }
116    }
117}
118
119impl R2Pipe {
120    pub fn load_native<T: AsRef<str>>(path: T) -> Result<R2Pipe> {
121        Ok(R2Pipe(Box::new(R2PipeNative::open(path.as_ref())?)))
122    }
123    #[cfg(not(windows))]
124    pub fn open() -> Result<R2Pipe> {
125        use std::os::unix::io::FromRawFd;
126
127        let (f_in, f_out) = R2Pipe::in_session().ok_or(Error::NoSession)?;
128
129        let res = unsafe {
130            // dup file descriptors to avoid from_raw_fd ownership issue
131            let (d_in, d_out) = (libc::dup(f_in), libc::dup(f_out));
132            R2PipeLang {
133                read: BufReader::new(File::from_raw_fd(d_in)),
134                write: File::from_raw_fd(d_out),
135            }
136        };
137        Ok(R2Pipe(Box::new(res)))
138    }
139
140    #[cfg(windows)]
141    pub fn open() -> Result<R2Pipe> {
142        unimplemented!()
143    }
144    pub fn cmd(&mut self, cmd: &str) -> Result<String> {
145        self.0.cmd(cmd.trim())
146    }
147
148    pub fn cmdj(&mut self, cmd: &str) -> Result<Value> {
149        self.0.cmdj(cmd.trim())
150    }
151
152    pub fn close(&mut self) {
153        self.0.close();
154    }
155    /// Escape the command before executing, valid only as of r2 v.5.8.0 "icebucket"
156    pub fn call(&mut self, cmd: &str) -> Result<String> {
157        self.0.call(cmd)
158    }
159    /// Escape the command before executing and convert it to a json value,
160    /// valid only as of r2 v.5.8.0 "icebucket"
161    pub fn callj(&mut self, cmd: &str) -> Result<Value> {
162        self.0.callj(cmd)
163    }
164
165    pub fn in_session() -> Option<(i32, i32)> {
166        let f_in = getenv("R2PIPE_IN")?;
167        let f_out = getenv("R2PIPE_OUT")?;
168        Some((f_in, f_out))
169    }
170
171    #[cfg(windows)]
172    pub fn in_windows_session() -> Option<String> {
173        match env::var("R2PIPE_PATH") {
174            Ok(val) => Some(format!("\\\\.\\pipe\\{}", val)),
175            Err(_) => None,
176        }
177    }
178
179    /// Creates a new R2PipeSpawn.
180    pub fn spawn<T: AsRef<str>>(name: T, opts: Option<R2PipeSpawnOptions>) -> Result<R2Pipe> {
181        if name.as_ref() == "" && R2Pipe::in_session().is_some() {
182            return R2Pipe::open();
183        }
184
185        let exepath = match opts {
186            Some(ref opt) => opt.exepath.clone(),
187            _ => {
188                if cfg!(windows) {
189                    "radare2.exe"
190                } else {
191                    "r2"
192                }
193            }
194            .to_owned(),
195        };
196        let args = match opts {
197            Some(ref opt) => opt.args.clone(),
198            _ => vec![],
199        };
200        let path = Path::new(name.as_ref());
201        let mut child = Command::new(exepath)
202            .arg("-q0")
203            .args(&args)
204            .arg(path)
205            .stdin(Stdio::piped())
206            .stdout(Stdio::piped())
207            .spawn()?;
208
209        // If stdin/stdout is not available, hard error
210        let sin = child.stdin.take().unwrap();
211        let mut sout = child.stdout.take().unwrap();
212
213        // flush out the initial null byte.
214        let mut w = [0; 1];
215        sout.read_exact(&mut w)?;
216
217        let res = R2PipeSpawn {
218            read: BufReader::new(sout),
219            write: sin,
220            child: Some(child),
221        };
222
223        Ok(R2Pipe(Box::new(res)))
224    }
225
226    /// Creates a new R2PipeTcp
227    pub fn tcp<A: ToSocketAddrs>(addr: A) -> Result<R2Pipe> {
228        // use `connect` to figure out which socket address works
229        let stream = TcpStream::connect(addr)?;
230        let addr = stream.peer_addr()?;
231        Ok(R2Pipe(Box::new(R2PipeTcp { socket_addr: addr })))
232    }
233
234    /// Creates a new R2PipeHttp
235    pub fn http(host: &str) -> R2Pipe {
236        R2Pipe(Box::new(R2PipeHttp {
237            host: host.to_string(),
238        }))
239    }
240
241    /// Creates new pipe threads
242    /// First two arguments for R2Pipe::threads() are the same as for R2Pipe::spawn() but inside vectors
243    /// Third and last argument is an option to a callback function
244    /// The callback function takes two Arguments: Thread ID and r2pipe output
245    pub fn threads(
246        names: Vec<&'static str>,
247        opts: Vec<Option<R2PipeSpawnOptions>>,
248        callback: Option<Arc<dyn Fn(u16, String) + Sync + Send>>,
249    ) -> Result<Vec<R2PipeThread>> {
250        if names.len() != opts.len() {
251            return Err(Error::ArgumentMismatch);
252        }
253
254        let mut pipes = Vec::new();
255
256        for n in 0..names.len() {
257            let (htx, rx) = mpsc::channel();
258            let (tx, hrx) = mpsc::channel();
259            let name = names[n];
260            let opt = opts[n].clone();
261            let cb = callback.clone();
262            let t = thread::spawn(move || -> Result<()> {
263                let mut r2 = R2Pipe::spawn(name, opt)?;
264                loop {
265                    let cmd: String = hrx.recv()?;
266                    if cmd == "q" {
267                        break;
268                    }
269                    let res = r2.cmdj(&cmd)?.to_string();
270                    htx.send(res.clone())?;
271                    if let Some(cbs) = cb.clone() {
272                        thread::spawn(move || {
273                            cbs(n as u16, res);
274                        });
275                    };
276                }
277                Ok(())
278            });
279            pipes.push(R2PipeThread {
280                r2recv: rx,
281                r2send: tx,
282                id: n as u16,
283                handle: t,
284            });
285        }
286        Ok(pipes)
287    }
288}
289
290impl R2PipeThread {
291    pub fn send(&self, cmd: String) -> Result<()> {
292        Ok(self.r2send.send(cmd)?)
293    }
294
295    pub fn recv(&self, block: bool) -> Result<String> {
296        if block {
297            Ok(self.r2recv.recv()?)
298        } else {
299            Ok(self.r2recv.try_recv()?)
300        }
301    }
302}
303
304impl Pipe for R2PipeSpawn {
305    fn cmd(&mut self, cmd: &str) -> Result<String> {
306        let cmd = cmd.to_owned() + "\n";
307        self.write.write_all(cmd.as_bytes())?;
308
309        let mut res: Vec<u8> = Vec::new();
310        self.read.read_until(0u8, &mut res)?;
311        process_result(res)
312    }
313
314    fn close(&mut self) {
315        let _ = self.cmd("q!");
316        if let Some(child) = &mut self.child {
317            let _ = child.wait();
318        }
319    }
320}
321
322impl R2PipeSpawn {
323    /// Attempts to take the pipes underlying child process handle.
324    /// On success the handle is returned.
325    /// If `None` is returned the child handle was already taken previously.
326    /// By using this method you take over the responsibility to `wait()` the child process in order to free all of it's resources.
327    pub fn take_child(&mut self) -> Option<process::Child> {
328        self.child.take()
329    }
330}
331
332impl Pipe for R2PipeLang {
333    fn cmd(&mut self, cmd: &str) -> Result<String> {
334        self.write.write_all(cmd.as_bytes())?;
335        let mut res: Vec<u8> = Vec::new();
336        self.read.read_until(0u8, &mut res)?;
337        process_result(res)
338    }
339}
340
341impl Pipe for R2PipeHttp {
342    fn cmd(&mut self, cmd: &str) -> Result<String> {
343        let host = if self.host.starts_with("http://") {
344            &self.host[7..]
345        } else {
346            &self.host
347        };
348        let mut stream = TcpStream::connect(host)?;
349        let req = format!("GET /cmd/{} HTTP/1.1\r\n", cmd);
350        let mut resp = Vec::with_capacity(1024);
351        stream.write_all(req.as_bytes())?;
352        stream.read_to_end(&mut resp)?;
353
354        // index of the start of response body
355        let index = resp
356            .windows(4)
357            .position(|w| w == "\r\n\r\n".as_bytes())
358            .map(|i| i + 4)
359            .unwrap_or(0);
360
361        Ok(str::from_utf8(&resp[index..]).map(|s| s.to_string())?)
362    }
363}
364
365impl Pipe for R2PipeTcp {
366    fn cmd(&mut self, cmd: &str) -> Result<String> {
367        let mut stream = TcpStream::connect(self.socket_addr)?;
368        stream.write_all(cmd.as_bytes())?;
369        let mut res: Vec<u8> = Vec::new();
370        stream.read_to_end(&mut res)?;
371        res.push(0);
372        process_result(res)
373    }
374}
375
376pub struct R2PipeNative {
377    lib: dlfcn::LibHandle,
378    r_core: std::sync::Mutex<*mut libc::c_void>,
379    r_core_cmd_str_handle: fn(*mut libc::c_void, *const libc::c_char) -> *mut libc::c_char,
380}
381
382impl R2PipeNative {
383    pub fn open(file: &str) -> Result<R2PipeNative> {
384        let mut lib = dlfcn::LibHandle::new("libr_core", None)?;
385        let r_core_new: fn() -> *mut libc::c_void = unsafe { lib.load_sym("r_core_new")? };
386        let r_core_cmd_str_handle = unsafe { lib.load_sym("r_core_cmd_str")? };
387        let r_core = r_core_new();
388        if r_core.is_null() {
389            Err(Error::SharedLibraryLoadError)
390        } else {
391            let mut ret = R2PipeNative {
392                lib,
393                r_core: std::sync::Mutex::new(r_core),
394                r_core_cmd_str_handle,
395            };
396            ret.cmd(&format!("o {}", file))?;
397            Ok(ret)
398        }
399    }
400}
401
402impl Pipe for R2PipeNative {
403    fn cmd(&mut self, cmd: &str) -> Result<String> {
404        let r_core = *self.r_core.lock().unwrap();
405        let cmd = dlfcn::to_cstr(cmd)?;
406        let res = (self.r_core_cmd_str_handle)(r_core, cmd);
407        if res.is_null() {
408            Err(Error::EmptyResponse)
409        } else {
410            Ok(unsafe { std::ffi::CStr::from_ptr(res).to_str()?.to_string() })
411        }
412    }
413}
414
415impl Drop for R2PipeNative {
416    fn drop(&mut self) {
417        let r_core = *self.r_core.lock().unwrap();
418        if let Ok(r_core_free) =
419            unsafe { self.lib.load_sym::<fn(*mut libc::c_void)>("r_core_free") }
420        {
421            r_core_free(r_core);
422        }
423    }
424}
425
426#[cfg(test)]
427mod test {
428    use super::Pipe;
429    use super::R2PipeNative;
430    use crate::R2Pipe;
431
432    #[test]
433    fn spawn_test() {
434        let mut pipe = R2Pipe::spawn("/bin/ls", None).unwrap();
435        assert_eq!(pipe.cmd("echo test").unwrap(), "test\n");
436    }
437
438    #[test]
439    fn native_test() {
440        let mut r2p = R2PipeNative::open("/bin/ls").unwrap();
441        assert_eq!("a\n", r2p.cmd("echo a").unwrap());
442    }
443}