UNPKG

@forabi/memfs

Version:

In-memory file-system with Node's fs API.

1,295 lines 77.3 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); var path_1 = require("path"); var pathModule = require("path"); var node_1 = require("./node"); var buffer_1 = require("buffer"); var setImmediate_1 = require("./setImmediate"); var process_1 = require("./process"); var extend = require('fast-extend'); var errors = require('./internal/errors'); var setTimeoutUnref_1 = require("./setTimeoutUnref"); var stream_1 = require("stream"); var util = require('util'); var constants_1 = require("./constants"); var events_1 = require("events"); var O_RDONLY = constants_1.constants.O_RDONLY, O_WRONLY = constants_1.constants.O_WRONLY, O_RDWR = constants_1.constants.O_RDWR, O_CREAT = constants_1.constants.O_CREAT, O_EXCL = constants_1.constants.O_EXCL, O_NOCTTY = constants_1.constants.O_NOCTTY, O_TRUNC = constants_1.constants.O_TRUNC, O_APPEND = constants_1.constants.O_APPEND, O_DIRECTORY = constants_1.constants.O_DIRECTORY, O_NOATIME = constants_1.constants.O_NOATIME, O_NOFOLLOW = constants_1.constants.O_NOFOLLOW, O_SYNC = constants_1.constants.O_SYNC, O_DIRECT = constants_1.constants.O_DIRECT, O_NONBLOCK = constants_1.constants.O_NONBLOCK, F_OK = constants_1.constants.F_OK, R_OK = constants_1.constants.R_OK, W_OK = constants_1.constants.W_OK, X_OK = constants_1.constants.X_OK; var sep, relative; if (pathModule.posix) { var posix = pathModule.posix; sep = posix.sep; relative = posix.relative; } else { sep = pathModule.sep; relative = pathModule.relative; } var isWin = process_1.default.platform === 'win32'; // type TCallbackWrite = (err?: IError, bytesWritten?: number, source?: Buffer) => void; // type TCallbackWriteStr = (err?: IError, written?: number, str?: string) => void; // ---------------------------------------- Constants var ENCODING_UTF8 = 'utf8'; var kMinPoolSpace = 128; // const kMaxLength = require('buffer').kMaxLength; // ---------------------------------------- Error messages // TODO: Use `internal/errors.js` in the future. var ERRSTR = { PATH_STR: 'path must be a string or Buffer', // FD: 'file descriptor must be a unsigned 32-bit integer', FD: 'fd must be a file descriptor', MODE_INT: 'mode must be an int', CB: 'callback must be a function', UID: 'uid must be an unsigned int', GID: 'gid must be an unsigned int', LEN: 'len must be an integer', ATIME: 'atime must be an integer', MTIME: 'mtime must be an integer', PREFIX: 'filename prefix is required', BUFFER: 'buffer must be an instance of Buffer or StaticBuffer', OFFSET: 'offset must be an integer', LENGTH: 'length must be an integer', POSITION: 'position must be an integer', }; var ERRSTR_OPTS = function (tipeof) { return "Expected options to be either an object or a string, but got " + tipeof + " instead"; }; // const ERRSTR_FLAG = flag => `Unknown file open flag: ${flag}`; var ENOENT = 'ENOENT'; var EBADF = 'EBADF'; var EINVAL = 'EINVAL'; var EPERM = 'EPERM'; var EPROTO = 'EPROTO'; var EEXIST = 'EEXIST'; var ENOTDIR = 'ENOTDIR'; var EMFILE = 'EMFILE'; var EACCES = 'EACCES'; var EISDIR = 'EISDIR'; var ENOTEMPTY = 'ENOTEMPTY'; function formatError(errorCode, func, path, path2) { if (func === void 0) { func = ''; } if (path === void 0) { path = ''; } if (path2 === void 0) { path2 = ''; } var pathFormatted = ''; if (path) pathFormatted = " '" + path + "'"; if (path2) pathFormatted += " -> '" + path2 + "'"; switch (errorCode) { case ENOENT: return "ENOENT: no such file or directory, " + func + pathFormatted; case EBADF: return "EBADF: bad file descriptor, " + func + pathFormatted; case EINVAL: return "EINVAL: invalid argument, " + func + pathFormatted; case EPERM: return "EPERM: operation not permitted, " + func + pathFormatted; case EPROTO: return "EPROTO: protocol error, " + func + pathFormatted; case EEXIST: return "EEXIST: file already exists, " + func + pathFormatted; case ENOTDIR: return "ENOTDIR: not a directory, " + func + pathFormatted; case EISDIR: return "EISDIR: illegal operation on a directory, " + func + pathFormatted; case EACCES: return "EACCES: permission denied, " + func + pathFormatted; case ENOTEMPTY: return "ENOTEMPTY: directory not empty, " + func + pathFormatted; case EMFILE: return "EMFILE: too many open files, " + func + pathFormatted; default: return errorCode + ": error occurred, " + func + pathFormatted; } } function createError(errorCode, func, path, path2, Constructor) { if (func === void 0) { func = ''; } if (path === void 0) { path = ''; } if (path2 === void 0) { path2 = ''; } if (Constructor === void 0) { Constructor = Error; } var error = new Constructor(formatError(errorCode, func, path, path2)); error.code = errorCode; return error; } function throwError(errorCode, func, path, path2, Constructor) { if (func === void 0) { func = ''; } if (path === void 0) { path = ''; } if (path2 === void 0) { path2 = ''; } if (Constructor === void 0) { Constructor = Error; } throw createError(errorCode, func, path, path2, Constructor); } // ---------------------------------------- Flags // List of file `flags` as defined by Node. var FLAGS; (function (FLAGS) { // Open file for reading. An exception occurs if the file does not exist. FLAGS[FLAGS["r"] = O_RDONLY] = "r"; // Open file for reading and writing. An exception occurs if the file does not exist. FLAGS[FLAGS["r+"] = O_RDWR] = "r+"; // Open file for reading in synchronous mode. Instructs the operating system to bypass the local file system cache. FLAGS[FLAGS["rs"] = O_RDONLY | O_SYNC] = "rs"; FLAGS[FLAGS["sr"] = FLAGS.rs] = "sr"; // Open file for reading and writing, telling the OS to open it synchronously. See notes for 'rs' about using this with caution. FLAGS[FLAGS["rs+"] = O_RDWR | O_SYNC] = "rs+"; FLAGS[FLAGS["sr+"] = FLAGS['rs+']] = "sr+"; // Open file for writing. The file is created (if it does not exist) or truncated (if it exists). FLAGS[FLAGS["w"] = O_WRONLY | O_CREAT | O_TRUNC] = "w"; // Like 'w' but fails if path exists. FLAGS[FLAGS["wx"] = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL] = "wx"; FLAGS[FLAGS["xw"] = FLAGS.wx] = "xw"; // Open file for reading and writing. The file is created (if it does not exist) or truncated (if it exists). FLAGS[FLAGS["w+"] = O_RDWR | O_CREAT | O_TRUNC] = "w+"; // Like 'w+' but fails if path exists. FLAGS[FLAGS["wx+"] = O_RDWR | O_CREAT | O_TRUNC | O_EXCL] = "wx+"; FLAGS[FLAGS["xw+"] = FLAGS['wx+']] = "xw+"; // Open file for appending. The file is created if it does not exist. FLAGS[FLAGS["a"] = O_WRONLY | O_APPEND | O_CREAT] = "a"; // Like 'a' but fails if path exists. FLAGS[FLAGS["ax"] = O_WRONLY | O_APPEND | O_CREAT | O_EXCL] = "ax"; FLAGS[FLAGS["xa"] = FLAGS.ax] = "xa"; // Open file for reading and appending. The file is created if it does not exist. FLAGS[FLAGS["a+"] = O_RDWR | O_APPEND | O_CREAT] = "a+"; // Like 'a+' but fails if path exists. FLAGS[FLAGS["ax+"] = O_RDWR | O_APPEND | O_CREAT | O_EXCL] = "ax+"; FLAGS[FLAGS["xa+"] = FLAGS['ax+']] = "xa+"; })(FLAGS = exports.FLAGS || (exports.FLAGS = {})); function flagsToNumber(flags) { if (typeof flags === 'number') return flags; if (typeof flags === 'string') { var flagsNum = FLAGS[flags]; if (typeof flagsNum !== 'undefined') return flagsNum; } // throw new TypeError(formatError(ERRSTR_FLAG(flags))); throw new errors.TypeError('ERR_INVALID_OPT_VALUE', 'flags', flags); } exports.flagsToNumber = flagsToNumber; // ---------------------------------------- Options function assertEncoding(encoding) { if (encoding && !buffer_1.Buffer.isEncoding(encoding)) throw new errors.TypeError('ERR_INVALID_OPT_VALUE_ENCODING', encoding); } function getOptions(defaults, options) { var opts; if (!options) return defaults; else { var tipeof = typeof options; switch (tipeof) { case 'string': opts = extend({}, defaults, { encoding: options }); break; case 'object': opts = extend({}, defaults, options); break; default: throw TypeError(ERRSTR_OPTS(tipeof)); } } if (opts.encoding !== 'buffer') assertEncoding(opts.encoding); return opts; } function optsGenerator(defaults) { return function (options) { return getOptions(defaults, options); }; } function validateCallback(callback) { if (typeof callback !== 'function') throw TypeError(ERRSTR.CB); return callback; } function optsAndCbGenerator(getOpts) { return function (options, callback) { return typeof options === 'function' ? [getOpts(), options] : [getOpts(options), validateCallback(callback)]; }; } var optsDefaults = { encoding: 'utf8', }; var getDefaultOpts = optsGenerator(optsDefaults); var getDefaultOptsAndCb = optsAndCbGenerator(getDefaultOpts); var readFileOptsDefaults = { flag: 'r', }; var getReadFileOptions = optsGenerator(readFileOptsDefaults); var writeFileDefaults = { encoding: 'utf8', mode: 438 /* DEFAULT */, flag: FLAGS[FLAGS.w], }; var getWriteFileOptions = optsGenerator(writeFileDefaults); var appendFileDefaults = { encoding: 'utf8', mode: 438 /* DEFAULT */, flag: FLAGS[FLAGS.a], }; var getAppendFileOpts = optsGenerator(appendFileDefaults); var getAppendFileOptsAndCb = optsAndCbGenerator(getAppendFileOpts); var realpathDefaults = optsDefaults; var getRealpathOptions = optsGenerator(realpathDefaults); var getRealpathOptsAndCb = optsAndCbGenerator(getRealpathOptions); // ---------------------------------------- Utility functions function getPathFromURLPosix(url) { if (url.hostname !== '') { return new errors.TypeError('ERR_INVALID_FILE_URL_HOST', process_1.default.platform); } var pathname = url.pathname; for (var n = 0; n < pathname.length; n++) { if (pathname[n] === '%') { var third = pathname.codePointAt(n + 2) | 0x20; if (pathname[n + 1] === '2' && third === 102) { return new errors.TypeError('ERR_INVALID_FILE_URL_PATH', 'must not include encoded / characters'); } } } return decodeURIComponent(pathname); } function pathToFilename(path) { if ((typeof path !== 'string') && !buffer_1.Buffer.isBuffer(path)) { try { if (!(path instanceof require('url').URL)) throw new TypeError(ERRSTR.PATH_STR); } catch (err) { throw new TypeError(ERRSTR.PATH_STR); } path = getPathFromURLPosix(path); } var pathString = String(path); nullCheck(pathString); // return slash(pathString); return pathString; } exports.pathToFilename = pathToFilename; var resolve = function (filename, base) { if (base === void 0) { base = process_1.default.cwd(); } return path_1.resolve(base, filename); }; if (isWin) { var _resolve_1 = resolve; var unixify_1 = require("fs-monkey/lib/correctPath").unixify; resolve = function (filename, base) { return unixify_1(_resolve_1(filename, base)); }; } function filenameToSteps(filename, base) { var fullPath = resolve(filename, base); var fullPathSansSlash = fullPath.substr(1); if (!fullPathSansSlash) return []; return fullPathSansSlash.split(sep); } exports.filenameToSteps = filenameToSteps; function pathToSteps(path) { return filenameToSteps(pathToFilename(path)); } exports.pathToSteps = pathToSteps; function dataToStr(data, encoding) { if (encoding === void 0) { encoding = ENCODING_UTF8; } if (buffer_1.Buffer.isBuffer(data)) return data.toString(encoding); else if (data instanceof Uint8Array) return buffer_1.Buffer.from(data).toString(encoding); else return String(data); } exports.dataToStr = dataToStr; function dataToBuffer(data, encoding) { if (encoding === void 0) { encoding = ENCODING_UTF8; } if (buffer_1.Buffer.isBuffer(data)) return data; else if (data instanceof Uint8Array) return buffer_1.Buffer.from(data); else return buffer_1.Buffer.from(String(data), encoding); } exports.dataToBuffer = dataToBuffer; function strToEncoding(str, encoding) { if (!encoding || (encoding === ENCODING_UTF8)) return str; // UTF-8 if (encoding === 'buffer') return new buffer_1.Buffer(str); // `buffer` encoding return (new buffer_1.Buffer(str)).toString(encoding); // Custom encoding } exports.strToEncoding = strToEncoding; function bufferToEncoding(buffer, encoding) { if (!encoding || (encoding === 'buffer')) return buffer; else return buffer.toString(encoding); } exports.bufferToEncoding = bufferToEncoding; function nullCheck(path, callback) { if (('' + path).indexOf('\u0000') !== -1) { var er = new Error('Path must be a string without null bytes'); er.code = ENOENT; if (typeof callback !== 'function') throw er; process_1.default.nextTick(callback, er); return false; } return true; } function _modeToNumber(mode, def) { if (typeof mode === 'number') return mode; if (typeof mode === 'string') return parseInt(mode, 8); if (def) return modeToNumber(def); return undefined; } function modeToNumber(mode, def) { var result = _modeToNumber(mode, def); if ((typeof result !== 'number') || isNaN(result)) throw new TypeError(ERRSTR.MODE_INT); return result; } function isFd(path) { return (path >>> 0) === path; } function validateFd(fd) { if (!isFd(fd)) throw TypeError(ERRSTR.FD); } // converts Date or number to a fractional UNIX timestamp function toUnixTimestamp(time) { if (typeof time === 'string' && (+time == time)) { return +time; } if (isFinite(time)) { if (time < 0) { return Date.now() / 1000; } return time; } if (time instanceof Date) { return time.getTime() / 1000; } throw new Error('Cannot parse time: ' + time); } exports.toUnixTimestamp = toUnixTimestamp; /** * Returns optional argument and callback * @param arg Argument or callback value * @param callback Callback or undefined * @param def Default argument value */ function getArgAndCb(arg, callback, def) { return typeof arg === 'function' ? [def, arg] : [arg, callback]; } function validateUid(uid) { if (typeof uid !== 'number') throw TypeError(ERRSTR.UID); } function validateGid(gid) { if (typeof gid !== 'number') throw TypeError(ERRSTR.GID); } // ---------------------------------------- Volume /** * `Volume` represents a file system. */ var Volume = /** @class */ (function () { function Volume(props) { if (props === void 0) { props = {}; } // I-node number counter. this.ino = 0; // A mapping for i-node numbers to i-nodes (`Node`); this.inodes = {}; // List of released i-node numbers, for reuse. this.releasedInos = []; // A mapping for file descriptors to `File`s. this.fds = {}; // A list of reusable (opened and closed) file descriptors, that should be // used first before creating a new file descriptor. this.releasedFds = []; // Max number of open files. this.maxFiles = 10000; // Current number of open files. this.openFiles = 0; this.statWatchers = {}; this.props = extend({ Node: node_1.Node, Link: node_1.Link, File: node_1.File }, props); var root = this.createLink(); root.setNode(this.createNode(true)); var self = this; this.StatWatcher = /** @class */ (function (_super) { __extends(class_1, _super); function class_1() { return _super.call(this, self) || this; } return class_1; }(StatWatcher)); var _ReadStream = FsReadStream; this.ReadStream = /** @class */ (function (_super) { __extends(class_2, _super); function class_2() { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return _super.apply(this, [self].concat(args)) || this; } return class_2; }(_ReadStream)); var _WriteStream = FsWriteStream; this.WriteStream = /** @class */ (function (_super) { __extends(class_3, _super); function class_3() { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return _super.apply(this, [self].concat(args)) || this; } return class_3; }(_WriteStream)); this.FSWatcher = /** @class */ (function (_super) { __extends(class_4, _super); function class_4() { return _super.call(this, self) || this; } return class_4; }(FSWatcher)); // root.setChild('.', root); // root.getNode().nlink++; // root.setChild('..', root); // root.getNode().nlink++; this.root = root; } Volume.fromJSON = function (json, cwd) { var vol = new Volume; vol.fromJSON(json, cwd); return vol; }; Volume.prototype.createLink = function (parent, name, isDirectory, perm) { if (isDirectory === void 0) { isDirectory = false; } return parent ? parent.createChild(name, this.createNode(isDirectory, perm)) : new this.props.Link(this, null, ''); }; Volume.prototype.deleteLink = function (link) { var parent = link.parent; if (parent) { parent.deleteChild(link); link.vol = null; link.parent = null; return true; } return false; }; Volume.prototype.newInoNumber = function () { if (this.releasedInos.length) return this.releasedInos.pop(); else { this.ino = (this.ino + 1) % 0xFFFFFFFF; return this.ino; } }; Volume.prototype.newFdNumber = function () { return this.releasedFds.length ? this.releasedFds.pop() : Volume.fd--; }; Volume.prototype.createNode = function (isDirectory, perm) { if (isDirectory === void 0) { isDirectory = false; } var node = new this.props.Node(this.newInoNumber(), perm); if (isDirectory) node.setIsDirectory(); this.inodes[node.ino] = node; return node; }; Volume.prototype.getNode = function (ino) { return this.inodes[ino]; }; Volume.prototype.deleteNode = function (node) { node.del(); delete this.inodes[node.ino]; this.releasedInos.push(node.ino); }; // Generates 6 character long random string, used by `mkdtemp`. Volume.prototype.genRndStr = function () { var str = (Math.random() + 1).toString(36).substr(2, 6); if (str.length === 6) return str; else return this.genRndStr(); }; // Returns a `Link` (hard link) referenced by path "split" into steps. Volume.prototype.getLink = function (steps) { return this.root.walk(steps); }; // Just link `getLink`, but throws a correct user error, if link to found. Volume.prototype.getLinkOrThrow = function (filename, funcName) { var steps = filenameToSteps(filename); var link = this.getLink(steps); if (!link) throwError(ENOENT, funcName, filename); return link; }; // Just like `getLink`, but also dereference/resolves symbolic links. Volume.prototype.getResolvedLink = function (filenameOrSteps) { var steps = typeof filenameOrSteps === 'string' ? filenameToSteps(filenameOrSteps) : filenameOrSteps; var link = this.root; var i = 0; while (i < steps.length) { var step = steps[i]; link = link.getChild(step); if (!link) return null; var node = link.getNode(); if (node.isSymlink()) { steps = node.symlink.concat(steps.slice(i + 1)); link = this.root; i = 0; continue; } i++; } return link; }; // Just like `getLinkOrThrow`, but also dereference/resolves symbolic links. Volume.prototype.getResolvedLinkOrThrow = function (filename, funcName) { var link = this.getResolvedLink(filename); if (!link) throwError(ENOENT, funcName, filename); return link; }; Volume.prototype.resolveSymlinks = function (link) { // let node: Node = link.getNode(); // while(link && node.isSymlink()) { // link = this.getLink(node.symlink); // if(!link) return null; // node = link.getNode(); // } // return link; return this.getResolvedLink(link.steps.slice(1)); }; // Just like `getLinkOrThrow`, but also verifies that the link is a directory. Volume.prototype.getLinkAsDirOrThrow = function (filename, funcName) { var link = this.getLinkOrThrow(filename, funcName); if (!link.getNode().isDirectory()) throwError(ENOTDIR, funcName, filename); return link; }; // Get the immediate parent directory of the link. Volume.prototype.getLinkParent = function (steps) { return this.root.walk(steps, steps.length - 1); }; Volume.prototype.getLinkParentAsDirOrThrow = function (filenameOrSteps, funcName) { var steps = filenameOrSteps instanceof Array ? filenameOrSteps : filenameToSteps(filenameOrSteps); var link = this.getLinkParent(steps); if (!link) throwError(ENOENT, funcName, sep + steps.join(sep)); if (!link.getNode().isDirectory()) throwError(ENOTDIR, funcName, sep + steps.join(sep)); return link; }; Volume.prototype.getFileByFd = function (fd) { return this.fds[String(fd)]; }; Volume.prototype.getFileByFdOrThrow = function (fd, funcName) { if (!isFd(fd)) throw TypeError(ERRSTR.FD); var file = this.getFileByFd(fd); if (!file) throwError(EBADF, funcName); return file; }; Volume.prototype.getNodeByIdOrCreate = function (id, flags, perm) { if (typeof id === 'number') { var file = this.getFileByFd(id); if (!file) throw Error('File nto found'); return file.node; } else { var steps = pathToSteps(id); var link = this.getLink(steps); if (link) return link.getNode(); // Try creating a node if not found. if (flags & O_CREAT) { var dirLink = this.getLinkParent(steps); if (dirLink) { var name_1 = steps[steps.length - 1]; link = this.createLink(dirLink, name_1, false, perm); return link.getNode(); } } throwError(ENOENT, 'getNodeByIdOrCreate', pathToFilename(id)); } }; Volume.prototype.wrapAsync = function (method, args, callback) { var _this = this; validateCallback(callback); setImmediate_1.default(function () { try { callback(null, method.apply(_this, args)); } catch (err) { callback(err); } }); }; Volume.prototype._toJSON = function (link, json, path) { if (link === void 0) { link = this.root; } if (json === void 0) { json = {}; } var isEmpty = true; for (var name_2 in link.children) { isEmpty = false; var child = link.getChild(name_2); var node = child.getNode(); if (node.isFile()) { var filename = child.getPath(); if (path) filename = relative(path, filename); json[filename] = node.getString(); } else if (node.isDirectory()) { this._toJSON(child, json, path); } } var dirPath = link.getPath(); if (path) dirPath = relative(path, dirPath); if (dirPath && isEmpty) { json[dirPath] = null; } return json; }; Volume.prototype.toJSON = function (paths, json, isRelative) { if (json === void 0) { json = {}; } if (isRelative === void 0) { isRelative = false; } var links = []; if (paths) { if (!(paths instanceof Array)) paths = [paths]; for (var _i = 0, paths_1 = paths; _i < paths_1.length; _i++) { var path = paths_1[_i]; var filename = pathToFilename(path); var link = this.getResolvedLink(filename); if (!link) continue; links.push(link); } } else { links.push(this.root); } if (!links.length) return json; for (var _a = 0, links_1 = links; _a < links_1.length; _a++) { var link = links_1[_a]; this._toJSON(link, json, isRelative ? link.getPath() : ''); } return json; }; // fromJSON(json: {[filename: string]: string}, cwd: string = '/') { Volume.prototype.fromJSON = function (json, cwd) { if (cwd === void 0) { cwd = process_1.default.cwd(); } for (var filename in json) { var data = json[filename]; if (typeof data === 'string') { filename = resolve(filename, cwd); var steps = filenameToSteps(filename); if (steps.length > 1) { var dirname = sep + steps.slice(0, steps.length - 1).join(sep); this.mkdirpBase(dirname, 511 /* DIR */); } this.writeFileSync(filename, data); } else { this.mkdirpBase(filename, 511 /* DIR */); } } }; Volume.prototype.reset = function () { this.ino = 0; this.inodes = {}; this.releasedInos = []; this.fds = {}; this.releasedFds = []; this.openFiles = 0; this.root = this.createLink(); this.root.setNode(this.createNode(true)); }; // Legacy interface Volume.prototype.mountSync = function (mountpoint, json) { this.fromJSON(json, mountpoint); }; Volume.prototype.openLink = function (link, flagsNum, resolveSymlinks) { if (resolveSymlinks === void 0) { resolveSymlinks = true; } if (this.openFiles >= this.maxFiles) { // Too many open files. throw createError(EMFILE, 'open', link.getPath()); } // Resolve symlinks. var realLink = link; if (resolveSymlinks) realLink = this.resolveSymlinks(link); if (!realLink) throwError(ENOENT, 'open', link.getPath()); var node = realLink.getNode(); if (node.isDirectory() && (flagsNum !== FLAGS.r)) throwError(EISDIR, 'open', link.getPath()); // Check node permissions if (!(flagsNum & O_WRONLY)) { if (!node.canRead()) { throwError(EACCES, 'open', link.getPath()); } } if (flagsNum & O_RDWR) { } var file = new this.props.File(link, node, flagsNum, this.newFdNumber()); this.fds[file.fd] = file; this.openFiles++; if (flagsNum & O_TRUNC) file.truncate(); return file; }; Volume.prototype.openFile = function (filename, flagsNum, modeNum, resolveSymlinks) { if (resolveSymlinks === void 0) { resolveSymlinks = true; } var steps = filenameToSteps(filename); var link = this.getResolvedLink(steps); // Try creating a new file, if it does not exist. if (!link) { // const dirLink: Link = this.getLinkParent(steps); var dirLink = this.getResolvedLink(steps.slice(0, steps.length - 1)); // if(!dirLink) throwError(ENOENT, 'open', filename); if (!dirLink) throwError(ENOENT, 'open', sep + steps.join(sep)); if ((flagsNum & O_CREAT) && (typeof modeNum === 'number')) { link = this.createLink(dirLink, steps[steps.length - 1], false, modeNum); } } if (link) return this.openLink(link, flagsNum, resolveSymlinks); }; Volume.prototype.openBase = function (filename, flagsNum, modeNum, resolveSymlinks) { if (resolveSymlinks === void 0) { resolveSymlinks = true; } var file = this.openFile(filename, flagsNum, modeNum, resolveSymlinks); if (!file) throwError(ENOENT, 'open', filename); return file.fd; }; Volume.prototype.openSync = function (path, flags, mode) { if (mode === void 0) { mode = 438 /* DEFAULT */; } // Validate (1) mode; (2) path; (3) flags - in that order. var modeNum = modeToNumber(mode); var fileName = pathToFilename(path); var flagsNum = flagsToNumber(flags); return this.openBase(fileName, flagsNum, modeNum); }; Volume.prototype.open = function (path, flags, a, b) { var mode = a; var callback = b; if (typeof a === 'function') { mode = 438 /* DEFAULT */; callback = a; } mode = mode || 438 /* DEFAULT */; var modeNum = modeToNumber(mode); var fileName = pathToFilename(path); var flagsNum = flagsToNumber(flags); this.wrapAsync(this.openBase, [fileName, flagsNum, modeNum], callback); }; Volume.prototype.closeFile = function (file) { if (!this.fds[file.fd]) return; this.openFiles--; delete this.fds[file.fd]; this.releasedFds.push(file.fd); }; Volume.prototype.closeSync = function (fd) { validateFd(fd); var file = this.getFileByFdOrThrow(fd, 'close'); this.closeFile(file); }; Volume.prototype.close = function (fd, callback) { validateFd(fd); this.wrapAsync(this.closeSync, [fd], callback); }; Volume.prototype.openFileOrGetById = function (id, flagsNum, modeNum) { if (typeof id === 'number') { var file = this.fds[id]; if (!file) throw createError(ENOENT); return file; } else { return this.openFile(pathToFilename(id), flagsNum, modeNum); } }; Volume.prototype.readBase = function (fd, buffer, offset, length, position) { var file = this.getFileByFdOrThrow(fd); return file.read(buffer, Number(offset), Number(length), position); }; Volume.prototype.readSync = function (fd, buffer, offset, length, position) { validateFd(fd); return this.readBase(fd, buffer, offset, length, position); }; Volume.prototype.read = function (fd, buffer, offset, length, position, callback) { var _this = this; validateCallback(callback); // This `if` branch is from Node.js if (length === 0) { return process_1.default.nextTick(function () { callback && callback(null, 0, buffer); }); } setImmediate_1.default(function () { try { var bytes = _this.readBase(fd, buffer, offset, length, position); callback(null, bytes, buffer); } catch (err) { callback(err); } }); }; Volume.prototype.readFileBase = function (id, flagsNum, encoding) { var result; var userOwnsFd = isFd(id); var fd; if (userOwnsFd) { fd = id; } else { fd = this.openSync(id, flagsNum); } try { result = bufferToEncoding(this.getFileByFdOrThrow(fd).getBuffer(), encoding); } finally { if (!userOwnsFd) { this.closeSync(fd); } } return result; }; Volume.prototype.readFileSync = function (file, options) { var opts = getReadFileOptions(options); var flagsNum = flagsToNumber(opts.flag); return this.readFileBase(file, flagsNum, opts.encoding); }; Volume.prototype.readFile = function (id, a, b) { var _a = optsAndCbGenerator(getReadFileOptions)(a, b), opts = _a[0], callback = _a[1]; var flagsNum = flagsToNumber(opts.flag); this.wrapAsync(this.readFileBase, [id, flagsNum, opts.encoding], callback); }; Volume.prototype.writeBase = function (fd, buf, offset, length, position) { var file = this.getFileByFdOrThrow(fd, 'write'); return file.write(buf, offset, length, position); }; Volume.prototype.writeSync = function (fd, a, b, c, d) { validateFd(fd); var encoding; var offset; var length; var position; var isBuffer = typeof a !== 'string'; if (isBuffer) { offset = b | 0; length = c; position = d; } else { position = b; encoding = c; } var buf = dataToBuffer(a, encoding); if (isBuffer) { if (typeof length === 'undefined') { length = buf.length; } } else { offset = 0; length = buf.length; } return this.writeBase(fd, buf, offset, length, position); }; Volume.prototype.write = function (fd, a, b, c, d, e) { var _this = this; validateFd(fd); var offset; var length; var position; var encoding; var callback; var tipa = typeof a; var tipb = typeof b; var tipc = typeof c; var tipd = typeof d; if (tipa !== 'string') { if (tipb === 'function') { callback = b; } else if (tipc === 'function') { offset = b | 0; callback = c; } else if (tipd === 'function') { offset = b | 0; length = c; callback = d; } else { offset = b | 0; length = c; position = d; callback = e; } } else { if (tipb === 'function') { callback = b; } else if (tipc === 'function') { position = b; callback = c; } else if (tipd === 'function') { position = b; encoding = c; callback = d; } } var buf = dataToBuffer(a, encoding); if (tipa !== 'string') { if (typeof length === 'undefined') length = buf.length; } else { offset = 0; length = buf.length; } validateCallback(callback); setImmediate_1.default(function () { try { var bytes = _this.writeBase(fd, buf, offset, length, position); if (tipa !== 'string') { callback(null, bytes, buf); } else { callback(null, bytes, a); } } catch (err) { callback(err); } }); }; Volume.prototype.writeFileBase = function (id, buf, flagsNum, modeNum) { // console.log('writeFileBase', id, buf, flagsNum, modeNum); // const node = this.getNodeByIdOrCreate(id, flagsNum, modeNum); // node.setBuffer(buf); var isUserFd = typeof id === 'number'; var fd; if (isUserFd) fd = id; else { fd = this.openBase(pathToFilename(id), flagsNum, modeNum); // fd = this.openSync(id as TFilePath, flagsNum, modeNum); } var offset = 0; var length = buf.length; var position = (flagsNum & O_APPEND) ? null : 0; try { while (length > 0) { var written = this.writeSync(fd, buf, offset, length, position); offset += written; length -= written; if (position !== null) position += written; } } finally { if (!isUserFd) this.closeSync(fd); } }; Volume.prototype.writeFileSync = function (id, data, options) { var opts = getWriteFileOptions(options); var flagsNum = flagsToNumber(opts.flag); var modeNum = modeToNumber(opts.mode); var buf = dataToBuffer(data, opts.encoding); this.writeFileBase(id, buf, flagsNum, modeNum); }; Volume.prototype.writeFile = function (id, data, a, b) { var options = a; var callback = b; if (typeof a === 'function') { options = writeFileDefaults; callback = a; } var opts = getWriteFileOptions(options); var flagsNum = flagsToNumber(opts.flag); var modeNum = modeToNumber(opts.mode); var buf = dataToBuffer(data, opts.encoding); this.wrapAsync(this.writeFileBase, [id, buf, flagsNum, modeNum], callback); }; Volume.prototype.linkBase = function (filename1, filename2) { var steps1 = filenameToSteps(filename1); var link1 = this.getLink(steps1); if (!link1) throwError(ENOENT, 'link', filename1, filename2); var steps2 = filenameToSteps(filename2); // Check new link directory exists. var dir2 = this.getLinkParent(steps2); if (!dir2) throwError(ENOENT, 'link', filename1, filename2); var name = steps2[steps2.length - 1]; // Check if new file already exists. if (dir2.getChild(name)) throwError(EEXIST, 'link', filename1, filename2); var node = link1.getNode(); node.nlink++; dir2.createChild(name, node); }; Volume.prototype.linkSync = function (existingPath, newPath) { var existingPathFilename = pathToFilename(existingPath); var newPathFilename = pathToFilename(newPath); this.linkBase(existingPathFilename, newPathFilename); }; Volume.prototype.link = function (existingPath, newPath, callback) { var existingPathFilename = pathToFilename(existingPath); var newPathFilename = pathToFilename(newPath); this.wrapAsync(this.linkBase, [existingPathFilename, newPathFilename], callback); }; Volume.prototype.unlinkBase = function (filename) { var steps = filenameToSteps(filename); var link = this.getLink(steps); if (!link) throwError(ENOENT, 'unlink', filename); // TODO: Check if it is file, dir, other... if (link.length) throw Error('Dir not empty...'); this.deleteLink(link); var node = link.getNode(); node.nlink--; // When all hard links to i-node are deleted, remove the i-node, too. if (node.nlink <= 0) { this.deleteNode(node); } }; Volume.prototype.unlinkSync = function (path) { var filename = pathToFilename(path); this.unlinkBase(filename); }; Volume.prototype.unlink = function (path, callback) { var filename = pathToFilename(path); this.wrapAsync(this.unlinkBase, [filename], callback); }; Volume.prototype.symlinkBase = function (targetFilename, pathFilename) { var pathSteps = filenameToSteps(pathFilename); // Check if directory exists, where we about to create a symlink. var dirLink = this.getLinkParent(pathSteps); if (!dirLink) throwError(ENOENT, 'symlink', targetFilename, pathFilename); var name = pathSteps[pathSteps.length - 1]; // Check if new file already exists. if (dirLink.getChild(name)) throwError(EEXIST, 'symlink', targetFilename, pathFilename); // Create symlink. var symlink = dirLink.createChild(name); symlink.getNode().makeSymlink(filenameToSteps(targetFilename)); return symlink; }; // `type` argument works only on Windows. Volume.prototype.symlinkSync = function (target, path, type) { var targetFilename = pathToFilename(target); var pathFilename = pathToFilename(path); this.symlinkBase(targetFilename, pathFilename); }; Volume.prototype.symlink = function (target, path, a, b) { var _a = getArgAndCb(a, b), type = _a[0], callback = _a[1]; var targetFilename = pathToFilename(target); var pathFilename = pathToFilename(path); this.wrapAsync(this.symlinkBase, [targetFilename, pathFilename], callback); }; Volume.prototype.realpathBase = function (filename, encoding) { var steps = filenameToSteps(filename); var link = this.getLink(steps); // TODO: this check has to be perfomed by `lstat`. if (!link) throwError(ENOENT, 'realpath', filename); // Resolve symlinks. var realLink = this.resolveSymlinks(link); if (!realLink) throwError(ENOENT, 'realpath', filename); return strToEncoding(realLink.getPath(), encoding); }; Volume.prototype.realpathSync = function (path, options) { return this.realpathBase(pathToFilename(path), getRealpathOptions(options).encoding); }; Volume.prototype.realpath = function (path, a, b) { var _a = getRealpathOptsAndCb(a, b), opts = _a[0], callback = _a[1]; var pathFilename = pathToFilename(path); this.wrapAsync(this.realpathBase, [pathFilename, opts.encoding], callback); }; Volume.prototype.lstatBase = function (filename) { var link = this.getLink(filenameToSteps(filename)); if (!link) throwError(ENOENT, 'lstat', filename); return node_1.Stats.build(link.getNode()); }; Volume.prototype.lstatSync = function (path) { return this.lstatBase(pathToFilename(path)); }; Volume.prototype.lstat = function (path, callback) { this.wrapAsync(this.lstatBase, [pathToFilename(path)], callback); }; Volume.prototype.statBase = function (filename) { var link = this.getLink(filenameToSteps(filename)); if (!link) throwError(ENOENT, 'stat', filename); // Resolve symlinks. link = this.resolveSymlinks(link); if (!link) throwError(ENOENT, 'stat', filename); return node_1.Stats.build(link.getNode()); }; Volume.prototype.statSync = function (path) { return this.statBase(pathToFilename(path)); }; Volume.prototype.stat = function (path, callback) { this.wrapAsync(this.statBase, [pathToFilename(path)], callback); }; Volume.prototype.fstatBase = function (fd) { var file = this.getFileByFd(fd); if (!file) throwError(EBADF, 'fstat'); return node_1.Stats.build(file.node); }; Volume.prototype.fstatSync = function (fd) { return this.fstatBase(fd); }; Volume.prototype.fstat = function (fd, callback) { this.wrapAsync(this.fstatBase, [fd], callback); }; Volume.prototype.renameBase = function (oldPathFilename, newPathFilename) { var link = this.getLink(filenameToSteps(oldPathFilename)); if (!link) throwError(ENOENT, 'rename', oldPathFilename, newPathFilename); // TODO: Check if it is directory, if non-empty, we cannot move it, right? var newPathSteps = filenameToSteps(newPathFilename); // Check directory exists for the new location. var newPathDirLink = this.getLinkParent(newPathSteps); if (!newPathDirLink) throwError(ENOENT, 'rename', oldPathFilename, newPathFilename); // TODO: Also treat cases with directories and symbolic links. // TODO: See: http://man7.org/linux/man-pages/man2/rename.2.html // Remove hard link from old folder. var oldLinkParent = link.parent; if (oldLinkParent) { oldLinkParent.deleteChild(link); } // Rename should overwrite the new path, if that exists. var name = newPathSteps[newPathSteps.length - 1]; link.steps = newPathDirLink.steps.concat([name]); newPathDirLink.setChild(link.getName(), link); }; Volume.prototype.renameSync = function (oldPath, newPath) { var oldPathFilename = pathToFilename(oldPath); var newPathFilename = pathToFilename(newPath); this.renameBase(oldPathFilename, newPathFilename); }; Volume.prototype.rename = function (oldPath, newPath, callback) { var oldPathFilename = pathToFilename(oldPath); var newPathFilename = pathToFilename(newPath); this.wrapAsync(this.renameBase, [oldPathFilename, newPathFilename], callback); }; Volume.prototype.existsBase = function (filename) { return !!this.statBase(filename); }; Volume.prototype.existsSync = function (path) { try { return this.existsBase(pathToFilename(path)); } catch (err) { return false; } }; Volume.prototype.exists = function (path, callback) { var _this = this; var filename = pathToFilename(path); if (typeof callback !== 'function') throw Error(ERRSTR.CB); setImmediate_1.default(function () { try { callback(_this.existsBase(filename)); } catch (err) { callback(false); } }); }; Volume.prototype.accessBase = function (filename, mode) { var link = this.getLinkOrThrow(filename, 'access'); // TODO: Verify permissions }; Volume.prototype.accessSync = function (path, mode) { if (mode === void 0) { mode = F_OK; } var filename = pathToFilename(path); mode = mode | 0; this.accessBase(filename, mode); }; Volume.prototype.access = function (path, a, b) { var mode = a; var callback = b; if (typeof mode === 'function') { mode = F_OK; callback = a; } var filename = pathToFilename(path); mode = mode | 0; this.wrapAsync(this.accessBase, [filename, mode], callback); }; Volume.prototype.appendFileSync = function (id, data, options) { if (options === void 0) { options = appendFileDefaults; } var opts = getAppendFileOpts(options); // force append behavior when using a supplied file descriptor if (!opts.flag || isFd(id)) opts.flag = 'a'; this.writeFileSync(id, data, opts); }; Volume.prototype.appendFile = function (id, data, a, b) { var _a = getAppendFileOptsAndCb(a, b), opts = _a[0], callback = _a[1]; // force append behavior when using a supplied file descriptor if (!opts.flag || isFd(id)) opts.flag = 'a'; this.writeFile(id, data, opts, callback); }; Volume.prototype.readdirBase = function (filename, encoding) { var steps = filenameToSteps(filename); var link = this.getResolvedLink(steps); if (!link) throwError(ENOENT, 'readdir', filename); var node = link.getNode(); if (!node.isDirectory()) throwError(ENOTDIR, 'scandir', filename); var list = []; for (var name_3 in link.children) list.push(strToEncoding(name_3, encoding)); if (!isWin && encoding !== 'buffer') list.sort(); return list; }; Volume.prototype.readdirSync = function (path, options) { var opts = getDefaultOpts(options); var filename = pathToFilename(path); return this.readdirBase(filename, opts.encoding); }; Volume.prototype.readdir = function (path, a, b) { var options = a; var callback = b; if (typeof a === 'function') { callback = a; options = optsDefaults; } var opts = getDefaultOpts(options); var filename = pathToFilename(path); this.wrapAsync(this.readdirBase, [filename, opts.encoding], callback); }; Volume.prototype.readlinkBase = function (filename, encoding) { var link = this.getLinkOrThrow(filename, 'readlink'); var node = link.getNode(); if (!node.isSymlink()) throwError(EINVAL, 'readlink', filename); va