13 Commits
Author SHA1 Message Date
pancake c72b4cba60 fi 2026-02-20 13:49:51 +01:00
pancake 391641f2ff Use libload instead of dlopen for portability reasons 2026-02-20 13:40:45 +01:00
Jonathan Creekmore 1208b28183 Provide a proper Default impl for R2PipeSpawnOptions 2023-06-08 18:28:46 -04:00
RHL120 522a9f7a4f Fix docs link in README 2022-12-30 13:05:38 -05:00
meme 89db8759ce 0.7.0 2022-12-29 12:17:09 -05:00
RHL120 a66ddad123 Add support for radare as a shlib 2022-12-29 10:40:29 -05:00
RHL120 50c689f4bc Install radare2 in CI and add a simple test 2022-12-29 10:15:35 -05:00
RHL120 c0260db239 Fix an issue with Windows 2022-12-25 08:26:35 -05:00
RHL120 14079c97e9 Add call and callj methods 2022-12-17 12:09:35 -05:00
RHL120 43b68268ca Add default implementation for cmdj 2022-12-07 17:25:44 -05:00
RHL120 8cb8d248a8 Box pipe code 2022-12-07 17:25:01 -05:00
RHL120 e67a685a5b Clean up in_session code 2022-12-07 17:19:36 -05:00
RHL120 08335660d4 Clean up code in R2Pipe
* Remove some repeated code
* Add a default implementation for close
2022-11-14 13:55:19 -05:00
8 changed files with 203 additions and 122 deletions

No files matched your search

