root/src/system/boot/loader/file_systems/fat/Volume.cpp
/*
 * 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 "Volume.h"
#include "Directory.h"
#include "CachedBlock.h"

#include <boot/partitions.h>
#include <boot/platform.h>

#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>


//#define TRACE(x) dprintf x
#define TRACE(x) do {} while (0)


using namespace FATFS;
using std::nothrow;


Volume::Volume(boot::Partition *partition)
        :
        fCachedBlock(NULL),
        fRoot(NULL)
{
        TRACE(("%s()\n", __FUNCTION__));
        if ((fDevice = open_node(partition, O_RDONLY)) < B_OK)
                return;

        fCachedBlock = new(nothrow) CachedBlock(*this);
        if (fCachedBlock == NULL)
                return;

        uint8 *buf;
        /* = (char *)malloc(4096);
        if (buf == NULL)
                return;*/

        fBlockSize = partition->block_size;
        switch (fBlockSize) {
        case 0x200:
                fBlockShift = 9;
                break;
        case 0x400:
                fBlockShift = 10;
                break;
        case 0x800:
                fBlockShift = 11;
                break;
        default:
                goto err1;
        }
        TRACE(("%s: reading bootsector\n", __FUNCTION__));
        // read boot sector
        buf = fCachedBlock->SetTo(0);
        if (buf == NULL)
                goto err1;

        TRACE(("%s: checking signature\n", __FUNCTION__));
        // check the signature
        if (((buf[0x1fe] != 0x55) || (buf[0x1ff] != 0xaa)) && (buf[0x15] == 0xf8))
                goto err1;

        if (!memcmp(buf+3, "NTFS    ", 8) || !memcmp(buf+3, "HPFS    ", 8))
                goto err1;

        TRACE(("%s: signature ok\n", __FUNCTION__));
        fBytesPerSector = read16(buf,0xb);
        switch (fBytesPerSector) {
        case 0x200:
                fSectorShift = 9;
                break;
        case 0x400:
                fSectorShift = 10;
                break;
        case 0x800:
                fSectorShift = 11;
                break;
        default:
                goto err1;
        }
        TRACE(("%s: block shift %d\n", __FUNCTION__, fBlockShift));

        fSectorsPerCluster = buf[0xd];
        switch (fSectorsPerCluster) {
        case 1: case 2: case 4: case 8:
        case 0x10:      case 0x20:      case 0x40:      case 0x80:
                break;
        default:
                goto err1;
        }
        TRACE(("%s: sect/cluster %d\n", __FUNCTION__, fSectorsPerCluster));
        fClusterShift = fSectorShift;
        for (uint32 spc = fSectorsPerCluster; !(spc & 0x01); ) {
                spc >>= 1;
                fClusterShift += 1;
        }
        TRACE(("%s: cluster shift %d\n", __FUNCTION__, fClusterShift));

        fReservedSectors = read16(buf,0xe);
        fFatCount = buf[0x10];
        if ((fFatCount == 0) || (fFatCount > 8))
                goto err1;

        fMediaDesc = buf[0x15];
        if ((fMediaDesc != 0xf0) && (fMediaDesc < 0xf8))
                goto err1;

        fSectorsPerFat = read16(buf,0x16);
        if (fSectorsPerFat == 0) {
                // FAT32
                fFatBits = 32;
                fSectorsPerFat = read32(buf,0x24);
                fTotalSectors = read32(buf,0x20);
                bool lFatMirrored = !(buf[0x28] & 0x80);
                fActiveFat = (lFatMirrored) ? (buf[0x28] & 0xf) : 0;
                fDataStart = fReservedSectors + fFatCount * fSectorsPerFat;
                fTotalClusters = (fTotalSectors - fDataStart) / fSectorsPerCluster;
                fRootDirCluster = read32(buf,0x2c);
                if (fRootDirCluster >= fTotalClusters)
                        goto err1;

                fFSInfoSector = read16(buf, 0x30);
                if (fFSInfoSector < 1 || fFSInfoSector > fTotalSectors)
                        goto err1;
        } else {
                // FAT12/16
                // XXX:FIXME
                fFatBits = 16;
                goto err1;
        }
        TRACE(("%s: block size %d, sector size %d, sectors/cluster %d\n",
                __FUNCTION__, fBlockSize, fBytesPerSector, fSectorsPerCluster));
        TRACE(("%s: block shift %d, sector shift %d, cluster shift %d\n",
                __FUNCTION__, fBlockShift, fSectorShift, fClusterShift));
        TRACE(("%s: reserved %d, max root entries %d, media %02x, sectors/fat %d\n",
                __FUNCTION__, fReservedSectors, fMaxRootEntries, fMediaDesc,
                fSectorsPerFat));

        //if (fTotalSectors > partition->sectors_per_track * partition->cylinder_count * partition->head_count)
        if ((off_t)fTotalSectors * fBytesPerSector > partition->size)
                goto err1;

        TRACE(("%s: found fat%d filesystem, root dir at cluster %d\n", __FUNCTION__,
                fFatBits, fRootDirCluster));

        fRoot = new(nothrow) Directory(*this, 0, fRootDirCluster, "/");
        return;

