1/*
2 * Copyright 2007-2009, Ingo Weinhold, bonefish@users.sf.net.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include <DiskSystemAddOnManager.h>
7
8#include <exception>
9#include <new>
10#include <set>
11#include <string>
12
13#include <stdio.h>
14#include <pthread.h>
15
16#include <Directory.h>
17#include <Entry.h>
18#include <image.h>
19#include <Path.h>
20
21#include <AutoDeleter.h>
22#include <AutoLocker.h>
23
24#include <DiskSystemAddOn.h>
25
26
27#undef TRACE
28#define TRACE(format...)
29//#define TRACE(format...)	printf(format)
30
31
32using std::nothrow;
33
34
35static pthread_once_t sManagerInitOnce = PTHREAD_ONCE_INIT;
36DiskSystemAddOnManager* DiskSystemAddOnManager::sManager = NULL;
37
38
39// AddOnImage
40struct DiskSystemAddOnManager::AddOnImage {
41	AddOnImage(image_id image)
42		: image(image),
43		  refCount(0)
44	{
45	}
46
47	~AddOnImage()
48	{
49		unload_add_on(image);
50	}
51
52	image_id			image;
53	int32				refCount;
54};
55
56
57// AddOn
58struct DiskSystemAddOnManager::AddOn {
59	AddOn(AddOnImage* image, BDiskSystemAddOn* addOn)
60		: image(image),
61		  addOn(addOn),
62		  refCount(1)
63	{
64	}
65
66	AddOnImage*			image;
67	BDiskSystemAddOn*	addOn;
68	int32				refCount;
69};
70
71
72// StringSet
73struct DiskSystemAddOnManager::StringSet : std::set<std::string> {
74};
75
76
77// Default
78DiskSystemAddOnManager*
79DiskSystemAddOnManager::Default()
80{
81	if (sManager == NULL)
82		pthread_once(&sManagerInitOnce, &_InitSingleton);
83
84	return sManager;
85}
86
87
88// Lock
89bool
90DiskSystemAddOnManager::Lock()
91{
92	return fLock.Lock();
93}
94
95
96// Unlock
97void
98DiskSystemAddOnManager::Unlock()
99{
100	fLock.Unlock();
101}
102
103
104// LoadDiskSystems
105status_t
106DiskSystemAddOnManager::LoadDiskSystems()
107{
108	AutoLocker<BLocker> _(fLock);
109
110	if (++fLoadCount > 1)
111		return B_OK;
112
113	StringSet alreadyLoaded;
114	status_t error
115		= _LoadAddOns(alreadyLoaded, B_USER_NONPACKAGED_ADDONS_DIRECTORY);
116
117	if (error == B_OK)
118		error = _LoadAddOns(alreadyLoaded, B_USER_ADDONS_DIRECTORY);
119
120	if (error == B_OK) {
121		error
122			= _LoadAddOns(alreadyLoaded, B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY);
123	}
124
125	if (error == B_OK)
126		error = _LoadAddOns(alreadyLoaded, B_SYSTEM_ADDONS_DIRECTORY);
127
128	if (error != B_OK)
129		UnloadDiskSystems();
130
131	return error;
132}
133
134
135// UnloadDiskSystems
136void
137DiskSystemAddOnManager::UnloadDiskSystems()
138{
139	AutoLocker<BLocker> _(fLock);
140
141	if (fLoadCount == 0 || --fLoadCount > 0)
142		return;
143
144	fAddOnsToBeUnloaded.AddList(&fAddOns);
145	fAddOns.MakeEmpty();
146
147	// put all add-ons -- that will cause them to be deleted as soon as they're
148	// unused
149	for (int32 i = fAddOnsToBeUnloaded.CountItems() - 1; i >= 0; i--)
150		_PutAddOn(i);
151}
152
153
154// CountAddOns
155int32
156DiskSystemAddOnManager::CountAddOns() const
157{
158	return fAddOns.CountItems();
159}
160
161
162// AddOnAt
163BDiskSystemAddOn*
164DiskSystemAddOnManager::AddOnAt(int32 index) const
165{
166	AddOn* addOn = _AddOnAt(index);
167	return addOn ? addOn->addOn : NULL;
168}
169
170
171// GetAddOn
172BDiskSystemAddOn*
173DiskSystemAddOnManager::GetAddOn(const char* name)
174{
175	if (!name)
176		return NULL;
177
178	AutoLocker<BLocker> _(fLock);
179
180	for (int32 i = 0; AddOn* addOn = _AddOnAt(i); i++) {
181		if (strcmp(addOn->addOn->Name(), name) == 0) {
182			addOn->refCount++;
183			return addOn->addOn;
184		}
185	}
186
187	return NULL;
188}
189
190
191// PutAddOn
192void
193DiskSystemAddOnManager::PutAddOn(BDiskSystemAddOn* _addOn)
194{
195	if (!_addOn)
196		return;
197
198	AutoLocker<BLocker> _(fLock);
199
200	for (int32 i = 0; AddOn* addOn = _AddOnAt(i); i++) {
201		if (_addOn == addOn->addOn) {
202			if (addOn->refCount > 1) {
203				addOn->refCount--;
204			} else {
205				debugger("Unbalanced call to "
206					"DiskSystemAddOnManager::PutAddOn()");
207			}
208			return;
209		}
210	}
211
212	for (int32 i = 0;
213		 AddOn* addOn = (AddOn*)fAddOnsToBeUnloaded.ItemAt(i); i++) {
214		if (_addOn == addOn->addOn) {
215			_PutAddOn(i);
216			return;
217		}
218	}
219
220	debugger("DiskSystemAddOnManager::PutAddOn(): disk system not found");
221}
222
223
224// constructor
225DiskSystemAddOnManager::DiskSystemAddOnManager()
226	: fLock("disk system add-ons manager"),
227	  fAddOns(),
228	  fAddOnsToBeUnloaded(),
229	  fLoadCount(0)
230{
231}
232
233
234/*static*/ void
235DiskSystemAddOnManager::_InitSingleton()
236{
237	sManager = new DiskSystemAddOnManager();
238}
239
240
241// _AddOnAt
242DiskSystemAddOnManager::AddOn*
243DiskSystemAddOnManager::_AddOnAt(int32 index) const
244{
245	return (AddOn*)fAddOns.ItemAt(index);
246}
247
248
249// _PutAddOn
250void
251DiskSystemAddOnManager::_PutAddOn(int32 index)
252{
253	AddOn* addOn = (AddOn*)fAddOnsToBeUnloaded.ItemAt(index);
254	if (!addOn)
255		return;
256
257	if (--addOn->refCount == 0) {
258		if (--addOn->image->refCount == 0)
259			delete addOn->image;
260
261		fAddOnsToBeUnloaded.RemoveItem(index);
262		delete addOn;
263	}
264}
265
266
267// _LoadAddOns
268status_t
269DiskSystemAddOnManager::_LoadAddOns(StringSet& alreadyLoaded,
270	directory_which addOnDir)
271{
272	// get the add-on directory path
273	BPath path;
274	status_t error = find_directory(addOnDir, &path, false);
275	if (error != B_OK)
276		return error;
277
278	TRACE("DiskSystemAddOnManager::_LoadAddOns(): %s\n", path.Path());
279
280	error = path.Append("disk_systems");
281	if (error != B_OK)
282		return error;
283
284	if (!BEntry(path.Path()).Exists())
285		return B_OK;
286
287	// open the directory and iterate through its entries
288	BDirectory directory;
289	error = directory.SetTo(path.Path());
290	if (error != B_OK)
291		return error;
292
293	entry_ref ref;
294	while (directory.GetNextRef(&ref) == B_OK) {
295		// skip, if already loaded
296		if (alreadyLoaded.find(ref.name) != alreadyLoaded.end()) {
297			TRACE("  skipping \"%s\" -- already loaded\n", ref.name);
298			continue;
299		}
300
301		// get the entry path
302		BPath entryPath;
303		error = entryPath.SetTo(&ref);
304		if (error != B_OK) {
305			if (error == B_NO_MEMORY)
306				return error;
307			TRACE("  skipping \"%s\" -- failed to get path\n", ref.name);
308			continue;
309		}
310
311		// load the add-on
312		image_id image = load_add_on(entryPath.Path());
313		if (image < 0) {
314			TRACE("  skipping \"%s\" -- failed to load add-on\n", ref.name);
315			continue;
316		}
317
318		AddOnImage* addOnImage = new(nothrow) AddOnImage(image);
319		if (!addOnImage) {
320			unload_add_on(image);
321			return B_NO_MEMORY;
322		}
323		ObjectDeleter<AddOnImage> addOnImageDeleter(addOnImage);
324
325		// get the add-on objects
326		status_t (*getAddOns)(BList*);
327		error = get_image_symbol(image, "get_disk_system_add_ons",
328			B_SYMBOL_TYPE_TEXT, (void**)&getAddOns);
329		if (error != B_OK) {
330			TRACE("  skipping \"%s\" -- function symbol not found\n", ref.name);
331			continue;
332		}
333
334		BList addOns;
335		error = getAddOns(&addOns);
336		if (error != B_OK || addOns.IsEmpty()) {
337			TRACE("  skipping \"%s\" -- getting add-ons failed\n", ref.name);
338			continue;
339		}
340
341		// create and add AddOn objects
342		int32 count = addOns.CountItems();
343		for (int32 i = 0; i < count; i++) {
344			BDiskSystemAddOn* diskSystemAddOn
345				= (BDiskSystemAddOn*)addOns.ItemAt(i);
346			AddOn* addOn = new(nothrow) AddOn(addOnImage, diskSystemAddOn);
347			if (!addOn)
348				return B_NO_MEMORY;
349
350			if (fAddOns.AddItem(addOn)) {
351				addOnImage->refCount++;
352				addOnImageDeleter.Detach();
353			} else {
354				delete addOn;
355				return B_NO_MEMORY;
356			}
357		}
358
359		TRACE("  got %ld BDiskSystemAddOn(s) from add-on \"%s\"\n", count,
360			ref.name);
361
362		// add the add-on name to the set of already loaded add-ons
363		try {
364			alreadyLoaded.insert(ref.name);
365		} catch (std::bad_alloc& exception) {
366			return B_NO_MEMORY;
367		}
368	}
369
370	return B_OK;
371}
372