├── .github ├── FUNDING.yml ├── scripts │ ├── setup_clang.sh │ └── setup_deno.sh └── workflows │ └── ci.yml ├── LICENSE ├── README.md ├── mod.ts ├── test.ts └── testdata ├── c ├── args.c ├── clock_getres.c ├── clock_gettime.c ├── exit.c ├── fseek.c ├── fwrite.c ├── getentropy.c ├── getenv.c ├── link.c ├── mkdir.c ├── rename.c ├── rmdir.c ├── stderr.c ├── stdin.c ├── stdout.c ├── symlink.c └── unlink.c └── fixture ├── .gitkeep └── directory ├── file └── symlink /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: caspervonb 2 | patreon: caspervonb 3 | open_collective: deno-wasi 4 | custom: "https://paypal.me/caspervonb" 5 | -------------------------------------------------------------------------------- /.github/scripts/setup_clang.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$OS" = "Windows_NT" ]; then 4 | wasi_uri="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-11/wasi-sdk-11.0-mingw.tar.gz" 5 | elif [ "$(uname -s)" = "Darwin" ]; then 6 | wasi_uri="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-11/wasi-sdk-11.0-macos.tar.gz" 7 | else 8 | wasi_uri="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-11/wasi-sdk-11.0-linux.tar.gz" 9 | fi 10 | 11 | if [ "$OS" = "Windows_NT" ]; then 12 | wasi_root="C:/wasi-sdk" 13 | wasi_bin="$wasi_root/bin" 14 | else 15 | wasi_root="/opt/wasi-sdk" 16 | wasi_bin="$wasi_root/bin" 17 | fi 18 | 19 | curl -L -o wasi-sdk.tar.gz "$wasi_uri" 20 | tar -xf wasi-sdk.tar.gz 21 | mv wasi-sdk-11.0 "$wasi_root" 22 | 23 | echo "::add-path::$wasi_bin" 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/scripts/setup_deno.sh: -------------------------------------------------------------------------------- 1 | if [ "$OS" = "Windows_NT" ]; then 2 | target="x86_64-pc-windows-msvc" 3 | elif [ "$(uname -s)" = "Darwin" ]; then 4 | target="x86_64-apple-darwin" 5 | else 6 | target="x86_64-unknown-linux-gnu" 7 | fi 8 | 9 | if [ $# -eq 0 ]; then 10 | deno_asset_path=$( 11 | curl -sSf https://github.com/denoland/deno/releases | 12 | grep -o "/denoland/deno/releases/download/.*/deno-${target}\\.zip" | 13 | head -n 1 14 | ) 15 | 16 | if [ ! "path" ]; then 17 | echo "Error: Unable to find latest Deno release on GitHub." 1>&2 18 | exit 1 19 | fi 20 | deno_uri="https://github.com${deno_asset_path}" 21 | else 22 | deno_uri="https://github.com/denoland/deno/releases/download/${1}/deno-${target}.zip" 23 | fi 24 | 25 | if [ "$OS" = "Windows_NT" ]; then 26 | deno_root="C:/deno" 27 | deno_bin="$deno_root/bin" 28 | deno_exe="$deno_bin/deno" 29 | else 30 | deno_root="/opt/deno" 31 | deno_bin="$deno_root/bin" 32 | deno_exe="$deno_bin/deno" 33 | fi 34 | 35 | mkdir -p "$deno_bin" 36 | cd "$deno_bin" 37 | 38 | curl -L -o "$deno_exe.zip" "$deno_uri" 39 | 40 | unzip -o "$deno_exe.zip" 41 | rm "$deno_exe.zip" 42 | chmod +x "$deno_exe" 43 | 44 | echo "::add-path::$deno_bin" 45 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-latest, macos-latest, windows-latest] 16 | 17 | steps: 18 | - name: Checkout project 19 | uses: actions/checkout@v2 20 | 21 | - name: Set up clang 22 | run: bash .github/scripts/setup_clang.sh 23 | 24 | - name: Set up deno 25 | run: bash .github/scripts/setup_deno.sh 26 | 27 | - name: Run tests 28 | run: deno test --unstable -A 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Casper Beyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebAssembly System Interface implementation for Deno 2 | 3 | **Note: This module has been merged into Deno, further development will take place in the [Deno repository](https://github.com/denoland/deno)** 4 | 5 | This package provides an implementation of the [WebAssembly System 6 | Interface](https://github.com/webassembly/wasi) for the 7 | [Deno](https://github.com/denoland/deno) TypeScript runtime. 8 | 9 | # Status 10 | 11 | This is a work in progress so if a function isn't implemented, hit refresh. 12 | 13 | | Name | Status | Notes | 14 | |---------------------------|:-------:|----------------------------------------------------------------------------------------| 15 | | `args_get` | ✓ | | 16 | | `args_sizes_get` | ✓ | | 17 | | `environ_get` | ✓ | | 18 | | `environ_sizes_get` | ✓ | | 19 | | `clock_res_get` | ✓ | | 20 | | `clock_time_get` | ✓ | | 21 | | `fd_advise` | | This has no obvious path to implementation at this time. | 22 | | `fd_allocate` | | This has no obvious path to implementation at this time. | 23 | | `fd_close` | ✓ | | 24 | | `fd_datasync` | | This is blocking on getting fdatasync(2) implemented upstream in Deno. | 25 | | `fd_fdstat_get` | ✓ | This currently does not write flags and rights as we do not track those at the moment. | 26 | | `fd_fdstat_set_flags` | | This has no obvious path to implementation at this time. | 27 | | `fd_fdstat_set_rights` | | | 28 | | `fd_filestat_get` | | This is blocking on getting fstat implemented upstream in Deno. | 29 | | `fd_filestat_set_size` | | This is blocking on getting ftruncate implemented upstream in Deno. | 30 | | `fd_filestat_set_times` | ✓ | | 31 | | `fd_pread` | ✓ | | 32 | | `fd_prestat_get` | ✓ | | 33 | | `fd_prestat_dir_name` | ✓ | | 34 | | `fd_pwrite` | ✓ | | 35 | | `fd_read` | ✓ | | 36 | | `fd_readdir` | | | 37 | | `fd_renumber` | ✓ | | 38 | | `fd_seek` | ✓ | | 39 | | `fd_sync` | ✓ | This is blocking on getting fsync(2) implemented upstream in Deno. | 40 | | `fd_tell` | ✓ | | 41 | | `fd_write` | ✓ | | 42 | | `path_create_directory` | ✓ | | 43 | | `path_filestat_get` | ✓ | | 44 | | `path_filestat_set_times` | ✓ | | 45 | | `path_link` | ✓ | |7 46 | | `path_open` | ✓ | Opening directories is not portable | 47 | | `path_readlink` | ✓ | | 48 | | `path_remove_directory` | ✓ | | 49 | | `path_rename` | ✓ | | 50 | | `path_symlink` | ✓ | | 51 | | `path_unlink_file` | ✓ | | 52 | | `poll_oneoff` | ✓ | | 53 | | `proc_exit` | ✓ | | 54 | | `proc_raise` | | | 55 | | `sched_yield` | | | 56 | | `random_get` | ✓ | | 57 | | `sock_recv` | | | 58 | | `sock_send` | | | 59 | | `sock_shutdown` | | | 60 | 61 | ## Example 62 | 63 | ```typescript 64 | instance.exports._start(); 65 | } else if (module.exports._initialize) { 66 | import WASI from "https://deno.land/x/wasi/mod.ts"; 67 | 68 | const wasi = new WASI({ 69 | args: Deno.args, 70 | env: Deno.env, 71 | }); 72 | 73 | const binary = Deno.readAll("command.wasm"); 74 | const module = await WebAssembly.compile(binary); 75 | const instance = await WebAssembly.instantiate(module, { 76 | wasi_snapshot_preview1: wasi.exports, 77 | }); 78 | 79 | wasi.memory = module.exports.memory; 80 | 81 | if (module.exports._start) { 82 | instance.exports._initialize(); 83 | } else { 84 | throw new Error("No entry point found"); 85 | } 86 | ``` 87 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | import { 2 | resolve 3 | } from "https://deno.land/std/path/mod.ts"; 4 | 5 | const CLOCKID_REALTIME = 0; 6 | const CLOCKID_MONOTONIC = 1; 7 | const CLOCKID_PROCESS_CPUTIME_ID = 2; 8 | const CLOCKID_THREAD_CPUTIME_ID = 3; 9 | 10 | const ERRNO_SUCCESS = 0; 11 | const ERRNO_2BIG = 1; 12 | const ERRNO_ACCES = 2; 13 | const ERRNO_ADDRINUSE = 3; 14 | const ERRNO_ADDRNOTAVAIL = 4; 15 | const ERRNO_AFNOSUPPORT = 5; 16 | const ERRNO_AGAIN = 6; 17 | const ERRNO_ALREADY = 7; 18 | const ERRNO_BADF = 8; 19 | const ERRNO_BADMSG = 9; 20 | const ERRNO_BUSY = 10; 21 | const ERRNO_CANCELED = 11; 22 | const ERRNO_CHILD = 12; 23 | const ERRNO_CONNABORTED = 13; 24 | const ERRNO_CONNREFUSED = 14; 25 | const ERRNO_CONNRESET = 15; 26 | const ERRNO_DEADLK = 16; 27 | const ERRNO_DESTADDRREQ = 17; 28 | const ERRNO_DOM = 18; 29 | const ERRNO_DQUOT = 19; 30 | const ERRNO_EXIST = 20; 31 | const ERRNO_FAULT = 21; 32 | const ERRNO_FBIG = 22; 33 | const ERRNO_HOSTUNREACH = 23; 34 | const ERRNO_IDRM = 24; 35 | const ERRNO_ILSEQ = 25; 36 | const ERRNO_INPROGRESS = 26; 37 | const ERRNO_INTR = 27; 38 | const ERRNO_INVAL = 28; 39 | const ERRNO_IO = 29; 40 | const ERRNO_ISCONN = 30; 41 | const ERRNO_ISDIR = 31; 42 | const ERRNO_LOOP = 32; 43 | const ERRNO_MFILE = 33; 44 | const ERRNO_MLINK = 34; 45 | const ERRNO_MSGSIZE = 35; 46 | const ERRNO_MULTIHOP = 36; 47 | const ERRNO_NAMETOOLONG = 37; 48 | const ERRNO_NETDOWN = 38; 49 | const ERRNO_NETRESET = 39; 50 | const ERRNO_NETUNREACH = 40; 51 | const ERRNO_NFILE = 41; 52 | const ERRNO_NOBUFS = 42; 53 | const ERRNO_NODEV = 43; 54 | const ERRNO_NOENT = 44; 55 | const ERRNO_NOEXEC = 45; 56 | const ERRNO_NOLCK = 46; 57 | const ERRNO_NOLINK = 47; 58 | const ERRNO_NOMEM = 48; 59 | const ERRNO_NOMSG = 49; 60 | const ERRNO_NOPROTOOPT = 50; 61 | const ERRNO_NOSPC = 51; 62 | const ERRNO_NOSYS = 52; 63 | const ERRNO_NOTCONN = 53; 64 | const ERRNO_NOTDIR = 54; 65 | const ERRNO_NOTEMPTY = 55; 66 | const ERRNO_NOTRECOVERABLE = 56; 67 | const ERRNO_NOTSOCK = 57; 68 | const ERRNO_NOTSUP = 58; 69 | const ERRNO_NOTTY = 59; 70 | const ERRNO_NXIO = 60; 71 | const ERRNO_OVERFLOW = 61; 72 | const ERRNO_OWNERDEAD = 62; 73 | const ERRNO_PERM = 63; 74 | const ERRNO_PIPE = 64; 75 | const ERRNO_PROTO = 65; 76 | const ERRNO_PROTONOSUPPORT = 66; 77 | const ERRNO_PROTOTYPE = 67; 78 | const ERRNO_RANGE = 68; 79 | const ERRNO_ROFS = 69; 80 | const ERRNO_SPIPE = 70; 81 | const ERRNO_SRCH = 71; 82 | const ERRNO_STALE = 72; 83 | const ERRNO_TIMEDOUT = 73; 84 | const ERRNO_TXTBSY = 74; 85 | const ERRNO_XDEV = 75; 86 | const ERRNO_NOTCAPABLE = 76; 87 | 88 | const RIGHTS_FD_DATASYNC = 0x0000000000000001n; 89 | const RIGHTS_FD_READ = 0x0000000000000002n; 90 | const RIGHTS_FD_SEEK = 0x0000000000000004n; 91 | const RIGHTS_FD_FDSTAT_SET_FLAGS = 0x0000000000000008n; 92 | const RIGHTS_FD_SYNC = 0x0000000000000010n; 93 | const RIGHTS_FD_TELL = 0x0000000000000020n; 94 | const RIGHTS_FD_WRITE = 0x0000000000000040n; 95 | const RIGHTS_FD_ADVISE = 0x0000000000000080n; 96 | const RIGHTS_FD_ALLOCATE = 0x0000000000000100n; 97 | const RIGHTS_PATH_CREATE_DIRECTORY = 0x0000000000000200n; 98 | const RIGHTS_PATH_CREATE_FILE = 0x0000000000000400n; 99 | const RIGHTS_PATH_LINK_SOURCE = 0x0000000000000800n; 100 | const RIGHTS_PATH_LINK_TARGET = 0x0000000000001000n; 101 | const RIGHTS_PATH_OPEN = 0x0000000000002000n; 102 | const RIGHTS_FD_READDIR = 0x0000000000004000n; 103 | const RIGHTS_PATH_READLINK = 0x0000000000008000n; 104 | const RIGHTS_PATH_RENAME_SOURCE = 0x0000000000010000n; 105 | const RIGHTS_PATH_RENAME_TARGET = 0x0000000000020000n; 106 | const RIGHTS_PATH_FILESTAT_GET = 0x0000000000040000n; 107 | const RIGHTS_PATH_FILESTAT_SET_SIZE = 0x0000000000080000n; 108 | const RIGHTS_PATH_FILESTAT_SET_TIMES = 0x0000000000100000n; 109 | const RIGHTS_FD_FILESTAT_GET = 0x0000000000200000n; 110 | const RIGHTS_FD_FILESTAT_SET_SIZE = 0x0000000000400000n; 111 | const RIGHTS_FD_FILESTAT_SET_TIMES = 0x0000000000800000n; 112 | const RIGHTS_PATH_SYMLINK = 0x0000000001000000n; 113 | const RIGHTS_PATH_REMOVE_DIRECTORY = 0x0000000002000000n; 114 | const RIGHTS_PATH_UNLINK_FILE = 0x0000000004000000n; 115 | const RIGHTS_POLL_FD_READWRITE = 0x0000000008000000n; 116 | const RIGHTS_SOCK_SHUTDOWN = 0x0000000010000000n; 117 | 118 | const WHENCE_SET = 0; 119 | const WHENCE_CUR = 1; 120 | const WHENCE_END = 2; 121 | 122 | const FILETYPE_UNKNOWN = 0; 123 | const FILETYPE_BLOCK_DEVICE = 1; 124 | const FILETYPE_CHARACTER_DEVICE = 2; 125 | const FILETYPE_DIRECTORY = 3; 126 | const FILETYPE_REGULAR_FILE = 4; 127 | const FILETYPE_SOCKET_DGRAM = 5; 128 | const FILETYPE_SOCKET_STREAM = 6; 129 | const FILETYPE_SYMBOLIC_LINK = 7; 130 | 131 | const ADVICE_NORMAL = 0; 132 | const ADVICE_SEQUENTIAL = 1; 133 | const ADVICE_RANDOM = 2; 134 | const ADVICE_WILLNEED = 3; 135 | const ADVICE_DONTNEED = 4; 136 | const ADVICE_NOREUSE = 5; 137 | 138 | const FDFLAGS_APPEND = 0x0001; 139 | const FDFLAGS_DSYNC = 0x0002; 140 | const FDFLAGS_NONBLOCK = 0x0004; 141 | const FDFLAGS_RSYNC = 0x0008; 142 | const FDFLAGS_SYNC = 0x0010; 143 | 144 | const FSTFLAGS_ATIM = 0x0001; 145 | const FSTFLAGS_ATIM_NOW = 0x0002; 146 | const FSTFLAGS_MTIM = 0x0004; 147 | const FSTFLAGS_MTIM_NOW = 0x0008; 148 | 149 | const LOOKUPFLAGS_SYMLINK_FOLLOW = 0x0001; 150 | 151 | const OFLAGS_CREAT = 0x0001; 152 | const OFLAGS_DIRECTORY = 0x0002; 153 | const OFLAGS_EXCL = 0x0004; 154 | const OFLAGS_TRUNC = 0x0008; 155 | 156 | const EVENTTYPE_CLOCK = 0; 157 | const EVENTTYPE_FD_READ = 1; 158 | const EVENTTYPE_FD_WRITE = 2; 159 | 160 | const EVENTRWFLAGS_FD_READWRITE_HANGUP = 1; 161 | const SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME = 1; 162 | 163 | const SIGNAL_NONE = 0; 164 | const SIGNAL_HUP = 1; 165 | const SIGNAL_INT = 2; 166 | const SIGNAL_QUIT = 3; 167 | const SIGNAL_ILL = 4; 168 | const SIGNAL_TRAP = 5; 169 | const SIGNAL_ABRT = 6; 170 | const SIGNAL_BUS = 7; 171 | const SIGNAL_FPE = 8; 172 | const SIGNAL_KILL = 9; 173 | const SIGNAL_USR1 = 10; 174 | const SIGNAL_SEGV = 11; 175 | const SIGNAL_USR2 = 12; 176 | const SIGNAL_PIPE = 13; 177 | const SIGNAL_ALRM = 14; 178 | const SIGNAL_TERM = 15; 179 | const SIGNAL_CHLD = 16; 180 | const SIGNAL_CONT = 17; 181 | const SIGNAL_STOP = 18; 182 | const SIGNAL_TSTP = 19; 183 | const SIGNAL_TTIN = 20; 184 | const SIGNAL_TTOU = 21; 185 | const SIGNAL_URG = 22; 186 | const SIGNAL_XCPU = 23; 187 | const SIGNAL_XFSZ = 24; 188 | const SIGNAL_VTALRM = 25; 189 | const SIGNAL_PROF = 26; 190 | const SIGNAL_WINCH = 27; 191 | const SIGNAL_POLL = 28; 192 | const SIGNAL_PWR = 29; 193 | const SIGNAL_SYS = 30; 194 | 195 | const RIFLAGS_RECV_PEEK = 0x0001; 196 | const RIFLAGS_RECV_WAITALL = 0x0002; 197 | 198 | const ROFLAGS_RECV_DATA_TRUNCATED = 0x0001; 199 | 200 | const SDFLAGS_RD = 0x0001; 201 | const SDFLAGS_WR = 0x0002; 202 | 203 | const PREOPENTYPE_DIR = 0; 204 | 205 | const clock_res_realtime = function() : bigint { 206 | return BigInt(1e6); 207 | }; 208 | 209 | const clock_res_monotonic = function() : bigint { 210 | return BigInt(1e3); 211 | }; 212 | 213 | const clock_res_process = clock_res_monotonic; 214 | const clock_res_thread = clock_res_monotonic; 215 | 216 | const clock_time_realtime = function() : bigint { 217 | return BigInt(Date.now()) * BigInt(1e6); 218 | }; 219 | 220 | const clock_time_monotonic = function() : bigint { 221 | const t = performance.now(); 222 | const s = Math.trunc(t); 223 | const ms = Math.floor((t - s) * 1e3); 224 | 225 | return (BigInt(s) * BigInt(1e9)) + (BigInt(ms) * BigInt(1e6)); 226 | }; 227 | 228 | const clock_time_process = clock_time_monotonic; 229 | const clock_time_thread = clock_time_monotonic; 230 | 231 | function errno(err : Error) { 232 | switch (err.name) { 233 | case "NotFound": 234 | return ERRNO_NOENT; 235 | 236 | case "PermissionDenied": 237 | return ERRNO_ACCES; 238 | 239 | case "ConnectionRefused": 240 | return ERRNO_CONNREFUSED; 241 | 242 | case "ConnectionReset": 243 | return ERRNO_CONNRESET; 244 | 245 | case "ConnectionAborted": 246 | return ERRNO_CONNABORTED; 247 | 248 | case "NotConnected": 249 | return ERRNO_NOTCONN; 250 | 251 | case "AddrInUse": 252 | return ERRNO_ADDRINUSE; 253 | 254 | case "AddrNotAvailable": 255 | return ERRNO_ADDRNOTAVAIL; 256 | 257 | case "BrokenPipe": 258 | return ERRNO_PIPE; 259 | 260 | case "InvalidData": 261 | return ERRNO_INVAL; 262 | 263 | case "TimedOut": 264 | return ERRNO_TIMEDOUT; 265 | 266 | case "Interrupted": 267 | return ERRNO_INTR; 268 | 269 | case "BadResource": 270 | return ERRNO_BADF; 271 | 272 | case "Busy": 273 | return ERRNO_BUSY; 274 | 275 | default: 276 | return ERRNO_INVAL; 277 | } 278 | } 279 | 280 | export type ModuleOptions = { 281 | args? : string[]; 282 | env? : { [key: string]: string | undefined }; 283 | preopens?: { [key: string]: string }; 284 | memory? : WebAssembly.Memory; 285 | }; 286 | 287 | export class Module { 288 | args : string[]; 289 | env : { [key: string]: string | undefined }; 290 | memory : WebAssembly.Memory; 291 | 292 | fds : any[]; 293 | exports: { [key: string]: any }; 294 | 295 | constructor(options : ModuleOptions) { 296 | this.args = options.args ? options.args : []; 297 | this.env = options.env ? options.env : {}; 298 | this.memory = options.memory as WebAssembly.Memory; 299 | 300 | this.fds = [ 301 | { 302 | type: FILETYPE_CHARACTER_DEVICE, 303 | handle: Deno.stdin, 304 | }, 305 | { 306 | type: FILETYPE_CHARACTER_DEVICE, 307 | handle: Deno.stdout, 308 | }, 309 | { 310 | type: FILETYPE_CHARACTER_DEVICE, 311 | handle: Deno.stderr, 312 | }, 313 | ]; 314 | 315 | if (options.preopens) { 316 | for (const [ vpath, path ] of Object.entries(options.preopens)) { 317 | const info = Deno.statSync(path); 318 | if (!info.isDirectory) { 319 | throw new TypeError(`${path} is not a directory`); 320 | } 321 | 322 | const type = FILETYPE_DIRECTORY; 323 | 324 | const entry = { 325 | type, 326 | path, 327 | vpath, 328 | }; 329 | 330 | this.fds.push(entry); 331 | } 332 | } 333 | 334 | this.exports = { 335 | args_get: (argv_ptr : number, argv_buf_ptr : number) : number => { 336 | const args = this.args; 337 | const text = new TextEncoder(); 338 | const heap = new Uint8Array(this.memory.buffer); 339 | const view = new DataView(this.memory.buffer); 340 | 341 | for (let arg of args) { 342 | view.setUint32(argv_ptr, argv_buf_ptr, true); 343 | argv_ptr += 4; 344 | 345 | const data = text.encode(`${arg}\0`); 346 | heap.set(data, argv_buf_ptr); 347 | argv_buf_ptr += data.length; 348 | } 349 | 350 | return ERRNO_SUCCESS; 351 | }, 352 | 353 | args_sizes_get: (argc_out : number, argv_buf_size_out : number) : number => { 354 | const args = this.args; 355 | const text = new TextEncoder(); 356 | const view = new DataView(this.memory.buffer); 357 | 358 | view.setUint32(argc_out, args.length, true); 359 | view.setUint32(argv_buf_size_out, args.reduce(function(acc, arg) { 360 | return acc + text.encode(`${arg}\0`).length; 361 | }, 0), true); 362 | 363 | return ERRNO_SUCCESS; 364 | }, 365 | 366 | environ_get: (environ_ptr : number, environ_buf_ptr : number) : number => { 367 | const entries = Object.entries(this.env); 368 | const text = new TextEncoder(); 369 | const heap = new Uint8Array(this.memory.buffer); 370 | const view = new DataView(this.memory.buffer); 371 | 372 | for (let [key, value] of entries) { 373 | view.setUint32(environ_ptr, environ_buf_ptr, true); 374 | environ_ptr += 4; 375 | 376 | const data = text.encode(`${key}=${value}\0`); 377 | heap.set(data, environ_buf_ptr); 378 | environ_buf_ptr += data.length; 379 | } 380 | 381 | return ERRNO_SUCCESS; 382 | }, 383 | 384 | environ_sizes_get: (environc_out : number, environ_buf_size_out : number) : number => { 385 | const entries = Object.entries(this.env); 386 | const text = new TextEncoder(); 387 | const view = new DataView(this.memory.buffer); 388 | 389 | view.setUint32(environc_out, entries.length, true); 390 | view.setUint32(environ_buf_size_out, entries.reduce(function(acc, [key, value]) { 391 | return acc + text.encode(`${key}=${value}\0`).length; 392 | }, 0), true); 393 | 394 | return ERRNO_SUCCESS; 395 | }, 396 | 397 | clock_res_get: (id: number, resolution_out : number) : number => { 398 | const view = new DataView(this.memory.buffer); 399 | 400 | switch (id) { 401 | case CLOCKID_REALTIME: 402 | view.setBigUint64(resolution_out, clock_res_realtime(), true); 403 | break; 404 | 405 | case CLOCKID_MONOTONIC: 406 | view.setBigUint64(resolution_out, clock_res_monotonic(), true); 407 | break; 408 | 409 | case CLOCKID_PROCESS_CPUTIME_ID: 410 | view.setBigUint64(resolution_out, clock_res_process(), true); 411 | break; 412 | 413 | case CLOCKID_THREAD_CPUTIME_ID: 414 | view.setBigUint64(resolution_out, clock_res_thread(), true); 415 | break; 416 | 417 | default: 418 | return ERRNO_INVAL; 419 | 420 | } 421 | 422 | return ERRNO_SUCCESS; 423 | }, 424 | 425 | clock_time_get: (id: number, precision : bigint, time_out : number) : number => { 426 | const view = new DataView(this.memory.buffer); 427 | 428 | switch (id) { 429 | case CLOCKID_REALTIME: 430 | view.setBigUint64(time_out, clock_time_realtime(), true); 431 | break; 432 | 433 | case CLOCKID_MONOTONIC: 434 | view.setBigUint64(time_out, clock_time_monotonic(), true); 435 | break; 436 | 437 | case CLOCKID_PROCESS_CPUTIME_ID: 438 | view.setBigUint64(time_out, clock_time_process(), true); 439 | break; 440 | 441 | case CLOCKID_THREAD_CPUTIME_ID: 442 | view.setBigUint64(time_out, clock_time_thread(), true); 443 | break; 444 | 445 | default: 446 | return ERRNO_INVAL; 447 | } 448 | 449 | return ERRNO_SUCCESS; 450 | }, 451 | 452 | fd_advise: (fd : number, offset : bigint, len : bigint, advice : number) : number => { 453 | return ERRNO_NOSYS; 454 | }, 455 | 456 | fd_allocate: (fd : number, offset : bigint, len : bigint) : number => { 457 | return ERRNO_NOSYS; 458 | }, 459 | 460 | fd_close: (fd : number) : number => { 461 | const entry = this.fds[fd]; 462 | if (!entry) { 463 | return ERRNO_BADF; 464 | } 465 | 466 | entry.handle.close(); 467 | delete this.fds[fd]; 468 | 469 | return ERRNO_SUCCESS; 470 | }, 471 | 472 | fd_datasync: (fd : number) : number => { 473 | return ERRNO_NOSYS; 474 | }, 475 | 476 | fd_fdstat_get: (fd : number, stat_out : number) : number => { 477 | const entry = this.fds[fd]; 478 | if (!entry) { 479 | return ERRNO_BADF; 480 | } 481 | 482 | const view = new DataView(this.memory.buffer); 483 | view.setUint8(stat_out, entry.type); 484 | view.setUint16(stat_out + 4, 0, true); // TODO 485 | view.setBigUint64(stat_out + 8, 0n, true); // TODO 486 | view.setBigUint64(stat_out + 16, 0n, true); // TODO 487 | 488 | return ERRNO_SUCCESS; 489 | }, 490 | 491 | fd_fdstat_set_flags: (fd : number, flags : number) : number => { 492 | return ERRNO_NOSYS; 493 | }, 494 | 495 | fd_fdstat_set_rights: (fd : number, fs_rights_base : bigint, fs_rights_inheriting : bigint) : number => { 496 | return ERRNO_NOSYS; 497 | }, 498 | 499 | fd_filestat_get: (fd : number, buf_out : number) : number => { 500 | return ERRNO_NOSYS; 501 | }, 502 | 503 | fd_filestat_set_size: (fd : number, size : bigint) : number => { 504 | return ERRNO_NOSYS; 505 | }, 506 | 507 | fd_filestat_set_times: (fd : number, atim : bigint, mtim : bigint, fst_flags : number) : number => { 508 | const entry = this.fds[fd]; 509 | if (!entry) { 510 | return ERRNO_BADF; 511 | } 512 | 513 | if (!entry.path) { 514 | return ERRNO_INVAL; 515 | } 516 | 517 | if ((fst_flags & FSTFLAGS_ATIM_NOW) == FSTFLAGS_ATIM_NOW) { 518 | atim = BigInt(Date.now() * 1e6); 519 | } 520 | 521 | if ((fst_flags & FSTFLAGS_MTIM_NOW) == FSTFLAGS_MTIM_NOW) { 522 | mtim = BigInt(Date.now() * 1e6); 523 | } 524 | 525 | try { 526 | Deno.utimeSync(entry.path, Number(atim), Number(mtim)); 527 | } catch (err) { 528 | return errno(err); 529 | } 530 | 531 | return ERRNO_SUCCESS; 532 | }, 533 | 534 | fd_pread: (fd : number, iovs_ptr : number, iovs_len : number, offset : bigint, nread_out : number) : number => { 535 | const entry = this.fds[fd]; 536 | if (!entry) { 537 | return ERRNO_BADF; 538 | } 539 | 540 | const seek = entry.handle.seekSync(0, Deno.SeekMode.Current); 541 | const view = new DataView(this.memory.buffer); 542 | 543 | let nread = 0; 544 | for (let i = 0; i < iovs_len; i++) { 545 | const data_ptr = view.getUint32(iovs_ptr, true); 546 | iovs_ptr += 4; 547 | 548 | const data_len = view.getUint32(iovs_ptr, true); 549 | iovs_ptr += 4; 550 | 551 | const data = new Uint8Array(this.memory.buffer, data_ptr, data_len); 552 | nread += entry.handle.readSync(data); 553 | } 554 | 555 | entry.handle.seekSync(seek, Deno.SeekMode.Start); 556 | view.setUint32(nread_out, nread, true); 557 | 558 | return ERRNO_SUCCESS; 559 | }, 560 | 561 | fd_prestat_get: (fd : number, buf_out : number) : number => { 562 | const entry = this.fds[fd]; 563 | if (!entry) { 564 | return ERRNO_BADF; 565 | } 566 | 567 | if (!entry.vpath) { 568 | return ERRNO_BADF; 569 | } 570 | 571 | const view = new DataView(this.memory.buffer); 572 | view.setUint8(buf_out, PREOPENTYPE_DIR); 573 | view.setUint32(buf_out + 4, new TextEncoder().encode(entry.vpath).byteLength, true); 574 | 575 | return ERRNO_SUCCESS; 576 | }, 577 | 578 | fd_prestat_dir_name: (fd : number, path_ptr : number, path_len : number) : number => { 579 | const entry = this.fds[fd]; 580 | if (!entry) { 581 | return ERRNO_BADF; 582 | } 583 | 584 | if (!entry.vpath) { 585 | return ERRNO_BADF; 586 | } 587 | 588 | const data = new Uint8Array(this.memory.buffer, path_ptr, path_len); 589 | data.set(new TextEncoder().encode(entry.vpath)); 590 | 591 | return ERRNO_SUCCESS; 592 | }, 593 | 594 | fd_pwrite: (fd : number, iovs_ptr : number, iovs_len : number, offset : bigint, nwritten_out : number) : number => { 595 | const entry = this.fds[fd]; 596 | if (!entry) { 597 | return ERRNO_BADF; 598 | } 599 | 600 | const seek = entry.handle.seekSync(0, Deno.SeekMode.Current); 601 | const view = new DataView(this.memory.buffer); 602 | 603 | let nwritten = 0; 604 | for (let i = 0; i < iovs_len; i++) { 605 | const data_ptr = view.getUint32(iovs_ptr, true); 606 | iovs_ptr += 4; 607 | 608 | const data_len = view.getUint32(iovs_ptr, true); 609 | iovs_ptr += 4; 610 | 611 | const data = new Uint8Array(this.memory.buffer, data_ptr, data_len); 612 | nwritten += entry.handle.writeSync(data); 613 | } 614 | 615 | entry.handle.seekSync(seek, Deno.SeekMode.Start); 616 | view.setUint32(nwritten_out, nwritten, true); 617 | 618 | return ERRNO_SUCCESS; 619 | }, 620 | 621 | fd_read: (fd : number, iovs_ptr : number, iovs_len : number, nread_out : number) : number => { 622 | const entry = this.fds[fd]; 623 | if (!entry) { 624 | return ERRNO_BADF; 625 | } 626 | 627 | const view = new DataView(this.memory.buffer); 628 | 629 | let nread = 0; 630 | for (let i = 0; i < iovs_len; i++) { 631 | const data_ptr = view.getUint32(iovs_ptr, true); 632 | iovs_ptr += 4; 633 | 634 | const data_len = view.getUint32(iovs_ptr, true); 635 | iovs_ptr += 4; 636 | 637 | const data = new Uint8Array(this.memory.buffer, data_ptr, data_len); 638 | nread += entry.handle.readSync(data); 639 | } 640 | 641 | view.setUint32(nread_out, nread, true); 642 | 643 | return ERRNO_SUCCESS; 644 | }, 645 | 646 | fd_readdir: (fd : number, buf_ptr : number, buf_len : number, cookie : bigint, bufused_out : number) : number => { 647 | return ERRNO_NOSYS; 648 | }, 649 | 650 | fd_renumber: (fd : number, to : number) : number => { 651 | if (!this.fds[fd]) { 652 | return ERRNO_BADF; 653 | } 654 | 655 | if (!this.fds[to]) { 656 | return ERRNO_BADF; 657 | } 658 | 659 | this.fds[to].handle.close(); 660 | this.fds[to] = this.fds[fd]; 661 | delete this.fds[fd]; 662 | 663 | return ERRNO_SUCCESS; 664 | }, 665 | 666 | fd_seek: (fd : number, offset : bigint, whence : number, newoffset_out : number) : number => { 667 | const entry = this.fds[fd]; 668 | if (!entry) { 669 | return ERRNO_BADF; 670 | } 671 | 672 | const view = new DataView(this.memory.buffer); 673 | 674 | try { 675 | // FIXME Deno does not support seeking with big integers 676 | 677 | const newoffset = entry.handle.seekSync(Number(offset), whence); 678 | view.setBigUint64(newoffset_out, BigInt(newoffset), true); 679 | } catch (err) { 680 | return ERRNO_INVAL; 681 | } 682 | 683 | return ERRNO_SUCCESS; 684 | }, 685 | 686 | fd_sync: (fd : number) : number => { 687 | return ERRNO_NOSYS; 688 | }, 689 | 690 | fd_tell: (fd : number, offset_out : number) : number => { 691 | const entry = this.fds[fd]; 692 | if (!entry) { 693 | return ERRNO_BADF; 694 | } 695 | 696 | const view = new DataView(this.memory.buffer); 697 | 698 | try { 699 | const offset = entry.handle.seekSync(0, Deno.SeekMode.Current); 700 | view.setBigUint64(offset_out, offset, true); 701 | } catch (err) { 702 | return ERRNO_INVAL; 703 | } 704 | 705 | return ERRNO_NOSYS; 706 | }, 707 | 708 | fd_write: (fd : number, iovs_ptr : number, iovs_len : number, nwritten_out : number) : number => { 709 | const entry = this.fds[fd]; 710 | if (!entry) { 711 | return ERRNO_BADF; 712 | } 713 | 714 | const view = new DataView(this.memory.buffer); 715 | 716 | let nwritten = 0; 717 | for (let i = 0; i < iovs_len; i++) { 718 | const data_ptr = view.getUint32(iovs_ptr, true); 719 | iovs_ptr += 4; 720 | 721 | const data_len = view.getUint32(iovs_ptr, true); 722 | iovs_ptr += 4; 723 | 724 | nwritten += entry.handle.writeSync(new Uint8Array(this.memory.buffer, data_ptr, data_len)); 725 | } 726 | 727 | view.setUint32(nwritten_out, nwritten, true); 728 | 729 | return ERRNO_SUCCESS; 730 | }, 731 | 732 | path_create_directory: (fd : number, path_ptr : number, path_len : number) : number => { 733 | const entry = this.fds[fd]; 734 | if (!entry) { 735 | return ERRNO_BADF; 736 | } 737 | 738 | if (!entry.path) { 739 | return ERRNO_INVAL; 740 | } 741 | 742 | const text = new TextDecoder(); 743 | const data = new Uint8Array(this.memory.buffer, path_ptr, path_len); 744 | const path = resolve(entry.path, text.decode(data)); 745 | 746 | try { 747 | Deno.mkdirSync(path); 748 | } catch (err) { 749 | return errno(err); 750 | } 751 | 752 | return ERRNO_SUCCESS; 753 | }, 754 | 755 | path_filestat_get: (fd : number, flags : number, path_ptr : number, path_len : number, buf_out : number) : number => { 756 | const entry = this.fds[fd]; 757 | if (!entry) { 758 | return ERRNO_BADF; 759 | } 760 | 761 | if (!entry.path) { 762 | return ERRNO_INVAL; 763 | } 764 | 765 | const text = new TextDecoder(); 766 | const data = new Uint8Array(this.memory.buffer, path_ptr, path_len); 767 | const path = resolve(entry.path, text.decode(data)); 768 | 769 | const view = new DataView(this.memory.buffer); 770 | 771 | try { 772 | const info = Deno.statSync(path); 773 | 774 | view.setBigUint64(buf_out, BigInt(info.dev ? info.dev : 0), true); 775 | buf_out += 8; 776 | 777 | view.setBigUint64(buf_out, BigInt(info.ino ? info.ino : 0), true); 778 | buf_out += 8; 779 | 780 | switch (true) { 781 | case info.isFile: 782 | view.setUint8(buf_out, FILETYPE_REGULAR_FILE); 783 | buf_out += 4; 784 | break; 785 | 786 | case info.isDirectory: 787 | view.setUint8(buf_out, FILETYPE_DIRECTORY); 788 | buf_out += 4; 789 | break; 790 | 791 | case info.isSymlink: 792 | view.setUint8(buf_out, FILETYPE_SYMBOLIC_LINK); 793 | buf_out += 4; 794 | break; 795 | 796 | default: 797 | view.setUint8(buf_out, FILETYPE_UNKNOWN); 798 | buf_out += 4; 799 | break; 800 | } 801 | 802 | 803 | view.setUint32(buf_out, Number(info.nlink), true); 804 | buf_out += 4; 805 | 806 | view.setBigUint64(buf_out, BigInt(info.size), true); 807 | buf_out += 8; 808 | 809 | view.setBigUint64(buf_out, BigInt(info.atime ? info.atime.getTime() * 1e6 : 0), true); 810 | buf_out += 8; 811 | 812 | view.setBigUint64(buf_out, BigInt(info.mtime ? info.mtime.getTime() * 1e6 : 0), true); 813 | buf_out += 8; 814 | 815 | view.setBigUint64(buf_out, BigInt(info.birthtime ? info.birthtime.getTime() * 1e6 : 0), true); 816 | buf_out += 8; 817 | } catch (err) { 818 | return errno(err); 819 | } 820 | 821 | return ERRNO_SUCCESS; 822 | }, 823 | 824 | path_filestat_set_times: (fd : number, flags : number, path_ptr : number, path_len : number, atim : bigint, mtim : bigint, fst_flags : number) : number => { 825 | const entry = this.fds[fd]; 826 | if (!entry) { 827 | return ERRNO_BADF; 828 | } 829 | 830 | if (!entry.path) { 831 | return ERRNO_INVAL; 832 | } 833 | 834 | const text = new TextDecoder(); 835 | const data = new Uint8Array(this.memory.buffer, path_ptr, path_len); 836 | const path = resolve(entry.path, text.decode(data)); 837 | 838 | if ((fst_flags & FSTFLAGS_ATIM_NOW) == FSTFLAGS_ATIM_NOW) { 839 | atim = BigInt(Date.now()) * BigInt(1e6); 840 | } 841 | 842 | if ((fst_flags & FSTFLAGS_MTIM_NOW) == FSTFLAGS_MTIM_NOW) { 843 | mtim = BigInt(Date.now()) * BigInt(1e6); 844 | } 845 | 846 | try { 847 | Deno.utimeSync(path, Number(atim), Number(mtim)); 848 | } catch (err) { 849 | return errno(err); 850 | } 851 | 852 | return ERRNO_SUCCESS; 853 | }, 854 | 855 | path_link: (old_fd : number, old_flags : number, old_path_ptr : number, old_path_len : number, new_fd : number, new_path_ptr : number, new_path_len : number) : number => { 856 | const old_entry = this.fds[old_fd]; 857 | const new_entry = this.fds[new_fd]; 858 | if (!old_entry || !new_entry) { 859 | return ERRNO_BADF; 860 | } 861 | 862 | if (!old_entry.path || !new_entry.path) { 863 | return ERRNO_INVAL; 864 | } 865 | 866 | const text = new TextDecoder(); 867 | const old_data = new Uint8Array(this.memory.buffer, old_path_ptr, old_path_len); 868 | const old_path = resolve(old_entry.path, text.decode(old_data)); 869 | const new_data = new Uint8Array(this.memory.buffer, new_path_ptr, new_path_len); 870 | const new_path = resolve(new_entry.path, text.decode(new_data)); 871 | 872 | try { 873 | Deno.linkSync(old_path, new_path); 874 | } catch (err) { 875 | return errno(err); 876 | } 877 | 878 | return ERRNO_SUCCESS; 879 | }, 880 | 881 | path_open: (fd : number, dirflags : number, path_ptr : number, path_len : number, oflags : number, fs_rights_base : number | bigint, fs_rights_inherting : number | bigint, fdflags : number, opened_fd_out : number) : number => { 882 | const entry = this.fds[fd]; 883 | if (!entry) { 884 | return ERRNO_BADF; 885 | } 886 | 887 | if (!entry.path) { 888 | return ERRNO_INVAL; 889 | } 890 | 891 | const text = new TextDecoder(); 892 | const data = new Uint8Array(this.memory.buffer, path_ptr, path_len); 893 | const path = resolve(entry.path, text.decode(data)); 894 | 895 | const options = { 896 | read: false, 897 | write: false, 898 | append: false, 899 | truncate: false, 900 | create: false, 901 | createNew: false, 902 | }; 903 | 904 | if ((oflags & OFLAGS_CREAT) !== 0) { 905 | options.create = true; 906 | options.write = true; 907 | } 908 | 909 | if ((oflags & OFLAGS_DIRECTORY) !== 0) { 910 | // TODO (caspervonb) review if we can 911 | // emulate this; unix supports opening 912 | // directories, windows does not. 913 | } 914 | 915 | if ((oflags & OFLAGS_EXCL) !== 0) { 916 | options.createNew = true; 917 | } 918 | 919 | if ((oflags & OFLAGS_TRUNC) !== 0) { 920 | options.truncate = true; 921 | options.write = true; 922 | } 923 | 924 | if ((BigInt(fs_rights_base) & BigInt(RIGHTS_FD_READ | RIGHTS_FD_READDIR)) != 0n) { 925 | options.read = true; 926 | } 927 | 928 | if ((BigInt(fs_rights_base) & BigInt(RIGHTS_FD_DATASYNC | RIGHTS_FD_WRITE | RIGHTS_FD_ALLOCATE | RIGHTS_FD_FILESTAT_SET_SIZE)) != 0n) { 929 | options.write = true; 930 | } 931 | 932 | if ((fdflags & FDFLAGS_APPEND) != 0) { 933 | options.append = true; 934 | } 935 | 936 | if ((fdflags & FDFLAGS_DSYNC) != 0) { 937 | // TODO (caspervonb) review if we can emulate this. 938 | } 939 | 940 | if ((fdflags & FDFLAGS_NONBLOCK) != 0) { 941 | // TODO (caspervonb) review if we can emulate this. 942 | } 943 | 944 | if ((fdflags & FDFLAGS_RSYNC) != 0) { 945 | // TODO (caspervonb) review if we can emulate this. 946 | } 947 | 948 | if ((fdflags & FDFLAGS_SYNC) != 0) { 949 | // TODO (caspervonb) review if we can emulate this. 950 | } 951 | 952 | if (!options.read && !options.write && !options.truncate) { 953 | options.read = true; 954 | } 955 | 956 | try { 957 | const handle = Deno.openSync(path, options); 958 | const opened_fd = this.fds.push({ 959 | handle, 960 | path, 961 | }) - 1; 962 | 963 | const view = new DataView(this.memory.buffer); 964 | view.setUint32(opened_fd_out, opened_fd, true); 965 | } catch (err) { 966 | return errno(err); 967 | } 968 | 969 | 970 | return ERRNO_SUCCESS; 971 | }, 972 | 973 | path_readlink: (fd : number, path_ptr : number, path_len : number, buf_ptr : number, buf_len : number, bufused_out : number) : number => { 974 | const entry = this.fds[fd]; 975 | if (!entry) { 976 | return ERRNO_BADF; 977 | } 978 | 979 | if (!entry.path) { 980 | return ERRNO_INVAL; 981 | } 982 | 983 | const view = new DataView(this.memory.buffer); 984 | const heap = new Uint8Array(this.memory.buffer); 985 | 986 | const data = new Uint8Array(this.memory.buffer, path_ptr, path_len); 987 | const path = resolve(entry.path, new TextDecoder().decode(data)); 988 | 989 | try { 990 | const link = Deno.readLinkSync(path); 991 | const data = new TextEncoder().encode(link); 992 | heap.set(new Uint8Array(data, 0, buf_len), buf_ptr); 993 | 994 | const bufused = Math.min(data.byteLength, buf_len); 995 | view.setUint32(bufused_out, bufused, true); 996 | } catch (err) { 997 | return errno(err); 998 | } 999 | 1000 | return ERRNO_SUCCESS; 1001 | }, 1002 | 1003 | path_remove_directory: (fd : number, path_ptr : number, path_len : number) : number => { 1004 | const entry = this.fds[fd]; 1005 | if (!entry) { 1006 | return ERRNO_BADF; 1007 | } 1008 | 1009 | if (!entry.path) { 1010 | return ERRNO_INVAL; 1011 | } 1012 | 1013 | const text = new TextDecoder(); 1014 | const data = new Uint8Array(this.memory.buffer, path_ptr, path_len); 1015 | const path = resolve(entry.path, text.decode(data)); 1016 | 1017 | try { 1018 | if (!Deno.statSync(path).isDirectory) { 1019 | return ERRNO_NOTDIR; 1020 | } 1021 | 1022 | Deno.removeSync(path); 1023 | } catch (err) { 1024 | return errno(err); 1025 | } 1026 | 1027 | return ERRNO_SUCCESS; 1028 | }, 1029 | 1030 | path_rename: (fd : number, old_path_ptr : number, old_path_len : number, new_fd : number, new_path_ptr : number, new_path_len : number) : number => { 1031 | const old_entry = this.fds[fd]; 1032 | const new_entry = this.fds[new_fd]; 1033 | if (!old_entry || !new_entry) { 1034 | return ERRNO_BADF; 1035 | } 1036 | 1037 | if (!old_entry.path || !new_entry.path) { 1038 | return ERRNO_INVAL; 1039 | } 1040 | 1041 | const text = new TextDecoder(); 1042 | const old_data = new Uint8Array(this.memory.buffer, old_path_ptr, old_path_len); 1043 | const old_path = resolve(old_entry.path, text.decode(old_data)); 1044 | const new_data = new Uint8Array(this.memory.buffer, new_path_ptr, new_path_len); 1045 | const new_path = resolve(new_entry.path, text.decode(new_data)); 1046 | 1047 | try { 1048 | Deno.renameSync(old_path, new_path); 1049 | } catch (err) { 1050 | return errno(err); 1051 | } 1052 | 1053 | return ERRNO_SUCCESS; 1054 | }, 1055 | 1056 | path_symlink: (old_path_ptr : number, old_path_len : number, fd : number, new_path_ptr : number, new_path_len : number) : number => { 1057 | const entry = this.fds[fd]; 1058 | if (!entry) { 1059 | return ERRNO_BADF; 1060 | } 1061 | 1062 | if (!entry.path) { 1063 | return ERRNO_INVAL; 1064 | } 1065 | 1066 | 1067 | const text = new TextDecoder(); 1068 | const old_data = new Uint8Array(this.memory.buffer, old_path_ptr, old_path_len); 1069 | const old_path = text.decode(old_data); 1070 | const new_data = new Uint8Array(this.memory.buffer, new_path_ptr, new_path_len); 1071 | const new_path = resolve(entry.path, text.decode(new_data)); 1072 | 1073 | try { 1074 | Deno.symlinkSync(old_path, new_path); 1075 | } catch (err) { 1076 | return errno(err); 1077 | } 1078 | 1079 | return ERRNO_SUCCESS; 1080 | }, 1081 | 1082 | path_unlink_file: (fd : number, path_ptr : number, path_len : number) : number => { 1083 | const entry = this.fds[fd]; 1084 | if (!entry) { 1085 | return ERRNO_BADF; 1086 | } 1087 | 1088 | if (!entry.path) { 1089 | return ERRNO_INVAL; 1090 | } 1091 | 1092 | const text = new TextDecoder(); 1093 | const data = new Uint8Array(this.memory.buffer, path_ptr, path_len); 1094 | const path = resolve(entry.path, text.decode(data)); 1095 | 1096 | try { 1097 | Deno.removeSync(path); 1098 | } catch (err) { 1099 | return errno(err); 1100 | } 1101 | 1102 | return ERRNO_SUCCESS; 1103 | }, 1104 | 1105 | poll_oneoff: (in_ptr : number, out_ptr : number, nsubscriptions : number, nevents_out : number) : number => { 1106 | return ERRNO_NOSYS; 1107 | }, 1108 | 1109 | proc_exit: (rval : number) : never => { 1110 | Deno.exit(rval); 1111 | }, 1112 | 1113 | proc_raise: (sig : number) : number => { 1114 | return ERRNO_NOSYS; 1115 | }, 1116 | 1117 | sched_yield: () : number => { 1118 | return ERRNO_SUCCESS; 1119 | }, 1120 | 1121 | random_get: (buf_ptr : number, buf_len : number) : number => { 1122 | const buffer = new Uint8Array(this.memory.buffer, buf_ptr, buf_len); 1123 | crypto.getRandomValues(buffer); 1124 | 1125 | return ERRNO_SUCCESS; 1126 | }, 1127 | 1128 | sock_recv: (fd : number, ri_data_ptr : number, ri_data_len : number, ri_flags : number, ro_datalen_out : number, ro_flags_out : number) : number => { 1129 | return ERRNO_NOSYS; 1130 | }, 1131 | 1132 | sock_send: (fd : number, si_data_ptr : number, si_data_len : number, si_flags : number, so_datalen_out : number) : number => { 1133 | return ERRNO_NOSYS; 1134 | }, 1135 | 1136 | sock_shutdown: (fd : number, how : number) : number => { 1137 | return ERRNO_NOSYS; 1138 | }, 1139 | }; 1140 | } 1141 | }; 1142 | 1143 | export default Module; 1144 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assert, 3 | assertEquals, 4 | } from "https://deno.land/std/testing/asserts.ts"; 5 | 6 | import WASI from "./mod.ts"; 7 | 8 | if (import.meta.main) { 9 | const bin = await Deno.readFile(Deno.args[0]); 10 | const mod = await WebAssembly.compile(bin); 11 | 12 | const wasi = new WASI({ 13 | env: Deno.env.toObject(), 14 | args: Deno.args.slice(1), 15 | preopens: { 16 | "/fixture": "testdata/fixture", 17 | "/tmp": Deno.makeTempDirSync(), 18 | }, 19 | }); 20 | 21 | const instance = new WebAssembly.Instance(mod, { 22 | wasi_snapshot_preview1: wasi.exports, 23 | }); 24 | 25 | wasi.memory = instance.exports.memory; 26 | 27 | instance.exports._start(); 28 | Deno.exit(0); 29 | } 30 | 31 | for await (const entry of Deno.readDir("testdata/c")) { 32 | if (!entry.name.endsWith(".c")) { 33 | continue; 34 | } 35 | 36 | Deno.test(entry.name, async function() { 37 | const sourcePath = `testdata/c/${entry.name}`; 38 | 39 | const moduleSource = await Deno.readTextFile(sourcePath); 40 | const moduleHeader = moduleSource.match(/^\/\/.*/); 41 | const moduleOptions = JSON.parse(moduleHeader ? moduleHeader[0].slice(2) : "{}"); 42 | const modulePath = sourcePath.replace(/.c$/, ".wasm"); 43 | 44 | const compilerProcess = await Deno.run({ 45 | cmd: ["clang", "-target", "wasm32-wasi", "-o", modulePath, sourcePath], 46 | stdout: "inherit", 47 | stderr: "inherit", 48 | }); 49 | 50 | const compilerStatus = await compilerProcess.status(); 51 | assertEquals(compilerStatus.success, true); 52 | 53 | compilerProcess.close() 54 | 55 | const moduleProcess = await Deno.run({ 56 | cmd: ["deno", "run", "--unstable", "--v8-flags=--experimental-wasm-bigint", "-q", "-A", "test.ts", modulePath, ...(moduleOptions.args ? moduleOptions.args : [])], 57 | env: moduleOptions.env ? moduleOptions.env : {}, 58 | stdin: "piped", 59 | stdout: "piped", 60 | stderr: "piped", 61 | }); 62 | 63 | if (moduleProcess.stdin) { 64 | if (moduleOptions.stdin) { 65 | const data = new TextEncoder().encode(moduleOptions.stdin); 66 | await Deno.writeAll(moduleProcess.stdin, data); 67 | } 68 | 69 | moduleProcess.stdin.close(); 70 | } 71 | 72 | if (moduleProcess.stdout) { 73 | const actual = new TextDecoder().decode(await Deno.readAll(moduleProcess.stdout)); 74 | const expected = moduleOptions.stdout ? moduleOptions.stdout : ""; 75 | 76 | assertEquals(actual, expected); 77 | 78 | moduleProcess.stdout.close(); 79 | } 80 | 81 | if (moduleProcess.stderr) { 82 | const actual = new TextDecoder().decode(await Deno.readAll(moduleProcess.stderr)); 83 | const expected = moduleOptions.stderr ? moduleOptions.stderr : ""; 84 | 85 | assertEquals(actual, expected); 86 | 87 | moduleProcess.stderr.close(); 88 | } 89 | 90 | const moduleStatus = await moduleProcess.status(); 91 | assertEquals(moduleStatus.code, moduleOptions.code ? moduleOptions.code : 0); 92 | 93 | moduleProcess.close() 94 | }); 95 | } 96 | -------------------------------------------------------------------------------- /testdata/c/args.c: -------------------------------------------------------------------------------- 1 | // { "args": ["one", "two", "three" ] } 2 | 3 | #include 4 | #include 5 | 6 | int main(int argc, char* argv[]) { 7 | assert(argc == 3); 8 | assert(strcmp(argv[0], "one") == 0); 9 | assert(strcmp(argv[1], "two") == 0); 10 | assert(strcmp(argv[2], "three") == 0); 11 | } 12 | -------------------------------------------------------------------------------- /testdata/c/clock_getres.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | struct timespec ts; 6 | 7 | assert(clock_getres(CLOCK_REALTIME, &ts) == 0); 8 | assert(clock_getres(CLOCK_MONOTONIC, &ts) == 0); 9 | assert(clock_getres(CLOCK_PROCESS_CPUTIME_ID, &ts) == 0); 10 | assert(clock_getres(CLOCK_THREAD_CPUTIME_ID, &ts) == 0); 11 | } 12 | -------------------------------------------------------------------------------- /testdata/c/clock_gettime.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | struct timespec ts; 6 | 7 | assert(clock_gettime(CLOCK_REALTIME, &ts) == 0); 8 | assert(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); 9 | assert(clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) == 0); 10 | assert(clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0); 11 | } 12 | -------------------------------------------------------------------------------- /testdata/c/exit.c: -------------------------------------------------------------------------------- 1 | // { "code": 120 } 2 | 3 | int main(void) { 4 | return 120; 5 | } 6 | -------------------------------------------------------------------------------- /testdata/c/fseek.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | FILE *file = fopen("/tmp/output.txt", "w"); 6 | assert(file != NULL); 7 | 8 | char data[32] = { 0 }; 9 | assert(fwrite(data, sizeof(char), sizeof(data), file) == 32); 10 | 11 | assert(fseek (file, 16, SEEK_SET) == 0); 12 | assert(ftell(file) == 16); 13 | 14 | assert(fseek (file, 16, SEEK_CUR) == 0); 15 | assert(ftell(file) == 32); 16 | 17 | assert(fseek (file, 0, SEEK_END) == 0); 18 | assert(ftell(file) == 32); 19 | 20 | assert(fclose(file) == 0); 21 | } 22 | -------------------------------------------------------------------------------- /testdata/c/fwrite.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | FILE* file = fopen("/tmp/output.txt", "w"); 7 | assert(file != NULL); 8 | 9 | char data[] = "Hello, output.txt!"; 10 | int nwritten = fwrite(data, sizeof(char), sizeof(data), file); 11 | assert(nwritten == sizeof(data)); 12 | 13 | assert(ferror(file) == 0); 14 | assert(fclose(file) == 0); 15 | } 16 | -------------------------------------------------------------------------------- /testdata/c/getentropy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | char buf[256] = {0}; 6 | assert(getentropy(buf, 256) == 0); 7 | 8 | int sum = 0; 9 | for (int i = 0; i < 256; i++) { 10 | sum += buf[i]; 11 | } 12 | 13 | assert(sum != 0); 14 | 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /testdata/c/getenv.c: -------------------------------------------------------------------------------- 1 | // { "env": { "1": "one", "2": "two", "3": "three" } } 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int main(void) { 8 | assert(strcmp(getenv("1"), "one") == 0); 9 | assert(strcmp(getenv("2"), "two") == 0); 10 | assert(strcmp(getenv("3"), "three") == 0); 11 | } 12 | -------------------------------------------------------------------------------- /testdata/c/link.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(void) { 6 | struct stat st_old; 7 | struct stat st_new; 8 | 9 | assert(stat("/fixture/directory/file", &st_old) == 0); 10 | assert(link("/fixture/directory/file", "/fixture/directory/link") == 0); 11 | assert(stat("/fixture/directory/file", &st_new) == 0); 12 | assert(st_old.st_ino == st_new.st_ino); 13 | assert(unlink("/fixture/directory/link") == 0); 14 | 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /testdata/c/mkdir.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | if (mkdir("/tmp/foo", 0700) != 0) { 6 | return errno; 7 | } 8 | 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /testdata/c/rename.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | char oldname[] = "/tmp/oldfile"; 6 | char newname[] = "/tmp/newfile"; 7 | 8 | FILE *oldfile = fopen(oldname, "w"); 9 | assert(oldfile != NULL); 10 | assert(fclose(oldfile) == 0); 11 | 12 | assert(rename(oldname, newname) == 0); 13 | 14 | FILE *newfile = fopen(newname, "r"); 15 | assert(newfile != NULL); 16 | assert(fclose(newfile) == 0); 17 | 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /testdata/c/rmdir.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | if (rmdir("/tmp") != 0) { 6 | return errno; 7 | } 8 | 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /testdata/c/stderr.c: -------------------------------------------------------------------------------- 1 | // { "stderr": "Hello, world!" } 2 | 3 | #include 4 | 5 | int main(void) { 6 | if (fputs("Hello, world!", stderr) == 0) { 7 | return ferror(stderr); 8 | } 9 | 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /testdata/c/stdin.c: -------------------------------------------------------------------------------- 1 | // { "stdin": "Hello, world!" } 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int main(void) { 8 | char data[32]; 9 | assert(fgets(data, sizeof(data), stdin) != NULL); 10 | assert(strcmp(data, "Hello, world!") == 0); 11 | 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /testdata/c/stdout.c: -------------------------------------------------------------------------------- 1 | // { "stdout": "Hello, world!" } 2 | 3 | #include 4 | 5 | int main(void) { 6 | if (fputs("Hello, world!", stdout) == 0) { 7 | return ferror(stdout); 8 | } 9 | 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /testdata/c/symlink.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | const char* filepath = "./testdata/fixture/directory/file"; 6 | const char* linkpath = "/fixture/directory/file_symlink"; 7 | 8 | assert(symlink(filepath, linkpath) == 0); 9 | assert(unlink(linkpath) == 0); 10 | } 11 | -------------------------------------------------------------------------------- /testdata/c/unlink.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | assert(unlink("/tmp") == 0); 6 | 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /testdata/fixture/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caspervonb/deno-wasi/dc0a28632263abf5a052837142dc67d45acaba56/testdata/fixture/.gitkeep -------------------------------------------------------------------------------- /testdata/fixture/directory/file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caspervonb/deno-wasi/dc0a28632263abf5a052837142dc67d45acaba56/testdata/fixture/directory/file -------------------------------------------------------------------------------- /testdata/fixture/directory/symlink: -------------------------------------------------------------------------------- 1 | file --------------------------------------------------------------------------------