diff --git a/src/add-ons/kernel/file_systems/Jamfile b/src/add-ons/kernel/file_systems/Jamfile index 1ffc8823be..24fe5eb6a1 100644 --- a/src/add-ons/kernel/file_systems/Jamfile +++ b/src/add-ons/kernel/file_systems/Jamfile @@ -4,6 +4,7 @@ SubInclude HAIKU_TOP src add-ons kernel file_systems bfs ; SubInclude HAIKU_TOP src add-ons kernel file_systems bindfs ; SubInclude HAIKU_TOP src add-ons kernel file_systems btrfs ; SubInclude HAIKU_TOP src add-ons kernel file_systems cdda ; +SubInclude HAIKU_TOP src add-ons kernel file_systems exfat ; 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 ; diff --git a/src/add-ons/kernel/file_systems/exfat/CachedBlock.h b/src/add-ons/kernel/file_systems/exfat/CachedBlock.h new file mode 100644 index 0000000000..09fec08abb --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/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, off_t block); + ~CachedBlock(); + + void Keep(); + void Unset(); + + const uint8* SetTo(off_t 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; + off_t fBlockNumber; + uint8* fBlock; +}; + + +// inlines + + +inline +CachedBlock::CachedBlock(Volume* volume) + : + fVolume(volume), + fBlockNumber(0), + fBlock(NULL) +{ +} + + +inline +CachedBlock::CachedBlock(Volume* volume, off_t 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(off_t 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/exfat/DataStream.cpp b/src/add-ons/kernel/file_systems/exfat/DataStream.cpp new file mode 100644 index 0000000000..4a2788cc3c --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/DataStream.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2011, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Jérôme Duval + */ + + +#include "DataStream.h" + +#include "Volume.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) + + +DataStream::DataStream(Volume* volume, Inode* inode, off_t size) + : + kBlockSize(volume->BlockSize()), + fVolume(volume), + fInode(inode), + fSize(size) +{ + fNumBlocks = size == 0 ? 0 : ((size - 1) / kBlockSize) + 1; +} + + +DataStream::~DataStream() +{ +} + + +status_t +DataStream::FindBlock(off_t pos, off_t& physical, off_t *_length) +{ + if (pos >= fSize) { + TRACE("FindBlock: offset larger than size\n"); + return B_ENTRY_NOT_FOUND; + } + cluster_t clusterIndex = pos / fVolume->ClusterSize(); + uint32 offset = pos % fVolume->ClusterSize(); + + cluster_t cluster = fInode->StartCluster(); + for (uint32 i = 0; i < clusterIndex; i++) + cluster = fInode->NextCluster(cluster); + fsblock_t block; + fVolume->ClusterToBlock(cluster, block); + physical = block * kBlockSize + offset; + *_length = min_c(kBlockSize, fSize - pos); + TRACE("inode %" B_PRIdINO ": cluster %ld, pos %lld, %lld\n", + fInode->ID(), fInode->StartCluster(), pos, physical); + return B_OK; +} + diff --git a/src/add-ons/kernel/file_systems/exfat/DataStream.h b/src/add-ons/kernel/file_systems/exfat/DataStream.h new file mode 100644 index 0000000000..ab43f6ece9 --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/DataStream.h @@ -0,0 +1,37 @@ +/* + * Copyright 2011, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Jérôme Duval + */ +#ifndef DATASTREAM_H +#define DATASTREAM_H + + +#include "exfat.h" +#include "Inode.h" + + +class Volume; + + +class DataStream +{ +public: + DataStream(Volume* volume, Inode* inode, + off_t size); + ~DataStream(); + + status_t FindBlock(off_t pos, off_t& physical, + off_t *_length = NULL); +private: + const uint32 kBlockSize; + Volume* fVolume; + Inode* fInode; + off_t fNumBlocks; + off_t fSize; +}; + +#endif // DATASTREAM_H + diff --git a/src/add-ons/kernel/file_systems/exfat/DirectoryIterator.cpp b/src/add-ons/kernel/file_systems/exfat/DirectoryIterator.cpp new file mode 100644 index 0000000000..8196dace37 --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/DirectoryIterator.cpp @@ -0,0 +1,257 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * This file may be used under the terms of the MIT License. + */ + + +#include "DirectoryIterator.h" + +#include "encodings.h" +#include "Inode.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) + + +DirectoryIterator::DirectoryIterator(Inode* inode) + : + fOffset(-2), + fCluster(inode->StartCluster()), + fInode(inode), + fBlock(inode->GetVolume()), + fCurrent(NULL) +{ + TRACE("DirectoryIterator::DirectoryIterator() %ld\n", fCluster); +} + + +DirectoryIterator::~DirectoryIterator() +{ +} + + +status_t +DirectoryIterator::InitCheck() +{ + return B_OK; +} + + +status_t +DirectoryIterator::GetNext(char* name, size_t* _nameLength, ino_t* _id, + EntryVisitor* visitor) +{ + if (fOffset == -2) { + *_nameLength = 3; + strlcpy(name, "..", *_nameLength); + if (fInode->ID() == 1) + *_id = fInode->ID(); + else + *_id = fInode->Parent(); + fOffset = -1; + TRACE("DirectoryIterator::GetNext() found ..\n"); + return B_OK; + } else if (fOffset == -1) { + *_nameLength = 2; + strlcpy(name, ".", *_nameLength); + *_id = fInode->ID(); + fOffset = 0; + TRACE("DirectoryIterator::GetNext() found .\n"); + return B_OK; + } + + uchar unicodeName[EXFAT_FILENAME_MAX_LENGTH]; + size_t nameLength = EXFAT_FILENAME_MAX_LENGTH; + status_t status = _GetNext(unicodeName, &nameLength, _id, visitor); + if (status == B_OK && name != NULL) { + unicode_to_utf8(unicodeName, nameLength, (uint8 *)name , _nameLength); + TRACE("DirectoryIterator::GetNext() %ld %s, %" B_PRIdINO "\n", + fInode->Cluster(), name, *_id); + } + + return status; +} + + +status_t +DirectoryIterator::Lookup(const char* name, size_t nameLength, ino_t* _id) +{ + if (strcmp(name, ".") == 0) { + *_id = fInode->ID(); + return B_OK; + } else if (strcmp(name, "..") == 0) { + if (fInode->ID() == 1) + *_id = fInode->ID(); + else + *_id = fInode->Parent(); + return B_OK; + } + + Rewind(); + fOffset = 0; + + uchar currentName[EXFAT_FILENAME_MAX_LENGTH]; + size_t currentLength = EXFAT_FILENAME_MAX_LENGTH; + while (_GetNext((uchar*)currentName, ¤tLength, _id) == B_OK) { + char utfName[EXFAT_FILENAME_MAX_LENGTH]; + size_t utfLength = EXFAT_FILENAME_MAX_LENGTH; + unicode_to_utf8(currentName, currentLength, (uint8*)utfName, &utfLength); + if (nameLength == utfLength + && strncmp(utfName, name, nameLength) == 0) { + TRACE("DirectoryIterator::Lookup() found ID %" B_PRIdINO "\n", *_id); + return B_OK; + } + currentLength = EXFAT_FILENAME_MAX_LENGTH; + } + + TRACE("DirectoryIterator::Lookup() not found %s\n", name); + + return B_ENTRY_NOT_FOUND; +} + + +status_t +DirectoryIterator::LookupEntry(EntryVisitor* visitor) +{ + fCluster = fInode->Cluster(); + fOffset = fInode->Offset(); + + uchar unicodeName[EXFAT_FILENAME_MAX_LENGTH]; + size_t nameLength = EXFAT_FILENAME_MAX_LENGTH; + return _GetNext(unicodeName, &nameLength, NULL, visitor); +} + + +status_t +DirectoryIterator::Rewind() +{ + fOffset = -2; + fCluster = fInode->StartCluster(); + return B_OK; +} + + +void +DirectoryIterator::Iterate(EntryVisitor &visitor) +{ + Rewind(); + + while (_NextEntry() != B_ENTRY_NOT_FOUND) { + switch (fCurrent->type) { + case EXFAT_ENTRY_TYPE_BITMAP: + visitor.VisitBitmap(fCurrent); + break; + case EXFAT_ENTRY_TYPE_UPPERCASE: + visitor.VisitUppercase(fCurrent); + break; + case EXFAT_ENTRY_TYPE_LABEL: + visitor.VisitLabel(fCurrent); + break; + case EXFAT_ENTRY_TYPE_FILE: + visitor.VisitFile(fCurrent); + break; + case EXFAT_ENTRY_TYPE_FILEINFO: + visitor.VisitFileInfo(fCurrent); + break; + case EXFAT_ENTRY_TYPE_FILENAME: + visitor.VisitFilename(fCurrent); + break; + } + } +} + + +status_t +DirectoryIterator::_GetNext(uchar* name, size_t* _nameLength, ino_t* _id, + EntryVisitor* visitor) +{ + size_t nameMax = *_nameLength; + size_t nameIndex = 0; + status_t status; + int32 chunkCount = 1; + while ((status = _NextEntry()) == B_OK) { + TRACE("DirectoryIterator::_GetNext() %ld/%p, type 0x%x, offset %lld\n", + fInode->Cluster(), fCurrent, fCurrent->type, fOffset); + if (fCurrent->type == EXFAT_ENTRY_TYPE_FILE) { + chunkCount = fCurrent->file.chunkCount; + if (_id != NULL) { + *_id = fInode->GetVolume()->GetIno(fCluster, fOffset - 1, + fInode->ID()); + } + if (visitor != NULL) + visitor->VisitFile(fCurrent); + TRACE("DirectoryIterator::_GetNext() File chunkCount %ld\n", + chunkCount); + } else if (fCurrent->type == EXFAT_ENTRY_TYPE_FILEINFO) { + chunkCount--; + TRACE("DirectoryIterator::_GetNext() Filename length %d\n", + fCurrent->file_info.name_length); + *_nameLength = fCurrent->file_info.name_length * 2; + if (visitor != NULL) + visitor->VisitFileInfo(fCurrent); + } else if (fCurrent->type == EXFAT_ENTRY_TYPE_FILENAME) { + TRACE("DirectoryIterator::_GetNext() Filename\n"); + memcpy((uint8*)name + nameIndex, fCurrent->name_label.name, + sizeof(fCurrent->name_label.name)); + nameIndex += sizeof(fCurrent->name_label.name); + name[nameIndex] = '\0'; + chunkCount--; + if (visitor != NULL) + visitor->VisitFilename(fCurrent); + } + + if (chunkCount == 0 || nameIndex >= nameMax) + break; + } + + if (status == B_OK) { + //*_nameLength = nameIndex; +#ifdef TRACE_EXFAT + char utfName[EXFAT_FILENAME_MAX_LENGTH]; + size_t utfLen = EXFAT_FILENAME_MAX_LENGTH; + unicode_to_utf8(name, nameIndex, (uint8*)utfName, &utfLen); + TRACE("DirectoryIterator::_GetNext() Found %s %ld\n", utfName, + *_nameLength); +#endif + } + + return status; +} + + +status_t +DirectoryIterator::_NextEntry() +{ + if (fCurrent == NULL) { + fsblock_t block; + fInode->GetVolume()->ClusterToBlock(fCluster, block); + block += (fOffset / fInode->GetVolume()->EntriesPerBlock()) + % (1 << fInode->GetVolume()->SuperBlock().BlocksPerClusterShift()); + TRACE("DirectoryIterator::_NextEntry() init to block %lld\n", block); + fCurrent = (struct exfat_entry*)fBlock.SetTo(block) + + fOffset % fInode->GetVolume()->EntriesPerBlock(); + } else if ((fOffset % fInode->GetVolume()->EntriesPerBlock()) == 0) { + fsblock_t block; + if ((fOffset % fInode->GetVolume()->EntriesPerCluster()) == 0) { + fCluster = fInode->NextCluster(fCluster); + if (fCluster == EXFAT_CLUSTER_END) + return B_ENTRY_NOT_FOUND; + fInode->GetVolume()->ClusterToBlock(fCluster, block); + } else + block = fBlock.BlockNumber() + 1; + TRACE("DirectoryIterator::_NextEntry() block %lld\n", block); + fCurrent = (struct exfat_entry*)fBlock.SetTo(block); + } else + fCurrent++; + fOffset++; + + return fCurrent->type == 0 ? B_ENTRY_NOT_FOUND : B_OK; +} + + diff --git a/src/add-ons/kernel/file_systems/exfat/DirectoryIterator.h b/src/add-ons/kernel/file_systems/exfat/DirectoryIterator.h new file mode 100644 index 0000000000..27b7dd3deb --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/DirectoryIterator.h @@ -0,0 +1,62 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * This file may be used under the terms of the MIT License. + */ +#ifndef DIRECTORYITERATOR_H +#define DIRECTORYITERATOR_H + + +#include "CachedBlock.h" +#include "exfat.h" + +class Inode; + +class EntryVisitor { +public: + EntryVisitor() {}; + virtual ~EntryVisitor() {}; + virtual bool VisitBitmap(struct exfat_entry*) + { return false; } + virtual bool VisitUppercase(struct exfat_entry*) + { return false; } + virtual bool VisitLabel(struct exfat_entry*) + { return false; } + virtual bool VisitFilename(struct exfat_entry*) + { return false; } + virtual bool VisitFile(struct exfat_entry*) + { return false; } + virtual bool VisitFileInfo(struct exfat_entry*) + { return false; } +}; + + +class DirectoryIterator { +public: + DirectoryIterator(Inode* inode); + ~DirectoryIterator(); + + status_t InitCheck(); + + status_t GetNext(char* name, size_t* _nameLength, + ino_t* _id, EntryVisitor* visitor = NULL); + status_t Lookup(const char* name, size_t nameLength, + ino_t* _id); + status_t LookupEntry(EntryVisitor* visitor); + status_t Rewind(); + + void Iterate(EntryVisitor &visitor); +private: + status_t _GetNext(uchar* unicodename, + size_t* _nameLength, ino_t* _id, + EntryVisitor* visitor = NULL); + status_t _NextEntry(); + + int64 fOffset; + cluster_t fCluster; + Inode* fInode; + CachedBlock fBlock; + struct exfat_entry* fCurrent; +}; + + +#endif // DIRECTORYITERATOR_H diff --git a/src/add-ons/kernel/file_systems/exfat/Inode.cpp b/src/add-ons/kernel/file_systems/exfat/Inode.cpp new file mode 100644 index 0000000000..f6ba127bad --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/Inode.cpp @@ -0,0 +1,283 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * 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 +#include + +#include "CachedBlock.h" +#include "DataStream.h" +#include "Utility.h" + + +#undef ASSERT +//#define TRACE_EXFAT +#ifdef TRACE_EXFAT +# define TRACE(x...) dprintf("\33[34mexfat:\33[0m " x) +# define ASSERT(x) { if (!(x)) kernel_debugger("exfat: assert failed: " #x "\n"); } +#else +# define TRACE(x...) ; +# define ASSERT(x) ; +#endif +#define ERROR(x...) dprintf("\33[34mexfat:\33[0m " x) + + +Inode::Inode(Volume* volume, cluster_t cluster, uint32 offset) + : + fVolume(volume), + fID(volume->GetIno(cluster, offset, 0)), + fCluster(cluster), + fOffset(offset), + fCache(NULL), + fMap(NULL) +{ + TRACE("Inode::Inode(%ld, %d) inode %" B_PRIdINO "\n", Cluster(), Offset(), + ID()); + _Init(); + + if (ID() == 1) { + fFileEntry.file.SetAttribs(EXFAT_ENTRY_ATTRIB_SUBDIR); + fFileEntry.file_info.SetStartCluster(Cluster()); + fFileInfoEntry.file_info.SetFlag(0); + } else + fInitStatus = UpdateNodeFromDisk(); + if (fInitStatus == B_OK) { + if (!IsDirectory() && !IsSymLink()) { + fCache = file_cache_create(fVolume->ID(), ID(), Size()); + fMap = file_map_create(fVolume->ID(), ID(), Size()); + } + } + TRACE("Inode::Inode(%" B_PRIdINO ") end\n", ID()); +} + + +Inode::Inode(Volume* volume, ino_t ino) + : + fVolume(volume), + fID(ino), + fCluster(0), + fOffset(0), + fCache(NULL), + fMap(NULL), + fInitStatus(B_NO_INIT) +{ + struct node_key *key = volume->GetNode(ino, fParent); + if (key != NULL) { + fCluster = key->cluster; + fOffset = key->offset; + fInitStatus = B_OK; + } + TRACE("Inode::Inode(%" B_PRIdINO ") cluster %ld\n", ID(), Cluster()); + _Init(); + + if (fInitStatus == B_OK && ID() != 1) + fInitStatus = UpdateNodeFromDisk(); + else if (fInitStatus == B_OK && ID() == 1) { + fFileEntry.file.SetAttribs(EXFAT_ENTRY_ATTRIB_SUBDIR); + fFileInfoEntry.file_info.SetStartCluster(Cluster()); + fFileInfoEntry.file_info.SetFlag(0); + } + if (fInitStatus == B_OK) { + if (!IsDirectory() && !IsSymLink()) { + fCache = file_cache_create(fVolume->ID(), ID(), Size()); + fMap = file_map_create(fVolume->ID(), ID(), Size()); + } + } + TRACE("Inode::Inode(%" B_PRIdINO ") end\n", ID()); +} + + +Inode::Inode(Volume* volume) + : + fVolume(volume), + fID(0), + fCache(NULL), + fMap(NULL), + fInitStatus(B_NO_INIT) +{ + _Init(); +} + + +Inode::~Inode() +{ + TRACE("Inode destructor\n"); + file_cache_delete(FileCache()); + file_map_delete(Map()); + TRACE("Inode destructor: Done\n"); +} + + +status_t +Inode::InitCheck() +{ + return fInitStatus; +} + + +status_t +Inode::UpdateNodeFromDisk() +{ + + DirectoryIterator iterator(this); + iterator.LookupEntry(this); + return B_OK; +} + + +cluster_t +Inode::NextCluster(cluster_t cluster) const +{ + if (!IsContiguous()) + return GetVolume()->NextCluster(cluster); + return cluster + 1; +} + + +mode_t +Inode::Mode() const +{ + mode_t mode = S_IRUSR | S_IRGRP | S_IROTH; + if (!fVolume->IsReadOnly()) + mode |= S_IWUSR | S_IWGRP | S_IWOTH; + if (fFileEntry.file.Attribs() & EXFAT_ENTRY_ATTRIB_SUBDIR) + mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH; + else + mode |= S_IFREG; + return mode; +} + + +status_t +Inode::CheckPermissions(int accessMode) const +{ + // you never have write access to a read-only volume + if ((accessMode & W_OK) != 0 && fVolume->IsReadOnly()) + return B_READ_ONLY_DEVICE; + + // get node permissions + mode_t mode = Mode(); + int userPermissions = (mode & S_IRWXU) >> 6; + int groupPermissions = (mode & S_IRWXG) >> 3; + int otherPermissions = mode & S_IRWXO; + + // get the node permissions for this uid/gid + int permissions = 0; + uid_t uid = geteuid(); + gid_t gid = getegid(); + + if (uid == 0) { + // user is root + // root has always read/write permission, but at least one of the + // X bits must be set for execute permission + permissions = userPermissions | groupPermissions | otherPermissions + | R_OK | W_OK; + } else if (uid == (uid_t)UserID()) { + // user is node owner + permissions = userPermissions; + } else if (gid == (gid_t)GroupID()) { + // user is in owning group + permissions = groupPermissions; + } else { + // user is one of the others + permissions = otherPermissions; + } + + return (accessMode & ~permissions) == 0 ? B_OK : B_NOT_ALLOWED; + return B_OK; +} + + +status_t +Inode::FindBlock(off_t pos, off_t& physical, off_t *_length) +{ + DataStream stream(fVolume, this, Size()); + return stream.FindBlock(pos, physical, _length); +} + + +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) { + ERROR("inode %" B_PRIdINO ": ReadAt failed(pos %lld, length %lu)\n", + ID(), pos, length); + return B_BAD_VALUE; + } + + if (pos >= Size() || length == 0) { + TRACE("inode %" B_PRIdINO ": ReadAt 0 (pos %lld, length %lu)\n", + ID(), pos, length); + *_length = 0; + return B_NO_ERROR; + } + + return file_cache_read(FileCache(), NULL, pos, buffer, _length); +} + + +bool +Inode::VisitFile(struct exfat_entry* entry) +{ + fFileEntry = *entry; + return false; +} + + +bool +Inode::VisitFileInfo(struct exfat_entry* entry) +{ + fFileInfoEntry = *entry; + return false; +} + + +void +Inode::_Init() +{ + memset(&fFileEntry, 0, sizeof(fFileEntry)); + memset(&fFileInfoEntry, 0, sizeof(fFileInfoEntry)); + rw_lock_init(&fLock, "exfat inode"); +} + + +// If divisible by 4, but not divisible by 100, but divisible by 400, it's a leap year +// 1996 is leap, 1900 is not, 2000 is, 2100 is not +#define IS_LEAP_YEAR(y) ((((y) % 4) == 0) && (((y) % 100) || ((((y)) % 400) == 0))) + +/* returns leap days since 1970 */ +static int leaps(int yr, int mon) +{ + // yr is 1970-based, mon 0-based + int result = (yr+2)/4 - (yr + 70) / 100; + if((yr+70) >= 100) result++; // correct for 2000 + if (IS_LEAP_YEAR(yr + 1970)) + if (mon < 2) result--; + return result; +} + +static int daze[] = { 0,0,31,59,90,120,151,181,212,243,273,304,334,0,0,0 }; + +void +Inode::_GetTimespec(uint16 date, uint16 time, struct timespec ×pec) const +{ + static int32 tzoffset = -1; /* in minutes */ + if (tzoffset == -1) + tzoffset = get_timezone_offset() / 60; + + time_t days = daze[(date>>5)&15] + ((date>>9)+10)*365 + leaps((date>>9)+10,((date>>5)&15)-1)+(date&31)-1; + + timespec.tv_sec = ((days * 24 + (time >> 11)) * 60 + ((time>>5)&63) + tzoffset) * 60 + 2*(time&31); + timespec.tv_nsec = 0; +} + + diff --git a/src/add-ons/kernel/file_systems/exfat/Inode.h b/src/add-ons/kernel/file_systems/exfat/Inode.h new file mode 100644 index 0000000000..e3539983a6 --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/Inode.h @@ -0,0 +1,245 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * 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 +#include + +#include "DirectoryIterator.h" +#include "exfat.h" +#include "SplayTree.h" +#include "Volume.h" + + +//#define TRACE_EXFAT +#ifdef TRACE_EXFAT +# define TRACEI(x...) dprintf("\33[34mexfat:\33[0m " x) +#else +# define TRACEI(x...) ; +#endif + + +struct InodesTreeDefinition; + + +class Inode : EntryVisitor { +public: + Inode(Volume* volume, cluster_t cluster, + uint32 offset); + Inode::Inode(Volume* volume, ino_t ino); + ~Inode(); + + status_t InitCheck(); + + ino_t ID() const { return fID; } + ino_t Parent() const { return fParent; } + cluster_t Cluster() const { return fCluster; } + uint32_t Offset() const { return fOffset; } + cluster_t StartCluster() const + { return fFileInfoEntry.file_info.StartCluster(); } + bool IsContiguous() const + { return fFileInfoEntry.file_info.IsContiguous(); } + cluster_t NextCluster(cluster_t cluster) const; + + rw_lock* Lock() { return &fLock; } + + status_t UpdateNodeFromDisk(); + + 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; + off_t Size() const { return fFileInfoEntry.file_info.Size(); } + uid_t UserID() const { return 0;/*fNode.UserID();*/ } + gid_t GroupID() const { return 0;/*fNode.GroupID();*/ } + void GetChangeTime(struct timespec ×pec) const + { GetModificationTime(timespec); } + void GetModificationTime(struct timespec ×pec) const + { _GetTimespec(fFileEntry.file.ModificationDate(), + fFileEntry.file.ModificationTime(), timespec); } + void GetCreationTime(struct timespec ×pec) const + { _GetTimespec(fFileEntry.file.CreationDate(), + fFileEntry.file.CreationTime(), timespec); } + void GetAccessTime(struct timespec ×pec) const + { _GetTimespec(fFileEntry.file.AccessDate(), + fFileEntry.file.AccessTime(), timespec); } + + Volume* GetVolume() const { return fVolume; } + + status_t FindBlock(off_t logical, off_t& physical, + off_t *_length = NULL); + status_t ReadAt(off_t pos, uint8 *buffer, size_t *length); + status_t FillGapWithZeros(off_t start, off_t end); + + void* FileCache() const { return fCache; } + void* Map() const { return fMap; } + + bool VisitFile(struct exfat_entry*); + bool VisitFileInfo(struct exfat_entry*); + +private: + friend struct InodesInoTreeDefinition; + friend struct InodesClusterTreeDefinition; + + Inode(Volume* volume); + Inode(const Inode&); + Inode &operator=(const Inode&); + // no implementation + + void _GetTimespec(uint16 date, uint16 time, + struct timespec ×pec) const; + void _Init(); + + rw_lock fLock; + ::Volume* fVolume; + ino_t fID; + ino_t fParent; + cluster_t fCluster; + uint32 fOffset; + uint32 fFlags; + void* fCache; + void* fMap; + status_t fInitStatus; + + SplayTreeLink fInoTreeLink; + Inode* fInoTreeNext; + SplayTreeLink fClusterTreeLink; + Inode* fClusterTreeNext; + + struct exfat_entry fFileEntry; + struct exfat_entry fFileInfoEntry; +}; + + +// The Vnode class provides a convenience layer upon get_vnode(), so that +// you don't have to call put_vnode() anymore, which may make code more +// readable in some cases + +class Vnode { +public: + Vnode(Volume* volume, ino_t id) + : + fInode(NULL) + { + SetTo(volume, id); + } + + Vnode() + : + fStatus(B_NO_INIT), + fInode(NULL) + { + } + + ~Vnode() + { + Unset(); + } + + status_t InitCheck() + { + return fStatus; + } + + void Unset() + { + if (fInode != NULL) { + put_vnode(fInode->GetVolume()->FSVolume(), fInode->ID()); + fInode = NULL; + fStatus = B_NO_INIT; + } + } + + status_t SetTo(Volume* volume, ino_t id) + { + Unset(); + + return fStatus = get_vnode(volume->FSVolume(), id, (void**)&fInode); + } + + status_t Get(Inode** _inode) + { + *_inode = fInode; + return fStatus; + } + + void Keep() + { + TRACEI("Vnode::Keep()\n"); + fInode = NULL; + } + +private: + status_t fStatus; + Inode* fInode; +}; + + +struct InodesInoTreeDefinition { + typedef ino_t KeyType; + typedef Inode NodeType; + + static KeyType GetKey(const NodeType* node) + { + return node->ID(); + } + + static SplayTreeLink* GetLink(NodeType* node) + { + return &node->fInoTreeLink; + } + + static int Compare(KeyType key, const NodeType* node) + { + return key == node->ID() ? 0 + : (key < node->ID() ? -1 : 1); + } + + static NodeType** GetListLink(NodeType* node) + { + return &node->fInoTreeNext; + } +}; + +typedef IteratableSplayTree InodesInoTree; + +struct InodesClusterTreeDefinition { + typedef cluster_t KeyType; + typedef Inode NodeType; + + static KeyType GetKey(const NodeType* node) + { + return node->Cluster(); + } + + static SplayTreeLink* GetLink(NodeType* node) + { + return &node->fClusterTreeLink; + } + + static int Compare(KeyType key, const NodeType* node) + { + return key == node->Cluster() ? 0 + : (key < node->Cluster() ? -1 : 1); + } + + static NodeType** GetListLink(NodeType* node) + { + return &node->fClusterTreeNext; + } +}; + +typedef IteratableSplayTree InodesClusterTree; + +#endif // INODE_H diff --git a/src/add-ons/kernel/file_systems/exfat/Jamfile b/src/add-ons/kernel/file_systems/exfat/Jamfile new file mode 100644 index 0000000000..f06190c736 --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/Jamfile @@ -0,0 +1,14 @@ +SubDir HAIKU_TOP src add-ons kernel file_systems exfat ; + +UsePrivateHeaders [ FDirName kernel util ] ; +UsePrivateHeaders shared storage ; +UsePrivateKernelHeaders ; + +KernelAddon exfat : + DataStream.cpp + DirectoryIterator.cpp + encodings.cpp + Inode.cpp + kernel_interface.cpp + Volume.cpp +; diff --git a/src/add-ons/kernel/file_systems/exfat/Utility.h b/src/add-ons/kernel/file_systems/exfat/Utility.h new file mode 100644 index 0000000000..8d4ebe0da5 --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/Utility.h @@ -0,0 +1,41 @@ +/* + * Copyright 2001-2009, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ +#ifndef UTILITY_H +#define UTILITY_H + + +#include "exfat.h" + + +enum inode_type { + S_DIRECTORY = S_IFDIR, + S_FILE = S_IFREG, + S_SYMLINK = S_IFLNK, + + S_INDEX_TYPES = (S_STR_INDEX | S_INT_INDEX | S_UINT_INDEX + | S_LONG_LONG_INDEX | S_ULONG_LONG_INDEX + | S_FLOAT_INDEX | S_DOUBLE_INDEX), + + S_EXTENDED_TYPES = (S_ATTR_DIR | S_ATTR | S_INDEX_DIR) +}; + + +/*! 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. +*/ +inline int +open_mode_to_access(int openMode) +{ + openMode &= O_RWMASK; + if (openMode == O_RDONLY) + return R_OK; + if (openMode == O_WRONLY) + return W_OK; + + return R_OK | W_OK; +} + +#endif // UTILITY_H diff --git a/src/add-ons/kernel/file_systems/exfat/Volume.cpp b/src/add-ons/kernel/file_systems/exfat/Volume.cpp new file mode 100644 index 0000000000..876e2ec9eb --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/Volume.cpp @@ -0,0 +1,474 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * Copyright 2008-2010, 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 + +#include + +#include "CachedBlock.h" +#include "Inode.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; +}; + + +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 - + + +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(fs_volume* volume) + : + fFSVolume(volume), + fFlags(0), + fRootNode(NULL), + fNextId(1) +{ + mutex_init(&fLock, "exfat volume"); + fInodesClusterTree = new InodesClusterTree; + fInodesInoTree = new InodesInoTree; +} + + +Volume::~Volume() +{ + TRACE("Volume destructor.\n"); + delete fInodesClusterTree; + delete fInodesInoTree; +} + + +bool +Volume::IsValidSuperBlock() +{ + return fSuperBlock.IsValid(); +} + + +const char* +Volume::Name() const +{ + /* TODO volume name is in the root directory */ + 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 super block + 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 %ld\n", fBlockSize); + fEntriesPerBlock = (fBlockSize / sizeof(struct exfat_entry)); + + // check if the device size is large enough to hold the file system + off_t diskSize; + status = opener.GetSize(&diskSize); + if (status != B_OK) + return status; + if (diskSize < (off_t)fSuperBlock.NumBlocks() << fSuperBlock.BlockShift()) + 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: %lld (%s)\n", fRootNode->ID(), + strerror(fRootNode->InitCheck())); + + // all went fine + opener.Keep(); + + /*if (!fSuperBlock.label[0]) {*/ + // generate a more or less descriptive volume name + off_t divisor = 1ULL << 40; + char unit = 'T'; + if (diskSize < divisor) { + divisor = 1UL << 30; + unit = 'G'; + if (diskSize < divisor) { + divisor = 1UL << 20; + unit = 'M'; + } + } + + double size = double((10 * diskSize + divisor - 1) / divisor); + // %g in the kernel does not support precision... + + snprintf(fName, sizeof(fName), "%g %cB ExFAT Volume", + size / 10, unit); + //} + + 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) +{ + block = ((cluster - 2) << SuperBlock().BlocksPerClusterShift()) + + SuperBlock().FirstDataBlock(); + TRACE("Volume::ClusterToBlock() cluster %lu %u %lu: %llu, %lu\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 %lu next %lu\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; + struct node* node = fNodeTree.Lookup(key); + if (node != NULL) { + TRACE("Volume::GetIno() cached cluster %lu offset %lu 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 %lu offset %lu ino %" B_PRIdINO "\n", + cluster, offset, node->ino); + return node->ino; +} + + +struct node_key* +Volume::GetNode(ino_t ino, ino_t &parent) +{ + 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 super block!\n"); + return B_BAD_VALUE; + } + + return B_OK; +} + diff --git a/src/add-ons/kernel/file_systems/exfat/Volume.h b/src/add-ons/kernel/file_systems/exfat/Volume.h new file mode 100644 index 0000000000..d3590102f4 --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/Volume.h @@ -0,0 +1,159 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * Copyright 2008-2010, 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 "exfat.h" +#include "SplayTree.h" + + +struct node_key { + cluster_t cluster; + uint32 offset; +}; + +struct node { + struct node_key key; + ino_t ino; + ino_t parent; + SplayTreeLink nodeTreeLink; + SplayTreeLink inoTreeLink; +}; + + +struct NodeTreeDefinition { + typedef struct node_key KeyType; + typedef struct node NodeType; + + static KeyType GetKey(const NodeType* node) + { + return node->key; + } + + static SplayTreeLink* GetLink(NodeType* node) + { + return &node->nodeTreeLink; + } + + static int Compare(KeyType key, const NodeType* node) + { + if (key.cluster == node->key.cluster) { + if (key.offset == node->key.offset) + return 0; + return key.offset < node->key.offset ? -1 : 1; + } + return key.cluster < node->key.cluster ? -1 : 1; + } +}; + +struct InoTreeDefinition { + typedef ino_t KeyType; + typedef struct node NodeType; + + static KeyType GetKey(const NodeType* node) + { + return node->ino; + } + + static SplayTreeLink* GetLink(NodeType* node) + { + return &node->inoTreeLink; + } + + static int Compare(KeyType key, const NodeType* node) + { + if (key != node->ino) + return key < node->ino ? -1 : 1; + return 0; + } +}; + + +typedef SplayTree NodeTree; +typedef SplayTree InoTree; +class Inode; +struct InodesInoTreeDefinition; +typedef IteratableSplayTree InodesInoTree; +struct InodesClusterTreeDefinition; +typedef IteratableSplayTree InodesClusterTree; + + +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) != 0; } + + 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; + + uint32 BlockSize() const { return fBlockSize; } + uint32 EntriesPerBlock() const + { return fEntriesPerBlock; } + uint32 EntriesPerCluster() + { return fEntriesPerBlock + << SuperBlock().BlocksPerClusterShift(); } + size_t ClusterSize() { return fBlockSize + << SuperBlock().BlocksPerClusterShift(); } + exfat_super_block& SuperBlock() { return fSuperBlock; } + + status_t LoadSuperBlock(); + + // cache access + void* BlockCache() { return fBlockCache; } + + static status_t Identify(int fd, exfat_super_block* superBlock); + + status_t ClusterToBlock(cluster_t cluster, + fsblock_t &block); + Inode * FindInode(ino_t id); + Inode * FindInode(cluster_t cluster); + cluster_t NextCluster(cluster_t cluster); + ino_t GetIno(cluster_t cluster, uint32 offset, ino_t parent); + struct node_key* GetNode(ino_t ino, ino_t &parent); +private: + ino_t _NextID() { return fNextId++; } + + mutex fLock; + fs_volume* fFSVolume; + int fDevice; + exfat_super_block fSuperBlock; + char fName[32]; + + uint16 fFlags; + uint32 fBlockSize; + uint32 fEntriesPerBlock; + Inode* fRootNode; + ino_t fNextId; + + void* fBlockCache; + InodesInoTree* fInodesInoTree; + InodesClusterTree* fInodesClusterTree; + NodeTree fNodeTree; + InoTree fInoTree; +}; + +#endif // VOLUME_H + diff --git a/src/add-ons/kernel/file_systems/exfat/encodings.cpp b/src/add-ons/kernel/file_systems/exfat/encodings.cpp new file mode 100644 index 0000000000..53e5b35b95 --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/encodings.cpp @@ -0,0 +1,210 @@ +/* + Copyright 1999-2001, Be Incorporated. All Rights Reserved. + This file may be used under the terms of the Be Sample Code License. +*/ + + +#include +#include +#include +#include +#include +#include + + +#include "encodings.h" + + +// Pierre's Uber Macro +#define u_lendian_to_utf8(str, uni_str)\ +{\ + if ((B_LENDIAN_TO_HOST_INT16(uni_str[0])&0xff80) == 0)\ + *str++ = B_LENDIAN_TO_HOST_INT16(*uni_str++);\ + else if ((B_LENDIAN_TO_HOST_INT16(uni_str[0])&0xf800) == 0) {\ + str[0] = 0xc0|(B_LENDIAN_TO_HOST_INT16(uni_str[0])>>6);\ + str[1] = 0x80|(B_LENDIAN_TO_HOST_INT16(*uni_str++)&0x3f);\ + str += 2;\ + } else if ((B_LENDIAN_TO_HOST_INT16(uni_str[0])&0xfc00) != 0xd800) {\ + str[0] = 0xe0|(B_LENDIAN_TO_HOST_INT16(uni_str[0])>>12);\ + str[1] = 0x80|((B_LENDIAN_TO_HOST_INT16(uni_str[0])>>6)&0x3f);\ + str[2] = 0x80|(B_LENDIAN_TO_HOST_INT16(*uni_str++)&0x3f);\ + str += 3;\ + } else {\ + int val;\ + val = ((B_LENDIAN_TO_HOST_INT16(uni_str[0])-0xd7c0)<<10) | (B_LENDIAN_TO_HOST_INT16(uni_str[1])&0x3ff);\ + str[0] = 0xf0 | (val>>18);\ + str[1] = 0x80 | ((val>>12)&0x3f);\ + str[2] = 0x80 | ((val>>6)&0x3f);\ + str[3] = 0x80 | (val&0x3f);\ + uni_str += 2; str += 4;\ + }\ +} + +// Pierre's Uber Macro +#define u_hostendian_to_utf8(str, uni_str)\ +{\ + if ((uni_str[0]&0xff80) == 0)\ + *str++ = *uni_str++;\ + else if ((uni_str[0]&0xf800) == 0) {\ + str[0] = 0xc0|(uni_str[0]>>6);\ + str[1] = 0x80|(*uni_str++&0x3f);\ + str += 2;\ + } else if ((uni_str[0]&0xfc00) != 0xd800) {\ + str[0] = 0xe0|(uni_str[0]>>12);\ + str[1] = 0x80|((uni_str[0]>>6)&0x3f);\ + str[2] = 0x80|(*uni_str++&0x3f);\ + str += 3;\ + } else {\ + int val;\ + val = ((uni_str[0]-0xd7c0)<<10) | (uni_str[1]&0x3ff);\ + str[0] = 0xf0 | (val>>18);\ + str[1] = 0x80 | ((val>>12)&0x3f);\ + str[2] = 0x80 | ((val>>6)&0x3f);\ + str[3] = 0x80 | (val&0x3f);\ + uni_str += 2; str += 4;\ + }\ +} + +// Another Uber Macro +#define utf8_to_u_hostendian(str, uni_str, err_flag) \ +{\ + err_flag = 0;\ + if ((str[0]&0x80) == 0)\ + *uni_str++ = *str++;\ + else if ((str[1] & 0xC0) != 0x80) {\ + *uni_str++ = 0xfffd;\ + str+=1;\ + } else if ((str[0]&0x20) == 0) {\ + *uni_str++ = ((str[0]&31)<<6) | (str[1]&63);\ + str+=2;\ + } else if ((str[2] & 0xC0) != 0x80) {\ + *uni_str++ = 0xfffd;\ + str+=2;\ + } else if ((str[0]&0x10) == 0) {\ + *uni_str++ = ((str[0]&15)<<12) | ((str[1]&63)<<6) | (str[2]&63);\ + str+=3;\ + } else if ((str[3] & 0xC0) != 0x80) {\ + *uni_str++ = 0xfffd;\ + str+=3;\ + } else {\ + err_flag = 1;\ + }\ +} + +// Count the number of bytes of a UTF-8 character +#define utf8_char_len(c) ((((int32)0xE5000000 >> ((c >> 3) & 0x1E)) & 3) + 1) + +// converts LENDIAN unicode to utf8 +static status_t +_lendian_unicode_to_utf8( + const char *src, + int32 *srcLen, + char *dst, + uint32 *dstLen) +{ + int32 srcLimit = *srcLen; + int32 dstLimit = *dstLen; + int32 srcCount = 0; + int32 dstCount = 0; + + for (srcCount = 0; srcCount < srcLimit; srcCount += 2) { + uint16 *UNICODE = (uint16 *)&src[srcCount]; + if (*UNICODE == 0) + break; + uchar utf8[4]; + uchar *UTF8 = utf8; + int32 utf8Len; + int32 j; + + u_lendian_to_utf8(UTF8, UNICODE); + + utf8Len = UTF8 - utf8; + if ((dstCount + utf8Len) > dstLimit) + break; + + for (j = 0; j < utf8Len; j++) + dst[dstCount + j] = utf8[j]; + dstCount += utf8Len; + } + + *srcLen = srcCount; + *dstLen = dstCount; + dst[dstCount] = '\0'; + + return ((dstCount > 0) ? B_NO_ERROR : B_ERROR); +} + +// utf8 to LENDIAN unicode +static status_t +_utf8_to_lendian_unicode( + const char *src, + int32 *srcLen, + char *dst, + uint32 *dstLen) +{ + int32 srcLimit = *srcLen; + int32 dstLimit = *dstLen - 1; + int32 srcCount = 0; + int32 dstCount = 0; + + while ((srcCount < srcLimit) && (dstCount < dstLimit)) { + uint16 unicode; + uint16 *UNICODE = &unicode; + uchar *UTF8 = (uchar *)src + srcCount; + int err_flag; + + if ((srcCount + utf8_char_len(src[srcCount])) > srcLimit) + break; + + utf8_to_u_hostendian(UTF8, UNICODE, err_flag); + if(err_flag == 1) + return EINVAL; + + unicode = B_HOST_TO_LENDIAN_INT16(unicode); + dst[dstCount++] = unicode & 0xFF; + dst[dstCount++] = unicode >> 8; + + srcCount += UTF8 - ((uchar *)(src + srcCount)); + } + + *srcLen = srcCount; + *dstLen = dstCount; + + return ((dstCount > 0) ? B_NO_ERROR : B_ERROR); +} + + +// takes a unicode name of unilen uchar's and converts to a utf8 name of at +// most utf8len uint8's +status_t unicode_to_utf8(const uchar *uni, uint32 unilen, uint8 *utf8, + uint32 *utf8len) +{ + uint32 origlen = unilen; + status_t result = _lendian_unicode_to_utf8((char *)uni, + (int32 *)&unilen, (char *)utf8, utf8len); + + /*if (unilen < origlen) { + panic("Name is too long (%lx < %lx)\n", unilen, origlen); + return B_ERROR; + }*/ + + return result; +} + + +status_t utf8_to_unicode(const char *utf8, uchar *uni, uint32 *unilen) +{ + uint32 origlen = strlen(utf8) + 1; + uint32 utf8len = origlen; + + status_t result = _utf8_to_lendian_unicode(utf8, + (int32 *)&utf8len, (char *)uni, unilen); + + /*if (origlen < utf8len) { + panic("Name is too long (%lx < %lx)\n", *unilen, origlen); + return B_ERROR; + }*/ + + return result; +} + diff --git a/src/add-ons/kernel/file_systems/exfat/encodings.h b/src/add-ons/kernel/file_systems/exfat/encodings.h new file mode 100644 index 0000000000..2d4fdcd33a --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/encodings.h @@ -0,0 +1,20 @@ +/* + Copyright 1999-2001, Be Incorporated. All Rights Reserved. + This file may be used under the terms of the Be Sample Code License. +*/ +#ifndef _ENCODINGS_H_ +#define _ENCODINGS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +status_t unicode_to_utf8(const uchar *uni, uint32 unilen, uint8 *utf8, + uint32 *utf8len); +status_t utf8_to_unicode(const char *utf8, uchar *uni, uint32 *unilen); + +#ifdef __cplusplus +} +#endif + +#endif // _ENCODINGS_H_ diff --git a/src/add-ons/kernel/file_systems/exfat/exfat.h b/src/add-ons/kernel/file_systems/exfat/exfat.h new file mode 100644 index 0000000000..1a3f52d2ef --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/exfat.h @@ -0,0 +1,171 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * Distributed under the terms of the MIT License. + */ +#ifndef EXFAT_H +#define EXFAT_H + + +#include + +#include +#include +#include + + +typedef uint64 fileblock_t; // file block number +typedef uint64 fsblock_t; // filesystem block number + +typedef uint32 cluster_t; + +#define EXFAT_SUPER_BLOCK_OFFSET 0x0 + + +struct exfat_super_block { + uint8 jump_boot[3]; + char filesystem[8]; + uint8 reserved[53]; + uint64 first_block; + uint64 num_blocks; + uint32 first_fat_block; + uint32 fat_length; + uint32 first_data_block; + uint32 cluster_count; + uint32 root_dir_cluster; + uint32 serial_number; + uint8 version_minor; + uint8 version_major; + uint16 flags; + uint8 block_shift; + uint8 blocks_per_cluster_shift; + uint8 fat_count; + uint8 drive_select; + uint8 used_percent; + uint8 reserved2[7]; + uint8 boot_code[390]; + uint16 signature; + + bool IsValid(); + // implemented in Volume.cpp + uint64 FirstBlock() const { return B_LENDIAN_TO_HOST_INT64(first_block); } + uint64 NumBlocks() const { return B_LENDIAN_TO_HOST_INT32(num_blocks); } + uint32 FirstFatBlock() const + { return B_LENDIAN_TO_HOST_INT32(first_fat_block); } + uint32 FatLength() const + { return B_LENDIAN_TO_HOST_INT32(fat_length); } + uint32 FirstDataBlock() const + { return B_LENDIAN_TO_HOST_INT32(first_data_block); } + uint32 ClusterCount() const + { return B_LENDIAN_TO_HOST_INT32(cluster_count); } + uint32 RootDirCluster() const + { return B_LENDIAN_TO_HOST_INT32(root_dir_cluster); } + uint32 SerialNumber() const + { return B_LENDIAN_TO_HOST_INT32(serial_number); } + uint8 VersionMinor() const { return version_minor; } + uint8 VersionMajor() const { return version_major; } + uint16 Flags() const { return B_LENDIAN_TO_HOST_INT16(flags); } + uint8 BlockShift() const { return block_shift; } + uint8 BlocksPerClusterShift() const { return blocks_per_cluster_shift; } + uint8 FatCount() const { return fat_count; } + uint8 DriveSelect() const { return drive_select; } + uint8 UsedPercent() const { return used_percent; } +} _PACKED; + + +#define EXFAT_SUPER_BLOCK_MAGIC "EXFAT " + +#define EXFAT_ENTRY_TYPE_BITMAP 0x81 +#define EXFAT_ENTRY_TYPE_UPPERCASE 0x82 +#define EXFAT_ENTRY_TYPE_LABEL 0x83 +#define EXFAT_ENTRY_TYPE_FILE 0x85 +#define EXFAT_ENTRY_TYPE_FILEINFO 0xc0 +#define EXFAT_ENTRY_TYPE_FILENAME 0xc1 +#define EXFAT_CLUSTER_END 0xffffffff +#define EXFAT_ENTRY_ATTRIB_SUBDIR 0x10 + +#define EXFAT_ENTRY_FLAG_CONTIGUOUS 0x3 + +#define EXFAT_FILENAME_MAX_LENGTH 512 + +struct exfat_entry { + uint8 type; + union { + struct { + uint8 length; + char name[30]; + } _PACKED name_label; + struct { + uint8 reserved[3]; + uint32 checksum; + uint8 reserved2[12]; + uint32 start_cluster; + uint64 size; + } _PACKED bitmap_uppercase; + struct { + uint8 chunkCount; + uint16 checksum; + uint16 attribs; + uint16 reserved; + uint16 creation_time; + uint16 creation_date; + uint16 modification_time; + uint16 modification_date; + uint16 access_time; + uint16 access_date; + uint8 creation_time_low; + uint8 modification_time_low; + uint8 reserved2[10]; + uint16 ModificationTime() const + { return B_LENDIAN_TO_HOST_INT16(modification_time); } + uint16 ModificationDate() const + { return B_LENDIAN_TO_HOST_INT16(modification_date); } + uint16 AccessTime() const + { return B_LENDIAN_TO_HOST_INT16(access_time); } + uint16 AccessDate() const + { return B_LENDIAN_TO_HOST_INT16(access_date); } + uint16 CreationTime() const + { return B_LENDIAN_TO_HOST_INT16(creation_time); } + uint16 CreationDate() const + { return B_LENDIAN_TO_HOST_INT16(creation_date); } + uint16 Attribs() const + { return B_LENDIAN_TO_HOST_INT16(attribs); } + void SetAttribs(uint16 newAttribs) + { attribs = B_HOST_TO_LENDIAN_INT16(newAttribs); } + } _PACKED file; + struct { + uint8 flag; + uint8 reserved; + uint8 name_length; + uint16 name_hash; + uint8 reserved2[2]; + uint64 size1; + uint8 reserved3[4]; + uint32 start_cluster; + uint64 size2; + uint32 StartCluster() const + { return B_LENDIAN_TO_HOST_INT32(start_cluster); } + void SetStartCluster(uint32 startCluster) + { start_cluster = B_HOST_TO_LENDIAN_INT32(startCluster); } + bool IsContiguous() const + { return (flag & EXFAT_ENTRY_FLAG_CONTIGUOUS) != 0; } + void SetFlag(uint8 newFlag) + { flag = newFlag; } + uint64 Size() const + { return B_LENDIAN_TO_HOST_INT64(size1); } + } _PACKED file_info; + }; +} _PACKED; + + +struct file_cookie { + bigtime_t last_notification; + off_t last_size; + int open_mode; +}; + +#define EXFAT_OPEN_MODE_USER_MASK 0x7fffffff + +extern fs_volume_ops gExfatVolumeOps; +extern fs_vnode_ops gExfatVnodeOps; + +#endif // EXFAT_H diff --git a/src/add-ons/kernel/file_systems/exfat/kernel_interface.cpp b/src/add-ons/kernel/file_systems/exfat/kernel_interface.cpp new file mode 100644 index 0000000000..496f989f15 --- /dev/null +++ b/src/add-ons/kernel/file_systems/exfat/kernel_interface.cpp @@ -0,0 +1,683 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * 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 +#include +#include +#include + +#include "DirectoryIterator.h" +#include "exfat.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) + + +#define EXFAT_IO_SIZE 65536 + + +struct identify_cookie { + exfat_super_block super_block; +}; + + +//! exfat_io() callback hook +static status_t +iterative_io_get_vecs_hook(void* cookie, io_request* request, off_t offset, + size_t size, struct file_io_vec* vecs, size_t* _count) +{ + Inode* inode = (Inode*)cookie; + + return file_map_translate(inode->Map(), offset, size, vecs, _count, + inode->GetVolume()->BlockSize()); +} + + +//! exfat_io() callback hook +static status_t +iterative_io_finished_hook(void* cookie, io_request* request, status_t status, + bool partialTransfer, size_t bytesTransferred) +{ + Inode* inode = (Inode*)cookie; + rw_lock_read_unlock(inode->Lock()); + return B_OK; +} + + +// #pragma mark - Scanning + + +static float +exfat_identify_partition(int fd, partition_data *partition, void **_cookie) +{ + exfat_super_block superBlock; + status_t status = Volume::Identify(fd, &superBlock); + if (status != B_OK) + return -1; + + identify_cookie *cookie = new identify_cookie; + memcpy(&cookie->super_block, &superBlock, sizeof(exfat_super_block)); + + *_cookie = cookie; + return 0.8f; +} + + +static status_t +exfat_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 = 1 << cookie->super_block.BlockShift(); + // TODO volume name isn't in the superblock + partition->content_name = strdup(cookie->super_block.filesystem); + if (partition->content_name == NULL) + return B_NO_MEMORY; + + return B_OK; +} + + +static void +exfat_free_identify_partition_cookie(partition_data* partition, void* _cookie) +{ + delete (identify_cookie*)_cookie; +} + + +// #pragma mark - + + +static status_t +exfat_mount(fs_volume* _volume, const char* device, uint32 flags, + const char* args, ino_t* _rootID) +{ + Volume* volume = new(std::nothrow) 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 = &gExfatVolumeOps; + + status_t status = volume->Mount(device, flags); + if (status != B_OK) { + ERROR("Failed mounting the volume. Error: %s\n", strerror(status)); + delete volume; + return status; + } + + *_rootID = volume->RootNode()->ID(); + return B_OK; +} + + +static status_t +exfat_unmount(fs_volume *_volume) +{ + Volume* volume = (Volume *)_volume->private_volume; + + status_t status = volume->Unmount(); + delete volume; + + return status; +} + + +static status_t +exfat_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 = EXFAT_IO_SIZE; + info->block_size = volume->BlockSize(); + info->total_blocks = volume->SuperBlock().NumBlocks(); + info->free_blocks = 0; //volume->NumFreeBlocks(); + + // Volume name + strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name)); + + // File system name + strlcpy(info->fsh_name, "exfat", sizeof(info->fsh_name)); + + return B_OK; +} + + +// #pragma mark - + + +static status_t +exfat_get_vnode(fs_volume* _volume, ino_t id, fs_vnode* _node, int* _type, + uint32* _flags, bool reenter) +{ + TRACE("get_vnode %lu\n", id); + Volume* volume = (Volume*)_volume->private_volume; + + Inode* inode = new(std::nothrow) 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 = &gExfatVnodeOps; + *_type = inode->Mode(); + *_flags = 0; + } else + ERROR("get_vnode: InitCheck() failed. Error: %s\n", strerror(status)); + + return status; +} + + +static status_t +exfat_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter) +{ + delete (Inode*)_node->private_node; + return B_OK; +} + + +static bool +exfat_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie) +{ + return true; +} + + +static status_t +exfat_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie, + off_t pos, const iovec* vecs, size_t count, size_t* _numBytes) +{ + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + + if (inode->FileCache() == NULL) + return B_BAD_VALUE; + + 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, 0); + 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; + } + + rw_lock_read_unlock(inode->Lock()); + + return status; +} + + +static status_t +exfat_io(fs_volume* _volume, fs_vnode* _node, void* _cookie, io_request* request) +{ + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + +#ifndef EXFAT_SHELL + if (io_request_is_write(request) && volume->IsReadOnly()) { + notify_io_request(request, B_READ_ONLY_DEVICE); + return B_READ_ONLY_DEVICE; + } +#endif + + if (inode->FileCache() == NULL) { +#ifndef EXFAT_SHELL + notify_io_request(request, B_BAD_VALUE); +#endif + return B_BAD_VALUE; + } + + // We lock the node here and will unlock it in the "finished" hook. + rw_lock_read_lock(inode->Lock()); + + return do_iterative_fd_io(volume->Device(), request, + iterative_io_get_vecs_hook, iterative_io_finished_hook, inode); +} + + +static status_t +exfat_get_file_map(fs_volume* _volume, fs_vnode* _node, off_t offset, + size_t size, struct file_io_vec* vecs, size_t* _count) +{ + TRACE("exfat_get_file_map()\n"); + Inode* inode = (Inode*)_node->private_node; + size_t index = 0, max = *_count; + + while (true) { + off_t blockOffset; + off_t blockLength; + status_t status = inode->FindBlock(offset, blockOffset, &blockLength); + if (status != B_OK) + return status; + + if (index > 0 && (vecs[index - 1].offset + == blockOffset - vecs[index - 1].length)) { + 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; + size -= blockLength; + + if (size <= vecs[index - 1].length || offset >= inode->Size()) { + // We're done! + *_count = index; + TRACE("exfat_get_file_map for inode %lld\n", inode->ID()); + return B_OK; + } + } + + // can never get here + return B_ERROR; +} + + +// #pragma mark - + + +static status_t +exfat_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name, + ino_t* _vnodeID) +{ + TRACE("exfat_lookup: name address: %p (%s)\n", name, name); + 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; + + status = DirectoryIterator(directory).Lookup(name, strlen(name), _vnodeID); + if (status != B_OK) { + ERROR("exfat_lookup: name %s (%s)\n", name, strerror(status)); + return status; + } + + TRACE("exfat_lookup: ID %d\n", *_vnodeID); + + return get_vnode(volume->FSVolume(), *_vnodeID, NULL); +} + + +static status_t +exfat_ioctl(fs_volume* _volume, fs_vnode* _node, void* _cookie, uint32 cmd, + void* buffer, size_t bufferLength) +{ + TRACE("ioctl: %lu\n", cmd); + + /*Volume* volume = (Volume*)_volume->private_volume;*/ + return B_OK; +} + + +static status_t +exfat_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat) +{ + Inode* inode = (Inode*)_node->private_node; + + stat->st_dev = inode->GetVolume()->ID(); + stat->st_ino = inode->ID(); + stat->st_nlink = 1; + stat->st_blksize = EXFAT_IO_SIZE; + + stat->st_uid = inode->UserID(); + stat->st_gid = inode->GroupID(); + stat->st_mode = inode->Mode(); + stat->st_type = 0; + + inode->GetAccessTime(stat->st_atim); + inode->GetModificationTime(stat->st_mtim); + inode->GetChangeTime(stat->st_ctim); + inode->GetCreationTime(stat->st_crtim); + + stat->st_size = inode->Size(); + stat->st_blocks = (inode->Size() + 511) / 512; + + return B_OK; +} + + +static status_t +exfat_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; + + status_t status = inode->CheckPermissions(open_mode_to_access(openMode) + | (openMode & O_TRUNC ? W_OK : 0)); + if (status != B_OK) + return status; + + // Prepare the cookie + file_cookie* cookie = new(std::nothrow) file_cookie; + if (cookie == NULL) + return B_NO_MEMORY; + ObjectDeleter cookieDeleter(cookie); + + cookie->open_mode = openMode & EXFAT_OPEN_MODE_USER_MASK; + cookie->last_size = inode->Size(); + cookie->last_notification = system_time(); + + if ((openMode & O_NOCACHE) != 0 && inode->FileCache() != NULL) { + // Disable the file cache, if requested? + status = file_cache_disable(inode->FileCache()); + if (status != B_OK) + return status; + } + + cookieDeleter.Detach(); + *_cookie = cookie; + + return B_OK; +} + + +static status_t +exfat_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 +exfat_close(fs_volume *_volume, fs_vnode *_node, void *_cookie) +{ + return B_OK; +} + + +static status_t +exfat_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie) +{ + file_cookie* cookie = (file_cookie*)_cookie; + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + + if (inode->Size() != cookie->last_size) + notify_stat_changed(volume->ID(), inode->ID(), B_STAT_SIZE); + + delete cookie; + return B_OK; +} + + +static status_t +exfat_access(fs_volume* _volume, fs_vnode* _node, int accessMode) +{ + Inode* inode = (Inode*)_node->private_node; + return inode->CheckPermissions(accessMode); +} + + +static status_t +exfat_read_link(fs_volume *_volume, fs_vnode *_node, char *buffer, + size_t *_bufferSize) +{ + Inode* inode = (Inode*)_node->private_node; + return inode->ReadAt(0, (uint8*)buffer, _bufferSize); +} + + +// #pragma mark - Directory functions + + +static status_t +exfat_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_NOT_A_DIRECTORY; + + DirectoryIterator* iterator = new(std::nothrow) DirectoryIterator(inode); + if (iterator == NULL || iterator->InitCheck() != B_OK) { + delete iterator; + return B_NO_MEMORY; + } + + *_cookie = iterator; + return B_OK; +} + + +static status_t +exfat_read_dir(fs_volume *_volume, fs_vnode *_node, void *_cookie, + struct dirent *dirent, size_t bufferSize, uint32 *_num) +{ + TRACE("exfat_read_dir\n"); + 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; + + TRACE("exfat_read_dir end\n"); + + return B_OK; +} + + +static status_t +exfat_rewind_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void *_cookie) +{ + DirectoryIterator* iterator = (DirectoryIterator*)_cookie; + + return iterator->Rewind(); +} + + +static status_t +exfat_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/) +{ + return B_OK; +} + + +static status_t +exfat_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie) +{ + delete (DirectoryIterator*)_cookie; + return B_OK; +} + + +fs_volume_ops gExfatVolumeOps = { + &exfat_unmount, + &exfat_read_fs_info, + NULL, // write_fs_info() + NULL, // fs_sync, + &exfat_get_vnode, +}; + + +fs_vnode_ops gExfatVnodeOps = { + /* vnode operations */ + &exfat_lookup, + NULL, + &exfat_put_vnode, + NULL, // exfat_remove_vnode, + + /* VM file access */ + &exfat_can_page, + &exfat_read_pages, + NULL, // exfat_write_pages, + + NULL, // io() + NULL, // cancel_io() + + &exfat_get_file_map, + + &exfat_ioctl, + NULL, + NULL, // fs_select + NULL, // fs_deselect + NULL, // fs_fsync, + + &exfat_read_link, + NULL, // fs_create_symlink, + + NULL, // fs_link, + NULL, // fs_unlink, + NULL, // fs_rename, + + &exfat_access, + &exfat_read_stat, + NULL, // fs_write_stat, + NULL, // fs_preallocate + + /* file operations */ + NULL, // fs_create, + &exfat_open, + &exfat_close, + &exfat_free_cookie, + &exfat_read, + NULL, // fs_write, + + /* directory operations */ + NULL, // fs_create_dir, + NULL, // fs_remove_dir, + &exfat_open_dir, + &exfat_close_dir, + &exfat_free_dir_cookie, + &exfat_read_dir, + &exfat_rewind_dir, + + /* attribute directory operations */ + NULL, // fs_open_attr_dir, + NULL, // fs_close_attr_dir, + NULL, // fs_free_attr_dir_cookie, + NULL, // fs_read_attr_dir, + NULL, // fs_rewind_attr_dir, + + /* attribute operations */ + NULL, // fs_create_attr, + NULL, // fs_open_attr, + NULL, // fs_close_attr, + NULL, // fs_free_attr_cookie, + NULL, // fs_read_attr, + NULL, // fs_write_attr, + NULL, // fs_read_attr_stat, + NULL, // fs_write_attr_stat, + NULL, // fs_rename_attr, + NULL, // fs_remove_attr, +}; + + +static file_system_module_info sExfatFileSystem = { + { + "file_systems/exfat" B_CURRENT_FS_API_VERSION, + 0, + NULL, + }, + + "exfat", // short_name + "ExFAT File System", // pretty_name + 0, // DDM flags + + // scanning + exfat_identify_partition, + exfat_scan_partition, + exfat_free_identify_partition_cookie, + NULL, // free_partition_content_cookie() + + &exfat_mount, + + NULL, +}; + + +module_info *modules[] = { + (module_info *)&sExfatFileSystem, + NULL, +};