1 //
2 // Boost.Process
3 //
4 // Copyright (c) 2006 Julio M. Merino Vidal.
5 //
6 // Distributed under the Boost Software License, Version 1.0.
7 // (See accompanying file LICENSE_1_0.txt or copy at
8 // http://www.boost.org/LICENSE_1_0.txt.)
9 //
10
11 //!
12 //! \file boost/process/operations.hpp
13 //!
14 //! Provides miscellaneous free functions.
15 //!
16
17 #if !defined(BOOST_PROCESS_OPERATIONS_HPP)
18 /** \cond */
19 #define BOOST_PROCESS_OPERATIONS_HPP
20 /** \endcond */
21
22 #include <boost/process/config.hpp>
23
24 #if defined(BOOST_PROCESS_POSIX_API)
25 # include <boost/process/detail/posix_ops.hpp>
26 #elif defined(BOOST_PROCESS_WIN32_API)
27 # include <tchar.h>
28 # include <windows.h>
29 # include <boost/process/detail/win32_ops.hpp>
30 #else
31 # error "Unsupported platform."
32 #endif
33
34 #include <string>
35 #include <vector>
36
37 #include <boost/assert.hpp>
38 #include <boost/process/child.hpp>
39 #include <boost/process/detail/file_handle.hpp>
40 #include <boost/process/exceptions.hpp>
41 #include <boost/throw_exception.hpp>
42
43 namespace boost {
44 namespace process {
45
46 // ------------------------------------------------------------------------
47
48 //!
49 //! \brief Locates a program in the path.
50 //!
51 //! Locates the executable program \a file in all the directory components
52 //! specified in \a path. If \a path is empty, the value of the PATH
53 //! environment variable is used.
54 //!
55 //! The path variable is interpreted following the same conventions used
56 //! to parse the PATH environment variable in the underlying platform.
57 //!
58 //! \throw not_found_error<std::string> If the file cannot be found
59 //! in the path.
60 //!
61 inline
62 std::string
find_executable_in_path(const std::string & file,std::string path="")63 find_executable_in_path(const std::string& file, std::string path = "")
64 {
65 #if defined(BOOST_PROCESS_POSIX_API)
66 BOOST_ASSERT(file.find('/') == std::string::npos);
67 #elif defined(BOOST_PROCESS_WIN32_API)
68 BOOST_ASSERT(file.find('\\') == std::string::npos);
69 #endif
70
71 std::string result;
72
73 #if defined(BOOST_PROCESS_POSIX_API)
74 if (path.empty()) {
75 const char* envpath = ::getenv("PATH");
76 if (envpath == NULL)
77 boost::throw_exception(not_found_error< std::string >
78 ("Cannot locate " + file + " in path; "
79 "error retrieving PATH's value", file));
80 path = envpath;
81 }
82 BOOST_ASSERT(!path.empty());
83
84 std::string::size_type pos1 = 0, pos2;
85 do {
86 pos2 = path.find(':', pos1);
87 std::string dir = path.substr(pos1, pos2 - pos1);
88 std::string f = dir + '/' + file;
89 if (::access(f.c_str(), X_OK) == 0)
90 result = f;
91 pos1 = pos2 + 1;
92 } while (pos2 != std::string::npos && result.empty());
93 #elif defined(BOOST_PROCESS_WIN32_API)
94 const char* exts[] = { "", ".exe", ".com", ".bat", NULL };
95 const char** ext = exts;
96 while (*ext != NULL) {
97 TCHAR buf[MAX_PATH];
98 TCHAR* dummy;
99 DWORD len = ::SearchPath(path.empty() ? NULL : TEXT(path.c_str()),
100 TEXT(file.c_str()), TEXT(*ext), MAX_PATH,
101 buf, &dummy);
102 BOOST_ASSERT(len < MAX_PATH);
103 if (len > 0) {
104 result = buf;
105 break;
106 }
107 ext++;
108 }
109 #endif
110
111 if (result.empty())
112 boost::throw_exception(not_found_error< std::string >
113 ("Cannot locate " + file + " in path", file));
114
115 return result;
116 }
117
118 // ------------------------------------------------------------------------
119
120 inline
121 std::string
executable_to_progname(const std::string & exe)122 executable_to_progname(const std::string& exe)
123 {
124 std::string::size_type tmp;
125 std::string::size_type begin = 0;
126 std::string::size_type end = std::string::npos;
127
128 #if defined(BOOST_PROCESS_POSIX_API)
129 tmp = exe.rfind('/');
130 #elif defined(BOOST_PROCESS_WIN32_API)
131 tmp = exe.rfind('\\');
132 if (tmp == std::string::npos)
133 tmp = exe.rfind('/');
134 #endif
135 if (tmp != std::string::npos)
136 begin = tmp + 1;
137
138 #if defined(BOOST_PROCESS_WIN32_API)
139 if (exe.length() > 4 &&
140 (exe.substr(exe.length() - 4) == ".exe" ||
141 exe.substr(exe.length() - 4) == ".com" ||
142 exe.substr(exe.length() - 4) == ".bat"))
143 end = exe.length() - 4;
144 #endif
145
146 return exe.substr(begin, end);
147 }
148
149 // ------------------------------------------------------------------------
150
151 //!
152 //! \brief Starts a new child process.
153 //!
154 //! Launches a new process based on the binary image specified by the
155 //! executable, the set of arguments to be passed to it and several
156 //! parameters that describe the execution context.
157 //!
158 //! \remark <b>Blocking remarks</b>: This function may block if the device
159 //! holding the executable blocks when loading the image. This might
160 //! happen if, e.g., the binary is being loaded from a network share.
161 //!
162 //! \return A handle to the new child process.
163 //!
164 template< class Executable, class Arguments, class Context >
165 child
launch(const Executable & exe,const Arguments & args,const Context & ctx)166 launch(const Executable& exe, const Arguments& args, const Context& ctx)
167 {
168 using detail::stream_info;
169
170 child::id_type pid;
171 detail::file_handle fhstdin, fhstdout, fhstderr;
172
173 BOOST_ASSERT(!args.empty());
174
175 // Validate execution context.
176 // XXX Should this be a 'validate()' method in it?
177 BOOST_ASSERT(!ctx.m_work_directory.empty());
178
179 #if defined(BOOST_PROCESS_POSIX_API)
180 detail::info_map infoin, infoout;
181
182 if (ctx.m_stdin_behavior.get_type() != stream_behavior::close) {
183 stream_info si = stream_info(ctx.m_stdin_behavior, false);
184 infoin.insert(detail::info_map::value_type(STDIN_FILENO, si));
185 }
186
187 if (ctx.m_stdout_behavior.get_type() != stream_behavior::close) {
188 stream_info si = stream_info(ctx.m_stdout_behavior, true);
189 infoout.insert(detail::info_map::value_type(STDOUT_FILENO, si));
190 }
191
192 if (ctx.m_stderr_behavior.get_type() != stream_behavior::close) {
193 stream_info si = stream_info(ctx.m_stderr_behavior, true);
194 infoout.insert(detail::info_map::value_type(STDERR_FILENO, si));
195 }
196
197 detail::posix_setup s;
198 s.m_work_directory = ctx.m_work_directory;
199
200 pid = detail::posix_start(exe, args, ctx.m_environment, infoin,
201 infoout, s);
202
203 if (ctx.m_stdin_behavior.get_type() == stream_behavior::capture) {
204 fhstdin = posix_info_locate_pipe(infoin, STDIN_FILENO, false);
205 BOOST_ASSERT(fhstdin.is_valid());
206 }
207
208 if (ctx.m_stdout_behavior.get_type() == stream_behavior::capture) {
209 fhstdout = posix_info_locate_pipe(infoout, STDOUT_FILENO, true);
210 BOOST_ASSERT(fhstdout.is_valid());
211 }
212
213 if (ctx.m_stderr_behavior.get_type() == stream_behavior::capture) {
214 fhstderr = posix_info_locate_pipe(infoout, STDERR_FILENO, true);
215 BOOST_ASSERT(fhstderr.is_valid());
216 }
217 #elif defined(BOOST_PROCESS_WIN32_API)
218 stream_info behin = stream_info(ctx.m_stdin_behavior, false);
219 if (behin.m_type == stream_info::use_pipe)
220 fhstdin = behin.m_pipe->wend();
221 stream_info behout = stream_info(ctx.m_stdout_behavior, true);
222 if (behout.m_type == stream_info::use_pipe)
223 fhstdout = behout.m_pipe->rend();
224 stream_info beherr = stream_info(ctx.m_stderr_behavior, true);
225 if (beherr.m_type == stream_info::use_pipe)
226 fhstderr = beherr.m_pipe->rend();
227
228 STARTUPINFO si;
229 ::ZeroMemory(&si, sizeof(si));
230 si.cb = sizeof(si);
231
232 detail::win32_setup s;
233 s.m_work_directory = ctx.m_work_directory;
234 s.m_startupinfo = &si;
235
236 PROCESS_INFORMATION pi =
237 detail::win32_start(exe, args, ctx.m_environment,
238 behin, behout, beherr, s);
239
240 pid = pi.dwProcessId;
241 #endif
242
243 return child(pid, fhstdin, fhstdout, fhstderr);
244 }
245
246 // ------------------------------------------------------------------------
247
248 //!
249 //! \brief Launches a shell-based command.
250 //!
251 //! Executes the given command through the default system shell. The
252 //! command is subject to pattern expansion, redirection and pipelining.
253 //! The shell is launched as described by the parameters in the execution
254 //! context.
255 //!
256 //! This function behaves similarly to the system(3) system call. In a
257 //! POSIX system, the command is fed to /bin/sh whereas under a Win32
258 //! system, it is fed to cmd.exe. It is difficult to write portable
259 //! commands as the first parameter, but this function comes in handy in
260 //! multiple situations.
261 //!
262 template< class Context >
263 inline
264 child
launch_shell(const std::string & command,const Context & ctx)265 launch_shell(const std::string& command, const Context& ctx)
266 {
267 std::string exe;
268 std::vector< std::string > args;
269
270 #if defined(BOOST_PROCESS_POSIX_API)
271 exe = "/bin/sh";
272 args.push_back("sh");
273 args.push_back("-c");
274 args.push_back(command);
275 #elif defined(BOOST_PROCESS_WIN32_API)
276 TCHAR buf[MAX_PATH];
277 UINT res = ::GetSystemDirectory(buf, MAX_PATH);
278 if (res == 0)
279 boost::throw_exception
280 (system_error("boost::process::launch_shell",
281 "GetWindowsDirectory failed", ::GetLastError()));
282 BOOST_ASSERT(res < MAX_PATH);
283
284 exe = std::string(buf) + "\\cmd.exe";
285 args.push_back("cmd");
286 args.push_back("/c");
287 args.push_back(command);
288 #endif
289
290 return launch(exe, args, ctx);
291 }
292
293 // ------------------------------------------------------------------------
294
295 //!
296 //! \brief Launches a pipelined set of child processes.
297 //!
298 //! Given a collection of pipeline_entry objects describing how to launch
299 //! a set of child processes, spawns them all and connects their inputs and
300 //! outputs in a way that permits pipelined communication.
301 //!
302 //! \pre Let 1..N be the processes in the collection: the input behavior of
303 //! the 2..N processes must be set to close_stream().
304 //! \pre Let 1..N be the processes in the collection: the output behavior of
305 //! the 1..N-1 processes must be set to close_stream().
306 //!
307 //! \remark <b>Blocking remarks</b>: This function may block if the
308 //! device holding the executable of one of the entries
309 //! blocks when loading the image. This might happen if, e.g.,
310 //! the binary is being loaded from a network share.
311 //!
312 //! \return A set of Child objects that represent all the processes spawned
313 //! by this call. You should use wait_children() to wait for their
314 //! termination.
315 //!
316 template< class Entries >
317 children
launch_pipeline(const Entries & entries)318 launch_pipeline(const Entries& entries)
319 {
320 using detail::stream_info;
321
322 BOOST_ASSERT(entries.size() >= 2);
323
324 // Method's result value.
325 children cs;
326
327 // Convenience variables to avoid clutter below.
328 detail::file_handle fhinvalid;
329
330 // The pipes used to connect the pipeline's internal process.
331 boost::scoped_array< detail::pipe > pipes
332 (new detail::pipe[entries.size() - 1]);
333
334 #if defined(BOOST_PROCESS_POSIX_API)
335 using detail::info_map;
336
337 // Configure and spawn the pipeline's first process.
338 {
339 typename Entries::size_type i = 0;
340 const typename Entries::value_type::context_type& ctx =
341 entries[i].m_context;
342
343 info_map infoin, infoout;
344
345 if (ctx.m_stdin_behavior.get_type() != stream_behavior::close) {
346 stream_info si = stream_info(ctx.m_stdin_behavior, false);
347 infoin.insert(info_map::value_type(STDIN_FILENO, si));
348 }
349
350 // XXX Simplify when we have a use_handle stream_behavior.
351 BOOST_ASSERT(ctx.m_stdout_behavior.get_type() ==
352 stream_behavior::close);
353 stream_info si2(close_stream(), true);
354 si2.m_type = stream_info::use_handle;
355 si2.m_handle = pipes[i].wend().disown();
356 infoout.insert(info_map::value_type(STDOUT_FILENO, si2));
357
358 if (ctx.m_stderr_behavior.get_type() != stream_behavior::close) {
359 stream_info si = stream_info(ctx.m_stderr_behavior, true);
360 infoout.insert(info_map::value_type(STDERR_FILENO, si));
361 }
362
363 detail::posix_setup s;
364 s.m_work_directory = ctx.m_work_directory;
365
366 pid_t pid = detail::posix_start(entries[i].m_executable,
367 entries[i].m_arguments,
368 ctx.m_environment,
369 infoin, infoout, s);
370
371 detail::file_handle fhstdin;
372
373 if (ctx.m_stdin_behavior.get_type() == stream_behavior::capture) {
374 fhstdin = posix_info_locate_pipe(infoin, STDIN_FILENO, false);
375 BOOST_ASSERT(fhstdin.is_valid());
376 }
377
378 cs.push_back(child(pid, fhstdin, fhinvalid, fhinvalid));
379 }
380
381 // Configure and spawn the pipeline's internal processes.
382 for (typename Entries::size_type i = 1; i < entries.size() - 1; i++) {
383 const typename Entries::value_type::context_type& ctx =
384 entries[i].m_context;
385 info_map infoin, infoout;
386
387 BOOST_ASSERT(ctx.m_stdin_behavior.get_type() ==
388 stream_behavior::close);
389 stream_info si1(close_stream(), false);
390 si1.m_type = stream_info::use_handle;
391 si1.m_handle = pipes[i - 1].rend().disown();
392 infoin.insert(info_map::value_type(STDIN_FILENO, si1));
393
394 BOOST_ASSERT(ctx.m_stdout_behavior.get_type() ==
395 stream_behavior::close);
396 stream_info si2(close_stream(), true);
397 si2.m_type = stream_info::use_handle;
398 si2.m_handle = pipes[i].wend().disown();
399 infoout.insert(info_map::value_type(STDOUT_FILENO, si2));
400
401 if (ctx.m_stderr_behavior.get_type() != stream_behavior::close) {
402 stream_info si = stream_info(ctx.m_stderr_behavior, true);
403 infoout.insert(info_map::value_type(STDERR_FILENO, si));
404 }
405
406 detail::posix_setup s;
407 s.m_work_directory = ctx.m_work_directory;
408
409 pid_t pid = detail::posix_start(entries[i].m_executable,
410 entries[i].m_arguments,
411 ctx.m_environment,
412 infoin, infoout, s);
413
414 cs.push_back(child(pid, fhinvalid, fhinvalid, fhinvalid));
415 }
416
417 // Configure and spawn the pipeline's last process.
418 {
419 typename Entries::size_type i = entries.size() - 1;
420 const typename Entries::value_type::context_type& ctx =
421 entries[i].m_context;
422
423 info_map infoin, infoout;
424
425 BOOST_ASSERT(ctx.m_stdin_behavior.get_type() ==
426 stream_behavior::close);
427 stream_info si1(close_stream(), true);
428 si1.m_type = stream_info::use_handle;
429 si1.m_handle = pipes[i - 1].rend().disown();
430 infoin.insert(info_map::value_type(STDIN_FILENO, si1));
431
432 if (ctx.m_stdout_behavior.get_type() != stream_behavior::close) {
433 stream_info si = stream_info(ctx.m_stdout_behavior, true);
434 infoout.insert(info_map::value_type(STDOUT_FILENO, si));
435 }
436
437 if (ctx.m_stderr_behavior.get_type() != stream_behavior::close) {
438 stream_info si = stream_info(ctx.m_stderr_behavior, true);
439 infoout.insert(info_map::value_type(STDERR_FILENO, si));
440 }
441
442 detail::posix_setup s;
443 s.m_work_directory = ctx.m_work_directory;
444
445 pid_t pid = detail::posix_start(entries[i].m_executable,
446 entries[i].m_arguments,
447 ctx.m_environment,
448 infoin, infoout, s);
449
450 detail::file_handle fhstdout, fhstderr;
451
452 if (ctx.m_stdout_behavior.get_type() == stream_behavior::capture) {
453 fhstdout = posix_info_locate_pipe(infoout, STDOUT_FILENO, true);
454 BOOST_ASSERT(fhstdout.is_valid());
455 }
456
457 if (ctx.m_stderr_behavior.get_type() == stream_behavior::capture) {
458 fhstderr = posix_info_locate_pipe(infoout, STDERR_FILENO, true);
459 BOOST_ASSERT(fhstderr.is_valid());
460 }
461
462 cs.push_back(child(pid, fhinvalid, fhstdout, fhstderr));
463 }
464 #elif defined(BOOST_PROCESS_WIN32_API)
465 // Process context configuration.
466 detail::win32_setup s;
467 STARTUPINFO si;
468 s.m_startupinfo = &si;
469
470 // Configure and spawn the pipeline's first process.
471 {
472 typename Entries::size_type i = 0;
473 const typename Entries::value_type::context_type& ctx =
474 entries[i].m_context;
475
476 stream_info sii = stream_info(ctx.m_stdin_behavior, false);
477 detail::file_handle fhstdin;
478 if (sii.m_type == stream_info::use_pipe)
479 fhstdin = sii.m_pipe->wend();
480
481 // XXX Simplify when we have a use_handle stream_behavior.
482 BOOST_ASSERT(ctx.m_stdout_behavior.get_type() ==
483 stream_behavior::close);
484 stream_info sio(close_stream(), true);
485 sio.m_type = stream_info::use_handle;
486 sio.m_handle = pipes[i].wend().disown();
487
488 stream_info sie(ctx.m_stderr_behavior, true);
489
490 s.m_work_directory = ctx.m_work_directory;
491
492 ::ZeroMemory(&si, sizeof(si));
493 si.cb = sizeof(si);
494 PROCESS_INFORMATION pi = detail::win32_start
495 (entries[i].m_executable, entries[i].m_arguments,
496 ctx.m_environment, sii, sio, sie, s);
497
498 cs.push_back(child(pi.dwProcessId, fhstdin, fhinvalid, fhinvalid));
499 }
500
501 // Configure and spawn the pipeline's internal processes.
502 for (typename Entries::size_type i = 1; i < entries.size() - 1; i++) {
503 const typename Entries::value_type::context_type& ctx =
504 entries[i].m_context;
505
506 BOOST_ASSERT(ctx.m_stdin_behavior.get_type() ==
507 stream_behavior::close);
508 stream_info sii(close_stream(), false);
509 sii.m_type = stream_info::use_handle;
510 sii.m_handle = pipes[i - 1].rend().disown();
511
512 stream_info sio(close_stream(), true);
513 sio.m_type = stream_info::use_handle;
514 sio.m_handle = pipes[i].wend().disown();
515
516 stream_info sie(ctx.m_stderr_behavior, true);
517
518 s.m_work_directory = ctx.m_work_directory;
519
520 ::ZeroMemory(&si, sizeof(si));
521 si.cb = sizeof(si);
522 PROCESS_INFORMATION pi = detail::win32_start
523 (entries[i].m_executable, entries[i].m_arguments,
524 ctx.m_environment, sii, sio, sie, s);
525
526 cs.push_back(child(pi.dwProcessId, fhinvalid, fhinvalid, fhinvalid));
527 }
528
529 // Configure and spawn the pipeline's last process.
530 {
531 typename Entries::size_type i = entries.size() - 1;
532 const typename Entries::value_type::context_type& ctx =
533 entries[i].m_context;
534
535 BOOST_ASSERT(ctx.m_stdin_behavior.get_type() ==
536 stream_behavior::close);
537 stream_info sii(close_stream(), true);
538 sii.m_type = stream_info::use_handle;
539 sii.m_handle = pipes[i - 1].rend().disown();
540
541 detail::file_handle fhstdout, fhstderr;
542
543 stream_info sio(ctx.m_stdout_behavior, true);
544 if (sio.m_type == stream_info::use_pipe)
545 fhstdout = sio.m_pipe->rend();
546 stream_info sie(ctx.m_stderr_behavior, true);
547 if (sie.m_type == stream_info::use_pipe)
548 fhstderr = sie.m_pipe->rend();
549
550 s.m_work_directory = ctx.m_work_directory;
551
552 ::ZeroMemory(&si, sizeof(si));
553 si.cb = sizeof(si);
554 PROCESS_INFORMATION pi = detail::win32_start
555 (entries[i].m_executable, entries[i].m_arguments,
556 ctx.m_environment, sii, sio, sie, s);
557
558 cs.push_back(child(pi.dwProcessId, fhinvalid, fhstdout, fhstderr));
559 }
560 #endif
561
562 return cs;
563 }
564
565 // ------------------------------------------------------------------------
566
567 //!
568 //! \brief Waits for a collection of children to terminate.
569 //!
570 //! Given a collection of Child objects (such as std::vector< child > or
571 //! the convenience children type), waits for the termination of all of
572 //! them.
573 //!
574 //! \remark <b>Blocking remarks</b>: This call blocks if any of the
575 //! children processes in the collection has not finalized execution and
576 //! waits until it terminates.
577 //!
578 //! \return The exit status of the first process that returns an error
579 //! code or, if all of them executed correctly, the exit status
580 //! of the last process in the collection.
581 //!
582 template< class Children >
583 const status
wait_children(Children & cs)584 wait_children(Children& cs)
585 {
586 BOOST_ASSERT(cs.size() >= 2);
587
588 typename Children::iterator iter = cs.begin();
589 while (iter != cs.end()) {
590 const status s = (*iter).wait();
591 iter++;
592 if (iter == cs.end())
593 return s;
594 else if (!s.exited() || s.exit_status() != EXIT_SUCCESS) {
595 while (iter != cs.end()) {
596 (*iter).wait();
597 iter++;
598 }
599 return s;
600 }
601 }
602
603 BOOST_ASSERT(false);
604 return (*cs.begin()).wait();
605 }
606
607 // ------------------------------------------------------------------------
608
609 } // namespace process
610 } // namespace boost
611
612 #endif // !defined(BOOST_PROCESS_OPERATIONS_HPP)
613