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

+98
-65
lines changed

doc/api/errors.md

+10-7
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

+1-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
const assert = require('internal/assert');
33
const net = require('net');
44
const { sendHelper } = require('internal/cluster/utils');
5-
const uv = 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

+13-8
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ function uvExceptionWithHostPort(err, syscall, address, port) {
423423
const ex = new Error(`${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
const ex = new Error(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
const ex = new Error(`${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
function dnsException(code, syscall, hostname) {
523+
let errno;
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 (typeof code === '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
const ex = new Error(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

+1-3
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
const ex = 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
return cb(ex);
6361
}
6462
cb();

test/internet/test-dns.js

+40-20
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
const common = require('../common');
2525
const { addresses } = require('../common/internet');
2626
const { internalBinding } = require('internal/test/binding');
27+
const { getSystemErrorName } = require('util');
2728
const assert = require('assert');
2829
const dns = require('dns');
2930
const net = require('net');
@@ -71,7 +72,10 @@ function checkWrap(req) {
7172
TEST(function test_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(function test_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
const req = dns.resolveMx(addresses.INVALID_HOST, function(err, result) {
167173
assert.ok(err instanceof Error);
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(function test_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
const req = dns.resolveNs(addresses.INVALID_HOST, function(err, result) {
205213
assert.ok(err instanceof Error);
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(function test_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
const req = dns.resolveSrv(addresses.INVALID_HOST, function(err, result) {
247257
assert.ok(err instanceof Error);
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(function test_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
const req = dns.resolvePtr(addresses.INVALID_HOST, function(err, result) {
285297
assert.ok(err instanceof Error);
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(function test_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
const req = dns.resolveNaptr(addresses.INVALID_HOST, function(err, result) {
328342
assert.ok(err instanceof Error);
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(function test_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
const req = dns.resolveSoa(addresses.INVALID_HOST, function(err, result) {
375391
assert.ok(err instanceof Error);
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(function test_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
const req = dns.resolveCname(addresses.INVALID_HOST, function(err, result) {
413431
assert.ok(err instanceof Error);
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(function test_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
const req = dns.resolveTxt(addresses.INVALID_HOST, function(err, result) {
449469
assert.ok(err instanceof Error);
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(function test_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
const req = dns.lookup(addresses.INVALID_HOST, 4, (err) => {
467487
assert.ok(err instanceof Error);
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

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

77
const assert = require('assert');
8+
const { getSystemErrorName } = require('util');
89
const { execFileSync } = require('child_process');
910
const msgOut = 'this is stdout';
1011
const msgOutBuf = 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
return true;
4952
});
5053
}

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

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

77
const assert = require('assert');
8+
const { getSystemErrorName } = require('util');
89
const { execSync } = require('child_process');
910
const msgOut = 'this is stdout';
1011
const msgOutBuf = 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
return true;
5154
});
5255
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
'use strict';
2323
const common = require('../common');
24+
const { getSystemErrorName } = require('util');
2425
const spawn = require('child_process').spawn;
2526
const assert = require('assert');
2627
const fs = 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

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require('../common');
66

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

@@ -19,7 +20,8 @@ const args = [
1920
const ret = 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
const ret = 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)