mirror of
https://github.com/denoland/deno.git
synced 2024-12-24 08:09:08 -05:00
1739 lines
44 KiB
TypeScript
1739 lines
44 KiB
TypeScript
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
|
|
import { relative, resolve } from "../path/mod.ts";
|
|
|
|
const CLOCKID_REALTIME = 0;
|
|
const CLOCKID_MONOTONIC = 1;
|
|
const CLOCKID_PROCESS_CPUTIME_ID = 2;
|
|
const CLOCKID_THREAD_CPUTIME_ID = 3;
|
|
|
|
const ERRNO_SUCCESS = 0;
|
|
const _ERRNO_2BIG = 1;
|
|
const ERRNO_ACCES = 2;
|
|
const ERRNO_ADDRINUSE = 3;
|
|
const ERRNO_ADDRNOTAVAIL = 4;
|
|
const _ERRNO_AFNOSUPPORT = 5;
|
|
const _ERRNO_AGAIN = 6;
|
|
const _ERRNO_ALREADY = 7;
|
|
const ERRNO_BADF = 8;
|
|
const _ERRNO_BADMSG = 9;
|
|
const ERRNO_BUSY = 10;
|
|
const _ERRNO_CANCELED = 11;
|
|
const _ERRNO_CHILD = 12;
|
|
const ERRNO_CONNABORTED = 13;
|
|
const ERRNO_CONNREFUSED = 14;
|
|
const ERRNO_CONNRESET = 15;
|
|
const _ERRNO_DEADLK = 16;
|
|
const _ERRNO_DESTADDRREQ = 17;
|
|
const _ERRNO_DOM = 18;
|
|
const _ERRNO_DQUOT = 19;
|
|
const _ERRNO_EXIST = 20;
|
|
const _ERRNO_FAULT = 21;
|
|
const _ERRNO_FBIG = 22;
|
|
const _ERRNO_HOSTUNREACH = 23;
|
|
const _ERRNO_IDRM = 24;
|
|
const _ERRNO_ILSEQ = 25;
|
|
const _ERRNO_INPROGRESS = 26;
|
|
const ERRNO_INTR = 27;
|
|
const ERRNO_INVAL = 28;
|
|
const _ERRNO_IO = 29;
|
|
const _ERRNO_ISCONN = 30;
|
|
const _ERRNO_ISDIR = 31;
|
|
const _ERRNO_LOOP = 32;
|
|
const _ERRNO_MFILE = 33;
|
|
const _ERRNO_MLINK = 34;
|
|
const _ERRNO_MSGSIZE = 35;
|
|
const _ERRNO_MULTIHOP = 36;
|
|
const _ERRNO_NAMETOOLONG = 37;
|
|
const _ERRNO_NETDOWN = 38;
|
|
const _ERRNO_NETRESET = 39;
|
|
const _ERRNO_NETUNREACH = 40;
|
|
const _ERRNO_NFILE = 41;
|
|
const _ERRNO_NOBUFS = 42;
|
|
const _ERRNO_NODEV = 43;
|
|
const ERRNO_NOENT = 44;
|
|
const _ERRNO_NOEXEC = 45;
|
|
const _ERRNO_NOLCK = 46;
|
|
const _ERRNO_NOLINK = 47;
|
|
const _ERRNO_NOMEM = 48;
|
|
const _ERRNO_NOMSG = 49;
|
|
const _ERRNO_NOPROTOOPT = 50;
|
|
const _ERRNO_NOSPC = 51;
|
|
const ERRNO_NOSYS = 52;
|
|
const ERRNO_NOTCONN = 53;
|
|
const ERRNO_NOTDIR = 54;
|
|
const _ERRNO_NOTEMPTY = 55;
|
|
const _ERRNO_NOTRECOVERABLE = 56;
|
|
const _ERRNO_NOTSOCK = 57;
|
|
const _ERRNO_NOTSUP = 58;
|
|
const _ERRNO_NOTTY = 59;
|
|
const _ERRNO_NXIO = 60;
|
|
const _ERRNO_OVERFLOW = 61;
|
|
const _ERRNO_OWNERDEAD = 62;
|
|
const _ERRNO_PERM = 63;
|
|
const ERRNO_PIPE = 64;
|
|
const _ERRNO_PROTO = 65;
|
|
const _ERRNO_PROTONOSUPPORT = 66;
|
|
const _ERRNO_PROTOTYPE = 67;
|
|
const _ERRNO_RANGE = 68;
|
|
const _ERRNO_ROFS = 69;
|
|
const _ERRNO_SPIPE = 70;
|
|
const _ERRNO_SRCH = 71;
|
|
const _ERRNO_STALE = 72;
|
|
const ERRNO_TIMEDOUT = 73;
|
|
const _ERRNO_TXTBSY = 74;
|
|
const _ERRNO_XDEV = 75;
|
|
const ERRNO_NOTCAPABLE = 76;
|
|
|
|
const RIGHTS_FD_DATASYNC = 0x0000000000000001n;
|
|
const RIGHTS_FD_READ = 0x0000000000000002n;
|
|
const _RIGHTS_FD_SEEK = 0x0000000000000004n;
|
|
const _RIGHTS_FD_FDSTAT_SET_FLAGS = 0x0000000000000008n;
|
|
const _RIGHTS_FD_SYNC = 0x0000000000000010n;
|
|
const _RIGHTS_FD_TELL = 0x0000000000000020n;
|
|
const RIGHTS_FD_WRITE = 0x0000000000000040n;
|
|
const _RIGHTS_FD_ADVISE = 0x0000000000000080n;
|
|
const RIGHTS_FD_ALLOCATE = 0x0000000000000100n;
|
|
const _RIGHTS_PATH_CREATE_DIRECTORY = 0x0000000000000200n;
|
|
const _RIGHTS_PATH_CREATE_FILE = 0x0000000000000400n;
|
|
const _RIGHTS_PATH_LINK_SOURCE = 0x0000000000000800n;
|
|
const _RIGHTS_PATH_LINK_TARGET = 0x0000000000001000n;
|
|
const _RIGHTS_PATH_OPEN = 0x0000000000002000n;
|
|
const RIGHTS_FD_READDIR = 0x0000000000004000n;
|
|
const _RIGHTS_PATH_READLINK = 0x0000000000008000n;
|
|
const _RIGHTS_PATH_RENAME_SOURCE = 0x0000000000010000n;
|
|
const _RIGHTS_PATH_RENAME_TARGET = 0x0000000000020000n;
|
|
const _RIGHTS_PATH_FILESTAT_GET = 0x0000000000040000n;
|
|
const _RIGHTS_PATH_FILESTAT_SET_SIZE = 0x0000000000080000n;
|
|
const _RIGHTS_PATH_FILESTAT_SET_TIMES = 0x0000000000100000n;
|
|
const _RIGHTS_FD_FILESTAT_GET = 0x0000000000200000n;
|
|
const RIGHTS_FD_FILESTAT_SET_SIZE = 0x0000000000400000n;
|
|
const _RIGHTS_FD_FILESTAT_SET_TIMES = 0x0000000000800000n;
|
|
const _RIGHTS_PATH_SYMLINK = 0x0000000001000000n;
|
|
const _RIGHTS_PATH_REMOVE_DIRECTORY = 0x0000000002000000n;
|
|
const _RIGHTS_PATH_UNLINK_FILE = 0x0000000004000000n;
|
|
const _RIGHTS_POLL_FD_READWRITE = 0x0000000008000000n;
|
|
const _RIGHTS_SOCK_SHUTDOWN = 0x0000000010000000n;
|
|
|
|
const _WHENCE_SET = 0;
|
|
const _WHENCE_CUR = 1;
|
|
const _WHENCE_END = 2;
|
|
|
|
const FILETYPE_UNKNOWN = 0;
|
|
const _FILETYPE_BLOCK_DEVICE = 1;
|
|
const FILETYPE_CHARACTER_DEVICE = 2;
|
|
const FILETYPE_DIRECTORY = 3;
|
|
const FILETYPE_REGULAR_FILE = 4;
|
|
const _FILETYPE_SOCKET_DGRAM = 5;
|
|
const _FILETYPE_SOCKET_STREAM = 6;
|
|
const FILETYPE_SYMBOLIC_LINK = 7;
|
|
|
|
const _ADVICE_NORMAL = 0;
|
|
const _ADVICE_SEQUENTIAL = 1;
|
|
const _ADVICE_RANDOM = 2;
|
|
const _ADVICE_WILLNEED = 3;
|
|
const _ADVICE_DONTNEED = 4;
|
|
const _ADVICE_NOREUSE = 5;
|
|
|
|
const FDFLAGS_APPEND = 0x0001;
|
|
const FDFLAGS_DSYNC = 0x0002;
|
|
const FDFLAGS_NONBLOCK = 0x0004;
|
|
const FDFLAGS_RSYNC = 0x0008;
|
|
const FDFLAGS_SYNC = 0x0010;
|
|
|
|
const _FSTFLAGS_ATIM = 0x0001;
|
|
const FSTFLAGS_ATIM_NOW = 0x0002;
|
|
const _FSTFLAGS_MTIM = 0x0004;
|
|
const FSTFLAGS_MTIM_NOW = 0x0008;
|
|
|
|
const LOOKUPFLAGS_SYMLINK_FOLLOW = 0x0001;
|
|
|
|
const OFLAGS_CREAT = 0x0001;
|
|
const OFLAGS_DIRECTORY = 0x0002;
|
|
const OFLAGS_EXCL = 0x0004;
|
|
const OFLAGS_TRUNC = 0x0008;
|
|
|
|
const _EVENTTYPE_CLOCK = 0;
|
|
const _EVENTTYPE_FD_READ = 1;
|
|
const _EVENTTYPE_FD_WRITE = 2;
|
|
|
|
const _EVENTRWFLAGS_FD_READWRITE_HANGUP = 1;
|
|
const _SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME = 1;
|
|
|
|
const _SIGNAL_NONE = 0;
|
|
const _SIGNAL_HUP = 1;
|
|
const _SIGNAL_INT = 2;
|
|
const _SIGNAL_QUIT = 3;
|
|
const _SIGNAL_ILL = 4;
|
|
const _SIGNAL_TRAP = 5;
|
|
const _SIGNAL_ABRT = 6;
|
|
const _SIGNAL_BUS = 7;
|
|
const _SIGNAL_FPE = 8;
|
|
const _SIGNAL_KILL = 9;
|
|
const _SIGNAL_USR1 = 10;
|
|
const _SIGNAL_SEGV = 11;
|
|
const _SIGNAL_USR2 = 12;
|
|
const _SIGNAL_PIPE = 13;
|
|
const _SIGNAL_ALRM = 14;
|
|
const _SIGNAL_TERM = 15;
|
|
const _SIGNAL_CHLD = 16;
|
|
const _SIGNAL_CONT = 17;
|
|
const _SIGNAL_STOP = 18;
|
|
const _SIGNAL_TSTP = 19;
|
|
const _SIGNAL_TTIN = 20;
|
|
const _SIGNAL_TTOU = 21;
|
|
const _SIGNAL_URG = 22;
|
|
const _SIGNAL_XCPU = 23;
|
|
const _SIGNAL_XFSZ = 24;
|
|
const _SIGNAL_VTALRM = 25;
|
|
const _SIGNAL_PROF = 26;
|
|
const _SIGNAL_WINCH = 27;
|
|
const _SIGNAL_POLL = 28;
|
|
const _SIGNAL_PWR = 29;
|
|
const _SIGNAL_SYS = 30;
|
|
|
|
const _RIFLAGS_RECV_PEEK = 0x0001;
|
|
const _RIFLAGS_RECV_WAITALL = 0x0002;
|
|
|
|
const _ROFLAGS_RECV_DATA_TRUNCATED = 0x0001;
|
|
|
|
const _SDFLAGS_RD = 0x0001;
|
|
const _SDFLAGS_WR = 0x0002;
|
|
|
|
const PREOPENTYPE_DIR = 0;
|
|
|
|
function syscall<T extends CallableFunction>(target: T) {
|
|
return function (...args: unknown[]) {
|
|
try {
|
|
return target(...args);
|
|
} catch (err) {
|
|
if (err instanceof ExitStatus) {
|
|
throw err;
|
|
}
|
|
|
|
switch (err.name) {
|
|
case "NotFound":
|
|
return ERRNO_NOENT;
|
|
|
|
case "PermissionDenied":
|
|
return ERRNO_ACCES;
|
|
|
|
case "ConnectionRefused":
|
|
return ERRNO_CONNREFUSED;
|
|
|
|
case "ConnectionReset":
|
|
return ERRNO_CONNRESET;
|
|
|
|
case "ConnectionAborted":
|
|
return ERRNO_CONNABORTED;
|
|
|
|
case "NotConnected":
|
|
return ERRNO_NOTCONN;
|
|
|
|
case "AddrInUse":
|
|
return ERRNO_ADDRINUSE;
|
|
|
|
case "AddrNotAvailable":
|
|
return ERRNO_ADDRNOTAVAIL;
|
|
|
|
case "BrokenPipe":
|
|
return ERRNO_PIPE;
|
|
|
|
case "InvalidData":
|
|
return ERRNO_INVAL;
|
|
|
|
case "TimedOut":
|
|
return ERRNO_TIMEDOUT;
|
|
|
|
case "Interrupted":
|
|
return ERRNO_INTR;
|
|
|
|
case "BadResource":
|
|
return ERRNO_BADF;
|
|
|
|
case "Busy":
|
|
return ERRNO_BUSY;
|
|
|
|
default:
|
|
return ERRNO_INVAL;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
interface FileDescriptor {
|
|
rid?: number;
|
|
type?: number;
|
|
flags?: number;
|
|
path?: string;
|
|
vpath?: string;
|
|
entries?: Deno.DirEntry[];
|
|
}
|
|
|
|
class ExitStatus {
|
|
code: number;
|
|
|
|
constructor(code: number) {
|
|
this.code = code;
|
|
}
|
|
}
|
|
|
|
export interface ContextOptions {
|
|
/**
|
|
* An array of strings that the WebAssembly instance will see as command-line
|
|
* arguments.
|
|
*
|
|
* The first argument is the virtual path to the command itself.
|
|
*/
|
|
args?: string[];
|
|
|
|
/**
|
|
* An object of string keys mapped to string values that the WebAssembly module will see as its environment.
|
|
*/
|
|
env?: { [key: string]: string | undefined };
|
|
|
|
/**
|
|
* An object of string keys mapped to string values that the WebAssembly module will see as it's filesystem.
|
|
*
|
|
* The string keys of are treated as directories within the sandboxed
|
|
* filesystem, the values are the real paths to those directories on the host
|
|
* machine.
|
|
*
|
|
*/
|
|
preopens?: { [key: string]: string };
|
|
|
|
/**
|
|
* Determines if calls to exit from within the WebAssembly module will terminate the proess or return.
|
|
*/
|
|
exitOnReturn?: boolean;
|
|
|
|
/**
|
|
* The resource descriptor used as standard input in the WebAssembly module.
|
|
*/
|
|
stdin?: number;
|
|
|
|
/**
|
|
* The resource descriptor used as standard output in the WebAssembly module.
|
|
*/
|
|
stdout?: number;
|
|
|
|
/**
|
|
* The resource descriptor used as standard error in the WebAssembly module.
|
|
*/
|
|
stderr?: number;
|
|
}
|
|
|
|
/**
|
|
* The Context class provides the environment required to run WebAssembly
|
|
* modules compiled to run with the WebAssembly System Interface.
|
|
*
|
|
* Each context represents a distinct sandboxed environment and must have its
|
|
* command-line arguments, environment variables, and pre-opened directory
|
|
* structure configured explicitly.
|
|
*/
|
|
export default class Context {
|
|
#args: string[];
|
|
#env: { [key: string]: string | undefined };
|
|
#exitOnReturn: boolean;
|
|
#memory: WebAssembly.Memory;
|
|
#fds: FileDescriptor[];
|
|
#started: boolean;
|
|
|
|
exports: Record<string, WebAssembly.ImportValue>;
|
|
|
|
constructor(options: ContextOptions) {
|
|
this.#args = options.args ?? [];
|
|
this.#env = options.env ?? {};
|
|
this.#exitOnReturn = options.exitOnReturn ?? true;
|
|
this.#memory = null!;
|
|
|
|
this.#fds = [
|
|
{
|
|
rid: options.stdin ?? Deno.stdin.rid,
|
|
type: FILETYPE_CHARACTER_DEVICE,
|
|
flags: FDFLAGS_APPEND,
|
|
},
|
|
{
|
|
rid: options.stdout ?? Deno.stdout.rid,
|
|
type: FILETYPE_CHARACTER_DEVICE,
|
|
flags: FDFLAGS_APPEND,
|
|
},
|
|
{
|
|
rid: options.stderr ?? Deno.stderr.rid,
|
|
type: FILETYPE_CHARACTER_DEVICE,
|
|
flags: FDFLAGS_APPEND,
|
|
},
|
|
];
|
|
|
|
if (options.preopens) {
|
|
for (const [vpath, path] of Object.entries(options.preopens)) {
|
|
const type = FILETYPE_DIRECTORY;
|
|
const entries = Array.from(Deno.readDirSync(path));
|
|
|
|
const entry = {
|
|
type,
|
|
entries,
|
|
path,
|
|
vpath,
|
|
};
|
|
|
|
this.#fds.push(entry);
|
|
}
|
|
}
|
|
|
|
this.exports = {
|
|
"args_get": syscall((
|
|
argvOffset: number,
|
|
argvBufferOffset: number,
|
|
): number => {
|
|
const args = this.#args;
|
|
const textEncoder = new TextEncoder();
|
|
const memoryData = new Uint8Array(this.#memory.buffer);
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
for (const arg of args) {
|
|
memoryView.setUint32(argvOffset, argvBufferOffset, true);
|
|
argvOffset += 4;
|
|
|
|
const data = textEncoder.encode(`${arg}\0`);
|
|
memoryData.set(data, argvBufferOffset);
|
|
argvBufferOffset += data.length;
|
|
}
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"args_sizes_get": syscall((
|
|
argcOffset: number,
|
|
argvBufferSizeOffset: number,
|
|
): number => {
|
|
const args = this.#args;
|
|
const textEncoder = new TextEncoder();
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
memoryView.setUint32(argcOffset, args.length, true);
|
|
memoryView.setUint32(
|
|
argvBufferSizeOffset,
|
|
args.reduce(function (acc, arg) {
|
|
return acc + textEncoder.encode(`${arg}\0`).length;
|
|
}, 0),
|
|
true,
|
|
);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"environ_get": syscall((
|
|
environOffset: number,
|
|
environBufferOffset: number,
|
|
): number => {
|
|
const entries = Object.entries(this.#env);
|
|
const textEncoder = new TextEncoder();
|
|
const memoryData = new Uint8Array(this.#memory.buffer);
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
for (const [key, value] of entries) {
|
|
memoryView.setUint32(environOffset, environBufferOffset, true);
|
|
environOffset += 4;
|
|
|
|
const data = textEncoder.encode(`${key}=${value}\0`);
|
|
memoryData.set(data, environBufferOffset);
|
|
environBufferOffset += data.length;
|
|
}
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"environ_sizes_get": syscall((
|
|
environcOffset: number,
|
|
environBufferSizeOffset: number,
|
|
): number => {
|
|
const entries = Object.entries(this.#env);
|
|
const textEncoder = new TextEncoder();
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
memoryView.setUint32(environcOffset, entries.length, true);
|
|
memoryView.setUint32(
|
|
environBufferSizeOffset,
|
|
entries.reduce(function (acc, [key, value]) {
|
|
return acc + textEncoder.encode(`${key}=${value}\0`).length;
|
|
}, 0),
|
|
true,
|
|
);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"clock_res_get": syscall((
|
|
id: number,
|
|
resolutionOffset: number,
|
|
): number => {
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
switch (id) {
|
|
case CLOCKID_REALTIME: {
|
|
const resolution = BigInt(1e6);
|
|
|
|
memoryView.setBigUint64(
|
|
resolutionOffset,
|
|
resolution,
|
|
true,
|
|
);
|
|
break;
|
|
}
|
|
|
|
case CLOCKID_MONOTONIC:
|
|
case CLOCKID_PROCESS_CPUTIME_ID:
|
|
case CLOCKID_THREAD_CPUTIME_ID: {
|
|
const resolution = BigInt(1e3);
|
|
memoryView.setBigUint64(resolutionOffset, resolution, true);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"clock_time_get": syscall((
|
|
id: number,
|
|
precision: bigint,
|
|
timeOffset: number,
|
|
): number => {
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
switch (id) {
|
|
case CLOCKID_REALTIME: {
|
|
const time = BigInt(Date.now()) * BigInt(1e6);
|
|
memoryView.setBigUint64(timeOffset, time, true);
|
|
break;
|
|
}
|
|
|
|
case CLOCKID_MONOTONIC:
|
|
case CLOCKID_PROCESS_CPUTIME_ID:
|
|
case CLOCKID_THREAD_CPUTIME_ID: {
|
|
const t = performance.now();
|
|
const s = Math.trunc(t);
|
|
const ms = Math.floor((t - s) * 1e3);
|
|
|
|
const time = BigInt(s) * BigInt(1e9) + BigInt(ms) * BigInt(1e6);
|
|
|
|
memoryView.setBigUint64(timeOffset, time, true);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_advise": syscall((
|
|
_fd: number,
|
|
_offset: bigint,
|
|
_length: bigint,
|
|
_advice: number,
|
|
): number => {
|
|
return ERRNO_NOSYS;
|
|
}),
|
|
|
|
"fd_allocate": syscall((
|
|
_fd: number,
|
|
_offset: bigint,
|
|
_length: bigint,
|
|
): number => {
|
|
return ERRNO_NOSYS;
|
|
}),
|
|
|
|
"fd_close": syscall((
|
|
fd: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (entry.rid) {
|
|
Deno.close(entry.rid);
|
|
}
|
|
|
|
delete this.#fds[fd];
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_datasync": syscall((
|
|
fd: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
Deno.fdatasyncSync(entry.rid!);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_fdstat_get": syscall((
|
|
fd: number,
|
|
offset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
memoryView.setUint8(offset, entry.type!);
|
|
memoryView.setUint16(offset + 2, entry.flags!, true);
|
|
// TODO(bartlomieju)
|
|
memoryView.setBigUint64(offset + 8, 0n, true);
|
|
// TODO(bartlomieju)
|
|
memoryView.setBigUint64(offset + 16, 0n, true);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_fdstat_set_flags": syscall((
|
|
_fd: number,
|
|
_flags: number,
|
|
): number => {
|
|
return ERRNO_NOSYS;
|
|
}),
|
|
|
|
"fd_fdstat_set_rights": syscall((
|
|
_fd: number,
|
|
_rightsBase: bigint,
|
|
_rightsInheriting: bigint,
|
|
): number => {
|
|
return ERRNO_NOSYS;
|
|
}),
|
|
|
|
"fd_filestat_get": syscall((
|
|
fd: number,
|
|
offset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
const info = Deno.fstatSync(entry.rid!);
|
|
|
|
if (entry.type === undefined) {
|
|
switch (true) {
|
|
case info.isFile:
|
|
entry.type = FILETYPE_REGULAR_FILE;
|
|
break;
|
|
|
|
case info.isDirectory:
|
|
entry.type = FILETYPE_DIRECTORY;
|
|
break;
|
|
|
|
case info.isSymlink:
|
|
entry.type = FILETYPE_SYMBOLIC_LINK;
|
|
break;
|
|
|
|
default:
|
|
entry.type = FILETYPE_UNKNOWN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
memoryView.setBigUint64(offset, BigInt(info.dev ? info.dev : 0), true);
|
|
offset += 8;
|
|
|
|
memoryView.setBigUint64(offset, BigInt(info.ino ? info.ino : 0), true);
|
|
offset += 8;
|
|
|
|
memoryView.setUint8(offset, entry.type);
|
|
offset += 8;
|
|
|
|
memoryView.setUint32(offset, Number(info.nlink), true);
|
|
offset += 8;
|
|
|
|
memoryView.setBigUint64(offset, BigInt(info.size), true);
|
|
offset += 8;
|
|
|
|
memoryView.setBigUint64(
|
|
offset,
|
|
BigInt(info.atime ? info.atime.getTime() * 1e6 : 0),
|
|
true,
|
|
);
|
|
offset += 8;
|
|
|
|
memoryView.setBigUint64(
|
|
offset,
|
|
BigInt(info.mtime ? info.mtime.getTime() * 1e6 : 0),
|
|
true,
|
|
);
|
|
offset += 8;
|
|
|
|
memoryView.setBigUint64(
|
|
offset,
|
|
BigInt(info.birthtime ? info.birthtime.getTime() * 1e6 : 0),
|
|
true,
|
|
);
|
|
offset += 8;
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_filestat_set_size": syscall((
|
|
fd: number,
|
|
size: bigint,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
Deno.ftruncateSync(entry.rid!, Number(size));
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_filestat_set_times": syscall((
|
|
fd: number,
|
|
atim: bigint,
|
|
mtim: bigint,
|
|
flags: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!entry.path) {
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
if ((flags & FSTFLAGS_ATIM_NOW) == FSTFLAGS_ATIM_NOW) {
|
|
atim = BigInt(Date.now() * 1e6);
|
|
}
|
|
|
|
if ((flags & FSTFLAGS_MTIM_NOW) == FSTFLAGS_MTIM_NOW) {
|
|
mtim = BigInt(Date.now() * 1e6);
|
|
}
|
|
|
|
Deno.utimeSync(entry.path!, Number(atim), Number(mtim));
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_pread": syscall((
|
|
fd: number,
|
|
iovsOffset: number,
|
|
iovsLength: number,
|
|
offset: bigint,
|
|
nreadOffset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (entry == null) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
const seek = Deno.seekSync(entry.rid!, 0, Deno.SeekMode.Current);
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
let nread = 0;
|
|
for (let i = 0; i < iovsLength; i++) {
|
|
const dataOffset = memoryView.getUint32(iovsOffset, true);
|
|
iovsOffset += 4;
|
|
|
|
const dataLength = memoryView.getUint32(iovsOffset, true);
|
|
iovsOffset += 4;
|
|
|
|
const data = new Uint8Array(
|
|
this.#memory.buffer,
|
|
dataOffset,
|
|
dataLength,
|
|
);
|
|
nread += Deno.readSync(entry.rid!, data) as number;
|
|
}
|
|
|
|
Deno.seekSync(entry.rid!, seek, Deno.SeekMode.Start);
|
|
memoryView.setUint32(nreadOffset, nread, true);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_prestat_get": syscall((
|
|
fd: number,
|
|
prestatOffset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!entry.vpath) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
memoryView.setUint8(prestatOffset, PREOPENTYPE_DIR);
|
|
memoryView.setUint32(
|
|
prestatOffset + 4,
|
|
new TextEncoder().encode(entry.vpath).byteLength,
|
|
true,
|
|
);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_prestat_dir_name": syscall((
|
|
fd: number,
|
|
pathOffset: number,
|
|
pathLength: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!entry.vpath) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
const data = new Uint8Array(
|
|
this.#memory.buffer,
|
|
pathOffset,
|
|
pathLength,
|
|
);
|
|
data.set(new TextEncoder().encode(entry.vpath));
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_pwrite": syscall((
|
|
fd: number,
|
|
iovsOffset: number,
|
|
iovsLength: number,
|
|
offset: bigint,
|
|
nwrittenOffset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
const seek = Deno.seekSync(entry.rid!, 0, Deno.SeekMode.Current);
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
let nwritten = 0;
|
|
for (let i = 0; i < iovsLength; i++) {
|
|
const dataOffset = memoryView.getUint32(iovsOffset, true);
|
|
iovsOffset += 4;
|
|
|
|
const dataLength = memoryView.getUint32(iovsOffset, true);
|
|
iovsOffset += 4;
|
|
|
|
const data = new Uint8Array(
|
|
this.#memory.buffer,
|
|
dataOffset,
|
|
dataLength,
|
|
);
|
|
nwritten += Deno.writeSync(entry.rid!, data) as number;
|
|
}
|
|
|
|
Deno.seekSync(entry.rid!, seek, Deno.SeekMode.Start);
|
|
memoryView.setUint32(nwrittenOffset, nwritten, true);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_read": syscall((
|
|
fd: number,
|
|
iovsOffset: number,
|
|
iovsLength: number,
|
|
nreadOffset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
let nread = 0;
|
|
for (let i = 0; i < iovsLength; i++) {
|
|
const dataOffset = memoryView.getUint32(iovsOffset, true);
|
|
iovsOffset += 4;
|
|
|
|
const dataLength = memoryView.getUint32(iovsOffset, true);
|
|
iovsOffset += 4;
|
|
|
|
const data = new Uint8Array(
|
|
this.#memory.buffer,
|
|
dataOffset,
|
|
dataLength,
|
|
);
|
|
nread += Deno.readSync(entry.rid!, data) as number;
|
|
}
|
|
|
|
memoryView.setUint32(nreadOffset, nread, true);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_readdir": syscall((
|
|
fd: number,
|
|
bufferOffset: number,
|
|
bufferLength: number,
|
|
cookie: bigint,
|
|
bufferUsedOffset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
const memoryData = new Uint8Array(this.#memory.buffer);
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
let bufferUsed = 0;
|
|
|
|
const entries = Array.from(Deno.readDirSync(entry.path!));
|
|
for (let i = Number(cookie); i < entries.length; i++) {
|
|
const nameData = new TextEncoder().encode(entries[i].name);
|
|
|
|
const entryInfo = Deno.statSync(
|
|
resolve(entry.path!, entries[i].name),
|
|
);
|
|
const entryData = new Uint8Array(24 + nameData.byteLength);
|
|
const entryView = new DataView(entryData.buffer);
|
|
|
|
entryView.setBigUint64(0, BigInt(i + 1), true);
|
|
entryView.setBigUint64(
|
|
8,
|
|
BigInt(entryInfo.ino ? entryInfo.ino : 0),
|
|
true,
|
|
);
|
|
entryView.setUint32(16, nameData.byteLength, true);
|
|
|
|
let type: number;
|
|
switch (true) {
|
|
case entries[i].isFile:
|
|
type = FILETYPE_REGULAR_FILE;
|
|
break;
|
|
|
|
case entries[i].isDirectory:
|
|
type = FILETYPE_REGULAR_FILE;
|
|
break;
|
|
|
|
case entries[i].isSymlink:
|
|
type = FILETYPE_SYMBOLIC_LINK;
|
|
break;
|
|
|
|
default:
|
|
type = FILETYPE_REGULAR_FILE;
|
|
break;
|
|
}
|
|
|
|
entryView.setUint8(20, type);
|
|
entryData.set(nameData, 24);
|
|
|
|
const data = entryData.slice(
|
|
0,
|
|
Math.min(entryData.length, bufferLength - bufferUsed),
|
|
);
|
|
memoryData.set(data, bufferOffset + bufferUsed);
|
|
bufferUsed += data.byteLength;
|
|
}
|
|
|
|
memoryView.setUint32(bufferUsedOffset, bufferUsed, true);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_renumber": syscall((
|
|
fd: number,
|
|
to: number,
|
|
): number => {
|
|
if (!this.#fds[fd]) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!this.#fds[to]) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (this.#fds[to].rid) {
|
|
Deno.close(this.#fds[to].rid!);
|
|
}
|
|
|
|
this.#fds[to] = this.#fds[fd];
|
|
delete this.#fds[fd];
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_seek": syscall((
|
|
fd: number,
|
|
offset: bigint,
|
|
whence: number,
|
|
newOffsetOffset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
// FIXME Deno does not support seeking with big integers
|
|
const newOffset = Deno.seekSync(entry.rid!, Number(offset), whence);
|
|
memoryView.setBigUint64(newOffsetOffset, BigInt(newOffset), true);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_sync": syscall((
|
|
fd: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
Deno.fsyncSync(entry.rid!);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_tell": syscall((
|
|
fd: number,
|
|
offsetOffset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
const offset = Deno.seekSync(entry.rid!, 0, Deno.SeekMode.Current);
|
|
memoryView.setBigUint64(offsetOffset, BigInt(offset), true);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"fd_write": syscall((
|
|
fd: number,
|
|
iovsOffset: number,
|
|
iovsLength: number,
|
|
nwrittenOffset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
let nwritten = 0;
|
|
for (let i = 0; i < iovsLength; i++) {
|
|
const dataOffset = memoryView.getUint32(iovsOffset, true);
|
|
iovsOffset += 4;
|
|
|
|
const dataLength = memoryView.getUint32(iovsOffset, true);
|
|
iovsOffset += 4;
|
|
|
|
const data = new Uint8Array(
|
|
this.#memory.buffer,
|
|
dataOffset,
|
|
dataLength,
|
|
);
|
|
nwritten += Deno.writeSync(entry.rid!, data) as number;
|
|
}
|
|
|
|
memoryView.setUint32(nwrittenOffset, nwritten, true);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"path_create_directory": syscall((
|
|
fd: number,
|
|
pathOffset: number,
|
|
pathLength: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!entry.path) {
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
const textDecoder = new TextDecoder();
|
|
const data = new Uint8Array(
|
|
this.#memory.buffer,
|
|
pathOffset,
|
|
pathLength,
|
|
);
|
|
const path = resolve(entry.path!, textDecoder.decode(data));
|
|
|
|
Deno.mkdirSync(path);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"path_filestat_get": syscall((
|
|
fd: number,
|
|
flags: number,
|
|
pathOffset: number,
|
|
pathLength: number,
|
|
bufferOffset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!entry.path) {
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
const textDecoder = new TextDecoder();
|
|
const data = new Uint8Array(
|
|
this.#memory.buffer,
|
|
pathOffset,
|
|
pathLength,
|
|
);
|
|
const path = resolve(entry.path!, textDecoder.decode(data));
|
|
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
const info = (flags & LOOKUPFLAGS_SYMLINK_FOLLOW) != 0
|
|
? Deno.statSync(path)
|
|
: Deno.lstatSync(path);
|
|
|
|
memoryView.setBigUint64(
|
|
bufferOffset,
|
|
BigInt(info.dev ? info.dev : 0),
|
|
true,
|
|
);
|
|
bufferOffset += 8;
|
|
|
|
memoryView.setBigUint64(
|
|
bufferOffset,
|
|
BigInt(info.ino ? info.ino : 0),
|
|
true,
|
|
);
|
|
bufferOffset += 8;
|
|
|
|
switch (true) {
|
|
case info.isFile:
|
|
memoryView.setUint8(bufferOffset, FILETYPE_REGULAR_FILE);
|
|
bufferOffset += 8;
|
|
break;
|
|
|
|
case info.isDirectory:
|
|
memoryView.setUint8(bufferOffset, FILETYPE_DIRECTORY);
|
|
bufferOffset += 8;
|
|
break;
|
|
|
|
case info.isSymlink:
|
|
memoryView.setUint8(bufferOffset, FILETYPE_SYMBOLIC_LINK);
|
|
bufferOffset += 8;
|
|
break;
|
|
|
|
default:
|
|
memoryView.setUint8(bufferOffset, FILETYPE_UNKNOWN);
|
|
bufferOffset += 8;
|
|
break;
|
|
}
|
|
|
|
memoryView.setUint32(bufferOffset, Number(info.nlink), true);
|
|
bufferOffset += 8;
|
|
|
|
memoryView.setBigUint64(bufferOffset, BigInt(info.size), true);
|
|
bufferOffset += 8;
|
|
|
|
memoryView.setBigUint64(
|
|
bufferOffset,
|
|
BigInt(info.atime ? info.atime.getTime() * 1e6 : 0),
|
|
true,
|
|
);
|
|
bufferOffset += 8;
|
|
|
|
memoryView.setBigUint64(
|
|
bufferOffset,
|
|
BigInt(info.mtime ? info.mtime.getTime() * 1e6 : 0),
|
|
true,
|
|
);
|
|
bufferOffset += 8;
|
|
|
|
memoryView.setBigUint64(
|
|
bufferOffset,
|
|
BigInt(info.birthtime ? info.birthtime.getTime() * 1e6 : 0),
|
|
true,
|
|
);
|
|
bufferOffset += 8;
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"path_filestat_set_times": syscall((
|
|
fd: number,
|
|
flags: number,
|
|
pathOffset: number,
|
|
pathLength: number,
|
|
atim: bigint,
|
|
mtim: bigint,
|
|
fstflags: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!entry.path) {
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
const textDecoder = new TextDecoder();
|
|
const data = new Uint8Array(
|
|
this.#memory.buffer,
|
|
pathOffset,
|
|
pathLength,
|
|
);
|
|
const path = resolve(entry.path!, textDecoder.decode(data));
|
|
|
|
if ((fstflags & FSTFLAGS_ATIM_NOW) == FSTFLAGS_ATIM_NOW) {
|
|
atim = BigInt(Date.now()) * BigInt(1e6);
|
|
}
|
|
|
|
if ((fstflags & FSTFLAGS_MTIM_NOW) == FSTFLAGS_MTIM_NOW) {
|
|
mtim = BigInt(Date.now()) * BigInt(1e6);
|
|
}
|
|
|
|
Deno.utimeSync(path, Number(atim), Number(mtim));
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"path_link": syscall((
|
|
oldFd: number,
|
|
oldFlags: number,
|
|
oldPathOffset: number,
|
|
oldPathLength: number,
|
|
newFd: number,
|
|
newPathOffset: number,
|
|
newPathLength: number,
|
|
): number => {
|
|
const oldEntry = this.#fds[oldFd];
|
|
const newEntry = this.#fds[newFd];
|
|
if (!oldEntry || !newEntry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!oldEntry.path || !newEntry.path) {
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
const textDecoder = new TextDecoder();
|
|
const oldData = new Uint8Array(
|
|
this.#memory.buffer,
|
|
oldPathOffset,
|
|
oldPathLength,
|
|
);
|
|
const oldPath = resolve(oldEntry.path!, textDecoder.decode(oldData));
|
|
const newData = new Uint8Array(
|
|
this.#memory.buffer,
|
|
newPathOffset,
|
|
newPathLength,
|
|
);
|
|
const newPath = resolve(newEntry.path!, textDecoder.decode(newData));
|
|
|
|
Deno.linkSync(oldPath, newPath);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"path_open": syscall((
|
|
fd: number,
|
|
dirflags: number,
|
|
pathOffset: number,
|
|
pathLength: number,
|
|
oflags: number,
|
|
rightsBase: bigint,
|
|
rightsInheriting: bigint,
|
|
fdflags: number,
|
|
openedFdOffset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!entry.path) {
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
const textDecoder = new TextDecoder();
|
|
const pathData = new Uint8Array(
|
|
this.#memory.buffer,
|
|
pathOffset,
|
|
pathLength,
|
|
);
|
|
const resolvedPath = resolve(entry.path!, textDecoder.decode(pathData));
|
|
|
|
if (relative(entry.path, resolvedPath).startsWith("..")) {
|
|
return ERRNO_NOTCAPABLE;
|
|
}
|
|
|
|
let path;
|
|
if (
|
|
(dirflags & LOOKUPFLAGS_SYMLINK_FOLLOW) == LOOKUPFLAGS_SYMLINK_FOLLOW
|
|
) {
|
|
try {
|
|
path = Deno.realPathSync(resolvedPath);
|
|
if (relative(entry.path, path).startsWith("..")) {
|
|
return ERRNO_NOTCAPABLE;
|
|
}
|
|
} catch (_err) {
|
|
path = resolvedPath;
|
|
}
|
|
} else {
|
|
path = resolvedPath;
|
|
}
|
|
|
|
if ((oflags & OFLAGS_DIRECTORY) !== 0) {
|
|
// XXX (caspervonb) this isn't ideal as we can't get a rid for the
|
|
// directory this way so there's no native fstat but Deno.open
|
|
// doesn't work with directories on windows so we'll have to work
|
|
// around it for now.
|
|
const entries = Array.from(Deno.readDirSync(path));
|
|
const openedFd = this.#fds.push({
|
|
flags: fdflags,
|
|
path,
|
|
entries,
|
|
}) - 1;
|
|
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
memoryView.setUint32(openedFdOffset, openedFd, true);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}
|
|
|
|
const options = {
|
|
read: false,
|
|
write: false,
|
|
append: false,
|
|
truncate: false,
|
|
create: false,
|
|
createNew: false,
|
|
};
|
|
|
|
if ((oflags & OFLAGS_CREAT) !== 0) {
|
|
options.create = true;
|
|
options.write = true;
|
|
}
|
|
|
|
if ((oflags & OFLAGS_EXCL) !== 0) {
|
|
options.createNew = true;
|
|
}
|
|
|
|
if ((oflags & OFLAGS_TRUNC) !== 0) {
|
|
options.truncate = true;
|
|
options.write = true;
|
|
}
|
|
|
|
const read = (
|
|
RIGHTS_FD_READ |
|
|
RIGHTS_FD_READDIR
|
|
);
|
|
|
|
if ((rightsBase & read) != 0n) {
|
|
options.read = true;
|
|
}
|
|
|
|
const write = (
|
|
RIGHTS_FD_DATASYNC |
|
|
RIGHTS_FD_WRITE |
|
|
RIGHTS_FD_ALLOCATE |
|
|
RIGHTS_FD_FILESTAT_SET_SIZE
|
|
);
|
|
|
|
if ((rightsBase & write) != 0n) {
|
|
options.write = true;
|
|
}
|
|
|
|
if ((fdflags & FDFLAGS_APPEND) != 0) {
|
|
options.append = true;
|
|
}
|
|
|
|
if ((fdflags & FDFLAGS_DSYNC) != 0) {
|
|
// TODO(caspervonb): review if we can emulate this.
|
|
}
|
|
|
|
if ((fdflags & FDFLAGS_NONBLOCK) != 0) {
|
|
// TODO(caspervonb): review if we can emulate this.
|
|
}
|
|
|
|
if ((fdflags & FDFLAGS_RSYNC) != 0) {
|
|
// TODO(caspervonb): review if we can emulate this.
|
|
}
|
|
|
|
if ((fdflags & FDFLAGS_SYNC) != 0) {
|
|
// TODO(caspervonb): review if we can emulate this.
|
|
}
|
|
|
|
if (!options.read && !options.write && !options.truncate) {
|
|
options.read = true;
|
|
}
|
|
|
|
const { rid } = Deno.openSync(path, options);
|
|
const openedFd = this.#fds.push({
|
|
rid,
|
|
flags: fdflags,
|
|
path,
|
|
}) - 1;
|
|
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
memoryView.setUint32(openedFdOffset, openedFd, true);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"path_readlink": syscall((
|
|
fd: number,
|
|
pathOffset: number,
|
|
pathLength: number,
|
|
bufferOffset: number,
|
|
bufferLength: number,
|
|
bufferUsedOffset: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!entry.path) {
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
const memoryData = new Uint8Array(this.#memory.buffer);
|
|
const memoryView = new DataView(this.#memory.buffer);
|
|
|
|
const pathData = new Uint8Array(
|
|
this.#memory.buffer,
|
|
pathOffset,
|
|
pathLength,
|
|
);
|
|
const path = resolve(entry.path!, new TextDecoder().decode(pathData));
|
|
|
|
const link = Deno.readLinkSync(path);
|
|
const linkData = new TextEncoder().encode(link);
|
|
memoryData.set(new Uint8Array(linkData, 0, bufferLength), bufferOffset);
|
|
|
|
const bufferUsed = Math.min(linkData.byteLength, bufferLength);
|
|
memoryView.setUint32(bufferUsedOffset, bufferUsed, true);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"path_remove_directory": syscall((
|
|
fd: number,
|
|
pathOffset: number,
|
|
pathLength: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!entry.path) {
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
const textDecoder = new TextDecoder();
|
|
const data = new Uint8Array(
|
|
this.#memory.buffer,
|
|
pathOffset,
|
|
pathLength,
|
|
);
|
|
const path = resolve(entry.path!, textDecoder.decode(data));
|
|
|
|
if (!Deno.statSync(path).isDirectory) {
|
|
return ERRNO_NOTDIR;
|
|
}
|
|
|
|
Deno.removeSync(path);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"path_rename": syscall((
|
|
fd: number,
|
|
oldPathOffset: number,
|
|
oldPathLength: number,
|
|
newFd: number,
|
|
newPathOffset: number,
|
|
newPathLength: number,
|
|
): number => {
|
|
const oldEntry = this.#fds[fd];
|
|
const newEntry = this.#fds[newFd];
|
|
if (!oldEntry || !newEntry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!oldEntry.path || !newEntry.path) {
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
const textDecoder = new TextDecoder();
|
|
const oldData = new Uint8Array(
|
|
this.#memory.buffer,
|
|
oldPathOffset,
|
|
oldPathLength,
|
|
);
|
|
const oldPath = resolve(oldEntry.path!, textDecoder.decode(oldData));
|
|
const newData = new Uint8Array(
|
|
this.#memory.buffer,
|
|
newPathOffset,
|
|
newPathLength,
|
|
);
|
|
const newPath = resolve(newEntry.path!, textDecoder.decode(newData));
|
|
|
|
Deno.renameSync(oldPath, newPath);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"path_symlink": syscall((
|
|
oldPathOffset: number,
|
|
oldPathLength: number,
|
|
fd: number,
|
|
newPathOffset: number,
|
|
newPathLength: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!entry.path) {
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
const textDecoder = new TextDecoder();
|
|
const oldData = new Uint8Array(
|
|
this.#memory.buffer,
|
|
oldPathOffset,
|
|
oldPathLength,
|
|
);
|
|
const oldPath = textDecoder.decode(oldData);
|
|
const newData = new Uint8Array(
|
|
this.#memory.buffer,
|
|
newPathOffset,
|
|
newPathLength,
|
|
);
|
|
const newPath = resolve(entry.path!, textDecoder.decode(newData));
|
|
|
|
Deno.symlinkSync(oldPath, newPath);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"path_unlink_file": syscall((
|
|
fd: number,
|
|
pathOffset: number,
|
|
pathLength: number,
|
|
): number => {
|
|
const entry = this.#fds[fd];
|
|
if (!entry) {
|
|
return ERRNO_BADF;
|
|
}
|
|
|
|
if (!entry.path) {
|
|
return ERRNO_INVAL;
|
|
}
|
|
|
|
const textDecoder = new TextDecoder();
|
|
const data = new Uint8Array(
|
|
this.#memory.buffer,
|
|
pathOffset,
|
|
pathLength,
|
|
);
|
|
const path = resolve(entry.path!, textDecoder.decode(data));
|
|
|
|
Deno.removeSync(path);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"poll_oneoff": syscall((
|
|
_inOffset: number,
|
|
_outOffset: number,
|
|
_nsubscriptions: number,
|
|
_neventsOffset: number,
|
|
): number => {
|
|
return ERRNO_NOSYS;
|
|
}),
|
|
|
|
"proc_exit": syscall((
|
|
rval: number,
|
|
): never => {
|
|
if (this.#exitOnReturn) {
|
|
Deno.exit(rval);
|
|
}
|
|
|
|
throw new ExitStatus(rval);
|
|
}),
|
|
|
|
"proc_raise": syscall((
|
|
_sig: number,
|
|
): number => {
|
|
return ERRNO_NOSYS;
|
|
}),
|
|
|
|
"sched_yield": syscall((): number => {
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"random_get": syscall((
|
|
bufferOffset: number,
|
|
bufferLength: number,
|
|
): number => {
|
|
const buffer = new Uint8Array(
|
|
this.#memory.buffer,
|
|
bufferOffset,
|
|
bufferLength,
|
|
);
|
|
crypto.getRandomValues(buffer);
|
|
|
|
return ERRNO_SUCCESS;
|
|
}),
|
|
|
|
"sock_recv": syscall((
|
|
_fd: number,
|
|
_riDataOffset: number,
|
|
_riDataLength: number,
|
|
_riFlags: number,
|
|
_roDataLengthOffset: number,
|
|
_roFlagsOffset: number,
|
|
): number => {
|
|
return ERRNO_NOSYS;
|
|
}),
|
|
|
|
"sock_send": syscall((
|
|
_fd: number,
|
|
_siDataOffset: number,
|
|
_siDataLength: number,
|
|
_siFlags: number,
|
|
_soDataLengthOffset: number,
|
|
): number => {
|
|
return ERRNO_NOSYS;
|
|
}),
|
|
|
|
"sock_shutdown": syscall((
|
|
_fd: number,
|
|
_how: number,
|
|
): number => {
|
|
return ERRNO_NOSYS;
|
|
}),
|
|
};
|
|
|
|
this.#started = false;
|
|
}
|
|
|
|
/**
|
|
* Attempt to begin execution of instance as a command by invoking its
|
|
* _start() export.
|
|
*
|
|
* If the instance does not contain a _start() export, or if the instance
|
|
* contains an _initialize export an error will be thrown.
|
|
*
|
|
* The instance must also have a WebAssembly.Memory export named "memory"
|
|
* which will be used as the address space, if it does not an error will be
|
|
* thrown.
|
|
*/
|
|
start(instance: WebAssembly.Instance): null | number | never {
|
|
if (this.#started) {
|
|
throw new Error("WebAssembly.Instance has already started");
|
|
}
|
|
|
|
this.#started = true;
|
|
|
|
const { _start, _initialize, memory } = instance.exports;
|
|
|
|
if (!(memory instanceof WebAssembly.Memory)) {
|
|
throw new TypeError("WebAsembly.instance must provide a memory export");
|
|
}
|
|
|
|
this.#memory = memory;
|
|
|
|
if (typeof _initialize == "function") {
|
|
throw new TypeError(
|
|
"WebAsembly.instance export _initialize must not be a function",
|
|
);
|
|
}
|
|
|
|
if (typeof _start != "function") {
|
|
throw new TypeError(
|
|
"WebAssembly.Instance export _start must be a function",
|
|
);
|
|
}
|
|
|
|
try {
|
|
_start();
|
|
} catch (err) {
|
|
if (err instanceof ExitStatus) {
|
|
return err.code;
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Attempt to initialize instance as a reactor by invoking its _initialize() export.
|
|
*
|
|
* If instance contains a _start() export, then an exception is thrown.
|
|
*
|
|
* The instance must also have a WebAssembly.Memory export named "memory"
|
|
* which will be used as the address space, if it does not an error will be
|
|
* thrown.
|
|
*/
|
|
initialize(instance: WebAssembly.Instance) {
|
|
if (this.#started) {
|
|
throw new Error("WebAssembly.Instance has already started");
|
|
}
|
|
|
|
this.#started = true;
|
|
|
|
const { _start, _initialize, memory } = instance.exports;
|
|
|
|
if (!(memory instanceof WebAssembly.Memory)) {
|
|
throw new TypeError("WebAsembly.instance must provide a memory export");
|
|
}
|
|
|
|
this.#memory = memory;
|
|
|
|
if (typeof _start == "function") {
|
|
throw new TypeError(
|
|
"WebAssembly.Instance export _start must not be a function",
|
|
);
|
|
}
|
|
|
|
if (typeof _initialize != "function") {
|
|
throw new TypeError(
|
|
"WebAsembly.instance export _initialize must be a function",
|
|
);
|
|
}
|
|
|
|
_initialize();
|
|
}
|
|
}
|