xref: /haiku/src/servers/launch/LaunchDaemon.cpp (revision c16361c49ce8db183df0a9c7abea0b5f1bc30581)
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_BOOL_TYPE, "legacy", NULL},
51 	{B_MESSAGE_TYPE, "port", kPortTemplate},
52 	{B_BOOL_TYPE, "no_safemode", NULL},
53 	{0, NULL, NULL}
54 };
55 
56 const static settings_template kSettingsTemplate[] = {
57 	{B_MESSAGE_TYPE, "job", kJobTemplate},
58 	{B_MESSAGE_TYPE, "service", kJobTemplate},
59 	{0, NULL, NULL}
60 };
61 
62 
63 typedef std::map<BString, BMessage> PortMap;
64 
65 
66 class Job {
67 public:
68 								Job(const char* name);
69 	virtual						~Job();
70 
71 			const char*			Name() const;
72 
73 			bool				IsEnabled() const;
74 			void				SetEnabled(bool enable);
75 
76 			bool				IsService() const;
77 			void				SetService(bool service);
78 
79 			bool				CreateDefaultPort() const;
80 			void				SetCreateDefaultPort(bool createPort);
81 
82 			void				AddPort(BMessage& data);
83 
84 			bool				LaunchInSafeMode() const;
85 			void				SetLaunchInSafeMode(bool launch);
86 
87 			const BStringList&	Arguments() const;
88 			BStringList&		Arguments();
89 			void				AddArgument(const char* argument);
90 
91 			status_t			Init();
92 			status_t			InitCheck() const;
93 
94 			team_id				Team() const;
95 
96 			const PortMap&		Ports() const;
97 			port_id				Port(const char* name = NULL) const;
98 
99 			status_t			Launch();
100 			bool				IsLaunched() const;
101 
102 private:
103 			BString				fName;
104 			BStringList			fArguments;
105 			bool				fEnabled;
106 			bool				fService;
107 			bool				fCreateDefaultPort;
108 			bool				fLaunchInSafeMode;
109 			PortMap				fPortMap;
110 			status_t			fInitStatus;
111 			team_id				fTeam;
112 };
113 
114 
115 typedef std::map<BString, Job*> JobMap;
116 
117 
118 class LaunchDaemon : public BServer {
119 public:
120 								LaunchDaemon(status_t& error);
121 	virtual						~LaunchDaemon();
122 
123 	virtual	void				ReadyToRun();
124 	virtual	void				MessageReceived(BMessage* message);
125 
126 private:
127 			void				_ReadPaths(const BStringList& paths);
128 			void				_ReadEntry(const char* context, BEntry& entry);
129 			void				_ReadDirectory(const char* context,
130 									BEntry& directory);
131 			status_t			_ReadFile(const char* context, BEntry& entry);
132 
133 			void				_AddJob(bool service, BMessage& message);
134 			Job*				_Job(const char* name);
135 			void				_InitJobs();
136 			void				_LaunchJobs();
137 
138 			void				_RetrieveKernelOptions();
139 			void				_SetupEnvironment();
140 			void				_InitSystem();
141 
142 			bool				_IsSafeMode() const;
143 
144 private:
145 			JobMap				fJobs;
146 			JobQueue			fJobQueue;
147 			bool				fSafeMode;
148 };
149 
150 
151 static const char*
152 get_leaf(const char* signature)
153 {
154 	const char* separator = strrchr(signature, '/');
155 	if (separator != NULL)
156 		return separator + 1;
157 
158 	return signature;
159 }
160 
161 
162 // #pragma mark -
163 
164 
165 Job::Job(const char* name)
166 	:
167 	fName(name),
168 	fEnabled(true),
169 	fService(false),
170 	fCreateDefaultPort(false),
171 	fLaunchInSafeMode(true),
172 	fInitStatus(B_NO_INIT),
173 	fTeam(-1)
174 {
175 	fName.ToLower();
176 }
177 
178 
179 Job::~Job()
180 {
181 	PortMap::const_iterator iterator = Ports().begin();
182 	for (; iterator != Ports().end(); iterator++) {
183 		port_id port = iterator->second.GetInt32("port", -1);
184 		if (port >= 0)
185 			delete_port(port);
186 	}
187 }
188 
189 
190 const char*
191 Job::Name() const
192 {
193 	return fName.String();
194 }
195 
196 
197 bool
198 Job::IsEnabled() const
199 {
200 	return fEnabled;
201 }
202 
203 
204 void
205 Job::SetEnabled(bool enable)
206 {
207 	fEnabled = enable;
208 }
209 
210 
211 bool
212 Job::IsService() const
213 {
214 	return fService;
215 }
216 
217 
218 void
219 Job::SetService(bool service)
220 {
221 	fService = service;
222 }
223 
224 
225 bool
226 Job::CreateDefaultPort() const
227 {
228 	return fCreateDefaultPort;
229 }
230 
231 
232 void
233 Job::SetCreateDefaultPort(bool createPort)
234 {
235 	fCreateDefaultPort = createPort;
236 }
237 
238 
239 void
240 Job::AddPort(BMessage& data)
241 {
242 	const char* name = data.GetString("name");
243 	fPortMap.insert(std::pair<BString, BMessage>(BString(name), data));
244 }
245 
246 
247 bool
248 Job::LaunchInSafeMode() const
249 {
250 	return fLaunchInSafeMode;
251 }
252 
253 
254 void
255 Job::SetLaunchInSafeMode(bool launch)
256 {
257 	fLaunchInSafeMode = launch;
258 }
259 
260 
261 const BStringList&
262 Job::Arguments() const
263 {
264 	return fArguments;
265 }
266 
267 
268 BStringList&
269 Job::Arguments()
270 {
271 	return fArguments;
272 }
273 
274 
275 void
276 Job::AddArgument(const char* argument)
277 {
278 	fArguments.Add(argument);
279 }
280 
281 
282 status_t
283 Job::Init()
284 {
285 	fInitStatus = B_OK;
286 
287 	// Create ports
288 	// TODO: prefix system ports with "system:"
289 
290 	bool defaultPort = false;
291 
292 	for (PortMap::iterator iterator = fPortMap.begin();
293 			iterator != fPortMap.end(); iterator++) {
294 		BString name(Name());
295 		const char* suffix = iterator->second.GetString("name");
296 		if (suffix != NULL)
297 			name << ':' << suffix;
298 		else
299 			defaultPort = true;
300 
301 		const int32 capacity = iterator->second.GetInt32("capacity",
302 			B_LOOPER_PORT_DEFAULT_CAPACITY);
303 
304 		port_id port = create_port(capacity, name.String());
305 		if (port < 0) {
306 			fInitStatus = port;
307 			break;
308 		}
309 		iterator->second.SetInt32("port", port);
310 	}
311 
312 	if (fInitStatus == B_OK && fCreateDefaultPort && !defaultPort) {
313 		BMessage data;
314 		data.AddInt32("capacity", B_LOOPER_PORT_DEFAULT_CAPACITY);
315 
316 		port_id port = create_port(B_LOOPER_PORT_DEFAULT_CAPACITY, Name());
317 		if (port < 0)
318 			fInitStatus = port;
319 		else {
320 			data.SetInt32("port", port);
321 			AddPort(data);
322 		}
323 	}
324 
325 	return fInitStatus;
326 }
327 
328 
329 status_t
330 Job::InitCheck() const
331 {
332 	return fInitStatus;
333 }
334 
335 
336 team_id
337 Job::Team() const
338 {
339 	return fTeam;
340 }
341 
342 
343 const PortMap&
344 Job::Ports() const
345 {
346 	return fPortMap;
347 }
348 
349 
350 port_id
351 Job::Port(const char* name) const
352 {
353 	PortMap::const_iterator found = fPortMap.find(name);
354 	if (found != fPortMap.end())
355 		return found->second.GetInt32("port", -1);
356 
357 	return B_NAME_NOT_FOUND;
358 }
359 
360 
361 status_t
362 Job::Launch()
363 {
364 	if (fArguments.IsEmpty()) {
365 		// TODO: Launch via signature
366 		// We cannot use the BRoster here as it tries to pre-register
367 		// the application.
368 		BString signature("application/");
369 		signature << fName;
370 		return B_NOT_SUPPORTED;
371 		//return be_roster->Launch(signature.String(), (BMessage*)NULL, &fTeam);
372 	}
373 
374 	entry_ref ref;
375 	status_t status = get_ref_for_path(fArguments.StringAt(0).String(), &ref);
376 	if (status != B_OK)
377 		return status;
378 
379 	size_t count = fArguments.CountStrings();
380 	const char* args[count + 1];
381 	for (int32 i = 0; i < fArguments.CountStrings(); i++) {
382 		args[i] = fArguments.StringAt(i);
383 	}
384 	args[count] = NULL;
385 
386 	thread_id thread = load_image(count, args,
387 		const_cast<const char**>(environ));
388 	if (thread >= 0)
389 		resume_thread(thread);
390 
391 	thread_info info;
392 	if (get_thread_info(thread, &info) == B_OK)
393 		fTeam = info.team;
394 	return B_OK;
395 //	return be_roster->Launch(&ref, count, args, &fTeam);
396 }
397 
398 
399 bool
400 Job::IsLaunched() const
401 {
402 	return fTeam >= 0;
403 }
404 
405 
406 // #pragma mark -
407 
408 
409 LaunchDaemon::LaunchDaemon(status_t& error)
410 	:
411 	BServer(kLaunchDaemonSignature, NULL,
412 		create_port(B_LOOPER_PORT_DEFAULT_CAPACITY,
413 			B_LAUNCH_DAEMON_PORT_NAME), false, &error)
414 {
415 }
416 
417 
418 LaunchDaemon::~LaunchDaemon()
419 {
420 }
421 
422 
423 void
424 LaunchDaemon::ReadyToRun()
425 {
426 	_RetrieveKernelOptions();
427 	_SetupEnvironment();
428 	_InitSystem();
429 
430 	BStringList paths;
431 	BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY, kLaunchDirectory,
432 		B_FIND_PATHS_SYSTEM_ONLY, paths);
433 	_ReadPaths(paths);
434 
435 	BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY, kLaunchDirectory,
436 		B_FIND_PATHS_SYSTEM_ONLY, paths);
437 	_ReadPaths(paths);
438 
439 	_InitJobs();
440 	_LaunchJobs();
441 }
442 
443 
444 void
445 LaunchDaemon::MessageReceived(BMessage* message)
446 {
447 	switch (message->what) {
448 		case B_GET_LAUNCH_DATA:
449 		{
450 			BMessage reply((uint32)B_OK);
451 			Job* job = _Job(get_leaf(message->GetString("name")));
452 			if (job == NULL) {
453 				reply.what = B_NAME_NOT_FOUND;
454 			} else {
455 				// If the job has not been launched yet, we'll pass on our
456 				// team here. The rationale behind this is that this team
457 				// will temporarily own the synchronous reply ports.
458 				reply.AddInt32("team", job->Team() < 0
459 					? current_team() : job->Team());
460 
461 				PortMap::const_iterator iterator = job->Ports().begin();
462 				for (; iterator != job->Ports().end(); iterator++) {
463 					BString name;
464 					if (iterator->second.HasString("name"))
465 						name << iterator->second.GetString("name") << "_";
466 					name << "port";
467 
468 					reply.AddInt32(name.String(),
469 						iterator->second.GetInt32("port", -1));
470 				}
471 
472 				// Launch job now if it isn't running yet
473 				if (!job->IsLaunched())
474 					job->Launch();
475 			}
476 			message->SendReply(&reply);
477 			break;
478 		}
479 
480 		default:
481 			BServer::MessageReceived(message);
482 			break;
483 	}
484 }
485 
486 
487 void
488 LaunchDaemon::_ReadPaths(const BStringList& paths)
489 {
490 	for (int32 i = 0; i < paths.CountStrings(); i++) {
491 		BEntry entry(paths.StringAt(i));
492 		if (entry.InitCheck() != B_OK || !entry.Exists())
493 			continue;
494 
495 		_ReadDirectory(NULL, entry);
496 	}
497 }
498 
499 
500 void
501 LaunchDaemon::_ReadEntry(const char* context, BEntry& entry)
502 {
503 	if (entry.IsDirectory())
504 		_ReadDirectory(context, entry);
505 	else
506 		_ReadFile(context, entry);
507 }
508 
509 
510 void
511 LaunchDaemon::_ReadDirectory(const char* context, BEntry& directoryEntry)
512 {
513 	BDirectory directory(&directoryEntry);
514 
515 	BEntry entry;
516 	while (directory.GetNextEntry(&entry) == B_OK) {
517 		_ReadEntry(context, entry);
518 	}
519 }
520 
521 
522 status_t
523 LaunchDaemon::_ReadFile(const char* context, BEntry& entry)
524 {
525 	DriverSettingsMessageAdapter adapter;
526 
527 	BPath path;
528 	status_t status = path.SetTo(&entry);
529 	if (status != B_OK)
530 		return status;
531 
532 	BMessage message;
533 	status = adapter.ConvertFromDriverSettings(path.Path(), kSettingsTemplate,
534 			message);
535 	if (status == B_OK) {
536 		message.PrintToStream();
537 		BMessage job;
538 		for (int32 index = 0; message.FindMessage("service", index,
539 				&job) == B_OK; index++) {
540 			_AddJob(true, job);
541 		}
542 
543 		for (int32 index = 0; message.FindMessage("job", index, &job) == B_OK;
544 				index++) {
545 			_AddJob(false, job);
546 		}
547 	}
548 
549 	return status;
550 }
551 
552 
553 void
554 LaunchDaemon::_AddJob(bool service, BMessage& message)
555 {
556 	const char* name = message.GetString("name");
557 	if (name == NULL || name[0] == '\0') {
558 		// Invalid job description
559 		return;
560 	}
561 
562 	Job* job = _Job(name);
563 	if (job == NULL)
564 		job = new Job(name);
565 
566 	job->SetEnabled(!message.GetBool("disabled", !job->IsEnabled()));
567 	job->SetService(service);
568 	job->SetCreateDefaultPort(!message.GetBool("legacy", !service));
569 	job->SetLaunchInSafeMode(
570 		!message.GetBool("no_safemode", !job->LaunchInSafeMode()));
571 
572 	BMessage portMessage;
573 	for (int32 index = 0;
574 			message.FindMessage("port", index, &portMessage) == B_OK; index++) {
575 		job->AddPort(portMessage);
576 	}
577 
578 	const char* argument;
579 	for (int32 index = 0;
580 			message.FindString("launch", index, &argument) == B_OK; index++) {
581 		job->AddArgument(argument);
582 	}
583 
584 	fJobs.insert(std::pair<BString, Job*>(job->Name(), job));
585 }
586 
587 
588 Job*
589 LaunchDaemon::_Job(const char* name)
590 {
591 	if (name == NULL)
592 		return NULL;
593 
594 	JobMap::const_iterator found = fJobs.find(BString(name).ToLower());
595 	if (found != fJobs.end())
596 		return found->second;
597 
598 	return NULL;
599 }
600 
601 
602 void
603 LaunchDaemon::_InitJobs()
604 {
605 	for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();
606 			iterator++) {
607 		Job* job = iterator->second;
608 		if (job->IsEnabled() && (!_IsSafeMode() || job->LaunchInSafeMode()))
609 			job->Init();
610 	}
611 }
612 
613 
614 void
615 LaunchDaemon::_LaunchJobs()
616 {
617 	for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();
618 			iterator++) {
619 		Job* job = iterator->second;
620 		if (job->IsEnabled() && job->InitCheck() == B_OK)
621 			job->Launch();
622 	}
623 }
624 
625 
626 void
627 LaunchDaemon::_RetrieveKernelOptions()
628 {
629 	char buffer[32];
630 	size_t size = sizeof(buffer);
631 	status_t status = _kern_get_safemode_option(B_SAFEMODE_SAFE_MODE, buffer,
632 		&size);
633 	if (status == B_OK) {
634 		fSafeMode = !strncasecmp(buffer, "true", size)
635 			|| !strncasecmp(buffer, "yes", size)
636 			|| !strncasecmp(buffer, "on", size)
637 			|| !strncasecmp(buffer, "enabled", size);
638 	} else
639 		fSafeMode = false;
640 }
641 
642 
643 void
644 LaunchDaemon::_SetupEnvironment()
645 {
646 	// Determine safemode kernel option
647 	BString safemode = "SAFEMODE=";
648 	safemode << _IsSafeMode() ? "yes" : "no";
649 
650 	putenv(safemode.String());
651 }
652 
653 
654 /*!	Basic system initialization that must happen before any jobs are launched.
655 */
656 void
657 LaunchDaemon::_InitSystem()
658 {
659 	fJobQueue.AddJob(new InitRealTimeClockJob());
660 	fJobQueue.AddJob(new InitSharedMemoryDirectoryJob());
661 	fJobQueue.AddJob(new InitTemporaryDirectoryJob());
662 
663 	// TODO: these should be done in parallel
664 	while (BJob* job = fJobQueue.Pop())
665 		job->Run();
666 }
667 
668 
669 bool
670 LaunchDaemon::_IsSafeMode() const
671 {
672 	return fSafeMode;
673 }
674 
675 
676 // #pragma mark -
677 
678 
679 int
680 main()
681 {
682 	// TODO: remove this again
683 	close(STDOUT_FILENO);
684 	int fd = open("/dev/dprintf", O_WRONLY);
685 	if (fd != STDOUT_FILENO)
686 		dup2(fd, STDOUT_FILENO);
687 	puts("launch_daemon is alive and kicking.");
688 	fflush(stdout);
689 
690 	status_t status;
691 	LaunchDaemon* daemon = new LaunchDaemon(status);
692 	if (status == B_OK)
693 		daemon->Run();
694 
695 	delete daemon;
696 	return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE;
697 }
698