proc/index.js

const errno = require('errno');
const io = require('io');

/**
 * A registered atexit() handler
 *
 * @typedef {object} AtexitHandler
 *
 * @property {number} [pid]
 * Process from which handler must be invoked. Or all processes if missing.
 *
 * @property {function} handler
 * The handler function to invoke
 *
 * @private
 */

/**
 * Process execution options.
 *
 * @typedef {object} ProcExecOptions
 *
 * @property {string} dir New working directory for new process
 *
 * @property {object} env
 * Environment variables to add/override for new process. Setting a variable to
 * `null` removes it.
 *
 * @property {boolean} search_path
 * Whether to search executable in PATH (default is `true`)
 */

/**
 * Information on a process execution result.
 *
 * @typedef {object} ProcResult
 *
 * @property {number} value
 * The child pid
 *
 * @property {number|undefined} exit_status
 * The exit status (a number between 0 and 255) or undefined if the process
 * exited because of a signal
 *
 * @property {number|undefined} term_signal
 * The termination signal or undefined if the process exited normally
 *
 * @property {number|undefined} stop_signal
 * The stop signal if the process was stopped
 *
 * @property {boolean} core_dump
 *
 * @property {boolean} continued
 */

/**
 * @exports proc
 * @readonly
 * @enum {number}
 */
const proc = {
	SIGHUP: 1,
	SIGINT: 2,
	SIGQUIT: 3,
	SIGILL: 4,
	SIGTRAP: 5,
	SIGABRT: 6,
	SIGIOT: 6,
	SIGBUS: 7,
	SIGFPE: 8,
	SIGKILL: 9,
	SIGUSR1: 10,
	SIGSEGV: 11,
	SIGUSR2: 12,
	SIGPIPE: 13,
	SIGALRM: 14,
	SIGTERM: 15,
	SIGSTKFLT: 16,
	SIGCHLD: 17,
	SIGCONT: 18,
	SIGSTOP: 19,
	SIGTSTP: 20,
	SIGTTIN: 21,
	SIGTTOU: 22,
	SIGURG: 23,
	SIGXCPU: 24,
	SIGXFSZ: 25,
	SIGVTALRM: 26,
	SIGPROF: 27,
	SIGWINCH: 28,
	SIGIO: 29,
	SIGPOLL: 29,
	SIGPWR: 30,
	SIGSYS: 31,
	SIGUNUSED: 31,

	/** Return immediately if no child has exited */
	WNOHANG: 1,
	/** Also return if a child has stopped (but not traced via ptrace(2)) */
	WUNTRACED: 2,
	/**
	 * Also return if a stopped child has been resumed by delivery of SIGCONT
	 * (since Linux 2.6.10)
	 */
	WCONTINUED: 8,
};

/**
 * atexit() handlers store
 *
 * @type {AtexitHandler[]}
 * @private
 */
const atexit_handlers = [];

/* Register our own handler with libc runtime */
j.atexit(function () {
	for (var i = atexit_handlers.length - 1; i >= 0; i--) {
		const desc = atexit_handlers[i];

		if (desc.pid && desc.pid !== proc.getpid()) {
			continue;
		}

		desc.handler();
	}
});

/**
 * The alarm() function shall cause the system to generate a SIGALRM signal for
 * the process after the number of realtime seconds specified by seconds have
 * elapsed.
 *
 * Processor scheduling delays may prevent the process from handling the signal
 * as soon as it is generated.
 *
 * If seconds is 0, a pending alarm request, if any, is canceled.
 *
 * Alarm requests are not stacked; only one SIGALRM generation can be scheduled
 * in this manner. If the SIGALRM signal has not yet been generated, the call
 * shall result in rescheduling the time at which the SIGALRM signal is
 * generated.
 *
 * @param {number} seconds
 *
 * @returns {number}
 * If there is a previous alarm() request with time remaining, alarm() shall
 * return a non-zero value that is the number of seconds until the previous
 * request would have generated a SIGALRM signal.
 *
 * Otherwise, alarm() shall return 0.
 */
proc.alarm = function (seconds) {
	return j.alarm(Number(seconds));
};

