diff --git a/errors/errno.fugu b/errors/errno.fugu index 00f7c6f..72e926d 100755 --- a/errors/errno.fugu +++ b/errors/errno.fugu @@ -1154,7 +1154,12 @@ errors fat FAT_ERR_ { failure BLOCK_BOUNDS "The block number is out of bounds", failure CREATE_ROOT "Tried to create root directory", failure BAD_FILENAME "Filename is not allowed", + failure TYPE_NOT_SUPPORTED "Only FAT32 is supported", + // NOTE rueegges: library internal + failure PATH_EMPTY "The path that was given is empty", + failure FILE_TOO_LONG "The cluster chain for this file would become longer than allowed by the FAT32 spec", failure TOO_MANY_HANDLES "Currently the FAT32 library allows at most FAT32_MAX_OPEN_HANDLERS handlers per dir/file", + failure OPENED "The directory entry is currently opened and can not be deleted", }; // errors generated by VFS's fs cache library diff --git a/include/fs/dirent.h b/include/fs/dirent.h index d3df8c0..c5f6023 100644 --- a/include/fs/dirent.h +++ b/include/fs/dirent.h @@ -66,7 +66,7 @@ errval_t closedir(fs_dirhandle_t handle); * * @returns SYS_ERR_OK on successful reading the next entry * FS_ERR_INVALID_FH if the directory handle was invalid - * FS_ERR_NOTFOUND there is no next file in the directory + * FS_ERR_INDEX_BOUNDS there is no next file in the directory */ errval_t readdir(fs_dirhandle_t handle, char **name); diff --git a/include/fs/fat32.h b/include/fs/fat32.h new file mode 100644 index 0000000..84e8ce2 --- /dev/null +++ b/include/fs/fat32.h @@ -0,0 +1,312 @@ +#ifndef _INIT_FAT32_H_ +#define _INIT_FAT32_H_ + +#include +#include + +#define FAT32_SHORTNAME_SIZE 8 // bytes +#define FAT32_SHORTNAME_EXT_SIZE 3 // bytes +#define FAT32_BLOCK_SIZE SDHC_BLOCK_SIZE + +#define FAT32_EOFC_MARK 0x0FFFFFF8 + +// "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 +#define FAT32_MAX_FILESIZE (0x100000000 - 1) + +/** + * ATTR_READ_ONLY Indicates that writes to the file should fail. + * ATTR_HIDDEN Indicates that normal directory listings should not show this file. + * ATTR_SYSTEM Indicates that this is an operating system file. + * ATTR_VOLUME_ID There should only be one “file” on the volume that has this attribute set, and that file must be in the root directory. This name of this file is actually the label for the volume. DIR_FstClusHI and DIR_FstClusLO must always be 0 for the volume label (no data clusters are allocated to the volume label file). + * ATTR_DIRECTORY Indicates that this file is actually a container for other files. + * // TODO rueegges: don't forget to set this + * ATTR_ARCHIVE This attribute supports backup utilities. This bit is set by the FAT file system driver when a file is created, renamed, or written to. Backup utilities may use this attribute to indicate which files on the volume have been modified since the last time that a backup was performed. + */ +#define FAT32_ATTR_READ_ONLY 0x01 +#define FAT32_ATTR_HIDDEN 0x02 +#define FAT32_ATTR_SYSTEM 0x04 +#define FAT32_ATTR_VOLUME_ID 0x08 +#define FAT32_ATTR_DIRECTORY 0x10 +#define FAT32_ATTR_ARCHIVE 0x20 +#define FAT32_ATTR_LONG_NAME (FAT32_ATTR_READ_ONLY|FAT32_ATTR_HIDDEN|FAT32_ATTR_SYSTEM|FAT32_ATTR_VOLUME_ID) + +typedef errval_t (*fat_block_read_fn_t)(uint32_t sector_number, size_t offset, size_t size, void *dst); +typedef errval_t (*fat_block_write_fn_t)(uint32_t sector_number, size_t offset, size_t size, const void *src); +typedef errval_t (*fat_block_lock_fn_t)(void); +typedef errval_t (*fat_block_unlock_fn_t)(void); +typedef errval_t (*fat_block_register_handle_fn_t)(uint64_t directory_entry_id); +typedef errval_t (*fat_block_unregister_handle_fn_t)(uint64_t directory_entry_id); +typedef errval_t (*fat_block_count_handles_fn_t)(uint64_t directory_entry_id, size_t *count); + +// typedef errval_t (*fat_block_lock_read_fn_t)(int block_index); +// typedef errval_t (*fat_block_lock_write_fn_t)(int block_index); +// typedef errval_t (*fat_block_unlock_read_fn_t)(int block_index); +// typedef errval_t (*fat_block_unlock_write_fn_t)(int block_index); + +#pragma pack(1) +/** + * @brief FAT32 specific part of the boot sector + */ +struct bpb_fat32 { + /** + * @brief count of sectors occupied by a single FAT + */ + uint32_t fat_sectors_32_count; + /** + * @brief only valid if mirror flag is set. zero based index of active FAT + */ + uint16_t ext_flag_active_fat:4; + uint16_t _ext_flag_reserved_1:3; + /** + * @brief 0 -> FAT is mirrored to all FATs, 1-> single FAT is active (specified in bits 0-3) + */ + uint16_t ext_flag_mirrored:1; + uint16_t _ext_flag_reserved_2:8; + /** + * @brief Currently only version 0 specified + */ + uint16_t fs_version; + /** + * @brief cluster number of the first cluster of the root directory + * + * usually 2 + */ + uint32_t root_cluster_number; + /** + * @brief sector number of the fs infor structure in the reserved area of the FAT32 volume + * + * usually 1 + */ + uint16_t fs_info_sector_number; + /** + * @brief if non-zero indicates the sector in the reserved area of the volume containing a copy of the bpb + * + * usually 6 + */ + uint16_t backup_boot_sector_number; + uint8_t _reserved[12]; + /** + * @brief int 0x13 drive number + * + * don't think we need this + */ + uint8_t drive_number; + uint8_t _reserved1; + /** + * @brief indicates that the next 3 fields are present + * 0x29 means that the next 3 fields are present? no other values specified... + */ + uint8_t extended_boot_signature; + /** + * @brief together with volume_label allows volume tracking on removable media + * + */ + uint32_t volume_serial_number; + /** + * @brief same as the 11-byte volume label recorded in the root directory + * "NO NAME " means that there is no label defined + */ + char volume_label[11]; + /** + * @brief Always set to "FAT32 " for FAT32 file systems + * + * MUST NOT be used for file system type determination. + * just seems to be here for fun? + */ + char file_system_type[8]; +}; + +#pragma pack(1) +union bpb_fat { + struct bpb_fat32 bpb_fat32; +}; + +#pragma pack(1) +struct bpb { + uint8_t jmp_boot[3]; + unsigned char oem_name[8]; + uint16_t bytes_per_sector; + uint8_t sectors_per_cluster; + uint16_t reserved_sector_count; + // number of consecutive FAT copies (there is only one "main" FAT but n-1 copies) + // should be 2 in most cases but can be any value >= 1 + uint8_t fat_count; + // MUST be 0 for FAT32 + uint16_t root_entry_count; + // MUST be 0 for FAT32 + uint16_t total_sector_16_count; + /* + * valid values: 0xF0 0xF8 0xF9 0xFA 0xFB 0xFC 0xFD 0xFE 0xFF + * not really used anymore + */ + uint8_t media; + // MUST be 0 for FAT32 + uint16_t fat_sectors_16_count; + // unsure. something to do with interrput 0x13 + // don't think we need this + uint16_t sectors_per_track; + // unsure. something to do with interrput 0x13 + // don't think we need this + uint16_t number_of_heads; + /** + * @brief number of sectors preceding the partition containing this FAT volume + * + * should be 0 for non-partitioned devices, operating system specific, mainly relevant for 0x13 interrupt + */ + uint32_t hidden_sector_count; + /** + * @brief total count of sectors on the volume + */ + uint32_t total_sector_32_count; + + /** + * @brief FAT type specific information + */ + union bpb_fat bpb_fat; +}; +STATIC_ASSERT(sizeof(struct bpb) <= 512-2, "BPB struct may be at most 512-2 bytes long"); + +#pragma pack(1) +struct fat32_fsinfo { + uint32_t lead_signature; // Value 0x41615252 + uint8_t _reserved1[480]; + uint32_t structure_signature; // Value 0x61417272. + uint32_t free_count; // 0xFFFFFFFF means free count is unknown + uint32_t next_free; // 0xFFFFFFFF means not set, just a hint at which we should start looking for free clusters + uint8_t _reserved2[12]; + uint32_t trail_signature; // Value 0xAA550000 +}; + +#pragma pack(1) +/** + * @brief Single entry of a directory + * a directory is like a file but containing a list of structs of this type + * + */ +struct fat32_directory_entry { + /** + * DIR_Name[0] == 0xE5, then the directory entry is free + * If DIR_Name[0] == 0x00, then the directory entry is free (same as for 0xE5), and there are no allocated directory entries after this on + * If DIR_Name[0] == 0x05, then the actual file name character for this byte is 0xE5. 0xE5 is actually a valid KANJI lead byte value for the character set used in Japan. The special 0x05 value is used so that this special file name case for Japan can be handled properly and not cause FAT file system code to think that the entry is free. + * + * The DIR_Name field is actually broken into two parts+ the 8-character main part of the name, and the 3-character extension. These two parts are “trailing space padded” with bytes of 0x20. + * DIR_Name[0] may not equal 0x20. There is an implied ‘.’ character between the main part of the name and the extension part of the name that is not present in DIR_Name + * Lower case characters are not allowed in DIR_Name (what these characters are is country specific). The following characters are not legal in any bytes of DIR_Name: + * - Values less than 0x20 except for the special case of 0x05 in DIR_Name[0] described above. + * - 0x22, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D, and 0x7C + */ + char short_name[FAT32_SHORTNAME_SIZE]; + char short_name_ext[FAT32_SHORTNAME_EXT_SIZE]; + uint8_t attributes; // upper two bits are reserved and should be 0 + uint8_t nt_reserved; + uint8_t creation_time_deci_seconds; + uint8_t _unknown[6]; + + uint16_t first_cluster_number_high_word; + uint16_t write_time; // also creation is write + uint16_t write_date; // also creation is write + uint16_t first_cluster_number_low_word; + // zero for directories + uint32_t file_size; // in bytes +}; +STATIC_ASSERT(sizeof(struct fat32_directory_entry) == 32, "FAT32 Directory entries are 32 bytes in size"); + +struct fat32_directory_entry_ref { + uint32_t sector_number; + size_t index_in_sector; +}; + +struct fat32_path_resolve_result { + struct fat32_directory_entry directory_entry; + struct fat32_directory_entry_ref directory_entry_ref; +}; + +/** + * @brief Struct for an open fat32 file system + * + * Note: We don't format file systems so the boot sector never changes + * look at the bpb and bpb_fat32 structs for more info on the fields + */ +struct fat32 { + + // block functions + 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_lock_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; + + // very primitive implementation of mounting + char *mount; + + // metadata + + uint16_t bytes_per_sector; + uint8_t sectors_per_cluster; + uint16_t reserved_sector_count; + uint8_t fat_count; + uint32_t total_sector_32_count; + uint32_t fat_sectors_32_count; + uint8_t ext_flag_active_fat:4; + uint8_t ext_flag_mirrored:1; + uint32_t root_cluster_number; + + // information calculated during init + + // the first sector containing actual directory or file data + uint32_t first_data_sector; + // the largest valid cluster number + uint32_t max_cluster_number; + // the number of bytes in a cluster + size_t bytes_per_cluster; +}; + +struct fat32_handle { + bool is_dir; + + uint32_t first_cluster; + uint32_t current_cluster; + size_t byte_offset; + + struct fat32_directory_entry_ref directory_entry_ref; +}; + +#define FAT32_FAT_ENTRIES_PER_SECTOR (SDHC_BLOCK_SIZE / sizeof(uint32_t)) +STATIC_ASSERT(FAT32_FAT_ENTRIES_PER_SECTOR * sizeof(uint32_t) == SDHC_BLOCK_SIZE, "fat does not align with blocks"); +#define FAT32_DIRECTORY_ENTRIES_PER_SECTOR (SDHC_BLOCK_SIZE / sizeof(struct fat32_directory_entry)) +STATIC_ASSERT(FAT32_DIRECTORY_ENTRIES_PER_SECTOR * sizeof(struct fat32_directory_entry) == SDHC_BLOCK_SIZE, "directories do not align with blocks"); + +errval_t fat32_init( + struct fat32 **return_fat32, + fat_block_read_fn_t read_block_fn, + fat_block_write_fn_t write_block_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 +); + +// dirs +errval_t fat32_mkdir(struct fat32 *fat32, const char *path); +errval_t fat32_rmdir(struct fat32 *fat32, const char *path); +errval_t fat32_opendir(struct fat32 *fat32, const char *path, struct fat32_handle **dir_handle); +errval_t fat32_closedir(struct fat32 *fat32, struct fat32_handle *dir_handle); +errval_t fat32_stat(struct fat32 *fat32, struct fat32_handle *dir_handle, struct fs_fileinfo *fileinfo); +errval_t fat32_readdir(struct fat32 *fat32, struct fat32_handle *dir_handle, char **name); +errval_t fat32_rm(struct fat32 *fat32, const char *path); + +// files +errval_t fat32_fopen(struct fat32 *fat32, const char *path, struct fat32_handle **file_handle); +errval_t fat32_fcreate(struct fat32 *fat32, const char *path, struct fat32_handle **file_handle); +errval_t fat32_fread(struct fat32 *fat32, struct fat32_handle *file_handle, void *buffer, size_t bytes, size_t *bytes_read); +errval_t fat32_fwrite(struct fat32 *fat32, struct fat32_handle *file_handle, const void *buffer, size_t bytes, size_t *bytes_written); +errval_t fat32_fclose(struct fat32 *fat32, struct fat32_handle *file_handle); +errval_t fat32_seek(struct fat32 *fat32, struct fat32_handle *file_handle, enum fs_seekpos whence, off_t offset); +errval_t fat32_tell(struct fat32 *fat32, struct fat32_handle *file_handle, size_t *pos); + +#endif /* _INIT_FAT32_H_ */ \ No newline at end of file diff --git a/lib/fs/Hakefile b/lib/fs/Hakefile index 2848dd5..2d3c4b6 100644 --- a/lib/fs/Hakefile +++ b/lib/fs/Hakefile @@ -17,7 +17,9 @@ "fs.c", "fopen.c", "ramfs.c", - "dirent.c" - ] + "dirent.c", + "fat32.c" + ], + addLibraries = [ "block_driver" ] } ] diff --git a/lib/fs/dirent.c b/lib/fs/dirent.c index 4b7750e..92da55a 100644 --- a/lib/fs/dirent.c +++ b/lib/fs/dirent.c @@ -105,7 +105,7 @@ errval_t closedir(fs_dirhandle_t handle) * * @returns SYS_ERR_OK on successful reading the next entry * FS_ERR_INVALID_FH if the directory handle was invalid - * FS_ERR_NOTFOUND there is no next file in the directory + * FS_ERR_INDEX_BOUNDS there is no next file in the directory */ errval_t readdir(fs_dirhandle_t handle, char **name) { @@ -148,7 +148,7 @@ errval_t rmdir(const char *path) * @param path the path to be removed * * @returns SYS_ERR_OK on successful removal - * FS_ERR_NOTDIR if the path is not a directory + * FS_ERR_NOTFILE if the path is not a file * FS_ERR_NOTFOUND if there is no such path */ errval_t rm(const char *path) @@ -163,7 +163,7 @@ errval_t rm(const char *path) * @param buf returned data of the filehandle information * * @returns SYS_ERR_OK on successful removal - * FS_ERR_INVALID_FH if the directory is not emtpy + * FS_ERR_INVALID_FH if the directory handle was invalid */ errval_t fstat(fs_dirhandle_t handle, struct fs_fileinfo *buf) { diff --git a/lib/fs/fat32.c b/lib/fs/fat32.c new file mode 100644 index 0000000..9fe9693 --- /dev/null +++ b/lib/fs/fat32.c @@ -0,0 +1,1689 @@ +#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)) +} diff --git a/lib/fs/fopen.c b/lib/fs/fopen.c index 67697fc..8cbef88 100644 --- a/lib/fs/fopen.c +++ b/lib/fs/fopen.c @@ -19,8 +19,8 @@ #include #include -#include #include "fs_internal.h" +#include static void *mount; @@ -92,29 +92,29 @@ static void fdtab_free(int fd) //XXX: flags are ignored... static int fs_libc_open(char *path, int flags) { - ramfs_handle_t vh; + struct fat32_handle *vh; errval_t err; - // If O_CREAT was given, we use ramfsfs_create() + // If O_CREAT was given, we use create if(flags & O_CREAT) { // If O_EXCL was also given, we check whether we can open() first if(flags & O_EXCL) { - err = ramfs_open(mount, path, &vh); + err = fat32_fopen(mount, path, &vh); if(err_is_ok(err)) { - ramfs_close(mount, vh); + fat32_fclose(mount, vh); errno = EEXIST; return -1; } assert(err_no(err) == FS_ERR_NOTFOUND); } - err = ramfs_create(mount, path, &vh); + err = fat32_fcreate(mount, path, &vh); if (err_is_fail(err) && err == FS_ERR_EXISTS) { - err = ramfs_open(mount, path, &vh); + err = fat32_fopen(mount, path, &vh); } } else { // Regular open() - err = ramfs_open(mount, path, &vh); + err = fat32_fopen(mount, path, &vh); } if (err_is_fail(err)) { @@ -137,7 +137,7 @@ static int fs_libc_open(char *path, int flags) }; int fd = fdtab_alloc(&e); if (fd < 0) { - ramfs_close(mount, vh); + fat32_fclose(mount, vh); return -1; } else { return fd; @@ -153,9 +153,9 @@ static int fs_libc_read(int fd, void *buf, size_t len) switch(e->type) { case FDTAB_TYPE_FILE: { - ramfs_handle_t fh = e->handle; + struct fat32_handle *fh = e->handle; assert(e->handle); - err = ramfs_read(mount, fh, buf, len, &retlen); + err = fat32_fread(mount, fh, buf, len, &retlen); if (err_is_fail(err)) { return -1; } @@ -180,8 +180,8 @@ static int fs_libc_write(int fd, void *buf, size_t len) switch(e->type) { case FDTAB_TYPE_FILE: { - ramfs_handle_t fh = e->handle; - errval_t err = ramfs_write(mount, fh, buf, len, &retlen); + struct fat32_handle *fh = e->handle; + errval_t err = fat32_fwrite(mount, fh, buf, len, &retlen); if (err_is_fail(err)) { return -1; } @@ -202,10 +202,10 @@ static int fs_libc_close(int fd) return -1; } - ramfs_handle_t fh = e->handle; + struct fat32_handle *fh = e->handle; switch(e->type) { case FDTAB_TYPE_FILE: - err = ramfs_close(mount, fh); + err = fat32_fclose(mount, fh); if (err_is_fail(err)) { return -1; } @@ -221,7 +221,7 @@ static int fs_libc_close(int fd) static off_t fs_libc_lseek(int fd, off_t offset, int whence) { struct fdtab_entry *e = fdtab_get(fd); - ramfs_handle_t fh = e->handle; + struct fat32_handle *fh = e->handle; switch(e->type) { case FDTAB_TYPE_FILE: { @@ -246,13 +246,13 @@ static off_t fs_libc_lseek(int fd, off_t offset, int whence) return -1; } - err = ramfs_seek(mount, fh, fs_whence, offset); + err = fat32_seek(mount, fh, fs_whence, offset); if(err_is_fail(err)) { DEBUG_ERR(err, "vfs_seek"); return -1; } - err = ramfs_tell(mount, fh, &retpos); + err = fat32_tell(mount, fh, &retpos); if(err_is_fail(err)) { return -1; } @@ -265,13 +265,13 @@ static off_t fs_libc_lseek(int fd, off_t offset, int whence) } } -static errval_t fs_mkdir(const char *path){ return ramfs_mkdir(mount, path);} -static errval_t fs_rmdir(const char *path){ return ramfs_rmdir(mount, path); } -static errval_t fs_rm(const char *path){ return ramfs_remove(mount, path); } -static errval_t fs_opendir(const char *path, fs_dirhandle_t *h){ return ramfs_opendir(mount, path, h); } -static errval_t fs_readdir(fs_dirhandle_t h, char **name) { return ramfs_dir_read_next(mount, h, name, NULL); } -static errval_t fs_closedir(fs_dirhandle_t h) { return ramfs_closedir(mount, h); } -static errval_t fs_fstat(fs_dirhandle_t h, struct fs_fileinfo *b) { return ramfs_stat(mount, h, b); } +static errval_t fs_mkdir(const char *path){ return fat32_mkdir(mount, path);} +static errval_t fs_rmdir(const char *path){ return fat32_rmdir(mount, path); } +static errval_t fs_rm(const char *path){ return fat32_rm(mount, path); } +static errval_t fs_opendir(const char *path, fs_dirhandle_t *h){ return fat32_opendir(mount, path, (struct fat32_handle **) h); } +static errval_t fs_readdir(fs_dirhandle_t h, char **name) { return fat32_readdir(mount, h, name); } +static errval_t fs_closedir(fs_dirhandle_t h) { return fat32_closedir(mount, h); } +static errval_t fs_fstat(fs_dirhandle_t h, struct fs_fileinfo *b) { return fat32_stat(mount, h, b); } typedef int fsopen_fn_t(char *, int); typedef int fsread_fn_t(int, void *buf, size_t); diff --git a/lib/fs/fs.c b/lib/fs/fs.c index 7553700..817f726 100644 --- a/lib/fs/fs.c +++ b/lib/fs/fs.c @@ -4,13 +4,12 @@ */ #include - +#include #include #include -#include - #include "fs_internal.h" +#include /* * Copyright (c) 2016 ETH Zurich. @@ -34,17 +33,33 @@ errval_t filesystem_init(void) errval_t err; /* TODO: Filesystem project: hook up your init code here */ + err = block_driver_init(); + if (err_is_fail(err)) return err; + struct fat32 *fat32; + err = fat32_init( + &fat32, + block_driver_read_object, + block_driver_write_object, + block_driver_lock, + block_driver_unlock, + block_driver_register_handle, + block_driver_unregister_handle, + block_driver_count_handles, + "/sdcard" + ); + if(err_is_fail(err)) return err; - ramfs_mount_t st = NULL; - err = ramfs_mount("/", &st); - if (err_is_fail(err)) { - return err; - } + // ramfs_mount_t st = NULL; + // err = ramfs_mount("/", &st); + // if (err_is_fail(err)) { + // return err; + // } /* TODO: Mount your sdcard at /sdcard */ + // done during fat32_init /* register libc fopen/fread and friends */ - fs_libc_init(st); + fs_libc_init(fat32); return SYS_ERR_OK; }