+1 -4
View File
@@ -13,6 +13,7 @@ jobs:
name: R2pipe.rs
runs-on: ubuntu-latest
steps:
- run: wget https://github.com/radareorg/radare2/releases/download/5.8.0/radare2_5.8.0_amd64.deb && sudo dpkg -i radare2*.deb
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
@@ -35,7 +36,3 @@ jobs:
with:
command: clippy
args: -- -D warnings
+6 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "r2pipe"
version = "0.6.0"
version = "0.7.0"
authors = [
"pancake <[email protected]>",
"sushant94 <[email protected]>"
@@ -20,8 +20,12 @@ readme = "README.md"
# License information
license = "MIT OR Apache-2.0"
[features]
docsrs = []
[dependencies]
libc = "0.2.81"
libloading = "0.8"
libc = "0.2"
serde = "1.0.118"
serde_json = "1.0.60"
serde_derive = "1.0.118"
+1 -1
View File
@@ -10,7 +10,7 @@ r2pipe.rs
The Rust Crate to interact with radare2.
Please check [Documentation](https://radare.github.io/r2pipe.rs) to get
Please check [Documentation](https://docs.rs/r2pipe/latest/r2pipe/) to get
started.
**NOTICE**
+29
View File
@@ -0,0 +1,29 @@
use crate::Result;
use libloading::{Library, Symbol};
use std::sync::Mutex;
/// Contains a handle to the dynamically loaded library.
pub struct LibHandle(Mutex<Library>);
impl LibHandle {
/// Load a shared library by name with platform-specific extension.
pub fn new(name: &str, end: Option<&str>) -> Result<LibHandle> {
let ext = if cfg!(windows) {
".dll"
} else if cfg!(target_os = "macos") {
".dylib"
} else {
".so"
};
let lib_name = format!("{}{}{}", name, ext, end.unwrap_or(""));
let lib = unsafe { Library::new(&lib_name) }?;
Ok(LibHandle(Mutex::new(lib)))
}
/// Load a symbol from the library and transmute it to the desired type.
pub unsafe fn load_sym<T>(&mut self, name: &str) -> Result<T> {
let lib = self.0.lock().unwrap();
let sym: Symbol<T> = lib.get(name.as_bytes())?;
Ok(std::mem::transmute_copy::<_, T>(&*sym))
}
}
+4
View File
@@ -40,4 +40,8 @@ pub enum Error {
/// Error sending data through channel.
#[error("Send channel data error")]
ChannelSendError(#[from] SendError<String>),
/// Error loading radare2 shared library.
#[error("Shared library error: {0}")]
SharedLibraryError(#[from] libloading::Error),
}
+2 -1
View File
@@ -50,11 +50,12 @@
//! The crate offers various methods to interact with r2pipe, eg. via process (multi-threadable), http or tcp.
//! Check the examples/ dir for more complete examples.
#![cfg_attr(doc_cfg, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(html_root_url = "https://radare.github.io/r2pipe.rs/")]
#[macro_use]
pub mod r2pipe;
mod dlfcn;
pub mod r2;
mod error;
+1 -1
View File
@@ -70,7 +70,7 @@ impl R2 {
}
pub fn recv_json(&mut self) -> Result<Value> {
let mut res = self.recv().replace("\n", "");
let mut res = self.recv().replace('\n', "");
if res.is_empty() {
res = "{}".to_owned();
}
+159 -113
View File
@@ -2,6 +2,7 @@
//!
//! Please check crate level documentation for more details and example.
use crate::dlfcn;
use crate::{Error, Result};
use std::env;
@@ -52,28 +53,49 @@ pub struct R2PipeThread {
pub handle: thread::JoinHandle<Result<()>>,
}
#[derive(Default, Clone)]
#[derive(Clone)]
pub struct R2PipeSpawnOptions {
pub exepath: String,
pub args: Vec<&'static str>,
}
impl Default for R2PipeSpawnOptions {
fn default() -> Self {
let exepath = if cfg!(windows) { "radare2.exe" } else { "r2" };
R2PipeSpawnOptions {
exepath: exepath.to_string(),
args: Vec::default(),
}
}
}
/// Provides abstraction between the three invocation methods.
pub enum R2Pipe {
Pipe(R2PipeSpawn),
Lang(R2PipeLang),
Tcp(R2PipeTcp),
Http(R2PipeHttp),
pub struct R2Pipe(Box<dyn Pipe>);
pub trait Pipe {
fn cmd(&mut self, cmd: &str) -> Result<String>;
fn cmdj(&mut self, cmd: &str) -> Result<Value> {
let result = self.cmd(cmd)?;
if result.is_empty() {
return Err(Error::EmptyResponse);
}
Ok(serde_json::from_str(&result)?)
}
/// Escape the command before executing, valid only as of r2 v.5.8.0 "icebucket"
fn call(&mut self, cmd: &str) -> Result<String> {
self.cmd(&format!("\"\"{}", cmd))
}
/// Escape the command before executing and convert it to a json value,
/// valid only as of r2 v.5.8.0 "icebucket"
fn callj(&mut self, cmd: &str) -> Result<Value> {
self.cmdj(&format!("\"\"{}", cmd))
}
fn close(&mut self) {}
}
fn atoi(k: &str) -> i32 {
k.parse::<i32>().unwrap_or(-1)
}
fn getenv(k: &str) -> i32 {
fn getenv(k: &str) -> Option<i32> {
match env::var(k) {
Ok(val) => atoi(&val),
Err(_) => -1,
Ok(val) => val.parse::<i32>().ok(),
Err(_) => None,
}
}
@@ -88,24 +110,27 @@ fn process_result(res: Vec<u8>) -> Result<String> {
#[macro_export]
macro_rules! open_pipe {
() => {
R2Pipe::open(),
() => {
R2Pipe::open(),
};
($x: expr) => {
match $x {
Some(path) => R2Pipe::load_native(&path.clone()).or_else(|_| R2Pipe::spawn(path, None)),
None => R2Pipe::open(),
}
};
($x: expr) => {
match $x {
Some(path) => R2Pipe::spawn(path, None),
None => R2Pipe::open(),
}
};
($x: expr, $y: expr) => {
match $x $y {
Some(path, opts) => R2Pipe::spawn(path, opts),
(None, None) => R2Pipe::open(),
}
}
($x: expr, $y: expr) => {
match $x $y {
Some(path, opts) => R2Pipe::spawn(path, opts),
(None, None) => R2Pipe::open(),
}
}
}
impl R2Pipe {
pub fn load_native<T: AsRef<str>>(path: T) -> Result<R2Pipe> {
Ok(R2Pipe(Box::new(R2PipeNative::open(path.as_ref())?)))
}
#[cfg(not(windows))]
pub fn open() -> Result<R2Pipe> {
use std::os::unix::io::FromRawFd;
@@ -120,47 +145,37 @@ impl R2Pipe {
write: File::from_raw_fd(d_out),
}
};
Ok(R2Pipe::Lang(res))
Ok(R2Pipe(Box::new(res)))
}
#[cfg(windows)]
pub fn open() -> Result<R2Pipe> {
unimplemented!()
}
pub fn cmd(&mut self, cmd: &str) -> Result<String> {
match *self {
R2Pipe::Pipe(ref mut x) => x.cmd(cmd.trim()),
R2Pipe::Lang(ref mut x) => x.cmd(cmd.trim()),
R2Pipe::Tcp(ref mut x) => x.cmd(cmd.trim()),
R2Pipe::Http(ref mut x) => x.cmd(cmd.trim()),
}
self.0.cmd(cmd.trim())
}
pub fn cmdj(&mut self, cmd: &str) -> Result<Value> {
match *self {
R2Pipe::Pipe(ref mut x) => x.cmdj(cmd.trim()),
R2Pipe::Lang(ref mut x) => x.cmdj(cmd.trim()),
R2Pipe::Tcp(ref mut x) => x.cmdj(cmd.trim()),
R2Pipe::Http(ref mut x) => x.cmdj(cmd.trim()),
}
self.0.cmdj(cmd.trim())
}
pub fn close(&mut self) {
match *self {
R2Pipe::Pipe(ref mut x) => x.close(),
R2Pipe::Lang(ref mut x) => x.close(),
R2Pipe::Tcp(ref mut x) => x.close(),
R2Pipe::Http(ref mut x) => x.close(),
}
self.0.close();
}
/// Escape the command before executing, valid only as of r2 v.5.8.0 "icebucket"
pub fn call(&mut self, cmd: &str) -> Result<String> {
self.0.call(cmd)
}
/// Escape the command before executing and convert it to a json value,
/// valid only as of r2 v.5.8.0 "icebucket"
pub fn callj(&mut self, cmd: &str) -> Result<Value> {
self.0.callj(cmd)
}
pub fn in_session() -> Option<(i32, i32)> {
let f_in = getenv("R2PIPE_IN");
let f_out = getenv("R2PIPE_OUT");
if f_in < 0 || f_out < 0 {
return None;
}
let f_in = getenv("R2PIPE_IN")?;
let f_out = getenv("R2PIPE_OUT")?;
Some((f_in, f_out))
}
@@ -173,19 +188,13 @@ impl R2Pipe {
}
/// Creates a new R2PipeSpawn.
pub fn spawn<T: AsRef<str>>(name: T, opts: Option<R2PipeSpawnOptions>) -> Result<R2Pipe> {
pub fn spawn<T: AsRef<str>>(name: T, mut opts: Option<R2PipeSpawnOptions>) -> Result<R2Pipe> {
if name.as_ref() == "" && R2Pipe::in_session().is_some() {
return R2Pipe::open();
}
let exepath = match opts {
Some(ref opt) => opt.exepath.clone(),
_ => "r2".to_owned(),
};
let args = match opts {
Some(ref opt) => opt.args.clone(),
_ => vec![],
};
let R2PipeSpawnOptions { exepath, args } = opts.take().unwrap_or_default();
let path = Path::new(name.as_ref());
let mut child = Command::new(exepath)
.arg("-q0")
@@ -209,7 +218,7 @@ impl R2Pipe {
child: Some(child),
};
Ok(R2Pipe::Pipe(res))
Ok(R2Pipe(Box::new(res)))
}
/// Creates a new R2PipeTcp
@@ -217,14 +226,14 @@ impl R2Pipe {
// use `connect` to figure out which socket address works
let stream = TcpStream::connect(addr)?;
let addr = stream.peer_addr()?;
Ok(R2Pipe::Tcp(R2PipeTcp { socket_addr: addr }))
Ok(R2Pipe(Box::new(R2PipeTcp { socket_addr: addr })))
}
/// Creates a new R2PipeHttp
pub fn http(host: &str) -> R2Pipe {
R2Pipe::Http(R2PipeHttp {
R2Pipe(Box::new(R2PipeHttp {
host: host.to_string(),
})
}))
}
/// Creates new pipe threads
@@ -290,8 +299,8 @@ impl R2PipeThread {
}
}
impl R2PipeSpawn {
pub fn cmd(&mut self, cmd: &str) -> Result<String> {
impl Pipe for R2PipeSpawn {
fn cmd(&mut self, cmd: &str) -> Result<String> {
let cmd = cmd.to_owned() + "\n";
self.write.write_all(cmd.as_bytes())?;
@@ -300,23 +309,7 @@ impl R2PipeSpawn {
process_result(res)
}
pub fn cmdj(&mut self, cmd: &str) -> Result<Value> {
let result = self.cmd(cmd)?;
if result.is_empty() {
return Err(Error::EmptyResponse);
}
Ok(serde_json::from_str(&result)?)
}
/// Attempts to take the pipes underlying child process handle.
/// On success the handle is returned.
/// If `None` is returned the child handle was already taken previously.
/// By using this method you take over the responsibility to `wait()` the child process in order to free all of it's resources.
pub fn take_child(&mut self) -> Option<process::Child> {
self.child.take()
}
pub fn close(&mut self) {
fn close(&mut self) {
let _ = self.cmd("q!");
if let Some(child) = &mut self.child {
let _ = child.wait();
@@ -324,28 +317,27 @@ impl R2PipeSpawn {
}
}
impl R2PipeLang {
pub fn cmd(&mut self, cmd: &str) -> Result<String> {
impl R2PipeSpawn {
/// Attempts to take the pipes underlying child process handle.
/// On success the handle is returned.
/// If `None` is returned the child handle was already taken previously.
/// By using this method you take over the responsibility to `wait()` the child process in order to free all of it's resources.
pub fn take_child(&mut self) -> Option<process::Child> {
self.child.take()
}
}
impl Pipe for R2PipeLang {
fn cmd(&mut self, cmd: &str) -> Result<String> {
self.write.write_all(cmd.as_bytes())?;
let mut res: Vec<u8> = Vec::new();
self.read.read_until(0u8, &mut res)?;
process_result(res)
}
pub fn cmdj(&mut self, cmd: &str) -> Result<Value> {
let res = self.cmd(cmd)?;
Ok(serde_json::from_str(&res)?)
}
pub fn close(&mut self) {
// self.read.close();
// self.write.close();
}
}
impl R2PipeHttp {
pub fn cmd(&mut self, cmd: &str) -> Result<String> {
impl Pipe for R2PipeHttp {
fn cmd(&mut self, cmd: &str) -> Result<String> {
let host = if self.host.starts_with("http://") {
&self.host[7..]
} else {
@@ -366,17 +358,10 @@ impl R2PipeHttp {
Ok(str::from_utf8(&resp[index..]).map(|s| s.to_string())?)
}
pub fn cmdj(&mut self, cmd: &str) -> Result<Value> {
let res = self.cmd(cmd)?;
Ok(serde_json::from_str(&res)?)
}
pub fn close(&mut self) {}
}
impl R2PipeTcp {
pub fn cmd(&mut self, cmd: &str) -> Result<String> {
impl Pipe for R2PipeTcp {
fn cmd(&mut self, cmd: &str) -> Result<String> {
let mut stream = TcpStream::connect(self.socket_addr)?;
stream.write_all(cmd.as_bytes())?;
let mut res: Vec<u8> = Vec::new();
@@ -384,11 +369,72 @@ impl R2PipeTcp {
res.push(0);
process_result(res)
}
}
pub fn cmdj(&mut self, cmd: &str) -> Result<Value> {
let res = self.cmd(cmd)?;
Ok(serde_json::from_str(&res)?)
pub struct R2PipeNative {
lib: dlfcn::LibHandle,
r_core: std::sync::Mutex<*mut libc::c_void>,
r_core_cmd_str_handle: fn(*mut libc::c_void, *const libc::c_char) -> *mut libc::c_char,
}
impl R2PipeNative {
pub fn open(file: &str) -> Result<R2PipeNative> {
let mut lib = dlfcn::LibHandle::new("libr_core", None)?;
let r_core_new: fn() -> *mut libc::c_void = unsafe { lib.load_sym("r_core_new")? };
let r_core_cmd_str_handle = unsafe { lib.load_sym("r_core_cmd_str")? };
let r_core = r_core_new();
if r_core.is_null() {
return Err(Error::EmptyResponse);
}
let mut ret = R2PipeNative {
lib,
r_core: std::sync::Mutex::new(r_core),
r_core_cmd_str_handle,
};
ret.cmd(&format!("o {}", file))?;
Ok(ret)
}
}
impl Pipe for R2PipeNative {
fn cmd(&mut self, cmd: &str) -> Result<String> {
let r_core = *self.r_core.lock().unwrap();
let cmd = std::ffi::CString::new(cmd).map_err(|_| Error::ArgumentMismatch)?;
let res = (self.r_core_cmd_str_handle)(r_core, cmd.as_ptr());
if res.is_null() {
Err(Error::EmptyResponse)
} else {
Ok(unsafe { std::ffi::CStr::from_ptr(res).to_str()?.to_string() })
}
}
}
impl Drop for R2PipeNative {
fn drop(&mut self) {
let r_core = *self.r_core.lock().unwrap();
if let Ok(r_core_free) =
unsafe { self.lib.load_sym::<fn(*mut libc::c_void)>("r_core_free") }
{
r_core_free(r_core);
}
}
}
#[cfg(test)]
mod test {
use super::Pipe;
use super::R2PipeNative;
use crate::R2Pipe;
#[test]
fn spawn_test() {
let mut pipe = R2Pipe::spawn("/bin/ls", None).unwrap();
assert_eq!(pipe.cmd("echo test").unwrap(), "test\n");
}
pub fn close(&mut self) {}
#[test]
fn native_test() {
let mut r2p = R2PipeNative::open("/bin/ls").unwrap();
assert_eq!("a\n", r2p.cmd("echo a").unwrap());
}
}