mFS file system library for EEPROM memory chips.

mfs.cpp

Committer:
HBP
Date:
2011-02-21
Revision:
5:a0fe74dce80d
Parent:
0:cbf45dde2b49
Child:
7:5ac5121bb4e0

File content as of revision 5:a0fe74dce80d:

/*CPP**************************************************************************
* FILENAME :        mfs.cpp                                                   *
*                                                                             *
* DESCRIPTION :                                                               *
*       mFS file system implementation for mBED with external I2C EEEPROM.    *
*                                                                             *
* AUTHOR :    Olli Vanhoja        START DATE :    2011-02-21                  *
******************************************************************************/

#include "mbed.h"
#include "mfs.h"
#include "i2c_eeprom.h"

#define RB 3 // Reseved bytes per block (1 attrb B, 2 B for next/prev pointers
#define DEBUG // Adds extra safety to reading and writing

DigitalOut FlushLed(LED2);

//extern Serial pc(USBTX, USBRX);

/* mFS ************************************************************************
 * | VOLinfo | fs | Data block 1 | etc..                                      *
 * VOLinfo = | szVolname[256] | unsigned int nFreeBlocks |                    *
 * fsH[n]  = | RObit | LOCKbit | FEndBit | FBeingBit | (12-bit) next |        *
 * fsL[n]  = | 4-bits unused | 12-bit address |                               *
 *****************************************************************************/

mfs::mfs(int i2c_address)
{
    mem = new i2c_eeprom(i2c_address, 200000);
}

char mfs::read(char *data, char block, unsigned int byte, unsigned int n)
{
    // Faster reading without DEBUG mode
    #ifdef DEBUG
    if ((byte+n-1 >= BS))
        return 1;
    #endif
    mem->read(BS*block+byte, n, data);
    //wait_ms(1);
    return 0;
}

char mfs::write(char *data, char block, unsigned int byte, unsigned int n)
{
    // Faster writing without DEBUG mode
    #ifdef DEBUG
    if (byte+n >= BS)
        return 1;
    #endif
    mem->write(data, BS*block+byte, n);
    return 0;
}

char mfs::getNextFreeBlock(char *blockOut)
{    
    // Locate free block by seeking EERPOM
    char cFlags[1];
    
    for (*blockOut=0; *blockOut < BC; (*blockOut)++)
    {
        read(cFlags, *blockOut, 0, 1);
        if (cFlags[0] == 0x04)
            break;
        if (*blockOut >= BC-1)
            return 1;
    }
    
    return 0;
}

unsigned int mfs::findNextFile(unsigned int block, char *filenameOut)
{
    unsigned int i=block;
    char cFlags[1];
    
    while (i < BC)
    {
        read(cFlags, i, 0, 1);
        
        if ((cFlags[0] & 0x8C) == 0x8C)
            break; // File found
        else
            i++;
    }
    
    if(i == BC)
    {
        strcpy(filenameOut, "");
        return 0xffff; // Empty fs
    }
    
    // Read filename
    read(filenameOut, i, 3, 20);
    return i; // Return block number
}

uint16 mfs::getFirstBlockOfFile(char filename[20])
{
    uint16 block=0;
    char tmpFilename[20]="";

    while (block < BC)
    {
        block = findNextFile(block, tmpFilename);
        if (block < BC)
        {
            if(strcmp(tmpFilename, filename) == 0)
               return block; // File exists
        }
        else return 0xFFFF; // File doesn't exist
        block++;
    }

    return 0xFFFF; // ??
}

char mfs::createFile(char filename[20])
{
    char tmpFilename[20];
    unsigned int n;
    char fb;

    for (n=0; n < BC; n++)
    {
        n=findNextFile(n, tmpFilename);
        if(n < BC)
        {
            if(strcmp(tmpFilename, filename) == 0)
                return 1; // File exist
        }
        else break; // We already reached the edge of the universe
        n++;
    }

    if(getNextFreeBlock(&fb))
        return 2; // Out of space
    
    char cData[23];
    cData[0] = '\xCC'; // Necessary flags for file
    cData[1] = '\0'; // Only this block yet
    cData[2] = '\0'; // First block there could't be prev blocks
    
    for (char i=0; i < 20; i++)
        cData[3+i] = filename[i];
    
    // Create file
    write(cData, fb, 0, 23);
        
    return 0;
}