/**
 * Register function handlers for the exit process event.
 *
 * Note that, unlike the libc atexit() function, this one only invokes handlers
 * which have been registered for the current process, and not its children.
 *
 * In libc's atexit(), all handlers are inherited when a fork() is done, leading
 * to children process to execute atexit() handlers too. In this framework, the
 * default is not to inherit unless requested with `inherit = true`.
 *
 * @param {boolean} [inherit=false]
 * Whether to inherit the handler in forked children
 *
 * @param {function} fn
 * Handler function
 *
 * @returns {void}
 */
proc.atexit = function (inherit, fn) {
	if (fn === undefined) {
		fn = inherit;
		inherit = false;
	}

	const pid = inherit ? undefined : proc.getpid();

	atexit_handlers.push({
		handler: fn,
		pid: pid,
	});
};

/**
 * Change process working directory.
 *
 * @returns {number} 0
 * @throws {SysError}
 */
proc.chdir = function (dir) {
	return j.chdir(dir);
};

/**
 * Replace the current process image with a new process image.
 *
 * Note that this function either fails or doesn't return ever because it
 * replaces the current process image.
 *
 * @param {string} executable Path or name of executable
 *
 * @param {string[]} [args=[]]
 * Array of arguments to pass to program (not including argv[0]).
 *
 * @param {ProcExecOptions} [opts={}]
 * Options for process execution.
 *
 * @throws {SysError}
 */
proc.exec = function (executable, args, opts) {
	if (!Array.isArray(args)) {
		opts = args;
		args = [];
	}

	args = args || [];
	opts = opts || {};

	const argv = [executable].concat(args).map(function (arg) {
		return arg.toString();
	});
	argv.push(null);

	if (opts.dir) {
		proc.chdir(opts.dir);
	}

	if (opts.env) {
		Object.entries(opts.env).forEach(function (entry) {
			const name = entry[0];
			const value = entry[1];

			if (value !== null) {
				proc.setenv(name, value);
			} else {
				proc.unsetenv(name);
			}
		});
	}

	try {
		if (opts.search_path !== false) {
			j.execvp(executable, argv);
		} else {
			j.execv(executable, argv);
		}

		proc.exit(-1);
	} catch (err) {
		err.message += ' (' + executable + ')';
		throw err;
	}
};

/**
 * End current process returning given status code.
 *
 * Note that this function doesn't return ever because it finishes the process.
 *
 * @param {number} [status=0] Status code to return to kernel
 */
proc.exit = function (status) {
	status = Number(status || 0);

	j.exit(status);
};

/**
 * This function creates a new process.
 *
 * @example
 * // Fork and return immediately (old school fork)
 * const pid = proc.fork();
 * if (pid === 0) {
 *   // Run child code
 *   ...
 * } else {
 *   // Run parent code
 *   ...
 *
 *   // Wait for child to finish
 *   proc.waitpid(pid);
 * }
 *
 * @example
 * const pid = proc.fork(function() {
 *   // Run child code
 *   ...
 * });
 *
 * // Wait for child to finish
 * proc.waitpid(pid);
 *
 * @example
 * const result = proc.fork(true, function() {
 *   // Run child code
 *   ...
 * });
 *
 * // Check result
 * if (result.exit_status !== 0) {
 *   ...
 * }
 *
 * @param {true} [wait]
 * Pass `true` to wait for child to finish. Can only be given if `fn` is given
 * since it doesn't make sense to wait for an empty child.
 *
 * @param {function} [fn]
 * The function that implements the child process. If not given, the method
 * returns and the return code must be inspected to decide whether to execute
 * parent or child code.
 *
 * @returns {number|ProcResult}
 * If `wait` is `true` returns the result of proc.waitpid(), otherwise, the
 * return value is 0 in the child and the pid of the child in the parent.
 *
 * @throws {SysError}
 */
proc.fork = function (wait, fn) {
	if (typeof wait === 'function') {
		fn = wait;
		wait = false;
	} else {
		wait = !!wait;
	}

	if (wait && fn === undefined) {
		throw new Error('Cannot wait on a fork with no function to execute');
	}

	const pid = j.fork();

	if (pid === 0) {
		if (fn) {
			try {
				fn();
			} catch (err) {
				io.write_string(2, err.stack + '\n');
				proc.exit(err.errno || 1);
			}

			proc.exit(0);
		} else {
			return pid;
		}
	}

	if (wait) {
		return proc.waitpid(pid);
	}

	return pid;
};

