Skip to content

Commit

Permalink
bootstrap: use different scripts to setup different configurations
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
joyeecheung committed Dec 14, 2019
1 parent e490b2c commit a36cad6
Show file tree
Hide file tree
Showing 14 changed files with 565 additions and 544 deletions.
117 changes: 18 additions & 99 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();

Expand All @@ -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
{
Expand All @@ -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');
Expand All @@ -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
Expand Down Expand Up @@ -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',
Expand Down
18 changes: 1 addition & 17 deletions lib/internal/bootstrap/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
18 changes: 18 additions & 0 deletions lib/internal/bootstrap/switches/does_not_own_process_state.js
Original file line number Diff line number Diff line change
@@ -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 -----
96 changes: 96 additions & 0 deletions lib/internal/bootstrap/switches/does_own_process_state.js
Original file line number Diff line number Diff line change
@@ -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)
};
}
Loading

0 comments on commit a36cad6

Please sign in to comment.