Newer
Older
UbixOS / sys / fs / fat / fat_filelib.c
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//                            FAT16/32 File IO Library
//                                    V2.6
//                              Ultra-Embedded.com
//                            Copyright 2003 - 2012
//
//                         Email: admin@ultra-embedded.com
//
//                                License: GPL
//   If you would like a version with a more permissive license for use in
//   closed source commercial applications please contact me for details.
//-----------------------------------------------------------------------------
//
// This file is part of FAT File IO Library.
//
// FAT File IO Library is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// FAT File IO Library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FAT File IO Library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//#include <stdlib.h>
#include <string.h>
#include "fat_defs.h"
#include "fat_access.h"
#include "fat_table.h"
#include "fat_write.h"
#include "fat_misc.h"
#include "fat_string.h"
#include "fat_filelib.h"
#include "fat_cache.h"

//-----------------------------------------------------------------------------
// Locals
//-----------------------------------------------------------------------------
static FL_FILE _files[FATFS_MAX_OPEN_FILES];
static int _filelib_init = 0;
static int _filelib_valid = 0;
static struct fatfs _fs;
static struct fat_list _open_file_list;
static struct fat_list _free_file_list;

//-----------------------------------------------------------------------------
// Macros
//-----------------------------------------------------------------------------

// Macro for checking if file lib is initialised
#define CHECK_FL_INIT()     { if (_filelib_init==0) fl_init(); }

#define FL_LOCK(a)          do { if ((a)->fl_lock) (a)->fl_lock(); } while (0)
#define FL_UNLOCK(a)        do { if ((a)->fl_unlock) (a)->fl_unlock(); } while (0)

//-----------------------------------------------------------------------------
// Local Functions
//-----------------------------------------------------------------------------
static void _fl_init();

//-----------------------------------------------------------------------------
// _allocate_file: Find a slot in the open files buffer for a new file
//-----------------------------------------------------------------------------
static FL_FILE* _allocate_file(void) {
  // Allocate free file
  struct fat_node *node = fat_list_pop_head(&_free_file_list);
  // Add to open list
  if (node)
    fat_list_insert_last(&_open_file_list, node);

  return fat_list_entry(node, FL_FILE, list_node);
}
//-----------------------------------------------------------------------------
// _check_file_open: Returns true if the file is already open
//-----------------------------------------------------------------------------
static int _check_file_open(FL_FILE *file) {
  struct fat_node *node;

  // Compare open files
  fat_list_for_each(&_open_file_list, node)
  {
    FL_FILE *openFile = fat_list_entry(node, FL_FILE, list_node);

    // If not the current file
    if (openFile != file) {
      // Compare path and name
      if ((fatfs_compare_names(openFile->path, file->path)) && (fatfs_compare_names(openFile->filename, file->filename)))
        return 1;
    }
  }

  return 0;
}
//-----------------------------------------------------------------------------
// _free_file: Free open file handle
//-----------------------------------------------------------------------------
static void _free_file(FL_FILE *file) {
  // Remove from open list
  fat_list_remove(&_open_file_list, &file->list_node);

  // Add to free list
  fat_list_insert_last(&_free_file_list, &file->list_node);
}

