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