Skip to content
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
7 changes: 5 additions & 2 deletions src/natives/io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ export function openFile(t: JThread, path: string, write: boolean, truncate: boo
let data: Uint8Array;
if (!st) {
if (!write) notFound(path);
if (!fs.write(abs, new Uint8Array(0))) notFound(path, fs.writable(abs) ? "No such file or directory" : "Read-only file system");
if (!fs.write(abs, new Uint8Array(0))) {
notFound(path, ERRNO[fs.writeError(abs) ?? ""] ?? (fs.writable(abs) ? "No such file or directory" : "Read-only file system"));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
data = new Uint8Array(0);
} else {
if (write && !fs.writable(abs)) notFound(path, "Read-only file system");
Expand Down Expand Up @@ -224,7 +226,8 @@ export function flush(t: JThread, f: OpenFile): boolean {
const ERRNO: Record<string, string> = {
ENOSPC: "No space left on device", EFBIG: "File too large", EDQUOT: "Disk quota exceeded",
EACCES: "Permission denied", EPERM: "Operation not permitted", EROFS: "Read-only file system",
ENOENT: "No such file or directory",
ENOENT: "No such file or directory", ENAMETOOLONG: "File name too long", ENOTDIR: "Not a directory",
EIO: "Input/output error", ELOOP: "Too many levels of symbolic links",
};

// Where Java learns of a write that failed: HotSpot reports it from write(),
Expand Down
16 changes: 11 additions & 5 deletions src/natives/nio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,23 @@ import type { Natives } from "./util.ts";

export const EPERM = 1, ENOENT = 2, EINTR = 4, EIO = 5, EBADF = 9, ENOMEM = 12, EACCES = 13,
EEXIST = 17, EXDEV = 18, ENOTDIR = 20, EISDIR = 21, EINVAL = 22, ENOSPC = 28, EROFS = 30,
ENOTEMPTY = 39, ENOSYS = 38, ELOOP = 40, ENOTSUP = 95, ESTALE = 116;
ENOTEMPTY = 39, ENOSYS = 38, ENAMETOOLONG = 36, ELOOP = 40, ENOTSUP = 95, ESTALE = 116, EDQUOT = 122;

const MESSAGES: Record<number, string> = {
[EPERM]: "Operation not permitted", [EINTR]: "Interrupted system call", [EIO]: "Input/output error",
[ENOMEM]: "Cannot allocate memory", [ENOSPC]: "No space left on device",
[ELOOP]: "Too many levels of symbolic links", [ENOTSUP]: "Operation not supported", [ESTALE]: "Stale file handle",
[ENAMETOOLONG]: "File name too long", [EDQUOT]: "Disk quota exceeded",
[ENOENT]: "No such file or directory", [EBADF]: "Bad file descriptor", [EACCES]: "Permission denied",
[EEXIST]: "File exists", [EXDEV]: "Invalid cross-device link", [ENOTDIR]: "Not a directory",
[EISDIR]: "Is a directory", [EINVAL]: "Invalid argument", [EROFS]: "Read-only file system",
[ENOTEMPTY]: "Directory not empty", [ENOSYS]: "Function not implemented",
};

const CHMOD_ERRNO: Record<string, number> = {
// errno numbers of the host errors Node reports by name
const HOST_ERRNO: Record<string, number> = {
EPERM, ENOENT, EINTR, EIO, EBADF, ENOMEM, EACCES, ENOTDIR, EINVAL, ENOSPC, EROFS, ELOOP,
ENOTSUP, EOPNOTSUPP: ENOTSUP, ESTALE,
ENOTSUP, EOPNOTSUPP: ENOTSUP, ESTALE, ENAMETOOLONG, EDQUOT,
};

// Linux open(2) flags
Expand Down Expand Up @@ -66,7 +68,7 @@ function chmod(t: JThread, p: string, mode: number) {
if (!fs.stat(p)) unixError(ENOENT);
unixError(EROFS);
}
unixError(CHMOD_ERRNO[fs.chmodError(p) ?? "EIO"] ?? EIO);
unixError(HOST_ERRNO[fs.chmodError(p) ?? "EIO"] ?? EIO);
}

function fillAttrs(t: JThread, attrs: JObject, p: string): number {
Expand Down Expand Up @@ -148,7 +150,11 @@ export const natives: Natives = {
fileOf(t, fd).ino = fileInode(t, abs);
return fd;
} catch (e) {
if (e instanceof PendingThrow) unixError(t.vm.fs.writable(abs) ? ENOENT : EROFS);
if (e instanceof PendingThrow) {
// a new file the host would not create: its reason
const host = st ? undefined : HOST_ERRNO[t.vm.fs.writeError(abs) ?? ""];
unixError(host ?? (t.vm.fs.writable(abs) ? ENOENT : EROFS));
}
throw e;
}
},
Expand Down
17 changes: 9 additions & 8 deletions src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,20 @@ export class NodeBackend implements Backend {
if (!this.writable) this.lists.set(p, names);
return names;
}
private writeErrors = new Map<string, string>();
// the host's reason for the last failed write, read right after it
private writeFailure: [string, string] | null = null;
write(p: string, data: Uint8Array) {
try {
fs.writeFileSync(this.host(p), data);
this.writeErrors.delete(p);
this.writeFailure = null;
return true;
} catch (e) {
this.writeErrors.set(p, (e as NodeJS.ErrnoException).code ?? "EIO");
this.writeFailure = [p, (e as NodeJS.ErrnoException).code ?? "EIO"];
return false;
}
}
writeError(p: string) {
return this.writeErrors.get(p);
return this.writeFailure?.[0] === p ? this.writeFailure[1] : undefined;
}
denied(p: string) {
const host = this.host(p);
Expand All @@ -103,19 +104,19 @@ export class NodeBackend implements Backend {
return (e as NodeJS.ErrnoException).code === "EACCES" || (e as NodeJS.ErrnoException).code === "EPERM";
}
}
private chmodErrors = new Map<string, string>();
private chmodFailure: [string, string] | null = null;
chmod(p: string, f: (mode: number) => number) {
try {
fs.chmodSync(this.host(p), f(fs.statSync(this.host(p)).mode & 0o7777));
this.chmodErrors.delete(p);
this.chmodFailure = null;
return true;
} catch (e) {
this.chmodErrors.set(p, (e as NodeJS.ErrnoException).code ?? "EIO");
this.chmodFailure = [p, (e as NodeJS.ErrnoException).code ?? "EIO"];
return false;
}
}
chmodError(p: string) {
return this.chmodErrors.get(p);
return this.chmodFailure?.[0] === p ? this.chmodFailure[1] : undefined;
}
access(p: string, mode: number) {
try {
Expand Down
26 changes: 26 additions & 0 deletions test/java/CreateRefused.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import java.io.*;
import java.nio.file.*;

// Creating a file the host refuses reports the host's reason, as HotSpot does.
public class CreateRefused {
public static void main(String[] args) throws Exception {
String name = Path.of(System.getProperty("java.io.tmpdir"), "x".repeat(300)).toString();
try (var out = new FileOutputStream(name)) {
System.out.println("created");
} catch (IOException e) {
System.out.println(e.getClass().getName() + ": " + e.getMessage().replace(name, "<tmp>/<long>"));
}
try {
Files.createFile(Path.of(name));
System.out.println("created");
} catch (IOException e) {
System.out.println(e.getClass().getName() + ": " + e.getMessage().replace(name, "<tmp>/<long>"));
}
try {
Files.newOutputStream(Path.of(name)).close();
System.out.println("created");
} catch (IOException e) {
System.out.println(e.getClass().getName() + ": " + e.getMessage().replace(name, "<tmp>/<long>"));
}
}
}
Loading