#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "Volume.h"
#include "Block.h"
#include "BlockCache.h"
#include "Debug.h"
#include "DirItem.h"
#include "IndirectItem.h"
#include "Iterators.h"
#include "reiserfs.h"
#include "Settings.h"
#include "StatItem.h"
#include "SuperBlock.h"
#include "Tree.h"
#include "VNode.h"
extern fs_vnode_ops gReiserFSVnodeOps;
template<typename C>
static inline C min(const C &a, const C &b) { return (a < b ? a : b); }
template<typename C>
static inline C max(const C &a, const C &b) { return (a > b ? a : b); }
Volume::Volume()
: fFSVolume(NULL),
fDevice(-1),
fBlockCache(NULL),
fTree(NULL),
fSuperBlock(NULL),
fHashFunction(NULL),
fRootVNode(NULL),
fDeviceName(NULL),
fSettings(NULL),
fNegativeEntries()
{
fVolumeName[0] = '\0';
}
Volume::~Volume()
{
Unmount();
}
status_t
Volume::Identify(int fd, partition_data *partition)
{
fDevice = dup(fd);
if (fDevice < 0)
return B_ERROR;
return _ReadSuperBlock();
}
status_t
Volume::Mount(fs_volume *fsVolume, const char *path)
{
Unmount();
status_t error = (path ? B_OK : B_BAD_VALUE);
fFSVolume = fsVolume;
if (error == B_OK) {
fSettings = new(nothrow) Settings;
if (fSettings)
error = fSettings->SetTo(path);
else
error = B_NO_MEMORY;
}
if (error == B_OK) {
fDeviceName = new(nothrow) char[strlen(path) + 1];
if (fDeviceName)
strcpy(fDeviceName, path);
else
error = B_NO_MEMORY;
}
if (error == B_OK) {
fDevice = open(path, O_RDONLY);
if (fDevice < 0)
SET_ERROR(error, errno);
}
if (error == B_OK)
error = _ReadSuperBlock();
if (error == B_OK)
UpdateName(fsVolume->partition);
if (error == B_OK) {
fBlockCache = new(nothrow) BlockCache;
if (fBlockCache)
error = fBlockCache->Init(fDevice, CountBlocks(), GetBlockSize());
else
error = B_NO_MEMORY;
}
if (error == B_OK) {
fTree = new(nothrow) Tree;
if (!fTree)
error = B_NO_MEMORY;
}
if (error == B_OK) {
Block *rootBlock = NULL;
error = fBlockCache->GetBlock(fSuperBlock->GetRootBlock(), &rootBlock);
REPORT_ERROR(error);
if (error == B_OK) {
rootBlock->SetKind(Block::KIND_FORMATTED);
error = fTree->Init(this, rootBlock->ToNode(),
fSuperBlock->GetTreeHeight());
REPORT_ERROR(error);
rootBlock->Put();
}
}
if (error == B_OK) {
fRootVNode = new(nothrow) VNode;
if (fRootVNode) {
error = FindVNode(REISERFS_ROOT_PARENT_OBJECTID,
REISERFS_ROOT_OBJECTID, fRootVNode);
REPORT_ERROR(error);
if (error == B_OK) {
error = publish_vnode(fFSVolume, fRootVNode->GetID(),
fRootVNode, &gReiserFSVnodeOps, S_IFDIR, 0);
}
REPORT_ERROR(error);
} else
error = B_NO_MEMORY;
}
if (error == B_OK)
_InitHashFunction();
if (error == B_OK)
_InitNegativeEntries();
if (error != B_OK)
Unmount();
RETURN_ERROR(error);
}
status_t
Volume::Unmount()
{
if (fRootVNode) {
delete fRootVNode;
fRootVNode = NULL;
}
if (fTree) {
delete fTree;
fTree = NULL;
}
if (fSuperBlock) {
delete fSuperBlock;
fSuperBlock = NULL;
}
if (fBlockCache) {
delete fBlockCache;
fBlockCache = NULL;
}
if (fDeviceName) {
delete[] fDeviceName;
fDeviceName = NULL;
}
if (fDevice >= 0) {
close(fDevice);
fDevice = -1;
}
if (fSettings) {
delete fSettings;
fSettings = NULL;
}
fNegativeEntries.MakeEmpty();
fHashFunction = NULL;
fFSVolume = NULL;
return B_OK;
}
off_t
Volume::GetBlockSize() const
{
return fSuperBlock->GetBlockSize();
}
off_t
Volume::CountBlocks() const
{
return fSuperBlock->CountBlocks();
}
off_t
Volume::CountFreeBlocks() const
{
return fSuperBlock->CountFreeBlocks();
}
const char *
Volume::GetName() const
{
return fVolumeName;
}
void
Volume::UpdateName(partition_id partitionID)
{
if (fSuperBlock->GetLabel(fVolumeName, sizeof(fVolumeName)) == B_OK)
return;
if (get_default_partition_content_name(partitionID, "ReiserFS",
fVolumeName, sizeof(fVolumeName)) == B_OK)
return;
strlcpy(fVolumeName, "ReiserFS Volume", sizeof(fVolumeName));
}
const char *
Volume::GetDeviceName() const
{
return fDeviceName;
}
status_t
Volume::GetKeyOffsetForName(const char *name, int len, uint64 *result)
{
status_t error = (name && result ? B_OK : B_BAD_VALUE);
if (error == B_OK) {
if (fHashFunction)
*result = key_offset_for_name(fHashFunction, name, len);
else
error = B_ERROR;
}
return error;
}
status_t
Volume::GetVNode(ino_t id, VNode **node)
{
return get_vnode(GetFSVolume(), id, (void**)node);
}
status_t
Volume::PutVNode(ino_t id)
{
return put_vnode(GetFSVolume(), id);
}
status_t
Volume::FindVNode(ino_t id, VNode *node)
{
return FindVNode(VNode::GetDirIDFor(id), VNode::GetObjectIDFor(id), node);
}
status_t
Volume::FindVNode(uint32 dirID, uint32 objectID, VNode *node)
{
status_t error = (node ? B_OK : B_BAD_VALUE);
if (error == B_OK)
error = node->SetTo(dirID, objectID);
StatItem item;
if (error == B_OK) {
error = fTree->FindStatItem(dirID, objectID, &item);
if (error != B_OK) {
FATAL(("Couldn't find stat item for node "
"(%" B_PRIu32 ", %" B_PRIu32 ")\n",
dirID, objectID));
}
}
if (error == B_OK)
SET_ERROR(error, item.GetStatData(node->GetStatData(), true));
if (error == B_OK && node->IsDir()) {
DirItem dirItem;
int32 index = 0;
error = fTree->FindDirEntry(dirID, objectID, "..", &dirItem, &index);
if (error == B_OK) {
DirEntry *entry = dirItem.EntryAt(index);
node->SetParentID(entry->GetDirID(), entry->GetObjectID());
}
else {
FATAL(("failed to find `..' entry for dir node "
"(%" B_PRIu32 ", %" B_PRIu32 ")\n",
dirID, objectID));
}
}
return error;
}
status_t
Volume::FindDirEntry(VNode *dir, const char *entryName, VNode *foundNode,
bool failIfHidden)
{
status_t error = (dir && foundNode ? B_OK : B_BAD_VALUE);
DirItem item;
int32 entryIndex = 0;
if (error == B_OK) {
error = fTree->FindDirEntry(dir->GetDirID(), dir->GetObjectID(),
entryName, &item, &entryIndex);
}
if (error == B_OK) {
DirEntry *entry = item.EntryAt(entryIndex);
error = FindVNode(entry->GetDirID(), entry->GetObjectID(), foundNode);
if (error == B_OK && failIfHidden && entry->IsHidden())
error = B_ENTRY_NOT_FOUND;
}
return error;
}
status_t
Volume::ReadLink(VNode *node, char *buffer, size_t bufferSize,
size_t *linkLength)
{
if (node == NULL || linkLength == NULL)
return B_BAD_VALUE;
if (!node->IsSymlink())
return B_BAD_VALUE;
StreamReader reader(fTree, node->GetDirID(), node->GetObjectID());
status_t result = reader.InitCheck();
if (result != B_OK)
return result;
size_t bytesCopied = bufferSize;
result = reader.ReadAt(0, buffer, bufferSize, &bytesCopied);
*linkLength = reader.StreamSize();
return result;
}
status_t
Volume::FindEntry(const VNode *rootDir, const char *path, VNode *foundNode)
{
PRINT(("Volume::FindEntry(`%s')\n", path));
status_t error = (rootDir && path && foundNode ? B_OK : B_BAD_VALUE);
if (error == B_OK && (path[0] == '\0' || path[0] == '/'))
error = B_ENTRY_NOT_FOUND;
if (error == B_OK)
*foundNode = *rootDir;
while (error == B_OK && path[0] != '\0') {
PRINT((" remaining path: `%s'\n", path));
int32 len = strlen(path);
const char *componentNameEnd = strchr(path, '/');
if (!componentNameEnd)
componentNameEnd = path + len;
int32 componentLen = componentNameEnd - path;
if (componentLen >= B_FILE_NAME_LENGTH)
return B_NAME_TOO_LONG;
char component[B_FILE_NAME_LENGTH];
strncpy(component, path, componentLen);
component[componentLen] = '\0';
PRINT((" looking for dir entry: `%s'\n", component));
error = FindDirEntry(foundNode, component, foundNode);
if (error == B_OK) {
path = componentNameEnd;
while (*path == '/')
path++;
}
}
PRINT(("Volume::FindEntry(`%s') done: %s\n", path, strerror(error)));
return error;
}
bool
Volume::IsNegativeEntry(ino_t id)
{
for (int32 i = fNegativeEntries.CountItems() - 1; i >= 0; i--) {
if (fNegativeEntries.ItemAt(i) == id)
return true;
}
return false;
}
bool
Volume::IsNegativeEntry(uint32 dirID, uint32 objectID)
{
return IsNegativeEntry(VNode::GetIDFor(dirID, objectID));
}
bool
Volume::GetHideEsoteric() const
{
return fSettings->GetHideEsoteric();
}
status_t
Volume::_ReadSuperBlock()
{
status_t error = B_OK;
fSuperBlock = new(nothrow) SuperBlock;
if (fSuperBlock)
error = fSuperBlock->Init(fDevice);
else
error = B_NO_MEMORY;
if (error == B_OK && fSuperBlock->GetState() != REISERFS_VALID_FS) {
FATAL(("The superblock indicates a non-valid FS! Bailing out."));
error = B_ERROR;
}
if (error == B_OK && fSuperBlock->GetVersion() > REISERFS_VERSION_2) {
FATAL(("The superblock indicates a version greater than 2 (%u)! "
"Bailing out.", fSuperBlock->GetVersion()));
error = B_ERROR;
}
RETURN_ERROR(error);
}
static
hash_function_t
hash_function_for_code(uint32 code)
{
hash_function_t function = NULL;
switch (code) {
case TEA_HASH:
function = keyed_hash;
break;
case YURA_HASH:
function = yura_hash;
break;
case R5_HASH:
function = r5_hash;
break;
case UNSET_HASH:
default:
break;
}
return function;
}
void
Volume::_InitHashFunction()
{
fHashFunction = hash_function_for_code(fSuperBlock->GetHashFunctionCode());
if (!fHashFunction || !_VerifyHashFunction(fHashFunction)) {
INFORM(("No or wrong directory hash function. Try to detect...\n"));
uint32 code = _DetectHashFunction();
fHashFunction = hash_function_for_code(code);
if (fHashFunction) {
if (_VerifyHashFunction(fHashFunction)) {
INFORM(("Directory hash function successfully detected: "
"%" B_PRIu32 "\n", code));
} else {
fHashFunction = NULL;
INFORM(("Detected directory hash function is not the right "
"one.\n"));
}
} else
INFORM(("Failed to detect the directory hash function.\n"));
}
}
uint32
Volume::_DetectHashFunction()
{
DirEntryIterator iterator(fTree, fRootVNode->GetDirID(),
fRootVNode->GetObjectID(), DOT_DOT_OFFSET + 1);
uint32 foundCode = UNSET_HASH;
DirItem item;
int32 index = 0;
while (foundCode == UNSET_HASH
&& iterator.GetNext(&item, &index) == B_OK) {
DirEntry *entry = item.EntryAt(index);
uint64 offset = entry->GetOffset();
uint32 hashCodes[] = { TEA_HASH, YURA_HASH, R5_HASH };
int32 hashCodeCount = sizeof(hashCodes) / sizeof(uint32);
size_t nameLen = 0;
const char *name = item.EntryNameAt(index, &nameLen);
if (!name)
continue;
for (int32 i = 0; i < hashCodeCount; i++) {
hash_function_t function = hash_function_for_code(hashCodes[i]);
uint64 testOffset = key_offset_for_name(function, name, nameLen);
if (offset_hash_value(offset) == offset_hash_value(testOffset)) {
if (foundCode != UNSET_HASH) {
foundCode = UNSET_HASH;
break;
} else
foundCode = hashCodes[i];
}
}
}
return foundCode;
}
bool
Volume::_VerifyHashFunction(hash_function_t function)
{
bool result = true;
DirEntryIterator iterator(fTree, fRootVNode->GetDirID(),
fRootVNode->GetObjectID(), DOT_DOT_OFFSET + 1);
DirItem item;
int32 index = 0;
while (iterator.GetNext(&item, &index) == B_OK) {
DirEntry *entry = item.EntryAt(index);
uint64 offset = entry->GetOffset();
size_t nameLen = 0;
if (const char *name = item.EntryNameAt(index, &nameLen)) {
uint64 testOffset = key_offset_for_name(function, name, nameLen);
if (offset_hash_value(offset) != offset_hash_value(testOffset)) {
result = false;
break;
}
}
}
return result;
}
void
Volume::_InitNegativeEntries()
{
for (int32 i = 0; const char *entry = fSettings->HiddenEntryAt(i); i++) {
if (entry && strlen(entry) > 0 && entry[0] != '/') {
VNode node;
if (FindEntry(fRootVNode, entry, &node) == B_OK
&& node.GetID() != fRootVNode->GetID()) {
fNegativeEntries.AddItem(node.GetID());
} else
INFORM(("WARNING: negative entry not found: `%s'\n", entry));
}
}
}