* 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);
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;
};
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;
};
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;
};
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') {
fEntries.Remove(child);
delete child;
} else {
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;
};
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);
}
};
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;
}
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;
};
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;
mode_t mode = entry->Mode() & ~(mode_t)(S_IWUSR | S_IWGRP | S_IWOTH);
PackageNode* node;
if (S_ISREG(mode)) {
node = new(std::nothrow) PackageFile(fVolume, mode, entry->Data());
} else if (S_ISLNK(mode)) {
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)) {
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);
}
parentDir->AddChild(node);
entry->SetUserToken(node);
return B_OK;
}
virtual status_t HandleEntryAttribute(BPackageEntry* entry,
BPackageEntryAttribute* attribute)
{
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)
{
return B_OK;
}
virtual void HandleErrorOccurred()
{
fErrorOccurred = true;
}
private:
PackageVolume* fVolume;
const PackageSettingsItem* fSettingsItem;
PackageSettingsItem::Entry* fLastSettingsEntry;
const BPackageEntry* fLastSettingsEntryEntry;
bool fErrorOccurred;
};
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;
};
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;
};
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)
{
PackageNode* child = fDirectory->Lookup(name);
if (child == NULL)
return NULL;
::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;
};
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;
}
}
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);
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);
PackageSettingsItem* settings = PackageSettingsItem::Load(systemDirectory,
"haiku");
ObjectDeleter<PackageSettingsItem> settingsDeleter(settings);
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);
::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());
}
}