diff options
Diffstat (limited to 'src/lib/formats/fs_fat.cpp')
-rw-r--r-- | src/lib/formats/fs_fat.cpp | 686 |
1 files changed, 605 insertions, 81 deletions
diff --git a/src/lib/formats/fs_fat.cpp b/src/lib/formats/fs_fat.cpp index f4cd99e36ed..8e47bbc5abe 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: @@ -66,13 +68,13 @@ 14 2 Reserved sector count (including boot sector) 16 1 Number of FATs (file allocation tables) 17 2 Number of root directory entries - 19 2 Total sectors (bits 0-15) + 19 2 Total sectors (0 if 0x10000 or more) 21 1 Media descriptor 22 2 Sectors per FAT 24 2 Sectors per track 26 2 Number of heads 28 4 Hidden sectors - 32 4 Total sectors (bits 16-47) + 32 4 Total sectors (0 if less than 0x10000) 36 1 Physical drive number 37 1 Current head 38 1 Signature @@ -147,6 +149,9 @@ #include "strformat.h" #include <optional> +#include <regex> +#include <string_view> + using namespace fs; @@ -163,41 +168,58 @@ namespace { class directory_entry { public: - static const int SIZE = 32; - - directory_entry(const fsblk_t::block_t &block, u32 offset) + 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(fsblk_t::block_t::ptr block, u32 offset) : m_block(block) , m_offset(offset) { } - 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 m_block->rstr(m_offset + OFFSET_FNAME, 8); } + std::string_view raw_ext() const { return m_block->rstr(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; } bool is_system() const { return (attributes() & 0x04) != 0x00; } bool is_volume_label() const { return (attributes() & 0x08) != 0x00; } + bool is_long_file_name() const { return attributes() == 0x0f; } bool is_subdirectory() const { return (attributes() & 0x10) != 0x00; } bool is_archive() const { return (attributes() & 0x20) != 0x00; } - bool is_deleted() const { return m_block.r8(m_offset) == DELETED_FILE_MARKER; } + bool is_deleted() const { return m_block->r8(m_offset) == DELETED_FILE_MARKER; } 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); } - fsblk_t::block_t m_block; - u32 m_offset; +private: + fsblk_t::block_t::ptr m_block; + u32 m_offset; }; - // ======================> directory_span class directory_span @@ -218,7 +240,7 @@ class impl : public filesystem_t { public: // ctor/dtor - impl(fsblk_t &blockdev, fsblk_t::block_t &&boot_sector_block, std::vector<u8> &&file_allocation_table, u32 starting_sector, u32 sector_count, u16 reserved_sector_count, u8 bits_per_fat_entry); + impl(fsblk_t &blockdev, fsblk_t::block_t::ptr &&boot_sector_block, std::vector<u8> &&file_allocation_table, u32 starting_sector, u32 sector_count, u16 reserved_sector_count, u8 bits_per_fat_entry); virtual ~impl() = default; // accessors @@ -228,27 +250,60 @@ public: // virtuals virtual meta_data volume_metadata() override; - virtual std::pair<err_t, meta_data> metadata(const std::vector<std::string> &path) override; - virtual std::pair<err_t, std::vector<dir_entry>> directory_contents(const std::vector<std::string> &path) override; - virtual std::pair<err_t, std::vector<u8>> file_read(const std::vector<std::string> &path) override; + virtual std::pair<std::error_condition, meta_data> metadata(const std::vector<std::string> &path) override; + virtual std::pair<std::error_condition, std::vector<dir_entry>> directory_contents(const std::vector<std::string> &path) override; + virtual std::pair<std::error_condition, std::vector<u8>> file_read(const std::vector<std::string> &path) override; + virtual std::error_condition file_create(const std::vector<std::string> &path, const meta_data &meta) override; + virtual std::error_condition file_write(const std::vector<std::string> &path, const std::vector<u8> &data) override; + virtual std::error_condition remove(const std::vector<std::string> &path) override; // methods std::vector<u32> 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: - fsblk_t::block_t m_boot_sector_block; + static constexpr u32 FIRST_VALID_CLUSTER = 2; + + fsblk_t::block_t::ptr m_boot_sector_block; std::vector<u8> 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<directory_entry> find_entity(const std::vector<std::string> &path) const; directory_span::ptr find_directory(std::vector<std::string>::const_iterator path_begin, std::vector<std::string>::const_iterator path_end) const; std::optional<directory_entry> find_child(const directory_span ¤t_dir, std::string_view target) const; - void iterate_directory_entries(const directory_span &dir, const std::function<bool(const directory_entry &dirent)> &callback) const; + template <typename T> void iterate_directory_entries(const directory_span &dir, T &&callback) const; + bool is_valid_short_filename(std::string const &filename); + std::error_condition build_direntry_filename(std::string const &filename, std::string &fname); + std::error_condition file_create_root(std::string &fname, u8 attributes = 0); + std::error_condition file_create_directory(directory_entry &dirent, std::string &fname, u8 attributes = 0); + std::error_condition file_create_sector(u32 sector, std::string &fname, u8 attributes); + std::error_condition initialize_directory(u32 directory_cluster, u32 parent_cluster); + std::error_condition initialize_directory_entry(fsblk_t::block_t &dirblk, u32 offset, std::string_view fname, u8 attributes, u32 start_cluster); + std::error_condition 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(); + }; @@ -297,7 +352,7 @@ private: namespace { bool validate_filename(std::string_view name) { - auto is_invalid_filename_char = [](char ch) + auto const is_invalid_filename_char = [] (char ch) { return ch == '\0' || strchr("\\/:*?\"<>|", ch); }; @@ -316,15 +371,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)); +} + } @@ -354,7 +421,7 @@ bool fs::fat_image::can_read() const bool fs::fat_image::can_write() const { - return false; + return true; } @@ -385,8 +452,8 @@ char fs::fat_image::directory_separator() const std::vector<meta_description> fs::fat_image::volume_meta_description() const { std::vector<meta_description> results; - results.emplace_back(meta_name::name, "UNTITLED", false, [](const meta_value &m) { return m.as_string().size() <= 11; }, "Volume name, up to 11 characters"); - results.emplace_back(meta_name::oem_name, "", false, [](const meta_value &m) { return m.as_string().size() <= 8; }, "OEM name, up to 8 characters"); + results.emplace_back(meta_name::name, "UNTITLED", false, [] (const meta_value &m) { return validate_filename(m.as_string()); }, "Volume name"); + results.emplace_back(meta_name::oem_name, "", false, [] (const meta_value &m) { return m.as_string().size() <= 8; }, "OEM name, up to 8 characters"); return results; } @@ -398,7 +465,7 @@ std::vector<meta_description> fs::fat_image::volume_meta_description() const std::vector<meta_description> fs::fat_image::file_meta_description() const { std::vector<meta_description> results; - results.emplace_back(meta_name::name, "", false, [](const meta_value &m) { return validate_filename(m.as_string()); }, "File name"); + results.emplace_back(meta_name::name, "", false, [] (const meta_value &m) { return validate_filename(m.as_string()); }, "File name"); results.emplace_back(meta_name::creation_date, util::arbitrary_datetime::now(), false, nullptr, "Creation time"); results.emplace_back(meta_name::modification_date, util::arbitrary_datetime::now(), false, nullptr, "Modification time"); results.emplace_back(meta_name::length, 0, true, nullptr, "Size of the file in bytes"); @@ -413,7 +480,7 @@ std::vector<meta_description> fs::fat_image::file_meta_description() const std::vector<meta_description> fs::fat_image::directory_meta_description() const { std::vector<meta_description> results; - results.emplace_back(meta_name::name, "", false, [](const meta_value &m) { return validate_filename(m.as_string()); }, "File name"); + results.emplace_back(meta_name::name, "", false, [] (const meta_value &m) { return validate_filename(m.as_string()); }, "File name"); results.emplace_back(meta_name::creation_date, util::arbitrary_datetime::now(), false, nullptr, "Creation time"); results.emplace_back(meta_name::modification_date, util::arbitrary_datetime::now(), false, nullptr, "Modification time"); return results; @@ -427,19 +494,19 @@ std::vector<meta_description> fs::fat_image::directory_meta_description() const std::unique_ptr<filesystem_t> fs::fat_image::mount_partition(fsblk_t &blockdev, u32 starting_sector, u32 sector_count, u8 bits_per_fat_entry) { // 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); + fsblk_t::block_t::ptr boot_sector_block = blockdev.get(starting_sector); + 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<u8> 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++) { - fsblk_t::block_t fatblk = blockdev.get(starting_sector + reserved_sector_count + i); - file_allocation_table.insert(file_allocation_table.end(), fatblk.rodata(), fatblk.rodata() + bytes_per_sector); + fsblk_t::block_t::ptr fatblk = blockdev.get(starting_sector + reserved_sector_count + i); + file_allocation_table.insert(file_allocation_table.end(), fatblk->rodata(), fatblk->rodata() + bytes_per_sector); } // and return the implementation @@ -481,16 +548,24 @@ meta_data directory_entry::metadata() const // impl ctor //------------------------------------------------- -impl::impl(fsblk_t &blockdev, fsblk_t::block_t &&boot_sector_block, std::vector<u8> &&file_allocation_table, u32 starting_sector, u32 sector_count, u16 reserved_sector_count, u8 bits_per_fat_entry) +impl::impl(fsblk_t &blockdev, fsblk_t::block_t::ptr &&boot_sector_block, std::vector<u8> &&file_allocation_table, u32 starting_sector, u32 sector_count, u16 reserved_sector_count, u8 bits_per_fat_entry) : filesystem_t(blockdev, 512) , m_boot_sector_block(std::move(boot_sector_block)) , m_file_allocation_table(std::move(file_allocation_table)) , 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) { + if (m_bytes_per_sector == 0) + m_bytes_per_sector = 512; } @@ -500,9 +575,26 @@ impl::impl(fsblk_t &blockdev, fsblk_t::block_t &&boot_sector_block, std::vector< meta_data impl::volume_metadata() { + std::vector<std::string> root_path; + directory_span::ptr root_dir = find_directory(root_path.begin(), root_path.end()); + assert(root_dir); + + // Get the volume label from the root directory, not the extended BPB (whose name field may not be kept up-to-date even when it exists) meta_data results; - results.set(meta_name::name, m_boot_sector_block.rstr(43, 11)); - results.set(meta_name::oem_name, m_boot_sector_block.rstr(3, 8)); + auto const callback = [&results] (const directory_entry &dirent) + { + if (dirent.is_volume_label()) + { + results.set(meta_name::name, dirent.name()); + return true; + } + return false; + }; + iterate_directory_entries(*root_dir, callback); + if (!results.has(meta_name::name)) + results.set(meta_name::name, "UNTITLED"); + + results.set(meta_name::oem_name, m_boot_sector_block->rstr(3, 8)); return results; } @@ -511,13 +603,13 @@ meta_data impl::volume_metadata() // impl::metadata //------------------------------------------------- -std::pair<err_t, meta_data> impl::metadata(const std::vector<std::string> &path) +std::pair<std::error_condition, meta_data> impl::metadata(const std::vector<std::string> &path) { std::optional<directory_entry> dirent = find_entity(path); if (!dirent) - return std::make_pair(ERR_NOT_FOUND, meta_data()); + return std::make_pair(error::not_found, meta_data()); - return std::make_pair(ERR_OK, dirent->metadata()); + return std::make_pair(std::error_condition(), dirent->metadata()); } @@ -525,21 +617,24 @@ std::pair<err_t, meta_data> impl::metadata(const std::vector<std::string> &path) // impl::directory_contents //------------------------------------------------- -std::pair<err_t, std::vector<dir_entry>> impl::directory_contents(const std::vector<std::string> &path) +std::pair<std::error_condition, std::vector<dir_entry>> impl::directory_contents(const std::vector<std::string> &path) { directory_span::ptr dir = find_directory(path.begin(), path.end()); if (!dir) - return std::make_pair(ERR_NOT_FOUND, std::vector<dir_entry>()); + return std::make_pair(error::not_found, std::vector<dir_entry>()); std::vector<dir_entry> results; - auto callback = [&results](const directory_entry &dirent) + auto const callback = [&results] (const directory_entry &dirent) { - dir_entry_type entry_type = dirent.is_subdirectory() ? dir_entry_type::dir : dir_entry_type::file; - results.emplace_back(entry_type, dirent.metadata()); + if (!dirent.is_volume_label()) + { + dir_entry_type entry_type = dirent.is_subdirectory() ? dir_entry_type::dir : dir_entry_type::file; + results.emplace_back(entry_type, dirent.metadata()); + } return false; }; iterate_directory_entries(*dir, callback); - return std::make_pair(ERR_OK, std::move(results)); + return std::make_pair(std::error_condition(), std::move(results)); } @@ -547,12 +642,14 @@ std::pair<err_t, std::vector<dir_entry>> impl::directory_contents(const std::vec // impl::file_read //------------------------------------------------- -std::pair<err_t, std::vector<u8>> impl::file_read(const std::vector<std::string> &path) +std::pair<std::error_condition, std::vector<u8>> impl::file_read(const std::vector<std::string> &path) { // find the file std::optional<directory_entry> dirent = find_entity(path); - if (!dirent || dirent->is_subdirectory()) - return std::make_pair(ERR_NOT_FOUND, std::vector<u8>()); + if (!dirent) + return std::make_pair(error::not_found, std::vector<u8>()); + if (dirent->is_subdirectory()) + return std::make_pair(error::invalid_name, std::vector<u8>()); // get the list of sectors for this file std::vector<u32> sectors = get_sectors_from_fat(*dirent); @@ -564,15 +661,353 @@ std::pair<err_t, std::vector<u8>> impl::file_read(const std::vector<std::string> // and add data from all sectors for (u32 sector : sectors) { - fsblk_t::block_t block = m_blockdev.get(sector); - const u8 *data = block.rodata(); - size_t length = std::min((size_t)dirent->file_size() - result.size(), (size_t)block.size()); + fsblk_t::block_t::ptr block = m_blockdev.get(sector); + const u8 *data = block->rodata(); + size_t length = std::min((size_t)dirent->file_size() - result.size(), (size_t)block->size()); result.insert(result.end(), data, data + length); } - return std::make_pair(ERR_OK, std::move(result)); + return std::make_pair(std::error_condition(), std::move(result)); +} + + +bool impl::is_valid_short_filename(std::string const &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); +} + + +std::error_condition impl::build_direntry_filename(std::string const &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 error::invalid_name; + if (smatch.size() != 4) + return error::invalid_name; + + 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 std::error_condition(); +} + + +std::error_condition impl::file_create(const std::vector<std::string> &path, const meta_data &meta) +{ + std::string filename = meta.get_string(meta_name::name, ""); + std::string fname; + std::error_condition err = build_direntry_filename(filename, fname); + if (err) + 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<directory_entry> dirent = find_entity(path); + if (!dirent) + { + std::vector<std::string> partial_path; + std::optional<directory_entry> parent_entry; + for (auto const &path_part : path) + { + partial_path.emplace_back(path_part); + std::optional<directory_entry> dir_entry = find_entity(partial_path); + if (!dir_entry) + { + if (!is_valid_short_filename(path_part)) + return error::invalid_name; + + std::string part_fname; + std::error_condition err = build_direntry_filename(path_part, part_fname); + if (err) + 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) + return err; + + dir_entry = find_entity(partial_path); + if (!dir_entry) + return error::invalid_name; + + err = initialize_directory(dir_entry->start_cluster(), parent_entry ? parent_entry->start_cluster() : 0); + if (err) + return err; + } + else + { + if (!dir_entry->is_subdirectory()) + return error::invalid_name; + } + parent_entry = dir_entry; + } + + dirent = find_entity(path); + if (!dirent) + return error::invalid_name; + } + + return file_create_directory(*dirent, fname); + } +} + + +std::error_condition 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] = '.'; + std::error_condition err = initialize_directory_entry(*dirblk, 0, dir_fname, directory_entry::ATTR_DIRECTORY, directory_cluster); + if (err) + return err; + + dir_fname[1] = '.'; + err = initialize_directory_entry(*dirblk, directory_entry::SIZE, dir_fname, directory_entry::ATTR_DIRECTORY, parent_cluster); + if (err) + return err; + + return std::error_condition(); +} + + +std::error_condition impl::initialize_directory_entry(fsblk_t::block_t &dirblk, u32 offset, std::string_view fname, u8 attributes, u32 start_cluster) +{ + if (fname.size() != directory_entry::FNAME_LENGTH) + return error::invalid_name; + + 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 std::error_condition(); +} + + +std::error_condition 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++) + { + std::error_condition err = file_create_sector(sector, fname, attributes); + if (err != error::not_found) + return err; + } + return error::no_space; +} + + +std::error_condition 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++) + { + std::error_condition err = file_create_sector(first_sector + i, fname, attributes); + if (err != error::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 error::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 error::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 error::not_found when no room could be found to create the file in the sector. +std::error_condition 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 error::no_space; + set_next_cluster(start_cluster, m_last_cluster_indicator); + + std::error_condition err = initialize_directory_entry(*dirblk, blkoffset, fname, attributes, start_cluster); + if (err) + return err; + + return std::error_condition(); + } + } + return error::not_found; +} + + +std::error_condition impl::file_write(const std::vector<std::string> &path, const std::vector<u8> &data) +{ + std::optional<directory_entry> dirent = find_entity(path); + if (!dirent) + return error::not_found; + + if (dirent->is_subdirectory()) + return error::invalid_name; + + 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 error::invalid_block; + } 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 error::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); + + std::error_condition err = free_clusters(next_cluster); + if (err) + 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; + datablk->write(0, 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 std::error_condition(); +} + + +std::error_condition impl::remove(const std::vector<std::string> &path) +{ + if (path.size() != 0) + return error::unsupported; + + std::optional<directory_entry> dirent = find_entity(path); + if (!dirent) + return std::error_condition(); + + // Removing directories is not supported yet + if (dirent->is_subdirectory()) + return error::unsupported; + + dirent->mark_deleted(); + + return std::error_condition(); +} + + +std::error_condition impl::free_clusters(u32 start_cluster) +{ + while (start_cluster < m_last_valid_cluster) + { + if (start_cluster < FIRST_VALID_CLUSTER) + return error::invalid_block; + + u32 next_cluster = get_next_cluster(start_cluster); + set_next_cluster(start_cluster, 0); + start_cluster = next_cluster; + } + return std::error_condition(); +} + //------------------------------------------------- // impl::get_sectors_from_fat //------------------------------------------------- @@ -584,22 +1019,20 @@ std::vector<u32> 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; @@ -623,8 +1056,8 @@ std::vector<u32> 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; } @@ -633,6 +1066,91 @@ std::vector<u32> 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 //------------------------------------------------- @@ -661,8 +1179,7 @@ directory_span::ptr impl::find_directory(std::vector<std::string>::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<root_directory_span>(*this, first_sector, directory_entry_count); + directory_span::ptr current_dir = std::make_unique<root_directory_span>(*this, first_sector, m_root_directory_size); // traverse the directory for (auto iter = path_begin; iter != path_end; iter++) @@ -687,7 +1204,7 @@ directory_span::ptr impl::find_directory(std::vector<std::string>::const_iterato std::optional<directory_entry> impl::find_child(const directory_span ¤t_dir, std::string_view target) const { std::optional<directory_entry> result; - auto callback = [&result, target](const directory_entry &dirent) + auto const callback = [&result, target] (const directory_entry &dirent) { bool found = dirent.name() == target; if (found) @@ -703,17 +1220,18 @@ std::optional<directory_entry> impl::find_child(const directory_span ¤t_di // impl::iterate_directory_entries //------------------------------------------------- -void impl::iterate_directory_entries(const directory_span &dir, const std::function<bool(const directory_entry &dirent)> &callback) const +template <typename T> +void impl::iterate_directory_entries(const directory_span &dir, T &&callback) const { std::vector<u32> sectors = dir.get_directory_sectors(); for (u32 sector : sectors) { bool done = false; - fsblk_t::block_t block = m_blockdev.get(sector); + fsblk_t::block_t::ptr block = m_blockdev.get(sector); for (u32 index = 0; !done && (index < dirents_per_sector()); index++) { directory_entry dirent(block, index * 32); - if (dirent.raw_stem()[0] != 0x00 && !dirent.is_deleted()) + if (dirent.raw_stem()[0] != 0x00 && !dirent.is_deleted() && !dirent.is_long_file_name()) { // get the filename std::string_view stem = trim_end_spaces(dirent.raw_stem()); @@ -815,6 +1333,12 @@ void pc_fat_image::enumerate_f(floppy_enumerator &fe) const fe.add(FLOPPY_PC_FORMAT, floppy_image::FF_35, floppy_image::DSDD, 737280, "pc_fat_dsdd", "PC FAT 3.5\" dual-sided double density"); fe.add(FLOPPY_PC_FORMAT, floppy_image::FF_35, floppy_image::DSHD, 1474560, "pc_fat_dshd", "PC FAT 3.5\" dual-sided high density"); fe.add(FLOPPY_PC_FORMAT, floppy_image::FF_35, floppy_image::DSED, 2949120, "pc_fat_dsed", "PC FAT 3.5\" dual-sided extra density"); + fe.add(FLOPPY_PC_FORMAT, floppy_image::FF_525, floppy_image::SSDD, 163840, "pc_fat_525ssdd_8", "PC FAT 5.25\" single-sided double density, 8 sectors/track"); + fe.add(FLOPPY_PC_FORMAT, floppy_image::FF_525, floppy_image::SSDD, 184320, "pc_fat_525ssdd", "PC FAT 5.25\" single-sided double density, 9 sectors/track"); + fe.add(FLOPPY_PC_FORMAT, floppy_image::FF_525, floppy_image::DSDD, 327680, "pc_fat_525dsdd_8", "PC FAT 5.25\" dual-sided double density, 8 sectors/track"); + fe.add(FLOPPY_PC_FORMAT, floppy_image::FF_525, floppy_image::DSDD, 368640, "pc_fat_525dsdd", "PC FAT 5.25\" dual-sided double density, 9 sectors/track"); + fe.add(FLOPPY_PC_FORMAT, floppy_image::FF_525, floppy_image::DSQD, 737280, "pc_fat_525dsqd", "PC FAT 5.25\" dual-sided quad density"); + fe.add(FLOPPY_PC_FORMAT, floppy_image::FF_525, floppy_image::DSHD, 1228800, "pc_fat_525dshd", "PC FAT 5.25\" dual-sided high density"); } |