1/*
2 * Copyright 2013-2014, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Ingo Weinhold <ingo_weinhold@gmx.de>
7 */
8
9
10#include "Root.h"
11
12#include <Alert.h>
13#include <Directory.h>
14#include <Entry.h>
15#include <package/CommitTransactionResult.h>
16#include <package/PackageDefs.h>
17#include <Path.h>
18
19#include <AutoDeleter.h>
20#include <AutoLocker.h>
21#include <Server.h>
22
23#include <package/DaemonDefs.h>
24#include <package/manager/Exceptions.h>
25
26#include "Constants.h"
27#include "DebugSupport.h"
28#include "PackageManager.h"
29
30
31using namespace BPackageKit::BPrivate;
32using namespace BPackageKit::BManager::BPrivate;
33
34
35// #pragma mark - AbstractVolumeJob
36
37
38struct Root::AbstractVolumeJob : public Job {
39	AbstractVolumeJob(Volume* volume)
40		:
41		fVolume(volume)
42	{
43	}
44
45	Volume* GetVolume() const
46	{
47		return fVolume;
48	}
49
50protected:
51	Volume*	fVolume;
52};
53
54
55// #pragma mark - VolumeJob
56
57
58struct Root::VolumeJob : public AbstractVolumeJob {
59	VolumeJob(Volume* volume, void (Root::*method)(Volume*))
60		:
61		AbstractVolumeJob(volume),
62		fMethod(method)
63	{
64	}
65
66	virtual void Do()
67	{
68		(fVolume->GetRoot()->*fMethod)(fVolume);
69	}
70
71private:
72	void	(Root::*fMethod)(Volume*);
73};
74
75
76// #pragma mark - ProcessNodeMonitorEventsJob
77
78
79struct Root::ProcessNodeMonitorEventsJob : public VolumeJob {
80	ProcessNodeMonitorEventsJob(Volume* volume, void (Root::*method)(Volume*))
81		:
82		VolumeJob(volume, method)
83	{
84		fVolume->PackageJobPending();
85	}
86
87	~ProcessNodeMonitorEventsJob()
88	{
89		fVolume->PackageJobFinished();
90	}
91};
92
93
94// #pragma mark - CommitTransactionJob
95
96
97struct Root::CommitTransactionJob : public AbstractVolumeJob {
98	CommitTransactionJob(Root* root, Volume* volume, BMessage* message)
99		:
100		AbstractVolumeJob(volume),
101		fRoot(root),
102		fMessage(message)
103	{
104		fVolume->PackageJobPending();
105	}
106
107	~CommitTransactionJob()
108	{
109		fVolume->PackageJobFinished();
110	}
111
112	virtual void Do()
113	{
114		fRoot->_CommitTransaction(fVolume, fMessage.Get());
115	}
116
117private:
118	Root*					fRoot;
119	ObjectDeleter<BMessage>	fMessage;
120};
121
122
123// #pragma mark - VolumeJobFilter
124
125
126struct Root::VolumeJobFilter : public ::JobQueue::Filter {
127	VolumeJobFilter(Volume* volume)
128		:
129		fVolume(volume)
130	{
131	}
132
133	virtual bool FilterJob(Job* job)
134	{
135		AbstractVolumeJob* volumeJob = dynamic_cast<AbstractVolumeJob*>(job);
136		return volumeJob != NULL && volumeJob->GetVolume() == fVolume;
137	}
138
139private:
140	Volume*	fVolume;
141};
142
143
144// #pragma mark - Root
145
146
147Root::Root()
148	:
149	fLock("packagefs root"),
150	fNodeRef(),
151	fIsSystemRoot(false),
152	fPath(),
153	fSystemVolume(NULL),
154	fHomeVolume(NULL),
155	fJobQueue(),
156	fJobRunner(-1)
157{
158}
159
160
161Root::~Root()
162{
163	fJobQueue.Close();
164
165	if (fJobRunner >= 0)
166		wait_for_thread(fJobRunner, NULL);
167}
168
169
170status_t
171Root::Init(const node_ref& nodeRef, bool isSystemRoot)
172{
173	fNodeRef = nodeRef;
174	fIsSystemRoot = isSystemRoot;
175
176	// init members and spawn job runner thread
177	status_t error = fJobQueue.Init();
178	if (error != B_OK)
179		RETURN_ERROR(error);
180
181	error = fLock.InitCheck();
182	if (error != B_OK)
183		RETURN_ERROR(error);
184
185	fJobRunner = spawn_thread(&_JobRunnerEntry, "job runner", B_NORMAL_PRIORITY,
186		this);
187	if (fJobRunner < 0)
188		RETURN_ERROR(fJobRunner);
189
190	// get the path
191	BDirectory directory;
192	error = directory.SetTo(&fNodeRef);
193	if (error != B_OK) {
194		ERROR("Root::Init(): failed to open directory: %s\n", strerror(error));
195		RETURN_ERROR(error);
196	}
197
198	BEntry entry;
199	error = directory.GetEntry(&entry);
200
201	BPath path;
202	if (error == B_OK)
203		error = entry.GetPath(&path);
204
205	if (error != B_OK) {
206		ERROR("Root::Init(): failed to get directory path: %s\n",
207			strerror(error));
208		RETURN_ERROR(error);
209	}
210
211	fPath = path.Path();
212	if (fPath.IsEmpty())
213		RETURN_ERROR(B_NO_MEMORY);
214
215	resume_thread(fJobRunner);
216
217	return B_OK;
218}
219
220
221status_t
222Root::RegisterVolume(Volume* volume)
223{
224	AutoLocker<BLocker> locker(fLock);
225
226	Volume** volumeToSet = _GetVolume(volume->MountType());
227	if (volumeToSet == NULL)
228		return B_BAD_VALUE;
229
230	if (*volumeToSet != NULL) {
231		ERROR("Root::RegisterVolume(): can't register volume at \"%s\", since "
232			"there's already volume at \"%s\" with the same type.\n",
233			volume->Path().String(), (*volumeToSet)->Path().String());
234		return B_BAD_VALUE;
235	}
236
237	*volumeToSet = volume;
238	volume->SetRoot(this);
239
240	// queue a job for reading the volume's packages
241	status_t error = _QueueJob(
242		new(std::nothrow) VolumeJob(volume, &Root::_InitPackages));
243	if (error != B_OK) {
244		volume->SetRoot(NULL);
245		*volumeToSet = NULL;
246		return error;
247	}
248
249	return B_OK;
250}
251
252
253void
254Root::UnregisterVolume(Volume* volume)
255{
256	AutoLocker<BLocker> locker(fLock);
257
258	Volume** volumeToSet = _GetVolume(volume->MountType());
259	if (volumeToSet == NULL || *volumeToSet != volume) {
260		ERROR("Root::UnregisterVolume(): can't unregister unknown volume at "
261			"\"%s.\n", volume->Path().String());
262		return;
263	}
264
265	*volumeToSet = NULL;
266
267	// Use the job queue to delete the volume to make sure there aren't any
268	// pending jobs that reference the volume.
269	_QueueJob(new(std::nothrow) VolumeJob(volume, &Root::_DeleteVolume));
270}
271
272
273Volume*
274Root::FindVolume(dev_t deviceID) const
275{
276	AutoLocker<BLocker> locker(fLock);
277
278	Volume* volumes[] = { fSystemVolume, fHomeVolume };
279	for (size_t i = 0; i < sizeof(volumes) / sizeof(volumes[0]); i++) {
280		Volume* volume = volumes[i];
281		if (volume != NULL && volume->DeviceID() == deviceID)
282			return volume;
283	}
284
285	return NULL;
286}
287
288
289Volume*
290Root::GetVolume(BPackageInstallationLocation location)
291{
292	switch ((BPackageInstallationLocation)location) {
293		case B_PACKAGE_INSTALLATION_LOCATION_SYSTEM:
294			return fSystemVolume;
295		case B_PACKAGE_INSTALLATION_LOCATION_HOME:
296			return fHomeVolume;
297		default:
298			return NULL;
299	}
300}
301
302
303void
304Root::HandleRequest(BMessage* message)
305{
306	ObjectDeleter<BMessage> messageDeleter(message);
307
308	// get the location and the volume
309	int32 location;
310	if (message->FindInt32("location", &location) != B_OK
311		|| location < 0
312		|| location >= B_PACKAGE_INSTALLATION_LOCATION_ENUM_COUNT) {
313		return;
314	}
315
316	AutoLocker<BLocker> locker(fLock);
317
318	Volume* volume = GetVolume((BPackageInstallationLocation)location);
319	if (volume == NULL)
320		return;
321
322	switch (message->what) {
323		case B_MESSAGE_GET_INSTALLATION_LOCATION_INFO:
324			volume->HandleGetLocationInfoRequest(message);
325			break;
326
327		case B_MESSAGE_COMMIT_TRANSACTION:
328		{
329			// The B_MESSAGE_COMMIT_TRANSACTION request must be handled in the
330			// job thread. But only queue a job, if there aren't package jobs
331			// pending already.
332			if (volume->IsPackageJobPending()) {
333				BMessage reply(B_MESSAGE_COMMIT_TRANSACTION_REPLY);
334				BCommitTransactionResult result(
335					B_TRANSACTION_INSTALLATION_LOCATION_BUSY);
336				if (result.AddToMessage(reply) == B_OK) {
337					message->SendReply(&reply, (BHandler*)NULL,
338						kCommunicationTimeout);
339				}
340				return;
341			}
342
343			CommitTransactionJob* job = new(std::nothrow) CommitTransactionJob(
344				this, volume, message);
345			if (job == NULL)
346				return;
347
348			messageDeleter.Detach();
349
350			_QueueJob(job);
351			break;
352		}
353
354		default:
355			break;
356	}
357}
358
359
360void
361Root::VolumeNodeMonitorEventOccurred(Volume* volume)
362{
363	_QueueJob(new(std::nothrow) ProcessNodeMonitorEventsJob(volume,
364		&Root::_ProcessNodeMonitorEvents));
365}
366
367
368void
369Root::LastReferenceReleased()
370{
371}
372
373
374Volume**
375Root::_GetVolume(PackageFSMountType mountType)
376{
377	switch (mountType) {
378		case PACKAGE_FS_MOUNT_TYPE_SYSTEM:
379			return &fSystemVolume;
380		case PACKAGE_FS_MOUNT_TYPE_HOME:
381			return &fHomeVolume;
382		case PACKAGE_FS_MOUNT_TYPE_CUSTOM:
383		default:
384			return NULL;
385	}
386}
387
388
389Volume*
390Root::_NextVolumeFor(Volume* volume)
391{
392	if (volume == NULL)
393		return NULL;
394
395	PackageFSMountType mountType = volume->MountType();
396
397	do {
398		switch (mountType) {
399			case PACKAGE_FS_MOUNT_TYPE_HOME:
400				mountType = PACKAGE_FS_MOUNT_TYPE_SYSTEM;
401				break;
402			case PACKAGE_FS_MOUNT_TYPE_SYSTEM:
403			case PACKAGE_FS_MOUNT_TYPE_CUSTOM:
404			default:
405				return NULL;
406		}
407
408		volume = *_GetVolume(mountType);
409	} while (volume == NULL);
410
411	return volume;
412}
413
414
415void
416Root::_InitPackages(Volume* volume)
417{
418	if (volume->InitPackages(this) == B_OK) {
419		AutoLocker<BLocker> locker(fLock);
420		Volume* nextVolume = _NextVolumeFor(volume);
421		Volume* nextNextVolume = _NextVolumeFor(nextVolume);
422		locker.Unlock();
423
424		volume->InitialVerify(nextVolume, nextNextVolume);
425	}
426}
427
428
429void
430Root::_DeleteVolume(Volume* volume)
431{
432	// delete all pending jobs for that volume
433	VolumeJobFilter filter(volume);
434	fJobQueue.DeleteJobs(&filter);
435
436	delete volume;
437}
438
439
440void
441Root::_ProcessNodeMonitorEvents(Volume* volume)
442{
443	volume->ProcessPendingNodeMonitorEvents();
444
445	if (!volume->HasPendingPackageActivationChanges())
446		return;
447
448	// If this is not the system root, just activate/deactivate the packages.
449	if (!fIsSystemRoot) {
450		volume->ProcessPendingPackageActivationChanges();
451		return;
452	}
453
454	// For the system root do the full dependency analysis.
455
456	PRINT("Root::_ProcessNodeMonitorEvents(): running package manager...\n");
457	try {
458		PackageManager packageManager(this, volume);
459		packageManager.HandleUserChanges();
460	} catch (BNothingToDoException&) {
461		PRINT("Root::_ProcessNodeMonitorEvents(): -> nothing to do\n");
462	} catch (std::bad_alloc&) {
463		_ShowError(
464			"Insufficient memory while trying to apply package changes.");
465	} catch (BFatalErrorException& exception) {
466		if (exception.Error() == B_OK) {
467			_ShowError(exception.Message());
468		} else {
469			_ShowError(BString().SetToFormat("%s: %s",
470				exception.Message().String(), strerror(exception.Error())));
471		}
472		// TODO: Print exception.Details()?
473	} catch (BAbortedByUserException&) {
474		PRINT("Root::_ProcessNodeMonitorEvents(): -> aborted by user\n");
475	}
476
477	volume->ClearPackageActivationChanges();
478}
479
480
481void
482Root::_CommitTransaction(Volume* volume, BMessage* message)
483{
484	volume->HandleCommitTransactionRequest(message);
485}
486
487
488status_t
489Root::_QueueJob(Job* job)
490{
491	if (job == NULL)
492		return B_NO_MEMORY;
493
494	BReference<Job> jobReference(job, true);
495	if (!fJobQueue.QueueJob(job)) {
496		// job queue already closed
497		return B_BAD_VALUE;
498	}
499
500	return B_OK;
501}
502
503
504/*static*/ status_t
505Root::_JobRunnerEntry(void* data)
506{
507	return ((Root*)data)->_JobRunner();
508}
509
510
511status_t
512Root::_JobRunner()
513{
514	while (Job* job = fJobQueue.DequeueJob()) {
515		job->Do();
516		job->ReleaseReference();
517	}
518
519	return B_OK;
520}
521
522
523/*static*/ void
524Root::_ShowError(const char* errorMessage)
525{
526	BServer* server = dynamic_cast<BServer*>(be_app);
527	if (server != NULL && server->InitGUIContext() == B_OK) {
528		BAlert* alert = new(std::nothrow) BAlert("Package error",
529			errorMessage, "OK", NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
530		if (alert != NULL) {
531			alert->SetShortcut(0, B_ESCAPE);
532			alert->Go();
533			return;
534		}
535	}
536
537	ERROR("Root::_ShowError(): %s\n", errorMessage);
538}
539