basant307/AI_Governance_Project
048
1const constants = require('constants');2const path = require('path');3const FileDescriptor = require('./descriptor.js');4const Directory = require('./directory.js');5const {FSError} = require('./error.js');6const File = require('./file.js');7const {getPathParts, getRealPath} = require('./filesystem.js');8const SymbolicLink = require('./symlink.js');9 10const MODE_TO_KTYPE = {11 [constants.S_IFREG]: constants.UV_DIRENT_FILE,12 [constants.S_IFDIR]: constants.UV_DIRENT_DIR,13 [constants.S_IFBLK]: constants.UV_DIRENT_BLOCK,14 [constants.S_IFCHR]: constants.UV_DIRENT_CHAR,15 [constants.S_IFLNK]: constants.UV_DIRENT_LINK,16 [constants.S_IFIFO]: constants.UV_DIRENT_FIFO,17 [constants.S_IFSOCK]: constants.UV_DIRENT_SOCKET,18};19 20/** Workaround for optimizations in node 8+ */21const fsBinding = process.binding('fs');22const kUsePromises = fsBinding.kUsePromises;23let statValues;24let bigintStatValues;25if (fsBinding.statValues) {26 statValues = fsBinding.statValues; // node 10+27 bigintStatValues = fsBinding.bigintStatValues;28}29 30const MAX_LINKS = 50;31 32/**33 * Call the provided function and either return the result or call the callback34 * with it (depending on if a callback is provided).35 * @param {function():void} callback Optional callback.36 * @param {Object} ctx Context object (optional), only for nodejs v10+.37 * @param {Object} thisArg This argument for the following function.38 * @param {function():any} func Function to call.39 * @return {*} Return (if callback is not provided).40 */41function maybeCallback(callback, ctx, thisArg, func) {42 let err = null;43 let val;44 45 if (usePromises(callback)) {46 // support nodejs v10+ fs.promises47 try {48 val = func.call(thisArg);49 } catch (e) {50 err = e;51 }52 return new Promise(function (resolve, reject) {53 setImmediate(function () {54 if (err) {55 reject(err);56 } else {57 resolve(val);58 }59 });60 });61 } else if (callback && typeof callback === 'function') {62 try {63 val = func.call(thisArg);64 } catch (e) {65 err = e;66 }67 setImmediate(function () {68 if (val === undefined) {69 callback(err);70 } else {71 callback(err, val);72 }73 });74 } else if (ctx && typeof ctx === 'object') {75 try {76 return func.call(thisArg);77 } catch (e) {78 // default to errno for UNKNOWN79 ctx.code = e.code || 'UNKNOWN';80 ctx.errno = e.errno || FSError.codes.UNKNOWN.errno;81 }82 } else {83 return func.call(thisArg);84 }85}86 87function usePromises(callback) {88 return kUsePromises && callback === kUsePromises;89}90 91/**92 * set syscall property on context object, only for nodejs v10+.93 * @param {Object} ctx Context object (optional), only for nodejs v10+.94 * @param {string} syscall Name of syscall.95 */96function markSyscall(ctx, syscall) {97 if (ctx && typeof ctx === 'object') {98 ctx.syscall = syscall;99 }100}101 102/**103 * Handle FSReqWrap oncomplete.104 * @param {Function} callback The callback.105 * @return {Function} The normalized callback.106 */107function normalizeCallback(callback) {108 if (callback && typeof callback.oncomplete === 'function') {109 // Unpack callback from FSReqWrap110 callback = callback.oncomplete.bind(callback);111 }112 return callback;113}114 115function getDirentType(mode) {116 const ktype = MODE_TO_KTYPE[mode & constants.S_IFMT];117 118 if (ktype === undefined) {119 return constants.UV_DIRENT_UNKNOWN;120 }121 122 return ktype;123}124 125function notImplemented() {126 throw new Error('Method not implemented');127}128 129function deBuffer(p) {130 return Buffer.isBuffer(p) ? p.toString() : p;131}132 133/**134 * Create a new binding with the given file system.135 * @param {FileSystem} system Mock file system.136 * @class137 */138function Binding(system) {139 /**140 * Mock file system.141 * @type {FileSystem}142 */143 this._system = system;144 145 /**146 * Lookup of open files.147 * @type {Object<number, FileDescriptor>}148 */149 this._openFiles = {};150 151 /**152 * Counter for file descriptors.153 * @type {number}154 */155 this._counter = -1;156 157 const stdin = new FileDescriptor(constants.O_RDWR);158 stdin.setItem(new File.StandardInput());159 this.trackDescriptor(stdin);160 161 const stdout = new FileDescriptor(constants.O_RDWR);162 stdout.setItem(new File.StandardOutput());163 this.trackDescriptor(stdout);164 165 const stderr = new FileDescriptor(constants.O_RDWR);166 stderr.setItem(new File.StandardError());167 this.trackDescriptor(stderr);168}169 170/**171 * Get the file system underlying this binding.172 * @return {FileSystem} The underlying file system.173 */174Binding.prototype.getSystem = function () {175 return this._system;176};177 178/**179 * Reset the file system underlying this binding.180 * @param {FileSystem} system The new file system.181 */182Binding.prototype.setSystem = function (system) {183 this._system = system;184};185 186/**187 * Get a file descriptor.188 * @param {number} fd File descriptor identifier.189 * @return {FileDescriptor} File descriptor.190 */191Binding.prototype.getDescriptorById = function (fd) {192 if (!this._openFiles.hasOwnProperty(fd)) {193 throw new FSError('EBADF');194 }195 return this._openFiles[fd];196};197 198/**199 * Keep track of a file descriptor as open.200 * @param {FileDescriptor} descriptor The file descriptor.201 * @return {number} Identifier for file descriptor.202 */203Binding.prototype.trackDescriptor = function (descriptor) {204 const fd = ++this._counter;205 this._openFiles[fd] = descriptor;206 return fd;207};208 209/**210 * Stop tracking a file descriptor as open.211 * @param {number} fd Identifier for file descriptor.212 */213Binding.prototype.untrackDescriptorById = function (fd) {214 if (!this._openFiles.hasOwnProperty(fd)) {215 throw new FSError('EBADF');216 }217 delete this._openFiles[fd];218};219 220/**221 * Resolve the canonicalized absolute pathname.222 * @param {string|Buffer} filepath The file path.223 * @param {string} encoding The encoding for the return.224 * @param {Function} callback The callback.225 * @param {Object} ctx Context object (optional), only for nodejs v10+.226 * @return {string|Buffer} The real path.227 */228Binding.prototype.realpath = function (filepath, encoding, callback, ctx) {229 markSyscall(ctx, 'realpath');230 231 return maybeCallback(normalizeCallback(callback), ctx, this, function () {232 let realPath;233 filepath = deBuffer(filepath);234 const resolved = path.resolve(filepath);235 const parts = getPathParts(resolved);236 let item = this._system.getRoot();237 let itemPath = '/';238 let name, i, ii;239 for (i = 0, ii = parts.length; i < ii; ++i) {240 name = parts[i];241 while (item instanceof SymbolicLink) {242 itemPath = path.resolve(path.dirname(itemPath), item.getPath());243 item = this._system.getItem(itemPath);244 }245 if (!item) {246 throw new FSError('ENOENT', filepath);247 }248 if (item instanceof Directory) {249 itemPath = path.resolve(itemPath, name);250 item = item.getItem(name);251 } else {252 throw new FSError('ENOTDIR', filepath);253 }254 }255 if (item) {256 while (item instanceof SymbolicLink) {257 itemPath = path.resolve(path.dirname(itemPath), item.getPath());258 item = this._system.getItem(itemPath);259 }260 realPath = itemPath;261 } else {262 throw new FSError('ENOENT', filepath);263 }264 265 // Remove win32 file namespace prefix \\?\266 realPath = getRealPath(realPath);267 268 if (encoding === 'buffer') {269 realPath = Buffer.from(realPath);270 }271 272 return realPath;273 });274};275 276function fillStats(stats, bigint) {277 const target = bigint ? bigintStatValues : statValues;278 for (let i = 0; i < 36; i++) {279 target[i] = stats[i];280 }281}282 283/**284 * Stat an item.285 * @param {string} filepath Path.286 * @param {boolean} bigint Use BigInt.287 * @param {function(Error, Float64Array|BigUint64Array):void} callback Callback (optional).288 * @param {Object} ctx Context object (optional), only for nodejs v10+.289 * @return {Float64Array|BigUint64Array|undefined} Stats or undefined (if sync).290 */291Binding.prototype.stat = function (filepath, bigint, callback, ctx) {292 markSyscall(ctx, 'stat');293 294 return maybeCallback(normalizeCallback(callback), ctx, this, function () {295 filepath = deBuffer(filepath);296 let item = this._system.getItem(filepath);297 if (item instanceof SymbolicLink) {298 item = this._system.getItem(299 path.resolve(path.dirname(filepath), item.getPath()),300 );301 }302 if (!item) {303 throw new FSError('ENOENT', filepath);304 }305 const stats = item.getStats(bigint);306 fillStats(stats, bigint);307 return stats;308 });309};310 311/**312 * Stat an item.313 * @param {string} filepath Path.314 * @param {boolean} bigint Use BigInt.315 * @param {Object} ctx Context object (optional), only for nodejs v10+.316 * @return {Float64Array|BigUint64Array|undefined} Stats or undefined if sync.317 */318Binding.prototype.statSync = function (filepath, bigint, ctx) {319 return this.stat(filepath, bigint, undefined, ctx);320};321 322/**323 * Stat an item.324 * @param {number} fd File descriptor.325 * @param {boolean} bigint Use BigInt.326 * @param {function(Error, Float64Array|BigUint64Array):void} callback Callback (optional).327 * @param {Object} ctx Context object (optional), only for nodejs v10+.328 * @return {Float64Array|BigUint64Array|undefined} Stats or undefined (if sync).329 */330Binding.prototype.fstat = function (fd, bigint, callback, ctx) {331 markSyscall(ctx, 'fstat');332 333 return maybeCallback(normalizeCallback(callback), ctx, this, function () {334 const descriptor = this.getDescriptorById(fd);335 const item = descriptor.getItem();336 const stats = item.getStats(bigint);337 fillStats(stats, bigint);338 return stats;339 });340};341 342/**343 * Close a file descriptor.344 * @param {number} fd File descriptor.345 * @param {function(Error):void} callback Callback (optional).346 * @param {Object} ctx Context object (optional), only for nodejs v10+.347 * @return {*} The return if no callback.348 */349Binding.prototype.close = function (fd, callback, ctx) {350 markSyscall(ctx, 'close');351 352 return maybeCallback(normalizeCallback(callback), ctx, this, function () {353 this.untrackDescriptorById(fd);354 });355};356 357/**358 * Close a file descriptor.359 * @param {number} fd File descriptor.360 * @param {Object} ctx Context object (optional), only for nodejs v10+.361 * @return {*} The return.362 */363Binding.prototype.closeSync = function (fd, ctx) {364 return this.close(fd, undefined, ctx);365};366 367/**368 * Open and possibly create a file.369 * @param {string} pathname File path.370 * @param {number} flags Flags.371 * @param {number} mode Mode.372 * @param {function(Error, string):void} callback Callback (optional).373 * @param {Object} ctx Context object (optional), only for nodejs v10+.374 * @return {string} File descriptor (if sync).375 */376Binding.prototype.open = function (pathname, flags, mode, callback, ctx) {377 markSyscall(ctx, 'open');378 379 return maybeCallback(normalizeCallback(callback), ctx, this, function () {380 pathname = deBuffer(pathname);381 const descriptor = new FileDescriptor(flags, usePromises(callback));382 let item = this._system.getItem(pathname);383 while (item instanceof SymbolicLink) {384 item = this._system.getItem(385 path.resolve(path.dirname(pathname), item.getPath()),386 );387 }388 if (descriptor.isExclusive() && item) {389 throw new FSError('EEXIST', pathname);390 }391 if (descriptor.isCreate() && !item) {392 const parent = this._system.getItem(path.dirname(pathname));393 if (!parent) {394 throw new FSError('ENOENT', pathname);395 }396 if (!(parent instanceof Directory)) {397 throw new FSError('ENOTDIR', pathname);398 }399 item = new File();400 if (mode) {401 item.setMode(mode);402 }403 parent.addItem(path.basename(pathname), item);404 }405 if (descriptor.isRead()) {406 if (!item) {407 throw new FSError('ENOENT', pathname);408 }409 if (!item.canRead()) {410 throw new FSError('EACCES', pathname);411 }412 }413 if (descriptor.isWrite() && !item.canWrite()) {414 throw new FSError('EACCES', pathname);415 }416 if (417 item instanceof Directory &&418 (descriptor.isTruncate() || descriptor.isAppend())419 ) {420 throw new FSError('EISDIR', pathname);421 }422 if (descriptor.isTruncate()) {423 if (!(item instanceof File)) {424 throw new FSError('EBADF');425 }426 item.setContent('');427 }428 if (descriptor.isTruncate() || descriptor.isAppend()) {429 descriptor.setPosition(item.getContent().length);430 }431 descriptor.setItem(item);432 return this.trackDescriptor(descriptor);433 });434};435 436/**437 * Open and possibly create a file.438 * @param {string} pathname File path.439 * @param {number} flags Flags.440 * @param {number} mode Mode.441 * @param {Object} ctx Context object (optional), only for nodejs v10+.442 * @return {string} File descriptor.443 */444Binding.prototype.openSync = function (pathname, flags, mode, ctx) {445 return this.open(pathname, flags, mode, undefined, ctx);446};447 448/**449 * Open a file handler. A new api in nodejs v10+ for fs.promises450 * @param {string} pathname File path.451 * @param {number} flags Flags.452 * @param {number} mode Mode.453 * @param {Function} callback Callback (optional), expecting kUsePromises in nodejs v10+.454 * @return {string} The file handle.455 */456Binding.prototype.openFileHandle = function (pathname, flags, mode, callback) {457 const self = this;458 459 return this.open(pathname, flags, mode, kUsePromises).then(function (fd) {460 // nodejs v10+ fs.promises FileHandler constructor only ask these three properties.461 return {462 getAsyncId: notImplemented,463 fd: fd,464 close: function () {465 return self.close(fd, kUsePromises);466 },467 };468 });469};470 471/**472 * Read from a file descriptor.473 * @param {string} fd File descriptor.474 * @param {Buffer} buffer Buffer that the contents will be written to.475 * @param {number} offset Offset in the buffer to start writing to.476 * @param {number} length Number of bytes to read.477 * @param {?number} position Where to begin reading in the file. If null,478 * data will be read from the current file position.479 * @param {function(Error, number, Buffer):void} callback Callback (optional) called480 * with any error, number of bytes read, and the buffer.481 * @param {Object} ctx Context object (optional), only for nodejs v10+.482 * @return {number} Number of bytes read (if sync).483 */484Binding.prototype.read = function (485 fd,486 buffer,487 offset,488 length,489 position,490 callback,491 ctx,492) {493 markSyscall(ctx, 'read');494 495 return maybeCallback(normalizeCallback(callback), ctx, this, function () {496 const descriptor = this.getDescriptorById(fd);497 if (!descriptor.isRead()) {498 throw new FSError('EBADF');499 }500 const file = descriptor.getItem();501 if (file instanceof Directory) {502 throw new FSError('EISDIR');503 }504 if (!(file instanceof File)) {505 // deleted or not a regular file506 throw new FSError('EBADF');507 }508 if (typeof position !== 'number' || position < 0) {509 position = descriptor.getPosition();510 }511 const content = file.getContent();512 const start = Math.min(position, content.length);513 const end = Math.min(position + length, content.length);514 const read = start < end ? content.copy(buffer, offset, start, end) : 0;515 descriptor.setPosition(position + read);516 return read;517 });518};519 520/**521 * Write to a file descriptor given a buffer.522 * @param {string} src Source file.523 * @param {string} dest Destination file.524 * @param {number} flags Modifiers for copy operation.525 * @param {function(Error):void} callback Callback (optional) called526 * with any error.527 * @param {Object} ctx Context object (optional), only for nodejs v10+.528 * @return {*} The return if no callback is provided.529 */530Binding.prototype.copyFile = function (src, dest, flags, callback, ctx) {531 markSyscall(ctx, 'copyfile');532 533 return maybeCallback(normalizeCallback(callback), ctx, this, function () {534 src = deBuffer(src);535 dest = deBuffer(dest);536 const srcFd = this.open(src, constants.O_RDONLY);537 538 try {539 const srcDescriptor = this.getDescriptorById(srcFd);540 if (!srcDescriptor.isRead()) {541 throw new FSError('EBADF');542 }543 const srcFile = srcDescriptor.getItem();544 if (!(srcFile instanceof File)) {545 throw new FSError('EBADF');546 }547 const srcContent = srcFile.getContent();548 549 let destFlags =550 constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC;551 552 if ((flags & constants.COPYFILE_EXCL) === constants.COPYFILE_EXCL) {553 destFlags |= constants.O_EXCL;554 }555 556 const destFd = this.open(dest, destFlags);557 558 try {559 this.writeBuffer(destFd, srcContent, 0, srcContent.length, 0);560 } finally {561 this.close(destFd);562 }563 } finally {564 this.close(srcFd);565 }566 });567};568 569/**570 * Write to a file descriptor given a buffer.571 * @param {string} src Source file.572 * @param {string} dest Destination file.573 * @param {number} flags Modifiers for copy operation.574 * @param {Object} ctx Context object (optional), only for nodejs v10+.575 * @return {*} The return if no callback is provided.576 */577Binding.prototype.copyFileSync = function (src, dest, flags, ctx) {578 return this.copyFile(src, dest, flags, undefined, ctx);579};580 581/**582 * Write to a file descriptor given a buffer.583 * @param {string} fd File descriptor.584 * @param {Array<Buffer>} buffers Array of buffers with contents to write.585 * @param {?number} position Where to begin writing in the file. If null,586 * data will be written to the current file position.587 * @param {function(Error, number, Buffer):void} callback Callback (optional) called588 * with any error, number of bytes written, and the buffer.589 * @param {Object} ctx Context object (optional), only for nodejs v10+.590 * @return {number} Number of bytes written (if sync).591 */592Binding.prototype.writeBuffers = function (593 fd,594 buffers,595 position,596 callback,597 ctx,598) {599 markSyscall(ctx, 'write');600 601 return maybeCallback(normalizeCallback(callback), ctx, this, function () {602 const descriptor = this.getDescriptorById(fd);603 if (!descriptor.isWrite()) {604 throw new FSError('EBADF');605 }606 const file = descriptor.getItem();607 if (!(file instanceof File)) {608 // not a regular file609 throw new FSError('EBADF');610 }611 if (typeof position !== 'number' || position < 0) {612 position = descriptor.getPosition();613 }614 let content = file.getContent();615 const newContent = Buffer.concat(buffers);616 const newLength = position + newContent.length;617 if (content.length < newLength) {618 const tempContent = Buffer.alloc(newLength);619 content.copy(tempContent);620 content = tempContent;621 }622 const written = newContent.copy(content, position);623 file.setContent(content);624 descriptor.setPosition(newLength);625 return written;626 });627};628 629/**630 * Write to a file descriptor given a buffer.631 * @param {string} fd File descriptor.632 * @param {Buffer} buffer Buffer with contents to write.633 * @param {number} offset Offset in the buffer to start writing from.634 * @param {number} length Number of bytes to write.635 * @param {?number} position Where to begin writing in the file. If null,636 * data will be written to the current file position.637 * @param {function(Error, number, Buffer):void} callback Callback (optional) called638 * with any error, number of bytes written, and the buffer.639 * @param {Object} ctx Context object (optional), only for nodejs v10+.640 * @return {number} Number of bytes written (if sync).641 */642Binding.prototype.writeBuffer = function (643 fd,644 buffer,645 offset,646 length,647 position,648 callback,649 ctx,650) {651 markSyscall(ctx, 'write');652 653 return maybeCallback(normalizeCallback(callback), ctx, this, function () {654 const descriptor = this.getDescriptorById(fd);655 if (!descriptor.isWrite()) {656 throw new FSError('EBADF');657 }658 const file = descriptor.getItem();659 if (!(file instanceof File)) {660 // not a regular file661 throw new FSError('EBADF');662 }663 if (typeof position !== 'number' || position < 0) {664 position = descriptor.getPosition();665 }666 let content = file.getContent();667 const newLength = position + length;668 if (content.length < newLength) {669 const newContent = Buffer.alloc(newLength);670 content.copy(newContent);671 content = newContent;672 }673 const sourceEnd = Math.min(offset + length, buffer.length);674 const written = Buffer.from(buffer).copy(675 content,676 position,677 offset,678 sourceEnd,679 );680 file.setContent(content);681 descriptor.setPosition(newLength);682 // If we're in fs.promises / FileHandle we need to return a promise683 // Both fs.promises.open().then(fd => fs.write())684 // and fs.openSync().writeSync() use this function685 // without a callback, so we have to check if the descriptor was opened686 // with kUsePromises687 return descriptor.isPromise() ? Promise.resolve(written) : written;688 });689};690 691/**692 * Write to a file descriptor given a string.693 * @param {string} fd File descriptor.694 * @param {string} string String with contents to write.695 * @param {number} position Where to begin writing in the file. If null,696 * data will be written to the current file position.697 * @param {string} encoding String encoding.698 * @param {function(Error, number, string):void} callback Callback (optional) called699 * with any error, number of bytes written, and the string.700 * @param {Object} ctx The context.701 * @return {number} Number of bytes written (if sync).702 */703Binding.prototype.writeString = function (704 fd,705 string,706 position,707 encoding,708 callback,709 ctx,710) {711 markSyscall(ctx, 'write');712 713 const buffer = Buffer.from(string, encoding);714 let wrapper;715 if (callback && callback !== kUsePromises) {716 if (callback.oncomplete) {717 callback = callback.oncomplete.bind(callback);718 }719 wrapper = function (err, written, returned) {720 callback(err, written, returned && string);721 };722 }723 return this.writeBuffer(fd, buffer, 0, buffer.length, position, wrapper, ctx);724};725 726/**727 * Rename a file.728 * @param {string} oldPath Old pathname.729 * @param {string} newPath New pathname.730 * @param {function(Error):void} callback Callback (optional).731 * @param {Object} ctx Context object (optional), only for nodejs v10+.732 * @return {undefined}733 */734Binding.prototype.rename = function (oldPath, newPath, callback, ctx) {735 markSyscall(ctx, 'rename');736 737 return maybeCallback(normalizeCallback(callback), ctx, this, function () {738 oldPath = deBuffer(oldPath);739 newPath = deBuffer(newPath);740 const oldItem = this._system.getItem(oldPath);741 if (!oldItem) {742 throw new FSError('ENOENT', oldPath);743 }744 const oldParent = this._system.getItem(path.dirname(oldPath));745 const oldName = path.basename(oldPath);746 const newItem = this._system.getItem(newPath);747 const newParent = this._system.getItem(path.dirname(newPath));748 const newName = path.basename(newPath);749 if (newItem) {750 // make sure they are the same type751 if (oldItem instanceof File) {752 if (newItem instanceof Directory) {753 throw new FSError('EISDIR', newPath);754 }755 } else if (oldItem instanceof Directory) {756 if (!(newItem instanceof Directory)) {757 throw new FSError('ENOTDIR', newPath);758 }759 if (newItem.list().length > 0) {760 throw new FSError('ENOTEMPTY', newPath);761 }762 }763 newParent.removeItem(newName);764 } else {765 if (!newParent) {766 throw new FSError('ENOENT', newPath);767 }768 if (!(newParent instanceof Directory)) {769 throw new FSError('ENOTDIR', newPath);770 }771 }772 oldParent.removeItem(oldName);773 newParent.addItem(newName, oldItem);774 });775};776 777/**778 * Rename a file.779 * @param {string} oldPath Old pathname.780 * @param {string} newPath New pathname.781 * @param {Object} ctx Context object (optional), only for nodejs v10+.782 * @return {undefined}783 */784Binding.prototype.renameSync = function (oldPath, newPath, ctx) {785 return this.rename(oldPath, newPath, undefined, ctx);786};787 788/**789 * Read a directory.790 * @param {string} dirpath Path to directory.791 * @param {string} encoding The encoding ('utf-8' or 'buffer').792 * @param {boolean} withFileTypes whether or not to return fs.Dirent objects793 * @param {function(Error, (Array<string> | Array<Buffer>)): void} callback Callback794 * (optional) called with any error or array of items in the directory.795 * @param {Object} ctx Context object (optional), only for nodejs v10+.796 * @return {Array<string> | Array<Buffer>} Array of items in directory (if sync).797 */798Binding.prototype.readdir = function (799 dirpath,800 encoding,801 withFileTypes,802 callback,803 ctx,804) {805 markSyscall(ctx, 'scandir');806 807 return maybeCallback(normalizeCallback(callback), ctx, this, function () {808 dirpath = deBuffer(dirpath);809 let dpath = dirpath;810 let dir = this._system.getItem(dirpath);811 while (dir instanceof SymbolicLink) {812 dpath = path.resolve(path.dirname(dpath), dir.getPath());813 dir = this._system.getItem(dpath);814 }815 if (!dir) {816 throw new FSError('ENOENT', dirpath);817 }818 if (!(dir instanceof Directory)) {819 throw new FSError('ENOTDIR', dirpath);820 }821 if (!dir.canRead()) {822 throw new FSError('EACCES', dirpath);823 }824 825 let list = dir.list();826 if (encoding === 'buffer') {827 list = list.map(function (item) {828 return Buffer.from(item);829 });830 }831 832 if (withFileTypes === true) {833 const types = list.map(function (name) {834 const stats = dir.getItem(name).getStats();835 836 return getDirentType(stats.mode);837 });838 list = [list, types];839 }840 841 return list;842 });843};844 845/**846 * Read file as utf8 string.847 * @param {string} name file to write.848 * @param {number} flags Flags.849 * @return {string} the file content.850 */851Binding.prototype.readFileUtf8 = function (name, flags) {852 const fd = this.open(name, flags);853 const descriptor = this.getDescriptorById(fd);854 855 if (!descriptor.isRead()) {856 throw new FSError('EBADF');857 }858 const file = descriptor.getItem();859 if (file instanceof Directory) {860 throw new FSError('EISDIR');861 }862 if (!(file instanceof File)) {863 // deleted or not a regular file864 throw new FSError('EBADF');865 }866 const content = file.getContent();867 return content.toString('utf8');868};869 870/**871 * Write a utf8 string.872 * @param {string} filepath file to write.873 * @param {string} data data to write to filepath.874 * @param {number} flags Flags.875 * @param {number} mode Mode.876 */877Binding.prototype.writeFileUtf8 = function (filepath, data, flags, mode) {878 const destFd = this.open(filepath, flags, mode);879 this.writeString(destFd, data, null, 'utf8');880};881 882/**883 * Create a directory.884 * @param {string} pathname Path to new directory.885 * @param {number} mode Permissions.886 * @param {boolean} recursive Recursively create deep directory. (added in nodejs v10+)887 * @param {function(Error):void} callback Optional callback.888 * @param {Object} ctx Context object (optional), only for nodejs v10+.889 * @return {*} The return if no callback is provided.890 */891Binding.prototype.mkdir = function (pathname, mode, recursive, callback, ctx) {892 markSyscall(ctx, 'mkdir');893 894 return maybeCallback(normalizeCallback(callback), ctx, this, function () {895 pathname = deBuffer(pathname);896 const item = this._system.getItem(pathname);897 if (item) {898 if (recursive && item instanceof Directory) {899 // silently pass existing folder in recursive mode900 return;901 }902 throw new FSError('EEXIST', pathname);903 }904 905 const _mkdir = function (_pathname) {906 const parentDir = path.dirname(_pathname);907 let parent = this._system.getItem(parentDir);908 if (!parent) {909 if (!recursive) {910 throw new FSError('ENOENT', _pathname);911 }912 parent = _mkdir(parentDir, true);913 }914 this.access(parentDir, parseInt('0002', 8));915 const dir = new Directory();916 if (mode) {917 dir.setMode(mode);918 }919 return parent.addItem(path.basename(_pathname), dir);920 }.bind(this);921 922 _mkdir(pathname);923 });924};925 926/**927 * Remove a directory.928 * @param {string} pathname Path to directory.929 * @param {function(Error):void} callback Optional callback.930 * @param {Object} ctx Context object (optional), only for nodejs v10+.931 * @return {*} The return if no callback is provided.932 */933Binding.prototype.rmdir = function (pathname, callback, ctx) {934 markSyscall(ctx, 'rmdir');935 936 return maybeCallback(normalizeCallback(callback), ctx, this, function () {937 pathname = deBuffer(pathname);938 const item = this._system.getItem(pathname);939 if (!item) {940 throw new FSError('ENOENT', pathname);941 }942 if (!(item instanceof Directory)) {943 throw new FSError('ENOTDIR', pathname);944 }945 if (item.list().length > 0) {946 throw new FSError('ENOTEMPTY', pathname);947 }948 this.access(path.dirname(pathname), parseInt('0002', 8));949 const parent = this._system.getItem(path.dirname(pathname));950 parent.removeItem(path.basename(pathname));951 });952};953 954const PATH_CHARS =955 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';956 957const MAX_ATTEMPTS = 62 * 62 * 62;958 959/**960 * Create a directory based on a template.961 * See http://web.mit.edu/freebsd/head/lib/libc/stdio/mktemp.c962 * @param {string} prefix Path template (trailing Xs will be replaced).963 * @param {string} encoding The encoding ('utf-8' or 'buffer').964 * @param {function(Error, string):void} callback Optional callback.965 * @param {Object} ctx Context object (optional), only for nodejs v10+.966 * @return {*} The return if no callback is provided.967 */968Binding.prototype.mkdtemp = function (prefix, encoding, callback, ctx) {969 if (encoding && typeof encoding !== 'string') {970 callback = encoding;971 encoding = 'utf-8';972 }973 974 markSyscall(ctx, 'mkdtemp');975 976 return maybeCallback(normalizeCallback(callback), ctx, this, function () {977 prefix = prefix.replace(/X{0,6}$/, 'XXXXXX');978 const parentPath = path.dirname(prefix);979 const parent = this._system.getItem(parentPath);980 if (!parent) {981 throw new FSError('ENOENT', prefix);982 }983 if (!(parent instanceof Directory)) {984 throw new FSError('ENOTDIR', prefix);985 }986 this.access(parentPath, parseInt('0002', 8));987 const template = path.basename(prefix);988 let unique = false;989 let count = 0;990 let name;991 while (!unique && count < MAX_ATTEMPTS) {992 let position = template.length - 1;993 let replacement = '';994 while (template.charAt(position) === 'X') {995 replacement += PATH_CHARS.charAt(996 Math.floor(PATH_CHARS.length * Math.random()),997 );998 position -= 1;999 }1000 const candidate = template.slice(0, position + 1) + replacement;1001 if (!parent.getItem(candidate)) {1002 name = candidate;1003 unique = true;1004 }1005 count += 1;1006 }1007 if (!name) {1008 throw new FSError('EEXIST', prefix);1009 }1010 const dir = new Directory();1011 parent.addItem(name, dir);1012 let uniquePath = path.join(parentPath, name);1013 if (encoding === 'buffer') {1014 uniquePath = Buffer.from(uniquePath);1015 }1016 return uniquePath;1017 });1018};1019 1020/**1021 * Truncate a file.1022 * @param {number} fd File descriptor.1023 * @param {number} len Number of bytes.1024 * @param {function(Error):void} callback Optional callback.1025 * @param {Object} ctx Context object (optional), only for nodejs v10+.1026 * @return {*} The return if no callback is provided.1027 */1028Binding.prototype.ftruncate = function (fd, len, callback, ctx) {1029 markSyscall(ctx, 'ftruncate');1030 1031 return maybeCallback(normalizeCallback(callback), ctx, this, function () {1032 const descriptor = this.getDescriptorById(fd);1033 if (!descriptor.isWrite()) {1034 throw new FSError('EINVAL');1035 }1036 const file = descriptor.getItem();1037 if (!(file instanceof File)) {1038 throw new FSError('EINVAL');1039 }1040 const content = file.getContent();1041 const newContent = Buffer.alloc(len);1042 content.copy(newContent);1043 file.setContent(newContent);1044 });1045};1046 1047/**1048 * Legacy support.1049 * @param {number} fd File descriptor.1050 * @param {number} len Number of bytes.1051 * @param {function(Error):void} callback Optional callback.1052 * @param {Object} ctx Context object (optional), only for nodejs v10+.1053 */1054Binding.prototype.truncate = Binding.prototype.ftruncate;1055 1056/**1057 * Change user and group owner.1058 * @param {string} pathname Path.1059 * @param {number} uid User id.1060 * @param {number} gid Group id.1061 * @param {function(Error):void} callback Optional callback.1062 * @param {Object} ctx Context object (optional), only for nodejs v10+.1063 * @return {*} The return if no callback is provided.1064 */1065Binding.prototype.chown = function (pathname, uid, gid, callback, ctx) {1066 markSyscall(ctx, 'chown');1067 1068 return maybeCallback(normalizeCallback(callback), ctx, this, function () {1069 pathname = deBuffer(pathname);1070 const item = this._system.getItem(pathname);1071 if (!item) {1072 throw new FSError('ENOENT', pathname);1073 }1074 item.setUid(uid);1075 item.setGid(gid);1076 });1077};1078 1079/**1080 * Change user and group owner.1081 * @param {number} fd File descriptor.1082 * @param {number} uid User id.1083 * @param {number} gid Group id.1084 * @param {function(Error):void} callback Optional callback.1085 * @param {Object} ctx Context object (optional), only for nodejs v10+.1086 * @return {*} The return if no callback is provided.1087 */1088Binding.prototype.fchown = function (fd, uid, gid, callback, ctx) {1089 markSyscall(ctx, 'fchown');1090 1091 return maybeCallback(normalizeCallback(callback), ctx, this, function () {1092 const descriptor = this.getDescriptorById(fd);1093 const item = descriptor.getItem();1094 item.setUid(uid);1095 item.setGid(gid);1096 });1097};1098 1099/**1100 * Change permissions.1101 * @param {string} pathname Path.1102 * @param {number} mode Mode.1103 * @param {function(Error):void} callback Optional callback.1104 * @param {Object} ctx Context object (optional), only for nodejs v10+.1105 * @return {*} The return if no callback is provided.1106 */1107Binding.prototype.chmod = function (pathname, mode, callback, ctx) {1108 markSyscall(ctx, 'chmod');1109 1110 return maybeCallback(normalizeCallback(callback), ctx, this, function () {1111 pathname = deBuffer(pathname);1112 const item = this._system.getItem(pathname);1113 if (!item) {1114 throw new FSError('ENOENT', pathname);1115 }1116 item.setMode(mode);1117 });1118};1119 1120/**1121 * Change permissions.1122 * @param {number} fd File descriptor.1123 * @param {number} mode Mode.1124 * @param {function(Error):void} callback Optional callback.1125 * @param {Object} ctx Context object (optional), only for nodejs v10+.1126 * @return {*} The return if no callback is provided.1127 */1128Binding.prototype.fchmod = function (fd, mode, callback, ctx) {1129 markSyscall(ctx, 'fchmod');1130 1131 return maybeCallback(normalizeCallback(callback), ctx, this, function () {1132 const descriptor = this.getDescriptorById(fd);1133 const item = descriptor.getItem();1134 item.setMode(mode);1135 });1136};1137 1138/**1139 * Delete a named item.1140 * @param {string} pathname Path to item.1141 * @param {function(Error):void} callback Optional callback.1142 * @param {Object} ctx Context object (optional), only for nodejs v10+.1143 * @return {*} The return if no callback is provided.1144 */1145Binding.prototype.unlink = function (pathname, callback, ctx) {1146 markSyscall(ctx, 'unlink');1147 1148 return maybeCallback(normalizeCallback(callback), ctx, this, function () {1149 pathname = deBuffer(pathname);1150 const item = this._system.getItem(pathname);1151 if (!item) {1152 throw new FSError('ENOENT', pathname);1153 }1154 if (item instanceof Directory) {1155 throw new FSError('EPERM', pathname);1156 }1157 const parent = this._system.getItem(path.dirname(pathname));1158 parent.removeItem(path.basename(pathname));1159 });1160};1161 1162/**1163 * Delete a named item.1164 * @param {string} pathname Path to item.1165 * @param {Object} ctx Context object (optional), only for nodejs v10+.1166 * @return {*} The return if no callback is provided.1167 */1168Binding.prototype.unlinkSync = function (pathname, ctx) {1169 return this.unlink(pathname, undefined, ctx);1170};1171 1172/**1173 * Update timestamps.1174 * @param {string} pathname Path to item.1175 * @param {number} atime Access time (in seconds).1176 * @param {number} mtime Modification time (in seconds).1177 * @param {function(Error):void} callback Optional callback.1178 * @param {Object} ctx Context object (optional), only for nodejs v10+.1179 * @return {*} The return if no callback is provided.1180 */1181Binding.prototype.utimes = function (pathname, atime, mtime, callback, ctx) {1182 markSyscall(ctx, 'utimes');1183 1184 return maybeCallback(normalizeCallback(callback), ctx, this, function () {1185 let filepath = deBuffer(pathname);1186 let item = this._system.getItem(filepath);1187 let links = 0;1188 while (item instanceof SymbolicLink) {1189 if (links > MAX_LINKS) {1190 throw new FSError('ELOOP', filepath);1191 }1192 filepath = path.resolve(path.dirname(filepath), item.getPath());1193 item = this._system.getItem(filepath);1194 ++links;1195 }1196 if (!item) {1197 throw new FSError('ENOENT', pathname);1198 }1199 item.setATime(new Date(atime * 1000));1200 item.setMTime(new Date(mtime * 1000));