mFS file system library for EEPROM memory chips.

mfs.cpp

Committer:
HBP
Date:
2011-02-24
Revision:
13:142b6be3e3c8
Parent:
12:928346513c87

File content as of revision 13:142b6be3e3c8:

/** @file mfs.cpp */
/*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 BLOCK_LLEN 2        /**< Block number link length in bytes */
#define RB 1+2*BLOCK_LLEN   /**< Reseved bytes per block (1 attrb B, 2 B for next/prev pointers */
#define I2C_SPEED 200000    /**< I2C bus speed in Hz */
#define DEBUG               /**< Adds extra safety in reading and writing */

DigitalOut FlushLed(LED2); /**< Flush led */

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

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

    return 0;
}

char mfs::write(char *data, uint32_t block, uint32_t byte, uint32_t 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(uint32_t *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;
}

char mfs::findNextFile(uint32_t block, char *filenameOut, uint32_t *blockOut)
{
    uint32_t 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 1; // Empty fs
    }
    
    // Read filename
    read(filenameOut, i, RB, 20);
    *blockOut = i; // Return block number
    return 0;
}

char mfs::getFirstBlockOfFile(char filename[20], uint32_t *blockOut)
{
    *blockOut=0;
    char tmpFilename[20]="";

    while (1)
    {
        if (findNextFile(*blockOut, tmpFilename, blockOut) == 0)
        {
            if(strcmp(tmpFilename, filename) == 0)
                return 0; // File exists
        }
        else return 1; // File doesn't exist
        (*blockOut)++;
    }
}

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

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

    if(getNextFreeBlock(&fb) != 0)
        return 2; // Out of space
    
    char cData[RB+20+1];
    cData[0] = '\xCC';   // Necessary flags for a file
    cData[1] = '\0';     // No more blocks yet
    cData[2] = '\0';
    cData[3] = '\0';     // First block so there is no prev blocks
    cData[4] = '\0';
    cData[RB+20] = mEOF; // Set EOF at the begining
    
    for (char i=0; i < 20; i++)
        cData[RB+i] = filename[i];
    
    // Create file
    write(cData, fb, 0, RB+20+1);
        
    return 0;
}

char mfs::removeFile(char filename[20])
{
    uint32_t block;
    char cData[RB-BLOCK_LLEN];
    char cDataNew[RB] = {'\x04', '\0', '\0', '\0', '\0'};
    uint32_t i=0;

    // Check if file exists
    if (getFirstBlockOfFile(filename, &block) != 0)
        return 1; // File not found
    
    read(cData, block, 0, RB-BLOCK_LLEN);
    
    // Check credentials
     if (((cData[0] & 0x01)|((cData[0] & 0x10) >> 3)|((cData[0] & 0x20) >> 3) & 0x04) != 0)
        return 2; // RO file
    
    // Clear blocks reserved by the file    
    while(1)
    {
        write(cDataNew, block, 0, RB);
        if ((cData[0] & 0x4C) == 0x4C)
            break; // End of file found
        else block = (uint32_t)(cData[1])<<8|cData[2]; // Set next block number
        i++;
        if (i > BC)
            return 1; // fs is corrupted
        read(cData, block, 0, RB-BLOCK_LLEN);
    }
    
    return 0; // Everything went better than expected
}

char mfs::renameFile(char oldFilename[20], char newFilename[20])
{
    uint32_t block;
    char cData[1];

    // Check if file exists
    if (getFirstBlockOfFile(oldFilename, &block) != 0)
        return 1; // File not found
    
    // Check credentials
    read(cData, block, 0, 1);
     char flags = (cData[0] & 0x01)|((cData[0] & 0x10) >> 3)|((cData[0] & 0x20) >> 3);
     if ((flags & 0x04) != 0)
        return 2; // RO file
    
    write(newFilename, block, RB, 20);
    
    return 0; // Everything went better than expected
}

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

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

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

    // Check if file exists
    if (getFirstBlockOfFile(filename, &n) != 0)
        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
uint32_t mfs::free()
{
    uint32_t blocks=0;
    uint32_t 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;
}

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

