#include #include #include #include #include "fs_internal.h" #include // NOTE rueegges: uncomment to enable fat32 performance measurements #define FAT32_PERFORMANCE #ifdef FAT32_PERFORMANCE #include 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) { /*debug_printf("Locking in %s\n", __FUNCTION__);*/ 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; } 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; } } static uint64_t fat32_lock_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; } 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; } static errval_t fat32_zero_cluster(struct fat32 *fat32, uint32_t cluster_number) { errval_t err; assert(cluster_number > 1); // debug_printf("[fat32_zero_cluster] cluster_number: %u\n", cluster_number); // set some memory to zero uint8_t zero_mem[FAT32_BLOCK_SIZE] = {0}; 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; } return SYS_ERR_OK; } static errval_t fat32_fat_write_entry(struct fat32 *fat32, uint32_t cluster_number, uint32_t entry) { errval_t err; // 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; } } 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); static errval_t fat32_alloc_cluster(struct fat32 *fat32, uint32_t *return_cluster_number) { errval_t err; // 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; // debug_printf("[fat32_alloc_cluster] found free cluster: %u\n", cluster_number); *return_cluster_number = cluster_number; return SYS_ERR_OK; } } // no more free space left return FAT_ERR_FAT_LOOKUP; } __attribute__((__used__)) static errval_t fat32_free_cluster(struct fat32 *fat32, uint32_t cluster_number) { errval_t err; assert(cluster_number > 1); 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; return SYS_ERR_OK; } 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); 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; 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) { errval_t err; assert(cluster_number > 1); assert(!fat32_is_end_of_cluster_chain(cluster_number)); 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; // debug_printf("[fat32_next_cluster_in_chain] FAT entry %u: %u\n", cluster_number, *return_next_cluster_number); return SYS_ERR_OK; } 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; // 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; return SYS_ERR_OK; } static errval_t fat32_free_cluster_chain(struct fat32 *fat32, uint32_t first_cluster) { errval_t err; 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; } 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; } 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); 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; } // debug_printf("[fat32_directory_entry_get_nice_name] short_name[%lu]: '%.8s', short_name_ext[%lu]: '%.3s', name: '%s'\n", 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; } // returns true -> if anything was left of the path, false -> nothing was left or for example only '/' 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); // 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 { // debug_printf("[fat32_extract_next_path_element] invalid dot start\n"); // 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) { // debug_printf("[fat32_extract_next_path_element] short_name too long\n"); 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] == ' ') { // debug_printf("[fat32_extract_next_path_element] short_name empty\n"); 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) { // debug_printf("[fat32_extract_next_path_element] short_name_ext too long\n"); 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; } } } if (return_path_cursor != NULL) { *return_path_cursor = path_cursor; } return SYS_ERR_OK; } 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); } static errval_t fat32_write_directory_entry( struct fat32 *fat32, struct fat32_directory_entry_ref directory_entry_ref, struct fat32_directory_entry *directory_entry ) { // debug_printf("[fat32_write_directory_entry] ref: %lu@%u\n", 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); } 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; 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; 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"); } static errval_t fat32_free_directory_entry(struct fat32 *fat32, struct fat32_directory_entry_ref directory_entry_ref) { errval_t err; 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_lock_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; return SYS_ERR_OK; } 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; 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) { // 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); } } return SYS_ERR_OK; } } } err = fat32_next_cluster_in_chain(fat32, current_cluster, ¤t_cluster); if (err_is_fail(err)) return err; } return FS_ERR_NOTFOUND; } 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; 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) ) { return FS_ERR_NOTEMPTY; } } } err = fat32_next_cluster_in_chain(fat32, current_cluster, ¤t_cluster); if (err_is_fail(err)) return err; } return SYS_ERR_OK; } 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; } 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); // debug_printf("[fat32_resolve_path] Resolving path: %.*s\n", path_end - path, path); // this MUST be ensured by whomever calls this library because we only allow absolute paths if (path[0] != '/') { debug_printf("[fat32_resolve_path] Only absolute paths are allowed (starting with a '/')"); return FS_ERR_NOTFOUND; } // 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; // debug_printf("[fat32_resolve_path] Got dirname: '%.8s.%3s'\n", short_name, short_name_ext); // we can only traverse directories if (!(path_resolve_result.directory_entry.attributes & FAT32_ATTR_DIRECTORY)) { 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 // debug_printf("[fat32_resolve_path] successfully resolved\n"); if (return_path_resolve_result != NULL) { *return_path_resolve_result = path_resolve_result; } return SYS_ERR_OK; } /** * @brief * * @param fat32 * @param path * @param return_directory_entry directory entry that was found * @param return_parent_sector_number zero if we are in the root directory * @param return_parent_directory_number undefined if the result is the root directory * @return errval_t */ 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__)) 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; // pass over preceding separators // 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; } 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; *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)->byte_offset = 0; (*handle)->directory_entry_ref = directory_entry_ref; err = fat32->register_handle_fn(fat32_lock_id_from_directory_entry_ref(directory_entry_ref)); if (err_is_fail(err)) return err; return SYS_ERR_OK; } static errval_t fat32_free_handle(struct fat32 *fat32, struct fat32_handle *handle) { errval_t err; err = fat32->unregister_handle_fn(fat32_lock_id_from_directory_entry_ref(handle->directory_entry_ref)); if (err_is_fail(err)) return err; free(handle); 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__)) static void fat32_print(struct bpb *bpb){ struct bpb_fat32 *fat32 = &bpb->bpb_fat.bpb_fat32; debug_printf("[fat32_print] BPB\n"); debug_printf("[fat32_print] boot_jump: 0x%x%x%x\n", bpb->jmp_boot[2], bpb->jmp_boot[1], bpb->jmp_boot[0]); debug_printf("[fat32_print] oem_name: %.8s\n", bpb->oem_name); debug_printf("[fat32_print] bytes_per_sector: %u\n", bpb->bytes_per_sector); debug_printf("[fat32_print] sectors_per_cluster: %u\n", bpb->sectors_per_cluster); debug_printf("[fat32_print] reserved_sector_count: %u\n", bpb->reserved_sector_count); debug_printf("[fat32_print] number_fats: %u\n", bpb->fat_count); debug_printf("[fat32_print] root_entry_count: %u\n", bpb->root_entry_count); debug_printf("[fat32_print] total_sector_16_count: %u\n", bpb->total_sector_16_count); debug_printf("[fat32_print] media: 0x%x\n", bpb->media); debug_printf("[fat32_print] fat_sectors_16_count: %u\n", bpb->fat_sectors_16_count); debug_printf("[fat32_print] sectors_per_track: %u\n", bpb->sectors_per_track); debug_printf("[fat32_print] number_of_heads: %u\n", bpb->number_of_heads); debug_printf("[fat32_print] hidden_sector_count: %u\n", bpb->hidden_sector_count); debug_printf("[fat32_print] total_sector_32_count: %u\n", bpb->total_sector_32_count); debug_printf("[fat32_print] FAT32\n"); debug_printf("[fat32_print] fat_sectors_32_count: %u\n", fat32->fat_sectors_32_count); debug_printf("[fat32_print] ext_flag_active_fat: %u\n", fat32->ext_flag_active_fat); debug_printf("[fat32_print] ext_flag_mirrored: %u\n", fat32->ext_flag_mirrored); debug_printf("[fat32_print] fs_version: %u\n", fat32->fs_version); debug_printf("[fat32_print] root_cluster_number: %u\n", fat32->root_cluster_number); debug_printf("[fat32_print] fs_info_cluster_number: %u\n", fat32->fs_info_sector_number); debug_printf("[fat32_print] backup_boot_sector_number: %u\n", fat32->backup_boot_sector_number); debug_printf("[fat32_print] drive_number: %u\n", fat32->drive_number); debug_printf("[fat32_print] extended_boot_signature: 0x%x\n", fat32->extended_boot_signature); if (fat32->extended_boot_signature == 0x29) { debug_printf("[fat32_print] volume_serial_number: %u\n", fat32->volume_serial_number); debug_printf("[fat32_print] volume_label: %.11s\n", fat32->volume_label); debug_printf("[fat32_print] file_system_type: %.8s\n", fat32->file_system_type); } } // authors are very strict that it MUST be done exactly this way so here we go static bool is_fat32(struct bpb *bpb) { debug_printf("[is_fat32] check fat type\n"); 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) { debug_printf("Got FAT12 file system\n"); return false; } else if (count_of_clusters < 65525) { debug_printf("Got FAT16 file system\n"); 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; debug_printf("[fat32_init]\n"); 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 debug_printf("[fat32_init] initialize state\n"); 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); debug_printf("[fat32_init] verified FAT32\n"); *return_fat32 = fat32; return SYS_ERR_OK; } static errval_t _fat32_mkdir(struct fat32 *fat32, const char *path) { // debug_printf("[fat32_mkdir]\n"); // 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); // 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; // debug_printf("[fat32_mkdir] basename: %s\n", 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; // debug_printf("[fat32_mkdir] got parent dir\n"); 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 // debug_printf("[fat32_mkdir] checking if dir exists already\n"); 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 // debug_printf("[fat32_mkdir] allocating directory\n"); 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 // debug_printf("[fat32_mkdir] get free cluster\n"); uint32_t free_cluster_number; err = fat32_alloc_cluster(fat32, &free_cluster_number); if (err_is_fail(err)) return err; // zero out cluster // debug_printf("[fat32_mkdir] zeroing\n"); 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, }; 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; // debug_printf("[fat32_mkdir] Create the directory entry\n"); // 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; err = fat32_write_directory_entry(fat32, free_path_resolve_result.directory_entry_ref, &directory_entry); if (err_is_fail(err)) return err; // debug_printf("[fat32_mkdir] done\n"); 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; // debug_printf("[fat32_opendir]\n"); // 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; 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) { debug_printf("[fat32_closedir]\n"); 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) { // debug_printf("[fat32_stat]\n"); assert(fileinfo != NULL); errval_t err; if (handle->is_dir) { fileinfo->type = FS_DIRECTORY; // TODO rueegges: somehow get directory sizes? 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) { // debug_printf("[fat32_readdir]\n"); errval_t err; if (dir_handle == NULL || !dir_handle->is_dir) { return FS_ERR_INVALID_FH; } *name = NULL; 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]); return SYS_ERR_OK; } } err = fat32_next_cluster_in_chain(fat32, dir_handle->current_cluster, &dir_handle->current_cluster); 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) { // debug_printf("[fat32_rmdir]\n"); 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; if (!(path_resolve_result.directory_entry.attributes & FAT32_ATTR_DIRECTORY)) { return FS_ERR_NOTDIR; } 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) { // debug_printf("[fat32_rm]\n"); 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; 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) { // debug_printf("[fat32_fcreate]\n"); // 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; // debug_printf("[fat32_fcreate] basename: %s\n", 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; // 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; // debug_printf("[fat32_fcreate] Get time information\n"); // get time information for directory entry creation uint16_t write_date, write_time; fat32_get_time_information(&write_date, &write_time); // debug_printf("[fat32_fcreate] Create the directory entry\n"); // store the directory in the space in the parent directory we found assert(free_cluster_number > 1); struct fat32_directory_entry directory_entry = { .attributes = 0, .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; // debug_printf("[fat32_fcreate] done\n"); 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; // debug_printf("[fat32_fcreate] first_cluster: %u\n", (*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) { // debug_printf("[fat32_fopen]\n"); assert(fat32 != NULL); assert(path != NULL); assert(file_handle != NULL); 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) { // debug_printf("[fat32_fread] on %p: %lu bytes from offset %lu\n", 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; 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 (file_handle->byte_offset >= directory_entry.file_size) { *bytes_read = 0; return SYS_ERR_OK; } size_t bytes_till_eof = directory_entry.file_size - file_handle->byte_offset; // debug_printf("[fat32_fread] bytes_till_eof: %lu\n", bytes_till_eof); size_t bytes_to_read = MIN(bytes, bytes_till_eof); // if (bytes_to_read < bytes) { // debug_printf("[fat32_fread] attempted to read over end of file, reading only %lu/%lu\n", bytes_to_read, bytes); // } // follow the chain if the current cluster is not defined if (file_handle->current_cluster == 0) { size_t cluster_min_count = ROUND_UP(file_handle->byte_offset, fat32->bytes_per_cluster) / fat32->bytes_per_cluster; // update the current_cluster in the file handle without extending the chain file_handle->current_cluster = file_handle->first_cluster; for (size_t i = 0; i + 1 < cluster_min_count; ++i) { err = fat32_next_cluster_in_chain(fat32, file_handle->current_cluster, &file_handle->current_cluster); if (err_is_fail(err)) return err; } assert(!fat32_is_end_of_cluster_chain(file_handle->current_cluster)); } *bytes_read = 0; while (*bytes_read < bytes_to_read) { // 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); 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) { // debug_printf("[fat32_fread] file now at offset %lu\n", file_handle->byte_offset); return SYS_ERR_OK; } } err = fat32_next_cluster_in_chain(fat32, file_handle->current_cluster, &file_handle->current_cluster); if (err_is_fail(err)) return err; } // debug_printf("[fat32_fread] file now at offset %lu\n", file_handle->byte_offset); return SYS_ERR_OK; } 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) { // debug_printf("[fat32_fwrite] %lu bytes\n", 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; // if the current position in the file is past the end we need to increase the file size accordingly if (file_handle->byte_offset >= directory_entry.file_size || file_handle->current_cluster == 0) { // debug_printf("Invalid current cluster %u, updating it: %u (%lu)\n", file_handle->current_cluster, file_handle->first_cluster, file_handle->byte_offset); // find the cluster containing the current position in the file size_t cluster_min_count = ROUND_UP(file_handle->byte_offset, fat32->bytes_per_cluster) / fat32->bytes_per_cluster; // when we are past the end of the file then the cluster_number is invalid file_handle->current_cluster = file_handle->first_cluster; for (size_t i = 0; i + 1 < cluster_min_count; ++i) { err = fat32_next_cluster_in_chain_or_extend(fat32, file_handle->current_cluster, &file_handle->current_cluster); if (err_is_fail(err)) return err; } // debug_printf("current_cluster: %u\n", file_handle->current_cluster); }; // 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 // debug_printf("Write in cluster: %u\n", 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; err = fat32_next_cluster_in_chain_or_extend(fat32, file_handle->current_cluster, &file_handle->current_cluster); if (err_is_fail(err)) break; } // 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; // debug_printf("File got larger, is now: %lu\n", 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; 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) { // debug_printf("[fat32_fclose]\n"); 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) { // debug_printf("[fat32_tell]\n"); *pos = file_handle->is_dir ? 0 : file_handle->byte_offset; // debug_printf("[fat32_tell] @%lu\n", 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) { // debug_printf("[fat32_seek] whence: %ld@%d\n", 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) { absolute_offset = offset * sizeof(struct fat32_directory_entry); } 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; // find the cluster containing the current position in the file size_t cluster_min_count = ROUND_UP(file_handle->byte_offset, fat32->bytes_per_cluster) / fat32->bytes_per_cluster; // update the current_cluster in the file handle without extending the chain file_handle->current_cluster = file_handle->first_cluster; for (size_t i = 0; i + 1 < cluster_min_count && !fat32_is_end_of_cluster_chain(file_handle->current_cluster); ++i) { err = fat32_next_cluster_in_chain(fat32, file_handle->current_cluster, &file_handle->current_cluster); if (err_is_fail(err)) return err; } // we are over the end of the cluster chain, so invalidate the current_cluster if (fat32_is_end_of_cluster_chain(file_handle->current_cluster)) { file_handle->current_cluster = 0; } 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)) }