//
// Boost.Process
// ~~~~~~~~~~~~~
//
// Copyright (c) 2006, 2007 Julio M. Merino Vidal
// Copyright (c) 2008 Ilya Sokolov, Boris Schaeling
// Copyright (c) 2009 Boris Schaeling
// Copyright (c) 2010 Felipe Tanus, Boris Schaeling
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

/**
 * \file boost/process/operations.hpp
 *
 * Provides miscellaneous free functions.
 */

#ifndef BOOST_PROCESS_OPERATIONS_HPP
#define BOOST_PROCESS_OPERATIONS_HPP

#include <boost/process/config.hpp>

#if defined(BOOST_POSIX_API)
#   include <boost/process/detail/posix_helpers.hpp>
#   include <utility>
#   include <cstddef>
#   include <stdlib.h>
#   include <unistd.h>
#   include <fcntl.h>
#   if defined(__CYGWIN__)
#       include <boost/scoped_array.hpp>
#       include <sys/cygwin.h>
#   endif
#elif defined(BOOST_WINDOWS_API)
#   include <boost/process/detail/windows_helpers.hpp>
#   include <boost/scoped_array.hpp>
#   include <boost/shared_array.hpp>
#   include <windows.h>
#else
#   error "Unsupported platform."
#endif

#include <boost/process/child.hpp>
#include <boost/process/context.hpp>
#include <boost/process/stream_id.hpp>
#include <boost/process/stream_ends.hpp>
#include <boost/process/handle.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/system/system_error.hpp>
#include <boost/throw_exception.hpp>
#include <boost/assert.hpp>
#include <string>
#include <vector>
#include <map>
#include <utility>

