Skip to content

Commit 1432065

Browse files
committed
lib: correct error.errno to always be numeric
Historically `error.errno` of system errors thrown by Node.js can sometimes be the same as `err.code`, which are string representations of the error numbers. This is useless and incorrect, and results in an information loss for users since then they will have to resort to something like `process.binding('uv'[`UV_${errno}`])` to get to the numeric error codes. This patch corrects this behavior by always setting `error.errno` to be negative numbers. For fabricated errors like `ENOTFOUND`, `error.errno` is now undefined since there is no numeric equivalent for them anyway. For c-ares errors, `error.errno` is now undefined because the numeric representations (negated) can be in conflict with libuv error codes - this is fine since numeric codes was not available for c-ares errors anyway. Users can use the public API `util.getSystemErrorName(errno)` to retrieve string codes for these numbers. PR-URL: #28140 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
1 parent 28d3f19 commit 1432065

19 files changed

Lines changed: 98 additions & 65 deletions

‎doc/api/errors.md‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ attempts to read a file that does not exist.
419419
*`code` {string} The string error code
420420
*`dest` {string} If present, the file path destination when reporting a file
421421
system error
422-
*`errno` {number|string} The system-provided error number
422+
*`errno` {number} The system-provided error number
423423
*`info` {Object} If present, extra details about the error condition
424424
*`message` {string} A system-provided human-readable description of the error
425425
*`path` {string} If present, the file path when reporting a file system error
@@ -448,13 +448,15 @@ system error.
448448

449449
### error.errno
450450

451-
* {string|number}
451+
* {number}
452+
453+
The `error.errno` property is a negative number which corresponds
454+
to the error code defined in [`libuv Error handling`].
455+
456+
On Windows the error number provided by the system will be normalized by libuv.
452457

453-
The `error.errno` property is a number or a string. If it is a number, it is a
454-
negative value which corresponds to the error code defined in
455-
[`libuv Error handling`]. See the libuv `errno.h` header file
456-
(`deps/uv/include/uv/errno.h` in the Node.js source tree) for details. In case
457-
of a string, it is the same as `error.code`.
458+
To get the string representation of the error code, use
459+
[`util.getSystemErrorName(error.errno)`].
458460

459461
### error.info
460462

@@ -2365,6 +2367,7 @@ such as `process.stdout.on('data')`.
23652367
[`stream.write()`]: stream.html#stream_writable_write_chunk_encoding_callback
23662368
[`subprocess.kill()`]: child_process.html#child_process_subprocess_kill_signal
23672369
[`subprocess.send()`]: child_process.html#child_process_subprocess_send_message_sendhandle_options_callback
2370+
[`util.getSystemErrorName(error.errno)`]: util.html#util_util_getsystemerrorname_err
23682371
[`zlib`]: zlib.html
23692372
[ES Module]: esm.html
23702373
[ICU]: intl.html#intl_internationalization_support

‎lib/internal/cluster/round_robin_handle.js‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
constassert=require('internal/assert');
33
constnet=require('net');
44
const{ sendHelper }=require('internal/cluster/utils');
5-
constuv=internalBinding('uv');
65
const{ constants }=internalBinding('tcp_wrap');
76

87
module.exports=RoundRobinHandle;
@@ -58,10 +57,7 @@ RoundRobinHandle.prototype.add = function(worker, send) {
5857
// Still busy binding.
5958
this.server.once('listening',done);
6059
this.server.once('error',(err)=>{
61-
// Hack: translate 'EADDRINUSE' error string back to numeric error code.
62-
// It works but ideally we'd have some backchannel between the net and
63-
// cluster modules for stuff like this.
64-
send(uv[`UV_${err.errno}`],null);
60+
send(err.errno,null);
6561
});
6662
};
6763

