diff --git a/src/servers/launch/BaseJob.cpp b/src/servers/launch/BaseJob.cpp new file mode 100644 index 0000000000..50398552bd --- /dev/null +++ b/src/servers/launch/BaseJob.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2015, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ + + +#include "BaseJob.h" + +#include "Conditions.h" + + +BaseJob::BaseJob(const char* name) + : + BJob(name), + fCondition(NULL) +{ +} + + +const char* +BaseJob::Name() const +{ + return Title().String(); +} + + +const ::Condition* +BaseJob::Condition() const +{ + return fCondition; +} + + +void +BaseJob::SetCondition(const ::Condition* condition) +{ + fCondition = condition; +} + + +bool +BaseJob::CheckCondition(ConditionContext& context) const +{ + if (fCondition != NULL) + return fCondition->Test(context); + + return true; +} diff --git a/src/servers/launch/BaseJob.h b/src/servers/launch/BaseJob.h new file mode 100644 index 0000000000..f4108140be --- /dev/null +++ b/src/servers/launch/BaseJob.h @@ -0,0 +1,33 @@ +/* + * Copyright 2015, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ +#ifndef BASE_JOB_H +#define BASE_JOB_H + + +#include + + +using namespace BSupportKit; + +class Condition; +class ConditionContext; + + +class BaseJob : public BJob { +public: + BaseJob(const char* name); + + const char* Name() const; + + const ::Condition* Condition() const; + void SetCondition(const ::Condition* condition); + bool CheckCondition(ConditionContext& context) const; + +protected: + const ::Condition* fCondition; +}; + + +#endif // BASE_JOB_H diff --git a/src/servers/launch/Conditions.cpp b/src/servers/launch/Conditions.cpp new file mode 100644 index 0000000000..6611f30aad --- /dev/null +++ b/src/servers/launch/Conditions.cpp @@ -0,0 +1,290 @@ +/* + * Copyright 2015, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ + + +#include "Conditions.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + + +class ConditionContainer : public Condition { +protected: + ConditionContainer(const BMessage& args); + + void AddCondition(Condition* condition); + +protected: + BObjectList fConditions; +}; + + +class AndCondition : public ConditionContainer { +public: + AndCondition(const BMessage& args); + + virtual bool Test(ConditionContext& context) const; +}; + + +class OrCondition : public ConditionContainer { +public: + OrCondition(const BMessage& args); + + virtual bool Test(ConditionContext& context) const; +}; + + +class NotCondition : public ConditionContainer { +public: + NotCondition(const BMessage& args); + + virtual bool Test(ConditionContext& context) const; +}; + + +class SafeModeCondition : public Condition { +public: + virtual bool Test(ConditionContext& context) const; +}; + + +class ReadOnlyCondition : public Condition { +public: + ReadOnlyCondition(const BMessage& args); + + virtual bool Test(ConditionContext& context) const; + +private: + dev_t fDevice; +}; + + +class FileExistsCondition : public Condition { +public: + FileExistsCondition(const BMessage& args); + + virtual bool Test(ConditionContext& context) const; + +private: + BStringList fPaths; +}; + + +static Condition* +create_condition(const char* name, const BMessage& args) +{ + if (strcmp(name, "and") == 0) + return new AndCondition(args); + if (strcmp(name, "or") == 0) + return new OrCondition(args); + if (strcmp(name, "not") == 0) + 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); + + return NULL; +} + + +// #pragma mark - + + +Condition::Condition() +{ +} + + +Condition::~Condition() +{ +} + + +// #pragma mark - + + +ConditionContainer::ConditionContainer(const BMessage& args) +{ + 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)); + } + } +} + + +void +ConditionContainer::AddCondition(Condition* condition) +{ + if (condition != NULL) + fConditions.AddItem(condition); +} + + +// #pragma mark - and + + +AndCondition::AndCondition(const BMessage& args) + : + ConditionContainer(args) +{ +} + + +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; +} + + +// #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; +} + + +// #pragma mark - or + + +NotCondition::NotCondition(const BMessage& args) + : + ConditionContainer(args) +{ +} + + +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; +} + + +// #pragma mark - safemode + + +bool +SafeModeCondition::Test(ConditionContext& context) const +{ + return context.IsSafeMode(); +} + + +// #pragma mark - read_only + + +ReadOnlyCondition::ReadOnlyCondition(const BMessage& args) +{ + fDevice = dev_for_path(args.GetString("args", "/boot")); +} + + +bool +ReadOnlyCondition::Test(ConditionContext& context) const +{ + BVolume volume; + status_t status = volume.SetTo(fDevice); + if (status != B_OK) { + fprintf(stderr, "Failed to get BVolume for device %" B_PRIdDEV + ": %s\n", fDevice, strerror(status)); + return false; + } + + BDiskDeviceRoster roster; + BDiskDevice diskDevice; + BPartition* partition; + status = roster.FindPartitionByVolume(volume, &diskDevice, &partition); + if (status != B_OK) { + fprintf(stderr, "Failed to get partition for device %" B_PRIdDEV + ": %s\n", fDevice, strerror(status)); + return false; + } + + return partition->IsReadOnly(); +} + + +// #pragma mark - file_exists + + +FileExistsCondition::FileExistsCondition(const BMessage& args) +{ + for (int32 index = 0; + const char* path = args.GetString("args", index, NULL); index++) { + fPaths.Add(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; +} + + +// #pragma mark - + + +/*static*/ Condition* +Conditions::FromMessage(const BMessage& message) +{ + return create_condition("and", message); +} diff --git a/src/servers/launch/Conditions.h b/src/servers/launch/Conditions.h new file mode 100644 index 0000000000..b6d1b890b6 --- /dev/null +++ b/src/servers/launch/Conditions.h @@ -0,0 +1,33 @@ +/* + * Copyright 2015, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ +#ifndef CONDITIONS_H +#define CONDITIONS_H + + +class BMessage; + + +class ConditionContext { +public: + virtual bool IsSafeMode() const = 0; +}; + + +class Condition { +public: + Condition(); + virtual ~Condition(); + + virtual bool Test(ConditionContext& context) const = 0; +}; + + +class Conditions { +public: + static Condition* FromMessage(const BMessage& message); +}; + + +#endif // CONDITIONS_H diff --git a/src/servers/launch/Jamfile b/src/servers/launch/Jamfile index ba29a02dd4..93c927146a 100644 --- a/src/servers/launch/Jamfile +++ b/src/servers/launch/Jamfile @@ -8,6 +8,9 @@ UseHeaders [ FDirName $(HAIKU_TOP) src bin multiuser ] ; Server launch_daemon : LaunchDaemon.cpp + + BaseJob.cpp + Conditions.cpp Job.cpp SettingsParser.cpp Target.cpp diff --git a/src/servers/launch/Job.cpp b/src/servers/launch/Job.cpp index 40263e123a..371e6007e8 100644 --- a/src/servers/launch/Job.cpp +++ b/src/servers/launch/Job.cpp @@ -16,7 +16,7 @@ Job::Job(const char* name) : - BJob(name), + BaseJob(name), fEnabled(true), fService(false), fCreateDefaultPort(false), @@ -30,7 +30,7 @@ Job::Job(const char* name) Job::Job(const Job& other) : - BJob(other.Name()), + BaseJob(other.Name()), fEnabled(other.IsEnabled()), fService(other.IsService()), fCreateDefaultPort(other.CreateDefaultPort()), @@ -39,6 +39,8 @@ Job::Job(const Job& other) fTeam(-1), fTarget(other.Target()) { + fCondition = other.fCondition; + for (int32 i = 0; i < other.Arguments().CountStrings(); i++) AddArgument(other.Arguments().StringAt(i)); @@ -63,13 +65,6 @@ Job::~Job() } -const char* -Job::Name() const -{ - return Title().String(); -} - - bool Job::IsEnabled() const { diff --git a/src/servers/launch/Job.h b/src/servers/launch/Job.h index 017555608f..efea7fe680 100644 --- a/src/servers/launch/Job.h +++ b/src/servers/launch/Job.h @@ -6,7 +6,7 @@ #define JOB_H -#include +#include "BaseJob.h" #include #include @@ -25,14 +25,12 @@ class Target; typedef std::map PortMap; -class Job : public BJob { +class Job : public BaseJob { public: Job(const char* name); Job(const Job& other); virtual ~Job(); - const char* Name() const; - bool IsEnabled() const; void SetEnabled(bool enable); @@ -89,6 +87,7 @@ private: status_t fInitStatus; team_id fTeam; ::Target* fTarget; + ::Condition* fCondition; }; diff --git a/src/servers/launch/LaunchDaemon.cpp b/src/servers/launch/LaunchDaemon.cpp index 8a0303c4f4..38b98415cc 100644 --- a/src/servers/launch/LaunchDaemon.cpp +++ b/src/servers/launch/LaunchDaemon.cpp @@ -30,6 +30,7 @@ #include "multiuser_utils.h" +#include "Conditions.h" #include "InitRealTimeClockJob.h" #include "InitSharedMemoryDirectoryJob.h" #include "InitTemporaryDirectoryJob.h" @@ -67,7 +68,7 @@ typedef std::map SessionMap; typedef std::map TargetMap; -class LaunchDaemon : public BServer, public Finder { +class LaunchDaemon : public BServer, public Finder, public ConditionContext { public: LaunchDaemon(bool userMode, status_t& error); virtual ~LaunchDaemon(); @@ -76,6 +77,8 @@ public: virtual Target* FindTarget(const char* name) const; Session* FindSession(uid_t user) const; + virtual bool IsSafeMode() const; + virtual void ReadyToRun(); virtual void MessageReceived(BMessage* message); @@ -104,8 +107,6 @@ private: void _InitSystem(); void _AddInitJob(BJob* job); - bool _IsSafeMode() const; - private: JobMap fJobs; TargetMap fTargets; @@ -205,6 +206,13 @@ LaunchDaemon::FindSession(uid_t user) const } +bool +LaunchDaemon::IsSafeMode() const +{ + return fSafeMode; +} + + void LaunchDaemon::ReadyToRun() { @@ -250,6 +258,12 @@ LaunchDaemon::MessageReceived(BMessage* message) break; } reply.what = B_NAME_NOT_FOUND; + } else if (!job->IsLaunched() && !job->CheckCondition(*this)) { + // The job exists, but cannot be started yet, as its + // conditions are not met; don't make it available yet + // TODO: we may not want to initialize jobs with conditions + // that aren't met yet + reply.what = B_NO_INIT; } else { // If the job has not been launched yet, we'll pass on our // team here. The rationale behind this is that this team @@ -547,7 +561,7 @@ LaunchDaemon::_InitJobs() JobMap::iterator remove = iterator++; status_t status = B_NO_INIT; - if (job->IsEnabled() && (!_IsSafeMode() || job->LaunchInSafeMode())) { + if (job->IsEnabled() && (!IsSafeMode() || job->LaunchInSafeMode())) { std::set dependencies; status = job->Init(*this, dependencies); } @@ -570,6 +584,9 @@ LaunchDaemon::_InitJobs() void LaunchDaemon::_LaunchJobs(Target* target) { + if (target != NULL && !target->CheckCondition(*this)) + return; + for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end(); iterator++) { Job* job = iterator->second; @@ -582,7 +599,7 @@ LaunchDaemon::_LaunchJobs(Target* target) void LaunchDaemon::_AddLaunchJob(Job* job) { - if (!job->IsLaunched()) + if (!job->IsLaunched() && job->CheckCondition(*this)) fJobQueue.AddJob(job); } @@ -677,7 +694,7 @@ LaunchDaemon::_SetupEnvironment() { // Determine safemode kernel option BString safemode = "SAFEMODE="; - safemode << (_IsSafeMode() ? "yes" : "no"); + safemode << (IsSafeMode() ? "yes" : "no"); putenv(safemode.String()); } @@ -704,13 +721,6 @@ LaunchDaemon::_AddInitJob(BJob* job) } -bool -LaunchDaemon::_IsSafeMode() const -{ - return fSafeMode; -} - - // #pragma mark - diff --git a/src/servers/launch/Target.cpp b/src/servers/launch/Target.cpp index 6ee87a7467..34bd13317e 100644 --- a/src/servers/launch/Target.cpp +++ b/src/servers/launch/Target.cpp @@ -9,18 +9,11 @@ Target::Target(const char* name) : - BJob(name) + BaseJob(name) { } -const char* -Target::Name() const -{ - return Title().String(); -} - - status_t Target::AddData(const char* name, BMessage& data) { diff --git a/src/servers/launch/Target.h b/src/servers/launch/Target.h index 546cb942af..0b4b37d497 100644 --- a/src/servers/launch/Target.h +++ b/src/servers/launch/Target.h @@ -6,19 +6,18 @@ #define TARGET_H -#include +#include "BaseJob.h" + #include using namespace BSupportKit; -class Target : public BJob { +class Target : public BaseJob { public: Target(const char* name); - const char* Name() const; - status_t AddData(const char* name, BMessage& data); const BMessage& Data() const { return fData; } diff --git a/src/tests/servers/launch/SettingsParserTest.cpp b/src/tests/servers/launch/SettingsParserTest.cpp index 767aae928c..f081fdcbf3 100644 --- a/src/tests/servers/launch/SettingsParserTest.cpp +++ b/src/tests/servers/launch/SettingsParserTest.cpp @@ -140,7 +140,7 @@ SettingsParserTest::TestConditionsMultiLineFlatNotWithArgs() { BMessage message; CPPUNIT_ASSERT_EQUAL(B_OK, _ParseCondition("if {\n" - "\tnot file_exists one\n" + "\tnot file_exists one two\n" "}\n", message)); BMessage subMessage; @@ -151,7 +151,9 @@ SettingsParserTest::TestConditionsMultiLineFlatNotWithArgs() CPPUNIT_ASSERT_EQUAL(B_OK, subMessage.FindMessage("file_exists", &args)); CPPUNIT_ASSERT_EQUAL(BString("one"), BString(args.GetString("args", 0, "-"))); - CPPUNIT_ASSERT_EQUAL(1, args.CountNames(B_ANY_TYPE)); + CPPUNIT_ASSERT_EQUAL(BString("two"), + BString(args.GetString("args", 1, "-"))); + CPPUNIT_ASSERT_EQUAL(2, args.CountNames(B_ANY_TYPE)); }