//-----------------------------------------------------------------------------
//                                Low Level
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// _open_directory: Cycle through path string to find the start cluster
// address of the highest subdir.
//-----------------------------------------------------------------------------
static int _open_directory(char *path, uint32 *pathCluster) {
  int levels;
  int sublevel;
  char currentfolder[FATFS_MAX_LONG_FILENAME];
  struct fat_dir_entry sfEntry;
  uint32 startcluster;

  // Set starting cluster to root cluster
  startcluster = fatfs_get_root_cluster(&_fs);

  // Find number of levels
  levels = fatfs_total_path_levels(path);

  // Cycle through each level and get the start sector
  for (sublevel = 0; sublevel < (levels + 1); sublevel++) {
    if (fatfs_get_substring(path, sublevel, currentfolder, sizeof(currentfolder)) == -1)
      return 0;

    // Find clusteraddress for folder (currentfolder)
    if (fatfs_get_file_entry(&_fs, startcluster, currentfolder, &sfEntry)) {
      // Check entry is folder
      if (fatfs_entry_is_dir(&sfEntry))
        startcluster = ((FAT_HTONS((uint32 )sfEntry.FstClusHI)) << 16) + FAT_HTONS(sfEntry.FstClusLO);
      else
        return 0;
    }
    else
      return 0;
  }

  *pathCluster = startcluster;
  return 1;
}
//-----------------------------------------------------------------------------
// _create_directory: Cycle through path string and create the end directory
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
static int _create_directory(char *path) {
  FL_FILE *file;
  struct fat_dir_entry sfEntry;
  char shortFilename[FAT_SFN_SIZE_FULL];
  int tailNum = 0;
  int i;

  // Allocate a new file handle
  file = _allocate_file();
  if (!file)
    return 0;

  // Clear filename
  memset(file->path, '\0', sizeof(file->path));
  memset(file->filename, '\0', sizeof(file->filename));

  // Split full path into filename and directory path
  if (fatfs_split_path((char*) path, file->path, sizeof(file->path), file->filename, sizeof(file->filename)) == -1) {
    _free_file(file);
    return 0;
  }

  // Check if file already open
  if (_check_file_open(file)) {
    _free_file(file);
    return 0;
  }

  // If file is in the root dir
  if (file->path[0] == 0)
    file->parentcluster = fatfs_get_root_cluster(&_fs);
  else {
    // Find parent directory start cluster
    if (!_open_directory(file->path, &file->parentcluster)) {
      _free_file(file);
      return 0;
    }
  }

  // Check if same filename exists in directory
  if (fatfs_get_file_entry(&_fs, file->parentcluster, file->filename, &sfEntry) == 1) {
    _free_file(file);
    return 0;
  }

  file->startcluster = 0;

  // Create the file space for the folder (at least one clusters worth!)
  if (!fatfs_allocate_free_space(&_fs, 1, &file->startcluster, 1)) {
    _free_file(file);
    return 0;
  }

  // Erase new directory cluster
  memset(file->file_data_sector, 0x00, FAT_SECTOR_SIZE);
  for (i = 0; i < _fs.sectors_per_cluster; i++) {
    if (!fatfs_write_sector(&_fs, file->startcluster, i, file->file_data_sector)) {
      _free_file(file);
      return 0;
    }
  }

#if FATFS_INC_LFN_SUPPORT

  // Generate a short filename & tail
  tailNum = 0;
  do {
    // Create a standard short filename (without tail)
    fatfs_lfn_create_sfn(shortFilename, file->filename);

    // If second hit or more, generate a ~n tail
    if (tailNum != 0)
      fatfs_lfn_generate_tail((char*) file->shortfilename, shortFilename, tailNum);
    // Try with no tail if first entry
    else
      memcpy(file->shortfilename, shortFilename, FAT_SFN_SIZE_FULL);

    // Check if entry exists already or not
    if (fatfs_sfn_exists(&_fs, file->parentcluster, (char*) file->shortfilename) == 0)
      break;

    tailNum++;
  } while (tailNum < 9999);

  // We reached the max number of duplicate short file names (unlikely!)
  if (tailNum == 9999) {
    // Delete allocated space
    fatfs_free_cluster_chain(&_fs, file->startcluster);

    _free_file(file);
    return 0;
  }
#else
    // Create a standard short filename (without tail)
    if (!fatfs_lfn_create_sfn(shortFilename, file->filename))
    {
        // Delete allocated space
        fatfs_free_cluster_chain(&_fs, file->startcluster);

        _free_file(file);
        return 0;
    }

    // Copy to SFN space
    memcpy(file->shortfilename, shortFilename, FAT_SFN_SIZE_FULL);

    // Check if entry exists already
    if (fatfs_sfn_exists(&_fs, file->parentcluster, (char*)file->shortfilename))
    {
        // Delete allocated space
        fatfs_free_cluster_chain(&_fs, file->startcluster);

        _free_file(file);
        return 0;
    }
#endif

  // Add file to disk
  if (!fatfs_add_file_entry(&_fs, file->parentcluster, (char*) file->filename, (char*) file->shortfilename, file->startcluster, 0, 1)) {
    // Delete allocated space
    fatfs_free_cluster_chain(&_fs, file->startcluster);

    _free_file(file);
    return 0;
  }

  // General
  file->filelength = 0;
  file->bytenum = 0;
  file->file_data_address = 0xFFFFFFFF;
  file->file_data_dirty = 0;
  file->filelength_changed = 0;

  // Quick lookup for next link in the chain
  file->last_fat_lookup.ClusterIdx = 0xFFFFFFFF;
  file->last_fat_lookup.CurrentCluster = 0xFFFFFFFF;

  fatfs_fat_purge(&_fs);

  _free_file(file);
  return 1;
}
#endif
//-----------------------------------------------------------------------------
// _open_file: Open a file for reading
//-----------------------------------------------------------------------------
static FL_FILE* _open_file(const char *path) {
  FL_FILE *file;
  struct fat_dir_entry sfEntry;

  // Allocate a new file handle
  file = _allocate_file();
  if (!file)
    return NULL;

  // Clear filename
  memset(file->path, '\0', sizeof(file->path));
  memset(file->filename, '\0', sizeof(file->filename));

  // Split full path into filename and directory path
  if (fatfs_split_path((char*) path, file->path, sizeof(file->path), file->filename, sizeof(file->filename)) == -1) {
    _free_file(file);
    return NULL;
  }

  // Check if file already open
  if (_check_file_open(file)) {
    _free_file(file);
    return NULL;
  }
  // If file is in the root dir
  if (file->path[0] == 0)
    file->parentcluster = fatfs_get_root_cluster(&_fs);
  else {
    // Find parent directory start cluster
    if (!_open_directory(file->path, &file->parentcluster)) {
      _free_file(file);
      return NULL;
    }
  }
  // Using dir cluster address search for filename
  if (fatfs_get_file_entry(&_fs, file->parentcluster, file->filename, &sfEntry)) {
    // Make sure entry is file not dir!
    if (fatfs_entry_is_file(&sfEntry)) {
      // Initialise file details
      memcpy(file->shortfilename, sfEntry.Name, FAT_SFN_SIZE_FULL);
      file->filelength = FAT_HTONL(sfEntry.FileSize);
      file->bytenum = 0;
      file->startcluster = ((FAT_HTONS((uint32 )sfEntry.FstClusHI)) << 16) + FAT_HTONS(sfEntry.FstClusLO);
      file->file_data_address = 0xFFFFFFFF;
      file->file_data_dirty = 0;
      file->filelength_changed = 0;

      // Quick lookup for next link in the chain
      file->last_fat_lookup.ClusterIdx = 0xFFFFFFFF;
      file->last_fat_lookup.CurrentCluster = 0xFFFFFFFF;

      fatfs_cache_init(&_fs, file);

      fatfs_fat_purge(&_fs);

      return file;
    }
  }
  _free_file(file);
  return NULL;
}
//-----------------------------------------------------------------------------
// _create_file: Create a new file
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
static FL_FILE* _create_file(const char *filename) {
  FL_FILE *file;
  struct fat_dir_entry sfEntry;
  char shortFilename[FAT_SFN_SIZE_FULL];
  int tailNum = 0;

  // No write access?
  if (!_fs.disk_io.write_media)
    return NULL;

  // Allocate a new file handle
  file = _allocate_file();
  if (!file)
    return NULL;

  // Clear filename
  memset(file->path, '\0', sizeof(file->path));
  memset(file->filename, '\0', sizeof(file->filename));

  // Split full path into filename and directory path
  if (fatfs_split_path((char*) filename, file->path, sizeof(file->path), file->filename, sizeof(file->filename)) == -1) {
    _free_file(file);
    return NULL;
  }

  // Check if file already open
  if (_check_file_open(file)) {
    _free_file(file);
    return NULL;
  }

  // If file is in the root dir
  if (file->path[0] == 0)
    file->parentcluster = fatfs_get_root_cluster(&_fs);
  else {
    // Find parent directory start cluster
    if (!_open_directory(file->path, &file->parentcluster)) {
      _free_file(file);
      return NULL;
    }
  }

  // Check if same filename exists in directory
  if (fatfs_get_file_entry(&_fs, file->parentcluster, file->filename, &sfEntry) == 1) {
    _free_file(file);
    return NULL;
  }

  file->startcluster = 0;

  // Create the file space for the file (at least one clusters worth!)
  if (!fatfs_allocate_free_space(&_fs, 1, &file->startcluster, 1)) {
    _free_file(file);
    return NULL;
  }

#if FATFS_INC_LFN_SUPPORT
  // Generate a short filename & tail
  tailNum = 0;
  do {
    // Create a standard short filename (without tail)
    fatfs_lfn_create_sfn(shortFilename, file->filename);

    // If second hit or more, generate a ~n tail
    if (tailNum != 0)
      fatfs_lfn_generate_tail((char*) file->shortfilename, shortFilename, tailNum);
    // Try with no tail if first entry
    else
      memcpy(file->shortfilename, shortFilename, FAT_SFN_SIZE_FULL);

    // Check if entry exists already or not
    if (fatfs_sfn_exists(&_fs, file->parentcluster, (char*) file->shortfilename) == 0)
      break;

    tailNum++;
  } while (tailNum < 9999);

  // We reached the max number of duplicate short file names (unlikely!)
  if (tailNum == 9999) {
    // Delete allocated space
    fatfs_free_cluster_chain(&_fs, file->startcluster);

    _free_file(file);
    return NULL;
  }
#else
    // Create a standard short filename (without tail)
    if (!fatfs_lfn_create_sfn(shortFilename, file->filename))
    {
        // Delete allocated space
        fatfs_free_cluster_chain(&_fs, file->startcluster);

        _free_file(file);
        return NULL;
    }

    // Copy to SFN space
    memcpy(file->shortfilename, shortFilename, FAT_SFN_SIZE_FULL);

    // Check if entry exists already
    if (fatfs_sfn_exists(&_fs, file->parentcluster, (char*)file->shortfilename))
    {
        // Delete allocated space
        fatfs_free_cluster_chain(&_fs, file->startcluster);

        _free_file(file);
        return NULL;
    }
#endif

  // Add file to disk
  if (!fatfs_add_file_entry(&_fs, file->parentcluster, (char*) file->filename, (char*) file->shortfilename, file->startcluster, 0, 0)) {
    // Delete allocated space
    fatfs_free_cluster_chain(&_fs, file->startcluster);

    _free_file(file);
    return NULL;
  }

  // General
  file->filelength = 0;
  file->bytenum = 0;
  file->file_data_address = 0xFFFFFFFF;
  file->file_data_dirty = 0;
  file->filelength_changed = 0;

  // Quick lookup for next link in the chain
  file->last_fat_lookup.ClusterIdx = 0xFFFFFFFF;
  file->last_fat_lookup.CurrentCluster = 0xFFFFFFFF;

  fatfs_cache_init(&_fs, file);

  fatfs_fat_purge(&_fs);

  return file;
}
#endif
//-----------------------------------------------------------------------------
// _read_sectors: Read sector(s) from disk to file
//-----------------------------------------------------------------------------
static uint32 _read_sectors(FL_FILE *file, uint32 offset, uint8 *buffer, uint32 count) {
  uint32 Sector = 0;
  uint32 ClusterIdx = 0;
  uint32 Cluster = 0;
  uint32 i;
  uint32 lba;

  // Find cluster index within file & sector with cluster
  ClusterIdx = offset / _fs.sectors_per_cluster;
  Sector = offset - (ClusterIdx * _fs.sectors_per_cluster);

  // Limit number of sectors read to the number remaining in this cluster
  if ((Sector + count) > _fs.sectors_per_cluster)
    count = _fs.sectors_per_cluster - Sector;

  // Quick lookup for next link in the chain
  if (ClusterIdx == file->last_fat_lookup.ClusterIdx)
    Cluster = file->last_fat_lookup.CurrentCluster;
  // Else walk the chain
  else {
    // Starting from last recorded cluster?
    if (ClusterIdx && ClusterIdx == file->last_fat_lookup.ClusterIdx + 1) {
      i = file->last_fat_lookup.ClusterIdx;
      Cluster = file->last_fat_lookup.CurrentCluster;
    }
    // Start searching from the beginning..
    else {
      // Set start of cluster chain to initial value
      i = 0;
      Cluster = file->startcluster;
    }

    // Follow chain to find cluster to read
    for (; i < ClusterIdx; i++) {
      uint32 nextCluster;

      // Does the entry exist in the cache?
      if (!fatfs_cache_get_next_cluster(&_fs, file, i, &nextCluster)) {
        // Scan file linked list to find next entry
        nextCluster = fatfs_find_next_cluster(&_fs, Cluster);

        // Push entry into cache
        fatfs_cache_set_next_cluster(&_fs, file, i, nextCluster);
      }

      Cluster = nextCluster;
    }

    // Record current cluster lookup details (if valid)
    if (Cluster != FAT32_LAST_CLUSTER) {
      file->last_fat_lookup.CurrentCluster = Cluster;
      file->last_fat_lookup.ClusterIdx = ClusterIdx;
    }
  }

  // If end of cluster chain then return false
  if (Cluster == FAT32_LAST_CLUSTER)
    return 0;

  // Calculate sector address
  lba = fatfs_lba_of_cluster(&_fs, Cluster) + Sector;

  // Read sector of file
  if (fatfs_sector_read(&_fs, lba, buffer, count))
    return count;
  else
    return 0;
}

