//===-- GDBRemoteCommunicationServerPlatform.cpp ----------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "GDBRemoteCommunicationServerPlatform.h"

#include <errno.h>

// C Includes
// C++ Includes
#include <cstring>
#include <chrono>
#include <mutex>
#include <sstream>

// Other libraries and framework includes
#include "llvm/Support/FileSystem.h"

#include "lldb/Core/Log.h"
#include "lldb/Core/StreamGDBRemote.h"
#include "lldb/Core/StreamString.h"
#include "lldb/Core/StructuredData.h"
#include "lldb/Host/Config.h"
#include "lldb/Host/ConnectionFileDescriptor.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/StringConvert.h"
#include "lldb/Target/FileAction.h"
#include "lldb/Target/Platform.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/UnixSignals.h"
#include "lldb/Utility/JSON.h"

// Project includes
#include "Utility/StringExtractorGDBRemote.h"
#include "Utility/UriParser.h"

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::process_gdb_remote;

//----------------------------------------------------------------------
// GDBRemoteCommunicationServerPlatform constructor
//----------------------------------------------------------------------
GDBRemoteCommunicationServerPlatform::GDBRemoteCommunicationServerPlatform(const Socket::SocketProtocol socket_protocol,
                                                                           const char *socket_scheme)
    : GDBRemoteCommunicationServerCommon("gdb-remote.server", "gdb-remote.server.rx_packet"),
      m_socket_protocol(socket_protocol),
      m_socket_scheme(socket_scheme),
      m_spawned_pids_mutex(),
      m_port_map(),
      m_port_offset(0)
{
    m_pending_gdb_server.pid = LLDB_INVALID_PROCESS_ID;
    m_pending_gdb_server.port = 0;

    RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qC,
                                  &GDBRemoteCommunicationServerPlatform::Handle_qC);
    RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qGetWorkingDir,
                                  &GDBRemoteCommunicationServerPlatform::Handle_qGetWorkingDir);
    RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qLaunchGDBServer,
                                  &GDBRemoteCommunicationServerPlatform::Handle_qLaunchGDBServer);
    RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qQueryGDBServer,
                                  &GDBRemoteCommunicationServerPlatform::Handle_qQueryGDBServer);
    RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qKillSpawnedProcess,
                                  &GDBRemoteCommunicationServerPlatform::Handle_qKillSpawnedProcess);
    RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qProcessInfo,
                                  &GDBRemoteCommunicationServerPlatform::Handle_qProcessInfo);
    RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_QSetWorkingDir,
                                  &GDBRemoteCommunicationServerPlatform::Handle_QSetWorkingDir);
    RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_jSignalsInfo,
                                  &GDBRemoteCommunicationServerPlatform::Handle_jSignalsInfo);

    RegisterPacketHandler(StringExtractorGDBRemote::eServerPacketType_interrupt,
                          [this](StringExtractorGDBRemote packet, Error &error, bool &interrupt, bool &quit) {
                              error.SetErrorString("interrupt received");
                              interrupt = true;
                              return PacketResult::Success;
                          });
}

//----------------------------------------------------------------------
// Destructor
//----------------------------------------------------------------------
GDBRemoteCommunicationServerPlatform::~GDBRemoteCommunicationServerPlatform()
{
}

Error
GDBRemoteCommunicationServerPlatform::LaunchGDBServer(const lldb_private::Args& args,
                                                      std::string hostname,
                                                      lldb::pid_t& pid,
                                                      uint16_t& port,
                                                      std::string& socket_name)
{
    if (port == UINT16_MAX)
        port = GetNextAvailablePort();
    
    // Spawn a new thread to accept the port that gets bound after
    // binding to port 0 (zero).

    // ignore the hostname send from the remote end, just use the ip address
    // that we're currently communicating with as the hostname

    // Spawn a debugserver and try to get the port it listens to.
    ProcessLaunchInfo debugserver_launch_info;
    if (hostname.empty())
        hostname = "127.0.0.1";

    Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM));
    if (log)
        log->Printf("Launching debugserver with: %s:%u...", hostname.c_str(), port);

    // Do not run in a new session so that it can not linger after the
    // platform closes.
    debugserver_launch_info.SetLaunchInSeparateProcessGroup(false);
    debugserver_launch_info.SetMonitorProcessCallback(
        std::bind(&GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped, this, std::placeholders::_1), false);

    std::string platform_scheme;
    std::string platform_ip;
    int platform_port;
    std::string platform_path;
    bool ok = UriParser::Parse(GetConnection()->GetURI().c_str(), platform_scheme, platform_ip, platform_port, platform_path);
    UNUSED_IF_ASSERT_DISABLED(ok);
    assert(ok);

    std::ostringstream url;
    // debugserver does not accept the URL scheme prefix.
