* Copyright 2007-2009, Ingo Weinhold, bonefish@users.sf.net.
* Distributed under the terms of the MIT License.
*/
#include <DiskSystemAddOnManager.h>
#include <exception>
#include <new>
#include <set>
#include <string>
#include <stdio.h>
#include <pthread.h>
#include <Directory.h>
#include <Entry.h>
#include <image.h>
#include <Path.h>
#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <DiskSystemAddOn.h>
#undef TRACE
#define TRACE(format...)
using std::nothrow;
static pthread_once_t sManagerInitOnce = PTHREAD_ONCE_INIT;
DiskSystemAddOnManager* DiskSystemAddOnManager::sManager = NULL;
struct DiskSystemAddOnManager::AddOnImage {
AddOnImage(image_id image)
: image(image),
refCount(0)
{
}
~AddOnImage()
{
unload_add_on(image);
}
image_id image;
int32 refCount;
};
struct DiskSystemAddOnManager::AddOn {
AddOn(AddOnImage* image, BDiskSystemAddOn* addOn)
: image(image),
addOn(addOn),
refCount(1)
{
}
AddOnImage* image;
BDiskSystemAddOn* addOn;
int32 refCount;
};
struct DiskSystemAddOnManager::StringSet : std::set<std::string> {
};
DiskSystemAddOnManager*
DiskSystemAddOnManager::Default()
{
if (sManager == NULL)
pthread_once(&sManagerInitOnce, &_InitSingleton);
return sManager;
}
bool
DiskSystemAddOnManager::Lock()
{
return fLock.Lock();
}
void
DiskSystemAddOnManager::Unlock()
{
fLock.Unlock();
}
status_t
DiskSystemAddOnManager::LoadDiskSystems()
{
AutoLocker<BLocker> _(fLock);
if (++fLoadCount > 1)
return B_OK;
StringSet alreadyLoaded;
status_t error
= _LoadAddOns(alreadyLoaded, B_USER_NONPACKAGED_ADDONS_DIRECTORY);
if (error == B_OK)
error = _LoadAddOns(alreadyLoaded, B_USER_ADDONS_DIRECTORY);
if (error == B_OK) {
error
= _LoadAddOns(alreadyLoaded, B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY);
}
if (error == B_OK)
error = _LoadAddOns(alreadyLoaded, B_SYSTEM_ADDONS_DIRECTORY);
if (error != B_OK)
UnloadDiskSystems();
return error;
}
void
DiskSystemAddOnManager::UnloadDiskSystems()
{
AutoLocker<BLocker> _(fLock);
if (fLoadCount == 0 || --fLoadCount > 0)
return;
fAddOnsToBeUnloaded.AddList(&fAddOns);
fAddOns.MakeEmpty();
for (int32 i = fAddOnsToBeUnloaded.CountItems() - 1; i >= 0; i--)
_PutAddOn(i);
}
int32
DiskSystemAddOnManager::CountAddOns() const
{
return fAddOns.CountItems();
}
BDiskSystemAddOn*
DiskSystemAddOnManager::AddOnAt(int32 index) const
{
AddOn* addOn = _AddOnAt(index);
return addOn ? addOn->addOn : NULL;
}
BDiskSystemAddOn*
DiskSystemAddOnManager::GetAddOn(const char* name)
{
if (!name)
return NULL;
AutoLocker<BLocker> _(fLock);
for (int32 i = 0; AddOn* addOn = _AddOnAt(i); i++) {
if (strcmp(addOn->addOn->Name(), name) == 0) {
addOn->refCount++;
return addOn->addOn;
}
}
return NULL;
}
void
DiskSystemAddOnManager::PutAddOn(BDiskSystemAddOn* _addOn)
{
if (!_addOn)
return;
AutoLocker<BLocker> _(fLock);
for (int32 i = 0; AddOn* addOn = _AddOnAt(i); i++) {
if (_addOn == addOn->addOn) {
if (addOn->refCount > 1) {
addOn->refCount--;
} else {
debugger("Unbalanced call to "
"DiskSystemAddOnManager::PutAddOn()");
}
return;
}
}
for (int32 i = 0;
AddOn* addOn = (AddOn*)fAddOnsToBeUnloaded.ItemAt(i); i++) {
if (_addOn == addOn->addOn) {
_PutAddOn(i);
return;
}
}
debugger("DiskSystemAddOnManager::PutAddOn(): disk system not found");
}
DiskSystemAddOnManager::DiskSystemAddOnManager()
: fLock("disk system add-ons manager"),
fAddOns(),
fAddOnsToBeUnloaded(),
fLoadCount(0)
{
}
void
DiskSystemAddOnManager::_InitSingleton()
{
sManager = new DiskSystemAddOnManager();
}
DiskSystemAddOnManager::AddOn*
DiskSystemAddOnManager::_AddOnAt(int32 index) const
{
return (AddOn*)fAddOns.ItemAt(index);
}
void
DiskSystemAddOnManager::_PutAddOn(int32 index)
{
AddOn* addOn = (AddOn*)fAddOnsToBeUnloaded.ItemAt(index);
if (!addOn)
return;
if (--addOn->refCount == 0) {
if (--addOn->image->refCount == 0)
delete addOn->image;
fAddOnsToBeUnloaded.RemoveItem(index);
delete addOn;
}
}
status_t
DiskSystemAddOnManager::_LoadAddOns(StringSet& alreadyLoaded,
directory_which addOnDir)
{
BPath path;
status_t error = find_directory(addOnDir, &path, false);
if (error != B_OK)
return error;
TRACE("DiskSystemAddOnManager::_LoadAddOns(): %s\n", path.Path());
error = path.Append("disk_systems");
if (error != B_OK)
return error;
if (!BEntry(path.Path()).Exists())
return B_OK;
BDirectory directory;
error = directory.SetTo(path.Path());
if (error != B_OK)
return error;
entry_ref ref;
while (directory.GetNextRef(&ref) == B_OK) {
if (alreadyLoaded.find(ref.name) != alreadyLoaded.end()) {
TRACE(" skipping \"%s\" -- already loaded\n", ref.name);
continue;
}
BPath entryPath;
error = entryPath.SetTo(&ref);
if (error != B_OK) {
if (error == B_NO_MEMORY)
return error;
TRACE(" skipping \"%s\" -- failed to get path\n", ref.name);
continue;
}
image_id image = load_add_on(entryPath.Path());
if (image < 0) {
TRACE(" skipping \"%s\" -- failed to load add-on\n", ref.name);
continue;
}
AddOnImage* addOnImage = new(nothrow) AddOnImage(image);
if (!addOnImage) {
unload_add_on(image);
return B_NO_MEMORY;
}
ObjectDeleter<AddOnImage> addOnImageDeleter(addOnImage);
status_t (*getAddOns)(BList*);
error = get_image_symbol(image, "get_disk_system_add_ons",
B_SYMBOL_TYPE_TEXT, (void**)&getAddOns);
if (error != B_OK) {
TRACE(" skipping \"%s\" -- function symbol not found\n", ref.name);
continue;
}
BList addOns;
error = getAddOns(&addOns);
if (error != B_OK || addOns.IsEmpty()) {
TRACE(" skipping \"%s\" -- getting add-ons failed\n", ref.name);
continue;
}
int32 count = addOns.CountItems();
for (int32 i = 0; i < count; i++) {
BDiskSystemAddOn* diskSystemAddOn
= (BDiskSystemAddOn*)addOns.ItemAt(i);
AddOn* addOn = new(nothrow) AddOn(addOnImage, diskSystemAddOn);
if (!addOn)
return B_NO_MEMORY;
if (fAddOns.AddItem(addOn)) {
addOnImage->refCount++;
addOnImageDeleter.Detach();
} else {
delete addOn;
return B_NO_MEMORY;
}
}
TRACE(" got %ld BDiskSystemAddOn(s) from add-on \"%s\"\n", count,
ref.name);
try {
alreadyLoaded.insert(ref.name);
} catch (std::bad_alloc& exception) {
return B_NO_MEMORY;
}
}
return B_OK;
}