char mfs::removeFile(char filename[20])
{
    unsigned int block;
    char cData[3] = {'\0','\0','\0'};

    // Check if file exists
    block = getFirstBlockOfFile(filename);
    if (block > BC)
        return 1; // File not found
    
    // Clear blocks reserver by the file
    unsigned int i=0;
    char tmp_cData[2];
    while(1)
    {
        read(cData, block, 0, 2);
        tmp_cData[0] = cData[0];
        tmp_cData[1] = cData[1];
        
        // Clear the block
        cData[0] = '\x04';
        write(cData, block, 0, 3);
        
        if ((tmp_cData[0] & 0x4C) != 0)
            break; // End of File found
        else if (i >= BC)
            return 2; // fs is corrupted
        block = tmp_cData[1];

        i++;
    }
    
    return 0; // Everything went better than expected
}

char mfs::setFileFlags(char *flags, char filename[20])
{
    /* RO|HIDDEN|LOCK  *
     * H            L */
    
    uint16 n;
    char cData[1] = {'\0'};

    // Check if file exists
    n = getFirstBlockOfFile(filename);
    if (n > BC)
        return 1; // File not found
    
    read(cData, n, 0, 1);
    cData[0] |= (flags[0] & 0x01)|((flags[0] & 0x02) << 3)|((flags[0] & 0x04) << 3);
    write(cData, n, 0, 1);
    
    return 0;
}

char mfs::getFileFlags(char *flags, char filename[20])
{
    /* RO|HIDDEN|LOCK  *
     * H            L */
    
    uint16 n;
    char cData[1] = {'\0'};

    // Check if file exists
    n = getFirstBlockOfFile(filename);
    if (n > BC)
        return 1; // File not found
    
    read(cData, n, 0, 1);
    flags[0] = (cData[0] & 0x01)|((cData[0] & 0x10) >> 3)|((cData[0] & 0x20) >> 3);
    
    return 0;
}

// Return number of free blocks
uint16 mfs::free()
{
    uint16 blocks=0;
    uint16 r;
    char cFlags[1];
    
    for (r=0; r < BC; r++)
    {
        read(cFlags, r, 0, 1);
        if (cFlags[0] == 0x04)
            blocks++;
        if (r >= BC-1)
            return blocks;
    }
    
    return 0;
}

char mfs::mkfs(char createLabel)
{
    unsigned int iAddr = 0;
    uint16 i = 0;
    uint16 bad = 0; // For counting bad block headers
    char cFlags[] = {'\0', '\0', '\0'}, a[1];
    
    if (createLabel >= 1)
    {
        // Write Volume label
        cFlags[0] = '\x0E';
        mem->write(a, iAddr, 3);
        iAddr = BS;
        i = 1;
    }
    
    cFlags[0] = '\x04';
    for (; i < BC; i++)
    {
        mem->write(cFlags, iAddr, 3);
        mem->read(iAddr, 1, a);
        if (a[0] != cFlags[0])
            bad++;
        iAddr += BS;
    }
    
    return bad;
}

file::file(mfs *fs_ref, char filename[20], char operation)
{
    // Operations:
    // 0 = Open RO
    // 1 = Open RW
    attr = operation;
    
    fs = fs_ref; // Don't forget this :)
    
    uint16 n;
    char cData[3] = {'\0','\0','\0'};

    // Check if file exists
    n = fs->getFirstBlockOfFile(filename);
    if (n == 0xFFFF)
        error("Oops, file \"%s\" not found! n=0x%X", filename, n); // File not found
    
    fs->read(cData, n, 0, 1);
    char flags = (cData[0] & 0x01)|((cData[0] & 0x10) >> 3)|((cData[0] & 0x20) >> 3);
    
    if (attr == 1)
    {
        if ((flags & 0x04) != 0)
            error("Oops, cant open in RW mode!");
    }

    // Store FBOF number
    firstBlock = n;
    currBlock = n;
    blockPos = RB+20; // skip flags + pointers + filename

    // Initialize buffer
    for (unsigned int i=0; i < BUF; i++)
        buffer[i] = '\0';
    bufPos = 0;
}

file::~file()
{
    flush();
}

void file::needsFlush()
{
    if (bufPos > 0)
    {
        char tmpBlock = currBlock;
        unsigned int tmpPos = blockPos;
        
        if(flush() != 0)
            error("Flush failed!");
        currBlock = tmpBlock;
        blockPos = tmpPos;
    }
}

void file::rewind()
{
    flush();
    currBlock = firstBlock;
    blockPos = RB+20; // skip flags & pointers + filename
}

