diff options
author | Joyee Cheung <joyeec9h3@gmail.com> | 2019-12-09 05:53:02 +0300 |
---|---|---|
committer | Joyee Cheung <joyeec9h3@gmail.com> | 2019-12-20 17:10:13 +0300 |
commit | c63d511c137130030c263fb98d0d261105244c08 (patch) | |
tree | ffda0e6de496ccb88080a5499ba55a2e28241ca2 /lib | |
parent | 277f3436890fac87fe6b762abc00f59d36a9ec6f (diff) |
bootstrap: use different scripts to setup different configurations
This patch splits the handling of `isMainThread` and
`ownsProcessState` from conditionals in
`lib/internal/bootstrap/node.js` into different scripts under
`lib/internal/bootstrap/switches/`, and call them accordingly
from C++ after `node.js` is run.
This:
- Creates a common denominator of the main thread and the worker
thread bootstrap that can be snapshotted and shared by
both.
- Makes it possible to override the configurations on-the-fly.
PR-URL: https://github.com/nodejs/node/pull/30862
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/internal/bootstrap/node.js | 117 | ||||
-rw-r--r-- | lib/internal/bootstrap/pre_execution.js | 18 | ||||
-rw-r--r-- | lib/internal/bootstrap/switches/does_not_own_process_state.js | 18 | ||||
-rw-r--r-- | lib/internal/bootstrap/switches/does_own_process_state.js | 96 | ||||
-rw-r--r-- | lib/internal/bootstrap/switches/is_main_thread.js | 263 | ||||
-rw-r--r-- | lib/internal/bootstrap/switches/is_not_main_thread.js | 68 | ||||
-rw-r--r-- | lib/internal/process/main_thread_only.js | 173 | ||||
-rw-r--r-- | lib/internal/process/signal.js | 49 | ||||
-rw-r--r-- | lib/internal/process/stdio.js | 206 | ||||
-rw-r--r-- | lib/internal/process/worker_thread_only.js | 36 |
10 files changed, 514 insertions, 530 deletions
diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index c586ddfd5e6..bb9a2b177c4 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -3,9 +3,7 @@ // This file is invoked by `node::RunBootstrapping()` in `src/node.cc`, and is // responsible for setting up node.js core before executing main scripts // under `lib/internal/main/`. -// This file is currently run to bootstrap both the main thread and the worker -// threads. Some setups are conditional, controlled with isMainThread and -// ownsProcessState. +// // This file is expected not to perform any asynchronous operations itself // when being executed - those should be done in either // `lib/internal/bootstrap/pre_execution.js` or in main scripts. The majority @@ -22,16 +20,21 @@ // module loaders, including `process.binding()`, `process._linkedBinding()`, // `internalBinding()` and `NativeModule`. // -// After this file is run, one of the main scripts under `lib/internal/main/` -// will be selected by C++ to start the actual execution. The main scripts may -// run additional setups exported by `lib/internal/bootstrap/pre_execution.js`, -// depending on the execution mode. +// This file is run to bootstrap both the main thread and the worker threads. +// After this file is run, certain properties are setup according to the +// configuration of the Node.js instance using the files in +// `lib/internal/bootstrap/switches/`. +// +// Then, depending on how the Node.js instance is launched, one of the main +// scripts in `lib/internal/main` will be selected by C++ to start the actual +// execution. They may run additional setups exported by +// `lib/internal/bootstrap/pre_execution.js` depending on the runtime states. 'use strict'; // This file is compiled as if it's wrapped in a function with arguments // passed by node::RunBootstrapping() -/* global process, require, internalBinding, isMainThread, ownsProcessState */ +/* global process, require, internalBinding */ setupPrepareStackTrace(); @@ -54,48 +57,12 @@ setupBuffer(); process.domain = null; process._exiting = false; -// Bootstrappers for all threads, including worker threads and main thread -const perThreadSetup = require('internal/process/per_thread'); -// Bootstrappers for the main thread only -let mainThreadSetup; -// Bootstrappers for the worker threads only -let workerThreadSetup; -if (ownsProcessState) { - mainThreadSetup = require( - 'internal/process/main_thread_only' - ); -} else { - workerThreadSetup = require( - 'internal/process/worker_thread_only' - ); -} - // process.config is serialized config.gypi process.config = JSONParse(internalBinding('native_module').config); +// Bootstrappers for all threads, including worker threads and main thread +const perThreadSetup = require('internal/process/per_thread'); const rawMethods = internalBinding('process_methods'); -// Set up methods and events on the process object for the main thread -if (isMainThread) { - process.abort = rawMethods.abort; - const wrapped = mainThreadSetup.wrapProcessMethods(rawMethods); - process.umask = wrapped.umask; - process.chdir = wrapped.chdir; - process.cwd = wrapped.cwd; - - // TODO(joyeecheung): deprecate and remove these underscore methods - process._debugProcess = rawMethods._debugProcess; - process._debugEnd = rawMethods._debugEnd; - process._startProfilerIdleNotifier = - rawMethods._startProfilerIdleNotifier; - process._stopProfilerIdleNotifier = rawMethods._stopProfilerIdleNotifier; -} else { - const wrapped = workerThreadSetup.wrapProcessMethods(rawMethods); - - process.abort = workerThreadSetup.unavailable('process.abort()'); - process.chdir = workerThreadSetup.unavailable('process.chdir()'); - process.umask = wrapped.umask; - process.cwd = rawMethods.cwd; -} // Set up methods on the process object for all threads { @@ -119,6 +86,11 @@ if (isMainThread) { process.memoryUsage = wrapped.memoryUsage; process.kill = wrapped.kill; process.exit = wrapped.exit; + + process.openStdin = function() { + process.stdin.resume(); + return process.stdin; + }; } const credentials = internalBinding('credentials'); @@ -128,34 +100,6 @@ if (credentials.implementsPosixCredentials) { process.getgid = credentials.getgid; process.getegid = credentials.getegid; process.getgroups = credentials.getgroups; - - if (ownsProcessState) { - const wrapped = mainThreadSetup.wrapPosixCredentialSetters(credentials); - process.initgroups = wrapped.initgroups; - process.setgroups = wrapped.setgroups; - process.setegid = wrapped.setegid; - process.seteuid = wrapped.seteuid; - process.setgid = wrapped.setgid; - process.setuid = wrapped.setuid; - } else { - process.initgroups = - workerThreadSetup.unavailable('process.initgroups()'); - process.setgroups = workerThreadSetup.unavailable('process.setgroups()'); - process.setegid = workerThreadSetup.unavailable('process.setegid()'); - process.seteuid = workerThreadSetup.unavailable('process.seteuid()'); - process.setgid = workerThreadSetup.unavailable('process.setgid()'); - process.setuid = workerThreadSetup.unavailable('process.setuid()'); - } -} - -if (isMainThread) { - const { getStdout, getStdin, getStderr } = - require('internal/process/stdio').getMainThreadStdio(); - setupProcessStdio(getStdout, getStdin, getStderr); -} else { - const { getStdout, getStdin, getStderr } = - workerThreadSetup.createStdioGetters(); - setupProcessStdio(getStdout, getStdin, getStderr); } // Setup the callbacks that node::AsyncWrap will call when there are hooks to @@ -343,31 +287,6 @@ function setupProcessObject() { }); } -function setupProcessStdio(getStdout, getStdin, getStderr) { - ObjectDefineProperty(process, 'stdout', { - configurable: true, - enumerable: true, - get: getStdout - }); - - ObjectDefineProperty(process, 'stderr', { - configurable: true, - enumerable: true, - get: getStderr - }); - - ObjectDefineProperty(process, 'stdin', { - configurable: true, - enumerable: true, - get: getStdin - }); - - process.openStdin = function() { - process.stdin.resume(); - return process.stdin; - }; -} - function setupGlobalProxy() { ObjectDefineProperty(global, SymbolToStringTag, { value: 'global', diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index 40dabdda504..7af4be660fc 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -36,9 +36,6 @@ function prepareMainThreadExecution(expandArgv1 = false) { setupDebugEnv(); - // Only main thread receives signals. - setupSignalHandlers(); - // Process initial diagnostic reporting configuration, if present. initializeReport(); initializeReportSignalHandlers(); // Main-thread-only. @@ -174,20 +171,7 @@ function setupDebugEnv() { } } -function setupSignalHandlers() { - const { - createSignalHandlers - } = require('internal/process/main_thread_only'); - const { - startListeningIfSignal, - stopListeningIfSignal - } = createSignalHandlers(); - process.on('newListener', startListeningIfSignal); - process.on('removeListener', stopListeningIfSignal); -} - -// This has to be called after both initializeReport() and -// setupSignalHandlers() are called +// This has to be called after initializeReport() is called function initializeReportSignalHandlers() { if (!getOptionValue('--experimental-report')) { return; diff --git a/lib/internal/bootstrap/switches/does_not_own_process_state.js b/lib/internal/bootstrap/switches/does_not_own_process_state.js new file mode 100644 index 00000000000..1ec449b34a9 --- /dev/null +++ b/lib/internal/bootstrap/switches/does_not_own_process_state.js @@ -0,0 +1,18 @@ +'use strict'; + +const credentials = internalBinding('credentials'); + +if (credentials.implementsPosixCredentials) { + // TODO: this should be detached from ERR_WORKER_UNSUPPORTED_OPERATION + const { unavailable } = require('internal/process/worker_thread_only'); + + process.initgroups = unavailable('process.initgroups()'); + process.setgroups = unavailable('process.setgroups()'); + process.setegid = unavailable('process.setegid()'); + process.seteuid = unavailable('process.seteuid()'); + process.setgid = unavailable('process.setgid()'); + process.setuid = unavailable('process.setuid()'); +} + +// ---- keep the attachment of the wrappers above so that it's easier to ---- +// ---- compare the setups side-by-side ----- diff --git a/lib/internal/bootstrap/switches/does_own_process_state.js b/lib/internal/bootstrap/switches/does_own_process_state.js new file mode 100644 index 00000000000..88f77520720 --- /dev/null +++ b/lib/internal/bootstrap/switches/does_own_process_state.js @@ -0,0 +1,96 @@ +'use strict'; + +const credentials = internalBinding('credentials'); + +if (credentials.implementsPosixCredentials) { + const wrapped = wrapPosixCredentialSetters(credentials); + + process.initgroups = wrapped.initgroups; + process.setgroups = wrapped.setgroups; + process.setegid = wrapped.setegid; + process.seteuid = wrapped.seteuid; + process.setgid = wrapped.setgid; + process.setuid = wrapped.setuid; +} + +// ---- keep the attachment of the wrappers above so that it's easier to ---- +// ---- compare the setups side-by-side ----- + +function wrapPosixCredentialSetters(credentials) { + const { + ArrayIsArray, + } = primordials; + const { + codes: { + ERR_INVALID_ARG_TYPE, + ERR_UNKNOWN_CREDENTIAL + } + } = require('internal/errors'); + const { + validateUint32 + } = require('internal/validators'); + + const { + initgroups: _initgroups, + setgroups: _setgroups, + setegid: _setegid, + seteuid: _seteuid, + setgid: _setgid, + setuid: _setuid + } = credentials; + + 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 setgroups(groups) { + if (!ArrayIsArray(groups)) { + throw new ERR_INVALID_ARG_TYPE('groups', 'Array', groups); + } + for (let 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 wrapIdSetter(type, method) { + return function(id) { + 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); + } + } + + return { + initgroups, + setgroups, + setegid: wrapIdSetter('Group', _setegid), + seteuid: wrapIdSetter('User', _seteuid), + setgid: wrapIdSetter('Group', _setgid), + setuid: wrapIdSetter('User', _setuid) + }; +} diff --git a/lib/internal/bootstrap/switches/is_main_thread.js b/lib/internal/bootstrap/switches/is_main_thread.js new file mode 100644 index 00000000000..4f7ef7b6a69 --- /dev/null +++ b/lib/internal/bootstrap/switches/is_main_thread.js @@ -0,0 +1,263 @@ +'use strict'; + +const { ObjectDefineProperty } = primordials; +const rawMethods = internalBinding('process_methods'); + +process.abort = rawMethods.abort; +process.umask = wrappedUmask; +process.chdir = wrappedChdir; +process.cwd = wrappedCwd; + +// TODO(joyeecheung): deprecate and remove these underscore methods +process._debugProcess = rawMethods._debugProcess; +process._debugEnd = rawMethods._debugEnd; +process._startProfilerIdleNotifier = rawMethods._startProfilerIdleNotifier; +process._stopProfilerIdleNotifier = rawMethods._stopProfilerIdleNotifier; + +function defineStream(name, getter) { + ObjectDefineProperty(process, name, { + configurable: true, + enumerable: true, + get: getter + }); +} + +defineStream('stdout', getStdout); +defineStream('stdin', getStdin); +defineStream('stderr', getStderr); + +// Worker threads don't receive signals. +const { + startListeningIfSignal, + stopListeningIfSignal +} = require('internal/process/signal'); +process.on('newListener', startListeningIfSignal); +process.on('removeListener', stopListeningIfSignal); + +// ---- keep the attachment of the wrappers above so that it's easier to ---- +// ---- compare the setups side-by-side ----- + +const { guessHandleType } = internalBinding('util'); +const { + parseMode, + validateString +} = require('internal/validators'); + +// Cache the working directory to prevent lots of lookups. If the working +// directory is changed by `chdir`, it'll be updated. +let cachedCwd = ''; + +function wrappedChdir(directory) { + validateString(directory, 'directory'); + rawMethods.chdir(directory); + // Mark cache that it requires an update. + cachedCwd = ''; +} + +function wrappedUmask(mask) { + if (mask !== undefined) { + mask = parseMode(mask, 'mask'); + } + return rawMethods.umask(mask); +} + +function wrappedCwd() { + if (cachedCwd === '') + cachedCwd = rawMethods.cwd(); + return cachedCwd; +} + +function createWritableStdioStream(fd) { + let stream; + // Note stream._type is used for test-module-load-list.js + switch (guessHandleType(fd)) { + case 'TTY': + const tty = require('tty'); + stream = new tty.WriteStream(fd); + stream._type = 'tty'; + break; + + case 'FILE': + const SyncWriteStream = require('internal/fs/sync_write_stream'); + stream = new SyncWriteStream(fd, { autoClose: false }); + stream._type = 'fs'; + break; + + case 'PIPE': + case 'TCP': + const net = require('net'); + + // If fd is already being used for the IPC channel, libuv will return + // an error when trying to use it again. In that case, create the socket + // using the existing handle instead of the fd. + if (process.channel && process.channel.fd === fd) { + stream = new net.Socket({ + handle: process.channel, + readable: false, + writable: true + }); + } else { + stream = new net.Socket({ + fd, + readable: false, + writable: true + }); + } + + stream._type = 'pipe'; + break; + + default: + // Provide a dummy black-hole output for e.g. non-console + // Windows applications. + const { Writable } = require('stream'); + stream = new Writable({ + write(buf, enc, cb) { + cb(); + } + }); + } + + // For supporting legacy API we put the FD here. + stream.fd = fd; + + stream._isStdio = true; + + return stream; +} + +function dummyDestroy(err, cb) { + cb(err); + + // We need to emit 'close' anyway so that the closing + // of the stream is observable. We just make sure we + // are not going to do it twice. + // The 'close' event is needed so that finished and + // pipeline work correctly. + if (!this._writableState.emitClose) { + process.nextTick(() => { + this.emit('close'); + }); + } +} + +let stdin; +let stdout; +let stderr; + +function getStdout() { + if (stdout) return stdout; + stdout = createWritableStdioStream(1); + stdout.destroySoon = stdout.destroy; + // Override _destroy so that the fd is never actually closed. + stdout._destroy = dummyDestroy; + if (stdout.isTTY) { + process.on('SIGWINCH', () => stdout._refreshSize()); + } + return stdout; +} + +function getStderr() { + if (stderr) return stderr; + stderr = createWritableStdioStream(2); + stderr.destroySoon = stderr.destroy; + // Override _destroy so that the fd is never actually closed. + stderr._destroy = dummyDestroy; + if (stderr.isTTY) { + process.on('SIGWINCH', () => stderr._refreshSize()); + } + return stderr; +} + +function getStdin() { + if (stdin) return stdin; + const fd = 0; + + switch (guessHandleType(fd)) { + case 'TTY': + const tty = require('tty'); + stdin = new tty.ReadStream(fd, { + highWaterMark: 0, + readable: true, + writable: false + }); + break; + + case 'FILE': + const fs = require('fs'); + stdin = new fs.ReadStream(null, { fd: fd, autoClose: false }); + break; + + case 'PIPE': + case 'TCP': + const net = require('net'); + + // It could be that process has been started with an IPC channel + // sitting on fd=0, in such case the pipe for this fd is already + // present and creating a new one will lead to the assertion failure + // in libuv. + if (process.channel && process.channel.fd === fd) { + stdin = new net.Socket({ + handle: process.channel, + readable: true, + writable: false, + manualStart: true + }); + } else { + stdin = new net.Socket({ + fd: fd, + readable: true, + writable: false, + manualStart: true + }); + } + // Make sure the stdin can't be `.end()`-ed + stdin._writableState.ended = true; + break; + + default: + // Provide a dummy contentless input for e.g. non-console + // Windows applications. + const { Readable } = require('stream'); + stdin = new Readable({ read() {} }); + stdin.push(null); + } + + // For supporting legacy API we put the FD here. + stdin.fd = fd; + + // `stdin` starts out life in a paused state, but node doesn't + // know yet. Explicitly to readStop() it to put it in the + // not-reading state. + if (stdin._handle && stdin._handle.readStop) { + stdin._handle.reading = false; + stdin._readableState.reading = false; + stdin._handle.readStop(); + } + + // If the user calls stdin.pause(), then we need to stop reading + // once the stream implementation does so (one nextTick later), + // so that the process can close down. + stdin.on('pause', () => { + process.nextTick(onpause); + }); + + function onpause() { + if (!stdin._handle) + return; + if (stdin._handle.reading && !stdin.readableFlowing) { + stdin._readableState.reading = false; + stdin._handle.reading = false; + stdin._handle.readStop(); + } + } + + return stdin; +} + +// Used by internal tests. +rawMethods.resetStdioForTesting = function() { + stdin = undefined; + stdout = undefined; + stderr = undefined; +}; diff --git a/lib/internal/bootstrap/switches/is_not_main_thread.js b/lib/internal/bootstrap/switches/is_not_main_thread.js new file mode 100644 index 00000000000..33e98d387cf --- /dev/null +++ b/lib/internal/bootstrap/switches/is_not_main_thread.js @@ -0,0 +1,68 @@ +'use strict'; + +const { ObjectDefineProperty } = primordials; +const rawMethods = internalBinding('process_methods'); +const { + unavailable +} = require('internal/process/worker_thread_only'); + +process.abort = unavailable('process.abort()'); +process.chdir = unavailable('process.chdir()'); +process.umask = wrappedUmask; +process.cwd = rawMethods.cwd; + +delete process._debugProcess; +delete process._debugEnd; +delete process._startProfilerIdleNotifier; +delete process._stopProfilerIdleNotifier; + +function defineStream(name, getter) { + ObjectDefineProperty(process, name, { + configurable: true, + enumerable: true, + get: getter + }); +} + +defineStream('stdout', getStdout); +defineStream('stdin', getStdin); +defineStream('stderr', getStderr); + +// Worker threads don't receive signals. +const { + startListeningIfSignal, + stopListeningIfSignal +} = require('internal/process/signal'); +process.removeListener('newListener', startListeningIfSignal); +process.removeListener('removeListener', stopListeningIfSignal); + +// ---- keep the attachment of the wrappers above so that it's easier to ---- +// ---- compare the setups side-by-side ----- + +const { + createWorkerStdio +} = require('internal/worker/io'); +const { + codes: { ERR_WORKER_UNSUPPORTED_OPERATION } +} = require('internal/errors'); + +let workerStdio; +function lazyWorkerStdio() { + if (!workerStdio) workerStdio = createWorkerStdio(); + return workerStdio; +} + +function getStdout() { return lazyWorkerStdio().stdout; } + +function getStderr() { return lazyWorkerStdio().stderr; } + +function getStdin() { return lazyWorkerStdio().stdin; } + +function wrappedUmask(mask) { + // process.umask() is a read-only operation in workers. + if (mask !== undefined) { + throw new ERR_WORKER_UNSUPPORTED_OPERATION('Setting process.umask()'); + } + + return rawMethods.umask(mask); +} diff --git a/lib/internal/process/main_thread_only.js b/lib/internal/process/main_thread_only.js deleted file mode 100644 index ab56500744b..00000000000 --- a/lib/internal/process/main_thread_only.js +++ /dev/null @@ -1,173 +0,0 @@ -'use strict'; - -// This file contains process bootstrappers that can only be -// run in the main thread - -const { - ArrayIsArray, -} = primordials; - -const { - errnoException, - codes: { - ERR_INVALID_ARG_TYPE, - ERR_UNKNOWN_CREDENTIAL - } -} = require('internal/errors'); -const { - parseMode, - validateUint32, - validateString -} = require('internal/validators'); - -const { signals } = internalBinding('constants').os; - -// The execution of this function itself should not cause any side effects. -function wrapProcessMethods(binding) { - // Cache the working directory to prevent lots of lookups. If the working - // directory is changed by `chdir`, it'll be updated. - let cachedCwd = ''; - - function chdir(directory) { - validateString(directory, 'directory'); - binding.chdir(directory); - // Mark cache that it requires an update. - cachedCwd = ''; - } - - function umask(mask) { - if (mask !== undefined) { - mask = parseMode(mask, 'mask'); - } - return binding.umask(mask); - } - - function cwd() { - if (cachedCwd === '') - cachedCwd = binding.cwd(); - return cachedCwd; - } - - return { - chdir, - umask, - cwd - }; -} - -function wrapPosixCredentialSetters(credentials) { - const { - initgroups: _initgroups, - setgroups: _setgroups, - setegid: _setegid, - seteuid: _seteuid, - setgid: _setgid, - setuid: _setuid - } = credentials; - - 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 setgroups(groups) { - if (!ArrayIsArray(groups)) { - throw new ERR_INVALID_ARG_TYPE('groups', 'Array', groups); - } - for (let 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 wrapIdSetter(type, method) { - return function(id) { - 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); - } - } - - return { - initgroups, - setgroups, - setegid: wrapIdSetter('Group', _setegid), - seteuid: wrapIdSetter('User', _seteuid), - setgid: wrapIdSetter('Group', _setgid), - setuid: wrapIdSetter('User', _setuid) - }; -} - -let Signal; -function isSignal(event) { - return typeof event === 'string' && signals[event] !== undefined; -} - -// Worker threads don't receive signals. -function createSignalHandlers() { - const signalWraps = new Map(); - - // Detect presence of a listener for the special signal types - function startListeningIfSignal(type) { - if (isSignal(type) && !signalWraps.has(type)) { - if (Signal === undefined) - Signal = internalBinding('signal_wrap').Signal; - const wrap = new Signal(); - - wrap.unref(); - - wrap.onsignal = process.emit.bind(process, type, type); - - const signum = signals[type]; - const err = wrap.start(signum); - if (err) { - wrap.close(); - throw errnoException(err, 'uv_signal_start'); - } - - signalWraps.set(type, wrap); - } - } - - function stopListeningIfSignal(type) { - const wrap = signalWraps.get(type); - if (wrap !== undefined && process.listenerCount(type) === 0) { - wrap.close(); - signalWraps.delete(type); - } - } - - return { - startListeningIfSignal, - stopListeningIfSignal - }; -} - -module.exports = { - wrapProcessMethods, - createSignalHandlers, - wrapPosixCredentialSetters -}; diff --git a/lib/internal/process/signal.js b/lib/internal/process/signal.js new file mode 100644 index 00000000000..6929c73c51f --- /dev/null +++ b/lib/internal/process/signal.js @@ -0,0 +1,49 @@ +'use strict'; + +const { + errnoException, +} = require('internal/errors'); + +const { signals } = internalBinding('constants').os; + +let Signal; +const signalWraps = new Map(); + +function isSignal(event) { + return typeof event === 'string' && signals[event] !== undefined; +} + +// Detect presence of a listener for the special signal types +function startListeningIfSignal(type) { + if (isSignal(type) && !signalWraps.has(type)) { + if (Signal === undefined) + Signal = internalBinding('signal_wrap').Signal; + const wrap = new Signal(); + + wrap.unref(); + + wrap.onsignal = process.emit.bind(process, type, type); + + const signum = signals[type]; + const err = wrap.start(signum); + if (err) { + wrap.close(); + throw errnoException(err, 'uv_signal_start'); + } + + signalWraps.set(type, wrap); + } +} + +function stopListeningIfSignal(type) { + const wrap = signalWraps.get(type); + if (wrap !== undefined && process.listenerCount(type) === 0) { + wrap.close(); + signalWraps.delete(type); + } +} + +module.exports = { + startListeningIfSignal, + stopListeningIfSignal +}; diff --git a/lib/internal/process/stdio.js b/lib/internal/process/stdio.js deleted file mode 100644 index 2491f14cdf0..00000000000 --- a/lib/internal/process/stdio.js +++ /dev/null @@ -1,206 +0,0 @@ -'use strict'; - -const { guessHandleType } = internalBinding('util'); -exports.getMainThreadStdio = getMainThreadStdio; - -function dummyDestroy(err, cb) { - cb(err); - - // We need to emit 'close' anyway so that the closing - // of the stream is observable. We just make sure we - // are not going to do it twice. - // The 'close' event is needed so that finished and - // pipeline work correctly. - if (!this._writableState.emitClose) { - process.nextTick(() => { - this.emit('close'); - }); - } -} - -function getMainThreadStdio() { - let stdin; - let stdout; - let stderr; - - function getStdout() { - if (stdout) return stdout; - stdout = createWritableStdioStream(1); - stdout.destroySoon = stdout.destroy; - // Override _destroy so that the fd is never actually closed. - stdout._destroy = dummyDestroy; - if (stdout.isTTY) { - process.on('SIGWINCH', () => stdout._refreshSize()); - } - return stdout; - } - - function getStderr() { - if (stderr) return stderr; - stderr = createWritableStdioStream(2); - stderr.destroySoon = stderr.destroy; - // Override _destroy so that the fd is never actually closed. - stderr._destroy = dummyDestroy; - if (stderr.isTTY) { - process.on('SIGWINCH', () => stderr._refreshSize()); - } - return stderr; - } - - function getStdin() { - if (stdin) return stdin; - const fd = 0; - - switch (guessHandleType(fd)) { - case 'TTY': - const tty = require('tty'); - stdin = new tty.ReadStream(fd, { - highWaterMark: 0, - readable: true, - writable: false - }); - break; - - case 'FILE': - const fs = require('fs'); - stdin = new fs.ReadStream(null, { fd: fd, autoClose: false }); - break; - - case 'PIPE': - case 'TCP': - const net = require('net'); - - // It could be that process has been started with an IPC channel - // sitting on fd=0, in such case the pipe for this fd is already - // present and creating a new one will lead to the assertion failure - // in libuv. - if (process.channel && process.channel.fd === fd) { - stdin = new net.Socket({ - handle: process.channel, - readable: true, - writable: false, - manualStart: true - }); - } else { - stdin = new net.Socket({ - fd: fd, - readable: true, - writable: false, - manualStart: true - }); - } - // Make sure the stdin can't be `.end()`-ed - stdin._writableState.ended = true; - break; - - default: - // Provide a dummy contentless input for e.g. non-console - // Windows applications. - const { Readable } = require('stream'); - stdin = new Readable({ read() {} }); - stdin.push(null); - } - - // For supporting legacy API we put the FD here. - stdin.fd = fd; - - // `stdin` starts out life in a paused state, but node doesn't - // know yet. Explicitly to readStop() it to put it in the - // not-reading state. - if (stdin._handle && stdin._handle.readStop) { - stdin._handle.reading = false; - stdin._readableState.reading = false; - stdin._handle.readStop(); - } - - // If the user calls stdin.pause(), then we need to stop reading - // once the stream implementation does so (one nextTick later), - // so that the process can close down. - stdin.on('pause', () => { - process.nextTick(onpause); - }); - - function onpause() { - if (!stdin._handle) - return; - if (stdin._handle.reading && !stdin.readableFlowing) { - stdin._readableState.reading = false; - stdin._handle.reading = false; - stdin._handle.readStop(); - } - } - - return stdin; - } - - exports.resetStdioForTesting = function() { - stdin = undefined; - stdout = undefined; - stderr = undefined; - }; - - return { - getStdout, - getStderr, - getStdin - }; -} - -function createWritableStdioStream(fd) { - let stream; - // Note stream._type is used for test-module-load-list.js - switch (guessHandleType(fd)) { - case 'TTY': - const tty = require('tty'); - stream = new tty.WriteStream(fd); - stream._type = 'tty'; - break; - - case 'FILE': - const SyncWriteStream = require('internal/fs/sync_write_stream'); - stream = new SyncWriteStream(fd, { autoClose: false }); - stream._type = 'fs'; - break; - - case 'PIPE': - case 'TCP': - const net = require('net'); - - // If fd is already being used for the IPC channel, libuv will return - // an error when trying to use it again. In that case, create the socket - // using the existing handle instead of the fd. - if (process.channel && process.channel.fd === fd) { - stream = new net.Socket({ - handle: process.channel, - readable: false, - writable: true - }); - } else { - stream = new net.Socket({ - fd, - readable: false, - writable: true - }); - } - - stream._type = 'pipe'; - break; - - default: - // Provide a dummy black-hole output for e.g. non-console - // Windows applications. - const { Writable } = require('stream'); - stream = new Writable({ - write(buf, enc, cb) { - cb(); - } - }); - } - - // For supporting legacy API we put the FD here. - stream.fd = fd; - - stream._isStdio = true; - - return stream; -} diff --git a/lib/internal/process/worker_thread_only.js b/lib/internal/process/worker_thread_only.js index 920f0f77261..9d2a43b441a 100644 --- a/lib/internal/process/worker_thread_only.js +++ b/lib/internal/process/worker_thread_only.js @@ -4,41 +4,9 @@ // run in the worker thread. const { - createWorkerStdio -} = require('internal/worker/io'); - -const { codes: { ERR_WORKER_UNSUPPORTED_OPERATION } } = require('internal/errors'); -let workerStdio; -function lazyWorkerStdio() { - if (!workerStdio) workerStdio = createWorkerStdio(); - return workerStdio; -} - -function createStdioGetters() { - return { - getStdout() { return lazyWorkerStdio().stdout; }, - getStderr() { return lazyWorkerStdio().stderr; }, - getStdin() { return lazyWorkerStdio().stdin; } - }; -} - -// The execution of this function itself should not cause any side effects. -function wrapProcessMethods(binding) { - function umask(mask) { - // process.umask() is a read-only operation in workers. - if (mask !== undefined) { - throw new ERR_WORKER_UNSUPPORTED_OPERATION('Setting process.umask()'); - } - - return binding.umask(mask); - } - - return { umask }; -} - function unavailable(name) { function unavailableInWorker() { throw new ERR_WORKER_UNSUPPORTED_OPERATION(name); @@ -49,7 +17,5 @@ function unavailable(name) { } module.exports = { - createStdioGetters, - unavailable, - wrapProcessMethods + unavailable }; |