⛏️ index : haiku.git

/*
 * Copyright 2011-2014, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include "packagefs.h"

#include <errno.h>
#include <unistd.h>

#include <package/hpkg/DataReader.h>
#include <package/hpkg/ErrorOutput.h>
#include <package/hpkg/PackageDataReader.h>
#include <package/hpkg/PackageEntry.h>
#include <package/hpkg/PackageEntryAttribute.h>
#include <package/hpkg/PackageFileHeapReader.h>
#include <package/hpkg/PackageReaderImpl.h>

#include <AutoDeleter.h>
#include <FdIO.h>

#include <util/DoublyLinkedList.h>

#include <Referenceable.h>

#include <boot/PathBlocklist.h>
#include <boot/platform.h>

#include "PackageSettingsItem.h"


#if 0
#	define RETURN_ERROR(error) return (error);
#else
#	define RETURN_ERROR(err)												\
	{																		\
		status_t _status = err;												\
		if (_status < B_OK)													\
			dprintf("%s:%d: %s\n", __FILE__, __LINE__, strerror(_status));	\
		return _status;														\
	}
#endif


using namespace BPackageKit;
using namespace BPackageKit::BHPKG;
using BPackageKit::BHPKG::BPrivate::PackageFileHeapReader;
using BPackageKit::BHPKG::BPrivate::PackageReaderImpl;

using namespace PackageFS;


namespace PackageFS {


struct PackageDirectory;
struct PackageNode;
struct PackageVolume;


static status_t create_node(PackageNode* packageNode, ::Node*& _node);


// #pragma mark - PackageNode


struct PackageNode : DoublyLinkedListLinkImpl<PackageNode> {
	PackageNode(PackageVolume* volume, mode_t mode)
		:
		fVolume(volume),
		fParentDirectory(NULL),
		fName(NULL),
		fNodeID(0),
		fMode(mode)
	{
	}

	virtual ~PackageNode()
	{
		free(fName);
	}

	status_t Init(PackageDirectory* parentDir, const char* name, ino_t nodeID)
	{
		fParentDirectory = parentDir;
		fName = strdup(name);
		fNodeID = nodeID;

		return fName != NULL ? B_OK : B_NO_MEMORY;
	}

	PackageVolume* Volume() const
	{
		return fVolume;
	}

	const char* Name() const
	{
		return fName;
	}

	ino_t NodeID() const
	{
		return fNodeID;
	}

	mode_t Mode() const
	{
		return fMode;
	}

	virtual void RemoveEntry(const char* path)
	{
	}

protected:
	PackageVolume*		fVolume;
	PackageDirectory*	fParentDirectory;
	char*				fName;
	ino_t				fNodeID;
	mode_t				fMode;
};


// #pragma mark - PackageFile


struct PackageFile : PackageNode {
	PackageFile(PackageVolume* volume, mode_t mode, const BPackageData& data)
		:
		PackageNode(volume, mode),
		fData(data)
	{
	}

	const BPackageData& Data() const
	{
		return fData;
	}

	off_t Size() const
	{
		return fData.Size();
	}

private:
	BPackageData	fData;
};


// #pragma mark - PackageSymlink


struct PackageSymlink : PackageNode {
	PackageSymlink(PackageVolume* volume, mode_t mode)
		:
		PackageNode(volume, mode),
		fPath(NULL)
	{
	}

	~PackageSymlink()
	{
		free(fPath);
	}

	status_t SetSymlinkPath(const char* path)
	{
		fPath = strdup(path);
		return fPath != NULL ? B_OK : B_NO_MEMORY;
	}

	const char* SymlinkPath() const
	{
		return fPath;
	}

private:
	char*	fPath;
};


// #pragma mark - PackageDirectory


struct PackageDirectory : PackageNode {
	PackageDirectory(PackageVolume* volume, mode_t mode)
		:
		PackageNode(volume, mode)
	{
	}

	~PackageDirectory()
	{
		while (PackageNode* node = fEntries.RemoveHead())
			delete node;
	}

	void AddChild(PackageNode* node)
	{
		fEntries.Add(node);
	}

	PackageNode* FirstChild() const
	{
		return fEntries.Head();
	}

	PackageNode* NextChild(PackageNode* child) const
	{
		return fEntries.GetNext(child);
	}

	PackageNode* Lookup(const char* name)
	{
		if (strcmp(name, ".") == 0)
			return this;
		if (strcmp(name, "..") == 0)
			return fParentDirectory;

		return _LookupChild(name, strlen(name));
	}

	virtual void RemoveEntry(const char* path)
	{
		const char* componentEnd = strchr(path, '/');
		if (componentEnd == NULL)
			componentEnd = path + strlen(path);

		PackageNode* child = _LookupChild(path, componentEnd - path);
		if (child == NULL)
			return;

		if (*componentEnd == '\0') {
			// last path component -- delete the child
			fEntries.Remove(child);
			delete child;
		} else {
			// must be a directory component -- continue resolving the path
			child->RemoveEntry(componentEnd + 1);
		}
	}

private:
	typedef DoublyLinkedList<PackageNode>	NodeList;

private:
	PackageNode* _LookupChild(const char* name, size_t nameLength)
	{
		for (NodeList::Iterator it = fEntries.GetIterator();
				PackageNode* child = it.Next();) {
			if (strncmp(child->Name(), name, nameLength) == 0
				&& child->Name()[nameLength] == '\0') {
				return child;
			}
		}

		return NULL;
	}

private:
	NodeList	fEntries;
};


// #pragma mark - PackageLoaderErrorOutput


struct PackageLoaderErrorOutput : BErrorOutput {
	PackageLoaderErrorOutput()
	{
	}

	virtual void PrintErrorVarArgs(const char* format, va_list args)
	{
		char buffer[256];
		vsnprintf(buffer, sizeof(buffer), format, args);
		dprintf("%s", buffer);
	}
};


// #pragma mark - PackageVolume


struct PackageVolume : BReferenceable, private PackageLoaderErrorOutput {
	PackageVolume()
		:
		fNextNodeID(1),
		fRootDirectory(this, S_IFDIR),
		fHeapReader(NULL),
		fFile(NULL)
	{
	}

	~PackageVolume()
	{
		delete fHeapReader;
		delete fFile;
	}

	status_t Init(int fd, const PackageFileHeapReader* heapReader)
	{
		status_t error = fRootDirectory.Init(&fRootDirectory, ".",
			NextNodeID());
		if (error != B_OK)
			return error;

		fd = dup(fd);
		if (fd < 0)
			return errno;

		fFile = new(std::nothrow) BFdIO(fd, true);
		if (fFile == NULL) {
			close(fd);
			return B_NO_MEMORY;
		}

		// clone a heap reader and adjust it for our use
		fHeapReader = heapReader->Clone();
		if (fHeapReader == NULL)
			return B_NO_MEMORY;

		fHeapReader->SetErrorOutput(this);
		fHeapReader->SetFile(fFile);

		return B_OK;
	}

	PackageDirectory* RootDirectory()
	{
		return &fRootDirectory;
	}

	ino_t NextNodeID()
	{
		return fNextNodeID++;
	}

	status_t CreateFileDataReader(const BPackageData& data,
		BAbstractBufferedDataReader*& _reader)
	{
		return BPackageDataReaderFactory().CreatePackageDataReader(fHeapReader,
			data, _reader);
	}

private:
	ino_t						fNextNodeID;
	PackageDirectory			fRootDirectory;
	PackageFileHeapReader*		fHeapReader;
	BPositionIO*				fFile;
};


// #pragma mark - PackageLoaderContentHandler


struct PackageLoaderContentHandler : BPackageContentHandler {
	PackageLoaderContentHandler(PackageVolume* volume,
		PackageSettingsItem* settingsItem)
		:
		fVolume(volume),
		fSettingsItem(settingsItem),
		fLastSettingsEntry(NULL),
		fLastSettingsEntryEntry(NULL),
		fErrorOccurred(false)
	{
	}

	status_t Init()
	{
		return B_OK;
	}

	virtual status_t HandleEntry(BPackageEntry* entry)
	{
		if (fErrorOccurred
			|| (fLastSettingsEntry != NULL
				&& fLastSettingsEntry->IsBlocked())) {
			return B_OK;
		}

		PackageDirectory* parentDir = NULL;
		if (const BPackageEntry* parentEntry = entry->Parent()) {
			if (!S_ISDIR(parentEntry->Mode()))
				RETURN_ERROR(B_BAD_DATA);

			parentDir = static_cast<PackageDirectory*>(
				(PackageNode*)parentEntry->UserToken());
		}

		if (fSettingsItem != NULL
			&& (parentDir == NULL
				|| entry->Parent() == fLastSettingsEntryEntry)) {
			PackageSettingsItem::Entry* settingsEntry
				= fSettingsItem->FindEntry(fLastSettingsEntry, entry->Name());
			if (settingsEntry != NULL) {
				fLastSettingsEntry = settingsEntry;
				fLastSettingsEntryEntry = entry;
				if (fLastSettingsEntry->IsBlocked())
					return B_OK;
			}
		}

		if (parentDir == NULL)
			parentDir = fVolume->RootDirectory();

		status_t error;

		// get the file mode -- filter out write permissions
		mode_t mode = entry->Mode() & ~(mode_t)(S_IWUSR | S_IWGRP | S_IWOTH);

		// create the package node
		PackageNode* node;
		if (S_ISREG(mode)) {
			// file
			node = new(std::nothrow) PackageFile(fVolume, mode, entry->Data());
		} else if (S_ISLNK(mode)) {
			// symlink
			PackageSymlink* symlink = new(std::nothrow) PackageSymlink(
				fVolume, mode);
			if (symlink == NULL)
				RETURN_ERROR(B_NO_MEMORY);

			error = symlink->SetSymlinkPath(entry->SymlinkPath());
			if (error != B_OK) {
				delete symlink;
				return error;
			}

			node = symlink;
		} else if (S_ISDIR(mode)) {
			// directory
			node = new(std::nothrow) PackageDirectory(fVolume, mode);
		} else
			RETURN_ERROR(B_BAD_DATA);

		if (node == NULL)
			RETURN_ERROR(B_NO_MEMORY);

		error = node->Init(parentDir, entry->Name(), fVolume->NextNodeID());
		if (error != B_OK) {
			delete node;
			RETURN_ERROR(error);
		}

		// add it to the parent directory
		parentDir->AddChild(node);

		entry->SetUserToken(node);

		return B_OK;
	}

	virtual status_t HandleEntryAttribute(BPackageEntry* entry,
		BPackageEntryAttribute* attribute)
	{
		// attributes aren't needed in the boot loader
		return B_OK;
	}

	virtual status_t HandleEntryDone(BPackageEntry* entry)
	{
		if (entry == fLastSettingsEntryEntry) {
			fLastSettingsEntryEntry = entry->Parent();
			fLastSettingsEntry = fLastSettingsEntry->Parent();
		}

		return B_OK;
	}

	virtual status_t HandlePackageAttribute(
		const BPackageInfoAttributeValue& value)
	{
		// TODO?
		return B_OK;
	}

	virtual void HandleErrorOccurred()
	{
		fErrorOccurred = true;
	}

private:
	PackageVolume*				fVolume;
	const PackageSettingsItem*	fSettingsItem;
	PackageSettingsItem::Entry*	fLastSettingsEntry;
	const BPackageEntry*		fLastSettingsEntryEntry;
	bool						fErrorOccurred;
};


// #pragma mark - File


struct File : ::Node {
	File(PackageFile* file)
		:
		fFile(file)
	{
		fFile->Volume()->AcquireReference();
	}

	~File()
	{
		fFile->Volume()->ReleaseReference();
	}

	virtual ssize_t ReadAt(void* cookie, off_t pos, void* buffer,
		size_t bufferSize)
	{
		off_t size = fFile->Size();
		if (pos < 0 || pos > size)
			return B_BAD_VALUE;
		if (pos + (off_t)bufferSize > size)
			bufferSize = size - pos;

		if (bufferSize > 0) {
			BAbstractBufferedDataReader* dataReader
				= (BAbstractBufferedDataReader*)cookie;
			status_t error = dataReader->ReadData(pos, buffer, bufferSize);
			if (error != B_OK)
				return error;
		}

		return bufferSize;
	}

	virtual ssize_t WriteAt(void* cookie, off_t pos, const void *buffer,
		size_t bufferSize)
	{
		return B_READ_ONLY_DEVICE;
	}

	virtual status_t GetName(char* nameBuffer, size_t bufferSize) const
	{
		strlcpy(nameBuffer, fFile->Name(), bufferSize);
		return B_OK;
	}

	virtual status_t Open(void** _cookie, int mode)
	{
		if ((mode & O_ACCMODE) != O_RDONLY && (mode & O_ACCMODE) != O_RDWR)
			return B_NOT_ALLOWED;

		BAbstractBufferedDataReader* dataReader;
		status_t error = fFile->Volume()->CreateFileDataReader(fFile->Data(),
			dataReader);
		if (error != B_OK)
			return error;

		*_cookie = dataReader;
		Acquire();
		return B_OK;
	}

	virtual status_t Close(void* cookie)
	{
		BAbstractBufferedDataReader* dataReader
			= (BAbstractBufferedDataReader*)cookie;
		delete dataReader;
		Release();
		return B_OK;
	}


	virtual int32 Type() const
	{
		return fFile->Mode() & S_IFMT;
	}

	virtual off_t Size() const
	{
		return fFile->Size();
	}

	virtual ino_t Inode() const
	{
		return fFile->NodeID();
	}

private:
	PackageFile*	fFile;
};


// #pragma mark - Symlink


struct Symlink : ::Node {
	Symlink(PackageSymlink* symlink)
		:
		fSymlink(symlink)
	{
		fSymlink->Volume()->AcquireReference();
	}

	~Symlink()
	{
		fSymlink->Volume()->ReleaseReference();
	}

	virtual ssize_t ReadAt(void* cookie, off_t pos, void* buffer,
		size_t bufferSize)
	{
		return B_BAD_VALUE;
	}

	virtual ssize_t WriteAt(void* cookie, off_t pos, const void *buffer,
		size_t bufferSize)
	{
		return B_READ_ONLY_DEVICE;
	}

	virtual status_t ReadLink(char* buffer, size_t bufferSize)
	{
		const char* path = fSymlink->SymlinkPath();
		size_t size = strlen(path) + 1;

		if (size > bufferSize)
			return B_BUFFER_OVERFLOW;

		memcpy(buffer, path, size);
		return B_OK;
	}

	virtual status_t GetName(char* nameBuffer, size_t bufferSize) const
	{
		strlcpy(nameBuffer, fSymlink->Name(), bufferSize);
		return B_OK;
	}

	virtual int32 Type() const
	{
		return fSymlink->Mode() & S_IFMT;
	}

	virtual off_t Size() const
	{
		return strlen(fSymlink->SymlinkPath()) + 1;
	}

	virtual ino_t Inode() const
	{
		return fSymlink->NodeID();
	}

private:
	PackageSymlink*	fSymlink;
};


// #pragma mark - Directory


struct Directory : ::Directory {
	Directory(PackageDirectory* symlink)
		:
		fDirectory(symlink)
	{
		fDirectory->Volume()->AcquireReference();
	}

	~Directory()
	{
		fDirectory->Volume()->ReleaseReference();
	}

	void RemoveEntry(const char* path)
	{
		fDirectory->RemoveEntry(path);
	}

	virtual ssize_t ReadAt(void* cookie, off_t pos, void* buffer,
		size_t bufferSize)
	{
		return B_IS_A_DIRECTORY;
	}

	virtual ssize_t WriteAt(void* cookie, off_t pos, const void *buffer,
		size_t bufferSize)
	{
		return B_IS_A_DIRECTORY;
	}

	virtual status_t GetName(char* nameBuffer, size_t bufferSize) const
	{
		strlcpy(nameBuffer, fDirectory->Name(), bufferSize);
		return B_OK;
	}

	virtual int32 Type() const
	{
		return fDirectory->Mode() & S_IFMT;
	}

	virtual ino_t Inode() const
	{
		return fDirectory->NodeID();
	}

	virtual status_t Open(void** _cookie, int mode)
	{
		if ((mode & O_ACCMODE) != O_RDONLY && (mode & O_ACCMODE) != O_RDWR)
			return B_NOT_ALLOWED;

		Cookie* cookie = new(std::nothrow) Cookie;
		if (cookie == NULL)
			return B_NO_MEMORY;

		cookie->nextChild = fDirectory->FirstChild();

		Acquire();
		*_cookie = cookie;
		return B_OK;
	}

	virtual status_t Close(void* _cookie)
	{
		Cookie* cookie = (Cookie*)_cookie;
		delete cookie;
		Release();
		return B_OK;
	}

	virtual Node* LookupDontTraverse(const char* name)
	{
		// look up the child
		PackageNode* child = fDirectory->Lookup(name);
		if (child == NULL)
			return NULL;

		// create the node
		::Node* node;
		return create_node(child, node) == B_OK ? node : NULL;
	}

	virtual status_t GetNextEntry(void* _cookie, char* nameBuffer,
		size_t bufferSize)
	{
		Cookie* cookie = (Cookie*)_cookie;
		PackageNode* child = cookie->nextChild;
		if (child == NULL)
			return B_ENTRY_NOT_FOUND;

		cookie->nextChild = fDirectory->NextChild(child);

		strlcpy(nameBuffer, child->Name(), bufferSize);
		return B_OK;
	}

	virtual status_t GetNextNode(void* _cookie, Node** _node)
	{
		Cookie* cookie = (Cookie*)_cookie;
		PackageNode* child = cookie->nextChild;
		if (child == NULL)
			return B_ENTRY_NOT_FOUND;

		cookie->nextChild = fDirectory->NextChild(child);

		return create_node(child, *_node);
	}

	virtual status_t Rewind(void* _cookie)
	{
		Cookie* cookie = (Cookie*)_cookie;
		cookie->nextChild = NULL;
		return B_OK;
	}

	virtual bool IsEmpty()
	{
		return fDirectory->FirstChild() == NULL;
	}

private:
	struct Cookie {
		PackageNode*	nextChild;
	};


private:
	PackageDirectory*	fDirectory;
};


// #pragma mark -


static status_t
create_node(PackageNode* packageNode, ::Node*& _node)
{
	if (packageNode == NULL)
		return B_BAD_VALUE;

	::Node* node;
	switch (packageNode->Mode() & S_IFMT) {
		case S_IFREG:
			node = new(std::nothrow) File(
				static_cast<PackageFile*>(packageNode));
			break;
		case S_IFLNK:
			node = new(std::nothrow) Symlink(
				static_cast<PackageSymlink*>(packageNode));
			break;
		case S_IFDIR:
			node = new(std::nothrow) Directory(
				static_cast<PackageDirectory*>(packageNode));
			break;
		default:
			return B_BAD_VALUE;
	}

	if (node == NULL)
		return B_NO_MEMORY;

	_node = node;
	return B_OK;
}


}	// namespace PackageFS


status_t
packagefs_mount_file(int fd, ::Directory* systemDirectory,
	::Directory*& _mountedDirectory)
{
	PackageLoaderErrorOutput errorOutput;
 	PackageReaderImpl packageReader(&errorOutput);
	status_t error = packageReader.Init(fd, false, 0);
 	if (error != B_OK)
 		RETURN_ERROR(error);

	// create the volume
	PackageVolume* volume = new(std::nothrow) PackageVolume;
	if (volume == NULL)
		return B_NO_MEMORY;
	BReference<PackageVolume> volumeReference(volume, true);

	error = volume->Init(fd, packageReader.RawHeapReader());
	if (error != B_OK)
		RETURN_ERROR(error);

	// load settings for the package
	PackageSettingsItem* settings = PackageSettingsItem::Load(systemDirectory,
		"haiku");
	ObjectDeleter<PackageSettingsItem> settingsDeleter(settings);

	// parse content -- this constructs the entry/node tree
	PackageLoaderContentHandler handler(volume, settings);
	error = handler.Init();
	if (error != B_OK)
		RETURN_ERROR(error);

	error = packageReader.ParseContent(&handler);
	if (error != B_OK)
		RETURN_ERROR(error);

	// create a VFS node for the root node
	::Node* rootNode;
	error = create_node(volume->RootDirectory(), rootNode);
	if (error != B_OK)
		RETURN_ERROR(error);

	_mountedDirectory = static_cast< ::Directory*>(rootNode);
	return B_OK;
}


void
packagefs_apply_path_blocklist(::Directory* systemDirectory,
	const PathBlocklist& pathBlocklist)
{
	PackageFS::Directory* directory
		= static_cast<PackageFS::Directory*>(systemDirectory);

	for (PathBlocklist::Iterator it = pathBlocklist.GetIterator();
		BlockedPath* path = it.Next();) {
		directory->RemoveEntry(path->Path());
	}
}