"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.File = exports.Link = exports.Node = exports.SEP = void 0; const process_1 = require("./process"); const buffer_1 = require("./internal/buffer"); const constants_1 = require("./constants"); const events_1 = require("events"); const Stats_1 = require("./Stats"); const { S_IFMT, S_IFDIR, S_IFREG, S_IFLNK, O_APPEND } = constants_1.constants; const getuid = () => { var _a, _b; return (_b = (_a = process_1.default.getuid) === null || _a === void 0 ? void 0 : _a.call(process_1.default)) !== null && _b !== void 0 ? _b : 0; }; const getgid = () => { var _a, _b; return (_b = (_a = process_1.default.getgid) === null || _a === void 0 ? void 0 : _a.call(process_1.default)) !== null && _b !== void 0 ? _b : 0; }; exports.SEP = '/'; /** * Node in a file system (like i-node, v-node). */ class Node extends events_1.EventEmitter { constructor(ino, perm = 0o666) { super(); // User ID and group ID. this._uid = getuid(); this._gid = getgid(); this._atime = new Date(); this._mtime = new Date(); this._ctime = new Date(); this._perm = 0o666; // Permissions `chmod`, `fchmod` this.mode = S_IFREG; // S_IFDIR, S_IFREG, etc.. (file by default?) // Number of hard links pointing at this Node. this._nlink = 1; this._perm = perm; this.mode |= perm; this.ino = ino; } set ctime(ctime) { this._ctime = ctime; } get ctime() { return this._ctime; } set uid(uid) { this._uid = uid; this.ctime = new Date(); } get uid() { return this._uid; } set gid(gid) { this._gid = gid; this.ctime = new Date(); } get gid() { return this._gid; } set atime(atime) { this._atime = atime; this.ctime = new Date(); } get atime() { return this._atime; } set mtime(mtime) { this._mtime = mtime; this.ctime = new Date(); } get mtime() { return this._mtime; } set perm(perm) { this._perm = perm; this.ctime = new Date(); } get perm() { return this._perm; } set nlink(nlink) { this._nlink = nlink; this.ctime = new Date(); } get nlink() { return this._nlink; } getString(encoding = 'utf8') { this.atime = new Date(); return this.getBuffer().toString(encoding); } setString(str) { // this.setBuffer(bufferFrom(str, 'utf8')); this.buf = (0, buffer_1.bufferFrom)(str, 'utf8'); this.touch(); } getBuffer() { this.atime = new Date(); if (!this.buf) this.setBuffer((0, buffer_1.bufferAllocUnsafe)(0)); return (0, buffer_1.bufferFrom)(this.buf); // Return a copy. } setBuffer(buf) { this.buf = (0, buffer_1.bufferFrom)(buf); // Creates a copy of data. this.touch(); } getSize() { return this.buf ? this.buf.length : 0; } setModeProperty(property) { this.mode = (this.mode & ~S_IFMT) | property; } setIsFile() { this.setModeProperty(S_IFREG); } setIsDirectory() { this.setModeProperty(S_IFDIR); } setIsSymlink() { this.setModeProperty(S_IFLNK); } isFile() { return (this.mode & S_IFMT) === S_IFREG; } isDirectory() { return (this.mode & S_IFMT) === S_IFDIR; } isSymlink() { // return !!this.symlink; return (this.mode & S_IFMT) === S_IFLNK; } makeSymlink(steps) { this.symlink = steps; this.setIsSymlink(); } write(buf, off = 0, len = buf.length, pos = 0) { if (!this.buf) this.buf = (0, buffer_1.bufferAllocUnsafe)(0); if (pos + len > this.buf.length) { const newBuf = (0, buffer_1.bufferAllocUnsafe)(pos + len); this.buf.copy(newBuf, 0, 0, this.buf.length); this.buf = newBuf; } buf.copy(this.buf, pos, off, off + len); this.touch(); return len; } // Returns the number of bytes read. read(buf, off = 0, len = buf.byteLength, pos = 0) { this.atime = new Date(); if (!this.buf) this.buf = (0, buffer_1.bufferAllocUnsafe)(0); let actualLen = len; if (actualLen > buf.byteLength) { actualLen = buf.byteLength; } if (actualLen + pos > this.buf.length) { actualLen = this.buf.length - pos; } const buf2 = buf instanceof buffer_1.Buffer ? buf : buffer_1.Buffer.from(buf.buffer); this.buf.copy(buf2, off, pos, pos + actualLen); return actualLen; } truncate(len = 0) { if (!len) this.buf = (0, buffer_1.bufferAllocUnsafe)(0); else { if (!this.buf) this.buf = (0, buffer_1.bufferAllocUnsafe)(0); if (len <= this.buf.length) { this.buf = this.buf.slice(0, len); } else { const buf = (0, buffer_1.bufferAllocUnsafe)(len); this.buf.copy(buf); buf.fill(0, this.buf.length); this.buf = buf; } } this.touch(); } chmod(perm) { this.perm = perm; this.mode = (this.mode & ~0o777) | perm; this.touch(); } chown(uid, gid) { this.uid = uid; this.gid = gid; this.touch(); } touch() { this.mtime = new Date(); this.emit('change', this); } canRead(uid = getuid(), gid = getgid()) { if (this.perm & 4 /* S.IROTH */) { return true; } if (gid === this.gid) { if (this.perm & 32 /* S.IRGRP */) { return true; } } if (uid === this.uid) { if (this.perm & 256 /* S.IRUSR */) { return true; } } return false; } canWrite(uid = getuid(), gid = getgid()) { if (this.perm & 2 /* S.IWOTH */) { return true; } if (gid === this.gid) { if (this.perm & 16 /* S.IWGRP */) { return true; } } if (uid === this.uid) { if (this.perm & 128 /* S.IWUSR */) { return true; } } return false; } del() { this.emit('delete', this); } toJSON() { return { ino: this.ino, uid: this.uid, gid: this.gid, atime: this.atime.getTime(), mtime: this.mtime.getTime(), ctime: this.ctime.getTime(), perm: this.perm, mode: this.mode, nlink: this.nlink, symlink: this.symlink, data: this.getString(), }; } } exports.Node = Node; /** * Represents a hard link that points to an i-node `node`. */ class Link extends events_1.EventEmitter { get steps() { return this._steps; } // Recursively sync children steps, e.g. in case of dir rename set steps(val) { this._steps = val; for (const [child, link] of this.children.entries()) { if (child === '.' || child === '..') { continue; } link === null || link === void 0 ? void 0 : link.syncSteps(); } } constructor(vol, parent, name) { super(); this.children = new Map(); // Path to this node as Array: ['usr', 'bin', 'node']. this._steps = []; // "i-node" number of the node. this.ino = 0; // Number of children. this.length = 0; this.vol = vol; this.parent = parent; this.name = name; this.syncSteps(); } setNode(node) { this.node = node; this.ino = node.ino; } getNode() { return this.node; } createChild(name, node = this.vol.createNode()) { const link = new Link(this.vol, this, name); link.setNode(node); if (node.isDirectory()) { link.children.set('.', link); link.getNode().nlink++; } this.setChild(name, link); return link; } setChild(name, link = new Link(this.vol, this, name)) { this.children.set(name, link); link.parent = this; this.length++; const node = link.getNode(); if (node.isDirectory()) { link.children.set('..', this); this.getNode().nlink++; } this.getNode().mtime = new Date(); this.emit('child:add', link, this); return link; } deleteChild(link) { const node = link.getNode(); if (node.isDirectory()) { link.children.delete('..'); this.getNode().nlink--; } this.children.delete(link.getName()); this.length--; this.getNode().mtime = new Date(); this.emit('child:delete', link, this); } getChild(name) { this.getNode().mtime = new Date(); return this.children.get(name); } getPath() { return this.steps.join(exports.SEP); } getParentPath() { return this.steps.slice(0, -1).join(exports.SEP); } getName() { return this.steps[this.steps.length - 1]; } // del() { // const parent = this.parent; // if(parent) { // parent.deleteChild(link); // } // this.parent = null; // this.vol = null; // } /** * Walk the tree path and return the `Link` at that location, if any. * @param steps {string[]} Desired location. * @param stop {number} Max steps to go into. * @param i {number} Current step in the `steps` array. * * @return {Link|null} */ walk(steps, stop = steps.length, i = 0) { if (i >= steps.length) return this; if (i >= stop) return this; const step = steps[i]; const link = this.getChild(step); if (!link) return null; return link.walk(steps, stop, i + 1); } toJSON() { return { steps: this.steps, ino: this.ino, children: Array.from(this.children.keys()), }; } syncSteps() { this.steps = this.parent ? this.parent.steps.concat([this.name]) : [this.name]; } } exports.Link = Link; /** * Represents an open file (file descriptor) that points to a `Link` (Hard-link) and a `Node`. */ class File { /** * Open a Link-Node pair. `node` is provided separately as that might be a different node * rather the one `link` points to, because it might be a symlink. * @param link * @param node * @param flags * @param fd */ constructor(link, node, flags, fd) { this.link = link; this.node = node; this.flags = flags; this.fd = fd; this.position = 0; if (this.flags & O_APPEND) this.position = this.getSize(); } getString(encoding = 'utf8') { return this.node.getString(); } setString(str) { this.node.setString(str); } getBuffer() { return this.node.getBuffer(); } setBuffer(buf) { this.node.setBuffer(buf); } getSize() { return this.node.getSize(); } truncate(len) { this.node.truncate(len); } seekTo(position) { this.position = position; } stats() { return Stats_1.default.build(this.node); } write(buf, offset = 0, length = buf.length, position) { if (typeof position !== 'number') position = this.position; const bytes = this.node.write(buf, offset, length, position); this.position = position + bytes; return bytes; } read(buf, offset = 0, length = buf.byteLength, position) { if (typeof position !== 'number') position = this.position; const bytes = this.node.read(buf, offset, length, position); this.position = position + bytes; return bytes; } chmod(perm) { this.node.chmod(perm); } chown(uid, gid) { this.node.chown(uid, gid); } } exports.File = File; //# sourceMappingURL=node.js.map