namespace boost {
namespace process {

/**
 * Locates the executable program \a file in all the directory components
 * specified in \a path. If \a path is empty, the value of the PATH
 * environment variable is used.
 *
 * The path variable is interpreted following the same conventions used
 * to parse the PATH environment variable in the underlying platform.
 *
 * \throw boost::filesystem::filesystem_error If the file cannot be found
 *        in the path.
 */
inline std::string find_executable_in_path(const std::string &file,
    std::string path = "")
{
#if defined(BOOST_POSIX_API)
    BOOST_ASSERT(file.find('/') == std::string::npos);
#elif defined(BOOST_WINDOWS_API)
    BOOST_ASSERT(file.find_first_of("\\/") == std::string::npos);
#endif

    std::string result;

#if defined(BOOST_POSIX_API)
    if (path.empty())
    {
        const char *envpath = getenv("PATH");
        if (!envpath)
            boost::throw_exception(boost::filesystem::filesystem_error(
                BOOST_PROCESS_SOURCE_LOCATION "file not found", file,
                boost::system::errc::make_error_code(
                boost::system::errc::no_such_file_or_directory)));
        path = envpath;
    }
    BOOST_ASSERT(!path.empty());

#if defined(__CYGWIN__)
    if (!cygwin_posix_path_list_p(path.c_str()))
    {
        int size = cygwin_win32_to_posix_path_list_buf_size(path.c_str());
        boost::scoped_array<char> cygpath(new char[size]);
        cygwin_win32_to_posix_path_list(path.c_str(), cygpath.get());
        path = cygpath.get();
    }
#endif

    std::string::size_type pos1 = 0, pos2;
    do
    {
        pos2 = path.find(':', pos1);
        std::string dir = (pos2 != std::string::npos) ?
            path.substr(pos1, pos2 - pos1) : path.substr(pos1);
        std::string f = dir +
            (boost::algorithm::ends_with(dir, "/") ? "" : "/") + file;
        if (!access(f.c_str(), X_OK))
            result = f;
        pos1 = pos2 + 1;
    } while (pos2 != std::string::npos && result.empty());
#elif defined(BOOST_WINDOWS_API)
    const char *exts[] = { "", ".exe", ".com", ".bat", NULL };
    const char **ext = exts;
    while (*ext)
    {
        char buf[MAX_PATH];
        char *dummy;
        DWORD size = SearchPathA(path.empty() ? NULL : path.c_str(),
            file.c_str(), *ext, MAX_PATH, buf, &dummy);
        BOOST_ASSERT(size < MAX_PATH);
        if (size > 0)
        {
            result = buf;
            break;
        }
        ++ext;
    }
#endif

    if (result.empty())
        boost::throw_exception(boost::filesystem::filesystem_error(
            BOOST_PROCESS_SOURCE_LOCATION "file not found", file,
            boost::system::errc::make_error_code(
            boost::system::errc::no_such_file_or_directory)));

    return result;
}

/**
 * Extracts the program name from a given executable.
 *
 * \return The program name. On Windows the program name
 *         is returned without a file extension.
 */
inline std::string executable_to_progname(const std::string &exe)
{
    std::string::size_type begin = 0;
    std::string::size_type end = std::string::npos;

#if defined(BOOST_POSIX_API)
    std::string::size_type slash = exe.rfind('/');
#elif defined(BOOST_WINDOWS_API)
    std::string::size_type slash = exe.find_last_of("/\\");
#endif
    if (slash != std::string::npos)
        begin = slash + 1;

#if defined(BOOST_WINDOWS_API)
    if (exe.size() > 4 && (boost::algorithm::iends_with(exe, ".exe") ||
        boost::algorithm::iends_with(exe, ".com") ||
        boost::algorithm::iends_with(exe, ".bat")))
        end = exe.size() - 4;
#endif

    return exe.substr(begin, end - begin);
}

/**
 * Starts a new child process.
 *
 * Launches a new process based on the binary image specified by the
 * executable, the set of arguments passed to it and the execution context.
 *
 * \remark Blocking remarks: This function may block if the device holding the
 *         executable blocks when loading the image. This might happen if, e.g.,
 *         the binary is being loaded from a network share.
 *
 * \return A handle to the new child process.
 */
template <typename Arguments, typename Context>
inline child create_child(const std::string &executable, Arguments args,
    Context ctx)
{
    typedef std::map<stream_id, stream_ends> handles_t;
    handles_t handles;
    typename Context::streams_t::iterator it = ctx.streams.begin();
    for (; it != ctx.streams.end(); ++it)
    {
        if (it->first == stdin_id)
            handles[it->first] = it->second(input_stream);
        else if (it->first == stdout_id)
            handles[it->first] = it->second(output_stream);
        else if (it->first == stderr_id)
            handles[it->first] = it->second(output_stream);
#if defined(BOOST_POSIX_API)
        else
            handles[it->first] = it->second(unknown_stream);
#endif
    }

    std::string p_name = ctx.process_name.empty() ?
        executable_to_progname(executable) : ctx.process_name;
    args.insert(args.begin(), p_name);

#if defined(BOOST_POSIX_API)
    // Between fork() and execve() only async-signal-safe functions
    // must be called if multithreaded applications should be supported.
    // That's why the following code is executed before fork() is called.
#if defined(F_MAXFD)
    int maxdescs = fcntl(-1, F_MAXFD, 0);
    if (maxdescs == -1)
        maxdescs = sysconf(_SC_OPEN_MAX);
#else
    int maxdescs = sysconf(_SC_OPEN_MAX);
#endif
    if (maxdescs == -1)
        maxdescs = 1024;
    std::vector<bool> closeflags(maxdescs, true);
    std::pair<std::size_t, char**> argv = detail::collection_to_argv(args);
    std::pair<std::size_t, char**> envp =
        detail::environment_to_envp(ctx.env);

    const char *work_dir = ctx.work_dir.c_str();

    pid_t pid = fork();
    if (pid == -1)
        BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("fork(2) failed");
    else if (pid == 0)
    {
        if (chdir(work_dir) == -1)
        {
            write(STDERR_FILENO, "chdir() failed\n", 15);
            _exit(127);
        }

        for (handles_t::iterator it = handles.begin(); it != handles.end();
            ++it)
        {
            if (it->second.child.valid())
            {
                handles_t::iterator it2 = it;
                ++it2;
                for (; it2 != handles.end(); ++it2)
                {
                    if (it2->second.child.native() == it->first)
                    {
                        int fd = fcntl(it2->second.child.native(), F_DUPFD,
                            it->first + 1);
                        if (fd == -1)
                        {
                            write(STDERR_FILENO, "fcntl() failed\n", 15);
                            _exit(127);
                        }
                        it2->second.child = fd;
                    }
                }

                if (dup2(it->second.child.native(), it->first) == -1)
                {
                    write(STDERR_FILENO, "dup2() failed\n", 14);
                    _exit(127);
                }
                closeflags[it->first] = false;
            }
        }

        if (ctx.setup)
            ctx.setup();

        for (std::size_t i = 0; i < closeflags.size(); ++i)
        {
            if (closeflags[i])
                close(i);
        }

        execve(executable.c_str(), argv.second, envp.second);

        // Actually we should delete argv and envp data. As we must not
        // call any non-async-signal-safe functions though we simply exit.
        write(STDERR_FILENO, "execve() failed\n", 16);
        _exit(127);
    }
    else
    {
        BOOST_ASSERT(pid > 0);

        for (std::size_t i = 0; i < argv.first; ++i)
            delete[] argv.second[i];
        delete[] argv.second;

        for (std::size_t i = 0; i < envp.first; ++i)
            delete[] envp.second[i];
        delete[] envp.second;

        std::map<stream_id, handle> parent_ends;
        for (handles_t::iterator it = handles.begin(); it != handles.end();
            ++it)
            parent_ends[it->first] = it->second.parent;

        return child(pid, parent_ends);
    }
#elif defined(BOOST_WINDOWS_API)
    STARTUPINFOA startup_info;
    ZeroMemory(&startup_info, sizeof(startup_info));
    startup_info.cb = sizeof(startup_info);
    startup_info.dwFlags |= STARTF_USESTDHANDLES;
    startup_info.hStdInput = handles[stdin_id].child.native();
    startup_info.hStdOutput = handles[stdout_id].child.native();
    startup_info.hStdError = handles[stderr_id].child.native();

    if (ctx.setup)
        ctx.setup(startup_info);

    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));

