Skip to content

Commit c3db742

Browse files
committed
fs: move getValidMode to c++ for better performance
1 parent 813713f commit c3db742

File tree

8 files changed

+93
-78
lines changed

8 files changed

+93
-78
lines changed

benchmark/fs/bench-accessSync.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ const tmpfile = tmpdir.resolve(`.existing-file-${process.pid}`);
99
fs.writeFileSync(tmpfile, 'this-is-for-a-benchmark', 'utf8');
1010

1111
const bench = common.createBenchmark(main, {
12-
type: ['existing', 'non-existing', 'non-flat-existing'],
12+
type: ['existing', 'non-existing', 'non-flat-existing', 'invalid-mode'],
1313
n: [1e5],
1414
});
1515

1616
function main({ n, type }) {
17-
let path;
17+
let path, mode;
1818

1919
switch (type) {
2020
case 'existing':
@@ -26,14 +26,18 @@ function main({ n, type }) {
2626
case 'non-existing':
2727
path = tmpdir.resolve(`.non-existing-file-${process.pid}`);
2828
break;
29+
case 'invalid-mode':
30+
path = __filename;
31+
mode = -1;
32+
break;
2933
default:
3034
new Error('Invalid type');
3135
}
3236

3337
bench.start();
3438
for (let i = 0; i < n; i++) {
3539
try {
36-
fs.accessSync(path);
40+
fs.accessSync(path, mode);
3741
} catch {
3842
// do nothing
3943
}

benchmark/fs/bench-copyFileSync.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,23 @@ const tmpdir = require('../../test/common/tmpdir');
66
tmpdir.refresh();
77

88
const bench = common.createBenchmark(main, {
9-
type: ['invalid', 'valid'],
9+
type: ['invalid-mode', 'invalid-path', 'valid'],
1010
n: [1e4],
1111
});
1212

1313
function main({ n, type }) {
1414
tmpdir.refresh();
1515
const dest = tmpdir.resolve(`copy-file-bench-${process.pid}`);
16-
let path;
16+
let path, mode;
1717

1818
switch (type) {
19-
case 'invalid':
19+
case 'invalid-path':
2020
path = tmpdir.resolve(`.existing-file-${process.pid}`);
2121
break;
22+
case 'invalid-mode':
23+
path = __filename;
24+
mode = -1;
25+
break;
2226
case 'valid':
2327
path = __filename;
2428
break;
@@ -28,7 +32,7 @@ function main({ n, type }) {
2832
bench.start();
2933
for (let i = 0; i < n; i++) {
3034
try {
31-
fs.copyFileSync(path, dest);
35+
fs.copyFileSync(path, dest, mode);
3236
} catch {
3337
// do nothing
3438
}

lib/fs.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ const {
103103
getOptions,
104104
getValidatedFd,
105105
getValidatedPath,
106-
getValidMode,
107106
handleErrorFromBinding,
108107
possiblyTransformPath,
109108
preprocessSymlinkDestination,
@@ -226,7 +225,6 @@ function access(path, mode, callback) {
226225
}
227226

228227
path = getValidatedPath(path);
229-
mode = getValidMode(mode, 'access');
230228
callback = makeCallback(callback);
231229

232230
const req = new FSReqCallback();
@@ -243,7 +241,6 @@ function access(path, mode, callback) {
243241
*/
244242
function accessSync(path, mode) {
245243
path = getValidatedPath(path);
246-
mode = getValidMode(mode, 'access');
247244

248245
binding.access(pathModule.toNamespacedPath(path), mode);
249246
}
@@ -2962,7 +2959,6 @@ function copyFile(src, dest, mode, callback) {
29622959

29632960
src = pathModule._makeLong(src);
29642961
dest = pathModule._makeLong(dest);
2965-
mode = getValidMode(mode, 'copyFile');
29662962
callback = makeCallback(callback);
29672963

29682964
const req = new FSReqCallback();
@@ -2985,7 +2981,7 @@ function copyFileSync(src, dest, mode) {
29852981
binding.copyFile(
29862982
pathModule.toNamespacedPath(src),
29872983
pathModule.toNamespacedPath(dest),
2988-
getValidMode(mode, 'copyFile'),
2984+
mode,
29892985
);
29902986
}
29912987

lib/internal/fs/promises.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ const {
5757
getStatFsFromBinding,
5858
getStatsFromBinding,
5959
getValidatedPath,
60-
getValidMode,
6160
preprocessSymlinkDestination,
6261
stringToFlags,
6362
stringToSymlinkType,
@@ -559,7 +558,6 @@ async function readFileHandle(filehandle, options) {
559558
async function access(path, mode = F_OK) {
560559
path = getValidatedPath(path);
561560

562-
mode = getValidMode(mode, 'access');
563561
return binding.access(pathModule.toNamespacedPath(path), mode,
564562
kUsePromises);
565563
}
@@ -574,7 +572,6 @@ async function cp(src, dest, options) {
574572
async function copyFile(src, dest, mode) {
575573
src = getValidatedPath(src, 'src');
576574
dest = getValidatedPath(dest, 'dest');
577-
mode = getValidMode(mode, 'copyFile');
578575
return binding.copyFile(pathModule.toNamespacedPath(src),
579576
pathModule.toNamespacedPath(dest),
580577
mode,

lib/internal/fs/utils.js

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const {
1010
FunctionPrototypeCall,
1111
Number,
1212
NumberIsFinite,
13-
MathMin,
1413
MathRound,
1514
ObjectIs,
1615
ObjectSetPrototypeOf,
@@ -62,17 +61,9 @@ const {
6261
const pathModule = require('path');
6362
const kType = Symbol('type');
6463
const kStats = Symbol('stats');
65-
const assert = require('internal/assert');
6664

6765
const {
6866
fs: {
69-
F_OK = 0,
70-
W_OK = 0,
71-
R_OK = 0,
72-
X_OK = 0,
73-
COPYFILE_EXCL,
74-
COPYFILE_FICLONE,
75-
COPYFILE_FICLONE_FORCE,
7667
O_APPEND,
7768
O_CREAT,
7869
O_EXCL,
@@ -107,26 +98,6 @@ const {
10798
},
10899
} = internalBinding('constants');
109100

110-
// The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be
111-
// available on specific systems. They can be used in combination as well
112-
// (F_OK | R_OK | W_OK | X_OK).
113-
const kMinimumAccessMode = MathMin(F_OK, W_OK, R_OK, X_OK);
114-
const kMaximumAccessMode = F_OK | W_OK | R_OK | X_OK;
115-
116-
const kDefaultCopyMode = 0;
117-
// The copy modes can be any of COPYFILE_EXCL, COPYFILE_FICLONE or
118-
// COPYFILE_FICLONE_FORCE. They can be used in combination as well
119-
// (COPYFILE_EXCL | COPYFILE_FICLONE | COPYFILE_FICLONE_FORCE).
120-
const kMinimumCopyMode = MathMin(
121-
kDefaultCopyMode,
122-
COPYFILE_EXCL,
123-
COPYFILE_FICLONE,
124-
COPYFILE_FICLONE_FORCE,
125-
);
126-
const kMaximumCopyMode = COPYFILE_EXCL |
127-
COPYFILE_FICLONE |
128-
COPYFILE_FICLONE_FORCE;
129-
130101
// Most platforms don't allow reads or writes >= 2 GiB.
131102
// See https://github.com/libuv/libuv/pull/1501.
132103
const kIoMaxLength = 2 ** 31 - 1;
@@ -795,7 +766,6 @@ const validateCpOptions = hideStackFrames((options) => {
795766
validateBoolean(options.preserveTimestamps, 'options.preserveTimestamps');
796767
validateBoolean(options.recursive, 'options.recursive');
797768
validateBoolean(options.verbatimSymlinks, 'options.verbatimSymlinks');
798-
options.mode = getValidMode(options.mode, 'copyFile');
799769
if (options.dereference === true && options.verbatimSymlinks === true) {
800770
throw new ERR_INCOMPATIBLE_OPTION_PAIR('dereference', 'verbatimSymlinks');
801771
}
@@ -888,24 +858,6 @@ const validateRmdirOptions = hideStackFrames(
888858
return options;
889859
});
890860

891-
const getValidMode = hideStackFrames((mode, type) => {
892-
let min = kMinimumAccessMode;
893-
let max = kMaximumAccessMode;
894-
let def = F_OK;
895-
if (type === 'copyFile') {
896-
min = kMinimumCopyMode;
897-
max = kMaximumCopyMode;
898-
def = mode || kDefaultCopyMode;
899-
} else {
900-
assert(type === 'access');
901-
}
902-
if (mode == null) {
903-
return def;
904-
}
905-
validateInteger(mode, 'mode', min, max);
906-
return mode;
907-
});
908-
909861
const validateStringAfterArrayBufferView = hideStackFrames((buffer, name) => {
910862
if (typeof buffer !== 'string') {
911863
throw new ERR_INVALID_ARG_TYPE(
@@ -949,7 +901,6 @@ module.exports = {
949901
getOptions,
950902
getValidatedFd,
951903
getValidatedPath,
952-
getValidMode,
953904
handleErrorFromBinding,
954905
nullCheck,
955906
possiblyTransformPath,

src/node_file.cc

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,25 @@ constexpr char kPathSeparator = '/';
8989
const char* const kPathSeparator = "\\/";
9090
#endif
9191

92+
// The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be
93+
// available on specific systems. They can be used in combination as well
94+
// (F_OK | R_OK | W_OK | X_OK).
95+
constexpr int kMaximumAccessMode = F_OK | W_OK | R_OK | X_OK;
96+
constexpr int kMinimumAccessMode = std::min({F_OK, W_OK, R_OK, X_OK});
97+
98+
constexpr int kDefaultCopyMode = 0;
99+
// The copy modes can be any of UV_FS_COPYFILE_EXCL, UV_FS_COPYFILE_FICLONE or
100+
// UV_FS_COPYFILE_FICLONE_FORCE. They can be used in combination as well
101+
// (US_FS_COPYFILE_EXCL | US_FS_COPYFILE_FICLONE |
102+
// US_FS_COPYFILE_FICLONE_FORCE).
103+
constexpr int kMinimumCopyMode = std::min({
104+
kDefaultCopyMode,
105+
UV_FS_COPYFILE_EXCL,
106+
UV_FS_COPYFILE_FICLONE,
107+
UV_FS_COPYFILE_FICLONE_FORCE});
108+
constexpr int kMaximumCopyMode = UV_FS_COPYFILE_EXCL |
109+
UV_FS_COPYFILE_FICLONE | UV_FS_COPYFILE_FICLONE_FORCE;
110+
92111
std::string Basename(const std::string& str, const std::string& extension) {
93112
// Remove everything leading up to and including the final path separator.
94113
std::string::size_type pos = str.find_last_of(kPathSeparator);
@@ -854,6 +873,44 @@ void FromNamespacedPath(std::string* path) {
854873
#endif
855874
}
856875

876+
877+
static inline int GetValidMode(Environment* env,
878+
Local<Value> mode_v, std::string_view type) {
879+
if (!mode_v->IsInt32() && !mode_v->IsNullOrUndefined()) {
880+
THROW_ERR_INVALID_ARG_TYPE(env, "mode must be int32 or null/undefined");
881+
return -1;
882+
}
883+
884+
int min = kMinimumAccessMode;
885+
int max = kMaximumAccessMode;
886+
int def = F_OK;
887+
888+
if (type == "copyFile") {
889+
min = kMinimumCopyMode;
890+
max = kMaximumCopyMode;
891+
def = mode_v->IsNullOrUndefined() ?
892+
kDefaultCopyMode :
893+
mode_v.As<Int32>()->Value();
894+
} else if (type != "access") {
895+
THROW_ERR_INVALID_ARG_TYPE(env,
896+
"type must be equal to \"copyFile\" or \"access\"");
897+
return -1;
898+
}
899+
900+
if (mode_v->IsNullOrUndefined()) {
901+
return def;
902+
}
903+
904+
const int mode = mode_v.As<Int32>()->Value();
905+
if (mode < min || mode > max) {
906+
THROW_ERR_OUT_OF_RANGE(env,
907+
"mode is out of range: >= %d && <= %d", min, max);
908+
return -1;
909+
}
910+
911+
return mode;
912+
}
913+
857914
void AfterMkdirp(uv_fs_t* req) {
858915
FSReqBase* req_wrap = FSReqBase::from_req(req);
859916
FSReqAfterScope after(req_wrap, req);
@@ -973,8 +1030,8 @@ void Access(const FunctionCallbackInfo<Value>& args) {
9731030
const int argc = args.Length();
9741031
CHECK_GE(argc, 2);
9751032

976-
CHECK(args[1]->IsInt32());
977-
int mode = args[1].As<Int32>()->Value();
1033+
int mode = GetValidMode(env, args[1], "access");
1034+
if (mode == -1) return;
9781035

9791036
BufferValue path(isolate, args[0]);
9801037
CHECK_NOT_NULL(*path);
@@ -2086,6 +2143,9 @@ static void CopyFile(const FunctionCallbackInfo<Value>& args) {
20862143
const int argc = args.Length();
20872144
CHECK_GE(argc, 3);
20882145

2146+
const int flags = GetValidMode(env, args[2], "copyFile");
2147+
if (flags == -1) return;
2148+
20892149
BufferValue src(isolate, args[0]);
20902150
CHECK_NOT_NULL(*src);
20912151
THROW_IF_INSUFFICIENT_PERMISSIONS(
@@ -2096,9 +2156,6 @@ static void CopyFile(const FunctionCallbackInfo<Value>& args) {
20962156
THROW_IF_INSUFFICIENT_PERMISSIONS(
20972157
env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView());
20982158

2099-
CHECK(args[2]->IsInt32());
2100-
const int flags = args[2].As<Int32>()->Value();
2101-
21022159
if (argc > 3) { // copyFile(src, dest, flags, req)
21032160
FSReqBase* req_wrap_async = GetReqWrap(args, 3);
21042161
FS_ASYNC_TRACE_BEGIN2(UV_FS_COPYFILE,

test/parallel/test-fs-access.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ fs.accessSync(readWriteFile, mode);
163163
{ [Symbol.toPrimitive]() { return fs.constants.R_OK; } },
164164
[1],
165165
'r',
166+
NaN,
167+
Infinity,
166168
].forEach((mode, i) => {
167169
console.log(mode, i);
168170
assert.throws(
@@ -183,8 +185,6 @@ fs.accessSync(readWriteFile, mode);
183185
[
184186
-1,
185187
8,
186-
Infinity,
187-
NaN,
188188
].forEach((mode, i) => {
189189
console.log(mode, i);
190190
assert.throws(

test/parallel/test-fs-cp.mjs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,10 @@ function nextdir() {
133133

134134
// It rejects if options.mode is invalid.
135135
{
136+
const src = './test/fixtures/copy/kitchen-sink';
137+
const dest = nextdir();
136138
assert.throws(
137-
() => cpSync('a', 'b', { mode: -1 }),
139+
() => cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true, mode: -1 })),
138140
{ code: 'ERR_OUT_OF_RANGE' }
139141
);
140142
}
@@ -858,10 +860,11 @@ if (!isWindows) {
858860

859861
// It throws if options is not object.
860862
{
861-
assert.throws(
862-
() => cp('a', 'b', { mode: -1 }, () => {}),
863-
{ code: 'ERR_OUT_OF_RANGE' }
864-
);
863+
const src = './test/fixtures/copy/kitchen-sink';
864+
const dest = nextdir();
865+
cp(src, dest, mustNotMutateObjectDeep({ recursive: true, mode: -1 }), (err) => {
866+
assert.strictEqual(err.code, 'ERR_OUT_OF_RANGE');
867+
});
865868
}
866869

867870
// Promises implementation of copy.
@@ -943,10 +946,13 @@ if (!isWindows) {
943946

944947
// It rejects if options.mode is invalid.
945948
{
949+
const src = './test/fixtures/copy/kitchen-sink';
950+
const dest = nextdir();
946951
await assert.rejects(
947-
fs.promises.cp('a', 'b', {
952+
fs.promises.cp(src, dest, mustNotMutateObjectDeep({
953+
recursive: true,
948954
mode: -1,
949-
}),
955+
})),
950956
{ code: 'ERR_OUT_OF_RANGE' }
951957
);
952958
}

0 commit comments

Comments
 (0)