-
-
Notifications
You must be signed in to change notification settings - Fork 70
Expand file tree
/
Copy pathShellExec.cpp
More file actions
308 lines (261 loc) · 9.72 KB
/
ShellExec.cpp
File metadata and controls
308 lines (261 loc) · 9.72 KB
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
//
// ShellExec.cpp
// MiniScript
//
// Created by Joe Strout on 2/9/24.
// Copyright © 2024 Joe Strout. All rights reserved.
//
#include "ShellExec.h"
#if _WIN32 || _WIN64
#define WINDOWS 1
#include <windows.h>
#include <Shlwapi.h>
#include <Fileapi.h>
#include <direct.h>
#include <locale>
#include <codecvt>
#else
#include <unistd.h> // for read()
#include <sys/wait.h> // for waitpid()
#endif
namespace MiniScript {
#if WINDOWS
// Helper function to read from file descriptor into string
String readFromFd(HANDLE fd, bool trimTrailingNewline=true) {
const int bufferSize = 1024;
DWORD bytesRead = 0;
CHAR buffer[bufferSize];
bool bSuccess;
String output;
bool trimmed = false;
for (;;) {
bSuccess = ReadFile(fd, buffer, bufferSize-1, &bytesRead, nullptr);
if (!bSuccess || bytesRead == 0) break;
buffer[bytesRead] = '\0';
if (trimTrailingNewline and bytesRead < bufferSize-1 and bytesRead > 0 and buffer[bytesRead-1] == '\n') {
// Efficiently trim \n or \r\n from the end of the buffer
bytesRead--;
if (bytesRead > 0 and buffer[bytesRead-1] == '\r') bytesRead--;
trimmed = true;
}
String s(buffer, bytesRead);
output += s;
}
if (trimTrailingNewline && !trimmed) {
// Not-so-efficiently trim our final string, in the case where our data happened
// to exactly align with the buffer size, so we couldn't know we were at the
// end of it to trim it above. (This is a rare edge case.)
int cut = 0;
if (output.LengthB() > 1 and output[-1] == '\n') {
cut = 1;
if (output.LengthB() > 2 and output[-2] == '\r') cut = 2;
}
if (cut) output = output.SubstringB(0, output.LengthB() - cut);
}
return output;
}
bool BeginExec(String cmd, double timeout, double currentTime, ValueList* outResult) {
// This is the initial entry into `exec`. Fork a subprocess to execute the
// given command, and return a partial result we can use to check on its progress.
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = nullptr;
HANDLE hChildStd_OUT_Rd = nullptr;
HANDLE hChildStd_OUT_Wr = nullptr;
HANDLE hChildStd_ERR_Rd = nullptr;
HANDLE hChildStd_ERR_Wr = nullptr;
// Create a pipe for the child process's STDOUT and STDERR.
// Disable the INHERIT flag to ensure each handle is not inherited
if (!CreatePipe(&hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &saAttr, 0)) return false;
SetHandleInformation(hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0);
if (!CreatePipe(&hChildStd_ERR_Rd, &hChildStd_ERR_Wr, &saAttr, 0)) return false;
SetHandleInformation(hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0);
STARTUPINFO siStartInfo;
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = hChildStd_ERR_Wr;
siStartInfo.hStdOutput = hChildStd_OUT_Wr;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
PROCESS_INFORMATION piProcInfo;
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// Start the child process.
if (!CreateProcessA(nullptr,
(LPSTR)cmd.c_str(), // command line
nullptr, // process security attributes
nullptr, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
nullptr, // use parent's environment
nullptr, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo)) // receives PROCESS_INFORMATION
{
return false;
}
// Close handles to the stdin and stdout pipes no longer needed by the child process.
// If they are not explicitly closed, there is no way to recognize that the child process has completed.
CloseHandle(hChildStd_OUT_Wr);
CloseHandle(hChildStd_ERR_Wr);
// As our partial result, return a list with the pid, the two read pipes, and the final time.
// (We're going to cast our HANDLE to an int, which is a little dicey but maybe we get away with it.
ValueList data;
data.Add(Value((int)piProcInfo.hProcess));
data.Add(Value((int)piProcInfo.hThread));
data.Add(Value((int)hChildStd_OUT_Rd));
data.Add(Value((int)hChildStd_ERR_Rd));
data.Add(Value(currentTime + timeout));
*outResult = data;
return true;
}
bool FinishExec(ValueList data, double currentTime, String* outStdout, String* outStderr, int* outStatus) {
// Start by getting the pid, the two read pipes, and the final time out of the partial result.
HANDLE hProcess = (HANDLE)data[0].IntValue();
HANDLE hThread = (HANDLE)data[1].IntValue();
HANDLE stdOutPipe = (HANDLE)data[2].IntValue();
HANDLE stdErrPipe = (HANDLE)data[3].IntValue();
double finalTime = data[4].DoubleValue();
int returnCode;
String stdoutContent, stderrContent;
// Wait a short time for the child process to exit
DWORD waitResult = WaitForSingleObject(hProcess, 0.01);
if (waitResult == WAIT_TIMEOUT) {
// Child process not finished yet.
if (currentTime < finalTime) {
// Not timed out, either — keep waiting.
return false;
}
// We've waited too long. Time out.
stderrContent = "Timed out";
returnCode = 124 << 8; // (124 is status code used by `timeout` command)
} else {
// Child process completed successfully. Huzzah!
// Read output from pipes.
stdoutContent = readFromFd(stdOutPipe);
stderrContent = readFromFd(stdErrPipe);
// Get the exit code.
DWORD returnDword;
if (!GetExitCodeProcess(hProcess, &returnDword)) {
returnDword = (DWORD)-1; // Use -1 or another value to indicate that getting the exit code failed
}
returnCode = (int)returnDword;
}
// Close handles to the child process and its primary thread.
CloseHandle(hProcess);
CloseHandle(hThread);
// Close the remaining pipe handles.
CloseHandle(stdOutPipe);
CloseHandle(stdErrPipe);
// Return results.
*outStdout = stdoutContent;
*outStderr = stderrContent;
*outStatus = returnCode;
return true;
}
#else
// Helper function to read from file descriptor into string
String readFromFd(int fd, bool trimTrailingNewline=true) {
String output;
const int bufferSize = 1024;
char buffer[bufferSize];
ssize_t bytesRead;
bool trimmed = false;
while ((bytesRead = read(fd, buffer, bufferSize)) > 0) {
if (trimTrailingNewline and bytesRead < bufferSize and bytesRead > 0 and buffer[bytesRead-1] == '\n') {
// Efficiently trim \n or \r\n from the end of the buffer
bytesRead--;
if (bytesRead > 0 and buffer[bytesRead-1] == '\r') bytesRead--;
trimmed = true;
}
output += String(buffer, bytesRead);
}
if (trimTrailingNewline && !trimmed) {
// Not-so-efficiently trim our final string, in the case where our data happened
// to exactly align with the buffer size, so we couldn't know we were at the
// end of it to trim it above. (This is a rare edge case.)
int cut = 0;
if (output.LengthB() > 1 and output[-1] == '\n') {
cut = 1;
if (output.LengthB() > 2 and output[-2] == '\r') cut = 2;
}
if (cut) output = output.SubstringB(0, output.LengthB() - cut);
}
return output;
}
bool BeginExec(String cmd, double timeout, double currentTime, ValueList* outResult) {
// This is the initial entry into `exec`. Fork a subprocess to execute the
// given command, and return a partial result we can use to check on its progress.
// Create a pipe each for stdout and stderr.
// File descriptor 0 of each is the read end; element 1 is the write end.
int stdoutPipe[2];
int stderrPipe[2];
pipe(stdoutPipe);
pipe(stderrPipe);
pid_t pid = fork(); // Fork the process
if (pid == -1) {
return false; // Error("Failed to fork the child process.");
} else if (pid == 0) {
// Child process.
// Redirect stdout and stderr to our pipes, and then close the read ends.
dup2(stdoutPipe[1], STDOUT_FILENO);
dup2(stderrPipe[1], STDERR_FILENO);
close(stdoutPipe[0]);
close(stderrPipe[0]);
// Call the host environment's command processor. Or if the command
// is empty, then return a nonzero value iff the command processor exists.
const char* cmdPtr = cmd.empty() ? nullptr : cmd.c_str();
int cmdResult = std::system(cmdPtr);
cmdResult = WEXITSTATUS(cmdResult);
// All done! Exit the child process and return the result.
exit(cmdResult);
}
// Parent process.
// Close the write end of the pipes.
close(stdoutPipe[1]);
close(stderrPipe[1]);
// As our partial result, return a list with the pid, the two read pipes, and the final time.
ValueList data;
data.Add(Value(pid));
data.Add(Value(stdoutPipe[0]));
data.Add(Value(stderrPipe[0]));
data.Add(Value(currentTime + timeout));
*outResult = data;
return true;
}
bool FinishExec(ValueList data, double currentTime, String* outStdout, String* outStderr, int* outStatus) {
// Start by getting the pid, the two read pipes, and the final time out of the partial result.
int pid = data[0].IntValue();
int stdoutPipe = data[1].IntValue();
int stderrPipe = data[2].IntValue();
double finalTime = data[3].DoubleValue();
// Then, see if the child process has finished.
int returnCode;
String stdoutContent, stderrContent;
int waitResult = waitpid(pid, &returnCode, WUNTRACED | WNOHANG);
//std::cout << "waitpid returned " << waitResult << ", returnCode is " << returnCode << std::endl;
if (waitResult <= 0) {
// Child process not finished yet.
if (currentTime < finalTime) {
// Not timed out, either — keep waiting.
return false;
}
// We've waited too long. Time out.
stderrContent = "Timed out";
returnCode = 124 << 8; // (124 is status code used by `timeout` command)
} else {
// Child process completed successfully. Huzzah!
// Read output from pipes.
stdoutContent = readFromFd(stdoutPipe);
stderrContent = readFromFd(stderrPipe);
}
// Close our pipes.
close(stdoutPipe);
close(stderrPipe);
// Return results.
*outStdout = stdoutContent;
*outStderr = stderrContent;
*outStatus = WEXITSTATUS(returnCode);
return true;
}
#endif
} // end of namespace MiniScript