    boost::shared_array<char> cmdline =
        detail::collection_to_windows_cmdline(args);

    boost::scoped_array<char> exe(new char[executable.size() + 1]);
#if (BOOST_MSVC >= 1400)
    strcpy_s(exe.get(), executable.size() + 1, executable.c_str());
#else
    strcpy(exe.get(), executable.c_str());
#endif

    boost::scoped_array<char> workdir(new char[ctx.work_dir.size() + 1]);
#if (BOOST_MSVC >= 1400)
    strcpy_s(workdir.get(), ctx.work_dir.size() + 1, ctx.work_dir.c_str());
#else
    strcpy(workdir.get(), ctx.work_dir.c_str());
#endif

    boost::shared_array<char> envstrs =
        detail::environment_to_windows_strings(ctx.env);

    if (CreateProcessA(exe.get(), cmdline.get(), NULL, NULL, TRUE, 0,
        envstrs.get(), workdir.get(), &startup_info, &pi) == 0)
        BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("CreateProcess() failed");

    handle hprocess(pi.hProcess);

    if (!CloseHandle(pi.hThread))
        BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("CloseHandle() failed");

    std::map<stream_id, handle> parent_ends;
    parent_ends[stdin_id] = handles[stdin_id].parent;
    parent_ends[stdout_id] = handles[stdout_id].parent;
    parent_ends[stderr_id] = handles[stderr_id].parent;

    return child(hprocess, parent_ends);
#endif
}

/**
 * \overload
 */
inline child create_child(const std::string &executable)
{
    return create_child(executable, std::vector<std::string>(), context());
}

/**
 * \overload
 */
template <typename Arguments>
inline child create_child(const std::string &executable, Arguments args)
{
    return create_child(executable, args, context());
}

/**
 * Starts a shell-based command.
 *
 * Executes the given command through the default system shell. The
 * command is subject to pattern expansion, redirection and pipelining.
 * The shell is launched as described by the parameters in the context.
 *
 * This function behaves similarly to the system(3) system call. In a
 * POSIX system, the command is fed to /bin/sh whereas under a Windows
 * system, it is fed to cmd.exe. It is difficult to write portable
 * commands, but this function comes in handy in multiple situations.
 *
 * \remark Blocking remarks: This function may block if the device holding the
 *         executable blocks when loading the image. This might happen if, e.g.,
 *         the binary is being loaded from a network share.
 *
 * \return A handle to the new child process.
 */
template <typename Context>
inline child shell(const std::string &command, Context ctx)
{
#if defined(BOOST_POSIX_API)
    std::string executable = "/bin/sh";
    std::vector<std::string> args;
    args.push_back("-c");
    args.push_back(command);
#elif defined(BOOST_WINDOWS_API)
    char sysdir[MAX_PATH];
    UINT size = GetSystemDirectoryA(sysdir, sizeof(sysdir));
    if (!size)
        BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("GetSystemDirectory() failed");
    std::string executable = std::string(sysdir) +
        (sysdir[size - 1] != '\\' ? "\\cmd.exe" : "cmd.exe");
    std::vector<std::string> args;
    args.push_back("/c");
    args.push_back(command);
#endif
    return create_child(executable, args, ctx);
}

/**
 * \overload
 */
inline child shell(const std::string &command)
{
    return shell(command, context());
}

}
}

#endif