char file::rewind(uint16 n)
{
    uint16 i;
    char cData[3];
    
    needsFlush(); // Check if flush is needed
    
    for (i=0; i < n; i++)
    {
        blockPos--;
    
        // Change Block?
        if (blockPos < 3)
        {
            // Fetch link to next block
            fs->read(cData, currBlock, 0, 3);
            if (!(cData[0] & 0x80))
            {
                currBlock = cData[2];
                blockPos = BS-1; // Set block postion offset at end of the block
            } else {
                blockPos++;
                return 1; // This is the last block
            }
        }
        fs->read(cData, currBlock, blockPos, 1);
        if (cData[0] == mEOF)
            return 1;
    }
    
    return 0; // OK
}

char file::forward()
{
    return forward(1);
}

char file::forward(uint16 n)
{
    uint16 i;
    char cData[2];

    needsFlush(); // Check if flush is needed
    
    for (i=0; i < n; i++)
    {
        blockPos++;
    
        // Change Block?
        if (blockPos > BS-1)
        {
            // Fetch link to next block
            fs->read(cData, currBlock, 0, 2);
            if (!(cData[0] & 0x40))
            {
                currBlock = cData[1];
                blockPos = RB; // Reset block position offset
            } else {
                blockPos--;
                return 1; // This is the last block
            }
        }
        fs->read(cData, currBlock, blockPos, 1);
        if (cData[0] == mEOF)
            return 1;
    }
    
    return 0; // OK
}

// Respects mEOF and automatically sets '\0' at the end of string
void file::read(char *data, unsigned int n)
{
    unsigned int i;
    char cData[2];
    
    needsFlush();
    
    for (i=0; i < n; i++)
    {
        // Change block?
        if (blockPos == BS-1)
        {
            // Fetch link to next block
            fs->read(cData, currBlock, 0, 2);
            if (!(cData[0] & 0x40))
            {
                currBlock = cData[1];
                blockPos = RB; // Reset block position offset
            } else goto stop;
        }
        
        // Read data
        fs->read(cData, currBlock, blockPos, 1);
        if (cData[0] == mEOF)
        {
            stop:
            data[i]='\0';
            return;
        } else {
            data[i] = cData[0];
            blockPos++;
        }
    }
    if (data[i] != '\0')
        data[i] = '\0';
}

// Ignores mEOF and doesn't set '\0' markings
void file::readBin(char *data, unsigned int n)
{
    unsigned int i;
    char cData[2];
    
    for (i=0; i < n; i++)
    {
        // Change block?
        if (blockPos == BS-1)
        {
            // Fetch link to next block
            fs->read(cData, currBlock, 0, 2);
            if (!(cData[0] & 0x40))
            {
                currBlock = cData[1];
                blockPos = RB; // Reset block position offset
            } else return;
        }
        
        // Read data
        fs->read(cData, currBlock, blockPos, 1);
        data[i] = cData[0];
        
        blockPos++;
    }
    data[i-1] = '\0';
}

// Always binary
char file::write(char *data, unsigned int n)
{
    if (attr == 0) return 1;
    
    for (unsigned int i=0; i < n; i++)
    {
        // write to the buffer
        buffer[bufPos] = data[i];
        bufPos++;
        
        // If the buffer is full then flush
        if(bufPos == BUF)
        {
            if(flush() != 0);
                return 1; // Flush failed
        }
    }
    
    return 0;
}

char file::flush()
{
    char cData[3], c[1];
    char nextFree;
    unsigned int i;
    
    if (bufPos == 0) return 0; // File up-to date
    if (attr == 0) return 1;
    
    for (i=0; i < bufPos; i++)
    {
        if(i%2)
            FlushLed = 1;
        else
            FlushLed = 0;
        
        // Change Block?
        if (blockPos >= BS-2) // Must left space for mEOF
        {
            // Fetch new unused block number
            if(fs->getNextFreeBlock(&nextFree) != 0)
                return 1;
            
            // Link old block with new block
            fs->read(cData, currBlock, 0, 3);
            cData[0] &= ~0x40; // Clear LBOF flag if set
            cData[1] = nextFree;
            cData[2] = '\0';
            fs->write(cData, currBlock, 0, 3); // Update Block Data
            
            // Take new block into use
            blockPos = RB; // Reset block position offset
            cData[0] = 0x4C; // New flags
            cData[1] = '\0';
            cData[2] = currBlock; // Prev Block
            currBlock = nextFree; // Change current block to new one
            fs->write(cData, currBlock, 0, 3); // Update Block Data
        }
        
        if (blockPos < 3)
            error("");
        
        // Write file
        c[0]=buffer[i];
        fs->write(c, currBlock, blockPos, 1);
        blockPos++;
    }
    // Write mEOF
    fs->write((char[]){mEOF}, currBlock, blockPos+1, 1);
    
    bufPos = 0; // Reset buffer position counter
    
    FlushLed = 0;
    return 0;
}