fs: add initial set of fs.promises APIs

Initial set of fs.promises APIs with documentation and one
benchmark.

PR-URL: https://github.com/nodejs/node/pull/18297
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
James M Snell 2018-01-21 10:21:25 -08:00
parent 85b37db684
commit 329fc78e49
10 changed files with 1957 additions and 187 deletions

View file

@ -0,0 +1,28 @@
'use strict';
const common = require('../common');
const fs = require('fs');
const bench = common.createBenchmark(main, {
n: [20e4],
statType: ['fstat', 'lstat', 'stat']
});
async function run(n, statType) {
const arg = statType === 'fstat' ?
await fs.promises.open(__filename, 'r') : __filename;
let remaining = n;
bench.start();
while (remaining-- > 0)
await fs.promises[statType](arg);
bench.end(n);
if (typeof arg.close === 'function')
await arg.close();
}
function main(conf) {
const n = conf.n >>> 0;
const statType = conf.statType;
run(n, statType).catch(console.log);
}

File diff suppressed because it is too large Load diff

492
lib/fs.js
View file

@ -53,6 +53,9 @@ Object.defineProperty(exports, 'constants', {
value: constants
});
const kHandle = Symbol('handle');
const { kUsePromises } = binding;
const kMinPoolSpace = 128;
const { kMaxLength } = require('buffer');
@ -343,13 +346,11 @@ Stats.prototype.isSocket = function() {
const statValues = binding.statValues;
function statsFromValues() {
return new Stats(statValues[0], statValues[1], statValues[2], statValues[3],
statValues[4], statValues[5],
statValues[6] < 0 ? undefined : statValues[6], statValues[7],
statValues[8], statValues[9] < 0 ? undefined : statValues[9],
statValues[10], statValues[11], statValues[12],
statValues[13]);
function statsFromValues(stats = statValues) {
return new Stats(stats[0], stats[1], stats[2], stats[3], stats[4], stats[5],
stats[6] < 0 ? undefined : stats[6], stats[7], stats[8],
stats[9] < 0 ? undefined : stats[9], stats[10], stats[11],
stats[12], stats[13]);
}
// Don't allow mode to accidentally be overwritten.
@ -2654,3 +2655,480 @@ Object.defineProperty(fs, 'SyncWriteStream', {
set: internalUtil.deprecate((val) => { SyncWriteStream = val; },
'fs.SyncWriteStream is deprecated.', 'DEP0061')
});
// Promises API
class FileHandle {
constructor(filehandle) {
this[kHandle] = filehandle;
}
getAsyncId() {
return this[kHandle].getAsyncId();
}
get fd() {
return this[kHandle].fd;
}
appendFile(data, options) {
return promises.appendFile(this, data, options);
}
chmod(mode) {
return promises.fchmod(this, mode);
}
chown(uid, gid) {
return promises.fchown(this, uid, gid);
}
datasync() {
return promises.fdatasync(this);
}
sync() {
return promises.fsync(this);
}
read(buffer, offset, length, position) {
return promises.read(this, buffer, offset, length, position);
}
readFile(options) {
return promises.readFile(this, options);
}
stat() {
return promises.fstat(this);
}
truncate(len = 0) {
return promises.ftruncate(this, len);
}
utimes(atime, mtime) {
return promises.futimes(this, atime, mtime);
}
write(buffer, offset, length, position) {
return promises.write(this, buffer, offset, length, position);
}
writeFile(data, options) {
return promises.writeFile(this, data, options);
}
close() {
return this[kHandle].close();
}
}
function validateFileHandle(handle) {
if (!(handle instanceof FileHandle))
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
'filehandle', 'FileHandle');
}
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 promises.write(filehandle, buffer, 0,
Math.min(16384, buffer.length));
remaining -= bytesWritten;
buffer = buffer.slice(bytesWritten);
} while (remaining > 0);
}
async function readFileHandle(filehandle, options) {
const statFields = await binding.fstat(filehandle.fd, kUsePromises);
let size;
if ((statFields[1/*mode*/] & S_IFMT) === S_IFREG) {
size = statFields[8/*size*/];
} else {
size = 0;
}
if (size === 0)
return Buffer.alloc(0);
if (size > kMaxLength)
throw new errors.RangeError('ERR_BUFFER_TOO_LARGE');
const chunks = [];
const chunkSize = Math.min(size, 16384);
const buf = Buffer.alloc(chunkSize);
let read = 0;
do {
const { bytesRead, buffer } =
await promises.read(filehandle, buf, 0, buf.length);
read = bytesRead;
if (read > 0)
chunks.push(buffer.slice(0, read));
} while (read === chunkSize);
return Buffer.concat(chunks);
}
// All of the functions in fs.promises are defined as async in order to
// ensure that errors thrown cause promise rejections rather than being
// thrown synchronously
const promises = {
async access(path, mode = fs.F_OK) {
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
mode = mode | 0;
return binding.access(pathModule.toNamespacedPath(path), mode,
kUsePromises);
},
async copyFile(src, dest, flags) {
handleError((src = getPathFromURL(src)));
handleError((dest = getPathFromURL(dest)));
nullCheck(src);
nullCheck(dest);
validatePath(src, 'src');
validatePath(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,
// promises.open() uses the fs.FileHandle class.
async open(path, flags, mode) {
mode = modeNum(mode, 0o666);
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
validateUint32(mode, 'mode');
return new FileHandle(
await binding.openFileHandle(pathModule.toNamespacedPath(path),
stringToFlags(flags),
mode, kUsePromises));
},
async read(handle, buffer, offset, length, position) {
validateFileHandle(handle);
validateBuffer(buffer);
offset |= 0;
length |= 0;
if (length === 0)
return { bytesRead: length, buffer };
validateOffsetLengthRead(offset, length, buffer.length);
if (!isUint32(position))
position = -1;
const bytesRead = (await binding.read(handle.fd, buffer, offset, length,
position, kUsePromises)) || 0;
return { bytesRead, buffer };
},
async write(handle, buffer, offset, length, position) {
validateFileHandle(handle);
if (buffer.length === 0)
return { bytesWritten: 0, buffer };
if (isUint8Array(buffer)) {
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);
const bytesWritten =
(await binding.writeBuffer(handle.fd, buffer, offset,
length, position, kUsePromises)) || 0;
return { bytesWritten, buffer };
}
if (typeof buffer !== 'string')
buffer += '';
if (typeof position !== 'function') {
if (typeof offset === 'function') {
position = offset;
offset = null;
} else {
position = length;
}
length = 'utf8';
}
const bytesWritten = (await binding.writeString(handle.fd, buffer, offset,
length, kUsePromises)) || 0;
return { bytesWritten, buffer };
},
async rename(oldPath, newPath) {
handleError((oldPath = getPathFromURL(oldPath)));
handleError((newPath = getPathFromURL(newPath)));
nullCheck(oldPath);
nullCheck(newPath);
validatePath(oldPath, 'oldPath');
validatePath(newPath, 'newPath');
return binding.rename(pathModule.toNamespacedPath(oldPath),
pathModule.toNamespacedPath(newPath),
kUsePromises);
},
async truncate(path, len = 0) {
return promises.ftruncate(await promises.open(path, 'r+'), len);
},
async ftruncate(handle, len = 0) {
validateFileHandle(handle);
validateLen(len);
len = Math.max(0, len);
return binding.ftruncate(handle.fd, len, kUsePromises);
},
async rmdir(path) {
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
return binding.rmdir(pathModule.toNamespacedPath(path), kUsePromises);
},
async fdatasync(handle) {
validateFileHandle(handle);
return binding.fdatasync(handle.fd, kUsePromises);
},
async fsync(handle) {
validateFileHandle(handle);
return binding.fsync(handle.fd, kUsePromises);
},
async mkdir(path, mode) {
mode = modeNum(mode, 0o777);
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
validateUint32(mode, 'mode');
return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises);
},
async readdir(path, options) {
options = getOptions(options, {});
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
return binding.readdir(pathModule.toNamespacedPath(path),
options.encoding, kUsePromises);
},
async readlink(path, options) {
options = getOptions(options, {});
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path, 'oldPath');
return binding.readlink(pathModule.toNamespacedPath(path),
options.encoding, kUsePromises);
},
async symlink(target, path, type_) {
const type = (typeof type_ === 'string' ? type_ : null);
handleError((target = getPathFromURL(target)));
handleError((path = getPathFromURL(path)));
nullCheck(target);
nullCheck(path);
validatePath(target, 'target');
validatePath(path);
return binding.symlink(preprocessSymlinkDestination(target, type, path),
pathModule.toNamespacedPath(path),
stringToSymlinkType(type),
kUsePromises);
},
async fstat(handle) {
validateFileHandle(handle);
return statsFromValues(await binding.fstat(handle.fd, kUsePromises));
},
async lstat(path) {
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
return statsFromValues(
await binding.lstat(pathModule.toNamespacedPath(path), kUsePromises));
},
async stat(path) {
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
return statsFromValues(
await binding.stat(pathModule.toNamespacedPath(path), kUsePromises));
},
async link(existingPath, newPath) {
handleError((existingPath = getPathFromURL(existingPath)));
handleError((newPath = getPathFromURL(newPath)));
nullCheck(existingPath);
nullCheck(newPath);
validatePath(existingPath, 'existingPath');
validatePath(newPath, 'newPath');
return binding.link(pathModule.toNamespacedPath(existingPath),
pathModule.toNamespacedPath(newPath),
kUsePromises);
},
async unlink(path) {
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
return binding.unlink(pathModule.toNamespacedPath(path), kUsePromises);
},
async fchmod(handle, mode) {
mode = modeNum(mode);
validateFileHandle(handle);
validateUint32(mode, 'mode');
if (mode < 0 || mode > 0o777)
throw new errors.RangeError('ERR_OUT_OF_RANGE', 'mode');
return binding.fchmod(handle.fd, mode, kUsePromises);
},
async chmod(path, mode) {
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
mode = modeNum(mode);
validateUint32(mode, 'mode');
return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises);
},
async lchmod(path, mode) {
if (constants.O_SYMLINK !== undefined) {
const fd = await promises.open(path,
constants.O_WRONLY | constants.O_SYMLINK);
return promises.fschmod(fd, mode).finally(fd.close.bind(fd));
}
throw new errors.Error('ERR_METHOD_NOT_IMPLEMENTED');
},
async lchown(path, uid, gid) {
if (constants.O_SYMLINK !== undefined) {
const fd = await promises.open(path,
constants.O_WRONLY | constants.O_SYMLINK);
return promises.fschmod(fd, uid, gid).finally(fd.close.bind(fd));
}
throw new errors.Error('ERR_METHOD_NOT_IMPLEMENTED');
},
async fchown(handle, uid, gid) {
validateFileHandle(handle);
validateUint32(uid, 'uid');
validateUint32(gid, 'gid');
return binding.fchown(handle.fd, uid, gid, kUsePromises);
},
async chown(path, uid, gid) {
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
validateUint32(uid, 'uid');
validateUint32(gid, 'gid');
return binding.chown(pathModule.toNamespacedPath(path),
uid, gid, kUsePromises);
},
async utimes(path, atime, mtime) {
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
return binding.utimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime),
toUnixTimestamp(mtime),
kUsePromises);
},
async futimes(handle, atime, mtime) {
validateFileHandle(handle);
atime = toUnixTimestamp(atime, 'atime');
mtime = toUnixTimestamp(mtime, 'mtime');
return binding.futimes(handle.fd, atime, mtime, kUsePromises);
},
async realpath(path, options) {
options = getOptions(options, {});
handleError((path = getPathFromURL(path)));
nullCheck(path);
validatePath(path);
return binding.realpath(path, options.encoding, kUsePromises);
},
async mkdtemp(prefix, options) {
options = getOptions(options, {});
if (!prefix || typeof prefix !== 'string') {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
'prefix',
'string',
prefix);
}
nullCheck(prefix);
return binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, kUsePromises);
},
async 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 promises.open(path, flag, options.mode);
return writeFileHandle(fd, data, options).finally(fd.close.bind(fd));
},
async appendFile(path, data, options) {
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
options = copyObject(options);
options.flag = options.flag || 'a';
return promises.writeFile(path, data, options);
},
async readFile(path, options) {
options = getOptions(options, { flag: 'r' });
if (path instanceof FileHandle)
return readFileHandle(path, options);
const fd = await promises.open(path, options.flag, 0o666);
return readFileHandle(fd, options).finally(fd.close.bind(fd));
}
};
let warn = true;
// TODO(jasnell): Exposing this as a property with a getter works fine with
// commonjs but is going to be problematic for named imports support under
// ESM. A different approach will have to be followed there.
Object.defineProperty(fs, 'promises', {
configurable: true,
enumerable: true,
get() {
if (warn) {
warn = false;
process.emitWarning('The fs.promises API is experimental',
'ExperimentalWarning');
}
return promises;
}
});

