root/src/kits/debugger/target_host_interface/TargetHostInterface.cpp
/*
 * Copyright 2016, Rene Gollent, rene@gollent.com.
 * Distributed under the terms of the MIT License.
 */

#include "TargetHostInterface.h"

#include <stdio.h>

#include <AutoLocker.h>

#include "DebuggerInterface.h"
#include "MessageCodes.h"
#include "TeamDebugger.h"


// #pragma mark - TeamDebuggerOptions


TeamDebuggerOptions::TeamDebuggerOptions()
        :
        requestType(TEAM_DEBUGGER_REQUEST_UNKNOWN),
        commandLineArgc(0),
        commandLineArgv(NULL),
        team(-1),
        thread(-1),
        settingsManager(NULL),
        userInterface(NULL),
        coreFilePath(NULL)
{
}


// #pragma mark - TargetHostInterface


TargetHostInterface::TargetHostInterface()
        :
        BLooper(),
        fListeners(),
        fTeamDebuggers(20)
{
}


TargetHostInterface::~TargetHostInterface()
{
        for (ListenerList::Iterator it = fListeners.GetIterator();
                        Listener* listener = it.Next();) {
                listener->TargetHostInterfaceQuit(this);
        }
}


status_t
TargetHostInterface::StartTeamDebugger(const TeamDebuggerOptions& options)
{
        // we only want to stop in main for teams we're responsible for
        // creating ourselves.
        bool stopInMain = options.requestType == TEAM_DEBUGGER_REQUEST_CREATE;
        team_id team = options.team;
        thread_id thread = options.thread;

        AutoLocker<TargetHostInterface> interfaceLocker(this);
        if (options.requestType == TEAM_DEBUGGER_REQUEST_CREATE) {
                status_t error = CreateTeam(options.commandLineArgc,
                        options.commandLineArgv, team);
                if (error != B_OK)
                        return error;
                thread = team;
        }

        if (options.requestType != TEAM_DEBUGGER_REQUEST_LOAD_CORE) {

                if (team < 0 && thread < 0)
                        return B_BAD_VALUE;

                if (team < 0) {
                        status_t error = FindTeamByThread(thread, team);
                        if (error != B_OK)
                                return error;
                }

                TeamDebugger* debugger = FindTeamDebugger(team);
                if (debugger != NULL) {
                        debugger->Activate();
                        return B_OK;
                }
        }

        return _StartTeamDebugger(team, options, stopInMain);
}


int32
TargetHostInterface::CountTeamDebuggers() const
{
        return fTeamDebuggers.CountItems();
}


TeamDebugger*
TargetHostInterface::TeamDebuggerAt(int32 index) const
{
        return fTeamDebuggers.ItemAt(index);
}


TeamDebugger*
TargetHostInterface::FindTeamDebugger(team_id team) const
{
        for (int32 i = 0; i < fTeamDebuggers.CountItems(); i++) {
                TeamDebugger* debugger = fTeamDebuggers.ItemAt(i);
                if (debugger->TeamID() == team && !debugger->IsPostMortem())
                        return debugger;
        }

        return NULL;
}


status_t
TargetHostInterface::AddTeamDebugger(TeamDebugger* debugger)
{
        if (!fTeamDebuggers.BinaryInsert(debugger, &_CompareDebuggers))
                return B_NO_MEMORY;

        return B_OK;
}


void
TargetHostInterface::RemoveTeamDebugger(TeamDebugger* debugger)
{
        for (int32 i = 0; i < fTeamDebuggers.CountItems(); i++) {
                if (fTeamDebuggers.ItemAt(i) == debugger) {
                        fTeamDebuggers.RemoveItemAt(i);
                        break;
                }
        }
}


void
TargetHostInterface::AddListener(Listener* listener)
{
        AutoLocker<TargetHostInterface> interfaceLocker(this);
        fListeners.Add(listener);
}


void
TargetHostInterface::RemoveListener(Listener* listener)
{
        AutoLocker<TargetHostInterface> interfaceLocker(this);
        fListeners.Remove(listener);
}


void
TargetHostInterface::Quit()
{
        if (fTeamDebuggers.CountItems() == 0)
                BLooper::Quit();
}


void
TargetHostInterface::MessageReceived(BMessage* message)
{
        switch (message->what) {
        case MSG_TEAM_DEBUGGER_QUIT:
        {
                thread_id thread;
                if (message->FindInt32("thread", &thread) == B_OK)
                        wait_for_thread(thread, NULL);
                break;
        }
        case MSG_TEAM_RESTART_REQUESTED:
        {
                int32 teamID;
                if (message->FindInt32("team", &teamID) != B_OK)
                        break;

                TeamDebugger* debugger = FindTeamDebugger(teamID);

                UserInterface* userInterface = debugger->GetUserInterface()->Clone();
                if (userInterface == NULL)
                        break;

                BReference<UserInterface> userInterfaceReference(userInterface, true);

                TeamDebuggerOptions options;
                options.requestType = TEAM_DEBUGGER_REQUEST_CREATE;
                options.commandLineArgc = debugger->ArgumentCount();
                options.commandLineArgv = debugger->Arguments();
                options.settingsManager = debugger->GetSettingsManager();
                options.userInterface = userInterface;
                status_t result = StartTeamDebugger(options);
                if (result == B_OK) {
                        userInterfaceReference.Detach();
                        debugger->PostMessage(B_QUIT_REQUESTED);
                }
                break;
        }
        default:
                BLooper::MessageReceived(message);
                break;
        }
}


