| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7d52f20 commit ab66de8
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1735,6 +1735,13 @@ that started the Node.js process. Symbolic links, if any, are resolved. | |||
| 1735 | 1735 | added: | |
| 1736 | 1736 | - v23.11.0 | |
| 1737 | 1737 | - v22.15.0 | |
| 1738 | + changes: | ||
| 1739 | + - version: REPLACEME | ||
| 1740 | + pr-url: https://github.com/nodejs/node/pull/62878 | ||
| 1741 | + description: A failed `execve(2)` system call now throws an exception | ||
| 1742 | + instead of aborting the process. Native `AtExit` | ||
| 1743 | + callbacks registered via the embedder API are no longer | ||
| 1744 | + invoked before the `execve(2)` call. | ||
| 1738 | 1745 | --> | |
| 1739 | 1746 | ||
| 1740 | 1747 | > Stability: 1 - Experimental | |
@@ -1751,10 +1758,19 @@ This is achieved by using the `execve` POSIX function and therefore no memory or | |||
| 1751 | 1758 | resources from the current process are preserved, except for the standard input, | |
| 1752 | 1759 | standard output and standard error file descriptor. | |
| 1753 | 1760 | ||
| 1754 | - All other resources are discarded by the system when the processes are swapped, without triggering | ||
| 1755 | - any exit or close events and without running any cleanup handler. | ||
| 1756 | - | ||
| 1757 | - This function will never return, unless an error occurred. | ||
| 1761 | + On success, all other resources are discarded by the system when the | ||
| 1762 | + processes are swapped, without triggering any exit or close events, without | ||
| 1763 | + running any JavaScript cleanup handler (for example `process.on('exit')`), | ||
| 1764 | + and without invoking native `AtExit` callbacks registered through the | ||
| 1765 | + embedder API. Callers that need to run cleanup logic should do so before | ||
| 1766 | + calling `process.execve()`. | ||
| 1767 | + | ||
| 1768 | + This function does not return on success. If the underlying `execve(2)` | ||
| 1769 | + system call fails, an `Error` is thrown whose `code` property is set to the | ||
| 1770 | + corresponding `errno` string (for example, `'ENOENT'` when `file` does not | ||
| 1771 | + exist), with `syscall` set to `'execve'` and `path` set to `file`. When | ||
| 1772 | + `execve(2)` fails the current process continues to run with its state | ||
| 1773 | + unchanged, so a caller may handle the error and take another action. | ||
| 1758 | 1774 | ||
| 1759 | 1775 | This function is not available on Windows or IBM i. | |
| 1760 | 1776 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -510,15 +510,22 @@ static void ReallyExit(const FunctionCallbackInfo<Value>& args) { | |||
| 510 | 510 | } | |
| 511 | 511 | ||
| 512 | 512 | #if defined __POSIX__ && !defined(__PASE__) | |
| 513 | + // Clears FD_CLOEXEC on `fd` so the descriptor is inherited across execve(2). | ||
| 514 | + // On success returns the previous F_GETFD flags (>= 0) so callers can | ||
| 515 | + // restore them if execve(2) subsequently fails. On failure returns -1 with | ||
| 516 | + // errno set. | ||
| 513 | 517 | inline int persist_standard_stream(int fd) { | |
| 514 | 518 | int flags = fcntl(fd, F_GETFD, 0); | |
| 515 | 519 | ||
| 516 | 520 | if (flags < 0) { | |
| 517 | 521 | return flags; | |
| 518 | 522 | } | |
| 519 | 523 | ||
| 520 | - flags &= ~FD_CLOEXEC; | ||
| 521 | - return fcntl(fd, F_SETFD, flags); | ||
| 524 | + if (fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) < 0) { | ||
| 525 | + return -1; | ||
| 526 | + } | ||
| 527 | + | ||
| 528 | + return flags; | ||
| 522 | 529 | } | |
| 523 | 530 | ||
| 524 | 531 | static void Execve(const FunctionCallbackInfo<Value>& args) { | |
@@ -571,32 +578,41 @@ static void Execve(const FunctionCallbackInfo<Value>& args) { | |||
| 571 | 578 | ||
| 572 | 579 | envp[envp_array->Length()] = nullptr; | |
| 573 | 580 | ||
| 574 | - // Set stdin, stdout and stderr to be non-close-on-exec | ||
| 575 | - // so that the new process will inherit it. | ||
| 576 | - if (persist_standard_stream(0) < 0 || persist_standard_stream(1) < 0 || | ||
| 577 | - persist_standard_stream(2) < 0) { | ||
| 578 | - env->ThrowErrnoException(errno, "fcntl"); | ||
| 579 | - return; | ||
| 581 | + // Set stdin, stdout and stderr to be non-close-on-exec so that the new | ||
| 582 | + // process will inherit them. Save the previous flags on each fd so we can | ||
| 583 | + // restore them if execve(2) fails and we throw back to JS. | ||
| 584 | + int saved_stdio_flags[3] = {-1, -1, -1}; | ||
| 585 | + for (int fd = 0; fd < 3; fd++) { | ||
| 586 | + int prev = persist_standard_stream(fd); | ||
| 587 | + if (prev < 0) { | ||
| 588 | + int fcntl_errno = errno; | ||
| 589 | + // Undo changes already applied to earlier fds before throwing. | ||
| 590 | + for (int j = 0; j < fd; j++) { | ||
| 591 | + fcntl(j, F_SETFD, saved_stdio_flags[j]); | ||
| 592 | + } | ||
| 593 | + env->ThrowErrnoException(fcntl_errno, "fcntl"); | ||
| 594 | + return; | ||
| 595 | + } | ||
| 596 | + saved_stdio_flags[fd] = prev; | ||
| 580 | 597 | } | |
| 581 | 598 | ||
| 582 | 599 | // Perform the execve operation. | |
| 583 | - RunAtExit(env); | ||
| 600 | + // | ||
| 601 | + // Note: we intentionally do not invoke RunAtExit(env) here. On success the | ||
| 602 | + // kernel discards the current address space when loading the new image, so | ||
| 603 | + // any in-memory side effects of AtExit callbacks are lost anyway. On | ||
| 604 | + // failure we want to leave the environment intact so the thrown exception | ||
| 605 | + // can be observed and handled by JS code. | ||
| 584 | 606 | execve(*executable, argv.data(), envp.data()); | |
| 585 | 607 | ||
| 586 | - // If it returns, it means that the execve operation failed. | ||
| 587 | - // In that case we abort the process. | ||
| 588 | - auto error_message = std::string("process.execve failed with error code ") + | ||
| 589 | - errors::errno_string(errno); | ||
| 590 | - | ||
| 591 | - // Abort the process | ||
| 592 | - Local<v8::Value> exception = | ||
| 593 | - ErrnoException(isolate, errno, "execve", *executable); | ||
| 594 | - Local<v8::Message> message = v8::Exception::CreateMessage(isolate, exception); | ||
| 595 | - | ||
| 596 | - std::string info = FormatErrorMessage( | ||
| 597 | - isolate, context, error_message.c_str(), message, true); | ||
| 598 | - FPrintF(stderr, "%s\n", info); | ||
| 599 | - ABORT(); | ||
| 608 | + // If execve returned, it failed. Restore the FD_CLOEXEC flags we cleared | ||
| 609 | + // above so that a failed call leaves no observable side effects, then | ||
| 610 | + // throw an ErrnoException so JS can catch it. | ||
| 611 | + int execve_errno = errno; | ||
| 612 | + for (int fd = 0; fd < 3; fd++) { | ||
| 613 | + fcntl(fd, F_SETFD, saved_stdio_flags[fd]); | ||
| 614 | + } | ||
| 615 | + env->ThrowErrnoException(execve_errno, "execve", nullptr, *executable); | ||
| 600 | 616 | } | |
| 601 | 617 | #endif | |
| 602 | 618 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,39 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + const assert = require('assert'); | ||
| 5 | + const { isMainThread } = require('worker_threads'); | ||
| 6 | + | ||
| 7 | + if (!isMainThread) { | ||
| 8 | + common.skip('process.execve is not available in Workers'); | ||
| 9 | + } else if (common.isWindows || common.isIBMi) { | ||
| 10 | + common.skip('process.execve is not available in Windows or IBM i'); | ||
| 11 | + } | ||
| 12 | + | ||
| 13 | + assert.throws( | ||
| 14 | + () => { | ||
| 15 | + process.execve( | ||
| 16 | + `${process.execPath}_non_existing`, | ||
| 17 | + [process.execPath, 'arg'], | ||
| 18 | + ); | ||
| 19 | + }, | ||
| 20 | + (err) => { | ||
| 21 | + assert.ok(err instanceof Error); | ||
| 22 | + assert.strictEqual(err.code, 'ENOENT'); | ||
| 23 | + assert.strictEqual(err.syscall, 'execve'); | ||
| 24 | + assert.strictEqual(typeof err.errno, 'number'); | ||
| 25 | + assert.ok(err.errno > 0, `expected positive errno, got ${err.errno}`); | ||
| 26 | + assert.match(err.path, /_non_existing$/); | ||
| 27 | + return true; | ||
| 28 | + }, | ||
| 29 | + ); | ||
| 30 | + | ||
| 31 | + assert.strictEqual(process.stdout.writable, true); | ||
| 32 | + assert.strictEqual(process.stderr.writable, true); | ||
| 33 | + assert.strictEqual(typeof process.pid, 'number'); | ||
| 34 | + | ||
| 35 | + let tickFired = false; | ||
| 36 | + process.nextTick(common.mustCall(() => { tickFired = true; })); | ||
| 37 | + setImmediate(common.mustCall(() => { | ||
| 38 | + assert.strictEqual(tickFired, true); | ||
| 39 | + })); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments