Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add file support for Windows #138

Merged
merged 1 commit into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 15 additions & 38 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
45 changes: 34 additions & 11 deletions tests/utils.rs
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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")
}
1 change: 1 addition & 0 deletions vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
4 changes: 2 additions & 2 deletions vm/src/execution_engine/system_native_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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",
Expand Down
20 changes: 9 additions & 11 deletions vm/src/system_native/file_descriptor.rs
Original file line number Diff line number Diff line change
@@ -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))
}
51 changes: 11 additions & 40 deletions vm/src/system_native/file_output_stream.rs
Original file line number Diff line number Diff line change
@@ -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];
Expand All @@ -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>> {
Expand All @@ -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)?;

Expand Down Expand Up @@ -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")
Expand All @@ -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
}
12 changes: 12 additions & 0 deletions vm/src/system_native/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
////
52 changes: 52 additions & 0 deletions vm/src/system_native/unix_file.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading
Loading