#include "LaunchDaemon.h"
#include <map>
#include <set>
#include <errno.h>
#include <grp.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <Directory.h>
#include <driver_settings.h>
#include <Entry.h>
#include <File.h>
#include <ObjectList.h>
#include <Path.h>
#include <PathFinder.h>
#include <Server.h>
#include <AppMisc.h>
#include <LaunchDaemonDefs.h>
#include <LaunchRosterPrivate.h>
#include <locks.h>
#include <MessengerPrivate.h>
#include <RosterPrivate.h>
#include <syscalls.h>
#include <system_info.h>
#include "multiuser_utils.h"
#include "Conditions.h"
#include "Events.h"
#include "InitRealTimeClockJob.h"
#include "InitSharedMemoryDirectoryJob.h"
#include "InitTemporaryDirectoryJob.h"
#include "Job.h"
#include "Log.h"
#include "SettingsParser.h"
#include "Target.h"
#include "Utility.h"
#include "Worker.h"
#ifdef DEBUG
# define TRACE(x, ...) debug_printf(x, __VA_ARGS__)
# define TRACE_ONLY
#else
# define TRACE(x, ...) ;
# define TRACE_ONLY __attribute__((unused))
#endif
using namespace ::BPrivate;
using namespace BSupportKit;
using BSupportKit::BPrivate::JobQueue;
#ifndef TEST_MODE
static const char* kLaunchDirectory = "launch";
static const char* kUserLaunchDirectory = "user_launch";
#endif
enum launch_options {
FORCE_NOW = 0x01,
TRIGGER_DEMAND = 0x02
};
class Session {
public:
Session(uid_t user, const BMessenger& target);
uid_t User() const
{ return fUser; }
const BMessenger& Daemon() const
{ return fDaemon; }
private:
uid_t fUser;
BMessenger fDaemon;
};
class ExternalEventSource {
public:
ExternalEventSource(BMessenger& source,
const char* ownerName,
const char* name, uint32 flags);
~ExternalEventSource();
const BMessenger& Source() const
{ return fSource; }
const char* Name() const;
const char* OwnerName() const;
uint32 Flags() const
{ return fFlags; }
void Trigger();
bool StickyTriggered() const
{ return fStickyTriggered; }
void ResetSticky();
status_t AddDestination(Event* event);
void RemoveDestination(Event* event);
private:
BMessenger fSource;
BString fName, fOwnerName;
uint32 fFlags;
BObjectList<Event> fDestinations;
bool fStickyTriggered;
};
typedef std::map<BString, Job*> JobMap;
typedef std::map<uid_t, Session*> SessionMap;
typedef std::map<BString, Target*> TargetMap;
typedef std::map<BString, ExternalEventSource*> EventMap;
typedef std::map<team_id, Job*> TeamMap;
class LaunchDaemon : public BServer, public Finder, public ConditionContext,
public EventRegistrator, public TeamListener {
public:
LaunchDaemon(bool userMode, status_t& error);
virtual ~LaunchDaemon();
virtual Job* FindJob(const char* name) const;
virtual Target* FindTarget(const char* name) const;
Session* FindSession(uid_t user) const;
virtual bool IsSafeMode() const;
virtual bool BootVolumeIsReadOnly() const;
virtual status_t RegisterExternalEvent(Event* event,
const char* name,
const BStringList& arguments);
virtual void UnregisterExternalEvent(Event* event,
const char* name);
virtual void TeamLaunched(Job* job, status_t status);
virtual void ReadyToRun();
virtual void MessageReceived(BMessage* message);
private:
void _HandleGetLaunchData(BMessage* message);
void _HandleLaunchTarget(BMessage* message);
void _HandleStopLaunchTarget(BMessage* message);
void _HandleLaunchJob(BMessage* message);
void _HandleEnableLaunchJob(BMessage* message);
void _HandleStopLaunchJob(BMessage* message);
void _HandleLaunchSession(BMessage* message);
void _HandleRegisterSessionDaemon(BMessage* message);
void _HandleRegisterLaunchEvent(BMessage* message);
void _HandleUnregisterLaunchEvent(BMessage* message);
void _HandleNotifyLaunchEvent(BMessage* message);
void _HandleResetStickyLaunchEvent(
BMessage* message);
void _HandleGetLaunchTargets(BMessage* message);
void _HandleGetLaunchTargetInfo(BMessage* message);
void _HandleGetLaunchJobs(BMessage* message);
void _HandleGetLaunchJobInfo(BMessage* message);
void _HandleGetLaunchLog(BMessage* message);
uid_t _GetUserID(BMessage* message);
void _ReadPaths(const BStringList& paths);
void _ReadEntry(const char* context, BEntry& entry);
void _ReadDirectory(const char* context,
BEntry& directory);
status_t _ReadFile(const char* context, BEntry& entry);
void _AddJobs(Target* target, BMessage& message);
void _AddTargets(BMessage& message);
void _AddRunTargets(BMessage& message);
void _AddRunTargets(BMessage& message,
const char* name);
void _AddJob(Target* target, bool service,
BMessage& message);
void _InitJobs(Target* target);
void _LaunchJobs(Target* target,
bool forceNow = false);
void _StopJobs(Target* target, bool force);
bool _CanLaunchJob(Job* job, uint32 options,
bool testOnly = false);
bool _CanLaunchJobRequirements(Job* job,
uint32 options);
bool _LaunchJob(Job* job, uint32 options = 0);
void _StopJob(Job* job, bool force);
void _AddTarget(Target* target);
void _SetCondition(BaseJob* job,
const BMessage& message);
void _SetEvent(BaseJob* job,
const BMessage& message);
void _SetEnvironment(BaseJob* job,
const BMessage& message);
ExternalEventSource*
_FindExternalEventSource(const char* owner,
const char* name) const;
void _ResolveExternalEvents(
ExternalEventSource* event,
const BString& name);
void _GetBaseJobInfo(BaseJob* job, BMessage& info);
void _ForwardEventMessage(uid_t user,
BMessage* message);
status_t _StartSession(const char* login);
void _RetrieveKernelOptions();
void _SetupEnvironment();
void _InitSystem();
void _AddInitJob(BJob* job);
private:
Log fLog;
JobMap fJobs;
TargetMap fTargets;
BStringList fRunTargets;
EventMap fEvents;
JobQueue fJobQueue;
SessionMap fSessions;
MainWorker* fMainWorker;
Target* fInitTarget;
TeamMap fTeams;
mutex fTeamsLock;
bool fSafeMode;
bool fReadOnlyBootVolume;
bool fUserMode;
};
static const char*
get_leaf(const char* signature)
{
if (signature == NULL)
return NULL;
const char* separator = strrchr(signature, '/');
if (separator != NULL)
return separator + 1;
return signature;
}
Session::Session(uid_t user, const BMessenger& daemon)
:
fUser(user),
fDaemon(daemon)
{
}
ExternalEventSource::ExternalEventSource(BMessenger& source,
const char* ownerName, const char* name, uint32 flags)
:
fSource(source),
fName(name),
fOwnerName(ownerName),
fFlags(flags),
fDestinations(5),
fStickyTriggered(false)
{
}
ExternalEventSource::~ExternalEventSource()
{
}
const char*
ExternalEventSource::Name() const
{
return fName.String();
}
const char*
ExternalEventSource::OwnerName() const
{
return fOwnerName.String();
}
void
ExternalEventSource::Trigger()
{
for (int32 index = 0; index < fDestinations.CountItems(); index++)
Events::TriggerExternalEvent(fDestinations.ItemAt(index));
if ((fFlags & B_STICKY_EVENT) != 0)
fStickyTriggered = true;
}
void
ExternalEventSource::ResetSticky()
{
if ((fFlags & B_STICKY_EVENT) != 0)
fStickyTriggered = false;
for (int32 index = 0; index < fDestinations.CountItems(); index++)
Events::ResetStickyExternalEvent(fDestinations.ItemAt(index));
}
status_t
ExternalEventSource::AddDestination(Event* event)
{
if (fStickyTriggered)
Events::TriggerExternalEvent(event);
if (fDestinations.AddItem(event))
return B_OK;
return B_NO_MEMORY;
}
void
ExternalEventSource::RemoveDestination(Event* event)
{
fDestinations.RemoveItem(event);
}
LaunchDaemon::LaunchDaemon(bool userMode, status_t& error)
:
BServer(kLaunchDaemonSignature, NULL,
create_port(B_LOOPER_PORT_DEFAULT_CAPACITY,
userMode ? "AppPort" : B_LAUNCH_DAEMON_PORT_NAME), false, &error),
fInitTarget(userMode ? NULL : new Target("init")),
#ifdef TEST_MODE
fUserMode(true)
#else
fUserMode(userMode)
#endif
{
mutex_init(&fTeamsLock, "teams lock");
fMainWorker = new MainWorker(fJobQueue);
fMainWorker->Init();
if (fInitTarget != NULL)
_AddTarget(fInitTarget);
if (!fUserMode)
BRoster::Private().SetWithoutRegistrar(true);
}
LaunchDaemon::~LaunchDaemon()
{
}
Job*
LaunchDaemon::FindJob(const char* name) const
{
if (name == NULL)
return NULL;
JobMap::const_iterator found = fJobs.find(BString(name).ToLower());
if (found != fJobs.end())
return found->second;
return NULL;
}
Target*
LaunchDaemon::FindTarget(const char* name) const
{
if (name == NULL)
return NULL;
TargetMap::const_iterator found = fTargets.find(BString(name).ToLower());
if (found != fTargets.end())
return found->second;
return NULL;
}
Session*
LaunchDaemon::FindSession(uid_t user) const
{
SessionMap::const_iterator found = fSessions.find(user);
if (found != fSessions.end())
return found->second;
return NULL;
}
bool
LaunchDaemon::IsSafeMode() const
{
return fSafeMode;
}
bool
LaunchDaemon::BootVolumeIsReadOnly() const
{
return fReadOnlyBootVolume;
}
status_t
LaunchDaemon::RegisterExternalEvent(Event* event, const char* name,
const BStringList& arguments)
{
status_t status TRACE_ONLY = B_NAME_NOT_FOUND;
for (EventMap::iterator iterator = fEvents.begin();
iterator != fEvents.end(); iterator++) {
ExternalEventSource* eventSource = iterator->second;
Event* externalEvent = Events::ResolveExternalEvent(event,
eventSource->Name(), eventSource->Flags());
if (externalEvent != NULL) {
status = eventSource->AddDestination(event);
break;
}
}
TRACE("Register external event '%s': %" B_PRId32 "\n", name, status);
return B_OK;
}
void
LaunchDaemon::UnregisterExternalEvent(Event* event, const char* name)
{
for (EventMap::iterator iterator = fEvents.begin();
iterator != fEvents.end(); iterator++) {
ExternalEventSource* eventSource = iterator->second;
Event* externalEvent = Events::ResolveExternalEvent(event,
eventSource->Name(), eventSource->Flags());
if (externalEvent != NULL) {
eventSource->RemoveDestination(event);
break;
}
}
}
void
LaunchDaemon::TeamLaunched(Job* job, status_t status)
{
fLog.JobLaunched(job, status);
MutexLocker locker(fTeamsLock);
fTeams.insert(std::make_pair(job->Team(), job));
}
void
LaunchDaemon::ReadyToRun()
{
_RetrieveKernelOptions();
_SetupEnvironment();
fReadOnlyBootVolume = Utility::IsReadOnlyVolume("/boot");
if (fReadOnlyBootVolume)
Utility::BlockMedia("/boot", true);
if (fUserMode) {
#ifndef TEST_MODE
BLaunchRoster roster;
BLaunchRoster::Private(roster).RegisterSessionDaemon(this);
#endif
} else
_InitSystem();
BStringList paths;
#ifdef TEST_MODE
paths.Add("/boot/home/test_launch");
#else
if (fUserMode) {
BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY, kUserLaunchDirectory,
B_FIND_PATHS_SYSTEM_ONLY, paths);
_ReadPaths(paths);
}
BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY, kLaunchDirectory,
fUserMode ? B_FIND_PATHS_USER_ONLY : B_FIND_PATHS_SYSTEM_ONLY, paths);
_ReadPaths(paths);
if (fUserMode) {
BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY,
kUserLaunchDirectory, B_FIND_PATHS_SYSTEM_ONLY, paths);
_ReadPaths(paths);
}
BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY, kLaunchDirectory,
fUserMode ? B_FIND_PATHS_USER_ONLY : B_FIND_PATHS_SYSTEM_ONLY, paths);
#endif
_ReadPaths(paths);
BMessenger target(this);
BMessenger::Private messengerPrivate(target);
port_id port = messengerPrivate.Port();
int32 token = messengerPrivate.Token();
__start_watching_system(-1, B_WATCH_SYSTEM_TEAM_DELETION, port, token);
_InitJobs(NULL);
_LaunchJobs(NULL);
for (int32 index = 0; index < fRunTargets.CountStrings(); index++) {
Target* target = FindTarget(fRunTargets.StringAt(index));
if (target != NULL)
_LaunchJobs(target);
}
if (fUserMode)
be_roster->StartWatching(this, B_REQUEST_LAUNCHED);
}
void
LaunchDaemon::MessageReceived(BMessage* message)
{
switch (message->what) {
case B_SYSTEM_OBJECT_UPDATE:
{
int32 opcode = message->GetInt32("opcode", 0);
team_id team = (team_id)message->GetInt32("team", -1);
if (opcode != B_TEAM_DELETED || team < 0)
break;
MutexLocker locker(fTeamsLock);
TeamMap::iterator found = fTeams.find(team);
if (found != fTeams.end()) {
Job* job = found->second;
TRACE("Job %s ended!\n", job->Name());
status_t exitStatus = B_OK;
wait_for_thread(team, &exitStatus);
fLog.JobTerminated(job, exitStatus);
job->TeamDeleted();
if (job->IsService()) {
bool inProgress = false;
BRoster roster;
BRoster::Private rosterPrivate(roster);
status_t status = rosterPrivate.IsShutDownInProgress(
&inProgress);
if (status != B_OK || !inProgress) {
_LaunchJob(job);
}
}
}
break;
}
case B_SOME_APP_LAUNCHED:
{
team_id team = (team_id)message->GetInt32("be:team", -1);
Job* job = NULL;
MutexLocker locker(fTeamsLock);
TeamMap::iterator found = fTeams.find(team);
if (found != fTeams.end()) {
job = found->second;
locker.Unlock();
} else {
locker.Unlock();
const char* signature = message->GetString("be:signature");
job = FindJob(get_leaf(signature));
if (job != NULL) {
TRACE("Updated default port of untracked team %d, %s\n",
(int)team, signature);
}
}
if (job != NULL) {
app_info info;
status_t status = be_roster->GetRunningAppInfo(team, &info);
if (status == B_OK && info.port != job->DefaultPort()) {
TRACE("Update default port for %s to %d\n", job->Name(),
(int)info.port);
job->SetDefaultPort(info.port);
}
}
break;
}
case B_GET_LAUNCH_DATA:
_HandleGetLaunchData(message);
break;
case B_LAUNCH_TARGET:
_HandleLaunchTarget(message);
break;
case B_STOP_LAUNCH_TARGET:
_HandleStopLaunchTarget(message);
break;
case B_LAUNCH_JOB:
_HandleLaunchJob(message);
break;
case B_ENABLE_LAUNCH_JOB:
_HandleEnableLaunchJob(message);
break;
case B_STOP_LAUNCH_JOB:
_HandleStopLaunchJob(message);
break;
case B_LAUNCH_SESSION:
_HandleLaunchSession(message);
break;
case B_REGISTER_SESSION_DAEMON:
_HandleRegisterSessionDaemon(message);
break;
case B_REGISTER_LAUNCH_EVENT:
_HandleRegisterLaunchEvent(message);
break;
case B_UNREGISTER_LAUNCH_EVENT:
_HandleUnregisterLaunchEvent(message);
break;
case B_NOTIFY_LAUNCH_EVENT:
_HandleNotifyLaunchEvent(message);
break;
case B_RESET_STICKY_LAUNCH_EVENT:
_HandleResetStickyLaunchEvent(message);
break;
case B_GET_LAUNCH_TARGETS:
_HandleGetLaunchTargets(message);
break;
case B_GET_LAUNCH_TARGET_INFO:
_HandleGetLaunchTargetInfo(message);
break;
case B_GET_LAUNCH_JOBS:
_HandleGetLaunchJobs(message);
break;
case B_GET_LAUNCH_JOB_INFO:
_HandleGetLaunchJobInfo(message);
break;
case B_GET_LAUNCH_LOG:
_HandleGetLaunchLog(message);
break;
case kMsgEventTriggered:
{
const char* name = message->GetString("owner");
if (name == NULL)
break;
Event* event = (Event*)message->GetPointer("event");
Job* job = FindJob(name);
if (job != NULL) {
fLog.EventTriggered(job, event);
_LaunchJob(job);
break;
}
Target* target = FindTarget(name);
if (target != NULL) {
fLog.EventTriggered(target, event);
_LaunchJobs(target);
break;
}
break;
}
default:
BServer::MessageReceived(message);
break;
}
}
void
LaunchDaemon::_HandleGetLaunchData(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
BMessage reply((uint32)B_OK);
bool launchJob = true;
Job* job = FindJob(get_leaf(message->GetString("name")));
if (job == NULL) {
Session* session = FindSession(user);
if (session != NULL) {
if (session->Daemon().SendMessage(message) == B_OK)
return;
}
reply.what = B_NAME_NOT_FOUND;
} else if (job->IsService() && !job->IsLaunched()) {
if (job->InitCheck() == B_NO_INIT || !job->CheckCondition(*this)) {
reply.what = B_NO_INIT;
} else if (job->Event() != NULL) {
if (!Events::TriggerDemand(job->Event())) {
reply.what = B_NO_INIT;
} else {
launchJob = false;
}
}
} else
launchJob = false;
bool ownsMessage = false;
if (reply.what == B_OK) {
if (launchJob)
_LaunchJob(job, TRIGGER_DEMAND);
DetachCurrentMessage();
status_t result = job->HandleGetLaunchData(message);
if (result == B_OK) {
return;
}
ownsMessage = true;
reply.what = result;
}
message->SendReply(&reply);
if (ownsMessage)
delete message;
}
void
LaunchDaemon::_HandleLaunchTarget(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
const char* name = message->GetString("target");
const char* baseName = message->GetString("base target");
Target* target = FindTarget(name);
if (target == NULL && baseName != NULL) {
Target* baseTarget = FindTarget(baseName);
if (baseTarget != NULL) {
target = new Target(name);
for (JobMap::iterator iterator = fJobs.begin();
iterator != fJobs.end();) {
Job* job = iterator->second;
iterator++;
if (job->Target() == baseTarget) {
Job* copy = new Job(*job);
copy->SetTarget(target);
fJobs.insert(std::make_pair(copy->Name(), copy));
}
}
}
}
if (target == NULL) {
Session* session = FindSession(user);
if (session != NULL) {
if (session->Daemon().SendMessage(message) == B_OK)
return;
}
BMessage reply(B_NAME_NOT_FOUND);
message->SendReply(&reply);
return;
}
BMessage data;
if (message->FindMessage("data", &data) == B_OK)
target->AddData(data.GetString("name"), data);
_LaunchJobs(target);
BMessage reply((uint32)B_OK);
message->SendReply(&reply);
}
void
LaunchDaemon::_HandleStopLaunchTarget(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
const char* name = message->GetString("target");
Target* target = FindTarget(name);
if (target == NULL) {
Session* session = FindSession(user);
if (session != NULL) {
if (session->Daemon().SendMessage(message) == B_OK)
return;
}
BMessage reply(B_NAME_NOT_FOUND);
message->SendReply(&reply);
return;
}
BMessage data;
if (message->FindMessage("data", &data) == B_OK)
target->AddData(data.GetString("name"), data);
bool force = message->GetBool("force");
fLog.JobStopped(target, force);
_StopJobs(target, force);
BMessage reply((uint32)B_OK);
message->SendReply(&reply);
}
void
LaunchDaemon::_HandleLaunchJob(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
const char* name = message->GetString("name");
Job* job = FindJob(name);
if (job == NULL) {
Session* session = FindSession(user);
if (session != NULL) {
if (session->Daemon().SendMessage(message) == B_OK)
return;
}
BMessage reply(B_NAME_NOT_FOUND);
message->SendReply(&reply);
return;
}
job->SetEnabled(true);
_LaunchJob(job, FORCE_NOW);
BMessage reply((uint32)B_OK);
message->SendReply(&reply);
}
void
LaunchDaemon::_HandleEnableLaunchJob(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
const char* name = message->GetString("name");
bool enable = message->GetBool("enable");
Job* job = FindJob(name);
if (job == NULL) {
Session* session = FindSession(user);
if (session != NULL) {
if (session->Daemon().SendMessage(message) == B_OK)
return;
}
BMessage reply(B_NAME_NOT_FOUND);
message->SendReply(&reply);
return;
}
job->SetEnabled(enable);
fLog.JobEnabled(job, enable);
BMessage reply((uint32)B_OK);
message->SendReply(&reply);
}
void
LaunchDaemon::_HandleStopLaunchJob(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
const char* name = message->GetString("name");
Job* job = FindJob(name);
if (job == NULL) {
Session* session = FindSession(user);
if (session != NULL) {
if (session->Daemon().SendMessage(message) == B_OK)
return;
}
BMessage reply(B_NAME_NOT_FOUND);
message->SendReply(&reply);
return;
}
bool force = message->GetBool("force");
fLog.JobStopped(job, force);
_StopJob(job, force);
BMessage reply((uint32)B_OK);
message->SendReply(&reply);
}
void
LaunchDaemon::_HandleLaunchSession(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
status_t status = B_OK;
const char* login = message->GetString("login");
if (login == NULL)
status = B_BAD_VALUE;
if (status == B_OK && user != 0) {
status = B_PERMISSION_DENIED;
}
if (status == B_OK)
status = _StartSession(login);
BMessage reply((uint32)status);
message->SendReply(&reply);
}
void
LaunchDaemon::_HandleRegisterSessionDaemon(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
status_t status = B_OK;
BMessenger target;
if (message->FindMessenger("daemon", &target) != B_OK)
status = B_BAD_VALUE;
if (status == B_OK) {
Session* session = new (std::nothrow) Session(user, target);
if (session != NULL)
fSessions.insert(std::make_pair(user, session));
else
status = B_NO_MEMORY;
for (EventMap::iterator iterator = fEvents.begin(); iterator != fEvents.end();
iterator++) {
ExternalEventSource* eventSource = iterator->second;
if (eventSource->Name() != iterator->first)
continue;
BMessage message(B_REGISTER_LAUNCH_EVENT);
message.AddInt32("user", 0);
message.AddString("name", eventSource->Name());
message.AddString("owner", eventSource->OwnerName());
message.AddUInt32("flags", eventSource->Flags());
message.AddMessenger("source", eventSource->Source());
target.SendMessage(&message);
if (eventSource->StickyTriggered()) {
message.what = B_NOTIFY_LAUNCH_EVENT;
target.SendMessage(&message);
}
}
}
BMessage reply((uint32)status);
message->SendReply(&reply);
}
void
LaunchDaemon::_HandleRegisterLaunchEvent(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
if (user == 0 || fUserMode) {
status_t status = B_OK;
const char* name = message->GetString("name");
const char* ownerName = message->GetString("owner");
uint32 flags = message->GetUInt32("flags", 0);
BMessenger source;
if (name != NULL && ownerName != NULL
&& message->FindMessenger("source", &source) == B_OK) {
ownerName = get_leaf(ownerName);
ExternalEventSource* event = new (std::nothrow)
ExternalEventSource(source, ownerName, name, flags);
if (event != NULL) {
BString eventName = name;
fEvents.insert(std::make_pair(eventName, event));
_ResolveExternalEvents(event, eventName);
eventName.Prepend("/");
eventName.Prepend(ownerName);
fEvents.insert(std::make_pair(eventName, event));
_ResolveExternalEvents(event, eventName);
fLog.ExternalEventRegistered(name);
} else
status = B_NO_MEMORY;
} else
status = B_BAD_VALUE;
BMessage reply((uint32)status);
message->SendReply(&reply);
}
_ForwardEventMessage(user, message);
}
void
LaunchDaemon::_HandleUnregisterLaunchEvent(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
if (user == 0 || fUserMode) {
status_t status = B_OK;
const char* name = message->GetString("name");
const char* ownerName = message->GetString("owner");
BMessenger source;
if (name != NULL && ownerName != NULL
&& message->FindMessenger("source", &source) == B_OK) {
ownerName = get_leaf(ownerName);
BString eventName = name;
fEvents.erase(eventName);
eventName.Prepend("/");
eventName.Prepend(ownerName);
fEvents.erase(eventName);
fLog.ExternalEventRegistered(name);
} else
status = B_BAD_VALUE;
BMessage reply((uint32)status);
message->SendReply(&reply);
}
_ForwardEventMessage(user, message);
}
void
LaunchDaemon::_HandleNotifyLaunchEvent(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
if (user == 0 || fUserMode) {
const char* name = message->GetString("name");
const char* ownerName = message->GetString("owner");
ExternalEventSource* event = _FindExternalEventSource(ownerName, name);
if (event != NULL) {
fLog.ExternalEventTriggered(name);
event->Trigger();
}
}
_ForwardEventMessage(user, message);
}
void
LaunchDaemon::_HandleResetStickyLaunchEvent(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
if (user == 0 || fUserMode) {
const char* name = message->GetString("name");
const char* ownerName = message->GetString("owner");
ExternalEventSource* eventSource = _FindExternalEventSource(ownerName, name);
if (eventSource != NULL)
eventSource->ResetSticky();
}
_ForwardEventMessage(user, message);
}
void
LaunchDaemon::_HandleGetLaunchTargets(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
BMessage reply;
status_t status = B_OK;
if (!fUserMode) {
Session* session = FindSession(user);
if (session != NULL) {
BMessage request(B_GET_LAUNCH_TARGETS);
status = request.AddInt32("user", 0);
if (status == B_OK) {
status = session->Daemon().SendMessage(&request,
&reply);
}
if (status == B_OK)
status = reply.what;
} else
status = B_NAME_NOT_FOUND;
}
if (status == B_OK) {
TargetMap::const_iterator iterator = fTargets.begin();
for (; iterator != fTargets.end(); iterator++)
reply.AddString("target", iterator->first);
}
reply.what = status;
message->SendReply(&reply);
}
void
LaunchDaemon::_HandleGetLaunchTargetInfo(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
const char* name = message->GetString("name");
Target* target = FindTarget(name);
if (target == NULL && !fUserMode) {
_ForwardEventMessage(user, message);
return;
}
BMessage info(uint32(target != NULL ? B_OK : B_NAME_NOT_FOUND));
if (target != NULL) {
_GetBaseJobInfo(target, info);
for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();
iterator++) {
Job* job = iterator->second;
if (job->Target() == target)
info.AddString("job", job->Name());
}
}
message->SendReply(&info);
}
void
LaunchDaemon::_HandleGetLaunchJobs(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
const char* targetName = message->GetString("target");
BMessage reply;
status_t status = B_OK;
if (!fUserMode) {
Session* session = FindSession(user);
if (session != NULL) {
BMessage request(B_GET_LAUNCH_JOBS);
status = request.AddInt32("user", 0);
if (status == B_OK && targetName != NULL)
status = request.AddString("target", targetName);
if (status == B_OK) {
status = session->Daemon().SendMessage(&request,
&reply);
}
if (status == B_OK)
status = reply.what;
} else
status = B_NAME_NOT_FOUND;
}
if (status == B_OK) {
JobMap::const_iterator iterator = fJobs.begin();
for (; iterator != fJobs.end(); iterator++) {
Job* job = iterator->second;
if (targetName != NULL && (job->Target() == NULL
|| job->Target()->Title() != targetName)) {
continue;
}
reply.AddString("job", iterator->first);
}
}
reply.what = status;
message->SendReply(&reply);
}
void
LaunchDaemon::_HandleGetLaunchJobInfo(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
const char* name = message->GetString("name");
Job* job = FindJob(name);
if (job == NULL && !fUserMode) {
_ForwardEventMessage(user, message);
return;
}
BMessage info(uint32(job != NULL ? B_OK : B_NAME_NOT_FOUND));
if (job != NULL) {
_GetBaseJobInfo(job, info);
info.SetInt32("team", job->Team());
info.SetBool("enabled", job->IsEnabled());
info.SetBool("running", job->IsRunning());
info.SetBool("launched", job->IsLaunched());
info.SetBool("service", job->IsService());
if (job->Target() != NULL)
info.SetString("target", job->Target()->Name());
for (int32 i = 0; i < job->Arguments().CountStrings(); i++)
info.AddString("launch", job->Arguments().StringAt(i));
for (int32 i = 0; i < job->Requirements().CountStrings(); i++)
info.AddString("requires", job->Requirements().StringAt(i));
PortMap::const_iterator iterator = job->Ports().begin();
for (; iterator != job->Ports().end(); iterator++)
info.AddMessage("port", &iterator->second);
}
message->SendReply(&info);
}
void
LaunchDaemon::_HandleGetLaunchLog(BMessage* message)
{
uid_t user = _GetUserID(message);
if (user < 0)
return;
BMessage filter;
BString jobName;
const char* event = NULL;
int32 limit = 0;
bool systemOnly = false;
bool userOnly = false;
if (message->FindMessage("filter", &filter) == B_OK) {
limit = filter.GetInt32("limit", 0);
jobName = filter.GetString("job");
jobName.ToLower();
event = filter.GetString("event");
systemOnly = filter.GetBool("systemOnly");
userOnly = filter.GetBool("userOnly");
}
BMessage info((uint32)B_OK);
int32 count = 0;
if (user == 0 || !userOnly) {
LogItemList::Iterator iterator = fLog.Iterator();
while (iterator.HasNext()) {
LogItem* item = iterator.Next();
if (!item->Matches(jobName.IsEmpty() ? NULL : jobName.String(),
event)) {
continue;
}
BMessage itemMessage;
itemMessage.AddUInt64("when", item->When());
itemMessage.AddInt32("type", (int32)item->Type());
itemMessage.AddString("message", item->Message());
BMessage parameter;
item->GetParameter(parameter);
itemMessage.AddMessage("parameter", ¶meter);
info.AddMessage("item", &itemMessage);
if (++count == limit)
break;
}
}
Session* session = FindSession(user);
if (session != NULL && !systemOnly) {
if (limit != 0) {
limit -= count;
if (limit <= 0) {
message->SendReply(&info);
return;
}
}
BMessage reply;
BMessage request(B_GET_LAUNCH_LOG);
status_t status = request.AddInt32("user", 0);
if (status == B_OK && (limit != 0 || !jobName.IsEmpty()
|| event != NULL)) {
status = filter.SetInt32("limit", limit);
if (status == B_OK)
status = request.AddMessage("filter", &filter);
}
if (status == B_OK)
status = session->Daemon().SendMessage(&request, &reply);
if (status == B_OK)
info.AddMessage("user", &reply);
}
message->SendReply(&info);
}
uid_t
LaunchDaemon::_GetUserID(BMessage* message)
{
uid_t user = (uid_t)message->GetInt32("user", -1);
if (user < 0) {
BMessage reply((uint32)B_BAD_VALUE);
message->SendReply(&reply);
}
return user;
}
void
LaunchDaemon::_ReadPaths(const BStringList& paths)
{
for (int32 i = 0; i < paths.CountStrings(); i++) {
BEntry entry(paths.StringAt(i));
if (entry.InitCheck() != B_OK || !entry.Exists())
continue;
_ReadDirectory(NULL, entry);
}
}
void
LaunchDaemon::_ReadEntry(const char* context, BEntry& entry)
{
if (entry.IsDirectory())
_ReadDirectory(context, entry);
else
_ReadFile(context, entry);
}
void
LaunchDaemon::_ReadDirectory(const char* context, BEntry& directoryEntry)
{
BDirectory directory(&directoryEntry);
BEntry entry;
while (directory.GetNextEntry(&entry) == B_OK) {
_ReadEntry(context, entry);
}
}
status_t
LaunchDaemon::_ReadFile(const char* context, BEntry& entry)
{
BPath path;
status_t status = path.SetTo(&entry);
if (status != B_OK)
return status;
SettingsParser parser;
BMessage message;
status = parser.ParseFile(path.Path(), message);
if (status == B_OK) {
TRACE("launch_daemon: read file %s\n", path.Path());
_AddJobs(NULL, message);
_AddTargets(message);
_AddRunTargets(message);
}
return status;
}
void
LaunchDaemon::_AddJobs(Target* target, BMessage& message)
{
BMessage job;
for (int32 index = 0; message.FindMessage("service", index,
&job) == B_OK; index++) {
_AddJob(target, true, job);
}
for (int32 index = 0; message.FindMessage("job", index, &job) == B_OK;
index++) {
_AddJob(target, false, job);
}
}
void
LaunchDaemon::_AddTargets(BMessage& message)
{
BMessage targetMessage;
for (int32 index = 0; message.FindMessage("target", index,
&targetMessage) == B_OK; index++) {
const char* name = targetMessage.GetString("name");
if (name == NULL) {
debug_printf("Target has no name, ignoring it!\n");
continue;
}
Target* target = FindTarget(name);
if (target == NULL) {
target = new Target(name);
_AddTarget(target);
} else if (targetMessage.GetBool("reset")) {
for (JobMap::iterator iterator = fJobs.begin();
iterator != fJobs.end();) {
Job* job = iterator->second;
JobMap::iterator remove = iterator++;
if (job->Target() == target) {
fJobs.erase(remove);
delete job;
}
}
}
_SetCondition(target, targetMessage);
_SetEvent(target, targetMessage);
_SetEnvironment(target, targetMessage);
_AddJobs(target, targetMessage);
if (target->Event() != NULL)
target->Event()->Register(*this);
}
}
void
LaunchDaemon::_AddRunTargets(BMessage& message)
{
BMessage runMessage;
for (int32 index = 0; message.FindMessage("run", index,
&runMessage) == B_OK; index++) {
BMessage conditions;
bool pass = true;
if (runMessage.FindMessage("if", &conditions) == B_OK) {
Condition* condition = Conditions::FromMessage(conditions);
if (condition != NULL) {
pass = condition->Test(*this);
debug_printf("Test: %s -> %d\n", condition->ToString().String(),
pass);
delete condition;
} else
debug_printf("Could not parse condition!\n");
}
if (pass) {
_AddRunTargets(runMessage, NULL);
_AddRunTargets(runMessage, "then");
} else {
_AddRunTargets(runMessage, "else");
}
}
}
void
LaunchDaemon::_AddRunTargets(BMessage& message, const char* name)
{
BMessage targets;
if (name != NULL && message.FindMessage(name, &targets) != B_OK)
return;
const char* target;
for (int32 index = 0; targets.FindString("target", index, &target) == B_OK;
index++) {
fRunTargets.Add(target);
}
}
void
LaunchDaemon::_AddJob(Target* target, bool service, BMessage& message)
{
BString name = message.GetString("name");
if (name.IsEmpty()) {
return;
}
name.ToLower();
Job* job = FindJob(name);
if (job == NULL) {
TRACE(" add job \"%s\"\n", name.String());
job = new (std::nothrow) Job(name);
if (job == NULL)
return;
job->SetTeamListener(this);
job->SetService(service);
job->SetCreateDefaultPort(service);
job->SetTarget(target);
} else
TRACE(" amend job \"%s\"\n", name.String());
if (message.HasBool("disabled")) {
job->SetEnabled(!message.GetBool("disabled", !job->IsEnabled()));
fLog.JobEnabled(job, job->IsEnabled());
}
if (message.HasBool("legacy"))
job->SetCreateDefaultPort(!message.GetBool("legacy", !service));
_SetCondition(job, message);
_SetEvent(job, message);
_SetEnvironment(job, message);
BMessage portMessage;
for (int32 index = 0;
message.FindMessage("port", index, &portMessage) == B_OK; index++) {
job->AddPort(portMessage);
}
if (message.HasString("launch"))
message.FindStrings("launch", &job->Arguments());
const char* requirement;
for (int32 index = 0;
message.FindString("requires", index, &requirement) == B_OK;
index++) {
job->AddRequirement(requirement);
}
if (fInitTarget != NULL)
job->AddRequirement(fInitTarget->Name());
fJobs.insert(std::make_pair(job->Title(), job));
}
void
LaunchDaemon::_InitJobs(Target* target)
{
for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();) {
Job* job = iterator->second;
JobMap::iterator remove = iterator++;
if (job->Target() != target)
continue;
status_t status = B_NO_INIT;
if (job->IsEnabled()) {
if (job->Condition() == NULL || !job->Condition()->IsConstant(*this)
|| job->Condition()->Test(*this)) {
std::set<BString> dependencies;
status = job->Init(*this, dependencies);
if (status == B_OK && job->Event() != NULL)
status = job->Event()->Register(*this);
}
}
if (status == B_OK) {
fLog.JobInitialized(job);
} else {
if (status != B_NO_INIT) {
debug_printf("Init \"%s\" failed: %s\n", job->Name(),
strerror(status));
}
fLog.JobIgnored(job, status);
fJobs.erase(remove);
delete job;
}
}
}
void
LaunchDaemon::_LaunchJobs(Target* target, bool forceNow)
{
if (!forceNow && target != NULL && (!target->EventHasTriggered()
|| !target->CheckCondition(*this))) {
return;
}
if (target != NULL && !target->HasLaunched()) {
target->SetLaunched(true);
_InitJobs(target);
}
for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();
iterator++) {
Job* job = iterator->second;
if (job->Target() == target)
_LaunchJob(job);
}
}
void
LaunchDaemon::_StopJobs(Target* target, bool force)
{
if (target != NULL && !target->HasLaunched())
return;
for (JobMap::reverse_iterator iterator = fJobs.rbegin();
iterator != fJobs.rend(); iterator++) {
Job* job = iterator->second;
if (job->Target() == target)
_StopJob(job, force);
}
}
bool
LaunchDaemon::_CanLaunchJob(Job* job, uint32 options, bool testOnly)
{
if (job == NULL || !job->CanBeLaunched())
return false;
if ((options & FORCE_NOW) != 0 || job->EventHasTriggered())
return true;
return (options & TRIGGER_DEMAND) != 0 && Events::TriggerDemand(job->Event(), testOnly);
}
bool
LaunchDaemon::_CanLaunchJobRequirements(Job* job, uint32 options)
{
int32 count = job->Requirements().CountStrings();
for (int32 index = 0; index < count; index++) {
Job* requirement = FindJob(job->Requirements().StringAt(index));
if (requirement == NULL || requirement->IsRunning() || requirement->IsLaunching())
continue;
if (_CanLaunchJob(requirement, options, true)
&& _CanLaunchJobRequirements(requirement, options)) {
continue;
}
requirement->AddPending(job->Name());
return false;
}
return true;
}
bool
LaunchDaemon::_LaunchJob(Job* job, uint32 options)
{
if (job != NULL && (job->IsLaunching() || job->IsRunning()))
return true;
if (!_CanLaunchJob(job, options))
return false;
if (!_CanLaunchJobRequirements(job, options | TRIGGER_DEMAND))
return false;
int32 count = job->Requirements().CountStrings();
for (int32 index = 0; index < count; index++) {
Job* requirement = FindJob(job->Requirements().StringAt(index));
if (requirement != NULL) {
if (requirement->IsLaunching() || requirement->IsRunning())
continue;
if (!_LaunchJob(requirement, options | TRIGGER_DEMAND)) {
return false;
}
}
}
if (job->Target() != NULL)
job->Target()->ResolveSourceFiles();
if (job->Event() != NULL)
job->Event()->ResetTrigger();
if (!job->IsLaunching() && !job->IsRunning()) {
if (job->CheckCondition(*this)) {
job->SetLaunching(true);
status_t status = fJobQueue.AddJob(job);
if (status != B_OK) {
debug_printf("Adding job %s to queue failed: %s\n", job->Name(), strerror(status));
return false;
}
} else {
fLog.JobSkipped(job);
while (BJob* dependantJob = job->DependantJobAt(0))
dependantJob->RemoveDependency(job);
}
}
count = job->Pending().CountStrings();
for (int32 index = 0; index < count; index++) {
Job* pending = FindJob(job->Pending().StringAt(index));
if (pending != NULL && _LaunchJob(pending, 0)) {
index--;
count--;
}
}
return true;
}
void
LaunchDaemon::_StopJob(Job* job, bool force)
{
job->SetEnabled(false);
if (!job->IsRunning())
return;
BMessenger messenger;
if (job->GetMessenger(messenger) == B_OK) {
BMessage request(B_QUIT_REQUESTED);
messenger.SendMessage(&request);
return;
}
send_signal(job->Team(), SIGTERM);
}
void
LaunchDaemon::_AddTarget(Target* target)
{
fTargets.insert(std::make_pair(target->Title(), target));
}
void
LaunchDaemon::_SetCondition(BaseJob* job, const BMessage& message)
{
Condition* condition = job->Condition();
bool updated = false;
BMessage conditions;
if (message.FindMessage("if", &conditions) == B_OK) {
condition = Conditions::FromMessage(conditions);
updated = true;
}
if (message.GetBool("no_safemode")) {
condition = Conditions::AddNotSafeMode(condition);
updated = true;
}
if (updated)
job->SetCondition(condition);
}
void
LaunchDaemon::_SetEvent(BaseJob* job, const BMessage& message)
{
Event* event = job->Event();
bool updated = false;
BMessage events;
if (message.FindMessage("on", &events) == B_OK) {
event = Events::FromMessage(this, events);
updated = true;
}
if (message.GetBool("on_demand")) {
event = Events::AddOnDemand(this, event);
updated = true;
}
if (updated) {
TRACE(" event: %s\n", event->ToString().String());
job->SetEvent(event);
}
}
void
LaunchDaemon::_SetEnvironment(BaseJob* job, const BMessage& message)
{
BMessage environmentMessage;
if (message.FindMessage("env", &environmentMessage) == B_OK)
job->SetEnvironment(environmentMessage);
}
ExternalEventSource*
LaunchDaemon::_FindExternalEventSource(const char* owner, const char* name) const
{
if (name == NULL)
return NULL;
BString eventName = name;
eventName.ToLower();
EventMap::const_iterator found = fEvents.find(eventName);
if (found != fEvents.end())
return found->second;
if (owner == NULL)
return NULL;
eventName.Prepend("/");
eventName.Prepend(get_leaf(owner));
found = fEvents.find(eventName);
if (found != fEvents.end())
return found->second;
return NULL;
}
void
LaunchDaemon::_ResolveExternalEvents(ExternalEventSource* eventSource,
const BString& name)
{
for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();
iterator++) {
Event* externalEvent = Events::ResolveExternalEvent(iterator->second->Event(),
name, eventSource->Flags());
if (externalEvent != NULL)
eventSource->AddDestination(externalEvent);
}
}
void
LaunchDaemon::_GetBaseJobInfo(BaseJob* job, BMessage& info)
{
info.SetString("name", job->Name());
if (job->Event() != NULL)
info.SetString("event", job->Event()->ToString());
if (job->Condition() != NULL)
info.SetString("condition", job->Condition()->ToString());
}
void
LaunchDaemon::_ForwardEventMessage(uid_t user, BMessage* message)
{
if (fUserMode)
return;
if (user == 0) {
for (SessionMap::iterator iterator = fSessions.begin();
iterator != fSessions.end(); iterator++) {
Session* session = iterator->second;
session->Daemon().SendMessage(message);
}
} else {
Session* session = FindSession(user);
if (session != NULL)
session->Daemon().SendMessage(message);
}
}
status_t
LaunchDaemon::_StartSession(const char* login)
{
char path[B_PATH_NAME_LENGTH];
status_t status = get_app_path(path);
if (status != B_OK)
return status;
pid_t pid = -1;
const char* argv[] = {path, login, NULL};
status = posix_spawn(&pid, path, NULL, NULL, (char* const*)argv, environ);
if (status != B_OK)
return status;
return B_OK;
}
void
LaunchDaemon::_RetrieveKernelOptions()
{
char buffer[32];
size_t size = sizeof(buffer);
status_t status = _kern_get_safemode_option(B_SAFEMODE_SAFE_MODE, buffer,
&size);
if (status == B_OK) {
fSafeMode = !strncasecmp(buffer, "true", size)
|| !strncasecmp(buffer, "yes", size)
|| !strncasecmp(buffer, "on", size)
|| !strncasecmp(buffer, "enabled", size);
} else
fSafeMode = false;
}
void
LaunchDaemon::_SetupEnvironment()
{
setenv("SAFEMODE", IsSafeMode() ? "yes" : "no", true);
}
void
LaunchDaemon::_InitSystem()
{
#ifndef TEST_MODE
_AddInitJob(new InitRealTimeClockJob());
_AddInitJob(new InitSharedMemoryDirectoryJob());
_AddInitJob(new InitTemporaryDirectoryJob());
#endif
fJobQueue.AddJob(fInitTarget);
}
void
LaunchDaemon::_AddInitJob(BJob* job)
{
fInitTarget->AddDependency(job);
fJobQueue.AddJob(job);
}
#ifndef TEST_MODE
static void
open_stdio(int targetFD, int openMode)
{
#ifdef DEBUG
int fd = open("/dev/dprintf", openMode);
#else
int fd = open("/dev/null", openMode);
#endif
if (fd != targetFD) {
dup2(fd, targetFD);
close(fd);
}
}
#endif
static int
user_main(const char* login)
{
struct passwd* passwd = getpwnam(login);
if (passwd == NULL)
return B_NAME_NOT_FOUND;
if (strcmp(passwd->pw_name, login) != 0)
return B_NAME_NOT_FOUND;
uid_t user = passwd->pw_uid;
gid_t group = passwd->pw_gid;
if (setsid() < 0)
exit(EXIT_FAILURE);
if (initgroups(login, group) == -1)
exit(EXIT_FAILURE);
if (setgid(group) != 0)
exit(EXIT_FAILURE);
if (setuid(user) != 0)
exit(EXIT_FAILURE);
if (passwd->pw_dir != NULL && passwd->pw_dir[0] != '\0') {
setenv("HOME", passwd->pw_dir, true);
if (chdir(passwd->pw_dir) != 0) {
debug_printf("Could not switch to home dir %s: %s\n",
passwd->pw_dir, strerror(errno));
}
}
status_t status;
LaunchDaemon* daemon = new LaunchDaemon(true, status);
if (status == B_OK)
daemon->Run();
delete daemon;
return 0;
}
int
main(int argc, char* argv[])
{
if (argc == 2 && geteuid() == 0)
return user_main(argv[1]);
if (find_port(B_LAUNCH_DAEMON_PORT_NAME) >= 0) {
fprintf(stderr, "The launch_daemon is already running!\n");
return EXIT_FAILURE;
}
#ifndef TEST_MODE
open_stdio(STDIN_FILENO, O_RDONLY);
open_stdio(STDOUT_FILENO, O_WRONLY);
dup2(STDOUT_FILENO, STDERR_FILENO);
#endif
status_t status;
LaunchDaemon* daemon = new LaunchDaemon(false, status);
if (status == B_OK)
daemon->Run();
delete daemon;
return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE;
}