UNPKG

@electric-sql/pglite-tools

Version:

Tools for working with PGlite databases

1,344 lines (1,122 loc) 38.8 kB
/* eslint-disable */ // 2024-11-21 import * as defs from './defs.js' export { defs } export class WASIProcExit extends Error { constructor(code) { super(`Exit with code ${code}`) this.code = code } } // FS interface that is used here. Implement your own, if you want, or use zenfs or node fs! export class FSDummy { appendFileSync(path, data, options = {}) { throw new Error('appendFileSync not implemented') } fsyncSync(fd) { throw new Error('fsyncSync not implemented') } linkSync(existingPath, newPath) { throw new Error('linkSync not implemented') } mkdirSync(path, options = {}) { throw new Error('mkdirSync not implemented') } readdirSync(path, options = {}) { throw new Error('readdirSync not implemented') } readFileSync(path, options = {}) { throw new Error('readFileSync not implemented') } readlinkSync(path, options = {}) { throw new Error('readlinkSync not implemented') } renameSync(oldPath, newPath) { throw new Error('renameSync not implemented') } rmdirSync(path, options = {}) { throw new Error('rmdirSync not implemented') } setFlagsSync(path, flags) { throw new Error('setFlagsSync not implemented') } statSync(path, options = {}) { throw new Error('statSync not implemented') } symlinkSync(target, path, type = 'file') { throw new Error('symlinkSync not implemented') } truncateSync(path, len = 0) { throw new Error('truncateSync not implemented') } unlinkSync(path) { throw new Error('unlinkSync not implemented') } utimesSync(path, atime, mtime) { throw new Error('utimesSync not implemented') } writeFileSync(path, data, options = {}) { throw new Error('writeFileSync not implemented') } } export class WasiPreview1 { constructor(options = {}) { this.args = options.args || [] this.env = options.env || {} this.fs = options.fs || new FSDummy() if (!this.fs) { throw new Error('File system implementation required') } // Initialize file descriptors with stdin(0), stdout(1), stderr(2), / // fd is first number this.fds = new Map([ [0, { type: 'stdio' }], // stdin [1, { type: 'stdio' }], // stdout [2, { type: 'stdio' }], // stderr [3, { type: 'directory', preopenPath: '/' }], // root directory ]) this.nextFd = this.fds.size this.textDecoder = new TextDecoder() this.textEncoder = new TextEncoder() // Bind all WASI functions to maintain correct 'this' context this.args_get = this.args_get.bind(this) this.args_sizes_get = this.args_sizes_get.bind(this) this.environ_get = this.environ_get.bind(this) this.environ_sizes_get = this.environ_sizes_get.bind(this) this.clock_res_get = this.clock_res_get.bind(this) this.clock_time_get = this.clock_time_get.bind(this) this.fd_close = this.fd_close.bind(this) this.fd_seek = this.fd_seek.bind(this) this.fd_write = this.fd_write.bind(this) this.fd_read = this.fd_read.bind(this) this.fd_fdstat_get = this.fd_fdstat_get.bind(this) this.fd_fdstat_set_flags = this.fd_fdstat_set_flags.bind(this) this.fd_prestat_get = this.fd_prestat_get.bind(this) this.fd_prestat_dir_name = this.fd_prestat_dir_name.bind(this) this.path_open = this.path_open.bind(this) this.path_filestat_get = this.path_filestat_get.bind(this) this.proc_exit = this.proc_exit.bind(this) this.fd_advise = this.fd_advise.bind(this) this.fd_allocate = this.fd_allocate.bind(this) this.fd_datasync = this.fd_datasync.bind(this) this.fd_filestat_get = this.fd_filestat_get.bind(this) this.fd_filestat_set_size = this.fd_filestat_set_size.bind(this) this.fd_filestat_set_times = this.fd_filestat_set_times.bind(this) this.fd_pread = this.fd_pread.bind(this) this.fd_pwrite = this.fd_pwrite.bind(this) this.fd_readdir = this.fd_readdir.bind(this) this.fd_renumber = this.fd_renumber.bind(this) this.fd_sync = this.fd_sync.bind(this) this.fd_tell = this.fd_tell.bind(this) this.path_create_directory = this.path_create_directory.bind(this) this.path_filestat_set_times = this.path_filestat_set_times.bind(this) this.path_link = this.path_link.bind(this) this.path_readlink = this.path_readlink.bind(this) this.path_remove_directory = this.path_remove_directory.bind(this) this.path_rename = this.path_rename.bind(this) this.path_symlink = this.path_symlink.bind(this) this.path_unlink_file = this.path_unlink_file.bind(this) this.poll_oneoff = this.poll_oneoff.bind(this) this.sock_accept = this.sock_accept.bind(this) this.sock_recv = this.sock_recv.bind(this) this.sock_send = this.sock_send.bind(this) this.sock_shutdown = this.sock_shutdown.bind(this) this.random_get = this.random_get.bind(this) this.sched_yield = this.sched_yield.bind(this) } // Helper methods // this binds the wasm to this WASI implementation setup(wasm) { this.wasm = wasm } // this binds the wasm to this WASI implementation // and calls it's main()' start(wasm) { this.setup(wasm) try { if (wasm._start) { wasm._start() } return 0 } catch (e) { if (e instanceof WASIProcExit) { return e.code } throw e } } // Standard input (for fd_read) stdin() { return new Uint8Array() } // Standard output handling (for fd_write) stdout(buffer) { const text = this.textDecoder.decode(buffer).replace(/\n$/g, '') if (text) console.log(text) } // Standard error handling (for fd_write) stderr(buffer) { const text = this.textDecoder.decode(buffer).replace(/\n$/g, '') if (text) console.error(text) } // Args functions args_get(argvP, argvBufP) { const view = new DataView(this.wasm.memory.buffer) const mem = new Uint8Array(this.wasm.memory.buffer) for (const arg of this.args) { view.setUint32(argvP, argvBufP, true) argvP += 4 const encoded = this.textEncoder.encode(arg) mem.set(encoded, argvBufP) mem[argvBufP + encoded.length] = 0 // null terminator argvBufP += encoded.length + 1 } return defs.ERRNO_SUCCESS } args_sizes_get(argcPtr, argvBufSizePtr) { const view = new DataView(this.wasm.memory.buffer) view.setUint32(argcPtr, this.args.length, true) const bufSize = this.args.reduce((acc, arg) => acc + arg.length + 1, 0) view.setUint32(argvBufSizePtr, bufSize, true) return defs.ERRNO_SUCCESS } // Environment functions environ_get(environP, environBufP) { const view = new DataView(this.wasm.memory.buffer) const mem = new Uint8Array(this.wasm.memory.buffer) for (const [key, value] of Object.entries(this.env)) { view.setUint32(environP, environBufP, true) environP += 4 const entry = `${key}=${value}\0` const encoded = this.textEncoder.encode(entry) mem.set(encoded, environBufP) environBufP += encoded.length } return defs.ERRNO_SUCCESS } environ_sizes_get(environCountPtr, environBufSizePtr) { const view = new DataView(this.wasm.memory.buffer) const count = Object.keys(this.env).length view.setUint32(environCountPtr, count, true) const bufSize = Object.entries(this.env).reduce( (acc, [k, v]) => acc + k.length + v.length + 2, 0, ) view.setUint32(environBufSizePtr, bufSize, true) return defs.ERRNO_SUCCESS } // Clock functions clock_res_get(id, resPtr) { const view = new DataView(this.wasm.memory.buffer) let resolution switch (id) { case defs.CLOCKID_REALTIME: resolution = 1_000_000n // 1ms in nanoseconds break case defs.CLOCKID_MONOTONIC: resolution = 1_000n // 1μs in nanoseconds break default: return defs.ERRNO_INVAL } view.setBigUint64(resPtr, resolution, true) return defs.ERRNO_SUCCESS } clock_time_get(id, precision, timePtr) { const view = new DataView(this.wasm.memory.buffer) let time switch (id) { case defs.CLOCKID_REALTIME: { const ms = Date.now() time = BigInt(ms) * 1_000_000n break } case defs.CLOCKID_MONOTONIC: { const ns = BigInt(Math.round(performance.now() * 1_000_000)) time = ns break } default: return defs.ERRNO_INVAL } view.setBigUint64(timePtr, time, true) return defs.ERRNO_SUCCESS } fd_close(fd) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF this.fds.delete(fd) return defs.ERRNO_SUCCESS } // TODO: BIGINT fd_seek(fd, offset, whence, newOffsetPtr) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (fileDesc.type === 'stdio') return defs.ERRNO_SPIPE var stats = null let newPosition = 0 let noffset = Number(offset) try { stats = this.fs.statSync(fileDesc.handle.path) } catch (e) { return defs.ERRNO_IO } switch (whence) { case defs.WHENCE_SET: newPosition = noffset break case defs.WHENCE_CUR: newPosition = Number(fileDesc.handle.position) + noffset break case defs.WHENCE_END: newPosition = Number(stats.size) + noffset break default: console.error('fd_seek invalid mode', whence) return defs.ERRNO_INVAL } // Update position fileDesc.handle.position = newPosition const view = new DataView(this.wasm.memory.buffer) view.setBigUint64(newOffsetPtr, BigInt(newPosition), true) return defs.ERRNO_SUCCESS } fd_write(fd, iovs, iovsLen, nwrittenPtr) { let written = 0 const chunks = [] const view = new DataView(this.wasm.memory.buffer) const mem = new Uint8Array(this.wasm.memory.buffer) // Gather all the chunks from the vectors for (let i = 0; i < iovsLen; i++) { const ptr = iovs + i * 8 const buf = view.getUint32(ptr, true) const bufLen = view.getUint32(ptr + 4, true) chunks.push(new Uint8Array(mem.buffer, buf, bufLen)) written += bufLen } // Concatenate chunks if needed let buffer if (chunks.length === 1) { buffer = chunks[0] } else { buffer = new Uint8Array(written) let offset = 0 for (const chunk of chunks) { buffer.set(chunk, offset) offset += chunk.length } } // Handle standard streams if (fd === 1) { this.stdout(buffer) } else if (fd === 2) { this.stderr(buffer) } else { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF fileDesc.handle.position += written try { // Write using ZenFS path-based API this.fs.writeFileSync(fileDesc.handle.path, buffer) } catch (e) { //console.error("fs.writeFileSync failed:", fileDesc.handle.path) return defs.ERRNO_IO } } //console.log("fd_write end", written) view.setUint32(nwrittenPtr, written, true) return defs.ERRNO_SUCCESS } fd_read(fd, iovs, iovsLen, nreadPtr) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF let totalRead = 0 const view = new DataView(this.wasm.memory.buffer) const mem = new Uint8Array(this.wasm.memory.buffer) try { let content if (fd === 0) { content = this.stdin() } else { content = this.fs.readFileSync(fileDesc.handle.path) } for (let i = 0; i < iovsLen; i++) { const ptr = iovs + i * 8 const buf = view.getUint32(ptr, true) const bufLen = view.getUint32(ptr + 4, true) const start = fileDesc.handle.position const end = Math.min(start + bufLen, content.length) const bytesToRead = end - start if (bytesToRead <= 0) break mem.set(new Uint8Array(content.slice(start, end)), buf) totalRead += bytesToRead fileDesc.handle.position += bytesToRead if (bytesToRead < bufLen) break } view.setUint32(nreadPtr, totalRead, true) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } path_open( dirfd, dirflags, path, pathLen, oflags, fsRightsBase, fsRightsInheriting, fdflags, fdPtr, ) { var fileDesc = this.fds.get(dirfd) if (!fileDesc) return defs.ERRNO_BADF const mem = new Uint8Array(this.wasm.memory.buffer) const pathBuffer = mem.slice(path, path + pathLen) const pathString = this.textDecoder.decode(pathBuffer) let resolvedPath = pathString var fd = 0 const view = new DataView(this.wasm.memory.buffer) // Resolve path relative to the directory fd if (fileDesc.preopenPath) { if (pathString.startsWith('/')) { resolvedPath = pathString.slice(1) } resolvedPath = fileDesc.preopenPath + (fileDesc.preopenPath.endsWith('/') ? '' : '/') + resolvedPath } var exists = false var stats = null const o_create = (oflags & defs.OFLAGS_CREAT) == defs.OFLAGS_CREAT const o_directory = (oflags & defs.OFLAGS_DIRECTORY) == defs.OFLAGS_DIRECTORY const o_exclusive = (oflags & defs.OFLAGS_EXCL) == defs.OFLAGS_EXCL const o_truncate = (oflags & defs.OFLAGS_TRUNC) == defs.OFLAGS_TRUNC try { // Verify file exists stats = this.fs.statSync(resolvedPath) exists = true } catch (e) {} if (o_exclusive || o_truncate) { if (o_exclusive && exists) { // null view.setUint32(fdPtr, fd, true) return defs.ERRNO_EXIST } } // Store path and initial position in handle TODO: could be BIGINT // fd = this.allocateFd({ path: resolvedPath, position: 0 }, 'file') const fileHandle = { path: resolvedPath, position: 0 } const type = 'file' fd = this.nextFd++ const descriptor = { type, handle: fileHandle, fd } this.fds.set(fd, descriptor) fileDesc = this.fds.get(fd) // TODO: could be BIGINT fileDesc.handle.position = 0 if (o_truncate) { // TODO: could be BIGINT fileDesc.handle.size = 0 } // console.log(`path_open[${fd}] : ${resolvedPath} o_directory=${o_directory} exists=${exists} o_exclusive=${o_exclusive} o_create=${o_create} o_truncate=${o_truncate}`) // if (stats) // console.log(`path_open[${fd}] : ${fileDesc.handle.position} / ${stats.size}`) // o_directory - ERRNO_NOTDIR // ERRNO_NOENT view.setUint32(fdPtr, fd, true) return defs.ERRNO_SUCCESS } proc_exit(code) { throw new WASIProcExit(code) } fd_fdstat_get(fd, statPtr) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF const view = new DataView(this.wasm.memory.buffer) // filetype - u8 let filetype switch (fileDesc.type) { case 'stdio': filetype = defs.FILETYPE_CHARACTER_DEVICE break case 'directory': filetype = defs.FILETYPE_DIRECTORY break case 'file': filetype = defs.FILETYPE_REGULAR_FILE break default: filetype = defs.FILETYPE_UNKNOWN } view.setUint8(statPtr, filetype) // fdflags - u16 // For now, we'll assume basic flags let fdflags = 0 if (fileDesc.append) fdflags |= defs.FDFLAGS_APPEND view.setUint16(statPtr + 2, fdflags, true) // fs_rights_base - u64 // Set basic rights depending on file type let fsRightsBase = 0n if (fileDesc.type === 'file') { fsRightsBase = defs.RIGHTS_FD_READ | defs.RIGHTS_FD_WRITE | defs.RIGHTS_FD_SEEK | defs.RIGHTS_FD_TELL | defs.RIGHTS_FD_FILESTAT_GET } else if (fileDesc.type === 'directory') { fsRightsBase = defs.RIGHTS_PATH_OPEN | defs.RIGHTS_FD_READDIR | defs.RIGHTS_PATH_CREATE_DIRECTORY | defs.RIGHTS_PATH_UNLINK_FILE | defs.RIGHTS_PATH_REMOVE_DIRECTORY } const bf = BigInt(fsRightsBase) view.setBigUint64(statPtr + 8, bf, true) // fs_rights_inheriting - u64 // Child files/directories inherit the same rights view.setBigUint64(statPtr + 16, bf, true) return defs.ERRNO_SUCCESS } fd_fdstat_set_flags(fd, flags) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF // Check if flags are valid const validFlags = defs.FDFLAGS_APPEND | defs.FDFLAGS_DSYNC | defs.FDFLAGS_NONBLOCK | defs.FDFLAGS_RSYNC | defs.FDFLAGS_SYNC if (flags & ~validFlags) { return defs.ERRNO_INVAL // Invalid flags specified } // For stdio handles, we can't set flags if (fileDesc.type === 'stdio') { return defs.ERRNO_NOTSUP } try { // Update internal file descriptor state fileDesc.append = Boolean(flags & defs.FDFLAGS_APPEND) // Try to apply flags to the underlying file system // Note: Many flags might not be supported by the underlying fs if (fileDesc.handle && typeof this.fs.setFlagsSync === 'function') { this.fs.setFlagsSync(fileDesc.handle, flags) } return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } fd_prestat_get(fd, prestatPtr) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF // Only directory file descriptors have prestats if (fileDesc.type !== 'directory') { return defs.ERRNO_BADF } // Ensure we have a preopened path for this fd if (!fileDesc.preopenPath) { return defs.ERRNO_BADF } const view = new DataView(this.wasm.memory.buffer) // Write prestat struct: // struct prestat { // u8 type; // offset 0 // u64 length; // offset 8 (with padding) // } // Set type to PREOPENTYPE_DIR (0) view.setUint8(prestatPtr, defs.PREOPENTYPE_DIR) // Get the length of the preopened directory path const pathLength = fileDesc.preopenPath.length view.setUint32(prestatPtr + 4, pathLength, true) return defs.ERRNO_SUCCESS } fd_prestat_dir_name(fd, pathPtr, pathLen) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF // Only directory file descriptors have prestats if (fileDesc.type !== 'directory') { return defs.ERRNO_BADF } // Ensure we have a preopened path for this fd if (!fileDesc.preopenPath) { return defs.ERRNO_BADF } // Check if the provided buffer is large enough if (pathLen < fileDesc.preopenPath.length) { return defs.ERRNO_NAMETOOLONG } // Write the path to memory const mem = new Uint8Array(this.wasm.memory.buffer) const pathBytes = this.textEncoder.encode(fileDesc.preopenPath) mem.set(pathBytes, pathPtr) return defs.ERRNO_SUCCESS } path_filestat_get(fd, flags, pathPtr, pathLen, filestatPtr) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF // Read the path from memory const mem = new Uint8Array(this.wasm.memory.buffer) const pathBytes = new Uint8Array(mem.buffer, pathPtr, pathLen) const pathString = this.textDecoder.decode(pathBytes) try { // Resolve path relative to the directory fd let resolvedPath = pathString if (fileDesc.preopenPath) { // If path starts with '/', make it relative to preopenPath if (pathString.startsWith('/')) { resolvedPath = pathString.slice(1) // Remove leading '/' } // Combine preopenPath with the relative path resolvedPath = fileDesc.preopenPath + (fileDesc.preopenPath.endsWith('/') ? '' : '/') + resolvedPath } // Get stats from filesystem const stats = this.fs.statSync(resolvedPath, { followSymlinks: (flags & defs.LOOKUPFLAGS_SYMLINK_FOLLOW) !== 0, }) const view = new DataView(this.wasm.memory.buffer) // Write filestat struct: // struct filestat { // dev: u64, // Device ID // ino: u64, // Inode number // filetype: u8, // File type // nlink: u64, // Number of hard links // size: u64, // File size // atim: u64, // Access time // mtim: u64, // Modification time // ctim: u64 // Change time // } // Device ID view.setBigUint64(filestatPtr, BigInt(stats.dev || 0), true) // Inode view.setBigUint64(filestatPtr + 8, BigInt(stats.ino || 0), true) // Filetype let filetype = defs.FILETYPE_UNKNOWN if (stats.isFile()) filetype = defs.FILETYPE_REGULAR_FILE else if (stats.isDirectory()) filetype = defs.FILETYPE_DIRECTORY else if (stats.isSymbolicLink()) filetype = defs.FILETYPE_SYMBOLIC_LINK else if (stats.isCharacterDevice()) filetype = defs.FILETYPE_CHARACTER_DEVICE else if (stats.isBlockDevice()) filetype = defs.FILETYPE_BLOCK_DEVICE else if (stats.isFIFO()) filetype = defs.FILETYPE_SOCKET_STREAM view.setUint8(filestatPtr + 16, filetype) // Number of hard links view.setBigUint64(filestatPtr + 24, BigInt(stats.nlink || 1), true) // File size view.setBigUint64(filestatPtr + 32, BigInt(stats.size || 0), true) // Access time (in nanoseconds) view.setBigUint64( filestatPtr + 40, BigInt(stats.atimeMs * 1_000_000), true, ) // Modification time (in nanoseconds) view.setBigUint64( filestatPtr + 48, BigInt(stats.mtimeMs * 1_000_000), true, ) // Change time (in nanoseconds) view.setBigUint64( filestatPtr + 56, BigInt(stats.ctimeMs * 1_000_000), true, ) return defs.ERRNO_SUCCESS } catch (e) { if (e.code === 'ENOENT') return defs.ERRNO_NOENT if (e.code === 'EACCES') return defs.ERRNO_ACCES return defs.ERRNO_IO } } // File/Directory Operations fd_advise(fd, offset, len, advice) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (fileDesc.type !== 'file') return defs.ERRNO_BADF // Most filesystems don't actually implement advisory hints, // so we'll just return success return defs.ERRNO_SUCCESS } fd_allocate(fd, offset, len) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (fileDesc.type !== 'file') return defs.ERRNO_BADF try { // Attempt to extend the file to the specified size const stats = this.fs.statSync(fileDesc.handle.path) const newSize = Number(offset) + Number(len) if (newSize > stats.size) { // Create a buffer of zeros to extend the file const zeros = new Uint8Array(newSize - stats.size) this.fs.appendFileSync(fileDesc.handle.path, zeros) } return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } fd_datasync(fd) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (fileDesc.type !== 'file') return defs.ERRNO_BADF try { // Most JavaScript filesystem implementations handle syncing automatically // If your fs implementation has a specific sync method, call it here if (typeof this.fs.fsyncSync === 'function') { this.fs.fsyncSync(fileDesc.handle.path) } return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } fd_filestat_get(fd, ptr) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (!fileDesc.handle) return defs.ERRNO_BADF const mem = new DataView(this.wasm.memory.buffer) const stats = this.fs.statSync(fileDesc.handle.path) mem.setBigUint64(ptr, BigInt(stats.dev), true) mem.setBigUint64(ptr + 8, BigInt(stats.ino), true) mem.setUint8(ptr + 16, stats.filetype) mem.setBigUint64(ptr + 24, BigInt(stats.nlink), true) mem.setBigUint64(ptr + 32, BigInt(stats.size), true) mem.setBigUint64(ptr + 38, BigInt(stats.atime), true) mem.setBigUint64(ptr + 46, BigInt(stats.mtime), true) mem.setBigUint64(ptr + 52, BigInt(stats.ctime), true) return defs.ERRNO_SUCCESS } fd_filestat_set_size(fd, size) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (fileDesc.type !== 'file') return defs.ERRNO_BADF try { this.fs.truncateSync(fileDesc.handle.path, Number(size)) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } fd_filestat_set_times(fd, atim, mtim, fst_flags) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (fileDesc.type !== 'file') return defs.ERRNO_BADF try { const times = { atime: Number(atim) / 1_000_000_000, mtime: Number(mtim) / 1_000_000_000, } this.fs.utimesSync(fileDesc.handle.path, times.atime, times.mtime) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } fd_pread(fd, iovs, iovsLen, offset, nreadPtr) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (fileDesc.type !== 'file') return defs.ERRNO_BADF try { const content = this.fs.readFileSync(fileDesc.handle.path) let totalRead = 0 const view = new DataView(this.wasm.memory.buffer) const mem = new Uint8Array(this.wasm.memory.buffer) const position = Number(offset) for (let i = 0; i < iovsLen; i++) { const ptr = iovs + i * 8 const buf = view.getUint32(ptr, true) const bufLen = view.getUint32(ptr + 4, true) const start = position + totalRead const end = Math.min(start + bufLen, content.length) const bytesToRead = end - start if (bytesToRead <= 0) break mem.set(new Uint8Array(content.slice(start, end)), buf) totalRead += bytesToRead if (bytesToRead < bufLen) break } view.setUint32(nreadPtr, totalRead, true) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } fd_pwrite(fd, iovs, iovsLen, offset, nwrittenPtr) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (fileDesc.type !== 'file') return defs.ERRNO_BADF try { let written = 0 const chunks = [] const view = new DataView(this.wasm.memory.buffer) const mem = new Uint8Array(this.wasm.memory.buffer) for (let i = 0; i < iovsLen; i++) { const ptr = iovs + i * 8 const buf = view.getUint32(ptr, true) const bufLen = view.getUint32(ptr + 4, true) chunks.push(new Uint8Array(mem.buffer, buf, bufLen)) written += bufLen } let buffer if (chunks.length === 1) { buffer = chunks[0] } else { buffer = new Uint8Array(written) let offset = 0 for (const chunk of chunks) { buffer.set(chunk, offset) offset += chunk.length } } // Read existing file content const content = this.fs.readFileSync(fileDesc.handle.path) const newContent = new Uint8Array( Math.max(Number(offset) + buffer.length, content.length), ) // Copy existing content newContent.set(content) // Write new data at specified offset newContent.set(buffer, Number(offset)) // Write back to file this.fs.writeFileSync(fileDesc.handle.path, newContent) view.setUint32(nwrittenPtr, written, true) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } fd_readdir(fd, buf, bufLen, cookie, bufusedPtr) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (fileDesc.type !== 'directory') return defs.ERRNO_NOTDIR try { const entries = this.fs.readdirSync(fileDesc.handle.path, { withFileTypes: true, }) const view = new DataView(this.wasm.memory.buffer) const mem = new Uint8Array(this.wasm.memory.buffer) let offset = 0 let entriesWritten = 0 // Skip entries according to cookie const startIndex = Number(cookie) for (let i = startIndex; i < entries.length; i++) { const entry = entries[i] const name = entry.name const nameBytes = this.textEncoder.encode(name) // dirent structure size: 24 bytes + name length const direntSize = 24 + nameBytes.length if (offset + direntSize > bufLen) { break } // Write dirent structure view.setBigUint64(buf + offset, BigInt(i + 1), true) // d_next view.setBigUint64(buf + offset + 8, 0n, true) // d_ino view.setUint32(buf + offset + 16, nameBytes.length, true) // d_namlen // d_type let filetype = defs.FILETYPE_UNKNOWN if (entry.isFile()) filetype = defs.FILETYPE_REGULAR_FILE else if (entry.isDirectory()) filetype = defs.FILETYPE_DIRECTORY view.setUint8(buf + offset + 20, filetype) // Write name mem.set(nameBytes, buf + offset + 24) offset += direntSize entriesWritten++ } view.setUint32(bufusedPtr, offset, true) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } fd_renumber(from, to) { const fromDesc = this.fds.get(from) if (!fromDesc) return defs.ERRNO_BADF // Close existing 'to' fd if it exists this.fds.delete(to) // Move the fd this.fds.set(to, fromDesc) this.fds.delete(from) return defs.ERRNO_SUCCESS } fd_sync(fd) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (fileDesc.type !== 'file') return defs.ERRNO_BADF try { // Similar to fd_datasync, but might include metadata if (typeof this.fs.fsyncSync === 'function') { this.fs.fsyncSync(fileDesc.handle.path) } return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } fd_tell(fd, offsetPtr) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF if (fileDesc.type !== 'file') return defs.ERRNO_BADF const view = new DataView(this.wasm.memory.buffer) view.setBigUint64(offsetPtr, BigInt(fileDesc.handle.position), true) return defs.ERRNO_SUCCESS } // Path Operations path_create_directory(fd, path, pathLen) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF const pathString = this.textDecoder.decode( new Uint8Array(this.wasm.memory.buffer, path, pathLen), ) try { let resolvedPath = pathString if (fileDesc.preopenPath) { if (pathString.startsWith('/')) { resolvedPath = pathString.slice(1) } resolvedPath = fileDesc.preopenPath + '/' + resolvedPath } this.fs.mkdirSync(resolvedPath) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } path_filestat_set_times(fd, flags, path, pathLen, atim, mtim, fst_flags) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF const pathString = this.textDecoder.decode( new Uint8Array(this.wasm.memory.buffer, path, pathLen), ) try { let resolvedPath = pathString if (fileDesc.preopenPath) { if (pathString.startsWith('/')) { resolvedPath = pathString.slice(1) } resolvedPath = fileDesc.preopenPath + '/' + resolvedPath } const times = { atime: Number(atim) / 1_000_000_000, mtime: Number(mtim) / 1_000_000_000, } this.fs.utimesSync(resolvedPath, times.atime, times.mtime) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } path_link( old_fd, old_flags, old_path, old_path_len, new_fd, new_path, new_path_len, ) { const oldFileDesc = this.fds.get(old_fd) const newFileDesc = this.fds.get(new_fd) if (!oldFileDesc || !newFileDesc) return defs.ERRNO_BADF const oldPathString = this.textDecoder.decode( new Uint8Array(this.wasm.memory.buffer, old_path, old_path_len), ) const newPathString = this.textDecoder.decode( new Uint8Array(this.wasm.memory.buffer, new_path, new_path_len), ) try { let resolvedOldPath = oldPathString let resolvedNewPath = newPathString if (oldFileDesc.preopenPath) { if (oldPathString.startsWith('/')) { resolvedOldPath = oldPathString.slice(1) } resolvedOldPath = oldFileDesc.preopenPath + '/' + resolvedOldPath } if (newFileDesc.preopenPath) { if (newPathString.startsWith('/')) { resolvedNewPath = newPathString.slice(1) } resolvedNewPath = newFileDesc.preopenPath + '/' + resolvedNewPath } this.fs.linkSync(resolvedOldPath, resolvedNewPath) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } path_readlink(fd, path, path_len, buf, buf_len, bufused) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF const pathString = this.textDecoder.decode( new Uint8Array(this.wasm.memory.buffer, path, path_len), ) try { let resolvedPath = pathString if (fileDesc.preopenPath) { if (pathString.startsWith('/')) { resolvedPath = pathString.slice(1) } resolvedPath = fileDesc.preopenPath + '/' + resolvedPath } const linkString = this.fs.readlinkSync(resolvedPath) const linkBytes = this.textEncoder.encode(linkString) if (linkBytes.length > buf_len) { return defs.ERRNO_OVERFLOW } const mem = new Uint8Array(this.wasm.memory.buffer) mem.set(linkBytes, buf) const view = new DataView(this.wasm.memory.buffer) view.setUint32(bufused, linkBytes.length, true) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } path_remove_directory(fd, path, path_len) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF const pathString = this.textDecoder.decode( new Uint8Array(this.wasm.memory.buffer, path, path_len), ) try { let resolvedPath = pathString if (fileDesc.preopenPath) { if (pathString.startsWith('/')) { resolvedPath = pathString.slice(1) } resolvedPath = fileDesc.preopenPath + '/' + resolvedPath } this.fs.rmdirSync(resolvedPath) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } path_rename(old_fd, old_path, old_path_len, new_fd, new_path, new_path_len) { const oldFileDesc = this.fds.get(old_fd) const newFileDesc = this.fds.get(new_fd) if (!oldFileDesc || !newFileDesc) return defs.ERRNO_BADF const oldPathString = this.textDecoder.decode( new Uint8Array(this.wasm.memory.buffer, old_path, old_path_len), ) const newPathString = this.textDecoder.decode( new Uint8Array(this.wasm.memory.buffer, new_path, new_path_len), ) try { let resolvedOldPath = oldPathString let resolvedNewPath = newPathString if (oldFileDesc.preopenPath) { if (oldPathString.startsWith('/')) { resolvedOldPath = oldPathString.slice(1) } resolvedOldPath = oldFileDesc.preopenPath + '/' + resolvedOldPath } if (newFileDesc.preopenPath) { if (newPathString.startsWith('/')) { resolvedNewPath = newPathString.slice(1) } resolvedNewPath = newFileDesc.preopenPath + '/' + resolvedNewPath } this.fs.renameSync(resolvedOldPath, resolvedNewPath) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } path_symlink(old_path, old_path_len, fd, new_path, new_path_len) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF const oldPathString = this.textDecoder.decode( new Uint8Array(this.wasm.memory.buffer, old_path, old_path_len), ) const newPathString = this.textDecoder.decode( new Uint8Array(this.wasm.memory.buffer, new_path, new_path_len), ) try { let resolvedNewPath = newPathString if (fileDesc.preopenPath) { if (newPathString.startsWith('/')) { resolvedNewPath = newPathString.slice(1) } resolvedNewPath = fileDesc.preopenPath + '/' + resolvedNewPath } this.fs.symlinkSync(oldPathString, resolvedNewPath) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } path_unlink_file(fd, path, path_len) { const fileDesc = this.fds.get(fd) if (!fileDesc) return defs.ERRNO_BADF const pathString = this.textDecoder.decode( new Uint8Array(this.wasm.memory.buffer, path, path_len), ) try { let resolvedPath = pathString if (fileDesc.preopenPath) { if (pathString.startsWith('/')) { resolvedPath = pathString.slice(1) } resolvedPath = fileDesc.preopenPath + '/' + resolvedPath } this.fs.unlinkSync(resolvedPath) return defs.ERRNO_SUCCESS } catch (e) { return defs.ERRNO_IO } } // Poll Operations poll_oneoff(in_, out, nsubscriptions, nevents) { // Basic implementation that just processes all subscriptions immediately const view = new DataView(this.wasm.memory.buffer) let numEvents = 0 for (let i = 0; i < nsubscriptions; i++) { const subPtr = in_ + i * 48 // size of subscription struct const userdata = view.getBigUint64(subPtr, true) const type = view.getUint8(subPtr + 8) // Write event const eventPtr = out + numEvents * 32 // size of event struct view.setBigUint64(eventPtr, userdata, true) view.setUint8(eventPtr + 8, type) view.setUint8(eventPtr + 9, defs.EVENTRWFLAGS_FD_READWRITE_HANGUP) view.setUint16(eventPtr + 10, 0, true) // error numEvents++ } view.setUint32(nevents, numEvents, true) return defs.ERRNO_SUCCESS } // Random Number Generation random_get(buf, buf_len) { const bytes = new Uint8Array(this.wasm.memory.buffer, buf, buf_len) crypto.getRandomValues(bytes) return defs.ERRNO_SUCCESS } // Scheduling Operations sched_yield() { os.sched_yield() return defs.ERRNO_SUCCESS } // STUB sock_accept(fd, flags) { return defs.ERRNO_NOSYS } sock_recv(fd, riData, riFlags) { return defs.ERRNO_NOSYS } sock_send(fd, siData, riFlags) { return defs.ERRNO_NOSYS } sock_shutdown(fd, how) { return defs.ERRNO_NOSYS } } export default WasiPreview1