diff --git a/lib/module.js b/lib/module.js index 09c4b85b9e5599..a46adcb98ccbd1 100644 --- a/lib/module.js +++ b/lib/module.js @@ -11,6 +11,7 @@ const path = require('path'); const internalModuleReadFile = process.binding('fs').internalModuleReadFile; const internalModuleStat = process.binding('fs').internalModuleStat; const preserveSymlinks = !!process.binding('config').preserveSymlinks; +const adjacentNodeModules = !!process.binding('config').adjacentNodeModules; // If obj.hasOwnProperty has been overridden, then calling // obj.hasOwnProperty(prop) will break. @@ -98,15 +99,15 @@ function readPackage(requestPath) { return pkg; } -function tryPackage(requestPath, exts, isMain) { +function tryPackage(requestPath, exts) { var pkg = readPackage(requestPath); if (!pkg) return false; var filename = path.resolve(requestPath, pkg); - return tryFile(filename, isMain) || - tryExtensions(filename, exts, isMain) || - tryExtensions(path.resolve(filename, 'index'), exts, isMain); + return tryFile(filename) || + tryExtensions(filename, exts) || + tryExtensions(path.resolve(filename, 'index'), exts); } // In order to minimize unnecessary lstat() calls, @@ -118,9 +119,9 @@ const realpathCache = new Map(); // if using --preserve-symlinks and isMain is false, // keep symlinks intact, otherwise resolve to the // absolute realpath. -function tryFile(requestPath, isMain) { +function tryFile(requestPath) { const rc = stat(requestPath); - if (preserveSymlinks && !isMain) { + if (preserveSymlinks) { return rc === 0 && path.resolve(requestPath); } return rc === 0 && toRealPath(requestPath); @@ -133,9 +134,9 @@ function toRealPath(requestPath) { } // given a path check a the file exists with any of the set extensions -function tryExtensions(p, exts, isMain) { +function tryExtensions(p, exts) { for (var i = 0; i < exts.length; i++) { - const filename = tryFile(p + exts[i], isMain); + const filename = tryFile(p + exts[i]); if (filename) { return filename; @@ -145,7 +146,7 @@ function tryExtensions(p, exts, isMain) { } var warned = false; -Module._findPath = function(request, paths, isMain) { +Module._findPath = function(request, paths) { if (path.isAbsolute(request)) { paths = ['']; } else if (!paths || paths.length === 0) { @@ -172,7 +173,7 @@ Module._findPath = function(request, paths, isMain) { const rc = stat(basePath); if (!trailingSlash) { if (rc === 0) { // File. - if (preserveSymlinks && !isMain) { + if (preserveSymlinks) { filename = path.resolve(basePath); } else { filename = toRealPath(basePath); @@ -180,28 +181,28 @@ Module._findPath = function(request, paths, isMain) { } else if (rc === 1) { // Directory. if (exts === undefined) exts = Object.keys(Module._extensions); - filename = tryPackage(basePath, exts, isMain); + filename = tryPackage(basePath, exts); } if (!filename) { // try it with each of the extensions if (exts === undefined) exts = Object.keys(Module._extensions); - filename = tryExtensions(basePath, exts, isMain); + filename = tryExtensions(basePath, exts); } } if (!filename && rc === 1) { // Directory. if (exts === undefined) exts = Object.keys(Module._extensions); - filename = tryPackage(basePath, exts, isMain); + filename = tryPackage(basePath, exts); } if (!filename && rc === 1) { // Directory. // try it with each of the extensions at "index" if (exts === undefined) exts = Object.keys(Module._extensions); - filename = tryExtensions(path.resolve(basePath, 'index'), exts, isMain); + filename = tryExtensions(path.resolve(basePath, 'index'), exts); } if (filename) { @@ -253,9 +254,24 @@ if (process.platform === 'win32') { // Use colon as an extra condition since we can get node_modules // path for dirver root like 'C:\node_modules' and don't need to // parse driver name. - if (code === 92/*\*/ || code === 47/*/*/ || code === 58/*:*/) { - if (p !== nmLen) - paths.push(from.slice(0, last) + '\\node_modules'); + if (code === 92/*\*/ || code === 47/*/*/ || code === 58/*:*/ || + (adjacentNodeModules && code === 43/*+*/)) { + if (p !== nmLen) { + var parent = from.slice(0, last); + paths.push(parent + '\\node_modules'); + + if (adjacentNodeModules) { + paths.push(parent + '+node_modules'); + if (code === 43/*+*/) { + while (i > 1) { + const code = from.charCodeAt(--i); + if (code === 92/*\*/ || code === 47/*/*/ || code === 58/*:*/) { + break; + } + } + } + } + } last = i; p = 0; } else if (p !== -1) { @@ -267,6 +283,9 @@ if (process.platform === 'win32') { } } + // superfluous "/.node_modules" + if (adjacentNodeModules) paths.pop(); + return paths; }; } else { // posix @@ -287,9 +306,22 @@ if (process.platform === 'win32') { var last = from.length; for (var i = from.length - 1; i >= 0; --i) { const code = from.charCodeAt(i); - if (code === 47/*/*/) { - if (p !== nmLen) - paths.push(from.slice(0, last) + '/node_modules'); + if (code === 47/*/*/ || (adjacentNodeModules && code === 43/*+*/)) { + if (p !== nmLen) { + var parent = from.slice(0, last); + paths.push(parent + '/node_modules'); + + if (adjacentNodeModules) { + paths.push(parent + '+node_modules'); + if (code === 43/*+*/) { + while (i > 1) { + if (from.charCodeAt(--i) === 47/*/*/) { + break; + } + } + } + } + } last = i; p = 0; } else if (p !== -1) { @@ -402,6 +434,24 @@ Module._resolveLookupPaths = function(request, parent) { return [id, [path.dirname(parent.filename)]]; }; +// when preserving symlinks, if the main file is a file symlink then +// its target is simply read and resolved, potentially relative to the +// symlink's dirname. in all other cases the original path is preserved. +Module._resolveMainRequest = function(request) { + if(!preserveSymlinks) { + return request; + } + + request = Module._resolveFilename(request, null); + if(!fs.lstatSync(request).isSymbolicLink()) { + return request; + } + + const targetPath = fs.readlinkSync(request); + return path.isAbsolute(targetPath) + ? targetPath + : path.resolve(path.dirname(request), targetPath); +} // Check the cache for the requested file. // 1. If a module already exists in the cache: return its exports object. @@ -415,14 +465,23 @@ Module._load = function(request, parent, isMain) { debug('Module._load REQUEST %s parent: %s', request, parent.id); } - var filename = Module._resolveFilename(request, parent, isMain); + var requestPath = isMain ? Module._resolveMainRequest(request) : request; + var filename = Module._resolveFilename(requestPath, parent); + var doesNonInternalExist = NativeModule.nonInternalExists(filename); + + // when supporting symlinks, always cache by realpath to fix + // memory bloat and addon crashing. module's __dirname will be that + // of first requestPath that require()d it, which could be a symlink. + var cachePath = preserveSymlinks && !doesNonInternalExist + ? toRealPath(filename) + : filename; - var cachedModule = Module._cache[filename]; + var cachedModule = Module._cache[cachePath]; if (cachedModule) { return cachedModule.exports; } - if (NativeModule.nonInternalExists(filename)) { + if (doesNonInternalExist) { debug('load native module %s', request); return NativeModule.require(filename); } @@ -434,26 +493,26 @@ Module._load = function(request, parent, isMain) { module.id = '.'; } - Module._cache[filename] = module; + Module._cache[cachePath] = module; - tryModuleLoad(module, filename); + tryModuleLoad(module, filename, cachePath); return module.exports; }; -function tryModuleLoad(module, filename) { +function tryModuleLoad(module, filename, cachePath) { var threw = true; try { module.load(filename); threw = false; } finally { if (threw) { - delete Module._cache[filename]; + delete Module._cache[cachePath]; } } } -Module._resolveFilename = function(request, parent, isMain) { +Module._resolveFilename = function(request, parent) { if (NativeModule.nonInternalExists(request)) { return request; } @@ -465,7 +524,7 @@ Module._resolveFilename = function(request, parent, isMain) { // look up the filename first, since that's the cache key. debug('looking for %j in %j', id, paths); - var filename = Module._findPath(request, paths, isMain); + var filename = Module._findPath(request, paths); if (!filename) { var err = new Error("Cannot find module '" + request + "'"); err.code = 'MODULE_NOT_FOUND'; diff --git a/src/node.cc b/src/node.cc index e87bddcb6ff2cc..c827dd9b063ea1 100644 --- a/src/node.cc +++ b/src/node.cc @@ -186,6 +186,7 @@ bool trace_warnings = false; // Used in node_config.cc to set a constant on process.binding('config') // that is used by lib/module.js bool config_preserve_symlinks = false; +bool config_adjacent_node_modules = false; bool v8_initialized = false; @@ -4243,6 +4244,15 @@ void Init(int* argc, config_preserve_symlinks = (*preserve_symlinks == '1'); } + if (auto adjacent_nm = secure_getenv("NODE_ADJACENT_NODE_MODULES")) { + config_adjacent_node_modules = (*adjacent_nm == '1'); + } + if (auto support_symlinks = secure_getenv("NODE_SUPPORT_SYMLINKS")) { + config_preserve_symlinks = (*support_symlinks == '1'); + config_adjacent_node_modules = (*support_symlinks == '1'); + } + + // Parse a few arguments which are specific to Node. int v8_argc; const char** v8_argv; diff --git a/src/node_config.cc b/src/node_config.cc index 401345f6a608be..39cb1badee5c14 100644 --- a/src/node_config.cc +++ b/src/node_config.cc @@ -44,6 +44,9 @@ void InitConfig(Local target, if (config_preserve_symlinks) READONLY_BOOLEAN_PROPERTY("preserveSymlinks"); + + if (config_adjacent_node_modules) + READONLY_BOOLEAN_PROPERTY("adjacentNodeModules"); } // InitConfig } // namespace node diff --git a/src/node_internals.h b/src/node_internals.h index 0a65be7642ff2a..de355d9ababf43 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -40,6 +40,7 @@ extern const char* openssl_config; // Used in node_config.cc to set a constant on process.binding('config') // that is used by lib/module.js extern bool config_preserve_symlinks; +extern bool config_adjacent_node_modules; // Tells whether it is safe to call v8::Isolate::GetCurrent(). extern bool v8_initialized;