#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"
struct VolumeManager::VolumeSet : HashSet<HashKeyPointer<Volume*> > {
};
struct VolumeManager::NodeIDVolumeMap : HashMap<HashKey64<vnode_id>, Volume*> {
};
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;
};
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)
{
}
VolumeManager::~VolumeManager()
{
if (fVolumeEvents)
fVolumeEvents->Close();
if (fEventDeliverer >= 0) {
int32 result;
wait_for_thread(fEventDeliverer, &result);
}
delete fVolumeEvents;
delete fVolumes;
delete fNodeIDs2Volumes;
delete fQueryManager;
}
status_t
VolumeManager::MountRootVolume(const char* device,
const char* parameters, int32 len, Volume** volume)
{
fMountUID = geteuid();
fMountGID = getegid();
fQueryManager = new(std::nothrow) QueryManager(this);
if (!fQueryManager)
return B_NO_MEMORY;
status_t error = fQueryManager->Init();
if (error != B_OK)
return error;
fVolumes = new(std::nothrow) VolumeSet;
if (!fVolumes)
return B_NO_MEMORY;
error = fVolumes->InitCheck();
if (error != B_OK)
return error;
fNodeIDs2Volumes = new(std::nothrow) NodeIDVolumeMap;
if (!fNodeIDs2Volumes)
return B_NO_MEMORY;
error = fNodeIDs2Volumes->InitCheck();
if (error != B_OK)
return error;
fVolumeEvents = new VolumeEventQueue;
if (!fVolumeEvents)
return B_NO_MEMORY;
error = fVolumeEvents->InitCheck();
if (error != B_OK)
return error;
#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;
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();
error = AddVolume(rootVolume);
if (error != B_OK) {
delete rootVolume;
return error;
}
error = rootVolume->Mount(device, fMountFlags, (const char*)parameters,
len);
if (error != B_OK) {
rootVolume->SetUnmounting(true);
PutVolume(rootVolume);
return error;
}
rootVolume->AcquireReference();
*volume = rootVolume;
resume_thread(fEventDeliverer);
return B_OK;
}
void
VolumeManager::UnmountRootVolume()
{
if (Volume* rootVolume = GetRootVolume()) {
rootVolume->SetUnmounting(true);
PutVolume(rootVolume);
} else {
ERROR(("VolumeManager::UnmountRootVolume(): ERROR: Couldn't get "
"root volume!\n"));
}
}
QueryManager*
VolumeManager::GetQueryManager() const
{
return fQueryManager;
}
Volume*
VolumeManager::GetRootVolume()
{
return GetVolume(fRootID);
}
status_t
VolumeManager::AddVolume(Volume* volume)
{
if (!volume)
return B_BAD_VALUE;
AutoLocker<Locker> _(this);
if (fVolumes->Contains(volume))
return B_BAD_VALUE;
return fVolumes->Add(volume);
}
Volume*
VolumeManager::GetVolume(vnode_id nodeID)
{
AutoLocker<Locker> _(this);
Volume* volume = fNodeIDs2Volumes->Get(nodeID);
if (volume && GetVolume(volume))
return volume;
return NULL;
}
Volume*
VolumeManager::GetVolume(Volume* volume)
{
if (!volume)
return NULL;
AutoLocker<Locker> _(this);
if (fVolumes->Contains(volume)) {
volume->AcquireReference();
return volume;
}
return NULL;
}
void
VolumeManager::PutVolume(Volume* volume)
{
if (!volume)
return;
{
AutoLocker<Locker> locker(this);
if (volume->IsUnmounting() && !volume->IsRemoved()) {
volume->MarkRemoved();
Volume* parentVolume = volume->GetParentVolume();
if (parentVolume && !GetVolume(parentVolume))
parentVolume = NULL;
locker.Unlock();
volume->PrepareToUnmount();
if (parentVolume) {
parentVolume->RemoveChildVolume(volume);
PutVolume(parentVolume);
}
}
}
{
AutoLocker<Locker> locker(this);
if (volume->ReleaseReference() && volume->IsRemoved()) {
PRINT("VolumeManager::PutVolume(%p): Removed volume "
"unreferenced. Unmounting...\n", volume);
fVolumes->Remove(volume);
locker.Unlock();
volume->Unmount();
delete volume;
}
}
}
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++;
}
void
VolumeManager::RemoveNodeID(vnode_id nodeID)
{
AutoLocker<Locker> _(this);
fNodeIDs2Volumes->Remove(nodeID);
}
void
VolumeManager::SendVolumeEvent(VolumeEvent* event)
{
if (!event)
return;
fVolumeEvents->Push(event);
}
int32
VolumeManager::_EventDelivererEntry(void* data)
{
return ((VolumeManager*)data)->_EventDeliverer();
}
int32
VolumeManager::_EventDeliverer()
{
while (VolumeEvent* event = fVolumeEvents->Pop()) {
if (Volume* volume = GetVolume(event->GetTarget())) {
volume->HandleEvent(event);
PutVolume(volume);
}
event->ReleaseReference();
}
return 0;
}