From 2a7e11ef11c73b3e4a6f58e41b8a111f15ed55d6 Mon Sep 17 00:00:00 2001 From: Joel Chen Date: Thu, 16 Mar 2017 20:19:38 -0700 Subject: [PATCH] module: check env NODE_PRELOAD for preload modules Adds a new feature that checks the environment variable NODE_PRELOAD that should be a : (or ; on windows) delimited string of modules inside the global node_modules dir to preload, the same as those that can be specified by the -r command line option except this is limited to global node_modules. Each module can be the absolute path of the module or just the name of the module under the global node_modules dir. Implements: https://github.com/nodejs/node/issues/11853 --- lib/internal/bootstrap_node.js | 34 +++++++- test/parallel/test-preload.js | 149 +++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 2 deletions(-) diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index f29f7a647a4bd3..8a200743820561 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -411,8 +411,38 @@ // Load preload modules function preloadModules() { - if (process._preload_modules) { - NativeModule.require('module')._preloadModules(process._preload_modules); + var allPreloads = process._preload_modules; + var isSetUidRoot = false; + + if (process.platform !== 'win32') { + isSetUidRoot = process.getuid() !== process.geteuid() || + process.getgid() !== process.getegid(); + } + + if (!isSetUidRoot && process.env.NODE_PRELOAD) { + const path = NativeModule.require('path'); + + const addPreload = (nmDir, preloads) => { + const addNmDir = (m) => { + return path.isAbsolute(m) ? m : path.join(nmDir, m); + }; + + if (preloads && preloads.length > 0) { + preloads = preloads.map((m) => addNmDir(m.trim())) + .filter((m) => (m.length > nmDir.length && m.startsWith(nmDir))); + + if (preloads.length > 0) { + allPreloads = (allPreloads || []).concat(preloads); + } + } + }; + + const globalNm = path.join(process.argv[0], '../../lib/node_modules/'); + addPreload(globalNm, process.env.NODE_PRELOAD.split(path.delimiter)); + } + + if (allPreloads) { + NativeModule.require('module')._preloadModules(allPreloads); } } diff --git a/test/parallel/test-preload.js b/test/parallel/test-preload.js index 8dc64cc6c60168..f25fb52054fd52 100644 --- a/test/parallel/test-preload.js +++ b/test/parallel/test-preload.js @@ -4,6 +4,7 @@ const common = require('../common'); const assert = require('assert'); const path = require('path'); const childProcess = require('child_process'); +const fs = require('fs'); // Refs: https://github.com/nodejs/node/pull/2253 if (common.isSunOS) { @@ -12,6 +13,8 @@ if (common.isSunOS) { } const nodeBinary = process.argv[0]; +const globalLibDir = path.join(nodeBinary, '../../lib'); +const globalNmDir = path.join(globalLibDir, 'node_modules'); const preloadOption = function(preloads) { let option = ''; @@ -25,12 +28,21 @@ const fixture = function(name) { return path.join(common.fixturesDir, name); }; +const globalNmFixture = function(name) { + return path.join(globalNmDir, name); +}; + const fixtureA = fixture('printA.js'); const fixtureB = fixture('printB.js'); const fixtureC = fixture('printC.js'); const fixtureD = fixture('define-global.js'); const fixtureThrows = fixture('throws_error4.js'); +const globalNmFixtureA = globalNmFixture('printA.js'); +const globalNmFixtureB = globalNmFixture('printB.js'); +const globalNmFixtureC = globalNmFixture('printC.js'); +const globalNmFixtureThrows = globalNmFixture('throws_error4.js'); + // test preloading a single module works childProcess.exec(nodeBinary + ' ' + preloadOption([fixtureA]) + ' ' + fixtureB, function(err, stdout, stderr) { @@ -146,3 +158,140 @@ childProcess.exec( assert.ok(/worker terminated with code 43/.test(stdout)); } ); + +// +// Test NODE_PRELOAD +// +let preloadTestsCount = 0; + +const copyFixturesToGlobalNm = function() { + try { + fs.mkdirSync(globalLibDir); + fs.mkdirSync(globalNmDir); + } catch (e) { + } + fs.writeFileSync(globalNmFixtureA, fs.readFileSync(fixtureA)); + fs.writeFileSync(globalNmFixtureB, fs.readFileSync(fixtureB)); + fs.writeFileSync(globalNmFixtureC, fs.readFileSync(fixtureC)); + fs.writeFileSync(globalNmFixtureThrows, fs.readFileSync(fixtureThrows)); +}; + +const cleanGlobalNmFixtures = function() { + preloadTestsCount--; + if (preloadTestsCount > 0) { + return; + } + + fs.unlinkSync(globalNmFixtureA); + fs.unlinkSync(globalNmFixtureB); + fs.unlinkSync(globalNmFixtureC); + fs.unlinkSync(globalNmFixtureThrows); + + try { + fs.rmdirSync(globalNmDir); + fs.rmdirSync(globalLibDir); + } catch (e) { + } +}; + +copyFixturesToGlobalNm(); + +const testNodePreload = function(cb) { + preloadTestsCount++; + cb(); + delete process.env.NODE_PRELOAD; +}; + +// test NODE_PRELOAD with a single module works +testNodePreload(function() { + process.env.NODE_PRELOAD = globalNmFixtureA; + childProcess.exec(nodeBinary + ' ' + fixtureB, + function(err, stdout, stderr) { + cleanGlobalNmFixtures(); + assert.ifError(err); + assert.strictEqual(stdout, 'A\nB\n'); + }); +}); + +// test NODE_PRELOAD with a relative module works +testNodePreload(function() { + process.env.NODE_PRELOAD = 'printA'; + childProcess.exec(nodeBinary + ' ' + fixtureB, + function(err, stdout, stderr) { + cleanGlobalNmFixtures(); + assert.ifError(err); + assert.strictEqual(stdout, 'A\nB\n'); + }); +}); + +// test NODE_PRELOAD with multiple modules works +testNodePreload(function() { + process.env.NODE_PRELOAD = + globalNmFixtureA + path.delimiter + globalNmFixtureB; + childProcess.exec( + nodeBinary + ' ' + fixtureC, + function(err, stdout, stderr) { + cleanGlobalNmFixtures(); + assert.ifError(err); + assert.strictEqual(stdout, 'A\nB\nC\n'); + } + ); +}); + +// test NODE_PRELOAD with multiple modules and space between delimiter works +testNodePreload(function() { + process.env.NODE_PRELOAD = + globalNmFixtureA + ' ' + path.delimiter + ' ' + globalNmFixtureB; + childProcess.exec( + nodeBinary + ' ' + fixtureC, + function(err, stdout, stderr) { + cleanGlobalNmFixtures(); + assert.ifError(err); + assert.strictEqual(stdout, 'A\nB\nC\n'); + } + ); +}); + +// test NODE_PRELOAD with a throwing module aborts +testNodePreload(function() { + process.env.NODE_PRELOAD = + globalNmFixtureA + path.delimiter + globalNmFixtureThrows; + childProcess.exec( + nodeBinary + ' ' + fixtureB, + function(err, stdout, stderr) { + cleanGlobalNmFixtures(); + if (err) { + assert.strictEqual(stdout, 'A\n'); + } else { + throw new Error('NODE_PRELOAD Preload should have failed'); + } + } + ); +}); + +// test mixing NODE_PRELOAD with -r works +testNodePreload(function() { + process.env.NODE_PRELOAD = globalNmFixtureB; + childProcess.exec( + nodeBinary + ' ' + preloadOption([fixtureA]) + ' ' + fixtureC, + function(err, stdout, stderr) { + cleanGlobalNmFixtures(); + assert.ifError(err); + assert.strictEqual(stdout, 'A\nB\nC\n'); + } + ); +}); + +// test ignoring NODE_PRELOAD modules not under global node_modules works +testNodePreload(function() { + process.env.NODE_PRELOAD = + globalNmFixtureA + ' ' + path.delimiter + ' ' + fixtureB; + childProcess.exec( + nodeBinary + ' ' + fixtureC, + function(err, stdout, stderr) { + cleanGlobalNmFixtures(); + assert.ifError(err); + assert.strictEqual(stdout, 'A\nC\n'); + } + ); +});