⛏️ index : haiku.git

// VolumeManager.cpp

#include <HashMap.h>
#include <HashSet.h>
#include <util/DoublyLinkedList.h>

#include "DebugSupport.h"
#include "QueryManager.h"
#include "RootVolume.h"
#include "VolumeEvent.h"
#include "VolumeManager.h"

// VolumeSet
struct VolumeManager::VolumeSet : HashSet<HashKeyPointer<Volume*> > {
};

// NodeIDVolumeMap
struct VolumeManager::NodeIDVolumeMap : HashMap<HashKey64<vnode_id>, Volume*> {
};

// VolumeEventQueue
class VolumeManager::VolumeEventQueue {
public:
	VolumeEventQueue()
		: fLock("volume event queue"),
		  fCounterSem(-1)
	{
		fCounterSem = create_sem(0, "volume event count");
		#if !USER
			if (fCounterSem >= 0)
				set_sem_owner(fCounterSem, B_SYSTEM_TEAM);
		#endif
	}

	~VolumeEventQueue()
	{
		Close();
	}

	status_t InitCheck() const
	{
		if (fCounterSem < 0)
			return fCounterSem;
		return B_OK;
	}

	void Close()
	{
		AutoLocker<Locker> _(fLock);
		if (fCounterSem >= 0) {
			delete_sem(fCounterSem);
			fCounterSem = -1;
		}

		while (VolumeEvent* event = fEvents.First()) {
			fEvents.Remove(event);
			event->ReleaseReference();
		}
	}

	void Push(VolumeEvent* event)
	{
		if (!event)
			return;

		AutoLocker<Locker> _(fLock);
		if (fCounterSem < 0)
			return;
		fEvents.Insert(event);
		event->AcquireReference();
		release_sem(fCounterSem);
	}

	VolumeEvent* Pop()
	{
		status_t error;
		do {
			error = acquire_sem(fCounterSem);
		} while (error == B_INTERRUPTED);
		if (error != B_OK)
			return NULL;

		AutoLocker<Locker> _(fLock);
		if (VolumeEvent* event = fEvents.First()) {
			fEvents.Remove(event);
			return event;
		}

		return NULL;
	}

private:
	Locker				fLock;
	sem_id				fCounterSem;
	DoublyLinkedList<VolumeEvent> fEvents;
};


// constructor
VolumeManager::VolumeManager(nspace_id id, uint32 flags)
	: Locker("volume manager"),
	  fID(id),
	  fMountFlags(flags),
	  fMountUID(0),
	  fMountGID(0),
	  fRootID(-1),
	  fNextNodeID(2),
	  fQueryManager(NULL),
	  fVolumes(NULL),
	  fNodeIDs2Volumes(NULL),
	  fVolumeEvents(NULL),
	  fEventDeliverer(-1)
{
}

// destructor
VolumeManager::~VolumeManager()
{
	if (fVolumeEvents)
		fVolumeEvents->Close();
	if (fEventDeliverer >= 0) {
		int32 result;
		wait_for_thread(fEventDeliverer, &result);
	}
	delete fVolumeEvents;
	delete fVolumes;
	delete fNodeIDs2Volumes;
	delete fQueryManager;
}

// MountRootVolume
status_t
VolumeManager::MountRootVolume(const char* device,
	const char* parameters, int32 len, Volume** volume)
{
	// Store the uid/gid of the mounting user -- when running in userland
	// this is always the owner of the UserlandFS server, but we can't help
	// that.
	fMountUID = geteuid();
	fMountGID = getegid();

	// create the query manager
	fQueryManager = new(std::nothrow) QueryManager(this);
	if (!fQueryManager)
		return B_NO_MEMORY;
	status_t error = fQueryManager->Init();
	if (error != B_OK)
		return error;

	// create volumes set
	fVolumes = new(std::nothrow) VolumeSet;
	if (!fVolumes)
		return B_NO_MEMORY;
	error = fVolumes->InitCheck();
	if (error != B_OK)
		return error;

	// create node ID to volumes map
	fNodeIDs2Volumes = new(std::nothrow) NodeIDVolumeMap;
	if (!fNodeIDs2Volumes)
		return B_NO_MEMORY;
	error = fNodeIDs2Volumes->InitCheck();
	if (error != B_OK)
		return error;

	// create the volume event queue
	fVolumeEvents = new VolumeEventQueue;
	if (!fVolumeEvents)
		return B_NO_MEMORY;
	error = fVolumeEvents->InitCheck();
	if (error != B_OK)
		return error;

	// spawn the event deliverer
	#if USER
		fEventDeliverer = spawn_thread(&_EventDelivererEntry,
			"volume event deliverer", B_NORMAL_PRIORITY, this);
	#else
		fEventDeliverer = spawn_kernel_thread(&_EventDelivererEntry,
			"volume event deliverer", B_NORMAL_PRIORITY, this);
	#endif
	if (fEventDeliverer < 0)
		return fEventDeliverer;

	// create the root volume
	RootVolume* rootVolume = new(std::nothrow) RootVolume(this);
	if (!rootVolume)
		return B_NO_MEMORY;
	error = rootVolume->Init();
	if (error != B_OK) {
		delete rootVolume;
		return error;
	}
	fRootID = rootVolume->GetRootID();

	// add the root volume
	error = AddVolume(rootVolume);
	if (error != B_OK) {
		delete rootVolume;
		return error;
	}

	// mount the root volume
	error = rootVolume->Mount(device, fMountFlags, (const char*)parameters,
		len);
	if (error != B_OK) {
		rootVolume->SetUnmounting(true);
		PutVolume(rootVolume);
		return error;
	}
	rootVolume->AcquireReference();
	*volume = rootVolume;

	// run the event deliverer
	resume_thread(fEventDeliverer);

	return B_OK;
}

