ian@0: // ian@0: // Boost.Process ian@0: // ~~~~~~~~~~~~~ ian@0: // ian@0: // Copyright (c) 2006, 2007 Julio M. Merino Vidal ian@0: // Copyright (c) 2008 Ilya Sokolov, Boris Schaeling ian@0: // Copyright (c) 2009 Boris Schaeling ian@0: // Copyright (c) 2010 Felipe Tanus, Boris Schaeling ian@0: // ian@0: // Distributed under the Boost Software License, Version 1.0. (See accompanying ian@0: // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ian@0: // ian@0: ian@0: /** ian@0: * \file boost/process/operations.hpp ian@0: * ian@0: * Provides miscellaneous free functions. ian@0: */ ian@0: ian@0: #ifndef BOOST_PROCESS_OPERATIONS_HPP ian@0: #define BOOST_PROCESS_OPERATIONS_HPP ian@0: ian@0: #include ian@0: ian@0: #if defined(BOOST_POSIX_API) ian@0: # include ian@0: # include ian@0: # include ian@0: # include ian@0: # include ian@0: # include ian@0: # if defined(__CYGWIN__) ian@0: # include ian@0: # include ian@0: # endif ian@0: #elif defined(BOOST_WINDOWS_API) ian@0: # include ian@0: # include ian@0: # include ian@0: # include ian@0: #else ian@0: # error "Unsupported platform." ian@0: #endif ian@0: ian@0: #include ian@0: #include ian@0: #include ian@0: #include ian@0: #include ian@0: #include ian@0: #include ian@0: #include ian@0: #include ian@0: #include ian@0: #include ian@0: #include ian@0: #include ian@0: #include ian@0: ian@0: namespace boost { ian@0: namespace process { ian@0: ian@0: /** ian@0: * Locates the executable program \a file in all the directory components ian@0: * specified in \a path. If \a path is empty, the value of the PATH ian@0: * environment variable is used. ian@0: * ian@0: * The path variable is interpreted following the same conventions used ian@0: * to parse the PATH environment variable in the underlying platform. ian@0: * ian@0: * \throw boost::filesystem::filesystem_error If the file cannot be found ian@0: * in the path. ian@0: */ ian@0: inline std::string find_executable_in_path(const std::string &file, ian@0: std::string path = "") ian@0: { ian@0: #if defined(BOOST_POSIX_API) ian@0: BOOST_ASSERT(file.find('/') == std::string::npos); ian@0: #elif defined(BOOST_WINDOWS_API) ian@0: BOOST_ASSERT(file.find_first_of("\\/") == std::string::npos); ian@0: #endif ian@0: ian@0: std::string result; ian@0: ian@0: #if defined(BOOST_POSIX_API) ian@0: if (path.empty()) ian@0: { ian@0: const char *envpath = getenv("PATH"); ian@0: if (!envpath) ian@0: boost::throw_exception(boost::filesystem::filesystem_error( ian@0: BOOST_PROCESS_SOURCE_LOCATION "file not found", file, ian@0: boost::system::errc::make_error_code( ian@0: boost::system::errc::no_such_file_or_directory))); ian@0: path = envpath; ian@0: } ian@0: BOOST_ASSERT(!path.empty()); ian@0: ian@0: #if defined(__CYGWIN__) ian@0: if (!cygwin_posix_path_list_p(path.c_str())) ian@0: { ian@0: int size = cygwin_win32_to_posix_path_list_buf_size(path.c_str()); ian@0: boost::scoped_array cygpath(new char[size]); ian@0: cygwin_win32_to_posix_path_list(path.c_str(), cygpath.get()); ian@0: path = cygpath.get(); ian@0: } ian@0: #endif ian@0: ian@0: std::string::size_type pos1 = 0, pos2; ian@0: do ian@0: { ian@0: pos2 = path.find(':', pos1); ian@0: std::string dir = (pos2 != std::string::npos) ? ian@0: path.substr(pos1, pos2 - pos1) : path.substr(pos1); ian@0: std::string f = dir + ian@0: (boost::algorithm::ends_with(dir, "/") ? "" : "/") + file; ian@0: if (!access(f.c_str(), X_OK)) ian@0: result = f; ian@0: pos1 = pos2 + 1; ian@0: } while (pos2 != std::string::npos && result.empty()); ian@0: #elif defined(BOOST_WINDOWS_API) ian@0: const char *exts[] = { "", ".exe", ".com", ".bat", NULL }; ian@0: const char **ext = exts; ian@0: while (*ext) ian@0: { ian@0: char buf[MAX_PATH]; ian@0: char *dummy; ian@0: DWORD size = SearchPathA(path.empty() ? NULL : path.c_str(), ian@0: file.c_str(), *ext, MAX_PATH, buf, &dummy); ian@0: BOOST_ASSERT(size < MAX_PATH); ian@0: if (size > 0) ian@0: { ian@0: result = buf; ian@0: break; ian@0: } ian@0: ++ext; ian@0: } ian@0: #endif ian@0: ian@0: if (result.empty()) ian@0: boost::throw_exception(boost::filesystem::filesystem_error( ian@0: BOOST_PROCESS_SOURCE_LOCATION "file not found", file, ian@0: boost::system::errc::make_error_code( ian@0: boost::system::errc::no_such_file_or_directory))); ian@0: ian@0: return result; ian@0: } ian@0: ian@0: /** ian@0: * Extracts the program name from a given executable. ian@0: * ian@0: * \return The program name. On Windows the program name ian@0: * is returned without a file extension. ian@0: */ ian@0: inline std::string executable_to_progname(const std::string &exe) ian@0: { ian@0: std::string::size_type begin = 0; ian@0: std::string::size_type end = std::string::npos; ian@0: ian@0: #if defined(BOOST_POSIX_API) ian@0: std::string::size_type slash = exe.rfind('/'); ian@0: #elif defined(BOOST_WINDOWS_API) ian@0: std::string::size_type slash = exe.find_last_of("/\\"); ian@0: #endif ian@0: if (slash != std::string::npos) ian@0: begin = slash + 1; ian@0: ian@0: #if defined(BOOST_WINDOWS_API) ian@0: if (exe.size() > 4 && (boost::algorithm::iends_with(exe, ".exe") || ian@0: boost::algorithm::iends_with(exe, ".com") || ian@0: boost::algorithm::iends_with(exe, ".bat"))) ian@0: end = exe.size() - 4; ian@0: #endif ian@0: ian@0: return exe.substr(begin, end - begin); ian@0: } ian@0: ian@0: /** ian@0: * Starts a new child process. ian@0: * ian@0: * Launches a new process based on the binary image specified by the ian@0: * executable, the set of arguments passed to it and the execution context. ian@0: * ian@0: * \remark Blocking remarks: This function may block if the device holding the ian@0: * executable blocks when loading the image. This might happen if, e.g., ian@0: * the binary is being loaded from a network share. ian@0: * ian@0: * \return A handle to the new child process. ian@0: */ ian@0: template ian@0: inline child create_child(const std::string &executable, Arguments args, ian@0: Context ctx) ian@0: { ian@0: typedef std::map handles_t; ian@0: handles_t handles; ian@0: typename Context::streams_t::iterator it = ctx.streams.begin(); ian@0: for (; it != ctx.streams.end(); ++it) ian@0: { ian@0: if (it->first == stdin_id) ian@0: handles[it->first] = it->second(input_stream); ian@0: else if (it->first == stdout_id) ian@0: handles[it->first] = it->second(output_stream); ian@0: else if (it->first == stderr_id) ian@0: handles[it->first] = it->second(output_stream); ian@0: #if defined(BOOST_POSIX_API) ian@0: else ian@0: handles[it->first] = it->second(unknown_stream); ian@0: #endif ian@0: } ian@0: ian@0: std::string p_name = ctx.process_name.empty() ? ian@0: executable_to_progname(executable) : ctx.process_name; ian@0: args.insert(args.begin(), p_name); ian@0: ian@0: #if defined(BOOST_POSIX_API) ian@0: // Between fork() and execve() only async-signal-safe functions ian@0: // must be called if multithreaded applications should be supported. ian@0: // That's why the following code is executed before fork() is called. ian@0: #if defined(F_MAXFD) ian@0: int maxdescs = fcntl(-1, F_MAXFD, 0); ian@0: if (maxdescs == -1) ian@0: maxdescs = sysconf(_SC_OPEN_MAX); ian@0: #else ian@0: int maxdescs = static_cast(sysconf(_SC_OPEN_MAX)); ian@0: #endif ian@0: if (maxdescs == -1) ian@0: maxdescs = 1024; ian@0: std::vector closeflags(maxdescs, true); ian@0: std::pair argv = detail::collection_to_argv(args); ian@0: std::pair envp = ian@0: detail::environment_to_envp(ctx.env); ian@0: ian@0: const char *work_dir = ctx.work_dir.c_str(); ian@0: ian@0: pid_t pid = fork(); ian@0: if (pid == -1) ian@0: BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("fork(2) failed"); ian@0: else if (pid == 0) ian@0: { ian@0: if (chdir(work_dir) == -1) ian@0: { ian@0: write(STDERR_FILENO, "chdir() failed\n", 15); ian@0: _exit(127); ian@0: } ian@0: ian@0: for (handles_t::iterator it = handles.begin(); it != handles.end(); ian@0: ++it) ian@0: { ian@0: if (it->second.child.valid()) ian@0: { ian@0: handles_t::iterator it2 = it; ian@0: ++it2; ian@0: for (; it2 != handles.end(); ++it2) ian@0: { ian@0: if (it2->second.child.native() == it->first) ian@0: { ian@0: int fd = fcntl(it2->second.child.native(), F_DUPFD, ian@0: it->first + 1); ian@0: if (fd == -1) ian@0: { ian@0: write(STDERR_FILENO, "fcntl() failed\n", 15); ian@0: _exit(127); ian@0: } ian@0: it2->second.child = fd; ian@0: } ian@0: } ian@0: ian@0: if (dup2(it->second.child.native(), it->first) == -1) ian@0: { ian@0: write(STDERR_FILENO, "dup2() failed\n", 14); ian@0: _exit(127); ian@0: } ian@0: closeflags[it->first] = false; ian@0: } ian@0: } ian@0: ian@0: if (ctx.setup) ian@0: ctx.setup(); ian@0: ian@0: for (std::size_t i = 0; i < closeflags.size(); ++i) ian@0: { ian@0: if (closeflags[i]) ian@0: close(static_cast(i)); ian@0: } ian@0: ian@0: execve(executable.c_str(), argv.second, envp.second); ian@0: ian@0: // Actually we should delete argv and envp data. As we must not ian@0: // call any non-async-signal-safe functions though we simply exit. ian@0: write(STDERR_FILENO, "execve() failed\n", 16); ian@0: _exit(127); ian@0: } ian@0: else ian@0: { ian@0: BOOST_ASSERT(pid > 0); ian@0: ian@0: for (std::size_t i = 0; i < argv.first; ++i) ian@0: delete[] argv.second[i]; ian@0: delete[] argv.second; ian@0: ian@0: for (std::size_t i = 0; i < envp.first; ++i) ian@0: delete[] envp.second[i]; ian@0: delete[] envp.second; ian@0: ian@0: std::map parent_ends; ian@0: for (handles_t::iterator it = handles.begin(); it != handles.end(); ian@0: ++it) ian@0: parent_ends[it->first] = it->second.parent; ian@0: ian@0: return child(pid, parent_ends); ian@0: } ian@0: #elif defined(BOOST_WINDOWS_API) ian@0: STARTUPINFOA startup_info; ian@0: ZeroMemory(&startup_info, sizeof(startup_info)); ian@0: startup_info.cb = sizeof(startup_info); ian@0: startup_info.dwFlags |= STARTF_USESTDHANDLES; ian@0: startup_info.hStdInput = handles[stdin_id].child.native(); ian@0: startup_info.hStdOutput = handles[stdout_id].child.native(); ian@0: startup_info.hStdError = handles[stderr_id].child.native(); ian@0: ian@0: if (ctx.setup) ian@0: ctx.setup(startup_info); ian@0: ian@0: PROCESS_INFORMATION pi; ian@0: ZeroMemory(&pi, sizeof(pi)); ian@0: ian@0: boost::shared_array cmdline = ian@0: detail::collection_to_windows_cmdline(args); ian@0: ian@0: boost::scoped_array exe(new char[executable.size() + 1]); ian@0: #if (BOOST_MSVC >= 1400) ian@0: strcpy_s(exe.get(), executable.size() + 1, executable.c_str()); ian@0: #else ian@0: strcpy(exe.get(), executable.c_str()); ian@0: #endif ian@0: ian@0: boost::scoped_array workdir(new char[ctx.work_dir.size() + 1]); ian@0: #if (BOOST_MSVC >= 1400) ian@0: strcpy_s(workdir.get(), ctx.work_dir.size() + 1, ctx.work_dir.c_str()); ian@0: #else ian@0: strcpy(workdir.get(), ctx.work_dir.c_str()); ian@0: #endif ian@0: ian@0: boost::shared_array envstrs = ian@0: detail::environment_to_windows_strings(ctx.env); ian@0: ian@0: if (CreateProcessA(exe.get(), cmdline.get(), NULL, NULL, TRUE, 0, ian@0: envstrs.get(), workdir.get(), &startup_info, &pi) == 0) ian@0: BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("CreateProcess() failed"); ian@0: ian@0: handle hprocess(pi.hProcess); ian@0: ian@0: if (!CloseHandle(pi.hThread)) ian@0: BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("CloseHandle() failed"); ian@0: ian@0: std::map parent_ends; ian@0: parent_ends[stdin_id] = handles[stdin_id].parent; ian@0: parent_ends[stdout_id] = handles[stdout_id].parent; ian@0: parent_ends[stderr_id] = handles[stderr_id].parent; ian@0: ian@0: return child(hprocess, parent_ends); ian@0: #endif ian@0: } ian@0: ian@0: /** ian@0: * \overload ian@0: */ ian@0: inline child create_child(const std::string &executable) ian@0: { ian@0: return create_child(executable, std::vector(), context()); ian@0: } ian@0: ian@0: /** ian@0: * \overload ian@0: */ ian@0: template ian@0: inline child create_child(const std::string &executable, Arguments args) ian@0: { ian@0: return create_child(executable, args, context()); ian@0: } ian@0: ian@0: /** ian@0: * Starts a shell-based command. ian@0: * ian@0: * Executes the given command through the default system shell. The ian@0: * command is subject to pattern expansion, redirection and pipelining. ian@0: * The shell is launched as described by the parameters in the context. ian@0: * ian@0: * This function behaves similarly to the system(3) system call. In a ian@0: * POSIX system, the command is fed to /bin/sh whereas under a Windows ian@0: * system, it is fed to cmd.exe. It is difficult to write portable ian@0: * commands, but this function comes in handy in multiple situations. ian@0: * ian@0: * \remark Blocking remarks: This function may block if the device holding the ian@0: * executable blocks when loading the image. This might happen if, e.g., ian@0: * the binary is being loaded from a network share. ian@0: * ian@0: * \return A handle to the new child process. ian@0: */ ian@0: template ian@0: inline child shell(const std::string &command, Context ctx) ian@0: { ian@0: #if defined(BOOST_POSIX_API) ian@0: std::string executable = "/bin/sh"; ian@0: std::vector args; ian@0: args.push_back("-c"); ian@0: args.push_back(command); ian@0: #elif defined(BOOST_WINDOWS_API) ian@0: char sysdir[MAX_PATH]; ian@0: UINT size = GetSystemDirectoryA(sysdir, sizeof(sysdir)); ian@0: if (!size) ian@0: BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("GetSystemDirectory() failed"); ian@0: std::string executable = std::string(sysdir) + ian@0: (sysdir[size - 1] != '\\' ? "\\cmd.exe" : "cmd.exe"); ian@0: std::vector args; ian@0: args.push_back("/c"); ian@0: args.push_back(command); ian@0: #endif ian@0: return create_child(executable, args, ctx); ian@0: } ian@0: ian@0: /** ian@0: * \overload ian@0: */ ian@0: inline child shell(const std::string &command) ian@0: { ian@0: return shell(command, context()); ian@0: } ian@0: ian@0: } ian@0: } ian@0: ian@0: #endif