* 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 <util/convertutf.h>
#include <StorageDefs.h>
#include "CachedBlock.h"
#include "File.h"
#include "Volume.h"
#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)
{
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];
const uint16* utf16 = nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength);
ssize_t count = utf16le_to_utf8(utf16, nameLength, name, sizeof(name));
if (count < 0)
return false;
strlcpy(Name(), name, sizeof(nameBuffer));
return true;
}
void
dir_cookie::Set8_3Name(const char* baseName, const char* extension)
{
uint32 baseNameLength = 8;
while (baseNameLength > 0 && baseName[baseNameLength - 1] == ' ')
baseNameLength--;
uint32 extensionLength = 3;
while (extensionLength > 0 && extension[extensionLength - 1] == ' ')
extensionLength--;
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';
}
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)
{
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;
}
while (baseNameLength > 0 && name[baseNameLength - 1] == ' ')
baseNameLength--;
while (extensionLength > 0 && extension[extensionLength - 1] == ' ')
extensionLength--;
if (baseNameLength == 0 || baseNameLength > 8 || extensionLength > 3)
return false;
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;
}
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;
}
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;
}
dir_entry entry;
memset(entry.fName, ' ', sizeof(entry.fName));
memset(entry.fExt, ' ', sizeof(entry.fExt));
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);
entry.fCreateDate = B_HOST_TO_LENDIAN_INT16((127 << 9) | (12 << 5) | 31);
entry.fAccessDate = entry.fCreateDate;
entry.fClusterMSB = 0;
entry.fModifiedTime = entry.fCreateTime;
entry.fModifiedDate = entry.fCreateDate;
entry.fClusterLSB = 0;
entry.fSize = 0;
off_t entryOffset;
status_t error = _AddEntry(entry, entryOffset);
if (error != B_OK)
return error;
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;
}
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 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)
return B_ENTRY_NOT_FOUND;
if ((uint8)c->entry.fName[0] == 0xe5)
continue;
if (c->entry.Flags() == 0x0f) {
uint8* nameEntry = (uint8*)&c->entry;
if ((*nameEntry & 0x40) != 0) {
c->ResetName();
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) {
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) {
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();
if ((dirSize + sizeof(entry)) % fVolume.ClusterSize() != 0) {
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;
}
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;
fStream.SetSize(dirSize + sizeof(entry));
if (firstCluster != fStream.FirstCluster()) {
error = UpdateDirEntry(fVolume, fDirEntryOffset, fStream.FirstCluster(),
0);
if (error != B_OK)
return error;
}
return B_OK;
}
off_t
Directory::_GetStreamSize()
{
off_t size = fStream.Size();
if (size != UINT32_MAX)
return size;
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;
}
}