#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); }
\class Volume
\brief Represents a volume.
The Volume class bundles all functionality related to a volume.
It knows the superblock and has some basic functionality needed
for handling VNodes. Actually it should be the layer that provides the
abstraction from VNodes. The design is not strict in this respect
(the whole thing evolved while I was in the process of understanding
the structures ;-), since the kernel interface does a good part of that
too (e.g. concerning dir iteration).
*/
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);
}
\note The method does not initialize the parent ID for non-directory nodes.
\param id ID of the node to be found.
\param node pointer to a pre-allocated VNode to be initialized to
the found node.
\return \c B_OK, if everything went fine.
*/
status_t
Volume::FindVNode(ino_t id, VNode *node)
{
return FindVNode(VNode::GetDirIDFor(id), VNode::GetObjectIDFor(id), node);
}
\note The method does not initialize the parent ID for non-directory nodes.
\param dirID Directory ID of the node to be found.
\param objectID Object ID of the node to be found.
\param node pointer to a pre-allocated VNode to be initialized to
the found node.
\return \c B_OK, if everything went fine.
*/
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;
}
\note Must not be called with \a entryName "." or ".."!
\param dir The directory.
\param entryName Name of the entry.
\param foundNode pointer to a pre-allocated VNode to be initialized to
the found entry.
\param failIfHidden The method shall fail, if the entry is hidden.
\return \c B_OK, if everything went fine.
*/
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;
}
\note It is not check whether the object is a symlink or not! The caller
is responsible for that.
\param node The symlink to be read from.
\param buffer The buffer into which the data shall be written.
\param bufferSize The number of bytes to be read.
\param linkLength Pointer to a size_t to be set to the length of the link.
\return \c B_OK, if everything went fine.
*/
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));
}
}
}