/* * Copyright 2001-2011, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ #include "Volume.h" #include #include #include #include #include "AutoLocker.h" #include "Compatibility.h" #include "Debug.h" #include "FileSystem.h" #include "HashMap.h" #include "kernel_interface.h" #include "KernelRequestHandler.h" #include "PortReleaser.h" #include "RequestAllocator.h" #include "Requests.h" #include "Settings.h" #include "SingleReplyRequestHandler.h" // The time after which the notification thread times out at the port and // restarts the loop. Of interest only when the FS is deleted. It is the // maximal time the destructor has to wait for the thread. static const bigtime_t kNotificationRequestTimeout = 50000; // 50 ms // #pragma mark - SelectSyncMap struct FileSystem::SelectSyncMap : public SynchronizedHashMap, int32*, Locker> { }; // #pragma mark - NodeListenerKey struct FileSystem::NodeListenerKey { NodeListenerKey(void* clientListener, dev_t device, ino_t node) : fClientListener(clientListener), fDevice(device), fNode(node) { } void* ClientListener() const { return fClientListener; } dev_t Device() const { return fDevice; } ino_t Node() const { return fNode; } uint32 HashValue() const { return (uint32)(addr_t)fClientListener ^ (uint32)fDevice ^ (uint32)fNode ^ (uint32)(fNode >> 32); } bool operator==(const NodeListenerKey& other) const { return fClientListener == other.fClientListener && fDevice == other.fDevice && fNode == other.fNode; } protected: void* fClientListener; dev_t fDevice; ino_t fNode; }; // #pragma mark - NodeListenerProxy struct FileSystem::NodeListenerProxy : NodeListenerKey, NotificationListener { NodeListenerProxy(FileSystem* fileSystem, void* clientListener, dev_t device, ino_t node) : NodeListenerKey(clientListener, device, node), fFileSystem(fileSystem) { } virtual void EventOccurred(NotificationService& service, const KMessage* event) { fFileSystem->_NodeListenerEventOccurred(this, event); } NodeListenerProxy*& HashTableLink() { return fHashTableLink; } status_t StartListening(uint32 flags) { return add_node_listener(fDevice, fNode, flags, *this); } status_t StopListening() { return remove_node_listener(fDevice, fNode, *this); } private: FileSystem* fFileSystem; NodeListenerProxy* fHashTableLink; }; // #pragma mark - NodeListenerHashDefinition struct FileSystem::NodeListenerHashDefinition { typedef NodeListenerKey KeyType; typedef NodeListenerProxy ValueType; size_t HashKey(const NodeListenerKey& key) const { return key.HashValue(); } size_t Hash(const NodeListenerProxy* value) const { return value->HashValue(); } bool Compare(const NodeListenerKey& key, const NodeListenerProxy* value) const { return key == *value; } NodeListenerProxy*& GetLink(NodeListenerProxy* value) const { return value->HashTableLink(); } }; // #pragma mark - FileSystem // constructor FileSystem::FileSystem() : fVolumes(), fName(), fTeam(-1), fNotificationPort(NULL), fNotificationThread(-1), fPortPool(), fSelectSyncs(NULL), fSettings(NULL), fUserlandServerTeam(-1), fInitialized(false), fTerminating(false) { mutex_init(&fVolumeLock, "userlandfs volumes"); mutex_init(&fVNodeOpsLock, "userlandfs vnode ops"); mutex_init(&fNodeListenersLock, "userlandfs node listeners"); } // destructor FileSystem::~FileSystem() { fTerminating = true; // wait for the notification thread to terminate if (fNotificationThread >= 0) { int32 result; wait_for_thread(fNotificationThread, &result); } // delete our data structures if (fNodeListeners != NULL) { MutexLocker nodeListenersLocker(fNodeListenersLock); NodeListenerProxy* proxy = fNodeListeners->Clear(true); while (proxy != NULL) { NodeListenerProxy* next = proxy->HashTableLink(); proxy->StopListening(); delete proxy; proxy = next; } } if (fSelectSyncs) { for (SelectSyncMap::Iterator it = fSelectSyncs->GetIterator(); it.HasNext();) { SelectSyncMap::Entry entry = it.Next(); delete entry.value; } delete fSelectSyncs; } delete fSettings; // delete vnode ops vectors -- there shouldn't be any left, though VNodeOps* ops = fVNodeOps.Clear(); int32 count = 0; while (ops != NULL) { count++; VNodeOps* next = ops->hash_link; free(ops); ops = next; } if (count > 0) WARN(("Deleted %" B_PRId32 " vnode ops vectors!\n", count)); mutex_destroy(&fVolumeLock); mutex_destroy(&fVNodeOpsLock); mutex_destroy(&fNodeListenersLock); } // Init status_t FileSystem::Init(const char* name, team_id team, Port::Info* infos, int32 count, const FSCapabilities& capabilities) { PRINT(("FileSystem::Init(\"%s\", %p, %" B_PRId32 ")\n", name, infos, count)); capabilities.Dump(); // check parameters if (!name || !infos || count < 2) RETURN_ERROR(B_BAD_VALUE); // set the name if (!fName.SetTo(name)) return B_NO_MEMORY; // init VNodeOps map status_t error = fVNodeOps.Init(); if (error != B_OK) return error; fTeam = team; fCapabilities = capabilities; // create the select sync entry map fSelectSyncs = new(nothrow) SelectSyncMap; if (!fSelectSyncs) return B_NO_MEMORY; // create the node listener proxy map fNodeListeners = new(std::nothrow) NodeListenerMap; if (fNodeListeners == NULL || fNodeListeners->Init() != B_OK) return B_NO_MEMORY; // create the request ports // the notification port fNotificationPort = new(nothrow) RequestPort(infos); if (!fNotificationPort) RETURN_ERROR(B_NO_MEMORY); error = fNotificationPort->InitCheck(); if (error != B_OK) return error; // the other request ports for (int32 i = 1; i < count; i++) { RequestPort* port = new(nothrow) RequestPort(infos + i); if (!port) RETURN_ERROR(B_NO_MEMORY); error = port->InitCheck(); if (error == B_OK) error = fPortPool.AddPort(port); if (error != B_OK) { delete port; RETURN_ERROR(error); } } // get the userland team port_info portInfo; error = get_port_info(infos[0].owner_port, &portInfo); if (error != B_OK) RETURN_ERROR(error); fUserlandServerTeam = portInfo.team; // print some info about the userland team D( PRINT((" userland team is: %" B_PRId32 "\n", fUserlandServerTeam)); int32 cookie = 0; thread_info threadInfo; while (get_next_thread_info(fUserlandServerTeam, &cookie, &threadInfo) == B_OK) { PRINT((" userland thread: %" B_PRId32 ": `%s'\n", threadInfo.thread, threadInfo.name)); } ); // load the settings fSettings = new(nothrow) Settings; if (fSettings) { status_t settingsError = fSettings->SetTo(fName.GetString()); if (settingsError != B_OK) { PRINT(("Failed to load settings: %s\n", strerror(settingsError))); delete fSettings; fSettings = NULL; } else fSettings->Dump(); } else ERROR(("Failed to allocate settings.\n")); // spawn the notification thread #if USER fNotificationThread = spawn_thread(_NotificationThreadEntry, "UFS notification thread", B_NORMAL_PRIORITY, this); #else fNotificationThread = spawn_kernel_thread(_NotificationThreadEntry, "UFS notification thread", B_NORMAL_PRIORITY, this); #endif if (fNotificationThread < 0) RETURN_ERROR(fNotificationThread); resume_thread(fNotificationThread); fInitialized = (error == B_OK); RETURN_ERROR(error); } // GetName const char* FileSystem::GetName() const { return fName.GetString(); } // GetCapabilities const FSCapabilities& FileSystem::GetCapabilities() const { return fCapabilities; } // GetPortPool RequestPortPool* FileSystem::GetPortPool() { return &fPortPool; } // Mount status_t FileSystem::Mount(fs_volume* fsVolume, const char* device, uint32 flags, const char* parameters, Volume** _volume) { // check initialization and parameters if (!fInitialized || !_volume) return B_BAD_VALUE; // create volume Volume* volume = new(nothrow) Volume(this, fsVolume); if (!volume) return B_NO_MEMORY; // add volume to the volume list MutexLocker locker(fVolumeLock); status_t error = fVolumes.PushBack(volume); locker.Unlock(); if (error != B_OK) return error; // mount volume error = volume->Mount(device, flags, parameters); if (error != B_OK) { MutexLocker locker(fVolumeLock); fVolumes.Remove(volume); locker.Unlock(); volume->ReleaseReference(); return error; } *_volume = volume; return error; } // Initialize /*status_t FileSystem::Initialize(const char* deviceName, const char* parameters, size_t len) { // get a free port RequestPort* port = fPortPool.AcquirePort(); if (!port) return B_ERROR; PortReleaser _(&fPortPool, port); // prepare the request RequestAllocator allocator(port->GetPort()); MountVolumeRequest* request; status_t error = AllocateRequest(allocator, &request); if (error != B_OK) return error; error = allocator.AllocateString(request->device, deviceName); if (error == B_OK) error = allocator.AllocateData(request->parameters, parameters, len, 1); if (error != B_OK) return error; // send the request SingleReplyRequestHandler handler(MOUNT_VOLUME_REPLY); InitializeVolumeReply* reply; error = port->SendRequest(&allocator, &handler, (Request**)&reply); if (error != B_OK) return error; RequestReleaser requestReleaser(port, reply); // process the reply if (reply->error != B_OK) return reply->error; return error; }*/ // VolumeUnmounted void FileSystem::VolumeUnmounted(Volume* volume) { MutexLocker locker(fVolumeLock); fVolumes.Remove(volume); } // GetVolume Volume* FileSystem::GetVolume(dev_t id) { MutexLocker _(fVolumeLock); for (Vector::Iterator it = fVolumes.Begin(); it != fVolumes.End(); it++) { Volume* volume = *it; if (volume->GetID() == id) { volume->AcquireReference(); return volume; } } return NULL; } // GetIOCtlInfo const IOCtlInfo* FileSystem::GetIOCtlInfo(int command) const { return (fSettings ? fSettings->GetIOCtlInfo(command) : NULL); } // AddSelectSyncEntry status_t FileSystem::AddSelectSyncEntry(selectsync* sync) { AutoLocker _(fSelectSyncs); int32* count = fSelectSyncs->Get(sync); if (!count) { count = new(nothrow) int32(0); if (!count) return B_NO_MEMORY; status_t error = fSelectSyncs->Put(sync, count); if (error != B_OK) { delete count; return error; } } (*count)++; return B_OK; } // RemoveSelectSyncEntry void FileSystem::RemoveSelectSyncEntry(selectsync* sync) { AutoLocker _(fSelectSyncs); if (int32* count = fSelectSyncs->Get(sync)) { if (--(*count) <= 0) { fSelectSyncs->Remove(sync); delete count; } } } // KnowsSelectSyncEntry bool FileSystem::KnowsSelectSyncEntry(selectsync* sync) { return fSelectSyncs->ContainsKey(sync); } // AddNodeListener status_t FileSystem::AddNodeListener(dev_t device, ino_t node, uint32 flags, void* listener) { MutexLocker nodeListenersLocker(fNodeListenersLock); // lookup the proxy NodeListenerProxy* proxy = fNodeListeners->Lookup( NodeListenerKey(listener, device, node)); if (proxy != NULL) return proxy->StartListening(flags); // it doesn't exist yet -- create it proxy = new(std::nothrow) NodeListenerProxy(this, listener, device, node); if (proxy == NULL) return B_NO_MEMORY; // start listening status_t error = proxy->StartListening(flags); if (error != B_OK) { delete proxy; return error; } fNodeListeners->Insert(proxy); return B_OK; } // RemoveNodeListener status_t FileSystem::RemoveNodeListener(dev_t device, ino_t node, void* listener) { MutexLocker nodeListenersLocker(fNodeListenersLock); // lookup the proxy NodeListenerProxy* proxy = fNodeListeners->Lookup( NodeListenerKey(listener, device, node)); if (proxy == NULL) return B_BAD_VALUE; status_t error = proxy->StopListening(); fNodeListeners->Remove(proxy); delete proxy; return error; } // GetVNodeOps VNodeOps* FileSystem::GetVNodeOps(const FSVNodeCapabilities& capabilities) { MutexLocker locker(fVNodeOpsLock); // do we already have ops for those capabilities VNodeOps* ops = fVNodeOps.Lookup(capabilities); if (ops != NULL) { ops->refCount++; return ops; } // no, create a new object fs_vnode_ops* opsVector = new(std::nothrow) fs_vnode_ops; if (opsVector == NULL) return NULL; // set the operations _InitVNodeOpsVector(opsVector, capabilities); // create the VNodeOps object ops = new(std::nothrow) VNodeOps(capabilities, opsVector); if (ops == NULL) { delete opsVector; return NULL; } fVNodeOps.Insert(ops); return ops; } // PutVNodeOps void FileSystem::PutVNodeOps(VNodeOps* ops) { MutexLocker locker(fVNodeOpsLock); if (--ops->refCount == 0) { fVNodeOps.Remove(ops); delete ops; } } // IsUserlandServerThread bool FileSystem::IsUserlandServerThread() const { thread_info info; get_thread_info(find_thread(NULL), &info); return (info.team == fUserlandServerTeam); } // _InitVNodeOpsVector void FileSystem::_InitVNodeOpsVector(fs_vnode_ops* ops, const FSVNodeCapabilities& capabilities) { memcpy(ops, &gUserlandFSVnodeOps, sizeof(fs_vnode_ops)); #undef CLEAR_UNSUPPORTED #define CLEAR_UNSUPPORTED(capability, op) \ if (!capabilities.Get(capability)) \ ops->op = NULL // vnode operations // FS_VNODE_CAPABILITY_LOOKUP: lookup // FS_VNODE_CAPABILITY_GET_VNODE_NAME: get_vnode_name // emulated in userland // FS_VNODE_CAPABILITY_PUT_VNODE: put_vnode // FS_VNODE_CAPABILITY_REMOVE_VNODE: remove_vnode // needed by Volume to clean up // asynchronous I/O CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_IO, io); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_CANCEL_IO, cancel_io); // cache file access ops->get_file_map = NULL; // never used // common operations // FS_VNODE_CAPABILITY_IOCTL: ioctl // needed by Volume CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_SET_FLAGS, set_flags); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_SELECT, select); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_DESELECT, deselect); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_FSYNC, fsync); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_READ_SYMLINK, read_symlink); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_CREATE_SYMLINK, create_symlink); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_LINK, link); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_UNLINK, unlink); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_RENAME, rename); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_ACCESS, access); // FS_VNODE_CAPABILITY_READ_STAT: read_stat // needed by Volume CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_WRITE_STAT, write_stat); // file operations CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_CREATE, create); // FS_VNODE_CAPABILITY_OPEN: open // mandatory // FS_VNODE_CAPABILITY_CLOSE: close // needed by Volume // FS_VNODE_CAPABILITY_FREE_COOKIE: free_cookie // needed by Volume CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_READ, read); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_WRITE, write); // directory operations CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_CREATE_DIR, create_dir); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_REMOVE_DIR, remove_dir); // FS_VNODE_CAPABILITY_OPEN_DIR: open_dir // mandatory // FS_VNODE_CAPABILITY_CLOSE_DIR: close_dir // needed by Volume // FS_VNODE_CAPABILITY_FREE_DIR_COOKIE: free_dir_cookie // needed by Volume CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_READ_DIR, read_dir); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_REWIND_DIR, rewind_dir); // attribute directory operations CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_OPEN_ATTR_DIR, open_attr_dir); // FS_VNODE_CAPABILITY_CLOSE_ATTR_DIR: close_attr_dir // needed by Volume // FS_VNODE_CAPABILITY_FREE_ATTR_DIR_COOKIE: free_attr_dir_cookie // needed by Volume CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_READ_ATTR_DIR, read_attr_dir); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_REWIND_ATTR_DIR, rewind_attr_dir); // attribute operations CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_CREATE_ATTR, create_attr); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_OPEN_ATTR, open_attr); // FS_VNODE_CAPABILITY_CLOSE_ATTR: close_attr // needed by Volume // FS_VNODE_CAPABILITY_FREE_ATTR_COOKIE: free_attr_cookie // needed by Volume CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_READ_ATTR, read_attr); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_WRITE_ATTR, write_attr); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_READ_ATTR_STAT, read_attr_stat); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_WRITE_ATTR_STAT, write_attr_stat); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_RENAME_ATTR, rename_attr); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_REMOVE_ATTR, remove_attr); // support for node and FS layers CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_CREATE_SPECIAL_NODE, create_special_node); CLEAR_UNSUPPORTED(FS_VNODE_CAPABILITY_GET_SUPER_VNODE, get_super_vnode); #undef CLEAR_UNSUPPORTED } // _NodeListenerEventOccurred void FileSystem::_NodeListenerEventOccurred(NodeListenerProxy* proxy, const KMessage* event) { // get a free port RequestPort* port = fPortPool.AcquirePort(); if (port == NULL) return; PortReleaser _(&fPortPool, port); // prepare the request RequestAllocator allocator(port->GetPort()); NodeMonitoringEventRequest* request; status_t error = AllocateRequest(allocator, &request); if (error != B_OK) return; error = allocator.AllocateData(request->event, event->Buffer(), event->ContentSize(), 1); if (error != B_OK) return; Thread* thread = thread_get_current_thread(); request->team = thread->team->id; request->thread = thread->id; request->user = geteuid(); request->group = getegid(); request->listener = proxy->ClientListener(); // send the request KernelRequestHandler handler(this, NODE_MONITORING_EVENT_REPLY); port->SendRequest(&allocator, &handler); } // _NotificationThreadEntry int32 FileSystem::_NotificationThreadEntry(void* data) { return ((FileSystem*)data)->_NotificationThread(); } // _NotificationThread int32 FileSystem::_NotificationThread() { // process the notification requests until the FS is deleted while (!fTerminating) { if (fNotificationPort->InitCheck() != B_OK) return fNotificationPort->InitCheck(); KernelRequestHandler handler(this, NO_REQUEST); fNotificationPort->HandleRequests(&handler, NULL, kNotificationRequestTimeout); } // We eat all remaining notification requests, so that they aren't // presented to the file system, when it is mounted next time. // TODO: We should probably use a special handler that sends an ack reply, // but ignores the requests otherwise. KernelRequestHandler handler(this, NO_REQUEST); fNotificationPort->HandleRequests(&handler, NULL, 0); return 0; }