| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
- import lodashOnce from 'lodash.once'
- import childProcess from 'node:child_process'
- import Settings from '@overleaf/settings'
- import Errors from './Errors.js'
- const { ConversionsDisabledError, FailedCommandError } = Errors
- // execute a command in the same way as 'exec' but with a timeout that
- // kills all child processes
- //
- // we spawn the command with 'detached:true' to make a new process
- // group, then we can kill everything in that process group.
- export default safeExec
- safeExec.promises = safeExecPromise
- // options are {timeout: number-of-milliseconds, killSignal: signal-name}
- function safeExec(command, options, callback) {
- if (!Settings.enableConversions) {
- return callback(
- new ConversionsDisabledError('image conversions are disabled')
- )
- }
- const [cmd, ...args] = command
- const child = childProcess.spawn(cmd, args, { detached: true })
- let stdout = ''
- let stderr = ''
- let killTimer
- const cleanup = lodashOnce(function (err) {
- if (killTimer) {
- clearTimeout(killTimer)
- }
- callback(err, stdout, stderr)
- })
- if (options.timeout) {
- killTimer = setTimeout(function () {
- try {
- // use negative process id to kill process group
- process.kill(-child.pid, options.killSignal || 'SIGTERM')
- } catch (error) {
- cleanup(
- new FailedCommandError('failed to kill process after timeout', {
- command,
- options,
- pid: child.pid,
- })
- )
- }
- }, options.timeout)
- }
- child.on('close', function (code, signal) {
- if (code || signal) {
- return cleanup(
- new FailedCommandError(command, code || signal, stdout, stderr)
- )
- }
- cleanup()
- })
- child.on('error', err => {
- cleanup(err)
- })
- child.stdout.on('data', chunk => {
- stdout += chunk
- })
- child.stderr.on('data', chunk => {
- stderr += chunk
- })
- }
- function safeExecPromise(command, options) {
- return new Promise((resolve, reject) => {
- safeExec(command, options, (err, stdout, stderr) => {
- if (err) {
- reject(err)
- }
- resolve({ stdout, stderr })
- })
- })
- }
|