xref: /haiku/src/kits/storage/AddOnMonitorHandler.cpp (revision e81a954787e50e56a7f06f72705b7859b6ab06d1)
1 /*
2  * Copyright 2004-2013 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus, superstippi@gmx.de
7  *		Andrew Bachmann
8  *		John Scipione, jscipione@gmail.com
9  */
10 
11 
12 #include "AddOnMonitorHandler.h"
13 
14 #include <string.h>
15 #include <strings.h>
16 
17 #include <Autolock.h>
18 #include <Directory.h>
19 #include <FindDirectory.h>
20 #include <Path.h>
21 
22 #include <driver_settings.h>
23 #include <safemode_defs.h>
24 #include <syscalls.h>
25 
26 
27 #ifndef ADD_ON_STABLE_SECONDS
28 #	define ADD_ON_STABLE_SECONDS 1
29 #endif
30 
31 
32 AddOnMonitorHandler::AddOnMonitorHandler(const char* name)
33 	:
34 	NodeMonitorHandler(name != NULL ? name : "AddOnMonitorHandler")
35 {
36 }
37 
38 
39 AddOnMonitorHandler::~AddOnMonitorHandler()
40 {
41 	// TODO: Actually calling watch_node() here should be too late, since we
42 	// are likely not attached to a looper anymore, and thus consitute no valid
43 	// BMessenger at this time.
44 	DirectoryList::iterator it = fDirectories.begin();
45 	for (; it != fDirectories.end(); it++) {
46 		EntryList::iterator eiter = it->entries.begin();
47 		for (; eiter != it->entries.end(); eiter++)
48 			watch_node(&eiter->addon_nref, B_STOP_WATCHING, this);
49 		watch_node(&it->nref, B_STOP_WATCHING, this);
50 	}
51 }
52 
53 
54 void
55 AddOnMonitorHandler::MessageReceived(BMessage* msg)
56 {
57 	if (msg->what == B_PULSE)
58 		_HandlePendingEntries();
59 
60 	inherited::MessageReceived(msg);
61 }
62 
63 
64 status_t
65 AddOnMonitorHandler::AddDirectory(const node_ref* nref, bool sync)
66 {
67 	// Keep the looper thread locked, since this method is likely to be called
68 	// in a thread other than the looper thread. Otherwise we may access the
69 	// lists concurrently with the looper thread, when node monitor
70 	// notifications arrive while we are still adding initial entries from the
71 	// directory, or (much more likely) if the looper thread handles a pulse
72 	// message and wants to process pending entries while we are still adding
73 	// them.
74 	BAutolock _(Looper());
75 
76 	// ignore directories added twice
77 	DirectoryList::iterator it = fDirectories.begin();
78 	for (; it != fDirectories.end(); it++) {
79 		if (it->nref == *nref)
80 			return B_OK;
81 	}
82 
83 	BDirectory directory(nref);
84 	status_t status = directory.InitCheck();
85 	if (status != B_OK)
86 		return status;
87 
88 	status = watch_node(nref, B_WATCH_DIRECTORY, this);
89 	if (status != B_OK)
90 		return status;
91 
92 	add_on_directory_info dirInfo;
93 	dirInfo.nref = *nref;
94 	fDirectories.push_back(dirInfo);
95 
96 	add_on_entry_info entryInfo;
97 	entryInfo.dir_nref = *nref;
98 
99 	BEntry entry;
100 	while (directory.GetNextEntry(&entry) == B_OK) {
101 		if (entry.GetName(entryInfo.name) != B_OK
102 			|| entry.GetNodeRef(&entryInfo.nref) != B_OK) {
103 			continue;
104 		}
105 
106 		fPendingEntries.push_back(entryInfo);
107 	}
108 
109 	if (sync)
110 		_HandlePendingEntries();
111 
112 	return B_OK;
113 }
114 
115 
116 status_t
117 AddOnMonitorHandler::AddAddOnDirectories(const char* leafPath)
118 {
119 	char parameter[32];
120 	size_t parameterLength = sizeof(parameter);
121 	uint32 start = 0;
122 
123 	const directory_which addOnDirectories[] = {
124 		B_USER_NONPACKAGED_ADDONS_DIRECTORY,
125 		B_USER_ADDONS_DIRECTORY,
126 		B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY,
127 		B_SYSTEM_ADDONS_DIRECTORY
128 	};
129 
130 	if (_kern_get_safemode_option(B_SAFEMODE_DISABLE_USER_ADD_ONS, parameter,
131 			&parameterLength) == B_OK) {
132 		if (!strcasecmp(parameter, "enabled") || !strcasecmp(parameter, "on")
133 			|| !strcasecmp(parameter, "true") || !strcasecmp(parameter, "yes")
134 			|| !strcasecmp(parameter, "enable") || !strcmp(parameter, "1")) {
135 			// skip user add on directories
136 			start = 2;
137 		}
138 	}
139 
140 	if (_kern_get_safemode_option(B_SAFEMODE_SAFE_MODE, parameter,
141 			&parameterLength) == B_OK) {
142 		if (!strcasecmp(parameter, "enabled") || !strcasecmp(parameter, "on")
143 			|| !strcasecmp(parameter, "true") || !strcasecmp(parameter, "yes")
144 			|| !strcasecmp(parameter, "enable") || !strcmp(parameter, "1")) {
145 			// safe mode, only B_SYSTEM_ADDONS_DIRECTORY is used
146 			start = 3;
147 		}
148 	}
149 
150 	for (uint32 i = start;
151 			i < sizeof(addOnDirectories) / sizeof(directory_which); i++) {
152 		BDirectory directory;
153 		node_ref nodeRef;
154 		BPath path;
155 		if (find_directory(addOnDirectories[i], &path) == B_OK
156 			&& path.Append(leafPath) == B_OK
157 			&& directory.SetTo(path.Path()) == B_OK
158 			&& directory.GetNodeRef(&nodeRef) == B_OK) {
159 			status_t result = this->AddDirectory(&nodeRef);
160 			if (result != B_OK)
161 				return result;
162 		}
163 	}
164 
165 	return B_OK;
166 }
167 
168 
169 //	#pragma mark - AddOnMonitorHandler hooks
170 
171 
172 void
173 AddOnMonitorHandler::AddOnCreated(const add_on_entry_info* entryInfo)
174 {
175 
176 }
177 
178 
179 void
180 AddOnMonitorHandler::AddOnEnabled(const add_on_entry_info* entryInfo)
181 {
182 }
183 
184 
185 void
186 AddOnMonitorHandler::AddOnDisabled(const add_on_entry_info* entryInfo)
187 {
188 }
189 
190 
191 void
192 AddOnMonitorHandler::AddOnRemoved(const add_on_entry_info* entryInfo)
193 {
194 }
195 
196 
197 //	#pragma mark - NodeMonitorHandler hooks
198 
199 
200 void
201 AddOnMonitorHandler::EntryCreated(const char* name, ino_t directory,
202 	dev_t device, ino_t node)
203 {
204 	add_on_entry_info entryInfo;
205 	strlcpy(entryInfo.name, name, sizeof(entryInfo.name));
206 	make_node_ref(device, node, &entryInfo.nref);
207 	make_node_ref(device, directory, &entryInfo.dir_nref);
208 	fPendingEntries.push_back(entryInfo);
209 }
210 
211 
212 void
213 AddOnMonitorHandler::EntryRemoved(const char* name, ino_t directory,
214 	dev_t device, ino_t node)
215 {
216 	node_ref entryNodeRef;
217 	make_node_ref(device, node, &entryNodeRef);
218 
219 	// Search pending entries first, which can simply be discarded
220 	// We might have this entry in the pending list multiple times,
221 	// so we search entire list through, even after finding one.
222 	EntryList::iterator eiter = fPendingEntries.begin();
223 	while (eiter != fPendingEntries.end()) {
224 		if (eiter->nref == entryNodeRef)
225 			eiter = fPendingEntries.erase(eiter);
226 		else
227 			eiter++;
228 	}
229 
230 	// Find the directory of the entry.
231 	DirectoryList::iterator diter = fDirectories.begin();
232 	if (!_FindDirectory(directory, device, diter)) {
233 		// If it was not found, we're done
234 		return;
235 	}
236 
237 	eiter = diter->entries.begin();
238 	if (!_FindEntry(entryNodeRef, diter->entries, eiter)) {
239 		// This must be the directory, but we didn't find the entry.
240 		return;
241 	}
242 
243 	add_on_entry_info info = *eiter;
244 	watch_node(&entryNodeRef, B_STOP_WATCHING, this);
245 	diter->entries.erase(eiter);
246 
247 	// Start at the top again, and search until the directory we found
248 	// the old add-on in. If we find an add-on with the same name then
249 	// the old add-on was not enabled. So we deallocate the old add-on and
250 	// return.
251 	DirectoryList::iterator diter2 = fDirectories.begin();
252 	for (; diter2 != diter; diter2++) {
253 		if (_HasEntry(info.name, diter2->entries)) {
254 			AddOnRemoved(&info);
255 			return;
256 		}
257 	}
258 
259 	// An active add-on was removed. We need to disable and then subsequently
260 	// deallocate it.
261 	AddOnDisabled(&info);
262 	AddOnRemoved(&info);
263 
264 	// Continue searching for an add-on below us. If we find an add-on
265 	// with the same name, we must enable it.
266 	for (diter++; diter != fDirectories.end(); diter++) {
267 		eiter = diter->entries.begin();
268 		if (_FindEntry(info.name, diter->entries, eiter)) {
269 			AddOnEnabled(&*eiter);
270 			break;
271 		}
272 	}
273 }
274 
275 
276 void
277 AddOnMonitorHandler::EntryMoved(const char* name, const char* fromName,
278 	ino_t fromDirectory, ino_t toDirectory, dev_t device, ino_t node,
279 	dev_t nodeDevice)
280 {
281 	node_ref toNodeRef;
282 	make_node_ref(device, toDirectory, &toNodeRef);
283 
284 	// Search the "from" and "to" directory in the known directories
285 	DirectoryList::iterator fromIter = fDirectories.begin();
286 	bool watchingFromDirectory = _FindDirectory(fromDirectory, device,
287 		fromIter);
288 
289 	DirectoryList::iterator toIter = fDirectories.begin();
290 	bool watchingToDirectory = _FindDirectory(toNodeRef, toIter);
291 
292 	if (!watchingFromDirectory && !watchingToDirectory) {
293 		// It seems the notification was for a directory we are not
294 		// actually watching (at least not by intention).
295 		return;
296 	}
297 
298 	add_on_entry_info info;
299 
300 	node_ref entryNodeRef;
301 	make_node_ref(device, node, &entryNodeRef);
302 
303 	if (!watchingToDirectory) {
304 		// moved out of our view
305 		EntryList::iterator eiter = fromIter->entries.begin();
306 		if (!_FindEntry(entryNodeRef, fromIter->entries, eiter)) {
307 			// we don't know anything about this entry yet.. ignore it
308 			return;
309 		}
310 
311 		// save the info and remove the entry
312 		info = *eiter;
313 		watch_node(&entryNodeRef, B_STOP_WATCHING, this);
314 		fromIter->entries.erase(eiter);
315 
316 		// Start at the top again, and search until the from directory.
317 		// If we find a add-on with the same name then the moved add-on
318 		// was not enabled.  So we are done.
319 		DirectoryList::iterator diter = fDirectories.begin();
320 		for (; diter != fromIter; diter++) {
321 			eiter = diter->entries.begin();
322 			if (_FindEntry(info.name, diter->entries, eiter))
323 				return;
324 		}
325 
326 		// finally disable the add-on
327 		AddOnDisabled(&info);
328 
329 		// Continue searching for a add-on below us.  If we find a add-on
330 		// with the same name, we must enable it.
331 		for (fromIter++; fromIter != fDirectories.end(); fromIter++) {
332 			eiter = fromIter->entries.begin();
333 			if (_FindEntry(info.name, fromIter->entries, eiter)) {
334 				AddOnEnabled(&*eiter);
335 				return;
336 			}
337 		}
338 
339 		// finally destroy the addon
340 		AddOnRemoved(&info);
341 
342 		// done
343 		return;
344 	}
345 
346 	if (!watchingFromDirectory) {
347 		// moved into our view
348 
349 		// update the info
350 		strlcpy(info.name, name, sizeof(info.name));
351 		info.nref = entryNodeRef;
352 		info.dir_nref = toNodeRef;
353 
354 		AddOnCreated(&info);
355 
356 		// Start at the top again, and search until the to directory.
357 		// If we find an add-on with the same name then the moved add-on
358 		// is not to be enabled. So we are done.
359 		DirectoryList::iterator diter = fDirectories.begin();
360 		for (; diter != toIter; diter++) {
361 			if (_HasEntry(info.name, diter->entries)) {
362 				// The new add-on is being shadowed.
363 				return;
364 			}
365 		}
366 
367 		// The new add-on should be enabled, but first we check to see
368 		// if there is an add-on below us. If we find one, we disable it.
369 		for (diter++ ; diter != fDirectories.end(); diter++) {
370 			EntryList::iterator eiter = diter->entries.begin();
371 			if (_FindEntry(info.name, diter->entries, eiter)) {
372 				AddOnDisabled(&*eiter);
373 				break;
374 			}
375 		}
376 
377 		// enable the new add-on
378 		AddOnEnabled(&info);
379 
380 		// put the new entry into the target directory
381 		_AddNewEntry(toIter->entries, info);
382 
383 		// done
384 		return;
385 	}
386 
387 	// The add-on was renamed, or moved within our hierarchy.
388 
389 	EntryList::iterator eiter = fromIter->entries.begin();
390 	if (_FindEntry(entryNodeRef, fromIter->entries, eiter)) {
391 		// save the old info and remove the entry
392 		info = *eiter;
393 	} else {
394 		// If an entry moved from one watched directory into another watched
395 		// directory, there will be two notifications, and this may be the
396 		// second. We have handled everything in the first. In that case the
397 		// entry was already removed from the fromDirectory and added in the
398 		// toDirectory list.
399 		return;
400 	}
401 
402 	if (strcmp(info.name, name) == 0) {
403 		// It should be impossible for the name to stay the same, unless the
404 		// node moved in the watched hierarchy. Handle this case by removing
405 		// the entry and readding it. TODO: This can temporarily enable add-ons
406 		// which should in fact stay hidden (moving add-on from home to common
407 		// folder or vice versa, the system add-on should remain hidden).
408 		EntryRemoved(name, fromDirectory, device, node);
409 		info.dir_nref = toNodeRef;
410 		_EntryCreated(info);
411 	} else {
412 		// Erase the entry
413 		fromIter->entries.erase(eiter);
414 
415 		// check to see if it was formerly enabled
416 		bool wasEnabled = true;
417 		DirectoryList::iterator oldIter = fDirectories.begin();
418 		for (; oldIter != fromIter; oldIter++) {
419 			if (_HasEntry(info.name, oldIter->entries)) {
420 				wasEnabled = false;
421 				break;
422 			}
423 		}
424 
425 		// If it was enabled, disable it and enable the one under us, if it
426 		// exists.
427 		if (wasEnabled) {
428 			AddOnDisabled(&info);
429 			for (; oldIter != fDirectories.end(); oldIter++) {
430 				eiter = oldIter->entries.begin();
431 				if (_FindEntry(info.name, oldIter->entries, eiter)) {
432 					AddOnEnabled(&*eiter);
433 					break;
434 				}
435 			}
436 		}
437 
438 		// kaboom!
439 		AddOnRemoved(&info);
440 
441 		// set up new addon info
442 		strlcpy(info.name, name, sizeof(info.name));
443 		info.dir_nref = toNodeRef;
444 
445 		// presto!
446 		AddOnCreated(&info);
447 
448 		// check to see if we are newly enabled
449 		bool isEnabled = true;
450 		DirectoryList::iterator newIter = fDirectories.begin();
451 		for (; newIter != toIter; newIter++) {
452 			if (_HasEntry(info.name, newIter->entries)) {
453 				isEnabled = false;
454 				break;
455 			}
456 		}
457 
458 		// if it is newly enabled, check under us for an enabled one, and
459 		// disable that first
460 		if (isEnabled) {
461 			for (; newIter != fDirectories.end(); newIter++) {
462 				eiter = newIter->entries.begin();
463 				if (_FindEntry(info.name, newIter->entries, eiter)) {
464 					AddOnDisabled(&*eiter);
465 					break;
466 				}
467 			}
468 			AddOnEnabled(&info);
469 		}
470 		// put the new entry into the target directory
471 		toIter->entries.push_back(info);
472 	}
473 }
474 
475 
476 void
477 AddOnMonitorHandler::StatChanged(ino_t node, dev_t device, int32 statFields)
478 {
479 	// This notification is received for the add-ons themselves.
480 
481 	// TODO: Add the entry to the pending list, disable/enable it
482 	// when the modification time remains stable.
483 
484 	node_ref entryNodeRef;
485 	make_node_ref(device, node, &entryNodeRef);
486 
487 	DirectoryList::iterator diter = fDirectories.begin();
488 	for (; diter != fDirectories.end(); diter++) {
489 		EntryList::iterator eiter = diter->entries.begin();
490 		for (; eiter != diter->entries.end(); eiter++) {
491 			if (eiter->addon_nref == entryNodeRef) {
492 				// Trigger reloading of the add-on
493 				const add_on_entry_info* info = &*eiter;
494 				AddOnDisabled(info);
495 				AddOnRemoved(info);
496 				AddOnCreated(info);
497 				AddOnEnabled(info);
498 				return;
499 			}
500 		}
501 	}
502 }
503 
504 
505 // #pragma mark - private
506 
507 
508 //!	Process pending entries.
509 void
510 AddOnMonitorHandler::_HandlePendingEntries()
511 {
512 	BDirectory directory;
513 	EntryList::iterator iter = fPendingEntries.begin();
514 	while (iter != fPendingEntries.end()) {
515 		add_on_entry_info info = *iter;
516 
517 		// Initialize directory, or re-use from previous iteration, if
518 		// directory node_ref remained the same from the last pending entry.
519 		node_ref dirNodeRef;
520 		if (directory.GetNodeRef(&dirNodeRef) != B_OK
521 			|| dirNodeRef != info.dir_nref) {
522 			if (directory.SetTo(&info.dir_nref) != B_OK) {
523 				// invalid directory, discard this pending entry
524 				iter = fPendingEntries.erase(iter);
525 				continue;
526 			}
527 			dirNodeRef = info.dir_nref;
528 		}
529 
530 		struct stat st;
531 		if (directory.GetStatFor(info.name, &st) != B_OK) {
532 			// invalid file name, discard this pending entry
533 			iter = fPendingEntries.erase(iter);
534 			continue;
535 		}
536 
537 		// stat units are seconds, real_time_clock units are seconds
538 		if (real_time_clock() - st.st_mtime < ADD_ON_STABLE_SECONDS) {
539 			// entry not stable, skip the entry for this pulse
540 			iter++;
541 			continue;
542 		}
543 
544 		// we are going to deal with the stable entry, so remove it
545 		iter = fPendingEntries.erase(iter);
546 
547 		_EntryCreated(info);
548 	}
549 }
550 
551 
552 void
553 AddOnMonitorHandler::_EntryCreated(add_on_entry_info& info)
554 {
555 	// put the new entry into the directory info
556 	DirectoryList::iterator diter = fDirectories.begin();
557 	for (; diter != fDirectories.end(); diter++) {
558 		if (diter->nref == info.dir_nref) {
559 			_AddNewEntry(diter->entries, info);
560 			break;
561 		}
562 	}
563 
564 	// report it
565 	AddOnCreated(&info);
566 
567 	// Start at the top again, and search until the directory we put
568 	// the new add-on in.  If we find an add-on with the same name then
569 	// the new add-on should not be enabled.
570 	bool enabled = true;
571 	DirectoryList::iterator diter2 = fDirectories.begin();
572 	for (; diter2 != diter; diter2++) {
573 		if (_HasEntry(info.name, diter2->entries)) {
574 			enabled = false;
575 			break;
576 		}
577 	}
578 	if (!enabled)
579 		return;
580 
581 	// The new add-on should be enabled, but first we check to see
582 	// if there is an add-on shadowed by the new one.  If we find one,
583 	// we disable it.
584 	for (diter++ ; diter != fDirectories.end(); diter++) {
585 		EntryList::iterator eiter = diter->entries.begin();
586 		if (_FindEntry(info.name, diter->entries, eiter)) {
587 			AddOnDisabled(&*eiter);
588 			break;
589 		}
590 	}
591 
592 	// enable the new entry
593 	AddOnEnabled(&info);
594 }
595 
596 
597 bool
598 AddOnMonitorHandler::_FindEntry(const node_ref& entry, const EntryList& list,
599 	EntryList::iterator& it) const
600 {
601 	for (; EntryList::const_iterator(it) != list.end(); it++) {
602 		if (it->nref == entry)
603 			return true;
604 	}
605 	return false;
606 }
607 
608 
609 bool
610 AddOnMonitorHandler::_FindEntry(const char* name, const EntryList& list,
611 	EntryList::iterator& it) const
612 {
613 	for (; EntryList::const_iterator(it) != list.end(); it++) {
614 		if (strcmp(it->name, name) == 0)
615 			return true;
616 	}
617 	return false;
618 }
619 
620 
621 bool
622 AddOnMonitorHandler::_HasEntry(const node_ref& entry, EntryList& list) const
623 {
624 	EntryList::iterator it = list.begin();
625 	return _FindEntry(entry, list, it);
626 }
627 
628 
629 bool
630 AddOnMonitorHandler::_HasEntry(const char* name, EntryList& list) const
631 {
632 	EntryList::iterator it = list.begin();
633 	return _FindEntry(name, list, it);
634 }
635 
636 
637 bool
638 AddOnMonitorHandler::_FindDirectory(ino_t directory, dev_t device,
639 	DirectoryList::iterator& it) const
640 {
641 	node_ref nodeRef;
642 	make_node_ref(device, directory, &nodeRef);
643 	return _FindDirectory(nodeRef, it, fDirectories.end());
644 }
645 
646 
647 bool
648 AddOnMonitorHandler::_FindDirectory(const node_ref& directoryNodeRef,
649 	DirectoryList::iterator& it) const
650 {
651 	return _FindDirectory(directoryNodeRef, it, fDirectories.end());
652 }
653 
654 
655 bool
656 AddOnMonitorHandler::_FindDirectory(ino_t directory, dev_t device,
657 	DirectoryList::iterator& it,
658 	const DirectoryList::const_iterator& end) const
659 {
660 	node_ref nodeRef;
661 	make_node_ref(device, directory, &nodeRef);
662 	return _FindDirectory(nodeRef, it, end);
663 }
664 
665 
666 bool
667 AddOnMonitorHandler::_FindDirectory(const node_ref& directoryNodeRef,
668 	DirectoryList::iterator& it,
669 	const DirectoryList::const_iterator& end) const
670 {
671 	for (; DirectoryList::const_iterator(it) != end; it++) {
672 		if (it->nref == directoryNodeRef)
673 			return true;
674 	}
675 	return false;
676 }
677 
678 
679 void
680 AddOnMonitorHandler::_AddNewEntry(EntryList& list, add_on_entry_info& info)
681 {
682 	BDirectory directory(&info.dir_nref);
683 	BEntry entry(&directory, info.name, true);
684 
685 	node_ref addOnRef;
686 	if (entry.GetNodeRef(&addOnRef) == B_OK) {
687 		watch_node(&addOnRef, B_WATCH_STAT, this);
688 		info.addon_nref = addOnRef;
689 	} else
690 		info.addon_nref = info.nref;
691 
692 	list.push_back(info);
693 }
694