#if !defined(__APPLE__)
    url << m_socket_scheme << "://";
#endif
    uint16_t* port_ptr = &port;
    if (m_socket_protocol == Socket::ProtocolTcp)
        url << platform_ip << ":" << port;
    else
    {
        socket_name = GetDomainSocketPath("gdbserver").GetPath();
        url << socket_name;
        port_ptr = nullptr;
    }

    Error error = StartDebugserverProcess (url.str().c_str(),
                                           nullptr,
                                           debugserver_launch_info,
                                           port_ptr,
                                           args);

    pid = debugserver_launch_info.GetProcessID();
    if (pid != LLDB_INVALID_PROCESS_ID)
    {
        std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
        m_spawned_pids.insert(pid);
        if (port > 0)
            AssociatePortWithProcess(port, pid);
    }
    else
    {
        if (port > 0)
            FreePort(port);
    }
    return error;
}

GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qLaunchGDBServer (StringExtractorGDBRemote &packet)
{
#ifdef _WIN32
    return SendErrorResponse(9);
#else
    // Spawn a local debugserver as a platform so we can then attach or launch
    // a process...

    Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM));
    if (log)
        log->Printf ("GDBRemoteCommunicationServerPlatform::%s() called", __FUNCTION__);

    ConnectionFileDescriptor file_conn;
    std::string hostname;
    packet.SetFilePos(::strlen ("qLaunchGDBServer;"));
    std::string name;
    std::string value;
    uint16_t port = UINT16_MAX;
    while (packet.GetNameColonValue(name, value))
    {
        if (name.compare ("host") == 0)
            hostname.swap(value);
        else if (name.compare ("port") == 0)
            port = StringConvert::ToUInt32(value.c_str(), 0, 0);
    }

    lldb::pid_t debugserver_pid = LLDB_INVALID_PROCESS_ID;
    std::string socket_name;
    Error error = LaunchGDBServer(Args(), hostname, debugserver_pid, port, socket_name);
    if (error.Fail())
    {
        if (log)
            log->Printf("GDBRemoteCommunicationServerPlatform::%s() debugserver launch failed: %s", __FUNCTION__, error.AsCString ());
        return SendErrorResponse(9);
    }

    if (log)
        log->Printf ("GDBRemoteCommunicationServerPlatform::%s() debugserver launched successfully as pid %" PRIu64, __FUNCTION__, debugserver_pid);

    StreamGDBRemote response;
    response.Printf("pid:%" PRIu64 ";port:%u;", debugserver_pid, port + m_port_offset);
    if (!socket_name.empty())
    {
        response.PutCString("socket_name:");
        response.PutCStringAsRawHex8(socket_name.c_str());
        response.PutChar(';');
    }

    PacketResult packet_result = SendPacketNoLock(response.GetData(), response.GetSize());
    if (packet_result != PacketResult::Success)
    {
        if (debugserver_pid != LLDB_INVALID_PROCESS_ID)
            ::kill (debugserver_pid, SIGINT);
    }
    return packet_result;
#endif
}

GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qQueryGDBServer (StringExtractorGDBRemote &packet)
{
    if (m_pending_gdb_server.pid == LLDB_INVALID_PROCESS_ID)
        return SendErrorResponse(4);

    JSONObject::SP server_sp = std::make_shared<JSONObject>();
    server_sp->SetObject("port", std::make_shared<JSONNumber>(m_pending_gdb_server.port));
    if (!m_pending_gdb_server.socket_name.empty())
        server_sp->SetObject("socket_name",
                             std::make_shared<JSONString>(m_pending_gdb_server.socket_name.c_str()));

    JSONArray server_list;
    server_list.AppendObject(server_sp);

    StreamGDBRemote response;
    server_list.Write(response);

    StreamGDBRemote escaped_response;
    escaped_response.PutEscapedBytes(response.GetData(), response.GetSize());
    return SendPacketNoLock(escaped_response.GetData(), escaped_response.GetSize());
}

GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qKillSpawnedProcess (StringExtractorGDBRemote &packet)
{
    packet.SetFilePos(::strlen ("qKillSpawnedProcess:"));

    lldb::pid_t pid = packet.GetU64(LLDB_INVALID_PROCESS_ID);

    // verify that we know anything about this pid.
    // Scope for locker
    {
        std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
        if (m_spawned_pids.find(pid) == m_spawned_pids.end())
        {
            // not a pid we know about
            return SendErrorResponse (10);
        }
    }

    // go ahead and attempt to kill the spawned process
    if (KillSpawnedProcess (pid))
        return SendOKResponse ();
    else
        return SendErrorResponse (11);
}

