#include <kernel/driver.h>
#include <os/stream.h>
#include "fat.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#define FAT_BYTES 2 // FAT16
// #define FAT_BYTES 4 // FAT32 (nyi)
#define CLUSTER_CACHE 1
#if 0
#define TRACE wprintf
#else
static int TRACE(...)
{
return 0;
}
#endif
extern "C" bool wildcard(const wchar_t* str, const wchar_t* spec);
struct folderitem_fat_t : folderitem_t
{
folderitem_fat_t* next;
IUnknown* mount;
fat_dirent_t dirent;
};
class CFatFile : public IUnknown, public IStream
{
protected:
folderitem_fat_t m_stat;
addr_t m_dwPosition;
byte* m_pCache;
dword m_dwCacheSize, m_dwCachePtr;
IBlockDevice* m_pDevice;
dword m_dwReadCluster;
fat_bootsector_t m_bpb;
bool m_bIsRoot;
dword GetNextCluster(dword dwCluster);
bool ReadCluster(dword dwCluster, void* pBuf, size_t size);
friend class CFatFolder;
public:
CFatFile(IBlockDevice* pDev, const folderitem_fat_t* stat);
virtual ~CFatFile();
STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject);
IMPLEMENT_IUNKNOWN(CFatFile);
STDMETHOD_(size_t, Read) (void* pBuffer, size_t dwLength);
STDMETHOD_(size_t, Write) (const void* pBuffer, size_t dwLength);
STDMETHOD(SetIoMode)(dword mode);
STDMETHOD(IsReady)();
STDMETHOD(Stat)(folderitem_t* buf);
STDMETHOD(Seek)(long offset, int origin);
};
class CFatFolder : public IUnknown, public IFolder
{
public:
CFatFolder(IBlockDevice* pDev, const folderitem_fat_t* stat, bool bIsRoot);
virtual ~CFatFolder();
STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject);
IMPLEMENT_IUNKNOWN(CFatFolder);
STDMETHOD(FindFirst)(const wchar_t* spec, folderitem_t* buf);
STDMETHOD(FindNext)(folderitem_t* buf);
STDMETHOD(FindClose)(folderitem_t* pBuf);
STDMETHOD(Open)(folderitem_t* item, const wchar_t* params);
STDMETHOD(Mount)(const wchar_t* name, IUnknown* obj);
protected:
CFatFile m_file;
folderitem_fat_t* m_item_first;
void ScanDir();
};
extern "C" IFolder* FatRoot_Create(IBlockDevice* pDevice)
{
folderitem_fat_t buf;
memset(&buf, 0, sizeof(buf));
buf.size = sizeof(buf);
return new CFatFolder(pDevice, &buf, true);
}
/****************************************************************************
* CFatFile *
****************************************************************************/
CFatFile::CFatFile(IBlockDevice* pDev, const folderitem_fat_t* stat)
{
m_pDevice = pDev;
//IUnknown_AddRef(pDev);
pDev->AddRef();
memset(&m_bpb, 0, sizeof(m_bpb));
//pDev->Seek(0, SEEK_SET);
//pDev->ReadFile(&m_bpb, sizeof(m_bpb));
//IBlockDevice_BlockRead(pDev, 0, 1, &m_bpb);
pDev->BlockRead(0, 1, &m_bpb);
m_stat = *stat;
m_dwReadCluster = (dword) -1;
m_bIsRoot = false;
m_dwCacheSize = m_bpb.nBytesPerSector * m_bpb.nSectorsPerCluster *
CLUSTER_CACHE;
m_dwCachePtr = (dword) -1;
m_pCache = (byte*) malloc(m_dwCacheSize);
m_refs = 0;
}
CFatFile::~CFatFile()
{
TRACE(L"[CFatFile] deleting\n");
if (m_pCache)
free(m_pCache);
if (m_pDevice)
m_pDevice->Release();
//IUnknown_Release(m_pDevice);
}
HRESULT CFatFile::QueryInterface(REFIID iid, void ** ppvObject)
{
if (InlineIsEqualGUID(iid, IID_IUnknown) ||
InlineIsEqualGUID(iid, IID_IStream))
{
AddRef();
*ppvObject = (IStream*) this;
return S_OK;
}
else
return E_FAIL;
}
size_t CFatFile::Read(void* pBuffer, size_t dwLength)
{
size_t dwRead = 0;
int i;
if (m_dwReadCluster == (dword) -1)
{
if (m_dwPosition == 0)
m_dwReadCluster = m_stat.dirent.nFirstCluster;
else
return 0;
}
if (m_dwPosition + dwLength > m_stat.length)
dwLength = m_stat.length - m_dwPosition;
while (dwRead < dwLength)
{
if (m_dwCachePtr >= m_dwCacheSize)
{
//TRACE(L"[clus = %x] ", m_dwReadCluster);
if (m_dwReadCluster >= 0xFFF8)
{
m_dwPosition += dwRead;
m_dwCachePtr = (dword) -1;
return dwRead;
}
if (!ReadCluster(m_dwReadCluster, m_pCache, m_dwCacheSize))
{
TRACE(L"ReadCluster failed\n");
m_dwPosition += dwRead;
m_dwCachePtr = (dword) -1;
return dwRead;
}
m_dwCachePtr = 0;
m_dwReadCluster = GetNextCluster(m_dwReadCluster);
}
//TRACE(L"{cache = %d} ", m_dwCacheSize - m_dwCachePtr);
i = min(m_dwCacheSize - m_dwCachePtr, dwLength - dwRead);
memcpy((byte*) pBuffer + dwRead, m_pCache + m_dwCachePtr, i);
m_dwCachePtr += i;
dwRead += i;
}
m_dwPosition += dwLength;
return dwRead;
}
size_t CFatFile::Write(const void* pBuffer, size_t dwLength)
{
return 0;
}
HRESULT CFatFile::SetIoMode(dword mode)
{
return S_OK;
}
HRESULT CFatFile::IsReady()
{
return S_OK;
}
HRESULT CFatFile::Stat(folderitem_t* buf)
{
if (buf->size == sizeof(folderitem_fat_t))
memcpy(buf, &m_stat, sizeof(folderitem_fat_t));
else
*buf = m_stat;
return S_OK;
}
dword CFatFile::GetNextCluster(dword dwCluster)
{
dword FATOffset, ThisFATSecNum, ThisFATEntOffset;
byte sector[512];
FATOffset = dwCluster * FAT_BYTES;
ThisFATSecNum = m_bpb.nReservedSectors +
(FATOffset / m_bpb.nBytesPerSector);
ThisFATEntOffset = FATOffset % m_bpb.nBytesPerSector;
//m_pDevice->Seek(ThisFATSecNum, SEEK_SET);
//m_pDevice->ReadFile(sector, sizeof(sector));
//IBlockDevice_BlockRead(m_pDevice, ThisFATSecNum, 1, sector);
m_pDevice->BlockRead(ThisFATSecNum, 1, sector);
return *(word*) (sector + ThisFATEntOffset);
}
HRESULT CFatFile::Seek(long offset, int origin)
{
return E_FAIL;
}
bool CFatFile::ReadCluster(dword dwCluster, void* pBuf, size_t size)
{
dword RootDirSectors, FirstDataSector, FirstSectorofCluster;
if (m_bIsRoot)
RootDirSectors = 0;
else
RootDirSectors =
((m_bpb.nRootDirectoryEntries * 32) + (m_bpb.nBytesPerSector - 1))
/ m_bpb.nBytesPerSector;
FirstDataSector = m_bpb.nReservedSectors +
(m_bpb.nFatCount * m_bpb.nSectorsPerFat) + RootDirSectors;
if (m_bIsRoot)
dwCluster += 2;
FirstSectorofCluster = ((dwCluster - 2) * m_bpb.nSectorsPerCluster) +
FirstDataSector;
//m_pDevice->Seek(FirstSectorofCluster, SEEK_SET);
//return m_pDevice->ReadFile(pBuf, size) == (int) size;
size /= 512;
//return IBlockDevice_BlockRead(m_pDevice, FirstSectorofCluster, size, pBuf) == size;
return m_pDevice->BlockRead(FirstSectorofCluster, size, pBuf) == size;
}
/****************************************************************************
* CFatFolder *
****************************************************************************/
CFatFolder::CFatFolder(IBlockDevice* pDev, const folderitem_fat_t* stat, bool bIsRoot) :
m_file(pDev, stat)
{
m_refs = 0;
m_file.m_bIsRoot = bIsRoot;
m_item_first = NULL;
if (bIsRoot)
m_file.m_stat.length = sizeof(fat_dirent_t) * m_file.m_bpb.nRootDirectoryEntries;
}
CFatFolder::~CFatFolder()
{
folderitem_fat_t *item, *next;
for (item = m_item_first; item; item = next)
{
next = item->next;
if (item->mount)
item->mount->Release();
//IUnknown_Release(item->mount);
item->mount = NULL;
free(item->name);
delete item;
}
}
HRESULT CFatFolder::QueryInterface(REFIID iid, void ** ppvObject)
{
if (InlineIsEqualGUID(iid, IID_IUnknown) ||
InlineIsEqualGUID(iid, IID_IFolder))
{
AddRef();
*ppvObject = (IFolder*) this;
return S_OK;
}
else if (InlineIsEqualGUID(iid, IID_IStream))
{
AddRef();
*ppvObject = (IStream*) &m_file;
return S_OK;
}
else
return E_FAIL;
}
HRESULT CFatFolder::FindFirst(const wchar_t* szSpec, folderitem_t* item)
{
if (m_item_first == NULL)
ScanDir();
item->spec = wcsdup(szSpec);
item->u.find_handle = (dword) m_item_first;
TRACE(L"FindFirst %s\n", item->spec);
return S_OK;
}
HRESULT CFatFolder::FindNext(folderitem_t* item)
{
folderitem_fat_t *fitem = (folderitem_fat_t*) item->u.find_handle;
if (fitem == NULL)
return E_FAIL;
else
{
while (fitem)
{
if (wildcard(fitem->name, item->spec))
{
TRACE(L"[fat] item = %s spec = %s\n", fitem->name, item->spec);
item->u.find_handle = (dword) fitem->next;
wcsncpy(item->name, fitem->name, item->name_max);
item->attributes = fitem->attributes;
item->length = fitem->length;
if (item->size == sizeof(folderitem_fat_t))
{
((folderitem_fat_t*) item)->mount = fitem->mount;
((folderitem_fat_t*) item)->next = fitem->next;
((folderitem_fat_t*) item)->dirent = fitem->dirent;
}
return S_OK;
}
fitem = fitem->next;
item->u.find_handle = (dword) fitem;
}
return E_FAIL;
}
}
HRESULT CFatFolder::FindClose(folderitem_t* pBuf)
{
//TRACE(L"FindClose\n");
free((void*) pBuf->spec);
return S_OK;
}
HRESULT CFatFolder::Open(folderitem_t* item, const wchar_t* params)
{
folderitem_fat_t buf;
IUnknown* pFile;
wchar_t temp[MAX_PATH];
TRACE(L"[OpenChild] %s\n", item->name);
buf.size = sizeof(folderitem_fat_t);
buf.name = temp;
buf.name_max = countof(temp);
if (FAILED(FindFirst(item->name, &buf)) ||
FAILED(FindNext(&buf)))
return E_FAIL;
if (buf.mount)
{
TRACE(L"Found mount point %p\n", buf.mount);
pFile = buf.mount;
//IUnknown_AddRef(pFile);
pFile->AddRef();
}
else if (buf.attributes & ATTR_DIRECTORY)
{
TRACE(L"Found dir at %x\n", buf.dirent.nFirstCluster);
pFile = new CFatFolder(m_file.m_pDevice, &buf, false);
}
else
{
TRACE(L"Found file at %x\n", buf.dirent.nFirstCluster);
pFile = new CFatFile(m_file.m_pDevice, &buf);
}
//FindClose((FileFind*) &buf);
if (item->size == sizeof(folderitem_fat_t))
memcpy(item, &buf, sizeof(folderitem_fat_t));
else
*item = buf;
item->u.item_handle = pFile;
FindClose(&buf);
//return pFile;
return S_OK;
}
HRESULT CFatFolder::Mount(const wchar_t* name, IUnknown* obj)
{
folderitem_fat_t* item = new folderitem_fat_t;
item->size = sizeof(folderitem_fat_t);
memset(item, 0, sizeof(item));
item->name = wcsdup(name);
item->attributes = ATTR_DIRECTORY | ATTR_LINK;
item->length = 0;
item->mount = obj;
obj->AddRef();
item->next = m_item_first;
m_item_first = item;
return S_OK;
}
void CFatFolder::ScanDir()
{
union
{
fat_dirent_t dirent;
fat_lfnslot_t lfn;
};
int i, j, count;
wchar_t name[MAX_PATH], temp[14], *nameptr;
m_file.m_dwPosition = 0;
m_file.m_dwCachePtr = (dword) -1;
m_file.m_dwReadCluster = (dword) -1;
count = 0;
while (1)
{
if (m_file.Read(&dirent, sizeof(dirent)) < sizeof(dirent))
{
TRACE(L"End of file: %d\n", count);
return;
}
if (dirent.szFilename[0] == 0)
{
TRACE(L"End of directory: %d\n", count);
return;
}
if (dirent.szFilename[0] != 0xe5)
{
memset(name, 0, sizeof(name));
if ((dirent.nAttributes & ATTR_LONG_NAME) != ATTR_LONG_NAME)
{
for (i = 0; i < 8; i++)
{
if (dirent.szFilename[i] == ' ')
{
name[i] = 0;
break;
}
else if (iswupper(dirent.szFilename[i]))
name[i] = towlower(dirent.szFilename[i]);
else
name[i] = dirent.szFilename[i];
}
if (dirent.szExtension[0] != ' ')
{
wcscat(name, L".");
j = wcslen(name);
for (i = 0; i < 3; i++)
{
if (dirent.szExtension[i] == ' ')
{
name[i + j] = 0;
break;
}
else if (iswupper(dirent.szExtension[i]))
name[i + j] = towlower(dirent.szExtension[i]);
else
name[i + j] = dirent.szExtension[i];
}
}
//TRACE(L"short: name = \"%s\" (\"%s\")\n", name, item->spec);
}
else
{
while ((dirent.nAttributes & ATTR_LONG_NAME)
== ATTR_LONG_NAME)
{
//TRACE(L"<lfn seq=%2x> ", dirent.szFilename[0]);
if (dirent.szFilename[0] != 0xe5)
{
memset(temp, 0, sizeof(temp));
nameptr = temp;
for (i = 0; i < 5; i++)
{
*nameptr = lfn.name0_4[i];
nameptr++;
}
for (i = 0; i < 6; i++)
{
*nameptr = lfn.name5_10[i];
nameptr++;
}
for (i = 0; i < 2; i++)
{
*nameptr = lfn.name11_12[i];
nameptr++;
}
i = wcslen(temp);
memmove(name + i, name, wcslen(name) * sizeof(wchar_t));
memcpy(name, temp, i * sizeof(wchar_t));
}
if (m_file.Read(&dirent, sizeof(dirent)) < sizeof(dirent))
{
TRACE(L"End of file (lfn): %d\n", count);
return;
}
}
//TRACE(L"long: name = \"%s\" (\"%s\")\n", name, item->spec);
}
//if (wildcard(name, item->spec))
{
folderitem_fat_t* item;
//TRACE(L"Found item %s size = %d\n", name, dirent.dwFileSize);
item = new folderitem_fat_t;
memset(item, 0, sizeof(item));
item->size = sizeof(folderitem_fat_t);
item->name = wcsdup(name);
item->length = dirent.dwFileSize;
item->attributes = dirent.nAttributes;
item->dirent = dirent;
item->next = m_item_first;
m_item_first = item;
count++;
}
}
}
}