/**
 * Double fork idiom used to launch detached (daemonized) processes. Note that
 * it has builtin support to obtain the child pid if requested.
 *
 * @example
 * proc.fork2(function() {
 *   // Run fire and forget daemon
 *   ...
 * });
 *
 * @example
 * const pid = proc.fork2(true, function() {
 *   // Run managed daemon
 *   ...
 * });
 *
 * ...
 *
 * // Kill daemon
 * proc.kill(pid);
 *
 * @param {true} [getpid] Pass `true` to make the function return daemon's pid
 * @param {function} fn The function that implements daemon's code
 * @returns {void|number} pid of daemon if `getpid = true`
 * @throws {SysError}
 */
proc.fork2 = function (getpid, fn) {
	if (typeof getpid === 'function') {
		fn = getpid;
		getpid = false;
	} else {
		getpid = !!getpid;
	}

	if (fn === undefined) {
		throw new Error('Cannot launch a daemon with no function to execute');
	}

	var fds;

	if (getpid) {
		fds = io.pipe();
	}

	var pid = proc.fork();

	if (pid === 0) {
		try {
			pid = proc.fork();

			if (pid === 0) {
				try {
					if (getpid) {
						io.close(fds[0]);
						io.write_string(fds[1], proc.getpid());
						io.close(fds[1]);
					}

					fn();
				} catch (err) {
					io.write_string(2, err.stack);
					proc.exit(err.errno === undefined ? -1 : err.errno);
				}

				proc.exit(0);
			}
		} catch (err) {
			io.write_string(2, err.stack);
			proc.exit(err.errno === undefined ? -1 : err.errno);
		}

		proc.exit(0);
	}

	var child_pid = undefined;

	if (getpid) {
		io.close(fds[1]);
		child_pid = Number(io.read_string(fds[0]));
		io.close(fds[0]);
	}

	return child_pid;
};

/**
 * Get the value of an environment variable
 *
 * @param {string} name The name of the environment variable
 * @returns {string|null} The value of the environment variable
 */
proc.getenv = function (name) {
	return j.getenv(name);
};

/**
 * Get the effective group id of the running process.
 *
 * @returns {number} A group id
 */
proc.getegid = function () {
	return j.getegid();
};

/**
 * Get the effective user id of the running process.
 *
 * @returns {number} A user id
 */
proc.geteuid = function () {
	return j.geteuid();
};

/**
 * Get the group id of the running process.
 *
 * @returns {number} A group id
 */
proc.getgid = function () {
	return j.getgid();
};

/**
 * Get the pid of the running process.
 *
 * @returns {number} A process id
 */
proc.getpid = function () {
	return j.getpid();
};

/**
 * Get the pid of the parent process.
 *
 * @returns {number} A process id
 */
proc.getppid = function () {
	return j.getppid();
};

/**
 * Get the user id of the running process.
 *
 * @returns {number} A user id
 */
proc.getuid = function () {
	return j.getuid();
};

/**
 * The kill() function can be used to send any signal to any process group or
 * process.
 *
 * For a process to have permission to send a signal, it must either be
 * privileged (under Linux: have the CAP_KILL capability in the user namespace
 * of the target process), or the real or effective user ID of the sending
 * process must equal the real or saved set-user-ID of the target process. In
 * the case of `SIGCONT`, it suffices when the sending and receiving processes
 * belong to the same session.
 *
 * @param {number} pid
 * If `pid` is positive, then signal `sig` is sent to the process with the ID
 * specified by `pid`.
 *
 * If `pid` equals `0`, then `sig` is sent to every process in the process group
 * of the calling process.
 *
 * If `pid` equals `-1`, then `sig` is sent to every process for which the
 * calling process has permission to send signals, except for process `1`
 * (init), but see below.
 *
 * If `pid` is less than `-1`, then `sig` is sent to every process in the
 * process group whose ID is `-pid`.
 *
 * @param {number} sig
 * If `sig` is `0`, then no signal is sent, but existence and permission checks
 * are still performed; this can be used to check for the existence of a process
 * ID or process group ID that the caller is permitted to signal.
 *
 * @returns {0}
 * @throws {SysError}
 */