bool
GDBRemoteCommunicationServerPlatform::KillSpawnedProcess (lldb::pid_t pid)
{
    // make sure we know about this process
    {
        std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
        if (m_spawned_pids.find(pid) == m_spawned_pids.end())
            return false;
    }

    // first try a SIGTERM (standard kill)
    Host::Kill (pid, SIGTERM);

    // check if that worked
    for (size_t i=0; i<10; ++i)
    {
        {
            std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
            if (m_spawned_pids.find(pid) == m_spawned_pids.end())
            {
                // it is now killed
                return true;
            }
        }
        usleep (10000);
    }

    // check one more time after the final usleep
    {
        std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
        if (m_spawned_pids.find(pid) == m_spawned_pids.end())
            return true;
    }

    // the launched process still lives.  Now try killing it again,
    // this time with an unblockable signal.
    Host::Kill (pid, SIGKILL);

    for (size_t i=0; i<10; ++i)
    {
        {
            std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
            if (m_spawned_pids.find(pid) == m_spawned_pids.end())
            {
                // it is now killed
                return true;
            }
        }
        usleep (10000);
    }

    // check one more time after the final usleep
    // Scope for locker
    {
        std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
        if (m_spawned_pids.find(pid) == m_spawned_pids.end())
            return true;
    }

    // no luck - the process still lives
    return false;
}

GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qProcessInfo (StringExtractorGDBRemote &packet)
{
    lldb::pid_t pid = m_process_launch_info.GetProcessID ();
    m_process_launch_info.Clear ();

    if (pid == LLDB_INVALID_PROCESS_ID)
        return SendErrorResponse (1);

    ProcessInstanceInfo proc_info;
    if (!Host::GetProcessInfo (pid, proc_info))
        return SendErrorResponse (1);

    StreamString response;
    CreateProcessInfoResponse_DebugServerStyle(proc_info, response);
    return SendPacketNoLock (response.GetData (), response.GetSize ());
}

GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qGetWorkingDir (StringExtractorGDBRemote &packet)
{
    // If this packet is sent to a platform, then change the current working directory

    char cwd[PATH_MAX];
    if (getcwd(cwd, sizeof(cwd)) == NULL)
        return SendErrorResponse(errno);

    StreamString response;
    response.PutBytesAsRawHex8(cwd, strlen(cwd));
    return SendPacketNoLock(response.GetData(), response.GetSize());
}

GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_QSetWorkingDir (StringExtractorGDBRemote &packet)
{
    packet.SetFilePos (::strlen ("QSetWorkingDir:"));
    std::string path;
    packet.GetHexByteString (path);

    // If this packet is sent to a platform, then change the current working directory
    if (::chdir(path.c_str()) != 0)
        return SendErrorResponse (errno);
    return SendOKResponse ();
}

GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qC (StringExtractorGDBRemote &packet)
{
    // NOTE: lldb should now be using qProcessInfo for process IDs.  This path here
    // should not be used.  It is reporting process id instead of thread id.  The
    // correct answer doesn't seem to make much sense for lldb-platform.
    // CONSIDER: flip to "unsupported".
    lldb::pid_t pid = m_process_launch_info.GetProcessID();

    StreamString response;
    response.Printf("QC%" PRIx64, pid);

    // If we launch a process and this GDB server is acting as a platform,
    // then we need to clear the process launch state so we can start
    // launching another process. In order to launch a process a bunch or
    // packets need to be sent: environment packets, working directory,
    // disable ASLR, and many more settings. When we launch a process we
    // then need to know when to clear this information. Currently we are
    // selecting the 'qC' packet as that packet which seems to make the most
    // sense.
    if (pid != LLDB_INVALID_PROCESS_ID)
    {
        m_process_launch_info.Clear();
    }

    return SendPacketNoLock (response.GetData(), response.GetSize());
}

GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_jSignalsInfo(StringExtractorGDBRemote &packet)
{
    StructuredData::Array signal_array;

    const auto &signals = Host::GetUnixSignals();
    for (auto signo = signals->GetFirstSignalNumber();
         signo != LLDB_INVALID_SIGNAL_NUMBER;
         signo = signals->GetNextSignalNumber(signo))
    {
        auto dictionary = std::make_shared<StructuredData::Dictionary>();

        dictionary->AddIntegerItem("signo", signo);
        dictionary->AddStringItem("name", signals->GetSignalAsCString(signo));

        bool suppress, stop, notify;
        signals->GetSignalInfo(signo, suppress, stop, notify);
        dictionary->AddBooleanItem("suppress", suppress);
        dictionary->AddBooleanItem("stop", stop);
        dictionary->AddBooleanItem("notify", notify);

        signal_array.Push(dictionary);
    }

    StreamString response;
    signal_array.Dump(response);
    return SendPacketNoLock(response.GetData(), response.GetSize());
}

