/*
* Copyright 2008-2010, Axel Dörfler, axeld@pinc-software.de.
* Copyright 2011-2019, Jérôme Duval, jerome.duval@gmail.com.
* Copyright 2014 Haiku, Inc. All rights reserved.
*
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, axeld@pinc-software.de
* Jérôme Duval, korli@users.berlios.de
* John Scipione, jscipione@gmail.com
*/
//! Superblock, mounting, etc.
#include "Volume.h"
#include <errno.h>
#include <unistd.h>
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fs_cache.h>
#include <fs_volume.h>
#include <util/AutoLock.h>
#include "CachedBlock.h"
#include "Inode.h"
#include "Utility.h"
//#define TRACE_EXFAT
#ifdef TRACE_EXFAT
# define TRACE(x...) dprintf("\33[34mexfat:\33[0m " x)
#else
# define TRACE(x...) ;
#endif
# define ERROR(x...) dprintf("\33[34mexfat:\33[0m " x)
class DeviceOpener {
public:
DeviceOpener(int fd, int mode);
DeviceOpener(const char* device, int mode);
~DeviceOpener();
int Open(const char* device, int mode);
int Open(int fd, int mode);
void* InitCache(off_t numBlocks, uint32 blockSize);
void RemoveCache(bool allowWrites);
void Keep();
int Device() const { return fDevice; }
int Mode() const { return fMode; }
bool IsReadOnly() const
{ return _IsReadOnly(fMode); }
status_t GetSize(off_t* _size,
uint32* _blockSize = NULL);
private:
static bool _IsReadOnly(int mode)
{ return (mode & O_RWMASK) == O_RDONLY;}
static bool _IsReadWrite(int mode)
{ return (mode & O_RWMASK) == O_RDWR;}
int fDevice;
int fMode;
void* fBlockCache;
};
// #pragma mark - DeviceOpener
DeviceOpener::DeviceOpener(const char* device, int mode)
:
fBlockCache(NULL)
{
Open(device, mode);
}
DeviceOpener::DeviceOpener(int fd, int mode)
:
fBlockCache(NULL)
{
Open(fd, mode);
}
DeviceOpener::~DeviceOpener()
{
if (fDevice >= 0) {
RemoveCache(false);
close(fDevice);
}
}
int
DeviceOpener::Open(const char* device, int mode)
{
fDevice = open(device, mode | O_NOCACHE);
if (fDevice < 0)
fDevice = errno;
if (fDevice < 0 && _IsReadWrite(mode)) {
// try again to open read-only (don't rely on a specific error code)
return Open(device, O_RDONLY | O_NOCACHE);
}
if (fDevice >= 0) {
// opening succeeded
fMode = mode;
if (_IsReadWrite(mode)) {
// check out if the device really allows for read/write access
device_geometry geometry;
if (!ioctl(fDevice, B_GET_GEOMETRY, &geometry)) {
if (geometry.read_only) {
// reopen device read-only
close(fDevice);
return Open(device, O_RDONLY | O_NOCACHE);
}
}
}
}
return fDevice;
}
int
DeviceOpener::Open(int fd, int mode)
{
fDevice = dup(fd);
if (fDevice < 0)
return errno;
fMode = mode;
return fDevice;
}
void*
DeviceOpener::InitCache(off_t numBlocks, uint32 blockSize)
{
return fBlockCache = block_cache_create(fDevice, numBlocks, blockSize,
IsReadOnly());
}
void
DeviceOpener::RemoveCache(bool allowWrites)
{
if (fBlockCache == NULL)
return;
block_cache_delete(fBlockCache, allowWrites);
fBlockCache = NULL;
}
void
DeviceOpener::Keep()
{
fDevice = -1;
}
/*! Returns the size of the device in bytes. It uses B_GET_GEOMETRY
to compute the size, or fstat() if that failed.
*/
status_t
DeviceOpener::GetSize(off_t* _size, uint32* _blockSize)
{
device_geometry geometry;
if (ioctl(fDevice, B_GET_GEOMETRY, &geometry) < 0) {
// maybe it's just a file
struct stat stat;
if (fstat(fDevice, &stat) < 0)
return B_ERROR;
if (_size)
*_size = stat.st_size;
if (_blockSize) // that shouldn't cause us any problems
*_blockSize = 512;
return B_OK;
}
if (_size) {
*_size = 1ULL * geometry.head_count * geometry.cylinder_count
* geometry.sectors_per_track * geometry.bytes_per_sector;
}
if (_blockSize)
*_blockSize = geometry.bytes_per_sector;
return B_OK;
}
// #pragma mark - LabelVisitor
class LabelVisitor : public EntryVisitor {
public:
LabelVisitor(Volume* volume);
bool VisitLabel(struct exfat_entry*);
private:
Volume* fVolume;
};
LabelVisitor::LabelVisitor(Volume* volume)
:
fVolume(volume)
{
}
bool
LabelVisitor::VisitLabel(struct exfat_entry* entry)
{
TRACE("LabelVisitor::VisitLabel()\n");
char name[B_FILE_NAME_LENGTH];
status_t result = get_volume_name(entry, name, sizeof(name));
if (result != B_OK)
return false;
fVolume->SetName(name);
return true;
}
// #pragma mark - exfat_super_block::IsValid()
bool
exfat_super_block::IsValid()
{
// TODO: check some more values!
if (strncmp(filesystem, EXFAT_SUPER_BLOCK_MAGIC, sizeof(filesystem)) != 0)
return false;
if (signature != 0xaa55)
return false;
if (jump_boot[0] != 0xeb || jump_boot[1] != 0x76 || jump_boot[2] != 0x90)
return false;
if (version_minor != 0 || version_major != 1)
return false;
return true;
}
// #pragma mark - Volume
Volume::Volume(fs_volume* volume)
:
fFSVolume(volume),
fFlags(0),
fRootNode(NULL),
fNextId(1)
{
mutex_init(&fLock, "exfat volume");
fInodesClusterTree = new InodesClusterTree;
fInodesInoTree = new InodesInoTree;
memset(fName, 0, sizeof(fName));
}
Volume::~Volume()
{
TRACE("Volume destructor.\n");
delete fInodesClusterTree;
delete fInodesInoTree;
}
bool
Volume::IsValidSuperBlock()
{
return fSuperBlock.IsValid();
}
const char*
Volume::Name() const
{
return fName;
}
status_t
Volume::Mount(const char* deviceName, uint32 flags)
{
flags |= B_MOUNT_READ_ONLY;
// we only support read-only for now
if ((flags & B_MOUNT_READ_ONLY) != 0) {
TRACE("Volume::Mount(): Read only\n");
} else {
TRACE("Volume::Mount(): Read write\n");
}
DeviceOpener opener(deviceName, (flags & B_MOUNT_READ_ONLY) != 0
? O_RDONLY : O_RDWR);
fDevice = opener.Device();
if (fDevice < B_OK) {
ERROR("Volume::Mount(): couldn't open device\n");
return fDevice;
}
if (opener.IsReadOnly())
fFlags |= VOLUME_READ_ONLY;
// read the superblock
status_t status = Identify(fDevice, &fSuperBlock);
if (status != B_OK) {
ERROR("Volume::Mount(): Identify() failed\n");
return status;
}
fBlockSize = 1 << fSuperBlock.BlockShift();
TRACE("block size %" B_PRIu32 "\n", fBlockSize);
fEntriesPerBlock = (fBlockSize / sizeof(struct exfat_entry));
// check that the device is large enough to hold the partition
off_t deviceSize;
status = opener.GetSize(&deviceSize);
if (status != B_OK)
return status;
off_t partitionSize = (off_t)fSuperBlock.NumBlocks()
<< fSuperBlock.BlockShift();
if (deviceSize < partitionSize)
return B_BAD_VALUE;
fBlockCache = opener.InitCache(fSuperBlock.NumBlocks(), fBlockSize);
if (fBlockCache == NULL)
return B_ERROR;
TRACE("Volume::Mount(): Initialized block cache: %p\n", fBlockCache);
ino_t rootIno;
// ready
{
Inode rootNode(this, fSuperBlock.RootDirCluster(), 0);
rootIno = rootNode.ID();
}
status = get_vnode(fFSVolume, rootIno, (void**)&fRootNode);
if (status != B_OK) {
ERROR("could not create root node: get_vnode() failed!\n");
return status;
}
TRACE("Volume::Mount(): Found root node: %" B_PRIdINO " (%s)\n",
fRootNode->ID(), strerror(fRootNode->InitCheck()));
// all went fine
opener.Keep();
DirectoryIterator iterator(fRootNode);
LabelVisitor visitor(this);
iterator.Iterate(visitor);
if (fName[0] == '\0')
get_default_volume_name(partitionSize, fName, sizeof(fName));
return B_OK;
}
status_t
Volume::Unmount()
{
TRACE("Volume::Unmount()\n");
TRACE("Volume::Unmount(): Putting root node\n");
put_vnode(fFSVolume, RootNode()->ID());
TRACE("Volume::Unmount(): Deleting the block cache\n");
block_cache_delete(fBlockCache, !IsReadOnly());
TRACE("Volume::Unmount(): Closing device\n");
close(fDevice);
TRACE("Volume::Unmount(): Done\n");
return B_OK;
}
status_t
Volume::LoadSuperBlock()
{
CachedBlock cached(this);
const uint8* block = cached.SetTo(EXFAT_SUPER_BLOCK_OFFSET / fBlockSize);
if (block == NULL)
return B_IO_ERROR;
memcpy(&fSuperBlock, block + EXFAT_SUPER_BLOCK_OFFSET % fBlockSize,
sizeof(fSuperBlock));
return B_OK;
}
status_t
Volume::ClusterToBlock(cluster_t cluster, fsblock_t &block)
{
if ((cluster - EXFAT_FIRST_DATA_CLUSTER) >= SuperBlock().ClusterCount()
|| cluster < EXFAT_FIRST_DATA_CLUSTER) {
return B_BAD_VALUE;
}
block = ((fsblock_t)(cluster - EXFAT_FIRST_DATA_CLUSTER)
<< SuperBlock().BlocksPerClusterShift())
+ SuperBlock().FirstDataBlock();
TRACE("Volume::ClusterToBlock() cluster %" B_PRIu32 " %u %" B_PRIu32 ": %"
B_PRIu64 ", %" B_PRIu32 "\n", cluster,
SuperBlock().BlocksPerClusterShift(), SuperBlock().FirstDataBlock(),
block, SuperBlock().FirstFatBlock());
return B_OK;
}
cluster_t
Volume::NextCluster(cluster_t _cluster)
{
uint32 clusterPerBlock = fBlockSize / sizeof(cluster_t);
CachedBlock block(this);
fsblock_t blockNum = SuperBlock().FirstFatBlock()
+ _cluster / clusterPerBlock;
cluster_t *cluster = (cluster_t *)block.SetTo(blockNum);
cluster += _cluster % clusterPerBlock;
TRACE("Volume::NextCluster() cluster %" B_PRIu32 " next %" B_PRIu32 "\n",
_cluster, *cluster);
return *cluster;
}
Inode*
Volume::FindInode(ino_t id)
{
return fInodesInoTree->Lookup(id);
}
Inode*
Volume::FindInode(cluster_t cluster)
{
return fInodesClusterTree->Lookup(cluster);
}
ino_t
Volume::GetIno(cluster_t cluster, uint32 offset, ino_t parent)
{
struct node_key key;
key.cluster = cluster;
key.offset = offset;
MutexLocker locker(fLock);
struct node* node = fNodeTree.Lookup(key);
if (node != NULL) {
TRACE("Volume::GetIno() cached cluster %" B_PRIu32 " offset %" B_PRIu32
" ino %" B_PRIdINO "\n", cluster, offset, node->ino);
return node->ino;
}
node = new struct node();
node->key = key;
node->ino = _NextID();
node->parent = parent;
fNodeTree.Insert(node);
fInoTree.Insert(node);
TRACE("Volume::GetIno() new cluster %" B_PRIu32 " offset %" B_PRIu32
" ino %" B_PRIdINO "\n", cluster, offset, node->ino);
return node->ino;
}
struct node_key*
Volume::GetNode(ino_t ino, ino_t &parent)
{
MutexLocker locker(fLock);
struct node* node = fInoTree.Lookup(ino);
if (node != NULL) {
parent = node->parent;
return &node->key;
}
return NULL;
}
// #pragma mark - Disk scanning and initialization
/*static*/ status_t
Volume::Identify(int fd, exfat_super_block* superBlock)
{
if (read_pos(fd, EXFAT_SUPER_BLOCK_OFFSET, superBlock,
sizeof(exfat_super_block)) != sizeof(exfat_super_block))
return B_IO_ERROR;
if (!superBlock->IsValid()) {
ERROR("invalid superblock!\n");
return B_BAD_VALUE;
}
return B_OK;
}
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fDevice, fSuperBlock, fBlockSize, fEntriesPerBlock, fBlockCache.