root/src/add-ons/kernel/file_systems/packagefs/volume/PackageSettings.cpp
/*
 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include "PackageSettings.h"

#include <driver_settings.h>

#include <AutoDeleterDrivers.h>
#include <directories.h>
#include <fs/KPath.h>
#include <vfs.h>

#include "DebugSupport.h"


static const char* const kBlockedEntriesParameterName = "BlockedEntries";
static const char* const kLegacyBlockedEntriesParameterName = "EntryBlacklist";


// #pragma mark - PackageSettingsItem


PackageSettingsItem::PackageSettingsItem()
        :
        fName(),
        fEntries()
{
}


PackageSettingsItem::~PackageSettingsItem()
{
        Entry* entry = fEntries.Clear(true);
        while (entry != NULL) {
                Entry* next = entry->HashNext();
                delete entry;
                entry = next;
        }
}


status_t
PackageSettingsItem::Init(const char* name)
{
        if (!fName.SetTo(name) || fEntries.Init() != B_OK)
                RETURN_ERROR(B_NO_MEMORY);
        return B_OK;
}


status_t
PackageSettingsItem::ApplySettings(const driver_parameter* parameters,
        int parameterCount)
{
        for (int i = 0; i < parameterCount; i++) {
                const driver_parameter& subParameter = parameters[i];
                if (strcmp(subParameter.name, kBlockedEntriesParameterName) != 0
                        && strcmp(subParameter.name, kLegacyBlockedEntriesParameterName)
                                != 0)
                        continue;

                status_t error = _AddBlockedEntries(subParameter);
                // abort only in case of serious issues (memory shortage)
                if (error == B_NO_MEMORY)
                        return error;
        }

        return B_OK;
}


void
PackageSettingsItem::AddEntry(Entry* entry)
{
        fEntries.Insert(entry);
}


status_t
PackageSettingsItem::AddEntry(const char* path, Entry*& _entry)
{
        Entry* parent = NULL;

        while (*path != '\0') {
                while (*path == '/') {
                        path++;
                        continue;
                }

                const char* componentEnd = strchr(path, '/');
                if (componentEnd == NULL)
                        componentEnd = path + strlen(path);

                String name;
                if (!name.SetTo(path, componentEnd - path))
                        RETURN_ERROR(B_NO_MEMORY);

                Entry* entry = FindEntry(parent, name);
                if (entry == NULL) {
                        entry = new(std::nothrow) Entry(parent, name);
                        if (entry == NULL)
                                RETURN_ERROR(B_NO_MEMORY);
                        AddEntry(entry);
                }

                path = componentEnd;
                parent = entry;
        }

        if (parent == NULL)
                return B_BAD_VALUE;

        _entry = parent;
        return B_OK;
}


PackageSettingsItem::Entry*
PackageSettingsItem::FindEntry(Entry* parent, const String& name) const
{
        return fEntries.Lookup(EntryKey(parent, name));
}


PackageSettingsItem::Entry*
PackageSettingsItem::FindEntry(Entry* parent, const char* name) const
{
        return fEntries.Lookup(EntryKey(parent, name));
}


status_t
PackageSettingsItem::_AddBlockedEntries(const driver_parameter& parameter)
{
        for (int i = 0; i < parameter.parameter_count; i++) {
                Entry* entry;
                status_t error = AddEntry(parameter.parameters[i].name, entry);
                // abort only in case of serious issues (memory shortage)
                if (error == B_NO_MEMORY)
                        return error;

                entry->SetBlocked(true);
        }

        return B_OK;
}


// #pragma mark - PackageSettings


PackageSettings::PackageSettings()
        :
        fPackageItems()
{
}


PackageSettings::~PackageSettings()
{
        PackageSettingsItem* item = fPackageItems.Clear(true);
        while (item != NULL) {
                PackageSettingsItem* next = item->HashNext();
                delete item;
                item = next;
        }
}


status_t
PackageSettings::Load(dev_t mountPointDeviceID, ino_t mountPointNodeID,
        PackageFSMountType mountType)
{
        status_t error = fPackageItems.Init();
        if (error != B_OK)
                RETURN_ERROR(error);

        // First get the safe mode options. Those apply to the system package.
        if (mountType == PACKAGE_FS_MOUNT_TYPE_SYSTEM) {
                void* settingsHandle = load_driver_settings(B_SAFEMODE_DRIVER_SETTINGS);
                if (settingsHandle != NULL) {
                        if (const driver_settings* settings
                                        = get_driver_settings(settingsHandle)) {
                                error = _AddPackageSettingsItem("haiku", settings->parameters,
                                        settings->parameter_count);
                                // abort only in case of serious issues (memory shortage)
                                if (error == B_NO_MEMORY)
                                        return error;
                        }
                        unload_driver_settings(settingsHandle);
                }
        }

        // get the mount point relative settings file path
        const char* settingsFilePath = mountType == PACKAGE_FS_MOUNT_TYPE_HOME
                ? &(kUserSettingsGlobalDirectory "/packages")
                        [strlen(kUserConfigDirectory) + 1]
                : &(kSystemSettingsDirectory "/packages")[strlen(kSystemDirectory) + 1];

        // get an absolute path
        KPath path;
        if (path.InitCheck() != B_OK)
                RETURN_ERROR(path.InitCheck());

        error = vfs_entry_ref_to_path(mountPointDeviceID, mountPointNodeID,
                NULL, true, path.LockBuffer(), path.BufferSize());
        if (error != B_OK)
                return error;
        path.UnlockBuffer();

        error = path.Append(settingsFilePath);
        if (error != B_OK)
                return error;

        // load the driver settings
        DriverSettingsUnloader settingsHandle(load_driver_settings(path.Path()));
        if (!settingsHandle.IsSet())
                return B_ENTRY_NOT_FOUND;

        const driver_settings* settings
                = get_driver_settings(settingsHandle.Get());
        for (int i = 0; i < settings->parameter_count; i++) {
                const driver_parameter& parameter = settings->parameters[i];
                if (strcmp(parameter.name, "Package") != 0
                        || parameter.value_count < 1) {
                        continue;
                }

                error = _AddPackageSettingsItem(parameter.values[0],
                        parameter.parameters, parameter.parameter_count);
                // abort only in case of serious issues (memory shortage)
                if (error == B_NO_MEMORY)
                        return error;
        }

        return B_OK;
}


const PackageSettingsItem*
PackageSettings::PackageItemFor(const String& name) const
{
        return fPackageItems.Lookup(name);
}


status_t
PackageSettings::_AddPackageSettingsItem(const char* name,
        const driver_parameter* parameters, int parameterCount)
{
        // get/create the package item
        PackageSettingsItem* packageItem = fPackageItems.Lookup(StringKey(name));
        if (packageItem == NULL) {
                packageItem = new(std::nothrow) PackageSettingsItem;
                if (packageItem == NULL || packageItem->Init(name) != B_OK) {
                        delete packageItem;
                        RETURN_ERROR(B_NO_MEMORY);
                }

                fPackageItems.Insert(packageItem);
        }

        // apply the settings
        return packageItem->ApplySettings(parameters, parameterCount);
}