err1:
        TRACE(("fatfs: cannot mount (bad superblock ?)\n"));
        // XXX !? this triple-faults in QEMU ..
        //delete fCachedBlock;
}


Volume::~Volume()
{
        delete fRoot;
        delete fCachedBlock;
        close(fDevice);
}


status_t
Volume::InitCheck()
{
        if (fCachedBlock == NULL)
                return B_ERROR;
        if (fRoot == NULL)
                return B_ERROR;

        return B_OK;
}


status_t
Volume::GetName(char *name, size_t size) const
{
        //TODO: WRITEME
        return strlcpy(name, "UNKNOWN", size);
}


off_t
Volume::ClusterToOffset(uint32 cluster) const
{
        return (fDataStart << SectorShift()) + ((cluster - 2) << ClusterShift());
}

uint32
Volume::NextCluster(uint32 cluster, uint32 skip)
{
        //TRACE(("%s(%d, %d)\n", __FUNCTION__, cluster, skip));
        // lookup the FAT for next cluster in chain
        off_t offset;
        uint8 *buf;
        int32 next;
        int fatBytes = (FatBits() + 7) / 8;

        switch (fFatBits) {
                case 32:
                case 16:
                        break;
                //XXX handle FAT12
                default:
                        return InvalidClusterID();
        }

again:
        offset = fBytesPerSector * fReservedSectors;
        //offset += fActiveFat * fTotalClusters * fFatBits / 8;
        offset += cluster * fatBytes;

        if (!IsValidCluster(cluster))
                return InvalidClusterID();

        buf = fCachedBlock->SetTo(ToBlock(offset));
        if (!buf)
                return InvalidClusterID();

        offset %= BlockSize();

        switch (fFatBits) {
                case 32:
                        next = read32(buf, offset);
                        next &= 0x0fffffff;
                        break;
                case 16:
                        next = read16(buf, offset);
                        break;
                default:
                        return InvalidClusterID();
        }
        if (skip--) {
                cluster = next;
                goto again;
        }
        return next;
}


bool
Volume::IsValidCluster(uint32 cluster) const
{
        if (cluster > 1 && cluster < fTotalClusters)
                return true;
        return false;
}


bool
Volume::IsLastCluster(uint32 cluster) const
{
        if (cluster >= fTotalClusters && ((cluster & 0xff8) == 0xff8))
                return true;
        return false;
}


