0,0 → 1,2233 |
|
/* |
* Copyright (c) 2006-2007 by Roland Riegel <feedback@roland-riegel.de> |
* |
* This file is free software; you can redistribute it and/or modify |
* it under the terms of either the GNU General Public License version 2 |
* or the GNU Lesser General Public License version 2.1, both as |
* published by the Free Software Foundation. |
*/ |
|
#include "partition.h" |
#include "fat16.h" |
#include "fat16_config.h" |
#include "sd-reader_config.h" |
|
#include <string.h> |
|
#if USE_DYNAMIC_MEMORY |
#include <stdlib.h> |
#endif |
|
/** |
* \addtogroup fat16 FAT16 support |
* |
* This module implements FAT16 read and write access. |
* |
* The following features are supported: |
* - File names up to 31 characters long. |
* - Unlimited depth of subdirectories. |
* - Short 8.3 and long filenames. |
* - Creating and deleting files. |
* - Reading and writing from and to files. |
* - File resizing. |
* - File sizes of up to 4 gigabytes. |
* |
* @{ |
*/ |
/** |
* \file |
* FAT16 implementation (license: GPLv2 or LGPLv2.1) |
* |
* \author Roland Riegel |
*/ |
|
/** |
* \addtogroup fat16_config FAT16 configuration |
* Preprocessor defines to configure the FAT16 implementation. |
*/ |
|
/** |
* \addtogroup fat16_fs FAT16 access |
* Basic functions for handling a FAT16 filesystem. |
*/ |
|
/** |
* \addtogroup fat16_file FAT16 file functions |
* Functions for managing files. |
*/ |
|
/** |
* \addtogroup fat16_dir FAT16 directory functions |
* Functions for managing directories. |
*/ |
|
/** |
* @} |
*/ |
|
#define FAT16_CLUSTER_FREE 0x0000 |
#define FAT16_CLUSTER_RESERVED_MIN 0xfff0 |
#define FAT16_CLUSTER_RESERVED_MAX 0xfff6 |
#define FAT16_CLUSTER_BAD 0xfff7 |
#define FAT16_CLUSTER_LAST_MIN 0xfff8 |
#define FAT16_CLUSTER_LAST_MAX 0xffff |
|
#define FAT16_DIRENTRY_DELETED 0xe5 |
#define FAT16_DIRENTRY_LFNLAST (1 << 6) |
#define FAT16_DIRENTRY_LFNSEQMASK ((1 << 6) - 1) |
|
/* Each entry within the directory table has a size of 32 bytes |
* and either contains a 8.3 DOS-style file name or a part of a |
* long file name, which may consist of several directory table |
* entries at once. |
* |
* multi-byte integer values are stored little-endian! |
* |
* 8.3 file name entry: |
* ==================== |
* offset length description |
* 0 8 name (space padded) |
* 8 3 extension (space padded) |
* 11 1 attributes (FAT16_ATTRIB_*) |
* |
* long file name (lfn) entry ordering for a single file name: |
* =========================================================== |
* LFN entry n |
* ... |
* LFN entry 2 |
* LFN entry 1 |
* 8.3 entry (see above) |
* |
* lfn entry: |
* ========== |
* offset length description |
* 0 1 ordinal field |
* 1 2 unicode character 1 |
* 3 3 unicode character 2 |
* 5 3 unicode character 3 |
* 7 3 unicode character 4 |
* 9 3 unicode character 5 |
* 11 1 attribute (always 0x0f) |
* 12 1 type (reserved, always 0) |
* 13 1 checksum |
* 14 2 unicode character 6 |
* 16 2 unicode character 7 |
* 18 2 unicode character 8 |
* 20 2 unicode character 9 |
* 22 2 unicode character 10 |
* 24 2 unicode character 11 |
* 26 2 cluster (unused, always 0) |
* 28 2 unicode character 12 |
* 30 2 unicode character 13 |
* |
* The ordinal field contains a descending number, from n to 1. |
* For the n'th lfn entry the ordinal field is or'ed with 0x40. |
* For deleted lfn entries, the ordinal field is set to 0xe5. |
*/ |
|
struct fat16_header_struct |
{ |
uint32_t size; |
|
uint32_t fat_offset; |
uint32_t fat_size; |
|
uint16_t sector_size; |
uint16_t cluster_size; |
|
uint32_t root_dir_offset; |
|
uint32_t cluster_zero_offset; |
}; |
|
struct fat16_fs_struct |
{ |
struct partition_struct* partition; |
struct fat16_header_struct header; |
}; |
|
struct fat16_file_struct |
{ |
struct fat16_fs_struct* fs; |
struct fat16_dir_entry_struct dir_entry; |
uint32_t pos; |
uint16_t pos_cluster; |
}; |
|
struct fat16_dir_struct |
{ |
struct fat16_fs_struct* fs; |
struct fat16_dir_entry_struct dir_entry; |
uint16_t entry_next; |
}; |
|
struct fat16_read_callback_arg |
{ |
uint16_t entry_cur; |
uint16_t entry_num; |
uint32_t entry_offset; |
uint8_t byte_count; |
}; |
|
struct fat16_usage_count_callback_arg |
{ |
uint16_t cluster_count; |
uint8_t buffer_size; |
}; |
|
#if !USE_DYNAMIC_MEMORY |
static struct fat16_fs_struct fat16_fs_handlers[FAT16_FS_COUNT]; |
static struct fat16_file_struct fat16_file_handlers[FAT16_FILE_COUNT]; |
static struct fat16_dir_struct fat16_dir_handlers[FAT16_DIR_COUNT]; |
#endif |
|
static uint8_t fat16_read_header(struct fat16_fs_struct* fs); |
static uint8_t fat16_read_root_dir_entry(const struct fat16_fs_struct* fs, uint16_t entry_num, struct fat16_dir_entry_struct* dir_entry); |
static uint8_t fat16_read_sub_dir_entry(const struct fat16_fs_struct* fs, uint16_t entry_num, const struct fat16_dir_entry_struct* parent, struct fat16_dir_entry_struct* dir_entry); |
static uint8_t fat16_dir_entry_seek_callback(uint8_t* buffer, uint32_t offset, void* p); |
static uint8_t fat16_dir_entry_read_callback(uint8_t* buffer, uint32_t offset, void* p); |
static uint8_t fat16_interpret_dir_entry(struct fat16_dir_entry_struct* dir_entry, const uint8_t* raw_entry); |
static uint16_t fat16_get_next_cluster(const struct fat16_fs_struct* fs, uint16_t cluster_num); |
static uint16_t fat16_append_clusters(const struct fat16_fs_struct* fs, uint16_t cluster_num, uint16_t count); |
static uint8_t fat16_free_clusters(const struct fat16_fs_struct* fs, uint16_t cluster_num); |
static uint8_t fat16_terminate_clusters(const struct fat16_fs_struct* fs, uint16_t cluster_num); |
static uint8_t fat16_clear_cluster(const struct fat16_fs_struct* fs, uint16_t cluster_num); |
static uint16_t fat16_clear_cluster_callback(uint8_t* buffer, uint32_t offset, void* p); |
static uint32_t fat16_find_offset_for_dir_entry(const struct fat16_fs_struct* fs, const struct fat16_dir_struct* parent, const struct fat16_dir_entry_struct* dir_entry); |
static uint8_t fat16_write_dir_entry(const struct fat16_fs_struct* fs, struct fat16_dir_entry_struct* dir_entry); |
|
static uint8_t fat16_get_fs_free_callback(uint8_t* buffer, uint32_t offset, void* p); |
|
static void fat16_set_file_modification_date(struct fat16_dir_entry_struct* dir_entry, uint16_t year, uint8_t month, uint8_t day); |
static void fat16_set_file_modification_time(struct fat16_dir_entry_struct* dir_entry, uint8_t hour, uint8_t min, uint8_t sec); |
|
/** |
* \ingroup fat16_fs |
* Opens a FAT16 filesystem. |
* |
* \param[in] partition Discriptor of partition on which the filesystem resides. |
* \returns 0 on error, a FAT16 filesystem descriptor on success. |
* \see fat16_open |
*/ |
struct fat16_fs_struct* fat16_open(struct partition_struct* partition) |
{ |
if(!partition || |
#if FAT16_WRITE_SUPPORT |
!partition->device_write || |
!partition->device_write_interval |
#else |
0 |
#endif |
) |
return 0; |
|
#if USE_DYNAMIC_MEMORY |
struct fat16_fs_struct* fs = malloc(sizeof(*fs)); |
if(!fs) |
return 0; |
#else |
struct fat16_fs_struct* fs = fat16_fs_handlers; |
uint8_t i; |
for(i = 0; i < FAT16_FS_COUNT; ++i) |
{ |
if(!fs->partition) |
break; |
|
++fs; |
} |
if(i >= FAT16_FS_COUNT) |
return 0; |
#endif |
|
memset(fs, 0, sizeof(*fs)); |
|
fs->partition = partition; |
if(!fat16_read_header(fs)) |
{ |
#if USE_DYNAMIC_MEMORY |
free(fs); |
#else |
fs->partition = 0; |
#endif |
return 0; |
} |
|
return fs; |
} |
|
/** |
* \ingroup fat16_fs |
* Closes a FAT16 filesystem. |
* |
* When this function returns, the given filesystem descriptor |
* will be invalid. |
* |
* \param[in] fs The filesystem to close. |
* \see fat16_open |
*/ |
void fat16_close(struct fat16_fs_struct* fs) |
{ |
if(!fs) |
return; |
|
#if USE_DYNAMIC_MEMORY |
free(fs); |
#else |
fs->partition = 0; |
#endif |
} |
|
/** |
* \ingroup fat16_fs |
* Reads and parses the header of a FAT16 filesystem. |
* |
* \param[inout] fs The filesystem for which to parse the header. |
* \returns 0 on failure, 1 on success. |
*/ |
uint8_t fat16_read_header(struct fat16_fs_struct* fs) |
{ |
if(!fs) |
return 0; |
|
struct partition_struct* partition = fs->partition; |
if(!partition) |
return 0; |
|
/* read fat parameters */ |
uint8_t buffer[25]; |
uint32_t partition_offset = partition->offset * 512; |
if(!partition->device_read(partition_offset + 0x0b, buffer, sizeof(buffer))) |
return 0; |
|
uint16_t bytes_per_sector = ((uint16_t) buffer[0x00]) | |
((uint16_t) buffer[0x01] << 8); |
uint8_t sectors_per_cluster = buffer[0x02]; |
uint16_t reserved_sectors = ((uint16_t) buffer[0x03]) | |
((uint16_t) buffer[0x04] << 8); |
uint8_t fat_copies = buffer[0x05]; |
uint16_t max_root_entries = ((uint16_t) buffer[0x06]) | |
((uint16_t) buffer[0x07] << 8); |
uint16_t sector_count_16 = ((uint16_t) buffer[0x08]) | |
((uint16_t) buffer[0x09] << 8); |
uint16_t sectors_per_fat = ((uint16_t) buffer[0x0b]) | |
((uint16_t) buffer[0x0c] << 8); |
uint32_t sector_count = ((uint32_t) buffer[0x15]) | |
((uint32_t) buffer[0x16] << 8) | |
((uint32_t) buffer[0x17] << 16) | |
((uint32_t) buffer[0x18] << 24); |
|
if(sectors_per_fat == 0) |
/* this is not a FAT16 */ |
return 0; |
|
if(sector_count == 0) |
{ |
if(sector_count_16 == 0) |
/* illegal volume size */ |
return 0; |
else |
sector_count = sector_count_16; |
} |
|
/* ensure we really have a FAT16 fs here */ |
uint32_t data_sector_count = sector_count |
- reserved_sectors |
- (uint32_t) sectors_per_fat * fat_copies |
- ((max_root_entries * 32 + bytes_per_sector - 1) / bytes_per_sector); |
uint32_t data_cluster_count = data_sector_count / sectors_per_cluster; |
if(data_cluster_count < 4085 || data_cluster_count >= 65525) |
/* this is not a FAT16 */ |
return 0; |
|
partition->type = PARTITION_TYPE_FAT16; |
|
/* fill header information */ |
struct fat16_header_struct* header = &fs->header; |
memset(header, 0, sizeof(*header)); |
|
header->size = sector_count * bytes_per_sector; |
|
header->fat_offset = /* jump to partition */ |
partition_offset + |
/* jump to fat */ |
(uint32_t) reserved_sectors * bytes_per_sector; |
header->fat_size = (data_cluster_count + 2) * 2; |
|
header->sector_size = bytes_per_sector; |
header->cluster_size = (uint32_t) bytes_per_sector * sectors_per_cluster; |
|
header->root_dir_offset = /* jump to fats */ |
header->fat_offset + |
/* jump to root directory entries */ |
(uint32_t) fat_copies * sectors_per_fat * bytes_per_sector; |
|
header->cluster_zero_offset = /* jump to root directory entries */ |
header->root_dir_offset + |
/* skip root directory entries */ |
(uint32_t) max_root_entries * 32; |
|
return 1; |
} |
|
/** |
* \ingroup fat16_fs |
* Reads a directory entry of the root directory. |
* |
* \param[in] fs Descriptor of file system to use. |
* \param[in] entry_num The index of the directory entry to read. |
* \param[out] dir_entry Directory entry descriptor which will get filled. |
* \returns 0 on failure, 1 on success |
* \see fat16_read_sub_dir_entry, fat16_read_dir_entry_by_path |
*/ |
uint8_t fat16_read_root_dir_entry(const struct fat16_fs_struct* fs, uint16_t entry_num, struct fat16_dir_entry_struct* dir_entry) |
{ |
if(!fs || !dir_entry) |
return 0; |
|
/* we read from the root directory entry */ |
const struct fat16_header_struct* header = &fs->header; |
device_read_interval_t device_read_interval = fs->partition->device_read_interval; |
uint8_t buffer[32]; |
|
/* seek to the n-th entry */ |
struct fat16_read_callback_arg arg; |
memset(&arg, 0, sizeof(arg)); |
arg.entry_num = entry_num; |
if(!device_read_interval(header->root_dir_offset, |
buffer, |
sizeof(buffer), |
header->cluster_zero_offset - header->root_dir_offset, |
fat16_dir_entry_seek_callback, |
&arg) || |
arg.entry_offset == 0 |
) |
return 0; |
|
/* read entry */ |
memset(dir_entry, 0, sizeof(*dir_entry)); |
if(!device_read_interval(arg.entry_offset, |
buffer, |
sizeof(buffer), |
arg.byte_count, |
fat16_dir_entry_read_callback, |
dir_entry)) |
return 0; |
|
return dir_entry->long_name[0] != '\0' ? 1 : 0; |
} |
|
/** |
* \ingroup fat16_fs |
* Reads a directory entry of a given parent directory. |
* |
* \param[in] fs Descriptor of file system to use. |
* \param[in] entry_num The index of the directory entry to read. |
* \param[in] parent Directory entry descriptor in which to read directory entry. |
* \param[out] dir_entry Directory entry descriptor which will get filled. |
* \returns 0 on failure, 1 on success |
* \see fat16_read_root_dir_entry, fat16_read_dir_entry_by_path |
*/ |
uint8_t fat16_read_sub_dir_entry(const struct fat16_fs_struct* fs, uint16_t entry_num, const struct fat16_dir_entry_struct* parent, struct fat16_dir_entry_struct* dir_entry) |
{ |
if(!fs || !parent || !dir_entry) |
return 0; |
|
/* we are in a parent directory and want to search within its directory entry table */ |
if(!(parent->attributes & FAT16_ATTRIB_DIR)) |
return 0; |
|
/* loop through all clusters of the directory */ |
uint8_t buffer[32]; |
uint32_t cluster_offset; |
uint16_t cluster_size = fs->header.cluster_size; |
uint16_t cluster_num = parent->cluster; |
struct fat16_read_callback_arg arg; |
|
while(1) |
{ |
/* calculate new cluster offset */ |
cluster_offset = fs->header.cluster_zero_offset + (uint32_t) (cluster_num - 2) * cluster_size; |
|
/* seek to the n-th entry */ |
memset(&arg, 0, sizeof(arg)); |
arg.entry_num = entry_num; |
if(!fs->partition->device_read_interval(cluster_offset, |
buffer, |
sizeof(buffer), |
cluster_size, |
fat16_dir_entry_seek_callback, |
&arg) |
) |
return 0; |
|
/* check if we found the entry */ |
if(arg.entry_offset) |
break; |
|
/* get number of next cluster */ |
if(!(cluster_num = fat16_get_next_cluster(fs, cluster_num))) |
return 0; /* directory entry not found */ |
} |
|
memset(dir_entry, 0, sizeof(*dir_entry)); |
|
/* read entry */ |
if(!fs->partition->device_read_interval(arg.entry_offset, |
buffer, |
sizeof(buffer), |
arg.byte_count, |
fat16_dir_entry_read_callback, |
dir_entry)) |
return 0; |
|
return dir_entry->long_name[0] != '\0' ? 1 : 0; |
} |
|
/** |
* \ingroup fat16_fs |
* Callback function for seeking through subdirectory entries. |
*/ |
uint8_t fat16_dir_entry_seek_callback(uint8_t* buffer, uint32_t offset, void* p) |
{ |
struct fat16_read_callback_arg* arg = p; |
|
/* skip deleted or empty entries */ |
if(buffer[0] == FAT16_DIRENTRY_DELETED || !buffer[0]) |
return 1; |
|
if(arg->entry_cur == arg->entry_num) |
{ |
arg->entry_offset = offset; |
arg->byte_count = buffer[11] == 0x0f ? |
((buffer[0] & FAT16_DIRENTRY_LFNSEQMASK) + 1) * 32 : |
32; |
return 0; |
} |
|
/* if we read a 8.3 entry, we reached a new directory entry */ |
if(buffer[11] != 0x0f) |
++arg->entry_cur; |
|
return 1; |
} |
|
/** |
* \ingroup fat16_fs |
* Callback function for reading a directory entry. |
*/ |
uint8_t fat16_dir_entry_read_callback(uint8_t* buffer, uint32_t offset, void* p) |
{ |
struct fat16_dir_entry_struct* dir_entry = p; |
|
/* there should not be any deleted or empty entries */ |
if(buffer[0] == FAT16_DIRENTRY_DELETED || !buffer[0]) |
return 0; |
|
if(!dir_entry->entry_offset) |
dir_entry->entry_offset = offset; |
|
switch(fat16_interpret_dir_entry(dir_entry, buffer)) |
{ |
case 0: /* failure */ |
return 0; |
case 1: /* buffer successfully parsed, continue */ |
return 1; |
case 2: /* directory entry complete, finish */ |
return 0; |
} |
|
return 0; |
} |
|
/** |
* \ingroup fat16_fs |
* Interprets a raw directory entry and puts the contained |
* information into the directory entry. |
* |
* For a single file there may exist multiple directory |
* entries. All except the last one are lfn entries, which |
* contain parts of the long filename. The last directory |
* entry is a traditional 8.3 style one. It contains all |
* other information like size, cluster, date and time. |
* |
* \param[in,out] dir_entry The directory entry to fill. |
* \param[in] raw_entry A pointer to 32 bytes of raw data. |
* \returns 0 on failure, 1 on success and 2 if the |
* directory entry is complete. |
*/ |
uint8_t fat16_interpret_dir_entry(struct fat16_dir_entry_struct* dir_entry, const uint8_t* raw_entry) |
{ |
if(!dir_entry || !raw_entry || !raw_entry[0]) |
return 0; |
|
char* long_name = dir_entry->long_name; |
if(raw_entry[11] == 0x0f) |
{ |
uint16_t char_offset = ((raw_entry[0] & 0x3f) - 1) * 13; |
|
if(char_offset + 12 < sizeof(dir_entry->long_name)) |
{ |
/* Lfn supports unicode, but we do not, for now. |
* So we assume pure ascii and read only every |
* second byte. |
*/ |
long_name[char_offset + 0] = raw_entry[1]; |
long_name[char_offset + 1] = raw_entry[3]; |
long_name[char_offset + 2] = raw_entry[5]; |
long_name[char_offset + 3] = raw_entry[7]; |
long_name[char_offset + 4] = raw_entry[9]; |
long_name[char_offset + 5] = raw_entry[14]; |
long_name[char_offset + 6] = raw_entry[16]; |
long_name[char_offset + 7] = raw_entry[18]; |
long_name[char_offset + 8] = raw_entry[20]; |
long_name[char_offset + 9] = raw_entry[22]; |
long_name[char_offset + 10] = raw_entry[24]; |
long_name[char_offset + 11] = raw_entry[28]; |
long_name[char_offset + 12] = raw_entry[30]; |
} |
|
return 1; |
} |
else |
{ |
/* if we do not have a long name, take the short one */ |
if(long_name[0] == '\0') |
{ |
uint8_t i; |
for(i = 0; i < 8; ++i) |
{ |
if(raw_entry[i] == ' ') |
break; |
long_name[i] = raw_entry[i]; |
} |
if(long_name[0] == 0x05) |
long_name[0] = (char) FAT16_DIRENTRY_DELETED; |
|
if(raw_entry[8] != ' ') |
{ |
long_name[i++] = '.'; |
|
uint8_t j = 8; |
for(; j < 11; ++j) |
{ |
if(raw_entry[j] != ' ') |
{ |
long_name[i++] = raw_entry[j]; |
} |
else |
{ |
break; |
} |
} |
} |
|
long_name[i] = '\0'; |
} |
|
/* extract properties of file and store them within the structure */ |
dir_entry->attributes = raw_entry[11]; |
dir_entry->cluster = ((uint16_t) raw_entry[26]) | |
((uint16_t) raw_entry[27] << 8); |
dir_entry->file_size = ((uint32_t) raw_entry[28]) | |
((uint32_t) raw_entry[29] << 8) | |
((uint32_t) raw_entry[30] << 16) | |
((uint32_t) raw_entry[31] << 24); |
|
#if FAT16_DATETIME_SUPPORT |
dir_entry->modification_time = ((uint16_t) raw_entry[22]) | |
((uint16_t) raw_entry[23] << 8); |
dir_entry->modification_date = ((uint16_t) raw_entry[24]) | |
((uint16_t) raw_entry[25] << 8); |
#endif |
|
return 2; |
} |
} |
|
/** |
* \ingroup fat16_file |
* Retrieves the directory entry of a path. |
* |
* The given path may both describe a file or a directory. |
* |
* \param[in] fs The FAT16 filesystem on which to search. |
* \param[in] path The path of which to read the directory entry. |
* \param[out] dir_entry The directory entry to fill. |
* \returns 0 on failure, 1 on success. |
* \see fat16_read_dir |
*/ |
uint8_t fat16_get_dir_entry_of_path(struct fat16_fs_struct* fs, const char* path, struct fat16_dir_entry_struct* dir_entry) |
{ |
if(!fs || !path || path[0] == '\0' || !dir_entry) |
return 0; |
|
if(path[0] == '/') |
++path; |
|
/* begin with the root directory */ |
memset(dir_entry, 0, sizeof(*dir_entry)); |
dir_entry->attributes = FAT16_ATTRIB_DIR; |
|
if(path[0] == '\0') |
return 1; |
|
while(1) |
{ |
struct fat16_dir_struct* dd = fat16_open_dir(fs, dir_entry); |
if(!dd) |
break; |
|
/* extract the next hierarchy we will search for */ |
const char* sep_pos = strchr(path, '/'); |
if(!sep_pos) |
sep_pos = path + strlen(path); |
uint8_t length_to_sep = sep_pos - path; |
|
/* read directory entries */ |
while(fat16_read_dir(dd, dir_entry)) |
{ |
/* check if we have found the next hierarchy */ |
if((strlen(dir_entry->long_name) != length_to_sep || |
strncmp(path, dir_entry->long_name, length_to_sep) != 0)) |
continue; |
|
fat16_close_dir(dd); |
dd = 0; |
|
if(path[length_to_sep] == '\0') |
/* we iterated through the whole path and have found the file */ |
return 1; |
|
if(dir_entry->attributes & FAT16_ATTRIB_DIR) |
{ |
/* we found a parent directory of the file we are searching for */ |
path = sep_pos + 1; |
break; |
} |
|
/* a parent of the file exists, but not the file itself */ |
return 0; |
} |
|
fat16_close_dir(dd); |
} |
|
return 0; |
} |
|
/** |
* \ingroup fat16_fs |
* Retrieves the next following cluster of a given cluster. |
* |
* Using the filesystem file allocation table, this function returns |
* the number of the cluster containing the data directly following |
* the data within the cluster with the given number. |
* |
* \param[in] fs The filesystem for which to determine the next cluster. |
* \param[in] cluster_num The number of the cluster for which to determine its successor. |
* \returns The wanted cluster number, or 0 on error. |
*/ |
uint16_t fat16_get_next_cluster(const struct fat16_fs_struct* fs, uint16_t cluster_num) |
{ |
if(!fs || cluster_num < 2) |
return 0; |
|
/* read appropriate fat entry */ |
uint8_t fat_entry[2]; |
if(!fs->partition->device_read(fs->header.fat_offset + 2 * cluster_num, fat_entry, 2)) |
return 0; |
|
/* determine next cluster from fat */ |
cluster_num = ((uint16_t) fat_entry[0]) | |
((uint16_t) fat_entry[1] << 8); |
|
if(cluster_num == FAT16_CLUSTER_FREE || |
cluster_num == FAT16_CLUSTER_BAD || |
(cluster_num >= FAT16_CLUSTER_RESERVED_MIN && cluster_num <= FAT16_CLUSTER_RESERVED_MAX) || |
(cluster_num >= FAT16_CLUSTER_LAST_MIN && cluster_num <= FAT16_CLUSTER_LAST_MAX)) |
return 0; |
|
return cluster_num; |
} |
|
/** |
* \ingroup fat16_fs |
* Appends a new cluster chain to an existing one. |
* |
* Set cluster_num to zero to create a completely new one. |
* |
* \param[in] fs The file system on which to operate. |
* \param[in] cluster_num The cluster to which to append the new chain. |
* \param[in] count The number of clusters to allocate. |
* \returns 0 on failure, the number of the first new cluster on success. |
*/ |
uint16_t fat16_append_clusters(const struct fat16_fs_struct* fs, uint16_t cluster_num, uint16_t count) |
{ |
#if FAT16_WRITE_SUPPORT |
if(!fs) |
return 0; |
|
device_read_t device_read = fs->partition->device_read; |
device_write_t device_write = fs->partition->device_write; |
uint32_t fat_offset = fs->header.fat_offset; |
uint16_t cluster_max = fs->header.fat_size / 2; |
uint16_t cluster_next = 0; |
uint16_t count_left = count; |
uint8_t buffer[2]; |
|
for(uint16_t cluster_new = 0; cluster_new < cluster_max; ++cluster_new) |
{ |
if(!device_read(fat_offset + 2 * cluster_new, buffer, sizeof(buffer))) |
return 0; |
|
/* check if this is a free cluster */ |
if(buffer[0] == (FAT16_CLUSTER_FREE & 0xff) && |
buffer[1] == ((FAT16_CLUSTER_FREE >> 8) & 0xff)) |
{ |
/* allocate cluster */ |
if(count_left == count) |
{ |
buffer[0] = FAT16_CLUSTER_LAST_MAX & 0xff; |
buffer[1] = (FAT16_CLUSTER_LAST_MAX >> 8) & 0xff; |
} |
else |
{ |
buffer[0] = cluster_next & 0xff; |
buffer[1] = (cluster_next >> 8) & 0xff; |
} |
|
if(!device_write(fat_offset + 2 * cluster_new, buffer, sizeof(buffer))) |
break; |
|
cluster_next = cluster_new; |
if(--count_left == 0) |
break; |
} |
} |
|
do |
{ |
if(count_left > 0) |
break; |
|
/* We allocated a new cluster chain. Now join |
* it with the existing one. |
*/ |
if(cluster_num >= 2) |
{ |
buffer[0] = cluster_next & 0xff; |
buffer[1] = (cluster_next >> 8) & 0xff; |
if(!device_write(fat_offset + 2 * cluster_num, buffer, sizeof(buffer))) |
break; |
} |
|
return cluster_next; |
|
} while(0); |
|
/* No space left on device or writing error. |
* Free up all clusters already allocated. |
*/ |
fat16_free_clusters(fs, cluster_next); |
|
return 0; |
#else |
return 0; |
#endif |
} |
|
/** |
* \ingroup fat16_fs |
* Frees a cluster chain, or a part thereof. |
* |
* Marks the specified cluster and all clusters which are sequentially |
* referenced by it as free. They may then be used again for future |
* file allocations. |
* |
* \note If this function is used for freeing just a part of a cluster |
* chain, the new end of the chain is not correctly terminated |
* within the FAT. Use fat16_terminate_clusters() instead. |
* |
* \param[in] fs The filesystem on which to operate. |
* \param[in] cluster_num The starting cluster of the chain which to free. |
* \returns 0 on failure, 1 on success. |
* \see fat16_terminate_clusters |
*/ |
uint8_t fat16_free_clusters(const struct fat16_fs_struct* fs, uint16_t cluster_num) |
{ |
#if FAT16_WRITE_SUPPORT |
if(!fs || cluster_num < 2) |
return 0; |
|
uint32_t fat_offset = fs->header.fat_offset; |
uint8_t buffer[2]; |
while(cluster_num) |
{ |
if(!fs->partition->device_read(fat_offset + 2 * cluster_num, buffer, 2)) |
return 0; |
|
/* get next cluster of current cluster before freeing current cluster */ |
uint16_t cluster_num_next = ((uint16_t) buffer[0]) | |
((uint16_t) buffer[1] << 8); |
|
if(cluster_num_next == FAT16_CLUSTER_FREE) |
return 1; |
if(cluster_num_next == FAT16_CLUSTER_BAD || |
(cluster_num_next >= FAT16_CLUSTER_RESERVED_MIN && |
cluster_num_next <= FAT16_CLUSTER_RESERVED_MAX |
) |
) |
return 0; |
if(cluster_num_next >= FAT16_CLUSTER_LAST_MIN && cluster_num_next <= FAT16_CLUSTER_LAST_MAX) |
cluster_num_next = 0; |
|
/* free cluster */ |
buffer[0] = FAT16_CLUSTER_FREE & 0xff; |
buffer[1] = (FAT16_CLUSTER_FREE >> 8) & 0xff; |
fs->partition->device_write(fat_offset + 2 * cluster_num, buffer, 2); |
|
/* We continue in any case here, even if freeing the cluster failed. |
* The cluster is lost, but maybe we can still free up some later ones. |
*/ |
|
cluster_num = cluster_num_next; |
} |
|
return 1; |
#else |
return 0; |
#endif |
} |
|
/** |
* \ingroup fat16_fs |
* Frees a part of a cluster chain and correctly terminates the rest. |
* |
* Marks the specified cluster as the new end of a cluster chain and |
* frees all following clusters. |
* |
* \param[in] fs The filesystem on which to operate. |
* \param[in] cluster_num The new end of the cluster chain. |
* \returns 0 on failure, 1 on success. |
* \see fat16_free_clusters |
*/ |
uint8_t fat16_terminate_clusters(const struct fat16_fs_struct* fs, uint16_t cluster_num) |
{ |
#if FAT16_WRITE_SUPPORT |
if(!fs || cluster_num < 2) |
return 0; |
|
/* fetch next cluster before overwriting the cluster entry */ |
uint16_t cluster_num_next = fat16_get_next_cluster(fs, cluster_num); |
|
/* mark cluster as the last one */ |
uint8_t buffer[2]; |
buffer[0] = FAT16_CLUSTER_LAST_MAX & 0xff; |
buffer[1] = (FAT16_CLUSTER_LAST_MAX >> 8) & 0xff; |
if(!fs->partition->device_write(fs->header.fat_offset + 2 * cluster_num, buffer, 2)) |
return 0; |
|
/* free remaining clusters */ |
if(cluster_num_next) |
return fat16_free_clusters(fs, cluster_num_next); |
else |
return 1; |
#else |
return 0; |
#endif |
} |
|
/** |
* \ingroup fat16_fs |
* Clears a single cluster. |
* |
* The complete cluster is filled with zeros. |
* |
* \param[in] fs The filesystem on which to operate. |
* \param[in] cluster_num The cluster to clear. |
* \returns 0 on failure, 1 on success. |
*/ |
uint8_t fat16_clear_cluster(const struct fat16_fs_struct* fs, uint16_t cluster_num) |
{ |
#if FAT16_WRITE_SUPPORT |
if(cluster_num < 2) |
return 0; |
|
uint32_t cluster_offset = fs->header.cluster_zero_offset + |
(uint32_t) (cluster_num - 2) * fs->header.cluster_size; |
uint8_t zero[16]; |
return fs->partition->device_write_interval(cluster_offset, |
zero, |
fs->header.cluster_size, |
fat16_clear_cluster_callback, |
0 |
); |
#else |
return 0; |
#endif |
} |
|
/** |
* \ingroup fat16_fs |
* Callback function for clearing a cluster. |
*/ |
uint16_t fat16_clear_cluster_callback(uint8_t* buffer, uint32_t offset, void* p) |
{ |
#if FAT16_WRITE_SUPPORT |
memset(buffer, 0, 16); |
return 16; |
#else |
return 0; |
#endif |
} |
|
/** |
* \ingroup fat16_file |
* Opens a file on a FAT16 filesystem. |
* |
* \param[in] fs The filesystem on which the file to open lies. |
* \param[in] dir_entry The directory entry of the file to open. |
* \returns The file handle, or 0 on failure. |
* \see fat16_close_file |
*/ |
struct fat16_file_struct* fat16_open_file(struct fat16_fs_struct* fs, const struct fat16_dir_entry_struct* dir_entry) |
{ |
if(!fs || !dir_entry || (dir_entry->attributes & FAT16_ATTRIB_DIR)) |
return 0; |
|
#if USE_DYNAMIC_MEMORY |
struct fat16_file_struct* fd = malloc(sizeof(*fd)); |
if(!fd) |
return 0; |
#else |
struct fat16_file_struct* fd = fat16_file_handlers; |
uint8_t i; |
for(i = 0; i < FAT16_FILE_COUNT; ++i) |
{ |
if(!fd->fs) |
break; |
|
++fd; |
} |
if(i >= FAT16_FILE_COUNT) |
return 0; |
#endif |
|
memcpy(&fd->dir_entry, dir_entry, sizeof(*dir_entry)); |
fd->fs = fs; |
fd->pos = 0; |
fd->pos_cluster = dir_entry->cluster; |
|
return fd; |
} |
|
/** |
* \ingroup fat16_file |
* Closes a file. |
* |
* \param[in] fd The file handle of the file to close. |
* \see fat16_open_file |
*/ |
void fat16_close_file(struct fat16_file_struct* fd) |
{ |
if(fd) |
#if USE_DYNAMIC_MEMORY |
free(fd); |
#else |
fd->fs = 0; |
#endif |
} |
|
/** |
* \ingroup fat16_file |
* Reads data from a file. |
* |
* The data requested is read from the current file location. |
* |
* \param[in] fd The file handle of the file from which to read. |
* \param[out] buffer The buffer into which to write. |
* \param[in] buffer_len The amount of data to read. |
* \returns The number of bytes read, 0 on end of file, or -1 on failure. |
* \see fat16_write_file |
*/ |
int16_t fat16_read_file(struct fat16_file_struct* fd, uint8_t* buffer, uint16_t buffer_len) |
{ |
/* check arguments */ |
if(!fd || !buffer || buffer_len < 1) |
return -1; |
|
/* determine number of bytes to read */ |
if(fd->pos + buffer_len > fd->dir_entry.file_size) |
buffer_len = fd->dir_entry.file_size - fd->pos; |
if(buffer_len == 0) |
return 0; |
|
uint16_t cluster_size = fd->fs->header.cluster_size; |
uint16_t cluster_num = fd->pos_cluster; |
uint16_t buffer_left = buffer_len; |
uint16_t first_cluster_offset = fd->pos % cluster_size; |
|
/* find cluster in which to start reading */ |
if(!cluster_num) |
{ |
cluster_num = fd->dir_entry.cluster; |
|
if(!cluster_num) |
{ |
if(!fd->pos) |
return 0; |
else |
return -1; |
} |
|
if(fd->pos) |
{ |
uint32_t pos = fd->pos; |
while(pos >= cluster_size) |
{ |
pos -= cluster_size; |
cluster_num = fat16_get_next_cluster(fd->fs, cluster_num); |
if(!cluster_num) |
return -1; |
} |
} |
} |
|
/* read data */ |
do |
{ |
/* calculate data size to copy from cluster */ |
uint32_t cluster_offset = fd->fs->header.cluster_zero_offset + |
(uint32_t) (cluster_num - 2) * cluster_size + first_cluster_offset; |
uint16_t copy_length = cluster_size - first_cluster_offset; |
if(copy_length > buffer_left) |
copy_length = buffer_left; |
|
/* read data */ |
if(!fd->fs->partition->device_read(cluster_offset, buffer, copy_length)) |
return buffer_len - buffer_left; |
|
/* calculate new file position */ |
buffer += copy_length; |
buffer_left -= copy_length; |
fd->pos += copy_length; |
|
if(first_cluster_offset + copy_length >= cluster_size) |
{ |
/* we are on a cluster boundary, so get the next cluster */ |
if((cluster_num = fat16_get_next_cluster(fd->fs, cluster_num))) |
{ |
first_cluster_offset = 0; |
} |
else |
{ |
fd->pos_cluster = 0; |
return buffer_len - buffer_left; |
} |
} |
|
fd->pos_cluster = cluster_num; |
|
} while(buffer_left > 0); /* check if we are done */ |
|
return buffer_len; |
} |
|
/** |
* \ingroup fat16_file |
* Writes data to a file. |
* |
* The data is written to the current file location. |
* |
* \param[in] fd The file handle of the file to which to write. |
* \param[in] buffer The buffer from which to read the data to be written. |
* \param[in] buffer_len The amount of data to write. |
* \returns The number of bytes written, 0 on disk full, or -1 on failure. |
* \see fat16_read_file |
*/ |
int16_t fat16_write_file(struct fat16_file_struct* fd, const uint8_t* buffer, uint16_t buffer_len) |
{ |
#if FAT16_WRITE_SUPPORT |
/* check arguments */ |
if(!fd || !buffer || buffer_len < 1) |
return -1; |
if(fd->pos > fd->dir_entry.file_size) |
return -1; |
|
uint16_t cluster_size = fd->fs->header.cluster_size; |
uint16_t cluster_num = fd->pos_cluster; |
uint16_t buffer_left = buffer_len; |
uint16_t first_cluster_offset = fd->pos % cluster_size; |
|
/* find cluster in which to start writing */ |
if(!cluster_num) |
{ |
cluster_num = fd->dir_entry.cluster; |
|
if(!cluster_num) |
{ |
if(!fd->pos) |
{ |
/* empty file */ |
fd->dir_entry.cluster = cluster_num = fat16_append_clusters(fd->fs, 0, 1); |
if(!cluster_num) |
return -1; |
} |
else |
{ |
return -1; |
} |
} |
|
if(fd->pos) |
{ |
uint32_t pos = fd->pos; |
uint16_t cluster_num_next; |
while(pos >= cluster_size) |
{ |
pos -= cluster_size; |
cluster_num_next = fat16_get_next_cluster(fd->fs, cluster_num); |
if(!cluster_num_next && pos == 0) |
/* the file exactly ends on a cluster boundary, and we append to it */ |
cluster_num_next = fat16_append_clusters(fd->fs, cluster_num, 1); |
if(!cluster_num_next) |
return -1; |
|
cluster_num = cluster_num_next; |
} |
} |
} |
|
/* write data */ |
do |
{ |
/* calculate data size to write to cluster */ |
uint32_t cluster_offset = fd->fs->header.cluster_zero_offset + |
(uint32_t) (cluster_num - 2) * cluster_size + first_cluster_offset; |
uint16_t write_length = cluster_size - first_cluster_offset; |
if(write_length > buffer_left) |
write_length = buffer_left; |
|
/* write data which fits into the current cluster */ |
if(!fd->fs->partition->device_write(cluster_offset, buffer, write_length)) |
break; |
|
/* calculate new file position */ |
buffer += write_length; |
buffer_left -= write_length; |
fd->pos += write_length; |
|
if(first_cluster_offset + write_length >= cluster_size) |
{ |
/* we are on a cluster boundary, so get the next cluster */ |
uint16_t cluster_num_next = fat16_get_next_cluster(fd->fs, cluster_num); |
if(!cluster_num_next && buffer_left > 0) |
/* we reached the last cluster, append a new one */ |
cluster_num_next = fat16_append_clusters(fd->fs, cluster_num, 1); |
if(!cluster_num_next) |
{ |
fd->pos_cluster = 0; |
break; |
} |
|
cluster_num = cluster_num_next; |
first_cluster_offset = 0; |
} |
|
fd->pos_cluster = cluster_num; |
|
} while(buffer_left > 0); /* check if we are done */ |
|
/* update directory entry */ |
if(fd->pos > fd->dir_entry.file_size) |
{ |
uint32_t size_old = fd->dir_entry.file_size; |
|
/* update file size */ |
fd->dir_entry.file_size = fd->pos; |
/* write directory entry */ |
if(!fat16_write_dir_entry(fd->fs, &fd->dir_entry)) |
{ |
/* We do not return an error here since we actually wrote |
* some data to disk. So we calculate the amount of data |
* we wrote to disk and which lies within the old file size. |
*/ |
buffer_left = fd->pos - size_old; |
fd->pos = size_old; |
} |
} |
|
return buffer_len - buffer_left; |
|
#else |
return -1; |
#endif |
} |
|
/** |
* \ingroup fat16_file |
* Repositions the read/write file offset. |
* |
* Changes the file offset where the next call to fat16_read_file() |
* or fat16_write_file() starts reading/writing. |
* |
* If the new offset is beyond the end of the file, fat16_resize_file() |
* is implicitly called, i.e. the file is expanded. |
* |
* The new offset can be given in different ways determined by |
* the \c whence parameter: |
* - \b FAT16_SEEK_SET: \c *offset is relative to the beginning of the file. |
* - \b FAT16_SEEK_CUR: \c *offset is relative to the current file position. |
* - \b FAT16_SEEK_END: \c *offset is relative to the end of the file. |
* |
* The resulting absolute offset is written to the location the \c offset |
* parameter points to. |
* |
* \param[in] fd The file decriptor of the file on which to seek. |
* \param[in,out] offset A pointer to the new offset, as affected by the \c whence |
* parameter. The function writes the new absolute offset |
* to this location before it returns. |
* \param[in] whence Affects the way \c offset is interpreted, see above. |
* \returns 0 on failure, 1 on success. |
*/ |
uint8_t fat16_seek_file(struct fat16_file_struct* fd, int32_t* offset, uint8_t whence) |
{ |
if(!fd || !offset) |
return 0; |
|
uint32_t new_pos = fd->pos; |
switch(whence) |
{ |
case FAT16_SEEK_SET: |
new_pos = *offset; |
break; |
case FAT16_SEEK_CUR: |
new_pos += *offset; |
break; |
case FAT16_SEEK_END: |
new_pos = fd->dir_entry.file_size + *offset; |
break; |
default: |
return 0; |
} |
|
if(new_pos > fd->dir_entry.file_size && !fat16_resize_file(fd, new_pos)) |
return 0; |
|
fd->pos = new_pos; |
fd->pos_cluster = 0; |
|
*offset = new_pos; |
return 1; |
} |
|
/** |
* \ingroup fat16_file |
* Resizes a file to have a specific size. |
* |
* Enlarges or shrinks the file pointed to by the file descriptor to have |
* exactly the specified size. |
* |
* If the file is truncated, all bytes having an equal or larger offset |
* than the given size are lost. If the file is expanded, the additional |
* bytes are allocated. |
* |
* \note Please be aware that this function just allocates or deallocates disk |
* space, it does not explicitely clear it. To avoid data leakage, this |
* must be done manually. |
* |
* \param[in] fd The file decriptor of the file which to resize. |
* \param[in] size The new size of the file. |
* \returns 0 on failure, 1 on success. |
*/ |
uint8_t fat16_resize_file(struct fat16_file_struct* fd, uint32_t size) |
{ |
#if FAT16_WRITE_SUPPORT |
if(!fd) |
return 0; |
|
uint16_t cluster_num = fd->dir_entry.cluster; |
uint16_t cluster_size = fd->fs->header.cluster_size; |
uint32_t size_new = size; |
|
do |
{ |
if(cluster_num == 0 && size_new == 0) |
/* the file stays empty */ |
break; |
|
/* seek to the next cluster as long as we need the space */ |
while(size_new > cluster_size) |
{ |
/* get next cluster of file */ |
uint16_t cluster_num_next = fat16_get_next_cluster(fd->fs, cluster_num); |
if(cluster_num_next) |
{ |
cluster_num = cluster_num_next; |
size_new -= cluster_size; |
} |
else |
{ |
break; |
} |
} |
|
if(size_new > cluster_size || cluster_num == 0) |
{ |
/* Allocate new cluster chain and append |
* it to the existing one, if available. |
*/ |
uint16_t cluster_count = size_new / cluster_size; |
if((uint32_t) cluster_count * cluster_size < size_new) |
++cluster_count; |
uint16_t cluster_new_chain = fat16_append_clusters(fd->fs, cluster_num, cluster_count); |
if(!cluster_new_chain) |
return 0; |
|
if(!cluster_num) |
{ |
cluster_num = cluster_new_chain; |
fd->dir_entry.cluster = cluster_num; |
} |
} |
|
/* write new directory entry */ |
fd->dir_entry.file_size = size; |
if(size == 0) |
fd->dir_entry.cluster = 0; |
if(!fat16_write_dir_entry(fd->fs, &fd->dir_entry)) |
return 0; |
|
if(size == 0) |
{ |
/* free all clusters of file */ |
fat16_free_clusters(fd->fs, cluster_num); |
} |
else if(size_new <= cluster_size) |
{ |
/* free all clusters no longer needed */ |
fat16_terminate_clusters(fd->fs, cluster_num); |
} |
|
} while(0); |
|
/* correct file position */ |
if(size < fd->pos) |
{ |
fd->pos = size; |
fd->pos_cluster = 0; |
} |
|
return 1; |
#else |
return 0; |
#endif |
} |
|
/** |
* \ingroup fat16_dir |
* Opens a directory. |
* |
* \param[in] fs The filesystem on which the directory to open resides. |
* \param[in] dir_entry The directory entry which stands for the directory to open. |
* \returns An opaque directory descriptor on success, 0 on failure. |
* \see fat16_close_dir |
*/ |
struct fat16_dir_struct* fat16_open_dir(struct fat16_fs_struct* fs, const struct fat16_dir_entry_struct* dir_entry) |
{ |
if(!fs || !dir_entry || !(dir_entry->attributes & FAT16_ATTRIB_DIR)) |
return 0; |
|
#if USE_DYNAMIC_MEMORY |
struct fat16_dir_struct* dd = malloc(sizeof(*dd)); |
if(!dd) |
return 0; |
#else |
struct fat16_dir_struct* dd = fat16_dir_handlers; |
uint8_t i; |
for(i = 0; i < FAT16_DIR_COUNT; ++i) |
{ |
if(!dd->fs) |
break; |
|
++dd; |
} |
if(i >= FAT16_DIR_COUNT) |
return 0; |
#endif |
|
memcpy(&dd->dir_entry, dir_entry, sizeof(*dir_entry)); |
dd->fs = fs; |
dd->entry_next = 0; |
|
return dd; |
} |
|
/** |
* \ingroup fat16_dir |
* Closes a directory descriptor. |
* |
* This function destroys a directory descriptor which was |
* previously obtained by calling fat16_open_dir(). When this |
* function returns, the given descriptor will be invalid. |
* |
* \param[in] dd The directory descriptor to close. |
* \see fat16_open_dir |
*/ |
void fat16_close_dir(struct fat16_dir_struct* dd) |
{ |
if(dd) |
#if USE_DYNAMIC_MEMORY |
free(dd); |
#else |
dd->fs = 0; |
#endif |
} |
|
/** |
* \ingroup fat16_dir |
* Reads the next directory entry contained within a parent directory. |
* |
* \param[in] dd The descriptor of the parent directory from which to read the entry. |
* \param[out] dir_entry Pointer to a buffer into which to write the directory entry information. |
* \returns 0 on failure, 1 on success. |
* \see fat16_reset_dir |
*/ |
uint8_t fat16_read_dir(struct fat16_dir_struct* dd, struct fat16_dir_entry_struct* dir_entry) |
{ |
if(!dd || !dir_entry) |
return 0; |
|
if(dd->dir_entry.cluster == 0) |
{ |
/* read entry from root directory */ |
if(fat16_read_root_dir_entry(dd->fs, dd->entry_next, dir_entry)) |
{ |
++dd->entry_next; |
return 1; |
} |
} |
else |
{ |
/* read entry from a subdirectory */ |
if(fat16_read_sub_dir_entry(dd->fs, dd->entry_next, &dd->dir_entry, dir_entry)) |
{ |
++dd->entry_next; |
return 1; |
} |
} |
|
/* restart reading */ |
dd->entry_next = 0; |
|
return 0; |
} |
|
/** |
* \ingroup fat16_dir |
* Resets a directory handle. |
* |
* Resets the directory handle such that reading restarts |
* with the first directory entry. |
* |
* \param[in] dd The directory handle to reset. |
* \returns 0 on failure, 1 on success. |
* \see fat16_read_dir |
*/ |
uint8_t fat16_reset_dir(struct fat16_dir_struct* dd) |
{ |
if(!dd) |
return 0; |
|
dd->entry_next = 0; |
return 1; |
} |
|
/** |
* \ingroup fat16_fs |
* Searches for space where to store a directory entry. |
* |
* \param[in] fs The filesystem on which to operate. |
* \param[in] dir_entry The directory entry for which to search space. |
* \returns 0 on failure, a device offset on success. |
*/ |
uint32_t fat16_find_offset_for_dir_entry(const struct fat16_fs_struct* fs, const struct fat16_dir_struct* parent, const struct fat16_dir_entry_struct* dir_entry) |
{ |
#if FAT16_WRITE_SUPPORT |
if(!fs || !dir_entry) |
return 0; |
|
/* search for a place where to write the directory entry to disk */ |
uint8_t free_dir_entries_needed = (strlen(dir_entry->long_name) + 12) / 13 + 1; |
uint8_t free_dir_entries_found = 0; |
uint16_t cluster_num = parent->dir_entry.cluster; |
uint32_t dir_entry_offset = 0; |
uint32_t offset = 0; |
uint32_t offset_to = 0; |
|
if(cluster_num == 0) |
{ |
/* we read/write from the root directory entry */ |
offset = fs->header.root_dir_offset; |
offset_to = fs->header.cluster_zero_offset; |
dir_entry_offset = offset; |
} |
|
while(1) |
{ |
if(offset == offset_to) |
{ |
if(cluster_num == 0) |
/* We iterated through the whole root directory entry |
* and could not find enough space for the directory entry. |
*/ |
return 0; |
|
if(offset) |
{ |
/* We reached a cluster boundary and have to |
* switch to the next cluster. |
*/ |
|
uint16_t cluster_next = fat16_get_next_cluster(fs, cluster_num); |
if(!cluster_next) |
{ |
cluster_next = fat16_append_clusters(fs, cluster_num, 1); |
if(!cluster_next) |
return 0; |
|
/* we appended a new cluster and know it is free */ |
dir_entry_offset = fs->header.cluster_zero_offset + |
(uint32_t) (cluster_next - 2) * fs->header.cluster_size; |
|
/* clear cluster to avoid garbage directory entries */ |
fat16_clear_cluster(fs, cluster_next); |
|
break; |
} |
cluster_num = cluster_next; |
} |
|
offset = fs->header.cluster_zero_offset + |
(uint32_t) (cluster_num - 2) * fs->header.cluster_size; |
offset_to = offset + fs->header.cluster_size; |
dir_entry_offset = offset; |
free_dir_entries_found = 0; |
} |
|
/* read next lfn or 8.3 entry */ |
uint8_t first_char; |
if(!fs->partition->device_read(offset, &first_char, sizeof(first_char))) |
return 0; |
|
/* check if we found a free directory entry */ |
if(first_char == FAT16_DIRENTRY_DELETED || !first_char) |
{ |
/* check if we have the needed number of available entries */ |
++free_dir_entries_found; |
if(free_dir_entries_found >= free_dir_entries_needed) |
break; |
|
offset += 32; |
} |
else |
{ |
offset += 32; |
dir_entry_offset = offset; |
free_dir_entries_found = 0; |
} |
} |
|
return dir_entry_offset; |
|
#else |
return 0; |
#endif |
} |
|
/** |
* \ingroup fat16_fs |
* Writes a directory entry to disk. |
* |
* \note The file name is not checked for invalid characters. |
* |
* \note The generation of the short 8.3 file name is quite |
* simple. The first eight characters are used for the filename. |
* The extension, if any, is made up of the first three characters |
* following the last dot within the long filename. If the |
* filename (without the extension) is longer than eight characters, |
* the lower byte of the cluster number replaces the last two |
* characters to avoid name clashes. In any other case, it is your |
* responsibility to avoid name clashes. |
* |
* \param[in] fs The filesystem on which to operate. |
* \param[in] dir_entry The directory entry to write. |
* \returns 0 on failure, 1 on success. |
*/ |
uint8_t fat16_write_dir_entry(const struct fat16_fs_struct* fs, struct fat16_dir_entry_struct* dir_entry) |
{ |
#if FAT16_WRITE_SUPPORT |
if(!fs || !dir_entry) |
return 0; |
|
#if FAT16_DATETIME_SUPPORT |
{ |
uint16_t year; |
uint8_t month; |
uint8_t day; |
uint8_t hour; |
uint8_t min; |
uint8_t sec; |
|
fat16_get_datetime(&year, &month, &day, &hour, &min, &sec); |
fat16_set_file_modification_date(dir_entry, year, month, day); |
fat16_set_file_modification_time(dir_entry, hour, min, sec); |
} |
#endif |
|
device_write_t device_write = fs->partition->device_write; |
uint32_t offset = dir_entry->entry_offset; |
const char* name = dir_entry->long_name; |
uint8_t name_len = strlen(name); |
uint8_t lfn_entry_count = (name_len + 12) / 13; |
uint8_t buffer[32]; |
|
/* write 8.3 entry */ |
|
/* generate 8.3 file name */ |
memset(&buffer[0], ' ', 11); |
char* name_ext = strrchr(name, '.'); |
if(name_ext && *++name_ext) |
{ |
uint8_t name_ext_len = strlen(name_ext); |
name_len -= name_ext_len + 1; |
|
if(name_ext_len > 3) |
name_ext_len = 3; |
|
memcpy(&buffer[8], name_ext, name_ext_len); |
} |
|
if(name_len <= 8) |
{ |
memcpy(buffer, name, name_len); |
|
/* For now, we create lfn entries for all files, |
* except the "." and ".." directory references. |
* This is to avoid difficulties with capitalization, |
* as 8.3 filenames allow uppercase letters only. |
* |
* Theoretically it would be possible to leave |
* the 8.3 entry alone if the basename and the |
* extension have no mixed capitalization. |
*/ |
if(name[0] == '.' && |
((name[1] == '.' && name[2] == '\0') || |
name[1] == '\0') |
) |
lfn_entry_count = 0; |
} |
else |
{ |
memcpy(buffer, name, 8); |
|
/* Minimize 8.3 name clashes by appending |
* the lower byte of the cluster number. |
*/ |
uint8_t num = dir_entry->cluster & 0xff; |
|
buffer[6] = (num < 0xa0) ? ('0' + (num >> 4)) : ('a' + (num >> 4)); |
num &= 0x0f; |
buffer[7] = (num < 0x0a) ? ('0' + num) : ('a' + num); |
} |
if(buffer[0] == FAT16_DIRENTRY_DELETED) |
buffer[0] = 0x05; |
|
/* fill directory entry buffer */ |
memset(&buffer[11], 0, sizeof(buffer) - 11); |
buffer[0x0b] = dir_entry->attributes; |
#if FAT16_DATETIME_SUPPORT |
buffer[0x16] = (dir_entry->modification_time >> 0) & 0xff; |
buffer[0x17] = (dir_entry->modification_time >> 8) & 0xff; |
buffer[0x18] = (dir_entry->modification_date >> 0) & 0xff; |
buffer[0x19] = (dir_entry->modification_date >> 8) & 0xff; |
#endif |
buffer[0x1a] = (dir_entry->cluster >> 0) & 0xff; |
buffer[0x1b] = (dir_entry->cluster >> 8) & 0xff; |
buffer[0x1c] = (dir_entry->file_size >> 0) & 0xff; |
buffer[0x1d] = (dir_entry->file_size >> 8) & 0xff; |
buffer[0x1e] = (dir_entry->file_size >> 16) & 0xff; |
buffer[0x1f] = (dir_entry->file_size >> 24) & 0xff; |
|
/* write to disk */ |
if(!device_write(offset + (uint32_t) lfn_entry_count * 32, buffer, sizeof(buffer))) |
return 0; |
|
/* calculate checksum of 8.3 name */ |
uint8_t checksum = buffer[0]; |
for(uint8_t i = 1; i < 11; ++i) |
checksum = ((checksum >> 1) | (checksum << 7)) + buffer[i]; |
|
/* write lfn entries */ |
for(uint8_t lfn_entry = lfn_entry_count; lfn_entry > 0; --lfn_entry) |
{ |
memset(buffer, 0xff, sizeof(buffer)); |
|
/* set file name */ |
const char* long_name_curr = name + (lfn_entry - 1) * 13; |
uint8_t i = 1; |
while(i < 0x1f) |
{ |
buffer[i++] = *long_name_curr; |
buffer[i++] = 0; |
|
switch(i) |
{ |
case 0x0b: |
i = 0x0e; |
break; |
case 0x1a: |
i = 0x1c; |
break; |
} |
|
if(!*long_name_curr++) |
break; |
} |
|
/* set index of lfn entry */ |
buffer[0x00] = lfn_entry; |
if(lfn_entry == lfn_entry_count) |
buffer[0x00] |= FAT16_DIRENTRY_LFNLAST; |
|
/* mark as lfn entry */ |
buffer[0x0b] = 0x0f; |
|
/* set 8.3 checksum */ |
buffer[0x0d] = checksum; |
|
/* clear reserved bytes */ |
buffer[0x0c] = 0; |
buffer[0x1a] = 0; |
buffer[0x1b] = 0; |
|
/* write entry */ |
device_write(offset, buffer, sizeof(buffer)); |
|
offset += sizeof(buffer); |
} |
|
return 1; |
|
#else |
return 0; |
#endif |
} |
|
/** |
* \ingroup fat16_file |
* Creates a file. |
* |
* Creates a file and obtains the directory entry of the |
* new file. If the file to create already exists, the |
* directory entry of the existing file will be returned |
* within the dir_entry parameter. |
* |
* \note The file name is not checked for invalid characters. |
* |
* \note The generation of the short 8.3 file name is quite |
* simple. The first eight characters are used for the filename. |
* The extension, if any, is made up of the first three characters |
* following the last dot within the long filename. If the |
* filename (without the extension) is longer than eight characters, |
* the lower byte of the cluster number replaces the last two |
* characters to avoid name clashes. In any other case, it is your |
* responsibility to avoid name clashes. |
* |
* \param[in] parent The handle of the directory in which to create the file. |
* \param[in] file The name of the file to create. |
* \param[out] dir_entry The directory entry to fill for the new file. |
* \returns 0 on failure, 1 on success. |
* \see fat16_delete_file |
*/ |
uint8_t fat16_create_file(struct fat16_dir_struct* parent, const char* file, struct fat16_dir_entry_struct* dir_entry) |
{ |
#if FAT16_WRITE_SUPPORT |
if(!parent || !file || !file[0] || !dir_entry) |
return 0; |
|
/* check if the file already exists */ |
while(1) |
{ |
if(!fat16_read_dir(parent, dir_entry)) |
break; |
|
if(strcmp(file, dir_entry->long_name) == 0) |
{ |
fat16_reset_dir(parent); |
return 0; |
} |
} |
|
struct fat16_fs_struct* fs = parent->fs; |
|
/* prepare directory entry with values already known */ |
memset(dir_entry, 0, sizeof(*dir_entry)); |
strncpy(dir_entry->long_name, file, sizeof(dir_entry->long_name) - 1); |
|
/* find place where to store directory entry */ |
if(!(dir_entry->entry_offset = fat16_find_offset_for_dir_entry(fs, parent, dir_entry))) |
return 0; |
|
/* write directory entry to disk */ |
if(!fat16_write_dir_entry(fs, dir_entry)) |
return 0; |
|
return 1; |
|
#else |
return 0; |
#endif |
} |
|
/** |
* \ingroup fat16_file |
* Deletes a file or directory. |
* |
* If a directory is deleted without first deleting its |
* subdirectories and files, disk space occupied by these |
* files will get wasted as there is no chance to release |
* it and mark it as free. |
* |
* \param[in] fs The filesystem on which to operate. |
* \param[in] dir_entry The directory entry of the file to delete. |
* \returns 0 on failure, 1 on success. |
* \see fat16_create_file |
*/ |
uint8_t fat16_delete_file(struct fat16_fs_struct* fs, struct fat16_dir_entry_struct* dir_entry) |
{ |
#if FAT16_WRITE_SUPPORT |
if(!fs || !dir_entry) |
return 0; |
|
/* get offset of the file's directory entry */ |
uint32_t dir_entry_offset = dir_entry->entry_offset; |
if(!dir_entry_offset) |
return 0; |
|
uint8_t buffer[12]; |
while(1) |
{ |
/* read directory entry */ |
if(!fs->partition->device_read(dir_entry_offset, buffer, sizeof(buffer))) |
return 0; |
|
/* mark the directory entry as deleted */ |
buffer[0] = FAT16_DIRENTRY_DELETED; |
|
/* write back entry */ |
if(!fs->partition->device_write(dir_entry_offset, buffer, sizeof(buffer))) |
return 0; |
|
/* check if we deleted the whole entry */ |
if(buffer[11] != 0x0f) |
break; |
|
dir_entry_offset += 32; |
} |
|
/* We deleted the directory entry. The next thing to do is |
* marking all occupied clusters as free. |
*/ |
return (dir_entry->cluster == 0 || fat16_free_clusters(fs, dir_entry->cluster)); |
#else |
return 0; |
#endif |
} |
|
/** |
* \ingroup fat16_dir |
* Creates a directory. |
* |
* Creates a directory and obtains its directory entry. |
* If the directory to create already exists, its |
* directory entry will be returned within the dir_entry |
* parameter. |
* |
* \note The notes which apply to fat16_create_file also |
* apply to this function. |
* |
* \param[in] parent The handle of the parent directory of the new directory. |
* \param[in] dir The name of the directory to create. |
* \param[out] dir_entry The directory entry to fill for the new directory. |
* \returns 0 on failure, 1 on success. |
* \see fat16_delete_dir |
*/ |
uint8_t fat16_create_dir(struct fat16_dir_struct* parent, const char* dir, struct fat16_dir_entry_struct* dir_entry) |
{ |
#if FAT16_WRITE_SUPPORT |
if(!parent || !dir || !dir[0] || !dir_entry) |
return 0; |
|
/* check if the file or directory already exists */ |
while(1) |
{ |
if(!fat16_read_dir(parent, dir_entry)) |
break; |
|
if(strcmp(dir, dir_entry->long_name) == 0) |
{ |
fat16_reset_dir(parent); |
return 0; |
} |
} |
|
struct fat16_fs_struct* fs = parent->fs; |
|
/* allocate cluster which will hold directory entries */ |
uint16_t dir_cluster = fat16_append_clusters(fs, 0, 1); |
if(!dir_cluster) |
return 0; |
|
/* clear cluster to prevent bogus directory entries */ |
fat16_clear_cluster(fs, dir_cluster); |
|
memset(dir_entry, 0, sizeof(*dir_entry)); |
dir_entry->attributes = FAT16_ATTRIB_DIR; |
|
/* create "." directory self reference */ |
dir_entry->entry_offset = fs->header.cluster_zero_offset + |
(uint32_t) (dir_cluster - 2) * fs->header.cluster_size; |
dir_entry->long_name[0] = '.'; |
dir_entry->cluster = dir_cluster; |
if(!fat16_write_dir_entry(fs, dir_entry)) |
{ |
fat16_free_clusters(fs, dir_cluster); |
return 0; |
} |
|
/* create ".." parent directory reference */ |
dir_entry->entry_offset += 32; |
dir_entry->long_name[1] = '.'; |
dir_entry->cluster = parent->dir_entry.cluster; |
if(!fat16_write_dir_entry(fs, dir_entry)) |
{ |
fat16_free_clusters(fs, dir_cluster); |
return 0; |
} |
|
/* fill directory entry */ |
strncpy(dir_entry->long_name, dir, sizeof(dir_entry->long_name) - 1); |
dir_entry->cluster = dir_cluster; |
|
/* find place where to store directory entry */ |
if(!(dir_entry->entry_offset = fat16_find_offset_for_dir_entry(fs, parent, dir_entry))) |
{ |
fat16_free_clusters(fs, dir_cluster); |
return 0; |
} |
|
/* write directory to disk */ |
if(!fat16_write_dir_entry(fs, dir_entry)) |
{ |
fat16_free_clusters(fs, dir_cluster); |
return 0; |
} |
|
return 1; |
|
#else |
return 0; |
#endif |
} |
|
/** |
* \ingroup fat16_dir |
* Deletes a directory. |
* |
* This is just a synonym for fat16_delete_file(). |
* If a directory is deleted without first deleting its |
* subdirectories and files, disk space occupied by these |
* files will get wasted as there is no chance to release |
* it and mark it as free. |
* |
* \param[in] fs The filesystem on which to operate. |
* \param[in] dir_entry The directory entry of the directory to delete. |
* \returns 0 on failure, 1 on success. |
* \see fat16_create_dir |
*/ |
#ifdef DOXYGEN |
uint8_t fat16_delete_dir(struct fat16_fs_struct* fs, struct fat16_dir_entry_struct* dir_entry); |
#endif |
|
/** |
* \ingroup fat16_file |
* Returns the modification date of a file. |
* |
* \param[in] dir_entry The directory entry of which to return the modification date. |
* \param[out] year The year the file was last modified. |
* \param[out] month The month the file was last modified. |
* \param[out] day The day the file was last modified. |
*/ |
void fat16_get_file_modification_date(const struct fat16_dir_entry_struct* dir_entry, uint16_t* year, uint8_t* month, uint8_t* day) |
{ |
#if FAT16_DATETIME_SUPPORT |
if(!dir_entry) |
return; |
|
*year = 1980 + ((dir_entry->modification_date >> 9) & 0x7f); |
*month = (dir_entry->modification_date >> 5) & 0x0f; |
*day = (dir_entry->modification_date >> 0) & 0x1f; |
#endif |
} |
|
/** |
* \ingroup fat16_file |
* Returns the modification time of a file. |
* |
* \param[in] dir_entry The directory entry of which to return the modification time. |
* \param[out] hour The hour the file was last modified. |
* \param[out] min The min the file was last modified. |
* \param[out] sec The sec the file was last modified. |
*/ |
void fat16_get_file_modification_time(const struct fat16_dir_entry_struct* dir_entry, uint8_t* hour, uint8_t* min, uint8_t* sec) |
{ |
#if FAT16_DATETIME_SUPPORT |
if(!dir_entry) |
return; |
|
*hour = (dir_entry->modification_time >> 11) & 0x1f; |
*min = (dir_entry->modification_time >> 5) & 0x3f; |
*sec = ((dir_entry->modification_time >> 0) & 0x1f) * 2; |
#endif |
} |
|
/** |
* \ingroup fat16_file |
* Sets the modification time of a date. |
* |
* \param[in] dir_entry The directory entry for which to set the modification date. |
* \param[in] year The year the file was last modified. |
* \param[in] month The month the file was last modified. |
* \param[in] day The day the file was last modified. |
*/ |
void fat16_set_file_modification_date(struct fat16_dir_entry_struct* dir_entry, uint16_t year, uint8_t month, uint8_t day) |
{ |
#if FAT16_WRITE_SUPPORT |
#if FAT16_DATETIME_SUPPORT |
if(!dir_entry) |
return; |
|
dir_entry->modification_date = |
((year - 1980) << 9) | |
((uint16_t) month << 5) | |
((uint16_t) day << 0); |
#endif |
#endif |
} |
|
/** |
* \ingroup fat16_file |
* Sets the modification time of a file. |
* |
* \param[in] dir_entry The directory entry for which to set the modification time. |
* \param[in] hour The year the file was last modified. |
* \param[in] min The month the file was last modified. |
* \param[in] sec The day the file was last modified. |
*/ |
void fat16_set_file_modification_time(struct fat16_dir_entry_struct* dir_entry, uint8_t hour, uint8_t min, uint8_t sec) |
{ |
#if FAT16_WRITE_SUPPORT |
#if FAT16_DATETIME_SUPPORT |
if(!dir_entry) |
return; |
|
dir_entry->modification_time = |
((uint16_t) hour << 11) | |
((uint16_t) min << 5) | |
((uint16_t) sec >> 1) ; |
#endif |
#endif |
} |
|
/** |
* \ingroup fat16_fs |
* Returns the amount of total storage capacity of the filesystem in bytes. |
* |
* \param[in] fs The filesystem on which to operate. |
* \returns 0 on failure, the filesystem size in bytes otherwise. |
*/ |
uint32_t fat16_get_fs_size(const struct fat16_fs_struct* fs) |
{ |
if(!fs) |
return 0; |
|
return (fs->header.fat_size / 2 - 2) * fs->header.cluster_size; |
} |
|
/** |
* \ingroup fat16_fs |
* Returns the amount of free storage capacity on the filesystem in bytes. |
* |
* \note As the FAT16 filesystem is cluster based, this function does not |
* return continuous values but multiples of the cluster size. |
* |
* \param[in] fs The filesystem on which to operate. |
* \returns 0 on failure, the free filesystem space in bytes otherwise. |
*/ |
uint32_t fat16_get_fs_free(const struct fat16_fs_struct* fs) |
{ |
if(!fs) |
return 0; |
|
uint8_t fat[32]; |
struct fat16_usage_count_callback_arg count_arg; |
count_arg.cluster_count = 0; |
count_arg.buffer_size = sizeof(fat); |
|
uint32_t fat_offset = fs->header.fat_offset; |
uint32_t fat_size = fs->header.fat_size; |
while(fat_size > 0) |
{ |
uint16_t length = UINT16_MAX - 1; |
if(fat_size < length) |
length = fat_size; |
|
if(!fs->partition->device_read_interval(fat_offset, |
fat, |
sizeof(fat), |
length, |
fat16_get_fs_free_callback, |
&count_arg |
) |
) |
return 0; |
|
fat_offset += length; |
fat_size -= length; |
} |
|
return (uint32_t) count_arg.cluster_count * fs->header.cluster_size; |
} |
|
/** |
* \ingroup fat16_fs |
* Callback function used for counting free clusters. |
*/ |
uint8_t fat16_get_fs_free_callback(uint8_t* buffer, uint32_t offset, void* p) |
{ |
struct fat16_usage_count_callback_arg* count_arg = (struct fat16_usage_count_callback_arg*) p; |
uint8_t buffer_size = count_arg->buffer_size; |
|
for(uint8_t i = 0; i < buffer_size; i += 2) |
{ |
if((((uint16_t) buffer[1] << 8) | ((uint16_t) buffer[0] << 0)) == FAT16_CLUSTER_FREE) |
++(count_arg->cluster_count); |
|
buffer += 2; |
} |
|
return 1; |
} |
|