bool
GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped (lldb::pid_t pid)
{
    std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
    FreePortForProcess(pid);
    m_spawned_pids.erase(pid);
    return true;
}

Error
GDBRemoteCommunicationServerPlatform::LaunchProcess ()
{
    if (!m_process_launch_info.GetArguments ().GetArgumentCount ())
        return Error ("%s: no process command line specified to launch", __FUNCTION__);

    // specify the process monitor if not already set.  This should
    // generally be what happens since we need to reap started
    // processes.
    if (!m_process_launch_info.GetMonitorProcessCallback ())
        m_process_launch_info.SetMonitorProcessCallback(
            std::bind(&GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped, this, std::placeholders::_1),
            false);

    Error error = Host::LaunchProcess(m_process_launch_info);
    if (!error.Success ())
    {
        fprintf (stderr, "%s: failed to launch executable %s", __FUNCTION__, m_process_launch_info.GetArguments ().GetArgumentAtIndex (0));
        return error;
    }

    printf ("Launched '%s' as process %" PRIu64 "...\n", m_process_launch_info.GetArguments ().GetArgumentAtIndex (0), m_process_launch_info.GetProcessID());

    // add to list of spawned processes.  On an lldb-gdbserver, we
    // would expect there to be only one.
    const auto pid = m_process_launch_info.GetProcessID();
    if (pid != LLDB_INVALID_PROCESS_ID)
    {
        // add to spawned pids
        std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
        m_spawned_pids.insert(pid);
    }

    return error;
}

void
GDBRemoteCommunicationServerPlatform::SetPortMap (PortMap &&port_map)
{
    m_port_map = port_map;
}

uint16_t
GDBRemoteCommunicationServerPlatform::GetNextAvailablePort ()
{
    if (m_port_map.empty())
        return 0; // Bind to port zero and get a port, we didn't have any limitations

    for (auto &pair : m_port_map)
    {
        if (pair.second == LLDB_INVALID_PROCESS_ID)
        {
            pair.second = ~(lldb::pid_t)LLDB_INVALID_PROCESS_ID;
            return pair.first;
        }
    }
    return UINT16_MAX;
}

bool
GDBRemoteCommunicationServerPlatform::AssociatePortWithProcess (uint16_t port, lldb::pid_t pid)
{
    PortMap::iterator pos = m_port_map.find(port);
    if (pos != m_port_map.end())
    {
        pos->second = pid;
        return true;
    }
    return false;
}

bool
GDBRemoteCommunicationServerPlatform::FreePort (uint16_t port)
{
    PortMap::iterator pos = m_port_map.find(port);
    if (pos != m_port_map.end())
    {
        pos->second = LLDB_INVALID_PROCESS_ID;
        return true;
    }
    return false;
}

bool
GDBRemoteCommunicationServerPlatform::FreePortForProcess (lldb::pid_t pid)
{
    if (!m_port_map.empty())
    {
        for (auto &pair : m_port_map)
        {
            if (pair.second == pid)
            {
                pair.second = LLDB_INVALID_PROCESS_ID;
                return true;
            }
        }
    }
    return false;
}

const FileSpec&
GDBRemoteCommunicationServerPlatform::GetDomainSocketDir()
{
    static FileSpec g_domainsocket_dir;
    static std::once_flag g_once_flag;

    std::call_once(g_once_flag, []() {
        const char* domainsocket_dir_env = ::getenv("LLDB_DEBUGSERVER_DOMAINSOCKET_DIR");
        if (domainsocket_dir_env != nullptr)
            g_domainsocket_dir = FileSpec(domainsocket_dir_env, false);
        else
            HostInfo::GetLLDBPath(ePathTypeLLDBTempSystemDir, g_domainsocket_dir);
    });

    return g_domainsocket_dir;
}

FileSpec
GDBRemoteCommunicationServerPlatform::GetDomainSocketPath(const char* prefix)
{
    llvm::SmallString<PATH_MAX> socket_path;
    llvm::SmallString<PATH_MAX> socket_name((llvm::StringRef(prefix) + ".%%%%%%").str());

    FileSpec socket_path_spec(GetDomainSocketDir());
    socket_path_spec.AppendPathComponent(socket_name.c_str());

    llvm::sys::fs::createUniqueFile(socket_path_spec.GetCString(), socket_path);
    return FileSpec(socket_path.c_str(), false);
}

void
GDBRemoteCommunicationServerPlatform::SetPortOffset(uint16_t port_offset)
{
    m_port_offset = port_offset;
}

void
GDBRemoteCommunicationServerPlatform::SetPendingGdbServer(lldb::pid_t pid,
                                                          uint16_t port,
                                                          const std::string& socket_name)
{
    m_pending_gdb_server.pid = pid;
    m_pending_gdb_server.port = port;
    m_pending_gdb_server.socket_name = socket_name;
}
