]>
Commit | Line | Data |
---|---|---|
df0e469f A |
1 | |
2 | #include <sys/wait.h> | |
3 | #include "ExecCLITool.h" | |
4 | #include <Security/utilities.h> | |
5 | #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h> | |
6 | ||
7 | #pragma mark -------------------- ExecCLITool implementation -------------------- | |
8 | ||
9 | ExecCLITool::ExecCLITool() : dataRead(NULL),dataLength(0),dataToWrite(NULL),dataToWriteLength(0) | |
10 | { | |
11 | stdinpipe[0]=0, stdinpipe[1]=0; | |
12 | stdoutpipe [0]=0, stdoutpipe [1]=0; | |
13 | } | |
14 | ||
15 | ExecCLITool::~ExecCLITool() | |
16 | { | |
17 | if (dataRead) | |
18 | free(dataRead); | |
19 | reset(); | |
20 | } | |
21 | ||
22 | int ExecCLITool::run(const char *toolPath, const char *toolEnvVar, ...) | |
23 | { | |
24 | try | |
25 | { | |
26 | reset(); | |
27 | initialize(); | |
28 | ||
29 | // try to run the tool | |
30 | switch (pid_t pid = fork()) | |
31 | { | |
32 | case 0: // child | |
33 | { | |
34 | VAArgList arglist; | |
35 | va_list params; | |
36 | va_start(params, toolEnvVar); | |
37 | arglist.set(toolPath,params); | |
38 | va_end(params); | |
39 | child(toolPath,toolEnvVar,arglist); | |
40 | } | |
41 | break; | |
42 | case -1: // error (in parent) | |
43 | UnixError::throwMe(); | |
44 | break; | |
45 | default: // parent | |
46 | parent(pid); | |
47 | break; | |
48 | } | |
49 | } | |
50 | catch (...) | |
51 | { | |
52 | closeAllPipes(); | |
53 | return errno; | |
54 | } | |
55 | ||
56 | closeAllPipes(); | |
57 | return 0; | |
58 | } | |
59 | ||
60 | void ExecCLITool::reset() | |
61 | { | |
62 | closeAllPipes(); | |
63 | #if 0 | |
64 | if (dataToWrite) | |
65 | { | |
66 | free(dataToWrite); | |
67 | dataToWrite = NULL; | |
68 | } | |
69 | dataToWriteLength = 0; | |
70 | #endif | |
71 | } | |
72 | ||
73 | void ExecCLITool::input(const char *data,unsigned int length) | |
74 | { | |
75 | if (dataToWrite) | |
76 | { | |
77 | ::free(dataToWrite); | |
78 | dataToWrite = NULL; | |
79 | } | |
80 | dataToWriteLength=length; | |
81 | if (!data) | |
82 | return; | |
83 | ||
84 | dataToWrite=reinterpret_cast<char *>(malloc(length)); | |
85 | ::memmove(dataToWrite, data, dataToWriteLength); | |
86 | } | |
87 | ||
88 | void ExecCLITool::input(CFStringRef theString, bool appendNULL) | |
89 | { | |
90 | // Used mainly for preserving UTF-8 passwords | |
91 | // hdiutil et al require the NULL to be sent as part of the password string from STDIN | |
92 | Boolean isExternalRepresentation = false; | |
93 | CFStringEncoding encoding = kCFStringEncodingUTF8; | |
94 | CFIndex usedBufLen = 0; | |
95 | UInt8 lossByte = 0; | |
96 | ||
97 | if (!theString) | |
98 | MacOSError::throwMe(paramErr); | |
99 | ||
100 | CFRange stringRange = CFRangeMake(0,CFStringGetLength(theString)); | |
101 | // Call once first just to get length | |
102 | CFIndex length = CFStringGetBytes(theString, stringRange, encoding, lossByte, | |
103 | isExternalRepresentation, NULL, 0, &usedBufLen); | |
104 | ||
105 | if (dataToWrite) | |
106 | ::free(dataToWrite); | |
107 | dataToWriteLength=usedBufLen; | |
108 | if (appendNULL) | |
109 | { | |
110 | dataToWriteLength++; | |
111 | dataToWriteLength++; | |
112 | } | |
113 | ||
114 | dataToWrite=reinterpret_cast<char *>(malloc(dataToWriteLength)); | |
115 | length = CFStringGetBytes(theString, stringRange, encoding, lossByte, isExternalRepresentation, | |
116 | reinterpret_cast<UInt8 *>(dataToWrite), dataToWriteLength, &usedBufLen); | |
117 | ||
118 | if (appendNULL) | |
119 | { | |
120 | dataToWrite[dataToWriteLength-1]=0; | |
121 | dataToWrite[dataToWriteLength]='\n'; | |
122 | } | |
123 | } | |
124 | ||
125 | void ExecCLITool::initialize() | |
126 | { | |
127 | dataLength = 0; // ignore any previous output on new run | |
128 | ||
129 | if (!dataRead) // Allocate buffer for child's STDOUT return | |
130 | { | |
131 | dataRead = (char *)malloc(256); | |
132 | if (!dataRead) | |
133 | UnixError::throwMe(); | |
134 | } | |
135 | ||
136 | // Create pipe to catch tool output | |
137 | if (pipe(stdoutpipe)) // for reading data from child into parent | |
138 | UnixError::throwMe(); | |
139 | ||
140 | if (pipe(stdinpipe)) // for writing data from parent to child | |
141 | UnixError::throwMe(); | |
142 | } | |
143 | ||
144 | void ExecCLITool::child(const char *toolPath, const char *toolEnvVar, VAArgList& arglist) | |
145 | { | |
146 | // construct path to tool | |
147 | try | |
148 | { | |
149 | char toolExecutable[PATH_MAX + 1]; | |
150 | const char *path = toolEnvVar ? getenv(toolEnvVar) : NULL; | |
151 | if (!path) | |
152 | path = toolPath; | |
153 | snprintf(toolExecutable, sizeof(toolExecutable), "%s", toolPath); | |
154 | ||
155 | close(stdoutpipe[0]); // parent read | |
156 | close(STDOUT_FILENO); | |
157 | if (dup2(stdoutpipe[1], STDOUT_FILENO) < 0) | |
158 | UnixError::throwMe(); | |
159 | close(stdoutpipe[1]); | |
160 | ||
161 | close(stdinpipe[1]); // parent write | |
162 | close(STDIN_FILENO); | |
163 | if (dup2(stdinpipe[0], STDIN_FILENO) < 0) | |
164 | UnixError::throwMe(); | |
165 | close(stdinpipe[0]); | |
166 | ||
167 | // std::cerr << "execl(\"" << toolExecutable << "\")" << std::endl; | |
168 | execv(toolPath, const_cast<char * const *>(arglist.get())); | |
169 | // std::cerr << "execl of " << toolExecutable << " failed, errno=" << errno << std::endl; | |
170 | } | |
171 | catch (...) | |
172 | { | |
173 | int err = errno; | |
174 | // closeAllPipes(); | |
175 | _exit(err); | |
176 | } | |
177 | ||
178 | // Unconditional suicide follows. | |
179 | _exit(1); | |
180 | } | |
181 | ||
182 | void ExecCLITool::parent(pid_t pid) | |
183 | { | |
184 | static const int timeout = 300; | |
185 | static const bool dontNeedToWait = false; | |
186 | ||
187 | close(stdinpipe[0]); // child read | |
188 | close(stdoutpipe[1]); // child write | |
189 | ||
190 | parentWriteInput(); | |
191 | ||
192 | parentReadOutput(); | |
193 | ||
194 | struct timespec rqtp = {0,}; | |
195 | rqtp.tv_nsec = 100000000; // 10^8 nanoseconds = 1/10th of a second | |
196 | for (int nn = timeout; nn > 0; nanosleep(&rqtp, NULL), nn--) | |
197 | { | |
198 | if (dontNeedToWait) | |
199 | break; | |
200 | int status; | |
201 | switch (waitpid(pid, &status, WNOHANG)) | |
202 | { | |
203 | case 0: // child still running | |
204 | break; | |
205 | case -1: // error | |
206 | switch (errno) | |
207 | { | |
208 | case EINTR: | |
209 | case EAGAIN: // transient | |
210 | continue; | |
211 | case ECHILD: // no such child (dead; already reaped elsewhere) | |
212 | CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION); | |
213 | default: | |
214 | UnixError::throwMe(); | |
215 | } | |
216 | default: | |
217 | // std::cerr << "waitpid succeeded, pid=" << rc << std::endl; | |
218 | return; | |
219 | } | |
220 | } | |
221 | } | |
222 | ||
223 | void ExecCLITool::parentReadOutput() | |
224 | { | |
225 | // parent - resulting blob comes in on stdoutpipe[0] | |
226 | unsigned int totalRead = 0; | |
227 | char buffer[kReadBufSize]; | |
228 | ||
229 | for (;;) | |
230 | { | |
231 | int thisRead = read(stdoutpipe[0], buffer, kReadBufSize); | |
232 | if (thisRead < 0) | |
233 | { | |
234 | if (errno==EINTR) // try some more | |
235 | continue; | |
236 | // std::cerr << "abnormal read end:" << errno << std::endl; | |
237 | break; | |
238 | } | |
239 | if (thisRead == 0) // normal termination | |
240 | { | |
241 | dataLength = totalRead; | |
242 | // std::cerr << "Normal read end" << std::endl; | |
243 | break; | |
244 | } | |
245 | ||
246 | // Resize dataRead if necessary | |
247 | if (kReadBufSize < (totalRead + (unsigned int)thisRead)) | |
248 | { | |
249 | uint32 newLen = dataLength + kReadBufSize; | |
250 | dataRead = (char *)realloc(dataRead, newLen); | |
251 | dataLength = newLen; | |
252 | } | |
253 | ||
254 | // Append the data to dataRead | |
255 | memmove(dataRead + totalRead, buffer, thisRead); | |
256 | totalRead += thisRead; | |
257 | } | |
258 | close(stdoutpipe[0]); | |
259 | ||
260 | } | |
261 | ||
262 | void ExecCLITool::parentWriteInput() | |
263 | { | |
264 | if (dataToWriteLength>0) | |
265 | { | |
266 | int bytesWritten = write(stdinpipe[1],dataToWrite,dataToWriteLength); | |
267 | if (bytesWritten < 0) | |
268 | UnixError::throwMe(); | |
269 | } | |
270 | close(stdinpipe[1]); | |
271 | } | |
272 | ||
273 | void ExecCLITool::closeAllPipes() | |
274 | { | |
275 | for (int ix=0;ix<2;ix++) | |
276 | if (stdoutpipe[ix]) | |
277 | { | |
278 | close(stdoutpipe[ix]); | |
279 | stdoutpipe[ix]=0; | |
280 | } | |
281 | ||
282 | for (int ix=0;ix<2;ix++) | |
283 | if (stdinpipe[ix]) | |
284 | { | |
285 | close(stdinpipe[ix]); | |
286 | stdinpipe[ix]=0; | |
287 | } | |
288 | } | |
289 | ||
290 | #pragma mark -------------------- VAArgList implementation -------------------- | |
291 | ||
292 | int VAArgList::set(const char *path,va_list params) | |
293 | { | |
294 | va_list params2; | |
295 | va_copy(params2, params); | |
296 | ||
297 | // Count up the number of arguments | |
298 | int nn = 1; | |
299 | while (va_arg(params,const char *) != NULL) | |
300 | nn++; | |
301 | argn = nn; | |
302 | argv = (ArgvArgPtr *)malloc((nn + 1) * sizeof(*argv)); | |
303 | if (argv == NULL) | |
304 | return 0; | |
305 | ||
306 | nn = 1; | |
307 | argv[0]=path; | |
308 | while ((argv[nn]=va_arg(params2,const char *)) != NULL) | |
309 | nn++; | |
310 | mSet = true; | |
311 | return 0; | |
312 | } | |
313 |