xref: /haiku/src/servers/launch/LaunchDaemon.cpp (revision f913a08780a3685898c776bab93289ac4a0c434c)
1 /*
2  * Copyright 2015, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <map>
8 
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <unistd.h>
12 
13 #include <Directory.h>
14 #include <driver_settings.h>
15 #include <Entry.h>
16 #include <File.h>
17 #include <ObjectList.h>
18 #include <Path.h>
19 #include <PathFinder.h>
20 #include <Server.h>
21 
22 #include <AppMisc.h>
23 #include <DriverSettingsMessageAdapter.h>
24 #include <JobQueue.h>
25 #include <LaunchDaemonDefs.h>
26 #include <syscalls.h>
27 
28 #include "InitRealTimeClockJob.h"
29 #include "InitSharedMemoryDirectoryJob.h"
30 #include "InitTemporaryDirectoryJob.h"
31 
32 
33 using namespace BPrivate;
34 using namespace BSupportKit;
35 using BSupportKit::BPrivate::JobQueue;
36 
37 
38 static const char* kLaunchDirectory = "launch";
39 
40 
41 const static settings_template kPortTemplate[] = {
42 	{B_STRING_TYPE, "name", NULL, true},
43 	{B_INT32_TYPE, "capacity", NULL},
44 };
45 
46 const static settings_template kJobTemplate[] = {
47 	{B_STRING_TYPE, "name", NULL, true},
48 	{B_BOOL_TYPE, "disabled", NULL},
49 	{B_STRING_TYPE, "launch", NULL},
50 	{B_STRING_TYPE, "requires", NULL},
51 	{B_BOOL_TYPE, "legacy", NULL},
52 	{B_MESSAGE_TYPE, "port", kPortTemplate},
53 	{B_BOOL_TYPE, "no_safemode", NULL},
54 	{0, NULL, NULL}
55 };
56 
57 const static settings_template kSettingsTemplate[] = {
58 	{B_MESSAGE_TYPE, "job", kJobTemplate},
59 	{B_MESSAGE_TYPE, "service", kJobTemplate},
60 	{0, NULL, NULL}
61 };
62 
63 
64 typedef std::map<BString, BMessage> PortMap;
65 
66 
67 class Job {
68 public:
69 								Job(const char* name);
70 	virtual						~Job();
71 
72 			const char*			Name() const;
73 
74 			bool				IsEnabled() const;
75 			void				SetEnabled(bool enable);
76 
77 			bool				IsService() const;
78 			void				SetService(bool service);
79 
80 			bool				CreateDefaultPort() const;
81 			void				SetCreateDefaultPort(bool createPort);
82 
83 			void				AddPort(BMessage& data);
84 
85 			bool				LaunchInSafeMode() const;
86 			void				SetLaunchInSafeMode(bool launch);
87 
88 			const BStringList&	Arguments() const;
89 			BStringList&		Arguments();
90 			void				AddArgument(const char* argument);
91 
92 			const BStringList&	Requirements() const;
93 			BStringList&		Requirements();
94 			void				AddRequirement(const char* requirement);
95 
96 			status_t			Init();
97 			status_t			InitCheck() const;
98 
99 			team_id				Team() const;
100 
101 			const PortMap&		Ports() const;
102 			port_id				Port(const char* name = NULL) const;
103 
104 			status_t			Launch();
105 			bool				IsLaunched() const;
106 
107 private:
108 			BString				fName;
109 			BStringList			fArguments;
110 			BStringList			fRequirements;
111 			bool				fEnabled;
112 			bool				fService;
113 			bool				fCreateDefaultPort;
114 			bool				fLaunchInSafeMode;
115 			PortMap				fPortMap;
116 			status_t			fInitStatus;
117 			team_id				fTeam;
118 };
119 
120 
121 class LaunchJob : public BJob {
122 public:
123 								LaunchJob(Job* job);
124 
125 protected:
126 	virtual	status_t			Execute();
127 
128 private:
129 			Job*				fJob;
130 };
131 
132 
133 class Target : public BJob {
134 public:
135 								Target(const char* name);
136 
137 protected:
138 	virtual	status_t			Execute();
139 };
140 
141 
142 class Worker {
143 public:
144 								Worker(JobQueue& queue);
145 	virtual						~Worker();
146 
147 protected:
148 	virtual	status_t			Process();
149 	virtual	bigtime_t			Timeout() const;
150 	virtual	status_t			Run(BJob* job);
151 
152 private:
153 	static	status_t			_Process(void* self);
154 
155 protected:
156 			thread_id			fThread;
157 			JobQueue&			fJobQueue;
158 };
159 
160 
161 class MainWorker : public Worker {
162 public:
163 								MainWorker(JobQueue& queue);
164 
165 protected:
166 	virtual	bigtime_t			Timeout() const;
167 	virtual	status_t			Run(BJob* job);
168 
169 private:
170 			int32				fCPUCount;
171 };
172 
173 
174 typedef std::map<BString, Job*> JobMap;
175 
176 
177 class LaunchDaemon : public BServer {
178 public:
179 								LaunchDaemon(status_t& error);
180 	virtual						~LaunchDaemon();
181 
182 	virtual	void				ReadyToRun();
183 	virtual	void				MessageReceived(BMessage* message);
184 
185 private:
186 			void				_ReadPaths(const BStringList& paths);
187 			void				_ReadEntry(const char* context, BEntry& entry);
188 			void				_ReadDirectory(const char* context,
189 									BEntry& directory);
190 			status_t			_ReadFile(const char* context, BEntry& entry);
191 
192 			void				_AddJob(bool service, BMessage& message);
193 			Job*				_Job(const char* name);
194 			void				_InitJobs();
195 			void				_LaunchJobs();
196 			LaunchJob*			_AddLaunchJob(Job* job);
197 
198 			void				_RetrieveKernelOptions();
199 			void				_SetupEnvironment();
200 			void				_InitSystem();
201 			void				_AddInitJob(BJob* job);
202 
203 			bool				_IsSafeMode() const;
204 
205 private:
206 			JobMap				fJobs;
207 			JobQueue			fJobQueue;
208 			MainWorker*			fMainWorker;
209 			Target*				fInitTarget;
210 			bool				fSafeMode;
211 };
212 
213 
214 static const bigtime_t kWorkerTimeout = 1000000;
215 	// One second until a worker thread quits without a job
216 
217 static int32 sWorkerCount;
218 
219 
220 static const char*
221 get_leaf(const char* signature)
222 {
223 	const char* separator = strrchr(signature, '/');
224 	if (separator != NULL)
225 		return separator + 1;
226 
227 	return signature;
228 }
229 
230 
231 // #pragma mark -
232 
233 
234 Job::Job(const char* name)
235 	:
236 	fName(name),
237 	fEnabled(true),
238 	fService(false),
239 	fCreateDefaultPort(false),
240 	fLaunchInSafeMode(true),
241 	fInitStatus(B_NO_INIT),
242 	fTeam(-1)
243 {
244 	fName.ToLower();
245 }
246 
247 
248 Job::~Job()
249 {
250 	PortMap::const_iterator iterator = Ports().begin();
251 	for (; iterator != Ports().end(); iterator++) {
252 		port_id port = iterator->second.GetInt32("port", -1);
253 		if (port >= 0)
254 			delete_port(port);
255 	}
256 }
257 
258 
259 const char*
260 Job::Name() const
261 {
262 	return fName.String();
263 }
264 
265 
266 bool
267 Job::IsEnabled() const
268 {
269 	return fEnabled;
270 }
271 
272 
273 void
274 Job::SetEnabled(bool enable)
275 {
276 	fEnabled = enable;
277 }
278 
279 
280 bool
281 Job::IsService() const
282 {
283 	return fService;
284 }
285 
286 
287 void
288 Job::SetService(bool service)
289 {
290 	fService = service;
291 }
292 
293 
294 bool
295 Job::CreateDefaultPort() const
296 {
297 	return fCreateDefaultPort;
298 }
299 
300 
301 void
302 Job::SetCreateDefaultPort(bool createPort)
303 {
304 	fCreateDefaultPort = createPort;
305 }
306 
307 
308 void
309 Job::AddPort(BMessage& data)
310 {
311 	const char* name = data.GetString("name");
312 	fPortMap.insert(std::pair<BString, BMessage>(BString(name), data));
313 }
314 
315 
316 bool
317 Job::LaunchInSafeMode() const
318 {
319 	return fLaunchInSafeMode;
320 }
321 
322 
323 void
324 Job::SetLaunchInSafeMode(bool launch)
325 {
326 	fLaunchInSafeMode = launch;
327 }
328 
329 
330 const BStringList&
331 Job::Arguments() const
332 {
333 	return fArguments;
334 }
335 
336 
337 BStringList&
338 Job::Arguments()
339 {
340 	return fArguments;
341 }
342 
343 
344 void
345 Job::AddArgument(const char* argument)
346 {
347 	fArguments.Add(argument);
348 }
349 
350 
351 const BStringList&
352 Job::Requirements() const
353 {
354 	return fRequirements;
355 }
356 
357 
358 BStringList&
359 Job::Requirements()
360 {
361 	return fRequirements;
362 }
363 
364 
365 void
366 Job::AddRequirement(const char* requirement)
367 {
368 	fRequirements.Add(requirement);
369 }
370 
371 
372 status_t
373 Job::Init()
374 {
375 	fInitStatus = B_OK;
376 
377 	// Create ports
378 	// TODO: prefix system ports with "system:"
379 
380 	bool defaultPort = false;
381 
382 	for (PortMap::iterator iterator = fPortMap.begin();
383 			iterator != fPortMap.end(); iterator++) {
384 		BString name(Name());
385 		const char* suffix = iterator->second.GetString("name");
386 		if (suffix != NULL)
387 			name << ':' << suffix;
388 		else
389 			defaultPort = true;
390 
391 		const int32 capacity = iterator->second.GetInt32("capacity",
392 			B_LOOPER_PORT_DEFAULT_CAPACITY);
393 
394 		port_id port = create_port(capacity, name.String());
395 		if (port < 0) {
396 			fInitStatus = port;
397 			break;
398 		}
399 		iterator->second.SetInt32("port", port);
400 	}
401 
402 	if (fInitStatus == B_OK && fCreateDefaultPort && !defaultPort) {
403 		BMessage data;
404 		data.AddInt32("capacity", B_LOOPER_PORT_DEFAULT_CAPACITY);
405 
406 		port_id port = create_port(B_LOOPER_PORT_DEFAULT_CAPACITY, Name());
407 		if (port < 0)
408 			fInitStatus = port;
409 		else {
410 			data.SetInt32("port", port);
411 			AddPort(data);
412 		}
413 	}
414 
415 	return fInitStatus;
416 }
417 
418 
419 status_t
420 Job::InitCheck() const
421 {
422 	return fInitStatus;
423 }
424 
425 
426 team_id
427 Job::Team() const
428 {
429 	return fTeam;
430 }
431 
432 
433 const PortMap&
434 Job::Ports() const
435 {
436 	return fPortMap;
437 }
438 
439 
440 port_id
441 Job::Port(const char* name) const
442 {
443 	PortMap::const_iterator found = fPortMap.find(name);
444 	if (found != fPortMap.end())
445 		return found->second.GetInt32("port", -1);
446 
447 	return B_NAME_NOT_FOUND;
448 }
449 
450 
451 status_t
452 Job::Launch()
453 {
454 	if (fArguments.IsEmpty()) {
455 		// TODO: Launch via signature
456 		// We cannot use the BRoster here as it tries to pre-register
457 		// the application.
458 		BString signature("application/");
459 		signature << fName;
460 		return B_NOT_SUPPORTED;
461 		//return be_roster->Launch(signature.String(), (BMessage*)NULL, &fTeam);
462 	}
463 
464 	entry_ref ref;
465 	status_t status = get_ref_for_path(fArguments.StringAt(0).String(), &ref);
466 	if (status != B_OK)
467 		return status;
468 
469 	size_t count = fArguments.CountStrings();
470 	const char* args[count + 1];
471 	for (int32 i = 0; i < fArguments.CountStrings(); i++) {
472 		args[i] = fArguments.StringAt(i);
473 	}
474 	args[count] = NULL;
475 
476 	thread_id thread = load_image(count, args,
477 		const_cast<const char**>(environ));
478 	if (thread >= 0)
479 		resume_thread(thread);
480 
481 	thread_info info;
482 	if (get_thread_info(thread, &info) == B_OK)
483 		fTeam = info.team;
484 	return B_OK;
485 //	return be_roster->Launch(&ref, count, args, &fTeam);
486 }
487 
488 
489 bool
490 Job::IsLaunched() const
491 {
492 	return fTeam >= 0;
493 }
494 
495 
496 // #pragma mark -
497 
498 
499 LaunchJob::LaunchJob(Job* job)
500 	:
501 	BJob(job->Name()),
502 	fJob(job)
503 {
504 }
505 
506 
507 status_t
508 LaunchJob::Execute()
509 {
510 	if (!fJob->IsLaunched())
511 		return fJob->Launch();
512 
513 	return B_OK;
514 }
515 
516 
517 // #pragma mark -
518 
519 
520 Target::Target(const char* name)
521 	:
522 	BJob(name)
523 {
524 }
525 
526 
527 status_t
528 Target::Execute()
529 {
530 	return B_OK;
531 }
532 
533 
534 // #pragma mark -
535 
536 
537 Worker::Worker(JobQueue& queue)
538 	:
539 	fJobQueue(queue)
540 {
541 	fThread = spawn_thread(&Worker::_Process, "worker", B_NORMAL_PRIORITY,
542 		this);
543 	if (fThread >= 0 && resume_thread(fThread) == B_OK)
544 		atomic_add(&sWorkerCount, 1);
545 }
546 
547 
548 Worker::~Worker()
549 {
550 }
551 
552 
553 status_t
554 Worker::Process()
555 {
556 	while (true) {
557 		BJob* job;
558 		status_t status = fJobQueue.Pop(Timeout(), false, &job);
559 		if (status != B_OK)
560 			return status;
561 
562 		Run(job);
563 			// TODO: proper error reporting on failed job!
564 	}
565 }
566 
567 
568 bigtime_t
569 Worker::Timeout() const
570 {
571 	return kWorkerTimeout;
572 }
573 
574 
575 status_t
576 Worker::Run(BJob* job)
577 {
578 	return job->Run();
579 }
580 
581 
582 /*static*/ status_t
583 Worker::_Process(void* _self)
584 {
585 	Worker* self = (Worker*)_self;
586 	status_t status = self->Process();
587 	delete self;
588 
589 	return status;
590 }
591 
592 
593 // #pragma mark -
594 
595 
596 MainWorker::MainWorker(JobQueue& queue)
597 	:
598 	Worker(queue)
599 {
600 	// TODO: keep track of workers, and quit them on destruction
601 	system_info info;
602 	if (get_system_info(&info) == B_OK)
603 		fCPUCount = info.cpu_count;
604 }
605 
606 
607 bigtime_t
608 MainWorker::Timeout() const
609 {
610 	return B_INFINITE_TIMEOUT;
611 }
612 
613 
614 status_t
615 MainWorker::Run(BJob* job)
616 {
617 	int32 count = atomic_get(&sWorkerCount);
618 
619 	size_t jobCount = fJobQueue.CountJobs();
620 	if (jobCount > INT_MAX)
621 		jobCount = INT_MAX;
622 
623 	if ((int32)jobCount > count && count < fCPUCount)
624 		new Worker(fJobQueue);
625 
626 	return Worker::Run(job);
627 }
628 
629 
630 // #pragma mark -
631 
632 
633 LaunchDaemon::LaunchDaemon(status_t& error)
634 	:
635 	BServer(kLaunchDaemonSignature, NULL,
636 		create_port(B_LOOPER_PORT_DEFAULT_CAPACITY,
637 			B_LAUNCH_DAEMON_PORT_NAME), false, &error),
638 	fInitTarget(new Target("init"))
639 {
640 	fMainWorker = new MainWorker(fJobQueue);
641 }
642 
643 
644 LaunchDaemon::~LaunchDaemon()
645 {
646 }
647 
648 
649 void
650 LaunchDaemon::ReadyToRun()
651 {
652 	_RetrieveKernelOptions();
653 	_SetupEnvironment();
654 	_InitSystem();
655 
656 	BStringList paths;
657 	BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY, kLaunchDirectory,
658 		B_FIND_PATHS_SYSTEM_ONLY, paths);
659 	_ReadPaths(paths);
660 
661 	BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY, kLaunchDirectory,
662 		B_FIND_PATHS_SYSTEM_ONLY, paths);
663 	_ReadPaths(paths);
664 
665 	_InitJobs();
666 	_LaunchJobs();
667 }
668 
669 
670 void
671 LaunchDaemon::MessageReceived(BMessage* message)
672 {
673 	switch (message->what) {
674 		case B_GET_LAUNCH_DATA:
675 		{
676 			BMessage reply((uint32)B_OK);
677 			Job* job = _Job(get_leaf(message->GetString("name")));
678 			if (job == NULL) {
679 				reply.what = B_NAME_NOT_FOUND;
680 			} else {
681 				// If the job has not been launched yet, we'll pass on our
682 				// team here. The rationale behind this is that this team
683 				// will temporarily own the synchronous reply ports.
684 				reply.AddInt32("team", job->Team() < 0
685 					? current_team() : job->Team());
686 
687 				PortMap::const_iterator iterator = job->Ports().begin();
688 				for (; iterator != job->Ports().end(); iterator++) {
689 					BString name;
690 					if (iterator->second.HasString("name"))
691 						name << iterator->second.GetString("name") << "_";
692 					name << "port";
693 
694 					reply.AddInt32(name.String(),
695 						iterator->second.GetInt32("port", -1));
696 				}
697 
698 				_AddLaunchJob(job);
699 			}
700 			message->SendReply(&reply);
701 			break;
702 		}
703 
704 		default:
705 			BServer::MessageReceived(message);
706 			break;
707 	}
708 }
709 
710 
711 void
712 LaunchDaemon::_ReadPaths(const BStringList& paths)
713 {
714 	for (int32 i = 0; i < paths.CountStrings(); i++) {
715 		BEntry entry(paths.StringAt(i));
716 		if (entry.InitCheck() != B_OK || !entry.Exists())
717 			continue;
718 
719 		_ReadDirectory(NULL, entry);
720 	}
721 }
722 
723 
724 void
725 LaunchDaemon::_ReadEntry(const char* context, BEntry& entry)
726 {
727 	if (entry.IsDirectory())
728 		_ReadDirectory(context, entry);
729 	else
730 		_ReadFile(context, entry);
731 }
732 
733 
734 void
735 LaunchDaemon::_ReadDirectory(const char* context, BEntry& directoryEntry)
736 {
737 	BDirectory directory(&directoryEntry);
738 
739 	BEntry entry;
740 	while (directory.GetNextEntry(&entry) == B_OK) {
741 		_ReadEntry(context, entry);
742 	}
743 }
744 
745 
746 status_t
747 LaunchDaemon::_ReadFile(const char* context, BEntry& entry)
748 {
749 	DriverSettingsMessageAdapter adapter;
750 
751 	BPath path;
752 	status_t status = path.SetTo(&entry);
753 	if (status != B_OK)
754 		return status;
755 
756 	BMessage message;
757 	status = adapter.ConvertFromDriverSettings(path.Path(), kSettingsTemplate,
758 			message);
759 	if (status == B_OK) {
760 		message.PrintToStream();
761 		BMessage job;
762 		for (int32 index = 0; message.FindMessage("service", index,
763 				&job) == B_OK; index++) {
764 			_AddJob(true, job);
765 		}
766 
767 		for (int32 index = 0; message.FindMessage("job", index, &job) == B_OK;
768 				index++) {
769 			_AddJob(false, job);
770 		}
771 	}
772 
773 	return status;
774 }
775 
776 
777 void
778 LaunchDaemon::_AddJob(bool service, BMessage& message)
779 {
780 	const char* name = message.GetString("name");
781 	if (name == NULL || name[0] == '\0') {
782 		// Invalid job description
783 		return;
784 	}
785 
786 	Job* job = _Job(name);
787 	if (job == NULL)
788 		job = new Job(name);
789 
790 	job->SetEnabled(!message.GetBool("disabled", !job->IsEnabled()));
791 	job->SetService(service);
792 	job->SetCreateDefaultPort(!message.GetBool("legacy", !service));
793 	job->SetLaunchInSafeMode(
794 		!message.GetBool("no_safemode", !job->LaunchInSafeMode()));
795 
796 	BMessage portMessage;
797 	for (int32 index = 0;
798 			message.FindMessage("port", index, &portMessage) == B_OK; index++) {
799 		job->AddPort(portMessage);
800 	}
801 
802 	const char* argument;
803 	for (int32 index = 0;
804 			message.FindString("launch", index, &argument) == B_OK; index++) {
805 		job->AddArgument(argument);
806 	}
807 
808 	const char* requirement;
809 	for (int32 index = 0;
810 			message.FindString("requires", index, &requirement) == B_OK;
811 			index++) {
812 		job->AddRequirement(requirement);
813 	}
814 
815 	fJobs.insert(std::pair<BString, Job*>(job->Name(), job));
816 }
817 
818 
819 Job*
820 LaunchDaemon::_Job(const char* name)
821 {
822 	if (name == NULL)
823 		return NULL;
824 
825 	JobMap::const_iterator found = fJobs.find(BString(name).ToLower());
826 	if (found != fJobs.end())
827 		return found->second;
828 
829 	return NULL;
830 }
831 
832 
833 void
834 LaunchDaemon::_InitJobs()
835 {
836 	for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();
837 			iterator++) {
838 		Job* job = iterator->second;
839 		if (job->IsEnabled() && (!_IsSafeMode() || job->LaunchInSafeMode()))
840 			job->Init();
841 	}
842 }
843 
844 
845 void
846 LaunchDaemon::_LaunchJobs()
847 {
848 	for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();
849 			iterator++) {
850 		Job* job = iterator->second;
851 		if (job->IsEnabled() && job->InitCheck() == B_OK)
852 			_AddLaunchJob(job);
853 	}
854 }
855 
856 
857 LaunchJob*
858 LaunchDaemon::_AddLaunchJob(Job* job)
859 {
860 	if (job->IsLaunched())
861 		return NULL;
862 
863 	LaunchJob* launchJob = new LaunchJob(job);
864 
865 	// All jobs depend on the init target
866 	if (fInitTarget->State() < B_JOB_STATE_SUCCEEDED)
867 		launchJob->AddDependency(fInitTarget);
868 
869 	for (int32 index = 0; index < job->Requirements().CountStrings(); index++) {
870 		Job* dependency = _Job(job->Requirements().StringAt(index));
871 		if (dependency != NULL) {
872 			// Create launch job
873 			// TODO: detect circular dependencies!
874 			LaunchJob* dependentLaunchJob = _AddLaunchJob(dependency);
875 			if (dependentLaunchJob != NULL)
876 				launchJob->AddDependency(dependentLaunchJob);
877 		}
878 	}
879 
880 	fJobQueue.AddJob(launchJob);
881 	return launchJob;
882 }
883 
884 
885 void
886 LaunchDaemon::_RetrieveKernelOptions()
887 {
888 	char buffer[32];
889 	size_t size = sizeof(buffer);
890 	status_t status = _kern_get_safemode_option(B_SAFEMODE_SAFE_MODE, buffer,
891 		&size);
892 	if (status == B_OK) {
893 		fSafeMode = !strncasecmp(buffer, "true", size)
894 			|| !strncasecmp(buffer, "yes", size)
895 			|| !strncasecmp(buffer, "on", size)
896 			|| !strncasecmp(buffer, "enabled", size);
897 	} else
898 		fSafeMode = false;
899 }
900 
901 
902 void
903 LaunchDaemon::_SetupEnvironment()
904 {
905 	// Determine safemode kernel option
906 	BString safemode = "SAFEMODE=";
907 	safemode << _IsSafeMode() ? "yes" : "no";
908 
909 	putenv(safemode.String());
910 }
911 
912 
913 /*!	Basic system initialization that must happen before any jobs are launched.
914 */
915 void
916 LaunchDaemon::_InitSystem()
917 {
918 	_AddInitJob(new InitRealTimeClockJob());
919 	_AddInitJob(new InitSharedMemoryDirectoryJob());
920 	_AddInitJob(new InitTemporaryDirectoryJob());
921 
922 	fJobQueue.AddJob(fInitTarget);
923 }
924 
925 
926 void
927 LaunchDaemon::_AddInitJob(BJob* job)
928 {
929 	fInitTarget->AddDependency(job);
930 	fJobQueue.AddJob(job);
931 }
932 
933 
934 bool
935 LaunchDaemon::_IsSafeMode() const
936 {
937 	return fSafeMode;
938 }
939 
940 
941 // #pragma mark -
942 
943 
944 int
945 main()
946 {
947 	// TODO: remove this again
948 	close(STDOUT_FILENO);
949 	int fd = open("/dev/dprintf", O_WRONLY);
950 	if (fd != STDOUT_FILENO)
951 		dup2(fd, STDOUT_FILENO);
952 	puts("launch_daemon is alive and kicking.");
953 	fflush(stdout);
954 
955 	status_t status;
956 	LaunchDaemon* daemon = new LaunchDaemon(status);
957 	if (status == B_OK)
958 		daemon->Run();
959 
960 	delete daemon;
961 	return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE;
962 }
963