root/src/servers/launch/Conditions.cpp
/*
 * Copyright 2015, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include "Conditions.h"

#include <stdio.h>

#include <driver_settings.h>
#include <Entry.h>
#include <File.h>
#include <ObjectList.h>
#include <Message.h>
#include <Path.h>
#include <StringList.h>

#include "NetworkWatcher.h"
#include "Utility.h"


class ConditionContainer : public Condition {
protected:
                                                                ConditionContainer(const BMessage& args);
                                                                ConditionContainer();

public:
                        void                            AddCondition(Condition* condition);

        virtual bool                            IsConstant(ConditionContext& context) const;

protected:
                        void                            AddConditionsToString(BString& string) const;

protected:
                        BObjectList<Condition, true> fConditions;
};


class AndCondition : public ConditionContainer {
public:
                                                                AndCondition(const BMessage& args);
                                                                AndCondition();

        virtual bool                            Test(ConditionContext& context) const;
        virtual BString                         ToString() const;
};


class OrCondition : public ConditionContainer {
public:
                                                                OrCondition(const BMessage& args);

        virtual bool                            Test(ConditionContext& context) const;
        virtual bool                            IsConstant(ConditionContext& context) const;

        virtual BString                         ToString() const;
};


class NotCondition : public ConditionContainer {
public:
                                                                NotCondition(const BMessage& args);
                                                                NotCondition();

        virtual bool                            Test(ConditionContext& context) const;
        virtual BString                         ToString() const;
};


class SafeModeCondition : public Condition {
public:
        virtual bool                            Test(ConditionContext& context) const;
        virtual bool                            IsConstant(ConditionContext& context) const;

        virtual BString                         ToString() const;
};


class ReadOnlyCondition : public Condition {
public:
                                                                ReadOnlyCondition(const BMessage& args);

        virtual bool                            Test(ConditionContext& context) const;
        virtual bool                            IsConstant(ConditionContext& context) const;

        virtual BString                         ToString() const;

private:
                        BString                         fPath;
        mutable bool                            fIsReadOnly;
        mutable bool                            fTestPerformed;
};


class FileExistsCondition : public Condition {
public:
                                                                FileExistsCondition(const BMessage& args);

        virtual bool                            Test(ConditionContext& context) const;
        virtual BString                         ToString() const;

private:
                        BStringList                     fPaths;
};


class NetworkAvailableCondition : public Condition {
public:
        virtual bool                            Test(ConditionContext& context) const;
        virtual bool                            IsConstant(ConditionContext& context) const;

        virtual BString                         ToString() const;
};


class SettingCondition : public Condition {
public:
                                                                SettingCondition(const BMessage& args);

        virtual bool                            Test(ConditionContext& context) const;

        virtual BString                         ToString() const;

private:
                        BPath                           fPath;
                        BString                         fField;
                        BString                         fValue;
};


static Condition*
create_condition(const char* name, const BMessage& args)
{
        if (strcmp(name, "and") == 0 && !args.IsEmpty())
                return new AndCondition(args);
        if (strcmp(name, "or") == 0 && !args.IsEmpty())
                return new OrCondition(args);
        if (strcmp(name, "not") == 0 && !args.IsEmpty())
                return new NotCondition(args);

        if (strcmp(name, "safemode") == 0)
                return new SafeModeCondition();
        if (strcmp(name, "read_only") == 0)
                return new ReadOnlyCondition(args);
        if (strcmp(name, "file_exists") == 0)
                return new FileExistsCondition(args);
        if (strcmp(name, "network_available") == 0)
                return new NetworkAvailableCondition();
        if (strcmp(name, "setting") == 0)
                return new SettingCondition(args);

        return NULL;
}


// #pragma mark -


Condition::Condition()
{
}


Condition::~Condition()
{
}


bool
Condition::IsConstant(ConditionContext& context) const
{
        return false;
}


// #pragma mark -


ConditionContainer::ConditionContainer(const BMessage& args)
        :
        fConditions(10)
{
        char* name;
        type_code type;
        int32 count;
        for (int32 index = 0; args.GetInfo(B_MESSAGE_TYPE, index, &name, &type,
                        &count) == B_OK; index++) {
                BMessage message;
                for (int32 messageIndex = 0; args.FindMessage(name, messageIndex,
                                &message) == B_OK; messageIndex++) {
                        AddCondition(create_condition(name, message));
                }
        }
}


ConditionContainer::ConditionContainer()
        :
        fConditions(10)
{
}


void
ConditionContainer::AddCondition(Condition* condition)
{
        if (condition != NULL)
                fConditions.AddItem(condition);
}


/*!     A single constant failing condition makes this constant, too, otherwise,
        a single non-constant condition makes this non-constant as well.
*/
bool
ConditionContainer::IsConstant(ConditionContext& context) const
{
        bool fixed = true;
        for (int32 index = 0; index < fConditions.CountItems(); index++) {
                const Condition* condition = fConditions.ItemAt(index);
                if (condition->IsConstant(context)) {
                        if (!condition->Test(context))
                                return true;
                } else
                        fixed = false;
        }
        return fixed;
}


