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
32AddOnMonitorHandler::AddOnMonitorHandler(const char* name)
33	:
34	NodeMonitorHandler(name != NULL ? name : "AddOnMonitorHandler")
35{
36}
37
38
39AddOnMonitorHandler::~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
54void
55AddOnMonitorHandler::MessageReceived(BMessage* msg)
56{
57	if (msg->what == B_PULSE)
58		_HandlePendingEntries();
59
60	inherited::MessageReceived(msg);
61}
62
63
64status_t
65AddOnMonitorHandler::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
116status_t
117AddOnMonitorHandler::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
172void
173AddOnMonitorHandler::AddOnCreated(const add_on_entry_info* entryInfo)
174{
175
176}
177
178
179void
180AddOnMonitorHandler::AddOnEnabled(const add_on_entry_info* entryInfo)
181{
182}
183
184
185void
186AddOnMonitorHandler::AddOnDisabled(const add_on_entry_info* entryInfo)
187{
188}
189
190
191void
192AddOnMonitorHandler::AddOnRemoved(const add_on_entry_info* entryInfo)
193{
194}
195
196
197//	#pragma mark - NodeMonitorHandler hooks
198
199
200void
201AddOnMonitorHandler::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
212void
213AddOnMonitorHandler::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
276void
277AddOnMonitorHandler::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
476void
477AddOnMonitorHandler::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.
509void
510AddOnMonitorHandler::_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
552void
553AddOnMonitorHandler::_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
597bool
598AddOnMonitorHandler::_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
609bool
610AddOnMonitorHandler::_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
621bool
622AddOnMonitorHandler::_HasEntry(const node_ref& entry, EntryList& list) const
623{
624	EntryList::iterator it = list.begin();
625	return _FindEntry(entry, list, it);
626}
627
628
629bool
630AddOnMonitorHandler::_HasEntry(const char* name, EntryList& list) const
631{
632	EntryList::iterator it = list.begin();
633	return _FindEntry(name, list, it);
634}
635
636
637bool
638AddOnMonitorHandler::_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
647bool
648AddOnMonitorHandler::_FindDirectory(const node_ref& directoryNodeRef,
649	DirectoryList::iterator& it) const
650{
651	return _FindDirectory(directoryNodeRef, it, fDirectories.end());
652}
653
654
655bool
656AddOnMonitorHandler::_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
666bool
667AddOnMonitorHandler::_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
679void
680AddOnMonitorHandler::_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