diff --git a/lib/fs.js b/lib/fs.js index a53290274c331e..a61cfaf4697bdd 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -297,6 +297,11 @@ fs.access = function(path, mode, callback) { if (handleError((path = getPathFromURL(path)), callback)) return; + if (typeof path !== 'string' && !(path instanceof Buffer)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + if (!nullCheck(path, callback)) return; @@ -308,6 +313,12 @@ fs.access = function(path, mode, callback) { fs.accessSync = function(path, mode) { handleError((path = getPathFromURL(path))); + + if (typeof path !== 'string' && !(path instanceof Buffer)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + nullCheck(path); if (mode === undefined) @@ -315,7 +326,12 @@ fs.accessSync = function(path, mode) { else mode = mode | 0; - binding.access(pathModule.toNamespacedPath(path), mode); + const ctx = {}; + binding.access(pathModule.toNamespacedPath(path), mode, undefined, ctx); + + if (ctx.code !== undefined) { + throw new errors.uvException(ctx); + } }; fs.exists = function(path, callback) { diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 3be2705648df3d..619732df5d6fbb 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -194,7 +194,49 @@ function E(sym, 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 +// using the context collected in CollectUVExceptionInfo +// The goal is to migrate them to ERR_* errors later when +// compatibility is not a concern +function uvException(ctx) { + const err = new Error(); + err.errno = ctx.errno; + err.code = ctx.code; + err.syscall = ctx.syscall; + + let message = `${ctx.code}: ${ctx.message}, ${ctx.syscall}`; + if (ctx.path) { + const path = stringFromPath(ctx.path); + message += ` '${path}'`; + err.path = path; + } + if (ctx.dest) { + const dest = stringFromPath(ctx.dest); + message += ` -> '${dest}'`; + err.dest = dest; + } + err.message = message; + Error.captureStackTrace(err, uvException); + return err; +} + module.exports = exports = { + uvException, message, Error: makeNodeError(Error), TypeError: makeNodeError(TypeError), diff --git a/src/node_file.cc b/src/node_file.cc index 12a9f411bc3df1..2b3b007976de14 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -349,6 +349,31 @@ class fs_req_wrap { DISALLOW_COPY_AND_ASSIGN(fs_req_wrap); }; +// Template counterpart of ASYNC_DEST_CALL +template +inline FSReqWrap* AsyncDestCall(Environment* env, Local req, + const char* dest, enum encoding enc, const char* syscall, + Func fn, Args... args) { + FSReqWrap* req_wrap = FSReqWrap::New(env, req, syscall, dest, enc); + int err = fn(env->event_loop(), req_wrap->req(), args..., After); + req_wrap->Dispatched(); + if (err < 0) { + uv_fs_t* uv_req = req_wrap->req(); + uv_req->result = err; + uv_req->path = nullptr; + After(uv_req); + req_wrap = nullptr; + } + + return req_wrap; +} + +// Template counterpart of ASYNC_CALL +template +inline FSReqWrap* AsyncCall(Environment* env, Local req, + enum encoding enc, const char* syscall, Func fn, Args... args) { + return AsyncDestCall(env, req, nullptr, enc, syscall, fn, args...); +} #define ASYNC_DEST_CALL(func, request, dest, encoding, ...) \ Environment* env = Environment::GetCurrent(args); \ @@ -373,6 +398,28 @@ class fs_req_wrap { #define ASYNC_CALL(func, req, encoding, ...) \ ASYNC_DEST_CALL(func, req, nullptr, encoding, __VA_ARGS__) \ +// Template counterpart of SYNC_DEST_CALL +template +inline void SyncDestCall(Environment* env, Local ctx, + const char* path, const char* dest, const char* syscall, + Func fn, Args... args) { + fs_req_wrap req_wrap; + env->PrintSyncTrace(); + int err = fn(env->event_loop(), &req_wrap.req, args..., nullptr); + if (err) { + Local context = env->context(); + Local ctx_obj = ctx->ToObject(context).ToLocalChecked(); + env->CollectUVExceptionInfo(ctx_obj, err, syscall, nullptr, path, dest); + } +} + +// Template counterpart of SYNC_CALL +template +inline void SyncCall(Environment* env, Local 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, ...) \ fs_req_wrap req_wrap; \ env->PrintSyncTrace(); \ @@ -394,21 +441,22 @@ class fs_req_wrap { void Access(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args.GetIsolate()); HandleScope scope(env->isolate()); - - if (args.Length() < 2) - return TYPE_ERROR("path and mode are required"); - if (!args[1]->IsInt32()) - return TYPE_ERROR("mode must be an integer"); + Local context = env->context(); + CHECK_GE(args.Length(), 2); + CHECK(args[1]->IsInt32()); BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) - - int mode = static_cast(args[1]->Int32Value()); + int mode = static_cast(args[1]->Int32Value(context).FromJust()); if (args[2]->IsObject()) { - ASYNC_CALL(access, args[2], UTF8, *path, mode); + Local req_obj = args[2]->ToObject(context).ToLocalChecked(); + FSReqWrap* req_wrap = AsyncCall( + env, req_obj, UTF8, "access", uv_fs_access, *path, mode); + if (req_wrap != nullptr) { + args.GetReturnValue().Set(req_wrap->persistent()); + } } else { - SYNC_CALL(access, *path, *path, mode); + SyncCall(env, args[3], *path, "access", uv_fs_access, *path, mode); } } diff --git a/test/parallel/test-fs-access.js b/test/parallel/test-fs-access.js index d3140941bd0e72..719e1108fe1c02 100644 --- a/test/parallel/test-fs-access.js +++ b/test/parallel/test-fs-access.js @@ -81,9 +81,16 @@ fs.access(readOnlyFile, fs.W_OK, common.mustCall((err) => { } })); -assert.throws(() => { - fs.access(100, fs.F_OK, common.mustNotCall()); -}, /^TypeError: path must be a string or Buffer$/); +common.expectsError( + () => { + fs.access(100, fs.F_OK, common.mustNotCall()); + }, + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + } +); common.expectsError( () => { diff --git a/test/parallel/test-fs-buffer.js b/test/parallel/test-fs-buffer.js index 1cbead434469e9..bc2add521873a3 100644 --- a/test/parallel/test-fs-buffer.js +++ b/test/parallel/test-fs-buffer.js @@ -25,9 +25,16 @@ assert.doesNotThrow(() => { })); }); -assert.throws(() => { - fs.accessSync(true); -}, /path must be a string or Buffer/); +common.expectsError( + () => { + fs.accessSync(true); + }, + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + } +); const dir = Buffer.from(fixtures.fixturesDir); fs.readdir(dir, 'hex', common.mustCall((err, hexList) => {