mirror of
https://github.com/radareorg/r2pipe.rs.git
synced 2026-11-09 04:29:32 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c3277646a | ||
|
|
c504deaff8 | ||
|
|
7cd1560585 | ||
|
|
ee36c46657 | ||
|
|
1208b28183 | ||
|
|
522a9f7a4f | ||
|
|
89db8759ce | ||
|
|
a66ddad123 | ||
|
|
50c689f4bc | ||
|
|
c0260db239 | ||
|
|
14079c97e9 | ||
|
|
43b68268ca | ||
|
|
8cb8d248a8 | ||
|
|
e67a685a5b | ||
|
|
08335660d4 | ||
|
|
32572abbb2 | ||
|
|
ee3fee9bb4 | ||
|
|
4ee88ba031 | ||
|
|
3796776e11 | ||
|
|
a622e4926c | ||
|
|
9310015b18 | ||
|
|
8dae2fcb73 | ||
|
|
15a83699ae | ||
|
|
3950fe4f0d | ||
|
|
591ac5c1b8 | ||
|
|
c2858b09d7 | ||
|
|
eec57de9d6 |
No files matched your search
@@ -1,4 +1,7 @@
|
||||
name: R2pipe Rust CI
|
||||
name: ci
|
||||
|
||||
env:
|
||||
R2V: 6.1.0
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -10,18 +13,48 @@ on:
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: R2pipe.rs
|
||||
name: R2pipe.rs (Ubuntu)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: wget https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2_${{env.R2V}}_amd64.deb && sudo dpkg -i radare2*.deb
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add rustfmt clippy
|
||||
- uses: actions-rs/cargo@v1
|
||||
name: Building
|
||||
with:
|
||||
command: build
|
||||
args: --release --all-features
|
||||
- uses: actions-rs/cargo@v1
|
||||
name: Testing
|
||||
with:
|
||||
command: test
|
||||
- uses: actions-rs/cargo@v1
|
||||
name: Format
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- uses: actions-rs/cargo@v1
|
||||
name: Clippy
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
|
||||
|
||||
|
||||
build_and_test_windows:
|
||||
name: R2pipe.rs (Windows)
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install radare2
|
||||
run: |
|
||||
Invoke-WebRequest -Uri "https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-${{env.R2V}}-w64.zip" -OutFile "radare2.zip"
|
||||
Expand-Archive -Path "radare2.zip" -DestinationPath "C:\radare2"
|
||||
$r2path = (Get-ChildItem -Path "C:\radare2" -Directory | Select-Object -First 1).FullName
|
||||
echo "$r2path\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo build --release --all-features
|
||||
- run: cargo test
|
||||
+9
-7
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "r2pipe"
|
||||
version = "0.5.0"
|
||||
version = "0.7.0"
|
||||
authors = [
|
||||
"pancake <[email protected]>",
|
||||
"sushant94 <[email protected]>"
|
||||
@@ -21,11 +21,13 @@ readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
docsrs = []
|
||||
http = []
|
||||
|
||||
[dependencies]
|
||||
libc = "*"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
reqwest = "0.9"
|
||||
libloading = "0.8"
|
||||
libc = "0.2"
|
||||
serde = "1.0.118"
|
||||
serde_json = "1.0.60"
|
||||
serde_derive = "1.0.118"
|
||||
thiserror = "1"
|
||||
@@ -1,5 +1,4 @@
|
||||

|
||||
[](https://travis-ci.org/radareorg/r2pipe.rs)
|
||||
[](https://github.com/radareorg/r2pipe.rs/actions/workflows/main.yml)
|
||||
[](https://docs.rs/r2pipe)
|
||||
[](https://crates.io/crates/r2pipe)
|
||||
[](
|
||||
@@ -10,7 +9,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**
|
||||
|
||||
+17
-11
@@ -1,15 +1,21 @@
|
||||
use serde_json;
|
||||
use r2pipe::Result;
|
||||
|
||||
use r2pipe::R2Pipe;
|
||||
fn main() -> Result<()> {
|
||||
#[cfg(feature = "http")]
|
||||
{
|
||||
use r2pipe::R2Pipe;
|
||||
use serde_json;
|
||||
|
||||
fn main() {
|
||||
let mut r2p = R2Pipe::http("http://localhost:9080").unwrap();
|
||||
let mut r2p = R2Pipe::http("http://localhost:9080")?;
|
||||
|
||||
let json = r2p.cmdj("ij").unwrap();
|
||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
||||
println!("ARCH {}", json["bin"]["arch"]);
|
||||
println!("BITS {}", json["bin"]["bits"]);
|
||||
println!("Disasm:\n{}", r2p.cmd("pd 20").unwrap());
|
||||
println!("Hexdump:\n{}", r2p.cmd("px 64").unwrap());
|
||||
r2p.close();
|
||||
let json = r2p.cmdj("ij")?;
|
||||
println!("{}", serde_json::to_string_pretty(&json)?);
|
||||
println!("ARCH {}", json["bin"]["arch"]);
|
||||
println!("BITS {}", json["bin"]["bits"]);
|
||||
println!("Disasm:\n{}", r2p.cmd("pd 20")?);
|
||||
println!("Hexdump:\n{}", r2p.cmd("px 64")?);
|
||||
r2p.close();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+19
-19
@@ -1,38 +1,38 @@
|
||||
use serde_json;
|
||||
|
||||
use r2pipe::R2Pipe;
|
||||
use r2pipe::R2PipeSpawnOptions;
|
||||
use r2pipe::{R2Pipe, Result};
|
||||
|
||||
fn test_trim() {
|
||||
let mut ns = R2Pipe::spawn("/bin/ls".to_owned(), None).unwrap();
|
||||
println!("(({}))", ns.cmd("\n\n?e hello world\n\n").unwrap());
|
||||
println!("(({}))", ns.cmd("\n\n?e hello world\n\n").unwrap());
|
||||
println!("(({}))", ns.cmd("\n\n?e hello world\n\n").unwrap());
|
||||
fn test_trim() -> Result<()> {
|
||||
let mut ns = R2Pipe::spawn("/bin/ls".to_owned(), None)?;
|
||||
println!("(({}))", ns.cmd("\n\n?e hello world\n\n")?);
|
||||
println!("(({}))", ns.cmd("\n\n?e hello world\n\n")?);
|
||||
println!("(({}))", ns.cmd("\n\n?e hello world\n\n")?);
|
||||
ns.close();
|
||||
// process::exit(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_trim();
|
||||
fn main() -> Result<()> {
|
||||
test_trim()?;
|
||||
|
||||
// let mut r2p = open_pipe!().unwrap();
|
||||
let opts = R2PipeSpawnOptions {
|
||||
exepath: "radare2".to_owned(),
|
||||
..Default::default()
|
||||
};
|
||||
let mut r2p = match R2Pipe::in_session() {
|
||||
Some(_) => R2Pipe::open(),
|
||||
None => R2Pipe::spawn("/bin/ls".to_owned(), Some(opts)),
|
||||
}
|
||||
.unwrap();
|
||||
Some(_) => R2Pipe::open()?,
|
||||
None => R2Pipe::spawn("/bin/ls".to_owned(), Some(opts))?,
|
||||
};
|
||||
|
||||
println!("{}", r2p.cmd("?e Hello World").unwrap());
|
||||
println!("{}", r2p.cmd("?e Hello World")?);
|
||||
|
||||
let json = r2p.cmdj("ij").unwrap();
|
||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
||||
let json = r2p.cmdj("ij")?;
|
||||
println!("{}", serde_json::to_string_pretty(&json)?);
|
||||
println!("ARCH {}", json["bin"]["arch"]);
|
||||
println!("BITS {}", json["bin"]["bits"]);
|
||||
println!("Disasm:\n{}", r2p.cmd("pd 20").unwrap());
|
||||
println!("Hexdump:\n{}", r2p.cmd("px 64").unwrap());
|
||||
println!("Disasm:\n{}", r2p.cmd("pd 20")?);
|
||||
println!("Hexdump:\n{}", r2p.cmd("px 64")?);
|
||||
r2p.close();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+5
-4
@@ -1,8 +1,8 @@
|
||||
use r2pipe::R2Pipe;
|
||||
use r2pipe::{R2Pipe, Result};
|
||||
|
||||
fn main() {
|
||||
let mut r2p = R2Pipe::spawn("/bin/ls".to_owned(), None).unwrap();
|
||||
println!("{}", r2p.cmd("?e Hello").unwrap());
|
||||
fn main() -> Result<()> {
|
||||
let mut r2p = R2Pipe::spawn("/bin/ls".to_owned(), None)?;
|
||||
println!("{}", r2p.cmd("?e Hello")?);
|
||||
if let Err(_) = r2p.cmd("q") {
|
||||
// !killall r2") {
|
||||
println!("Quit happens!");
|
||||
@@ -16,4 +16,5 @@ fn main() {
|
||||
}
|
||||
}
|
||||
println!("Byebye");
|
||||
Ok(())
|
||||
}
|
||||
+10
-8
@@ -1,17 +1,19 @@
|
||||
use serde_json;
|
||||
|
||||
use r2pipe::R2Pipe;
|
||||
use r2pipe::{R2Pipe, Result};
|
||||
|
||||
fn main() {
|
||||
let mut r2p = R2Pipe::tcp("localhost:9080").unwrap();
|
||||
fn main() -> Result<()> {
|
||||
let mut r2p = R2Pipe::tcp("localhost:9080")?;
|
||||
|
||||
println!("{}", r2p.cmd("?e Hello World").unwrap());
|
||||
println!("{}", r2p.cmd("?e Hello World")?);
|
||||
|
||||
let json = r2p.cmdj("ij").unwrap();
|
||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
||||
let json = r2p.cmdj("ij")?;
|
||||
println!("{}", serde_json::to_string_pretty(&json)?);
|
||||
println!("ARCH {}", json["bin"]["arch"]);
|
||||
println!("BITS {}", json["bin"]["bits"]);
|
||||
println!("Disasm:\n{}", r2p.cmd("pd 20").unwrap());
|
||||
println!("Hexdump:\n{}", r2p.cmd("px 64").unwrap());
|
||||
println!("Disasm:\n{}", r2p.cmd("pd 20")?);
|
||||
println!("Hexdump:\n{}", r2p.cmd("px 64")?);
|
||||
r2p.close();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+6
-4
@@ -1,6 +1,6 @@
|
||||
use r2pipe::R2Pipe;
|
||||
use r2pipe::{R2Pipe, Result};
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<()> {
|
||||
// Lets spawn some r2pipes to open some binaries
|
||||
// First two arguments for R2Pipe::threads() are the same as for R2Pipe::spawn() but inside vectors
|
||||
// Third and last argument is an option of a callback function
|
||||
@@ -12,7 +12,7 @@ fn main() {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
println!("Error spawning Pipes: {}", e);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,6 +35,8 @@ fn main() {
|
||||
// Note: For "join()" we need to borrow so pipes.iter() won't work for this
|
||||
for p in pipes {
|
||||
if let Ok(_) = p.send("q".to_string()) {};
|
||||
p.handle.join().unwrap();
|
||||
p.handle.join().unwrap()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use r2pipe::R2Pipe;
|
||||
use r2pipe::{R2Pipe, Result};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<()> {
|
||||
// First we define a callback. It doesn't block and gets called after a thread receives output from r2pipe
|
||||
// Note: First argument to the callback is the thread id, second one the r2pipe output
|
||||
let callback = Arc::new(|id, result| {
|
||||
@@ -19,7 +19,7 @@ fn main() {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
println!("Error spawning Pipes: {}", e);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,6 +35,8 @@ fn main() {
|
||||
// Note: For "join()" we need to borrow so pipes.iter() won't work for this
|
||||
for p in pipes {
|
||||
if let Ok(_) = p.send("q".to_string()) {};
|
||||
p.handle.join().unwrap();
|
||||
p.handle.join().unwrap()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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 {
|
||||
#[cfg(windows)]
|
||||
fn load_library(path: &str) -> std::result::Result<Library, libloading::Error> {
|
||||
use libloading::os::windows::{
|
||||
Library as WindowsLibrary, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
|
||||
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR,
|
||||
};
|
||||
|
||||
if path.contains('\\') || path.contains('/') {
|
||||
unsafe {
|
||||
WindowsLibrary::load_with_flags(
|
||||
path,
|
||||
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
|
||||
)
|
||||
.map(Into::into)
|
||||
}
|
||||
} else {
|
||||
unsafe { Library::new(path) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn load_library(path: &str) -> std::result::Result<Library, libloading::Error> {
|
||||
unsafe { Library::new(path) }
|
||||
}
|
||||
|
||||
/// 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(""));
|
||||
|
||||
// Prepare list of paths to try
|
||||
let mut lib_paths = vec![lib_name.clone()];
|
||||
|
||||
// On Windows, add common radare2 installation paths
|
||||
if cfg!(windows) {
|
||||
// CI installs to C:\radare2\<version>\bin, where version changes
|
||||
// So we need to search for the latest version directory
|
||||
lib_paths.push(format!("C:\\radare2\\bin\\{}", lib_name));
|
||||
// Also try the versioned path pattern from CI
|
||||
if let Ok(entries) = std::fs::read_dir("C:\\radare2") {
|
||||
for entry in entries.flatten() {
|
||||
if entry.file_type().is_ok_and(|ft| ft.is_dir()) {
|
||||
let version_path = format!("{}\\bin\\{}", entry.path().display(), lib_name);
|
||||
lib_paths.push(version_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try each path until we find one that works
|
||||
for lib_path in &lib_paths {
|
||||
if let Ok(lib) = Self::load_library(lib_path) {
|
||||
return Ok(LibHandle(Mutex::new(lib)));
|
||||
}
|
||||
}
|
||||
|
||||
// If none worked, return the error from the first attempt
|
||||
Err(Self::load_library(&lib_paths[0]).unwrap_err().into())
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use std::sync::mpsc::{RecvError, SendError, TryRecvError};
|
||||
use std::{io, str};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Custom `Error` for r2pipe.rs.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
/// An I/O error occurred.
|
||||
#[error("I/O error")]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
/// No open radare2 session, perhaps path was not specified.
|
||||
#[error("No open session")]
|
||||
NoSession,
|
||||
|
||||
/// Response had invalid/missing JSON.
|
||||
#[error("Empty response from JSON")]
|
||||
EmptyResponse,
|
||||
|
||||
/// Incorrect number of arguments, or incorrect format.
|
||||
#[error("Argument mismatch")]
|
||||
ArgumentMismatch,
|
||||
|
||||
/// An error occurred inside of serde.
|
||||
#[error("Serde deserialization error")]
|
||||
SerdeError(#[from] serde_json::Error),
|
||||
|
||||
/// Error during UTF-8 decoding.
|
||||
#[error("UTF-8 decoding error")]
|
||||
Utf8(#[from] str::Utf8Error),
|
||||
|
||||
/// Error receiving data from channel.
|
||||
#[error("Receive channel data error")]
|
||||
ChannelReceiveError(#[from] RecvError),
|
||||
|
||||
/// Error trying to receive data from channel.
|
||||
#[error("Trying receive channel data error")]
|
||||
ChannelTryReceiveError(#[from] TryRecvError),
|
||||
|
||||
/// 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),
|
||||
}
|
||||
@@ -50,12 +50,20 @@
|
||||
//! 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(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;
|
||||
pub use error::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub type Result<T> = std::result::Result<T, error::Error>;
|
||||
|
||||
// Rexport to bring it out one module.
|
||||
pub use self::r2::R2;
|
||||
pub use self::r2pipe::R2Pipe;
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
//! contribute to the r2pipe.rs-frontend project. This aims to be a
|
||||
//! barebones implementation of the pipe concept.
|
||||
|
||||
use crate::r2pipe::R2Pipe;
|
||||
use serde_json;
|
||||
use crate::{r2pipe::R2Pipe, Error, Result};
|
||||
use serde_json::Value;
|
||||
|
||||
pub struct R2 {
|
||||
@@ -30,15 +29,13 @@ impl Default for R2 {
|
||||
// i.e. The ones that are not currently abstracted by the R2 API.
|
||||
// Ideally, all commonly used commands must be supported for easier use.
|
||||
impl R2 {
|
||||
// TODO: Use an error type
|
||||
pub fn new<T: AsRef<str>>(path: Option<T>) -> Result<R2, String> {
|
||||
pub fn new<T: AsRef<str>>(path: Option<T>) -> Result<R2> {
|
||||
if path.is_none() && !R2::in_session() {
|
||||
let e = "No r2 session open. Please specify path!".to_owned();
|
||||
return Err(e);
|
||||
return Err(Error::NoSession);
|
||||
}
|
||||
|
||||
// This means that path is `Some` or we have an open session.
|
||||
let pipe = open_pipe!(path.as_ref()).unwrap();
|
||||
let pipe = open_pipe!(path.as_ref())?;
|
||||
Ok(R2 {
|
||||
pipe,
|
||||
readin: String::new(),
|
||||
@@ -56,12 +53,14 @@ impl R2 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
self.send("q!");
|
||||
pub fn close(&mut self) -> Result<()> {
|
||||
self.send("q!")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send(&mut self, cmd: &str) {
|
||||
self.readin = self.pipe.cmd(cmd).unwrap();
|
||||
pub fn send(&mut self, cmd: &str) -> Result<()> {
|
||||
self.readin = self.pipe.cmd(cmd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn recv(&mut self) -> String {
|
||||
@@ -70,14 +69,13 @@ impl R2 {
|
||||
res
|
||||
}
|
||||
|
||||
pub fn recv_json(&mut self) -> Value {
|
||||
let mut res = self.recv().replace("\n", "");
|
||||
pub fn recv_json(&mut self) -> Result<Value> {
|
||||
let mut res = self.recv().replace('\n', "");
|
||||
if res.is_empty() {
|
||||
res = "{}".to_owned();
|
||||
}
|
||||
|
||||
// TODO: this should probably return a Result<Value, Error>
|
||||
serde_json::from_str(&res).unwrap()
|
||||
Ok(serde_json::from_str(&res)?)
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) {
|
||||
|
||||
+237
-183
@@ -2,9 +2,9 @@
|
||||
//!
|
||||
//! Please check crate level documentation for more details and example.
|
||||
|
||||
use reqwest;
|
||||
use crate::dlfcn;
|
||||
use crate::{Error, Result};
|
||||
|
||||
use libc;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
@@ -19,7 +19,6 @@ use std::sync::mpsc;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use serde_json;
|
||||
use serde_json::Value;
|
||||
|
||||
/// File descriptors to the parent r2 process.
|
||||
@@ -32,6 +31,7 @@ pub struct R2PipeLang {
|
||||
pub struct R2PipeSpawn {
|
||||
read: BufReader<process::ChildStdout>,
|
||||
write: process::ChildStdin,
|
||||
child: Option<process::Child>,
|
||||
}
|
||||
|
||||
/// Stores the socket address of the r2 process.
|
||||
@@ -50,76 +50,93 @@ pub struct R2PipeThread {
|
||||
r2recv: mpsc::Receiver<String>,
|
||||
r2send: mpsc::Sender<String>,
|
||||
pub id: u16,
|
||||
pub handle: thread::JoinHandle<()>,
|
||||
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),
|
||||
}
|
||||
|
||||
fn atoi(k: &str) -> i32 {
|
||||
match k.parse::<i32>() {
|
||||
Ok(val) => val,
|
||||
Err(_) => -1,
|
||||
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 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,
|
||||
}
|
||||
}
|
||||
|
||||
fn process_result(res: Vec<u8>) -> Result<String, String> {
|
||||
fn process_result(res: Vec<u8>) -> Result<String> {
|
||||
let len = res.len();
|
||||
if len == 0 {
|
||||
return Err("Failed".to_string());
|
||||
Err(Error::EmptyResponse)
|
||||
} else {
|
||||
Ok(str::from_utf8(&res[..len - 1])?.to_string())
|
||||
}
|
||||
let result = str::from_utf8(&res[..len - 1])
|
||||
.map_err(|e| e.to_string())?
|
||||
.to_string();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[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, &'static str> {
|
||||
pub fn open() -> Result<R2Pipe> {
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
let (f_in, f_out) = match R2Pipe::in_session() {
|
||||
Some(x) => x,
|
||||
None => return Err("Pipe not open. Please run from r2"),
|
||||
};
|
||||
let (f_in, f_out) = R2Pipe::in_session().ok_or(Error::NoSession)?;
|
||||
|
||||
let res = unsafe {
|
||||
// dup file descriptors to avoid from_raw_fd ownership issue
|
||||
let (d_in, d_out) = (libc::dup(f_in), libc::dup(f_out));
|
||||
@@ -128,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, &'static str> {
|
||||
Err("`open()` is not yet supported on windows")
|
||||
pub fn open() -> Result<R2Pipe> {
|
||||
unimplemented!()
|
||||
}
|
||||
pub fn cmd(&mut self, cmd: &str) -> Result<String> {
|
||||
self.0.cmd(cmd.trim())
|
||||
}
|
||||
|
||||
pub fn cmd(&mut self, cmd: &str) -> Result<String, 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()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmdj(&mut self, cmd: &str) -> Result<Value, String> {
|
||||
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()),
|
||||
}
|
||||
pub fn cmdj(&mut self, cmd: &str) -> Result<Value> {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -181,60 +188,50 @@ impl R2Pipe {
|
||||
}
|
||||
|
||||
/// Creates a new R2PipeSpawn.
|
||||
pub fn spawn<T: AsRef<str>>(
|
||||
name: T,
|
||||
opts: Option<R2PipeSpawnOptions>,
|
||||
) -> Result<R2Pipe, &'static str> {
|
||||
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 child = Command::new(exepath)
|
||||
let mut child = Command::new(exepath)
|
||||
.arg("-q0")
|
||||
.args(&args)
|
||||
.arg(path)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|_| "Unable to spawn r2.")?;
|
||||
.spawn()?;
|
||||
|
||||
let sin = child.stdin.unwrap();
|
||||
let mut sout = child.stdout.unwrap();
|
||||
// If stdin/stdout is not available, hard error
|
||||
let sin = child.stdin.take().unwrap();
|
||||
let mut sout = child.stdout.take().unwrap();
|
||||
|
||||
// flush out the initial null byte.
|
||||
let mut w = [0; 1];
|
||||
sout.read_exact(&mut w).unwrap();
|
||||
sout.read_exact(&mut w)?;
|
||||
|
||||
let res = R2PipeSpawn {
|
||||
read: BufReader::new(sout),
|
||||
write: sin,
|
||||
child: Some(child),
|
||||
};
|
||||
|
||||
Ok(R2Pipe::Pipe(res))
|
||||
Ok(R2Pipe(Box::new(res)))
|
||||
}
|
||||
|
||||
/// Creates a new R2PipeTcp
|
||||
pub fn tcp<A: ToSocketAddrs>(addr: A) -> Result<R2Pipe, &'static str> {
|
||||
pub fn tcp<A: ToSocketAddrs>(addr: A) -> Result<R2Pipe> {
|
||||
// use `connect` to figure out which socket address works
|
||||
let stream = TcpStream::connect(addr).map_err(|_| "Unable to connect TCP stream")?;
|
||||
let addr = stream
|
||||
.peer_addr()
|
||||
.map_err(|_| "Unable to get peer address")?;
|
||||
Ok(R2Pipe::Tcp(R2PipeTcp { socket_addr: addr }))
|
||||
let stream = TcpStream::connect(addr)?;
|
||||
let addr = stream.peer_addr()?;
|
||||
Ok(R2Pipe(Box::new(R2PipeTcp { socket_addr: addr })))
|
||||
}
|
||||
|
||||
/// Creates a new R2PipeHttp
|
||||
pub fn http(host: &str) -> Result<R2Pipe, &'static str> {
|
||||
Ok(R2Pipe::Http(R2PipeHttp {
|
||||
pub fn http(host: &str) -> R2Pipe {
|
||||
R2Pipe(Box::new(R2PipeHttp {
|
||||
host: host.to_string(),
|
||||
}))
|
||||
}
|
||||
@@ -247,9 +244,9 @@ impl R2Pipe {
|
||||
names: Vec<&'static str>,
|
||||
opts: Vec<Option<R2PipeSpawnOptions>>,
|
||||
callback: Option<Arc<dyn Fn(u16, String) + Sync + Send>>,
|
||||
) -> Result<Vec<R2PipeThread>, &'static str> {
|
||||
) -> Result<Vec<R2PipeThread>> {
|
||||
if names.len() != opts.len() {
|
||||
return Err("Please provide 2 Vectors of the same size for names and options");
|
||||
return Err(Error::ArgumentMismatch);
|
||||
}
|
||||
|
||||
let mut pipes = Vec::new();
|
||||
@@ -260,21 +257,22 @@ impl R2Pipe {
|
||||
let name = names[n];
|
||||
let opt = opts[n].clone();
|
||||
let cb = callback.clone();
|
||||
let t = thread::spawn(move || {
|
||||
let mut r2 = R2Pipe::spawn(name, opt).unwrap();
|
||||
let t = thread::spawn(move || -> Result<()> {
|
||||
let mut r2 = R2Pipe::spawn(name, opt)?;
|
||||
loop {
|
||||
let cmd: String = hrx.recv().unwrap();
|
||||
let cmd: String = hrx.recv()?;
|
||||
if cmd == "q" {
|
||||
break;
|
||||
}
|
||||
let res = r2.cmdj(&cmd).unwrap().to_string();
|
||||
htx.send(res.clone()).unwrap();
|
||||
let res = r2.cmdj(&cmd)?.to_string();
|
||||
htx.send(res.clone())?;
|
||||
if let Some(cbs) = cb.clone() {
|
||||
thread::spawn(move || {
|
||||
cbs(n as u16, res);
|
||||
});
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
pipes.push(R2PipeThread {
|
||||
r2recv: rx,
|
||||
@@ -288,102 +286,158 @@ impl R2Pipe {
|
||||
}
|
||||
|
||||
impl R2PipeThread {
|
||||
pub fn send(&self, cmd: String) -> Result<(), &'static str> {
|
||||
self.r2send.send(cmd).map_err(|_| "Channel send error")
|
||||
pub fn send(&self, cmd: String) -> Result<()> {
|
||||
Ok(self.r2send.send(cmd)?)
|
||||
}
|
||||
|
||||
pub fn recv(&self, block: bool) -> Result<String, &'static str> {
|
||||
pub fn recv(&self, block: bool) -> Result<String> {
|
||||
if block {
|
||||
return self.r2recv.recv().map_err(|_| "Channel recv error");
|
||||
Ok(self.r2recv.recv()?)
|
||||
} else {
|
||||
Ok(self.r2recv.try_recv()?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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())?;
|
||||
|
||||
let mut res: Vec<u8> = Vec::new();
|
||||
self.read.read_until(0u8, &mut res)?;
|
||||
process_result(res)
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
let _ = self.cmd("q!");
|
||||
if let Some(child) = &mut self.child {
|
||||
let _ = child.wait();
|
||||
}
|
||||
self.r2recv.try_recv().map_err(|_| "Channel try_recv error")
|
||||
}
|
||||
}
|
||||
|
||||
impl R2PipeSpawn {
|
||||
pub fn cmd(&mut self, cmd: &str) -> Result<String, String> {
|
||||
let cmd = cmd.to_owned() + "\n";
|
||||
self.write
|
||||
.write_all(cmd.as_bytes())
|
||||
.map_err(|e| e.to_string())?;
|
||||
/// 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)
|
||||
.map_err(|e| e.to_string())?;
|
||||
self.read.read_until(0u8, &mut res)?;
|
||||
process_result(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmdj(&mut self, cmd: &str) -> Result<Value, String> {
|
||||
let result = self.cmd(cmd)?;
|
||||
if result == "" {
|
||||
return Err("Empty JSON".to_string());
|
||||
}
|
||||
serde_json::from_str(&result).map_err(|e| e.to_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 {
|
||||
&self.host
|
||||
};
|
||||
let mut stream = TcpStream::connect(host)?;
|
||||
let req = format!("GET /cmd/{} HTTP/1.1\r\n", cmd);
|
||||
let mut resp = Vec::with_capacity(1024);
|
||||
stream.write_all(req.as_bytes())?;
|
||||
stream.read_to_end(&mut resp)?;
|
||||
|
||||
pub fn close(&mut self) {
|
||||
let _ = self.cmd("q!");
|
||||
// index of the start of response body
|
||||
let index = resp
|
||||
.windows(4)
|
||||
.position(|w| w == "\r\n\r\n".as_bytes())
|
||||
.map(|i| i + 4)
|
||||
.unwrap_or(0);
|
||||
|
||||
Ok(str::from_utf8(&resp[index..]).map(|s| s.to_string())?)
|
||||
}
|
||||
}
|
||||
|
||||
impl R2PipeLang {
|
||||
pub fn cmd(&mut self, cmd: &str) -> Result<String, String> {
|
||||
self.write.write_all(cmd.as_bytes()).unwrap();
|
||||
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();
|
||||
self.read.read_until(0u8, &mut res).unwrap();
|
||||
process_result(res)
|
||||
}
|
||||
|
||||
pub fn cmdj(&mut self, cmd: &str) -> Result<Value, String> {
|
||||
let res = self.cmd(cmd)?;
|
||||
|
||||
serde_json::from_str(&res).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
// self.read.close();
|
||||
// self.write.close();
|
||||
}
|
||||
}
|
||||
|
||||
impl R2PipeHttp {
|
||||
pub fn cmd(&mut self, cmd: &str) -> Result<String, String> {
|
||||
let url = format!("http://{}/cmd/{}", self.host, cmd);
|
||||
let res = reqwest::get(&url).unwrap();
|
||||
let bytes = res.bytes().filter_map(|e| e.ok()).collect::<Vec<_>>();
|
||||
str::from_utf8(bytes.as_slice())
|
||||
.map(|s| s.to_string())
|
||||
.map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
pub fn cmdj(&mut self, cmd: &str) -> Result<Value, String> {
|
||||
let res = self.cmd(cmd)?;
|
||||
serde_json::from_str(&res).map_err(|e| format!("Unable to parse json: {}", e))
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {}
|
||||
}
|
||||
|
||||
impl R2PipeTcp {
|
||||
pub fn cmd(&mut self, cmd: &str) -> Result<String, String> {
|
||||
let mut stream = TcpStream::connect(self.socket_addr)
|
||||
.map_err(|e| format!("Unable to connect TCP stream: {}", e))?;
|
||||
stream
|
||||
.write_all(cmd.as_bytes())
|
||||
.map_err(|e| format!("Unable to write to TCP stream: {}", e))?;
|
||||
let mut res: Vec<u8> = Vec::new();
|
||||
stream
|
||||
.read_to_end(&mut res)
|
||||
.map_err(|e| format!("Unable to read from TCP stream: {}", e))?;
|
||||
stream.read_to_end(&mut res)?;
|
||||
res.push(0);
|
||||
process_result(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmdj(&mut self, cmd: &str) -> Result<Value, String> {
|
||||
let res = self.cmd(cmd)?;
|
||||
serde_json::from_str(&res).map_err(|e| format!("Unable to parse json: {}", e))
|
||||
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 lib_name = if cfg!(windows) { "r_core" } else { "libr_core" };
|
||||
let mut lib = dlfcn::LibHandle::new(lib_name, 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;
|
||||
#[cfg(not(windows))]
|
||||
use crate::R2Pipe;
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
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("malloc://32").unwrap();
|
||||
assert_eq!("a\n", r2p.cmd("echo a").unwrap());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user