2 * Copyright 2015-2017, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
7 #include "LaunchDaemon.h"
18 #include <Directory.h>
19 #include <driver_settings.h>
22 #include <ObjectList.h>
24 #include <PathFinder.h>
28 #include <LaunchDaemonDefs.h>
29 #include <LaunchRosterPrivate.h>
31 #include <MessengerPrivate.h>
32 #include <RosterPrivate.h>
34 #include <system_info.h>
36 #include "multiuser_utils.h"
38 #include "Conditions.h"
40 #include "InitRealTimeClockJob.h"
41 #include "InitSharedMemoryDirectoryJob.h"
42 #include "InitTemporaryDirectoryJob.h"
44 #include "SettingsParser.h"
51 # define TRACE(x, ...) debug_printf(x, __VA_ARGS__)
53 # define TRACE(x, ...) ;
57 using namespace ::BPrivate
;
58 using namespace BSupportKit
;
59 using BSupportKit::BPrivate::JobQueue
;
63 static const char* kLaunchDirectory
= "launch";
64 static const char* kUserLaunchDirectory
= "user_launch";
76 Session(uid_t user
, const BMessenger
& target
);
80 const BMessenger
& Daemon() const
89 /*! This class is the connection between the external events that are part of
90 a job, and the external event source.
92 There is one object per registered event source, and it keeps all jobs that
93 reference the event as listeners. If the event source triggers the event,
94 the object will be used to trigger the jobs.
96 class ExternalEventSource
{
98 ExternalEventSource(BMessenger
& source
,
99 const char* ownerName
,
100 const char* name
, uint32 flags
);
101 ~ExternalEventSource();
103 const char* Name() const;
107 int32
CountListeners() const;
108 BaseJob
* ListenerAt(int32 index
) const;
110 status_t
AddListener(BaseJob
* job
);
111 void RemoveListener(BaseJob
* job
);
116 BObjectList
<BaseJob
> fListeners
;
120 typedef std::map
<BString
, Job
*> JobMap
;
121 typedef std::map
<uid_t
, Session
*> SessionMap
;
122 typedef std::map
<BString
, Target
*> TargetMap
;
123 typedef std::map
<BString
, ExternalEventSource
*> EventMap
;
124 typedef std::map
<team_id
, Job
*> TeamMap
;
127 class LaunchDaemon
: public BServer
, public Finder
, public ConditionContext
,
128 public EventRegistrator
, public TeamRegistrator
{
130 LaunchDaemon(bool userMode
,
131 const EventMap
& events
, status_t
& error
);
132 virtual ~LaunchDaemon();
134 virtual Job
* FindJob(const char* name
) const;
135 virtual Target
* FindTarget(const char* name
) const;
136 Session
* FindSession(uid_t user
) const;
139 virtual bool IsSafeMode() const;
140 virtual bool BootVolumeIsReadOnly() const;
143 virtual status_t
RegisterExternalEvent(Event
* event
,
145 const BStringList
& arguments
);
146 virtual void UnregisterExternalEvent(Event
* event
,
150 virtual void RegisterTeam(Job
* job
);
152 virtual void ReadyToRun();
153 virtual void MessageReceived(BMessage
* message
);
156 void _HandleGetLaunchData(BMessage
* message
);
157 void _HandleLaunchTarget(BMessage
* message
);
158 void _HandleStopLaunchTarget(BMessage
* message
);
159 void _HandleLaunchJob(BMessage
* message
);
160 void _HandleEnableLaunchJob(BMessage
* message
);
161 void _HandleStopLaunchJob(BMessage
* message
);
162 void _HandleLaunchSession(BMessage
* message
);
163 void _HandleRegisterSessionDaemon(BMessage
* message
);
164 void _HandleRegisterLaunchEvent(BMessage
* message
);
165 void _HandleUnregisterLaunchEvent(BMessage
* message
);
166 void _HandleNotifyLaunchEvent(BMessage
* message
);
167 void _HandleResetStickyLaunchEvent(
169 void _HandleGetLaunchTargets(BMessage
* message
);
170 void _HandleGetLaunchTargetInfo(BMessage
* message
);
171 void _HandleGetLaunchJobs(BMessage
* message
);
172 void _HandleGetLaunchJobInfo(BMessage
* message
);
173 uid_t
_GetUserID(BMessage
* message
);
175 void _ReadPaths(const BStringList
& paths
);
176 void _ReadEntry(const char* context
, BEntry
& entry
);
177 void _ReadDirectory(const char* context
,
179 status_t
_ReadFile(const char* context
, BEntry
& entry
);
181 void _AddJobs(Target
* target
, BMessage
& message
);
182 void _AddTargets(BMessage
& message
);
183 void _AddRunTargets(BMessage
& message
);
184 void _AddRunTargets(BMessage
& message
,
186 void _AddJob(Target
* target
, bool service
,
188 void _InitJobs(Target
* target
);
189 void _LaunchJobs(Target
* target
,
190 bool forceNow
= false);
191 void _StopJobs(Target
* target
, bool force
);
192 bool _CanLaunchJob(Job
* job
, uint32 options
,
193 bool testOnly
= false);
194 bool _CanLaunchJobRequirements(Job
* job
,
196 bool _LaunchJob(Job
* job
, uint32 options
= 0);
197 void _StopJob(Job
* job
, bool force
);
198 void _AddTarget(Target
* target
);
199 void _SetCondition(BaseJob
* job
,
200 const BMessage
& message
);
201 void _SetEvent(BaseJob
* job
,
202 const BMessage
& message
);
203 void _SetEnvironment(BaseJob
* job
,
204 const BMessage
& message
);
207 _FindEvent(const char* owner
,
208 const char* name
) const;
209 void _ResolveExternalEvents(
210 ExternalEventSource
* event
,
211 const BString
& name
);
212 void _ResolveExternalEvents(BaseJob
* job
);
213 void _GetBaseJobInfo(BaseJob
* job
, BMessage
& info
);
214 void _ForwardEventMessage(uid_t user
,
217 status_t
_StartSession(const char* login
);
219 void _RetrieveKernelOptions();
220 void _SetupEnvironment();
222 void _AddInitJob(BJob
* job
);
227 BStringList fRunTargets
;
230 SessionMap fSessions
;
231 MainWorker
* fMainWorker
;
236 bool fReadOnlyBootVolume
;
242 get_leaf(const char* signature
)
244 if (signature
== NULL
)
247 const char* separator
= strrchr(signature
, '/');
248 if (separator
!= NULL
)
249 return separator
+ 1;
258 Session::Session(uid_t user
, const BMessenger
& daemon
)
269 ExternalEventSource::ExternalEventSource(BMessenger
& source
,
270 const char* ownerName
, const char* name
, uint32 flags
)
279 ExternalEventSource::~ExternalEventSource()
285 ExternalEventSource::Name() const
287 return fName
.String();
292 ExternalEventSource::CountListeners() const
294 return fListeners
.CountItems();
299 ExternalEventSource::ListenerAt(int32 index
) const
301 return fListeners
.ItemAt(index
);
306 ExternalEventSource::AddListener(BaseJob
* job
)
308 if (fListeners
.AddItem(job
))
316 ExternalEventSource::RemoveListener(BaseJob
* job
)
318 fListeners
.RemoveItem(job
);
325 LaunchDaemon::LaunchDaemon(bool userMode
, const EventMap
& events
,
328 BServer(kLaunchDaemonSignature
, NULL
,
329 create_port(B_LOOPER_PORT_DEFAULT_CAPACITY
,
330 userMode
? "AppPort" : B_LAUNCH_DAEMON_PORT_NAME
), false, &error
),
332 fInitTarget(userMode
? NULL
: new Target("init")),
339 mutex_init(&fTeamsLock
, "teams lock");
341 fMainWorker
= new MainWorker(fJobQueue
);
344 if (fInitTarget
!= NULL
)
345 _AddTarget(fInitTarget
);
347 // We may not be able to talk to the registrar
349 BRoster::Private().SetWithoutRegistrar(true);
353 LaunchDaemon::~LaunchDaemon()
359 LaunchDaemon::FindJob(const char* name
) const
364 JobMap::const_iterator found
= fJobs
.find(BString(name
).ToLower());
365 if (found
!= fJobs
.end())
366 return found
->second
;
373 LaunchDaemon::FindTarget(const char* name
) const
378 TargetMap::const_iterator found
= fTargets
.find(BString(name
).ToLower());
379 if (found
!= fTargets
.end())
380 return found
->second
;
387 LaunchDaemon::FindSession(uid_t user
) const
389 SessionMap::const_iterator found
= fSessions
.find(user
);
390 if (found
!= fSessions
.end())
391 return found
->second
;
398 LaunchDaemon::IsSafeMode() const
405 LaunchDaemon::BootVolumeIsReadOnly() const
407 return fReadOnlyBootVolume
;
412 LaunchDaemon::RegisterExternalEvent(Event
* event
, const char* name
,
413 const BStringList
& arguments
)
415 // TODO: register actual event with event source
421 LaunchDaemon::UnregisterExternalEvent(Event
* event
, const char* name
)
428 LaunchDaemon::RegisterTeam(Job
* job
)
430 MutexLocker
locker(fTeamsLock
);
431 fTeams
.insert(std::make_pair(job
->Team(), job
));
436 LaunchDaemon::ReadyToRun()
438 _RetrieveKernelOptions();
441 fReadOnlyBootVolume
= Utility::IsReadOnlyVolume("/boot");
442 if (fReadOnlyBootVolume
)
443 Utility::BlockMedia("/boot", true);
447 BLaunchRoster roster
;
448 BLaunchRoster::Private(roster
).RegisterSessionDaemon(this);
455 paths
.Add("/boot/home/test_launch");
458 // System-wide user specific jobs
459 BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY
, kUserLaunchDirectory
,
460 B_FIND_PATHS_SYSTEM_ONLY
, paths
);
464 BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY
, kLaunchDirectory
,
465 fUserMode
? B_FIND_PATHS_USER_ONLY
: B_FIND_PATHS_SYSTEM_ONLY
, paths
);
469 BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY
,
470 kUserLaunchDirectory
, B_FIND_PATHS_SYSTEM_ONLY
, paths
);
474 BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY
, kLaunchDirectory
,
475 fUserMode
? B_FIND_PATHS_USER_ONLY
: B_FIND_PATHS_SYSTEM_ONLY
, paths
);
479 BMessenger
target(this);
480 BMessenger::Private
messengerPrivate(target
);
481 port_id port
= messengerPrivate
.Port();
482 int32 token
= messengerPrivate
.Token();
483 __start_watching_system(-1, B_WATCH_SYSTEM_TEAM_DELETION
, port
, token
);
488 // Launch run targets (ignores events)
489 for (int32 index
= 0; index
< fRunTargets
.CountStrings(); index
++) {
490 Target
* target
= FindTarget(fRunTargets
.StringAt(index
));
496 be_roster
->StartWatching(this, B_REQUEST_LAUNCHED
);
501 LaunchDaemon::MessageReceived(BMessage
* message
)
503 switch (message
->what
) {
504 case B_SYSTEM_OBJECT_UPDATE
:
506 int32 opcode
= message
->GetInt32("opcode", 0);
507 team_id team
= (team_id
)message
->GetInt32("team", -1);
508 if (opcode
!= B_TEAM_DELETED
|| team
< 0)
511 MutexLocker
locker(fTeamsLock
);
513 TeamMap::iterator found
= fTeams
.find(team
);
514 if (found
!= fTeams
.end()) {
515 Job
* job
= found
->second
;
516 TRACE("Job %s ended!\n", job
->Name());
519 if (job
->IsService()) {
520 // TODO: take restart throttle into account
521 // TODO: don't restart on shutdown
527 case B_SOME_APP_LAUNCHED
:
529 team_id team
= (team_id
)message
->GetInt32("be:team", -1);
532 MutexLocker
locker(fTeamsLock
);
534 TeamMap::iterator found
= fTeams
.find(team
);
535 if (found
!= fTeams
.end()) {
541 // Find job by name instead
542 const char* signature
= message
->GetString("be:signature");
543 job
= FindJob(get_leaf(signature
));
545 TRACE("Updated default port of untracked team %d, %s\n",
546 (int)team
, signature
);
553 status_t status
= be_roster
->GetRunningAppInfo(team
, &info
);
554 if (status
== B_OK
&& info
.port
!= job
->DefaultPort()) {
555 TRACE("Update default port for %s to %d\n", job
->Name(),
557 job
->SetDefaultPort(info
.port
);
563 case B_GET_LAUNCH_DATA
:
564 _HandleGetLaunchData(message
);
567 case B_LAUNCH_TARGET
:
568 _HandleLaunchTarget(message
);
570 case B_STOP_LAUNCH_TARGET
:
571 _HandleStopLaunchTarget(message
);
574 _HandleLaunchJob(message
);
576 case B_ENABLE_LAUNCH_JOB
:
577 _HandleEnableLaunchJob(message
);
579 case B_STOP_LAUNCH_JOB
:
580 _HandleStopLaunchJob(message
);
583 case B_LAUNCH_SESSION
:
584 _HandleLaunchSession(message
);
586 case B_REGISTER_SESSION_DAEMON
:
587 _HandleRegisterSessionDaemon(message
);
590 case B_REGISTER_LAUNCH_EVENT
:
591 _HandleRegisterLaunchEvent(message
);
593 case B_UNREGISTER_LAUNCH_EVENT
:
594 _HandleUnregisterLaunchEvent(message
);
596 case B_NOTIFY_LAUNCH_EVENT
:
597 _HandleNotifyLaunchEvent(message
);
599 case B_RESET_STICKY_LAUNCH_EVENT
:
600 _HandleResetStickyLaunchEvent(message
);
603 case B_GET_LAUNCH_TARGETS
:
604 _HandleGetLaunchTargets(message
);
606 case B_GET_LAUNCH_TARGET_INFO
:
607 _HandleGetLaunchTargetInfo(message
);
609 case B_GET_LAUNCH_JOBS
:
610 _HandleGetLaunchJobs(message
);
612 case B_GET_LAUNCH_JOB_INFO
:
613 _HandleGetLaunchJobInfo(message
);
616 case kMsgEventTriggered
:
618 // An internal event has been triggered.
619 // Check if its job can be launched now.
620 const char* name
= message
->GetString("owner");
624 Job
* job
= FindJob(name
);
630 Target
* target
= FindTarget(name
);
631 if (target
!= NULL
) {
639 BServer::MessageReceived(message
);
646 LaunchDaemon::_HandleGetLaunchData(BMessage
* message
)
648 uid_t user
= _GetUserID(message
);
652 BMessage
reply((uint32
)B_OK
);
653 bool launchJob
= true;
655 Job
* job
= FindJob(get_leaf(message
->GetString("name")));
657 Session
* session
= FindSession(user
);
658 if (session
!= NULL
) {
659 // Forward request to user launch_daemon
660 if (session
->Daemon().SendMessage(message
) == B_OK
)
663 reply
.what
= B_NAME_NOT_FOUND
;
664 } else if (job
->IsService() && !job
->IsLaunched()) {
665 if (job
->InitCheck() == B_NO_INIT
|| !job
->CheckCondition(*this)) {
666 // The job exists, but cannot be started yet, as its
667 // conditions are not met; don't make it available yet
668 // TODO: we may not want to initialize jobs with conditions
669 // that aren't met yet
670 reply
.what
= B_NO_INIT
;
671 } else if (job
->Event() != NULL
) {
672 if (!Events::TriggerDemand(job
->Event())) {
673 // The job is not triggered by demand; we cannot start it now
674 reply
.what
= B_NO_INIT
;
676 // The job has already been triggered, don't launch it again
683 bool ownsMessage
= false;
684 if (reply
.what
== B_OK
) {
685 // Launch the job if it hasn't been launched already
687 _LaunchJob(job
, TRIGGER_DEMAND
);
689 DetachCurrentMessage();
690 status_t result
= job
->HandleGetLaunchData(message
);
691 if (result
== B_OK
) {
692 // Replying is delegated to the job.
700 message
->SendReply(&reply
);
707 LaunchDaemon::_HandleLaunchTarget(BMessage
* message
)
709 uid_t user
= _GetUserID(message
);
713 const char* name
= message
->GetString("target");
714 const char* baseName
= message
->GetString("base target");
716 Target
* target
= FindTarget(name
);
717 if (target
== NULL
&& baseName
!= NULL
) {
718 Target
* baseTarget
= FindTarget(baseName
);
719 if (baseTarget
!= NULL
) {
720 target
= new Target(name
);
722 // Copy all jobs with the base target into the new target
723 for (JobMap::iterator iterator
= fJobs
.begin();
724 iterator
!= fJobs
.end();) {
725 Job
* job
= iterator
->second
;
728 if (job
->Target() == baseTarget
) {
729 Job
* copy
= new Job(*job
);
730 copy
->SetTarget(target
);
732 fJobs
.insert(std::make_pair(copy
->Name(), copy
));
737 if (target
== NULL
) {
738 Session
* session
= FindSession(user
);
739 if (session
!= NULL
) {
740 // Forward request to user launch_daemon
741 if (session
->Daemon().SendMessage(message
) == B_OK
)
745 BMessage
reply(B_NAME_NOT_FOUND
);
746 message
->SendReply(&reply
);
751 if (message
->FindMessage("data", &data
) == B_OK
)
752 target
->AddData(data
.GetString("name"), data
);
756 BMessage
reply((uint32
)B_OK
);
757 message
->SendReply(&reply
);
762 LaunchDaemon::_HandleStopLaunchTarget(BMessage
* message
)
764 uid_t user
= _GetUserID(message
);
768 const char* name
= message
->GetString("target");
770 Target
* target
= FindTarget(name
);
771 if (target
== NULL
) {
772 Session
* session
= FindSession(user
);
773 if (session
!= NULL
) {
774 // Forward request to user launch_daemon
775 if (session
->Daemon().SendMessage(message
) == B_OK
)
779 BMessage
reply(B_NAME_NOT_FOUND
);
780 message
->SendReply(&reply
);
785 if (message
->FindMessage("data", &data
) == B_OK
)
786 target
->AddData(data
.GetString("name"), data
);
788 _StopJobs(target
, message
->GetBool("force"));
790 BMessage
reply((uint32
)B_OK
);
791 message
->SendReply(&reply
);
796 LaunchDaemon::_HandleLaunchJob(BMessage
* message
)
798 uid_t user
= _GetUserID(message
);
802 const char* name
= message
->GetString("name");
804 Job
* job
= FindJob(name
);
806 Session
* session
= FindSession(user
);
807 if (session
!= NULL
) {
808 // Forward request to user launch_daemon
809 if (session
->Daemon().SendMessage(message
) == B_OK
)
813 BMessage
reply(B_NAME_NOT_FOUND
);
814 message
->SendReply(&reply
);
818 job
->SetEnabled(true);
819 _LaunchJob(job
, FORCE_NOW
);
821 BMessage
reply((uint32
)B_OK
);
822 message
->SendReply(&reply
);
827 LaunchDaemon::_HandleEnableLaunchJob(BMessage
* message
)
829 uid_t user
= _GetUserID(message
);
833 const char* name
= message
->GetString("name");
834 bool enable
= message
->GetBool("enable");
836 Job
* job
= FindJob(name
);
838 Session
* session
= FindSession(user
);
839 if (session
!= NULL
) {
840 // Forward request to user launch_daemon
841 if (session
->Daemon().SendMessage(message
) == B_OK
)
845 BMessage
reply(B_NAME_NOT_FOUND
);
846 message
->SendReply(&reply
);
850 job
->SetEnabled(enable
);
852 BMessage
reply((uint32
)B_OK
);
853 message
->SendReply(&reply
);
858 LaunchDaemon::_HandleStopLaunchJob(BMessage
* message
)
860 uid_t user
= _GetUserID(message
);
864 const char* name
= message
->GetString("name");
866 Job
* job
= FindJob(name
);
868 Session
* session
= FindSession(user
);
869 if (session
!= NULL
) {
870 // Forward request to user launch_daemon
871 if (session
->Daemon().SendMessage(message
) == B_OK
)
875 BMessage
reply(B_NAME_NOT_FOUND
);
876 message
->SendReply(&reply
);
880 _StopJob(job
, message
->GetBool("force"));
882 BMessage
reply((uint32
)B_OK
);
883 message
->SendReply(&reply
);
888 LaunchDaemon::_HandleLaunchSession(BMessage
* message
)
890 uid_t user
= _GetUserID(message
);
894 status_t status
= B_OK
;
895 const char* login
= message
->GetString("login");
897 status
= B_BAD_VALUE
;
898 if (status
== B_OK
&& user
!= 0) {
899 // Only the root user can start sessions
900 // TODO: we'd actually need to know the uid of the sender
901 status
= B_PERMISSION_DENIED
;
904 status
= _StartSession(login
);
906 BMessage
reply((uint32
)status
);
907 message
->SendReply(&reply
);
912 LaunchDaemon::_HandleRegisterSessionDaemon(BMessage
* message
)
914 uid_t user
= _GetUserID(message
);
918 status_t status
= B_OK
;
921 if (message
->FindMessenger("daemon", &target
) != B_OK
)
922 status
= B_BAD_VALUE
;
924 if (status
== B_OK
) {
925 Session
* session
= new (std::nothrow
) Session(user
, target
);
927 fSessions
.insert(std::make_pair(user
, session
));
929 status
= B_NO_MEMORY
;
932 BMessage
reply((uint32
)status
);
933 message
->SendReply(&reply
);
938 LaunchDaemon::_HandleRegisterLaunchEvent(BMessage
* message
)
940 uid_t user
= _GetUserID(message
);
944 if (user
== 0 || fUserMode
) {
945 status_t status
= B_OK
;
947 const char* name
= message
->GetString("name");
948 const char* ownerName
= message
->GetString("owner");
949 uint32 flags
= message
->GetUInt32("flags", 0);
951 if (name
!= NULL
&& ownerName
!= NULL
952 && message
->FindMessenger("source", &source
) == B_OK
) {
954 ownerName
= get_leaf(ownerName
);
956 ExternalEventSource
* event
= new (std::nothrow
)
957 ExternalEventSource(source
, ownerName
, name
, flags
);
959 // Use short name, and fully qualified name
960 BString eventName
= name
;
961 fEvents
.insert(std::make_pair(eventName
, event
));
962 _ResolveExternalEvents(event
, eventName
);
964 eventName
.Prepend("/");
965 eventName
.Prepend(ownerName
);
966 fEvents
.insert(std::make_pair(eventName
, event
));
967 _ResolveExternalEvents(event
, eventName
);
969 status
= B_NO_MEMORY
;
971 status
= B_BAD_VALUE
;
973 BMessage
reply((uint32
)status
);
974 message
->SendReply(&reply
);
977 _ForwardEventMessage(user
, message
);
982 LaunchDaemon::_HandleUnregisterLaunchEvent(BMessage
* message
)
984 uid_t user
= _GetUserID(message
);
988 if (user
== 0 || fUserMode
) {
989 status_t status
= B_OK
;
991 const char* name
= message
->GetString("name");
992 const char* ownerName
= message
->GetString("owner");
994 if (name
!= NULL
&& ownerName
!= NULL
995 && message
->FindMessenger("source", &source
) == B_OK
) {
996 // Unregister short and fully qualified event name
997 ownerName
= get_leaf(ownerName
);
999 BString eventName
= name
;
1000 fEvents
.erase(eventName
);
1002 eventName
.Prepend("/");
1003 eventName
.Prepend(ownerName
);
1004 fEvents
.erase(eventName
);
1006 status
= B_BAD_VALUE
;
1008 BMessage
reply((uint32
)status
);
1009 message
->SendReply(&reply
);
1012 _ForwardEventMessage(user
, message
);
1017 LaunchDaemon::_HandleNotifyLaunchEvent(BMessage
* message
)
1019 uid_t user
= _GetUserID(message
);
1023 if (user
== 0 || fUserMode
) {
1025 const char* name
= message
->GetString("name");
1026 const char* ownerName
= message
->GetString("owner");
1027 // TODO: support arguments (as selectors)
1029 ExternalEventSource
* event
= _FindEvent(ownerName
, name
);
1030 if (event
!= NULL
) {
1031 // Evaluate all of its jobs
1032 int32 count
= event
->CountListeners();
1033 for (int32 index
= 0; index
< count
; index
++) {
1034 BaseJob
* listener
= event
->ListenerAt(index
);
1035 Events::TriggerExternalEvent(listener
->Event(), name
);
1040 _ForwardEventMessage(user
, message
);
1045 LaunchDaemon::_HandleResetStickyLaunchEvent(BMessage
* message
)
1047 uid_t user
= _GetUserID(message
);
1051 if (user
== 0 || fUserMode
) {
1052 // Reset sticky events
1053 const char* name
= message
->GetString("name");
1054 const char* ownerName
= message
->GetString("owner");
1055 // TODO: support arguments (as selectors)
1057 ExternalEventSource
* event
= _FindEvent(ownerName
, name
);
1058 if (event
!= NULL
) {
1059 // Evaluate all of its jobs
1060 int32 count
= event
->CountListeners();
1061 for (int32 index
= 0; index
< count
; index
++) {
1062 BaseJob
* listener
= event
->ListenerAt(index
);
1063 Events::ResetStickyExternalEvent(listener
->Event(), name
);
1068 _ForwardEventMessage(user
, message
);
1073 LaunchDaemon::_HandleGetLaunchTargets(BMessage
* message
)
1075 uid_t user
= _GetUserID(message
);
1080 status_t status
= B_OK
;
1083 // Request the data from the user's daemon, too
1084 Session
* session
= FindSession(user
);
1085 if (session
!= NULL
) {
1086 BMessage
request(B_GET_LAUNCH_TARGETS
);
1087 status
= request
.AddInt32("user", 0);
1088 if (status
== B_OK
) {
1089 status
= session
->Daemon().SendMessage(&request
,
1093 status
= reply
.what
;
1095 status
= B_NAME_NOT_FOUND
;
1098 if (status
== B_OK
) {
1099 TargetMap::const_iterator iterator
= fTargets
.begin();
1100 for (; iterator
!= fTargets
.end(); iterator
++)
1101 reply
.AddString("target", iterator
->first
);
1104 reply
.what
= status
;
1105 message
->SendReply(&reply
);
1110 LaunchDaemon::_HandleGetLaunchTargetInfo(BMessage
* message
)
1112 uid_t user
= _GetUserID(message
);
1116 const char* name
= message
->GetString("name");
1117 Target
* target
= FindTarget(name
);
1118 if (target
== NULL
&& !fUserMode
) {
1119 _ForwardEventMessage(user
, message
);
1123 BMessage
info(uint32(target
!= NULL
? B_OK
: B_NAME_NOT_FOUND
));
1124 if (target
!= NULL
) {
1125 _GetBaseJobInfo(target
, info
);
1127 for (JobMap::iterator iterator
= fJobs
.begin(); iterator
!= fJobs
.end();
1129 Job
* job
= iterator
->second
;
1130 if (job
->Target() == target
)
1131 info
.AddString("job", job
->Name());
1134 message
->SendReply(&info
);
1139 LaunchDaemon::_HandleGetLaunchJobs(BMessage
* message
)
1141 uid_t user
= _GetUserID(message
);
1145 const char* targetName
= message
->GetString("target");
1148 status_t status
= B_OK
;
1151 // Request the data from the user's daemon, too
1152 Session
* session
= FindSession(user
);
1153 if (session
!= NULL
) {
1154 BMessage
request(B_GET_LAUNCH_JOBS
);
1155 status
= request
.AddInt32("user", 0);
1156 if (status
== B_OK
&& targetName
!= NULL
)
1157 status
= request
.AddString("target", targetName
);
1158 if (status
== B_OK
) {
1159 status
= session
->Daemon().SendMessage(&request
,
1163 status
= reply
.what
;
1165 status
= B_NAME_NOT_FOUND
;
1168 if (status
== B_OK
) {
1169 JobMap::const_iterator iterator
= fJobs
.begin();
1170 for (; iterator
!= fJobs
.end(); iterator
++) {
1171 Job
* job
= iterator
->second
;
1172 if (targetName
!= NULL
&& (job
->Target() == NULL
1173 || job
->Target()->Title() != targetName
)) {
1176 reply
.AddString("job", iterator
->first
);
1180 reply
.what
= status
;
1181 message
->SendReply(&reply
);
1186 LaunchDaemon::_HandleGetLaunchJobInfo(BMessage
* message
)
1188 uid_t user
= _GetUserID(message
);
1192 const char* name
= message
->GetString("name");
1193 Job
* job
= FindJob(name
);
1194 if (job
== NULL
&& !fUserMode
) {
1195 _ForwardEventMessage(user
, message
);
1199 BMessage
info(uint32(job
!= NULL
? B_OK
: B_NAME_NOT_FOUND
));
1201 _GetBaseJobInfo(job
, info
);
1203 info
.SetInt32("team", job
->Team());
1204 info
.SetBool("enabled", job
->IsEnabled());
1205 info
.SetBool("running", job
->IsRunning());
1206 info
.SetBool("launched", job
->IsLaunched());
1207 info
.SetBool("service", job
->IsService());
1209 if (job
->Target() != NULL
)
1210 info
.SetString("target", job
->Target()->Name());
1212 for (int32 i
= 0; i
< job
->Arguments().CountStrings(); i
++)
1213 info
.AddString("launch", job
->Arguments().StringAt(i
));
1215 for (int32 i
= 0; i
< job
->Requirements().CountStrings(); i
++)
1216 info
.AddString("requires", job
->Requirements().StringAt(i
));
1218 PortMap::const_iterator iterator
= job
->Ports().begin();
1219 for (; iterator
!= job
->Ports().end(); iterator
++)
1220 info
.AddMessage("port", &iterator
->second
);
1222 message
->SendReply(&info
);
1227 LaunchDaemon::_GetUserID(BMessage
* message
)
1229 uid_t user
= (uid_t
)message
->GetInt32("user", -1);
1231 BMessage
reply((uint32
)B_BAD_VALUE
);
1232 message
->SendReply(&reply
);
1239 LaunchDaemon::_ReadPaths(const BStringList
& paths
)
1241 for (int32 i
= 0; i
< paths
.CountStrings(); i
++) {
1242 BEntry
entry(paths
.StringAt(i
));
1243 if (entry
.InitCheck() != B_OK
|| !entry
.Exists())
1246 _ReadDirectory(NULL
, entry
);
1252 LaunchDaemon::_ReadEntry(const char* context
, BEntry
& entry
)
1254 if (entry
.IsDirectory())
1255 _ReadDirectory(context
, entry
);
1257 _ReadFile(context
, entry
);
1262 LaunchDaemon::_ReadDirectory(const char* context
, BEntry
& directoryEntry
)
1264 BDirectory
directory(&directoryEntry
);
1267 while (directory
.GetNextEntry(&entry
) == B_OK
) {
1268 _ReadEntry(context
, entry
);
1274 LaunchDaemon::_ReadFile(const char* context
, BEntry
& entry
)
1277 status_t status
= path
.SetTo(&entry
);
1281 SettingsParser parser
;
1283 status
= parser
.ParseFile(path
.Path(), message
);
1284 if (status
== B_OK
) {
1285 TRACE("launch_daemon: read file %s\n", path
.Path());
1286 _AddJobs(NULL
, message
);
1287 _AddTargets(message
);
1288 _AddRunTargets(message
);
1296 LaunchDaemon::_AddJobs(Target
* target
, BMessage
& message
)
1299 for (int32 index
= 0; message
.FindMessage("service", index
,
1300 &job
) == B_OK
; index
++) {
1301 _AddJob(target
, true, job
);
1304 for (int32 index
= 0; message
.FindMessage("job", index
, &job
) == B_OK
;
1306 _AddJob(target
, false, job
);
1312 LaunchDaemon::_AddTargets(BMessage
& message
)
1314 BMessage targetMessage
;
1315 for (int32 index
= 0; message
.FindMessage("target", index
,
1316 &targetMessage
) == B_OK
; index
++) {
1317 const char* name
= targetMessage
.GetString("name");
1320 debug_printf("Target has no name, ignoring it!\n");
1324 Target
* target
= FindTarget(name
);
1325 if (target
== NULL
) {
1326 target
= new Target(name
);
1328 } else if (targetMessage
.GetBool("reset")) {
1329 // Remove all jobs from this target
1330 for (JobMap::iterator iterator
= fJobs
.begin();
1331 iterator
!= fJobs
.end();) {
1332 Job
* job
= iterator
->second
;
1333 JobMap::iterator remove
= iterator
++;
1335 if (job
->Target() == target
) {
1336 fJobs
.erase(remove
);
1342 _SetCondition(target
, targetMessage
);
1343 _SetEvent(target
, targetMessage
);
1344 _SetEnvironment(target
, targetMessage
);
1345 _AddJobs(target
, targetMessage
);
1347 if (target
->Event() != NULL
)
1348 target
->Event()->Register(*this);
1354 LaunchDaemon::_AddRunTargets(BMessage
& message
)
1356 BMessage runMessage
;
1357 for (int32 index
= 0; message
.FindMessage("run", index
,
1358 &runMessage
) == B_OK
; index
++) {
1359 BMessage conditions
;
1361 if (runMessage
.FindMessage("if", &conditions
) == B_OK
) {
1362 Condition
* condition
= Conditions::FromMessage(conditions
);
1363 if (condition
!= NULL
) {
1364 pass
= condition
->Test(*this);
1365 debug_printf("Test: %s -> %d\n", condition
->ToString().String(),
1369 debug_printf("Could not parse condition!\n");
1373 _AddRunTargets(runMessage
, NULL
);
1374 _AddRunTargets(runMessage
, "then");
1376 _AddRunTargets(runMessage
, "else");
1383 LaunchDaemon::_AddRunTargets(BMessage
& message
, const char* name
)
1386 if (name
!= NULL
&& message
.FindMessage(name
, &targets
) != B_OK
)
1390 for (int32 index
= 0; targets
.FindString("target", index
, &target
) == B_OK
;
1392 fRunTargets
.Add(target
);
1398 LaunchDaemon::_AddJob(Target
* target
, bool service
, BMessage
& message
)
1400 BString name
= message
.GetString("name");
1401 if (name
.IsEmpty()) {
1402 // Invalid job description
1407 Job
* job
= FindJob(name
);
1409 TRACE(" add job \"%s\"\n", name
.String());
1411 job
= new (std::nothrow
) Job(name
);
1415 job
->SetTeamRegistrator(this);
1416 job
->SetService(service
);
1417 job
->SetCreateDefaultPort(service
);
1418 job
->SetTarget(target
);
1420 TRACE(" amend job \"%s\"\n", name
.String());
1422 if (message
.HasBool("disabled"))
1423 job
->SetEnabled(!message
.GetBool("disabled", !job
->IsEnabled()));
1425 if (message
.HasBool("legacy"))
1426 job
->SetCreateDefaultPort(!message
.GetBool("legacy", !service
));
1428 _SetCondition(job
, message
);
1429 _SetEvent(job
, message
);
1430 _SetEnvironment(job
, message
);
1432 BMessage portMessage
;
1433 for (int32 index
= 0;
1434 message
.FindMessage("port", index
, &portMessage
) == B_OK
; index
++) {
1435 job
->AddPort(portMessage
);
1438 if (message
.HasString("launch"))
1439 message
.FindStrings("launch", &job
->Arguments());
1441 const char* requirement
;
1442 for (int32 index
= 0;
1443 message
.FindString("requires", index
, &requirement
) == B_OK
;
1445 job
->AddRequirement(requirement
);
1447 if (fInitTarget
!= NULL
)
1448 job
->AddRequirement(fInitTarget
->Name());
1450 fJobs
.insert(std::make_pair(job
->Title(), job
));
1454 /*! Initializes all jobs for the specified target (may be \c NULL).
1455 Jobs that cannot be initialized, and those that never will be due to
1456 conditions, will be removed from the list.
1459 LaunchDaemon::_InitJobs(Target
* target
)
1461 for (JobMap::iterator iterator
= fJobs
.begin(); iterator
!= fJobs
.end();) {
1462 Job
* job
= iterator
->second
;
1463 JobMap::iterator remove
= iterator
++;
1465 if (job
->Target() != target
)
1468 status_t status
= B_NO_INIT
;
1469 if (job
->IsEnabled()) {
1470 // Filter out jobs that have a constant and failing condition
1471 if (job
->Condition() == NULL
|| !job
->Condition()->IsConstant(*this)
1472 || job
->Condition()->Test(*this)) {
1473 std::set
<BString
> dependencies
;
1474 status
= job
->Init(*this, dependencies
);
1475 if (status
== B_OK
&& job
->Event() != NULL
)
1476 status
= job
->Event()->Register(*this);
1480 if (status
!= B_OK
) {
1481 if (status
!= B_NO_INIT
) {
1483 debug_printf("Init \"%s\" failed: %s\n", job
->Name(),
1487 // Remove jobs that won't be used later on
1488 fJobs
.erase(remove
);
1495 /*! Adds all jobs for the specified target (may be \c NULL) to the launch
1496 queue, except those that are triggered by events that haven't been
1499 Unless \a forceNow is true, the target is only launched if its events,
1500 if any, have been triggered already, and its conditions are met.
1503 LaunchDaemon::_LaunchJobs(Target
* target
, bool forceNow
)
1505 if (!forceNow
&& target
!= NULL
&& (!target
->EventHasTriggered()
1506 || !target
->CheckCondition(*this))) {
1510 if (target
!= NULL
&& !target
->HasLaunched()) {
1511 target
->SetLaunched(true);
1515 for (JobMap::iterator iterator
= fJobs
.begin(); iterator
!= fJobs
.end();
1517 Job
* job
= iterator
->second
;
1518 if (job
->Target() == target
)
1524 /*! Stops all running jobs of the specified target (may be \c NULL).
1527 LaunchDaemon::_StopJobs(Target
* target
, bool force
)
1529 if (target
!= NULL
&& !target
->HasLaunched())
1532 for (JobMap::reverse_iterator iterator
= fJobs
.rbegin();
1533 iterator
!= fJobs
.rend(); iterator
++) {
1534 Job
* job
= iterator
->second
;
1535 if (job
->Target() == target
)
1536 _StopJob(job
, force
);
1541 /*! Checks whether or not the specified \a job can be launched.
1542 If \a testOnly is \c false, calling this method will trigger a demand
1546 LaunchDaemon::_CanLaunchJob(Job
* job
, uint32 options
, bool testOnly
)
1548 if (job
== NULL
|| !job
->CanBeLaunched())
1551 return (options
& FORCE_NOW
) != 0
1552 || (job
->EventHasTriggered() && job
->CheckCondition(*this)
1553 && ((options
& TRIGGER_DEMAND
) == 0
1554 || Events::TriggerDemand(job
->Event(), testOnly
)));
1558 /*! Checks recursively if the requirements of the specified job can be launched,
1559 if they are not running already.
1560 Calling this method will not trigger a demand for the requirements.
1563 LaunchDaemon::_CanLaunchJobRequirements(Job
* job
, uint32 options
)
1565 int32 count
= job
->Requirements().CountStrings();
1566 for (int32 index
= 0; index
< count
; index
++) {
1567 Job
* requirement
= FindJob(job
->Requirements().StringAt(index
));
1568 if (requirement
!= NULL
1569 && !requirement
->IsRunning() && !requirement
->IsLaunching()
1570 && (!_CanLaunchJob(requirement
, options
, true)
1571 || _CanLaunchJobRequirements(requirement
, options
))) {
1572 requirement
->AddPending(job
->Name());
1581 /*! Adds the specified \a job to the launch queue
1582 queue, except those that are triggered by events.
1584 Unless \c FORCE_NOW is set, the target is only launched if its events,
1585 if any, have been triggered already.
1587 Calling this method will trigger a demand event if \c TRIGGER_DEMAND has
1591 LaunchDaemon::_LaunchJob(Job
* job
, uint32 options
)
1593 if (job
!= NULL
&& (job
->IsLaunching() || job
->IsRunning()))
1596 if (!_CanLaunchJob(job
, options
))
1599 // Test if we can launch all requirements
1600 if (!_CanLaunchJobRequirements(job
, options
| TRIGGER_DEMAND
))
1603 // Actually launch the requirements
1604 int32 count
= job
->Requirements().CountStrings();
1605 for (int32 index
= 0; index
< count
; index
++) {
1606 Job
* requirement
= FindJob(job
->Requirements().StringAt(index
));
1607 if (requirement
!= NULL
) {
1608 // TODO: For jobs that have their communication channels set up,
1609 // we would not need to trigger demand at this point
1610 if (!_LaunchJob(requirement
, options
| TRIGGER_DEMAND
)) {
1611 // Failed to put a requirement into the launch queue
1617 if (job
->Target() != NULL
)
1618 job
->Target()->ResolveSourceFiles();
1619 if (job
->Event() != NULL
)
1620 job
->Event()->ResetTrigger();
1622 job
->SetLaunching(true);
1624 status_t status
= fJobQueue
.AddJob(job
);
1625 if (status
!= B_OK
) {
1626 debug_printf("Adding job %s to queue failed: %s\n", job
->Name(),
1631 // Try to launch pending jobs as well
1632 count
= job
->Pending().CountStrings();
1633 for (int32 index
= 0; index
< count
; index
++) {
1634 Job
* pending
= FindJob(job
->Pending().StringAt(index
));
1635 if (pending
!= NULL
&& _LaunchJob(pending
, 0)) {
1636 // Remove the job from the pending list once its in the launch
1637 // queue, so that is not being launched again next time.
1648 LaunchDaemon::_StopJob(Job
* job
, bool force
)
1650 // TODO: find out which jobs require this job, and don't stop if any,
1651 // unless force, and then stop them all.
1652 job
->SetEnabled(false);
1654 if (!job
->IsRunning())
1657 // Be nice first, and send a simple quit message
1658 BMessenger messenger
;
1659 if (job
->GetMessenger(messenger
) == B_OK
) {
1660 BMessage
request(B_QUIT_REQUESTED
);
1661 messenger
.SendMessage(&request
);
1663 // TODO: wait a bit before going further
1666 // TODO: allow custom shutdown
1668 send_signal(-job
->Team(), SIGINT
);
1669 // TODO: this would be the next step, again, after a delay
1670 //send_signal(job->Team(), SIGKILL);
1675 LaunchDaemon::_AddTarget(Target
* target
)
1677 fTargets
.insert(std::make_pair(target
->Title(), target
));
1682 LaunchDaemon::_SetCondition(BaseJob
* job
, const BMessage
& message
)
1684 Condition
* condition
= job
->Condition();
1685 bool updated
= false;
1687 BMessage conditions
;
1688 if (message
.FindMessage("if", &conditions
) == B_OK
) {
1689 condition
= Conditions::FromMessage(conditions
);
1693 if (message
.GetBool("no_safemode")) {
1694 condition
= Conditions::AddNotSafeMode(condition
);
1699 job
->SetCondition(condition
);
1704 LaunchDaemon::_SetEvent(BaseJob
* job
, const BMessage
& message
)
1706 Event
* event
= job
->Event();
1707 bool updated
= false;
1710 if (message
.FindMessage("on", &events
) == B_OK
) {
1711 event
= Events::FromMessage(this, events
);
1715 if (message
.GetBool("on_demand")) {
1716 event
= Events::AddOnDemand(this, event
);
1721 TRACE(" event: %s\n", event
->ToString().String());
1722 job
->SetEvent(event
);
1723 _ResolveExternalEvents(job
);
1729 LaunchDaemon::_SetEnvironment(BaseJob
* job
, const BMessage
& message
)
1731 BMessage environmentMessage
;
1732 if (message
.FindMessage("env", &environmentMessage
) == B_OK
)
1733 job
->SetEnvironment(environmentMessage
);
1737 ExternalEventSource
*
1738 LaunchDaemon::_FindEvent(const char* owner
, const char* name
) const
1743 BString eventName
= name
;
1744 eventName
.ToLower();
1746 EventMap::const_iterator found
= fEvents
.find(eventName
);
1747 if (found
!= fEvents
.end())
1748 return found
->second
;
1753 eventName
.Prepend("/");
1754 eventName
.Prepend(get_leaf(owner
));
1756 found
= fEvents
.find(eventName
);
1757 if (found
!= fEvents
.end())
1758 return found
->second
;
1765 LaunchDaemon::_ResolveExternalEvents(ExternalEventSource
* event
,
1766 const BString
& name
)
1768 for (JobMap::iterator iterator
= fJobs
.begin(); iterator
!= fJobs
.end();
1770 Job
* job
= iterator
->second
;
1771 if (Events::ResolveExternalEvent(job
->Event(), name
, event
->Flags()))
1772 event
->AddListener(job
);
1778 LaunchDaemon::_ResolveExternalEvents(BaseJob
* job
)
1780 if (job
->Event() == NULL
)
1783 for (EventMap::iterator iterator
= fEvents
.begin();
1784 iterator
!= fEvents
.end(); iterator
++) {
1785 ExternalEventSource
* event
= iterator
->second
;
1786 if (Events::ResolveExternalEvent(job
->Event(), event
->Name(),
1788 event
->AddListener(job
);
1794 LaunchDaemon::_GetBaseJobInfo(BaseJob
* job
, BMessage
& info
)
1796 info
.SetString("name", job
->Name());
1798 if (job
->Event() != NULL
)
1799 info
.SetString("event", job
->Event()->ToString());
1801 if (job
->Condition() != NULL
)
1802 info
.SetString("condition", job
->Condition()->ToString());
1807 LaunchDaemon::_ForwardEventMessage(uid_t user
, BMessage
* message
)
1812 // Forward event to user launch_daemon(s)
1814 for (SessionMap::iterator iterator
= fSessions
.begin();
1815 iterator
!= fSessions
.end(); iterator
++) {
1816 Session
* session
= iterator
->second
;
1817 session
->Daemon().SendMessage(message
);
1821 Session
* session
= FindSession(user
);
1822 if (session
!= NULL
)
1823 session
->Daemon().SendMessage(message
);
1829 LaunchDaemon::_StartSession(const char* login
)
1831 // TODO: enable user/group code
1832 // The launch_daemon currently cannot talk to the registrar, though
1834 struct passwd
* passwd
= getpwnam(login
);
1836 return B_NAME_NOT_FOUND
;
1837 if (strcmp(passwd
->pw_name
, login
) != 0)
1838 return B_NAME_NOT_FOUND
;
1840 // Check if there is a user session running already
1841 uid_t user
= passwd
->pw_uid
;
1842 gid_t group
= passwd
->pw_gid
;
1850 if (initgroups(login
, group
) == -1)
1852 if (setgid(group
) != 0)
1854 if (setuid(user
) != 0)
1857 if (passwd
->pw_dir
!= NULL
&& passwd
->pw_dir
[0] != '\0') {
1858 setenv("HOME", passwd
->pw_dir
, true);
1860 if (chdir(passwd
->pw_dir
) != 0) {
1861 debug_printf("Could not switch to home dir %s: %s\n",
1862 passwd
->pw_dir
, strerror(errno
));
1866 // TODO: This leaks the parent application
1869 // Reinitialize be_roster
1870 BRoster::Private().DeleteBeRoster();
1871 BRoster::Private().InitBeRoster();
1873 // TODO: take over system jobs, and reserve their names (or ask parent)
1875 LaunchDaemon
* daemon
= new LaunchDaemon(true, fEvents
, status
);
1888 LaunchDaemon::_RetrieveKernelOptions()
1891 size_t size
= sizeof(buffer
);
1892 status_t status
= _kern_get_safemode_option(B_SAFEMODE_SAFE_MODE
, buffer
,
1894 if (status
== B_OK
) {
1895 fSafeMode
= !strncasecmp(buffer
, "true", size
)
1896 || !strncasecmp(buffer
, "yes", size
)
1897 || !strncasecmp(buffer
, "on", size
)
1898 || !strncasecmp(buffer
, "enabled", size
);
1905 LaunchDaemon::_SetupEnvironment()
1907 // Determine safemode kernel option
1908 setenv("SAFEMODE", IsSafeMode() ? "yes" : "no", true);
1910 // Default locale settings
1911 setenv("LC_TYPE", "en_US.UTF-8", true);
1915 /*! Basic system initialization that must happen before any jobs are launched.
1918 LaunchDaemon::_InitSystem()
1921 _AddInitJob(new InitRealTimeClockJob());
1922 _AddInitJob(new InitSharedMemoryDirectoryJob());
1923 _AddInitJob(new InitTemporaryDirectoryJob());
1926 fJobQueue
.AddJob(fInitTarget
);
1931 LaunchDaemon::_AddInitJob(BJob
* job
)
1933 fInitTarget
->AddDependency(job
);
1934 fJobQueue
.AddJob(job
);
1945 open_stdio(int targetFD
, int openMode
)
1948 int fd
= open("/dev/dprintf", openMode
);
1950 int fd
= open("/dev/null", openMode
);
1952 if (fd
!= targetFD
) {
1965 if (find_port(B_LAUNCH_DAEMON_PORT_NAME
) >= 0) {
1966 fprintf(stderr
, "The launch_daemon is already running!\n");
1967 return EXIT_FAILURE
;
1971 // Make stdin/out/err available
1972 open_stdio(STDIN_FILENO
, O_RDONLY
);
1973 open_stdio(STDOUT_FILENO
, O_WRONLY
);
1974 dup2(STDOUT_FILENO
, STDERR_FILENO
);
1979 LaunchDaemon
* daemon
= new LaunchDaemon(false, events
, status
);
1984 return status
== B_OK
? EXIT_SUCCESS
: EXIT_FAILURE
;