root/src/servers/launch/LaunchDaemon.cpp
/*
 * 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;
};


/*!     This class is the connection between the external events that are part of
        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;

        // ConditionContext
        virtual bool                            IsSafeMode() const;
        virtual bool                            BootVolumeIsReadOnly() const;

        // EventRegistrator
        virtual status_t                        RegisterExternalEvent(Event* event,
                                                                        const char* name,
                                                                        const BStringList& arguments);
        virtual void                            UnregisterExternalEvent(Event* event,
                                                                        const char* name);

        // TeamListener
        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;
}


// #pragma mark -


Session::Session(uid_t user, const BMessenger& daemon)
        :
        fUser(user),
        fDaemon(daemon)
{
}


// #pragma mark -


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);
}


// #pragma mark -


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);

        // We may not be able to talk to the registrar
        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);

        // Even if we failed to find a matching source, we do not want to return an error,
        // as that will be propagated up the chain and prevent this job from being instantiated.
        // Jobs will be re-scanned later for unregistered external events.
        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  // TEST_MODE
        } else
                _InitSystem();

        BStringList paths;
#ifdef TEST_MODE
        paths.Add("/boot/home/test_launch");
#else
        if (fUserMode) {
                // System-wide user specific jobs
                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  // TEST_MODE
        _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);

        // Launch run targets (ignores events)
        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());

                                // Get the exit status, and pass it on to the log
                                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) {
                                                // TODO: take restart throttle into account
                                                _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();

                                // Find job by name instead
                                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) {
                                // Update port info
                                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:
                {
                        // An internal event has been triggered.
                        // Check if its job(s) can be launched now.
                        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) {
                        // Forward request to user launch_daemon
                        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)) {
                        // The job exists, but cannot be started yet, as its
                        // conditions are not met; don't make it available yet
                        // TODO: we may not want to initialize jobs with conditions
                        // that aren't met yet
                        reply.what = B_NO_INIT;
                } else if (job->Event() != NULL) {
                        if (!Events::TriggerDemand(job->Event())) {
                                // The job is not triggered by demand; we cannot start it now
                                reply.what = B_NO_INIT;
                        } else {
                                // The job has already been triggered, don't launch it again
                                launchJob = false;
                        }
                }
        } else
                launchJob = false;

        bool ownsMessage = false;
        if (reply.what == B_OK) {
                // Launch the job if it hasn't been launched already
                if (launchJob)
                        _LaunchJob(job, TRIGGER_DEMAND);

                DetachCurrentMessage();
                status_t result = job->HandleGetLaunchData(message);
                if (result == B_OK) {
                        // Replying is delegated to the job.
                        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);

                        // Copy all jobs with the base target into the new target
                        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) {
                        // Forward request to user launch_daemon
                        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) {
                        // Forward request to user launch_daemon
                        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) {
                        // Forward request to user launch_daemon
                        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) {
                        // Forward request to user launch_daemon
                        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) {
                        // Forward request to user launch_daemon
                        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) {
                // Only the root user can start sessions
                // TODO: we'd actually need to know the uid of the sender
                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;

                // Send registration messages for all already-known events.
                for (EventMap::iterator iterator = fEvents.begin(); iterator != fEvents.end();
                                iterator++) {
                        ExternalEventSource* eventSource = iterator->second;
                        if (eventSource->Name() != iterator->first)
                                continue; // skip alternative event names

                        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) {
                        // Register event
                        ownerName = get_leaf(ownerName);

                        ExternalEventSource* event = new (std::nothrow)
                                ExternalEventSource(source, ownerName, name, flags);
                        if (event != NULL) {
                                // Use short name, and fully qualified name
                                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) {
                        // Unregister short and fully qualified event name
                        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) {
                // Trigger events
                const char* name = message->GetString("name");
                const char* ownerName = message->GetString("owner");
                // TODO: support arguments (as selectors)

                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) {
                // Reset sticky events
                const char* name = message->GetString("name");
                const char* ownerName = message->GetString("owner");
                // TODO: support arguments (as selectors)

                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) {
                // Request the data from the user's daemon, too
                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) {
                // Request the data from the user's daemon, too
                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", &parameter);

                        info.AddMessage("item", &itemMessage);

                        // limit == 0 means no limit
                        if (++count == limit)
                                break;
                }
        }

        // Get the list from the user daemon, and merge it into our reply
        Session* session = FindSession(user);
        if (session != NULL && !systemOnly) {
                if (limit != 0) {
                        // Update limit for user daemon
                        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)) {
                        // Forward filter specification when needed
                        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) {
                        // TODO: log error
                        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")) {
                        // Remove all jobs from this target
                        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()) {
                // Invalid job description
                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));
}


/*!     Initializes all jobs for the specified target (may be \c NULL).
        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()) {
                        // Filter out jobs that have a constant and failing condition
                        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) {
                                // TODO: log error
                                debug_printf("Init \"%s\" failed: %s\n", job->Name(),
                                        strerror(status));
                        }
                        fLog.JobIgnored(job, status);

                        // Remove jobs that won't be used later on
                        fJobs.erase(remove);
                        delete job;
                }
        }
}


/*!     Adds all jobs for the specified target (may be \c NULL) to the launch
        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);
        }
}


/*!     Stops all running jobs of the specified target (may be \c NULL).
*/
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);
        }
}


