-
Notifications
You must be signed in to change notification settings - Fork 3
/
execFile.js
171 lines (142 loc) · 3.67 KB
/
execFile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/* globals NSMutableData, NSData */
var spawn = require('./spawn')
var handleData = require('./handleData')
function validateTimeout(timeout) {
if (timeout != null && !(Number.isInteger(timeout) && timeout >= 0)) {
throw new Error('ERR_OUT_OF_RANGE options.timeout')
}
}
function validateMaxBuffer(maxBuffer) {
if (maxBuffer != null && !(typeof maxBuffer === 'number' && maxBuffer >= 0)) {
throw new Error('ERR_OUT_OF_RANGE options.maxBuffer')
}
}
function concatData(prev, data) {
prev.appendData(data)
return prev
}
module.exports = function execFile(file, args, options, callback) {
var defaultOptions = {
encoding: 'utf8',
timeout: 0,
maxBuffer: 200 * 1024,
killSignal: 'SIGTERM',
cwd: undefined,
env: undefined,
shell: false
}
if (typeof args === 'function') {
// function (file, callback)
callback = args
args = []
options = defaultOptions
} else if (typeof args === 'object' && !Array.isArray(args)) {
// function (file, options, callback)
callback = options
options = Object.assign(defaultOptions, args)
args = []
} else {
// function (file, args, options, callback)
options = Object.assign(defaultOptions, options)
}
// Validate the timeout, if present.
validateTimeout(options.timeout)
// Validate maxBuffer, if present.
validateMaxBuffer(options.maxBuffer)
var child = spawn(file, args, {
cwd: options.cwd,
env: options.env,
gid: options.gid,
uid: options.uid,
shell: options.shell
})
var encoding = options.encoding
var _stdout = []
var _stderr = []
var stdoutLen = 0
var stderrLen = 0
var killed = false
var exited = false
var timeoutId
var ex = null
var cmd = file
function exithandler(code, signal) {
if (exited) return
exited = true
if (timeoutId) {
clearTimeout(timeoutId)
timeoutId = null
}
if (!callback) return
// merge chunks
var stdout = handleData(
NSData.dataWithData(_stdout.reduce(concatData, NSMutableData.data())),
encoding
)
var stderr = handleData(
NSData.dataWithData(_stderr.reduce(concatData, NSMutableData.data())),
encoding
)
if (!ex && code === 0 && signal === null) {
callback(null, stdout, stderr)
return
}
if (args.length !== 0) {
cmd += ' ' + args.join(' ')
}
if (!ex) {
ex = new Error('Command failed: ' + cmd + '\n' + stderr)
ex.killed = child.killed || killed
ex.code = code
ex.signal = signal
}
ex.cmd = cmd
callback(ex, stdout, stderr)
}
function errorhandler(e) {
ex = e
exithandler()
}
function kill() {
killed = true
try {
child.kill(options.killSignal)
} catch (e) {
ex = e
exithandler()
}
}
if (options.timeout > 0) {
timeoutId = setTimeout(function delayedKill() {
kill()
timeoutId = null
}, options.timeout)
}
if (child.stdout) {
child.stdout.setEncoding('NSData')
child.stdout.on('data', function onChildStdout(chunk) {
stdoutLen += chunk.length()
if (stdoutLen > options.maxBuffer) {
ex = new Error('ERR_CHILD_PROCESS_STDIO_MAXBUFFER stdout')
kill()
} else {
_stdout.push(chunk)
}
})
}
if (child.stderr) {
child.stderr.setEncoding('NSData')
child.stderr.on('data', function onChildStderr(chunk) {
stderrLen += chunk.length()
if (stderrLen > options.maxBuffer) {
ex = new Error('ERR_CHILD_PROCESS_STDIO_MAXBUFFER stderr')
kill()
} else {
_stderr.push(chunk)
}
})
}
child.addListener('close', exithandler)
child.addListener('error', errorhandler)
return child
}