file::file(mfs *fs_ref, char filename[20], FileOpenMode operation)
{
    fMode = operation;
    
    fs = fs_ref; // Don't forget this :)
    
    uint32_t n;
    char cData[1] = {'\0'};

    // Check if file exists
    if (fs->getFirstBlockOfFile(filename, &n) != 0)
        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 ((fMode != RO) && ((flags & 0x04) != 0))
        error("Oops, cant open in RW mode!");

    // Store FBOF number
    firstBlock = n;
    currBlock = n;
    blockPos = RB+20; // skip flags + pointers + filename
    byteCount = 0; // First byte of the file

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

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

char file::getBlockLink(BlockLinkType linkSelection, uint32_t *blockOut)
{
    char cData[1+BLOCK_LLEN];

    if (linkSelection == NEXT)
    {
        // Fetch link to next block
        fs->read(cData, currBlock, 0, 1+BLOCK_LLEN);
        if ((cData[0] & 0x40) == 0)
        {
            *blockOut  = ((uint32_t)(cData[1])) << 8; // Hbyte of next block link
            *blockOut |= (uint32_t)cData[2];          // Lbyte of next block link
            return 0;
        } else return 1; // Already at last block
    } else if (linkSelection == PREV)
    {
        if (currBlock != firstBlock)
        {
            fs->read(cData, currBlock, 1+BLOCK_LLEN, BLOCK_LLEN);
            *blockOut  = ((uint32_t)(cData[0])) << 8; // Hbyte of next block link
            *blockOut |= (uint32_t)cData[1];          // Lbyte of next block link
            return 0;
        } else return 1; // Already at first block
    }
    
    return 0;
}

char file::removeFollowingBlocks(uint32_t block)
{
    char cData[RB-BLOCK_LLEN];
    char cDataNew[RB] = {'\x04', '\0', '\0', '\0', '\0'};
    uint32_t i=0;
    
    while(1)
    {
        fs->read(cData, block, 0, RB-BLOCK_LLEN);
        fs->write(cDataNew, block, 0, RB);
        if ((cData[0] & 0x4C) == 0x4C)
            break; // End of file found
        else block = (uint32_t)(cData[0])<<8|cData[1]; // Set next block number
        i++;
        if (i > BC)
            return 1; // fs is corrupted
    }
    
    return 0;
}

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

char file::rewind(uint32_t n)
{
    uint32_t i;
    uint32_t block;
    uint32_t varBlockOffset;
    
    flush(); // Check if flush is needed
    
    for (i=0; i < n; i++)
    {
        blockPos--;
        byteCount--;
    
        // Change Block?
        if (blockPos == firstBlock)
            varBlockOffset = RB+20;
        else
            varBlockOffset = RB;
        if ((blockPos < varBlockOffset) && (currBlock > firstBlock))
        {
            // Fetch link to previous block
            if(getBlockLink(PREV, &block) == 0)
            {
                currBlock = block;
                blockPos  = BS-1; // blockPos should be set at the end of block
            } else {
                blockPos++;
                byteCount++;
                return 1; // This is the first block and byte
            }
        }
    }
    
    return 0; // OK
}

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

char file::forward(uint32_t n)
{
    uint32_t i;
    uint32_t block; // Next block number
    char cData[1];

    flush(); // Check if flush is needed
    
    for (i=0; i < n; i++)
    {
        // If this is empty file EOF will be at first byte
        fs->read(cData, currBlock, blockPos, 1);
        if (cData[0] == mEOF)
        {
            return 1;
        }
        
        blockPos++;
        byteCount++;
    
        // Change Block?
        if (blockPos >= BS)
        {
            // Fetch link to next block
            if (getBlockLink(NEXT, &block) == 0)
            {
                currBlock = block;
                blockPos = RB; // Reset block position offset
            } else {
                blockPos--;
                byteCount--;
                return 1; // This is the last block & byte
            }
        }
        fs->read(cData, currBlock, blockPos, 1);
        if (cData[0] == mEOF)
        {
            rewind(1); // Get back to the byte before EOF
            return 1;
        }
    }
    
    return 0; // OK
}

char file::seek(uint32_t byte)
{
    if (byte > byteCount)
        return forward(byte-byteCount);
    else if (byte < byteCount)
        return rewind(byteCount-byte);
    else return 0;
}

// Respects mEOF and automatically sets '\0' at the end of string
void file::read(char *data, uint32_t n)
{
    uint32_t i;
    uint32_t block;
    char cData[1];
    
    flush();
    
    for (i=0; i < n; i++)
    {
        // Change block?
        if (blockPos >= BS-1)
        {
            // Fetch link to next block
            if (getBlockLink(NEXT, &block) == 0)
            {
                currBlock = block;
                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++;
            byteCount++;
        }
    }
    if (data[n-1] != '\0')
        data[n-1] = '\0';
}

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

// Always binary
char file::write(char *data, uint32_t n)
{
    if (fMode == RO) return 1;
    
    for (uint32_t 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[RB], cDataOB[RB-BLOCK_LLEN], c[1];
    uint32_t nextFree;
    uint32_t i;
    uint32_t leftSpaceEOF;
    
    bool destructiveFlag = false; // Set this true if there is any data to be removed
    uint32_t destructiveBlock=0;  // First block to be removed by DWRITE
    
    static bool fEOFow = false;   /* END of file found while appending (and overwritten).
                                     Must be static because we don't want to found many EOF's. */
    
    
    if (bufPos == 0) return 0; // File up-to date
    if (fMode == RO) return 1;
    
    for (i=0; i <= bufPos; i++)
    {
        if(i%2)
            FlushLed = 1;
        else
            FlushLed = 0;
        
        // Change Block?
        if ((bufPos - i) == 1)
            leftSpaceEOF = 1;
        else
            leftSpaceEOF = 0;
        if (blockPos >= BS-leftSpaceEOF)
        {
            // Fetch new unused block number
            if(fs->getNextFreeBlock(&nextFree) != 0)
                return 2; // No free space left
            
            // Read flags from current block
            fs->read(cDataOB, currBlock, 0, RB-BLOCK_LLEN);
            
            /* If destructive write is set then check if there is something
               to be marked for removal */
            if (((cDataOB[0] & 0x40) != 0x40) && (fMode == DWRITE))
            {
                destructiveFlag = true;
                destructiveBlock = (uint32_t)cDataOB[1]<<8|cDataOB[2];
                goto allocate_new_block;
            } else if ((cDataOB[0] & 0x40) != 0x40) // fMode == AWRITE
            {
                // Update current block info
                currBlock = (uint32_t)cDataOB[1]<<8|cDataOB[2];
                blockPos  = RB; // Reset block position offset
            } else // There is no block to append so we allocate a new one
            {
                allocate_new_block:
                // Allocate new block for use
                cData[0] = 0x4C;                              // New flags
                cData[1] = '\0';                              // Hbyte of Next Block link
                cData[2] = '\0';                              // Lbyte of Next Block link
                cData[3] = (char)((currBlock & 0xff00) >> 8); // Hbyte of Prev Block link
                cData[4] = (char)(currBlock & 0x00ff);        // Lbyte of Prev Block link
                fs->write(cData, nextFree, 0, RB);            // Update Block Data
            
                // Link old block with new block
                cDataOB[0] &= ~0x40;                             // Clear LBOF flag if set
                cDataOB[1]  = (char)((nextFree & 0xff00) >> 8);  // Hbyte of Next Block link
                cDataOB[2]  = (char)(nextFree & 0x00ff);         // Lbyte of Next Block link
                fs->write(cDataOB, currBlock, 0, RB-BLOCK_LLEN); // Update Block Data
            
                // Update current block info
                currBlock = nextFree;
                blockPos  = RB; // Reset block position offset
            }
        }
        
        if (fMode == AWRITE) // Check if EOF is here
        {
            fs->read(c, currBlock, blockPos, 1);
            if ((c[0] == mEOF) && (fEOFow == false))
                fEOFow = true;
        }
        
        // Write file
        c[0]=buffer[i];
        fs->write(c, currBlock, blockPos, 1);
        blockPos++;
        byteCount++;
        
        // For fail safe, write EOF now
        if ((fMode == DWRITE)||(fEOFow == true))
            fs->write((char[]){mEOF}, currBlock, blockPos, 1); // Write mEOF
    }
    
    bufPos = 0; // Reset buffer position counter
    
    /* If destructive write flag is set
       and there is data to be removed then remove data now */
    if (destructiveFlag == true)
        if(removeFollowingBlocks(destructiveBlock) != 0)
            return 3;
        
    FlushLed = 0;
    return 0;
}