mirror of
https://github.com/nodejs/node.git
synced 2024-12-01 16:10:02 +01:00
0ff4a558af
This seems to make sense if we want to promote the use of `fs.promises`, although it’s not strictly necessary. PR-URL: https://github.com/nodejs/node/pull/29876 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Minwoo Jung <minwoo@nodesource.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
540 lines
14 KiB
JavaScript
540 lines
14 KiB
JavaScript
'use strict';
|
|
|
|
const { Math } = primordials;
|
|
|
|
const {
|
|
F_OK,
|
|
O_SYMLINK,
|
|
O_WRONLY,
|
|
S_IFMT,
|
|
S_IFREG
|
|
} = internalBinding('constants').fs;
|
|
const binding = internalBinding('fs');
|
|
const { Buffer, kMaxLength } = require('buffer');
|
|
const {
|
|
ERR_FS_FILE_TOO_LARGE,
|
|
ERR_INVALID_ARG_TYPE,
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_METHOD_NOT_IMPLEMENTED
|
|
} = require('internal/errors').codes;
|
|
const { isUint8Array } = require('internal/util/types');
|
|
const { rimrafPromises } = require('internal/fs/rimraf');
|
|
const {
|
|
copyObject,
|
|
getDirents,
|
|
getOptions,
|
|
getStatsFromBinding,
|
|
getValidatedPath,
|
|
nullCheck,
|
|
preprocessSymlinkDestination,
|
|
stringToFlags,
|
|
stringToSymlinkType,
|
|
toUnixTimestamp,
|
|
validateBufferArray,
|
|
validateOffsetLengthRead,
|
|
validateOffsetLengthWrite,
|
|
validateRmdirOptions,
|
|
warnOnNonPortableTemplate
|
|
} = require('internal/fs/utils');
|
|
const { opendir } = require('internal/fs/dir');
|
|
const {
|
|
parseMode,
|
|
validateBuffer,
|
|
validateInteger,
|
|
validateUint32
|
|
} = require('internal/validators');
|
|
const pathModule = require('path');
|
|
const { promisify } = require('internal/util');
|
|
|
|
const kHandle = Symbol('kHandle');
|
|
const { kUsePromises } = binding;
|
|
|
|
const getDirectoryEntriesPromise = promisify(getDirents);
|
|
|
|
class FileHandle {
|
|
constructor(filehandle) {
|
|
this[kHandle] = filehandle;
|
|
}
|
|
|
|
getAsyncId() {
|
|
return this[kHandle].getAsyncId();
|
|
}
|
|
|
|
get fd() {
|
|
return this[kHandle].fd;
|
|
}
|
|
|
|
appendFile(data, options) {
|
|
return appendFile(this, data, options);
|
|
}
|
|
|
|
chmod(mode) {
|
|
return fchmod(this, mode);
|
|
}
|
|
|
|
chown(uid, gid) {
|
|
return fchown(this, uid, gid);
|
|
}
|
|
|
|
datasync() {
|
|
return fdatasync(this);
|
|
}
|
|
|
|
sync() {
|
|
return fsync(this);
|
|
}
|
|
|
|
read(buffer, offset, length, position) {
|
|
return read(this, buffer, offset, length, position);
|
|
}
|
|
|
|
readFile(options) {
|
|
return readFile(this, options);
|
|
}
|
|
|
|
stat(options) {
|
|
return fstat(this, options);
|
|
}
|
|
|
|
truncate(len = 0) {
|
|
return ftruncate(this, len);
|
|
}
|
|
|
|
utimes(atime, mtime) {
|
|
return futimes(this, atime, mtime);
|
|
}
|
|
|
|
write(buffer, offset, length, position) {
|
|
return write(this, buffer, offset, length, position);
|
|
}
|
|
|
|
writev(buffers, position) {
|
|
return writev(this, buffers, position);
|
|
}
|
|
|
|
writeFile(data, options) {
|
|
return writeFile(this, data, options);
|
|
}
|
|
|
|
close() {
|
|
return this[kHandle].close();
|
|
}
|
|
}
|
|
|
|
function validateFileHandle(handle) {
|
|
if (!(handle instanceof FileHandle))
|
|
throw new ERR_INVALID_ARG_TYPE('filehandle', 'FileHandle', handle);
|
|
}
|
|
|
|
async function writeFileHandle(filehandle, data, options) {
|
|
let buffer = isUint8Array(data) ?
|
|
data : Buffer.from('' + data, options.encoding || 'utf8');
|
|
let remaining = buffer.length;
|
|
if (remaining === 0) return;
|
|
do {
|
|
const { bytesWritten } =
|
|
await write(filehandle, buffer, 0,
|
|
Math.min(16384, buffer.length));
|
|
remaining -= bytesWritten;
|
|
buffer = buffer.slice(bytesWritten);
|
|
} while (remaining > 0);
|
|
}
|
|
|
|
// Note: This is different from kReadFileBufferLength used for non-promisified
|
|
// fs.readFile.
|
|
const kReadFileMaxChunkSize = 16384;
|
|
|
|
async function readFileHandle(filehandle, options) {
|
|
const statFields = await binding.fstat(filehandle.fd, false, kUsePromises);
|
|
|
|
let size;
|
|
if ((statFields[1/* mode */] & S_IFMT) === S_IFREG) {
|
|
size = statFields[8/* size */];
|
|
} else {
|
|
size = 0;
|
|
}
|
|
|
|
if (size > kMaxLength)
|
|
throw new ERR_FS_FILE_TOO_LARGE(size);
|
|
|
|
const chunks = [];
|
|
const chunkSize = size === 0 ?
|
|
kReadFileMaxChunkSize :
|
|
Math.min(size, kReadFileMaxChunkSize);
|
|
let endOfFile = false;
|
|
do {
|
|
const buf = Buffer.alloc(chunkSize);
|
|
const { bytesRead, buffer } =
|
|
await read(filehandle, buf, 0, chunkSize, -1);
|
|
endOfFile = bytesRead === 0;
|
|
if (bytesRead > 0)
|
|
chunks.push(buffer.slice(0, bytesRead));
|
|
} while (!endOfFile);
|
|
|
|
const result = Buffer.concat(chunks);
|
|
if (options.encoding) {
|
|
return result.toString(options.encoding);
|
|
} else {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// All of the functions are defined as async in order to ensure that errors
|
|
// thrown cause promise rejections rather than being thrown synchronously.
|
|
async function access(path, mode = F_OK) {
|
|
path = getValidatedPath(path);
|
|
|
|
mode = mode | 0;
|
|
return binding.access(pathModule.toNamespacedPath(path), mode,
|
|
kUsePromises);
|
|
}
|
|
|
|
async function copyFile(src, dest, flags) {
|
|
src = getValidatedPath(src, 'src');
|
|
dest = getValidatedPath(dest, 'dest');
|
|
flags = flags | 0;
|
|
return binding.copyFile(pathModule.toNamespacedPath(src),
|
|
pathModule.toNamespacedPath(dest),
|
|
flags, kUsePromises);
|
|
}
|
|
|
|
// Note that unlike fs.open() which uses numeric file descriptors,
|
|
// fsPromises.open() uses the fs.FileHandle class.
|
|
async function open(path, flags, mode) {
|
|
path = getValidatedPath(path);
|
|
if (arguments.length < 2) flags = 'r';
|
|
const flagsNumber = stringToFlags(flags);
|
|
mode = parseMode(mode, 'mode', 0o666);
|
|
return new FileHandle(
|
|
await binding.openFileHandle(pathModule.toNamespacedPath(path),
|
|
flagsNumber, mode, kUsePromises));
|
|
}
|
|
|
|
async function read(handle, buffer, offset, length, position) {
|
|
validateFileHandle(handle);
|
|
validateBuffer(buffer);
|
|
|
|
if (offset == null) {
|
|
offset = 0;
|
|
} else {
|
|
validateInteger(offset, 'offset');
|
|
}
|
|
|
|
length |= 0;
|
|
|
|
if (length === 0)
|
|
return { bytesRead: length, buffer };
|
|
|
|
if (buffer.length === 0) {
|
|
throw new ERR_INVALID_ARG_VALUE('buffer', buffer,
|
|
'is empty and cannot be written');
|
|
}
|
|
|
|
validateOffsetLengthRead(offset, length, buffer.length);
|
|
|
|
if (!Number.isSafeInteger(position))
|
|
position = -1;
|
|
|
|
const bytesRead = (await binding.read(handle.fd, buffer, offset, length,
|
|
position, kUsePromises)) || 0;
|
|
|
|
return { bytesRead, buffer };
|
|
}
|
|
|
|
async function write(handle, buffer, offset, length, position) {
|
|
validateFileHandle(handle);
|
|
|
|
if (buffer.length === 0)
|
|
return { bytesWritten: 0, buffer };
|
|
|
|
if (isUint8Array(buffer)) {
|
|
if (offset == null) {
|
|
offset = 0;
|
|
} else {
|
|
validateInteger(offset, 'offset');
|
|
}
|
|
if (typeof length !== 'number')
|
|
length = buffer.length - offset;
|
|
if (typeof position !== 'number')
|
|
position = null;
|
|
validateOffsetLengthWrite(offset, length, buffer.byteLength);
|
|
const bytesWritten =
|
|
(await binding.writeBuffer(handle.fd, buffer, offset,
|
|
length, position, kUsePromises)) || 0;
|
|
return { bytesWritten, buffer };
|
|
}
|
|
|
|
if (typeof buffer !== 'string')
|
|
buffer += '';
|
|
const bytesWritten = (await binding.writeString(handle.fd, buffer, offset,
|
|
length, kUsePromises)) || 0;
|
|
return { bytesWritten, buffer };
|
|
}
|
|
|
|
async function writev(handle, buffers, position) {
|
|
validateFileHandle(handle);
|
|
validateBufferArray(buffers);
|
|
|
|
if (typeof position !== 'number')
|
|
position = null;
|
|
|
|
const bytesWritten = (await binding.writeBuffers(handle.fd, buffers, position,
|
|
kUsePromises)) || 0;
|
|
return { bytesWritten, buffers };
|
|
}
|
|
|
|
async function rename(oldPath, newPath) {
|
|
oldPath = getValidatedPath(oldPath, 'oldPath');
|
|
newPath = getValidatedPath(newPath, 'newPath');
|
|
return binding.rename(pathModule.toNamespacedPath(oldPath),
|
|
pathModule.toNamespacedPath(newPath),
|
|
kUsePromises);
|
|
}
|
|
|
|
async function truncate(path, len = 0) {
|
|
const fd = await open(path, 'r+');
|
|
return ftruncate(fd, len).finally(fd.close.bind(fd));
|
|
}
|
|
|
|
async function ftruncate(handle, len = 0) {
|
|
validateFileHandle(handle);
|
|
validateInteger(len, 'len');
|
|
len = Math.max(0, len);
|
|
return binding.ftruncate(handle.fd, len, kUsePromises);
|
|
}
|
|
|
|
async function rmdir(path, options) {
|
|
path = pathModule.toNamespacedPath(getValidatedPath(path));
|
|
options = validateRmdirOptions(options);
|
|
|
|
if (options.recursive) {
|
|
return rimrafPromises(path, options);
|
|
}
|
|
|
|
return binding.rmdir(path, kUsePromises);
|
|
}
|
|
|
|
async function fdatasync(handle) {
|
|
validateFileHandle(handle);
|
|
return binding.fdatasync(handle.fd, kUsePromises);
|
|
}
|
|
|
|
async function fsync(handle) {
|
|
validateFileHandle(handle);
|
|
return binding.fsync(handle.fd, kUsePromises);
|
|
}
|
|
|
|
async function mkdir(path, options) {
|
|
if (typeof options === 'number' || typeof options === 'string') {
|
|
options = { mode: options };
|
|
}
|
|
const {
|
|
recursive = false,
|
|
mode = 0o777
|
|
} = options || {};
|
|
path = getValidatedPath(path);
|
|
if (typeof recursive !== 'boolean')
|
|
throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
|
|
|
|
return binding.mkdir(pathModule.toNamespacedPath(path),
|
|
parseMode(mode, 'mode', 0o777), recursive,
|
|
kUsePromises);
|
|
}
|
|
|
|
async function readdir(path, options) {
|
|
options = getOptions(options, {});
|
|
path = getValidatedPath(path);
|
|
const result = await binding.readdir(pathModule.toNamespacedPath(path),
|
|
options.encoding,
|
|
!!options.withFileTypes,
|
|
kUsePromises);
|
|
return options.withFileTypes ?
|
|
getDirectoryEntriesPromise(path, result) :
|
|
result;
|
|
}
|
|
|
|
async function readlink(path, options) {
|
|
options = getOptions(options, {});
|
|
path = getValidatedPath(path, 'oldPath');
|
|
return binding.readlink(pathModule.toNamespacedPath(path),
|
|
options.encoding, kUsePromises);
|
|
}
|
|
|
|
async function symlink(target, path, type_) {
|
|
const type = (typeof type_ === 'string' ? type_ : null);
|
|
target = getValidatedPath(target, 'target');
|
|
path = getValidatedPath(path);
|
|
return binding.symlink(preprocessSymlinkDestination(target, type, path),
|
|
pathModule.toNamespacedPath(path),
|
|
stringToSymlinkType(type),
|
|
kUsePromises);
|
|
}
|
|
|
|
async function fstat(handle, options = { bigint: false }) {
|
|
validateFileHandle(handle);
|
|
const result = await binding.fstat(handle.fd, options.bigint, kUsePromises);
|
|
return getStatsFromBinding(result);
|
|
}
|
|
|
|
async function lstat(path, options = { bigint: false }) {
|
|
path = getValidatedPath(path);
|
|
const result = await binding.lstat(pathModule.toNamespacedPath(path),
|
|
options.bigint, kUsePromises);
|
|
return getStatsFromBinding(result);
|
|
}
|
|
|
|
async function stat(path, options = { bigint: false }) {
|
|
path = getValidatedPath(path);
|
|
const result = await binding.stat(pathModule.toNamespacedPath(path),
|
|
options.bigint, kUsePromises);
|
|
return getStatsFromBinding(result);
|
|
}
|
|
|
|
async function link(existingPath, newPath) {
|
|
existingPath = getValidatedPath(existingPath, 'existingPath');
|
|
newPath = getValidatedPath(newPath, 'newPath');
|
|
return binding.link(pathModule.toNamespacedPath(existingPath),
|
|
pathModule.toNamespacedPath(newPath),
|
|
kUsePromises);
|
|
}
|
|
|
|
async function unlink(path) {
|
|
path = getValidatedPath(path);
|
|
return binding.unlink(pathModule.toNamespacedPath(path), kUsePromises);
|
|
}
|
|
|
|
async function fchmod(handle, mode) {
|
|
validateFileHandle(handle);
|
|
mode = parseMode(mode, 'mode');
|
|
return binding.fchmod(handle.fd, mode, kUsePromises);
|
|
}
|
|
|
|
async function chmod(path, mode) {
|
|
path = getValidatedPath(path);
|
|
mode = parseMode(mode, 'mode');
|
|
return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises);
|
|
}
|
|
|
|
async function lchmod(path, mode) {
|
|
if (O_SYMLINK === undefined)
|
|
throw new ERR_METHOD_NOT_IMPLEMENTED('lchmod()');
|
|
|
|
const fd = await open(path, O_WRONLY | O_SYMLINK);
|
|
return fchmod(fd, mode).finally(fd.close.bind(fd));
|
|
}
|
|
|
|
async function lchown(path, uid, gid) {
|
|
path = getValidatedPath(path);
|
|
validateUint32(uid, 'uid');
|
|
validateUint32(gid, 'gid');
|
|
return binding.lchown(pathModule.toNamespacedPath(path),
|
|
uid, gid, kUsePromises);
|
|
}
|
|
|
|
async function fchown(handle, uid, gid) {
|
|
validateFileHandle(handle);
|
|
validateUint32(uid, 'uid');
|
|
validateUint32(gid, 'gid');
|
|
return binding.fchown(handle.fd, uid, gid, kUsePromises);
|
|
}
|
|
|
|
async function chown(path, uid, gid) {
|
|
path = getValidatedPath(path);
|
|
validateUint32(uid, 'uid');
|
|
validateUint32(gid, 'gid');
|
|
return binding.chown(pathModule.toNamespacedPath(path),
|
|
uid, gid, kUsePromises);
|
|
}
|
|
|
|
async function utimes(path, atime, mtime) {
|
|
path = getValidatedPath(path);
|
|
return binding.utimes(pathModule.toNamespacedPath(path),
|
|
toUnixTimestamp(atime),
|
|
toUnixTimestamp(mtime),
|
|
kUsePromises);
|
|
}
|
|
|
|
async function futimes(handle, atime, mtime) {
|
|
validateFileHandle(handle);
|
|
atime = toUnixTimestamp(atime, 'atime');
|
|
mtime = toUnixTimestamp(mtime, 'mtime');
|
|
return binding.futimes(handle.fd, atime, mtime, kUsePromises);
|
|
}
|
|
|
|
async function realpath(path, options) {
|
|
options = getOptions(options, {});
|
|
path = getValidatedPath(path);
|
|
return binding.realpath(path, options.encoding, kUsePromises);
|
|
}
|
|
|
|
async function mkdtemp(prefix, options) {
|
|
options = getOptions(options, {});
|
|
if (!prefix || typeof prefix !== 'string') {
|
|
throw new ERR_INVALID_ARG_TYPE('prefix', 'string', prefix);
|
|
}
|
|
nullCheck(prefix);
|
|
warnOnNonPortableTemplate(prefix);
|
|
return binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, kUsePromises);
|
|
}
|
|
|
|
async function writeFile(path, data, options) {
|
|
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
|
|
const flag = options.flag || 'w';
|
|
|
|
if (path instanceof FileHandle)
|
|
return writeFileHandle(path, data, options);
|
|
|
|
const fd = await open(path, flag, options.mode);
|
|
return writeFileHandle(fd, data, options).finally(fd.close.bind(fd));
|
|
}
|
|
|
|
async function appendFile(path, data, options) {
|
|
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
|
|
options = copyObject(options);
|
|
options.flag = options.flag || 'a';
|
|
return writeFile(path, data, options);
|
|
}
|
|
|
|
async function readFile(path, options) {
|
|
options = getOptions(options, { flag: 'r' });
|
|
const flag = options.flag || 'r';
|
|
|
|
if (path instanceof FileHandle)
|
|
return readFileHandle(path, options);
|
|
|
|
const fd = await open(path, flag, 0o666);
|
|
return readFileHandle(fd, options).finally(fd.close.bind(fd));
|
|
}
|
|
|
|
module.exports = {
|
|
exports: {
|
|
access,
|
|
copyFile,
|
|
open,
|
|
opendir: promisify(opendir),
|
|
rename,
|
|
truncate,
|
|
rmdir,
|
|
mkdir,
|
|
readdir,
|
|
readlink,
|
|
symlink,
|
|
lstat,
|
|
stat,
|
|
link,
|
|
unlink,
|
|
chmod,
|
|
lchmod,
|
|
lchown,
|
|
chown,
|
|
utimes,
|
|
realpath,
|
|
mkdtemp,
|
|
writeFile,
|
|
appendFile,
|
|
readFile,
|
|
},
|
|
|
|
FileHandle
|
|
};
|