View file

@ -242,7 +242,6 @@ class ModuleWrap;
V(sni_context_string, "sni_context") \
V(stack_string, "stack") \
V(status_string, "status") \
V(statfields_string, "statFields") \
V(stdio_string, "stdio") \
V(subject_string, "subject") \
V(subjectaltname_string, "subjectaltname") \
@ -284,6 +283,7 @@ class ModuleWrap;
V(context, v8::Context) \
V(domain_callback, v8::Function) \
V(fd_constructor_template, v8::ObjectTemplate) \
V(fsreqpromise_constructor_template, v8::ObjectTemplate) \
V(fdclose_constructor_template, v8::ObjectTemplate) \
V(host_import_module_dynamically_callback, v8::Function) \
V(host_initialize_import_meta_object_callback, v8::Function) \
@ -313,6 +313,7 @@ class ModuleWrap;
V(vm_parsing_context_symbol, v8::Symbol) \
V(url_constructor_function, v8::Function) \
V(write_wrap_constructor_function, v8::Function) \
V(fs_use_promises_symbol, v8::Symbol)
class Environment;

View file

@ -85,6 +85,7 @@ namespace fs {
using v8::Array;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Float64Array;
using v8::Function;
using v8::FunctionCallbackInfo;
@ -100,6 +101,7 @@ using v8::Object;
using v8::ObjectTemplate;
using v8::Promise;
using v8::String;
using v8::Symbol;
using v8::Undefined;
using v8::Value;
@ -169,7 +171,7 @@ inline void FileHandle::Close() {
// If the close was successful, we still want to emit a process warning
// to notify that the file descriptor was gc'd. We want to be noisy about
// this because not explicitly closing the garbage collector is a bug.
// this because not explicitly closing the FileHandle is a bug.
env()->SetUnrefImmediate([](Environment* env, void* data) {
char msg[70];
err_detail* detail = static_cast<err_detail*>(data);
@ -182,22 +184,22 @@ inline void FileHandle::Close() {
}
void FileHandle::CloseReq::Resolve() {
InternalCallbackScope callback_scope(this);
HandleScope scope(env()->isolate());
InternalCallbackScope callback_scope(this);
Local<Promise> promise = promise_.Get(env()->isolate());
Local<Promise::Resolver> resolver = promise.As<Promise::Resolver>();
resolver->Resolve(env()->context(), Undefined(env()->isolate()));
resolver->Resolve(env()->context(), Undefined(env()->isolate())).FromJust();
}
void FileHandle::CloseReq::Reject(Local<Value> reason) {
InternalCallbackScope callback_scope(this);
HandleScope scope(env()->isolate());
InternalCallbackScope callback_scope(this);
Local<Promise> promise = promise_.Get(env()->isolate());
Local<Promise::Resolver> resolver = promise.As<Promise::Resolver>();
resolver->Reject(env()->context(), reason);
resolver->Reject(env()->context(), reason).FromJust();
}
FileHandle* FileHandle::CloseReq::fd() {
FileHandle* FileHandle::CloseReq::file_handle() {
HandleScope scope(env()->isolate());
Local<Value> val = ref_.Get(env()->isolate());
Local<Object> obj = val.As<Object>();
@ -209,9 +211,9 @@ FileHandle* FileHandle::CloseReq::fd() {
// there was a problem closing the fd. This is the preferred mechanism for
// closing the FD object even tho the object will attempt to close
// automatically on gc.
inline Local<Promise> FileHandle::ClosePromise() {
inline MaybeLocal<Promise> FileHandle::ClosePromise() {
Isolate* isolate = env()->isolate();
HandleScope scope(isolate);
EscapableHandleScope scope(isolate);
Local<Context> context = env()->context();
auto maybe_resolver = Promise::Resolver::New(context);
CHECK(!maybe_resolver.IsEmpty());
@ -223,12 +225,12 @@ inline Local<Promise> FileHandle::ClosePromise() {
auto AfterClose = [](uv_fs_t* req) {
CloseReq* close = static_cast<CloseReq*>(req->data);
CHECK_NE(close, nullptr);
close->fd()->closing_ = false;
close->file_handle()->closing_ = false;
Isolate* isolate = close->env()->isolate();
if (req->result < 0) {
close->Reject(UVException(isolate, req->result, "close"));
} else {
close->fd()->closed_ = true;
close->file_handle()->closed_ = true;
close->Resolve();
}
delete close;
@ -241,15 +243,16 @@ inline Local<Promise> FileHandle::ClosePromise() {
}
} else {
// Already closed. Just reject the promise immediately
resolver->Reject(context, UVException(isolate, UV_EBADF, "close"));
resolver->Reject(context, UVException(isolate, UV_EBADF, "close"))
.FromJust();
}
return promise;
return scope.Escape(promise);
}
void FileHandle::Close(const FunctionCallbackInfo<Value>& args) {
FileHandle* fd;
ASSIGN_OR_RETURN_UNWRAP(&fd, args.Holder());
args.GetReturnValue().Set(fd->ClosePromise());
args.GetReturnValue().Set(fd->ClosePromise().ToLocalChecked());
}
@ -273,24 +276,31 @@ void FSReqWrap::Resolve(Local<Value> value) {
MakeCallback(env()->oncomplete_string(), arraysize(argv), argv);
}
void FSReqWrap::SetReturnValue(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().SetUndefined();
}
void FSReqPromise::SetReturnValue(const FunctionCallbackInfo<Value>& args) {
Local<Context> context = env()->context();
args.GetReturnValue().Set(
object()->Get(context, env()->promise_string()).ToLocalChecked());
}
void NewFSReqWrap(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
Environment* env = Environment::GetCurrent(args.GetIsolate());
new FSReqWrap(env, args.This());
}
FSReqPromise::FSReqPromise(Environment* env, Local<Object> req)
: FSReqBase(env, req, AsyncWrap::PROVIDER_FSREQPROMISE) {
FSReqPromise::FSReqPromise(Environment* env)
: FSReqBase(env,
env->fsreqpromise_constructor_template()
->NewInstance(env->context()).ToLocalChecked(),
AsyncWrap::PROVIDER_FSREQPROMISE),
stats_field_array_(env->isolate(), 14) {
auto resolver = Promise::Resolver::New(env->context()).ToLocalChecked();
req->Set(env->context(), env->promise_string(),
resolver.As<Promise>()).FromJust();
Local<ArrayBuffer> ab =
ArrayBuffer::New(env->isolate(), statFields_,
sizeof(double) * 14);
object()->Set(env->context(),
env->statfields_string(),
Float64Array::New(ab, 0, 14)).FromJust();
object()->Set(env->context(), env->promise_string(),
resolver.As<Promise>()).FromJust();
}
FSReqPromise::~FSReqPromise() {
@ -300,44 +310,35 @@ FSReqPromise::~FSReqPromise() {
void FSReqPromise::Reject(Local<Value> reject) {
finished_ = true;
InternalCallbackScope callback_scope(this);
HandleScope scope(env()->isolate());
InternalCallbackScope callback_scope(this);
Local<Value> value =
object()->Get(env()->context(),
env()->promise_string()).ToLocalChecked();
CHECK(value->IsPromise());
Local<Promise> promise = value.As<Promise>();
Local<Promise::Resolver> resolver = promise.As<Promise::Resolver>();
resolver->Reject(env()->context(), reject);
resolver->Reject(env()->context(), reject).FromJust();
}
void FSReqPromise::FillStatsArray(const uv_stat_t* stat) {
node::FillStatsArray(statFields_, stat);
node::FillStatsArray(&stats_field_array_, stat);
}
void FSReqPromise::ResolveStat() {
Resolve(
object()->Get(env()->context(),
env()->statfields_string()).ToLocalChecked());
Resolve(stats_field_array_.GetJSArray());
}
void FSReqPromise::Resolve(Local<Value> value) {
finished_ = true;
InternalCallbackScope callback_scope(this);
HandleScope scope(env()->isolate());
InternalCallbackScope callback_scope(this);
Local<Value> val =
object()->Get(env()->context(),
env()->promise_string()).ToLocalChecked();
CHECK(val->IsPromise());
Local<Promise> promise = val.As<Promise>();
Local<Promise::Resolver> resolver = promise.As<Promise::Resolver>();
resolver->Resolve(env()->context(), value);
}
void NewFSReqPromise(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
Environment* env = Environment::GetCurrent(args.GetIsolate());
new FSReqPromise(env, args.This());
Local<Promise::Resolver> resolver = val.As<Promise::Resolver>();
resolver->Resolve(env()->context(), value).FromJust();
}
FSReqAfterScope::FSReqAfterScope(FSReqBase* wrap, uv_fs_t* req)
@ -519,11 +520,10 @@ class fs_req_wrap {
template <typename Func, typename... Args>
inline FSReqBase* AsyncDestCall(Environment* env,
FSReqBase* req_wrap,
const FunctionCallbackInfo<Value>& args,
const char* syscall, const char* dest, size_t len,
enum encoding enc, uv_fs_cb after, Func fn, Args... fn_args) {
Local<Object> req = args[args.Length() - 1].As<Object>();
FSReqBase* req_wrap = Unwrap<FSReqBase>(req);
CHECK_NE(req_wrap, nullptr);
req_wrap->Init(syscall, dest, len, enc);
int err = fn(env->event_loop(), req_wrap->req(), fn_args..., after);
@ -544,10 +544,11 @@ inline FSReqBase* AsyncDestCall(Environment* env,
template <typename Func, typename... Args>
inline FSReqBase* AsyncCall(Environment* env,
FSReqBase* req_wrap,
const FunctionCallbackInfo<Value>& args,
const char* syscall, enum encoding enc,
uv_fs_cb after, Func fn, Args... fn_args) {
return AsyncDestCall(env, args,
return AsyncDestCall(env, req_wrap, args,
syscall, nullptr, 0, enc,
after, fn, fn_args...);
}
@ -576,10 +577,10 @@ inline int SyncCall(Environment* env, Local<Value> ctx, fs_req_wrap* req_wrap,
}
#define SYNC_DEST_CALL(func, path, dest, ...) \
fs_req_wrap req_wrap; \
fs_req_wrap sync_wrap; \
env->PrintSyncTrace(); \
int err = uv_fs_ ## func(env->event_loop(), \
&req_wrap.req, \
&sync_wrap.req, \
__VA_ARGS__, \
nullptr); \
if (err < 0) { \
@ -589,10 +590,19 @@ inline int SyncCall(Environment* env, Local<Value> ctx, fs_req_wrap* req_wrap,
#define SYNC_CALL(func, path, ...) \
SYNC_DEST_CALL(func, path, nullptr, __VA_ARGS__) \
#define SYNC_REQ req_wrap.req
#define SYNC_REQ sync_wrap.req
#define SYNC_RESULT err
inline FSReqBase* GetReqWrap(Environment* env, Local<Value> value) {
if (value->IsObject()) {
return Unwrap<FSReqBase>(value.As<Object>());
} else if (value->StrictEquals(env->fs_use_promises_symbol())) {
return new FSReqPromise(env);
}
return nullptr;
}
void Access(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args.GetIsolate());
HandleScope scope(env->isolate());
@ -606,10 +616,11 @@ void Access(const FunctionCallbackInfo<Value>& args) {
BufferValue path(env->isolate(), args[0]);
CHECK_NE(*path, nullptr);
if (args[2]->IsObject()) { // access(path, mode, req)
CHECK_EQ(argc, 3);
AsyncCall(env, args, "access", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[2]);
if (req_wrap != nullptr) { // access(path, mode, req)
AsyncCall(env, req_wrap, args, "access", UTF8, AfterNoArgs,
uv_fs_access, *path, mode);
req_wrap->SetReturnValue(args);
} else { // access(path, mode, undefined, ctx)
CHECK_EQ(argc, 4);
fs_req_wrap req_wrap;
@ -627,10 +638,11 @@ void Close(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsInt32());
int fd = args[0].As<Int32>()->Value();
if (args[1]->IsObject()) { // close(fd, req)
CHECK_EQ(argc, 2);
AsyncCall(env, args, "close", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[1]);
if (req_wrap != nullptr) { // close(fd, req)
AsyncCall(env, req_wrap, args, "close", UTF8, AfterNoArgs,
uv_fs_close, fd);
req_wrap->SetReturnValue(args);
} else { // close(fd, undefined, ctx)
CHECK_EQ(argc, 3);
fs_req_wrap req_wrap;
@ -732,10 +744,11 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
BufferValue path(env->isolate(), args[0]);
CHECK_NE(*path, nullptr);
if (args[1]->IsObject()) { // stat(path, req)
CHECK_EQ(argc, 2);
AsyncCall(env, args, "stat", UTF8, AfterStat,
FSReqBase* req_wrap = GetReqWrap(env, args[1]);
if (req_wrap != nullptr) { // stat(path, req)
AsyncCall(env, req_wrap, args, "stat", UTF8, AfterStat,
uv_fs_stat, *path);
req_wrap->SetReturnValue(args);
} else { // stat(path, undefined, ctx)
CHECK_EQ(argc, 3);
fs_req_wrap req_wrap;
@ -756,10 +769,11 @@ static void LStat(const FunctionCallbackInfo<Value>& args) {
BufferValue path(env->isolate(), args[0]);
CHECK_NE(*path, nullptr);
if (args[1]->IsObject()) { // lstat(path, req)
CHECK_EQ(argc, 2);
AsyncCall(env, args, "lstat", UTF8, AfterStat,
FSReqBase* req_wrap = GetReqWrap(env, args[1]);
if (req_wrap != nullptr) { // lstat(path, req)
AsyncCall(env, req_wrap, args, "lstat", UTF8, AfterStat,
uv_fs_lstat, *path);
req_wrap->SetReturnValue(args);
} else { // lstat(path, undefined, ctx)
CHECK_EQ(argc, 3);
fs_req_wrap req_wrap;
@ -780,10 +794,11 @@ static void FStat(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsInt32());
int fd = args[0].As<Int32>()->Value();
if (args[1]->IsObject()) { // fstat(fd, req)
CHECK_EQ(argc, 2);
AsyncCall(env, args, "fstat", UTF8, AfterStat,
FSReqBase* req_wrap = GetReqWrap(env, args[1]);
if (req_wrap != nullptr) { // fstat(fd, req)
AsyncCall(env, req_wrap, args, "fstat", UTF8, AfterStat,
uv_fs_fstat, fd);
req_wrap->SetReturnValue(args);
} else { // fstat(fd, undefined, ctx)
CHECK_EQ(argc, 3);
fs_req_wrap req_wrap;
@ -809,14 +824,14 @@ static void Symlink(const FunctionCallbackInfo<Value>& args) {
CHECK(args[2]->IsInt32());
int flags = args[2].As<Int32>()->Value();
if (args[3]->IsObject()) { // symlink(target, path, flags, req)
CHECK_EQ(args.Length(), 4);
AsyncDestCall(env, args, "symlink", *path, path.length(), UTF8,
FSReqBase* req_wrap = GetReqWrap(env, args[3]);
if (req_wrap != nullptr) { // symlink(target, path, flags, req)
AsyncDestCall(env, req_wrap, args, "symlink", *path, path.length(), UTF8,
AfterNoArgs, uv_fs_symlink, *target, *path, flags);
} else { // symlink(target, path, flags, undefinec, ctx)
CHECK_EQ(argc, 5);
fs_req_wrap req_wrap;
SyncCall(env, args[4], &req_wrap, "symlink",
fs_req_wrap req;
SyncCall(env, args[4], &req, "symlink",
uv_fs_symlink, *target, *path, flags);
}
}
@ -833,14 +848,15 @@ static void Link(const FunctionCallbackInfo<Value>& args) {
BufferValue dest(env->isolate(), args[1]);
CHECK_NE(*dest, nullptr);
if (args[2]->IsObject()) { // link(src, dest, req)
CHECK_EQ(argc, 3);
AsyncDestCall(env, args, "link", *dest, dest.length(), UTF8,
FSReqBase* req_wrap = GetReqWrap(env, args[2]);
if (req_wrap != nullptr) { // link(src, dest, req)
AsyncDestCall(env, req_wrap, args, "link", *dest, dest.length(), UTF8,
AfterNoArgs, uv_fs_link, *src, *dest);
} else { // link(src, dest, undefined, ctx)
req_wrap->SetReturnValue(args);
} else { // link(src, dest)
CHECK_EQ(argc, 4);
fs_req_wrap req_wrap;
SyncCall(env, args[3], &req_wrap, "link",
fs_req_wrap req;
SyncCall(env, args[3], &req, "link",
uv_fs_link, *src, *dest);
}
}
@ -856,19 +872,20 @@ static void ReadLink(const FunctionCallbackInfo<Value>& args) {
const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);
if (args[2]->IsObject()) { // readlink(path, encoding, req)
CHECK_EQ(argc, 3);
AsyncCall(env, args, "readlink", encoding, AfterStringPtr,
FSReqBase* req_wrap = GetReqWrap(env, args[2]);
if (req_wrap != nullptr) { // readlink(path, encoding, req)
AsyncCall(env, req_wrap, args, "readlink", encoding, AfterStringPtr,
uv_fs_readlink, *path);
} else { // readlink(path, encoding, undefined, ctx)
req_wrap->SetReturnValue(args);
} else {
CHECK_EQ(argc, 4);
fs_req_wrap req_wrap;
int err = SyncCall(env, args[3], &req_wrap, "readlink",
fs_req_wrap req;
int err = SyncCall(env, args[3], &req, "readlink",
uv_fs_readlink, *path);
if (err) {
return; // syscall failed, no need to continue, error info is in ctx
}
const char* link_path = static_cast<const char*>(req_wrap.req.ptr);
const char* link_path = static_cast<const char*>(req.req.ptr);
Local<Value> error;
MaybeLocal<Value> rc = StringBytes::Encode(env->isolate(),
@ -896,15 +913,15 @@ static void Rename(const FunctionCallbackInfo<Value>& args) {
BufferValue new_path(env->isolate(), args[1]);
CHECK_NE(*new_path, nullptr);
if (args[2]->IsObject()) { // rename(old_path, new_path, req)
CHECK_EQ(argc, 3);
AsyncDestCall(env, args, "rename", *new_path, new_path.length(),
FSReqBase* req_wrap = GetReqWrap(env, args[2]);
if (req_wrap != nullptr) {
AsyncDestCall(env, req_wrap, args, "rename", *new_path, new_path.length(),
UTF8, AfterNoArgs, uv_fs_rename, *old_path, *new_path);
} else { // rename(old_path, new_path, undefined, ctx)
req_wrap->SetReturnValue(args);
} else {
CHECK_EQ(argc, 4);
fs_req_wrap req_wrap;
SyncCall(env, args[3], &req_wrap, "rename",
uv_fs_rename, *old_path, *new_path);
fs_req_wrap req;
SyncCall(env, args[3], &req, "rename", uv_fs_rename, *old_path, *new_path);
}
}
@ -920,15 +937,15 @@ static void FTruncate(const FunctionCallbackInfo<Value>& args) {
CHECK(args[1]->IsNumber());
const int64_t len = args[1].As<Integer>()->Value();
if (args[2]->IsObject()) { // ftruncate(fd, len, req)
CHECK_EQ(argc, 3);
AsyncCall(env, args, "ftruncate", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[2]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "ftruncate", UTF8, AfterNoArgs,
uv_fs_ftruncate, fd, len);
} else { // ftruncate(fd, len, undefined, ctx)
req_wrap->SetReturnValue(args);
} else {
CHECK_EQ(argc, 4);
fs_req_wrap req_wrap;
SyncCall(env, args[3], &req_wrap, "ftruncate",
uv_fs_ftruncate, fd, len);
fs_req_wrap req;
SyncCall(env, args[3], &req, "ftruncate", uv_fs_ftruncate, fd, len);
}
}
@ -941,15 +958,15 @@ static void Fdatasync(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsInt32());
const int fd = args[0].As<Int32>()->Value();
if (args[1]->IsObject()) { // fdatasync(fd, req)
CHECK_EQ(argc, 2);
AsyncCall(env, args, "fdatasync", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[1]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "fdatasync", UTF8, AfterNoArgs,
uv_fs_fdatasync, fd);
} else { // fdatasync(fd, undefined, ctx)
req_wrap->SetReturnValue(args);
} else {
CHECK_EQ(argc, 3);
fs_req_wrap req_wrap;
SyncCall(env, args[2], &req_wrap, "fdatasync",
uv_fs_fdatasync, fd);
fs_req_wrap req;
SyncCall(env, args[2], &req, "fdatasync", uv_fs_fdatasync, fd);
}
}
@ -962,15 +979,15 @@ static void Fsync(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsInt32());
const int fd = args[0].As<Int32>()->Value();
if (args[1]->IsObject()) { // fsync(fd, req)
CHECK_EQ(argc, 2);
AsyncCall(env, args, "fsync", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[1]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "fsync", UTF8, AfterNoArgs,
uv_fs_fsync, fd);
} else { // fsync(fd, undefined, ctx)
req_wrap->SetReturnValue(args);
} else {
CHECK_EQ(argc, 3);
fs_req_wrap req_wrap;
SyncCall(env, args[2], &req_wrap, "fsync",
uv_fs_fsync, fd);
fs_req_wrap req;
SyncCall(env, args[2], &req, "fsync", uv_fs_fsync, fd);
}
}
@ -983,15 +1000,15 @@ static void Unlink(const FunctionCallbackInfo<Value>& args) {
BufferValue path(env->isolate(), args[0]);
CHECK_NE(*path, nullptr);
if (args[1]->IsObject()) { // unlink(fd, req)
CHECK_EQ(argc, 2);
AsyncCall(env, args, "unlink", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[1]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "unlink", UTF8, AfterNoArgs,
uv_fs_unlink, *path);
} else { // unlink(fd, undefined, ctx)
req_wrap->SetReturnValue(args);
} else {
CHECK_EQ(argc, 3);
fs_req_wrap req_wrap;
SyncCall(env, args[2], &req_wrap, "unlink",
uv_fs_unlink, *path);
fs_req_wrap req;
SyncCall(env, args[2], &req, "unlink", uv_fs_unlink, *path);
}
}
@ -1003,10 +1020,11 @@ static void RMDir(const FunctionCallbackInfo<Value>& args) {
BufferValue path(env->isolate(), args[0]);
CHECK_NE(*path, nullptr);
if (args[1]->IsObject()) {
CHECK_EQ(args.Length(), 2);
AsyncCall(env, args, "rmdir", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[1]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "rmdir", UTF8, AfterNoArgs,
uv_fs_rmdir, *path);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(rmdir, *path, *path)
}
@ -1023,10 +1041,11 @@ static void MKDir(const FunctionCallbackInfo<Value>& args) {
int mode = static_cast<int>(args[1]->Int32Value());
if (args[2]->IsObject()) {
CHECK_EQ(args.Length(), 3);
AsyncCall(env, args, "mkdir", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[2]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "mkdir", UTF8, AfterNoArgs,
uv_fs_mkdir, *path, mode);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(mkdir, *path, *path, mode)
}
@ -1040,10 +1059,11 @@ static void RealPath(const FunctionCallbackInfo<Value>& args) {
const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);
if (args[2]->IsObject()) {
CHECK_EQ(args.Length(), 3);
AsyncCall(env, args, "realpath", encoding, AfterStringPtr,
FSReqBase* req_wrap = GetReqWrap(env, args[2]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "realpath", encoding, AfterStringPtr,
uv_fs_realpath, *path);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(realpath, *path, *path);
const char* link_path = static_cast<const char*>(SYNC_REQ.ptr);
@ -1071,10 +1091,11 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);
if (args[2]->IsObject()) {
CHECK_EQ(args.Length(), 3);
AsyncCall(env, args, "scandir", encoding, AfterScanDir,
FSReqBase* req_wrap = GetReqWrap(env, args[2]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "scandir", encoding, AfterScanDir,
uv_fs_scandir, *path, 0 /*flags*/);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(scandir, *path, *path, 0 /*flags*/)
@ -1135,10 +1156,11 @@ static void Open(const FunctionCallbackInfo<Value>& args) {
int flags = args[1]->Int32Value(context).ToChecked();
int mode = args[2]->Int32Value(context).ToChecked();
if (args[3]->IsObject()) {
CHECK_EQ(args.Length(), 4);
AsyncCall(env, args, "open", UTF8, AfterInteger,
FSReqBase* req_wrap = GetReqWrap(env, args[3]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "open", UTF8, AfterInteger,
uv_fs_open, *path, flags, mode);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(open, *path, *path, flags, mode)
args.GetReturnValue().Set(SYNC_RESULT);
@ -1159,10 +1181,11 @@ static void OpenFileHandle(const FunctionCallbackInfo<Value>& args) {
int flags = args[1]->Int32Value(context).ToChecked();
int mode = args[2]->Int32Value(context).ToChecked();
if (args[3]->IsObject()) {
CHECK_EQ(args.Length(), 4);
AsyncCall(env, args, "open", UTF8, AfterOpenFileHandle,
FSReqBase* req_wrap = GetReqWrap(env, args[3]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "open", UTF8, AfterOpenFileHandle,
uv_fs_open, *path, flags, mode);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(open, *path, *path, flags, mode)
if (SYNC_RESULT < 0) {
@ -1187,10 +1210,11 @@ static void CopyFile(const FunctionCallbackInfo<Value>& args) {
CHECK_NE(*dest, nullptr);
int flags = args[2]->Int32Value();
if (args[3]->IsObject()) {
CHECK_EQ(args.Length(), 4);
AsyncCall(env, args, "copyfile", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[3]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "copyfile", UTF8, AfterNoArgs,
uv_fs_copyfile, *src, *dest, flags);
req_wrap->SetReturnValue(args);
} else {
SYNC_DEST_CALL(copyfile, *src, *dest, *src, *dest, flags)
}
@ -1229,11 +1253,11 @@ static void WriteBuffer(const FunctionCallbackInfo<Value>& args) {
uv_buf_t uvbuf = uv_buf_init(const_cast<char*>(buf), len);
if (args[5]->IsObject()) {
CHECK_EQ(args.Length(), 6);
AsyncCall(env, args, "write", UTF8, AfterInteger,
FSReqBase* req_wrap = GetReqWrap(env, args[5]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "write", UTF8, AfterInteger,
uv_fs_write, fd, &uvbuf, 1, pos);
return;
return req_wrap->SetReturnValue(args);
}
SYNC_CALL(write, nullptr, fd, &uvbuf, 1, pos)
@ -1266,11 +1290,11 @@ static void WriteBuffers(const FunctionCallbackInfo<Value>& args) {
iovs[i] = uv_buf_init(Buffer::Data(chunk), Buffer::Length(chunk));
}
if (args[3]->IsObject()) {
CHECK_EQ(args.Length(), 4);
AsyncCall(env, args, "write", UTF8, AfterInteger,
FSReqBase* req_wrap = GetReqWrap(env, args[3]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "write", UTF8, AfterInteger,
uv_fs_write, fd, *iovs, iovs.length(), pos);
return;
return req_wrap->SetReturnValue(args);
}
SYNC_CALL(write, nullptr, fd, *iovs, iovs.length(), pos)
@ -1299,7 +1323,9 @@ static void WriteString(const FunctionCallbackInfo<Value>& args) {
size_t len;
const int64_t pos = GET_OFFSET(args[2]);
const auto enc = ParseEncoding(env->isolate(), args[3], UTF8);
const auto is_async = args[4]->IsObject();
FSReqBase* req_wrap = GetReqWrap(env, args[4]);
const auto is_async = req_wrap != nullptr;
// Avoid copying the string when it is externalized but only when:
// 1. The target encoding is compatible with the string's encoding, and
@ -1333,10 +1359,10 @@ static void WriteString(const FunctionCallbackInfo<Value>& args) {
uv_buf_t uvbuf = uv_buf_init(buf, len);
if (is_async) {
CHECK_EQ(args.Length(), 5);
AsyncCall(env, args, "write", UTF8, AfterInteger,
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "write", UTF8, AfterInteger,
uv_fs_write, fd, &uvbuf, 1, pos);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(write, nullptr, fd, &uvbuf, 1, pos)
return args.GetReturnValue().Set(SYNC_RESULT);
@ -1387,10 +1413,11 @@ static void Read(const FunctionCallbackInfo<Value>& args) {
uv_buf_t uvbuf = uv_buf_init(const_cast<char*>(buf), len);
if (args[5]->IsObject()) {
CHECK_EQ(args.Length(), 6);
AsyncCall(env, args, "read", UTF8, AfterInteger,
FSReqBase* req_wrap = GetReqWrap(env, args[5]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "read", UTF8, AfterInteger,
uv_fs_read, fd, &uvbuf, 1, pos);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(read, 0, fd, &uvbuf, 1, pos)
args.GetReturnValue().Set(SYNC_RESULT);
@ -1412,10 +1439,11 @@ static void Chmod(const FunctionCallbackInfo<Value>& args) {
int mode = static_cast<int>(args[1]->Int32Value());
if (args[2]->IsObject()) {
CHECK_EQ(args.Length(), 3);
AsyncCall(env, args, "chmod", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[2]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "chmod", UTF8, AfterNoArgs,
uv_fs_chmod, *path, mode);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(chmod, *path, *path, mode);
}
@ -1434,10 +1462,11 @@ static void FChmod(const FunctionCallbackInfo<Value>& args) {
int fd = args[0]->Int32Value();
int mode = static_cast<int>(args[1]->Int32Value());
if (args[2]->IsObject()) {
CHECK_EQ(args.Length(), 3);
AsyncCall(env, args, "fchmod", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[2]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "fchmod", UTF8, AfterNoArgs,
uv_fs_fchmod, fd, mode);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(fchmod, 0, fd, mode);
}
@ -1461,10 +1490,11 @@ static void Chown(const FunctionCallbackInfo<Value>& args) {
uv_uid_t uid = static_cast<uv_uid_t>(args[1]->Uint32Value());
uv_gid_t gid = static_cast<uv_gid_t>(args[2]->Uint32Value());
if (args[3]->IsObject()) {
CHECK_EQ(args.Length(), 4);
AsyncCall(env, args, "chown", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[3]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "chown", UTF8, AfterNoArgs,
uv_fs_chown, *path, uid, gid);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(chown, *path, *path, uid, gid);
}
@ -1485,10 +1515,11 @@ static void FChown(const FunctionCallbackInfo<Value>& args) {
uv_uid_t uid = static_cast<uv_uid_t>(args[1]->Uint32Value());
uv_gid_t gid = static_cast<uv_gid_t>(args[2]->Uint32Value());
if (args[3]->IsObject()) {
CHECK_EQ(args.Length(), 4);
AsyncCall(env, args, "fchown", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[3]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "fchown", UTF8, AfterNoArgs,
uv_fs_fchown, fd, uid, gid);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(fchown, 0, fd, uid, gid);
}
@ -1508,10 +1539,11 @@ static void UTimes(const FunctionCallbackInfo<Value>& args) {
const double atime = static_cast<double>(args[1]->NumberValue());
const double mtime = static_cast<double>(args[2]->NumberValue());
if (args[3]->IsObject()) {
CHECK_EQ(args.Length(), 4);
AsyncCall(env, args, "utime", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[3]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "utime", UTF8, AfterNoArgs,
uv_fs_utime, *path, atime, mtime);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(utime, *path, *path, atime, mtime);
}
@ -1528,10 +1560,11 @@ static void FUTimes(const FunctionCallbackInfo<Value>& args) {
const double atime = static_cast<double>(args[1]->NumberValue());
const double mtime = static_cast<double>(args[2]->NumberValue());
if (args[3]->IsObject()) {
CHECK_EQ(args.Length(), 4);
AsyncCall(env, args, "futime", UTF8, AfterNoArgs,
FSReqBase* req_wrap = GetReqWrap(env, args[3]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "futime", UTF8, AfterNoArgs,
uv_fs_futime, fd, atime, mtime);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(futime, 0, fd, atime, mtime);
}
@ -1547,10 +1580,11 @@ static void Mkdtemp(const FunctionCallbackInfo<Value>& args) {
const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);
if (args[2]->IsObject()) {
CHECK_EQ(args.Length(), 3);
AsyncCall(env, args, "mkdtemp", encoding, AfterStringPath,
FSReqBase* req_wrap = GetReqWrap(env, args[2]);
if (req_wrap != nullptr) {
AsyncCall(env, req_wrap, args, "mkdtemp", encoding, AfterStringPath,
uv_fs_mkdtemp, *tmpl);
req_wrap->SetReturnValue(args);
} else {
SYNC_CALL(mkdtemp, *tmpl, *tmpl);
const char* path = static_cast<const char*>(SYNC_REQ.path);
@ -1629,14 +1663,14 @@ void InitFs(Local<Object> target,
target->Set(context, wrapString, fst->GetFunction()).FromJust();
// Create Function Template for FSReqPromise
Local<FunctionTemplate> fpt =
FunctionTemplate::New(env->isolate(), NewFSReqPromise);
fpt->InstanceTemplate()->SetInternalFieldCount(1);
Local<FunctionTemplate> fpt = FunctionTemplate::New(env->isolate());
AsyncWrap::AddWrapMethods(env, fpt);
Local<String> promiseString =
FIXED_ONE_BYTE_STRING(env->isolate(), "FSReqPromise");
fpt->SetClassName(promiseString);
target->Set(context, promiseString, fpt->GetFunction()).FromJust();
Local<ObjectTemplate> fpo = fpt->InstanceTemplate();
fpo->SetInternalFieldCount(1);
env->set_fsreqpromise_constructor_template(fpo);
// Create FunctionTemplate for FileHandle
Local<FunctionTemplate> fd = FunctionTemplate::New(env->isolate());
@ -1658,6 +1692,14 @@ void InitFs(Local<Object> target,
Local<ObjectTemplate> fdcloset = fdclose->InstanceTemplate();
fdcloset->SetInternalFieldCount(1);
env->set_fdclose_constructor_template(fdcloset);
Local<Symbol> use_promises_symbol =
Symbol::New(env->isolate(),
FIXED_ONE_BYTE_STRING(env->isolate(), "use promises"));
env->set_fs_use_promises_symbol(use_promises_symbol);
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "kUsePromises"),
use_promises_symbol).FromJust();
}
} // namespace fs

View file

@ -12,6 +12,7 @@ using v8::Context;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Local;
using v8::MaybeLocal;
using v8::Object;
using v8::Persistent;
using v8::Promise;
@ -51,6 +52,7 @@ class FSReqBase : public ReqWrap<uv_fs_t> {
virtual void Reject(Local<Value> reject) = 0;
virtual void Resolve(Local<Value> value) = 0;
virtual void ResolveStat() = 0;
virtual void SetReturnValue(const FunctionCallbackInfo<Value>& args) = 0;
const char* syscall() const { return syscall_; }
const char* data() const { return data_; }
@ -77,6 +79,7 @@ class FSReqWrap : public FSReqBase {
void Reject(Local<Value> reject) override;
void Resolve(Local<Value> value) override;
void ResolveStat() override;
void SetReturnValue(const FunctionCallbackInfo<Value>& args) override;
private:
DISALLOW_COPY_AND_ASSIGN(FSReqWrap);
@ -84,7 +87,7 @@ class FSReqWrap : public FSReqBase {
class FSReqPromise : public FSReqBase {
public:
FSReqPromise(Environment* env, Local<Object> req);
explicit FSReqPromise(Environment* env);
~FSReqPromise() override;
@ -92,10 +95,11 @@ class FSReqPromise : public FSReqBase {
void Reject(Local<Value> reject) override;
void Resolve(Local<Value> value) override;
void ResolveStat() override;
void SetReturnValue(const FunctionCallbackInfo<Value>& args) override;
private:
bool finished_ = false;
double statFields_[14] {};
AliasedBuffer<double, v8::Float64Array> stats_field_array_;
DISALLOW_COPY_AND_ASSIGN(FSReqPromise);
};
@ -152,7 +156,7 @@ class FileHandle : public AsyncWrap {
ref_.Empty();
}
FileHandle* fd();
FileHandle* file_handle();
size_t self_size() const override { return sizeof(*this); }
@ -166,7 +170,7 @@ class FileHandle : public AsyncWrap {
};
// Asynchronous close
inline Local<Promise> ClosePromise();
inline MaybeLocal<Promise> ClosePromise();
int fd_;
bool closing_ = false;

View file

@ -0,0 +1,25 @@
// Flags: --expose-gc --no-warnings --expose-internals
'use strict';
const common = require('../common');
const path = require('path');
const fs = process.binding('fs');
const { stringToFlags } = require('internal/fs');
// Verifies that the FileHandle object is garbage collected and that a
// warning is emitted if it is not closed.
let fdnum;
{
fdnum = fs.openFileHandle(path.toNamespacedPath(__filename),
stringToFlags('r'), 0o666).fd;
}
common.expectWarning(
'Warning',
`Closing file descriptor ${fdnum} on garbage collection`
);
gc(); // eslint-disable-line no-undef
setTimeout(() => {}, 10);

View file

@ -0,0 +1,40 @@
'use strict';
const common = require('../common');
const fs = require('fs');
const path = require('path');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const tmpDir = tmpdir.path;
tmpdir.refresh();
common.crashOnUnhandledRejection();
const dest = path.resolve(tmpDir, 'tmp.txt');
const buffer = Buffer.from('abc'.repeat(1000));
const buffer2 = Buffer.from('xyz'.repeat(1000));
async function doWrite() {
await fs.promises.writeFile(dest, buffer);
const data = fs.readFileSync(dest);
assert.deepStrictEqual(data, buffer);
}
async function doAppend() {
await fs.promises.appendFile(dest, buffer2);
const data = fs.readFileSync(dest);
const buf = Buffer.concat([buffer, buffer2]);
assert.deepStrictEqual(buf, data);
}
async function doRead() {
const data = await fs.promises.readFile(dest);
const buf = fs.readFileSync(dest);
assert.deepStrictEqual(buf, data);
}
doWrite()
.then(doAppend)
.then(doRead)
.then(common.mustCall());

View file

@ -0,0 +1,150 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tmpdir = require('../common/tmpdir');
const fixtures = require('../common/fixtures');
const path = require('path');
const fs = require('fs');
const {
access,
chmod,
copyFile,
fchmod,
fdatasync,
fstat,
fsync,
ftruncate,
futimes,
link,
lstat,
mkdir,
mkdtemp,
open,
read,
readdir,
readlink,
realpath,
rename,
rmdir,
stat,
symlink,
write,
unlink,
utimes
} = fs.promises;
const tmpDir = tmpdir.path;
common.crashOnUnhandledRejection();
{
access(__filename, 'r')
.then(common.mustCall())
.catch(common.mustNotCall());
access('this file does not exist', 'r')
.then(common.mustNotCall())
.catch(common.expectsError({
code: 'ENOENT',
type: Error,
message:
/^ENOENT: no such file or directory, access/
}));
}
function verifyStatObject(stat) {
assert.strictEqual(typeof stat, 'object');
assert.strictEqual(typeof stat.dev, 'number');
assert.strictEqual(typeof stat.mode, 'number');
}
{
async function doTest() {
tmpdir.refresh();
const dest = path.resolve(tmpDir, 'baz.js');
await copyFile(fixtures.path('baz.js'), dest);
await access(dest, 'r');
const handle = await open(dest, 'r+');
assert.strictEqual(typeof handle, 'object');
let stats = await fstat(handle);
verifyStatObject(stats);
assert.strictEqual(stats.size, 35);
await ftruncate(handle, 1);
stats = await fstat(handle);
verifyStatObject(stats);
assert.strictEqual(stats.size, 1);
stats = await stat(dest);
verifyStatObject(stats);
await fdatasync(handle);
await fsync(handle);
const buf = Buffer.from('hello world');
await write(handle, buf);
const ret = await read(handle, Buffer.alloc(11), 0, 11, 0);
assert.strictEqual(ret.bytesRead, 11);
assert.deepStrictEqual(ret.buffer, buf);
await chmod(dest, 0o666);
await fchmod(handle, 0o666);
await utimes(dest, new Date(), new Date());
try {
await futimes(handle, new Date(), new Date());
} catch (err) {
// Some systems do not have futimes. If there is an error,
// expect it to be ENOSYS
common.expectsError({
code: 'ENOSYS',
type: Error
})(err);
}
await handle.close();
const newPath = path.resolve(tmpDir, 'baz2.js');
await rename(dest, newPath);
stats = await stat(newPath);
verifyStatObject(stats);
const newLink = path.resolve(tmpDir, 'baz3.js');
await symlink(newPath, newLink);
const newLink2 = path.resolve(tmpDir, 'baz4.js');
await link(newPath, newLink2);
stats = await lstat(newLink);
verifyStatObject(stats);
assert.strictEqual(newPath.toLowerCase(),
(await realpath(newLink)).toLowerCase());
assert.strictEqual(newPath.toLowerCase(),
(await readlink(newLink)).toLowerCase());
await unlink(newLink);
await unlink(newLink2);
const newdir = path.resolve(tmpDir, 'dir');
await mkdir(newdir);
stats = await stat(newdir);
assert(stats.isDirectory());
const list = await readdir(tmpDir);
assert.deepStrictEqual(list, ['baz2.js', 'dir']);
await rmdir(newdir);
await mkdtemp(path.resolve(tmpDir, 'FOO'));
}
doTest().then(common.mustCall());
}

View file

@ -9,6 +9,8 @@ const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const { getSystemErrorName } = require('util');
common.crashOnUnhandledRejection();
// Make sure that all Providers are tested.
{
const hooks = require('async_hooks').createHook({
@ -167,6 +169,14 @@ if (common.hasCrypto) { // eslint-disable-line crypto-check
testInitialized(new Signal(), 'Signal');
}
{
async function openTest() {
const fd = await fs.promises.open(__filename, 'r');
testInitialized(fd, 'FileHandle');
await fd.close();
}
openTest().then(common.mustCall()).catch(common.mustNotCall());
}
{
const binding = process.binding('stream_wrap');