* Copyright 2015-2018, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
#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;
};
a job, and the external event source.
There is one object per registered event source, and it keeps all jobs that
reference the event as listeners. If the event source triggers the event,
the object will be used to trigger the jobs.
*/
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));
}
Jobs that cannot be initialized, and those that never will be due to
conditions, will be removed from the list.
*/
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;
}
}
}
queue, except those that are triggered by events that haven't been
triggered yet.
Unless \a forceNow is true, the target is only launched if its events,
if any, have been triggered already, and its conditions are met.
*/
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);
}
}
If \a testOnly is \c false, calling this method will trigger a demand
to the \a job.
*/
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);
}
if they are not running already.
Calling this method will not trigger a demand for the requirements.
*/
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;
}
queue, except those that are triggered by events.
Unless \c FORCE_NOW is set, the target is only launched if its events,
if any, have been triggered already.
Calling this method will trigger a demand event if \c TRIGGER_DEMAND has
been set.
*/
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;
}