proc.kill = function (pid, sig) {
	if (sig === undefined) {
		sig = proc.SIGKILL;
	}

	return j.kill(pid, sig);
};

/**
 * Set an environment variable.
 *
 * @param {string} name Name of variable
 * @param {string|null} value Value to set or `null` to unset
 * @param {boolean} [overwrite=true] Whether to overwrite the value if it exists
 * @returns {0}
 * @throws SysError
 */
proc.setenv = function (name, value, overwrite) {
	name = name.toString();
	overwrite = overwrite === undefined ? true : !!overwrite;

	if (value === null) {
		if (!overwrite) {
			return 0;
		}

		return j.unsetenv(name);
	}

	return j.setenv(name, value.toString(), overwrite ? 1 : 0);
};

/**
 * Creates a new session if the calling process is not a process group leader.
 * The calling process is the leader of the new session (i.e., its session ID is
 * made the same as its process ID).
 *
 * The calling process also becomes the process group leader of a new process
 * group in the session (i.e., its process group ID is made the same as its
 * process ID).
 *
 * The calling process will be the only process in the new process group and in
 * the new session.
 *
 * Initially, the new session has no controlling terminal. For details of how a
 * session acquires a controlling terminal, see credentials(7).
 *
 * @returns {number} The (new) session ID of the calling process
 * @throws SysError
 */
proc.setsid = function () {
	return j.setsid();
};

/**
 * The signal() function chooses one of three ways in which receipt of the
 * signal number `sig` is to be subsequently handled.
 *
 * @param {number} sig The signal number
 *
 * @param {undefined|null|function} func
 * If the value of `func` is `undefined`, default handling for that signal shall
 * occur.
 *
 * If the value of `func` is `null`, the signal shall be ignored.
 *
 * Otherwise, the application shall ensure that `func` points to a function to
 * be called when that signal occurs.
 *
 * @returns {void}
 * @throws SysError
 */
proc.signal = function (sig, func) {
	j.signal(sig, func);
};

/**
 * Pause execution of the running process for a given number of seconds.
 *
 * @param {number} seconds Seconds to wait
 * @returns {0}
 * @throws SysError
 */
proc.sleep = function (seconds) {
	while (seconds > 0) {
		seconds = j.sleep(seconds);
	}
};

/**
 * Delete an environment variable.
 *
 * @param {string} name Name of environment variable
 * @returns {0}
 * @throws SysError
 */
proc.unsetenv = function (name) {
	return j.unsetenv(name);
};

/**
 *
 * @param {number} pid
 * < -1 meaning wait for any child process whose process group ID is
 * equal to the absolute value of pid.
 *
 * -1 meaning wait for any child process.
 *
 * 0 meaning wait for any child process whose process group ID is equal to that
 * of the calling process at the time of the call to waitpid().
 *
 * &gt; 0 meaning wait for the child whose process ID is equal to the value of pid.
 *
 * @param {number} options
 * Zero or an OR of proc.WNOHANG, proc.WUNTRACED, or proc.WCONTINUED.
 *
 * @return {ProcResult}
 * @throws SysError
 */
proc.waitpid = function (pid, options) {
	if (options === undefined) {
		options = 0;
	}

	var result;

	while (!result) {
		try {
			result = j.waitpid(pid, options);
		} catch (err) {
			if (err.errno !== errno.EINTR) {
				throw err;
			}
		}
	}

	const wstatus = result.wstatus;

	const exit_status = (wstatus & 0xff00) >> 8;
	const term_signal = wstatus & 0x7f;

	return {
		value: result.value,
		exit_status: term_signal === 0 ? exit_status : undefined,
		term_signal: term_signal === 0 ? undefined : term_signal,
		stop_signal: (wstatus & 0xff) == 0x7f ? exit_status : undefined,
		core_dump: (wstatus & 0x80) != 0,
		continued: wstatus == 0xffff,
	};
};

return proc;