diff --git a/Cargo.lock b/Cargo.lock index f243ff42..57716f4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -560,6 +560,7 @@ dependencies = [ "serde_json", "tracing", "tracing-subscriber", + "winapi", ] [[package]] diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index c0c33acf..c1e6cf33 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -235,7 +235,7 @@ fn should_do_native_call_on_system_array_copy() { ); } -use crate::utils::get_output; +use crate::utils::{assert_file, get_output}; use std::time::{SystemTime, UNIX_EPOCH}; #[test] @@ -462,62 +462,39 @@ fn should_use_grandparent_method_via_super() { ); } -use std::fs; - #[test] fn should_write_file_to_fs() { - let file_path = "tests/tmp/test.txt"; - - assert_success( + assert_file( "samples.io.fileoutputstreamexample.FileOutputStreamExample", - "", - ); - - assert!(fs::metadata(file_path).is_ok(), "File does not exist"); - let content = fs::read_to_string(file_path).expect("Failed to read file"); - assert_eq!(content, "CAFEBABE", "File content does not match"); - fs::remove_file(file_path).expect("Failed to delete file"); + "tests/tmp/test.txt", + "CAFEBABE", + ) } #[test] fn should_write_file_to_fs_with_buffered_stream() { - let file_path = "tests/tmp/buffered_output.txt"; - - assert_success( + assert_file( "samples.io.bufferedoutputstreamchunkingexample.BufferedOutputStreamChunkingExample", - "", - ); - - assert!(fs::metadata(file_path).is_ok(), "File does not exist"); - let content = fs::read_to_string(file_path).expect("Failed to read file"); - assert_eq!( - content, "This is a test for BufferedOutputStream chunking.", - "File content does not match" - ); - fs::remove_file(file_path).expect("Failed to delete file"); + "tests/tmp/buffered_output.txt", + "This is a test for BufferedOutputStream chunking.", + ) } #[test] fn should_write_file_with_print_stream() { - let file_path = "tests/tmp/print_stream_test.txt"; - - assert_success("samples.io.printstreamexample.PrintStreamExample", ""); - - assert!(fs::metadata(file_path).is_ok(), "File does not exist"); - let content = fs::read_to_string(file_path).expect("Failed to read file"); - assert_eq!( - content, - r#"Hello, PrintStream! + let expected_file_content = r#"Hello, PrintStream! First Line Second Line Third Line Hello as raw bytes This is written immediately. This follows after flush. This is an example of chaining PrintStreams. -"#, - "File content does not match" +"#; + assert_file( + "samples.io.printstreamexample.PrintStreamExample", + "tests/tmp/print_stream_test.txt", + expected_file_content, ); - fs::remove_file(file_path).expect("Failed to delete file"); } #[test] diff --git a/tests/utils.rs b/tests/utils.rs index daa33230..080a352c 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -1,20 +1,13 @@ use assert_cmd::Command; -use std::env; +use std::{env, fs}; const PATH: &str = "tests/test_data"; -fn get_command(entry: &str) -> Command { - let repo_path = env::current_dir().expect("Failed to get current directory"); - - let mut cmd = Command::cargo_bin("rusty-jvm").expect("Failed to locate rusty-jvm binary"); - cmd.env("RUSTY_JAVA_HOME", repo_path) - .current_dir(PATH) - .arg(entry); - cmd -} - #[allow(dead_code)] pub fn assert_success(entry: &str, expected: &str) { + #[cfg(target_os = "windows")] + let expected = to_windows(expected); + get_command(entry) .assert() .success() @@ -29,3 +22,33 @@ pub fn get_output(entry: &str) -> String { String::from_utf8(output.stdout).expect("Failed to convert output to string") } + +pub fn assert_file(entry: &str, file_path: &str, expected_file_content: &str) { + assert_success(entry, ""); + + #[cfg(target_os = "windows")] + let expected_file_content = to_windows(expected_file_content); + + assert!(fs::metadata(file_path).is_ok(), "File does not exist"); + let content = fs::read_to_string(file_path).expect("Failed to read file"); + assert_eq!( + content, expected_file_content, + "File content does not match" + ); + fs::remove_file(file_path).expect("Failed to delete file"); +} + +fn get_command(entry: &str) -> Command { + let repo_path = env::current_dir().expect("Failed to get current directory"); + + let mut cmd = Command::cargo_bin("rusty-jvm").expect("Failed to locate rusty-jvm binary"); + cmd.env("RUSTY_JAVA_HOME", repo_path) + .current_dir(PATH) + .arg(entry); + cmd +} + +#[cfg(target_os = "windows")] +fn to_windows(input: &str) -> String { + input.replace("\n", "\r\n") +} diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 56ff547b..1cb1a9f0 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -14,3 +14,4 @@ tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"]} num-traits = "0.2.19" murmur3 = "0.5.2" +winapi = { version = "0.3.9", features = ["winbase", "processenv", "minwindef"] } diff --git a/vm/src/execution_engine/system_native_table.rs b/vm/src/execution_engine/system_native_table.rs index 9723e56f..1b2a8461 100644 --- a/vm/src/execution_engine/system_native_table.rs +++ b/vm/src/execution_engine/system_native_table.rs @@ -6,7 +6,7 @@ use crate::system_native::class::{ class_init_class_name_wrp, for_name0_wrp, get_modifiers_wrp, get_primitive_class_wrp, is_array_wrp, is_interface_wrp, is_primitive_wrp, }; -use crate::system_native::file_descriptor::file_descriptor_close0_wrp; +use crate::system_native::file_descriptor::{file_descriptor_close0_wrp, get_handle_wrp}; use crate::system_native::file_output_stream::{ file_output_stream_open0_wrp, file_output_stream_write_bytes_wrp, file_output_stream_write_wrp, }; @@ -181,7 +181,7 @@ static SYSTEM_NATIVE_TABLE: Lazy<HashMap<&'static str, NativeMethod>> = Lazy::ne table.insert("java/io/FileDescriptor:initIDs:()V", Basic(void_stub)); table.insert( "java/io/FileDescriptor:getHandle:(I)J", - Basic(|args: &[i32]| return_argument_stub(&vec![0, args[0]])), + Basic(get_handle_wrp), ); table.insert( "java/io/FileDescriptor:getAppend:(I)Z", diff --git a/vm/src/system_native/file_descriptor.rs b/vm/src/system_native/file_descriptor.rs index 0295066d..b10bd26b 100644 --- a/vm/src/system_native/file_descriptor.rs +++ b/vm/src/system_native/file_descriptor.rs @@ -1,20 +1,18 @@ -use crate::heap::heap::with_heap_read_lock; -use std::fs::File; -use std::os::fd::FromRawFd; +use crate::helper::i64_to_vec; + +use crate::system_native::PlatformFile; pub(crate) fn file_descriptor_close0_wrp(args: &[i32]) -> crate::error::Result<Vec<i32>> { let fd_ref = args[0]; - close0(fd_ref)?; + PlatformFile::close(fd_ref)?; Ok(vec![]) } -fn close0(fd_ref: i32) -> crate::error::Result<()> { - let raw_fd = with_heap_read_lock(|heap| { - heap.get_object_field_value(fd_ref, "java/io/FileDescriptor", "fd") - })?[0]; +pub(crate) fn get_handle_wrp(args: &[i32]) -> crate::error::Result<Vec<i32>> { + let fd = args[0]; + + let handle = PlatformFile::get_handle(fd)?; - let file = unsafe { File::from_raw_fd(raw_fd) }; - drop(file); - Ok(()) + Ok(i64_to_vec(handle)) } diff --git a/vm/src/system_native/file_output_stream.rs b/vm/src/system_native/file_output_stream.rs index e41afbf6..7a8a8f22 100644 --- a/vm/src/system_native/file_output_stream.rs +++ b/vm/src/system_native/file_output_stream.rs @@ -1,9 +1,8 @@ -use crate::heap::heap::{with_heap_read_lock, with_heap_write_lock}; +use crate::heap::heap::with_heap_read_lock; use crate::system_native::string::get_utf8_string_by_ref; +use crate::system_native::PlatformFile; use std::fs::OpenOptions; use std::io::Write; -use std::mem::ManuallyDrop; -use std::os::fd::{FromRawFd, IntoRawFd, RawFd}; pub(crate) fn file_output_stream_open0_wrp(args: &[i32]) -> crate::error::Result<Vec<i32>> { let obj_ref = args[0]; @@ -16,18 +15,14 @@ pub(crate) fn file_output_stream_open0_wrp(args: &[i32]) -> crate::error::Result fn open0(obj_ref: i32, file_name_ref: i32, append: bool) -> crate::error::Result<()> { let file_name = get_utf8_string_by_ref(file_name_ref)?; - let posix_fd = open_file_raw(&file_name, append)?; // throw FileNotFoundException if file not found - let fd = posix_fd as i32; - - with_heap_write_lock(|heap| { - let fd_ref = heap - .get_object_field_value(obj_ref, "java/io/FileOutputStream", "fd") - .expect("fd field not found")[0]; - heap.set_object_field_value(fd_ref, "java/io/FileDescriptor", "fd", vec![fd]) - .expect("fd field not found"); - }); + let file = OpenOptions::new() // throw FileNotFoundException if file not found + .write(true) + .create(true) + .truncate(!append) + .append(append) + .open(file_name)?; - Ok(()) + Ok(PlatformFile::set_raw_id(obj_ref, file)?) } pub(crate) fn file_output_stream_write_wrp(args: &[i32]) -> crate::error::Result<Vec<i32>> { @@ -38,9 +33,7 @@ pub(crate) fn file_output_stream_write_wrp(args: &[i32]) -> crate::error::Result Ok(vec![]) } fn write(obj_ref: i32, byte: i32, _append: bool) -> crate::error::Result<()> { - let fd = extract_fd(obj_ref)?; - - let mut file = ManuallyDrop::new(unsafe { std::fs::File::from_raw_fd(fd) }); // ManuallyDrop prevents `file` from being dropped + let mut file = PlatformFile::get_by_raw_id(obj_ref)?; write!(file, "{}", byte as u8 as char)?; @@ -69,10 +62,7 @@ fn write_bytes( len: i32, _append: bool, ) -> crate::error::Result<()> { - let fd = extract_fd(obj_ref)?; - - let mut file = ManuallyDrop::new(unsafe { std::fs::File::from_raw_fd(fd) }); // ManuallyDrop prevents `file` from being dropped - + let mut file = PlatformFile::get_by_raw_id(obj_ref)?; let array = with_heap_read_lock(|heap| { heap.get_entire_array(bytes_ref) .expect("error getting array value") @@ -87,22 +77,3 @@ fn write_bytes( Ok(()) } - -fn extract_fd(obj_ref: i32) -> crate::error::Result<i32> { - with_heap_read_lock(|heap| { - let fd_ref = heap.get_object_field_value(obj_ref, "java/io/FileOutputStream", "fd")?[0]; - let fd = heap.get_object_field_value(fd_ref, "java/io/FileDescriptor", "fd")?[0]; - - Ok(fd) - }) -} - -fn open_file_raw(file_name: &str, append: bool) -> std::io::Result<RawFd> { - let file = OpenOptions::new() - .write(true) - .create(true) - .truncate(!append) - .append(append) - .open(file_name)?; - Ok(file.into_raw_fd()) // move ownership out of file -} diff --git a/vm/src/system_native/mod.rs b/vm/src/system_native/mod.rs index 1e847c76..d91811f1 100644 --- a/vm/src/system_native/mod.rs +++ b/vm/src/system_native/mod.rs @@ -8,3 +8,15 @@ pub(crate) mod system; pub(crate) mod system_props_raw; pub(crate) mod thread; pub(crate) mod unsafe_; + +//// Platform-specific file operations. +#[cfg(unix)] +mod unix_file; +#[cfg(windows)] +mod windows_file; + +#[cfg(unix)] +pub use unix_file::PlatformFile; +#[cfg(windows)] +pub use windows_file::PlatformFile; +//// diff --git a/vm/src/system_native/unix_file.rs b/vm/src/system_native/unix_file.rs new file mode 100644 index 00000000..21daf99e --- /dev/null +++ b/vm/src/system_native/unix_file.rs @@ -0,0 +1,52 @@ +use crate::error::Error; +use crate::heap::heap::{with_heap_read_lock, with_heap_write_lock}; +use std::fs::File; +use std::mem::ManuallyDrop; +use std::os::fd::{FromRawFd, IntoRawFd}; + +pub struct PlatformFile {} + +impl PlatformFile { + pub fn close(file_descriptor_ref: i32) -> crate::error::Result<()> { + let raw_fd = with_heap_read_lock(|heap| { + heap.get_object_field_value(file_descriptor_ref, "java/io/FileDescriptor", "fd") + })?[0]; + + let file = unsafe { File::from_raw_fd(raw_fd) }; + drop(file); + + Ok(()) + } + + pub fn get_handle(_fd: i32) -> crate::error::Result<i64> { + Ok(0) + } + + pub fn set_raw_id(output_stream_ref: i32, file: File) -> std::io::Result<()> { + let posix_fd = file.into_raw_fd(); // move ownership out of file + let fd = posix_fd as i32; + + with_heap_write_lock(|heap| { + let fd_ref = heap + .get_object_field_value(output_stream_ref, "java/io/FileOutputStream", "fd") + .expect("fd field not found")[0]; + heap.set_object_field_value(fd_ref, "java/io/FileDescriptor", "fd", vec![fd]) + .expect("fd field not found"); + }); + + Ok(()) + } + + pub fn get_by_raw_id(obj_ref: i32) -> crate::error::Result<ManuallyDrop<File>> { + let fd = with_heap_read_lock(|heap| { + let fd_ref = + heap.get_object_field_value(obj_ref, "java/io/FileOutputStream", "fd")?[0]; + let fd = heap.get_object_field_value(fd_ref, "java/io/FileDescriptor", "fd")?[0]; + + Ok::<i32, Error>(fd) + })?; + + let file = ManuallyDrop::new(unsafe { std::fs::File::from_raw_fd(fd) }); // ManuallyDrop prevents `file` from being dropped + Ok(file) + } +} diff --git a/vm/src/system_native/windows_file.rs b/vm/src/system_native/windows_file.rs new file mode 100644 index 00000000..638ee8e1 --- /dev/null +++ b/vm/src/system_native/windows_file.rs @@ -0,0 +1,81 @@ +use crate::error::Error; +use crate::heap::heap::{with_heap_read_lock, with_heap_write_lock}; +use crate::helper::{i32toi64, i64_to_vec}; +use std::fs::File; +use std::mem::ManuallyDrop; +use std::os::windows::io::{FromRawHandle, IntoRawHandle, RawHandle}; +use winapi::shared::minwindef::DWORD; +use winapi::um::processenv::GetStdHandle; +use winapi::um::winbase::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}; + +pub struct PlatformFile {} + +impl PlatformFile { + pub fn close(file_descriptor_ref: i32) -> crate::error::Result<()> { + let raw_fd = with_heap_read_lock(|heap| { + let raw = heap.get_object_field_value( + file_descriptor_ref, + "java/io/FileDescriptor", + "handle", + )?; + Ok::<i64, Error>(i32toi64(raw[0], raw[1])) + })?; + + eprintln!("close0: raw_fd={raw_fd}"); + + let file = unsafe { File::from_raw_handle(raw_fd as RawHandle) }; + drop(file); + Ok(()) + } + + pub fn get_handle(fd: i32) -> crate::error::Result<i64> { + match fd { + 0 => Self::get_std_handle(STD_INPUT_HANDLE), + 1 => Self::get_std_handle(STD_OUTPUT_HANDLE), + 2 => Self::get_std_handle(STD_ERROR_HANDLE), + _ => Err(Error::new_execution(&format!("fd {fd} is not supported"))), + } + } + + fn get_std_handle(handle: DWORD) -> crate::error::Result<i64> { + unsafe { + let handle = GetStdHandle(handle); + if handle.is_null() { + return Err(Error::new_execution(&format!( + "Failed to get handle by {handle:?}" + ))); + } + + Ok(handle as isize as i64) + } + } + + pub fn set_raw_id(output_stream_ref: i32, file: File) -> std::io::Result<()> { + let raw_handle = file.into_raw_handle(); // move ownership out of file + let handle = raw_handle as isize as i64; + let raw = i64_to_vec(handle); + + with_heap_write_lock(|heap| { + let fd_ref = heap + .get_object_field_value(output_stream_ref, "java/io/FileOutputStream", "fd") + .expect("fd field not found")[0]; + heap.set_object_field_value(fd_ref, "java/io/FileDescriptor", "handle", raw) + .expect("handle field not found"); + }); + + Ok(()) + } + + pub fn get_by_raw_id(obj_ref: i32) -> crate::error::Result<ManuallyDrop<File>> { + let handle = with_heap_read_lock(|heap| { + let fd_ref = + heap.get_object_field_value(obj_ref, "java/io/FileOutputStream", "fd")?[0]; + let raw = heap.get_object_field_value(fd_ref, "java/io/FileDescriptor", "handle")?; + + Ok::<i64, Error>(i32toi64(raw[0], raw[1])) + })?; + + let file = ManuallyDrop::new(unsafe { File::from_raw_handle(handle as RawHandle) }); // ManuallyDrop prevents `file` from being dropped + Ok(file) + } +}