use std::{
fs::File,
io::{self, Error as IoError, Read, Write},
os::fd::FromRawFd,
sync::Arc,
};
use jni::{
Executor, JNIEnv,
errors::Error as JniError,
objects::{JClass, JObject, JValueGen},
sys::{jint, jlong, jsize},
};
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_app_accrescent_ina_Patcher_patch(
env: JNIEnv,
_class: JClass,
old_file_fd: jint,
patch: JObject,
new: JObject,
) -> jlong {
let old_file = unsafe { File::from_raw_fd(old_file_fd) };
let vm = match env.get_java_vm() {
Ok(vm) => Arc::new(vm),
Err(_) => return -1,
};
let patch_stream = InputStream::new(Executor::new(Arc::clone(&vm)), patch);
let mut new_stream = OutputStream::new(Executor::new(vm), new);
match crate::patch(old_file, patch_stream, &mut new_stream) {
Ok(read) => read as jlong,
Err(_) => -1,
}
}
struct InputStream<'a> {
executor: Executor,
input_stream: JObject<'a>,
}
impl<'a> InputStream<'a> {
fn new(executor: Executor, input_stream: JObject<'a>) -> Self {
Self {
executor,
input_stream,
}
}
}
impl<'a> Read for InputStream<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.executor
.with_attached(|env| {
let java_buf_len: jsize = buf.len().try_into().unwrap_or(jsize::MAX);
let java_buf = env.new_byte_array(java_buf_len)?;
let read: jint = env
.call_method(
&self.input_stream,
"read",
"([BII)I",
&[
JValueGen::Object(&java_buf),
JValueGen::Int(0),
JValueGen::Int(java_buf_len),
],
)?
.try_into()?;
env.get_byte_array_region(java_buf, 0, bytemuck::cast_slice_mut::<u8, i8>(buf))?;
Ok(read)
})
.map(|read| read.try_into().unwrap_or(0))
.map_err(|e: JniError| IoError::other(e))
}
}
struct OutputStream<'a> {
executor: Executor,
output_stream: JObject<'a>,
}
impl<'a> OutputStream<'a> {
fn new(executor: Executor, output_stream: JObject<'a>) -> Self {
Self {
executor,
output_stream,
}
}
}
impl<'a> Write for OutputStream<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.executor
.with_attached(|env| {
let java_buf = env.byte_array_from_slice(buf)?;
env.call_method(
&self.output_stream,
"write",
"([B)V",
&[JValueGen::Object(&java_buf)],
)?;
Ok(buf.len())
})
.map_err(|e: JniError| IoError::other(e))
}
fn flush(&mut self) -> io::Result<()> {
self.executor
.with_attached(|env| {
env.call_method(&self.output_stream, "flush", "()V", &[])?;
Ok(())
})
.map_err(|e: JniError| IoError::other(e))
}
}
#[unsafe(no_mangle)]
#[cfg(feature = "sandbox")]
extern "system" fn Java_app_accrescent_ina_Patcher_enableSandbox(
_env: JNIEnv,
_class: JClass,
) -> jint {
match crate::sandbox::enable_for_patching() {
Ok(enabled) => jint::from(enabled),
Err(_) => -1,
}
}