/*
* Copyright 2003-2013, Axel Dörfler, axeld@pinc-software.de.
* Copyright 2008, François Revol <revol@free.fr>
* Distributed under the terms of the MIT License.
*/
#include "Directory.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <new>
#include <StorageDefs.h>
#include "CachedBlock.h"
#include "File.h"
#include "Volume.h"
//#define TRACE(x) dprintf x
#define TRACE(x) do {} while (0)
using std::nothrow;
namespace FATFS {
struct dir_entry {
void *Buffer() const { return (void *)fName; };
const char *BaseName() const { return fName; };
const char *Extension() const { return fExt; };
uint8 Flags() const { return fFlags; };
uint32 Cluster(int32 fatBits) const;
void SetCluster(uint32 cluster, int32 fatBits);
uint32 Size() const { return B_LENDIAN_TO_HOST_INT32(fSize); };
void SetSize(uint32 size);
bool IsFile() const;
bool IsDir() const;
char fName[8];
char fExt[3];
uint8 fFlags;
uint8 fReserved1;
uint8 fCreateTime10ms;
uint16 fCreateTime;
uint16 fCreateDate;
uint16 fAccessDate;
uint16 fClusterMSB;
uint16 fModifiedTime;
uint16 fModifiedDate;
uint16 fClusterLSB;
uint32 fSize;
} _PACKED;
uint32
dir_entry::Cluster(int32 fatBits) const
{
uint32 c = B_LENDIAN_TO_HOST_INT16(fClusterLSB);
if (fatBits == 32)
c += ((uint32)B_LENDIAN_TO_HOST_INT16(fClusterMSB) << 16);
return c;
}
void
dir_entry::SetCluster(uint32 cluster, int32 fatBits)
{
fClusterLSB = B_HOST_TO_LENDIAN_INT16((uint16)cluster);
if (fatBits == 32)
fClusterMSB = B_HOST_TO_LENDIAN_INT16(cluster >> 16);
}
void
dir_entry::SetSize(uint32 size)
{
fSize = B_HOST_TO_LENDIAN_INT32(size);
}
bool
dir_entry::IsFile() const
{
return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == 0);
}
bool
dir_entry::IsDir() const
{
return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_SUBDIR);
}
struct dir_cookie {
enum {
MAX_UTF16_NAME_LENGTH = 255
};
int32 index;
struct dir_entry entry;
off_t entryOffset;
uint16 nameBuffer[MAX_UTF16_NAME_LENGTH];
uint32 nameLength;
off_t Offset() const { return index * sizeof(struct dir_entry); }
char* Name() { return (char*)nameBuffer; }
void ResetName();
bool AddNameChars(const uint16* chars, uint32 count);
bool ConvertNameToUTF8();
void Set8_3Name(const char* baseName, const char* extension);
};
void
dir_cookie::ResetName()
{
nameLength = 0;
}
bool
dir_cookie::AddNameChars(const uint16* chars, uint32 count)
{
// If there is a null character, we ignore it and all subsequent characters.
for (uint32 i = 0; i < count; i++) {
if (chars[i] == 0) {
count = i;
break;
}
}
if (count > 0) {
if (count > (MAX_UTF16_NAME_LENGTH - nameLength))
return false;
nameLength += count;
memcpy(nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength),
chars, count * 2);
}
return true;
}
bool
dir_cookie::ConvertNameToUTF8()
{
char name[B_FILE_NAME_LENGTH];
uint32 nameOffset = 0;
const uint16* utf16 = nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength);
for (uint32 i = 0; i < nameLength; i++) {
uint8 utf8[4];
uint32 count;
uint16 c = B_LENDIAN_TO_HOST_INT16(utf16[i]);
if (c < 0x80) {
utf8[0] = c;
count = 1;
} else if (c < 0xff80) {
utf8[0] = 0xc0 | (c >> 6);
utf8[1] = 0x80 | (c & 0x3f);
count = 2;
} else if ((c & 0xfc00) != 0xd800) {
utf8[0] = 0xe0 | (c >> 12);
utf8[1] = 0x80 | ((c >> 6) & 0x3f);
utf8[2] = 0x80 | (c & 0x3f);
count = 3;
} else {
// surrogate pair
if (i + 1 >= nameLength)
return false;
uint16 c2 = B_LENDIAN_TO_HOST_INT16(utf16[++i]);
if ((c2 & 0xfc00) != 0xdc00)
return false;
uint32 value = ((c - 0xd7c0) << 10) | (c2 & 0x3ff);
utf8[0] = 0xf0 | (value >> 18);
utf8[1] = 0x80 | ((value >> 12) & 0x3f);
utf8[2] = 0x80 | ((value >> 6) & 0x3f);
utf8[3] = 0x80 | (value & 0x3f);
count = 4;
}
if (nameOffset + count >= sizeof(name))
return false;
memcpy(name + nameOffset, utf8, count);
nameOffset += count;
}
name[nameOffset] = '\0';
strlcpy(Name(), name, sizeof(nameBuffer));
return true;
}
void
dir_cookie::Set8_3Name(const char* baseName, const char* extension)
{
// trim base name
uint32 baseNameLength = 8;
while (baseNameLength > 0 && baseName[baseNameLength - 1] == ' ')
baseNameLength--;
// trim extension
uint32 extensionLength = 3;
while (extensionLength > 0 && extension[extensionLength - 1] == ' ')
extensionLength--;
// compose the name
char* name = Name();
memcpy(name, baseName, baseNameLength);
if (extensionLength > 0) {
name[baseNameLength] = '.';
memcpy(name + baseNameLength + 1, extension, extensionLength);
name[baseNameLength + 1 + extensionLength] = '\0';
} else
name[baseNameLength] = '\0';
}
// #pragma mark -
static bool
is_valid_8_3_file_name_char(char c)
{
if ((uint8)c >= 128)
return true;
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
return true;
return strchr("*!#$%&'()-@^_`{}~ ", c) != NULL;
}
static bool
check_valid_8_3_file_name(const char* name, const char*& _baseName,
uint32& _baseNameLength, const char*& _extension, uint32& _extensionLength)
{
// check length of base name and extension
size_t nameLength = strlen(name);
const char* extension = strchr(name, '.');
size_t baseNameLength;
size_t extensionLength;
if (extension != NULL) {
baseNameLength = extension - name;
extensionLength = nameLength - baseNameLength - 1;
if (extensionLength > 0)
extension++;
else
extension = NULL;
} else {
baseNameLength = nameLength;
extensionLength = 0;
}
// trim trailing space
while (baseNameLength > 0 && name[baseNameLength - 1] == ' ')
baseNameLength--;
while (extensionLength > 0 && extension[extensionLength - 1] == ' ')
extensionLength--;
if (baseNameLength == 0 || baseNameLength > 8 || extensionLength > 3)
return false;
// check the chars
for (size_t i = 0; i < baseNameLength; i++) {
if (!is_valid_8_3_file_name_char(name[i]))
return false;
}
for (size_t i = 0; i < extensionLength; i++) {
if (!is_valid_8_3_file_name_char(extension[i]))
return false;
}
_baseName = name;
_baseNameLength = baseNameLength;
_extension = extension;
_extensionLength = extensionLength;
return true;
}
// #pragma mark - Directory
Directory::Directory(Volume &volume, off_t dirEntryOffset, uint32 cluster,
const char *name)
:
fVolume(volume),
fStream(volume, cluster, UINT32_MAX, name),
fDirEntryOffset(dirEntryOffset)
{
TRACE(("FASFS::Directory::(, %lu, %s)\n", cluster, name));
}
Directory::~Directory()
{
TRACE(("FASFS::Directory::~()\n"));
}
status_t
Directory::InitCheck()
{
status_t err;
err = fStream.InitCheck();
if (err < B_OK)
return err;
return B_OK;
}
status_t
Directory::Open(void **_cookie, int mode)
{
TRACE(("FASFS::Directory::%s(, %d)\n", __FUNCTION__, mode));
_inherited::Open(_cookie, mode);
dir_cookie *c = new(nothrow) dir_cookie;
if (c == NULL)
return B_NO_MEMORY;
c->index = -1;
c->entryOffset = 0;
*_cookie = (void *)c;
return B_OK;
}
status_t
Directory::Close(void *cookie)
{
TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
_inherited::Close(cookie);
delete (struct dir_cookie *)cookie;
return B_OK;
}
Node*
Directory::LookupDontTraverse(const char* name)
{
TRACE(("FASFS::Directory::%s('%s')\n", __FUNCTION__, name));
if (!strcmp(name, ".")) {
Acquire();
return this;
}
status_t err;
struct dir_cookie cookie;
struct dir_cookie *c = &cookie;
c->index = -1;
c->entryOffset = 0;
do {
err = GetNextEntry(c);
if (err < B_OK)
return NULL;
TRACE(("FASFS::Directory::%s: %s <> '%s'\n", __FUNCTION__,
name, c->Name()));
if (strcasecmp(name, c->Name()) == 0) {
TRACE(("GOT IT!\n"));
break;
}
} while (true);
if (c->entry.IsFile()) {
TRACE(("IS FILE\n"));
return new File(fVolume, c->entryOffset,
c->entry.Cluster(fVolume.FatBits()), c->entry.Size(), name);
}
if (c->entry.IsDir()) {
TRACE(("IS DIR\n"));
return new Directory(fVolume, c->entryOffset,
c->entry.Cluster(fVolume.FatBits()), name);
}
return NULL;
}
status_t
Directory::GetNextEntry(void *cookie, char *name, size_t size)
{
TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
struct dir_cookie *c = (struct dir_cookie *)cookie;
status_t err;
err = GetNextEntry(cookie);
if (err < B_OK)
return err;
strlcpy(name, c->Name(), size);
return B_OK;
}
status_t
Directory::GetNextNode(void *cookie, Node **_node)
{
return B_ERROR;
}
status_t
Directory::Rewind(void *cookie)
{
TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
struct dir_cookie *c = (struct dir_cookie *)cookie;
c->index = -1;
c->entryOffset = 0;
return B_OK;
}
bool
Directory::IsEmpty()
{
TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
struct dir_cookie cookie;
struct dir_cookie *c = &cookie;
c->index = -1;
c->entryOffset = 0;
if (GetNextEntry(c) == B_OK)
return false;
return true;
}
status_t
Directory::GetName(char *name, size_t size) const
{
TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
if (this == fVolume.Root())
return fVolume.GetName(name, size);
return fStream.GetName(name, size);
}
ino_t
Directory::Inode() const
{
TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
return fStream.FirstCluster() << 16;
}
status_t
Directory::CreateFile(const char* name, mode_t permissions, Node** _node)
{
if (Node* node = Lookup(name, false)) {
node->Release();
return B_FILE_EXISTS;
}
// We only support 8.3 file names ATM.
const char* baseName;
const char* extension;
uint32 baseNameLength;
uint32 extensionLength;
if (!check_valid_8_3_file_name(name, baseName, baseNameLength, extension,
extensionLength)) {
return B_UNSUPPORTED;
}
// prepare a directory entry for the new file
dir_entry entry;
memset(entry.fName, ' ', sizeof(entry.fName));
memset(entry.fExt, ' ', sizeof(entry.fExt));
// clear both base name and extension
memcpy(entry.fName, baseName, baseNameLength);
if (extensionLength > 0)
memcpy(entry.fExt, extension, extensionLength);
entry.fFlags = 0;
entry.fReserved1 = 0;
entry.fCreateTime10ms = 199;
entry.fCreateTime = B_HOST_TO_LENDIAN_INT16((23 << 11) | (59 << 5) | 29);
// 23:59:59.9
entry.fCreateDate = B_HOST_TO_LENDIAN_INT16((127 << 9) | (12 << 5) | 31);
// 2107-12-31
entry.fAccessDate = entry.fCreateDate;
entry.fClusterMSB = 0;
entry.fModifiedTime = entry.fCreateTime;
entry.fModifiedDate = entry.fCreateDate;
entry.fClusterLSB = 0;
entry.fSize = 0;
// add the entry to the directory
off_t entryOffset;
status_t error = _AddEntry(entry, entryOffset);
if (error != B_OK)
return error;
// create a File object
File* file = new(nothrow) File(fVolume, entryOffset,
entry.Cluster(fVolume.FatBits()), entry.Size(), name);
if (file == NULL)
return B_NO_MEMORY;
*_node = file;
return B_OK;
}
/*static*/ status_t
Directory::UpdateDirEntry(Volume& volume, off_t dirEntryOffset,
uint32 firstCluster, uint32 size)
{
if (dirEntryOffset == 0)
return B_BAD_VALUE;
CachedBlock cachedBlock(volume);
off_t block = volume.ToBlock(dirEntryOffset);
status_t error = cachedBlock.SetTo(block, CachedBlock::READ);
if (error != B_OK)
return error;
dir_entry* entry = (dir_entry*)(cachedBlock.Block()
+ dirEntryOffset % volume.BlockSize());
entry->SetCluster(firstCluster, volume.FatBits());
entry->SetSize(size);
return cachedBlock.Flush();
}
status_t
Directory::GetNextEntry(void *cookie, uint8 mask, uint8 match)
{
TRACE(("FASFS::Directory::%s(, %02x, %02x)\n", __FUNCTION__, mask, match));
struct dir_cookie *c = (struct dir_cookie *)cookie;
bool hasLongName = false;
bool longNameValid = false;
do {
c->index++;
size_t len = sizeof(c->entry);
if (fStream.ReadAt(c->Offset(), (uint8 *)&c->entry, &len,
&c->entryOffset) != B_OK || len != sizeof(c->entry)) {
return B_ENTRY_NOT_FOUND;
}
TRACE(("FASFS::Directory::%s: got one entry\n", __FUNCTION__));
if ((uint8)c->entry.fName[0] == 0x00) // last one
return B_ENTRY_NOT_FOUND;
if ((uint8)c->entry.fName[0] == 0xe5) // deleted
continue;
if (c->entry.Flags() == 0x0f) { // LFN entry
uint8* nameEntry = (uint8*)&c->entry;
if ((*nameEntry & 0x40) != 0) {
c->ResetName();
hasLongName = true;
longNameValid = true;
}
uint16 nameChars[13];
memcpy(nameChars, nameEntry + 0x01, 10);
memcpy(nameChars + 5, nameEntry + 0x0e, 12);
memcpy(nameChars + 11, nameEntry + 0x1c, 4);
longNameValid |= c->AddNameChars(nameChars, 13);
continue;
}
if ((c->entry.Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_VOLUME) {
// TODO handle Volume name (set fVolume's name)
continue;
}
TRACE(("FASFS::Directory::%s: checking '%8.8s.%3.3s', %02x\n", __FUNCTION__,
c->entry.BaseName(), c->entry.Extension(), c->entry.Flags()));
if ((c->entry.Flags() & mask) == match) {
if (longNameValid)
longNameValid = c->ConvertNameToUTF8();
if (!longNameValid) {
// copy 8.3 name to name buffer
c->Set8_3Name(c->entry.BaseName(), c->entry.Extension());
}
break;
}
} while (true);
TRACE(("FATFS::Directory::%s: '%8.8s.%3.3s'\n", __FUNCTION__,
c->entry.BaseName(), c->entry.Extension()));
return B_OK;
}
status_t
Directory::_AddEntry(dir_entry& entry, off_t& _entryOffset)
{
off_t dirSize = _GetStreamSize();
if (dirSize < 0)
return dirSize;
uint32 firstCluster = fStream.FirstCluster();
// First null-terminate the new entry list, so we don't leave the list in
// a broken state, if writing the actual entry fails. We only need to do
// that when the entry is not at the end of a cluster.
if ((dirSize + sizeof(entry)) % fVolume.ClusterSize() != 0) {
// TODO: Rather zero the complete remainder of the cluster?
size_t size = 1;
char terminator = 0;
status_t error = fStream.WriteAt(dirSize + sizeof(entry), &terminator,
&size);
if (error != B_OK)
return error;
if (size != 1)
return B_ERROR;
}
// write the entry
size_t size = sizeof(entry);
status_t error = fStream.WriteAt(dirSize, &entry, &size, &_entryOffset);
if (error != B_OK)
return error;
if (size != sizeof(entry))
return B_ERROR;
// TODO: Undo changes!
fStream.SetSize(dirSize + sizeof(entry));
// If the directory cluster has changed (which should only happen, if the
// directory was empty before), we need to adjust the directory entry.
if (firstCluster != fStream.FirstCluster()) {
error = UpdateDirEntry(fVolume, fDirEntryOffset, fStream.FirstCluster(),
0);
if (error != B_OK)
return error;
// TODO: Undo changes!
}
return B_OK;
}
off_t
Directory::_GetStreamSize()
{
off_t size = fStream.Size();
if (size != UINT32_MAX)
return size;
// iterate to the end of the directory
size = 0;
while (true) {
dir_entry entry;
size_t entrySize = sizeof(entry);
status_t error = fStream.ReadAt(size, &entry, &entrySize);
if (error != B_OK)
return error;
if (entrySize != sizeof(entry) || entry.fName[0] == 0)
break;
size += sizeof(entry);
}
fStream.SetSize(size);
return size;
}
} // namespace FATFS
↑ V629 Consider inspecting the 'fStream.FirstCluster() << 16' expression. Bit shifting of the 32-bit value with a subsequent expansion to the 64-bit type.