* Implemented a read-only ext2 file system.

* It's not yet complete, doesn't support some ext2 stuff (like files over 4 GB),
  and might have some other bugs (I only tested it with a single 20 MB ext2
  image).
* To have a read/write ext2 file system, it would probably make more sense to
  port GNU sources (like ext2fs lib), and use that. But a small read-only ext2
  file sytem doesn't hurt, I think, and I don't know if ext2fs lib would be
  feasible for kernel use (porting the file system from Linux directly would
  also be an alternative, but probably more work).


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@26187 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler 2008-07-01 10:12:19 +00:00
parent fdf3fa9fe1
commit 63db34c816
11 changed files with 1778 additions and 0 deletions

View File

@ -2,6 +2,7 @@ SubDir HAIKU_TOP src add-ons kernel file_systems ;
SubInclude HAIKU_TOP src add-ons kernel file_systems bfs ;
SubInclude HAIKU_TOP src add-ons kernel file_systems cdda ;
SubInclude HAIKU_TOP src add-ons kernel file_systems ext2 ;
SubInclude HAIKU_TOP src add-ons kernel file_systems fat ;
SubInclude HAIKU_TOP src add-ons kernel file_systems googlefs ;
SubInclude HAIKU_TOP src add-ons kernel file_systems iso9660 ;

View File

@ -0,0 +1,97 @@
/*
* Copyright 2001-2008, Axel Dörfler, axeld@pinc-software.de.
* This file may be used under the terms of the MIT License.
*/
#ifndef CACHED_BLOCK_H
#define CACHED_BLOCK_H
//! interface for the block cache
#include <fs_cache.h>
#include "Volume.h"
class CachedBlock {
public:
CachedBlock(Volume* volume);
CachedBlock(Volume* volume, uint32 block);
~CachedBlock();
void Keep();
void Unset();
const uint8* SetTo(uint32 block);
const uint8* Block() const { return fBlock; }
off_t BlockNumber() const { return fBlockNumber; }
private:
CachedBlock(const CachedBlock &);
CachedBlock &operator=(const CachedBlock &);
// no implementation
protected:
Volume* fVolume;
uint32 fBlockNumber;
uint8* fBlock;
};
// inlines
inline
CachedBlock::CachedBlock(Volume* volume)
:
fVolume(volume),
fBlockNumber(0),
fBlock(NULL)
{
}
inline
CachedBlock::CachedBlock(Volume* volume, uint32 block)
:
fVolume(volume),
fBlockNumber(0),
fBlock(NULL)
{
SetTo(block);
}
inline
CachedBlock::~CachedBlock()
{
Unset();
}
inline void
CachedBlock::Keep()
{
fBlock = NULL;
}
inline void
CachedBlock::Unset()
{
if (fBlock != NULL) {
block_cache_put(fVolume->BlockCache(), fBlockNumber);
fBlock = NULL;
}
}
inline const uint8 *
CachedBlock::SetTo(uint32 block)
{
Unset();
fBlockNumber = block;
return fBlock = (uint8 *)block_cache_get(fVolume->BlockCache(), block);
}
#endif // CACHED_BLOCK_H

View File

