UNPKG

ext2fs

Version:

WASM bindings to libext2fs for cross-platform ext filesystem handling

1,200 lines (1,041 loc) 29.7 kB
// Maintainers, keep in mind that ES1-style octal literals (`0666`) are not // allowed in strict mode. Use ES6-style octal literals instead (`0o666`). 'use strict'; const Buffer = require('buffer').Buffer; const Stream = require('stream').Stream; const EventEmitter = require('events'); const path = require('path'); const { CODE_TO_ERRNO } = require('./wasi'); const { ErrnoException, usePath, useBuffer, usePaths, withHooks, useObject, } = require('./util'); const { callbackify } = require('util'); const assert = require('assert'); const binding = require('./binding'); const Readable = Stream.Readable; const Writable = Stream.Writable; const kMinPoolSpace = 128; const kMaxLength = require('buffer').kMaxLength; const kFd = Symbol("fd"); // Same constants for all platforms: const constants = Object.freeze({ O_RDONLY: 0, O_WRONLY: 1, O_RDWR: 2, S_IFMT: 61440, S_IFREG: 32768, S_IFDIR: 16384, S_IFCHR: 8192, S_IFBLK: 24576, S_IFIFO: 4096, S_IFLNK: 40960, S_IFSOCK: 49152, O_CREAT: 64, O_EXCL: 128, O_NOCTTY: 256, O_TRUNC: 512, O_APPEND: 1024, O_DIRECTORY: 65536, O_NOATIME: 262144, O_NOFOLLOW: 131072, O_SYNC: 1052672, O_DIRECT: 16384, O_NONBLOCK: 2048, S_IRWXU: 448, S_IRUSR: 256, S_IWUSR: 128, S_IXUSR: 64, S_IRWXG: 56, S_IRGRP: 32, S_IWGRP: 16, S_IXGRP: 8, S_IRWXO: 7, S_IROTH: 4, S_IWOTH: 2, S_IXOTH: 1, F_OK: 0, R_OK: 4, W_OK: 2, X_OK: 1 }); function assertEncoding(encoding) { if (encoding && !Buffer.isEncoding(encoding)) { throw new Error(`Unknown encoding: ${encoding}`); } } function stringToFlags(flag) { if (typeof flag === 'number') { return flag; } switch (flag) { case 'r' : return constants.O_RDONLY; case 'rs' : // Fall through. case 'sr' : return constants.O_RDONLY | constants.O_SYNC; case 'r+' : return constants.O_RDWR; case 'rs+' : // Fall through. case 'sr+' : return constants.O_RDWR | constants.O_SYNC; case 'w' : return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY; case 'wx' : // Fall through. case 'xw' : return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL; case 'w+' : return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR; case 'wx+': // Fall through. case 'xw+': return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL; case 'a' : return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY; case 'ax' : // Fall through. case 'xa' : return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL; case 'a+' : return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR; case 'ax+': // Fall through. case 'xa+': return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL; } throw new Error('Unknown file open flag: ' + flag); } function getOptions(options, defaultOptions) { if (options == null) { return defaultOptions; } if (typeof options === 'string') { if (defaultOptions === undefined) { defaultOptions = {} } options = { ...defaultOptions, encoding: options, }; } else if (typeof options !== 'object') { throw new TypeError('"options" must be a string or an object, got ' + typeof options + ' instead.'); } if (options.encoding !== 'buffer') assertEncoding(options.encoding); return { ...options, ...defaultOptions, }; } function pathCheck(path) { if (('' + path).indexOf('\u0000') !== -1) { const er = new Error('Path must be a string without null bytes'); er.code = 'ENOENT'; throw er; } } function isFd(path) { return (path >>> 0) === path; } class Stats { constructor( dev, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks, atim_msec, mtim_msec, ctim_msec, birthtim_msec) { this.dev = dev; this.mode = mode; this.nlink = nlink; this.uid = uid; this.gid = gid; this.rdev = rdev; this.blksize = blksize; this.ino = ino; this.size = size; this.blocks = blocks; this.atime = new Date(atim_msec); this.mtime = new Date(mtim_msec); this.ctime = new Date(ctim_msec); this.birthtime = new Date(birthtim_msec); } _checkModeProperty(property) { return ((this.mode & constants.S_IFMT) === property); }; isDirectory() { return this._checkModeProperty(constants.S_IFDIR); } isFile() { return this._checkModeProperty(constants.S_IFREG); }; isBlockDevice() { return this._checkModeProperty(constants.S_IFBLK); }; isCharacterDevice() { return this._checkModeProperty(constants.S_IFCHR); }; isSymbolicLink() { return this._checkModeProperty(constants.S_IFLNK); }; isFIFO() { return this._checkModeProperty(constants.S_IFIFO); }; isSocket() { return this._checkModeProperty(constants.S_IFSOCK); }; }; function modeNum(m, def) { if (typeof m === 'number') return m; if (typeof m === 'string') return parseInt(m, 8); if (def) return modeNum(def); return undefined; } // converts Date or number to a fractional UNIX timestamp function toUnixTimestamp(time) { if (typeof time === 'string' && +time == time) { return +time; } if (typeof time === 'number') { if (!Number.isFinite(time) || time < 0) { return Date.now() / 1000; } return time; } if (util.isDate(time)) { // convert to 123.456 UNIX timestamp return time.getTime() / 1000; } throw new Error('Cannot parse time: ' + time); } class UnimplementedError extends Error { constructor(method) { super(`\`${method}\` not yet implemented.`); } } module.exports = fsPointer => { const { X_OK = 0, O_RDONLY, O_NOFOLLOW, S_IXUSR, S_IXGRP, S_IXOTH, } = constants; // TODO(zwhitchcox): Should keep track of position in file here const openFiles = new Map(); async function closeAllFileDescriptors() { for (const fd of openFiles.keys()) { await close(fd); } }; function checkFd(fd, syscall, args) { if (!openFiles.has(fd)) { throw new ErrnoException(CODE_TO_ERRNO['EBADF'], syscall, args); } } const DEBUG = process.env.NODE_DEBUG && /fs/.test(process.env.NODE_DEBUG); const EXECUTE_ABILITY = (S_IXUSR | S_IXGRP | S_IXOTH); const access = async (path, mode) => { // we know we can read/write being that we control the file // so we just need to check that the file exists and maybe execution ability const stats = await stat(path); if (((mode & X_OK) !== 0) && ((stats.mode & EXECUTE_ABILITY) === 0)) { throw new ErrnoException(CODE_TO_ERRNO['EACCES'], 'access', [path, mode]); } }; const open = withHooks(async (path, flags, mode) => { mode = modeNum(mode, 0o666); flags = stringToFlags(flags); path = await usePath(path); const fd = await binding.open(fsPointer, path, flags, mode); openFiles.set(fd, flags); return fd; }); async function close(fd) { checkFd(fd, 'close', [fd]); await binding.close(fd); openFiles.delete(fd); } const read = withHooks(async (fd, buffer, offset, length, position) => { position = (typeof position !== 'number') ? -1 : position; if (length === 0) { return { bytesRead: 0, buffer, } } checkFd(fd, 'read', [fd, buffer, offset, length, position]); const [_readBuffer, readPointer] = await useBuffer(length); const bytesRead = await binding.read(fd, openFiles.get(fd), readPointer, length, position); _readBuffer.copy(buffer, offset); return { bytesRead, buffer, } }); const readv = withHooks(async (fd, buffers, position) => { if (typeof position !== 'number') position = -1; let bytesRead = 0; for (const buffer of buffers) { const { bytesRead:numRead } = await read(fd, buffer, position); bytesRead += numRead; } return { bytesRead, buffers: Buffer.concat(buffers, bytesRead), }; }); function readCb(fd, buffer, offset, length, position, cb) { // follow node fs api and don't return promises ;(async () => { try { const { bytesRead, buffer:outBuffer } = await read(fd, buffer, offset, length, position); cb(null, bytesRead, outBuffer); } catch (err) { cb(err); } })() } const write = (fd, stringOrBuffer, offsetOrOptions, length, position) => { // fs.write(fd, buffer[, offset[, length[, position]]]); if (stringOrBuffer instanceof Buffer) { const buffer = stringOrBuffer; let offset; if (typeof offset === 'object') { ({ offset = 0, length = buffer.byteLength - offset, position = null, } = offsetOrOptions); } if (typeof offsetOrOptions !== 'number') offset = 0; if (typeof length !== 'number') length = buffer.byteLength - offset; return writeBuffer(fd, buffer, offset, length, position); } if (typeof stringOrBuffer !== "string") { throw new Error(`Argument must be buffer or string. Got ${typeof buffer}`); } // fs.write(fd, string[, position[, encoding]]); const buffer = Buffer.from(String(stringOrBuffer)); const encoding = length !== undefined ? length : 'utf8'; position = offsetOrOptions; return writeBuffer(fd, buffer, 0, buffer.byteLength, position) .then(({bytesWritten, buffer}) => { return { bytesWritten, buffer: buffer.toString(encoding) }; }); }; function writeCb(fd, buffer, offset, length, position, cb) { cb = cb !== undefined ? cb : arguments[arguments.length - 1]; // follow node fs api and don't return promises ;(async () => { try { const { bytesWritten, buffer:outBuffer } = await write(fd, buffer, offset, length, position); cb(null, bytesWritten, outBuffer); } catch (err) { cb(err); } })(); } const writeBuffer = withHooks(async (fd, buffer, offset, length, position) => { position = (typeof position !== 'number') ? -1 : position; checkFd(fd, 'node_ext2fs_write', [fd, buffer, offset, length, position]); const [writeBuffer, writePointer] = await useBuffer(length); if (typeof offset !== 'number') { offset = 0; } if (typeof length !== 'number') { length = buffer.length - offset; } if (typeof position !== 'number') { position = null; } buffer.copy(writeBuffer, 0, offset, offset + length); const bytesWritten = await binding.write(fd, openFiles.get(fd), writePointer, length, position); return { bytesWritten, buffer, } }); const writev = async (fd, buffers, position) => { if (typeof position !== 'number') position = -1; let bytesWritten = 0; for (const buffer of buffers) { const { bytesWritten:written } = await writeBuffer(fd, buffer, 0, buffer.byteLength, position); bytesWritten += written; position > -1 && (position += bytesWritten); } return { bytesWritten, buffers }; } async function truncate(path, len) { // if (typeof path === 'number') { // open // } // if (typeof len === 'function') { // callback = len; // len = 0; // } else if (len === undefined) { // len = 0; // } // callback = maybeCallback(callback); // fs.open(path, 'r+', function(er, fd) { // if (er) return callback(er); // var req = new FSReqWrap(); // req.oncomplete = function oncomplete(er) { // fs.close(fd, function(er2) { // callback(er || er2); // }); // }; // binding.ftruncate(fd, len, req); // }); throw new UnimplementedError('truncate'); }; async function ftruncate(fd, len, callback) { // if (typeof len === 'function') { // callback = len; // len = 0; // } else if (len === undefined) { // len = 0; // } // var req = new FSReqWrap(); // req.oncomplete = makeCallback(callback); // binding.ftruncate(fd, len, req); throw new UnimplementedError('ftruncate'); }; const rmdir = withHooks(async path => { path = await usePath(path); return await binding.rmdir(fsPointer, path); }); const unlink = withHooks(async path => { path = await usePath(path); await binding.unlink(fsPointer, path); }); async function fdatasync(fd, callback) { throw new UnimplementedError("fdatasync");; }; async function fsync(fd, callback) { throw new UnimplementedError("fsync");; }; const mkdir = withHooks(async (path, mode) => { mode = modeNum(mode, 0o777); path = await usePath(path); await binding.mkdir(fsPointer, path, mode); }); const mkdtemp = withHooks(async (prefix, options) => { // options = getOptions(options, {}); // if (!prefix || typeof prefix !== 'string') // throw new TypeError('filename prefix is required'); // pathCheck(prefix); // prefix += 'XXXXXX'; throw new UnimplementedError('mkdtemp');; }); const readdir = withHooks(async (path, options) => { options = getOptions(options, {encoding: 'utf8'}); path = await usePath(path); const [entries, entriesId] = await useObject([]); await binding.readdir(fsPointer, path, entriesId); if (options.encoding === 'buffer') { return entries; } return entries.map((b) => b.toString(options.encoding)); }); const fstat = withHooks(async (fd) => { checkFd(fd, 'fstat', [fd]); const ctime = (await binding.stat_i_ctime(fd)) * 1000; // TODO(zwhitchcox) get whole structure return new Stats(...(await Promise.all([ 0, // dev binding.stat_i_mode(fd), binding.stat_i_links_count(fd), binding.stat_i_uid(fd), binding.stat_i_gid(fd), 0, // rdev binding.stat_blocksize(fd), binding.stat_ino(fd), binding.stat_i_size(fd), binding.stat_i_blocks(fd), binding.stat_i_atime(fd).then(x => x * 1000), binding.stat_i_mtime(fd).then(x => x * 1000), ctime, ctime, ]))); }); async function lstat(path) { const fd = await open(path, O_RDONLY | O_NOFOLLOW); const stats = await fstat(fd); await close(fd); return stats; }; async function stat(path) { const fd = await open(path, 'r', 0); const stats = await fstat(fd); await close(fd); return stats; } const readlink = withHooks(async (path, options) => { options = getOptions(options, {encoding: 'utf8'}); path = await usePath(path); const [array, arrayId] = await useObject([]); await binding.readlink(fsPointer, path, arrayId); const [target] = array; const {encoding} = options; return encoding === 'buffer' ? target : target.toString(encoding); }); const rename = withHooks(async (existingPath, newPath) => { existingPath = await usePath(existingPath); newPath = await usePath(newPath); await binding.rename(fsPointer, existingPath, newPath); }) const link = withHooks(async (existingPath, newPath) => binding.link(fsPointer, ...(await usePaths(existingPath, newPath))) ); let symlinkWarned = false; const symlink = withHooks(async (target, path, type) => { if (type && !symlinkWarned) { symlinkWarned = true; console.warn("Type is not supported on ext2fs."); } return binding.symlink(fsPointer, ...(await usePaths(target, path))); }); const fchmod = withHooks(async (fd, mode) => { mode = modeNum(mode); checkFd(fd, 'node_ext2fs_chmod', [fd, mode]); await binding.chmod(fd, mode); }); async function lchmod(path, mode) { const fd = await open(path, constants.O_WRONLY | constants.O_NOFOLLOW) try { await fchmod(fd, mode); } finally { await close(fd); } }; async function chmod(path, mode) { const fd = await open(path, 0, 0); try { await fchmod(fd, modeNum(mode)); } finally { await close(fd); } }; async function lchown(path, uid, gid) { const fd = await open(path, constants.O_WRONLY | constants.O_NOFOLLOW) try { await fchown(fd, uid, gid); } finally { await close(fd); } }; async function fchown(fd, uid, gid) { checkFd(fd, 'node_ext2fs_chown', [fd, uid, gid]); await binding.chown(fd, uid, gid); }; async function chown(path, uid, gid) { const fd = await open(path, 0, 0); try { await fchown(fd, uid, gid); } finally { await close(fd); } }; async function utimes(path, atime, mtime) { // atime = toUnixTimestamp(atime); // mtime = toUnixTimestamp(mtime); // pathCheck(path); throw new UnimplementedError('futimes');; }; async function futimes(fd, atime, mtime) { // atime = toUnixTimestamp(atime); // mtime = toUnixTimestamp(mtime); throw new UnimplementedError('utimes');; }; async function writeAll(fd, buffer, offset, length, position) { let bytesWritten; while ({ bytesWritten } = await write(fd, buffer, offset, length, position)) { if (bytesWritten === length) return; offset += bytesWritten; length -= bytesWritten; if (position !== null) { position += bytesWritten; } } } const kReadFileBufferLength = 8 * 1024; async function readFile(path, options) { options = getOptions(options, { flag: 'r', encoding: 'utf8', }); pathCheck(path); const fd = isFd(path) ? path : await open(path, stringToFlags(options.flag || 'r'), 0o666); try { const s = await fstat(fd) if (!s.isFile()) throw new Error('Not a file.'); if (s.size > kMaxLength) { throw new RangeError('File size is greater than possible Buffer: ' + `0x${kMaxLength.toString(16)} bytes`); } if (s.size === 0) { const buffers = []; let totalRead = 0, bytesRead; do { const buffer = Buffer.allocUnsafeSlow(kReadFileBufferLength); ({ bytesRead } = await read(fd, buffer, 0, kReadFileBufferLength, -1)); totalRead += bytesRead; buffers.push(buffer); } while (bytesRead); return Buffer.concat(buffers, totalRead).toString(); } const buffer = Buffer.allocUnsafeSlow(s.size); const { bytesRead } = await read(fd, buffer, 0, s.size, -1); return buffer.subarray(0, bytesRead).toString(options.encoding); } finally { if (path !== fd) { await close(fd); } } }; async function writeFile(path, data, options) { const { flag, mode, encoding } = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); const fd = isFd(path) ? fd : await open(path, flag, mode); try { const buffer = (data instanceof Buffer) ? data : Buffer.from(String(data), encoding || 'utf8'); const position = /a/.test(flag) ? null : 0; await writeAll(fd, buffer, 0, buffer.byteLength, position); } finally { await close(fd); } }; async function appendFile(path, data, options) { options = { ...getOptions(options, { encoding: 'utf8', mode: 0o666 }), flag: 'a', }; await writeFile(path, data, options); }; // Regexp that finds the next portion of a (partial) path // result is [base_with_slash, base], e.g. ['somedir/', 'somedir'] const nextPartRe = /(.*?)(?:[/]+|$)/g; // Regex to find the device root, including trailing slash. E.g. 'c:\\'. const splitRootRe = /^[/]*/; function encodeRealpathResult(result, options, err) { if (!options || !options.encoding || options.encoding === 'utf8' || err) return result; const asBuffer = Buffer.from(result); if (options.encoding === 'buffer') { return asBuffer; } else { return asBuffer.toString(options.encoding); } } // TODO(zwhitchcox): test async function realpath(p, options) { options = getOptions(options); path = path.resolve(p.toString()); pathCheck(p); while((await lstat(p).isSymbolicLink())) p = path.resolve(p, await readlink(p)); const buffer = Buffer.from(p); return options.encoding === 'buffer' ? buffer : buffer.toString(options.encoding); }; let pool; function allocNewPool(poolSize) { pool = Buffer.allocUnsafe(poolSize); pool.used = 0; } function createReadStream(path, options) { return new ReadStream(path, options); }; class ReadStream extends Readable { constructor (path, options) { super(options); // a little bit bigger buffer and water marks by default options = getOptions(options, { highWaterMark: 64 * 1024, }) this.path = path; this.fd = options.fd !== undefined ? options.fd : null; this.flags = options.flags !== undefined ? options.flags : 'r'; this.mode = options.mode !== undefined ? options.mode : 0o666; this.autoClose = options.autoClose !== undefined ? options.autoClose : true; this.start = options.start; this.bytesWritten = 0; this._isClosed = false; if (this.start !== undefined) { if (typeof this.start !== 'number') { throw new TypeError('"start" option must be a Number'); } if (this.end === undefined) { this.end = Infinity; } else if (typeof this.end !== 'number') { throw new TypeError('"end" option must be a Number'); } if (this.start > this.end) { throw new Error('"start" option must be <= "end" option'); } this.pos = this.start; } if (typeof this.fd !== 'number') this.open(); this.on('end', function() { if (this.autoClose) { this.destroy(); } }); } async open() { try { const fd = await open(this.path, this.flags, this.mode); this.fd = fd; this.emit('open', fd); // start the flow of data. this.read(); } catch (error) { this.emit('error', error); if (this.autoClose) { this.destroy(); } } } async _read(n) { if (typeof this.fd !== 'number') return this.once('open', function() { this._read(n); }); if (this.destroyed) return; if (!pool || pool.length - pool.used < kMinPoolSpace) { // discard the old pool. allocNewPool(this._readableState.highWaterMark); } // Grab another reference to the pool in the case that while we're // in the thread pool another read() finishes up the pool, and // allocates a new one. const thisPool = pool; const toRead = Math.min(pool.length - pool.used, n); const start = pool.used; if (this.pos !== undefined) toRead = Math.min(this.end - this.pos + 1, toRead); // already read everything we were supposed to read! // treat as EOF. if (toRead <= 0) return this.push(null); let bytesRead; try { // the actual read. ({bytesRead} = await read(this.fd, pool, pool.used, toRead, this.pos)); } catch (error) { this.emit('error', error); if (this.autoClose) this.destroy(); return; } if (this.pos !== undefined) this.pos += toRead; // move the pool positions, and internal position for reading. if (this.pos !== undefined) this.pos += toRead; pool.used += toRead; let b = null; if (bytesRead > 0) { this.bytesRead += bytesRead; b = thisPool.slice(start, start + bytesRead); } this.push(b); } destroy() { if (this.destroyed) return; this.destroyed = true; this.close(); } async close(cb) { if (cb) this.once('close', cb); const _close = async fd => { try { await close(fd || this.fd); this.emit('close'); } catch (err) { this.emit('error', err); } this.fd = null; } if (typeof this.fd !== 'number') return this.once('open', _close); if (this._isClosed) { return process.nextTick(() => this.emit('close')); } this._isClosed = true; _close(); } } function createWriteStream(path, options) { return new WriteStream(path, options); }; class WriteStream extends Writable { constructor(path, options) { super(options); Object.assign(this, options = getOptions(options, { fd: null, flags: 'w', mode: 0o666, autoClose: true, path: path, bytesWritten: 0, })); if (this.start !== undefined) { if (typeof this.start !== 'number') { throw new TypeError('"start" option must be a Number'); } if (this.start < 0) { throw new Error('"start" must be >= zero'); } this.pos = this.start; } if (options.encoding) this.setDefaultEncoding(options.encoding); if (typeof this.fd !== 'number') this.open(); // dispose on finish. this.once('finish', function() { if (this.autoClose) this.close(); }); } async open() { try { const fd = await open(this.path, this.flags, this.mode); this.fd = fd; this.emit('open', fd); } catch (error) { if (this.autoClose) { this.destroy(); } this.emit('error', error); } } async _write(data, encoding, cb) { if (!(data instanceof Buffer)) return this.emit('error', new Error('Invalid data')); if (typeof this.fd !== 'number') return this.once('open', () => this._write(data, encoding, cb)); try { const { bytesWritten } = await write(this.fd, data, 0, data.length, this.pos); this.bytesWritten += bytesWritten; cb(); } catch (error) { if (this.autoClose) this.destroy(); cb(error); } if (this.pos !== undefined) this.pos += data.length; } async _writev(data, cb) { if (typeof this.fd !== 'number') return this.once('open', function() { this._writev(data, cb); }); const len = data.length; const chunks = new Array(len); let size = 0; for (let i = 0; i < len; i++) { const chunk = data[i].chunk; chunks[i] = chunk; size += chunk.length; } try { const bytesWritten = await writev(this.fd, chunks, this.pos); this.bytesWritten += bytesWritten; cb(); } catch (error) { this.destroy(); cb(error); } if (this.pos !== undefined) this.pos += size; } } WriteStream.prototype.destroy = ReadStream.prototype.destroy; WriteStream.prototype.close = ReadStream.prototype.close; // There is no shutdown() for files. WriteStream.prototype.destroySoon = WriteStream.prototype.end; class FileHandle extends EventEmitter { constructor(fd) { super(); this[kFd] = fd; } get fd() { return this[kFd]; } close() { this.emit('close'); fsCall(close, this); this[kFd] = -1; } appendFile(data, options) { // this seems wrong to me, but it's consistent with NodeJS return fsCall(writeFile, this, data, options); } chmod(mode) { return fsCall(fchmod, this, mode); } chown(uid, gid) { return fsCall(fchown, this, uid, gid); } datasync() { return fsCall(fdatasync, this); } sync() { return fsCall(fsync, this); } read(buffer, offset, length, position) { return fsCall(read, this, buffer, offset, length, position); } readv(buffers, position) { return fsCall(readv, this, buffers, position); } readFile(options) { return fsCall(readFile, this, options); } stat(options) { return fsCall(fstat, this, options); } truncate(len = 0) { return fsCall(ftruncate, this, len); } utimes(atime, mtime) { return fsCall(futimes, this, atime, mtime); } write(buffer, offset, length, position) { return fsCall(write, this, buffer, offset, length, position); } writev(buffers, position) { return fsCall(writev, this, buffers, position); } writeFile(data, options) { return fsCall(writeFile, this, data, options); } } async function fsCall(fn, handle, ...args) { const fd = handle[kFd]; assert(kFd !== undefined, 'handle must be an instance of FileHandle'); if (fd === -1) { const err = new Error('file closed'); err.code = 'EBADF'; err.syscall = fn.name; throw err; } return await fn(fd, ...args); } async function openFileHandle(path, flags, mode) { return new FileHandle(await open(path, flags, mode)); } const fsPromises = { fsPointer, closeAllFileDescriptors, openFiles, constants, access, open: openFileHandle, close, read, write, readFile, writeFile, appendFile, rename, truncate, ftruncate, rmdir, unlink, fdatasync, fsync, mkdir, mkdtemp, readdir, fstat, lstat, stat, readlink, symlink, link, fchmod, lchmod, chmod, lchown, fchown, chown, createReadStream, ReadStream, createWriteStream, WriteStream, } const fs = { fsPointer, closeAllFileDescriptors, openFiles, constants, access: callbackify(access), open: callbackify(open), close: callbackify(close), read: readCb, write: writeCb, readFile: callbackify(readFile), writeFile: callbackify(writeFile), appendFile: callbackify(appendFile), rename: callbackify(rename), truncate: callbackify(truncate), ftruncate: callbackify(ftruncate), rmdir: callbackify(rmdir), unlink: callbackify(unlink), fdatasync: callbackify(fdatasync), fsync: callbackify(fsync), mkdir: callbackify(mkdir), mkdtemp: callbackify(mkdtemp), readdir: callbackify(readdir), fstat: callbackify(fstat), lstat: callbackify(lstat), stat: callbackify(stat), readlink: callbackify(readlink), symlink: callbackify(symlink), link: callbackify(link), fchmod: callbackify(fchmod), lchmod: callbackify(lchmod), chmod: callbackify(chmod), lchown: callbackify(lchown), fchown: callbackify(fchown), chown: callbackify(chown), createReadStream, ReadStream: callbackify(ReadStream), createWriteStream, WriteStream: callbackify(WriteStream), } for (const _fs of [fs, fsPromises]) { // Don't allow mode to accidentally be overwritten. ['F_OK', 'R_OK', 'W_OK', 'X_OK'].forEach(key => { Object.defineProperty(_fs, key, { enumerable: true, value: constants[key] || 0, writable: false }); }); Object.defineProperty(_fs, 'constants', { enumerable: true, value: constants, writable: false }) } fs.promises = fsPromises; return fs; };