| // Copyright Joyent, Inc. and other Node contributors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the |
| // "Software"), to deal in the Software without restriction, including |
| // without limitation the rights to use, copy, modify, merge, publish, |
| // distribute, sublicense, and/or sell copies of the Software, and to permit |
| // persons to whom the Software is furnished to do so, subject to the |
| // following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included |
| // in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| // 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 { fs: constants } = internalBinding('constants'); |
| const { |
| S_IFIFO, |
| S_IFLNK, |
| S_IFMT, |
| S_IFREG, |
| S_IFSOCK, |
| F_OK, |
| R_OK, |
| W_OK, |
| X_OK, |
| O_WRONLY, |
| O_SYMLINK |
| } = constants; |
| |
| const { _extend } = require('util'); |
| const pathModule = require('path'); |
| const { isArrayBufferView } = require('internal/util/types'); |
| const binding = process.binding('fs'); |
| const { Buffer, kMaxLength } = require('buffer'); |
| const errors = require('internal/errors'); |
| const { |
| ERR_FS_FILE_TOO_LARGE, |
| ERR_INVALID_ARG_VALUE, |
| ERR_INVALID_ARG_TYPE, |
| ERR_INVALID_CALLBACK |
| } = errors.codes; |
| |
| const { FSReqCallback, statValues } = binding; |
| const internalFS = require('internal/fs/utils'); |
| const { toPathIfFileURL } = require('internal/url'); |
| const internalUtil = require('internal/util'); |
| const { |
| copyObject, |
| Dirent, |
| getDirents, |
| getOptions, |
| nullCheck, |
| preprocessSymlinkDestination, |
| Stats, |
| getStatsFromBinding, |
| realpathCacheKey, |
| stringToFlags, |
| stringToSymlinkType, |
| toUnixTimestamp, |
| validateBuffer, |
| validateOffsetLengthRead, |
| validateOffsetLengthWrite, |
| validatePath |
| } = internalFS; |
| const { |
| CHAR_FORWARD_SLASH, |
| CHAR_BACKWARD_SLASH, |
| } = require('internal/constants'); |
| const { |
| isUint32, |
| validateMode, |
| validateInteger, |
| validateInt32, |
| validateUint32 |
| } = require('internal/validators'); |
| |
| let promisesWarn = true; |
| let truncateWarn = true; |
| let fs; |
| |
| // Lazy loaded |
| let promises; |
| let watchers; |
| let ReadFileContext; |
| let ReadStream; |
| let WriteStream; |
| |
| // These have to be separate because of how graceful-fs happens to do it's |
| // monkeypatching. |
| let FileReadStream; |
| let FileWriteStream; |
| |
| const isWindows = process.platform === 'win32'; |
| |
| |
| function showTruncateDeprecation() { |
| if (truncateWarn) { |
| process.emitWarning( |
| 'Using fs.truncate with a file descriptor is deprecated. Please use ' + |
| 'fs.ftruncate with a file descriptor instead.', |
| 'DeprecationWarning', 'DEP0081'); |
| truncateWarn = false; |
| } |
| } |
| |
| function handleErrorFromBinding(ctx) { |
| if (ctx.errno !== undefined) { // libuv error numbers |
| const err = errors.uvException(ctx); |
| Error.captureStackTrace(err, handleErrorFromBinding); |
| throw err; |
| } else if (ctx.error !== undefined) { // errors created in C++ land. |
| // TODO(joyeecheung): currently, ctx.error are encoding errors |
| // usually caused by memory problems. We need to figure out proper error |
| // code(s) for this. |
| Error.captureStackTrace(ctx.error, handleErrorFromBinding); |
| throw ctx.error; |
| } |
| } |
| |
| function maybeCallback(cb) { |
| if (typeof cb === 'function') |
| return cb; |
| |
| throw new ERR_INVALID_CALLBACK(); |
| } |
| |
| // Ensure that callbacks run in the global context. Only use this function |
| // for callbacks that are passed to the binding layer, callbacks that are |
| // invoked from JS already run in the proper scope. |
| function makeCallback(cb) { |
| if (typeof cb !== 'function') { |
| throw new ERR_INVALID_CALLBACK(); |
| } |
| |
| return function(...args) { |
| return Reflect.apply(cb, undefined, args); |
| }; |
| } |
| |
| // Special case of `makeCallback()` that is specific to async `*stat()` calls as |
| // an optimization, since the data passed back to the callback needs to be |
| // transformed anyway. |
| function makeStatsCallback(cb) { |
| if (typeof cb !== 'function') { |
| throw new ERR_INVALID_CALLBACK(); |
| } |
| |
| return function(err, stats) { |
| if (err) return cb(err); |
| cb(err, getStatsFromBinding(stats)); |
| }; |
| } |
| |
| const isFd = isUint32; |
| |
| function isFileType(stats, fileType) { |
| // Use stats array directly to avoid creating an fs.Stats instance just for |
| // our internal use. |
| return (stats[1/* mode */] & S_IFMT) === fileType; |
| } |
| |
| function access(path, mode, callback) { |
| if (typeof mode === 'function') { |
| callback = mode; |
| mode = F_OK; |
| } |
| |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| |
| mode = mode | 0; |
| const req = new FSReqCallback(); |
| req.oncomplete = makeCallback(callback); |
| binding.access(pathModule.toNamespacedPath(path), mode, req); |
| } |
| |
| function accessSync(path, mode) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| |
| if (mode === undefined) |
| mode = F_OK; |
| else |
| mode = mode | 0; |
| |
| const ctx = { path }; |
| binding.access(pathModule.toNamespacedPath(path), mode, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function exists(path, callback) { |
| maybeCallback(callback); |
| |
| function suppressedCallback(err) { |
| callback(err ? false : true); |
| } |
| |
| try { |
| fs.access(path, F_OK, suppressedCallback); |
| } catch (err) { |
| return callback(false); |
| } |
| } |
| |
| Object.defineProperty(exists, internalUtil.promisify.custom, { |
| value: (path) => { |
| return new Promise((resolve) => fs.exists(path, resolve)); |
| } |
| }); |
| |
| // fs.existsSync never throws, it only returns true or false. |
| // Since fs.existsSync never throws, users have established |
| // the expectation that passing invalid arguments to it, even like |
| // fs.existsSync(), would only get a false in return, so we cannot signal |
| // validation errors to users properly out of compatibility concerns. |
| // TODO(joyeecheung): deprecate the never-throw-on-invalid-arguments behavior |
| function existsSync(path) { |
| try { |
| fs.accessSync(path, F_OK); |
| return true; |
| } catch (e) { |
| return false; |
| } |
| } |
| |
| function readFileAfterOpen(err, fd) { |
| const context = this.context; |
| |
| if (err) { |
| context.callback(err); |
| return; |
| } |
| |
| context.fd = fd; |
| |
| const req = new FSReqCallback(); |
| req.oncomplete = readFileAfterStat; |
| req.context = context; |
| binding.fstat(fd, false, req); |
| } |
| |
| function readFileAfterStat(err, stats) { |
| const context = this.context; |
| |
| if (err) |
| return context.close(err); |
| |
| const size = context.size = isFileType(stats, S_IFREG) ? stats[8] : 0; |
| |
| if (size === 0) { |
| context.buffers = []; |
| context.read(); |
| return; |
| } |
| |
| if (size > kMaxLength) { |
| err = new ERR_FS_FILE_TOO_LARGE(size); |
| return context.close(err); |
| } |
| |
| try { |
| context.buffer = Buffer.allocUnsafeSlow(size); |
| } catch (err) { |
| return context.close(err); |
| } |
| context.read(); |
| } |
| |
| function readFile(path, options, callback) { |
| callback = maybeCallback(callback || options); |
| options = getOptions(options, { flag: 'r' }); |
| if (!ReadFileContext) |
| ReadFileContext = require('internal/fs/read_file_context'); |
| const context = new ReadFileContext(callback, options.encoding); |
| context.isUserFd = isFd(path); // file descriptor ownership |
| |
| const req = new FSReqCallback(); |
| req.context = context; |
| req.oncomplete = readFileAfterOpen; |
| |
| if (context.isUserFd) { |
| process.nextTick(function tick() { |
| req.oncomplete(null, path); |
| }); |
| return; |
| } |
| |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| binding.open(pathModule.toNamespacedPath(path), |
| stringToFlags(options.flag || 'r'), |
| 0o666, |
| req); |
| } |
| |
| function tryStatSync(fd, isUserFd) { |
| const ctx = {}; |
| const stats = binding.fstat(fd, false, undefined, ctx); |
| if (ctx.errno !== undefined && !isUserFd) { |
| fs.closeSync(fd); |
| throw errors.uvException(ctx); |
| } |
| return stats; |
| } |
| |
| function tryCreateBuffer(size, fd, isUserFd) { |
| let threw = true; |
| let buffer; |
| try { |
| if (size > kMaxLength) { |
| throw new ERR_FS_FILE_TOO_LARGE(size); |
| } |
| buffer = Buffer.allocUnsafe(size); |
| threw = false; |
| } finally { |
| if (threw && !isUserFd) fs.closeSync(fd); |
| } |
| return buffer; |
| } |
| |
| function tryReadSync(fd, isUserFd, buffer, pos, len) { |
| let threw = true; |
| let bytesRead; |
| try { |
| bytesRead = fs.readSync(fd, buffer, pos, len); |
| threw = false; |
| } finally { |
| if (threw && !isUserFd) fs.closeSync(fd); |
| } |
| return bytesRead; |
| } |
| |
| function readFileSync(path, options) { |
| options = getOptions(options, { flag: 'r' }); |
| const isUserFd = isFd(path); // file descriptor ownership |
| const fd = isUserFd ? path : fs.openSync(path, options.flag || 'r', 0o666); |
| |
| const stats = tryStatSync(fd, isUserFd); |
| const size = isFileType(stats, S_IFREG) ? stats[8] : 0; |
| let pos = 0; |
| let buffer; // single buffer with file data |
| let buffers; // list for when size is unknown |
| |
| if (size === 0) { |
| buffers = []; |
| } else { |
| buffer = tryCreateBuffer(size, fd, isUserFd); |
| } |
| |
| let bytesRead; |
| |
| if (size !== 0) { |
| do { |
| bytesRead = tryReadSync(fd, isUserFd, buffer, pos, size - pos); |
| pos += bytesRead; |
| } while (bytesRead !== 0 && pos < size); |
| } else { |
| do { |
| // the kernel lies about many files. |
| // Go ahead and try to read some bytes. |
| buffer = Buffer.allocUnsafe(8192); |
| bytesRead = tryReadSync(fd, isUserFd, buffer, 0, 8192); |
| if (bytesRead !== 0) { |
| buffers.push(buffer.slice(0, bytesRead)); |
| } |
| pos += bytesRead; |
| } while (bytesRead !== 0); |
| } |
| |
| if (!isUserFd) |
| fs.closeSync(fd); |
| |
| if (size === 0) { |
| // data was collected into the buffers list. |
| buffer = Buffer.concat(buffers, pos); |
| } else if (pos < size) { |
| buffer = buffer.slice(0, pos); |
| } |
| |
| if (options.encoding) buffer = buffer.toString(options.encoding); |
| return buffer; |
| } |
| |
| function close(fd, callback) { |
| validateUint32(fd, 'fd'); |
| const req = new FSReqCallback(); |
| req.oncomplete = makeCallback(callback); |
| binding.close(fd, req); |
| } |
| |
| function closeSync(fd) { |
| validateUint32(fd, 'fd'); |
| |
| const ctx = {}; |
| binding.close(fd, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function open(path, flags, mode, callback) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const flagsNumber = stringToFlags(flags); |
| if (arguments.length < 4) { |
| callback = makeCallback(mode); |
| mode = 0o666; |
| } else { |
| mode = validateMode(mode, 'mode', 0o666); |
| callback = makeCallback(callback); |
| } |
| |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| |
| binding.open(pathModule.toNamespacedPath(path), |
| flagsNumber, |
| mode, |
| req); |
| } |
| |
| |
| function openSync(path, flags, mode) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const flagsNumber = stringToFlags(flags); |
| mode = validateMode(mode, 'mode', 0o666); |
| |
| const ctx = { path }; |
| const result = binding.open(pathModule.toNamespacedPath(path), |
| flagsNumber, mode, |
| undefined, ctx); |
| handleErrorFromBinding(ctx); |
| return result; |
| } |
| |
| function read(fd, buffer, offset, length, position, callback) { |
| validateUint32(fd, 'fd'); |
| validateBuffer(buffer); |
| callback = maybeCallback(callback); |
| |
| offset |= 0; |
| length |= 0; |
| |
| if (length === 0) { |
| return process.nextTick(function tick() { |
| callback(null, 0, buffer); |
| }); |
| } |
| |
| if (buffer.byteLength === 0) { |
| throw new ERR_INVALID_ARG_VALUE('buffer', buffer, |
| 'is empty and cannot be written'); |
| } |
| |
| validateOffsetLengthRead(offset, length, buffer.byteLength); |
| |
| if (!Number.isSafeInteger(position)) |
| position = -1; |
| |
| function wrapper(err, bytesRead) { |
| // Retain a reference to buffer so that it can't be GC'ed too soon. |
| callback(err, bytesRead || 0, buffer); |
| } |
| |
| const req = new FSReqCallback(); |
| req.oncomplete = wrapper; |
| |
| binding.read(fd, buffer, offset, length, position, req); |
| } |
| |
| Object.defineProperty(read, internalUtil.customPromisifyArgs, |
| { value: ['bytesRead', 'buffer'], enumerable: false }); |
| |
| function readSync(fd, buffer, offset, length, position) { |
| validateUint32(fd, 'fd'); |
| validateBuffer(buffer); |
| |
| offset |= 0; |
| length |= 0; |
| |
| if (length === 0) { |
| return 0; |
| } |
| |
| if (buffer.byteLength === 0) { |
| throw new ERR_INVALID_ARG_VALUE('buffer', buffer, |
| 'is empty and cannot be written'); |
| } |
| |
| validateOffsetLengthRead(offset, length, buffer.byteLength); |
| |
| if (!Number.isSafeInteger(position)) |
| position = -1; |
| |
| const ctx = {}; |
| const result = binding.read(fd, buffer, offset, length, position, |
| undefined, ctx); |
| handleErrorFromBinding(ctx); |
| return result; |
| } |
| |
| // usage: |
| // fs.write(fd, buffer[, offset[, length[, position]]], callback); |
| // OR |
| // fs.write(fd, string[, position[, encoding]], callback); |
| function write(fd, buffer, offset, length, position, callback) { |
| function wrapper(err, written) { |
| // Retain a reference to buffer so that it can't be GC'ed too soon. |
| callback(err, written || 0, buffer); |
| } |
| |
| validateUint32(fd, 'fd'); |
| |
| const req = new FSReqCallback(); |
| req.oncomplete = wrapper; |
| |
| if (isArrayBufferView(buffer)) { |
| callback = maybeCallback(callback || position || length || offset); |
| if (typeof offset !== 'number') |
| offset = 0; |
| if (typeof length !== 'number') |
| length = buffer.length - offset; |
| if (typeof position !== 'number') |
| position = null; |
| validateOffsetLengthWrite(offset, length, buffer.byteLength); |
| return binding.writeBuffer(fd, buffer, offset, length, position, req); |
| } |
| |
| if (typeof buffer !== 'string') |
| buffer += ''; |
| if (typeof position !== 'function') { |
| if (typeof offset === 'function') { |
| position = offset; |
| offset = null; |
| } else { |
| position = length; |
| } |
| length = 'utf8'; |
| } |
| callback = maybeCallback(position); |
| return binding.writeString(fd, buffer, offset, length, req); |
| } |
| |
| Object.defineProperty(write, internalUtil.customPromisifyArgs, |
| { value: ['bytesWritten', 'buffer'], enumerable: false }); |
| |
| // usage: |
| // fs.writeSync(fd, buffer[, offset[, length[, position]]]); |
| // OR |
| // fs.writeSync(fd, string[, position[, encoding]]); |
| function writeSync(fd, buffer, offset, length, position) { |
| validateUint32(fd, 'fd'); |
| const ctx = {}; |
| let result; |
| if (isArrayBufferView(buffer)) { |
| if (position === undefined) |
| position = null; |
| if (typeof offset !== 'number') |
| offset = 0; |
| if (typeof length !== 'number') |
| length = buffer.byteLength - offset; |
| validateOffsetLengthWrite(offset, length, buffer.byteLength); |
| result = binding.writeBuffer(fd, buffer, offset, length, position, |
| undefined, ctx); |
| } else { |
| if (typeof buffer !== 'string') |
| buffer += ''; |
| if (offset === undefined) |
| offset = null; |
| result = binding.writeString(fd, buffer, offset, length, |
| undefined, ctx); |
| } |
| handleErrorFromBinding(ctx); |
| return result; |
| } |
| |
| function rename(oldPath, newPath, callback) { |
| callback = makeCallback(callback); |
| oldPath = toPathIfFileURL(oldPath); |
| validatePath(oldPath, 'oldPath'); |
| newPath = toPathIfFileURL(newPath); |
| validatePath(newPath, 'newPath'); |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| binding.rename(pathModule.toNamespacedPath(oldPath), |
| pathModule.toNamespacedPath(newPath), |
| req); |
| } |
| |
| function renameSync(oldPath, newPath) { |
| oldPath = toPathIfFileURL(oldPath); |
| validatePath(oldPath, 'oldPath'); |
| newPath = toPathIfFileURL(newPath); |
| validatePath(newPath, 'newPath'); |
| const ctx = { path: oldPath, dest: newPath }; |
| binding.rename(pathModule.toNamespacedPath(oldPath), |
| pathModule.toNamespacedPath(newPath), undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function truncate(path, len, callback) { |
| if (typeof path === 'number') { |
| showTruncateDeprecation(); |
| return fs.ftruncate(path, len, callback); |
| } |
| if (typeof len === 'function') { |
| callback = len; |
| len = 0; |
| } else if (len === undefined) { |
| len = 0; |
| } |
| |
| validateInteger(len, 'len'); |
| callback = maybeCallback(callback); |
| fs.open(path, 'r+', function(er, fd) { |
| if (er) return callback(er); |
| const req = new FSReqCallback(); |
| req.oncomplete = function oncomplete(er) { |
| fs.close(fd, function(er2) { |
| callback(er || er2); |
| }); |
| }; |
| binding.ftruncate(fd, len, req); |
| }); |
| } |
| |
| function truncateSync(path, len) { |
| if (typeof path === 'number') { |
| // legacy |
| showTruncateDeprecation(); |
| return fs.ftruncateSync(path, len); |
| } |
| if (len === undefined) { |
| len = 0; |
| } |
| // allow error to be thrown, but still close fd. |
| const fd = fs.openSync(path, 'r+'); |
| let ret; |
| |
| try { |
| ret = fs.ftruncateSync(fd, len); |
| } finally { |
| fs.closeSync(fd); |
| } |
| return ret; |
| } |
| |
| function ftruncate(fd, len = 0, callback) { |
| if (typeof len === 'function') { |
| callback = len; |
| len = 0; |
| } |
| validateUint32(fd, 'fd'); |
| validateInteger(len, 'len'); |
| len = Math.max(0, len); |
| const req = new FSReqCallback(); |
| req.oncomplete = makeCallback(callback); |
| binding.ftruncate(fd, len, req); |
| } |
| |
| function ftruncateSync(fd, len = 0) { |
| validateUint32(fd, 'fd'); |
| validateInteger(len, 'len'); |
| len = Math.max(0, len); |
| const ctx = {}; |
| binding.ftruncate(fd, len, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function rmdir(path, callback) { |
| callback = makeCallback(callback); |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| binding.rmdir(pathModule.toNamespacedPath(path), req); |
| } |
| |
| function rmdirSync(path) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const ctx = { path }; |
| binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function fdatasync(fd, callback) { |
| validateUint32(fd, 'fd'); |
| const req = new FSReqCallback(); |
| req.oncomplete = makeCallback(callback); |
| binding.fdatasync(fd, req); |
| } |
| |
| function fdatasyncSync(fd) { |
| validateUint32(fd, 'fd'); |
| const ctx = {}; |
| binding.fdatasync(fd, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function fsync(fd, callback) { |
| validateUint32(fd, 'fd'); |
| const req = new FSReqCallback(); |
| req.oncomplete = makeCallback(callback); |
| binding.fsync(fd, req); |
| } |
| |
| function fsyncSync(fd) { |
| validateUint32(fd, 'fd'); |
| const ctx = {}; |
| binding.fsync(fd, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function mkdir(path, options, callback) { |
| if (typeof options === 'function') { |
| callback = options; |
| options = {}; |
| } else if (typeof options === 'number' || typeof options === 'string') { |
| options = { mode: options }; |
| } |
| const { |
| recursive = false, |
| mode = 0o777 |
| } = options || {}; |
| callback = makeCallback(callback); |
| path = toPathIfFileURL(path); |
| |
| validatePath(path); |
| if (typeof recursive !== 'boolean') |
| throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive); |
| |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| binding.mkdir(pathModule.toNamespacedPath(path), |
| validateMode(mode, 'mode', 0o777), recursive, req); |
| } |
| |
| function mkdirSync(path, options) { |
| if (typeof options === 'number' || typeof options === 'string') { |
| options = { mode: options }; |
| } |
| path = toPathIfFileURL(path); |
| const { |
| recursive = false, |
| mode = 0o777 |
| } = options || {}; |
| |
| validatePath(path); |
| if (typeof recursive !== 'boolean') |
| throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive); |
| |
| const ctx = { path }; |
| binding.mkdir(pathModule.toNamespacedPath(path), |
| validateMode(mode, 'mode', 0o777), recursive, undefined, |
| ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function readdir(path, options, callback) { |
| callback = makeCallback(typeof options === 'function' ? options : callback); |
| options = getOptions(options, {}); |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| |
| const req = new FSReqCallback(); |
| if (!options.withFileTypes) { |
| req.oncomplete = callback; |
| } else { |
| req.oncomplete = (err, result) => { |
| if (err) { |
| callback(err); |
| return; |
| } |
| getDirents(path, result, callback); |
| }; |
| } |
| binding.readdir(pathModule.toNamespacedPath(path), options.encoding, |
| !!options.withFileTypes, req); |
| } |
| |
| function readdirSync(path, options) { |
| options = getOptions(options, {}); |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const ctx = { path }; |
| const result = binding.readdir(pathModule.toNamespacedPath(path), |
| options.encoding, !!options.withFileTypes, |
| undefined, ctx); |
| handleErrorFromBinding(ctx); |
| return options.withFileTypes ? getDirents(path, result) : result; |
| } |
| |
| function fstat(fd, options, callback) { |
| if (arguments.length < 3) { |
| callback = options; |
| options = {}; |
| } |
| validateUint32(fd, 'fd'); |
| const req = new FSReqCallback(options.bigint); |
| req.oncomplete = makeStatsCallback(callback); |
| binding.fstat(fd, options.bigint, req); |
| } |
| |
| function lstat(path, options, callback) { |
| if (arguments.length < 3) { |
| callback = options; |
| options = {}; |
| } |
| callback = makeStatsCallback(callback); |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const req = new FSReqCallback(options.bigint); |
| req.oncomplete = callback; |
| binding.lstat(pathModule.toNamespacedPath(path), options.bigint, req); |
| } |
| |
| function stat(path, options, callback) { |
| if (arguments.length < 3) { |
| callback = options; |
| options = {}; |
| } |
| callback = makeStatsCallback(callback); |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const req = new FSReqCallback(options.bigint); |
| req.oncomplete = callback; |
| binding.stat(pathModule.toNamespacedPath(path), options.bigint, req); |
| } |
| |
| function fstatSync(fd, options = {}) { |
| validateUint32(fd, 'fd'); |
| const ctx = { fd }; |
| const stats = binding.fstat(fd, options.bigint, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| return getStatsFromBinding(stats); |
| } |
| |
| function lstatSync(path, options = {}) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const ctx = { path }; |
| const stats = binding.lstat(pathModule.toNamespacedPath(path), |
| options.bigint, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| return getStatsFromBinding(stats); |
| } |
| |
| function statSync(path, options = {}) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const ctx = { path }; |
| const stats = binding.stat(pathModule.toNamespacedPath(path), |
| options.bigint, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| return getStatsFromBinding(stats); |
| } |
| |
| function readlink(path, options, callback) { |
| callback = makeCallback(typeof options === 'function' ? options : callback); |
| options = getOptions(options, {}); |
| path = toPathIfFileURL(path); |
| validatePath(path, 'oldPath'); |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| binding.readlink(pathModule.toNamespacedPath(path), options.encoding, req); |
| } |
| |
| function readlinkSync(path, options) { |
| options = getOptions(options, {}); |
| path = toPathIfFileURL(path); |
| validatePath(path, 'oldPath'); |
| const ctx = { path }; |
| const result = binding.readlink(pathModule.toNamespacedPath(path), |
| options.encoding, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| return result; |
| } |
| |
| function symlink(target, path, type_, callback_) { |
| const type = (typeof type_ === 'string' ? type_ : null); |
| const callback = makeCallback(arguments[arguments.length - 1]); |
| |
| target = toPathIfFileURL(target); |
| path = toPathIfFileURL(path); |
| validatePath(target, 'target'); |
| validatePath(path); |
| |
| const flags = stringToSymlinkType(type); |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| |
| binding.symlink(preprocessSymlinkDestination(target, type, path), |
| pathModule.toNamespacedPath(path), flags, req); |
| } |
| |
| function symlinkSync(target, path, type) { |
| type = (typeof type === 'string' ? type : null); |
| target = toPathIfFileURL(target); |
| path = toPathIfFileURL(path); |
| validatePath(target, 'target'); |
| validatePath(path); |
| const flags = stringToSymlinkType(type); |
| |
| const ctx = { path: target, dest: path }; |
| binding.symlink(preprocessSymlinkDestination(target, type, path), |
| pathModule.toNamespacedPath(path), flags, undefined, ctx); |
| |
| handleErrorFromBinding(ctx); |
| } |
| |
| function link(existingPath, newPath, callback) { |
| callback = makeCallback(callback); |
| |
| existingPath = toPathIfFileURL(existingPath); |
| newPath = toPathIfFileURL(newPath); |
| validatePath(existingPath, 'existingPath'); |
| validatePath(newPath, 'newPath'); |
| |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| |
| binding.link(pathModule.toNamespacedPath(existingPath), |
| pathModule.toNamespacedPath(newPath), |
| req); |
| } |
| |
| function linkSync(existingPath, newPath) { |
| existingPath = toPathIfFileURL(existingPath); |
| newPath = toPathIfFileURL(newPath); |
| validatePath(existingPath, 'existingPath'); |
| validatePath(newPath, 'newPath'); |
| |
| const ctx = { path: existingPath, dest: newPath }; |
| const result = binding.link(pathModule.toNamespacedPath(existingPath), |
| pathModule.toNamespacedPath(newPath), |
| undefined, ctx); |
| handleErrorFromBinding(ctx); |
| return result; |
| } |
| |
| function unlink(path, callback) { |
| callback = makeCallback(callback); |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| binding.unlink(pathModule.toNamespacedPath(path), req); |
| } |
| |
| function unlinkSync(path) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const ctx = { path }; |
| binding.unlink(pathModule.toNamespacedPath(path), undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function fchmod(fd, mode, callback) { |
| validateInt32(fd, 'fd', 0); |
| mode = validateMode(mode, 'mode'); |
| callback = makeCallback(callback); |
| |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| binding.fchmod(fd, mode, req); |
| } |
| |
| function fchmodSync(fd, mode) { |
| validateInt32(fd, 'fd', 0); |
| mode = validateMode(mode, 'mode'); |
| const ctx = {}; |
| binding.fchmod(fd, mode, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function lchmod(path, mode, callback) { |
| callback = maybeCallback(callback); |
| fs.open(path, O_WRONLY | O_SYMLINK, function(err, fd) { |
| if (err) { |
| callback(err); |
| return; |
| } |
| // Prefer to return the chmod error, if one occurs, |
| // but still try to close, and report closing errors if they occur. |
| fs.fchmod(fd, mode, function(err) { |
| fs.close(fd, function(err2) { |
| callback(err || err2); |
| }); |
| }); |
| }); |
| } |
| |
| function lchmodSync(path, mode) { |
| const fd = fs.openSync(path, O_WRONLY | O_SYMLINK); |
| |
| // Prefer to return the chmod error, if one occurs, |
| // but still try to close, and report closing errors if they occur. |
| let ret; |
| try { |
| ret = fs.fchmodSync(fd, mode); |
| } finally { |
| fs.closeSync(fd); |
| } |
| return ret; |
| } |
| |
| |
| function chmod(path, mode, callback) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| mode = validateMode(mode, 'mode'); |
| callback = makeCallback(callback); |
| |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| binding.chmod(pathModule.toNamespacedPath(path), mode, req); |
| } |
| |
| function chmodSync(path, mode) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| mode = validateMode(mode, 'mode'); |
| |
| const ctx = { path }; |
| binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function lchown(path, uid, gid, callback) { |
| callback = makeCallback(callback); |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| validateUint32(uid, 'uid'); |
| validateUint32(gid, 'gid'); |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| binding.lchown(pathModule.toNamespacedPath(path), uid, gid, req); |
| } |
| |
| function lchownSync(path, uid, gid) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| validateUint32(uid, 'uid'); |
| validateUint32(gid, 'gid'); |
| const ctx = { path }; |
| binding.lchown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function fchown(fd, uid, gid, callback) { |
| validateUint32(fd, 'fd'); |
| validateUint32(uid, 'uid'); |
| validateUint32(gid, 'gid'); |
| |
| const req = new FSReqCallback(); |
| req.oncomplete = makeCallback(callback); |
| binding.fchown(fd, uid, gid, req); |
| } |
| |
| function fchownSync(fd, uid, gid) { |
| validateUint32(fd, 'fd'); |
| validateUint32(uid, 'uid'); |
| validateUint32(gid, 'gid'); |
| |
| const ctx = {}; |
| binding.fchown(fd, uid, gid, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function chown(path, uid, gid, callback) { |
| callback = makeCallback(callback); |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| validateUint32(uid, 'uid'); |
| validateUint32(gid, 'gid'); |
| |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| binding.chown(pathModule.toNamespacedPath(path), uid, gid, req); |
| } |
| |
| function chownSync(path, uid, gid) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| validateUint32(uid, 'uid'); |
| validateUint32(gid, 'gid'); |
| const ctx = { path }; |
| binding.chown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function utimes(path, atime, mtime, callback) { |
| callback = makeCallback(callback); |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| binding.utimes(pathModule.toNamespacedPath(path), |
| toUnixTimestamp(atime), |
| toUnixTimestamp(mtime), |
| req); |
| } |
| |
| function utimesSync(path, atime, mtime) { |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const ctx = { path }; |
| binding.utimes(pathModule.toNamespacedPath(path), |
| toUnixTimestamp(atime), toUnixTimestamp(mtime), |
| undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function futimes(fd, atime, mtime, callback) { |
| validateUint32(fd, 'fd'); |
| atime = toUnixTimestamp(atime, 'atime'); |
| mtime = toUnixTimestamp(mtime, 'mtime'); |
| const req = new FSReqCallback(); |
| req.oncomplete = makeCallback(callback); |
| binding.futimes(fd, atime, mtime, req); |
| } |
| |
| function futimesSync(fd, atime, mtime) { |
| validateUint32(fd, 'fd'); |
| atime = toUnixTimestamp(atime, 'atime'); |
| mtime = toUnixTimestamp(mtime, 'mtime'); |
| const ctx = {}; |
| binding.futimes(fd, atime, mtime, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function writeAll(fd, isUserFd, buffer, offset, length, position, callback) { |
| // write(fd, buffer, offset, length, position, callback) |
| fs.write(fd, buffer, offset, length, position, function(writeErr, written) { |
| if (writeErr) { |
| if (isUserFd) { |
| callback(writeErr); |
| } else { |
| fs.close(fd, function close() { |
| callback(writeErr); |
| }); |
| } |
| } else if (written === length) { |
| if (isUserFd) { |
| callback(null); |
| } else { |
| fs.close(fd, callback); |
| } |
| } else { |
| offset += written; |
| length -= written; |
| if (position !== null) { |
| position += written; |
| } |
| writeAll(fd, isUserFd, buffer, offset, length, position, callback); |
| } |
| }); |
| } |
| |
| function writeFile(path, data, options, callback) { |
| callback = maybeCallback(callback || options); |
| options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); |
| const flag = options.flag || 'w'; |
| |
| if (isFd(path)) { |
| writeFd(path, true); |
| return; |
| } |
| |
| fs.open(path, flag, options.mode, function(openErr, fd) { |
| if (openErr) { |
| callback(openErr); |
| } else { |
| writeFd(fd, false); |
| } |
| }); |
| |
| function writeFd(fd, isUserFd) { |
| const buffer = isArrayBufferView(data) ? |
| data : Buffer.from('' + data, options.encoding || 'utf8'); |
| const position = /a/.test(flag) ? null : 0; |
| |
| writeAll(fd, isUserFd, buffer, 0, buffer.byteLength, position, callback); |
| } |
| } |
| |
| function writeFileSync(path, data, options) { |
| options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); |
| const flag = options.flag || 'w'; |
| |
| const isUserFd = isFd(path); // file descriptor ownership |
| const fd = isUserFd ? path : fs.openSync(path, flag, options.mode); |
| |
| if (!isArrayBufferView(data)) { |
| data = Buffer.from('' + data, options.encoding || 'utf8'); |
| } |
| let offset = 0; |
| let length = data.byteLength; |
| let position = /a/.test(flag) ? null : 0; |
| try { |
| while (length > 0) { |
| const written = fs.writeSync(fd, data, offset, length, position); |
| offset += written; |
| length -= written; |
| if (position !== null) { |
| position += written; |
| } |
| } |
| } finally { |
| if (!isUserFd) fs.closeSync(fd); |
| } |
| } |
| |
| function appendFile(path, data, options, callback) { |
| callback = maybeCallback(callback || options); |
| options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); |
| |
| // Don't make changes directly on options object |
| options = copyObject(options); |
| |
| // force append behavior when using a supplied file descriptor |
| if (!options.flag || isFd(path)) |
| options.flag = 'a'; |
| |
| fs.writeFile(path, data, options, callback); |
| } |
| |
| function appendFileSync(path, data, options) { |
| options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); |
| |
| // Don't make changes directly on options object |
| options = copyObject(options); |
| |
| // force append behavior when using a supplied file descriptor |
| if (!options.flag || isFd(path)) |
| options.flag = 'a'; |
| |
| fs.writeFileSync(path, data, options); |
| } |
| |
| function watch(filename, options, listener) { |
| if (typeof options === 'function') { |
| listener = options; |
| } |
| options = getOptions(options, {}); |
| |
| // Don't make changes directly on options object |
| options = copyObject(options); |
| |
| if (options.persistent === undefined) options.persistent = true; |
| if (options.recursive === undefined) options.recursive = false; |
| |
| if (!watchers) |
| watchers = require('internal/fs/watchers'); |
| const watcher = new watchers.FSWatcher(); |
| watcher.start(filename, |
| options.persistent, |
| options.recursive, |
| options.encoding); |
| |
| if (listener) { |
| watcher.addListener('change', listener); |
| } |
| |
| return watcher; |
| } |
| |
| |
| const statWatchers = new Map(); |
| |
| function watchFile(filename, options, listener) { |
| filename = toPathIfFileURL(filename); |
| validatePath(filename); |
| filename = pathModule.resolve(filename); |
| let stat; |
| |
| const defaults = { |
| // Poll interval in milliseconds. 5007 is what libev used to use. It's |
| // a little on the slow side but let's stick with it for now to keep |
| // behavioral changes to a minimum. |
| interval: 5007, |
| persistent: true |
| }; |
| |
| if (options !== null && typeof options === 'object') { |
| options = _extend(defaults, options); |
| } else { |
| listener = options; |
| options = defaults; |
| } |
| |
| if (typeof listener !== 'function') { |
| throw new ERR_INVALID_ARG_TYPE('listener', 'Function', listener); |
| } |
| |
| stat = statWatchers.get(filename); |
| |
| if (stat === undefined) { |
| if (!watchers) |
| watchers = require('internal/fs/watchers'); |
| stat = new watchers.StatWatcher(options.bigint); |
| stat.start(filename, options.persistent, options.interval); |
| statWatchers.set(filename, stat); |
| } |
| |
| stat.addListener('change', listener); |
| return stat; |
| } |
| |
| function unwatchFile(filename, listener) { |
| filename = toPathIfFileURL(filename); |
| validatePath(filename); |
| filename = pathModule.resolve(filename); |
| const stat = statWatchers.get(filename); |
| |
| if (stat === undefined) return; |
| |
| if (typeof listener === 'function') { |
| stat.removeListener('change', listener); |
| } else { |
| stat.removeAllListeners('change'); |
| } |
| |
| if (stat.listenerCount('change') === 0) { |
| stat.stop(); |
| statWatchers.delete(filename); |
| } |
| } |
| |
| |
| let splitRoot; |
| if (isWindows) { |
| // Regex to find the device root on Windows (e.g. 'c:\\'), including trailing |
| // slash. |
| const splitRootRe = /^(?:[a-zA-Z]:|[\\/]{2}[^\\/]+[\\/][^\\/]+)?[\\/]*/; |
| splitRoot = function splitRoot(str) { |
| return splitRootRe.exec(str)[0]; |
| }; |
| } else { |
| splitRoot = function splitRoot(str) { |
| for (var i = 0; i < str.length; ++i) { |
| if (str.charCodeAt(i) !== CHAR_FORWARD_SLASH) |
| return str.slice(0, i); |
| } |
| return str; |
| }; |
| } |
| |
| function encodeRealpathResult(result, options) { |
| if (!options || !options.encoding || options.encoding === 'utf8') |
| return result; |
| const asBuffer = Buffer.from(result); |
| if (options.encoding === 'buffer') { |
| return asBuffer; |
| } else { |
| return asBuffer.toString(options.encoding); |
| } |
| } |
| |
| // Finds the next portion of a (partial) path, up to the next path delimiter |
| let nextPart; |
| if (isWindows) { |
| nextPart = function nextPart(p, i) { |
| for (; i < p.length; ++i) { |
| const ch = p.charCodeAt(i); |
| |
| // Check for a separator character |
| if (ch === CHAR_BACKWARD_SLASH || ch === CHAR_FORWARD_SLASH) |
| return i; |
| } |
| return -1; |
| }; |
| } else { |
| nextPart = function nextPart(p, i) { return p.indexOf('/', i); }; |
| } |
| |
| const emptyObj = Object.create(null); |
| function realpathSync(p, options) { |
| if (!options) |
| options = emptyObj; |
| else |
| options = getOptions(options, emptyObj); |
| p = toPathIfFileURL(p); |
| if (typeof p !== 'string') { |
| p += ''; |
| } |
| validatePath(p); |
| p = pathModule.resolve(p); |
| |
| const cache = options[realpathCacheKey]; |
| const maybeCachedResult = cache && cache.get(p); |
| if (maybeCachedResult) { |
| return maybeCachedResult; |
| } |
| |
| const seenLinks = Object.create(null); |
| const knownHard = Object.create(null); |
| const original = p; |
| |
| // current character position in p |
| let pos; |
| // the partial path so far, including a trailing slash if any |
| let current; |
| // the partial path without a trailing slash (except when pointing at a root) |
| let base; |
| // the partial path scanned in the previous round, with slash |
| let previous; |
| |
| // Skip over roots |
| current = base = splitRoot(p); |
| pos = current.length; |
| |
| // On windows, check that the root exists. On unix there is no need. |
| if (isWindows && !knownHard[base]) { |
| const ctx = { path: base }; |
| binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| knownHard[base] = true; |
| } |
| |
| // walk down the path, swapping out linked path parts for their real |
| // values |
| // NB: p.length changes. |
| while (pos < p.length) { |
| // find the next part |
| const result = nextPart(p, pos); |
| previous = current; |
| if (result === -1) { |
| const last = p.slice(pos); |
| current += last; |
| base = previous + last; |
| pos = p.length; |
| } else { |
| current += p.slice(pos, result + 1); |
| base = previous + p.slice(pos, result); |
| pos = result + 1; |
| } |
| |
| // continue if not a symlink, break if a pipe/socket |
| if (knownHard[base] || (cache && cache.get(base) === base)) { |
| if (isFileType(statValues, S_IFIFO) || |
| isFileType(statValues, S_IFSOCK)) { |
| break; |
| } |
| continue; |
| } |
| |
| let resolvedLink; |
| const maybeCachedResolved = cache && cache.get(base); |
| if (maybeCachedResolved) { |
| resolvedLink = maybeCachedResolved; |
| } else { |
| // Use stats array directly to avoid creating an fs.Stats instance just |
| // for our internal use. |
| |
| const baseLong = pathModule.toNamespacedPath(base); |
| const ctx = { path: base }; |
| const stats = binding.lstat(baseLong, false, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| |
| if (!isFileType(stats, S_IFLNK)) { |
| knownHard[base] = true; |
| if (cache) cache.set(base, base); |
| continue; |
| } |
| |
| // read the link if it wasn't read before |
| // dev/ino always return 0 on windows, so skip the check. |
| let linkTarget = null; |
| let id; |
| if (!isWindows) { |
| const dev = stats[0].toString(32); |
| const ino = stats[7].toString(32); |
| id = `${dev}:${ino}`; |
| if (seenLinks[id]) { |
| linkTarget = seenLinks[id]; |
| } |
| } |
| if (linkTarget === null) { |
| const ctx = { path: base }; |
| binding.stat(baseLong, false, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| linkTarget = binding.readlink(baseLong, undefined, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| resolvedLink = pathModule.resolve(previous, linkTarget); |
| |
| if (cache) cache.set(base, resolvedLink); |
| if (!isWindows) seenLinks[id] = linkTarget; |
| } |
| |
| // resolve the link, then start over |
| p = pathModule.resolve(resolvedLink, p.slice(pos)); |
| |
| // Skip over roots |
| current = base = splitRoot(p); |
| pos = current.length; |
| |
| // On windows, check that the root exists. On unix there is no need. |
| if (isWindows && !knownHard[base]) { |
| const ctx = { path: base }; |
| binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| knownHard[base] = true; |
| } |
| } |
| |
| if (cache) cache.set(original, p); |
| return encodeRealpathResult(p, options); |
| } |
| |
| |
| realpathSync.native = function(path, options) { |
| options = getOptions(options, {}); |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const ctx = { path }; |
| const result = binding.realpath(path, options.encoding, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| return result; |
| }; |
| |
| |
| function realpath(p, options, callback) { |
| callback = maybeCallback(typeof options === 'function' ? options : callback); |
| if (!options) |
| options = emptyObj; |
| else |
| options = getOptions(options, emptyObj); |
| p = toPathIfFileURL(p); |
| if (typeof p !== 'string') { |
| p += ''; |
| } |
| validatePath(p); |
| p = pathModule.resolve(p); |
| |
| const seenLinks = Object.create(null); |
| const knownHard = Object.create(null); |
| |
| // current character position in p |
| let pos; |
| // the partial path so far, including a trailing slash if any |
| let current; |
| // the partial path without a trailing slash (except when pointing at a root) |
| let base; |
| // the partial path scanned in the previous round, with slash |
| let previous; |
| |
| current = base = splitRoot(p); |
| pos = current.length; |
| |
| // On windows, check that the root exists. On unix there is no need. |
| if (isWindows && !knownHard[base]) { |
| fs.lstat(base, function(err, stats) { |
| if (err) return callback(err); |
| knownHard[base] = true; |
| LOOP(); |
| }); |
| } else { |
| process.nextTick(LOOP); |
| } |
| |
| // walk down the path, swapping out linked path parts for their real |
| // values |
| function LOOP() { |
| // stop if scanned past end of path |
| if (pos >= p.length) { |
| return callback(null, encodeRealpathResult(p, options)); |
| } |
| |
| // find the next part |
| const result = nextPart(p, pos); |
| previous = current; |
| if (result === -1) { |
| const last = p.slice(pos); |
| current += last; |
| base = previous + last; |
| pos = p.length; |
| } else { |
| current += p.slice(pos, result + 1); |
| base = previous + p.slice(pos, result); |
| pos = result + 1; |
| } |
| |
| // continue if not a symlink, break if a pipe/socket |
| if (knownHard[base]) { |
| if (isFileType(statValues, S_IFIFO) || |
| isFileType(statValues, S_IFSOCK)) { |
| return callback(null, encodeRealpathResult(p, options)); |
| } |
| return process.nextTick(LOOP); |
| } |
| |
| return fs.lstat(base, gotStat); |
| } |
| |
| function gotStat(err, stats) { |
| if (err) return callback(err); |
| |
| // if not a symlink, skip to the next path part |
| if (!stats.isSymbolicLink()) { |
| knownHard[base] = true; |
| return process.nextTick(LOOP); |
| } |
| |
| // stat & read the link if not read before |
| // call gotTarget as soon as the link target is known |
| // dev/ino always return 0 on windows, so skip the check. |
| let id; |
| if (!isWindows) { |
| const dev = stats.dev.toString(32); |
| const ino = stats.ino.toString(32); |
| id = `${dev}:${ino}`; |
| if (seenLinks[id]) { |
| return gotTarget(null, seenLinks[id], base); |
| } |
| } |
| fs.stat(base, function(err) { |
| if (err) return callback(err); |
| |
| fs.readlink(base, function(err, target) { |
| if (!isWindows) seenLinks[id] = target; |
| gotTarget(err, target); |
| }); |
| }); |
| } |
| |
| function gotTarget(err, target, base) { |
| if (err) return callback(err); |
| |
| gotResolvedLink(pathModule.resolve(previous, target)); |
| } |
| |
| function gotResolvedLink(resolvedLink) { |
| // resolve the link, then start over |
| p = pathModule.resolve(resolvedLink, p.slice(pos)); |
| current = base = splitRoot(p); |
| pos = current.length; |
| |
| // On windows, check that the root exists. On unix there is no need. |
| if (isWindows && !knownHard[base]) { |
| fs.lstat(base, function(err) { |
| if (err) return callback(err); |
| knownHard[base] = true; |
| LOOP(); |
| }); |
| } else { |
| process.nextTick(LOOP); |
| } |
| } |
| } |
| |
| |
| realpath.native = function(path, options, callback) { |
| callback = makeCallback(callback || options); |
| options = getOptions(options, {}); |
| path = toPathIfFileURL(path); |
| validatePath(path); |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| return binding.realpath(path, options.encoding, req); |
| }; |
| |
| function mkdtemp(prefix, options, callback) { |
| callback = makeCallback(typeof options === 'function' ? options : callback); |
| options = getOptions(options, {}); |
| if (!prefix || typeof prefix !== 'string') { |
| throw new ERR_INVALID_ARG_TYPE('prefix', 'string', prefix); |
| } |
| nullCheck(prefix, 'prefix'); |
| const req = new FSReqCallback(); |
| req.oncomplete = callback; |
| binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, req); |
| } |
| |
| |
| function mkdtempSync(prefix, options) { |
| options = getOptions(options, {}); |
| if (!prefix || typeof prefix !== 'string') { |
| throw new ERR_INVALID_ARG_TYPE('prefix', 'string', prefix); |
| } |
| nullCheck(prefix, 'prefix'); |
| const path = `${prefix}XXXXXX`; |
| const ctx = { path }; |
| const result = binding.mkdtemp(path, options.encoding, |
| undefined, ctx); |
| handleErrorFromBinding(ctx); |
| return result; |
| } |
| |
| |
| function copyFile(src, dest, flags, callback) { |
| if (typeof flags === 'function') { |
| callback = flags; |
| flags = 0; |
| } else if (typeof callback !== 'function') { |
| throw new ERR_INVALID_CALLBACK(); |
| } |
| |
| src = toPathIfFileURL(src); |
| dest = toPathIfFileURL(dest); |
| validatePath(src, 'src'); |
| validatePath(dest, 'dest'); |
| |
| src = pathModule._makeLong(src); |
| dest = pathModule._makeLong(dest); |
| flags = flags | 0; |
| const req = new FSReqCallback(); |
| req.oncomplete = makeCallback(callback); |
| binding.copyFile(src, dest, flags, req); |
| } |
| |
| |
| function copyFileSync(src, dest, flags) { |
| src = toPathIfFileURL(src); |
| dest = toPathIfFileURL(dest); |
| validatePath(src, 'src'); |
| validatePath(dest, 'dest'); |
| |
| const ctx = { path: src, dest }; // non-prefixed |
| |
| src = pathModule._makeLong(src); |
| dest = pathModule._makeLong(dest); |
| flags = flags | 0; |
| binding.copyFile(src, dest, flags, undefined, ctx); |
| handleErrorFromBinding(ctx); |
| } |
| |
| function lazyLoadStreams() { |
| if (!ReadStream) { |
| ({ ReadStream, WriteStream } = require('internal/fs/streams')); |
| [ FileReadStream, FileWriteStream ] = [ ReadStream, WriteStream ]; |
| } |
| } |
| |
| function createReadStream(path, options) { |
| lazyLoadStreams(); |
| return new ReadStream(path, options); |
| } |
| |
| function createWriteStream(path, options) { |
| lazyLoadStreams(); |
| return new WriteStream(path, options); |
| } |
| |
| |
| module.exports = fs = { |
| appendFile, |
| appendFileSync, |
| access, |
| accessSync, |
| chown, |
| chownSync, |
| chmod, |
| chmodSync, |
| close, |
| closeSync, |
| copyFile, |
| copyFileSync, |
| createReadStream, |
| createWriteStream, |
| exists, |
| existsSync, |
| fchown, |
| fchownSync, |
| fchmod, |
| fchmodSync, |
| fdatasync, |
| fdatasyncSync, |
| fstat, |
| fstatSync, |
| fsync, |
| fsyncSync, |
| ftruncate, |
| ftruncateSync, |
| futimes, |
| futimesSync, |
| lchown, |
| lchownSync, |
| lchmod: constants.O_SYMLINK !== undefined ? lchmod : undefined, |
| lchmodSync: constants.O_SYMLINK !== undefined ? lchmodSync : undefined, |
| link, |
| linkSync, |
| lstat, |
| lstatSync, |
| mkdir, |
| mkdirSync, |
| mkdtemp, |
| mkdtempSync, |
| open, |
| openSync, |
| readdir, |
| readdirSync, |
| read, |
| readSync, |
| readFile, |
| readFileSync, |
| readlink, |
| readlinkSync, |
| realpath, |
| realpathSync, |
| rename, |
| renameSync, |
| rmdir, |
| rmdirSync, |
| stat, |
| statSync, |
| symlink, |
| symlinkSync, |
| truncate, |
| truncateSync, |
| unwatchFile, |
| unlink, |
| unlinkSync, |
| utimes, |
| utimesSync, |
| watch, |
| watchFile, |
| writeFile, |
| writeFileSync, |
| write, |
| writeSync, |
| Dirent, |
| Stats, |
| |
| get ReadStream() { |
| lazyLoadStreams(); |
| return ReadStream; |
| }, |
| |
| set ReadStream(val) { |
| ReadStream = val; |
| }, |
| |
| get WriteStream() { |
| lazyLoadStreams(); |
| return WriteStream; |
| }, |
| |
| set WriteStream(val) { |
| WriteStream = val; |
| }, |
| |
| // Legacy names... these have to be separate because of how graceful-fs |
| // (and possibly other) modules monkey patch the values. |
| get FileReadStream() { |
| lazyLoadStreams(); |
| return FileReadStream; |
| }, |
| |
| set FileReadStream(val) { |
| FileReadStream = val; |
| }, |
| |
| get FileWriteStream() { |
| lazyLoadStreams(); |
| return FileWriteStream; |
| }, |
| |
| set FileWriteStream(val) { |
| FileWriteStream = val; |
| }, |
| |
| // For tests |
| _toUnixTimestamp: toUnixTimestamp |
| }; |
| |
| Object.defineProperties(fs, { |
| F_OK: { enumerable: true, value: F_OK || 0 }, |
| R_OK: { enumerable: true, value: R_OK || 0 }, |
| W_OK: { enumerable: true, value: W_OK || 0 }, |
| X_OK: { enumerable: true, value: X_OK || 0 }, |
| constants: { |
| configurable: false, |
| enumerable: true, |
| value: constants |
| }, |
| promises: { |
| configurable: true, |
| enumerable: false, |
| get() { |
| if (promisesWarn) { |
| promises = require('internal/fs/promises'); |
| promisesWarn = false; |
| process.emitWarning('The fs.promises API is experimental', |
| 'ExperimentalWarning'); |
| } |
| return promises; |
| } |
| } |
| }); |