xref: /haiku/src/servers/package/Root.cpp (revision 220d04022750f40f8bac8f01fa551211e28d04f2)
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 
31 using namespace BPackageKit::BPrivate;
32 using namespace BPackageKit::BManager::BPrivate;
33 
34 
35 // #pragma mark - AbstractVolumeJob
36 
37 
38 struct 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 
50 protected:
51 	Volume*	fVolume;
52 };
53 
54 
55 // #pragma mark - VolumeJob
56 
57 
58 struct 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 
71 private:
72 	void	(Root::*fMethod)(Volume*);
73 };
74 
75 
76 // #pragma mark - ProcessNodeMonitorEventsJob
77 
78 
79 struct 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 
97 struct 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 
117 private:
118 	Root*					fRoot;
119 	ObjectDeleter<BMessage>	fMessage;
120 };
121 
122 
123 // #pragma mark - VolumeJobFilter
124 
125 
126 struct 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 
139 private:
140 	Volume*	fVolume;
141 };
142 
143 
144 // #pragma mark - Root
145 
146 
147 Root::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 
161 Root::~Root()
162 {
163 	fJobQueue.Close();
164 
165 	if (fJobRunner >= 0)
166 		wait_for_thread(fJobRunner, NULL);
167 }
168 
169 
170 status_t
171 Root::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 
221 status_t
222 Root::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 
253 void
254 Root::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 
273 Volume*
274 Root::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 
289 Volume*
290 Root::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 
303 void
304 Root::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 
360 void
361 Root::VolumeNodeMonitorEventOccurred(Volume* volume)
362 {
363 	_QueueJob(new(std::nothrow) ProcessNodeMonitorEventsJob(volume,
364 		&Root::_ProcessNodeMonitorEvents));
365 }
366 
367 
368 void
369 Root::LastReferenceReleased()
370 {
371 }
372 
373 
374 Volume**
375 Root::_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 
389 Volume*
390 Root::_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 
415 void
416 Root::_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 
429 void
430 Root::_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 
440 void
441 Root::_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 
481 void
482 Root::_CommitTransaction(Volume* volume, BMessage* message)
483 {
484 	volume->HandleCommitTransactionRequest(message);
485 }
486 
487 
488 status_t
489 Root::_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
505 Root::_JobRunnerEntry(void* data)
506 {
507 	return ((Root*)data)->_JobRunner();
508 }
509 
510 
511 status_t
512 Root::_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
524 Root::_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