1968 lines
76 KiB
C
1968 lines
76 KiB
C
#include <aos/aos.h>
|
|
|
|
#include <fs/fs.h>
|
|
#include <fs/fat32.h>
|
|
|
|
#include "fs_internal.h"
|
|
#include <time.h>
|
|
|
|
// enable debug printing
|
|
// #define FAT32_DEBUG_ENABLE
|
|
#ifdef FAT32_DEBUG_ENABLE
|
|
#define FAT32_DEBUG_PRINTF(fmt, args...) DEBUG_PRINTF("[%s] " fmt "\n", __FUNCTION__, args)
|
|
#define FAT32_DEBUG_PRINT(s) DEBUG_PRINTF("[%s] " s "\n", __FUNCTION__)
|
|
#else
|
|
#define FAT32_DEBUG_PRINTF(...)
|
|
#define FAT32_DEBUG_PRINT(...)
|
|
#endif
|
|
|
|
// NOTE rueegges: uncomment to enable fat32 performance measurements
|
|
// #define FAT32_PERFORMANCE
|
|
#ifdef FAT32_PERFORMANCE
|
|
#include <aos/performance.h>
|
|
static struct performance_context fat32_perf_context;
|
|
#define FAT32_PERFORMANCE_START {perf_init(&fat32_perf_context, __FUNCTION__); perf_add_now(&fat32_perf_context, "start"); }
|
|
#define FAT32_PERFORMANCE_END {perf_add_now(&fat32_perf_context, "done"); perf_print(&fat32_perf_context); }
|
|
#else
|
|
#define FAT32_PERFORMANCE_START
|
|
#define FAT32_PERFORMANCE_END
|
|
#endif
|
|
#define FAT32_RUN_LOCKED(s) { FAT32_DEBUG_PRINT("Locking"); FAT32_PERFORMANCE_START; err = fat32->lock_fn(); if (err_is_fail(err)) { DEBUG_ERR(err, "locking"); FAT32_PERFORMANCE_END; return err; } err = s; fat32->unlock_fn(); FAT32_PERFORMANCE_END; return err; }
|
|
|
|
struct fat32_path_resolve_result {
|
|
struct fat32_directory_entry directory_entry;
|
|
struct fat32_directory_entry_ref directory_entry_ref;
|
|
};
|
|
|
|
static void fat32_get_time_information(uint16_t *write_date, uint16_t *write_time) {
|
|
time_t t = time(NULL);
|
|
struct tm tm = *localtime(&t);
|
|
*write_date = tm.tm_mday | ((tm.tm_mon + 1) << 5) | ((tm.tm_year - 80) << 9);
|
|
*write_time = (tm.tm_sec / 2) | (tm.tm_min << 5) | (tm.tm_hour << 11);
|
|
}
|
|
|
|
static bool fat32_is_end_of_cluster_chain(uint32_t cluster_number) {
|
|
return cluster_number >= 0x0FFFFFF8;
|
|
}
|
|
|
|
static uint32_t fat32_first_sector_of_cluster(struct fat32 *fat32, uint32_t cluster_number) {
|
|
return ((cluster_number - 2) * fat32->sectors_per_cluster) + fat32->first_data_sector;
|
|
}
|
|
|
|
static uint32_t fat32_directory_entry_to_first_cluster(struct fat32_directory_entry *directory_entry) {
|
|
return directory_entry->first_cluster_number_high_word << 16 | directory_entry->first_cluster_number_low_word;
|
|
}
|
|
|
|
static uint16_t fat32_cluster_number_low_word(uint32_t cluster_number) {
|
|
return cluster_number & 0xFFFF;
|
|
}
|
|
|
|
static uint16_t fat32_cluster_number_high_word(uint32_t cluster_number) {
|
|
return (cluster_number >> 16) & 0xFFFF;
|
|
}
|
|
|
|
static uint8_t fat32_get_active_fat(struct fat32 *fat32) {
|
|
if (fat32->ext_flag_mirrored) {
|
|
return 0;
|
|
} else {
|
|
return fat32->ext_flag_active_fat;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
*
|
|
* @param directory_entry_ref reference to the directory entry that we want to get an id for
|
|
* @return uint64_t unique identifier for the directory entry referenced
|
|
*/
|
|
static uint64_t fat32_id_from_directory_entry_ref(struct fat32_directory_entry_ref directory_entry_ref) {
|
|
// there can never be this many directory entries in a sector
|
|
return ((uint64_t)directory_entry_ref.sector_number << 32) | directory_entry_ref.index_in_sector;
|
|
}
|
|
|
|
/**
|
|
* @brief given a cluster number calculate information about where in the FAT this entry is located
|
|
*/
|
|
static void fat32_fat_entry_parameters(struct fat32 *fat32, uint32_t cluster_number, uint32_t *fat_sector_num, uint32_t *fat_sector_offset) {
|
|
uint8_t fat_index = fat32_get_active_fat(fat32);
|
|
uint32_t fat_offset = cluster_number * 4;
|
|
*fat_sector_num = fat32->reserved_sector_count + (fat_offset / fat32->bytes_per_sector) + fat_index * fat32->fat_sectors_32_count;
|
|
*fat_sector_offset = fat_offset % fat32->bytes_per_sector;
|
|
}
|
|
|
|
// set some memory to zero for the operation
|
|
static uint8_t zero_mem[FAT32_BLOCK_SIZE] = {0};
|
|
/**
|
|
* @brief set all bytes of all sectors of a cluster to zero
|
|
*/
|
|
static errval_t fat32_zero_cluster(struct fat32 *fat32, uint32_t cluster_number) {
|
|
errval_t err;
|
|
|
|
assert(cluster_number > 1);
|
|
|
|
FAT32_DEBUG_PRINTF("cluster_number: %u", cluster_number);
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_zero_cluster" "_pre");
|
|
#endif
|
|
|
|
uint32_t first_sector = fat32_first_sector_of_cluster(fat32, cluster_number);
|
|
for (size_t i = 0; i < fat32->sectors_per_cluster; ++i) {
|
|
uint32_t current_sector = first_sector + i;
|
|
err = fat32->write_object_fn(current_sector, 0, FAT32_BLOCK_SIZE, zero_mem);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
}
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_zero_cluster" "_post");
|
|
#endif
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief write to en entry given by the cluster number in the FAT
|
|
*/
|
|
static errval_t fat32_fat_write_entry(struct fat32 *fat32, uint32_t cluster_number, uint32_t entry) {
|
|
errval_t err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_fat_write_entry" "_pre");
|
|
#endif
|
|
|
|
// write to the first or active FAT
|
|
uint32_t fat_sector_num, fat_sector_offset;
|
|
fat32_fat_entry_parameters(fat32, cluster_number, &fat_sector_num, &fat_sector_offset);
|
|
err = fat32->write_object_fn(fat_sector_num, fat_sector_offset, sizeof(uint32_t), &entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// if we are in mirrored mode then we just wrote to the first FAT and need to also write to the others
|
|
if (fat32->ext_flag_mirrored) {
|
|
for (size_t i = 1; i < fat32->fat_count; ++i) {
|
|
err = fat32->write_object_fn(fat_sector_num + i * fat32->fat_sectors_32_count, fat_sector_offset, sizeof(uint32_t), &entry);
|
|
if (err_is_fail(err)) return err;
|
|
}
|
|
}
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_fat_write_entry" "_post");
|
|
#endif
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
static errval_t fat32_next_cluster_in_chain(struct fat32 *fat32, uint32_t cluster_number, uint32_t *return_next_cluster_number);
|
|
/**
|
|
* @brief find a free cluster number and store the end of chain mark so it is not seen as free anymore
|
|
* @param return_cluster_number the free cluster if it was found
|
|
*/
|
|
static errval_t fat32_alloc_cluster(struct fat32 *fat32, uint32_t *return_cluster_number) {
|
|
errval_t err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_alloc_cluster" "_pre");
|
|
#endif
|
|
|
|
// just iterate over all FAT entries and return the first free one
|
|
uint32_t fat[FAT32_FAT_ENTRIES_PER_SECTOR];
|
|
for (size_t cluster_number = 2; cluster_number <= fat32->max_cluster_number; ++cluster_number) {
|
|
uint32_t fat_sector_num, fat_sector_offset;
|
|
fat32_fat_entry_parameters(fat32, cluster_number, &fat_sector_num, &fat_sector_offset);
|
|
|
|
// only read the sector when necessary to speed up the search
|
|
if (cluster_number == 2 || cluster_number % FAT32_FAT_ENTRIES_PER_SECTOR == 0) {
|
|
err = fat32->read_object_fn(fat_sector_num, 0, FAT32_BLOCK_SIZE, fat);
|
|
if (err_is_fail(err)) return err;
|
|
}
|
|
|
|
// check if the entry is currently unused
|
|
if ((fat[cluster_number % FAT32_FAT_ENTRIES_PER_SECTOR] & 0x0FFFFFFF) == 0x00000000) {
|
|
assert(cluster_number > 1);
|
|
|
|
// set the entry to the endofchain so it is not seen as unused anymore
|
|
uint32_t entry = (fat[cluster_number % FAT32_FAT_ENTRIES_PER_SECTOR] & 0xF0000000) | FAT32_EOFC_MARK;
|
|
|
|
// write the FAT back to disk
|
|
err = fat32_fat_write_entry(fat32, cluster_number, entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// FAT32_DEBUG_PRINTF("found free cluster: %u", cluster_number);
|
|
*return_cluster_number = cluster_number;
|
|
return SYS_ERR_OK;
|
|
}
|
|
}
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_alloc_cluster" "_post");
|
|
#endif
|
|
|
|
// no more free space left
|
|
return FAT_ERR_FAT_LOOKUP;
|
|
}
|
|
|
|
__attribute__((__used__))
|
|
/**
|
|
* @brief free a cluster by writing zero to its entry in the FAT
|
|
*/
|
|
static errval_t fat32_free_cluster(struct fat32 *fat32, uint32_t cluster_number) {
|
|
errval_t err;
|
|
|
|
assert(cluster_number > 1);
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_free_cluster" "_pre");
|
|
#endif
|
|
|
|
uint32_t fat_sector_num, fat_sector_offset;
|
|
fat32_fat_entry_parameters(fat32, cluster_number, &fat_sector_num, &fat_sector_offset);
|
|
|
|
uint32_t fat_entry;
|
|
err = fat32->read_object_fn(fat_sector_num, fat_sector_offset, 4, &fat_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// preserve the upper 4 bits
|
|
fat_entry &= 0xF0000000;
|
|
err = fat32_fat_write_entry(fat32, cluster_number, fat_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_free_cluster" "_post");
|
|
#endif
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief add the given cluster to the end of the given chain tail
|
|
*/
|
|
static errval_t fat32_extend_cluster_chain(struct fat32 *fat32, uint32_t current_tail_cluster_number, uint32_t new_tail_cluster_number) {
|
|
errval_t err;
|
|
|
|
assert(current_tail_cluster_number > 1);
|
|
assert(new_tail_cluster_number > 1);
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_extend_cluster_chain" "_pre");
|
|
#endif
|
|
|
|
uint32_t fat_sector_num, fat_sector_offset;
|
|
fat32_fat_entry_parameters(fat32, current_tail_cluster_number, &fat_sector_num, &fat_sector_offset);
|
|
|
|
uint32_t old_entry;
|
|
err = fat32->read_object_fn(fat_sector_num, fat_sector_offset, 4, &old_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
assert(new_tail_cluster_number <= 0x0FFFFFFF);
|
|
// preserve the upper 4 bits of the entry
|
|
uint32_t new_entry = (old_entry & 0xF0000000) | new_tail_cluster_number;
|
|
err = fat32_fat_write_entry(fat32, current_tail_cluster_number, new_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_extend_cluster_chain" "_post");
|
|
#endif
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief get the next cluster in the chain by reading the value for the current cluster in the FAT
|
|
*/
|
|
static errval_t fat32_next_cluster_in_chain(struct fat32 *fat32, uint32_t cluster_number, uint32_t *return_next_cluster_number) {
|
|
errval_t err;
|
|
|
|
assert(cluster_number > 1);
|
|
|
|
assert(!fat32_is_end_of_cluster_chain(cluster_number));
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_next_cluster_in_chain" "_pre");
|
|
#endif
|
|
|
|
uint32_t fat_sector_num, fat_sector_offset;
|
|
fat32_fat_entry_parameters(fat32, cluster_number, &fat_sector_num, &fat_sector_offset);
|
|
|
|
err = fat32->read_object_fn(fat_sector_num, fat_sector_offset, 4, return_next_cluster_number);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// upper 4 bits have to be ignored
|
|
*return_next_cluster_number &= 0x0FFFFFFF;
|
|
|
|
// FAT32_DEBUG_PRINTF("FAT entry %u: %u", cluster_number, *return_next_cluster_number);
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_next_cluster_in_chain" "_post");
|
|
#endif
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief try to get the next cluster in the chain. If there is none then extend the chain.
|
|
*/
|
|
static errval_t fat32_next_cluster_in_chain_or_extend(struct fat32 *fat32, uint32_t cluster_number, uint32_t *return_next_cluster_number) {
|
|
errval_t err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_next_cluster_in_chain_or_extend" "_pre");
|
|
#endif
|
|
|
|
// first see what the next cluster might be
|
|
err = fat32_next_cluster_in_chain(fat32, cluster_number, return_next_cluster_number);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
if (!fat32_is_end_of_cluster_chain(*return_next_cluster_number)) {
|
|
// we have a valid next cluster
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
// we need to extend the chain
|
|
|
|
// find a free cluster
|
|
err = fat32_alloc_cluster(fat32, return_next_cluster_number);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// zero the new cluster
|
|
err = fat32_zero_cluster(fat32, *return_next_cluster_number);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// add the cluster to the chain
|
|
err = fat32_extend_cluster_chain(fat32, cluster_number, *return_next_cluster_number);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_next_cluster_in_chain_or_extend" "_post");
|
|
#endif
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief free all clusters in the chain starting at the first_cluster
|
|
|
|
* @param first_cluster first cluster in the chain to be freed
|
|
*/
|
|
static errval_t fat32_free_cluster_chain(struct fat32 *fat32, uint32_t first_cluster) {
|
|
errval_t err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_free_cluster_chain" "_pre");
|
|
#endif
|
|
|
|
uint32_t current_cluster = first_cluster;
|
|
while (!fat32_is_end_of_cluster_chain(current_cluster)) {
|
|
// free cluster entry
|
|
uint32_t next_cluster;
|
|
err = fat32_next_cluster_in_chain(fat32, current_cluster, &next_cluster);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
err = fat32_free_cluster(fat32, current_cluster);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
current_cluster = next_cluster;
|
|
}
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_free_cluster_chain" "_post");
|
|
#endif
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
static bool fat32_name_is_dot(const char *short_name, const char *short_name_ext){
|
|
if (short_name[0] != '.') return false;
|
|
for (size_t i = 1; i < FAT32_SHORTNAME_SIZE; ++i) {
|
|
if (short_name[i] != ' ') return false;
|
|
}
|
|
for (size_t i = 0; i < FAT32_SHORTNAME_EXT_SIZE; ++i) {
|
|
if (short_name_ext[i] != ' ') return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool fat32_name_is_dotdot(const char *short_name, const char *short_name_ext){
|
|
if (short_name[0] != '.') return false;
|
|
if (short_name[1] != '.') return false;
|
|
for (size_t i = 2; i < FAT32_SHORTNAME_SIZE; ++i) {
|
|
if (short_name[i] != ' ') return false;
|
|
}
|
|
for (size_t i = 0; i < FAT32_SHORTNAME_EXT_SIZE; ++i) {
|
|
if (short_name_ext[i] != ' ') return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief turn the weird short_name and short_name_ext fields of a directory entry into a beautiful name
|
|
*
|
|
* @param directory_entry entry for which we want the nice name
|
|
* @return char* memory allocated using malloc containing the nice name or NULL if malloc failed
|
|
*/
|
|
static char* fat32_directory_entry_get_nice_name(struct fat32_directory_entry *directory_entry){
|
|
// get length of short_name by searching for first space
|
|
// NOTE rueegges: short_name is not allowed to contain spaces
|
|
size_t short_name_len = 0;
|
|
for (; short_name_len < FAT32_SHORTNAME_SIZE && directory_entry->short_name[short_name_len] != ' '; ++short_name_len);
|
|
|
|
// get length of short_name_ext by searching for first space
|
|
// NOTE rueegges: short_name_ext is not allowed to contain spaces
|
|
size_t short_name_ext_len = 0;
|
|
for (; short_name_ext_len < FAT32_SHORTNAME_EXT_SIZE && directory_entry->short_name_ext[short_name_ext_len] != ' '; ++short_name_ext_len);
|
|
|
|
// get total length of the name string plus terminating 0x0 character
|
|
size_t name_len = short_name_len + short_name_ext_len + 1;
|
|
// if there is a short_name_ext then we need an additional byte for a '.'
|
|
if (short_name_ext_len != 0) ++name_len;
|
|
|
|
// allocate output name and copy data
|
|
char *name = malloc(name_len);
|
|
if (name == NULL) return NULL;
|
|
memcpy(name, directory_entry->short_name, short_name_len);
|
|
if (short_name_ext_len > 0) {
|
|
name[short_name_len] = '.';
|
|
memcpy(name + short_name_len + 1, directory_entry->short_name_ext, short_name_ext_len);
|
|
}
|
|
// place string termination character
|
|
name[name_len - 1] = '\0';
|
|
|
|
// replace the 0x05 in the first place of the short_name with 0xE5 as per FAT32 spec
|
|
if (name[0] == 0x05) {
|
|
name[0] = 0xE5;
|
|
}
|
|
|
|
FAT32_DEBUG_PRINTF("short_name[%lu]: '%.8s', short_name_ext[%lu]: '%.3s', name: '%s'", short_name_len, directory_entry->short_name, short_name_ext_len, directory_entry->short_name_ext, name);
|
|
|
|
// empty short_name is not allowed in FAT32 spec
|
|
assert(short_name_len > 0);
|
|
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the next element of a path. elements are separated with one or more FS_PATH_SEP. Checks for validity of path elements
|
|
*
|
|
* @param path_cursor where to start looking for the next element
|
|
* @param path_end first byte that should not be looked at anymore
|
|
* @param return_path_cursor where in the path we ended up after extracting the next element
|
|
* @param return_short_name location to save the short name extracted
|
|
* @param return_short_name_ext location to save the short name extension extracted
|
|
*/
|
|
static errval_t fat32_extract_next_path_element(
|
|
const char *path_cursor,
|
|
const char *path_end,
|
|
const char **return_path_cursor,
|
|
char *return_short_name,
|
|
char *return_short_name_ext
|
|
) {
|
|
assert(path_cursor != NULL);
|
|
assert(return_short_name != NULL);
|
|
assert(return_short_name_ext != NULL);
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_extract_next_path_element" "_post");
|
|
#endif
|
|
|
|
// remove any leading separators
|
|
while (path_cursor < path_end && *path_cursor == FS_PATH_SEP) {
|
|
path_cursor++;
|
|
}
|
|
|
|
// if we are at the end of the path already we stop
|
|
if (path_cursor >= path_end) {
|
|
return FAT_ERR_PATH_EMPTY;
|
|
}
|
|
|
|
// initialize names
|
|
for (size_t i = 0; i < FAT32_SHORTNAME_SIZE; ++i) {
|
|
return_short_name[i] = ' ';
|
|
}
|
|
for (size_t i = 0; i < FAT32_SHORTNAME_EXT_SIZE; ++i) {
|
|
return_short_name_ext[i] = ' ';
|
|
}
|
|
|
|
// special case for dot and dotdot directories
|
|
if (*path_cursor == '.') {
|
|
++path_cursor;
|
|
if (path_cursor < path_end && *path_cursor == FS_PATH_SEP) {
|
|
// dot directory
|
|
return_short_name[0] = '.';
|
|
} else if (path_cursor + 1 < path_end && *path_cursor == '.' && *(path_cursor + 1) == FS_PATH_SEP) {
|
|
// dotdot directory
|
|
return_short_name[0] = '.';
|
|
return_short_name[1] = '.';
|
|
++path_cursor;
|
|
} else {
|
|
FAT32_DEBUG_PRINT("invalid dot start");
|
|
// short names of other FAT entries are not allowed to start with a '.'
|
|
return FAT_ERR_BAD_FILENAME;
|
|
}
|
|
} else {
|
|
// NOTE: FAT32 short_name files and directories are only allowed to have at most a single dot in the name (except for the dotdot dir)
|
|
// and cannot begin with a dot because the short_name must not be empty
|
|
|
|
// the first part of the name cannot contain '.' characters
|
|
for (size_t short_name_index = 0; path_cursor < path_end && *path_cursor != FS_PATH_SEP && *path_cursor != '.'; ++path_cursor, ++short_name_index) {
|
|
// the first part of a short name can only contain up to FAT32_SHORTNAME_SIZE characters
|
|
if (short_name_index >= FAT32_SHORTNAME_SIZE) {
|
|
FAT32_DEBUG_PRINT("short_name too long");
|
|
return FAT_ERR_BAD_FILENAME;
|
|
}
|
|
|
|
return_short_name[short_name_index] = *path_cursor;
|
|
// special case so the character 0xE5 can be in a pathname
|
|
if (short_name_index == 0 && *path_cursor == 0xE5) {
|
|
return_short_name[short_name_index] = 0x05;
|
|
}
|
|
// FAT32 is case insensitive
|
|
// NOTE rueegges: this implementation only works for arabic letter interpretation of upper case
|
|
if (return_short_name[short_name_index] >= 'a' && return_short_name[short_name_index] <= 'z') {
|
|
return_short_name[short_name_index] -= 32;
|
|
}
|
|
}
|
|
// short name may not be empty according to FAT32 spec
|
|
if (return_short_name[0] == ' ') {
|
|
FAT32_DEBUG_PRINT("short_name empty");
|
|
return FAT_ERR_BAD_FILENAME;
|
|
}
|
|
// the dot is not stored so we need to skip it
|
|
if (path_cursor < path_end && *path_cursor == '.') {
|
|
++path_cursor;
|
|
}
|
|
// get name ext of current directory in path
|
|
for (size_t short_name_ext_index = 0; path_cursor < path_end && *path_cursor != FS_PATH_SEP; ++path_cursor, ++short_name_ext_index) {
|
|
// the second part of a short name can only contain up to FAT32_SHORTNAME_EXT_SIZE characters
|
|
if (short_name_ext_index >= FAT32_SHORTNAME_EXT_SIZE) {
|
|
FAT32_DEBUG_PRINT("short_name_ext too long");
|
|
return FAT_ERR_BAD_FILENAME;
|
|
}
|
|
// no dot allowed in shortname extension
|
|
if (*path_cursor == '.') {
|
|
return FAT_ERR_BAD_FILENAME;
|
|
}
|
|
return_short_name_ext[short_name_ext_index] = *path_cursor;
|
|
// FAT32 is case insensitive
|
|
// NOTE rueegges: this implementation only works for arabic letter interpretation of upper case
|
|
if (return_short_name_ext[short_name_ext_index] >= 'a' && return_short_name_ext[short_name_ext_index] <= 'z') {
|
|
return_short_name_ext[short_name_ext_index] -= 32;
|
|
}
|
|
}
|
|
}
|
|
|
|
// return the position we ended up in
|
|
if (return_path_cursor != NULL) {
|
|
*return_path_cursor = path_cursor;
|
|
}
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_extract_next_path_element" "_post");
|
|
#endif
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Read the directory entry identified by the given reference from disk
|
|
*/
|
|
static errval_t fat32_read_directory_entry(
|
|
struct fat32 *fat32,
|
|
struct fat32_directory_entry_ref directory_entry_ref,
|
|
struct fat32_directory_entry *directory_entry
|
|
) {
|
|
return fat32->read_object_fn(
|
|
directory_entry_ref.sector_number,
|
|
directory_entry_ref.index_in_sector * sizeof(struct fat32_directory_entry),
|
|
sizeof(struct fat32_directory_entry),
|
|
directory_entry);
|
|
}
|
|
|
|
/**
|
|
* @brief Write the directory entry identified by the given reference to disk
|
|
*/
|
|
static errval_t fat32_write_directory_entry(
|
|
struct fat32 *fat32,
|
|
struct fat32_directory_entry_ref directory_entry_ref,
|
|
struct fat32_directory_entry *directory_entry
|
|
) {
|
|
// FAT32_DEBUG_PRINTF("ref: %lu@%u", directory_entry_ref.index_in_sector, directory_entry_ref.sector_number);
|
|
assert(directory_entry_ref.sector_number >= fat32->reserved_sector_count);
|
|
return fat32->write_object_fn(
|
|
directory_entry_ref.sector_number,
|
|
directory_entry_ref.index_in_sector * sizeof(struct fat32_directory_entry),
|
|
sizeof(struct fat32_directory_entry),
|
|
directory_entry);
|
|
}
|
|
|
|
/**
|
|
* @brief try to find a free directory entry in the given directory. If there is no free entry left then we extend the cluster chain
|
|
*/
|
|
static errval_t fat32_allocate_directory_entry(
|
|
struct fat32 *fat32,
|
|
struct fat32_directory_entry *search_directory,
|
|
struct fat32_path_resolve_result *path_resolve_result
|
|
) {
|
|
assert(fat32 != NULL);
|
|
assert(path_resolve_result != NULL);
|
|
|
|
errval_t err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_allocate_directory_entry" "_pre");
|
|
#endif
|
|
|
|
uint32_t current_cluster = fat32_directory_entry_to_first_cluster(search_directory);
|
|
|
|
while (!fat32_is_end_of_cluster_chain(current_cluster)) {
|
|
// make sure we never try to look at the reserved clusters during traversal
|
|
assert(current_cluster > 1);
|
|
|
|
// if this occurs we have a broken filesystem
|
|
if (current_cluster == 0x0FFFFFF7) {
|
|
return FAT_ERR_BAD_FS;
|
|
}
|
|
|
|
uint32_t first_sector_of_cluster = fat32_first_sector_of_cluster(fat32, current_cluster);
|
|
for (uint32_t sector_offset = 0; sector_offset < fat32->sectors_per_cluster; ++sector_offset) {
|
|
|
|
uint32_t current_sector = first_sector_of_cluster + sector_offset;
|
|
|
|
struct fat32_directory_entry directory_entries[FAT32_DIRECTORY_ENTRIES_PER_SECTOR];
|
|
err = fat32->read_object_fn(current_sector, 0, FAT32_BLOCK_SIZE, directory_entries);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
for (size_t directory_index = 0; directory_index < FAT32_DIRECTORY_ENTRIES_PER_SECTOR; ++directory_index) {
|
|
if (directory_entries[directory_index].short_name[0] == 0xE5 || directory_entries[directory_index].short_name[0] == 0x00) {
|
|
path_resolve_result->directory_entry_ref.sector_number = current_sector;
|
|
path_resolve_result->directory_entry_ref.index_in_sector = directory_index;
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_allocate_directory_entry" "_post");
|
|
#endif
|
|
return SYS_ERR_OK;
|
|
}
|
|
}
|
|
}
|
|
err = fat32_next_cluster_in_chain_or_extend(fat32, current_cluster, ¤t_cluster);
|
|
if (err_is_fail(err)) return err;
|
|
}
|
|
|
|
USER_PANIC("This code should be unreachable");
|
|
}
|
|
|
|
/**
|
|
* @brief try to free a directory entry by writing 0xE5 to the first byte of the shortname. if there are open handles for the given directory entry then we fail.
|
|
*/
|
|
static errval_t fat32_free_directory_entry(struct fat32 *fat32, struct fat32_directory_entry_ref directory_entry_ref) {
|
|
errval_t err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_free_directory_entry" "_pre");
|
|
#endif
|
|
|
|
struct fat32_directory_entry directory_entry;
|
|
err = fat32_read_directory_entry(fat32, directory_entry_ref, &directory_entry);
|
|
if(err_is_fail(err)) return err;
|
|
|
|
// if the directory entry is still opened by someone we cannot delete it yet
|
|
size_t handler_count;
|
|
err = fat32->count_handles_fn(fat32_id_from_directory_entry_ref(directory_entry_ref), &handler_count);
|
|
if(err_is_fail(err)) return err;
|
|
if(handler_count > 0) return FAT_ERR_OPENED;
|
|
|
|
struct fat32_directory_entry free_dir = {
|
|
.short_name = "\xE5 "
|
|
};
|
|
err = fat32_write_directory_entry(fat32, directory_entry_ref, &free_dir);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// delete the cluster chain
|
|
err = fat32_free_cluster_chain(fat32, fat32_directory_entry_to_first_cluster(&directory_entry));
|
|
if (err_is_fail(err)) return err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_free_directory_entry" "_post");
|
|
#endif
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief look for a directory entry with the given name inside the given directory
|
|
*/
|
|
static errval_t fat32_search_in_directory(
|
|
struct fat32 *fat32,
|
|
struct fat32_directory_entry *search_directory,
|
|
const char *short_name,
|
|
const char *short_name_ext,
|
|
struct fat32_path_resolve_result *path_resolve_result
|
|
) {
|
|
assert(fat32 != NULL);
|
|
assert(short_name != NULL);
|
|
assert(short_name_ext != NULL);
|
|
|
|
errval_t err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_search_in_directory" "_pre");
|
|
#endif
|
|
|
|
uint32_t current_cluster = fat32_directory_entry_to_first_cluster(search_directory);
|
|
|
|
while (!fat32_is_end_of_cluster_chain(current_cluster)) {
|
|
// make sure we never try to look at the reserved clusters during traversal
|
|
assert(current_cluster > 1);
|
|
|
|
// if this occurs we have a broken filesystem
|
|
if (current_cluster == 0x0FFFFFF7) {
|
|
return FAT_ERR_BAD_FS;
|
|
}
|
|
|
|
uint32_t first_sector_of_cluster = fat32_first_sector_of_cluster(fat32, current_cluster);
|
|
for (uint32_t sector_offset = 0; sector_offset < fat32->sectors_per_cluster; ++sector_offset) {
|
|
|
|
uint32_t current_sector = first_sector_of_cluster + sector_offset;
|
|
|
|
struct fat32_directory_entry directory_entries[FAT32_DIRECTORY_ENTRIES_PER_SECTOR];
|
|
err = fat32->read_object_fn(current_sector, 0, FAT32_BLOCK_SIZE, directory_entries);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
for (size_t directory_index = 0; directory_index < FAT32_DIRECTORY_ENTRIES_PER_SECTOR; ++directory_index) {
|
|
FAT32_DEBUG_PRINTF("Found entry: %.8s.%.3s", directory_entries[directory_index].short_name, directory_entries[directory_index].short_name_ext);
|
|
// empty entry
|
|
if (directory_entries[directory_index].short_name[0] == 0xE5) continue;
|
|
// no non-empty entry after this entry
|
|
if (directory_entries[directory_index].short_name[0] == 0x00) return FS_ERR_NOTFOUND;
|
|
// deleted directory that still has open handles should not be shown
|
|
if (
|
|
memcmp(directory_entries[directory_index].short_name, short_name, FAT32_SHORTNAME_SIZE) == 0 &&
|
|
memcmp(directory_entries[directory_index].short_name_ext, short_name_ext, FAT32_SHORTNAME_EXT_SIZE) == 0
|
|
) {
|
|
// found the directory
|
|
|
|
if (path_resolve_result != NULL) {
|
|
path_resolve_result->directory_entry = directory_entries[directory_index];
|
|
path_resolve_result->directory_entry_ref.sector_number = current_sector;
|
|
path_resolve_result->directory_entry_ref.index_in_sector = directory_index;
|
|
|
|
// NOTE: there is a special case for dotdot directories that reside in direct child directories of
|
|
// the root directory. The cluster number is set to 0 and we need to change it to the first root cluster
|
|
// for traversal
|
|
if (fat32_name_is_dotdot(short_name, short_name_ext) && fat32_directory_entry_to_first_cluster(&directory_entries[directory_index]) == 0) {
|
|
path_resolve_result->directory_entry.first_cluster_number_low_word = fat32_cluster_number_low_word(fat32->root_cluster_number);
|
|
path_resolve_result->directory_entry.first_cluster_number_high_word = fat32_cluster_number_high_word(fat32->root_cluster_number);
|
|
}
|
|
}
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_search_in_directory" "_post");
|
|
#endif
|
|
return SYS_ERR_OK;
|
|
}
|
|
}
|
|
}
|
|
err = fat32_next_cluster_in_chain(fat32, current_cluster, ¤t_cluster);
|
|
if (err_is_fail(err)) return err;
|
|
}
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_search_in_directory" "_post");
|
|
#endif
|
|
|
|
FAT32_DEBUG_PRINT("directory entry not found");
|
|
return FS_ERR_NOTFOUND;
|
|
}
|
|
|
|
/**
|
|
* @brief check if a directory is empty. Does not ignore hidden entries
|
|
*/
|
|
static errval_t fat32_check_directory_empty(
|
|
struct fat32 *fat32,
|
|
struct fat32_directory_entry *search_directory
|
|
) {
|
|
assert(fat32 != NULL);
|
|
assert(search_directory != NULL);
|
|
|
|
errval_t err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_check_directory_empty" "_pre");
|
|
#endif
|
|
|
|
uint32_t current_cluster = fat32_directory_entry_to_first_cluster(search_directory);
|
|
|
|
while (!fat32_is_end_of_cluster_chain(current_cluster)) {
|
|
// make sure we never try to look at the reserved clusters during traversal
|
|
assert(current_cluster > 1);
|
|
|
|
// if this occurs we have a broken filesystem
|
|
if (current_cluster == 0x0FFFFFF7) {
|
|
return FAT_ERR_BAD_FS;
|
|
}
|
|
|
|
uint32_t first_sector_of_cluster = fat32_first_sector_of_cluster(fat32, current_cluster);
|
|
for (uint32_t sector_offset = 0; sector_offset < fat32->sectors_per_cluster; ++sector_offset) {
|
|
|
|
uint32_t current_sector = first_sector_of_cluster + sector_offset;
|
|
|
|
struct fat32_directory_entry directory_entries[FAT32_DIRECTORY_ENTRIES_PER_SECTOR];
|
|
err = fat32->read_object_fn(current_sector, 0, FAT32_BLOCK_SIZE, directory_entries);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
|
|
for (size_t directory_index = 0; directory_index < FAT32_DIRECTORY_ENTRIES_PER_SECTOR; ++directory_index) {
|
|
// ensure that the entry is either empty or the dot or dotdot entry
|
|
if (directory_entries[directory_index].short_name[0] == 0x00) {
|
|
return SYS_ERR_OK;
|
|
} else if (
|
|
directory_entries[directory_index].short_name[0] != 0xE5 &&
|
|
!fat32_name_is_dot(directory_entries[directory_index].short_name, directory_entries[directory_index].short_name_ext) &&
|
|
!fat32_name_is_dotdot(directory_entries[directory_index].short_name, directory_entries[directory_index].short_name_ext)
|
|
) {
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_check_directory_empty" "_post");
|
|
#endif
|
|
return FS_ERR_NOTEMPTY;
|
|
}
|
|
}
|
|
}
|
|
err = fat32_next_cluster_in_chain(fat32, current_cluster, ¤t_cluster);
|
|
if (err_is_fail(err)) return err;
|
|
}
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_check_directory_empty" "_post");
|
|
#endif
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief check if the path starts with the mount string and if so calculate the position in the path right after the mount
|
|
*/
|
|
static errval_t fat32_skip_mount_point(struct fat32 *fat32, const char *path, const char **result_path) {
|
|
// remove the mount point from the path. This is a very simple approach
|
|
// if the path is shorter than the mount point then we have nothing to show
|
|
size_t mount_point_len = strlen(fat32->mount);
|
|
if (strlen(path) < mount_point_len) {
|
|
return FS_ERR_NOTFOUND;
|
|
}
|
|
// make sure the mount prefix matches otherwise we have nothing to show
|
|
if (strncmp(fat32->mount, path, mount_point_len)) {
|
|
return FS_ERR_NOTFOUND;
|
|
}
|
|
|
|
*result_path = path + strlen(fat32->mount);
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Try to resolve the given path to a directory entry reference.
|
|
* Allows resolving a path up to a specific point.
|
|
* This way we don't have to copy the path string to get the parent directory during mkdir and fcreate.
|
|
*/
|
|
static errval_t fat32_resolve_path_partial(
|
|
struct fat32 *fat32,
|
|
const char *path,
|
|
const char *path_end,
|
|
struct fat32_path_resolve_result *return_path_resolve_result
|
|
) {
|
|
errval_t err;
|
|
|
|
assert(fat32 != NULL);
|
|
assert(path != NULL);
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_resolve_path_partial" "_pre");
|
|
#endif
|
|
|
|
FAT32_DEBUG_PRINTF("Resolving path: %.*s", path_end - path, path);
|
|
|
|
// always start traversal in the root directory so we create a mock structure for root
|
|
struct fat32_path_resolve_result path_resolve_result = {
|
|
.directory_entry = {
|
|
.attributes = FAT32_ATTR_DIRECTORY,
|
|
.first_cluster_number_low_word = fat32_cluster_number_low_word(fat32->root_cluster_number),
|
|
.first_cluster_number_high_word = fat32_cluster_number_high_word(fat32->root_cluster_number),
|
|
.file_size = 0,
|
|
},
|
|
.directory_entry_ref = {
|
|
.sector_number = UINT32_MAX,
|
|
.index_in_sector = UINT32_MAX,
|
|
}
|
|
};
|
|
|
|
const char *path_cursor = path;
|
|
// while we haven't reached the end of the path yet
|
|
while (path_cursor < path_end) {
|
|
char short_name[FAT32_SHORTNAME_SIZE];
|
|
char short_name_ext[FAT32_SHORTNAME_EXT_SIZE];
|
|
|
|
err = fat32_extract_next_path_element(path_cursor, path_end, &path_cursor, short_name, short_name_ext);
|
|
// we are done with the path traversal
|
|
if (err == FAT_ERR_PATH_EMPTY) break;
|
|
if (err_is_fail(err)) return err;
|
|
|
|
FAT32_DEBUG_PRINTF("Got dirname: '%.8s.%3s'", short_name, short_name_ext);
|
|
|
|
// we can only traverse directories
|
|
if (!(path_resolve_result.directory_entry.attributes & FAT32_ATTR_DIRECTORY)) {
|
|
FAT32_DEBUG_PRINT("Not a directory on the path");
|
|
return FS_ERR_NOTDIR;
|
|
}
|
|
err = fat32_search_in_directory(fat32, &path_resolve_result.directory_entry, short_name, short_name_ext, &path_resolve_result);
|
|
if (err_is_fail(err)) return err;
|
|
}
|
|
|
|
// we parsed the full path at this point
|
|
FAT32_DEBUG_PRINT("successfully resolved");
|
|
|
|
if (return_path_resolve_result != NULL) {
|
|
*return_path_resolve_result = path_resolve_result;
|
|
}
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_resolve_path_partial" "_post");
|
|
#endif
|
|
return SYS_ERR_OK;
|
|
}
|
|
/**
|
|
* @brief try to resolve the given path to a directory entry reference
|
|
*/
|
|
static errval_t fat32_resolve_path(
|
|
struct fat32 *fat32,
|
|
const char *path,
|
|
struct fat32_path_resolve_result *return_path_resolve_result
|
|
) {
|
|
return fat32_resolve_path_partial(fat32, path, path + strlen(path), return_path_resolve_result);
|
|
}
|
|
|
|
__attribute__((__used__))
|
|
/**
|
|
* @brief get the point in the string where the last element of the path starts
|
|
*/
|
|
static errval_t fat32_find_basename(const char *path, const char **basename) {
|
|
assert(path != NULL);
|
|
assert(basename != NULL);
|
|
|
|
// get the end of the path
|
|
size_t path_len = strlen(path);
|
|
const char* path_end = path + path_len - 1;
|
|
|
|
// skip potential trailing separators
|
|
while (path_end >= path && *path_end == FS_PATH_SEP) --path_end;
|
|
|
|
// if we ended up at the start of the path then there is no basename
|
|
if (path_end == path) return FAT_ERR_PATH_EMPTY;
|
|
|
|
// pass over the basename
|
|
while (path_end >= path && *path_end != FS_PATH_SEP) --path_end;
|
|
|
|
// plus one because we don't want the preceding slash in the basename
|
|
*basename = path_end + 1;
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief create a new handle and register it with the block driver
|
|
*/
|
|
static errval_t fat32_create_handle(struct fat32 *fat32, struct fat32_handle **handle, bool is_dir, uint32_t first_cluster, struct fat32_directory_entry_ref directory_entry_ref) {
|
|
assert(first_cluster > 1);
|
|
|
|
errval_t err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_create_handle" "_pre");
|
|
#endif
|
|
|
|
*handle = malloc(sizeof(struct fat32_handle));
|
|
if (*handle == NULL) return LIB_ERR_MALLOC_FAIL;
|
|
|
|
(*handle)->is_dir = is_dir;
|
|
(*handle)->first_cluster = first_cluster;
|
|
(*handle)->current_cluster = first_cluster;
|
|
(*handle)->current_cluster_index_in_chain = 0;
|
|
(*handle)->byte_offset = 0;
|
|
(*handle)->directory_entry_ref = directory_entry_ref;
|
|
|
|
err = fat32->register_handle_fn(fat32_id_from_directory_entry_ref(directory_entry_ref));
|
|
if (err_is_fail(err)) return err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_create_handle" "_post");
|
|
#endif
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief free the given handle and unregister it with the block driver
|
|
*/
|
|
static errval_t fat32_free_handle(struct fat32 *fat32, struct fat32_handle *handle) {
|
|
errval_t err;
|
|
|
|
err = fat32->unregister_handle_fn(fat32_id_from_directory_entry_ref(handle->directory_entry_ref));
|
|
if (err_is_fail(err)) return err;
|
|
|
|
free(handle);
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
static errval_t fat32_handle_update_current_cluster(struct fat32 *fat32, struct fat32_handle *handle, bool extend) {
|
|
errval_t err;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_handle_update_current_cluster" "_pre");
|
|
#endif
|
|
|
|
size_t cluster_index = handle->byte_offset / fat32->bytes_per_cluster;
|
|
|
|
// FAT32_DEBUG_PRINTF("target cluster %lu for offset %lu", cluster_index, handle->byte_offset);
|
|
|
|
if (handle->current_cluster_index_in_chain == cluster_index) return SYS_ERR_OK;
|
|
|
|
// if we are too far in the chain then we have to re-resolve from the start
|
|
if (handle->current_cluster_index_in_chain > cluster_index) {
|
|
handle->current_cluster_index_in_chain = 0;
|
|
handle->current_cluster = handle->first_cluster;
|
|
}
|
|
|
|
// advance in the cluster chain until we reach the desired index or run out of clusters
|
|
assert(!fat32_is_end_of_cluster_chain(handle->current_cluster));
|
|
while(handle->current_cluster_index_in_chain < cluster_index) {
|
|
if (extend) {
|
|
err = fat32_next_cluster_in_chain_or_extend(fat32, handle->current_cluster, &handle->current_cluster);
|
|
if (err_is_fail(err)) return err;
|
|
assert(!fat32_is_end_of_cluster_chain(handle->current_cluster));
|
|
} else {
|
|
uint32_t next_cluster;
|
|
err = fat32_next_cluster_in_chain(fat32, handle->current_cluster, &next_cluster);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
if(fat32_is_end_of_cluster_chain(next_cluster)) {
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_handle_update_current_cluster" "_post");
|
|
#endif
|
|
return FS_ERR_INDEX_BOUNDS;
|
|
}
|
|
handle->current_cluster = next_cluster;
|
|
}
|
|
++handle->current_cluster_index_in_chain;
|
|
}
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_handle_update_current_cluster" "_post");
|
|
#endif
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Verify contents of the bootsector of the SD Card
|
|
*
|
|
* Note: This is only valid for very specific FAT32 configurations used in this course
|
|
*
|
|
* @param bpb struct to verify
|
|
*/
|
|
__attribute__((__unused__))
|
|
static void fat32_verify(struct bpb *bpb){
|
|
struct bpb_fat32 *fat32 = &bpb->bpb_fat.bpb_fat32;
|
|
|
|
// NOTE rueegges: course specific
|
|
assert(bpb->jmp_boot[0] == 235);
|
|
assert(bpb->jmp_boot[2] == 144);
|
|
// NOTE rueegges: only assert for specific fat init in course
|
|
assert(bpb->bytes_per_sector == FAT32_BLOCK_SIZE);
|
|
// NOTE rueegges: only assert for specific fat init in course
|
|
assert(bpb->sectors_per_cluster == 8);
|
|
assert(bpb->reserved_sector_count > 0);
|
|
// NOTE rueegges: we assume FAT32 file system
|
|
assert(bpb->root_entry_count == 0);
|
|
// NOTE rueegges: we assume FAT32 file system
|
|
assert(bpb->total_sector_16_count == 0);
|
|
assert(bpb->fat_sectors_16_count == 0);
|
|
// NOTE rueegges: we assume FAT32 file system
|
|
assert(bpb->total_sector_32_count > 0);
|
|
|
|
assert(fat32->fs_version== 0);
|
|
assert(fat32->root_cluster_number > 0);
|
|
assert(fat32->fs_info_sector_number < bpb->reserved_sector_count);
|
|
|
|
// bpb has to end with 0x55 and then 0xAA
|
|
assert(*(((uint8_t *)bpb) + 510) == 0x55);
|
|
assert(*(((uint8_t *)bpb) + 511) == 0xAA);
|
|
}
|
|
|
|
__attribute__((__unused__))
|
|
/**
|
|
* @brief print some information about the file system useful for early debugging
|
|
*/
|
|
static void fat32_print(struct bpb *bpb){
|
|
struct bpb_fat32 *fat32 = &bpb->bpb_fat.bpb_fat32;
|
|
|
|
FAT32_DEBUG_PRINT("BPB");
|
|
FAT32_DEBUG_PRINTF(" boot_jump: 0x%x%x%x", bpb->jmp_boot[2], bpb->jmp_boot[1], bpb->jmp_boot[0]);
|
|
FAT32_DEBUG_PRINTF(" oem_name: %.8s", bpb->oem_name);
|
|
FAT32_DEBUG_PRINTF(" bytes_per_sector: %u", bpb->bytes_per_sector);
|
|
FAT32_DEBUG_PRINTF(" sectors_per_cluster: %u", bpb->sectors_per_cluster);
|
|
FAT32_DEBUG_PRINTF(" reserved_sector_count: %u", bpb->reserved_sector_count);
|
|
FAT32_DEBUG_PRINTF(" number_fats: %u", bpb->fat_count);
|
|
FAT32_DEBUG_PRINTF(" root_entry_count: %u", bpb->root_entry_count);
|
|
FAT32_DEBUG_PRINTF(" total_sector_16_count: %u", bpb->total_sector_16_count);
|
|
FAT32_DEBUG_PRINTF(" media: 0x%x", bpb->media);
|
|
FAT32_DEBUG_PRINTF(" fat_sectors_16_count: %u", bpb->fat_sectors_16_count);
|
|
FAT32_DEBUG_PRINTF(" sectors_per_track: %u", bpb->sectors_per_track);
|
|
FAT32_DEBUG_PRINTF(" number_of_heads: %u", bpb->number_of_heads);
|
|
FAT32_DEBUG_PRINTF(" hidden_sector_count: %u", bpb->hidden_sector_count);
|
|
FAT32_DEBUG_PRINTF(" total_sector_32_count: %u", bpb->total_sector_32_count);
|
|
|
|
FAT32_DEBUG_PRINT("FAT32");
|
|
FAT32_DEBUG_PRINTF(" fat_sectors_32_count: %u", fat32->fat_sectors_32_count);
|
|
FAT32_DEBUG_PRINTF(" ext_flag_active_fat: %u", fat32->ext_flag_active_fat);
|
|
FAT32_DEBUG_PRINTF(" ext_flag_mirrored: %u", fat32->ext_flag_mirrored);
|
|
FAT32_DEBUG_PRINTF(" fs_version: %u", fat32->fs_version);
|
|
FAT32_DEBUG_PRINTF(" root_cluster_number: %u", fat32->root_cluster_number);
|
|
FAT32_DEBUG_PRINTF(" fs_info_cluster_number: %u", fat32->fs_info_sector_number);
|
|
FAT32_DEBUG_PRINTF(" backup_boot_sector_number: %u", fat32->backup_boot_sector_number);
|
|
FAT32_DEBUG_PRINTF(" drive_number: %u", fat32->drive_number);
|
|
FAT32_DEBUG_PRINTF(" extended_boot_signature: 0x%x", fat32->extended_boot_signature);
|
|
if (fat32->extended_boot_signature == 0x29) {
|
|
FAT32_DEBUG_PRINTF(" volume_serial_number: %u", fat32->volume_serial_number);
|
|
FAT32_DEBUG_PRINTF(" volume_label: %.11s", fat32->volume_label);
|
|
FAT32_DEBUG_PRINTF(" file_system_type: %.8s", fat32->file_system_type);
|
|
}
|
|
}
|
|
|
|
//
|
|
/**
|
|
* @brief Check if the file system is FAT32.
|
|
* The authors of the FAT32 specification are very strict that it MUST be done exactly this way so here we go.
|
|
*/
|
|
static bool is_fat32(struct bpb *bpb) {
|
|
// FAT32_DEBUG_PRINTF("check fat type");
|
|
uint32_t root_dir_sectors = ((bpb->root_entry_count * 32) + (bpb->bytes_per_sector - 1)) / bpb->bytes_per_sector;
|
|
|
|
uint32_t fat_size;
|
|
if (bpb->fat_sectors_16_count != 0)
|
|
fat_size = bpb->fat_sectors_16_count;
|
|
else
|
|
fat_size = bpb->bpb_fat.bpb_fat32.fat_sectors_32_count;
|
|
|
|
uint32_t tot_sec;
|
|
if (bpb->total_sector_16_count != 0)
|
|
tot_sec = bpb->total_sector_16_count;
|
|
else
|
|
tot_sec = bpb->total_sector_32_count;
|
|
|
|
// NOTE: the start of the data region (first_data_sector) is the first sector of cluster 2
|
|
uint32_t first_data_sector = bpb->reserved_sector_count + (bpb->fat_count * fat_size) + root_dir_sectors;
|
|
|
|
// NOTE: the number of data sectors
|
|
uint32_t data_sec = tot_sec - first_data_sector;
|
|
// NOTE: the number of clusters
|
|
uint32_t count_of_clusters = data_sec / bpb->sectors_per_cluster;
|
|
if (count_of_clusters < 4085) {
|
|
FAT32_DEBUG_PRINT("Got FAT12 file system");
|
|
return false;
|
|
} else if (count_of_clusters < 65525) {
|
|
FAT32_DEBUG_PRINT("Got FAT16 file system");
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
errval_t fat32_init(
|
|
struct fat32 **return_fat32,
|
|
fat_block_read_fn_t read_object_fn,
|
|
fat_block_write_fn_t write_object_fn,
|
|
fat_block_lock_fn_t lock_fn,
|
|
fat_block_unlock_fn_t unlock_fn,
|
|
fat_block_register_handle_fn_t register_handle_fn,
|
|
fat_block_unregister_handle_fn_t unregister_handle_fn,
|
|
fat_block_count_handles_fn_t count_handles_fn,
|
|
char *mount
|
|
) {
|
|
errval_t err;
|
|
|
|
// FAT32_DEBUG_PRINTF("[fat32_init]");
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_init" "_pre");
|
|
#endif
|
|
|
|
struct fat32 *fat32 = malloc(sizeof(struct fat32));
|
|
if (fat32 == NULL) return LIB_ERR_MALLOC_FAIL;
|
|
fat32->read_object_fn = read_object_fn;
|
|
fat32->write_object_fn = write_object_fn;
|
|
fat32->lock_fn = lock_fn;
|
|
fat32->unlock_fn = unlock_fn;
|
|
fat32->register_handle_fn = register_handle_fn;
|
|
fat32->unregister_handle_fn = unregister_handle_fn;
|
|
fat32->count_handles_fn = count_handles_fn;
|
|
fat32->mount = mount;
|
|
|
|
struct bpb *bpb = malloc(FAT32_BLOCK_SIZE);
|
|
if (bpb == NULL) return LIB_ERR_MALLOC_FAIL;
|
|
err = fat32->read_object_fn(0, 0, FAT32_BLOCK_SIZE, bpb);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// FAT type determination
|
|
if (!is_fat32(bpb)) {
|
|
return FAT_ERR_TYPE_NOT_SUPPORTED;
|
|
}
|
|
|
|
// initialize struct contents
|
|
// FAT32_DEBUG_PRINTF("initialize state");
|
|
fat32->bytes_per_sector = bpb->bytes_per_sector;
|
|
fat32->sectors_per_cluster = bpb->sectors_per_cluster;
|
|
fat32->reserved_sector_count = bpb->reserved_sector_count;
|
|
fat32->fat_count = bpb->fat_count;
|
|
fat32->total_sector_32_count = bpb->total_sector_32_count;
|
|
fat32->fat_sectors_32_count = bpb->bpb_fat.bpb_fat32.fat_sectors_32_count;
|
|
fat32->ext_flag_active_fat = bpb->bpb_fat.bpb_fat32.ext_flag_active_fat;
|
|
fat32->ext_flag_mirrored = bpb->bpb_fat.bpb_fat32.ext_flag_mirrored;
|
|
fat32->root_cluster_number = bpb->bpb_fat.bpb_fat32.root_cluster_number;
|
|
|
|
fat32->first_data_sector = fat32->reserved_sector_count + (bpb->fat_count * fat32->fat_sectors_32_count);
|
|
fat32->max_cluster_number = ((bpb->total_sector_32_count - fat32->first_data_sector) / bpb->sectors_per_cluster) + 1;
|
|
fat32->bytes_per_cluster = fat32->bytes_per_sector * fat32->sectors_per_cluster;
|
|
|
|
// fat32_print(bpb);
|
|
fat32_verify(bpb);
|
|
|
|
// FAT32_DEBUG_PRINTF("verified FAT32");
|
|
|
|
*return_fat32 = fat32;
|
|
|
|
#ifdef FAT32_PERFORMANCE
|
|
perf_add_now(&fat32_perf_context, "fat32_init" "_post");
|
|
#endif
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
static errval_t _fat32_mkdir(struct fat32 *fat32, const char *path) {
|
|
// FAT32_DEBUG_PRINTF("[fat32_mkdir]");
|
|
errval_t err;
|
|
|
|
// split the path into the part that should exist and the part to create
|
|
const char *basename;
|
|
err = fat32_find_basename(path, &basename);
|
|
// an error in finding means that the basename is empty and thus we are trying to create the root dir
|
|
if (err_is_fail(err)) return FS_ERR_EXISTS;
|
|
|
|
// FAT32_DEBUG_PRINTF("basename: %s", basename);
|
|
|
|
// resolve the parent path
|
|
struct fat32_path_resolve_result parent_path_resolve_result;
|
|
err = fat32_resolve_path_partial(fat32, path, basename, &parent_path_resolve_result);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// FAT32_DEBUG_PRINTF("got parent dir");
|
|
|
|
char short_name[FAT32_SHORTNAME_SIZE];
|
|
char short_name_ext[FAT32_SHORTNAME_EXT_SIZE];
|
|
err = fat32_extract_next_path_element(
|
|
basename,
|
|
basename + strlen(basename),
|
|
NULL,
|
|
short_name,
|
|
short_name_ext
|
|
);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// check if the entry already exists
|
|
// FAT32_DEBUG_PRINTF("checking if dir exists already");
|
|
err = fat32_search_in_directory(
|
|
fat32,
|
|
&parent_path_resolve_result.directory_entry,
|
|
short_name,
|
|
short_name_ext,
|
|
NULL
|
|
);
|
|
if (err_is_ok(err)) return FS_ERR_EXISTS;
|
|
if (err_no(err) != FS_ERR_NOTFOUND) return err;
|
|
|
|
// iterate throught the parent directory to find a free entry
|
|
// FAT32_DEBUG_PRINTF("allocating directory");
|
|
struct fat32_path_resolve_result free_path_resolve_result;
|
|
err = fat32_allocate_directory_entry(
|
|
fat32,
|
|
&parent_path_resolve_result.directory_entry,
|
|
&free_path_resolve_result
|
|
);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// find a free cluster for the directory
|
|
// FAT32_DEBUG_PRINTF("get free cluster");
|
|
uint32_t free_cluster_number;
|
|
err = fat32_alloc_cluster(fat32, &free_cluster_number);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// zero out cluster
|
|
// FAT32_DEBUG_PRINTF("zeroing");
|
|
err = fat32_zero_cluster(fat32, free_cluster_number);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// get time information for directory entry creation
|
|
uint16_t write_date, write_time;
|
|
fat32_get_time_information(&write_date, &write_time);
|
|
|
|
// create the dot and dotdot entries
|
|
struct fat32_directory_entry dot_directory_entry = {
|
|
.short_name = ". ",
|
|
.short_name_ext = " ",
|
|
.attributes = FAT32_ATTR_DIRECTORY,
|
|
.first_cluster_number_low_word = fat32_cluster_number_low_word(free_cluster_number),
|
|
.first_cluster_number_high_word = fat32_cluster_number_high_word(free_cluster_number),
|
|
.file_size = 0,
|
|
.write_date = write_date,
|
|
.write_time = write_time,
|
|
};
|
|
struct fat32_directory_entry dotdot_directory_entry = {
|
|
.short_name = ".. ",
|
|
.short_name_ext = " ",
|
|
.attributes = FAT32_ATTR_DIRECTORY,
|
|
.first_cluster_number_low_word = parent_path_resolve_result.directory_entry.first_cluster_number_low_word,
|
|
.first_cluster_number_high_word = parent_path_resolve_result.directory_entry.first_cluster_number_high_word,
|
|
.file_size = 0,
|
|
.write_date = write_date,
|
|
.write_time = write_time,
|
|
};
|
|
|
|
// store the dot and dotdot entries to disk
|
|
struct fat32_directory_entry_ref ref = {
|
|
.sector_number = fat32_first_sector_of_cluster(fat32, free_cluster_number),
|
|
};
|
|
ref.index_in_sector = 0;
|
|
err = fat32_write_directory_entry(fat32, ref, &dot_directory_entry);
|
|
if (err_is_fail(err)) return err;
|
|
ref.index_in_sector = 1;
|
|
err = fat32_write_directory_entry(fat32, ref, &dotdot_directory_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// FAT32_DEBUG_PRINTF("Create the directory entry");
|
|
// store the directory in the space in the parent directory we found
|
|
struct fat32_directory_entry directory_entry = {
|
|
.attributes = FAT32_ATTR_DIRECTORY,
|
|
.first_cluster_number_low_word = fat32_cluster_number_low_word(free_cluster_number),
|
|
.first_cluster_number_high_word = fat32_cluster_number_high_word(free_cluster_number),
|
|
.file_size = 0,
|
|
.write_date = write_date,
|
|
.write_time = write_time,
|
|
};
|
|
err = fat32_extract_next_path_element(basename, basename + strlen(basename), NULL, directory_entry.short_name, directory_entry.short_name_ext);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// store the new directory entry to disk
|
|
err = fat32_write_directory_entry(fat32, free_path_resolve_result.directory_entry_ref, &directory_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// FAT32_DEBUG_PRINTF("done");
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
errval_t fat32_mkdir(struct fat32 *fat32, const char *path) {
|
|
errval_t err;
|
|
|
|
// do the mountpoint check before taking the lock to avoid deadlocks
|
|
const char *path_without_mount;
|
|
err = fat32_skip_mount_point(fat32, path, &path_without_mount);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
FAT32_RUN_LOCKED(_fat32_mkdir(fat32, path_without_mount))
|
|
}
|
|
|
|
static errval_t _fat32_opendir(struct fat32 *fat32, const char *path, struct fat32_handle **dir_handle) {
|
|
assert(fat32 != NULL);
|
|
assert(path != NULL);
|
|
assert(dir_handle != NULL);
|
|
|
|
errval_t err;
|
|
|
|
// FAT32_DEBUG_PRINTF("[fat32_opendir]");
|
|
|
|
// resolve the path to a directory entry
|
|
struct fat32_path_resolve_result path_resolve_result;
|
|
err = fat32_resolve_path(fat32, path, &path_resolve_result);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// check that it is a directory and not a file
|
|
if (!(path_resolve_result.directory_entry.attributes & FAT32_ATTR_DIRECTORY)) {
|
|
return FS_ERR_NOTDIR;
|
|
}
|
|
|
|
err = fat32_create_handle(
|
|
fat32,
|
|
dir_handle,
|
|
true,
|
|
fat32_directory_entry_to_first_cluster(&path_resolve_result.directory_entry),
|
|
path_resolve_result.directory_entry_ref
|
|
);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
errval_t fat32_opendir(struct fat32 *fat32, const char *path, struct fat32_handle **dir_handle) {
|
|
errval_t err;
|
|
|
|
// do the mountpoint check before taking the lock to avoid deadlocks
|
|
const char *path_without_mount;
|
|
err = fat32_skip_mount_point(fat32, path, &path_without_mount);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
FAT32_RUN_LOCKED(_fat32_opendir(fat32, path_without_mount, dir_handle))
|
|
}
|
|
|
|
static errval_t _fat32_closedir(struct fat32 *fat32, struct fat32_handle *dir_handle) {
|
|
FAT32_DEBUG_PRINT("[fat32_closedir]");
|
|
if (!dir_handle->is_dir) return FS_ERR_INVALID_FH;
|
|
|
|
return fat32_free_handle(fat32, dir_handle);
|
|
}
|
|
errval_t fat32_closedir(struct fat32 *fat32, struct fat32_handle *dir_handle) {
|
|
errval_t err;
|
|
FAT32_RUN_LOCKED(_fat32_closedir(fat32, dir_handle))
|
|
}
|
|
|
|
static errval_t _fat32_stat(struct fat32 *fat32, struct fat32_handle *handle, struct fs_fileinfo *fileinfo) {
|
|
// FAT32_DEBUG_PRINTF("[fat32_stat]");
|
|
assert(fileinfo != NULL);
|
|
|
|
errval_t err;
|
|
|
|
if (handle->is_dir) {
|
|
fileinfo->type = FS_DIRECTORY;
|
|
// NOTE rueegges: This should probably be changed to sum over all files in subtree or similar
|
|
fileinfo->size = 0;
|
|
} else {
|
|
struct fat32_directory_entry directory_entry;
|
|
err = fat32_read_directory_entry(fat32, handle->directory_entry_ref, &directory_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
fileinfo->type = FS_FILE;
|
|
fileinfo->size = directory_entry.file_size;
|
|
}
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
errval_t fat32_stat(struct fat32 *fat32, struct fat32_handle *handle, struct fs_fileinfo *fileinfo) {
|
|
errval_t err;
|
|
FAT32_RUN_LOCKED(_fat32_stat(fat32, handle, fileinfo))
|
|
}
|
|
|
|
static errval_t _fat32_readdir(struct fat32 *fat32, struct fat32_handle *dir_handle, char **name) {
|
|
// FAT32_DEBUG_PRINTF("[fat32_readdir]");
|
|
errval_t err;
|
|
|
|
if (dir_handle == NULL || !dir_handle->is_dir) {
|
|
return FS_ERR_INVALID_FH;
|
|
}
|
|
|
|
// follow the chain if the current cluster is not defined
|
|
err = fat32_handle_update_current_cluster(fat32, dir_handle, false);
|
|
if(err_is_fail(err)) return err;
|
|
|
|
*name = NULL;
|
|
|
|
// search for the next file in the directory starting at the current byte offset
|
|
while (!fat32_is_end_of_cluster_chain(dir_handle->current_cluster)) {
|
|
uint32_t first_sector_of_cluster = fat32_first_sector_of_cluster(fat32, dir_handle->current_cluster);
|
|
for (uint32_t sector_offset = (dir_handle->byte_offset / fat32->bytes_per_sector) % fat32->sectors_per_cluster; sector_offset < fat32->sectors_per_cluster; ++sector_offset) {
|
|
|
|
uint32_t current_sector = first_sector_of_cluster + sector_offset;
|
|
|
|
struct fat32_directory_entry directory_entries[FAT32_DIRECTORY_ENTRIES_PER_SECTOR];
|
|
err = fat32->read_object_fn(current_sector, 0, FAT32_BLOCK_SIZE, directory_entries);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
for (size_t directory_index = (dir_handle->byte_offset % fat32->bytes_per_sector) / sizeof(struct fat32_directory_entry); directory_index < FAT32_DIRECTORY_ENTRIES_PER_SECTOR; ++directory_index) {
|
|
// increment early in case we return
|
|
dir_handle->byte_offset += sizeof(struct fat32_directory_entry);
|
|
|
|
if (directory_entries[directory_index].short_name[0] == 0xE5) continue;
|
|
if (directory_entries[directory_index].short_name[0] == 0x00) return FS_ERR_INDEX_BOUNDS;
|
|
// hide hidden entries
|
|
if (directory_entries[directory_index].attributes & FAT32_ATTR_HIDDEN) continue;
|
|
|
|
// found the next directory
|
|
*name = fat32_directory_entry_get_nice_name(&directory_entries[directory_index]);
|
|
if(*name == NULL) return LIB_ERR_MALLOC_FAIL;
|
|
return SYS_ERR_OK;
|
|
}
|
|
}
|
|
err = fat32_handle_update_current_cluster(fat32, dir_handle, false);
|
|
if(err_is_fail(err)) return err;
|
|
}
|
|
|
|
return FS_ERR_INDEX_BOUNDS;
|
|
}
|
|
errval_t fat32_readdir(struct fat32 *fat32, struct fat32_handle *dir_handle, char **name) {
|
|
errval_t err;
|
|
FAT32_RUN_LOCKED(_fat32_readdir(fat32, dir_handle, name))
|
|
}
|
|
|
|
static errval_t _fat32_rmdir(struct fat32 *fat32, const char *path) {
|
|
// FAT32_DEBUG_PRINTF("[fat32_rmdir]");
|
|
errval_t err;
|
|
|
|
struct fat32_path_resolve_result path_resolve_result;
|
|
err = fat32_resolve_path(fat32, path, &path_resolve_result);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// this function can only be used to delete directories
|
|
if (!(path_resolve_result.directory_entry.attributes & FAT32_ATTR_DIRECTORY)) {
|
|
return FS_ERR_NOTDIR;
|
|
}
|
|
|
|
// we don't allow deleting the root directory
|
|
if (path_resolve_result.directory_entry_ref.sector_number == UINT32_MAX || path_resolve_result.directory_entry_ref.index_in_sector == UINT32_MAX) {
|
|
return FS_ERR_NOTEMPTY;
|
|
}
|
|
|
|
// we refuse to delete non-empty directories
|
|
err = fat32_check_directory_empty(fat32, &path_resolve_result.directory_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// try to delete the directory entry
|
|
err = fat32_free_directory_entry(fat32, path_resolve_result.directory_entry_ref);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
errval_t fat32_rmdir(struct fat32 *fat32, const char *path) {
|
|
errval_t err;
|
|
|
|
// do the mountpoint check before taking the lock to avoid deadlocks
|
|
const char *path_without_mount;
|
|
err = fat32_skip_mount_point(fat32, path, &path_without_mount);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
FAT32_RUN_LOCKED(_fat32_rmdir(fat32, path_without_mount))
|
|
}
|
|
|
|
static errval_t _fat32_rm(struct fat32 *fat32, const char *path) {
|
|
// FAT32_DEBUG_PRINTF("[fat32_rm]");
|
|
errval_t err;
|
|
|
|
struct fat32_path_resolve_result path_resolve_result;
|
|
err = fat32_resolve_path(fat32, path, &path_resolve_result);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// only files can be deleted this way
|
|
if (path_resolve_result.directory_entry.attributes & FAT32_ATTR_DIRECTORY) {
|
|
return FS_ERR_NOTFILE;
|
|
}
|
|
|
|
if (path[strlen(path) - 1] == FS_PATH_SEP) {
|
|
return FS_ERR_NOTFILE;
|
|
}
|
|
|
|
// free directory entry
|
|
err = fat32_free_directory_entry(fat32, path_resolve_result.directory_entry_ref);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
errval_t fat32_rm(struct fat32 *fat32, const char *path) {
|
|
errval_t err;
|
|
|
|
// do the mountpoint check before taking the lock to avoid deadlocks
|
|
const char *path_without_mount;
|
|
err = fat32_skip_mount_point(fat32, path, &path_without_mount);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
FAT32_RUN_LOCKED(_fat32_rm(fat32, path_without_mount))
|
|
}
|
|
|
|
static errval_t _fat32_fcreate(struct fat32 *fat32, const char *path, struct fat32_handle **file_handle) {
|
|
// FAT32_DEBUG_PRINTF("[fat32_fcreate]");
|
|
|
|
// resolve path to directory cluster
|
|
errval_t err;
|
|
|
|
// split the path into the part that should exist and the part to create
|
|
const char *basename;
|
|
err = fat32_find_basename(path, &basename);
|
|
// if the basename is empty we are trying to create the root dir but it already exists
|
|
if (err_no(err) == FAT_ERR_PATH_EMPTY) return FS_ERR_NOTFILE;
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// FAT32_DEBUG_PRINTF("basename: %s", basename);
|
|
|
|
// resolve the parent path
|
|
struct fat32_path_resolve_result parent_path_resolve_result;
|
|
err = fat32_resolve_path_partial(fat32, path, basename, &parent_path_resolve_result);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
char short_name[FAT32_SHORTNAME_SIZE];
|
|
char short_name_ext[FAT32_SHORTNAME_EXT_SIZE];
|
|
err = fat32_extract_next_path_element(
|
|
basename,
|
|
basename + strlen(basename),
|
|
NULL,
|
|
short_name,
|
|
short_name_ext
|
|
);
|
|
if (err_is_fail(err)) return err;
|
|
// check if the entry already exists
|
|
err = fat32_search_in_directory(
|
|
fat32,
|
|
&parent_path_resolve_result.directory_entry,
|
|
short_name,
|
|
short_name_ext,
|
|
NULL
|
|
);
|
|
err = fat32_resolve_path(fat32, path, NULL);
|
|
if (err_is_ok(err)) return FS_ERR_EXISTS;
|
|
if (err_no(err) != FS_ERR_NOTFOUND) return err;
|
|
|
|
// make sure the path is a valid file path (i.e. does not end with a slash) (emulating behaviour seen on linux)
|
|
if (path[strlen(path) - 1] == FS_PATH_SEP) return FS_ERR_NOTFILE;
|
|
|
|
// iterate throught the parent directory to find a free entry
|
|
struct fat32_path_resolve_result free_path_resolve_result;
|
|
err = fat32_allocate_directory_entry(
|
|
fat32,
|
|
&parent_path_resolve_result.directory_entry,
|
|
&free_path_resolve_result
|
|
);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// find a free cluster for the file
|
|
uint32_t free_cluster_number;
|
|
err = fat32_alloc_cluster(fat32, &free_cluster_number);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// FAT32_DEBUG_PRINTF("Get time information");
|
|
|
|
// get time information for directory entry creation
|
|
uint16_t write_date, write_time;
|
|
fat32_get_time_information(&write_date, &write_time);
|
|
|
|
// FAT32_DEBUG_PRINTF("Create the directory entry");
|
|
|
|
// store the directory in the space in the parent directory we found
|
|
assert(free_cluster_number > 1);
|
|
struct fat32_directory_entry directory_entry = {
|
|
.attributes = FAT32_ATTR_ARCHIVE, // mark the file for backup utilities
|
|
.first_cluster_number_low_word = fat32_cluster_number_low_word(free_cluster_number),
|
|
.first_cluster_number_high_word = fat32_cluster_number_high_word(free_cluster_number),
|
|
.file_size = 0,
|
|
.write_date = write_date,
|
|
.write_time = write_time,
|
|
};
|
|
err = fat32_extract_next_path_element(basename, basename + strlen(basename), NULL, directory_entry.short_name, directory_entry.short_name_ext);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
err = fat32->write_object_fn(free_path_resolve_result.directory_entry_ref.sector_number, free_path_resolve_result.directory_entry_ref.index_in_sector * sizeof(struct fat32_directory_entry), sizeof(struct fat32_directory_entry), &directory_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// FAT32_DEBUG_PRINTF("done");
|
|
|
|
err = fat32_create_handle(
|
|
fat32,
|
|
file_handle,
|
|
false,
|
|
free_cluster_number,
|
|
free_path_resolve_result.directory_entry_ref
|
|
);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// FAT32_DEBUG_PRINTF("first_cluster: %u", (*file_handle)->first_cluster);
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
errval_t fat32_fcreate(struct fat32 *fat32, const char *path, struct fat32_handle **file_handle) {
|
|
errval_t err;
|
|
|
|
// do the mountpoint check before taking the lock to avoid deadlocks
|
|
const char *path_without_mount;
|
|
err = fat32_skip_mount_point(fat32, path, &path_without_mount);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
FAT32_RUN_LOCKED(_fat32_fcreate(fat32, path_without_mount, file_handle))
|
|
}
|
|
|
|
static errval_t _fat32_fopen(struct fat32 *fat32, const char *path, struct fat32_handle **file_handle) {
|
|
assert(fat32 != NULL);
|
|
assert(path != NULL);
|
|
assert(file_handle != NULL);
|
|
|
|
FAT32_DEBUG_PRINT("start");
|
|
|
|
errval_t err;
|
|
|
|
struct fat32_path_resolve_result path_resolve_result;
|
|
err = fat32_resolve_path(fat32, path, &path_resolve_result);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// make sure the path is a valid file path (i.e. does not end with a slash) (emulating behaviour seen on linux)
|
|
if (path[strlen(path) - 1] == FS_PATH_SEP) return FS_ERR_NOTFILE;
|
|
|
|
if (path_resolve_result.directory_entry.attributes & FAT32_ATTR_DIRECTORY) return FS_ERR_NOTFILE;
|
|
|
|
err = fat32_create_handle(
|
|
fat32,
|
|
file_handle,
|
|
false,
|
|
fat32_directory_entry_to_first_cluster(&path_resolve_result.directory_entry),
|
|
path_resolve_result.directory_entry_ref
|
|
);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
errval_t fat32_fopen(struct fat32 *fat32, const char *path, struct fat32_handle **file_handle) {
|
|
errval_t err;
|
|
|
|
// do the mountpoint check before taking the lock to avoid deadlocks
|
|
const char *path_without_mount;
|
|
err = fat32_skip_mount_point(fat32, path, &path_without_mount);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
FAT32_RUN_LOCKED(_fat32_fopen(fat32, path_without_mount, file_handle))
|
|
}
|
|
|
|
static errval_t _fat32_fread(struct fat32 *fat32, struct fat32_handle *file_handle, void *buffer, size_t bytes, size_t *bytes_read) {
|
|
FAT32_DEBUG_PRINTF("on %p: %lu bytes from offset %lu", file_handle, bytes, file_handle->byte_offset);
|
|
errval_t err;
|
|
|
|
if (file_handle == NULL || file_handle->is_dir) return FS_ERR_INVALID_FH;
|
|
|
|
struct fat32_directory_entry directory_entry;
|
|
// FAT32_DEBUG_PRINTF("read directory entry");
|
|
err = fat32_read_directory_entry(fat32, file_handle->directory_entry_ref, &directory_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// trying to read outside of the file
|
|
if (bytes == 0 || file_handle->byte_offset >= directory_entry.file_size) {
|
|
*bytes_read = 0;
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
// calculate how much we can read until the end of the file
|
|
size_t bytes_till_eof = directory_entry.file_size - file_handle->byte_offset;
|
|
// FAT32_DEBUG_PRINTF("bytes_till_eof: %lu", bytes_till_eof);
|
|
size_t bytes_to_read = MIN(bytes, bytes_till_eof);
|
|
// if (bytes_to_read < bytes) {
|
|
// FAT32_DEBUG_PRINTF("attempted to read over end of file, reading only %lu/%lu", bytes_to_read, bytes);
|
|
// }
|
|
|
|
// make sure we have the correct current cluster
|
|
err = fat32_handle_update_current_cluster(fat32, file_handle, false);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// read until we have reached the desired amount of bytes
|
|
// where we read is determined by the current byte offset of the file handle
|
|
*bytes_read = 0;
|
|
while (true) {
|
|
// make sure we never try to look at the reserved clusters during traversal
|
|
assert(file_handle->current_cluster > 1);
|
|
assert(!fat32_is_end_of_cluster_chain(file_handle->current_cluster));
|
|
|
|
// if this occurs we have a broken filesystem
|
|
if (file_handle->current_cluster == 0x0FFFFFF7) {
|
|
return FAT_ERR_BAD_FS;
|
|
}
|
|
|
|
uint32_t first_sector_of_cluster = fat32_first_sector_of_cluster(fat32, file_handle->current_cluster);
|
|
for (uint32_t sector_offset = (file_handle->byte_offset / fat32->bytes_per_sector) % fat32->sectors_per_cluster; sector_offset < fat32->sectors_per_cluster; ++sector_offset) {
|
|
uint32_t current_sector = first_sector_of_cluster + sector_offset;
|
|
|
|
size_t offset_in_sector = file_handle->byte_offset % fat32->bytes_per_sector;
|
|
size_t bytes_left_in_sector = FAT32_BLOCK_SIZE - offset_in_sector;
|
|
size_t bytes_in_sector = MIN(bytes_left_in_sector, bytes_to_read - *bytes_read);
|
|
// FAT32_DEBUG_PRINTF("reading %lu from sector %u due to offset %u", bytes_in_sector, current_sector, sector_offset);
|
|
err = fat32->read_object_fn(current_sector, offset_in_sector, bytes_in_sector, (buffer + *bytes_read));
|
|
if (err_is_fail(err)) return err;
|
|
|
|
*bytes_read += bytes_in_sector;
|
|
file_handle->byte_offset += bytes_in_sector;
|
|
// cancel early if we already read enough
|
|
if (*bytes_read == bytes_to_read) {
|
|
// FAT32_DEBUG_PRINTF("file now at offset %lu", file_handle->byte_offset);
|
|
return SYS_ERR_OK;
|
|
}
|
|
}
|
|
// continue in the next cluster
|
|
err = fat32_handle_update_current_cluster(fat32, file_handle, false);
|
|
if(err_is_fail(err)) return err;
|
|
// we have checked in advance that this exists by checking the file size
|
|
assert(!fat32_is_end_of_cluster_chain(file_handle->current_cluster));
|
|
}
|
|
|
|
USER_PANIC("Unreachable code");
|
|
}
|
|
errval_t fat32_fread(struct fat32 *fat32, struct fat32_handle *file_handle, void *buffer, size_t bytes, size_t *bytes_read) {
|
|
errval_t err;
|
|
FAT32_RUN_LOCKED(_fat32_fread(fat32, file_handle, buffer, bytes, bytes_read))
|
|
}
|
|
|
|
static errval_t _fat32_fwrite(struct fat32 *fat32, struct fat32_handle *file_handle, const void *buffer, size_t bytes, size_t *bytes_written) {
|
|
// FAT32_DEBUG_PRINTF("%lu bytes", bytes);
|
|
errval_t err;
|
|
|
|
if (file_handle == NULL || file_handle->is_dir) return FS_ERR_INVALID_FH;
|
|
|
|
// FAT32 spec defines a maximum size of a file (a maximum number of bytes in a cluster chain to be precise)
|
|
// "your FAT file system driver must not allow a cluster chain to be created that is longer than 0x100000000 bytes,
|
|
// and the last byte of the last cluster in a chain that long cannot be allocated to the file" - FAT32 spec
|
|
if (file_handle->byte_offset + bytes >= FAT32_MAX_FILESIZE) {
|
|
return FAT_ERR_FILE_TOO_LONG;
|
|
}
|
|
|
|
struct fat32_directory_entry directory_entry;
|
|
err = fat32_read_directory_entry(fat32, file_handle->directory_entry_ref, &directory_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
// check if the directory entry is readonly
|
|
if (directory_entry.attributes & FAT32_ATTR_READ_ONLY) {
|
|
return FS_ERR_WRITE;
|
|
}
|
|
|
|
// make sure we have a valid current cluster
|
|
err = fat32_handle_update_current_cluster(fat32, file_handle, true);
|
|
|
|
// write the data to the file
|
|
*bytes_written = 0;
|
|
while (*bytes_written < bytes) {
|
|
// make sure we never try to look at the reserved clusters during traversal
|
|
// FAT32_DEBUG_PRINTF("Write in cluster: %u", file_handle->current_cluster);
|
|
assert(file_handle->current_cluster > 1);
|
|
assert(!fat32_is_end_of_cluster_chain(file_handle->current_cluster));
|
|
|
|
// if this occurs we have a broken filesystem
|
|
if (file_handle->current_cluster == 0x0FFFFFF7) {
|
|
return FAT_ERR_BAD_FS;
|
|
}
|
|
|
|
uint32_t first_sector_of_cluster = fat32_first_sector_of_cluster(fat32, file_handle->current_cluster);
|
|
for (uint32_t sector_offset = (file_handle->byte_offset / fat32->bytes_per_sector) % fat32->sectors_per_cluster; sector_offset < fat32->sectors_per_cluster; ++sector_offset) {
|
|
uint32_t current_sector = first_sector_of_cluster + sector_offset;
|
|
|
|
size_t offset_in_sector = file_handle->byte_offset % fat32->bytes_per_sector;
|
|
size_t bytes_left_in_sector = FAT32_BLOCK_SIZE - offset_in_sector;
|
|
size_t bytes_in_sector = MIN(bytes_left_in_sector, bytes - *bytes_written);
|
|
err = fat32->write_object_fn(current_sector, offset_in_sector, bytes_in_sector, (buffer + *bytes_written));
|
|
if (err_is_fail(err)) break;
|
|
|
|
*bytes_written += bytes_in_sector;
|
|
file_handle->byte_offset += bytes_in_sector;
|
|
// cancel early if we already read enough
|
|
if (*bytes_written == bytes) break;
|
|
}
|
|
// cancel early if we already read enough or had an error
|
|
if (err_is_fail(err)) break;
|
|
if (*bytes_written == bytes) break;
|
|
|
|
// continue in the next cluster
|
|
err = fat32_handle_update_current_cluster(fat32, file_handle, true);
|
|
if(err_is_fail(err)) return err;
|
|
// we extend the chain if we reach the end so we should never teach the end of the chain
|
|
assert(!fat32_is_end_of_cluster_chain(file_handle->current_cluster)); }
|
|
|
|
// if we had to increase the file size write it in the directory
|
|
if (directory_entry.file_size < file_handle->byte_offset) {
|
|
directory_entry.file_size = file_handle->byte_offset;
|
|
// FAT32_DEBUG_PRINTF("File got larger, is now: %lu", directory_entry.file_size);
|
|
}
|
|
|
|
// update last write time
|
|
uint16_t write_date, write_time;
|
|
fat32_get_time_information(&write_date, &write_time);
|
|
directory_entry.write_date = write_date;
|
|
directory_entry.write_time = write_time;
|
|
|
|
// set the archive flag
|
|
directory_entry.attributes |= FAT32_ATTR_ARCHIVE;
|
|
|
|
// update the directory information on disk
|
|
errval_t dir_update_err = fat32_write_directory_entry(
|
|
fat32,
|
|
file_handle->directory_entry_ref,
|
|
&directory_entry
|
|
);
|
|
if (err_is_fail(dir_update_err)) return dir_update_err;
|
|
|
|
return err;
|
|
}
|
|
errval_t fat32_fwrite(struct fat32 *fat32, struct fat32_handle *file_handle, const void *buffer, size_t bytes, size_t *bytes_written) {
|
|
errval_t err;
|
|
FAT32_RUN_LOCKED(_fat32_fwrite(fat32, file_handle, buffer, bytes, bytes_written))
|
|
}
|
|
|
|
static errval_t _fat32_fclose(struct fat32 *fat32, struct fat32_handle *file_handle) {
|
|
// FAT32_DEBUG_PRINTF("[fat32_fclose]");
|
|
|
|
if (file_handle == NULL || file_handle->is_dir) return FS_ERR_INVALID_FH;
|
|
|
|
return fat32_free_handle(fat32, file_handle);
|
|
}
|
|
errval_t fat32_fclose(struct fat32 *fat32, struct fat32_handle *file_handle) {
|
|
errval_t err;
|
|
FAT32_RUN_LOCKED(_fat32_fclose(fat32, file_handle))
|
|
}
|
|
|
|
errval_t fat32_tell(struct fat32 *fat32, struct fat32_handle *file_handle, size_t *pos) {
|
|
// FAT32_DEBUG_PRINTF("[fat32_tell]");
|
|
// get the current position if it is a file and return 0 otherwise
|
|
*pos = file_handle->is_dir ? 0 : file_handle->byte_offset;
|
|
// FAT32_DEBUG_PRINTF("@%lu", file_handle->byte_offset);
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
static errval_t _fat32_seek(struct fat32 *fat32, struct fat32_handle *file_handle, enum fs_seekpos whence, off_t offset) {
|
|
// FAT32_DEBUG_PRINTF("whence: %ld@%d", offset, whence);
|
|
errval_t err;
|
|
|
|
if (file_handle == NULL || file_handle->is_dir) return FS_ERR_INVALID_FH;
|
|
|
|
size_t absolute_offset = 0;
|
|
switch (whence) {
|
|
case FS_SEEK_SET:
|
|
assert(offset >= 0);
|
|
if (file_handle->is_dir) {
|
|
// allow restarting directory reading from the beginning
|
|
assert(offset == 0);
|
|
absolute_offset = 0;
|
|
} else {
|
|
absolute_offset = offset;
|
|
}
|
|
break;
|
|
|
|
case FS_SEEK_CUR:
|
|
if (file_handle->is_dir) {
|
|
assert(!"NYI");
|
|
} else {
|
|
assert(offset >= 0 || -offset <= file_handle->byte_offset);
|
|
absolute_offset = file_handle->byte_offset + offset;
|
|
}
|
|
|
|
break;
|
|
|
|
case FS_SEEK_END:
|
|
if (file_handle->is_dir) {
|
|
assert(!"NYI");
|
|
} else {
|
|
struct fat32_directory_entry directory_entry;
|
|
err = fat32_read_directory_entry(fat32, file_handle->directory_entry_ref, &directory_entry);
|
|
if (err_is_fail(err)) return err;
|
|
|
|
assert(offset >= 0 || -offset <= directory_entry.file_size);
|
|
absolute_offset = directory_entry.file_size + offset;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
USER_PANIC("invalid whence argument to ramfs seek");
|
|
}
|
|
|
|
file_handle->byte_offset = absolute_offset;
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
errval_t fat32_seek(struct fat32 *fat32, struct fat32_handle *file_handle, enum fs_seekpos whence, off_t offset) {
|
|
errval_t err;
|
|
FAT32_RUN_LOCKED(_fat32_seek(fat32, file_handle, whence, offset))
|
|
}
|