/* * Copyright 2012-2020 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Paweł Dziepak, pdziepak@quarnos.org */ #include "FileSystem.h" #include #include #include #include #include "Request.h" #include "RootInode.h" #include "VnodeToInode.h" #include "WorkQueue.h" extern RPC::ServerManager* gRPCServerManager; extern RPC::ProgramData* CreateNFS4Server(RPC::Server* serv); FileSystem::FileSystem(const MountConfiguration& configuration) : fOpenCount(0), fOpenOwnerSequence(0), fNamedAttrs(true), fPath(NULL), fRoot(NULL), fServer(NULL), fId(1), fConfiguration(configuration) { fOpenOwner = get_random(); mutex_init(&fOpenOwnerLock, "nfs4 FileSystem::fOpenOwnerLock"); mutex_init(&fOpenLock, "nfs4 FileSystem::fOpenLock"); mutex_init(&fDelegationLock, "nfs4 FileSystem::fDelegationLock"); mutex_init(&fCreateFileLock, "nfs4 FileSystem::fCreateFileLock"); } FileSystem::~FileSystem() { if (fServer != NULL) { NFS4Server* server = reinterpret_cast(fServer->PrivateData()); if (server != NULL) server->RemoveFileSystem(this); } mutex_destroy(&fDelegationLock); mutex_destroy(&fOpenLock); mutex_destroy(&fOpenOwnerLock); mutex_destroy(&fCreateFileLock); if (fPath != NULL) { for (uint32 i = 0; fPath[i] != NULL; i++) free(const_cast(fPath[i])); } delete[] fPath; } static InodeNames* GetInodeNames(const char** root, const char* _path) { CALLED(); ASSERT(_path != NULL); int i; char* path = strdup(_path); if (path == NULL) return NULL; MemoryDeleter _(path); if (root != NULL) { for (i = 0; root[i] != NULL; i++) { char* pathEnd = strchr(path, '/'); if (pathEnd == path) { path++; i--; continue; } if (pathEnd == NULL) { path = NULL; break; } else path = pathEnd + 1; } } InodeNames* names = NULL; if (path == NULL) { names = new InodeNames; if (names == NULL) return NULL; names->AddName(NULL, ""); return names; } do { char* pathEnd = strchr(path, '/'); if (pathEnd != NULL) *pathEnd = '\0'; InodeNames* name = new InodeNames; if (name == NULL) { delete names; return NULL; } name->AddName(names, path); names = name; if (pathEnd == NULL) break; path = pathEnd + 1; } while (*path != '\0'); return names; } status_t FileSystem::Mount(FileSystem** _fs, RPC::Server* serv, const char* serverName, const char* fsPath, fs_volume* volume, const MountConfiguration& configuration) { CALLED(); ASSERT(_fs != NULL); ASSERT(serv != NULL); ASSERT(fsPath != NULL); FileSystem* fs = new(std::nothrow) FileSystem(configuration); if (fs == NULL) return B_NO_MEMORY; ObjectDeleter fsDeleter(fs); Request request(serv, fs, geteuid(), getegid()); RequestBuilder& req = request.Builder(); req.PutRootFH(); uint32 lookupCount = 0; status_t result = _ParsePath(req, lookupCount, fsPath); if (result != B_OK) return result; req.GetFH(); req.Access(); Attribute attr[] = { FATTR4_SUPPORTED_ATTRS, FATTR4_FH_EXPIRE_TYPE, FATTR4_FSID, FATTR4_FS_LOCATIONS }; req.GetAttr(attr, sizeof(attr) / sizeof(Attribute)); result = request.Send(); if (result != B_OK) return result; ReplyInterpreter& reply = request.Reply(); reply.PutRootFH(); for (uint32 i = 0; i < lookupCount; i++) reply.LookUp(); FileHandle fh; reply.GetFH(&fh); uint32 allowed; result = reply.Access(NULL, &allowed); if (result != B_OK) return result; else if ((allowed & (ACCESS4_READ | ACCESS4_LOOKUP)) != (ACCESS4_READ | ACCESS4_LOOKUP)) return B_PERMISSION_DENIED; AttrValue* values; uint32 count; result = reply.GetAttr(&values, &count); if (result != B_OK || count < 2) return result; // FATTR4_SUPPORTED_ATTRS is mandatory memcpy(fs->fSupAttrs, &values[0].fData.fValue64, sizeof(fs->fSupAttrs)); // FATTR4_FH_EXPIRE_TYPE is mandatory fs->fExpireType = values[1].fData.fValue32; // FATTR4_FSID is mandatory FileSystemId* fsid = reinterpret_cast(values[2].fData.fPointer); if (count == 4 && values[3].fAttribute == FATTR4_FS_LOCATIONS) { FSLocations* locs = reinterpret_cast(values[3].fData.fLocations); fs->fPath = locs->fRootPath; locs->fRootPath = NULL; } else fs->fPath = NULL; FileInfo fi; fs->fServer = serv; fs->fDevId = volume->id; fs->fFsId = *fsid; fs->fFsVolume = volume; fi.fHandle = fh; fi.fNames = GetInodeNames(fs->fPath, fsPath); if (fi.fNames == NULL) { delete[] values; return B_NO_MEMORY; } fi.fNames->fHandle = fh; delete[] values; Inode* inode; result = Inode::CreateInode(fs, fi, &inode); if (result != B_OK) return result; RootInode* rootInode = reinterpret_cast(inode); fs->fRoot = rootInode; char* fsName = strdup(fsPath); if (fsName == NULL) return B_NO_MEMORY; for (int i = strlen(fsName) - 1; i >= 0 && fsName[i] == '/'; i--) fsName[i] = '\0'; char* name = strrchr(fsName, '/'); if (name != NULL) rootInode->SetName(name + 1); else if (fsName[0] != '\0') rootInode->SetName(fsName); else rootInode->SetName(serverName); free(fsName); fs->NFSServer()->AddFileSystem(fs); *_fs = fs; fsDeleter.Detach(); return B_OK; } status_t FileSystem::GetInode(ino_t id, Inode** _inode) { CALLED(); ASSERT(_inode != NULL); FileInfo fi; status_t result = fInoIdMap.GetFileInfo(&fi, id); if (result != B_OK) return result; Inode* inode; result = Inode::CreateInode(this, fi, &inode); if (result != B_OK) return result; *_inode = inode; return B_OK; } status_t FileSystem::Migrate(const RPC::Server* serv) { CALLED(); ASSERT(serv != NULL); MutexLocker _(fOpenLock); if (serv != fServer) return B_OK; if (!fRoot->ProbeMigration()) return B_OK; AttrValue* values; status_t result = fRoot->GetLocations(&values); if (result != B_OK) return result; FSLocations* locs = reinterpret_cast(values[0].fData.fLocations); RPC::Server* server = fServer; for (uint32 i = 0; i < locs->fCount; i++) { for (uint32 j = 0; j < locs->fLocations[i].fCount; j++) { AddressResolver resolver(locs->fLocations[i].fLocations[j]); if (gRPCServerManager->Acquire(&fServer, &resolver, CreateNFS4Server) == B_OK) { if (fPath != NULL) { for (uint32 i = 0; fPath[i] != NULL; i++) free(const_cast(fPath[i])); } delete[] fPath; fPath = locs->fLocations[i].fRootPath; locs->fLocations[i].fRootPath = NULL; if (fPath == NULL) { gRPCServerManager->Release(fServer); fServer = server; delete[] values; return B_NO_MEMORY; } break; } } } delete[] values; if (server == fServer) { gRPCServerManager->Release(server); return B_ERROR; } NFS4Server* old = reinterpret_cast(server->PrivateData()); old->RemoveFileSystem(this); NFSServer()->AddFileSystem(this); gRPCServerManager->Release(server); return B_OK; } DoublyLinkedList& FileSystem::OpenFilesLock() { CALLED(); mutex_lock(&fOpenLock); return fOpenFiles; } void FileSystem::OpenFilesUnlock() { CALLED(); mutex_unlock(&fOpenLock); } void FileSystem::AddOpenFile(OpenState* state) { CALLED(); ASSERT(state != NULL); MutexLocker _(fOpenLock); fOpenFiles.InsertBefore(fOpenFiles.Head(), state); NFSServer()->IncUsage(); } void FileSystem::RemoveOpenFile(OpenState* state) { CALLED(); ASSERT(state != NULL); MutexLocker _(fOpenLock); fOpenFiles.Remove(state); NFSServer()->DecUsage(); } DoublyLinkedList& FileSystem::DelegationsLock() { CALLED(); mutex_lock(&fDelegationLock); return fDelegationList; } void FileSystem::DelegationsUnlock() { CALLED(); mutex_unlock(&fDelegationLock); } void FileSystem::AddDelegation(Delegation* delegation) { CALLED(); ASSERT(delegation != NULL); MutexLocker _(fDelegationLock); fDelegationList.InsertBefore(fDelegationList.Head(), delegation); fHandleToDelegation.Remove(delegation->fInfo.fHandle); fHandleToDelegation.Insert(delegation->fInfo.fHandle, delegation); } void FileSystem::RemoveDelegation(Delegation* delegation) { CALLED(); ASSERT(delegation != NULL); MutexLocker _(fDelegationLock); fDelegationList.Remove(delegation); fHandleToDelegation.Remove(delegation->fInfo.fHandle); } Delegation* FileSystem::GetDelegation(const FileHandle& handle) { CALLED(); MutexLocker _(fDelegationLock); AVLTreeMap::Iterator it; it = fHandleToDelegation.Find(handle); if (!it.HasCurrent()) return NULL; return it.Current(); } /*! Mark this node removed and free it to ensure that no operation will try to use it. @pre We hold a VFS ref to this node. @post The ref needs to be released by the caller. */ status_t FileSystem::TrashStaleNode(Inode* inode) { ino_t ino = inode->ID(); INFORM("TrashStaleNode %p %" B_PRIdINO "\n", inode, ino); inode->SetStale(); status_t result = remove_vnode(fFsVolume, ino); ASSERT(result == B_OK); return result; } /*! Check whether the server node still has any hard links (including links that the client may be unaware of). If none, mark the client node removed. */ status_t FileSystem::TrashIfStale(Inode* inode) { struct stat stat; status_t result = inode->Stat(&stat, NULL, true); if (result != B_OK) result = TrashStaleNode(inode); return result; } /*! Used when creating a file to check for a stale node with the same ino. If it exists, the stale node is deleted. @param newID The file ID assigned by the server to a file now being created. @param handle The handle assigned by the server to the same file. @pre The caller has not yet updated fInoIdMap with the FileInfo of the file that we are creating. The VFS list of vnodes has also not been updated yet. @post Any stale node object with this ID is gone. Any stale entry in fInoIdMap is still present, and will be replaced when the caller calls fInoIdMap->AddName. */ void FileSystem::EnsureNoCollision(ino_t newId, const FileHandle& handle) { VnodeToInode* existingVti = NULL; status_t result = get_vnode(fFsVolume, newId, reinterpret_cast(&existingVti)); if (result == B_OK) { // We haven't finished creating the new node yet, so whatever get_vnode returned must be // stale since the server just re-issued its inode number. ASSERT(handle != existingVti->GetPointer()->fInfo.fHandle); result = TrashStaleNode(existingVti->GetPointer()); if (result != B_OK) INFORM("EnsureNoCollision: Couldn't trash stale node %" B_PRIdINO "\n", newId); put_vnode(fFsVolume, newId); } return; } /*! Delete a name from a client-side node after someone else has unlinked that name on the server side. If that was the last name known to the client, call TrashIfStale. @param missingName A previously cached file name that is no longer valid. */ void FileSystem::ServerUnlinkCleanup(ino_t id, Inode* parent, const char* missingName) { FileInfo fileInfo; status_t result = fInoIdMap.GetFileInfo(&fileInfo, id); VnodeToInode* vti = NULL; result = get_vnode(fFsVolume, id, reinterpret_cast(&vti)); if (result == B_OK) { bool noRemainingNames = false; noRemainingNames = fileInfo.fNames->RemoveName(parent->fInfo.fNames, missingName); if (noRemainingNames) { // This client knows of no hard links to this node. result = TrashIfStale(vti->GetPointer()); if (result != B_OK) INFORM("ServerUnlinkCleanup: Couldn't trash stale node %" B_PRIdINO "\n", id); } put_vnode(fFsVolume, id); } return; } void FileSystem::Dump(void (*xprintf)(const char*, ...)) { xprintf("FileSystem at %p\n", this); bool dumpDelegations = true; bool dumpOpenFiles = true; if (xprintf != kprintf) { status_t status = mutex_trylock(&fDelegationLock); if (status != B_OK) dumpDelegations = false; status = mutex_trylock(&fOpenLock); if (status != B_OK) dumpOpenFiles = false; } _DumpLocked(xprintf, dumpDelegations, dumpOpenFiles); if (xprintf != kprintf) { if (dumpDelegations) mutex_unlock(&fDelegationLock); if (dumpOpenFiles) mutex_unlock(&fOpenLock); } xprintf("\n"); fInoIdMap.Dump(xprintf); xprintf("\n"); gWorkQueue->Dump(xprintf); return; } status_t FileSystem::_ParsePath(RequestBuilder& req, uint32& count, const char* _path) { CALLED(); ASSERT(_path != NULL); char* path = strdup(_path); if (path == NULL) return B_NO_MEMORY; char* pathStart = path; char* pathEnd; while (pathStart != NULL) { pathEnd = strchr(pathStart, '/'); if (pathEnd != NULL) *pathEnd = '\0'; if (pathEnd != pathStart) { if (!strcmp(pathStart, "..")) { req.LookUpUp(); count++; } else if (strcmp(pathStart, ".")) { req.LookUp(pathStart); count++; } } if (pathEnd != NULL && pathEnd[1] != '\0') pathStart = pathEnd + 1; else pathStart = NULL; } free(path); return B_OK; } void FileSystem::_DumpLocked(void (*xprintf)(const char*, ...), bool dumpDelegations, bool dumpOpenFiles) const { xprintf("\tRootInode at %p\n", fRoot); xprintf("\tfOpenFiles\n", fOpenFiles); if (dumpOpenFiles) { uint64 entries = 0; for (DoublyLinkedList::ConstIterator it = fOpenFiles.GetIterator(); const OpenState* state = it.Next(); ++entries) { xprintf("\t\tOpenState at %p for ino %" B_PRIdINO "\n", state, state->fInfo.fFileId); } if (entries == 0) xprintf("\t\tNone\n"); } else { xprintf("\tfOpenLock is locked\n"); } xprintf("\tDelegations\n"); if (dumpDelegations) { uint64 entries = 0; for (DoublyLinkedList::ConstIterator it = fDelegationList.GetIterator(); const Delegation* del = it.Next(); ++entries) { xprintf("\t\tDelegation at %p for Inode at %p (ino %" B_PRIdINO ")\n", del, del->GetInode(), del->GetInode()->ID()); } if (entries == 0) xprintf("\t\tNone"); } else { xprintf("\tfDelegationLock is locked\n"); } return; }