/*!     Allocates a free cluster.
        If \a previousCluster is a valid cluster idnex, its chain pointer is
        changed to point to the newly allocated cluster.
*/
status_t
Volume::AllocateCluster(uint32 previousCluster, uint32& _newCluster)
{
        if (fFatBits != 32)
                return B_UNSUPPORTED;
                // TODO: Support FAT16 and FAT12.

        const int fatBytes = FatBits() / 8;
        const uint32 blockOffsetMask = (uint32)BlockSize() - 1;

        // Iterate through the FAT to find a free cluster.
        off_t offset = fBytesPerSector * fReservedSectors;
        offset += 2 * fatBytes;
        for (uint32 i = 2; i < fTotalClusters; i++, offset += fatBytes) {
                uint8* buffer = fCachedBlock->SetTo(ToBlock(offset));
                if (buffer == NULL)
                        return B_ERROR;

                uint32 value = read32(buffer, offset & blockOffsetMask);
                if (value == 0) {
                        // found one -- mark it used (end of file)
                        status_t error = _UpdateCluster(i, 0x0ffffff8);
                        if (error != B_OK)
                                return error;

                        // If a previous cluster was given, update its list link.
                        if (IsValidCluster(previousCluster)) {
                                error = _UpdateCluster(previousCluster, i);
                                if (error != B_OK) {
                                        _UpdateCluster(i, 0);
                                        return error;
                                }
                        }

                        _ClusterAllocated(i);

                        _newCluster = i;
                        return B_OK;
                }
        }

        return B_DEVICE_FULL;
}


status_t
Volume::_UpdateCluster(uint32 cluster, uint32 value)
{
        if (fFatBits != 32 && fFatBits != 16)
                return B_UNSUPPORTED;
                // TODO: Support FAT12.

        if (!IsValidCluster(cluster))
                return InvalidClusterID();

        // get the buffer we need to change
        const int fatBytes = FatBits() / 8;

        for (uint8 i = 0; i < fFatCount; i++) {
                off_t offset
                        = fBytesPerSector * (fReservedSectors + i * fSectorsPerFat);
                offset += cluster * fatBytes;

                uint8* buffer = fCachedBlock->SetTo(ToBlock(offset));
                if (buffer == NULL)
                        return InvalidClusterID();

                offset %= BlockSize();

                // set the value
                switch (fFatBits) {
                        case 32:
                                *(uint32*)(buffer + offset) = B_HOST_TO_LENDIAN_INT32(value);
                                break;
                        case 16:
                                *(uint16*)(buffer + offset) = B_HOST_TO_LENDIAN_INT16(value);
                                break;
                        default:
                                return InvalidClusterID();
                }

                // write the block back to disk
                status_t error = fCachedBlock->Flush();
                if (error != B_OK) {
                        fCachedBlock->Unset();
                        return error;
                }
        }

        return B_OK;
}


status_t
Volume::_ClusterAllocated(uint32 cluster)
{
        // update the FS info

        // exists only for FAT32
        if (fFatBits != 32)
                return B_OK;

        off_t offset = fBytesPerSector * fFSInfoSector;

        status_t error = fCachedBlock->SetTo(offset / BlockSize(),
                CachedBlock::READ);
        if (error != B_OK)
                return error;

        uint8* buffer = fCachedBlock->Block() + offset % BlockSize();

        // update number of free cluster
        int32 freeClusters = read32(buffer, 0x1e8);
        if (freeClusters != -1)
                write32(buffer, 0x1e8, freeClusters - 1);

        // update number of most recently allocated cluster
        write32(buffer, 0x1ec, cluster);

        // write the block back
        error = fCachedBlock->Flush();
        if (error != B_OK)
                fCachedBlock->Unset();

        return error;
}


//      #pragma mark -


float
dosfs_identify_file_system(boot::Partition *partition)
{
        TRACE(("%s()\n", __FUNCTION__));
        Volume volume(partition);

        return volume.InitCheck() < B_OK ? 0 : 0.8;
}


static status_t
dosfs_get_file_system(boot::Partition *partition, ::Directory **_root)
{
        TRACE(("%s()\n", __FUNCTION__));
        Volume *volume = new(nothrow) Volume(partition);
        if (volume == NULL)
                return B_NO_MEMORY;

        if (volume->InitCheck() < B_OK) {
                delete volume;
                return B_ERROR;
        }

        *_root = volume->Root();
        return B_OK;
}


file_system_module_info gFATFileSystemModule = {
        "file_systems/dosfs/v1",
        kPartitionTypeFAT32, // XXX:FIXME: FAT16 too ?
        dosfs_identify_file_system,
        dosfs_get_file_system
};