//-----------------------------------------------------------------------------
//                                External API
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// fl_init: Initialise library
//-----------------------------------------------------------------------------
void fl_init(void) {
  int i;

  fat_list_init(&_free_file_list);
  fat_list_init(&_open_file_list);

  // Add all file objects to free list
  for (i = 0; i < FATFS_MAX_OPEN_FILES; i++)
    fat_list_insert_last(&_free_file_list, &_files[i].list_node);

  _filelib_init = 1;
}
//-----------------------------------------------------------------------------
// fl_attach_locks:
//-----------------------------------------------------------------------------
void fl_attach_locks(void (*lock)(void), void (*unlock)(void)) {
  _fs.fl_lock = lock;
  _fs.fl_unlock = unlock;
}
//-----------------------------------------------------------------------------
// fl_attach_media:
//-----------------------------------------------------------------------------
int fl_attach_media(fn_diskio_read rd, fn_diskio_write wr) {
  int res;

  // If first call to library, initialise
  CHECK_FL_INIT();

  _fs.disk_io.read_media = rd;
  _fs.disk_io.write_media = wr;

  // Initialise FAT parameters
  if ((res = fatfs_init(&_fs)) != FAT_INIT_OK) {
    FAT_PRINTF(("FAT_FS: Error could not load FAT details (%d)!\r\n", res));
    return res;
  }

  _filelib_valid = 1;
  return FAT_INIT_OK;
}
//-----------------------------------------------------------------------------
// fl_shutdown: Call before shutting down system
//-----------------------------------------------------------------------------
void fl_shutdown(void) {
  // If first call to library, initialise
  CHECK_FL_INIT();

  FL_LOCK(&_fs);
  fatfs_fat_purge(&_fs);
  FL_UNLOCK(&_fs);
}
//-----------------------------------------------------------------------------
// fopen: Open or Create a file for reading or writing
//-----------------------------------------------------------------------------
void* fl_fopen(const char *path, const char *mode) {
  int i;
  FL_FILE *file;
  uint8 flags = 0;

  // If first call to library, initialise
  CHECK_FL_INIT();

  if (!_filelib_valid)
    return NULL;

  if (!path || !mode)
    return NULL;

  // Supported Modes:
  // "r" Open a file for reading.
  //        The file must exist.
  // "w" Create an empty file for writing.
  //        If a file with the same name already exists its content is erased and the file is treated as a new empty file.
  // "a" Append to a file.
  //        Writing operations append data at the end of the file.
  //        The file is created if it does not exist.
  // "r+" Open a file for update both reading and writing.
  //        The file must exist.
  // "w+" Create an empty file for both reading and writing.
  //        If a file with the same name already exists its content is erased and the file is treated as a new empty file.
  // "a+" Open a file for reading and appending.
  //        All writing operations are performed at the end of the file, protecting the previous content to be overwritten.
  //        You can reposition (fseek, rewind) the internal pointer to anywhere in the file for reading, but writing operations
  //        will move it back to the end of file.
  //        The file is created if it does not exist.

  for (i = 0; i < (int) strlen(mode); i++) {
    switch (mode[i]) {
      case 'r':
      case 'R':
        flags |= FILE_READ;
        break;
      case 'w':
      case 'W':
        flags |= FILE_WRITE;
        flags |= FILE_ERASE;
        flags |= FILE_CREATE;
        break;
      case 'a':
      case 'A':
        flags |= FILE_WRITE;
        flags |= FILE_APPEND;
        flags |= FILE_CREATE;
        break;
      case '+':
        if (flags & FILE_READ)
          flags |= FILE_WRITE;
        else if (flags & FILE_WRITE) {
          flags |= FILE_READ;
          flags |= FILE_ERASE;
          flags |= FILE_CREATE;
        }
        else if (flags & FILE_APPEND) {
          flags |= FILE_READ;
          flags |= FILE_WRITE;
          flags |= FILE_APPEND;
          flags |= FILE_CREATE;
        }
        break;
      case 'b':
      case 'B':
        flags |= FILE_BINARY;
        break;
    }
  }

  file = NULL;

#if FATFS_INC_WRITE_SUPPORT == 0
    // No write support!
    flags &= ~(FILE_CREATE | FILE_WRITE | FILE_APPEND);
#endif

  // No write access - remove write/modify flags
  if (!_fs.disk_io.write_media)
    flags &= ~(FILE_CREATE | FILE_WRITE | FILE_APPEND);

  FL_LOCK(&_fs);

  // Read
  if (flags & FILE_READ) {
    file = _open_file(path);
  }

  // Create New
#if FATFS_INC_WRITE_SUPPORT
  if (!file && (flags & FILE_CREATE))
    file = _create_file(path);
#endif

  // Write Existing (and not open due to read or create)
  if (!(flags & FILE_READ))
    if ((flags & FILE_CREATE) && !file)
      if (flags & (FILE_WRITE | FILE_APPEND))
        file = _open_file(path);

  if (file)
    file->flags = flags;

  FL_UNLOCK(&_fs);
  return file;
}
//-----------------------------------------------------------------------------
// _write_sectors: Write sector(s) to disk
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
static uint32 _write_sectors(FL_FILE *file, uint32 offset, uint8 *buf, uint32 count) {
  uint32 SectorNumber = 0;
  uint32 ClusterIdx = 0;
  uint32 Cluster = 0;
  uint32 LastCluster = FAT32_LAST_CLUSTER;
  uint32 i;
  uint32 lba;
  uint32 TotalWriteCount = count;

  // Find values for Cluster index & sector within cluster
  ClusterIdx = offset / _fs.sectors_per_cluster;
  SectorNumber = offset - (ClusterIdx * _fs.sectors_per_cluster);

  // Limit number of sectors written to the number remaining in this cluster
  if ((SectorNumber + count) > _fs.sectors_per_cluster)
    count = _fs.sectors_per_cluster - SectorNumber;

  // Quick lookup for next link in the chain
  if (ClusterIdx == file->last_fat_lookup.ClusterIdx)
    Cluster = file->last_fat_lookup.CurrentCluster;
  // Else walk the chain
  else {
    // Starting from last recorded cluster?
    if (ClusterIdx && ClusterIdx == file->last_fat_lookup.ClusterIdx + 1) {
      i = file->last_fat_lookup.ClusterIdx;
      Cluster = file->last_fat_lookup.CurrentCluster;
    }
    // Start searching from the beginning..
    else {
      // Set start of cluster chain to initial value
      i = 0;
      Cluster = file->startcluster;
    }

    // Follow chain to find cluster to read
    for (; i < ClusterIdx; i++) {
      uint32 nextCluster;

      // Does the entry exist in the cache?
      if (!fatfs_cache_get_next_cluster(&_fs, file, i, &nextCluster)) {
        // Scan file linked list to find next entry
        nextCluster = fatfs_find_next_cluster(&_fs, Cluster);

        // Push entry into cache
        fatfs_cache_set_next_cluster(&_fs, file, i, nextCluster);
      }

      LastCluster = Cluster;
      Cluster = nextCluster;

      // Dont keep following a dead end
      if (Cluster == FAT32_LAST_CLUSTER)
        break;
    }

    // If we have reached the end of the chain, allocate more!
    if (Cluster == FAT32_LAST_CLUSTER) {
      // Add some more cluster(s) to the last good cluster chain
      if (!fatfs_add_free_space(&_fs, &LastCluster, (TotalWriteCount + _fs.sectors_per_cluster - 1) / _fs.sectors_per_cluster))
        return 0;

      Cluster = LastCluster;
    }

    // Record current cluster lookup details
    file->last_fat_lookup.CurrentCluster = Cluster;
    file->last_fat_lookup.ClusterIdx = ClusterIdx;
  }

  // Calculate write address
  lba = fatfs_lba_of_cluster(&_fs, Cluster) + SectorNumber;

  if (fatfs_sector_write(&_fs, lba, buf, count))
    return count;
  else
    return 0;
}
#endif
//-----------------------------------------------------------------------------
// fl_fflush: Flush un-written data to the file
//-----------------------------------------------------------------------------
int fl_fflush(void *f) {
#if FATFS_INC_WRITE_SUPPORT
  FL_FILE *file = (FL_FILE*) f;

  // If first call to library, initialise
  CHECK_FL_INIT();

  if (file) {
    FL_LOCK(&_fs);

    // If some write data still in buffer
    if (file->file_data_dirty) {
      // Write back current sector before loading next
      if (_write_sectors(file, file->file_data_address, file->file_data_sector, 1))
        file->file_data_dirty = 0;
    }

    FL_UNLOCK(&_fs);
  }
#endif
  return 0;
}
//-----------------------------------------------------------------------------
// fl_fclose: Close an open file
//-----------------------------------------------------------------------------
void fl_fclose(void *f) {
  FL_FILE *file = (FL_FILE*) f;

  // If first call to library, initialise
  CHECK_FL_INIT();

  if (file) {
    FL_LOCK(&_fs);

    // Flush un-written data to file
    fl_fflush(f);

    // File size changed?
    if (file->filelength_changed) {
#if FATFS_INC_WRITE_SUPPORT
      // Update filesize in directory
      fatfs_update_file_length(&_fs, file->parentcluster, (char*) file->shortfilename, file->filelength);
#endif
      file->filelength_changed = 0;
    }

    file->bytenum = 0;
    file->filelength = 0;
    file->startcluster = 0;
    file->file_data_address = 0xFFFFFFFF;
    file->file_data_dirty = 0;
    file->filelength_changed = 0;

    // Free file handle
    _free_file(file);

    fatfs_fat_purge(&_fs);

    FL_UNLOCK(&_fs);
  }
}
//-----------------------------------------------------------------------------
// fl_fgetc: Get a character in the stream
//-----------------------------------------------------------------------------
int fl_fgetc(void *f) {
  int res;
  uint8 data = 0;

  res = fl_fread(&data, 1, 1, f);
  if (res == 1)
    return (int) data;
  else
    return res;
}
//-----------------------------------------------------------------------------
// fl_fgets: Get a string from a stream
//-----------------------------------------------------------------------------
char* fl_fgets(char *s, int n, void *f) {
  int idx = 0;

  // Space for null terminator?
  if (n > 0) {
    // While space (+space for null terminator)
    while (idx < (n - 1)) {
      int ch = fl_fgetc(f);

      // EOF / Error?
      if (ch < 0)
        break;

      // Store character read from stream
      s[idx++] = (char) ch;

      // End of line?
      if (ch == '\n')
        break;
    }

    if (idx > 0)
      s[idx] = '\0';
  }

  return (idx > 0) ? s : 0;
}
//-----------------------------------------------------------------------------
// fl_fread: Read a block of data from the file
//-----------------------------------------------------------------------------
int fl_fread(void *buffer, int size, int length, void *f) {
  uint32 sector;
  uint32 offset;
  int copyCount;
  int count = size * length;
  int bytesRead = 0;

  FL_FILE *file = (FL_FILE*) f;

  // If first call to library, initialise
  CHECK_FL_INIT();

  if (buffer == NULL || file == NULL)
    return -1;

  // No read permissions
  if (!(file->flags & FILE_READ))
    return -1;

  // Nothing to be done
  if (!count)
    return 0;

  // Check if read starts past end of file
  if (file->bytenum >= file->filelength)
    return -1;

  // Limit to file size
  if ((file->bytenum + count) > file->filelength)
    count = file->filelength - file->bytenum;

  // Calculate start sector
  sector = file->bytenum / FAT_SECTOR_SIZE;

  // Offset to start copying data from first sector
  offset = file->bytenum % FAT_SECTOR_SIZE;

  while (bytesRead < count) {
    // Read whole sector, read from media directly into target buffer
    if ((offset == 0) && ((count - bytesRead) >= FAT_SECTOR_SIZE)) {
      // Read as many sectors as possible into target buffer
      uint32 sectorsRead = _read_sectors(file, sector, (uint8*) ((uint8*) buffer + bytesRead), (count - bytesRead) / FAT_SECTOR_SIZE);
      if (sectorsRead) {
        // We have upto one sector to copy
        copyCount = FAT_SECTOR_SIZE * sectorsRead;

        // Move onto next sector and reset copy offset
        sector += sectorsRead;
        offset = 0;
      }
      else
        break;
    }
    else {
      // Do we need to re-read the sector?
      if (file->file_data_address != sector) {
        // Flush un-written data to file
        if (file->file_data_dirty)
          fl_fflush(file);

        // Get LBA of sector offset within file
        if (!_read_sectors(file, sector, file->file_data_sector, 1))
          // Read failed - out of range (probably)
          break;

        file->file_data_address = sector;
        file->file_data_dirty = 0;
      }

      // We have upto one sector to copy
      copyCount = FAT_SECTOR_SIZE - offset;

      // Only require some of this sector?
      if (copyCount > (count - bytesRead))
        copyCount = (count - bytesRead);

      // Copy to application buffer
      memcpy((uint8*) ((uint8*) buffer + bytesRead), (uint8*) (file->file_data_sector + offset), copyCount);

      // Move onto next sector and reset copy offset
      sector++;
      offset = 0;
    }

    // Increase total read count
    bytesRead += copyCount;

    // Increment file pointer
    file->bytenum += copyCount;
  }

  return bytesRead;
}
//-----------------------------------------------------------------------------
// fl_fseek: Seek to a specific place in the file
//-----------------------------------------------------------------------------
int fl_fseek(void *f, long offset, int origin) {
  FL_FILE *file = (FL_FILE*) f;
  int res = -1;

  // If first call to library, initialise
  CHECK_FL_INIT();

  if (!file)
    return -1;

  if (origin == SEEK_END && offset != 0)
    return -1;

  FL_LOCK(&_fs);

  // Invalidate file buffer
  file->file_data_address = 0xFFFFFFFF;
  file->file_data_dirty = 0;

  if (origin == SEEK_SET) {
    file->bytenum = (uint32) offset;

    if (file->bytenum > file->filelength)
      file->bytenum = file->filelength;

    res = 0;
  }
  else if (origin == SEEK_CUR) {
    // Positive shift
    if (offset >= 0) {
      file->bytenum += offset;

      if (file->bytenum > file->filelength)
        file->bytenum = file->filelength;
    }
    // Negative shift
    else {
      // Make shift positive
      offset = -offset;

      // Limit to negative shift to start of file
      if ((uint32) offset > file->bytenum)
        file->bytenum = 0;
      else
        file->bytenum -= offset;
    }

    res = 0;
  }
  else if (origin == SEEK_END) {
    file->bytenum = file->filelength;
    res = 0;
  }
  else
    res = -1;

  FL_UNLOCK(&_fs);

  return res;
}
//-----------------------------------------------------------------------------
// fl_fgetpos: Get the current file position
//-----------------------------------------------------------------------------
int fl_fgetpos(void *f, uint32 *position) {
  FL_FILE *file = (FL_FILE*) f;

  if (!file)
    return -1;

  FL_LOCK(&_fs);

  // Get position
  *position = file->bytenum;

  FL_UNLOCK(&_fs);

  return 0;
}
//-----------------------------------------------------------------------------
// fl_ftell: Get the current file position
//-----------------------------------------------------------------------------
long fl_ftell(void *f) {
  uint32 pos = 0;

  fl_fgetpos(f, &pos);

  return (long) pos;
}
//-----------------------------------------------------------------------------
// fl_feof: Is the file pointer at the end of the stream?
//-----------------------------------------------------------------------------
int fl_feof(void *f) {
  FL_FILE *file = (FL_FILE*) f;
  int res;

  if (!file)
    return -1;

  FL_LOCK(&_fs);

  if (file->bytenum == file->filelength)
    res = EOF;
  else
    res = 0;

  FL_UNLOCK(&_fs);

  return res;
}
//-----------------------------------------------------------------------------
// fl_fputc: Write a character to the stream
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fl_fputc(int c, void *f) {
  uint8 data = (uint8) c;
  int res;

  res = fl_fwrite(&data, 1, 1, f);
  if (res == 1)
    return c;
  else
    return res;
}
#endif
//-----------------------------------------------------------------------------
// fl_fwrite: Write a block of data to the stream
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fl_fwrite(const void *data, int size, int count, void *f) {
  FL_FILE *file = (FL_FILE*) f;
  uint32 sector;
  uint32 offset;
  uint32 length = (size * count);
  uint8 *buffer = (uint8*) data;
  uint32 bytesWritten = 0;
  uint32 copyCount;

  // If first call to library, initialise
  CHECK_FL_INIT();

  if (!file)
    return -1;

  FL_LOCK(&_fs);

  // No write permissions
  if (!(file->flags & FILE_WRITE)) {
    FL_UNLOCK(&_fs);
    return -1;
  }

  // Append writes to end of file
  if (file->flags & FILE_APPEND)
    file->bytenum = file->filelength;
  // Else write to current position

  // Calculate start sector
  sector = file->bytenum / FAT_SECTOR_SIZE;

  // Offset to start copying data from first sector
  offset = file->bytenum % FAT_SECTOR_SIZE;

  while (bytesWritten < length) {
    // Whole sector or more to be written?
    if ((offset == 0) && ((length - bytesWritten) >= FAT_SECTOR_SIZE)) {
      uint32 sectorsWrote;

      // Buffered sector, flush back to disk
      if (file->file_data_address != 0xFFFFFFFF) {
        // Flush un-written data to file
        if (file->file_data_dirty)
          fl_fflush(file);

        file->file_data_address = 0xFFFFFFFF;
        file->file_data_dirty = 0;
      }

      // Write as many sectors as possible
      sectorsWrote = _write_sectors(file, sector, (uint8*) (buffer + bytesWritten), (length - bytesWritten) / FAT_SECTOR_SIZE);
      copyCount = FAT_SECTOR_SIZE * sectorsWrote;

      // Increase total read count
      bytesWritten += copyCount;

      // Increment file pointer
      file->bytenum += copyCount;

      // Move onto next sector and reset copy offset
      sector += sectorsWrote;
      offset = 0;

      if (!sectorsWrote)
        break;
    }
    else {
      // We have upto one sector to copy
      copyCount = FAT_SECTOR_SIZE - offset;

      // Only require some of this sector?
      if (copyCount > (length - bytesWritten))
        copyCount = (length - bytesWritten);

      // Do we need to read a new sector?
      if (file->file_data_address != sector) {
        // Flush un-written data to file
        if (file->file_data_dirty)
          fl_fflush(file);

        // If we plan to overwrite the whole sector, we don't need to read it first!
        if (copyCount != FAT_SECTOR_SIZE) {
          // NOTE: This does not have succeed; if last sector of file
          // reached, no valid data will be read in, but write will
          // allocate some more space for new data.

          // Get LBA of sector offset within file
          if (!_read_sectors(file, sector, file->file_data_sector, 1))
            memset(file->file_data_sector, 0x00, FAT_SECTOR_SIZE);
        }

        file->file_data_address = sector;
        file->file_data_dirty = 0;
      }

      // Copy from application buffer into sector buffer
      memcpy((uint8*) (file->file_data_sector + offset), (uint8*) (buffer + bytesWritten), copyCount);

      // Mark buffer as dirty
      file->file_data_dirty = 1;

      // Increase total read count
      bytesWritten += copyCount;

      // Increment file pointer
      file->bytenum += copyCount;

      // Move onto next sector and reset copy offset
      sector++;
      offset = 0;
    }
  }

  // Write increased extent of the file?
  if (file->bytenum > file->filelength) {
    // Increase file size to new point
    file->filelength = file->bytenum;

    // We are changing the file length and this
    // will need to be writen back at some point
    file->filelength_changed = 1;
  }

#if FATFS_INC_TIME_DATE_SUPPORT
    // If time & date support is enabled, always force directory entry to be
    // written in-order to update file modify / access time & date.
    file->filelength_changed = 1;
#endif

  FL_UNLOCK(&_fs);

  return (size * count);
}
#endif
//-----------------------------------------------------------------------------
// fl_fputs: Write a character string to the stream
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fl_fputs(const char *str, void *f) {
  int len = (int) strlen(str);
  int res = fl_fwrite(str, 1, len, f);

  if (res == len)
    return len;
  else
    return res;
}
#endif
//-----------------------------------------------------------------------------
// fl_remove: Remove a file from the filesystem
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fl_remove(const char *filename) {
  FL_FILE *file;
  int res = -1;

  FL_LOCK(&_fs);

  // Use read_file as this will check if the file is already open!
  file = fl_fopen((char*) filename, "r");
  if (file) {
    // Delete allocated space
    if (fatfs_free_cluster_chain(&_fs, file->startcluster)) {
      // Remove directory entries
      if (fatfs_mark_file_deleted(&_fs, file->parentcluster, (char*) file->shortfilename)) {
        // Close the file handle (this should not write anything to the file
        // as we have not changed the file since opening it!)
        fl_fclose(file);

        res = 0;
      }
    }
  }

  FL_UNLOCK(&_fs);

  return res;
}
#endif
//-----------------------------------------------------------------------------
// fl_createdirectory: Create a directory based on a path
//-----------------------------------------------------------------------------
#if FATFS_INC_WRITE_SUPPORT
int fl_createdirectory(const char *path) {
  int res;

  // If first call to library, initialise
  CHECK_FL_INIT();

  FL_LOCK(&_fs);
  res = _create_directory((char*) path);
  FL_UNLOCK(&_fs);

  return res;
}
#endif
//-----------------------------------------------------------------------------
// fl_listdirectory: List a directory based on a path
//-----------------------------------------------------------------------------
#if FATFS_DIR_LIST_SUPPORT
void fl_listdirectory(const char *path) {
  FL_DIR dirstat;

  // If first call to library, initialise
  CHECK_FL_INIT();

  FL_LOCK(&_fs);

  FAT_PRINTF(("\r\nDirectory %s\r\n", path));

  if (fl_opendir(path, &dirstat)) {
    struct fs_dir_ent dirent;

    while (fl_readdir(&dirstat, &dirent) == 0) {
#if FATFS_INC_TIME_DATE_SUPPORT
            int d,m,y,h,mn,s;
            fatfs_convert_from_fat_time(dirent.write_time, &h,&m,&s);
            fatfs_convert_from_fat_date(dirent.write_date, &d,&mn,&y);
            FAT_PRINTF(("%02d/%02d/%04d  %02d:%02d      ", d,mn,y,h,m));
#endif

      if (dirent.is_dir) {
        FAT_PRINTF(("%s <DIR>\r\n", dirent.filename));
      }
      else {
        FAT_PRINTF(("%s [%d bytes]\r\n", dirent.filename, dirent.size));
      }
    }

    fl_closedir(&dirstat);
  }

  FL_UNLOCK(&_fs);
}
#endif
//-----------------------------------------------------------------------------
// fl_opendir: Opens a directory for listing
//-----------------------------------------------------------------------------
#if FATFS_DIR_LIST_SUPPORT
FL_DIR* fl_opendir(const char *path, FL_DIR *dir) {
  int levels;
  int res = 1;
  uint32 cluster = FAT32_INVALID_CLUSTER;

  // If first call to library, initialise
  CHECK_FL_INIT();

  FL_LOCK(&_fs);

  levels = fatfs_total_path_levels((char*) path) + 1;

  // If path is in the root dir
  if (levels == 0)
    cluster = fatfs_get_root_cluster(&_fs);
  // Find parent directory start cluster
  else
    res = _open_directory((char*) path, &cluster);

  if (res)
    fatfs_list_directory_start(&_fs, dir, cluster);

  FL_UNLOCK(&_fs);

  return cluster != FAT32_INVALID_CLUSTER ? dir : 0;
}
#endif
//-----------------------------------------------------------------------------
// fl_readdir: Get next item in directory
//-----------------------------------------------------------------------------
#if FATFS_DIR_LIST_SUPPORT
int fl_readdir(FL_DIR *dirls, fl_dirent *entry) {
  int res = 0;

  // If first call to library, initialise
  CHECK_FL_INIT();

  FL_LOCK(&_fs);

  res = fatfs_list_directory_next(&_fs, dirls, entry);

  FL_UNLOCK(&_fs);

  return res ? 0 : -1;
}
#endif
//-----------------------------------------------------------------------------
// fl_closedir: Close directory after listing
//-----------------------------------------------------------------------------
#if FATFS_DIR_LIST_SUPPORT
int fl_closedir(FL_DIR *dir) {
  // Not used
  return 0;
}
#endif
//-----------------------------------------------------------------------------
// fl_is_dir: Is this a directory?
//-----------------------------------------------------------------------------
#if FATFS_DIR_LIST_SUPPORT
int fl_is_dir(const char *path) {
  int res = 0;
  FL_DIR dir;

  if (fl_opendir(path, &dir)) {
    res = 1;
    fl_closedir(&dir);
  }

  return res;
}
#endif
//-----------------------------------------------------------------------------
// fl_format: Format a partition with either FAT16 or FAT32 based on size
//-----------------------------------------------------------------------------
#if FATFS_INC_FORMAT_SUPPORT
int fl_format(uint32 volume_sectors, const char *name) {
  return fatfs_format(&_fs, volume_sectors, name);
}
#endif /*FATFS_INC_FORMAT_SUPPORT*/
//-----------------------------------------------------------------------------
// fl_get_fs:
//-----------------------------------------------------------------------------
#ifdef FATFS_INC_TEST_HOOKS
struct fatfs* fl_get_fs(void)
{
    return &_fs;
}
#endif