‎lib/internal/errors.js‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ function uvExceptionWithHostPort(err, syscall, address, port) {
423423
constex=newError(`${message}${details}`);
424424
Error.stackTraceLimit=tmpLimit;
425425
ex.code=code;
426-
ex.errno=code;
426+
ex.errno=err;
427427
ex.syscall=syscall;
428428
ex.address=address;
429429
if(port){
@@ -455,8 +455,8 @@ function errnoException(err, syscall, original) {
455455

456456
// eslint-disable-next-line no-restricted-syntax
457457
constex=newError(message);
458-
// TODO(joyeecheung): errno is supposed to err, like in uvException
459-
ex.code=ex.errno=code;
458+
ex.errno=err;
459+
ex.code=code;
460460
ex.syscall=syscall;
461461

462462
// eslint-disable-next-line no-restricted-syntax
@@ -499,9 +499,9 @@ function exceptionWithHostPort(err, syscall, address, port, additional) {
499499
Error.stackTraceLimit=0;
500500
// eslint-disable-next-line no-restricted-syntax
501501
constex=newError(`${syscall}${code}${details}`);
502-
// TODO(joyeecheung): errno is supposed to err, like in uvException
503502
Error.stackTraceLimit=tmpLimit;
504-
ex.code=ex.errno=code;
503+
ex.errno=err;
504+
ex.code=code;
505505
ex.syscall=syscall;
506506
ex.address=address;
507507
if(port){
@@ -520,9 +520,16 @@ function exceptionWithHostPort(err, syscall, address, port, additional) {
520520
* @returns {Error}
521521
*/
522522
functiondnsException(code,syscall,hostname){
523+
leterrno;
523524
// If `code` is of type number, it is a libuv error number, else it is a
524525
// c-ares error code.
526+
// TODO(joyeecheung): translate c-ares error codes into numeric ones and
527+
// make them available in a property that's not error.errno (since they
528+
// can be in conflict with libuv error codes). Also make sure
529+
// util.getSystemErrorName() can understand them when an being informed that
530+
// the number is a c-ares error code.
525531
if(typeofcode==='number'){
532+
errno=code;
526533
// ENOTFOUND is not a proper POSIX error, but this error has been in place
527534
// long enough that it's not practical to remove it.
528535
if(code===lazyUv().UV_EAI_NODATA||code===lazyUv().UV_EAI_NONAME){
@@ -539,10 +546,8 @@ function dnsException(code, syscall, hostname) {
539546
Error.stackTraceLimit=0;
540547
// eslint-disable-next-line no-restricted-syntax
541548
constex=newError(message);
542-
// TODO(joyeecheung): errno is supposed to be a number / err, like in
543549
Error.stackTraceLimit=tmpLimit;
544-
// uvException.
545-
ex.errno=code;
550+
ex.errno=errno;
546551
ex.code=code;
547552
ex.syscall=syscall;
548553
if(hostname){

‎lib/internal/net.js‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ function makeSyncWrite(fd) {
5656
writeBuffer(fd,chunk,0,chunk.length,null,undefined,ctx);
5757
if(ctx.errno!==undefined){
5858
constex=errors.uvException(ctx);
59-
// Legacy: net writes have .code === .errno, whereas writeBuffer gives the
60-
// raw errno number in .errno.
61-
ex.errno=ex.code;
59+
ex.errno=ctx.errno;
6260
returncb(ex);
6361
}
6462
cb();

‎test/internet/test-dns.js‎

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
constcommon=require('../common');
2525
const{ addresses }=require('../common/internet');
2626
const{ internalBinding }=require('internal/test/binding');
27+
const{ getSystemErrorName }=require('util');
2728
constassert=require('assert');
2829
constdns=require('dns');
2930
constnet=require('net');
@@ -71,7 +72,10 @@ function checkWrap(req) {
7172
TEST(functiontest_reverse_bogus(done){
7273
dnsPromises.reverse('bogus ip')
7374
.then(common.mustNotCall())
74-
.catch(common.expectsError({errno: 'EINVAL'}));
75+
.catch(common.mustCall((err)=>{
76+
assert.strictEqual(err.code,'EINVAL');
77+
assert.strictEqual(getSystemErrorName(err.errno),'EINVAL');
78+
}));
7579

7680
assert.throws(()=>{
7781
dns.reverse('bogus ip',common.mustNotCall());
@@ -161,11 +165,13 @@ TEST(async function test_resolveMx(done) {
161165
TEST(functiontest_resolveMx_failure(done){
162166
dnsPromises.resolveMx(addresses.INVALID_HOST)
163167
.then(common.mustNotCall())
164-
.catch(common.expectsError({errno: 'ENOTFOUND'}));
168+
.catch(common.mustCall((err)=>{
169+
assert.strictEqual(err.code,'ENOTFOUND');
170+
}));
165171

166172
constreq=dns.resolveMx(addresses.INVALID_HOST,function(err,result){
167173
assert.ok(errinstanceofError);
168-
assert.strictEqual(err.errno,'ENOTFOUND');
174+
assert.strictEqual(err.code,'ENOTFOUND');
169175

170176
assert.strictEqual(result,undefined);
171177

@@ -199,11 +205,13 @@ TEST(async function test_resolveNs(done) {
199205
TEST(functiontest_resolveNs_failure(done){
200206
dnsPromises.resolveNs(addresses.INVALID_HOST)
201207
.then(common.mustNotCall())
202-
.catch(common.expectsError({errno: 'ENOTFOUND'}));
208+
.catch(common.mustCall((err)=>{
209+
assert.strictEqual(err.code,'ENOTFOUND');
210+
}));
203211

204212
constreq=dns.resolveNs(addresses.INVALID_HOST,function(err,result){
205213
assert.ok(errinstanceofError);
206-
assert.strictEqual(err.errno,'ENOTFOUND');
214+
assert.strictEqual(err.code,'ENOTFOUND');
207215

208216
assert.strictEqual(result,undefined);
209217

@@ -241,11 +249,13 @@ TEST(async function test_resolveSrv(done) {
241249
TEST(functiontest_resolveSrv_failure(done){
242250
dnsPromises.resolveSrv(addresses.INVALID_HOST)
243251
.then(common.mustNotCall())
244-
.catch(common.expectsError({errno: 'ENOTFOUND'}));
252+
.catch(common.mustCall((err)=>{
253+
assert.strictEqual(err.code,'ENOTFOUND');
254+
}));
245255

246256
constreq=dns.resolveSrv(addresses.INVALID_HOST,function(err,result){
247257
assert.ok(errinstanceofError);
248-
assert.strictEqual(err.errno,'ENOTFOUND');
258+
assert.strictEqual(err.code,'ENOTFOUND');
249259

250260
assert.strictEqual(result,undefined);
251261

@@ -279,11 +289,13 @@ TEST(async function test_resolvePtr(done) {
279289
TEST(functiontest_resolvePtr_failure(done){
280290
dnsPromises.resolvePtr(addresses.INVALID_HOST)
281291
.then(common.mustNotCall())
282-
.catch(common.expectsError({errno: 'ENOTFOUND'}));
292+
.catch(common.mustCall((err)=>{
293+
assert.strictEqual(err.code,'ENOTFOUND');
294+
}));
283295

284296
constreq=dns.resolvePtr(addresses.INVALID_HOST,function(err,result){
285297
assert.ok(errinstanceofError);
286-
assert.strictEqual(err.errno,'ENOTFOUND');
298+
assert.strictEqual(err.code,'ENOTFOUND');
287299

288300
assert.strictEqual(result,undefined);
289301

@@ -322,11 +334,13 @@ TEST(async function test_resolveNaptr(done) {
322334
TEST(functiontest_resolveNaptr_failure(done){
323335
dnsPromises.resolveNaptr(addresses.INVALID_HOST)
324336
.then(common.mustNotCall())
325-
.catch(common.expectsError({errno: 'ENOTFOUND'}));
337+
.catch(common.mustCall((err)=>{
338+
assert.strictEqual(err.code,'ENOTFOUND');
339+
}));
326340

327341
constreq=dns.resolveNaptr(addresses.INVALID_HOST,function(err,result){
328342
assert.ok(errinstanceofError);
329-
assert.strictEqual(err.errno,'ENOTFOUND');
343+
assert.strictEqual(err.code,'ENOTFOUND');
330344

331345
assert.strictEqual(result,undefined);
332346

@@ -369,11 +383,13 @@ TEST(async function test_resolveSoa(done) {
369383
TEST(functiontest_resolveSoa_failure(done){
370384
dnsPromises.resolveSoa(addresses.INVALID_HOST)
371385
.then(common.mustNotCall())
372-
.catch(common.expectsError({errno: 'ENOTFOUND'}));
386+
.catch(common.mustCall((err)=>{
387+
assert.strictEqual(err.code,'ENOTFOUND');
388+
}));
373389

374390
constreq=dns.resolveSoa(addresses.INVALID_HOST,function(err,result){
375391
assert.ok(errinstanceofError);
376-
assert.strictEqual(err.errno,'ENOTFOUND');
392+
assert.strictEqual(err.code,'ENOTFOUND');
377393

378394
assert.strictEqual(result,undefined);
379395

@@ -407,11 +423,13 @@ TEST(async function test_resolveCname(done) {
407423
TEST(functiontest_resolveCname_failure(done){
408424
dnsPromises.resolveCname(addresses.INVALID_HOST)
409425
.then(common.mustNotCall())
410-
.catch(common.expectsError({errno: 'ENOTFOUND'}));
426+
.catch(common.mustCall((err)=>{
427+
assert.strictEqual(err.code,'ENOTFOUND');
428+
}));
411429

412430
constreq=dns.resolveCname(addresses.INVALID_HOST,function(err,result){
413431
assert.ok(errinstanceofError);
414-
assert.strictEqual(err.errno,'ENOTFOUND');
432+
assert.strictEqual(err.code,'ENOTFOUND');
415433

416434
assert.strictEqual(result,undefined);
417435

@@ -443,11 +461,13 @@ TEST(async function test_resolveTxt(done) {
443461
TEST(functiontest_resolveTxt_failure(done){
444462
dnsPromises.resolveTxt(addresses.INVALID_HOST)
445463
.then(common.mustNotCall())
446-
.catch(common.expectsError({errno: 'ENOTFOUND'}));
464+
.catch(common.mustCall((err)=>{
465+
assert.strictEqual(err.code,'ENOTFOUND');
466+
}));
447467

448468
constreq=dns.resolveTxt(addresses.INVALID_HOST,function(err,result){
449469
assert.ok(errinstanceofError);
450-
assert.strictEqual(err.errno,'ENOTFOUND');
470+
assert.strictEqual(err.code,'ENOTFOUND');
451471

452472
assert.strictEqual(result,undefined);
453473

@@ -461,12 +481,12 @@ TEST(function test_resolveTxt_failure(done) {
461481
TEST(functiontest_lookup_failure(done){
462482
dnsPromises.lookup(addresses.INVALID_HOST,4)
463483
.then(common.mustNotCall())
464-
.catch(common.expectsError({errno: dns.NOTFOUND}));
484+
.catch(common.expectsError({code: dns.NOTFOUND}));
465485

466486
constreq=dns.lookup(addresses.INVALID_HOST,4,(err)=>{
467487
assert.ok(errinstanceofError);
468-
assert.strictEqual(err.errno,dns.NOTFOUND);
469-
assert.strictEqual(err.errno,'ENOTFOUND');
488+
assert.strictEqual(err.code,dns.NOTFOUND);
489+
assert.strictEqual(err.code,'ENOTFOUND');
470490
assert.ok(!/ENOENT/.test(err.message));
471491
assert.ok(err.message.includes(addresses.INVALID_HOST));
472492

‎test/parallel/test-child-process-execfilesync-maxbuf.js‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ require('../common');
55
// works as expected.
66

77
constassert=require('assert');
8+
const{ getSystemErrorName }=require('util');
89
const{ execFileSync }=require('child_process');
910
constmsgOut='this is stdout';
1011
constmsgOutBuf=Buffer.from(`${msgOut}\n`);
@@ -20,7 +21,8 @@ const args = [
2021
execFileSync(process.execPath,args,{maxBuffer: 1});
2122
},(e)=>{
2223
assert.ok(e,'maxBuffer should error');
23-
assert.strictEqual(e.errno,'ENOBUFS');
24+
assert.strictEqual(e.code,'ENOBUFS');
25+
assert.strictEqual(getSystemErrorName(e.errno),'ENOBUFS');
2426
// We can have buffers larger than maxBuffer because underneath we alloc 64k
2527
// that matches our read sizes.
2628
assert.deepStrictEqual(e.stdout,msgOutBuf);
@@ -44,7 +46,8 @@ const args = [
4446
);
4547
},(e)=>{
4648
assert.ok(e,'maxBuffer should error');
47-
assert.strictEqual(e.errno,'ENOBUFS');
49+
assert.strictEqual(e.code,'ENOBUFS');
50+
assert.strictEqual(getSystemErrorName(e.errno),'ENOBUFS');
4851
returntrue;
4952
});
5053
}

‎test/parallel/test-child-process-execsync-maxbuf.js‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ require('../common');
55
// works as expected.
66

77
constassert=require('assert');
8+
const{ getSystemErrorName }=require('util');
89
const{ execSync }=require('child_process');
910
constmsgOut='this is stdout';
1011
constmsgOutBuf=Buffer.from(`${msgOut}\n`);
@@ -20,7 +21,8 @@ const args = [
2021
execSync(`"${process.execPath}" ${args.join(' ')}`,{maxBuffer: 1});
2122
},(e)=>{
2223
assert.ok(e,'maxBuffer should error');
23-
assert.strictEqual(e.errno,'ENOBUFS');
24+
assert.strictEqual(e.code,'ENOBUFS');
25+
assert.strictEqual(getSystemErrorName(e.errno),'ENOBUFS');
2426
// We can have buffers larger than maxBuffer because underneath we alloc 64k
2527
// that matches our read sizes.
2628
assert.deepStrictEqual(e.stdout,msgOutBuf);
@@ -46,7 +48,8 @@ const args = [
4648
);
4749
},(e)=>{
4850
assert.ok(e,'maxBuffer should error');
49-
assert.strictEqual(e.errno,'ENOBUFS');
51+
assert.strictEqual(e.code,'ENOBUFS');
52+
assert.strictEqual(getSystemErrorName(e.errno),'ENOBUFS');
5053
returntrue;
5154
});
5255
}

‎test/parallel/test-child-process-spawn-error.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
'use strict';
2323
constcommon=require('../common');
24+
const{ getSystemErrorName }=require('util');
2425
constspawn=require('child_process').spawn;
2526
constassert=require('assert');
2627
constfs=require('fs');
@@ -42,7 +43,7 @@ assert.strictEqual(enoentChild.stdio[2], enoentChild.stderr);
4243

4344
enoentChild.on('error',common.mustCall(function(err){
4445
assert.strictEqual(err.code,'ENOENT');
45-
assert.strictEqual(err.errno,'ENOENT');
46+
assert.strictEqual(getSystemErrorName(err.errno),'ENOENT');
4647
assert.strictEqual(err.syscall,`spawn ${enoentPath}`);
4748
assert.strictEqual(err.path,enoentPath);
4849
assert.deepStrictEqual(err.spawnargs,spawnargs);

‎test/parallel/test-child-process-spawnsync-maxbuf.js‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require('../common');
66

77
constassert=require('assert');
88
constspawnSync=require('child_process').spawnSync;
9+
const{ getSystemErrorName }=require('util');
910
constmsgOut='this is stdout';
1011
constmsgOutBuf=Buffer.from(`${msgOut}\n`);
1112

@@ -19,7 +20,8 @@ const args = [
1920
constret=spawnSync(process.execPath,args,{maxBuffer: 1});
2021

2122
assert.ok(ret.error,'maxBuffer should error');
22-
assert.strictEqual(ret.error.errno,'ENOBUFS');
23+
assert.strictEqual(ret.error.code,'ENOBUFS');
24+
assert.strictEqual(getSystemErrorName(ret.error.errno),'ENOBUFS');
2325
// We can have buffers larger than maxBuffer because underneath we alloc 64k
2426
// that matches our read sizes.
2527
assert.deepStrictEqual(ret.stdout,msgOutBuf);
@@ -39,7 +41,8 @@ const args = [
3941
constret=spawnSync(process.execPath,args);
4042

4143
assert.ok(ret.error,'maxBuffer should error');
42-
assert.strictEqual(ret.error.errno,'ENOBUFS');
44+
assert.strictEqual(ret.error.code,'ENOBUFS');
45+
assert.strictEqual(getSystemErrorName(ret.error.errno),'ENOBUFS');
4346
}
4447

4548
// Default maxBuffer size is 1024 * 1024.

0 commit comments

Comments
 (0)