@ -0,0 +1,77 @@
/*
* Copyright 2008, Axel Dörfler, axeld@pinc-software.de.
* This file may be used under the terms of the MIT License.
*/
#include "DirectoryIterator.h"
#include <string.h>
#include "Inode.h"
DirectoryIterator::DirectoryIterator(Inode* inode)
:
fInode(inode),
fOffset(0)
{
}
DirectoryIterator::~DirectoryIterator()
{
}
status_t
DirectoryIterator::GetNext(char* name, size_t* _nameLength, ino_t* _id)
{
if (fOffset + sizeof(ext2_dir_entry) >= fInode->Size())
return B_ENTRY_NOT_FOUND;
ext2_dir_entry entry;
while (true) {
size_t length = ext2_dir_entry::MinimumSize();
status_t status = fInode->ReadAt(fOffset, (uint8*)&entry, &length);
if (status != B_OK)
return status;
if (length < ext2_dir_entry::MinimumSize() || entry.Length() == 0)
return B_ENTRY_NOT_FOUND;
if (!entry.IsValid())
return B_BAD_DATA;
if (entry.NameLength() != 0)
break;
fOffset += entry.Length();
}
// read name
size_t length = entry.NameLength();
status_t status = fInode->ReadAt(fOffset + ext2_dir_entry::MinimumSize(),
(uint8*)entry.name, &length);
if (status == B_OK) {
if (*_nameLength < length)
length = *_nameLength - 1;
memcpy(name, entry.name, length);
name[length] = '\0';
*_id = entry.InodeID();
*_nameLength = length;
fOffset += entry.Length();
}
return status;
}
status_t
DirectoryIterator::Rewind()
{
fOffset = 0;
return B_OK;
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2008, Axel Dörfler, axeld@pinc-software.de.
* This file may be used under the terms of the MIT License.
*/
#ifndef DIRECTORY_ITERATOR_H
#define DIRECTORY_ITERATOR_H
#include <SupportDefs.h>
class Inode;
class DirectoryIterator {
public:
DirectoryIterator(Inode* inode);
~DirectoryIterator();
status_t GetNext(char* name, size_t* _nameLength, ino_t* id);
status_t Rewind();
private:
DirectoryIterator(const DirectoryIterator&);
DirectoryIterator &operator=(const DirectoryIterator&);
// no implementation
private:
Inode* fInode;
off_t fOffset;
};
#endif // DIRECTORY_ITERATOR_H

View File

@ -0,0 +1,172 @@
/*
* Copyright 2008, Axel Dörfler, axeld@pinc-software.de.
* This file may be used under the terms of the MIT License.
*/
#include "Inode.h"
#include <fs_cache.h>
#include "CachedBlock.h"
Inode::Inode(Volume* volume, ino_t id)
:
fVolume(volume),
fID(id),
fCache(NULL),
fMap(NULL),
fNode(NULL)
{
rw_lock_init(&fLock, "ext2 inode");
uint32 block;
if (volume->GetInodeBlock(id, block) == B_OK) {
ext2_inode* inodes = (ext2_inode*)block_cache_get(volume->BlockCache(),
block);
if (inodes != NULL)
fNode = inodes + volume->InodeBlockIndex(id);
}
if (fNode != NULL) {
fCache = file_cache_create(fVolume->ID(), ID(), Size());
fMap = file_map_create(fVolume->ID(), ID(), Size());
}
}
Inode::~Inode()
{
file_cache_delete(FileCache());
file_map_delete(Map());
if (fNode != NULL) {
uint32 block;
if (fVolume->GetInodeBlock(ID(), block) == B_OK)
block_cache_put(fVolume->BlockCache(), block);
}
}
status_t
Inode::InitCheck()
{
return fNode != NULL ? B_OK : B_ERROR;
}
status_t
Inode::CheckPermissions(int accessMode) const
{
uid_t user = geteuid();
gid_t group = getegid();
// you never have write access to a read-only volume
if (accessMode & W_OK && fVolume->IsReadOnly())
return B_READ_ONLY_DEVICE;
// root users always have full access (but they can't execute files without
// any execute permissions set)
if (user == 0) {
if (!((accessMode & X_OK) != 0 && (Mode() & S_IXUSR) == 0)
|| S_ISDIR(Mode()))
return B_OK;
}
// shift mode bits, to check directly against accessMode
mode_t mode = Mode();
if (user == (uid_t)fNode->UserID())
mode >>= 6;
else if (group == (gid_t)fNode->GroupID())
mode >>= 3;
if (accessMode & ~(mode & S_IRWXO))
return B_NOT_ALLOWED;
return B_OK;
}
status_t
Inode::FindBlock(off_t offset, uint32& block)
{
uint32 perBlock = fVolume->BlockSize() / 4;
uint32 perIndirectBlock = perBlock * perBlock;
uint32 index = offset >> fVolume->BlockShift();
if (offset >= Size())
return B_ENTRY_NOT_FOUND;
if (index < EXT2_DIRECT_BLOCKS) {
// direct blocks
block = B_LENDIAN_TO_HOST_INT32(Node().stream.direct[index]);
} else if ((index -= EXT2_DIRECT_BLOCKS) < perBlock) {
// indirect blocks
CachedBlock cached(fVolume);
uint32* indirectBlocks = (uint32*)cached.SetTo(Node().stream.indirect);
if (indirectBlocks != NULL)
return B_IO_ERROR;
block = B_LENDIAN_TO_HOST_INT32(indirectBlocks[index]);
} else if ((index -= perBlock) < perIndirectBlock) {
// double indirect blocks
CachedBlock cached(fVolume);
uint32* indirectBlocks = (uint32*)cached.SetTo(B_LENDIAN_TO_HOST_INT32(
Node().stream.double_indirect));
if (indirectBlocks != NULL)
return B_IO_ERROR;
indirectBlocks = (uint32*)cached.SetTo(B_LENDIAN_TO_HOST_INT32(
indirectBlocks[index / perBlock]));
if (indirectBlocks != NULL)
return B_IO_ERROR;
block = B_LENDIAN_TO_HOST_INT32(indirectBlocks[index & (perBlock - 1)]);
} else if ((index -= perIndirectBlock) / perBlock < perIndirectBlock) {
// triple indirect blocks
CachedBlock cached(fVolume);
uint32* indirectBlocks = (uint32*)cached.SetTo(B_LENDIAN_TO_HOST_INT32(
Node().stream.triple_indirect));
if (indirectBlocks != NULL)
return B_IO_ERROR;
indirectBlocks = (uint32*)cached.SetTo(B_LENDIAN_TO_HOST_INT32(
indirectBlocks[index / perIndirectBlock]));
if (indirectBlocks != NULL)
return B_IO_ERROR;
indirectBlocks = (uint32*)cached.SetTo(B_LENDIAN_TO_HOST_INT32(
indirectBlocks[(index / perBlock) & (perBlock - 1)]));
if (indirectBlocks != NULL)
return B_IO_ERROR;
block = B_LENDIAN_TO_HOST_INT32(indirectBlocks[index & (perBlock - 1)]);
} else {
// outside of the possible data stream
dprintf("ext2: block outside datastream!\n");
return B_ERROR;
}
//dprintf("FindBlock(offset %Ld): %lu\n", offset, block);
return B_OK;
}
status_t
Inode::ReadAt(off_t pos, uint8* buffer, size_t* _length)
{
size_t length = *_length;
// set/check boundaries for pos/length
if (pos < 0)
return B_BAD_VALUE;
if (pos >= Size() || length == 0) {
*_length = 0;
return B_NO_ERROR;
}
return file_cache_read(FileCache(), NULL, pos, buffer, _length);
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2008, Axel Dörfler, axeld@pinc-software.de.
* This file may be used under the terms of the MIT License.
*/
#ifndef INODE_H
#define INODE_H
#include <lock.h>
#include "ext2.h"
#include "Volume.h"
class Inode {
public:
Inode(Volume* volume, ino_t id);
~Inode();
status_t InitCheck();
ino_t ID() const { return fID; }
rw_lock* Lock() { return &fLock; }
bool IsDirectory() const
{ return S_ISDIR(Mode()); }
bool IsFile() const
{ return S_ISREG(Mode()); }
bool IsSymLink() const
{ return S_ISLNK(Mode()); }
status_t CheckPermissions(int accessMode) const;
mode_t Mode() const { return fNode->Mode(); }
int32 Flags() const { return fNode->Flags(); }
off_t Size() const { return fNode->Size(); }
time_t ModificationTime() const
{ return fNode->ModificationTime(); }
time_t CreationTime() const
{ return fNode->CreationTime(); }
time_t AccessTime() const
{ return fNode->AccessTime(); }
//::Volume* _Volume() const { return fVolume; }
Volume* GetVolume() const { return fVolume; }
status_t FindBlock(off_t offset, uint32& block);
status_t ReadAt(off_t pos, uint8 *buffer, size_t *length);
ext2_inode& Node() { return *fNode; }
void* FileCache() const { return fCache; }
void* Map() const { return fMap; }
private:
Inode(const Inode&);
Inode &operator=(const Inode&);
// no implementation
private:
rw_lock fLock;
::Volume* fVolume;
ino_t fID;
void* fCache;
void* fMap;
ext2_inode* fNode;
};
#endif // INODE_H

View File

@ -0,0 +1,19 @@
SubDir HAIKU_TOP src add-ons kernel file_systems ext2 ;
# set some additional defines
{
SubDirCcFlags -Wall -Wno-multichar ;
SubDirC++Flags -Wall -Wno-multichar ;
}
#UsePrivateHeaders [ FDirName kernel disk_device_manager ] ;
UsePrivateHeaders shared storage ;
UsePrivateKernelHeaders ;
KernelAddon ext2 :
Volume.cpp
Inode.cpp
DirectoryIterator.cpp
kernel_interface.cpp
;

View File

@ -0,0 +1,405 @@
/*
* Copyright 2008, Axel Dörfler, axeld@pinc-software.de.
* This file may be used under the terms of the MIT License.
*/
//! super block, mounting, etc.
#include "Volume.h"
#include <errno.h>
#include <new>
#include <stdlib.h>
#include <fs_cache.h>
#include <fs_volume.h>
#include <util/AutoLock.h>
#include "Inode.h"
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;
};
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 = 1LL * 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 -
bool
ext2_super_block::IsValid()
{
// TODO: check some more values!
if (Magic() != (uint32)EXT2_SUPER_BLOCK_MAGIC)
return false;
return true;
}
// #pragma mark -
Volume::Volume(fs_volume* volume)
:
fFSVolume(volume),
fFlags(0),
fGroupBlocks(NULL),
fRootNode(NULL)
{
mutex_init(&fLock, "ext2 volume");
}
Volume::~Volume()
{
if (fGroupBlocks != NULL) {
uint32 blockCount = (fNumGroups + fGroupsPerBlock - 1)
/ fGroupsPerBlock;
for (uint32 i = 0; i < blockCount; i++) {
free(fGroupBlocks[i]);
}
free(fGroupBlocks);
}
}
bool
Volume::IsValidSuperBlock()
{
return fSuperBlock.IsValid();
}
const char*
Volume::Name() const
{
if (fSuperBlock.name[0])
return fSuperBlock.name;
return "Unnamed Ext2 Volume";
}
status_t
Volume::Mount(const char* deviceName, uint32 flags)
{
flags |= B_MOUNT_READ_ONLY;
// we only support read-only for now
DeviceOpener opener(deviceName, (flags & B_MOUNT_READ_ONLY) != 0
? O_RDONLY : O_RDWR);
fDevice = opener.Device();
if (fDevice < B_OK)
return fDevice;
if (opener.IsReadOnly())
fFlags |= VOLUME_READ_ONLY;
// read the super block
if (Identify(fDevice, &fSuperBlock) != B_OK) {
//FATAL(("invalid super block!\n"));
return B_BAD_VALUE;
}
if ((fSuperBlock.IncompatibleFeatures()
& EXT2_INCOMPATIBLE_FEATURE_COMPRESSION) != 0) {
dprintf("ext2: compression not supported.\n");
return B_NOT_SUPPORTED;
}
// initialize short hands to the super block (to save byte swapping)
fBlockShift = fSuperBlock.BlockShift();
fBlockSize = 1UL << fSuperBlock.BlockShift();
fNumGroups = (fSuperBlock.NumBlocks() - fSuperBlock.FirstDataBlock() - 1)
/ fSuperBlock.BlocksPerGroup() + 1;
fGroupsPerBlock = fBlockSize / sizeof(ext2_block_group);
uint32 blockCount = (fNumGroups + fGroupsPerBlock - 1) / fGroupsPerBlock;
fGroupBlocks = (ext2_block_group**)malloc(blockCount * sizeof(void*));
if (fGroupBlocks == NULL)
return B_NO_MEMORY;
memset(fGroupBlocks, 0, blockCount * sizeof(void*));
fInodesPerBlock = fBlockSize / sizeof(ext2_inode);
// check if the device size is large enough to hold the file system
off_t diskSize;
status_t status = opener.GetSize(&diskSize);
if (status != B_OK)
return status;
if (diskSize < (NumBlocks() << BlockShift()))
return B_BAD_VALUE;
if ((fBlockCache = opener.InitCache(NumBlocks(), fBlockSize)) == NULL)
return B_ERROR;
status = get_vnode(fFSVolume, EXT2_ROOT_NODE, (void**)&fRootNode);
if (status == B_OK) {
// all went fine
opener.Keep();
return B_OK;
} else
dprintf("ext2: could not create root node: get_vnode() failed!\n");
return status;
}
status_t
Volume::Unmount()
{
//put_vnode(fVolume, ToVnode(Root()));
//block_cache_delete(fBlockCache, !IsReadOnly());
close(fDevice);
return B_OK;
}
status_t
Volume::GetInodeBlock(ino_t id, uint32& block)
{
ext2_block_group* group;
status_t status = GetBlockGroup((id - 1) / fSuperBlock.InodesPerGroup(),
&group);
if (status != B_OK)
return status;
block = group->InodeTable()
+ ((id - 1) % fSuperBlock.InodesPerGroup()) / fInodesPerBlock;
return B_OK;
}
uint32
Volume::InodeBlockIndex(ino_t id) const
{
return ((id - 1) % fSuperBlock.InodesPerGroup()) % fInodesPerBlock;
}
off_t
Volume::_GroupBlockOffset(uint32 blockIndex)
{
if ((fSuperBlock.IncompatibleFeatures()
& EXT2_INCOMPATIBLE_FEATURE_META_GROUP) == 0
|| blockIndex < fSuperBlock.FirstMetaBlockGroup())
return EXT2_SUPER_BLOCK_OFFSET + fBlockSize * (1 + blockIndex);
panic("meta block");
return 0;
}
/*! Makes the requested block group available.
The block groups are loaded on demand, but are kept in memory until the
volume is unmounted; therefore we don't use the block cache.
*/
status_t
Volume::GetBlockGroup(int32 index, ext2_block_group** _group)
{
if (index < 0 || (uint32)index > fNumGroups)
return B_BAD_VALUE;
int32 blockIndex = index / fGroupsPerBlock;
MutexLocker _(fLock);
if (fGroupBlocks[blockIndex] == NULL) {
ext2_block_group* groupBlock = (ext2_block_group*)malloc(fBlockSize);
if (groupBlock == NULL)
return B_NO_MEMORY;
ssize_t bytesRead = read_pos(fDevice, _GroupBlockOffset(blockIndex),
groupBlock, fBlockSize);
if (bytesRead >= B_OK && (uint32)bytesRead != fBlockSize)
bytesRead = B_IO_ERROR;
if (bytesRead < B_OK) {
free(groupBlock);
return bytesRead;
}
fGroupBlocks[blockIndex] = groupBlock;
}
*_group = fGroupBlocks[blockIndex] + index % fGroupsPerBlock;
return B_OK;
}
// #pragma mark - Disk scanning and initialization
/*static*/ status_t
Volume::Identify(int fd, ext2_super_block* superBlock)
{
if (read_pos(fd, EXT2_SUPER_BLOCK_OFFSET, superBlock,
sizeof(ext2_super_block)) != sizeof(ext2_super_block))
return B_IO_ERROR;
if (!superBlock->IsValid())
return B_BAD_VALUE;
return B_OK;
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2008, Axel Dörfler, axeld@pinc-software.de.
* This file may be used under the terms of the MIT License.
*/
#ifndef VOLUME_H
#define VOLUME_H
#include <lock.h>
#include "ext2.h"
class Inode;
enum volume_flags {
VOLUME_READ_ONLY = 0x0001
};
class Volume {
public:
Volume(fs_volume* volume);
~Volume();
status_t Mount(const char* device, uint32 flags);
status_t Unmount();
bool IsValidSuperBlock();
bool IsReadOnly() const { return fFlags & VOLUME_READ_ONLY; }
Inode* RootNode() const { return fRootNode; }
int Device() const { return fDevice; }
dev_t ID() const { return fFSVolume ? fFSVolume->id : -1; }
fs_volume* FSVolume() const { return fFSVolume; }
const char* Name() const;
off_t NumBlocks() const { return fSuperBlock.NumBlocks(); }
off_t FreeBlocks() const { return fSuperBlock.FreeBlocks(); }
uint32 BlockSize() const { return fBlockSize; }
uint32 BlockShift() const { return fBlockShift; }
uint32 InodeSize() const { return fSuperBlock.InodeSize(); }
ext2_super_block& SuperBlock() { return fSuperBlock; }
status_t GetInodeBlock(ino_t id, uint32& block);
uint32 InodeBlockIndex(ino_t id) const;
status_t GetBlockGroup(int32 index, ext2_block_group** _group);
// cache access
void* BlockCache() { return fBlockCache; }
static status_t Identify(int fd, ext2_super_block* superBlock);
private:
off_t _GroupBlockOffset(uint32 blockIndex);
private:
mutex fLock;
fs_volume* fFSVolume;
int fDevice;
ext2_super_block fSuperBlock;
uint32 fFlags;
uint32 fBlockSize;
uint32 fBlockShift;
uint32 fNumGroups;
uint32 fGroupsPerBlock;
ext2_block_group** fGroupBlocks;
uint32 fInodesPerBlock;
void* fBlockCache;
Inode* fRootNode;
};
#endif // VOLUME_H

View File

@ -0,0 +1,251 @@
/*
* Copyright 2008, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
#ifndef EXT2_H
#define EXT2_H
#include <ByteOrder.h>
#include <fs_interface.h>
#define EXT2_SUPER_BLOCK_OFFSET 1024
struct ext2_super_block {
uint32 num_inodes;
uint32 num_blocks;
uint32 reserved_blocks;
uint32 free_blocks;
uint32 free_inodes;
uint32 first_data_block;
uint32 block_shift;
uint32 fragment_shift;
uint32 blocks_per_group;
uint32 fragments_per_group;
uint32 inodes_per_group;
uint32 mount_time;
uint32 write_time;
uint16 mount_count;
uint16 max_mount_count;
uint16 magic;
uint16 state;
uint16 error_handling;
uint16 minor_revision_level;
uint32 last_check_time;
uint32 check_interval;
uint32 creator_os;
uint32 revision_level;
uint16 reserved_blocks_uid;
uint16 reserved_blocks_gid;
uint32 first_inode;
uint16 inode_size;
uint16 block_group;
uint32 compatible_features;
uint32 incompatible_features;
uint32 readonly_features;
uint8 uuid[16];
char name[16];
char last_mount_point[64];
uint32 algorithm_usage_bitmap;
uint8 preallocated_blocks;
uint8 preallocated_directory_blocks;
uint16 _padding;
// journaling ext3 support
uint8 journal_uuid[16];
uint32 journal_inode;
uint32 journal_device;
uint32 last_orphan;
uint32 hash_seed[4];
uint8 default_hash_version;
uint8 _reserved1;
uint16 _reserved2;
uint32 default_mount_options;
uint32 first_meta_block_group;
uint32 _reserved3[190];
uint16 Magic() const { return B_LENDIAN_TO_HOST_INT16(magic); }
uint32 BlockShift() const { return B_LENDIAN_TO_HOST_INT32(block_shift) + 10; }
uint32 NumInodes() const { return B_LENDIAN_TO_HOST_INT32(num_inodes); }
uint32 NumBlocks() const { return B_LENDIAN_TO_HOST_INT32(num_blocks); }
uint32 FreeInodes() const { return B_LENDIAN_TO_HOST_INT32(free_inodes); }
uint32 FreeBlocks() const { return B_LENDIAN_TO_HOST_INT32(free_blocks); }
uint16 InodeSize() const { return B_LENDIAN_TO_HOST_INT16(inode_size); }
uint32 FirstDataBlock() const
{ return B_LENDIAN_TO_HOST_INT32(first_data_block); }
uint32 BlocksPerGroup() const
{ return B_LENDIAN_TO_HOST_INT32(blocks_per_group); }
uint32 InodesPerGroup() const
{ return B_LENDIAN_TO_HOST_INT32(inodes_per_group); }
uint32 FirstMetaBlockGroup() const
{ return B_LENDIAN_TO_HOST_INT32(first_meta_block_group); }
uint32 CompatibleFeatures() const
{ return B_LENDIAN_TO_HOST_INT32(compatible_features); }
uint32 ReadOnlyFeatures() const
{ return B_LENDIAN_TO_HOST_INT32(readonly_features); }
uint32 IncompatibleFeatures() const
{ return B_LENDIAN_TO_HOST_INT32(incompatible_features); }
bool IsValid();
// implemented in Volume.cpp
} _PACKED;
#define EXT2_OLD_REVISION 0
#define EXT2_DYNAMIC_REVISION 1
// compatible features
#define EXT2_FEATURE_DIRECTORY_PREALLOCATION 0x01
#define EXT2_FEATURE_IMAGIC_INODES 0x02
#define EXT2_FEATURE_HAS_JOURNAL 0x04
#define EXT2_FEATURE_EXT_ATTR 0x08
#define EXT2_FEATURE_RESIZE_INODE 0x10
#define EXT2_FEATURE_DIRECTORY_INDEX 0x20
// read-only compatible features
#define EXT2_READ_ONLY_FEATURE_SPARSE_SUPER 0x01
#define EXT2_READ_ONLY_FEATURE_LARGE_FILE 0x02
#define EXT2_READ_ONLY_FEATURE_BTREE_DIRECTORY 0x04
// incompatible features
#define EXT2_INCOMPATIBLE_FEATURE_COMPRESSION 0x01
#define EXT2_INCOMPATIBLE_FEATURE_FILE_TYPE 0x02
#define EXT2_INCOMPATIBLE_FEATURE_RECOVER 0x04
#define EXT2_INCOMPATIBLE_FEATURE_JOURNAL 0x08
#define EXT2_INCOMPATIBLE_FEATURE_META_GROUP 0x10
// states
#define EXT2_STATE_VALID 0x01
#define EXT2_STATE_INVALID 0x02
struct ext2_block_group {
uint32 block_bitmap;
uint32 inode_bitmap;
uint32 inode_table;
uint16 free_blocks;
uint16 free_inodes;
uint16 used_directories;
uint16 _padding;
uint32 _reserved[3];
uint32 InodeTable() const
{ return B_LENDIAN_TO_HOST_INT32(inode_table); }
};
#define EXT2_DIRECT_BLOCKS 12
#define EXT2_ROOT_NODE 2
#define EXT2_SHORT_SYMLINK_LENGTH 60
struct ext2_inode {
uint16 mode;
uint16 uid;
uint32 size;
uint32 access_time;
uint32 creation_time;
uint32 modification_time;
uint32 deletion_time;
uint16 gid;
uint16 num_links;
uint32 num_blocks;
uint32 flags;
uint32 _reserved1;
union {
struct data_stream {
uint32 direct[EXT2_DIRECT_BLOCKS];
uint32 indirect;
uint32 double_indirect;
uint32 triple_indirect;
} stream;
char symlink[EXT2_SHORT_SYMLINK_LENGTH];
};
uint32 generation;
uint32 file_access_control;
uint32 directory_access_control;
uint32 fragment;
uint8 fragment_number;
uint8 fragment_size;
uint16 _padding;
uint16 uid_high;
uint16 gid_high;
uint32 _reserved2;
uint16 Mode() const { return B_LENDIAN_TO_HOST_INT16(mode); }
uint32 Size() const { return B_LENDIAN_TO_HOST_INT32(size); }
uint32 Flags() const { return B_LENDIAN_TO_HOST_INT32(flags); }
time_t AccessTime() const { return B_LENDIAN_TO_HOST_INT32(access_time); }
time_t CreationTime() const { return B_LENDIAN_TO_HOST_INT32(creation_time); }
time_t ModificationTime() const { return B_LENDIAN_TO_HOST_INT32(modification_time); }
time_t DeletionTime() const { return B_LENDIAN_TO_HOST_INT32(deletion_time); }
uint16 UserID() const
{
return B_LENDIAN_TO_HOST_INT16(uid)
| (B_LENDIAN_TO_HOST_INT16(uid_high) << 16);
}
uint16 GroupID() const
{
return B_LENDIAN_TO_HOST_INT16(gid)
| (B_LENDIAN_TO_HOST_INT16(gid_high) << 16);
}
} _PACKED;
#define EXT2_SUPER_BLOCK_MAGIC 0xef53
// flags
#define EXT2_INODE_SECURE_DELETION 0x00000001
#define EXT2_INODE_UNDELETE 0x00000002
#define EXT2_INODE_COMPRESSED 0x00000004
#define EXT2_INODE_SYNCHRONOUS 0x00000008
#define EXT2_INODE_IMMUTABLE 0x00000010
#define EXT2_INODE_APPEND_ONLY 0x00000020
#define EXT2_INODE_NO_DUMP 0x00000040
#define EXT2_INODE_NO_ACCESS_TIME 0x00000080
#define EXT2_INODE_DIRTY 0x00000100
#define EXT2_INODE_COMPRESSED_BLOCKS 0x00000200
#define EXT2_INODE_DO_NOT_COMPRESS 0x00000400
#define EXT2_INODE_COMPRESSION_ERROR 0x00000800
#define EXT2_INODE_BTREE 0x00001000
#define EXT2_NAME_LENGTH 255
struct ext2_dir_entry {
uint32 inode_id;
uint16 length;
uint8 name_length;
uint8 file_type;
char name[EXT2_NAME_LENGTH];
uint32 InodeID() const { return B_LENDIAN_TO_HOST_INT32(inode_id); }
uint16 Length() const { return B_LENDIAN_TO_HOST_INT16(length); }
uint8 NameLength() const { return name_length; }
uint8 FileType() const { return file_type; }
bool IsValid() const
{
return Length() > MinimumSize();
// There is no maximum size, as the last entry spans until the
// end of the block
}
static size_t MinimumSize()
{
return sizeof(ext2_dir_entry) - EXT2_NAME_LENGTH;
}
} _PACKED;
// file types
#define EXT2_TYPE_UNKOWN 0
#define EXT2_TYPE_FILE 1
#define EXT2_TYPE_DIRECTORY 2
#define EXT2_TYPE_CHAR_DEVICE 3
#define EXT2_TYPE_BLOCK_DEVICE 4
#define EXT2_TYPE_FIFO 5
#define EXT2_TYPE_SOCKET 6
#define EXT2_TYPE_SYMLINK 7
extern fs_volume_ops gExt2VolumeOps;
extern fs_vnode_ops gExt2VnodeOps;
#endif // EXT2_H

View File

@ -0,0 +1,578 @@
/*
* Copyright 2008, Axel Dörfler, axeld@pinc-software.de.
* This file may be used under the terms of the MIT License.
*/
#include <dirent.h>
#include <new>
#include <string.h>
#include <fs_cache.h>
#include <fs_info.h>
#include "DirectoryIterator.h"
#include "ext2.h"
#include "Inode.h"
#define EXT2_IO_SIZE 65536
struct identify_cookie {
ext2_super_block super_block;
};
/*! Converts the open mode, the open flags given to bfs_open(), into
access modes, e.g. since O_RDONLY requires read access to the
file, it will be converted to R_OK.
*/
int
open_mode_to_access(int openMode)
{
openMode &= O_RWMASK;
if (openMode == O_RDONLY)
return R_OK;
else if (openMode == O_WRONLY)
return W_OK;
return R_OK | W_OK;
}
// #pragma mark - Scanning
static float
ext2_identify_partition(int fd, partition_data *partition, void **_cookie)
{
ext2_super_block superBlock;
status_t status = Volume::Identify(fd, &superBlock);
if (status != B_OK)
return status;
identify_cookie *cookie = new identify_cookie;
memcpy(&cookie->super_block, &superBlock, sizeof(ext2_super_block));
*_cookie = cookie;
return 0.8f;
}
static status_t
ext2_scan_partition(int fd, partition_data *partition, void *_cookie)
{
identify_cookie *cookie = (identify_cookie *)_cookie;
partition->status = B_PARTITION_VALID;
partition->flags |= B_PARTITION_FILE_SYSTEM;
partition->content_size = cookie->super_block.NumBlocks()
<< cookie->super_block.BlockShift();
partition->block_size = 1UL << cookie->super_block.BlockShift();
partition->content_name = strdup(cookie->super_block.name);
if (partition->content_name == NULL)
return B_NO_MEMORY;
return B_OK;
}
static void
ext2_free_identify_partition_cookie(partition_data* partition, void* _cookie)
{
delete (identify_cookie*)_cookie;
}
// #pragma mark -
static status_t
ext2_mount(fs_volume* _volume, const char* device, uint32 flags,
const char* args, ino_t* _rootID)
{
Volume* volume = new Volume(_volume);
if (volume == NULL)
return B_NO_MEMORY;
// TODO: this is a bit hacky: we can't use publish_vnode() to publish
// the root node, or else its file cache cannot be created (we could
// create it later, though). Therefore we're using get_vnode() in Mount(),
// but that requires us to export our volume data before calling it.
_volume->private_volume = volume;
_volume->ops = &gExt2VolumeOps;
status_t status = volume->Mount(device, flags);
if (status != B_OK) {
delete volume;
return status;
}
*_rootID = volume->RootNode()->ID();
return B_OK;
}
static status_t
ext2_unmount(fs_volume *_volume)
{
Volume* volume = (Volume *)_volume->private_volume;
status_t status = volume->Unmount();
delete volume;
return status;
}
static status_t
ext2_read_fs_info(fs_volume* _volume, struct fs_info* info)
{
Volume* volume = (Volume*)_volume->private_volume;
// File system flags
info->flags = B_FS_IS_PERSISTENT
| (volume->IsReadOnly() ? B_FS_IS_READONLY : 0);
info->io_size = EXT2_IO_SIZE;
info->block_size = volume->BlockSize();
info->total_blocks = volume->NumBlocks();
info->free_blocks = volume->FreeBlocks();
// Volume name
strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name));
// File system name
strlcpy(info->fsh_name, "ext2", sizeof(info->fsh_name));
return B_OK;
}
// #pragma mark -
static status_t
ext2_get_vnode(fs_volume* _volume, ino_t id, fs_vnode* _node, int* _type,
uint32* _flags, bool reenter)
{
Volume* volume = (Volume*)_volume->private_volume;
if (id < 2 || id > volume->NumBlocks()) {
dprintf("ext2: inode at %Ld requested!\n", id);
return B_ERROR;
}
Inode* inode = new Inode(volume, id);
if (inode == NULL)
return B_NO_MEMORY;
status_t status = inode->InitCheck();
if (status < B_OK)
delete inode;
if (status == B_OK) {
_node->private_node = inode;
_node->ops = &gExt2VnodeOps;
*_type = inode->Mode();
*_flags = 0;
}
return status;
}
static status_t
ext2_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
{
delete (Inode*)_node->private_node;
return B_OK;
}
static bool
ext2_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
return true;
}
static status_t
ext2_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie,
off_t pos, const iovec* vecs, size_t count, size_t* _numBytes, bool reenter)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_node;
if (inode->FileCache() == NULL)
return B_BAD_VALUE;
if (!reenter)
rw_lock_read_lock(inode->Lock());
uint32 vecIndex = 0;
size_t vecOffset = 0;
size_t bytesLeft = *_numBytes;
status_t status;
while (true) {
file_io_vec fileVecs[8];
uint32 fileVecCount = 8;
status = file_map_translate(inode->Map(), pos, bytesLeft, fileVecs,
&fileVecCount);
if (status != B_OK && status != B_BUFFER_OVERFLOW)
break;
bool bufferOverflow = status == B_BUFFER_OVERFLOW;
size_t bytes = bytesLeft;
status = read_file_io_vec_pages(volume->Device(), fileVecs,
fileVecCount, vecs, count, &vecIndex, &vecOffset, &bytes);
if (status != B_OK || !bufferOverflow)
break;
pos += bytes;
bytesLeft -= bytes;
}
if (!reenter)
rw_lock_read_unlock(inode->Lock());
return status;
}
static status_t
ext2_get_file_map(fs_volume* _volume, fs_vnode* _node, off_t offset,
size_t size, struct file_io_vec* vecs, size_t* _count)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_node;
size_t index = 0, max = *_count;
while (true) {
uint32 block;
status_t status = inode->FindBlock(offset, block);
if (status != B_OK)
return status;
off_t blockOffset = block << volume->BlockShift();
uint32 blockLength = volume->BlockSize();
if (index > 0 && vecs[index - 1].offset == blockOffset - blockLength) {
vecs[index - 1].length += blockLength;
} else {
if (index >= max) {
// we're out of file_io_vecs; let's bail out
*_count = index;
return B_BUFFER_OVERFLOW;
}
vecs[index].offset = blockOffset;
vecs[index].length = blockLength;
index++;
}
offset += blockLength;
if (size <= vecs[index - 1].length || offset >= inode->Size()) {
// We're done!
*_count = index;
return B_OK;
}
}
// can never get here
return B_ERROR;
}
// #pragma mark -
static status_t
ext2_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name,
ino_t* _vnodeID)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* directory = (Inode*)_directory->private_node;
// check access permissions
status_t status = directory->CheckPermissions(X_OK);
if (status < B_OK)
return status;
DirectoryIterator iterator(directory);
while (true) {
char buffer[B_FILE_NAME_LENGTH];
size_t length = sizeof(buffer);
status = iterator.GetNext(buffer, &length, _vnodeID);
if (status != B_OK)
return status;
if (!strcmp(buffer, name))
break;
}
return get_vnode(volume->FSVolume(), *_vnodeID, NULL);
}
static status_t
ext2_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat)
{
Inode* inode = (Inode*)_node->private_node;
const ext2_inode& node = inode->Node();
stat->st_dev = inode->GetVolume()->ID();
stat->st_ino = inode->ID();
stat->st_nlink = 1;
stat->st_blksize = EXT2_IO_SIZE;
stat->st_uid = node.UserID();
stat->st_gid = node.GroupID();
stat->st_mode = node.Mode();
stat->st_type = 0;
stat->st_atime = node.AccessTime();
stat->st_mtime = stat->st_ctime = node.ModificationTime();
stat->st_crtime = node.CreationTime();
stat->st_size = inode->Size();
return B_OK;
}
static status_t
ext2_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie)
{
Inode* inode = (Inode*)_node->private_node;
// opening a directory read-only is allowed, although you can't read
// any data from it.
if (inode->IsDirectory() && (openMode & O_RWMASK) != 0)
return B_IS_A_DIRECTORY;
if ((openMode & O_TRUNC) != 0)
return B_READ_ONLY_DEVICE;
return inode->CheckPermissions(open_mode_to_access(openMode)
| (openMode & O_TRUNC ? W_OK : 0));
}
static status_t
ext2_read(fs_volume *_volume, fs_vnode *_node, void *_cookie, off_t pos,
void *buffer, size_t *_length)
{
Inode *inode = (Inode *)_node->private_node;
if (!inode->IsFile()) {
*_length = 0;
return inode->IsDirectory() ? B_IS_A_DIRECTORY : B_BAD_VALUE;
}
return inode->ReadAt(pos, (uint8 *)buffer, _length);
}
static status_t
ext2_close(fs_volume *_volume, fs_vnode *_node, void *_cookie)
{
return B_OK;
}
static status_t
ext2_free_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie)
{
return B_OK;
}
static status_t
ext2_access(fs_volume* _volume, fs_vnode* _node, int accessMode)
{
Inode* inode = (Inode*)_node->private_node;
return inode->CheckPermissions(accessMode);
}
static status_t
ext2_read_link(fs_volume *_volume, fs_vnode *_node, char *buffer,
size_t *_bufferSize)
{
Inode* inode = (Inode*)_node->private_node;
if (!inode->IsSymLink())
return B_BAD_VALUE;
if (inode->Size() < *_bufferSize)
*_bufferSize = inode->Size();
if (inode->Size() > EXT2_SHORT_SYMLINK_LENGTH)
return inode->ReadAt(0, (uint8 *)buffer, _bufferSize);
memcpy(buffer, inode->Node().symlink, *_bufferSize);
return B_OK;
}
// #pragma mark - Directory functions
static status_t
ext2_open_dir(fs_volume *_volume, fs_vnode *_node, void **_cookie)
{
Inode *inode = (Inode *)_node->private_node;
status_t status = inode->CheckPermissions(R_OK);
if (status < B_OK)
return status;
if (!inode->IsDirectory())
return B_BAD_VALUE;
DirectoryIterator* iterator = new(std::nothrow) DirectoryIterator(inode);
if (iterator == NULL)
return B_NO_MEMORY;
*_cookie = iterator;
return B_OK;
}
static status_t
ext2_read_dir(fs_volume *_volume, fs_vnode *_node, void *_cookie,
struct dirent *dirent, size_t bufferSize, uint32 *_num)
{
DirectoryIterator *iterator = (DirectoryIterator *)_cookie;
size_t length = bufferSize;
ino_t id;
status_t status = iterator->GetNext(dirent->d_name, &length, &id);
if (status == B_ENTRY_NOT_FOUND) {
*_num = 0;
return B_OK;
} else if (status != B_OK)
return status;
Volume* volume = (Volume*)_volume->private_volume;
dirent->d_dev = volume->ID();
dirent->d_ino = id;
dirent->d_reclen = sizeof(struct dirent) + length;
*_num = 1;
return B_OK;
}
static status_t
ext2_rewind_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void *_cookie)
{
DirectoryIterator *iterator = (DirectoryIterator *)_cookie;
return iterator->Rewind();
}
static status_t
ext2_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/)
{
return B_OK;
}
static status_t
ext2_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie)
{
delete (DirectoryIterator *)_cookie;
return B_OK;
}
fs_volume_ops gExt2VolumeOps = {
&ext2_unmount,
&ext2_read_fs_info,
NULL, // write_fs_info()
NULL, // sync()
&ext2_get_vnode,
};
fs_vnode_ops gExt2VnodeOps = {
/* vnode operations */
&ext2_lookup,
NULL,
&ext2_put_vnode,
NULL,
/* VM file access */
&ext2_can_page,
&ext2_read_pages,
NULL,
&ext2_get_file_map,
NULL,
NULL,
NULL, // fs_select
NULL, // fs_deselect
NULL,
&ext2_read_link,
NULL,
NULL,
NULL,
NULL,
&ext2_access,
&ext2_read_stat,
NULL,
/* file operations */
NULL,
&ext2_open,
&ext2_close,
&ext2_free_cookie,
&ext2_read,
NULL,
/* directory operations */
NULL,
NULL,
&ext2_open_dir,
&ext2_close_dir,
&ext2_free_dir_cookie,
&ext2_read_dir,
&ext2_rewind_dir,
NULL,
};
static file_system_module_info sExt2FileSystem = {
{
"file_systems/ext2" B_CURRENT_FS_API_VERSION,
0,
NULL,
},
"ext2", // short_name
"Ext2 File System", // pretty_name
0, // DDM flags
// scanning
ext2_identify_partition,
ext2_scan_partition,
ext2_free_identify_partition_cookie,
NULL, // free_partition_content_cookie()
&ext2_mount,
NULL,
};
module_info *modules[] = {
(module_info *)&sExt2FileSystem,
NULL,
};