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