/* * 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 #include #include #include #include #include #include #include #include #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(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; }