void
ConditionContainer::AddConditionsToString(BString& string) const
{
        string += "[";

        for (int32 index = 0; index < fConditions.CountItems(); index++) {
                if (index != 0)
                        string += ", ";
                string += fConditions.ItemAt(index)->ToString();
        }
        string += "]";
}


// #pragma mark - and


AndCondition::AndCondition(const BMessage& args)
        :
        ConditionContainer(args)
{
}


AndCondition::AndCondition()
{
}


bool
AndCondition::Test(ConditionContext& context) const
{
        for (int32 index = 0; index < fConditions.CountItems(); index++) {
                Condition* condition = fConditions.ItemAt(index);
                if (!condition->Test(context))
                        return false;
        }
        return true;
}


BString
AndCondition::ToString() const
{
        BString string = "and ";
        ConditionContainer::AddConditionsToString(string);
        return string;
}


// #pragma mark - or


OrCondition::OrCondition(const BMessage& args)
        :
        ConditionContainer(args)
{
}


bool
OrCondition::Test(ConditionContext& context) const
{
        if (fConditions.IsEmpty())
                return true;

        for (int32 index = 0; index < fConditions.CountItems(); index++) {
                Condition* condition = fConditions.ItemAt(index);
                if (condition->Test(context))
                        return true;
        }
        return false;
}


/*!     If there is a single succeeding constant condition, this is constant, too.
        Otherwise, it is non-constant if there is a single non-constant condition.
*/
bool
OrCondition::IsConstant(ConditionContext& context) const
{
        bool fixed = true;
        for (int32 index = 0; index < fConditions.CountItems(); index++) {
                const Condition* condition = fConditions.ItemAt(index);
                if (condition->IsConstant(context)) {
                        if (condition->Test(context))
                                return true;
                } else
                        fixed = false;
        }
        return fixed;
}


BString
OrCondition::ToString() const
{
        BString string = "or ";
        ConditionContainer::AddConditionsToString(string);
        return string;
}


// #pragma mark - or


NotCondition::NotCondition(const BMessage& args)
        :
        ConditionContainer(args)
{
}


NotCondition::NotCondition()
{
}


bool
NotCondition::Test(ConditionContext& context) const
{
        for (int32 index = 0; index < fConditions.CountItems(); index++) {
                Condition* condition = fConditions.ItemAt(index);
                if (condition->Test(context))
                        return false;
        }
        return true;
}


BString
NotCondition::ToString() const
{
        BString string = "not ";
        ConditionContainer::AddConditionsToString(string);
        return string;
}


// #pragma mark - safemode


bool
SafeModeCondition::Test(ConditionContext& context) const
{
        return context.IsSafeMode();
}


bool
SafeModeCondition::IsConstant(ConditionContext& context) const
{
        return true;
}


BString
SafeModeCondition::ToString() const
{
        return "safemode";
}


// #pragma mark - read_only


ReadOnlyCondition::ReadOnlyCondition(const BMessage& args)
        :
        fPath(args.GetString("args")),
        fIsReadOnly(false),
        fTestPerformed(false)
{
}


bool
ReadOnlyCondition::Test(ConditionContext& context) const
{
        if (fTestPerformed)
                return fIsReadOnly;

        if (fPath.IsEmpty() || fPath == "/boot")
                fIsReadOnly = context.BootVolumeIsReadOnly();
        else
                fIsReadOnly = Utility::IsReadOnlyVolume(fPath);

        fTestPerformed = true;

        return fIsReadOnly;
}


