⛏️ index : haiku.git

/*
 * 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...)
//#define TRACE(format...)	printf(format)


using std::nothrow;


static pthread_once_t sManagerInitOnce = PTHREAD_ONCE_INIT;
DiskSystemAddOnManager* DiskSystemAddOnManager::sManager = NULL;


// AddOnImage
struct DiskSystemAddOnManager::AddOnImage {
	AddOnImage(image_id image)
		: image(image),
		  refCount(0)
	{
	}

	~AddOnImage()
	{
		unload_add_on(image);
	}

	image_id			image;
	int32				refCount;
};


// AddOn
struct DiskSystemAddOnManager::AddOn {
	AddOn(AddOnImage* image, BDiskSystemAddOn* addOn)
		: image(image),
		  addOn(addOn),
		  refCount(1)
	{
	}

	AddOnImage*			image;
	BDiskSystemAddOn*	addOn;
	int32				refCount;
};


// StringSet
struct DiskSystemAddOnManager::StringSet : std::set<std::string> {
};


// Default
DiskSystemAddOnManager*
DiskSystemAddOnManager::Default()
{
	if (sManager == NULL)
		pthread_once(&sManagerInitOnce, &_InitSingleton);

	return sManager;
}


// Lock
bool
DiskSystemAddOnManager::Lock()
{
	return fLock.Lock();
}


// Unlock
void
DiskSystemAddOnManager::Unlock()
{
	fLock.Unlock();
}


// LoadDiskSystems
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;
}


// UnloadDiskSystems
void
DiskSystemAddOnManager::UnloadDiskSystems()
{
	AutoLocker<BLocker> _(fLock);

	if (fLoadCount == 0 || --fLoadCount > 0)
		return;

	fAddOnsToBeUnloaded.AddList(&fAddOns);
	fAddOns.MakeEmpty();

	// put all add-ons -- that will cause them to be deleted as soon as they're
	// unused
	for (int32 i = fAddOnsToBeUnloaded.CountItems() - 1; i >= 0; i--)
		_PutAddOn(i);
}


// CountAddOns
int32
DiskSystemAddOnManager::CountAddOns() const
{
	return fAddOns.CountItems();
}


// AddOnAt
BDiskSystemAddOn*
DiskSystemAddOnManager::AddOnAt(int32 index) const
{
	AddOn* addOn = _AddOnAt(index);
	return addOn ? addOn->addOn : NULL;
}


// GetAddOn
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;
}


// PutAddOn
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");
}


// constructor
DiskSystemAddOnManager::DiskSystemAddOnManager()
	: fLock("disk system add-ons manager"),
	  fAddOns(),
	  fAddOnsToBeUnloaded(),
	  fLoadCount(0)
{
}


/*static*/ void
DiskSystemAddOnManager::_InitSingleton()
{
	sManager = new DiskSystemAddOnManager();
}


// _AddOnAt
DiskSystemAddOnManager::AddOn*
DiskSystemAddOnManager::_AddOnAt(int32 index) const
{
	return (AddOn*)fAddOns.ItemAt(index);
}


// _PutAddOn
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;
	}
}


// _LoadAddOns
status_t
DiskSystemAddOnManager::_LoadAddOns(StringSet& alreadyLoaded,
	directory_which addOnDir)
{
	// get the add-on directory path
	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;

	// open the directory and iterate through its entries
	BDirectory directory;
	error = directory.SetTo(path.Path());
	if (error != B_OK)
		return error;

	entry_ref ref;
	while (directory.GetNextRef(&ref) == B_OK) {
		// skip, if already loaded
		if (alreadyLoaded.find(ref.name) != alreadyLoaded.end()) {
			TRACE("  skipping \"%s\" -- already loaded\n", ref.name);
			continue;
		}

		// get the entry path
		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;
		}

		// load the add-on
		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);

		// get the add-on objects
		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;
		}

		// create and add AddOn objects
		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);

		// add the add-on name to the set of already loaded add-ons
		try {
			alreadyLoaded.insert(ref.name);
		} catch (std::bad_alloc& exception) {
			return B_NO_MEMORY;
		}
	}

	return B_OK;
}