diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 300a4f9..89d7e6b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,6 +13,7 @@ permissions: pull-requests: write env: CARGO_TERM_COLOR: always + MOONBIT_INSTALL_VERSION: 0.6.19 jobs: build: @@ -33,7 +34,7 @@ jobs: override: true - name: Install moonbit run: | - curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash -s -- 0.6.19 + curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash -s -- ${{env.MOONBIT_INSTALL_VERSION}} echo "$HOME/.moon/bin" >> $GITHUB_PATH - name: Bundle core MoonBit library run: moon bundle --target wasm @@ -66,7 +67,7 @@ jobs: run: cargo binstall --force --locked wasmtime-cli@33.0.0 - name: Install moonbit run: | - curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash -s -- 0.6.19 + curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash -s -- ${{env.MOONBIT_INSTALL_VERSION}} echo "$HOME/.moon/bin" >> $GITHUB_PATH - name: Bundle core MoonBit library run: moon bundle --target wasm @@ -80,8 +81,77 @@ jobs: report_paths: "**/target/report-*.xml" detailed_summary: true include_passed: true + check_name: "Unix Unit Tests" + build-win: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: Swatinem/rust-cache@v2 + with: + prefix-key: v1-rust + shared-key: debug + cache-all-crates: true + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - name: Install moonbit + shell: pwsh + run: | + Set-ExecutionPolicy RemoteSigned -Scope CurrentUser; irm https://cli.moonbitlang.com/install/powershell.ps1 | iex + echo ($env:USERPROFILE + "\.moon\bin") | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Bundle core MoonBit library + run: moon.exe bundle --target wasm + working-directory: core + - name: Check formatting + run: cargo fmt -- --check + - name: Clippy + run: cargo clippy -- -Dwarnings + - name: Build + run: cargo build --all-features --all-targets + test-win: + needs: [build-win] + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: Swatinem/rust-cache@v2 + with: + prefix-key: v1-rust + shared-key: debug + cache-all-crates: false + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - uses: cargo-bins/cargo-binstall@main + - name: Install wasmtime-cli + run: cargo binstall --force --locked wasmtime-cli@33.0.0 + - name: Install moonbit + shell: pwsh + run: | + Set-ExecutionPolicy RemoteSigned -Scope CurrentUser; irm https://cli.moonbitlang.com/install/powershell.ps1 | iex + echo ($env:USERPROFILE + "\.moon\bin") | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Bundle core MoonBit library + run: moon.exe bundle --target wasm + working-directory: core + - name: Tests + run: cargo test -p moonbit-component-generator -- --nocapture --report-time --format junit --test-threads=1 > target/report-win.xml + - name: Publish Test Report + uses: mikepenz/action-junit-report@v5 + if: success() || failure() # always run even if the previous step fails + with: + report_paths: "**/target/report-*.xml" + detailed_summary: true + include_passed: true + check_name: "Windows Unit Tests" publish: - needs: [test] + needs: [test, test-win] if: "startsWith(github.ref, 'refs/tags/v')" runs-on: ubuntu-latest steps: @@ -96,7 +166,7 @@ jobs: override: true - name: Install moonbit run: | - curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash -s -- 0.6.19 + curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash -s -- ${{env.MOONBIT_INSTALL_VERSION}} echo "$HOME/.moon/bin" >> $GITHUB_PATH - name: Bundle core MoonBit library run: moon bundle --target wasm diff --git a/Cargo.lock b/Cargo.lock index 544c84d..3e393c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -802,6 +802,7 @@ dependencies = [ "serde_json", "test-r", "topologic", + "unix_path", "v8", "wit-bindgen-core", "wit-bindgen-moonbit", @@ -1271,6 +1272,21 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unix_path" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8e291873ae77c4c8d9c9b34d0bee68a35b048fb39c263a5155e0e353783eaf" +dependencies = [ + "unix_str", +] + +[[package]] +name = "unix_str" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ace0b4755d0a2959962769239d56267f8a024fef2d9b32666b3dcd0946b0906" + [[package]] name = "unty" version = "0.0.4" diff --git a/Cargo.toml b/Cargo.toml index a96ade5..c0f8327 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ log = "0.4.27" serde = { version = "1", features = ["derive"] } serde_json = "1" topologic = "1.1.0" +unix_path = "1.0.1" v8 = "0.106.0" wit-bindgen-core = "0.43.0" wit-bindgen-moonbit = "0.43.0" diff --git a/moonc_wasm/src/lib.rs b/moonc_wasm/src/lib.rs index d24512f..736193c 100644 --- a/moonc_wasm/src/lib.rs +++ b/moonc_wasm/src/lib.rs @@ -1,3 +1,6 @@ +use std::path::Path as StdPath; +use unix_path::PathBuf as UnixPathBuf; + mod wasmoo_extern; pub fn run_wasmoo(argv: Vec) -> anyhow::Result<()> { @@ -16,8 +19,12 @@ pub fn run_wasmoo(argv: Vec) -> anyhow::Result<()> { let process_argv = v8::Array::new(scope, argv.len() as i32); for (i, s) in argv.iter().enumerate() { - let s = v8::String::new(scope, s).unwrap(); - process_argv.set_index(scope, i as u32, s.into()); + let p = as_unix_path(s); + let v8_str = match p { + Some(p_str) => v8::String::new(scope, &p_str).unwrap(), + None => v8::String::new(scope, s).unwrap(), + }; + process_argv.set_index(scope, i as u32, v8_str.into()); } let ident = v8::String::new(scope, "process_argv").unwrap(); global_proxy.set(scope, ident.into(), process_argv.into()); @@ -54,3 +61,19 @@ pub fn initialize_v8() -> anyhow::Result<()> { v8::V8::initialize(); Ok(()) } + +pub fn as_unix_path(path_str: &String) -> Option { + let path = StdPath::new(path_str); + if !path.has_root() { + return None; + } + let mut u_path = UnixPathBuf::new(); + for component in path.components() { + if let Some(part_str) = component.as_os_str().to_str() { + u_path.push(part_str); + } else { + return None; // Invalid UTF-8 sequence in path component + } + } + u_path.to_str().map(String::from) +} diff --git a/moonc_wasm/src/wasmoo_extern.rs b/moonc_wasm/src/wasmoo_extern.rs index 64a3c96..0ca2a4b 100644 --- a/moonc_wasm/src/wasmoo_extern.rs +++ b/moonc_wasm/src/wasmoo_extern.rs @@ -1,16 +1,18 @@ use std::{ collections::HashMap, - ffi::CString, - fs::{self, File, OpenOptions, Permissions, metadata}, + fs::{self, File, OpenOptions, metadata}, io::{IsTerminal, Read, Write}, - os::{ - fd::AsRawFd, - unix::fs::{FileTypeExt, MetadataExt, OpenOptionsExt, PermissionsExt}, - }, path::Path, process::{Command, Stdio}, }; +#[cfg(unix)] +use std::{ + ffi::CString, + os::fd::AsRawFd, + os::unix::fs::{FileTypeExt, MetadataExt, OpenOptionsExt, PermissionsExt}, +}; + // getenv : JSString -> JSString fn getenv( scope: &mut v8::HandleScope, @@ -144,24 +146,33 @@ fn chmod( args: v8::FunctionCallbackArguments, mut ret: v8::ReturnValue, ) { - let path = args.get(0); - let path = path.to_string(scope).unwrap(); - let path = path.to_rust_string_lossy(scope); - let path = Path::new(&path); - let mode = args.get(1); - let mode = mode.to_number(scope).unwrap().value() as u32; - let permission = Permissions::from_mode(mode); - match fs::set_permissions(path, permission) { - Err(err) => { - let message = v8::String::new(scope, &err.to_string()).unwrap(); - let exn = v8::Exception::error(scope, message); - scope.throw_exception(exn); - } - Ok(_) => { - let undefined = v8::undefined(scope); - ret.set(undefined.into()) + #[cfg(unix)] + { + let path = args.get(0); + let path = path.to_string(scope).unwrap(); + let path = path.to_rust_string_lossy(scope); + let path = Path::new(&path); + let mode = args.get(1); + let mode = mode.to_number(scope).unwrap().value() as u32; + let permission = PermissionsExt::from_mode(mode); + match fs::set_permissions(path, permission) { + Err(err) => { + let message = v8::String::new(scope, &err.to_string()).unwrap(); + let exn = v8::Exception::error(scope, message); + scope.throw_exception(exn); + } + Ok(_) => { + let undefined = v8::undefined(scope); + ret.set(undefined.into()) + } } } + #[cfg(not(unix))] + { + let _ = args; + let undefined = v8::undefined(scope); + ret.set(undefined.into()) + } } // truncate: JSString, u64 as Length -> undefined @@ -251,11 +262,17 @@ const O_APPEND: i32 = 8; const O_CREAT: i32 = 16; const O_TRUNC: i32 = 32; const O_EXCL: i32 = 64; + +#[cfg(unix)] const O_NONBLOCK: i32 = 128; +#[cfg(unix)] const O_NOCTTY: i32 = 256; +#[cfg(unix)] const O_DSYNC: i32 = 512; +#[cfg(unix)] const O_SYNC: i32 = 1024; +#[cfg(unix)] // open : JSString as Path, Number as Flags, Number as PermissionMode -> FileDescriptor fn open( scope: &mut v8::HandleScope, @@ -299,16 +316,16 @@ fn open( } let mut custom_flags = 0; if (flags & O_NONBLOCK) != 0 { - custom_flags |= libc::O_NONBLOCK; + custom_flags |= O_NONBLOCK; } if (flags & O_NOCTTY) != 0 { - custom_flags |= libc::O_NOCTTY; + custom_flags |= O_NOCTTY; } if (flags & O_DSYNC) != 0 { - custom_flags |= libc::O_DSYNC; + custom_flags |= O_DSYNC; } if (flags & O_SYNC) != 0 { - custom_flags |= libc::O_SYNC; + custom_flags |= O_SYNC; } opts.custom_flags(custom_flags); opts.mode((mode & 0o777) as u32); // assure permission is legal @@ -328,6 +345,64 @@ fn open( } } +#[cfg(not(unix))] +// open : JSString as Path, Number as Flags, Number as PermissionMode -> FileDescriptor +fn open( + scope: &mut v8::HandleScope, + args: v8::FunctionCallbackArguments, + mut ret: v8::ReturnValue, +) { + let path = args.get(0); + let path = path.to_string(scope).unwrap(); + let path = path.to_rust_string_lossy(scope); + let flags = args.get(1); + let flags = flags.to_number(scope).unwrap().value() as i32; + let _mode = args.get(2); // mode is ignored on non-unix + + let access_mode = flags & (O_RDONLY | O_WRONLY | O_RDWR); + let (read, write) = match access_mode { + O_RDONLY => (true, false), + O_WRONLY => (false, true), + O_RDWR => (true, true), + _ => { + let err_msg = "Invalid Flags: Must specify O_RDONLY, O_WRONLY or O_RDWR"; + let message = v8::String::new(scope, err_msg).unwrap(); + let exn = v8::Exception::error(scope, message); + scope.throw_exception(exn); + return; + } + }; + + let mut opts = OpenOptions::new(); + opts.read(read) + .write(write) + .append((flags & O_APPEND) != 0) + .truncate((flags & O_TRUNC) != 0); + + let has_creat = (flags & O_CREAT) != 0; + let has_excl = (flags & O_EXCL) != 0; + if has_creat && has_excl { + opts.create_new(true); + } else if has_creat { + opts.create(true); + } + + match opts.open(path) { + Err(err) => { + let message = v8::String::new(scope, &err.to_string()).unwrap(); + let exn = v8::Exception::error(scope, message); + scope.throw_exception(exn); + } + Ok(file) => { + let context = scope.get_current_context(); + let fd_table = context.get_slot_mut::().unwrap(); + let fd = fd_table.add(file) as f64; + let fd = v8::Number::new(scope, fd); + ret.set(fd.into()) + } + } +} + // close : FileDescriptor -> undefined fn close( scope: &mut v8::HandleScope, @@ -359,12 +434,13 @@ fn close( } } -// access flags for wasm_of_ocaml const R_OK: i32 = 8; const W_OK: i32 = 4; -const X_OK: i32 = 2; const F_OK: i32 = 1; +#[cfg(unix)] +const X_OK: i32 = 2; +#[cfg(unix)] // access : JSString as Path, i32 as Mode -> undefined fn access( scope: &mut v8::HandleScope, @@ -428,6 +504,52 @@ fn access( ret.set(undefined.into()) } +#[cfg(not(unix))] +// access : JSString as Path, i32 as Mode -> undefined +fn access( + scope: &mut v8::HandleScope, + args: v8::FunctionCallbackArguments, + mut ret: v8::ReturnValue, +) { + let path = args.get(0); + let path = path.to_string(scope).unwrap(); + let path = path.to_rust_string_lossy(scope); + let path = Path::new(&path); + let mode = args.get(1); + let mode_val = mode.to_number(scope).unwrap().value() as i32; + if mode_val & F_OK != 0 + && let Err(err) = metadata(path) + { + let message = v8::String::new(scope, &err.to_string()).unwrap(); + let exn = v8::Exception::error(scope, message); + scope.throw_exception(exn); + return; + } + + if mode_val & R_OK != 0 + && let Err(err) = File::open(path) + { + let message = v8::String::new(scope, &err.to_string()).unwrap(); + let exn = v8::Exception::error(scope, message); + scope.throw_exception(exn); + return; + } + + if mode_val & W_OK != 0 + && let Err(err) = OpenOptions::new().write(true).open(path) + { + let message = v8::String::new(scope, &err.to_string()).unwrap(); + let exn = v8::Exception::error(scope, message); + scope.throw_exception(exn); + return; + } + + // X_OK not checked on non-unix. + + let undefined = v8::undefined(scope); + ret.set(undefined.into()) +} + // write: i32 as FileDescriptor, UInt8Array as Buffer, i32 as Offset, i32 as Length, null as Position -> Number fn write( scope: &mut v8::HandleScope, @@ -610,6 +732,7 @@ fn file_size( ret.set(size.into()); } +#[cfg(unix)] fn timeval_from_f64(t: f64) -> std::io::Result { if !t.is_finite() { return Err(std::io::Error::new( @@ -629,6 +752,7 @@ fn timeval_from_f64(t: f64) -> std::io::Result { }) } +#[cfg(unix)] fn __utimes(path: String, atime: f64, mtime: f64) -> std::io::Result<()> { let c_path = CString::new(path)?; @@ -646,6 +770,7 @@ fn __utimes(path: String, atime: f64, mtime: f64) -> std::io::Result<()> { } } +#[cfg(unix)] // utimes: JSString as Path, F64 as AccessTime, F64 as ModifyTime -> undefined fn utimes( scope: &mut v8::HandleScope, @@ -671,12 +796,24 @@ fn utimes( } } +#[cfg(not(unix))] +// utimes: JSString as Path, F64 as AccessTime, F64 as ModifyTime -> undefined +fn utimes( + scope: &mut v8::HandleScope, + _args: v8::FunctionCallbackArguments, + mut ret: v8::ReturnValue, +) { + let undefined = v8::undefined(scope); + ret.set(undefined.into()); +} + // exit: i32 -> undefined fn exit(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, _ret: v8::ReturnValue) { let code = args.get(0).to_int32(scope).unwrap(); std::process::exit(code.value()); } +#[cfg(unix)] // isatty: FileDescriptor -> Number(1 | 0) fn isatty( scope: &mut v8::HandleScope, @@ -712,6 +849,36 @@ fn isatty( ret.set(rescode.into()); } +#[cfg(not(unix))] +// isatty: FileDescriptor -> Number(1 | 0) +fn isatty( + scope: &mut v8::HandleScope, + args: v8::FunctionCallbackArguments, + mut ret: v8::ReturnValue, +) { + let fd = args.get(0); + let fd = fd.to_number(scope).unwrap().value() as i32; + let rescode = if fd == STDIN { + if std::io::stdin().is_terminal() { 1 } else { 0 } + } else if fd == STDOUT { + if std::io::stdout().is_terminal() { + 1 + } else { + 0 + } + } else if fd == STDERR { + if std::io::stderr().is_terminal() { + 1 + } else { + 0 + } + } else { + 0 + }; + let rescode = v8::Number::new(scope, rescode as f64); + ret.set(rescode.into()); +} + // getcwd: () -> JSString fn getcwd( scope: &mut v8::HandleScope, @@ -754,6 +921,7 @@ fn chdir( } } +#[cfg(unix)] // mkdir: JSString as Path, i32 as Mode -> undefined fn mkdir( scope: &mut v8::HandleScope, @@ -772,7 +940,7 @@ fn mkdir( scope.throw_exception(exn); return; } - let permissions = fs::Permissions::from_mode(mode as u32); + let permissions = PermissionsExt::from_mode(mode as u32); match fs::set_permissions(path, permissions) { Err(err) => { let message = v8::String::new(scope, &err.to_string()).unwrap(); @@ -786,6 +954,31 @@ fn mkdir( } } +#[cfg(not(unix))] +// mkdir: JSString as Path, i32 as Mode -> undefined +fn mkdir( + scope: &mut v8::HandleScope, + args: v8::FunctionCallbackArguments, + mut ret: v8::ReturnValue, +) { + let path = args.get(0); + let path = path.to_string(scope).unwrap(); + let path = path.to_rust_string_lossy(scope); + let path = Path::new(&path); + // mode is ignored on non-unix + match fs::create_dir(path) { + Err(err) => { + let message = v8::String::new(scope, &err.to_string()).unwrap(); + let exn = v8::Exception::error(scope, message); + scope.throw_exception(exn); + } + Ok(_) => { + let undefined = v8::undefined(scope); + ret.set(undefined.into()); + } + } +} + // rmdir: JSString as Path -> undefined fn rmdir( scope: &mut v8::HandleScope, @@ -1086,6 +1279,7 @@ fn read_dir( // } // } +#[cfg(unix)] // stat : JSString as Path -> Object fn stat( scope: &mut v8::HandleScope, @@ -1177,6 +1371,51 @@ fn stat( ret.set(stat.into()); } +#[cfg(not(unix))] +// stat : JSString as Path -> Object +fn stat( + scope: &mut v8::HandleScope, + args: v8::FunctionCallbackArguments, + mut ret: v8::ReturnValue, +) { + let path = args.get(0); + let path = path.to_string(scope).unwrap(); + let path = path.to_rust_string_lossy(scope); + let path = Path::new(&path); + let metadata = match fs::metadata(path) { + Err(err) => { + let message = v8::String::new(scope, &err.to_string()).unwrap(); + let exn = v8::Exception::error(scope, message); + scope.throw_exception(exn); + return; + } + Ok(metadata) => metadata, + }; + let filetype = metadata.file_type(); + let kind = if filetype.is_file() { + 0 + } else if filetype.is_dir() { + 1 + } else if filetype.is_symlink() { + 4 + } else { + 0 // Fallback for other types + }; + + let stat = v8::Object::new(scope); + + let id = v8::String::new(scope, "kind").unwrap(); + let kind = v8::Number::new(scope, kind as f64); + stat.set(scope, id.into(), kind.into()); + + let id = v8::String::new(scope, "size").unwrap(); + let size = v8::Number::new(scope, metadata.len() as f64); + stat.set(scope, id.into(), size.into()); + + ret.set(stat.into()); +} + +#[cfg(unix)] // lstat : JSString as Path -> Object fn lstat( scope: &mut v8::HandleScope, @@ -1268,6 +1507,51 @@ fn lstat( ret.set(stat.into()); } +#[cfg(not(unix))] +// lstat : JSString as Path -> Object +fn lstat( + scope: &mut v8::HandleScope, + args: v8::FunctionCallbackArguments, + mut ret: v8::ReturnValue, +) { + let path = args.get(0); + let path = path.to_string(scope).unwrap(); + let path = path.to_rust_string_lossy(scope); + let path = Path::new(&path); + let metadata = match fs::symlink_metadata(path) { + Err(err) => { + let message = v8::String::new(scope, &err.to_string()).unwrap(); + let exn = v8::Exception::error(scope, message); + scope.throw_exception(exn); + return; + } + Ok(metadata) => metadata, + }; + let filetype = metadata.file_type(); + let kind = if filetype.is_file() { + 0 + } else if filetype.is_dir() { + 1 + } else if filetype.is_symlink() { + 4 + } else { + 0 + }; + + let stat = v8::Object::new(scope); + + let id = v8::String::new(scope, "kind").unwrap(); + let kind = v8::Number::new(scope, kind as f64); + stat.set(scope, id.into(), kind.into()); + + let id = v8::String::new(scope, "size").unwrap(); + let size = v8::Number::new(scope, metadata.len() as f64); + stat.set(scope, id.into(), size.into()); + + ret.set(stat.into()); +} + +#[cfg(unix)] // fstat: i32 as FileDescriptor -> Object fn fstat( scope: &mut v8::HandleScope, @@ -1368,17 +1652,15 @@ fn fstat( ret.set(stat.into()); } -// fchmod: i32 as FileDescriptor, i32 as Mode -> undefined -fn fchmod( +#[cfg(not(unix))] +// fstat: i32 as FileDescriptor -> Object +fn fstat( scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut ret: v8::ReturnValue, ) { let fd = args.get(0); let fd = fd.to_number(scope).unwrap().value() as i32; - let mode = args.get(1); - let mode = mode.to_number(scope).unwrap().value() as u32; - let permission = Permissions::from_mode(mode); let context = scope.get_current_context(); let fd_table = context.get_slot_mut::().unwrap(); let file = match fd_table.get(fd) { @@ -1390,17 +1672,81 @@ fn fchmod( return; } }; - match file.set_permissions(permission) { + let metadata = match file.metadata() { Err(err) => { let message = v8::String::new(scope, &err.to_string()).unwrap(); let exn = v8::Exception::error(scope, message); scope.throw_exception(exn); + return; } - Ok(_) => { - let undefined = v8::undefined(scope); - ret.set(undefined.into()) + Ok(metadata) => metadata, + }; + let filetype = metadata.file_type(); + let kind = if filetype.is_file() { + 0 + } else if filetype.is_dir() { + 1 + } else if filetype.is_symlink() { + 4 + } else { + 0 + }; + + let stat = v8::Object::new(scope); + + let id = v8::String::new(scope, "kind").unwrap(); + let kind = v8::Number::new(scope, kind as f64); + stat.set(scope, id.into(), kind.into()); + + let id = v8::String::new(scope, "size").unwrap(); + let size = v8::Number::new(scope, metadata.len() as f64); + stat.set(scope, id.into(), size.into()); + + ret.set(stat.into()); +} + +// fchmod: i32 as FileDescriptor, i32 as Mode -> undefined +fn fchmod( + scope: &mut v8::HandleScope, + args: v8::FunctionCallbackArguments, + mut ret: v8::ReturnValue, +) { + #[cfg(unix)] + { + let fd = args.get(0); + let fd = fd.to_number(scope).unwrap().value() as i32; + let mode = args.get(1); + let mode = mode.to_number(scope).unwrap().value() as u32; + let permission = PermissionsExt::from_mode(mode); + let context = scope.get_current_context(); + let fd_table = context.get_slot_mut::().unwrap(); + let file = match fd_table.get(fd) { + Ok(file) => file, + Err(err_msg) => { + let message = v8::String::new(scope, &err_msg).unwrap(); + let exn = v8::Exception::error(scope, message); + scope.throw_exception(exn); + return; + } + }; + match file.set_permissions(permission) { + Err(err) => { + let message = v8::String::new(scope, &err.to_string()).unwrap(); + let exn = v8::Exception::error(scope, message); + scope.throw_exception(exn); + } + Ok(_) => { + let undefined = v8::undefined(scope); + ret.set(undefined.into()) + } } } + #[cfg(not(unix))] + { + let _ = args; + let undefined = v8::undefined(scope); + ret.set(undefined.into()) + } } // ftruncate: i32 as FileDescriptor, u64 as Length -> undefined