Skip to content

Commit 6ca10de

Browse files
committed
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: #17338 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
1 parent c569727 commit 6ca10de

File tree

4 files changed

+66
-39
lines changed

4 files changed

+66
-39
lines changed

lib/fs.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -329,10 +329,10 @@ fs.accessSync = function(path, mode) {
329329
else
330330
mode = mode | 0;
331331

332-
const ctx = {};
332+
const ctx = { path };
333333
binding.access(pathModule.toNamespacedPath(path), mode, undefined, ctx);
334334

335-
if (ctx.code !== undefined) {
335+
if (ctx.errno !== undefined) {
336336
throw new errors.uvException(ctx);
337337
}
338338
};

lib/internal/errors.js

+14-21
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const kCode = Symbol('code');
1414
const kInfo = Symbol('info');
1515
const messages = new Map();
1616

17+
const { errmap } = process.binding('uv');
1718
const { kMaxLength } = process.binding('buffer');
1819
const { defineProperty } = Object;
1920

@@ -194,43 +195,35 @@ function E(sym, val) {
194195
messages.set(sym, typeof val === 'function' ? val : String(val));
195196
}
196197

197-
// JS counterpart of StringFromPath, although here path is a buffer.
198-
function stringFromPath(path) {
199-
const str = path.toString();
200-
if (process.platform !== 'win32') {
201-
return str;
202-
}
203-
204-
if (str.startsWith('\\\\?\\UNC\\')) {
205-
return '\\\\' + str.slice(8);
206-
} else if (str.startsWith('\\\\?\\')) {
207-
return str.slice(4);
208-
}
209-
return str;
210-
}
211-
212198
// This creates an error compatible with errors produced in UVException
213199
// using the context collected in CollectUVExceptionInfo
214200
// The goal is to migrate them to ERR_* errors later when
215201
// compatibility is not a concern
216202
function uvException(ctx) {
217203
const err = new Error();
218-
err.errno = ctx.errno;
219-
err.code = ctx.code;
220-
err.syscall = ctx.syscall;
221204

222-
let message = `${ctx.code}: ${ctx.message}, ${ctx.syscall}`;
205+
for (const prop of Object.keys(ctx)) {
206+
if (prop === 'message' || prop === 'path' || prop === 'dest') {
207+
continue;
208+
}
209+
err[prop] = ctx[prop];
210+
}
211+
212+
const [ code, uvmsg ] = errmap.get(ctx.errno);
213+
err.code = code;
214+
let message = `${code}: ${uvmsg}, ${ctx.syscall}`;
223215
if (ctx.path) {
224-
const path = stringFromPath(ctx.path);
216+
const path = ctx.path.toString();
225217
message += ` '${path}'`;
226218
err.path = path;
227219
}
228220
if (ctx.dest) {
229-
const dest = stringFromPath(ctx.dest);
221+
const dest = ctx.dest.toString();
230222
message += ` -> '${dest}'`;
231223
err.dest = dest;
232224
}
233225
err.message = message;
226+
234227
Error.captureStackTrace(err, uvException);
235228
return err;
236229
}

src/node_file.cc

+26-16
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ using v8::FunctionCallbackInfo;
8888
using v8::FunctionTemplate;
8989
using v8::HandleScope;
9090
using v8::Integer;
91+
using v8::Isolate;
9192
using v8::Local;
9293
using v8::MaybeLocal;
9394
using v8::Number;
@@ -157,6 +158,15 @@ FSReqAfterScope::~FSReqAfterScope() {
157158
wrap_->Dispose();
158159
}
159160

161+
// TODO(joyeecheung): create a normal context object, and
162+
// construct the actual errors in the JS land using the context.
163+
// The context should include fds for some fs APIs, currently they are
164+
// missing in the error messages. The path, dest, syscall, fd, .etc
165+
// can be put into the context before the binding is even invoked,
166+
// the only information that has to come from the C++ layer is the
167+
// error number (and possibly the syscall for abstraction),
168+
// which is also why the errors should have been constructed
169+
// in JS for more flexibility.
160170
void FSReqAfterScope::Reject(uv_fs_t* req) {
161171
wrap_->Reject(UVException(wrap_->env()->isolate(),
162172
req->result,
@@ -354,28 +364,28 @@ inline FSReqWrap* AsyncCall(Environment* env, Local<Object> req,
354364
#define ASYNC_CALL(after, func, req, encoding, ...) \
355365
ASYNC_DEST_CALL(after, func, req, nullptr, encoding, __VA_ARGS__) \
356366

357-
// Template counterpart of SYNC_DEST_CALL
367+
// Template counterpart of SYNC_CALL, except that it only puts
368+
// the error number and the syscall in the context instead of
369+
// creating an error in the C++ land.
358370
template <typename Func, typename... Args>
359-
inline void SyncDestCall(Environment* env, Local<Value> ctx,
360-
const char* path, const char* dest, const char* syscall,
361-
Func fn, Args... args) {
371+
inline void SyncCall(Environment* env, Local<Value> ctx,
372+
const char* syscall, Func fn, Args... args) {
362373
fs_req_wrap req_wrap;
363374
env->PrintSyncTrace();
364375
int err = fn(env->event_loop(), &req_wrap.req, args..., nullptr);
365-
if (err) {
376+
if (err < 0) {
366377
Local<Context> context = env->context();
367378
Local<Object> ctx_obj = ctx->ToObject(context).ToLocalChecked();
368-
env->CollectUVExceptionInfo(ctx_obj, err, syscall, nullptr, path, dest);
379+
Isolate *isolate = env->isolate();
380+
ctx_obj->Set(context,
381+
env->errno_string(),
382+
Integer::New(isolate, err)).FromJust();
383+
ctx_obj->Set(context,
384+
env->syscall_string(),
385+
OneByteString(isolate, syscall)).FromJust();
369386
}
370387
}
371388

372-
// Template counterpart of SYNC_CALL
373-
template <typename Func, typename... Args>
374-
inline void SyncCall(Environment* env, Local<Value> ctx,
375-
const char* path, const char* syscall, Func fn, Args... args) {
376-
return SyncDestCall(env, ctx, path, nullptr, syscall, fn, args...);
377-
}
378-
379389
#define SYNC_DEST_CALL(func, path, dest, ...) \
380390
fs_req_wrap req_wrap; \
381391
env->PrintSyncTrace(); \
@@ -404,15 +414,15 @@ void Access(const FunctionCallbackInfo<Value>& args) {
404414
BufferValue path(env->isolate(), args[0]);
405415
int mode = static_cast<int>(args[1]->Int32Value(context).FromJust());
406416

407-
if (args[2]->IsObject()) {
417+
if (args[2]->IsObject()) { // access(path, mode, req)
408418
Local<Object> req_obj = args[2]->ToObject(context).ToLocalChecked();
409419
FSReqWrap* req_wrap = AsyncCall(
410420
env, req_obj, UTF8, "access", AfterNoArgs, uv_fs_access, *path, mode);
411421
if (req_wrap != nullptr) {
412422
args.GetReturnValue().Set(req_wrap->persistent());
413423
}
414-
} else {
415-
SyncCall(env, args[3], *path, "access", uv_fs_access, *path, mode);
424+
} else { // access(path, mode, undefined, ctx)
425+
SyncCall(env, args[3], "access", uv_fs_access, *path, mode);
416426
}
417427
}
418428

test/parallel/test-fs-access.js

+24
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
'use strict';
2+
3+
// This tests that fs.access and fs.accessSync works as expected
4+
// and the errors thrown from these APIs include the desired properties
5+
26
const common = require('../common');
37
const assert = require('assert');
48
const fs = require('fs');
59
const path = require('path');
10+
const uv = process.binding('uv');
11+
612
const doesNotExist = path.join(common.tmpDir, '__this_should_not_exist');
713
const readOnlyFile = path.join(common.tmpDir, 'read_only_file');
814
const readWriteFile = path.join(common.tmpDir, 'read_write_file');
@@ -130,6 +136,24 @@ assert.throws(
130136
`ENOENT: no such file or directory, access '${doesNotExist}'`
131137
);
132138
assert.strictEqual(err.constructor, Error);
139+
assert.strictEqual(err.syscall, 'access');
140+
assert.strictEqual(err.errno, uv.UV_ENOENT);
141+
return true;
142+
}
143+
);
144+
145+
assert.throws(
146+
() => { fs.accessSync(Buffer.from(doesNotExist)); },
147+
(err) => {
148+
assert.strictEqual(err.code, 'ENOENT');
149+
assert.strictEqual(err.path, doesNotExist);
150+
assert.strictEqual(
151+
err.message,
152+
`ENOENT: no such file or directory, access '${doesNotExist}'`
153+
);
154+
assert.strictEqual(err.constructor, Error);
155+
assert.strictEqual(err.syscall, 'access');
156+
assert.strictEqual(err.errno, uv.UV_ENOENT);
133157
return true;
134158
}
135159
);

0 commit comments

Comments
 (0)