comparison third_party/boost/process/operations.hpp @ 0:add35537fdbb tip

Initial import
author irh <ian.r.hobson@gmail.com>
date Thu, 25 Aug 2011 11:05:55 +0100
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:add35537fdbb
1 //
2 // Boost.Process
3 // ~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2006, 2007 Julio M. Merino Vidal
6 // Copyright (c) 2008 Ilya Sokolov, Boris Schaeling
7 // Copyright (c) 2009 Boris Schaeling
8 // Copyright (c) 2010 Felipe Tanus, Boris Schaeling
9 //
10 // Distributed under the Boost Software License, Version 1.0. (See accompanying
11 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
12 //
13
14 /**
15 * \file boost/process/operations.hpp
16 *
17 * Provides miscellaneous free functions.
18 */
19
20 #ifndef BOOST_PROCESS_OPERATIONS_HPP
21 #define BOOST_PROCESS_OPERATIONS_HPP
22
23 #include <boost/process/config.hpp>
24
25 #if defined(BOOST_POSIX_API)
26 # include <boost/process/detail/posix_helpers.hpp>
27 # include <utility>
28 # include <cstddef>
29 # include <stdlib.h>
30 # include <unistd.h>
31 # include <fcntl.h>
32 # if defined(__CYGWIN__)
33 # include <boost/scoped_array.hpp>
34 # include <sys/cygwin.h>
35 # endif
36 #elif defined(BOOST_WINDOWS_API)
37 # include <boost/process/detail/windows_helpers.hpp>
38 # include <boost/scoped_array.hpp>
39 # include <boost/shared_array.hpp>
40 # include <windows.h>
41 #else
42 # error "Unsupported platform."
43 #endif
44
45 #include <boost/process/child.hpp>
46 #include <boost/process/context.hpp>
47 #include <boost/process/stream_id.hpp>
48 #include <boost/process/stream_ends.hpp>
49 #include <boost/process/handle.hpp>
50 #include <boost/filesystem/path.hpp>
51 #include <boost/algorithm/string/predicate.hpp>
52 #include <boost/system/system_error.hpp>
53 #include <boost/throw_exception.hpp>
54 #include <boost/assert.hpp>
55 #include <string>
56 #include <vector>
57 #include <map>
58 #include <utility>
59
60 namespace boost {
61 namespace process {
62
63 /**
64 * Locates the executable program \a file in all the directory components
65 * specified in \a path. If \a path is empty, the value of the PATH
66 * environment variable is used.
67 *
68 * The path variable is interpreted following the same conventions used
69 * to parse the PATH environment variable in the underlying platform.
70 *
71 * \throw boost::filesystem::filesystem_error If the file cannot be found
72 * in the path.
73 */
74 inline std::string find_executable_in_path(const std::string &file,
75 std::string path = "")
76 {
77 #if defined(BOOST_POSIX_API)
78 BOOST_ASSERT(file.find('/') == std::string::npos);
79 #elif defined(BOOST_WINDOWS_API)
80 BOOST_ASSERT(file.find_first_of("\\/") == std::string::npos);
81 #endif
82
83 std::string result;
84
85 #if defined(BOOST_POSIX_API)
86 if (path.empty())
87 {
88 const char *envpath = getenv("PATH");
89 if (!envpath)
90 boost::throw_exception(boost::filesystem::filesystem_error(
91 BOOST_PROCESS_SOURCE_LOCATION "file not found", file,
92 boost::system::errc::make_error_code(
93 boost::system::errc::no_such_file_or_directory)));
94 path = envpath;
95 }
96 BOOST_ASSERT(!path.empty());
97
98 #if defined(__CYGWIN__)
99 if (!cygwin_posix_path_list_p(path.c_str()))
100 {
101 int size = cygwin_win32_to_posix_path_list_buf_size(path.c_str());
102 boost::scoped_array<char> cygpath(new char[size]);
103 cygwin_win32_to_posix_path_list(path.c_str(), cygpath.get());
104 path = cygpath.get();
105 }
106 #endif
107
108 std::string::size_type pos1 = 0, pos2;
109 do
110 {
111 pos2 = path.find(':', pos1);
112 std::string dir = (pos2 != std::string::npos) ?
113 path.substr(pos1, pos2 - pos1) : path.substr(pos1);
114 std::string f = dir +
115 (boost::algorithm::ends_with(dir, "/") ? "" : "/") + file;
116 if (!access(f.c_str(), X_OK))
117 result = f;
118 pos1 = pos2 + 1;
119 } while (pos2 != std::string::npos && result.empty());
120 #elif defined(BOOST_WINDOWS_API)
121 const char *exts[] = { "", ".exe", ".com", ".bat", NULL };
122 const char **ext = exts;
123 while (*ext)
124 {
125 char buf[MAX_PATH];
126 char *dummy;
127 DWORD size = SearchPathA(path.empty() ? NULL : path.c_str(),
128 file.c_str(), *ext, MAX_PATH, buf, &dummy);
129 BOOST_ASSERT(size < MAX_PATH);
130 if (size > 0)
131 {
132 result = buf;
133 break;
134 }
135 ++ext;
136 }
137 #endif
138
139 if (result.empty())
140 boost::throw_exception(boost::filesystem::filesystem_error(
141 BOOST_PROCESS_SOURCE_LOCATION "file not found", file,
142 boost::system::errc::make_error_code(
143 boost::system::errc::no_such_file_or_directory)));
144
145 return result;
146 }
147
148 /**
149 * Extracts the program name from a given executable.
150 *
151 * \return The program name. On Windows the program name
152 * is returned without a file extension.
153 */
154 inline std::string executable_to_progname(const std::string &exe)
155 {
156 std::string::size_type begin = 0;
157 std::string::size_type end = std::string::npos;
158
159 #if defined(BOOST_POSIX_API)
160 std::string::size_type slash = exe.rfind('/');
161 #elif defined(BOOST_WINDOWS_API)
162 std::string::size_type slash = exe.find_last_of("/\\");
163 #endif
164 if (slash != std::string::npos)
165 begin = slash + 1;
166
167 #if defined(BOOST_WINDOWS_API)
168 if (exe.size() > 4 && (boost::algorithm::iends_with(exe, ".exe") ||
169 boost::algorithm::iends_with(exe, ".com") ||
170 boost::algorithm::iends_with(exe, ".bat")))
171 end = exe.size() - 4;
172 #endif
173
174 return exe.substr(begin, end - begin);
175 }
176
177 /**
178 * Starts a new child process.
179 *
180 * Launches a new process based on the binary image specified by the
181 * executable, the set of arguments passed to it and the execution context.
182 *
183 * \remark Blocking remarks: This function may block if the device holding the
184 * executable blocks when loading the image. This might happen if, e.g.,
185 * the binary is being loaded from a network share.
186 *
187 * \return A handle to the new child process.
188 */
189 template <typename Arguments, typename Context>
190 inline child create_child(const std::string &executable, Arguments args,
191 Context ctx)
192 {
193 typedef std::map<stream_id, stream_ends> handles_t;
194 handles_t handles;
195 typename Context::streams_t::iterator it = ctx.streams.begin();
196 for (; it != ctx.streams.end(); ++it)
197 {
198 if (it->first == stdin_id)
199 handles[it->first] = it->second(input_stream);
200 else if (it->first == stdout_id)
201 handles[it->first] = it->second(output_stream);
202 else if (it->first == stderr_id)
203 handles[it->first] = it->second(output_stream);
204 #if defined(BOOST_POSIX_API)
205 else
206 handles[it->first] = it->second(unknown_stream);
207 #endif
208 }
209
210 std::string p_name = ctx.process_name.empty() ?
211 executable_to_progname(executable) : ctx.process_name;
212 args.insert(args.begin(), p_name);
213
214 #if defined(BOOST_POSIX_API)
215 // Between fork() and execve() only async-signal-safe functions
216 // must be called if multithreaded applications should be supported.
217 // That's why the following code is executed before fork() is called.
218 #if defined(F_MAXFD)
219 int maxdescs = fcntl(-1, F_MAXFD, 0);
220 if (maxdescs == -1)
221 maxdescs = sysconf(_SC_OPEN_MAX);
222 #else
223 int maxdescs = static_cast<int>(sysconf(_SC_OPEN_MAX));
224 #endif
225 if (maxdescs == -1)
226 maxdescs = 1024;
227 std::vector<bool> closeflags(maxdescs, true);
228 std::pair<std::size_t, char**> argv = detail::collection_to_argv(args);
229 std::pair<std::size_t, char**> envp =
230 detail::environment_to_envp(ctx.env);
231
232 const char *work_dir = ctx.work_dir.c_str();
233
234 pid_t pid = fork();
235 if (pid == -1)
236 BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("fork(2) failed");
237 else if (pid == 0)
238 {
239 if (chdir(work_dir) == -1)
240 {
241 write(STDERR_FILENO, "chdir() failed\n", 15);
242 _exit(127);
243 }
244
245 for (handles_t::iterator it = handles.begin(); it != handles.end();
246 ++it)
247 {
248 if (it->second.child.valid())
249 {
250 handles_t::iterator it2 = it;
251 ++it2;
252 for (; it2 != handles.end(); ++it2)
253 {
254 if (it2->second.child.native() == it->first)
255 {
256 int fd = fcntl(it2->second.child.native(), F_DUPFD,
257 it->first + 1);
258 if (fd == -1)
259 {
260 write(STDERR_FILENO, "fcntl() failed\n", 15);
261 _exit(127);
262 }
263 it2->second.child = fd;
264 }
265 }
266
267 if (dup2(it->second.child.native(), it->first) == -1)
268 {
269 write(STDERR_FILENO, "dup2() failed\n", 14);
270 _exit(127);
271 }
272 closeflags[it->first] = false;
273 }
274 }
275
276 if (ctx.setup)
277 ctx.setup();
278
279 for (std::size_t i = 0; i < closeflags.size(); ++i)
280 {
281 if (closeflags[i])
282 close(static_cast<int>(i));
283 }
284
285 execve(executable.c_str(), argv.second, envp.second);
286
287 // Actually we should delete argv and envp data. As we must not
288 // call any non-async-signal-safe functions though we simply exit.
289 write(STDERR_FILENO, "execve() failed\n", 16);
290 _exit(127);
291 }
292 else
293 {
294 BOOST_ASSERT(pid > 0);
295
296 for (std::size_t i = 0; i < argv.first; ++i)
297 delete[] argv.second[i];
298 delete[] argv.second;
299
300 for (std::size_t i = 0; i < envp.first; ++i)
301 delete[] envp.second[i];
302 delete[] envp.second;
303
304 std::map<stream_id, handle> parent_ends;
305 for (handles_t::iterator it = handles.begin(); it != handles.end();
306 ++it)
307 parent_ends[it->first] = it->second.parent;
308
309 return child(pid, parent_ends);
310 }
311 #elif defined(BOOST_WINDOWS_API)
312 STARTUPINFOA startup_info;
313 ZeroMemory(&startup_info, sizeof(startup_info));
314 startup_info.cb = sizeof(startup_info);
315 startup_info.dwFlags |= STARTF_USESTDHANDLES;
316 startup_info.hStdInput = handles[stdin_id].child.native();
317 startup_info.hStdOutput = handles[stdout_id].child.native();
318 startup_info.hStdError = handles[stderr_id].child.native();
319
320 if (ctx.setup)
321 ctx.setup(startup_info);
322
323 PROCESS_INFORMATION pi;
324 ZeroMemory(&pi, sizeof(pi));
325
326 boost::shared_array<char> cmdline =
327 detail::collection_to_windows_cmdline(args);
328
329 boost::scoped_array<char> exe(new char[executable.size() + 1]);
330 #if (BOOST_MSVC >= 1400)
331 strcpy_s(exe.get(), executable.size() + 1, executable.c_str());
332 #else
333 strcpy(exe.get(), executable.c_str());
334 #endif
335
336 boost::scoped_array<char> workdir(new char[ctx.work_dir.size() + 1]);
337 #if (BOOST_MSVC >= 1400)
338 strcpy_s(workdir.get(), ctx.work_dir.size() + 1, ctx.work_dir.c_str());
339 #else
340 strcpy(workdir.get(), ctx.work_dir.c_str());
341 #endif
342
343 boost::shared_array<char> envstrs =
344 detail::environment_to_windows_strings(ctx.env);
345
346 if (CreateProcessA(exe.get(), cmdline.get(), NULL, NULL, TRUE, 0,
347 envstrs.get(), workdir.get(), &startup_info, &pi) == 0)
348 BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("CreateProcess() failed");
349
350 handle hprocess(pi.hProcess);
351
352 if (!CloseHandle(pi.hThread))
353 BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("CloseHandle() failed");
354
355 std::map<stream_id, handle> parent_ends;
356 parent_ends[stdin_id] = handles[stdin_id].parent;
357 parent_ends[stdout_id] = handles[stdout_id].parent;
358 parent_ends[stderr_id] = handles[stderr_id].parent;
359
360 return child(hprocess, parent_ends);
361 #endif
362 }
363
364 /**
365 * \overload
366 */
367 inline child create_child(const std::string &executable)
368 {
369 return create_child(executable, std::vector<std::string>(), context());
370 }
371
372 /**
373 * \overload
374 */
375 template <typename Arguments>
376 inline child create_child(const std::string &executable, Arguments args)
377 {
378 return create_child(executable, args, context());
379 }
380
381 /**
382 * Starts a shell-based command.
383 *
384 * Executes the given command through the default system shell. The
385 * command is subject to pattern expansion, redirection and pipelining.
386 * The shell is launched as described by the parameters in the context.
387 *
388 * This function behaves similarly to the system(3) system call. In a
389 * POSIX system, the command is fed to /bin/sh whereas under a Windows
390 * system, it is fed to cmd.exe. It is difficult to write portable
391 * commands, but this function comes in handy in multiple situations.
392 *
393 * \remark Blocking remarks: This function may block if the device holding the
394 * executable blocks when loading the image. This might happen if, e.g.,
395 * the binary is being loaded from a network share.
396 *
397 * \return A handle to the new child process.
398 */
399 template <typename Context>
400 inline child shell(const std::string &command, Context ctx)
401 {
402 #if defined(BOOST_POSIX_API)
403 std::string executable = "/bin/sh";
404 std::vector<std::string> args;
405 args.push_back("-c");
406 args.push_back(command);
407 #elif defined(BOOST_WINDOWS_API)
408 char sysdir[MAX_PATH];
409 UINT size = GetSystemDirectoryA(sysdir, sizeof(sysdir));
410 if (!size)
411 BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("GetSystemDirectory() failed");
412 std::string executable = std::string(sysdir) +
413 (sysdir[size - 1] != '\\' ? "\\cmd.exe" : "cmd.exe");
414 std::vector<std::string> args;
415 args.push_back("/c");
416 args.push_back(command);
417 #endif
418 return create_child(executable, args, ctx);
419 }
420
421 /**
422 * \overload
423 */
424 inline child shell(const std::string &command)
425 {
426 return shell(command, context());
427 }
428
429 }
430 }
431
432 #endif