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