/*!     Checks whether or not the specified \a job can be launched.
        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);
}


/*!     Checks recursively if the requirements of the specified job can be launched,
        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));
                // running already?
                if (requirement == NULL || requirement->IsRunning() || requirement->IsLaunching())
                        continue;
                // can be launched?
                if (_CanLaunchJob(requirement, options, true)
                        && _CanLaunchJobRequirements(requirement, options)) {
                        continue;
                }
                // launch again when required job is launched
                requirement->AddPending(job->Name());
                return false;
        }

        return true;
}


/*!     Adds the specified \a job to the launch queue
        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;

        // Test if we can launch all requirements
        if (!_CanLaunchJobRequirements(job, options | TRIGGER_DEMAND))
                return false;

        // Actually launch the requirements
        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;

                        // TODO: For jobs that have their communication channels set up,
                        // we would not need to trigger demand at this point
                        if (!_LaunchJob(requirement, options | TRIGGER_DEMAND)) {
                                // Failed to put a requirement into the launch queue
                                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);

                        // job conditions not present, launch
                        while (BJob* dependantJob = job->DependantJobAt(0))
                                dependantJob->RemoveDependency(job);
                }
        }

        // Try to launch pending jobs as well
        count = job->Pending().CountStrings();
        for (int32 index = 0; index < count; index++) {
                Job* pending = FindJob(job->Pending().StringAt(index));
                if (pending != NULL && _LaunchJob(pending, 0)) {
                        // Remove the job from the pending list once its in the launch
                        // queue, so that is not being launched again next time.
                        index--;
                        count--;
                }
        }

        return true;
}


void
LaunchDaemon::_StopJob(Job* job, bool force)
{
        // TODO: find out which jobs require this job, and don't stop if any,
        // unless force, and then stop them all.
        job->SetEnabled(false);

        if (!job->IsRunning())
                return;

        // Be nice first, and send a simple quit message
        BMessenger messenger;
        if (job->GetMessenger(messenger) == B_OK) {
                BMessage request(B_QUIT_REQUESTED);
                messenger.SendMessage(&request);

                // TODO: wait a bit before going further
                return;
        }
        // TODO: allow custom shutdown

        send_signal(job->Team(), SIGTERM);
        // TODO: this would be the next step, again, after a delay
        //send_signal(job->Team(), SIGKILL);
}


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;

        // Forward event to user launch_daemon(s)
        if (user == 0) {
                for (SessionMap::iterator iterator = fSessions.begin();
                                iterator != fSessions.end(); iterator++) {
                        Session* session = iterator->second;
                        session->Daemon().SendMessage(message);
                                // ignore reply
                }
        } 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()
{
        // Determine safemode kernel option
        setenv("SAFEMODE", IsSafeMode() ? "yes" : "no", true);
}


/*!     Basic system initialization that must happen before any jobs are launched.
*/
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);
}


// #pragma mark -


#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  // TEST_MODE


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;

        // Check if there is a user session running already
        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));
                }
        }

        // TODO: take over system jobs, and reserve their names? (by asking parent)
        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
        // Make stdin/out/err available
        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;
}