mirror of
https://github.com/holub/mame
synced 2025-04-23 08:49:55 +03:00
lib/formats/fs_fat.cpp: Add write support. (#12363)
This commit is contained in:
parent
f9ef8589ef
commit
7f8793967f
@ -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 ¤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;
|
||||
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++)
|
||||
|
Loading…
Reference in New Issue
Block a user