⛏️ index : haiku.git

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