summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author wilbertpol <wilbertpol@users.noreply.github.com>2024-05-12 21:47:07 +0100
committer GitHub <noreply@github.com>2024-05-12 22:47:07 +0200
commit7f8793967ffadf36e0c439db406ae1635d5c3a69 (patch)
tree9dedb6d699f91207007c905f77931ae182263e61
parentf9ef8589ef2bd692df1b75f9f56efea5c065693b (diff)
lib/formats/fs_fat.cpp: Add write support. (#12363)
-rw-r--r--src/lib/formats/fs_fat.cpp557
1 files 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 <optional>
+#include <regex>
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<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 err_t file_create(const std::vector<std::string> &path, const meta_data &meta) override;
+ virtual err_t file_write(const std::vector<std::string> &path, const std::vector<u8> &data) override;
+ virtual err_t 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:
+ static constexpr u32 FIRST_VALID_CLUSTER = 2;
+
fsblk_t::block_t 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 &current_dir, std::string_view target) const;
void iterate_directory_entries(const directory_span &dir, const std::function<bool(const directory_entry &dirent)> &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<filesystem_t> 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<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++)
@@ -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<err_t, std::vector<u8>> impl::file_read(const std::vector<std::string>
}
+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<std::string> &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<directory_entry> dirent = find_entity(path);
+ if (!dirent)
+ {
+ std::vector<std::string> partial_path;
+ std::optional<directory_entry> parent_entry = { };
+ for (auto 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 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<std::string> &path, const std::vector<u8> &data)
+{
+ std::optional<directory_entry> 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<std::string> &path)
+{
+ if (path.size() != 0)
+ return ERR_UNSUPPORTED;
+
+ std::optional<directory_entry> 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<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;
@@ -644,8 +1045,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;
}
@@ -654,6 +1055,89 @@ 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
//-------------------------------------------------
@@ -682,8 +1166,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++)