// UnmountRootVolume
void
VolumeManager::UnmountRootVolume()
{
	if (Volume* rootVolume = GetRootVolume()) {
		rootVolume->SetUnmounting(true);
		PutVolume(rootVolume);
	} else {
		ERROR(("VolumeManager::UnmountRootVolume(): ERROR: Couldn't get "
			"root volume!\n"));
	}
}

// GetQueryManager
QueryManager*
VolumeManager::GetQueryManager() const
{
	return fQueryManager;
}

// GetRootVolume
Volume*
VolumeManager::GetRootVolume()
{
	return GetVolume(fRootID);
}

// AddVolume
//
// The caller must have a reference to the volume and retains it.
status_t
VolumeManager::AddVolume(Volume* volume)
{
	if (!volume)
		return B_BAD_VALUE;

	// check, if it already exists
	AutoLocker<Locker> _(this);
	if (fVolumes->Contains(volume))
		return B_BAD_VALUE;

	// add the volume
	return fVolumes->Add(volume);
}

// GetVolume
Volume*
VolumeManager::GetVolume(vnode_id nodeID)
{
	AutoLocker<Locker> _(this);
	Volume* volume = fNodeIDs2Volumes->Get(nodeID);
	if (volume && GetVolume(volume))
		return volume;
	return NULL;
}

// GetVolume
Volume*
VolumeManager::GetVolume(Volume* volume)
{
	if (!volume)
		return NULL;

	AutoLocker<Locker> _(this);
	if (fVolumes->Contains(volume)) {
// TODO: Any restrictions regarding volumes about to be removed?
		volume->AcquireReference();
		return volume;
	}
	return NULL;
}

// PutVolume
//
// The VolumeManager must not be locked, when this method is invoked.
void
VolumeManager::PutVolume(Volume* volume)
{
	if (!volume)
		return;

	// If the volume is marked unmounting and is not yet marked removed, we
	// initiate the removal process.
	{
		AutoLocker<Locker> locker(this);
//PRINT(("VolumeManager::PutVolume(%p): reference count before: %ld\n",
//volume, volume->CountReferences()));
		if (volume->IsUnmounting() && !volume->IsRemoved()) {
//PRINT(("VolumeManager::PutVolume(%p): Volume connection broken, marking "
//"removed and removing all nodes.\n", volume));
			// mark removed
			volume->MarkRemoved();

			// get parent volume
			Volume* parentVolume = volume->GetParentVolume();
			if (parentVolume && !GetVolume(parentVolume))
				parentVolume = NULL;

			locker.Unlock();

			// prepare to unmount
			volume->PrepareToUnmount();

			// remove from parent volume
			if (parentVolume) {
				parentVolume->RemoveChildVolume(volume);
				PutVolume(parentVolume);
			}
		}
	}

	// If the volume is marked removed and it's reference count drops to 0,
	// we unmount and delete it.
	{
		AutoLocker<Locker> locker(this);
		if (volume->ReleaseReference() && volume->IsRemoved()) {
			PRINT("VolumeManager::PutVolume(%p): Removed volume "
				"unreferenced. Unmounting...\n", volume);
			// remove from volume set -- now noone can get a reference to it
			// anymore
			fVolumes->Remove(volume);

			locker.Unlock();

			// unmount and delete the volume
// TODO: At some point all the volume's node IDs have to be removed from
// fNodeIDs2Volumes. For the time being we expect the volume to do that itself
// in Unmount().
			volume->Unmount();
			delete volume;
		}
	}
}

// NewNodeID
vnode_id
VolumeManager::NewNodeID(Volume* volume)
{
	if (!volume)
		return B_BAD_VALUE;

	AutoLocker<Locker> _(this);
	vnode_id nodeID = fNextNodeID;
	status_t error = fNodeIDs2Volumes->Put(nodeID, volume);
	if (error != B_OK)
		return error;
	return fNextNodeID++;
}

// RemoveNodeID
void
VolumeManager::RemoveNodeID(vnode_id nodeID)
{
	AutoLocker<Locker> _(this);
	fNodeIDs2Volumes->Remove(nodeID);
}

// SendVolumeEvent
void
VolumeManager::SendVolumeEvent(VolumeEvent* event)
{
	if (!event)
		return;

	fVolumeEvents->Push(event);
}

// _EventDelivererEntry
int32
VolumeManager::_EventDelivererEntry(void* data)
{
	return ((VolumeManager*)data)->_EventDeliverer();
}

// _EventDeliverer
int32
VolumeManager::_EventDeliverer()
{
	while (VolumeEvent* event = fVolumeEvents->Pop()) {
		if (Volume* volume = GetVolume(event->GetTarget())) {
			volume->HandleEvent(event);
			PutVolume(volume);
		}
		event->ReleaseReference();
	}
	return 0;
}