From 7f8793967ffadf36e0c439db406ae1635d5c3a69 Mon Sep 17 00:00:00 2001 From: wilbertpol Date: Sun, 12 May 2024 21:47:07 +0100 Subject: [PATCH] lib/formats/fs_fat.cpp: Add write support. (#12363) --- src/lib/formats/fs_fat.cpp | 557 ++++++++++++++++++++++++++++++++++--- 1 file changed, 520 insertions(+), 37 deletions(-) diff --git a/src/lib/formats/fs_fat.cpp b/src/lib/formats/fs_fat.cpp index 4aad4f6b573..fe9a7b949c4 100644 --- a/src/lib/formats/fs_fat.cpp +++ b/src/lib/formats/fs_fat.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Nathan Woods +// copyright-holders:Nathan Woods,Wilbert Pol /*************************************************************************** fs_fat.cpp @@ -7,11 +7,13 @@ PC FAT disk images Current Limitations: - - Read only - Only supports floppy disks - No FAT32 support - No Long Filenames Support + Removal of files is untested; floptool does not have a command to delete + a file. + ***************************************************************************** Master boot record format: @@ -147,6 +149,7 @@ #include "strformat.h" #include +#include using namespace fs; @@ -163,7 +166,22 @@ namespace { class directory_entry { public: - static const int SIZE = 32; + static constexpr int SIZE = 32; + static constexpr int OFFSET_FNAME = 0; + static constexpr int FNAME_LENGTH = 11; + static constexpr int OFFSET_ATTRIBUTES = 11; + static constexpr int OFFSET_CREATE_DATETIME = 14; + static constexpr int OFFSET_START_CLUSTER_HI = 20; + static constexpr int OFFSET_MODIFIED_DATETIME = 22; + static constexpr int OFFSET_START_CLUSTER = 26; + static constexpr int OFFSET_FILE_SIZE = 28; + static constexpr u8 DELETED_FILE_MARKER = 0xe5; + static constexpr u8 ATTR_READ_ONLY = 0x01; + static constexpr u8 ATTR_HIDDEN = 0x02; + static constexpr u8 ATTR_SYSTEM = 0x04; + static constexpr u8 ATTR_VOLUME_LABEL = 0x08; + static constexpr u8 ATTR_DIRECTORY = 0x10; + static constexpr u8 ATTR_ARCHIVE = 0x20; directory_entry(const fsblk_t::block_t &block, u32 offset) : m_block(block) @@ -171,13 +189,13 @@ public: { } - std::string_view raw_stem() const { return std::string_view((const char *) &m_block.rodata()[m_offset + 0], 8); } - std::string_view raw_ext() const { return std::string_view((const char *) &m_block.rodata()[m_offset + 8], 3); } - u8 attributes() const { return m_block.r8(m_offset + 11); } - u32 raw_create_datetime() const { return m_block.r32l(m_offset + 14); } - u32 raw_modified_datetime() const { return m_block.r32l(m_offset + 22); } - u32 start_cluster() const { return ((u32)m_block.r16l(m_offset + 20)) << 16 | m_block.r16l(m_offset + 26); } - u32 file_size() const { return m_block.r32l(m_offset + 28); } + std::string_view raw_stem() const { return std::string_view((const char *) &m_block.rodata()[m_offset + OFFSET_FNAME], 8); } + std::string_view raw_ext() const { return std::string_view((const char *) &m_block.rodata()[m_offset + OFFSET_FNAME + 8], 3); } + u8 attributes() const { return m_block.r8(m_offset + OFFSET_ATTRIBUTES); } + u32 raw_create_datetime() const { return m_block.r32l(m_offset + OFFSET_CREATE_DATETIME); } + u32 raw_modified_datetime() const { return m_block.r32l(m_offset + OFFSET_MODIFIED_DATETIME); } + u32 start_cluster() const { return ((u32)m_block.r16l(m_offset + OFFSET_START_CLUSTER_HI)) << 16 | m_block.r16l(m_offset + OFFSET_START_CLUSTER); } + u32 file_size() const { return m_block.r32l(m_offset + OFFSET_FILE_SIZE); } bool is_read_only() const { return (attributes() & 0x01) != 0x00; } bool is_hidden() const { return (attributes() & 0x02) != 0x00; } @@ -191,14 +209,15 @@ public: std::string name() const; meta_data metadata() const; -private: - static constexpr u8 DELETED_FILE_MARKER = 0xe5; + void set_file_size(u32 file_size) { m_block.w32l(m_offset + OFFSET_FILE_SIZE, file_size); } + void set_raw_modified_datetime(u32 datetime) { m_block.w32l(m_offset + OFFSET_MODIFIED_DATETIME, datetime); } + void mark_deleted() { m_block.w8(m_offset + OFFSET_FNAME, DELETED_FILE_MARKER); } +private: fsblk_t::block_t m_block; u32 m_offset; }; - // ======================> directory_span class directory_span @@ -232,24 +251,57 @@ public: virtual std::pair metadata(const std::vector &path) override; virtual std::pair> directory_contents(const std::vector &path) override; virtual std::pair> file_read(const std::vector &path) override; + virtual err_t file_create(const std::vector &path, const meta_data &meta) override; + virtual err_t file_write(const std::vector &path, const std::vector &data) override; + virtual err_t remove(const std::vector &path) override; // methods std::vector get_sectors_from_fat(const directory_entry &dirent) const; + // Boot sector settings + static constexpr u32 OFFSET_BYTES_PER_SECTOR = 0x0b; + static constexpr u32 OFFSET_CLUSTER_SECTOR_COUNT = 0x0d; + static constexpr u32 OFFSET_RESERVED_SECTOR_COUNT = 0x0e; + static constexpr u32 OFFSET_FAT_COUNT = 0x10; + static constexpr u32 OFFSET_DIRECTORY_ENTRY_COUNT = 0x11; + static constexpr u32 OFFSET_FAT_SECTOR_COUNT = 0x16; + private: + static constexpr u32 FIRST_VALID_CLUSTER = 2; + fsblk_t::block_t m_boot_sector_block; std::vector m_file_allocation_table; u32 m_starting_sector; u32 m_sector_count; u16 m_reserved_sector_count; u16 m_bytes_per_sector; + u16 m_root_directory_size; + u16 m_sectors_per_cluster; + u8 m_fat_count; + u16 m_fat_sector_count; u8 m_bits_per_fat_entry; + u32 m_last_cluster_indicator; + u32 m_last_valid_cluster; // methods std::optional find_entity(const std::vector &path) const; directory_span::ptr find_directory(std::vector::const_iterator path_begin, std::vector::const_iterator path_end) const; std::optional find_child(const directory_span ¤t_dir, std::string_view target) const; void iterate_directory_entries(const directory_span &dir, const std::function &callback) const; + bool is_valid_short_filename(std::string &filename); + err_t build_direntry_filename(std::string &filename, std::string &fname); + err_t file_create_root(std::string &fname, u8 attributes = 0); + err_t file_create_directory(directory_entry &dirent, std::string &fname, u8 attributes = 0); + err_t file_create_sector(u32 sector, std::string &fname, u8 attributes); + err_t initialize_directory(u32 directory_cluster, u32 parent_cluster); + err_t initialize_directory_entry(fsblk_t::block_t &dirblk, u32 offset, const std::string_view &fname, u8 attributes, u32 start_cluster); + err_t free_clusters(u32 start_cluster); + void clear_cluster_sectors(u32 cluster, u8 fill_byte); + u32 first_cluster_sector(u32 cluster); + u32 get_next_cluster(u32 cluster); + void set_next_cluster(u32 cluster, u32 next_cluster); + u32 find_free_cluster(); + }; @@ -317,15 +369,27 @@ util::arbitrary_datetime decode_fat_datetime(u32 dt) util::arbitrary_datetime result; memset(&result, 0, sizeof(result)); - result.year = ((dt >> 25) & 0x7F) + 1980; - result.month = (dt >> 21) & 0x0F; - result.day_of_month = (dt >> 16) & 0x1F; - result.hour = (dt >> 11) & 0x1F; - result.minute = (dt >> 5) & 0x3F; - result.second = ((dt >> 0) & 0x1F) * 2; + result.year = ((dt >> 25) & 0x7f) + 1980; + result.month = (dt >> 21) & 0x0f; + result.day_of_month = (dt >> 16) & 0x1f; + result.hour = (dt >> 11) & 0x1f; + result.minute = (dt >> 5) & 0x3f; + result.second = ((dt >> 0) & 0x1f) * 2; return result; } +u32 encode_now_fat_datetime() +{ + auto now = util::arbitrary_datetime::now(); + + return u32((((now.year - 1980) & 0x7f) << 25) | + ((now.month & 0x0f) << 21) | + ((now.day_of_month & 0x1f) << 16) | + ((now.hour & 0x1f) << 11) | + ((now.minute & 0x3f) << 5) | + ((now.second >> 1) & 0x1f)); +} + } @@ -355,7 +419,7 @@ bool fs::fat_image::can_read() const bool fs::fat_image::can_write() const { - return false; + return true; } @@ -429,12 +493,12 @@ std::unique_ptr fs::fat_image::mount_partition(fsblk_t &blockdev, { // load the boot sector block and get some basic info fsblk_t::block_t boot_sector_block = blockdev.get(starting_sector); - u16 reserved_sector_count = boot_sector_block.r16l(14); + u16 reserved_sector_count = boot_sector_block.r16l(impl::OFFSET_RESERVED_SECTOR_COUNT); // load all file allocation table sectors - u32 fat_count = boot_sector_block.r8(16); - u32 sectors_per_fat = boot_sector_block.r16l(22); - u16 bytes_per_sector = boot_sector_block.r16l(11); + u32 fat_count = boot_sector_block.r8(impl::OFFSET_FAT_COUNT); + u32 sectors_per_fat = boot_sector_block.r16l(impl::OFFSET_FAT_SECTOR_COUNT); + u16 bytes_per_sector = boot_sector_block.r16l(impl::OFFSET_BYTES_PER_SECTOR); std::vector file_allocation_table; file_allocation_table.reserve(fat_count * sectors_per_fat * bytes_per_sector); for (auto i = 0; i < fat_count * sectors_per_fat; i++) @@ -489,8 +553,14 @@ impl::impl(fsblk_t &blockdev, fsblk_t::block_t &&boot_sector_block, std::vector< , m_starting_sector(starting_sector) , m_sector_count(sector_count) , m_reserved_sector_count(reserved_sector_count) - , m_bytes_per_sector(m_boot_sector_block.r16l(11)) + , m_bytes_per_sector(m_boot_sector_block.r16l(OFFSET_BYTES_PER_SECTOR)) + , m_root_directory_size(m_boot_sector_block.r16l(OFFSET_DIRECTORY_ENTRY_COUNT)) + , m_sectors_per_cluster(m_boot_sector_block.r8(OFFSET_CLUSTER_SECTOR_COUNT)) + , m_fat_count(m_boot_sector_block.r8(OFFSET_FAT_COUNT)) + , m_fat_sector_count(m_boot_sector_block.r16l(OFFSET_FAT_SECTOR_COUNT)) , m_bits_per_fat_entry(bits_per_fat_entry) + , m_last_cluster_indicator(((u64)1 << bits_per_fat_entry) - 1) + , m_last_valid_cluster(m_last_cluster_indicator - 0x10) { } @@ -594,6 +664,339 @@ std::pair> impl::file_read(const std::vector } +bool impl::is_valid_short_filename(std::string &filename) +{ + /* + Valid characters in DOS file names: + - Upper case letters A-Z + - Numbers 0-9 + - Space (though there is no way to identify a trailing space) + - ! # $ % & ( ) - @ ^ _ ` { } ~ + - Characters 128-255, except e5 (though the code page is indeterminate) + We currently do not check for characters 128-255. + */ + std::regex filename_regex("([A-Z0-9!#\\$%&\\(\\)\\-@^_`\\{\\}~]{0,8})(\\.([A-Z0-9!#\\$%&\\(\\)\\-@^_`\\{\\}~]{0,3}))?"); + return std::regex_match(filename, filename_regex); +} + + +err_t impl::build_direntry_filename(std::string &filename, std::string &fname) +{ + std::regex filename_regex("([A-Z0-9!#\\$%&\\(\\)\\-@^_`\\{\\}~]{0,8})(\\.([A-Z0-9!#\\$%&\\(\\)\\-@^_`\\{\\}~]{0,3}))?"); + std::smatch smatch; + if (!std::regex_match(filename, smatch, filename_regex)) + return ERR_INVALID; + if (smatch.size() != 4) + return ERR_INVALID; + + fname.resize(directory_entry::FNAME_LENGTH, ' '); + + for (int i = 0; i < 8 && i < smatch.str(1).size(); i++) + fname[i] = smatch.str(1)[i]; + + for (int j = 0; j < 3 && j < smatch.str(3).size(); j++) + fname[8 + j] = smatch.str(3)[j]; + + return ERR_OK; +} + + +err_t impl::file_create(const std::vector &path, const meta_data &meta) +{ + std::string filename = meta.get_string(meta_name::name, ""); + std::string fname; + err_t err = build_direntry_filename(filename, fname); + if (err != ERR_OK) + return err; + + if (path.empty()) + { + return file_create_root(fname); + } else { + // Make sure that all parts of the path exist, creating the path parts as needed. + std::optional dirent = find_entity(path); + if (!dirent) + { + std::vector partial_path; + std::optional parent_entry = { }; + for (auto path_part : path) + { + partial_path.emplace_back(path_part); + std::optional dir_entry = find_entity(partial_path); + if (!dir_entry) + { + if (!is_valid_short_filename(path_part)) + return ERR_INVALID; + + std::string part_fname; + err_t err = build_direntry_filename(path_part, part_fname); + if (err != ERR_OK) + return err; + err = !parent_entry ? + file_create_root(part_fname, directory_entry::ATTR_DIRECTORY) : + file_create_directory(parent_entry.value(), part_fname, directory_entry::ATTR_DIRECTORY); + if (err != ERR_OK) + return err; + + dir_entry = find_entity(partial_path); + if (!dir_entry) + return ERR_INVALID; + + err = initialize_directory(dir_entry->start_cluster(), parent_entry ? parent_entry->start_cluster() : 0); + if (err != ERR_OK) + return err; + } + else + { + if (!dir_entry->is_subdirectory()) + return ERR_INVALID; + } + parent_entry = dir_entry; + } + + dirent = find_entity(path); + if (!dirent) + return ERR_INVALID; + } + + return file_create_directory(dirent.value(), fname); + } +} + + +err_t impl::initialize_directory(u32 directory_cluster, u32 parent_cluster) +{ + clear_cluster_sectors(directory_cluster, 0x00); + + auto dirblk = m_blockdev.get(first_cluster_sector(directory_cluster)); + + // Add special directory entries for . and .. + std::string dir_fname; + dir_fname.resize(directory_entry::FNAME_LENGTH, ' '); + dir_fname[0] = '.'; + err_t err = initialize_directory_entry(dirblk, 0, dir_fname, directory_entry::ATTR_DIRECTORY, directory_cluster); + if (err != ERR_OK) + return err; + + dir_fname[1] = '.'; + err = initialize_directory_entry(dirblk, directory_entry::SIZE, dir_fname, directory_entry::ATTR_DIRECTORY, parent_cluster); + if (err != ERR_OK) + return err; + + return ERR_OK; +} + + +err_t impl::initialize_directory_entry(fsblk_t::block_t &dirblk, u32 offset, const std::string_view &fname, u8 attributes, u32 start_cluster) +{ + if (fname.size() != directory_entry::FNAME_LENGTH) + return ERR_INVALID; + + for (int i = 0; i < directory_entry::SIZE; i += 4) + dirblk.w32l(offset + i, 0); + + dirblk.wstr(offset + directory_entry::OFFSET_FNAME, fname); + dirblk.w8(offset + directory_entry::OFFSET_ATTRIBUTES, attributes); + dirblk.w32l(offset + directory_entry::OFFSET_CREATE_DATETIME, encode_now_fat_datetime()); + dirblk.w32l(offset + directory_entry::OFFSET_MODIFIED_DATETIME, encode_now_fat_datetime()); + dirblk.w16l(offset + directory_entry::OFFSET_START_CLUSTER_HI, u16(start_cluster >> 16)); + dirblk.w16l(offset + directory_entry::OFFSET_START_CLUSTER, u16(start_cluster & 0xffff)); + + return ERR_OK; +} + + +err_t impl::file_create_root(std::string &fname, u8 attributes) +{ + const u32 first_directory_sector = m_starting_sector + m_reserved_sector_count + ((u32)m_file_allocation_table.size() / m_bytes_per_sector); + const u32 directory_sector_count = (m_root_directory_size * directory_entry::SIZE) / m_bytes_per_sector; + for (u32 sector = first_directory_sector; sector < first_directory_sector + directory_sector_count; sector++) + { + err_t err = file_create_sector(sector, fname, attributes); + if (err != ERR_NOT_FOUND) + return err; + } + return ERR_NO_SPACE; +} + + +err_t impl::file_create_directory(directory_entry &dirent, std::string &fname, u8 attributes) +{ + u32 current_cluster = dirent.start_cluster(); + do { + const u32 first_sector = first_cluster_sector(current_cluster); + for (int i = 0; i < m_sectors_per_cluster; i++) { + err_t err = file_create_sector(first_sector + i, fname, attributes); + if (err != ERR_NOT_FOUND) + return err; + } + + // File could not be created yet. Move to next cluster, allocating a new cluster when needed. + u32 next_cluster = get_next_cluster(current_cluster); + if (next_cluster >= m_last_valid_cluster) { + next_cluster = find_free_cluster(); + if (next_cluster == 0) + return ERR_NO_SPACE; + + set_next_cluster(current_cluster, next_cluster); + set_next_cluster(next_cluster, m_last_cluster_indicator); + + clear_cluster_sectors(next_cluster, 0x00); + } + current_cluster = next_cluster; + } while (current_cluster > FIRST_VALID_CLUSTER && current_cluster < m_last_valid_cluster); + return ERR_NO_SPACE; +} + + +u32 impl::first_cluster_sector(u32 cluster) +{ + return m_starting_sector + m_reserved_sector_count + + ((u32)m_file_allocation_table.size() / m_bytes_per_sector) + + ((m_root_directory_size + 1) / dirents_per_sector()) + + ((cluster - FIRST_VALID_CLUSTER) * m_sectors_per_cluster); +} + + +void impl::clear_cluster_sectors(u32 cluster, u8 fill_byte) +{ + const u32 sector = first_cluster_sector(cluster); + for (int i = 0; i < m_sectors_per_cluster; i++) { + auto dirblk = m_blockdev.get(sector + i); + for (int offset = 0; offset < m_bytes_per_sector; offset++) + dirblk.w8(offset, fill_byte); + } +} + + +// Returns ERR_NOT_FOUND when no room could be found to create the file in the sector. +err_t impl::file_create_sector(u32 sector, std::string &fname, u8 attributes) +{ + auto dirblk = m_blockdev.get(sector); + for (u32 blkoffset = 0; blkoffset < m_bytes_per_sector; blkoffset += directory_entry::SIZE) + { + u8 first_byte = dirblk.r8(blkoffset); + if (first_byte == 0x00 || first_byte == directory_entry::DELETED_FILE_MARKER) + { + u32 start_cluster = find_free_cluster(); + if (start_cluster == 0) + return ERR_NO_SPACE; + set_next_cluster(start_cluster, m_last_cluster_indicator); + + err_t err = initialize_directory_entry(dirblk, blkoffset, fname, attributes, start_cluster); + if (err != ERR_OK) + return err; + + return ERR_OK; + } + } + return ERR_NOT_FOUND; +} + + +err_t impl::file_write(const std::vector &path, const std::vector &data) +{ + std::optional dirent = find_entity(path); + if (!dirent) + return ERR_NOT_FOUND; + + if (dirent->is_subdirectory()) + return ERR_INVALID; + + u32 current_length = dirent->file_size(); + const size_t data_length = data.size(); + const u32 bytes_per_cluster = m_sectors_per_cluster * bytes_per_sector(); + const u32 current_clusters = (current_length + bytes_per_cluster - 1) / bytes_per_cluster; + const u32 required_clusters = (data_length + bytes_per_cluster - 1) / bytes_per_cluster; + + if (required_clusters > current_clusters) + { + u32 current_cluster = dirent->start_cluster(); + u32 next_cluster = 0; + do { + next_cluster = get_next_cluster(current_cluster); + if (next_cluster < FIRST_VALID_CLUSTER) + return ERR_INVALID; + } while (next_cluster < m_last_valid_cluster); + for (int i = current_clusters; i < required_clusters; i++) + { + u32 free_cluster = find_free_cluster(); + if (free_cluster < FIRST_VALID_CLUSTER) + return ERR_NO_SPACE; + + set_next_cluster(current_cluster, free_cluster); + set_next_cluster(free_cluster, m_last_cluster_indicator); + current_cluster = free_cluster; + } + } + if (required_clusters < current_clusters) + { + u32 current_cluster = dirent->start_cluster(); + for (int i = 0; i < required_clusters; i++) + { + current_cluster = get_next_cluster(current_cluster); + } + u32 next_cluster = get_next_cluster(current_cluster); + set_next_cluster(current_cluster, m_last_cluster_indicator); + + err_t err = free_clusters(next_cluster); + if (err != ERR_OK) + return err; + } + + auto sectors = get_sectors_from_fat(*dirent); + size_t offset = 0; + for (auto sector : sectors) + { + if (offset < data_length) + { + auto datablk = m_blockdev.get(sector); + u32 bytes = (data_length - offset > m_bytes_per_sector) ? m_bytes_per_sector : data_length - offset; + memcpy(datablk.data(), data.data() + offset, bytes); + offset += m_bytes_per_sector; + } + } + + dirent->set_raw_modified_datetime(encode_now_fat_datetime()); + dirent->set_file_size(data_length); + + return ERR_OK; +} + + +err_t impl::remove(const std::vector &path) +{ + if (path.size() != 0) + return ERR_UNSUPPORTED; + + std::optional dirent = find_entity(path); + if (!dirent) + return ERR_OK; + + // Removing directories is not supported yet + if (dirent->is_subdirectory()) + return ERR_UNSUPPORTED; + + dirent->mark_deleted(); + + return ERR_OK; +} + + +err_t impl::free_clusters(u32 start_cluster) +{ + while (start_cluster < m_last_valid_cluster) + { + if (start_cluster < FIRST_VALID_CLUSTER) + return ERR_INVALID; + + u32 next_cluster = get_next_cluster(start_cluster); + set_next_cluster(start_cluster, 0); + start_cluster = next_cluster; + } + return ERR_OK; +} + //------------------------------------------------- // impl::get_sectors_from_fat //------------------------------------------------- @@ -605,22 +1008,20 @@ std::vector impl::get_sectors_from_fat(const directory_entry &dirent) const results.reserve(dirent.file_size() / bytes_per_sector()); // get critical information - u8 sectors_per_cluster = m_boot_sector_block.r8(13); - u16 root_directory_entry_count = m_boot_sector_block.r16l(17); - u16 root_directory_sector_count = (root_directory_entry_count + 1) / dirents_per_sector(); - u32 fat_sector_count = (u32)(m_file_allocation_table.size() / m_bytes_per_sector); + u16 root_directory_sector_count = (m_root_directory_size + 1) / dirents_per_sector(); + u32 fat_sector_count = m_fat_count * m_fat_sector_count; u32 data_starting_sector = m_starting_sector + m_reserved_sector_count + fat_sector_count + root_directory_sector_count; - u32 data_cluster_count = (m_sector_count - data_starting_sector) / sectors_per_cluster; + u32 data_cluster_count = (m_sector_count - data_starting_sector) / m_sectors_per_cluster; // find all clusters u32 start_cluster_mask = ((u64)1 << m_bits_per_fat_entry) - 1; u32 cluster = dirent.start_cluster() & start_cluster_mask; - while (cluster >= 2 && cluster < (data_cluster_count + 2)) + while (cluster >= FIRST_VALID_CLUSTER && cluster < (data_cluster_count + 2)) { // add the sectors for this cluster - for (auto i = 0; i < sectors_per_cluster; i++) - results.push_back(data_starting_sector + (cluster - 2) * sectors_per_cluster + i); + for (auto i = 0; i < m_sectors_per_cluster; i++) + results.push_back(data_starting_sector + (cluster - FIRST_VALID_CLUSTER) * m_sectors_per_cluster + i); // determine the bit position of this entry u32 entry_bit_position = cluster * m_bits_per_fat_entry; @@ -644,8 +1045,8 @@ std::vector impl::get_sectors_from_fat(const directory_entry &dirent) const } // normalize special cluster IDs - if (new_cluster > ((u32)1 << m_bits_per_fat_entry) - 0x10) - new_cluster |= ~(((u32)1 << m_bits_per_fat_entry) - 1); + if (new_cluster > m_last_valid_cluster) + new_cluster |= ~m_last_cluster_indicator; } cluster = new_cluster; } @@ -654,6 +1055,89 @@ std::vector impl::get_sectors_from_fat(const directory_entry &dirent) const } +u32 impl::get_next_cluster(u32 cluster) +{ + u32 entry_bit_position = cluster * m_bits_per_fat_entry; + u32 new_cluster = 0; + if (entry_bit_position + m_bits_per_fat_entry <= m_file_allocation_table.size() * 8) + { + u32 current_bit = 0; + while (current_bit < m_bits_per_fat_entry) + { + u32 pos = entry_bit_position + current_bit; + u32 shift = pos % 8; + u32 bit_count = std::min(8 - shift, m_bits_per_fat_entry - current_bit); + u32 bits = (m_file_allocation_table[pos / 8] >> shift) & ((1 << bit_count) - 1); + + new_cluster |= (bits << current_bit); + current_bit += bit_count; + } + } + return new_cluster; +} + + +void impl::set_next_cluster(u32 cluster, u32 next_cluster) +{ + const u32 m_fat_start_sector = m_starting_sector + m_reserved_sector_count; + const u32 entry_bit_position = cluster * m_bits_per_fat_entry; + if (entry_bit_position + m_bits_per_fat_entry <= m_file_allocation_table.size() * 8) + { + u32 current_bit = 0; + while (current_bit < m_bits_per_fat_entry) + { + u32 pos = entry_bit_position + current_bit; + u32 shift = pos % 8; + u32 bit_count = std::min(8 - shift, m_bits_per_fat_entry - current_bit); + u32 byte_pos = pos / 8; + u32 mask = ((1 << bit_count) - 1); + m_file_allocation_table[byte_pos] = (m_file_allocation_table[byte_pos] & ~(mask << shift)) | ((next_cluster & mask) << shift); + next_cluster = next_cluster >> bit_count; + current_bit += bit_count; + // Write back to backing blocks + for (int i = 0; i < m_fat_count; i++) { + u32 fat_sector = m_fat_start_sector + (i * m_fat_sector_count) + (byte_pos / m_bytes_per_sector); + auto fatblk = m_blockdev.get(fat_sector); + fatblk.w8(byte_pos % m_bytes_per_sector, m_file_allocation_table[byte_pos]); + } + } + } +} + + +// Returns 0 if no free cluster could be found +u32 impl::find_free_cluster() +{ + u16 root_directory_sector_count = (m_root_directory_size + 1) / dirents_per_sector(); + u32 fat_sector_count = m_fat_count * m_fat_sector_count; + u32 data_starting_sector = m_starting_sector + m_reserved_sector_count + fat_sector_count + root_directory_sector_count; + u32 data_cluster_count = (m_sector_count - data_starting_sector) / m_sectors_per_cluster; + + for (u32 cluster = FIRST_VALID_CLUSTER; cluster < (data_cluster_count + 2); cluster++) { + u32 entry_bit_position = cluster * m_bits_per_fat_entry; + + if (entry_bit_position + m_bits_per_fat_entry <= m_file_allocation_table.size() * 8) + { + u32 new_cluster = 0; + u32 current_bit = 0; + while (current_bit < m_bits_per_fat_entry) + { + u32 pos = entry_bit_position + current_bit; + u32 shift = pos % 8; + u32 bit_count = std::min(8 - shift, m_bits_per_fat_entry - current_bit); + u32 bits = (m_file_allocation_table[pos / 8] >> shift) & ((1 << bit_count) - 1); + + new_cluster |= (bits << current_bit); + current_bit += bit_count; + } + + if (new_cluster == 0) + return cluster; + } + } + return 0; +} + //------------------------------------------------- // impl::find_entity //------------------------------------------------- @@ -682,8 +1166,7 @@ directory_span::ptr impl::find_directory(std::vector::const_iterato { // the root directory is treated differently u32 first_sector = m_starting_sector + m_reserved_sector_count + (u32)m_file_allocation_table.size() / m_bytes_per_sector; - u16 directory_entry_count = m_boot_sector_block.r16l(17); - directory_span::ptr current_dir = std::make_unique(*this, first_sector, directory_entry_count); + directory_span::ptr current_dir = std::make_unique(*this, first_sector, m_root_directory_size); // traverse the directory for (auto iter = path_begin; iter != path_end; iter++)