Library of Assembled Shared Sources
subprocess.cpp
Go to the documentation of this file.
1/** @file
2* @author Bram de Greve (bram@cocamware.com)
3* @author Tom De Muer (tom@cocamware.com)
4*
5* *** BEGIN LICENSE INFORMATION ***
6*
7* The contents of this file are subject to the Common Public Attribution License
8* Version 1.0 (the "License"); you may not use this file except in compliance with
9* the License. You may obtain a copy of the License at
10* http://lass.sourceforge.net/cpal-license. The License is based on the
11* Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12* use of software over a computer network and provide for limited attribution for
13* the Original Developer. In addition, Exhibit A has been modified to be consistent
14* with Exhibit B.
15*
16* Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17* WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18* language governing rights and limitations under the License.
19*
20* The Original Code is LASS - Library of Assembled Shared Sources.
21*
22* The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23* The Original Developer is the Initial Developer.
24*
25* All portions of the code written by the Initial Developer are:
26* Copyright (C) 2004-2013 the Initial Developer.
27* All Rights Reserved.
28*
29* Contributor(s):
30*
31* Alternatively, the contents of this file may be used under the terms of the
32* GNU General Public License Version 2 or later (the GPL), in which case the
33* provisions of GPL are applicable instead of those above. If you wish to allow use
34* of your version of this file only under the terms of the GPL and not to allow
35* others to use your version of this file under the CPAL, indicate your decision by
36* deleting the provisions above and replace them with the notice and other
37* provisions required by the GPL License. If you do not delete the provisions above,
38* a recipient may use your version of this file under either the CPAL or the GPL.
39*
40* *** END LICENSE INFORMATION ***
41*/
42
43#include "lass_common.h"
44#include "subprocess.h"
45#include "wchar_support.h"
46
47#if LASS_PLATFORM_TYPE == LASS_PLATFORM_TYPE_WIN32
48
50#define NOMINMAX
51#define WIN32_LEAN_AND_MEAN
52#include <Windows.h>
53
54namespace lass
55{
56namespace util
57{
58namespace impl
59{
60
61class SubprocessImpl
62{
63public:
64 typedef experimental::Subprocess::TArgs TArgs;
65 typedef experimental::Subprocess::TWArgs TWArgs;
66
67 SubprocessImpl(const TArgs& args)
68 {
69 for (TArgs::const_iterator i = args.begin(); i != args.end(); ++i)
70 {
71 wargs_.push_back(util::utf8ToWchar(*i));
72 }
73 }
74
75 SubprocessImpl(const TWArgs& wargs):
76 wargs_(wargs)
77 {
78 }
79
80 ~SubprocessImpl()
81 {
82 ::CloseHandle(pi_.hProcess);
83 ::CloseHandle(pi_.hThread);
84 }
85
86 void run()
87 {
88 if (wargs_.empty())
89 {
90 LASS_THROW("You need to specify at least one argument: the binary to start.");
91 }
92
93 ZeroMemory(&si_, sizeof(si_));
94 si_.cb = sizeof(si_);
95 si_.dwFlags = STARTF_USESHOWWINDOW;
96 si_.wShowWindow = SW_HIDE;
97 ZeroMemory(&pi_, sizeof(pi_));
98
99 std::vector<wchar_t> cmd;
100 makeCommandline(cmd);
101
102 LASS_ENFORCE_WINAPI(::CreateProcessW(wargs_[0].c_str(), &cmd[0], 0, 0, FALSE, 0, 0, 0, &si_, &pi_));
103 }
104
105 void sendSignal(int /*signal*/)
106 {
107 // not implemented;
108 LASS_ASSERT(false);
109 }
110
111 void kill()
112 {
113 ::TerminateProcess(pi_.hProcess, 0xffffffff);
114 }
115
116 bool checkExitCode(int& exitCode, bool join) const
117 {
118 if (join && ::WaitForSingleObject(pi_.hProcess, INFINITE) == WAIT_FAILED)
119 {
120 const unsigned err = lass_GetLastError();
121 LASS_THROW("Failed to wait for subprocess: (" << err << ") " << lass_FormatMessage(err));
122 }
123 DWORD value;
124 LASS_ENFORCE_WINAPI(::GetExitCodeProcess(pi_.hProcess, &value));
125 if (value == STILL_ACTIVE)
126 {
127 return false;
128 }
129 exitCode = static_cast<int>(value);
130 return true;
131 }
132
133
134private:
135
136 void makeCommandline(std::vector<wchar_t>& cmd) const
137 {
138 // this is jolly good fun ...
139 cmd.clear();
140 for (TWArgs::const_iterator i = wargs_.begin(); i != wargs_.end(); ++i)
141 {
142 if (i != wargs_.begin())
143 {
144 cmd.push_back(L' ');
145 }
146 const std::wstring& arg = *i;
147 const bool mustQuote = arg.empty() || arg.find_first_of(L" \t") != std::wstring::npos;
148 if (mustQuote)
149 {
150 cmd.push_back(L'"');
151 }
152 size_t numBackslashes = 0;
153 for (size_t k = 0, n = arg.size(); k < n; ++k)
154 {
155 switch (arg[k])
156 {
157 case L'\\':
158 ++numBackslashes;
159 break;
160 case L'"':
161 // double preceding backslashes, and add one more to escape me
162 numBackslashes = 2 * numBackslashes + 1;
163 // and fall through
164 default:
165 cmd.insert(cmd.end(), numBackslashes, L'\\');
166 numBackslashes = 0;
167 cmd.push_back(arg[k]);
168 }
169 }
170 cmd.insert(cmd.end(), numBackslashes, L'\\'); // remaining backslashes ...
171 if (mustQuote)
172 {
173 cmd.insert(cmd.end(), numBackslashes, L'\\'); // must be doubled, but don't escape theq final quote.
174 cmd.push_back(L'"');
175 }
176 }
177 cmd.push_back(0);
178 }
179
180 STARTUPINFOW si_;
181 PROCESS_INFORMATION pi_;
182 TWArgs wargs_;
183};
184
185}
186}
187}
188
189#else
190
191#include <string.h> // strdup
192#include <signal.h>
193#include <spawn.h> // posix_spawn
194#include <sys/wait.h> // waitpid
195
196extern char **environ;
197
198namespace lass
199{
200namespace util
201{
202namespace impl
203{
204
205class SubprocessImpl
206{
207public:
208 typedef experimental::Subprocess::TArgs TArgs;
209 typedef experimental::Subprocess::TWArgs TWArgs;
210
211 SubprocessImpl(const TArgs& args):
212 args_(args),
213 pid_(0)
214 {
215 }
216
217 SubprocessImpl(const TWArgs& wargs):
218 pid_(0)
219 {
220 for (TWArgs::const_iterator i = wargs.begin(); i != wargs.end(); ++i)
221 {
222 args_.push_back(util::wcharToUtf8(*i));
223 }
224 }
225
226 ~SubprocessImpl()
227 {
228 if (pid_)
229 {
230 // last chance to waitpid to avoid zombiestate.
231 int dummy;
232 checkExitCode(dummy, false);
233 }
234 }
235
236 void run()
237 {
238 LASS_ASSERT(!pid_);
239
240 if (args_.empty())
241 {
242 LASS_THROW("You need to specify at least one argument: the binary to start.");
243 }
244
245 const size_t argc = args_.size();
246 ScopedArgv argv(argc);
247 for (size_t k = 0; k < argc; ++k)
248 {
249 argv[k] = ::strdup(args_[k].c_str());
250 }
251
252 LASS_ENFORCE_CLIB_RC(posix_spawnp(&pid_, argv[0], 0, 0, argv.ptr(), environ));
253 }
254
255 void sendSignal(int signal)
256 {
257 if (pid_)
258 {
259 ::kill(pid_, signal);
260 }
261 }
262
263 void kill()
264 {
265 sendSignal(SIGKILL);
266 }
267
268 bool checkExitCode(int& exitCode, bool join)
269 {
270 int status;
271 const int options = join ? 0 : WNOHANG;
272
273 if (!pid_)
274 {
275 return false; // not running yet, or already has exit code.
276 }
277
278 const pid_t pid = LASS_ENFORCE_CLIB(waitpid(pid_, &status, options));
279 if (pid == 0)
280 {
281 return false; // still running
282 }
283 LASS_ASSERT(pid == pid_);
284
285 if (WIFSIGNALED(status))
286 {
287 exitCode = -(WTERMSIG(status));
288 pid_ = 0;
289 return true;
290 }
291 else if (WIFEXITED(status))
292 {
293 exitCode = WEXITSTATUS(status);
294 pid_ = 0;
295 return true;
296 }
297 else
298 {
299 LASS_THROW("Internal error"); // uh-oh
300 }
301 }
302
303private:
304
305 class ScopedArgv
306 {
307 public:
308 ScopedArgv(size_t argc)
309 {
310 argv_.resize(argc + 1, 0);
311 }
312 ~ScopedArgv()
313 {
314 for (TArgv::reverse_iterator i = argv_.rbegin(), end = argv_.rend(); i != end; ++i)
315 {
316 ::free(*i);
317 }
318 }
319 char*& operator[](size_t index)
320 {
321 return argv_[index];
322 }
323 char** ptr()
324 {
325 return &argv_[0];
326 }
327 private:
328 typedef std::vector<char*> TArgv;
329 TArgv argv_;
330 };
331
332 TArgs args_;
333 pid_t pid_;
334};
335
336}
337}
338}
339
340#endif
341
342// --- Subprocess ------------------------------------------------------------------------------------------------------
343
344namespace lass
345{
346namespace util
347{
348namespace experimental
349{
350
351Subprocess::Subprocess(const TArgs& args) :
352 pimpl_(new impl::SubprocessImpl(args)),
353 exitCode_(0),
354 hasExitCode_(false),
355 isStarted_(false),
356 isDetached_(false)
357{
358}
359
360
361Subprocess::Subprocess(const TWArgs& args) :
362 pimpl_(new impl::SubprocessImpl(args)),
363 exitCode_(0),
364 hasExitCode_(false),
365 isStarted_(false),
366 isDetached_(false)
367{
368}
369
370
371Subprocess::~Subprocess()
372{
373 try
374 {
375 if (!isDetached_)
376 {
377 pimpl_->kill();
378 }
379 }
380 catch (const std::exception& error)
381 {
382 std::cerr << "[LASS RUN MSG] UNDEFINED BEHAVIOUR WARNING: " << error.what() << std::endl;
383 }
384 delete pimpl_;
385}
386
387
388void Subprocess::run()
389{
390 if (isStarted_)
391 {
392 LASS_THROW("Subprocess cannot be run more than once.");
393 }
394 pimpl_->run();
395 isStarted_ = true;
396}
397
398
399int Subprocess::join()
400{
401 enforceStarted();
402 if (!checkExitCode(true))
403 {
404 LASS_THROW("Failed to join subprocess.");
405 }
406 LASS_ASSERT(hasExitCode_);
407 return exitCode_;
408}
409
410
411void Subprocess::detach()
412{
413 isDetached_ = true;
414}
415
416
417void Subprocess::sendSignal(int signal)
418{
419 pimpl_->sendSignal(signal);
420}
421
422
423void Subprocess::kill()
424{
425 if (!isStarted_ || !isRunning())
426 {
427 return;
428 }
429 enforceNotDetached();
430 pimpl_->kill();
431}
432
433
434bool Subprocess::isRunning() const
435{
436 return isStarted_ && !checkExitCode();
437}
438
439
440bool Subprocess::isDetached() const
441{
442 return isDetached_;
443}
444
445
446int Subprocess::exitCode() const
447{
448 enforceStarted();
449 if (!checkExitCode())
450 {
451 LASS_THROW("Subprocess is still running.");
452 }
453 LASS_ASSERT(hasExitCode_);
454 return exitCode_;
455}
456
457
458void Subprocess::enforceStarted() const
459{
460 if (!isStarted_)
461 {
462 LASS_THROW("Subprocess has not started yet.");
463 }
464}
465
466
467void Subprocess::enforceNotDetached() const
468{
469 if (isDetached_)
470 {
471 LASS_THROW("Subprocess is detached.");
472 }
473}
474
475
476bool Subprocess::checkExitCode(bool join) const
477{
478 if (hasExitCode_)
479 {
480 return true;
481 }
482 enforceNotDetached();
483 if (pimpl_->checkExitCode(exitCode_, join))
484 {
485 hasExitCode_ = true;
486 return true;
487 }
488 return false;
489}
490
491}
492}
493}
494
495// EOF
general utility, debug facilities, ...
Library for Assembled Shared Sources.
Definition config.h:53