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&lt;std::string&gt; 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