-
Notifications
You must be signed in to change notification settings - Fork 29.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate process errors from C++ to JS #19973
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
'use strict'; | ||
|
||
const { | ||
ERR_INVALID_ARG_TYPE, | ||
ERR_INVALID_ARG_VALUE, | ||
ERR_UNKNOWN_CREDENTIAL | ||
} = require('internal/errors').codes; | ||
const { | ||
validateUint32 | ||
} = require('internal/validators'); | ||
|
||
function setupProcessMethods() { | ||
// Non-POSIX platforms like Windows don't have certain methods. | ||
if (process.setgid !== undefined) { | ||
setupPosixMethods(); | ||
} | ||
|
||
const { | ||
chdir: _chdir, | ||
umask: _umask, | ||
} = process; | ||
|
||
process.chdir = chdir; | ||
process.umask = umask; | ||
|
||
function chdir(directory) { | ||
if (typeof directory !== 'string') { | ||
throw new ERR_INVALID_ARG_TYPE('directory', 'string', directory); | ||
} | ||
return _chdir(directory); | ||
} | ||
|
||
const octalReg = /^[0-7]+$/; | ||
function umask(mask) { | ||
if (typeof mask === 'undefined') { | ||
return _umask(mask); | ||
} | ||
|
||
if (typeof mask === 'number') { | ||
validateUint32(mask, 'mask'); | ||
return _umask(mask); | ||
} | ||
|
||
if (typeof mask === 'string') { | ||
if (!octalReg.test(mask)) { | ||
throw new ERR_INVALID_ARG_VALUE('mask', mask, | ||
'must be an octal string'); | ||
} | ||
const octal = Number.parseInt(mask, 8); | ||
validateUint32(octal, 'mask'); | ||
return _umask(octal); | ||
} | ||
|
||
throw new ERR_INVALID_ARG_TYPE('mask', ['number', 'string', 'undefined'], | ||
mask); | ||
} | ||
} | ||
|
||
function setupPosixMethods() { | ||
const { | ||
initgroups: _initgroups, | ||
setegid: _setegid, | ||
seteuid: _seteuid, | ||
setgid: _setgid, | ||
setuid: _setuid, | ||
setgroups: _setgroups | ||
} = process; | ||
|
||
process.initgroups = initgroups; | ||
process.setegid = setegid; | ||
process.seteuid = seteuid; | ||
process.setgid = setgid; | ||
process.setuid = setuid; | ||
process.setgroups = setgroups; | ||
|
||
function initgroups(user, extraGroup) { | ||
validateId(user, 'user'); | ||
validateId(extraGroup, 'extraGroup'); | ||
// Result is 0 on success, 1 if user is unknown, 2 if group is unknown. | ||
const result = _initgroups(user, extraGroup); | ||
if (result === 1) { | ||
throw new ERR_UNKNOWN_CREDENTIAL('User', user); | ||
} else if (result === 2) { | ||
throw new ERR_UNKNOWN_CREDENTIAL('Group', extraGroup); | ||
} | ||
} | ||
|
||
function setegid(id) { | ||
return execId(id, 'Group', _setegid); | ||
} | ||
|
||
function seteuid(id) { | ||
return execId(id, 'User', _seteuid); | ||
} | ||
|
||
function setgid(id) { | ||
return execId(id, 'Group', _setgid); | ||
} | ||
|
||
function setuid(id) { | ||
return execId(id, 'User', _setuid); | ||
} | ||
|
||
function setgroups(groups) { | ||
if (!Array.isArray(groups)) { | ||
throw new ERR_INVALID_ARG_TYPE('groups', 'Array', groups); | ||
} | ||
for (var i = 0; i < groups.length; i++) { | ||
validateId(groups[i], `groups[${i}]`); | ||
} | ||
// Result is 0 on success. A positive integer indicates that the | ||
// corresponding group was not found. | ||
const result = _setgroups(groups); | ||
if (result > 0) { | ||
throw new ERR_UNKNOWN_CREDENTIAL('Group', groups[result - 1]); | ||
} | ||
} | ||
|
||
function execId(id, type, method) { | ||
validateId(id, 'id'); | ||
// Result is 0 on success, 1 if credential is unknown. | ||
const result = method(id); | ||
if (result === 1) { | ||
throw new ERR_UNKNOWN_CREDENTIAL(type, id); | ||
} | ||
} | ||
|
||
function validateId(id, name) { | ||
if (typeof id === 'number') { | ||
validateUint32(id, name); | ||
} else if (typeof id !== 'string') { | ||
throw new ERR_INVALID_ARG_TYPE(name, ['number', 'string'], id); | ||
} | ||
} | ||
} | ||
|
||
exports.setup = setupProcessMethods; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
'use strict'; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the consolidation of validation functions! |
||
const { | ||
ERR_INVALID_ARG_TYPE, | ||
ERR_OUT_OF_RANGE | ||
} = require('internal/errors').codes; | ||
|
||
function isInt32(value) { | ||
return value === (value | 0); | ||
} | ||
|
||
function isUint32(value) { | ||
return value === (value >>> 0); | ||
} | ||
|
||
function validateInt32(value, name) { | ||
if (!isInt32(value)) { | ||
let err; | ||
if (typeof value !== 'number') { | ||
err = new ERR_INVALID_ARG_TYPE(name, 'number', value); | ||
} else if (!Number.isInteger(value)) { | ||
err = new ERR_OUT_OF_RANGE(name, 'an integer', value); | ||
} else { | ||
// 2 ** 31 === 2147483648 | ||
err = new ERR_OUT_OF_RANGE(name, '> -2147483649 && < 2147483648', value); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just as a note: as far as I know, this function should actually check |
||
} | ||
Error.captureStackTrace(err, validateInt32); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: I think it is best to actually keep the internal structure visible in most cases. So I personally would not remove this frame. The same applies to the other validation functions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer not to change this in this PR. |
||
throw err; | ||
} | ||
} | ||
|
||
function validateUint32(value, name, positive) { | ||
if (!isUint32(value)) { | ||
let err; | ||
if (typeof value !== 'number') { | ||
err = new ERR_INVALID_ARG_TYPE(name, 'number', value); | ||
} else if (!Number.isInteger(value)) { | ||
err = new ERR_OUT_OF_RANGE(name, 'an integer', value); | ||
} else { | ||
const min = positive ? 1 : 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: it is not much overhead but the default argument is slower than not having a default in all cases. The check itself could stay as it is, since it already tests for truthy values only. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the default value |
||
// 2 ** 32 === 4294967296 | ||
err = new ERR_OUT_OF_RANGE(name, `>= ${min} && < 4294967296`, value); | ||
} | ||
Error.captureStackTrace(err, validateUint32); | ||
throw err; | ||
} else if (positive && value === 0) { | ||
const err = new ERR_OUT_OF_RANGE(name, '>= 1 && < 4294967296', value); | ||
Error.captureStackTrace(err, validateUint32); | ||
throw err; | ||
} | ||
} | ||
|
||
module.exports = { | ||
isInt32, | ||
isUint32, | ||
validateInt32, | ||
validateUint32 | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: This is actually only testing for
<= 2 ** 32
. All other checks are already done due to the RegExp above.I personally would either remove the extra check or make the whole function stricter to only accept valid entries.
The reason
validateUint32
is used for numbers is only about making sure no negative numbers got passed in (as far as I know).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know what is the largest allowed value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@targos In some fs methods we throw if the mode is larger than
0o777
. It is not strictly documented wether we mask off or throw if themode_t
type of value passed to any API is larger than that though.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens in the system if the value is larger than
0o777
?For example if you pass
0o12345
? is it truncated to0o345
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: add a TODO to investigate and land the PR. In
fs
there are a couple of validations that need improvements, especially having more consistent checks. I am also working on some of those.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@targos Yes it gets truncated/masked. http://man7.org/linux/man-pages/man2/umask.2.html