void
TargetHostInterface::TeamDebuggerStarted(TeamDebugger* debugger)
{
        AutoLocker<TargetHostInterface> locker(this);
        AddTeamDebugger(debugger);
        _NotifyTeamDebuggerStarted(debugger);
}


void
TargetHostInterface::TeamDebuggerRestartRequested(TeamDebugger* debugger)
{
        BMessage message(MSG_TEAM_RESTART_REQUESTED);
        message.AddInt32("team", debugger->TeamID());
        PostMessage(&message);
}


void
TargetHostInterface::TeamDebuggerQuit(TeamDebugger* debugger)
{
        AutoLocker<TargetHostInterface> interfaceLocker(this);
        RemoveTeamDebugger(debugger);

        if (debugger->Thread() >= 0) {
                _NotifyTeamDebuggerQuit(debugger);
                BMessage message(MSG_TEAM_DEBUGGER_QUIT);
                message.AddInt32("thread", debugger->Thread());
                PostMessage(&message);
        }
}


status_t
TargetHostInterface::_StartTeamDebugger(team_id teamID,
        const TeamDebuggerOptions& options, bool stopInMain)
{
        UserInterface* userInterface = options.userInterface;
        if (userInterface == NULL) {
                fprintf(stderr, "Error: Requested team debugger start without "
                        "valid user interface!\n");
                return B_BAD_VALUE;
        }

        thread_id threadID = options.thread;
        if (options.commandLineArgv != NULL)
                threadID = teamID;

        DebuggerInterface* interface = NULL;
        TeamDebugger* debugger = NULL;
        status_t error = B_OK;
        if (options.requestType != TEAM_DEBUGGER_REQUEST_LOAD_CORE) {
                error = Attach(teamID, options.thread, interface);
                if (error != B_OK) {
                        fprintf(stderr, "Error: Failed to attach to team %" B_PRId32
                                ": %s!\n", teamID, strerror(error));
                        return error;
                }
        } else {
                error = LoadCore(options.coreFilePath, interface, threadID);
                if (error != B_OK) {
                        fprintf(stderr, "Error: Failed to load core file '%s': %s!\n",
                                options.coreFilePath, strerror(error));
                        return error;
                }
        }

        BReference<DebuggerInterface> debuggerInterfaceReference(interface,
                true);
        debugger = new(std::nothrow) TeamDebugger(this, userInterface,
                options.settingsManager);
        if (debugger != NULL) {
                error = debugger->Init(interface, threadID,
                        options.commandLineArgc, options.commandLineArgv, stopInMain);
        }

        if (error != B_OK) {
                printf("Error: debugger for team %" B_PRId32 " on interface %s failed"
                        " to init: %s!\n", interface->TeamID(), Name(), strerror(error));
                delete debugger;
                debugger = NULL;
        } else {
                printf("debugger for team %" B_PRId32 " on interface %s created and"
                        " initialized successfully!\n", interface->TeamID(), Name());
        }

        return error;
}


void
TargetHostInterface::_NotifyTeamDebuggerStarted(TeamDebugger* debugger)
{
        for (ListenerList::Iterator it = fListeners.GetIterator();
                        Listener* listener = it.Next();) {
                listener->TeamDebuggerStarted(debugger);
        }
}


void
TargetHostInterface::_NotifyTeamDebuggerQuit(TeamDebugger* debugger)
{
        for (ListenerList::Iterator it = fListeners.GetIterator();
                        Listener* listener = it.Next();) {
                listener->TeamDebuggerQuit(debugger);
        }
}


/*static*/ int
TargetHostInterface::_CompareDebuggers(const TeamDebugger* a,
        const TeamDebugger* b)
{
        return a->TeamID() < b->TeamID() ? -1 : 1;
}


// #pragma mark - TargetHostInterface::Listener


TargetHostInterface::Listener::~Listener()
{
}


void
TargetHostInterface::Listener::TeamDebuggerStarted(TeamDebugger* debugger)
{
}


void
TargetHostInterface::Listener::TeamDebuggerQuit(TeamDebugger* debugger)
{
}


void
TargetHostInterface::Listener::TargetHostInterfaceQuit(
        TargetHostInterface* interface)
{
}