bool
ReadOnlyCondition::IsConstant(ConditionContext& context) const
{
        return true;
}


BString
ReadOnlyCondition::ToString() const
{
        BString string = "readonly ";
        string << fPath;
        return string;
}


// #pragma mark - file_exists


FileExistsCondition::FileExistsCondition(const BMessage& args)
{
        for (int32 index = 0;
                        const char* path = args.GetString("args", index, NULL); index++) {
                fPaths.Add(Utility::TranslatePath(path));
        }
}


bool
FileExistsCondition::Test(ConditionContext& context) const
{
        for (int32 index = 0; index < fPaths.CountStrings(); index++) {
                BEntry entry;
                if (entry.SetTo(fPaths.StringAt(index)) != B_OK
                        || !entry.Exists())
                        return false;
        }
        return true;
}


BString
FileExistsCondition::ToString() const
{
        BString string = "file_exists [";
        for (int32 index = 0; index < fPaths.CountStrings(); index++) {
                if (index != 0)
                        string << ", ";
                string << fPaths.StringAt(index);
        }
        string += "]";
        return string;
}


// #pragma mark - network_available


bool
NetworkAvailableCondition::Test(ConditionContext& context) const
{
        return NetworkWatcher::NetworkAvailable(false);
}


bool
NetworkAvailableCondition::IsConstant(ConditionContext& context) const
{
        return false;
}


BString
NetworkAvailableCondition::ToString() const
{
        return "network_available";
}


// #pragma mark - setting


SettingCondition::SettingCondition(const BMessage& args)
{
        fPath.SetTo(Utility::TranslatePath(args.GetString("args", 0, NULL)));
        fField = args.GetString("args", 1, NULL);
        fValue = args.GetString("args", 2, NULL);
}


bool
SettingCondition::Test(ConditionContext& context) const
{
        BFile file(fPath.Path(), B_READ_ONLY);
        if (file.InitCheck() != B_OK)
                return false;

        BMessage settings;
        if (settings.Unflatten(&file) == B_OK) {
                type_code type;
                int32 count;
                if (settings.GetInfo(fField, &type, &count) == B_OK) {
                        switch (type) {
                                case B_BOOL_TYPE:
                                {
                                        bool value = settings.GetBool(fField);
                                        bool expect = fValue.IsEmpty();
                                        if (!expect) {
                                                expect = fValue == "true" || fValue == "yes"
                                                        || fValue == "on" || fValue == "1";
                                        }
                                        return value == expect;
                                }
                                case B_STRING_TYPE:
                                {
                                        BString value = settings.GetString(fField);
                                        if (fValue.IsEmpty() && !value.IsEmpty())
                                                return true;

                                        return fValue == value;
                                }
                        }
                }
                return false;
        }

        void* handle = load_driver_settings(fPath.Path());
        if (handle != NULL) {
                char buffer[512];
                size_t bufferSize = sizeof(buffer);
                if (get_driver_settings_string(handle, buffer, &bufferSize, true) == B_OK) {
                        BString pattern(fField);
                        if (!fValue.IsEmpty()) {
                                pattern << " = ";
                                pattern << fValue;
                        }
                        return strstr(buffer, pattern.String()) != NULL;
                }
                unload_driver_settings(handle);
        }

        return false;
}


BString
SettingCondition::ToString() const
{
        BString string = "setting file ";
        string << fPath.Path() << ", field " << fField;
        if (!fValue.IsEmpty())
                string << ", value " << fValue;

        return string;
}


// #pragma mark -


/*static*/ Condition*
Conditions::FromMessage(const BMessage& message)
{
        return create_condition("and", message);
}


/*static*/ Condition*
Conditions::AddNotSafeMode(Condition* condition)
{
        AndCondition* andCondition = dynamic_cast<AndCondition*>(condition);
        if (andCondition == NULL)
                andCondition = new AndCondition();
        if (andCondition != condition && condition != NULL)
                andCondition->AddCondition(condition);

        NotCondition* notCondition = new NotCondition();
        notCondition->AddCondition(new SafeModeCondition());

        andCondition->AddCondition(notCondition);
        return andCondition;
}