From 63db34c816c7067b251f03c50286d594ceac3cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20D=C3=B6rfler?= Date: Tue, 1 Jul 2008 10:12:19 +0000 Subject: [PATCH] * 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 --- src/add-ons/kernel/file_systems/Jamfile | 1 + .../kernel/file_systems/ext2/CachedBlock.h | 97 +++ .../file_systems/ext2/DirectoryIterator.cpp | 77 +++ .../file_systems/ext2/DirectoryIterator.h | 33 + .../kernel/file_systems/ext2/Inode.cpp | 172 ++++++ src/add-ons/kernel/file_systems/ext2/Inode.h | 71 +++ src/add-ons/kernel/file_systems/ext2/Jamfile | 19 + .../kernel/file_systems/ext2/Volume.cpp | 405 ++++++++++++ src/add-ons/kernel/file_systems/ext2/Volume.h | 74 +++ src/add-ons/kernel/file_systems/ext2/ext2.h | 251 ++++++++ .../file_systems/ext2/kernel_interface.cpp | 578 ++++++++++++++++++ 11 files changed, 1778 insertions(+) create mode 100644 src/add-ons/kernel/file_systems/ext2/CachedBlock.h create mode 100644 src/add-ons/kernel/file_systems/ext2/DirectoryIterator.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/DirectoryIterator.h create mode 100644 src/add-ons/kernel/file_systems/ext2/Inode.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/Inode.h create mode 100644 src/add-ons/kernel/file_systems/ext2/Jamfile create mode 100644 src/add-ons/kernel/file_systems/ext2/Volume.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/Volume.h create mode 100644 src/add-ons/kernel/file_systems/ext2/ext2.h create mode 100644 src/add-ons/kernel/file_systems/ext2/kernel_interface.cpp diff --git a/src/add-ons/kernel/file_systems/Jamfile b/src/add-ons/kernel/file_systems/Jamfile index df5f37d14a..85311738da 100644 --- a/src/add-ons/kernel/file_systems/Jamfile +++ b/src/add-ons/kernel/file_systems/Jamfile @@ -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 ; diff --git a/src/add-ons/kernel/file_systems/ext2/CachedBlock.h b/src/add-ons/kernel/file_systems/ext2/CachedBlock.h new file mode 100644 index 0000000000..161bef6f8e --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/CachedBlock.h @@ -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 + +#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 diff --git a/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.cpp b/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.cpp new file mode 100644 index 0000000000..3d681e4a48 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.cpp @@ -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 + +#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; +} diff --git a/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.h b/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.h new file mode 100644 index 0000000000..48fd5138f6 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.h @@ -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 + + +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 diff --git a/src/add-ons/kernel/file_systems/ext2/Inode.cpp b/src/add-ons/kernel/file_systems/ext2/Inode.cpp new file mode 100644 index 0000000000..845fded414 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/Inode.cpp @@ -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 + +#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); +} + diff --git a/src/add-ons/kernel/file_systems/ext2/Inode.h b/src/add-ons/kernel/file_systems/ext2/Inode.h new file mode 100644 index 0000000000..c7d1015367 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/Inode.h @@ -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 + +#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 diff --git a/src/add-ons/kernel/file_systems/ext2/Jamfile b/src/add-ons/kernel/file_systems/ext2/Jamfile new file mode 100644 index 0000000000..eafc2a5efa --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/Jamfile @@ -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 +; diff --git a/src/add-ons/kernel/file_systems/ext2/Volume.cpp b/src/add-ons/kernel/file_systems/ext2/Volume.cpp new file mode 100644 index 0000000000..cdd9fd1267 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/Volume.cpp @@ -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 +#include +#include + +#include +#include + +#include + +#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; +} + diff --git a/src/add-ons/kernel/file_systems/ext2/Volume.h b/src/add-ons/kernel/file_systems/ext2/Volume.h new file mode 100644 index 0000000000..79a8e6e4d3 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/Volume.h @@ -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 + +#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 diff --git a/src/add-ons/kernel/file_systems/ext2/ext2.h b/src/add-ons/kernel/file_systems/ext2/ext2.h new file mode 100644 index 0000000000..75e47fc2f9 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/ext2.h @@ -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 +#include + + +#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 diff --git a/src/add-ons/kernel/file_systems/ext2/kernel_interface.cpp b/src/add-ons/kernel/file_systems/ext2/kernel_interface.cpp new file mode 100644 index 0000000000..2d8ec478d6 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/kernel_interface.cpp @@ -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 +#include +#include + +#include +#include + +#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, +};