Skip to content

Commit 4b278d1

Browse files
joyeecheungtargos
authored andcommitted
lib: unmask mode_t values with 0o777
This commit allows permission bits higher than 0o777 to go through the API (e.g. `S_ISVTX`=`0o1000`, `S_ISGID`=`0o2000`, `S_ISUID`=`0o4000`). Also documents that these bits are not exposed through `fs.constants` and their behaviors are platform-specific, so the users need to use them on their own risk. PR-URL: #20975 Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Backport-PR-URL: #21172
1 parent 34a6c89 commit 4b278d1

9 files changed

+43
-27
lines changed

doc/api/fs.md

+6
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,12 @@ For example, the octal value `0o765` means:
10891089
* The group may read and write the file.
10901090
* Others may read and execute the file.
10911091

1092+
Note: When using raw numbers where file modes are expected,
1093+
any value larger than `0o777` may result in platform-specific
1094+
behaviors that are not supported to work consistently.
1095+
Therefore constants like `S_ISVTX`, `S_ISGID` or `S_ISUID` are
1096+
not exposed in `fs.constants`.
1097+
10921098
Caveats: on Windows only the write permission can be changed, and the
10931099
distinction among the permissions of group, owner or others is not
10941100
implemented.

lib/fs.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const {
7777
} = require('internal/constants');
7878
const {
7979
isUint32,
80-
validateAndMaskMode,
80+
validateMode,
8181
validateInteger,
8282
validateInt32,
8383
validateUint32
@@ -416,7 +416,7 @@ function open(path, flags, mode, callback) {
416416
callback = makeCallback(mode);
417417
mode = 0o666;
418418
} else {
419-
mode = validateAndMaskMode(mode, 'mode', 0o666);
419+
mode = validateMode(mode, 'mode', 0o666);
420420
callback = makeCallback(callback);
421421
}
422422

@@ -434,7 +434,7 @@ function openSync(path, flags, mode) {
434434
path = getPathFromURL(path);
435435
validatePath(path);
436436
const flagsNumber = stringToFlags(flags);
437-
mode = validateAndMaskMode(mode, 'mode', 0o666);
437+
mode = validateMode(mode, 'mode', 0o666);
438438

439439
const ctx = { path };
440440
const result = binding.open(pathModule.toNamespacedPath(path),
@@ -721,7 +721,7 @@ function mkdir(path, mode, callback) {
721721
mode = 0o777;
722722
} else {
723723
callback = makeCallback(callback);
724-
mode = validateAndMaskMode(mode, 'mode', 0o777);
724+
mode = validateMode(mode, 'mode', 0o777);
725725
}
726726

727727
const req = new FSReqWrap();
@@ -732,7 +732,7 @@ function mkdir(path, mode, callback) {
732732
function mkdirSync(path, mode) {
733733
path = getPathFromURL(path);
734734
validatePath(path);
735-
mode = validateAndMaskMode(mode, 'mode', 0o777);
735+
mode = validateMode(mode, 'mode', 0o777);
736736
const ctx = { path };
737737
binding.mkdir(pathModule.toNamespacedPath(path), mode, undefined, ctx);
738738
handleErrorFromBinding(ctx);
@@ -915,7 +915,7 @@ function unlinkSync(path) {
915915

916916
function fchmod(fd, mode, callback) {
917917
validateInt32(fd, 'fd', 0);
918-
mode = validateAndMaskMode(mode, 'mode');
918+
mode = validateMode(mode, 'mode');
919919
callback = makeCallback(callback);
920920

921921
const req = new FSReqWrap();
@@ -925,7 +925,7 @@ function fchmod(fd, mode, callback) {
925925

926926
function fchmodSync(fd, mode) {
927927
validateInt32(fd, 'fd', 0);
928-
mode = validateAndMaskMode(mode, 'mode');
928+
mode = validateMode(mode, 'mode');
929929
const ctx = {};
930930
binding.fchmod(fd, mode, undefined, ctx);
931931
handleErrorFromBinding(ctx);
@@ -966,7 +966,7 @@ function lchmodSync(path, mode) {
966966
function chmod(path, mode, callback) {
967967
path = getPathFromURL(path);
968968
validatePath(path);
969-
mode = validateAndMaskMode(mode, 'mode');
969+
mode = validateMode(mode, 'mode');
970970
callback = makeCallback(callback);
971971

972972
const req = new FSReqWrap();
@@ -977,7 +977,7 @@ function chmod(path, mode, callback) {
977977
function chmodSync(path, mode) {
978978
path = getPathFromURL(path);
979979
validatePath(path);
980-
mode = validateAndMaskMode(mode, 'mode');
980+
mode = validateMode(mode, 'mode');
981981

982982
const ctx = { path };
983983
binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx);

lib/internal/fs/promises.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const {
3232
} = require('internal/fs/utils');
3333
const {
3434
isUint32,
35-
validateAndMaskMode,
35+
validateMode,
3636
validateInteger,
3737
validateUint32
3838
} = require('internal/validators');
@@ -192,7 +192,7 @@ async function copyFile(src, dest, flags) {
192192
async function open(path, flags, mode) {
193193
path = getPathFromURL(path);
194194
validatePath(path);
195-
mode = validateAndMaskMode(mode, 'mode', 0o666);
195+
mode = validateMode(mode, 'mode', 0o666);
196196
return new FileHandle(
197197
await binding.openFileHandle(pathModule.toNamespacedPath(path),
198198
stringToFlags(flags),
@@ -287,7 +287,7 @@ async function fsync(handle) {
287287
async function mkdir(path, mode) {
288288
path = getPathFromURL(path);
289289
validatePath(path);
290-
mode = validateAndMaskMode(mode, 'mode', 0o777);
290+
mode = validateMode(mode, 'mode', 0o777);
291291
return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises);
292292
}
293293

@@ -359,14 +359,14 @@ async function unlink(path) {
359359

360360
async function fchmod(handle, mode) {
361361
validateFileHandle(handle);
362-
mode = validateAndMaskMode(mode, 'mode');
362+
mode = validateMode(mode, 'mode');
363363
return binding.fchmod(handle.fd, mode, kUsePromises);
364364
}
365365

366366
async function chmod(path, mode) {
367367
path = getPathFromURL(path);
368368
validatePath(path);
369-
mode = validateAndMaskMode(mode, 'mode');
369+
mode = validateMode(mode, 'mode');
370370
return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises);
371371
}
372372

lib/internal/validators.js

+17-6
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,22 @@ function isUint32(value) {
1616

1717
const octalReg = /^[0-7]+$/;
1818
const modeDesc = 'must be a 32-bit unsigned integer or an octal string';
19-
// Validator for mode_t (the S_* constants). Valid numbers or octal strings
20-
// will be masked with 0o777 to be consistent with the behavior in POSIX APIs.
21-
function validateAndMaskMode(value, name, def) {
19+
20+
/**
21+
* Validate values that will be converted into mode_t (the S_* constants).
22+
* Only valid numbers and octal strings are allowed. They could be converted
23+
* to 32-bit unsigned integers or non-negative signed integers in the C++
24+
* land, but any value higher than 0o777 will result in platform-specific
25+
* behaviors.
26+
*
27+
* @param {*} value Values to be validated
28+
* @param {string} name Name of the argument
29+
* @param {number} def If specified, will be returned for invalid values
30+
* @returns {number}
31+
*/
32+
function validateMode(value, name, def) {
2233
if (isUint32(value)) {
23-
return value & 0o777;
34+
return value;
2435
}
2536

2637
if (typeof value === 'number') {
@@ -37,7 +48,7 @@ function validateAndMaskMode(value, name, def) {
3748
throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc);
3849
}
3950
const parsed = parseInt(value, 8);
40-
return parsed & 0o777;
51+
return parsed;
4152
}
4253

4354
// TODO(BridgeAR): Only return `def` in case `value == null`
@@ -106,7 +117,7 @@ function validateUint32(value, name, positive) {
106117
module.exports = {
107118
isInt32,
108119
isUint32,
109-
validateAndMaskMode,
120+
validateMode,
110121
validateInteger,
111122
validateInt32,
112123
validateUint32

test/parallel/test-fs-chmod-mask.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
// This tests that mode > 0o777 will be masked off with 0o777 in fs APIs.
3+
// This tests that the lower bits of mode > 0o777 still works in fs APIs.
44

55
const common = require('../common');
66
const assert = require('assert');

test/parallel/test-fs-mkdir-mode-mask.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
// This tests that mode > 0o777 will be masked off with 0o777 in fs.mkdir().
3+
// This tests that the lower bits of mode > 0o777 still works in fs.mkdir().
44

55
const common = require('../common');
66
const assert = require('assert');

test/parallel/test-fs-open-mode-mask.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
// This tests that mode > 0o777 will be masked off with 0o777 in fs.open().
3+
// This tests that the lower bits of mode > 0o777 still works in fs.open().
44

55
const common = require('../common');
66
const assert = require('assert');

test/parallel/test-fs-promises.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,8 @@ function verifyStatObject(stat) {
107107
await chmod(dest, 0o666);
108108
await handle.chmod(0o666);
109109

110-
// Mode larger than 0o777 should be masked off.
111-
await chmod(dest, (0o777 + 1));
112-
await handle.chmod(0o777 + 1);
110+
await chmod(dest, (0o10777));
111+
await handle.chmod(0o10777);
113112

114113
await utimes(dest, new Date(), new Date());
115114

test/parallel/test-process-umask-mask.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
// This tests that mask > 0o777 will be masked off with 0o777 in
3+
// This tests that the lower bits of mode > 0o777 still works in
44
// process.umask()
55

66
const common = require('../common');

0 commit comments

Comments
 (0)