xref: /haiku/src/servers/launch/LaunchDaemon.cpp (revision d482c7ca5ba4f9041dfd5db328bd9703ea57d2ae)
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 <ObjectList.h>
17 #include <Path.h>
18 #include <PathFinder.h>
19 #include <Server.h>
20 
21 #include <AppMisc.h>
22 #include <DriverSettingsMessageAdapter.h>
23 #include <LaunchDaemonDefs.h>
24 #include <syscalls.h>
25 
26 
27 using namespace BPrivate;
28 
29 
30 static const char* kLaunchDirectory = "launch";
31 
32 
33 const static settings_template kJobTemplate[] = {
34 	{B_STRING_TYPE, "name", NULL, true},
35 	{B_BOOL_TYPE, "disabled", NULL},
36 	{B_STRING_TYPE, "launch", NULL},
37 	{B_BOOL_TYPE, "create_port", NULL},
38 	{B_BOOL_TYPE, "no_safemode", NULL},
39 	{0, NULL, NULL}
40 };
41 
42 const static settings_template kSettingsTemplate[] = {
43 	{B_MESSAGE_TYPE, "job", kJobTemplate},
44 	{B_MESSAGE_TYPE, "service", kJobTemplate},
45 	{0, NULL, NULL}
46 };
47 
48 
49 class Job {
50 public:
51 								Job(const char* name);
52 	virtual						~Job();
53 
54 			const char*			Name() const;
55 
56 			bool				IsEnabled() const;
57 			void				SetEnabled(bool enable);
58 
59 			bool				IsService() const;
60 			void				SetService(bool service);
61 
62 			bool				CreatePort() const;
63 			void				SetCreatePort(bool createPort);
64 
65 			bool				LaunchInSafeMode() const;
66 			void				SetLaunchInSafeMode(bool launch);
67 
68 			const BStringList&	Arguments() const;
69 			BStringList&		Arguments();
70 			void				AddArgument(const char* argument);
71 
72 			status_t			Init();
73 			status_t			InitCheck() const;
74 
75 			team_id				Team() const;
76 			port_id				Port() const;
77 
78 			status_t			Launch();
79 			bool				IsLaunched() const;
80 
81 private:
82 			BString				fName;
83 			BStringList			fArguments;
84 			bool				fEnabled;
85 			bool				fService;
86 			bool				fCreatePort;
87 			bool				fLaunchInSafeMode;
88 			port_id				fPort;
89 			status_t			fInitStatus;
90 			team_id				fTeam;
91 };
92 
93 
94 typedef std::map<BString, Job*> JobMap;
95 
96 
97 class LaunchDaemon : public BServer {
98 public:
99 								LaunchDaemon(status_t& error);
100 	virtual						~LaunchDaemon();
101 
102 	virtual	void				ReadyToRun();
103 	virtual	void				MessageReceived(BMessage* message);
104 
105 private:
106 			void				_ReadPaths(const BStringList& paths);
107 			void				_ReadEntry(const char* context, BEntry& entry);
108 			void				_ReadDirectory(const char* context,
109 									BEntry& directory);
110 			status_t			_ReadFile(const char* context, BEntry& entry);
111 
112 			void				_AddJob(bool service, BMessage& message);
113 			Job*				_Job(const char* name);
114 			void				_InitJobs();
115 			void				_LaunchJobs();
116 
117 			void				_SetupEnvironment();
118 			bool				_IsSafeMode() const;
119 
120 private:
121 			JobMap				fJobs;
122 };
123 
124 
125 static const char*
126 get_leaf(const char* signature)
127 {
128 	const char* separator = strrchr(signature, '/');
129 	if (separator != NULL)
130 		return separator + 1;
131 
132 	return signature;
133 }
134 
135 
136 // #pragma mark -
137 
138 
139 Job::Job(const char* name)
140 	:
141 	fName(name),
142 	fEnabled(true),
143 	fService(false),
144 	fCreatePort(false),
145 	fLaunchInSafeMode(true),
146 	fPort(-1),
147 	fInitStatus(B_NO_INIT),
148 	fTeam(-1)
149 {
150 	fName.ToLower();
151 }
152 
153 
154 Job::~Job()
155 {
156 	if (fPort >= 0)
157 		delete_port(fPort);
158 }
159 
160 
161 const char*
162 Job::Name() const
163 {
164 	return fName.String();
165 }
166 
167 
168 bool
169 Job::IsEnabled() const
170 {
171 	return fEnabled;
172 }
173 
174 
175 void
176 Job::SetEnabled(bool enable)
177 {
178 	fEnabled = enable;
179 }
180 
181 
182 bool
183 Job::IsService() const
184 {
185 	return fService;
186 }
187 
188 
189 void
190 Job::SetService(bool service)
191 {
192 	fService = service;
193 }
194 
195 
196 bool
197 Job::CreatePort() const
198 {
199 	return fCreatePort;
200 }
201 
202 
203 void
204 Job::SetCreatePort(bool createPort)
205 {
206 	fCreatePort = createPort;
207 }
208 
209 
210 bool
211 Job::LaunchInSafeMode() const
212 {
213 	return fLaunchInSafeMode;
214 }
215 
216 
217 void
218 Job::SetLaunchInSafeMode(bool launch)
219 {
220 	fLaunchInSafeMode = launch;
221 }
222 
223 
224 const BStringList&
225 Job::Arguments() const
226 {
227 	return fArguments;
228 }
229 
230 
231 BStringList&
232 Job::Arguments()
233 {
234 	return fArguments;
235 }
236 
237 
238 void
239 Job::AddArgument(const char* argument)
240 {
241 	fArguments.Add(argument);
242 }
243 
244 
245 status_t
246 Job::Init()
247 {
248 	fInitStatus = B_OK;
249 
250 	if (fCreatePort) {
251 		// TODO: prefix system ports with "system:"
252 		fPort = create_port(B_LOOPER_PORT_DEFAULT_CAPACITY, Name());
253 		if (fPort < 0)
254 			fInitStatus = fPort;
255 	}
256 
257 	return fInitStatus;
258 }
259 
260 
261 status_t
262 Job::InitCheck() const
263 {
264 	return fInitStatus;
265 }
266 
267 
268 team_id
269 Job::Team() const
270 {
271 	return fTeam;
272 }
273 
274 
275 port_id
276 Job::Port() const
277 {
278 	return fPort;
279 }
280 
281 
282 status_t
283 Job::Launch()
284 {
285 	if (fArguments.IsEmpty()) {
286 		// TODO: Launch via signature
287 		// We cannot use the BRoster here as it tries to pre-register
288 		// the application.
289 		BString signature("application/");
290 		signature << fName;
291 		return B_NOT_SUPPORTED;
292 		//return be_roster->Launch(signature.String(), (BMessage*)NULL, &fTeam);
293 	}
294 
295 	entry_ref ref;
296 	status_t status = get_ref_for_path(fArguments.StringAt(0).String(), &ref);
297 	if (status != B_OK)
298 		return status;
299 
300 	size_t count = fArguments.CountStrings();
301 	const char* args[count + 1];
302 	for (int32 i = 0; i < fArguments.CountStrings(); i++) {
303 		args[i] = fArguments.StringAt(i);
304 	}
305 	args[count] = NULL;
306 
307 	thread_id thread = load_image(count, args,
308 		const_cast<const char**>(environ));
309 	if (thread >= 0)
310 		resume_thread(thread);
311 
312 	thread_info info;
313 	if (get_thread_info(thread, &info) == B_OK)
314 		fTeam = info.team;
315 	return B_OK;
316 //	return be_roster->Launch(&ref, count, args, &fTeam);
317 }
318 
319 
320 bool
321 Job::IsLaunched() const
322 {
323 	return fTeam >= 0;
324 }
325 
326 
327 // #pragma mark -
328 
329 
330 LaunchDaemon::LaunchDaemon(status_t& error)
331 	:
332 	BServer(kLaunchDaemonSignature, NULL,
333 		create_port(B_LOOPER_PORT_DEFAULT_CAPACITY,
334 			B_LAUNCH_DAEMON_PORT_NAME), false, &error)
335 {
336 }
337 
338 
339 LaunchDaemon::~LaunchDaemon()
340 {
341 }
342 
343 
344 void
345 LaunchDaemon::ReadyToRun()
346 {
347 	_SetupEnvironment();
348 
349 	BStringList paths;
350 	BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY, kLaunchDirectory,
351 		B_FIND_PATHS_SYSTEM_ONLY, paths);
352 	_ReadPaths(paths);
353 
354 	BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY, kLaunchDirectory,
355 		B_FIND_PATHS_SYSTEM_ONLY, paths);
356 	_ReadPaths(paths);
357 
358 	_InitJobs();
359 	_LaunchJobs();
360 }
361 
362 
363 void
364 LaunchDaemon::MessageReceived(BMessage* message)
365 {
366 	switch (message->what) {
367 		case B_GET_LAUNCH_DATA:
368 		{
369 			BMessage reply;
370 			Job* job = _Job(get_leaf(message->GetString("name")));
371 			if (job == NULL) {
372 				reply.AddInt32("error", B_NAME_NOT_FOUND);
373 			} else {
374 				// If the job has not been launched yet, we'll pass on our
375 				// team here. The rationale behind this is that this team
376 				// will temporarily own the synchronous reply ports.
377 				reply.AddInt32("team", job->Team() < 0
378 					? current_team() : job->Team());
379 				if (job->CreatePort())
380 					reply.AddInt32("port", job->Port());
381 
382 				// Launch job now if it isn't running yet
383 				if (!job->IsLaunched())
384 					job->Launch();
385 			}
386 			message->SendReply(&reply);
387 			break;
388 		}
389 
390 		default:
391 			BServer::MessageReceived(message);
392 			break;
393 	}
394 }
395 
396 
397 void
398 LaunchDaemon::_ReadPaths(const BStringList& paths)
399 {
400 	for (int32 i = 0; i < paths.CountStrings(); i++) {
401 		BEntry entry(paths.StringAt(i));
402 		if (entry.InitCheck() != B_OK || !entry.Exists())
403 			continue;
404 
405 		_ReadDirectory(NULL, entry);
406 	}
407 }
408 
409 
410 void
411 LaunchDaemon::_ReadEntry(const char* context, BEntry& entry)
412 {
413 	if (entry.IsDirectory())
414 		_ReadDirectory(context, entry);
415 	else
416 		_ReadFile(context, entry);
417 }
418 
419 
420 void
421 LaunchDaemon::_ReadDirectory(const char* context, BEntry& directoryEntry)
422 {
423 	BDirectory directory(&directoryEntry);
424 
425 	BEntry entry;
426 	while (directory.GetNextEntry(&entry) == B_OK) {
427 		_ReadEntry(context, entry);
428 	}
429 }
430 
431 
432 status_t
433 LaunchDaemon::_ReadFile(const char* context, BEntry& entry)
434 {
435 	DriverSettingsMessageAdapter adapter;
436 
437 	BPath path;
438 	status_t status = path.SetTo(&entry);
439 	if (status != B_OK)
440 		return status;
441 
442 	BMessage message;
443 	status = adapter.ConvertFromDriverSettings(path.Path(), kSettingsTemplate,
444 			message);
445 	if (status == B_OK) {
446 		message.PrintToStream();
447 		BMessage job;
448 		for (int32 index = 0; message.FindMessage("service", index,
449 				&job) == B_OK; index++) {
450 			_AddJob(false, job);
451 		}
452 
453 		for (int32 index = 0; message.FindMessage("job", index, &job) == B_OK;
454 				index++) {
455 			_AddJob(false, job);
456 		}
457 	}
458 
459 	return status;
460 }
461 
462 
463 void
464 LaunchDaemon::_AddJob(bool service, BMessage& message)
465 {
466 	const char* name = message.GetString("name");
467 	if (name == NULL || name[0] == '\0') {
468 		// Invalid job description
469 		return;
470 	}
471 
472 	Job* job = _Job(name);
473 	if (job == NULL)
474 		job = new Job(name);
475 
476 	job->SetEnabled(!message.GetBool("disabled", !job->IsEnabled()));
477 	job->SetService(service);
478 	job->SetCreatePort(message.GetBool("create_port", job->CreatePort()));
479 	job->SetLaunchInSafeMode(
480 		!message.GetBool("no_safemode", !job->LaunchInSafeMode()));
481 
482 	const char* argument;
483 	for (int32 index = 0;
484 			message.FindString("launch", index, &argument) == B_OK; index++) {
485 		job->AddArgument(argument);
486 	}
487 
488 	fJobs.insert(std::pair<BString, Job*>(job->Name(), job));
489 }
490 
491 
492 Job*
493 LaunchDaemon::_Job(const char* name)
494 {
495 	if (name == NULL)
496 		return NULL;
497 
498 	JobMap::const_iterator found = fJobs.find(BString(name).ToLower());
499 	if (found != fJobs.end())
500 		return found->second;
501 
502 	return NULL;
503 }
504 
505 
506 void
507 LaunchDaemon::_InitJobs()
508 {
509 	for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();
510 			iterator++) {
511 		Job* job = iterator->second;
512 		if (job->IsEnabled() && (!_IsSafeMode() || job->LaunchInSafeMode()))
513 			job->Init();
514 	}
515 }
516 
517 
518 void
519 LaunchDaemon::_LaunchJobs()
520 {
521 	for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();
522 			iterator++) {
523 		Job* job = iterator->second;
524 		if (job->IsEnabled() && job->InitCheck() == B_OK)
525 			job->Launch();
526 	}
527 }
528 
529 
530 void
531 LaunchDaemon::_SetupEnvironment()
532 {
533 	// Determine safemode kernel option
534 	BString safemode = "SAFEMODE=";
535 	safemode << _IsSafeMode() ? "yes" : "no";
536 }
537 
538 
539 bool
540 LaunchDaemon::_IsSafeMode() const
541 {
542 	char buffer[32];
543 	size_t size = sizeof(buffer);
544 	status_t status = _kern_get_safemode_option(B_SAFEMODE_SAFE_MODE, buffer,
545 		&size);
546 	if (status == B_OK) {
547 		return !strncasecmp(buffer, "true", size)
548 			|| !strncasecmp(buffer, "yes", size)
549 			|| !strncasecmp(buffer, "on", size)
550 			|| !strncasecmp(buffer, "enabled", size);
551 	}
552 
553 	return false;
554 }
555 
556 
557 // #pragma mark -
558 
559 
560 int
561 main()
562 {
563 	// TODO: remove this again
564 	close(STDOUT_FILENO);
565 	int fd = open("/dev/dprintf", O_WRONLY);
566 	if (fd != STDOUT_FILENO)
567 		dup2(fd, STDOUT_FILENO);
568 	puts("launch_daemon is alive and kicking.");
569 
570 	status_t status;
571 	LaunchDaemon* daemon = new LaunchDaemon(status);
572 	if (status == B_OK)
573 		daemon->Run();
574 
575 	delete daemon;
576 	return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE;
577 }
578