fs: simplify the error context collection in C++
- Simplify the SyncCall template function, only collect error number and syscall in the C++ layer and collect the rest of context in JS for flexibility. - Remove the stringFromPath JS helper now that the unprefixed path is directly put into the context before the binding is invoked with the prefixed path. - Validate more properties in fs.access tests. PR-URL: https://github.com/nodejs/node/pull/17338 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
This commit is contained in:
parent
c56972779b
commit
6ca10de946
@ -329,10 +329,10 @@ fs.accessSync = function(path, mode) {
|
|||||||
else
|
else
|
||||||
mode = mode | 0;
|
mode = mode | 0;
|
||||||
|
|
||||||
const ctx = {};
|
const ctx = { path };
|
||||||
binding.access(pathModule.toNamespacedPath(path), mode, undefined, ctx);
|
binding.access(pathModule.toNamespacedPath(path), mode, undefined, ctx);
|
||||||
|
|
||||||
if (ctx.code !== undefined) {
|
if (ctx.errno !== undefined) {
|
||||||
throw new errors.uvException(ctx);
|
throw new errors.uvException(ctx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,7 @@ const kCode = Symbol('code');
|
|||||||
const kInfo = Symbol('info');
|
const kInfo = Symbol('info');
|
||||||
const messages = new Map();
|
const messages = new Map();
|
||||||
|
|
||||||
|
const { errmap } = process.binding('uv');
|
||||||
const { kMaxLength } = process.binding('buffer');
|
const { kMaxLength } = process.binding('buffer');
|
||||||
const { defineProperty } = Object;
|
const { defineProperty } = Object;
|
||||||
|
|
||||||
@ -194,43 +195,35 @@ function E(sym, val) {
|
|||||||
messages.set(sym, typeof val === 'function' ? val : String(val));
|
messages.set(sym, typeof val === 'function' ? val : String(val));
|
||||||
}
|
}
|
||||||
|
|
||||||
// JS counterpart of StringFromPath, although here path is a buffer.
|
|
||||||
function stringFromPath(path) {
|
|
||||||
const str = path.toString();
|
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str.startsWith('\\\\?\\UNC\\')) {
|
|
||||||
return '\\\\' + str.slice(8);
|
|
||||||
} else if (str.startsWith('\\\\?\\')) {
|
|
||||||
return str.slice(4);
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This creates an error compatible with errors produced in UVException
|
// This creates an error compatible with errors produced in UVException
|
||||||
// using the context collected in CollectUVExceptionInfo
|
// using the context collected in CollectUVExceptionInfo
|
||||||
// The goal is to migrate them to ERR_* errors later when
|
// The goal is to migrate them to ERR_* errors later when
|
||||||
// compatibility is not a concern
|
// compatibility is not a concern
|
||||||
function uvException(ctx) {
|
function uvException(ctx) {
|
||||||
const err = new Error();
|
const err = new Error();
|
||||||
err.errno = ctx.errno;
|
|
||||||
err.code = ctx.code;
|
|
||||||
err.syscall = ctx.syscall;
|
|
||||||
|
|
||||||
let message = `${ctx.code}: ${ctx.message}, ${ctx.syscall}`;
|
for (const prop of Object.keys(ctx)) {
|
||||||
|
if (prop === 'message' || prop === 'path' || prop === 'dest') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
err[prop] = ctx[prop];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ code, uvmsg ] = errmap.get(ctx.errno);
|
||||||
|
err.code = code;
|
||||||
|
let message = `${code}: ${uvmsg}, ${ctx.syscall}`;
|
||||||
if (ctx.path) {
|
if (ctx.path) {
|
||||||
const path = stringFromPath(ctx.path);
|
const path = ctx.path.toString();
|
||||||
message += ` '${path}'`;
|
message += ` '${path}'`;
|
||||||
err.path = path;
|
err.path = path;
|
||||||
}
|
}
|
||||||
if (ctx.dest) {
|
if (ctx.dest) {
|
||||||
const dest = stringFromPath(ctx.dest);
|
const dest = ctx.dest.toString();
|
||||||
message += ` -> '${dest}'`;
|
message += ` -> '${dest}'`;
|
||||||
err.dest = dest;
|
err.dest = dest;
|
||||||
}
|
}
|
||||||
err.message = message;
|
err.message = message;
|
||||||
|
|
||||||
Error.captureStackTrace(err, uvException);
|
Error.captureStackTrace(err, uvException);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,7 @@ using v8::FunctionCallbackInfo;
|
|||||||
using v8::FunctionTemplate;
|
using v8::FunctionTemplate;
|
||||||
using v8::HandleScope;
|
using v8::HandleScope;
|
||||||
using v8::Integer;
|
using v8::Integer;
|
||||||
|
using v8::Isolate;
|
||||||
using v8::Local;
|
using v8::Local;
|
||||||
using v8::MaybeLocal;
|
using v8::MaybeLocal;
|
||||||
using v8::Number;
|
using v8::Number;
|
||||||
@ -157,6 +158,15 @@ FSReqAfterScope::~FSReqAfterScope() {
|
|||||||
wrap_->Dispose();
|
wrap_->Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(joyeecheung): create a normal context object, and
|
||||||
|
// construct the actual errors in the JS land using the context.
|
||||||
|
// The context should include fds for some fs APIs, currently they are
|
||||||
|
// missing in the error messages. The path, dest, syscall, fd, .etc
|
||||||
|
// can be put into the context before the binding is even invoked,
|
||||||
|
// the only information that has to come from the C++ layer is the
|
||||||
|
// error number (and possibly the syscall for abstraction),
|
||||||
|
// which is also why the errors should have been constructed
|
||||||
|
// in JS for more flexibility.
|
||||||
void FSReqAfterScope::Reject(uv_fs_t* req) {
|
void FSReqAfterScope::Reject(uv_fs_t* req) {
|
||||||
wrap_->Reject(UVException(wrap_->env()->isolate(),
|
wrap_->Reject(UVException(wrap_->env()->isolate(),
|
||||||
req->result,
|
req->result,
|
||||||
@ -354,28 +364,28 @@ inline FSReqWrap* AsyncCall(Environment* env, Local<Object> req,
|
|||||||
#define ASYNC_CALL(after, func, req, encoding, ...) \
|
#define ASYNC_CALL(after, func, req, encoding, ...) \
|
||||||
ASYNC_DEST_CALL(after, func, req, nullptr, encoding, __VA_ARGS__) \
|
ASYNC_DEST_CALL(after, func, req, nullptr, encoding, __VA_ARGS__) \
|
||||||
|
|
||||||
// Template counterpart of SYNC_DEST_CALL
|
// Template counterpart of SYNC_CALL, except that it only puts
|
||||||
|
// the error number and the syscall in the context instead of
|
||||||
|
// creating an error in the C++ land.
|
||||||
template <typename Func, typename... Args>
|
template <typename Func, typename... Args>
|
||||||
inline void SyncDestCall(Environment* env, Local<Value> ctx,
|
inline void SyncCall(Environment* env, Local<Value> ctx,
|
||||||
const char* path, const char* dest, const char* syscall,
|
const char* syscall, Func fn, Args... args) {
|
||||||
Func fn, Args... args) {
|
|
||||||
fs_req_wrap req_wrap;
|
fs_req_wrap req_wrap;
|
||||||
env->PrintSyncTrace();
|
env->PrintSyncTrace();
|
||||||
int err = fn(env->event_loop(), &req_wrap.req, args..., nullptr);
|
int err = fn(env->event_loop(), &req_wrap.req, args..., nullptr);
|
||||||
if (err) {
|
if (err < 0) {
|
||||||
Local<Context> context = env->context();
|
Local<Context> context = env->context();
|
||||||
Local<Object> ctx_obj = ctx->ToObject(context).ToLocalChecked();
|
Local<Object> ctx_obj = ctx->ToObject(context).ToLocalChecked();
|
||||||
env->CollectUVExceptionInfo(ctx_obj, err, syscall, nullptr, path, dest);
|
Isolate *isolate = env->isolate();
|
||||||
|
ctx_obj->Set(context,
|
||||||
|
env->errno_string(),
|
||||||
|
Integer::New(isolate, err)).FromJust();
|
||||||
|
ctx_obj->Set(context,
|
||||||
|
env->syscall_string(),
|
||||||
|
OneByteString(isolate, syscall)).FromJust();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template counterpart of SYNC_CALL
|
|
||||||
template <typename Func, typename... Args>
|
|
||||||
inline void SyncCall(Environment* env, Local<Value> ctx,
|
|
||||||
const char* path, const char* syscall, Func fn, Args... args) {
|
|
||||||
return SyncDestCall(env, ctx, path, nullptr, syscall, fn, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SYNC_DEST_CALL(func, path, dest, ...) \
|
#define SYNC_DEST_CALL(func, path, dest, ...) \
|
||||||
fs_req_wrap req_wrap; \
|
fs_req_wrap req_wrap; \
|
||||||
env->PrintSyncTrace(); \
|
env->PrintSyncTrace(); \
|
||||||
@ -404,15 +414,15 @@ void Access(const FunctionCallbackInfo<Value>& args) {
|
|||||||
BufferValue path(env->isolate(), args[0]);
|
BufferValue path(env->isolate(), args[0]);
|
||||||
int mode = static_cast<int>(args[1]->Int32Value(context).FromJust());
|
int mode = static_cast<int>(args[1]->Int32Value(context).FromJust());
|
||||||
|
|
||||||
if (args[2]->IsObject()) {
|
if (args[2]->IsObject()) { // access(path, mode, req)
|
||||||
Local<Object> req_obj = args[2]->ToObject(context).ToLocalChecked();
|
Local<Object> req_obj = args[2]->ToObject(context).ToLocalChecked();
|
||||||
FSReqWrap* req_wrap = AsyncCall(
|
FSReqWrap* req_wrap = AsyncCall(
|
||||||
env, req_obj, UTF8, "access", AfterNoArgs, uv_fs_access, *path, mode);
|
env, req_obj, UTF8, "access", AfterNoArgs, uv_fs_access, *path, mode);
|
||||||
if (req_wrap != nullptr) {
|
if (req_wrap != nullptr) {
|
||||||
args.GetReturnValue().Set(req_wrap->persistent());
|
args.GetReturnValue().Set(req_wrap->persistent());
|
||||||
}
|
}
|
||||||
} else {
|
} else { // access(path, mode, undefined, ctx)
|
||||||
SyncCall(env, args[3], *path, "access", uv_fs_access, *path, mode);
|
SyncCall(env, args[3], "access", uv_fs_access, *path, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// This tests that fs.access and fs.accessSync works as expected
|
||||||
|
// and the errors thrown from these APIs include the desired properties
|
||||||
|
|
||||||
const common = require('../common');
|
const common = require('../common');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const uv = process.binding('uv');
|
||||||
|
|
||||||
const doesNotExist = path.join(common.tmpDir, '__this_should_not_exist');
|
const doesNotExist = path.join(common.tmpDir, '__this_should_not_exist');
|
||||||
const readOnlyFile = path.join(common.tmpDir, 'read_only_file');
|
const readOnlyFile = path.join(common.tmpDir, 'read_only_file');
|
||||||
const readWriteFile = path.join(common.tmpDir, 'read_write_file');
|
const readWriteFile = path.join(common.tmpDir, 'read_write_file');
|
||||||
@ -130,6 +136,24 @@ assert.throws(
|
|||||||
`ENOENT: no such file or directory, access '${doesNotExist}'`
|
`ENOENT: no such file or directory, access '${doesNotExist}'`
|
||||||
);
|
);
|
||||||
assert.strictEqual(err.constructor, Error);
|
assert.strictEqual(err.constructor, Error);
|
||||||
|
assert.strictEqual(err.syscall, 'access');
|
||||||
|
assert.strictEqual(err.errno, uv.UV_ENOENT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => { fs.accessSync(Buffer.from(doesNotExist)); },
|
||||||
|
(err) => {
|
||||||
|
assert.strictEqual(err.code, 'ENOENT');
|
||||||
|
assert.strictEqual(err.path, doesNotExist);
|
||||||
|
assert.strictEqual(
|
||||||
|
err.message,
|
||||||
|
`ENOENT: no such file or directory, access '${doesNotExist}'`
|
||||||
|
);
|
||||||
|
assert.strictEqual(err.constructor, Error);
|
||||||
|
assert.strictEqual(err.syscall, 'access');
|
||||||
|
assert.strictEqual(err.errno, uv.UV_ENOENT);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user