1/*
2 * Copyright 2015-2018, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "Job.h"
8
9#include <stdlib.h>
10
11#include <Entry.h>
12#include <Looper.h>
13#include <Message.h>
14#include <Roster.h>
15
16#include <MessagePrivate.h>
17#include <RosterPrivate.h>
18#include <user_group.h>
19
20#include "Target.h"
21#include "Utility.h"
22
23
24Job::Job(const char* name)
25	:
26	BaseJob(name),
27	fEnabled(true),
28	fService(false),
29	fCreateDefaultPort(false),
30	fLaunching(false),
31	fInitStatus(B_NO_INIT),
32	fTeam(-1),
33	fDefaultPort(-1),
34	fToken((uint32)B_PREFERRED_TOKEN),
35	fLaunchStatus(B_NO_INIT),
36	fTarget(NULL),
37	fPendingLaunchDataReplies(0, false),
38	fTeamListener(NULL)
39{
40	mutex_init(&fLaunchStatusLock, "launch status lock");
41}
42
43
44Job::Job(const Job& other)
45	:
46	BaseJob(other.Name()),
47	fEnabled(other.IsEnabled()),
48	fService(other.IsService()),
49	fCreateDefaultPort(other.CreateDefaultPort()),
50	fLaunching(other.IsLaunching()),
51	fInitStatus(B_NO_INIT),
52	fTeam(-1),
53	fDefaultPort(-1),
54	fToken((uint32)B_PREFERRED_TOKEN),
55	fLaunchStatus(B_NO_INIT),
56	fTarget(other.Target()),
57	fPendingLaunchDataReplies(0, false)
58{
59	mutex_init(&fLaunchStatusLock, "launch status lock");
60
61	fCondition = other.fCondition;
62	// TODO: copy events
63	//fEvent = other.fEvent;
64	fEnvironment = other.fEnvironment;
65	fSourceFiles = other.fSourceFiles;
66
67	for (int32 i = 0; i < other.Arguments().CountStrings(); i++)
68		AddArgument(other.Arguments().StringAt(i));
69
70	for (int32 i = 0; i < other.Requirements().CountStrings(); i++)
71		AddRequirement(other.Requirements().StringAt(i));
72
73	PortMap::const_iterator constIterator = other.Ports().begin();
74	for (; constIterator != other.Ports().end(); constIterator++) {
75		fPortMap.insert(
76			std::make_pair(constIterator->first, constIterator->second));
77	}
78
79	PortMap::iterator iterator = fPortMap.begin();
80	for (; iterator != fPortMap.end(); iterator++)
81		iterator->second.RemoveData("port");
82}
83
84
85Job::~Job()
86{
87	_DeletePorts();
88}
89
90
91::TeamListener*
92Job::TeamListener() const
93{
94	return fTeamListener;
95}
96
97
98void
99Job::SetTeamListener(::TeamListener* listener)
100{
101	fTeamListener = listener;
102}
103
104
105bool
106Job::IsEnabled() const
107{
108	return fEnabled;
109}
110
111
112void
113Job::SetEnabled(bool enable)
114{
115	fEnabled = enable;
116}
117
118
119bool
120Job::IsService() const
121{
122	return fService;
123}
124
125
126void
127Job::SetService(bool service)
128{
129	fService = service;
130}
131
132
133bool
134Job::CreateDefaultPort() const
135{
136	return fCreateDefaultPort;
137}
138
139
140void
141Job::SetCreateDefaultPort(bool createPort)
142{
143	fCreateDefaultPort = createPort;
144}
145
146
147void
148Job::AddPort(BMessage& data)
149{
150	const char* name = data.GetString("name");
151	fPortMap.insert(std::pair<BString, BMessage>(BString(name), data));
152}
153
154
155const BStringList&
156Job::Arguments() const
157{
158	return fArguments;
159}
160
161
162BStringList&
163Job::Arguments()
164{
165	return fArguments;
166}
167
168
169void
170Job::AddArgument(const char* argument)
171{
172	fArguments.Add(argument);
173}
174
175
176::Target*
177Job::Target() const
178{
179	return fTarget;
180}
181
182
183void
184Job::SetTarget(::Target* target)
185{
186	fTarget = target;
187}
188
189
190const BStringList&
191Job::Requirements() const
192{
193	return fRequirements;
194}
195
196
197BStringList&
198Job::Requirements()
199{
200	return fRequirements;
201}
202
203
204void
205Job::AddRequirement(const char* requirement)
206{
207	fRequirements.Add(requirement);
208}
209
210
211const BStringList&
212Job::Pending() const
213{
214	return fPendingJobs;
215}
216
217
218BStringList&
219Job::Pending()
220{
221	return fPendingJobs;
222}
223
224
225void
226Job::AddPending(const char* pending)
227{
228	fPendingJobs.Add(pending);
229}
230
231
232bool
233Job::CheckCondition(ConditionContext& context) const
234{
235	if (Target() != NULL && !Target()->HasLaunched())
236		return false;
237
238	return BaseJob::CheckCondition(context);
239}
240
241
242status_t
243Job::Init(const Finder& finder, std::set<BString>& dependencies)
244{
245	// Only initialize the jobs once
246	if (fInitStatus != B_NO_INIT)
247		return fInitStatus;
248
249	fInitStatus = B_OK;
250
251	if (fTarget != NULL)
252		fTarget->AddDependency(this);
253
254	// Check dependencies
255
256	for (int32 index = 0; index < Requirements().CountStrings(); index++) {
257		const BString& requirement = Requirements().StringAt(index);
258		if (dependencies.find(requirement) != dependencies.end()) {
259			// Found a cyclic dependency
260			// TODO: log error
261			return fInitStatus = B_ERROR;
262		}
263		dependencies.insert(requirement);
264
265		Job* dependency = finder.FindJob(requirement);
266		if (dependency != NULL) {
267			std::set<BString> subDependencies = dependencies;
268
269			fInitStatus = dependency->Init(finder, subDependencies);
270			if (fInitStatus != B_OK) {
271				// TODO: log error
272				return fInitStatus;
273			}
274
275			fInitStatus = _AddRequirement(dependency);
276		} else {
277			::Target* target = finder.FindTarget(requirement);
278			if (target != NULL)
279				fInitStatus = _AddRequirement(dependency);
280			else {
281				// Could not find dependency
282				fInitStatus = B_NAME_NOT_FOUND;
283			}
284		}
285		if (fInitStatus != B_OK) {
286			// TODO: log error
287			return fInitStatus;
288		}
289	}
290
291	return fInitStatus;
292}
293
294
295status_t
296Job::InitCheck() const
297{
298	return fInitStatus;
299}
300
301
302team_id
303Job::Team() const
304{
305	return fTeam;
306}
307
308
309const PortMap&
310Job::Ports() const
311{
312	return fPortMap;
313}
314
315
316port_id
317Job::Port(const char* name) const
318{
319	PortMap::const_iterator found = fPortMap.find(name);
320	if (found != fPortMap.end())
321		return found->second.GetInt32("port", -1);
322
323	return B_NAME_NOT_FOUND;
324}
325
326
327port_id
328Job::DefaultPort() const
329{
330	return fDefaultPort;
331}
332
333
334void
335Job::SetDefaultPort(port_id port)
336{
337	fDefaultPort = port;
338
339	PortMap::iterator iterator = fPortMap.begin();
340	for (; iterator != fPortMap.end(); iterator++) {
341		BString name;
342		if (iterator->second.HasString("name"))
343			continue;
344
345		iterator->second.SetInt32("port", (int32)port);
346		break;
347	}
348}
349
350
351status_t
352Job::Launch()
353{
354	// Build environment
355
356	std::vector<const char*> environment;
357	for (const char** variable = (const char**)environ; variable[0] != NULL;
358			variable++) {
359		environment.push_back(variable[0]);
360	}
361
362	if (Target() != NULL)
363		_AddStringList(environment, Target()->Environment());
364	_AddStringList(environment, Environment());
365
366	// Resolve source files
367	BStringList sourceFilesEnvironment;
368	GetSourceFilesEnvironment(sourceFilesEnvironment);
369	_AddStringList(environment, sourceFilesEnvironment);
370
371	environment.push_back(NULL);
372
373	if (fArguments.IsEmpty()) {
374		// Launch by signature
375		BString signature("application/");
376		signature << Name();
377
378		return _Launch(signature.String(), NULL, 0, NULL, &environment[0]);
379	}
380
381	// Build argument vector
382
383	entry_ref ref;
384	status_t status = get_ref_for_path(
385		Utility::TranslatePath(fArguments.StringAt(0).String()), &ref);
386	if (status != B_OK) {
387		_SetLaunchStatus(status);
388		return status;
389	}
390
391	std::vector<BString> strings;
392	std::vector<const char*> args;
393
394	size_t count = fArguments.CountStrings() - 1;
395	if (count > 0) {
396		for (int32 i = 1; i < fArguments.CountStrings(); i++) {
397			strings.push_back(Utility::TranslatePath(fArguments.StringAt(i)));
398			args.push_back(strings.back());
399		}
400		args.push_back(NULL);
401	}
402
403	// Launch via entry_ref
404	return _Launch(NULL, &ref, count, &args[0], &environment[0]);
405}
406
407
408bool
409Job::IsLaunched() const
410{
411	return fLaunchStatus != B_NO_INIT;
412}
413
414
415bool
416Job::IsRunning() const
417{
418	return fTeam >= 0;
419}
420
421
422void
423Job::TeamDeleted()
424{
425	fTeam = -1;
426	fDefaultPort = -1;
427
428	if (IsService())
429		SetState(B_JOB_STATE_WAITING_TO_RUN);
430
431	MutexLocker locker(fLaunchStatusLock);
432	fLaunchStatus = B_NO_INIT;
433}
434
435
436bool
437Job::CanBeLaunched() const
438{
439	// Services cannot be launched while they are running
440	return IsEnabled() && !IsLaunching() && (!IsService() || !IsRunning());
441}
442
443
444bool
445Job::IsLaunching() const
446{
447	return fLaunching;
448}
449
450
451void
452Job::SetLaunching(bool launching)
453{
454	fLaunching = launching;
455}
456
457
458status_t
459Job::HandleGetLaunchData(BMessage* message)
460{
461	MutexLocker launchLocker(fLaunchStatusLock);
462	if (IsLaunched())
463		return _SendLaunchDataReply(message);
464
465	if (!IsEnabled())
466		return B_NOT_ALLOWED;
467
468	return fPendingLaunchDataReplies.AddItem(message) ? B_OK : B_NO_MEMORY;
469}
470
471
472status_t
473Job::GetMessenger(BMessenger& messenger)
474{
475	if (fDefaultPort < 0)
476		return B_NAME_NOT_FOUND;
477
478	BMessenger::Private(messenger).SetTo(fTeam, fDefaultPort, fToken);
479	return B_OK;
480}
481
482
483status_t
484Job::Run()
485{
486	status_t status = BJob::Run();
487
488	// Jobs can be relaunched at any time
489	if (!IsService())
490		SetState(B_JOB_STATE_WAITING_TO_RUN);
491
492	return status;
493}
494
495
496status_t
497Job::Execute()
498{
499	status_t status = B_OK;
500	if (!IsRunning() || !IsService())
501		status = Launch();
502	else
503		debug_printf("Ignore launching %s\n", Name());
504
505	fLaunching = false;
506	return status;
507}
508
509
510void
511Job::_DeletePorts()
512{
513	PortMap::const_iterator iterator = Ports().begin();
514	for (; iterator != Ports().end(); iterator++) {
515		port_id port = iterator->second.GetInt32("port", -1);
516		if (port >= 0)
517			delete_port(port);
518	}
519}
520
521
522status_t
523Job::_AddRequirement(BJob* dependency)
524{
525	if (dependency == NULL)
526		return B_OK;
527
528	switch (dependency->State()) {
529		case B_JOB_STATE_WAITING_TO_RUN:
530		case B_JOB_STATE_STARTED:
531		case B_JOB_STATE_IN_PROGRESS:
532			AddDependency(dependency);
533			break;
534
535		case B_JOB_STATE_SUCCEEDED:
536			// Just queue it without any dependencies
537			break;
538
539		case B_JOB_STATE_FAILED:
540		case B_JOB_STATE_ABORTED:
541			// TODO: return appropriate error
542			return B_BAD_VALUE;
543	}
544
545	return B_OK;
546}
547
548
549void
550Job::_AddStringList(std::vector<const char*>& array, const BStringList& list)
551{
552	int32 count = list.CountStrings();
553	for (int32 index = 0; index < count; index++) {
554		array.push_back(list.StringAt(index).String());
555	}
556}
557
558
559void
560Job::_SetLaunchStatus(status_t launchStatus)
561{
562	MutexLocker launchLocker(fLaunchStatusLock);
563	fLaunchStatus = launchStatus != B_NO_INIT ? launchStatus : B_ERROR;
564	launchLocker.Unlock();
565
566	_SendPendingLaunchDataReplies();
567}
568
569
570status_t
571Job::_SendLaunchDataReply(BMessage* message)
572{
573	BMessage reply(fTeam < 0 ? fTeam : (uint32)B_OK);
574	if (reply.what == B_OK) {
575		reply.AddInt32("team", fTeam);
576
577		PortMap::const_iterator iterator = fPortMap.begin();
578		for (; iterator != fPortMap.end(); iterator++) {
579			BString name;
580			if (iterator->second.HasString("name"))
581				name << iterator->second.GetString("name") << "_";
582			name << "port";
583
584			reply.AddInt32(name.String(),
585				iterator->second.GetInt32("port", -1));
586		}
587	}
588
589	message->SendReply(&reply);
590	delete message;
591	return B_OK;
592}
593
594
595void
596Job::_SendPendingLaunchDataReplies()
597{
598	for (int32 i = 0; i < fPendingLaunchDataReplies.CountItems(); i++)
599		_SendLaunchDataReply(fPendingLaunchDataReplies.ItemAt(i));
600
601	fPendingLaunchDataReplies.MakeEmpty();
602}
603
604
605/*!	Creates the ports for a newly launched job. If the registrar already
606	pre-registered the application, \c fDefaultPort will already be set, and
607	honored when filling the ports message.
608*/
609status_t
610Job::_CreateAndTransferPorts()
611{
612	// TODO: prefix system ports with "system:"
613
614	bool defaultPort = false;
615
616	for (PortMap::iterator iterator = fPortMap.begin();
617			iterator != fPortMap.end(); iterator++) {
618		BString name(Name());
619		const char* suffix = iterator->second.GetString("name");
620		if (suffix != NULL)
621			name << ':' << suffix;
622		else
623			defaultPort = true;
624
625		const int32 capacity = iterator->second.GetInt32("capacity",
626			B_LOOPER_PORT_DEFAULT_CAPACITY);
627
628		port_id port = -1;
629		if (suffix != NULL || fDefaultPort < 0) {
630			port = _CreateAndTransferPort(name.String(), capacity);
631			if (port < 0)
632				return port;
633
634			if (suffix == NULL)
635				fDefaultPort = port;
636		} else if (suffix == NULL)
637			port = fDefaultPort;
638
639		iterator->second.SetInt32("port", port);
640
641		if (name == "x-vnd.haiku-registrar:auth") {
642			// Allow the launch_daemon to access the registrar authentication
643			BPrivate::set_registrar_authentication_port(port);
644		}
645	}
646
647	if (fCreateDefaultPort && !defaultPort) {
648		BMessage data;
649		data.AddInt32("capacity", B_LOOPER_PORT_DEFAULT_CAPACITY);
650
651		port_id port = -1;
652		if (fDefaultPort < 0) {
653			port = _CreateAndTransferPort(Name(),
654				B_LOOPER_PORT_DEFAULT_CAPACITY);
655			if (port < 0)
656				return port;
657
658			fDefaultPort = port;
659		} else
660			port = fDefaultPort;
661
662		data.SetInt32("port", port);
663		AddPort(data);
664	}
665
666	return B_OK;
667}
668
669
670port_id
671Job::_CreateAndTransferPort(const char* name, int32 capacity)
672{
673	port_id port = create_port(B_LOOPER_PORT_DEFAULT_CAPACITY, Name());
674	if (port < 0)
675		return port;
676
677	status_t status = set_port_owner(port, fTeam);
678	if (status != B_OK) {
679		delete_port(port);
680		return status;
681	}
682
683	return port;
684}
685
686
687status_t
688Job::_Launch(const char* signature, entry_ref* ref, int argCount,
689	const char* const* args, const char** environment)
690{
691	thread_id mainThread = -1;
692	status_t result = BRoster::Private().Launch(signature, ref, NULL, argCount,
693		args, environment, &fTeam, &mainThread, &fDefaultPort, NULL, true);
694	if (result == B_OK) {
695		result = _CreateAndTransferPorts();
696
697		if (result == B_OK) {
698			resume_thread(mainThread);
699
700			if (fTeamListener != NULL)
701				fTeamListener->TeamLaunched(this, result);
702		} else
703			kill_thread(mainThread);
704	}
705
706	_SetLaunchStatus(result);
707	return result;
708}
709