root/src/kits/debugger/debug_info/loading_state_handlers/DwarfLoadingStateHandler.cpp
/*
 * Copyright 2014, Rene Gollent, rene@gollent.com.
 * Distributed under the terms of the MIT License.
 */


#include "DwarfLoadingStateHandler.h"

#include <sys/wait.h>

#include <Entry.h>
#include <InterfaceDefs.h>
#include <Path.h>
#include <package/solver/Solver.h>
#include <package/solver/SolverPackage.h>

#include "AutoDeleter.h"
#include "DwarfFile.h"
#include "DwarfImageDebugInfoLoadingState.h"
#include "package/manager/PackageManager.h"
#include "Tracing.h"
#include "UserInterface.h"


using namespace BPackageKit;
using BPackageKit::BManager::BPrivate::BPackageManager;


enum {
        USER_CHOICE_INSTALL_PACKAGE = 0,
        USER_CHOICE_LOCATE_FILE ,
        USER_CHOICE_SKIP
};


DwarfLoadingStateHandler::DwarfLoadingStateHandler()
        :
        ImageDebugLoadingStateHandler()
{
}


DwarfLoadingStateHandler::~DwarfLoadingStateHandler()
{
}


bool
DwarfLoadingStateHandler::SupportsState(
        SpecificImageDebugInfoLoadingState* state)
{
        return dynamic_cast<DwarfImageDebugInfoLoadingState*>(state) != NULL;
}


void
DwarfLoadingStateHandler::HandleState(
        SpecificImageDebugInfoLoadingState* state, UserInterface* interface)
{
        DwarfImageDebugInfoLoadingState* dwarfState
                = dynamic_cast<DwarfImageDebugInfoLoadingState*>(state);

        if (dwarfState == NULL) {
                ERROR("DwarfLoadingStateHandler::HandleState() passed "
                        "non-dwarf state object %p.", state);
                return;
        }

        DwarfFileLoadingState& fileState = dwarfState->GetFileState();

        if (!interface->IsInteractive()) {
                fileState.state = DWARF_FILE_LOADING_STATE_USER_INPUT_PROVIDED;
                return;
        }

        BString requiredPackage;
        try {
                // Package Kit may throw exceptions.
                _GetMatchingDebugInfoPackage(fileState.externalInfoFileName,
                        requiredPackage);
        } catch (...) {
                requiredPackage = BString();
        }

        // loop so that the user has a chance to retry or locate the file manually
        // in case package installation fails, e.g. due to transient download
        // issues.
        for (;;) {
                int32 choice;
                BString message;
                if (requiredPackage.IsEmpty()) {
                        message.SetToFormat("The debug information file '%s' for "
                                "image '%s' is missing. Would you like to locate the file "
                                "manually?", fileState.externalInfoFileName.String(),
                                fileState.dwarfFile->Name());
                        choice = interface->SynchronouslyAskUser("Debug info missing",
                                message.String(), "Locate", "Skip", NULL);
                        if (choice == 0)
                                choice = USER_CHOICE_LOCATE_FILE;
                        else if (choice == 1)
                                choice = USER_CHOICE_SKIP;
                } else {
                        message.SetToFormat("The debug information file '%s' for "
                                "image '%s' is missing, but can be found in the package "
                                "'%s'. Would you like to install it, or locate the file "
                                "manually?", fileState.externalInfoFileName.String(),
                                fileState.dwarfFile->Name(), requiredPackage.String());
                        choice = interface->SynchronouslyAskUser("Debug info missing",
                                message.String(), "Install", "Locate", "Skip");
                }

                if (choice == USER_CHOICE_INSTALL_PACKAGE) {
                        // TODO: integrate the package installation functionality directly.
                        BString command;
                        command.SetToFormat("/bin/pkgman install -y %s",
                                requiredPackage.String());
                        BString notification;
                        notification.SetToFormat("Installing package %s" B_UTF8_ELLIPSIS,
                                requiredPackage.String());
                        interface->NotifyBackgroundWorkStatus(notification);
                        int error = system(command.String());
                        if (interface->IsInteractive()) {
                                if (WIFEXITED(error)) {
                                        error = WEXITSTATUS(error);
                                        if (error == B_OK)
                                                break;
                                        message.SetToFormat("Package installation failed: %s.",
                                                strerror(error));
                                        interface->NotifyUser("Error", message.String(),
                                                USER_NOTIFICATION_ERROR);
                                        continue;
                                }
                        }
                        break;
                } else if (choice == USER_CHOICE_LOCATE_FILE) {
                        entry_ref ref;
                        interface->SynchronouslyAskUserForFile(&ref);
                        BPath path(&ref);
                        if (path.InitCheck() == B_OK)
                                fileState.locatedExternalInfoPath = path.Path();
                        break;
                } else
                        break;
        }

        fileState.state = DWARF_FILE_LOADING_STATE_USER_INPUT_PROVIDED;
}


status_t
DwarfLoadingStateHandler::_GetMatchingDebugInfoPackage(
        const BString& debugFileName, BString& _packageName)
{
        BString resolvableName;
        BPackageVersion requiredVersion;
        BPackageManager::ClientInstallationInterface clientInterface;
        BPackageManager::UserInteractionHandler handler;

        BPackageManager packageManager(B_PACKAGE_INSTALLATION_LOCATION_SYSTEM,
                &clientInterface, &handler);
        packageManager.Init(BPackageManager::B_ADD_INSTALLED_REPOSITORIES
                | BPackageManager::B_ADD_REMOTE_REPOSITORIES);
        BObjectList<BSolverPackage> packages;
        status_t error = _GetResolvableName(debugFileName, resolvableName,
                requiredVersion);
        if (error != B_OK)
                return error;

        error = packageManager.Solver()->FindPackages(resolvableName,
                BSolver::B_FIND_IN_PROVIDES, packages);
        if (error != B_OK)
                return error;
        else if (packages.CountItems() == 0)
                return B_ENTRY_NOT_FOUND;

        for (int32 i = 0; i < packages.CountItems(); i++) {
                BSolverPackage* package = packages.ItemAt(i);
                if (requiredVersion.Compare(package->Version()) == 0) {
                        _packageName = package->Name();
                        return B_OK;
                }
        }

        return B_ENTRY_NOT_FOUND;
}


status_t
DwarfLoadingStateHandler::_GetResolvableName(const BString& debugFileName,
        BString& _resolvableName, BPackageVersion& _resolvableVersion)
{
        BString fileName;
        BString packageName;
        BString packageVersion;

        int32 startIndex = 0;
        int32 endIndex = debugFileName.FindFirst('(');
        if (endIndex < 0)
                return B_BAD_VALUE;

        debugFileName.CopyInto(fileName, 0, endIndex);
        startIndex = endIndex + 1;
        endIndex = debugFileName.FindFirst('-', startIndex);
        if (endIndex < 0)
                return B_BAD_VALUE;

        debugFileName.CopyInto(packageName, startIndex, endIndex - startIndex);
        startIndex = endIndex + 1;
        endIndex = debugFileName.FindFirst(')', startIndex);
        if (endIndex < 0)
                return B_BAD_VALUE;

        debugFileName.CopyInto(packageVersion, startIndex,
                endIndex - startIndex);

        _resolvableName.SetToFormat("debuginfo:%s(%s)", fileName.String(), packageName.String());

        return _resolvableVersion.SetTo(packageVersion);
}