⛏️ index : haiku.git

/*
 * 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 <string.h>

#include <AutoDeleter.h>
#include <lock.h>
#include <util/Random.h>

#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<uint64>();

	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<NFS4Server*>(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<char*>(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<FileSystem> 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<FileSystemId*>(values[2].fData.fPointer);

	if (count == 4 && values[3].fAttribute == FATTR4_FS_LOCATIONS) {
		FSLocations* locs
			= reinterpret_cast<FSLocations*>(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<RootInode*>(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<FSLocations*>(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<char*>(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<NFS4Server*>(server->PrivateData());
	old->RemoveFileSystem(this);
	NFSServer()->AddFileSystem(this);

	gRPCServerManager->Release(server);

	return B_OK;
}


DoublyLinkedList<OpenState>&
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<Delegation>&
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<FileHandle, Delegation*>::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<void**>(&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<void**>(&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<OpenState>::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<Delegation>::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;
}