// Copyright (c) 2019 Klemens D. Morgenstern
//
// 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)

#ifndef BOOST_PROCESS_DETAIL_POSIX_HANDLES_HPP_
#define BOOST_PROCESS_DETAIL_POSIX_HANDLES_HPP_

#include <vector>
#include <system_error>
#include <dirent.h>
#include <sys/stat.h>
#include <algorithm>
#include <memory>
#include <cstdlib>
#include <boost/process/detail/posix/handler.hpp>

namespace boost { namespace process { namespace detail { namespace posix {


using native_handle_type = int;

inline std::vector<native_handle_type> get_handles(std::error_code & ec)
{
    std::vector<native_handle_type> res;

    std::unique_ptr<DIR, void(*)(DIR*)> dir{::opendir("/dev/fd"), +[](DIR* p){::closedir(p);}};
    if (!dir)
    {
        ec = ::boost::process::detail::get_last_error();
        return {};
    }
    else
        ec.clear();

    auto my_fd = dirfd(dir.get());

    struct ::dirent * ent_p;

    while ((ent_p = readdir(dir.get())) != nullptr)
    {
        if (ent_p->d_name[0] == '.')
            continue;

        const auto conv = std::atoi(ent_p->d_name);
        if (conv == 0 && (ent_p->d_name[0] != '0' && ent_p->d_name[1] != '\0'))
            continue;

        if (conv == my_fd)
            continue;

        res.push_back(conv);
    }
    return res;
}

inline std::vector<native_handle_type> get_handles()
{
    std::error_code ec;

    auto res = get_handles(ec);
    if (ec)
        boost::process::detail::throw_error(ec, "open_dir(\"/dev/fd\") failed");

    return res;
}


inline bool is_stream_handle(native_handle_type handle, std::error_code & ec)
{
    struct ::stat stat_;

    if (::fstat(handle, &stat_) != 0)
    {
        ec = ::boost::process::detail::get_last_error();
    }
    else
        ec.clear();

    return S_ISCHR  (stat_.st_mode)  //This macro returns non-zero if the file is a character special file (a device like a terminal).
        || S_ISBLK  (stat_.st_mode) // This macro returns non-zero if the file is a block special file (a device like a disk).
        || S_ISREG  (stat_.st_mode) // This macro returns non-zero if the file is a regular file.
        || S_ISFIFO (stat_.st_mode) // This macro returns non-zero if the file is a FIFO special file, or a pipe. See section 15. Pipes and FIFOs.
        || S_ISSOCK (stat_.st_mode) ;// This macro returns non-zero if the file is a socket. See section 16. Sockets.;
}


inline bool is_stream_handle(native_handle_type handle)
{
    std::error_code ec;
    auto res = is_stream_handle(handle, ec);
    if (ec)
        boost::process::detail::throw_error(ec, "fstat() failed");

    return res;
}

struct limit_handles_ : handler_base_ext
{
    limit_handles_() {}
    ~limit_handles_() {}
    mutable std::vector<int> used_handles;

    template<typename Executor>
    void on_setup(Executor & exec) const
    {
        used_handles = get_used_handles(exec);
    }

    template<typename Executor>
    void on_exec_setup(Executor & exec) const
    {
        auto dir = ::opendir("/dev/fd");
        if (!dir)
        {
            exec.set_error(::boost::process::detail::get_last_error(), "opendir(\"/dev/fd\")");
            return;
        }

        auto my_fd = dirfd(dir);
        struct ::dirent * ent_p;

        while ((ent_p = readdir(dir)) != nullptr)
        {
            if (ent_p->d_name[0] == '.')
                continue;

            const auto conv = std::atoi(ent_p->d_name);

            if ((conv == my_fd) || (conv == -1))
                continue;

            if (std::find(used_handles.begin(), used_handles.end(), conv) != used_handles.end())
                continue;

            if (::close(conv) != 0)
            {
                exec.set_error(::boost::process::detail::get_last_error(), "close() failed");
                return;
            }
        }
        ::closedir(dir);
    }
};

}}}}

#endif //PROCESS_HANDLES_HPP