SafeExec.js 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. const _ = require('underscore')
  2. const logger = require('logger-sharelatex')
  3. const childProcess = require('child_process')
  4. const Settings = require('settings-sharelatex')
  5. const { ConversionsDisabledError, FailedCommandError } = require('./Errors')
  6. // execute a command in the same way as 'exec' but with a timeout that
  7. // kills all child processes
  8. //
  9. // we spawn the command with 'detached:true' to make a new process
  10. // group, then we can kill everything in that process group.
  11. module.exports = safeExec
  12. module.exports.promises = safeExecPromise
  13. // options are {timeout: number-of-milliseconds, killSignal: signal-name}
  14. function safeExec(command, options, callback) {
  15. if (!Settings.enableConversions) {
  16. return callback(
  17. new ConversionsDisabledError('image conversions are disabled')
  18. )
  19. }
  20. const [cmd, ...args] = command
  21. const child = childProcess.spawn(cmd, args, { detached: true })
  22. let stdout = ''
  23. let stderr = ''
  24. let killTimer
  25. if (options.timeout) {
  26. killTimer = setTimeout(function() {
  27. try {
  28. // use negative process id to kill process group
  29. process.kill(-child.pid, options.killSignal || 'SIGTERM')
  30. } catch (error) {
  31. logger.log(
  32. { process: child.pid, kill_error: error },
  33. 'error killing process'
  34. )
  35. }
  36. }, options.timeout)
  37. }
  38. const cleanup = _.once(function(err) {
  39. if (killTimer) {
  40. clearTimeout(killTimer)
  41. }
  42. callback(err, stdout, stderr)
  43. })
  44. child.on('close', function(code, signal) {
  45. if (code || signal) {
  46. return cleanup(
  47. new FailedCommandError(command, code || signal, stdout, stderr)
  48. )
  49. }
  50. cleanup()
  51. })
  52. child.on('error', err => {
  53. cleanup(err)
  54. })
  55. child.stdout.on('data', chunk => {
  56. stdout += chunk
  57. })
  58. child.stderr.on('data', chunk => {
  59. stderr += chunk
  60. })
  61. }
  62. function safeExecPromise(command, options) {
  63. return new Promise((resolve, reject) => {
  64. safeExec(command, options, (err, stdout, stderr) => {
  65. if (err) {
  66. reject(err)
  67. }
  68. resolve({ stdout, stderr })
  69. })
  70. })
  71. }