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