launch_daemon: Added basic support for conditions.

* Admittedly not very well thought out, but it should be good
  enough for now; it doesn't really make sense to initialize jobs
  that is never run due to failed conditions.
* Job, and Target now have a common base class BaseJob that deals
  with the conditions.
This commit is contained in:
Axel Dörfler
2015-07-22 20:42:41 +02:00
parent f7cf381a14
commit 1e9c987102
11 changed files with 445 additions and 40 deletions
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright 2015, Axel Dörfler, [email protected].
* 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;
}
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright 2015, Axel Dörfler, [email protected].
* Distributed under the terms of the MIT License.
*/
#ifndef BASE_JOB_H
#define BASE_JOB_H
#include <Job.h>
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
+290
View File
@@ -0,0 +1,290 @@
/*
* Copyright 2015, Axel Dörfler, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "Conditions.h"
#include <stdio.h>
#include <Entry.h>
#include <ObjectList.h>
#include <Message.h>
#include <StringList.h>
#include <DiskDevice.h>
#include <DiskDeviceRoster.h>
#include <Volume.h>
class ConditionContainer : public Condition {
protected:
ConditionContainer(const BMessage& args);
void AddCondition(Condition* condition);
protected:
BObjectList<Condition> 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);
}
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright 2015, Axel Dörfler, [email protected].
* 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
+3
View File
@@ -8,6 +8,9 @@ UseHeaders [ FDirName $(HAIKU_TOP) src bin multiuser ] ;
Server launch_daemon Server launch_daemon
: :
LaunchDaemon.cpp LaunchDaemon.cpp
BaseJob.cpp
Conditions.cpp
Job.cpp Job.cpp
SettingsParser.cpp SettingsParser.cpp
Target.cpp Target.cpp
+4 -9
View File
@@ -16,7 +16,7 @@
Job::Job(const char* name) Job::Job(const char* name)
: :
BJob(name), BaseJob(name),
fEnabled(true), fEnabled(true),
fService(false), fService(false),
fCreateDefaultPort(false), fCreateDefaultPort(false),
@@ -30,7 +30,7 @@ Job::Job(const char* name)
Job::Job(const Job& other) Job::Job(const Job& other)
: :
BJob(other.Name()), BaseJob(other.Name()),
fEnabled(other.IsEnabled()), fEnabled(other.IsEnabled()),
fService(other.IsService()), fService(other.IsService()),
fCreateDefaultPort(other.CreateDefaultPort()), fCreateDefaultPort(other.CreateDefaultPort()),
@@ -39,6 +39,8 @@ Job::Job(const Job& other)
fTeam(-1), fTeam(-1),
fTarget(other.Target()) fTarget(other.Target())
{ {
fCondition = other.fCondition;
for (int32 i = 0; i < other.Arguments().CountStrings(); i++) for (int32 i = 0; i < other.Arguments().CountStrings(); i++)
AddArgument(other.Arguments().StringAt(i)); AddArgument(other.Arguments().StringAt(i));
@@ -63,13 +65,6 @@ Job::~Job()
} }
const char*
Job::Name() const
{
return Title().String();
}
bool bool
Job::IsEnabled() const Job::IsEnabled() const
{ {
+3 -4
View File
@@ -6,7 +6,7 @@
#define JOB_H #define JOB_H
#include <Job.h> #include "BaseJob.h"
#include <map> #include <map>
#include <set> #include <set>
@@ -25,14 +25,12 @@ class Target;
typedef std::map<BString, BMessage> PortMap; typedef std::map<BString, BMessage> PortMap;
class Job : public BJob { class Job : public BaseJob {
public: public:
Job(const char* name); Job(const char* name);
Job(const Job& other); Job(const Job& other);
virtual ~Job(); virtual ~Job();
const char* Name() const;
bool IsEnabled() const; bool IsEnabled() const;
void SetEnabled(bool enable); void SetEnabled(bool enable);
@@ -89,6 +87,7 @@ private:
status_t fInitStatus; status_t fInitStatus;
team_id fTeam; team_id fTeam;
::Target* fTarget; ::Target* fTarget;
::Condition* fCondition;
}; };
+23 -13
View File
@@ -30,6 +30,7 @@
#include "multiuser_utils.h" #include "multiuser_utils.h"
#include "Conditions.h"
#include "InitRealTimeClockJob.h" #include "InitRealTimeClockJob.h"
#include "InitSharedMemoryDirectoryJob.h" #include "InitSharedMemoryDirectoryJob.h"
#include "InitTemporaryDirectoryJob.h" #include "InitTemporaryDirectoryJob.h"
@@ -67,7 +68,7 @@ typedef std::map<uid_t, Session*> SessionMap;
typedef std::map<BString, Target*> TargetMap; typedef std::map<BString, Target*> TargetMap;
class LaunchDaemon : public BServer, public Finder { class LaunchDaemon : public BServer, public Finder, public ConditionContext {
public: public:
LaunchDaemon(bool userMode, status_t& error); LaunchDaemon(bool userMode, status_t& error);
virtual ~LaunchDaemon(); virtual ~LaunchDaemon();
@@ -76,6 +77,8 @@ public:
virtual Target* FindTarget(const char* name) const; virtual Target* FindTarget(const char* name) const;
Session* FindSession(uid_t user) const; Session* FindSession(uid_t user) const;
virtual bool IsSafeMode() const;
virtual void ReadyToRun(); virtual void ReadyToRun();
virtual void MessageReceived(BMessage* message); virtual void MessageReceived(BMessage* message);
@@ -104,8 +107,6 @@ private:
void _InitSystem(); void _InitSystem();
void _AddInitJob(BJob* job); void _AddInitJob(BJob* job);
bool _IsSafeMode() const;
private: private:
JobMap fJobs; JobMap fJobs;
TargetMap fTargets; TargetMap fTargets;
@@ -205,6 +206,13 @@ LaunchDaemon::FindSession(uid_t user) const
} }
bool
LaunchDaemon::IsSafeMode() const
{
return fSafeMode;
}
void void
LaunchDaemon::ReadyToRun() LaunchDaemon::ReadyToRun()
{ {
@@ -250,6 +258,12 @@ LaunchDaemon::MessageReceived(BMessage* message)
break; break;
} }
reply.what = B_NAME_NOT_FOUND; 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 { } else {
// If the job has not been launched yet, we'll pass on our // If the job has not been launched yet, we'll pass on our
// team here. The rationale behind this is that this team // team here. The rationale behind this is that this team
@@ -547,7 +561,7 @@ LaunchDaemon::_InitJobs()
JobMap::iterator remove = iterator++; JobMap::iterator remove = iterator++;
status_t status = B_NO_INIT; status_t status = B_NO_INIT;
if (job->IsEnabled() && (!_IsSafeMode() || job->LaunchInSafeMode())) { if (job->IsEnabled() && (!IsSafeMode() || job->LaunchInSafeMode())) {
std::set<BString> dependencies; std::set<BString> dependencies;
status = job->Init(*this, dependencies); status = job->Init(*this, dependencies);
} }
@@ -570,6 +584,9 @@ LaunchDaemon::_InitJobs()
void void
LaunchDaemon::_LaunchJobs(Target* target) LaunchDaemon::_LaunchJobs(Target* target)
{ {
if (target != NULL && !target->CheckCondition(*this))
return;
for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end(); for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();
iterator++) { iterator++) {
Job* job = iterator->second; Job* job = iterator->second;
@@ -582,7 +599,7 @@ LaunchDaemon::_LaunchJobs(Target* target)
void void
LaunchDaemon::_AddLaunchJob(Job* job) LaunchDaemon::_AddLaunchJob(Job* job)
{ {
if (!job->IsLaunched()) if (!job->IsLaunched() && job->CheckCondition(*this))
fJobQueue.AddJob(job); fJobQueue.AddJob(job);
} }
@@ -677,7 +694,7 @@ LaunchDaemon::_SetupEnvironment()
{ {
// Determine safemode kernel option // Determine safemode kernel option
BString safemode = "SAFEMODE="; BString safemode = "SAFEMODE=";
safemode << (_IsSafeMode() ? "yes" : "no"); safemode << (IsSafeMode() ? "yes" : "no");
putenv(safemode.String()); putenv(safemode.String());
} }
@@ -704,13 +721,6 @@ LaunchDaemon::_AddInitJob(BJob* job)
} }
bool
LaunchDaemon::_IsSafeMode() const
{
return fSafeMode;
}
// #pragma mark - // #pragma mark -
+1 -8
View File
@@ -9,18 +9,11 @@
Target::Target(const char* name) Target::Target(const char* name)
: :
BJob(name) BaseJob(name)
{ {
} }
const char*
Target::Name() const
{
return Title().String();
}
status_t status_t
Target::AddData(const char* name, BMessage& data) Target::AddData(const char* name, BMessage& data)
{ {
+3 -4
View File
@@ -6,19 +6,18 @@
#define TARGET_H #define TARGET_H
#include <Job.h> #include "BaseJob.h"
#include <Message.h> #include <Message.h>
using namespace BSupportKit; using namespace BSupportKit;
class Target : public BJob { class Target : public BaseJob {
public: public:
Target(const char* name); Target(const char* name);
const char* Name() const;
status_t AddData(const char* name, BMessage& data); status_t AddData(const char* name, BMessage& data);
const BMessage& Data() const const BMessage& Data() const
{ return fData; } { return fData; }
@@ -140,7 +140,7 @@ SettingsParserTest::TestConditionsMultiLineFlatNotWithArgs()
{ {
BMessage message; BMessage message;
CPPUNIT_ASSERT_EQUAL(B_OK, _ParseCondition("if {\n" CPPUNIT_ASSERT_EQUAL(B_OK, _ParseCondition("if {\n"
"\tnot file_exists one\n" "\tnot file_exists one two\n"
"}\n", message)); "}\n", message));
BMessage subMessage; BMessage subMessage;
@@ -151,7 +151,9 @@ SettingsParserTest::TestConditionsMultiLineFlatNotWithArgs()
CPPUNIT_ASSERT_EQUAL(B_OK, subMessage.FindMessage("file_exists", &args)); CPPUNIT_ASSERT_EQUAL(B_OK, subMessage.FindMessage("file_exists", &args));
CPPUNIT_ASSERT_EQUAL(BString("one"), CPPUNIT_ASSERT_EQUAL(BString("one"),
BString(args.GetString("args", 0, "-"))); 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));
} }