1'use strict'; 2 3// This files contains process bootstrappers that can be 4// run when setting up each thread, including the main 5// thread and the worker threads. 6 7const { 8 ArrayIsArray, 9 ArrayPrototypePush, 10 BigUint64Array, 11 Float64Array, 12 NumberMAX_SAFE_INTEGER, 13 ObjectDefineProperties, 14 ObjectDefineProperty, 15 ObjectFreeze, 16 ObjectGetOwnPropertyDescriptors, 17 RegExpPrototypeTest, 18 Set, 19 SetPrototype, 20 SetPrototypeHas, 21 StringPrototypeReplace, 22 Uint32Array, 23} = primordials; 24 25const { 26 errnoException, 27 codes: { 28 ERR_ASSERTION, 29 ERR_CPU_USAGE, 30 ERR_INVALID_ARG_TYPE, 31 ERR_INVALID_OPT_VALUE, 32 ERR_OUT_OF_RANGE, 33 ERR_UNKNOWN_SIGNAL 34 } 35} = require('internal/errors'); 36const format = require('internal/util/inspect').format; 37const constants = internalBinding('constants').os.signals; 38 39const { 40 handleProcessExit, 41} = require('internal/modules/esm/handle_process_exit'); 42 43function assert(x, msg) { 44 if (!x) throw new ERR_ASSERTION(msg || 'assertion error'); 45} 46 47// The execution of this function itself should not cause any side effects. 48function wrapProcessMethods(binding) { 49 const { 50 hrtime: _hrtime, 51 hrtimeBigInt: _hrtimeBigInt, 52 cpuUsage: _cpuUsage, 53 memoryUsage: _memoryUsage, 54 rss, 55 resourceUsage: _resourceUsage 56 } = binding; 57 58 function _rawDebug(...args) { 59 binding._rawDebug(format.apply(null, args)); 60 } 61 62 // Create the argument array that will be passed to the native function. 63 const cpuValues = new Float64Array(2); 64 65 // Replace the native function with the JS version that calls the native 66 // function. 67 function cpuUsage(prevValue) { 68 // If a previous value was passed in, ensure it has the correct shape. 69 if (prevValue) { 70 if (!previousValueIsValid(prevValue.user)) { 71 if (typeof prevValue !== 'object') 72 throw new ERR_INVALID_ARG_TYPE('prevValue', 'object', prevValue); 73 74 if (typeof prevValue.user !== 'number') { 75 throw new ERR_INVALID_ARG_TYPE('prevValue.user', 76 'number', prevValue.user); 77 } 78 throw new ERR_INVALID_OPT_VALUE.RangeError('prevValue.user', 79 prevValue.user); 80 } 81 82 if (!previousValueIsValid(prevValue.system)) { 83 if (typeof prevValue.system !== 'number') { 84 throw new ERR_INVALID_ARG_TYPE('prevValue.system', 85 'number', prevValue.system); 86 } 87 throw new ERR_INVALID_OPT_VALUE.RangeError('prevValue.system', 88 prevValue.system); 89 } 90 } 91 92 // Call the native function to get the current values. 93 const errmsg = _cpuUsage(cpuValues); 94 if (errmsg) { 95 throw new ERR_CPU_USAGE(errmsg); 96 } 97 98 // If a previous value was passed in, return diff of current from previous. 99 if (prevValue) { 100 return { 101 user: cpuValues[0] - prevValue.user, 102 system: cpuValues[1] - prevValue.system 103 }; 104 } 105 106 // If no previous value passed in, return current value. 107 return { 108 user: cpuValues[0], 109 system: cpuValues[1] 110 }; 111 } 112 113 // Ensure that a previously passed in value is valid. Currently, the native 114 // implementation always returns numbers <= Number.MAX_SAFE_INTEGER. 115 function previousValueIsValid(num) { 116 return typeof num === 'number' && 117 num <= NumberMAX_SAFE_INTEGER && 118 num >= 0; 119 } 120 121 // The 3 entries filled in by the original process.hrtime contains 122 // the upper/lower 32 bits of the second part of the value, 123 // and the remaining nanoseconds of the value. 124 const hrValues = new Uint32Array(3); 125 126 function hrtime(time) { 127 _hrtime(hrValues); 128 129 if (time !== undefined) { 130 if (!ArrayIsArray(time)) { 131 throw new ERR_INVALID_ARG_TYPE('time', 'Array', time); 132 } 133 if (time.length !== 2) { 134 throw new ERR_OUT_OF_RANGE('time', 2, time.length); 135 } 136 137 const sec = (hrValues[0] * 0x100000000 + hrValues[1]) - time[0]; 138 const nsec = hrValues[2] - time[1]; 139 const needsBorrow = nsec < 0; 140 return [needsBorrow ? sec - 1 : sec, needsBorrow ? nsec + 1e9 : nsec]; 141 } 142 143 return [ 144 hrValues[0] * 0x100000000 + hrValues[1], 145 hrValues[2], 146 ]; 147 } 148 149 // Use a BigUint64Array in the closure because this is actually a bit 150 // faster than simply returning a BigInt from C++ in V8 7.1. 151 const hrBigintValues = new BigUint64Array(1); 152 function hrtimeBigInt() { 153 _hrtimeBigInt(hrBigintValues); 154 return hrBigintValues[0]; 155 } 156 157 const memValues = new Float64Array(5); 158 function memoryUsage() { 159 _memoryUsage(memValues); 160 return { 161 rss: memValues[0], 162 heapTotal: memValues[1], 163 heapUsed: memValues[2], 164 external: memValues[3], 165 arrayBuffers: memValues[4] 166 }; 167 } 168 169 memoryUsage.rss = rss; 170 171 function exit(code) { 172 process.off('exit', handleProcessExit); 173 174 if (code || code === 0) 175 process.exitCode = code; 176 177 if (!process._exiting) { 178 process._exiting = true; 179 process.emit('exit', process.exitCode || 0); 180 } 181 // FIXME(joyeecheung): This is an undocumented API that gets monkey-patched 182 // in the user land. Either document it, or deprecate it in favor of a 183 // better public alternative. 184 process.reallyExit(process.exitCode || 0); 185 } 186 187 function kill(pid, sig) { 188 let err; 189 190 // eslint-disable-next-line eqeqeq 191 if (pid != (pid | 0)) { 192 throw new ERR_INVALID_ARG_TYPE('pid', 'number', pid); 193 } 194 195 // Preserve null signal 196 if (sig === (sig | 0)) { 197 // XXX(joyeecheung): we have to use process._kill here because 198 // it's monkey-patched by tests. 199 err = process._kill(pid, sig); 200 } else { 201 sig = sig || 'SIGTERM'; 202 if (constants[sig]) { 203 err = process._kill(pid, constants[sig]); 204 } else { 205 throw new ERR_UNKNOWN_SIGNAL(sig); 206 } 207 } 208 209 if (err) 210 throw errnoException(err, 'kill'); 211 212 return true; 213 } 214 215 const resourceValues = new Float64Array(16); 216 function resourceUsage() { 217 _resourceUsage(resourceValues); 218 return { 219 userCPUTime: resourceValues[0], 220 systemCPUTime: resourceValues[1], 221 maxRSS: resourceValues[2], 222 sharedMemorySize: resourceValues[3], 223 unsharedDataSize: resourceValues[4], 224 unsharedStackSize: resourceValues[5], 225 minorPageFault: resourceValues[6], 226 majorPageFault: resourceValues[7], 227 swappedOut: resourceValues[8], 228 fsRead: resourceValues[9], 229 fsWrite: resourceValues[10], 230 ipcSent: resourceValues[11], 231 ipcReceived: resourceValues[12], 232 signalsCount: resourceValues[13], 233 voluntaryContextSwitches: resourceValues[14], 234 involuntaryContextSwitches: resourceValues[15] 235 }; 236 } 237 238 239 return { 240 _rawDebug, 241 hrtime, 242 hrtimeBigInt, 243 cpuUsage, 244 resourceUsage, 245 memoryUsage, 246 kill, 247 exit 248 }; 249} 250 251const replaceUnderscoresRegex = /_/g; 252const leadingDashesRegex = /^--?/; 253const trailingValuesRegex = /=.*$/; 254 255// This builds the initial process.allowedNodeEnvironmentFlags 256// from data in the config binding. 257function buildAllowedFlags() { 258 const { 259 envSettings: { kAllowedInEnvironment }, 260 types: { kBoolean }, 261 } = internalBinding('options'); 262 const { options, aliases } = require('internal/options'); 263 264 const allowedNodeEnvironmentFlags = []; 265 for (const [name, info] of options) { 266 if (info.envVarSettings === kAllowedInEnvironment) { 267 ArrayPrototypePush(allowedNodeEnvironmentFlags, name); 268 if (info.type === kBoolean) { 269 const negatedName = `--no-${name.slice(2)}`; 270 ArrayPrototypePush(allowedNodeEnvironmentFlags, negatedName); 271 } 272 } 273 } 274 275 for (const [ from, expansion ] of aliases) { 276 let isAccepted = true; 277 for (const to of expansion) { 278 if (!to.startsWith('-') || to === '--') continue; 279 const recursiveExpansion = aliases.get(to); 280 if (recursiveExpansion) { 281 if (recursiveExpansion[0] === to) 282 recursiveExpansion.splice(0, 1); 283 expansion.push(...recursiveExpansion); 284 continue; 285 } 286 isAccepted = options.get(to).envVarSettings === kAllowedInEnvironment; 287 if (!isAccepted) break; 288 } 289 if (isAccepted) { 290 let canonical = from; 291 if (canonical.endsWith('=')) 292 canonical = canonical.substr(0, canonical.length - 1); 293 if (canonical.endsWith(' <arg>')) 294 canonical = canonical.substr(0, canonical.length - 4); 295 allowedNodeEnvironmentFlags.push(canonical); 296 } 297 } 298 299 const trimLeadingDashes = 300 (flag) => StringPrototypeReplace(flag, leadingDashesRegex, ''); 301 302 // Save these for comparison against flags provided to 303 // process.allowedNodeEnvironmentFlags.has() which lack leading dashes. 304 // Avoid interference w/ user code by flattening `Set.prototype` into 305 // each object. 306 const nodeFlags = ObjectDefineProperties( 307 new Set(allowedNodeEnvironmentFlags.map(trimLeadingDashes)), 308 ObjectGetOwnPropertyDescriptors(SetPrototype) 309 ); 310 311 class NodeEnvironmentFlagsSet extends Set { 312 constructor(...args) { 313 super(...args); 314 315 // The super constructor consumes `add`, but 316 // disallow any future adds. 317 ObjectDefineProperty(this, 'add', { 318 value: () => this 319 }); 320 } 321 322 delete() { 323 // No-op, `Set` API compatible 324 return false; 325 } 326 327 clear() { 328 // No-op 329 } 330 331 has(key) { 332 // This will return `true` based on various possible 333 // permutations of a flag, including present/missing leading 334 // dash(es) and/or underscores-for-dashes. 335 // Strips any values after `=`, inclusive. 336 // TODO(addaleax): It might be more flexible to run the option parser 337 // on a dummy option set and see whether it rejects the argument or 338 // not. 339 if (typeof key === 'string') { 340 key = StringPrototypeReplace(key, replaceUnderscoresRegex, '-'); 341 if (RegExpPrototypeTest(leadingDashesRegex, key)) { 342 key = StringPrototypeReplace(key, trailingValuesRegex, ''); 343 return SetPrototypeHas(this, key); 344 } 345 return SetPrototypeHas(nodeFlags, key); 346 } 347 return false; 348 } 349 } 350 351 ObjectFreeze(NodeEnvironmentFlagsSet.prototype.constructor); 352 ObjectFreeze(NodeEnvironmentFlagsSet.prototype); 353 354 return ObjectFreeze(new NodeEnvironmentFlagsSet( 355 allowedNodeEnvironmentFlags 356 )); 357} 358 359// Lazy load internal/trace_events_async_hooks only if the async_hooks 360// trace event category is enabled. 361let traceEventsAsyncHook; 362// Dynamically enable/disable the traceEventsAsyncHook 363function toggleTraceCategoryState(asyncHooksEnabled) { 364 if (asyncHooksEnabled) { 365 if (!traceEventsAsyncHook) { 366 traceEventsAsyncHook = 367 require('internal/trace_events_async_hooks').createHook(); 368 } 369 traceEventsAsyncHook.enable(); 370 } else if (traceEventsAsyncHook) { 371 traceEventsAsyncHook.disable(); 372 } 373} 374 375module.exports = { 376 toggleTraceCategoryState, 377 assert, 378 buildAllowedFlags, 379 wrapProcessMethods 380}; 381