root/src/servers/package/PackageDaemon.cpp
/*
 * Copyright 2013, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Ingo Weinhold <ingo_weinhold@gmx.de>
 */


#include "PackageDaemon.h"

#include <errno.h>
#include <string.h>

#include <Directory.h>
#include <NodeMonitor.h>

#include <AutoDeleter.h>
#include <package/DaemonDefs.h>

#include "DebugSupport.h"
#include "Root.h"
#include "Volume.h"


using namespace BPackageKit::BPrivate;


PackageDaemon::PackageDaemon(status_t* _error)
        :
        BServer(B_PACKAGE_DAEMON_APP_SIGNATURE, false, _error),
        fSystemRoot(NULL),
        fRoots(10),
        fVolumeWatcher()
{
}


PackageDaemon::~PackageDaemon()
{
        for (int32 i = 0; Root* root = fRoots.ItemAt(i); i++)
                root->ReleaseReference();
        fRoots.MakeEmpty(false);
}


status_t
PackageDaemon::Init()
{
        status_t error = fVolumeWatcher.StartWatching(BMessenger(this, this));
        if (error != B_OK) {
                ERROR("PackageDaemon::Init(): failed to start volume watching: %s\n",
                        strerror(error));
        }

        // register all packagefs volumes
        for (int32 cookie = 0;;) {
                dev_t device = next_dev(&cookie);
                if (device < 0)
                        break;

                _RegisterVolume(device);
        }

        return B_OK;
}


void
PackageDaemon::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case B_NODE_MONITOR:
                {
                        int32 opcode;
                        if (message->FindInt32("opcode", &opcode) != B_OK)
                                break;

                        if (opcode == B_DEVICE_MOUNTED)
                                _HandleVolumeMounted(message);
                        else if (opcode == B_DEVICE_UNMOUNTED)
                                _HandleVolumeUnmounted(message);
                        break;
                }

                case B_MESSAGE_GET_INSTALLATION_LOCATION_INFO:
                case B_MESSAGE_COMMIT_TRANSACTION:
                {
                        status_t error;
                        node_ref nodeRef;

                        // Get the node_ref of the filesystem root to see which one it is
                        error = message->FindInt32("volume", &nodeRef.device);
                        if (error == B_OK)
                                error = message->FindInt64("root", &nodeRef.node);

                        if (fSystemRoot != NULL && (error != B_OK
                                        || fSystemRoot->NodeRef() == nodeRef)) {
                                fSystemRoot->HandleRequest(DetachCurrentMessage());
                        } else if (error == B_OK) {
                                Root* root = _FindRoot(nodeRef);
                                if (root != NULL) {
                                        root->HandleRequest(DetachCurrentMessage());
                                }
                        }
                        break;
                }

                default:
                        BServer::MessageReceived(message);
                        break;
        }
}


status_t
PackageDaemon::_RegisterVolume(dev_t deviceID)
{
        // get the FS info and check whether this is a package FS volume at all
        fs_info info;
        status_t error = fs_stat_dev(deviceID, &info);
        if (error != 0)
                RETURN_ERROR(error);

        if (strcmp(info.fsh_name, "packagefs") != 0)
                RETURN_ERROR(B_BAD_VALUE);

        // create a volume
        Volume* volume = new(std::nothrow) Volume(this);
        if (volume == NULL)
                RETURN_ERROR(B_NO_MEMORY);
        ObjectDeleter<Volume> volumeDeleter(volume);

        node_ref rootRef;
        error = volume->Init(node_ref(info.dev, info.root), rootRef);
        if (error != B_OK)
                RETURN_ERROR(error);

        if (volume->MountType() == PACKAGE_FS_MOUNT_TYPE_CUSTOM) {
// TODO: Or maybe not?
                INFORM("skipping custom mounted volume at \"%s\"\n",
                        volume->Path().String());
                return B_OK;
        }

        // get the root for the volume and register it
        Root* root;
        error = _GetOrCreateRoot(rootRef, root);
        if (error != B_OK)
                RETURN_ERROR(error);

        error = root->RegisterVolume(volume);
        if (error != B_OK) {
                _PutRoot(root);
                RETURN_ERROR(error);
        }
        volumeDeleter.Detach();

        INFORM("volume at \"%s\" registered\n", volume->Path().String());

        return B_OK;
}


void
PackageDaemon::_UnregisterVolume(Volume* volume)
{
        volume->Unmounted();

        INFORM("volume at \"%s\" unregistered\n", volume->Path().String());

        Root* root = volume->GetRoot();
        root->UnregisterVolume(volume);

        _PutRoot(root);
}


status_t
PackageDaemon::_GetOrCreateRoot(const node_ref& nodeRef, Root*& _root)
{
        Root* root = _FindRoot(nodeRef);
        if (root != NULL) {
                root->AcquireReference();
        } else {
                root = new(std::nothrow) Root;
                if (root == NULL)
                        RETURN_ERROR(B_NO_MEMORY);
                ObjectDeleter<Root> rootDeleter(root);

                bool isSystemRoot = false;
                if (fSystemRoot == NULL) {
                        struct stat st;
                        isSystemRoot = stat("/boot", &st) == 0
                                && node_ref(st.st_dev, st.st_ino) == nodeRef;
                }

                status_t error = root->Init(nodeRef, isSystemRoot);
                if (error != B_OK)
                        RETURN_ERROR(error);

                if (!fRoots.AddItem(root))
                        RETURN_ERROR(B_NO_MEMORY);

                rootDeleter.Detach();

                if (isSystemRoot)
                        fSystemRoot = root;

                INFORM("root at \"%s\" (device: %" B_PRIdDEV ", node: %" B_PRIdINO ") "
                        "registered\n", root->Path().String(), nodeRef.device,
                        nodeRef.node);
        }

        _root = root;
        return B_OK;
}


Root*
PackageDaemon::_FindRoot(const node_ref& nodeRef) const
{
        for (int32 i = 0; Root* root = fRoots.ItemAt(i); i++) {
                if (root->NodeRef() == nodeRef)
                        return root;
        }

        return NULL;
}


void
PackageDaemon::_PutRoot(Root* root)
{
        if (root->ReleaseReference() == 1) {
                INFORM("root at \"%s\" unregistered\n", root->Path().String());
                fRoots.RemoveItem(root, true);
                        // deletes the object
        }
}


Volume*
PackageDaemon::_FindVolume(dev_t deviceID) const
{
        for (int32 i = 0; Root* root = fRoots.ItemAt(i); i++) {
                if (Volume* volume = root->FindVolume(deviceID))
                        return volume;
        }

        return NULL;
}


void
PackageDaemon::_HandleVolumeMounted(const BMessage* message)
{
        int32 device;
        if (message->FindInt32("new device", &device) != B_OK)
                return;

        // _RegisterVolume() also checks whether it is a package FS volume, so we
        // don't need to bother.
        _RegisterVolume(device);
}


void
PackageDaemon::_HandleVolumeUnmounted(const BMessage* message)
{
        int32 device;
        if (message->FindInt32("device", &device) != B_OK)
                return;

        if (Volume* volume = _FindVolume(device))
                _UnregisterVolume(volume);
}


// #pragma mark -


int
main(int argc, const char* const* argv)
{
        status_t error;
        PackageDaemon daemon(&error);
        if (error == B_OK)
                error = daemon.Init();
        if (error != B_OK) {
                FATAL("failed to init server application: %s\n", strerror(error));
                return 1;
        }

        daemon.Run();
        return 0;
}