447 lines
11 KiB
C++
447 lines
11 KiB
C++
|
/*
|
||
|
* Copyright (c) 2013, 2014, 2015, 2023 Jonas 'Sortie' Termansen.
|
||
|
*
|
||
|
* Permission to use, copy, modify, and distribute this software for any
|
||
|
* purpose with or without fee is hereby granted, provided that the above
|
||
|
* copyright notice and this permission notice appear in all copies.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
*
|
||
|
* filesystem.cpp
|
||
|
* Filesystem.
|
||
|
*/
|
||
|
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/types.h>
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <endian.h>
|
||
|
#include <errno.h>
|
||
|
#include <stddef.h>
|
||
|
#include <stdint.h>
|
||
|
#include <string.h>
|
||
|
#include <time.h>
|
||
|
|
||
|
#include "fat.h"
|
||
|
|
||
|
#include "block.h"
|
||
|
#include "device.h"
|
||
|
#include "filesystem.h"
|
||
|
#include "inode.h"
|
||
|
#include "util.h"
|
||
|
|
||
|
// TODO: Be more precise.
|
||
|
static bool is_8_3_char(char c)
|
||
|
{
|
||
|
return ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9');
|
||
|
}
|
||
|
|
||
|
// TODO: Is this fully precise? What about .FOO?
|
||
|
bool is_8_3(const char* name)
|
||
|
{
|
||
|
if ( !name[0] )
|
||
|
return false;
|
||
|
size_t b = 0;
|
||
|
while ( name[b] && is_8_3_char(name[b]) )
|
||
|
b++;
|
||
|
if ( 8 < b )
|
||
|
return false;
|
||
|
if ( !name[b] )
|
||
|
return true;
|
||
|
if ( name[b] != '.' )
|
||
|
return false;
|
||
|
size_t e = 0;
|
||
|
while ( name[b+1+e] && is_8_3_char(name[b+1+e]) )
|
||
|
e++;
|
||
|
if ( 3 < e )
|
||
|
return false;
|
||
|
if ( name[b+1+e] )
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void encode_8_3(const char* decoded, char encoded[8 + 3])
|
||
|
{
|
||
|
size_t i = 0;
|
||
|
for ( size_t o = 0; o < 8 + 3; o++ )
|
||
|
{
|
||
|
char c = ' ';
|
||
|
if ( decoded[i] == '.' && o == 8 )
|
||
|
i++;
|
||
|
if ( decoded[i] && decoded[i] != '.' )
|
||
|
c = decoded[i++];
|
||
|
if ( (unsigned char) c == 0xE5 )
|
||
|
c = 0x05;
|
||
|
encoded[o] = c;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void decode_8_3(const char encoded[8 + 3], char decoded[8 + 1 + 3 + 1])
|
||
|
{
|
||
|
size_t o = 0;
|
||
|
for ( size_t i = 0; i < 8; i++ )
|
||
|
{
|
||
|
char c = encoded[i];
|
||
|
if ( !c || c == ' ' )
|
||
|
break;
|
||
|
if ( c == 0x05 )
|
||
|
c = (char) 0xE5;
|
||
|
decoded[o++] = c;
|
||
|
}
|
||
|
for ( size_t i = 8; i < 8 + 3; i++ )
|
||
|
{
|
||
|
char c = encoded[i];
|
||
|
if ( !c || c == ' ' )
|
||
|
break;
|
||
|
if ( i == 8 )
|
||
|
decoded[o++] = '.';
|
||
|
if ( c == 0x05 )
|
||
|
c = (char) 0xE5;
|
||
|
decoded[o++] = c;
|
||
|
}
|
||
|
decoded[o] = '\0';
|
||
|
}
|
||
|
|
||
|
uint8_t timespec_to_fat_tenths(struct timespec* ts)
|
||
|
{
|
||
|
// TODO: Work with a struct tm instead.
|
||
|
uint16_t hundreds = ts->tv_nsec / 10000000;
|
||
|
struct tm tm;
|
||
|
gmtime_r(&ts->tv_sec, &tm);
|
||
|
if ( tm.tm_sec & 1 )
|
||
|
hundreds += 100;
|
||
|
return hundreds;
|
||
|
}
|
||
|
|
||
|
uint16_t timespec_to_fat_time(struct timespec* ts)
|
||
|
{
|
||
|
// TODO: Work with a struct tm instead.
|
||
|
struct tm tm;
|
||
|
gmtime_r(&ts->tv_sec, &tm);
|
||
|
return (tm.tm_sec / 2) << 0 | tm.tm_min << 5 | tm.tm_hour << 11;
|
||
|
}
|
||
|
|
||
|
uint16_t timespec_to_fat_date(struct timespec* ts)
|
||
|
{
|
||
|
// TODO: Work with a struct tm instead.
|
||
|
struct tm tm;
|
||
|
gmtime_r(&ts->tv_sec, &tm);
|
||
|
return tm.tm_mday << 0 | (tm.tm_mon + 1) << 5 | (tm.tm_year - 80) << 9;
|
||
|
}
|
||
|
|
||
|
// TODO: Rename tenths to a better name.
|
||
|
void timespec_to_fat(const struct timespec* ts, uint16_t* date, uint16_t* time,
|
||
|
uint8_t* tenths)
|
||
|
{
|
||
|
struct tm tm;
|
||
|
gmtime_r(&ts->tv_sec, &tm);
|
||
|
// TODO: Endian.
|
||
|
*date = tm.tm_mday << 0 | (tm.tm_mon + 1) << 5 | (tm.tm_year - 80) << 9;
|
||
|
*time = (tm.tm_sec / 2) << 0 | tm.tm_min << 5 | tm.tm_hour << 11;
|
||
|
*tenths = ts->tv_nsec / 10000000 + (tm.tm_sec & 1 ? 100 : 0);
|
||
|
}
|
||
|
|
||
|
Filesystem::Filesystem(Device* device, const char* mount_path)
|
||
|
{
|
||
|
this->bpb_block = device->GetBlock(0);
|
||
|
assert(bpb_block); // TODO: This can fail.
|
||
|
this->bpb = (struct fat_bpb*) bpb_block->block_data;
|
||
|
this->device = device;
|
||
|
this->mount_path = mount_path;
|
||
|
this->mode_reg = S_IFREG | 0644;
|
||
|
this->mode_dir = S_IFDIR | 0755;
|
||
|
this->block_size = device->block_size;
|
||
|
this->bytes_per_sector =
|
||
|
bpb->bytes_per_sector_low | bpb->bytes_per_sector_high << 8;
|
||
|
this->root_dirent_count =
|
||
|
bpb->root_dirent_count_low | bpb->root_dirent_count_high << 8;
|
||
|
uint32_t root_dir_sectors =
|
||
|
divup<uint32_t>(root_dirent_count * sizeof(fat_dirent), bytes_per_sector);
|
||
|
this->sectors_per_fat =
|
||
|
bpb->sectors_per_fat ? bpb->sectors_per_fat : bpb->fat32_sectors_per_fat;
|
||
|
this->total_sectors =
|
||
|
bpb->total_sectors_low | bpb->total_sectors_high << 8;
|
||
|
if ( !this->total_sectors )
|
||
|
this->total_sectors = bpb->total_sectors_large;
|
||
|
this->fat_sector = bpb->reserved_sectors;
|
||
|
this->root_sector = fat_sector + bpb->fat_count * sectors_per_fat;
|
||
|
this->data_sector = root_sector + root_dir_sectors;
|
||
|
uint32_t data_sectors = total_sectors - data_sector;
|
||
|
this->cluster_count = data_sectors / bpb->sectors_per_cluster;
|
||
|
this->cluster_size = bpb->sectors_per_cluster * bytes_per_sector;
|
||
|
this->fat_type = cluster_count < 4085 ? 12 : cluster_count < 65525 ? 16 : 32;
|
||
|
// Use cluster 1 as the root inode on FAT12/FAT16 since it's not a valid
|
||
|
// cluster for use in the FAT.
|
||
|
this->root_inode_id = fat_type == 32 ? bpb->fat32_root_cluster : 1;
|
||
|
// TODO: Okay we actually need to compare with their lower bounds.
|
||
|
this->eio_cluster =
|
||
|
fat_type == 12 ? 0xFF7 : fat_type == 16 ? 0xFFF7 : 0xFFFFFF7;
|
||
|
this->eof_cluster =
|
||
|
fat_type == 12 ? 0xFFF : fat_type == 16 ? 0xFFFF : 0xFFFFFFF;
|
||
|
// TODO: Obtain and verify this from the fsinfo.
|
||
|
this->free_search = 0;
|
||
|
this->mru_inode = NULL;
|
||
|
this->lru_inode = NULL;
|
||
|
this->dirty_inode = NULL;
|
||
|
for ( size_t i = 0; i < INODE_HASH_LENGTH; i++ )
|
||
|
this->hash_inodes[i] = NULL;
|
||
|
this->dirty = false;
|
||
|
|
||
|
if ( device->write )
|
||
|
{
|
||
|
BeginWrite();
|
||
|
// TODO: Mark as mounted in fat[1] if FAT16/32.
|
||
|
FinishWrite();
|
||
|
Sync();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Filesystem::~Filesystem()
|
||
|
{
|
||
|
Sync();
|
||
|
while ( mru_inode )
|
||
|
delete mru_inode;
|
||
|
if ( device->write )
|
||
|
{
|
||
|
BeginWrite();
|
||
|
// TODO: Mark as unounted in fat[1] if FAT16/32.
|
||
|
FinishWrite();
|
||
|
Sync();
|
||
|
}
|
||
|
bpb_block->Unref();
|
||
|
}
|
||
|
|
||
|
void Filesystem::BeginWrite()
|
||
|
{
|
||
|
bpb_block->BeginWrite();
|
||
|
}
|
||
|
|
||
|
void Filesystem::FinishWrite()
|
||
|
{
|
||
|
dirty = true;
|
||
|
bpb_block->FinishWrite();
|
||
|
}
|
||
|
|
||
|
void Filesystem::Sync()
|
||
|
{
|
||
|
// TODO: Replacement concept?
|
||
|
while ( dirty_inode )
|
||
|
dirty_inode->Sync();
|
||
|
if ( dirty )
|
||
|
{
|
||
|
bpb_block->Sync();
|
||
|
dirty = false;
|
||
|
}
|
||
|
device->Sync();
|
||
|
}
|
||
|
|
||
|
Inode* Filesystem::GetInode(uint32_t inode_id, Block* dirent_block,
|
||
|
struct fat_dirent* dirent)
|
||
|
{
|
||
|
#if 0
|
||
|
if ( !inode_id || num_inodes <= inode_id )
|
||
|
return errno = EBADF, (Inode*) NULL;
|
||
|
if ( !inode_id )
|
||
|
return errno = EBADF, (Inode*) NULL;
|
||
|
#endif
|
||
|
|
||
|
size_t bin = inode_id % INODE_HASH_LENGTH;
|
||
|
for ( Inode* iter = hash_inodes[bin]; iter; iter = iter->next_hashed )
|
||
|
if ( iter->inode_id == inode_id )
|
||
|
return iter->Refer(), iter;
|
||
|
|
||
|
if ( inode_id != root_inode_id && !dirent_block )
|
||
|
return errno = EBADF, (Inode*) NULL;
|
||
|
|
||
|
Inode* inode = new Inode(this, inode_id);
|
||
|
if ( !inode )
|
||
|
return (Inode*) NULL;
|
||
|
inode->first_cluster =
|
||
|
inode_id == root_inode_id && fat_type != 32 ? 0 : inode_id;
|
||
|
if ( (inode->data_block = dirent_block) )
|
||
|
inode->data_block->Refer();
|
||
|
inode->dirent = dirent;
|
||
|
inode->Prelink();
|
||
|
|
||
|
return inode;
|
||
|
}
|
||
|
|
||
|
uint32_t Filesystem::AllocateCluster()
|
||
|
{
|
||
|
for ( size_t i = 0; i < cluster_count; i++ )
|
||
|
{
|
||
|
size_t n = 2 + (free_search + i) % cluster_count;
|
||
|
if ( !ReadFAT(n) )
|
||
|
{
|
||
|
free_search = (i + 1) % cluster_count;
|
||
|
if ( fat_type == 32 )
|
||
|
{
|
||
|
Block* block = device->GetBlock(bpb->fat32_fsinfo);
|
||
|
if ( block )
|
||
|
{
|
||
|
struct fat_fsinfo* fsinfo =
|
||
|
(struct fat_fsinfo*) block->block_data;
|
||
|
block->BeginWrite();
|
||
|
uint32_t free_count = le32toh(fsinfo->free_count);
|
||
|
if ( free_count )
|
||
|
free_count--;
|
||
|
fsinfo->free_count = htole32(free_count);
|
||
|
fsinfo->next_free = n;
|
||
|
block->FinishWrite();
|
||
|
block->Unref();
|
||
|
}
|
||
|
}
|
||
|
return n;
|
||
|
}
|
||
|
}
|
||
|
return errno = ENOSPC, 0;
|
||
|
}
|
||
|
|
||
|
void Filesystem::FreeCluster(uint32_t cluster)
|
||
|
{
|
||
|
if ( fat_type != 32 )
|
||
|
return;
|
||
|
Block* block = device->GetBlock(bpb->fat32_fsinfo);
|
||
|
if ( !block )
|
||
|
return;
|
||
|
struct fat_fsinfo* fsinfo = (struct fat_fsinfo*) block->block_data;
|
||
|
block->BeginWrite();
|
||
|
uint32_t free_count = le32toh(fsinfo->free_count);
|
||
|
if ( free_count < cluster_count )
|
||
|
free_count++;
|
||
|
fsinfo->free_count = htole32(free_count);
|
||
|
if ( !fsinfo->free_count || le32toh(fsinfo->next_free) == cluster + 1 )
|
||
|
{
|
||
|
fsinfo->next_free = htole32(cluster);
|
||
|
free_search = cluster - 2;
|
||
|
}
|
||
|
block->FinishWrite();
|
||
|
block->Unref();
|
||
|
}
|
||
|
|
||
|
uint32_t Filesystem::ReadFAT(uint32_t cluster)
|
||
|
{
|
||
|
// TODO: Bounds check.
|
||
|
if ( fat_type == 12 )
|
||
|
{
|
||
|
size_t position = cluster + (cluster / 2);
|
||
|
size_t lba = position / bytes_per_sector;
|
||
|
size_t offset = position % bytes_per_sector;
|
||
|
Block* block = device->GetBlock(fat_sector + lba);
|
||
|
if ( !block )
|
||
|
return eio_cluster;
|
||
|
uint8_t lower = block->block_data[offset];
|
||
|
if ( ++offset == bytes_per_sector )
|
||
|
{
|
||
|
block->Unref();
|
||
|
if ( !(block = device->GetBlock(fat_sector + lba)) )
|
||
|
return eio_cluster;
|
||
|
offset = 0;
|
||
|
}
|
||
|
uint8_t higher = block->block_data[offset];
|
||
|
block->Unref();
|
||
|
uint16_t value = lower | higher << 8;
|
||
|
if ( cluster & 1 )
|
||
|
return value >> 4;
|
||
|
else
|
||
|
return value & 0xFFF;
|
||
|
}
|
||
|
size_t fat_size = fat_type / 8;
|
||
|
size_t position = cluster * fat_size;
|
||
|
size_t lba = position / bytes_per_sector;
|
||
|
size_t entry = (position % bytes_per_sector) / fat_size;
|
||
|
Block* block = device->GetBlock(fat_sector + lba);
|
||
|
if ( !block )
|
||
|
return eio_cluster;
|
||
|
uint32_t result = 0;
|
||
|
if ( fat_type == 16 )
|
||
|
result = ((uint16_t*) block->block_data)[entry];
|
||
|
else if ( fat_type == 32 )
|
||
|
result = ((uint32_t*) block->block_data)[entry] & 0x0FFFFFFF;
|
||
|
block->Unref();
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
bool Filesystem::WriteFAT(uint32_t cluster, uint32_t value)
|
||
|
{
|
||
|
assert(device->write);
|
||
|
// TODO: Bounds check.
|
||
|
if ( fat_type == 12 )
|
||
|
{
|
||
|
size_t position = cluster + (cluster / 2);
|
||
|
size_t lba = position / bytes_per_sector;
|
||
|
size_t offset = position % bytes_per_sector;
|
||
|
Block* block = device->GetBlock(fat_sector + lba);
|
||
|
if ( !block )
|
||
|
return false;
|
||
|
value = cluster & 1 ? value << 4 : value;
|
||
|
uint16_t mask = cluster & 1 ? 0xFFF0 : 0x0FFF;
|
||
|
block->BeginWrite();
|
||
|
block->block_data[offset] &= ~mask;
|
||
|
block->block_data[offset] |= value;
|
||
|
if ( ++offset == bytes_per_sector )
|
||
|
{
|
||
|
block->FinishWrite();
|
||
|
block->Unref();
|
||
|
if ( !(block = device->GetBlock(fat_sector + lba)) )
|
||
|
return false;
|
||
|
offset = 0;
|
||
|
block->BeginWrite();
|
||
|
}
|
||
|
block->block_data[offset] &= ~(mask >> 8);
|
||
|
block->block_data[offset] |= value >> 8;
|
||
|
block->FinishWrite();
|
||
|
block->Unref();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// TODO: Mirror to the other FATs.
|
||
|
size_t fat_size = fat_type / 8;
|
||
|
size_t position = cluster * fat_size;
|
||
|
size_t lba = position / bytes_per_sector;
|
||
|
size_t entry = (position % bytes_per_sector) / fat_size;
|
||
|
Block* block = device->GetBlock(fat_sector + lba);
|
||
|
if ( !block )
|
||
|
return false;
|
||
|
block->BeginWrite();
|
||
|
if ( fat_type == 16 )
|
||
|
((uint16_t*) block->block_data)[entry] = value;
|
||
|
else if ( fat_type == 32 )
|
||
|
{
|
||
|
uint32_t old = ((uint32_t*) block->block_data)[entry] & 0xF0000000;
|
||
|
((uint32_t*) block->block_data)[entry] = value | old;
|
||
|
}
|
||
|
block->FinishWrite();
|
||
|
block->Unref();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
uint32_t Filesystem::CalculateFreeCount()
|
||
|
{
|
||
|
if ( fat_type == 32 )
|
||
|
{
|
||
|
// TODO: Verify the fsinfo.
|
||
|
Block* block = device->GetBlock(bpb->fat32_fsinfo);
|
||
|
if ( !block )
|
||
|
return 0xFFFFFFFF;
|
||
|
const struct fat_fsinfo* fsinfo =
|
||
|
(const struct fat_fsinfo*) block->block_data;
|
||
|
uint32_t result = le32toh(fsinfo->free_count);
|
||
|
block->Unref();
|
||
|
if ( result != 0xFFFFFFFF )
|
||
|
return result;
|
||
|
}
|
||
|
// TODO: Cache these.
|
||
|
size_t count = 0;
|
||
|
for ( size_t i = 0; i < cluster_count; i++ )
|
||
|
if ( !ReadFAT(2 + i) )
|
||
|
count++;
|
||
|
return count;
|
||
|
}
|