diff --git a/disked/disked.c b/disked/disked.c index f7cdea8c..3ac32d84 100644 --- a/disked/disked.c +++ b/disked/disked.c @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -1771,9 +1772,9 @@ static void on_mkpart(size_t argc, char** argv) bool is_gpt = current_pt_type == PARTITION_TABLE_TYPE_GPT; const char* question = "Format a filesystem? (no/ext2)"; if ( is_mbr ) - question = "Format a filesystem? (no/ext2/extended)"; + question = "Format a filesystem? (no/ext2/extended/fat)"; else if ( is_gpt ) - question = "Format a filesystem? (no/ext2/biosboot)"; + question = "Format a filesystem? (no/ext2/biosboot/efi/fat)"; if ( 5 <= argc ) strlcpy(fstype, argv[4], sizeof(fstype)); else @@ -1781,7 +1782,9 @@ static void on_mkpart(size_t argc, char** argv) if ( strcmp(fstype, "no") != 0 && strcmp(fstype, "ext2") != 0 && (!is_mbr || strcmp(fstype, "extended") != 0) && - (!is_gpt || strcmp(fstype, "biosboot") != 0) ) + (!is_gpt || strcmp(fstype, "biosboot") != 0) && + (!is_gpt || strcmp(fstype, "efi") != 0) && + strcmp(fstype, "fat") != 0 ) { command_errorx("%s: %s: Invalid filesystem choice: %s", argv[0], device_name(current_hd->path), fstype); @@ -1801,14 +1804,18 @@ static void on_mkpart(size_t argc, char** argv) break; } char mountpoint[256] = ""; - bool mountable = !strcmp(fstype, "ext2"); + // TODO: Get this information from libmount. + bool mountable = !strcmp(fstype, "ext2") || + !strcmp(fstype, "fat") || + !strcmp(fstype, "efi"); while ( mountable ) { + const char* def = !strcmp(fstype, "efi") ? "/boot/efi" : "no"; if ( 6 <= argc ) strlcpy(mountpoint, argv[5], sizeof(mountpoint)); else prompt(mountpoint, sizeof(mountpoint), - "Where to mount partition? (mountpoint or 'no')", "no"); + "Where to mount partition? (mountpoint or 'no')", def); if ( !strcmp(mountpoint, "no") ) { mountpoint[0] = '\0'; @@ -2003,6 +2010,10 @@ static void on_mkpart(size_t argc, char** argv) const char* type_uuid_str = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"; if ( !strcmp(fstype, "biosboot") ) type_uuid_str = BIOSBOOT_GPT_TYPE_UUID; + else if ( !strcmp(fstype, "efi") ) + type_uuid_str = ESP_GPT_TYPE_UUID; + else if ( !strcmp(fstype, "fat") ) + type_uuid_str = BDP_GPT_TYPE_UUID; uuid_from_string(p.partition_type_guid, type_uuid_str); arc4random_buf(p.unique_partition_guid, sizeof(p.unique_partition_guid)); off_t pstart = hole->start + start; @@ -2162,6 +2173,67 @@ static void on_mkpart(size_t argc, char** argv) } } } + if ( !strcmp(fstype, "efi") || !strcmp(fstype, "fat") ) + { + printf("(Formatting %s as %s...)\n", device_name(p->path), fstype); + // TODO: Zero superblock? + // TODO: Run this in its own foreground process group so ^C works. + pid_t child_pid = fork(); + if ( child_pid < 0 ) + { + command_error("%s: fork", argv[0]); + return; + } + const char* mkfs_argv[] = + { + "mformat", + "-i", + p->path, + NULL + }; + if ( child_pid == 0 ) + { + execvp(mkfs_argv[0], (char* const*) mkfs_argv); + warn("%s", mkfs_argv[0]); + _exit(127); + } + int status; + waitpid(child_pid, &status, 0); + if ( WIFEXITED(status) && WEXITSTATUS(status) == 127 ) + { + command_errorx("%s: Failed to format filesystem (%s is not installed)", + argv[0], mkfs_argv[0]); + return; + } + else if ( WIFEXITED(status) && WEXITSTATUS(status) != 0 ) + { + command_errorx("%s: Failed to format filesystem", argv[0]); + return; + } + else if ( WIFSIGNALED(status) ) + { + command_errorx("%s: Failed to format filesystem (%s)", + argv[0], strsignal(WTERMSIG(status))); + return; + } + printf("(Formatted %s as %s)\n", device_name(p->path), fstype); + scan_partition(p); + if ( !p->bdev.fs /* TODO: || !(p->bdev.fs->flags & FILESYSTEM_FLAG_UUID) */ ) + { + command_errorx("%s: %s: Failed to scan expected %s filesystem", + argv[0], device_name(p->path), fstype); + return; + } + if ( mountpoint[0] ) + { + if ( !add_blockdevice_to_fstab(&p->bdev, mountpoint) ) + { + command_error("%s: %s: Failed to add partition", argv[0], fstab_path); + return; + } + } + + } } static void on_mktable(size_t argc, char** argv) diff --git a/fat/.gitignore b/fat/.gitignore new file mode 100644 index 00000000..fcd07ed4 --- /dev/null +++ b/fat/.gitignore @@ -0,0 +1,2 @@ +fatfs +*.o diff --git a/fat/Makefile b/fat/Makefile new file mode 100644 index 00000000..a2a2ccc6 --- /dev/null +++ b/fat/Makefile @@ -0,0 +1,34 @@ +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/version.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=$(DEFAULT_OPTLEVEL) +CXXFLAGS?=$(OPTLEVEL) + +CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\" +CXXFLAGS:=$(CXXFLAGS) -Wall -Wextra -fno-exceptions -fno-rtti -fcheck-new + +LIBS:=$(LIBS) + +ifeq ($(HOST_IS_SORTIX),0) + PTHREAD_OPTION:=-pthread + LIBS:=$(LIBS) -lfuse + CPPFLAGS:=$(CPPFLAGS) -D_FILE_OFFSET_BITS=64 +endif + +BINARIES:=fatfs + +all: $(BINARIES) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(SBINDIR) + install $(BINARIES) $(DESTDIR)$(SBINDIR) + +fatfs: *.cpp *.h + $(CXX) $(PTHREAD_OPTION) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) *.cpp -o $@ $(LIBS) + +clean: + rm -f $(BINARIES) *.o diff --git a/fat/block.cpp b/fat/block.cpp new file mode 100644 index 00000000..1d358d33 --- /dev/null +++ b/fat/block.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2013, 2014, 2015 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. + * + * block.cpp + * Blocks in the filesystem. + */ + +#include + +#include +#include +#include +#include + +#include "block.h" +#include "device.h" +#include "ioleast.h" + +Block::Block() +{ + this->block_data = NULL; +} + +Block::Block(Device* device, uint32_t block_id) +{ + Construct(device, block_id); +} + +void Block::Construct(Device* device, uint32_t block_id) +{ + this->modify_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + this->transit_done_cond = PTHREAD_COND_INITIALIZER; + this->prev_block = NULL; + this->next_block = NULL; + this->prev_hashed = NULL; + this->next_hashed = NULL; + this->prev_dirty = NULL; + this->next_dirty = NULL; + this->device = device; + this->reference_count = 1; + this->block_id = block_id; + this->dirty = false; + this->is_in_transit = false; +} + +Block::~Block() +{ + Destruct(); + delete[] block_data; +} + +void Block::Destruct() +{ + Sync(); + Unlink(); +} + +void Block::Refer() +{ + reference_count++; +} + +void Block::Unref() +{ + if ( !--reference_count ) + { +#if 0 + device->block_count--; + delete this; +#endif + } +} + +void Block::Sync() +{ + if ( device->has_sync_thread ) + { + pthread_mutex_lock(&device->sync_thread_lock); + while ( dirty || is_in_transit ) + pthread_cond_wait(&transit_done_cond, &device->sync_thread_lock); + pthread_mutex_unlock(&device->sync_thread_lock); + return; + } + + if ( !dirty ) + return; + dirty = false; + (prev_dirty ? prev_dirty->next_dirty : device->dirty_block) = next_dirty; + if ( next_dirty ) + next_dirty->prev_dirty = prev_dirty; + prev_dirty = NULL; + next_dirty = NULL; + if ( !device->write ) + return; + off_t file_offset = (off_t) device->block_size * (off_t) block_id; + pwriteall(device->fd, block_data, device->block_size, file_offset); +} + +void Block::BeginWrite() +{ + assert(device->write); + pthread_mutex_lock(&modify_lock); +} + +void Block::FinishWrite() +{ + pthread_mutex_unlock(&modify_lock); + pthread_mutex_lock(&device->sync_thread_lock); + if ( !dirty ) + { + dirty = true; + prev_dirty = NULL; + next_dirty = device->dirty_block; + if ( next_dirty ) + next_dirty->prev_dirty = this; + device->dirty_block = this; + pthread_cond_signal(&device->sync_thread_cond); + } + pthread_mutex_unlock(&device->sync_thread_lock); + Use(); +} + +void Block::Use() +{ + Unlink(); + Prelink(); +} + +void Block::Unlink() +{ + (prev_block ? prev_block->next_block : device->mru_block) = next_block; + (next_block ? next_block->prev_block : device->lru_block) = prev_block; + size_t bin = block_id % DEVICE_HASH_LENGTH; + (prev_hashed ? prev_hashed->next_hashed : device->hash_blocks[bin]) = next_hashed; + if ( next_hashed ) next_hashed->prev_hashed = prev_hashed; +} + +void Block::Prelink() +{ + prev_block = NULL; + next_block = device->mru_block; + if ( device->mru_block ) + device->mru_block->prev_block = this; + device->mru_block = this; + if ( !device->lru_block ) + device->lru_block = this; + size_t bin = block_id % DEVICE_HASH_LENGTH; + prev_hashed = NULL; + next_hashed = device->hash_blocks[bin]; + device->hash_blocks[bin] = this; + if ( next_hashed ) + next_hashed->prev_hashed = this; +} diff --git a/fat/block.h b/fat/block.h new file mode 100644 index 00000000..71a536c1 --- /dev/null +++ b/fat/block.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2013, 2014, 2015 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. + * + * block.h + * Blocks in the filesystem. + */ + +#ifndef BLOCK_H +#define BLOCK_H + +class Device; + +class Block +{ +public: + Block(); + Block(Device* device, uint32_t block_id); + ~Block(); + void Construct(Device* device, uint32_t block_id); + void Destruct(); + +public: + pthread_mutex_t modify_lock; + pthread_cond_t transit_done_cond; + Block* prev_block; + Block* next_block; + Block* prev_hashed; + Block* next_hashed; + Block* prev_dirty; + Block* next_dirty; + Device* device; + size_t reference_count; + uint32_t block_id; + bool dirty; + bool is_in_transit; + uint8_t* block_data; + +public: + void Refer(); + void Unref(); + void Sync(); + void BeginWrite(); + void FinishWrite(); + void Use(); + void Unlink(); + void Prelink(); + +}; + +#endif diff --git a/fat/blockgroup.cpp b/fat/blockgroup.cpp new file mode 100644 index 00000000..9bfe0e48 --- /dev/null +++ b/fat/blockgroup.cpp @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2013, 2014, 2015 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. + * + * blockgroup.cpp + * Filesystem block group. + */ + +#include + +#include +#include +#include +#include + +#include "ext-constants.h" +#include "ext-structs.h" + +#include "block.h" +#include "blockgroup.h" +#include "device.h" +#include "filesystem.h" +#include "util.h" + +BlockGroup::BlockGroup(Filesystem* filesystem, uint32_t group_id) +{ + this->data_block = NULL; + this->data = NULL; + this->filesystem = filesystem; + this->block_bitmap_chunk = NULL; + this->inode_bitmap_chunk = NULL; + this->reference_count = 1; + this->group_id = group_id; + this->block_alloc_chunk = 0; + this->inode_alloc_chunk = 0; + this->block_bitmap_chunk_i = 0; + this->inode_bitmap_chunk_i = 0; + this->first_block_id = filesystem->sb->s_first_data_block + + filesystem->sb->s_blocks_per_group * group_id; + this->first_inode_id = 1 + + filesystem->sb->s_inodes_per_group * group_id; + this->num_blocks = group_id+1== filesystem->num_groups ? + filesystem->num_blocks - first_block_id : + filesystem->sb->s_blocks_per_group; + this->num_inodes = group_id+1== filesystem->num_groups ? + filesystem->num_inodes - first_inode_id : + filesystem->sb->s_inodes_per_group; + size_t num_chunk_bits = filesystem->block_size * 8UL; + this->num_block_bitmap_chunks = divup(num_blocks, (uint32_t) num_chunk_bits); + this->num_inode_bitmap_chunks = divup(num_inodes, (uint32_t) num_chunk_bits); + this->dirty = false; +} + +BlockGroup::~BlockGroup() +{ + Sync(); + if ( block_bitmap_chunk ) + block_bitmap_chunk->Unref(); + if ( inode_bitmap_chunk ) + inode_bitmap_chunk->Unref(); + if ( data_block ) + data_block->Unref(); + filesystem->block_groups[group_id] = NULL; +} + +uint32_t BlockGroup::AllocateBlock() +{ + if ( !filesystem->device->write ) + return errno = EROFS, 0; + if ( !data->bg_free_blocks_count ) + return errno = ENOSPC, 0; + size_t num_chunk_bits = filesystem->block_size * 8UL; + uint32_t begun_chunk = block_alloc_chunk; + for ( uint32_t i = 0; i < num_block_bitmap_chunks; i++ ) + { + block_alloc_chunk = (begun_chunk + i) % num_block_bitmap_chunks; + bool last = block_alloc_chunk + 1 == num_block_bitmap_chunks; + if ( !block_bitmap_chunk ) + { + uint32_t block_id = data->bg_block_bitmap + block_alloc_chunk; + block_bitmap_chunk = filesystem->device->GetBlock(block_id); + if ( !block_bitmap_chunk ) + return 0; + block_bitmap_chunk_i = 0; + } + uint32_t chunk_offset = block_alloc_chunk * num_chunk_bits; + uint8_t* chunk_bits = block_bitmap_chunk->block_data; + size_t num_bits = last ? num_blocks - chunk_offset : num_chunk_bits; + // TODO: This can be made faster by caching if previous bits were set. + for ( ; block_bitmap_chunk_i < num_bits; block_bitmap_chunk_i++ ) + { + if ( !checkbit(chunk_bits, block_bitmap_chunk_i) ) + { + block_bitmap_chunk->BeginWrite(); + setbit(chunk_bits, block_bitmap_chunk_i); + block_bitmap_chunk->FinishWrite(); + BeginWrite(); + data->bg_free_blocks_count--; + FinishWrite(); + filesystem->BeginWrite(); + filesystem->sb->s_free_blocks_count--; + filesystem->FinishWrite(); + uint32_t group_block_id = chunk_offset + block_bitmap_chunk_i++; + uint32_t block_id = first_block_id + group_block_id; + return block_id; + } + } + block_bitmap_chunk->Unref(); + block_bitmap_chunk = NULL; + } + BeginWrite(); + data->bg_free_blocks_count = 0; + FinishWrite(); + return errno = ENOSPC, 0; +} + +uint32_t BlockGroup::AllocateInode() +{ + if ( !filesystem->device->write ) + return errno = EROFS, 0; + if ( !data->bg_free_inodes_count ) + return errno = ENOSPC, 0; + size_t num_chunk_bits = filesystem->block_size * 8UL; + uint32_t begun_chunk = inode_alloc_chunk; + for ( uint32_t i = 0; i < num_inode_bitmap_chunks; i++ ) + { + inode_alloc_chunk = (begun_chunk + i) % num_inode_bitmap_chunks; + bool last = inode_alloc_chunk + 1 == num_inode_bitmap_chunks; + if ( !inode_bitmap_chunk ) + { + uint32_t block_id = data->bg_inode_bitmap + inode_alloc_chunk; + inode_bitmap_chunk = filesystem->device->GetBlock(block_id); + if ( !inode_bitmap_chunk ) + return 0; + inode_bitmap_chunk_i = 0; + } + uint32_t chunk_offset = inode_alloc_chunk * num_chunk_bits; + uint8_t* chunk_bits = inode_bitmap_chunk->block_data; + size_t num_bits = last ? num_inodes - chunk_offset : num_chunk_bits; + // TODO: This can be made faster by caching if previous bits were set. + for ( ; inode_bitmap_chunk_i < num_bits; inode_bitmap_chunk_i++ ) + { + if ( !checkbit(chunk_bits, inode_bitmap_chunk_i) ) + { + inode_bitmap_chunk->BeginWrite(); + setbit(chunk_bits, inode_bitmap_chunk_i); + inode_bitmap_chunk->FinishWrite(); + BeginWrite(); + data->bg_free_inodes_count--; + FinishWrite(); + filesystem->BeginWrite(); + filesystem->sb->s_free_inodes_count--; + filesystem->FinishWrite(); + uint32_t group_inode_id = chunk_offset + inode_bitmap_chunk_i++; + uint32_t inode_id = first_inode_id + group_inode_id; + return inode_id; + } + } + inode_bitmap_chunk->Unref(); + inode_bitmap_chunk = NULL; + } + BeginWrite(); + data->bg_free_inodes_count = 0; + FinishWrite(); + return errno = ENOSPC, 0; +} + +void BlockGroup::FreeBlock(uint32_t block_id) +{ + assert(filesystem->device->write); + block_id -= first_block_id; + size_t num_chunk_bits = filesystem->block_size * 8UL; + uint32_t chunk_id = block_id / num_chunk_bits; + uint32_t chunk_bit = block_id % num_chunk_bits; + if ( !block_bitmap_chunk || chunk_id != block_alloc_chunk ) + { + if ( block_bitmap_chunk ) + block_bitmap_chunk->Unref(); + block_alloc_chunk = chunk_id; + uint32_t block_id = data->bg_block_bitmap + block_alloc_chunk; + block_bitmap_chunk = filesystem->device->GetBlock(block_id); + block_bitmap_chunk_i = 0; + } + + block_bitmap_chunk->BeginWrite(); + uint8_t* chunk_bits = block_bitmap_chunk->block_data; + clearbit(chunk_bits, chunk_bit); + block_bitmap_chunk->FinishWrite(); + if ( chunk_bit < inode_bitmap_chunk_i ) + block_bitmap_chunk_i = chunk_bit; + BeginWrite(); + data->bg_free_blocks_count++; + FinishWrite(); + filesystem->BeginWrite(); + filesystem->sb->s_free_blocks_count++; + filesystem->FinishWrite(); +} + +void BlockGroup::FreeInode(uint32_t inode_id) +{ + assert(filesystem->device->write); + inode_id -= first_inode_id; + size_t num_chunk_bits = filesystem->block_size * 8UL; + uint32_t chunk_id = inode_id / num_chunk_bits; + uint32_t chunk_bit = inode_id % num_chunk_bits; + if ( !inode_bitmap_chunk || chunk_id != inode_alloc_chunk ) + { + if ( inode_bitmap_chunk ) + inode_bitmap_chunk->Unref(); + inode_alloc_chunk = chunk_id; + uint32_t block_id = data->bg_inode_bitmap + inode_alloc_chunk; + inode_bitmap_chunk = filesystem->device->GetBlock(block_id); + inode_bitmap_chunk_i = 0; + } + + inode_bitmap_chunk->BeginWrite(); + uint8_t* chunk_bits = inode_bitmap_chunk->block_data; + clearbit(chunk_bits, chunk_bit); + inode_bitmap_chunk->FinishWrite(); + if ( chunk_bit < inode_bitmap_chunk_i ) + inode_bitmap_chunk_i = chunk_bit; + BeginWrite(); + data->bg_free_inodes_count++; + FinishWrite(); + filesystem->BeginWrite(); + filesystem->sb->s_free_inodes_count++; + filesystem->FinishWrite(); +} + +void BlockGroup::Refer() +{ + // TODO +} + +void BlockGroup::Unref() +{ + // TODO +} + +void BlockGroup::Sync() +{ + if ( block_bitmap_chunk ) + block_bitmap_chunk->Sync(); + if ( inode_bitmap_chunk ) + inode_bitmap_chunk->Sync(); + if ( dirty ) + data_block->Sync(); + dirty = false; +} + +void BlockGroup::BeginWrite() +{ + data_block->BeginWrite(); +} + +void BlockGroup::FinishWrite() +{ + dirty = true; + data_block->FinishWrite(); + Use(); +} + +void BlockGroup::Use() +{ +} diff --git a/fat/blockgroup.h b/fat/blockgroup.h new file mode 100644 index 00000000..08a304cc --- /dev/null +++ b/fat/blockgroup.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013, 2014, 2015 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. + * + * blockgroup.h + * Filesystem block group. + */ + +#ifndef BLOCKGROUP_H +#define BLOCKGROUP_H + +class Block; +class Filesystem; + +class BlockGroup +{ +public: + BlockGroup(Filesystem* filesystem, uint32_t group_id); + ~BlockGroup(); + +public: + Block* data_block; + struct ext_blockgrpdesc* data; + Filesystem* filesystem; + Block* block_bitmap_chunk; + Block* inode_bitmap_chunk; + size_t reference_count; + uint32_t group_id; + uint32_t block_alloc_chunk; + uint32_t inode_alloc_chunk; + uint32_t block_bitmap_chunk_i; + uint32_t inode_bitmap_chunk_i; + uint32_t first_block_id; + uint32_t first_inode_id; + uint32_t num_blocks; + uint32_t num_inodes; + uint32_t num_block_bitmap_chunks; + uint32_t num_inode_bitmap_chunks; + bool dirty; + +public: + uint32_t AllocateBlock(); + uint32_t AllocateInode(); + void FreeBlock(uint32_t block_id); + void FreeInode(uint32_t inode_id); + void Refer(); + void Unref(); + void Sync(); + void BeginWrite(); + void FinishWrite(); + void Use(); + void Unlink(); + void Prelink(); + +}; + +#endif diff --git a/fat/device.cpp b/fat/device.cpp new file mode 100644 index 00000000..e316165d --- /dev/null +++ b/fat/device.cpp @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2013, 2014, 2015 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. + * + * device.cpp + * Block device. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "block.h" +#include "device.h" +#include "ioleast.h" + +void* Device__SyncThread(void* ctx) +{ + ((Device*) ctx)->SyncThread(); + return NULL; +} + +Device::Device(int fd, const char* path, uint32_t block_size, bool write) +{ + // sync_thread unset. + this->sync_thread_cond = PTHREAD_COND_INITIALIZER; + this->sync_thread_idle_cond = PTHREAD_COND_INITIALIZER; + this->sync_thread_lock = PTHREAD_MUTEX_INITIALIZER; + this->mru_block = NULL; + this->lru_block = NULL; + this->dirty_block = NULL; + for ( size_t i = 0; i < DEVICE_HASH_LENGTH; i++ ) + hash_blocks[i] = NULL; + struct stat st; + fstat(fd, &st); + this->device_size = st.st_size; + this->path = path; + this->block_size = block_size; + this->fd = fd; + this->write = write; + this->has_sync_thread = false; + this->sync_thread_should_exit = false; + this->sync_in_transit = false; + this->block_count = 0; +#ifdef __sortix__ + // TODO: This isn't scaleable if there's multiple filesystems mounted. + size_t memory; + memstat(NULL, &memory); + this->block_limit = (memory / 10) / block_size; +#else + this->block_limit = 32768; +#endif +} + +Device::~Device() +{ + if ( has_sync_thread ) + { + pthread_mutex_lock(&sync_thread_lock); + sync_thread_should_exit = true; + pthread_cond_signal(&sync_thread_cond); + pthread_mutex_unlock(&sync_thread_lock); + pthread_join(sync_thread, NULL); + has_sync_thread = false; + } + Sync(); + while ( mru_block ) + delete mru_block; + close(fd); +} + +void Device::SpawnSyncThread() +{ + if ( this->has_sync_thread ) + return; + this->has_sync_thread = write && + pthread_create(&this->sync_thread, NULL, Device__SyncThread, this) == 0; +} + +Block* Device::AllocateBlock() +{ + if ( block_limit <= block_count ) + { + for ( Block* block = lru_block; block; block = block->prev_block ) + { + if ( block->reference_count ) + continue; + block->Destruct(); // Syncs. + return block; + } + } + uint8_t* data = new uint8_t[block_size]; + if ( !data ) // TODO: Use operator new nothrow! + return NULL; + Block* block = new Block(); + if ( !block ) // TODO: Use operator new nothrow! + return delete[] data, (Block*) NULL; + block->block_data = data; + block_count++; + return block; +} + +Block* Device::GetBlock(uint32_t block_id) +{ + if ( Block* block = GetCachedBlock(block_id) ) + return block; + Block* block = AllocateBlock(); + if ( !block ) + return NULL; + block->Construct(this, block_id); + off_t file_offset = (off_t) block_size * (off_t) block_id; + preadall(fd, block->block_data, block_size, file_offset); + block->Prelink(); + return block; +} + +Block* Device::GetBlockZeroed(uint32_t block_id) +{ + assert(write); + if ( Block* block = GetCachedBlock(block_id) ) + { + block->BeginWrite(); + memset(block->block_data, 0, block_size); + block->FinishWrite(); + return block; + } + Block* block = AllocateBlock(); + if ( !block ) + return NULL; + block->Construct(this, block_id); + memset(block->block_data, 0, block_size); + block->Prelink(); + block->BeginWrite(); + block->FinishWrite(); + return block; +} + +Block* Device::GetCachedBlock(uint32_t block_id) +{ + size_t bin = block_id % DEVICE_HASH_LENGTH; + for ( Block* iter = hash_blocks[bin]; iter; iter = iter->next_hashed ) + if ( iter->block_id == block_id ) + return iter->Refer(), iter; + return NULL; +} + +void Device::Sync() +{ + if ( has_sync_thread ) + { + pthread_mutex_lock(&sync_thread_lock); + while ( dirty_block || sync_in_transit ) + pthread_cond_wait(&sync_thread_cond, &sync_thread_lock); + pthread_mutex_unlock(&sync_thread_lock); + fsync(fd); + return; + } + + while ( dirty_block ) + dirty_block->Sync(); + fsync(fd); +} + +void Device::SyncThread() +{ + uint8_t transit_block_data[block_size]; + pthread_mutex_lock(&sync_thread_lock); + while ( true ) + { + while ( !(dirty_block || sync_thread_should_exit) ) + pthread_cond_wait(&sync_thread_cond, &sync_thread_lock); + if ( sync_thread_should_exit ) + break; + + Block* block = dirty_block; + + if ( block->next_dirty ) + block->next_dirty->prev_dirty = NULL; + dirty_block = block->next_dirty; + block->next_dirty = NULL; + + block->dirty = false; + block->is_in_transit = true; + sync_in_transit = true; + + pthread_mutex_unlock(&sync_thread_lock); + + pthread_mutex_lock(&block->modify_lock); + memcpy(transit_block_data, block->block_data, block_size); + pthread_mutex_unlock(&block->modify_lock); + + off_t offset = (off_t) block_size * (off_t) block->block_id; + pwriteall(fd, transit_block_data, block_size, offset); + + pthread_mutex_lock(&sync_thread_lock); + block->is_in_transit = false; + sync_in_transit = false; + pthread_cond_signal(&block->transit_done_cond); + if ( !dirty_block ) + pthread_cond_signal(&sync_thread_idle_cond); + } + pthread_mutex_unlock(&sync_thread_lock); +} diff --git a/fat/device.h b/fat/device.h new file mode 100644 index 00000000..b09a63ef --- /dev/null +++ b/fat/device.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013, 2014, 2015 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. + * + * device.h + * Block device. + */ + +#ifndef DEVICE_H +#define DEVICE_H + +class Block; + +static const size_t DEVICE_HASH_LENGTH = 1 << 16; + +class Device +{ +public: + Device(int fd, const char* path, uint32_t block_size, bool write); + ~Device(); + +public: + pthread_t sync_thread; + pthread_cond_t sync_thread_cond; + pthread_cond_t sync_thread_idle_cond; + pthread_mutex_t sync_thread_lock; + Block* mru_block; + Block* lru_block; + Block* dirty_block; + Block* hash_blocks[DEVICE_HASH_LENGTH]; + off_t device_size; + const char* path; + uint32_t block_size; + int fd; + bool write; + bool has_sync_thread; + bool sync_thread_should_exit; + bool sync_in_transit; + size_t block_count; + size_t block_limit; + +public: + void SpawnSyncThread(); + Block* AllocateBlock(); + Block* GetBlock(uint32_t block_id); + Block* GetBlockZeroed(uint32_t block_id); + Block* GetCachedBlock(uint32_t block_id); + void Sync(); + void SyncThread(); + +}; + +#endif diff --git a/fat/ext-constants.h b/fat/ext-constants.h new file mode 100644 index 00000000..f84b682a --- /dev/null +++ b/fat/ext-constants.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2013, 2015 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. + * + * ext-constants.h + * Constants for the extended filesystem. + */ + +#ifndef EXT_CONSTANTS_H +#define EXT_CONSTANTS_H + +static const uint16_t FAT_SUPER_MAGIC = 0xEF53; +static const uint16_t FAT_VALID_FS = 1; +static const uint16_t FAT_ERROR_FS = 2; +static const uint16_t FAT_ERRORS_CONTINUE = 1; +static const uint16_t FAT_ERRORS_RO = 2; +static const uint16_t FAT_ERRORS_PANIC = 3; +static const uint32_t FAT_OS_LINUX = 0; +static const uint32_t FAT_OS_HURD = 1; +static const uint32_t FAT_OS_MASIX = 2; +static const uint32_t FAT_OS_FREEBSD = 3; +static const uint32_t FAT_OS_LITES = 4; +static const uint32_t FAT_GOOD_OLD_REV = 0; +static const uint32_t FAT_DYNAMIC_REV = 1; +static const uint16_t FAT_DEF_RESUID = 0; +static const uint16_t FAT_DEF_RESGID = 0; +static const uint16_t FAT_GOOD_OLD_FIRST_INO = 11; +static const uint16_t FAT_GOOD_OLD_INODE_SIZE = 128; +static const uint32_t FAT_FEATURE_COMPAT_DIR_PREALLOC = 1U << 0U; +static const uint32_t FAT_FEATURE_COMPAT_IMAGIC_INODES = 1U << 1U; +static const uint32_t EXT3_FEATURE_COMPAT_HAS_JOURNAL = 1U << 2U; +static const uint32_t FAT_FEATURE_COMPAT_EXT_ATTR = 1U << 3U; +static const uint32_t FAT_FEATURE_COMPAT_RESIZE_INO = 1U << 4U; +static const uint32_t FAT_FEATURE_COMPAT_DIR_INDEX = 1U << 5U; +static const uint32_t FAT_FEATURE_INCOMPAT_COMPRESSION = 1U << 0U; +static const uint32_t FAT_FEATURE_INCOMPAT_FILETYPE = 1U << 1U; +static const uint32_t FAT_FEATURE_INCOMPAT_RECOVER = 1U << 2U; +static const uint32_t FAT_FEATURE_INCOMPAT_JOURNAL_DEV = 1U << 3U; +static const uint32_t FAT_FEATURE_INCOMPAT_META_BG = 1U << 4U; +static const uint32_t FAT_FEATURE_RO_COMPAT_SPARSE_SUPER = 1U << 0U; +static const uint32_t FAT_FEATURE_RO_COMPAT_LARGE_FILE = 1U << 1U; +static const uint32_t FAT_FEATURE_RO_COMPAT_BTREE_DIR = 1U << 2U; +static const uint32_t FAT_LZV1_ALG = 1U << 0U; +static const uint32_t FAT_LZRW3A_ALG = 1U << 1U; +static const uint32_t FAT_GZIP_ALG = 1U << 2U; +static const uint32_t FAT_BZIP2_ALG = 1U << 3U; +static const uint32_t FAT_LZO_ALG = 1U << 4U; +static const uint16_t FAT_S_IFMT = 0xF000; +static const uint16_t FAT_S_IFSOCK = 0xC000; +static const uint16_t FAT_S_IFLNK = 0xA000; +static const uint16_t FAT_S_IFREG = 0x8000; +static const uint16_t FAT_S_IFBLK = 0x6000; +static const uint16_t FAT_S_IFDIR = 0x4000; +static const uint16_t FAT_S_IFCHR = 0x2000; +static const uint16_t FAT_S_IFIFO = 0x1000; +static const uint16_t FAT_S_ISUID = 0x0800; +static const uint16_t FAT_S_ISGID = 0x0400; +static const uint16_t FAT_S_ISVTX = 0x0200; +static const uint16_t FAT_S_IRUSR = 0x0100; +static const uint16_t FAT_S_IWUSR = 0x0080; +static const uint16_t FAT_S_IXUSR = 0x0040; +static const uint16_t FAT_S_IRGRP = 0x0020; +static const uint16_t FAT_S_IWGRP = 0x0010; +static const uint16_t FAT_S_IXGRP = 0x0008; +static const uint16_t FAT_S_IROTH = 0x0004; +static const uint16_t FAT_S_IWOTH = 0x0002; +static const uint16_t FAT_S_IXOTH = 0x0001; +#define FAT_S_ISSOCK(mode) (((mode) & FAT_S_IFMT) == FAT_S_IFSOCK) +#define FAT_S_ISLNK(mode) (((mode) & FAT_S_IFMT) == FAT_S_IFLNK) +#define FAT_S_ISREG(mode) (((mode) & FAT_S_IFMT) == FAT_S_IFREG) +#define FAT_S_ISBLK(mode) (((mode) & FAT_S_IFMT) == FAT_S_IFBLK) +#define FAT_S_ISDIR(mode) (((mode) & FAT_S_IFMT) == FAT_S_IFDIR) +#define FAT_S_ISCHR(mode) (((mode) & FAT_S_IFMT) == FAT_S_IFCHR) +#define FAT_S_ISFIFO(mode) (((mode) & FAT_S_IFMT) == FAT_S_IFIFO) +static const uint32_t FAT_SECRM_FL = 0x00000001U; +static const uint32_t FAT_UNRM_FL = 0x00000002U; +static const uint32_t FAT_COMPR_FL = 0x00000004U; +static const uint32_t FAT_SYNC_FL = 0x00000008U; +static const uint32_t FAT_IMMUTABLE_FL = 0x00000010U; +static const uint32_t FAT_APPEND_FL = 0x00000020U; +static const uint32_t FAT_NODUMP_FL = 0x00000040U; +static const uint32_t FAT_NOATIME_FL = 0x00000080U; +static const uint32_t FAT_DIRTY_FL = 0x00000100U; +static const uint32_t FAT_COMPRBLK_FL = 0x00000200U; +static const uint32_t FAT_NOCOMPR_FL = 0x00000400U; +static const uint32_t FAT_ECOMPR_FL = 0x00000800U; +static const uint32_t FAT_BTREE_FL = 0x00001000U; +static const uint32_t FAT_INDEX_FL = 0x00001000U; +static const uint32_t FAT_IMAGIC_FL = 0x00002000U; +static const uint32_t EXT3_JOURNAL_DATA_FL = 0x00004000U; +static const uint32_t FAT_RESERVED_FL = 0x80000000U; +static const uint32_t FAT_ROOT_INO = 2; +static const uint8_t FAT_FT_UNKNOWN = 0; +static const uint8_t FAT_FT_REG_FILE = 1; +static const uint8_t FAT_FT_DIR = 2; +static const uint8_t FAT_FT_CHRDEV = 3; +static const uint8_t FAT_FT_BLKDEV = 4; +static const uint8_t FAT_FT_FIFO = 5; +static const uint8_t FAT_FT_SOCK = 6; +static const uint8_t FAT_FT_SYMLINK = 7; + +static inline uint8_t FAT_FT_OF_MODE(mode_t mode) +{ + if ( FAT_S_ISREG(mode) ) + return FAT_FT_REG_FILE; + if ( FAT_S_ISDIR(mode) ) + return FAT_FT_DIR; + if ( FAT_S_ISCHR(mode) ) + return FAT_FT_CHRDEV; + if ( FAT_S_ISBLK(mode) ) + return FAT_FT_BLKDEV; + if ( FAT_S_ISFIFO(mode) ) + return FAT_FT_FIFO; + if ( FAT_S_ISSOCK(mode) ) + return FAT_FT_SOCK; + if ( FAT_S_ISLNK(mode) ) + return FAT_FT_SYMLINK; + return FAT_FT_UNKNOWN; +} + +#endif diff --git a/fat/ext-structs.h b/fat/ext-structs.h new file mode 100644 index 00000000..f0442dd7 --- /dev/null +++ b/fat/ext-structs.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2013 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. + * + * ext-structs.h + * Data structures for the extended filesystem. + */ + +#ifndef EXT_STRUCTS_H +#define EXT_STRUCTS_H + +struct ext_superblock +{ + uint32_t s_inodes_count; + uint32_t s_blocks_count; + uint32_t s_r_blocks_count; + uint32_t s_free_blocks_count; + uint32_t s_free_inodes_count; + uint32_t s_first_data_block; + uint32_t s_log_block_size; + int32_t s_log_frag_size; + uint32_t s_blocks_per_group; + uint32_t s_frags_per_group; + uint32_t s_inodes_per_group; + uint32_t s_mtime; + uint32_t s_wtime; + uint16_t s_mnt_count; + uint16_t s_max_mnt_count; + uint16_t s_magic; + uint16_t s_state; + uint16_t s_errors; + uint16_t s_minor_rev_level; + uint32_t s_lastcheck; + uint32_t s_checkinterval; + uint32_t s_creator_os; + uint32_t s_rev_level; + uint16_t s_def_resuid; + uint16_t s_def_resgid; +// FAT_DYNAMIC_REV + uint32_t s_first_ino; + uint16_t s_inode_size; + uint16_t s_block_group_nr; + uint32_t s_feature_compat; + uint32_t s_feature_incompat; + uint32_t s_feature_ro_compat; + uint8_t s_uuid[16]; + /*uint8_t*/ char s_volume_name[16]; + /*uint8_t*/ char s_last_mounted[64]; + uint32_t s_algo_bitmap; +// Performance Hints + uint8_t s_prealloc_blocks; + uint8_t s_prealloc_dir_blocks; + uint16_t alignment0; +// Journaling Support + uint8_t s_journal_uuid[16]; + uint32_t s_journal_inum; + uint32_t s_journal_dev; + uint32_t s_last_orphan; +// Directory Indexing Support + uint32_t s_hash_seed[4]; + uint8_t s_def_hash_version; + uint8_t alignment1[3]; +// Other options + uint32_t s_default_mount_options; + uint32_t s_first_meta_bg; + uint8_t alignment2[760]; +}; + +struct ext_blockgrpdesc +{ + uint32_t bg_block_bitmap; + uint32_t bg_inode_bitmap; + uint32_t bg_inode_table; + uint16_t bg_free_blocks_count; + uint16_t bg_free_inodes_count; + uint16_t bg_used_dirs_count; + uint16_t alignment0; + uint8_t alignment1[12]; +}; + +struct ext_inode +{ + uint16_t i_mode; + uint16_t i_uid; + uint32_t i_size; + uint32_t i_atime; + uint32_t i_ctime; + uint32_t i_mtime; + uint32_t i_dtime; + uint16_t i_gid; + uint16_t i_links_count; + uint32_t i_blocks; + uint32_t i_flags; + uint32_t i_osd1; + uint32_t i_block[15]; + uint32_t i_generation; + uint32_t i_file_acl; + uint32_t i_dir_acl; + uint32_t i_faddr; + uint8_t i_frag; + uint8_t i_fsize; + uint16_t i_mode_high; + uint16_t i_uid_high; + uint16_t i_gid_high; + uint32_t i_osd2_alignment0; +}; + +struct ext_dirent +{ + uint32_t inode; + uint16_t reclen; + uint8_t name_len; + uint8_t file_type; + char name[0]; +}; + +#endif diff --git a/fat/fat.h b/fat/fat.h new file mode 100644 index 00000000..e1a88262 --- /dev/null +++ b/fat/fat.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 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. + * + * fat.h + * The File Allocation Table (FAT) filesystem. + */ + +#ifndef FAT_H +#define FAT_H + +#include +#include + +// TODO: Dammit. I forgot to swap endian all across the codebase. + +struct fat_bpb +{ + uint8_t jump[3]; + char oem[8]; + uint8_t bytes_per_sector_low; + uint8_t bytes_per_sector_high; + uint8_t sectors_per_cluster; + uint16_t reserved_sectors; + uint8_t fat_count; + uint8_t root_dirent_count_low; + uint8_t root_dirent_count_high; + uint8_t total_sectors_low; + uint8_t total_sectors_high; + uint8_t media_descriptor_type; + uint16_t sectors_per_fat; + uint16_t sectors_per_track; + uint16_t head_count; + uint16_t hidden_sectors; + uint32_t total_sectors_large; + union + { + struct + { + uint8_t fat12_drive_number; + uint8_t fat12_reserved; + uint8_t fat12_signature; + uint8_t fat12_volume_id[4]; + uint8_t fat12_volume_label[11]; + uint8_t fat12_system[8]; + }; + struct + { + uint32_t fat32_sectors_per_fat; + uint16_t fat32_flags; + uint16_t fat32_version; + uint32_t fat32_root_cluster; + uint16_t fat32_fsinfo; + uint16_t fat32_backup_boot; + uint32_t fat32_reserved1[3]; + uint8_t fat32_drive_number; + uint8_t fat32_reserved2; + uint8_t fat32_signature; + uint8_t fat32_volume_id[4]; + uint8_t fat32_volume_label[11]; + uint8_t fat32_system[8]; + }; + struct + { + uint8_t bootloader[510 - 36]; + uint8_t boot_signature[2]; + }; + }; +}; + +static_assert(sizeof(struct fat_bpb) == 512, "sizeof(struct fat_bpb) == 512"); + +struct fat_fsinfo +{ + uint32_t signature1; + uint32_t reserved1[120]; + uint32_t signature2; + uint32_t free_count; + uint32_t next_free; + uint32_t reserved2[3]; + uint32_t signature3; +}; + +static_assert(sizeof(struct fat_fsinfo) == 512, "sizeof(struct fat_fsinfo) == 512"); + +struct fat_dirent +{ + char name[11]; + uint8_t attributes; + uint8_t reserved; + uint8_t creation_tenths; // TODO: Misnamed semantically. + uint16_t creation_time; + uint16_t creation_date; + uint16_t access_date; + uint16_t cluster_high; + uint16_t modified_time; + uint16_t modified_date; + uint16_t cluster_low; + uint32_t size; +}; + +static_assert(sizeof(struct fat_dirent) == 32, "sizeof(struct fat_dirent) == 32"); + +#define FAT_ATTRIBUTE_READ_ONLY (1 << 0) +#define FAT_ATTRIBUTE_HIDDEN (1 << 1) +#define FAT_ATTRIBUTE_SYSTEM (1 << 2) +#define FAT_ATTRIBUTE_VOLUME_ID (1 << 3) +#define FAT_ATTRIBUTE_DIRECTORY (1 << 4) +#define FAT_ATTRIBUTE_ARCHIVE (1 << 5) + +#define FAT_ATTRIBUTE_LONG_NAME 0x0F + +#endif diff --git a/fat/fatfs.cpp b/fat/fatfs.cpp new file mode 100644 index 00000000..36d6307a --- /dev/null +++ b/fat/fatfs.cpp @@ -0,0 +1,339 @@ + + +/* + * Copyright (c) 2013, 2014, 2015, 2016, 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. + * + * fatfs.cpp + * The File Allocation Table (FAT) filesystem. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__sortix__) +#include "fsmarshall.h" +#else +#include "fuse.h" +#endif + +// TODO: Remove. +#include "ext-constants.h" +#include "ext-structs.h" + +#include "blockgroup.h" +#include "block.h" +#include "device.h" +#include "fat.h" +#include "fatfs.h" +#include "filesystem.h" +#include "inode.h" +#include "ioleast.h" +#include "util.h" + +// These must be kept up to date with libmount/fat.c. +static const uint32_t FAT_FEATURE_COMPAT_SUPPORTED = 0; +static const uint32_t FAT_FEATURE_INCOMPAT_SUPPORTED = \ + FAT_FEATURE_INCOMPAT_FILETYPE; +static const uint32_t FAT_FEATURE_RO_COMPAT_SUPPORTED = \ + FAT_FEATURE_RO_COMPAT_LARGE_FILE; + +uid_t request_uid; +uid_t request_gid; + +mode_t HostModeFromExtMode(uint32_t extmode) +{ + mode_t hostmode = extmode & 0777; + if ( extmode & FAT_S_ISVTX ) hostmode |= S_ISVTX; + if ( extmode & FAT_S_ISGID ) hostmode |= S_ISGID; + if ( extmode & FAT_S_ISUID ) hostmode |= S_ISUID; + if ( FAT_S_ISSOCK(extmode) ) hostmode |= S_IFSOCK; + if ( FAT_S_ISLNK(extmode) ) hostmode |= S_IFLNK; + if ( FAT_S_ISREG(extmode) ) hostmode |= S_IFREG; + if ( FAT_S_ISBLK(extmode) ) hostmode |= S_IFBLK; + if ( FAT_S_ISDIR(extmode) ) hostmode |= S_IFDIR; + if ( FAT_S_ISCHR(extmode) ) hostmode |= S_IFCHR; + if ( FAT_S_ISFIFO(extmode) ) hostmode |= S_IFIFO; + return hostmode; +} + +uint32_t ExtModeFromHostMode(mode_t hostmode) +{ + uint32_t extmode = hostmode & 0777; + if ( hostmode & S_ISVTX ) extmode |= FAT_S_ISVTX; + if ( hostmode & S_ISGID ) extmode |= FAT_S_ISGID; + if ( hostmode & S_ISUID ) extmode |= FAT_S_ISUID; + if ( S_ISSOCK(hostmode) ) extmode |= FAT_S_IFSOCK; + if ( S_ISLNK(hostmode) ) extmode |= FAT_S_IFLNK; + if ( S_ISREG(hostmode) ) extmode |= FAT_S_IFREG; + if ( S_ISBLK(hostmode) ) extmode |= FAT_S_IFBLK; + if ( S_ISDIR(hostmode) ) extmode |= FAT_S_IFDIR; + if ( S_ISCHR(hostmode) ) extmode |= FAT_S_IFCHR; + if ( S_ISFIFO(hostmode) ) extmode |= FAT_S_IFIFO; + return extmode; +} + +uint8_t HostDTFromExtDT(uint8_t extdt) +{ + switch ( extdt ) + { + case FAT_FT_UNKNOWN: return DT_UNKNOWN; + case FAT_FT_REG_FILE: return DT_REG; + case FAT_FT_DIR: return DT_DIR; + case FAT_FT_CHRDEV: return DT_CHR; + case FAT_FT_BLKDEV: return DT_BLK; + case FAT_FT_FIFO: return DT_FIFO; + case FAT_FT_SOCK: return DT_SOCK; + case FAT_FT_SYMLINK: return DT_LNK; + } + return DT_UNKNOWN; +} + +// TODO: Encapsulate. +void StatInode(Inode* inode, struct stat* st) +{ + memset(st, 0, sizeof(*st)); + st->st_ino = inode->inode_id; + st->st_mode = inode->Mode(); + st->st_nlink = 1; // TODO: Encapsulate. + st->st_uid = inode->UserId(); + st->st_gid = inode->GroupId(); + st->st_size = inode->Size(); + // TODO: Encapsulate. + // TODO: For the root dir, mount time, or maybe volume label or first file? + // Or maybe the time of the mount point? + time_t mtime = 0; + if ( inode->dirent ) + { + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_sec = ((inode->dirent->modified_time >> 0) & 0x1F) * 2; + tm.tm_min = (inode->dirent->modified_time >> 5) & 0x3F; + tm.tm_hour = (inode->dirent->modified_time >> 11) & 0x1F; + tm.tm_mday = (inode->dirent->modified_date >> 0) & 0x1F; + tm.tm_mon = ((inode->dirent->modified_date >> 5) & 0xF) - 1; + tm.tm_year = ((inode->dirent->modified_date >> 9) & 0x7F) + 80; + mtime = mktime(&tm); + } + st->st_atim.tv_sec = mtime; // TODO: The actual accessed time; + st->st_atim.tv_nsec = 0; + st->st_ctim.tv_sec = mtime; // TODO: Probably fine to keep as modified time. + st->st_ctim.tv_nsec = 0; + st->st_mtim.tv_sec = mtime; + st->st_mtim.tv_nsec = 0; + st->st_blksize = inode->filesystem->bytes_per_sector * + inode->filesystem->bpb->sectors_per_cluster; + // TODO: Encapsulate. + st->st_blocks = 0; // TODO inode->data->i_blocks; +} + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + { + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } + } +} + +static void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... DEVICE [MOUNT-POINT]\n", argv0); +} + +static void version(FILE* fp, const char* argv0) +{ + fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR); +} + +int main(int argc, char* argv[]) +{ + const char* argv0 = argv[0]; + const char* pretend_mount_path = NULL; + bool foreground = false; + bool read = false; + bool write = false; + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' || !arg[1] ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + case 'r': read = true; break; + case 'w': write = true; break; + default: + fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); + help(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) + help(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--version") ) + version(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--background") ) + foreground = false; + else if ( !strcmp(arg, "--foreground") ) + foreground = true; + else if ( !strcmp(arg, "--read") ) + read = true; + else if ( !strcmp(arg, "--write") ) + write = true; + else if ( !strncmp(arg, "--pretend-mount-path=", strlen("--pretend-mount-path=")) ) + pretend_mount_path = arg + strlen("--pretend-mount-path="); + else if ( !strcmp(arg, "--pretend-mount-path") ) + { + if ( i+1 == argc ) + { + fprintf(stderr, "%s: --pretend-mount-path: Missing operand\n", argv0); + exit(1); + } + pretend_mount_path = argv[++i]; + argv[i] = NULL; + } + else + { + fprintf(stderr, "%s: unknown option: %s\n", argv0, arg); + help(stderr, argv0); + exit(1); + } + } + + // It doesn't make sense to have a write-only filesystem. + read = read || write; + + // Default to read and write filesystem access. + if ( !read && !write ) + read = write = true; + + if ( argc == 1 ) + { + help(stdout, argv0); + exit(0); + } + + compact_arguments(&argc, &argv); + + const char* device_path = 2 <= argc ? argv[1] : NULL; + const char* mount_path = 3 <= argc ? argv[2] : NULL; + + if ( !device_path ) + { + help(stderr, argv0); + exit(1); + } + + if ( !pretend_mount_path ) + pretend_mount_path = mount_path; + + int fd = open(device_path, write ? O_RDWR : O_RDONLY); + if ( fd < 0 ) + err(1, "%s", device_path); + + // Read the bios parameter block from the filesystem so we can verify it. + struct fat_bpb bpb; + if ( preadall(fd, &bpb, sizeof(bpb), 0) != sizeof(bpb) ) + { + if ( errno == EEOF ) + errx(1, "%s: Isn't a valid FAT filesystem (too short)", device_path); + else + err(1, "read: %s", device_path); + } + + // Verify the boot. + if ( !(bpb.boot_signature[0] == 0x55 && bpb.boot_signature[1] == 0xAA) ) + errx(1, "%s: Isn't a valid FAT filesystem (no boot signature)", device_path); + + // Verify the jump instruction at the start of the boot sector. + if ( !(bpb.jump[0] == 0xEB && bpb.jump[2] == 0x90) && + !(bpb.jump[0] == 0xE9) ) + errx(1, "%s: Isn't a valid FAT filesystem (bad jump)", device_path); + + // TODO: Validate all parameters make sense. + + uint16_t bytes_per_sector = + bpb.bytes_per_sector_low | bpb.bytes_per_sector_high << 8; + uint16_t 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); + uint32_t sectors_per_fat = + bpb.sectors_per_fat ? bpb.sectors_per_fat : bpb.fat32_sectors_per_fat; + uint32_t total_sectors = + bpb.total_sectors_low | bpb.total_sectors_high << 8; + if ( !total_sectors ) + total_sectors = bpb.total_sectors_large; + + uint32_t data_offset = + bpb.reserved_sectors + bpb.fat_count * sectors_per_fat + root_dir_sectors; + uint32_t data_sectors = total_sectors - data_offset; + uint32_t cluster_count = data_sectors / bpb.sectors_per_cluster; + + uint8_t fat_type = + cluster_count < 4085 ? 12 : cluster_count < 65525 ? 16 : 32; + + // Verify the filesystem version. + if ( fat_type == 32 && bpb.fat32_version != 0x0000 ) + errx(1, "%s: Unsupported filesystem version 0x%04x", device_path, + bpb.fat32_version); + + // TODO: On FAT16/32 check FAT entry 1 for the high bits to see if fsck is needed. + // Check whether the filesystem was unmounted cleanly. + //if ( !(fat[1] & FAT_UNMOUNTED) || !(fat[1] & FAT_NO_IO_ERR) ) + // warn("warning: %s: Filesystem wasn't unmounted cleanly\n", device_path); + + // TODO: The FAT and clusters are not aligned to cluster size so + // we can't use the cluster size here. Perhaps refactor the + // device so we can deal with whole clusters. + Device* dev = new Device(fd, device_path, bytes_per_sector, write); + if ( !dev ) // TODO: Use operator new nothrow! + err(1, "malloc"); + Filesystem* fs = new Filesystem(dev, pretend_mount_path); + if ( !fs ) // TODO: Use operator new nothrow! + err(1, "malloc"); + + printf("So far so good, data_offset=%u, data_sectors=%u, cluster_count=%u, sectors_per_cluster=%u, fat_type=%i\n", data_offset, data_sectors, cluster_count, bpb.sectors_per_cluster, fat_type); + + if ( !mount_path ) + return 0; + +#if defined(__sortix__) + return fsmarshall_main(argv0, mount_path, foreground, fs, dev); +#else + return fat_fuse_main(argv0, mount_path, foreground, fs, dev); +#endif +} diff --git a/fat/fatfs.h b/fat/fatfs.h new file mode 100644 index 00000000..ba2e87ac --- /dev/null +++ b/fat/fatfs.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015 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. + * + * fatfs.h + * Implementation of the extended filesystem. + */ + +#ifndef EXTFS_H +#define EXTFS_H + +extern uid_t request_uid; +extern gid_t request_gid; + +class Inode; + +mode_t HostModeFromExtMode(uint32_t extmode); +uint32_t ExtModeFromHostMode(mode_t hostmode); +uint8_t HostDTFromExtDT(uint8_t extdt); +void StatInode(Inode* inode, struct stat* st); + +#endif diff --git a/fat/filesystem.cpp b/fat/filesystem.cpp new file mode 100644 index 00000000..4ad7dbfc --- /dev/null +++ b/fat/filesystem.cpp @@ -0,0 +1,547 @@ +/* + * 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 // TODO: Remove. +#include +#include +#include // TODO: Debug. +#include +#include +#include + +#include "ext-constants.h" +#include "ext-structs.h" +#include "fat.h" + +#include "block.h" +#include "blockgroup.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; +} + +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->sb = NULL; // TODO: Remove. + this->device = device; + this->block_groups = NULL; // TODO: Remove. + 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; + // TODO: Update to FAT: + //this->num_blocks = this->sb->s_blocks_count; + 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(); +} + +BlockGroup* Filesystem::GetBlockGroup(uint32_t group_id) +{ + err(1, "deleteme: %s\n", __func__); + assert(group_id < num_groups); + if ( block_groups[group_id] ) + return block_groups[group_id]->Refer(), block_groups[group_id]; + + size_t group_size = sizeof(ext_blockgrpdesc); + uint32_t first_block_id = sb->s_first_data_block + 1 /* superblock */; + uint32_t block_id = first_block_id + (group_id * group_size) / block_size; + uint32_t offset = (group_id * group_size) % block_size; + + Block* block = device->GetBlock(block_id); + if ( !block ) + return (BlockGroup*) NULL; + BlockGroup* group = new BlockGroup(this, group_id); + if ( !group ) // TODO: Use operator new nothrow! + return block->Unref(), (BlockGroup*) NULL; + group->data_block = block; + uint8_t* buf = group->data_block->block_data + offset; + group->data = (struct ext_blockgrpdesc*) buf; + return block_groups[group_id] = group; +} + +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::AllocateBlock(BlockGroup* preferred) +{ + err(1, "deleteme: %s\n", __func__); + if ( !device->write ) + return errno = EROFS, 0; + if ( !sb->s_free_blocks_count ) + return errno = ENOSPC, 0; + if ( preferred ) + if ( uint32_t block_id = preferred->AllocateBlock() ) + return block_id; + // TODO: This can be made faster by maintaining a linked list of block + // groups that definitely have free blocks. + for ( uint32_t group_id = 0; group_id < num_groups; group_id++ ) + if ( uint32_t block_id = GetBlockGroup(group_id)->AllocateBlock() ) + return block_id; + // TODO: This case should only be fit in the event of corruption. We should + // rebuild all these values upon filesystem mount instead so we know + // this can't happen. That also allows us to make the linked list + // requested above. + BeginWrite(); + sb->s_free_blocks_count = 0; + FinishWrite(); + return errno = ENOSPC, 0; +} + +uint32_t Filesystem::AllocateInode(BlockGroup* preferred) +{ + err(1, "deleteme: %s\n", __func__); + if ( !device->write ) + return errno = EROFS, 0; + if ( !sb->s_free_inodes_count ) + return errno = ENOSPC, 0; + if ( preferred ) + if ( uint32_t inode_id = preferred->AllocateInode() ) + return inode_id; + // TODO: This can be made faster by maintaining a linked list of block + // groups that definitely have free inodes. + for ( uint32_t group_id = 0; group_id < num_groups; group_id++ ) + if ( uint32_t inode_id = GetBlockGroup(group_id)->AllocateInode() ) + return inode_id; + // TODO: This case should only be fit in the event of corruption. We should + // rebuild all these values upon filesystem mount instead so we know + // this can't happen. That also allows us to make the linked list + // requested above. + BeginWrite(); + sb->s_free_inodes_count = 0; + FinishWrite(); + return errno = ENOSPC, 0; +} + +void Filesystem::FreeBlock(uint32_t block_id) +{ + err(1, "deleteme: %s\n", __func__); + assert(device->write); + assert(block_id); + assert(block_id < num_blocks); + uint32_t group_id = (block_id - sb->s_first_data_block) / sb->s_blocks_per_group; + assert(group_id < num_groups); + BlockGroup* group = GetBlockGroup(group_id); + if ( !group ) + return; + group->FreeBlock(block_id); + group->Unref(); +} + +void Filesystem::FreeInode(uint32_t inode_id) +{ + err(1, "deleteme: %s\n", __func__); + assert(device->write); + assert(inode_id); + assert(inode_id < num_inodes); + uint32_t group_id = (inode_id-1) / sb->s_inodes_per_group; + assert(group_id < num_groups); + BlockGroup* group = GetBlockGroup(group_id); + if ( !group ) + return; + group->FreeInode(inode_id); + group->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; +} diff --git a/fat/filesystem.h b/fat/filesystem.h new file mode 100644 index 00000000..ee5fa10f --- /dev/null +++ b/fat/filesystem.h @@ -0,0 +1,97 @@ +/* + * 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.h + * Filesystem. + */ + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +bool is_8_3(const char* name); +void encode_8_3(const char* decoded, char encoded[8 + 3]); +void decode_8_3(const char encoded[8 + 3], char decoded[8 + 1 + 3 + 1]); +uint8_t timespec_to_fat_tenths(struct timespec* ts); +uint16_t timespec_to_fat_time(struct timespec* ts); +uint16_t timespec_to_fat_date(struct timespec* ts); + +class BlockGroup; +class Device; +class Inode; + +static const size_t INODE_HASH_LENGTH = 1 << 16; + +class Filesystem +{ +public: + Filesystem(Device* device, const char* mount_path); + ~Filesystem(); + +public: + Block* bpb_block; + struct fat_bpb* bpb; + struct ext_superblock* sb; //TODO: Remove. + Device* device; + BlockGroup** block_groups; + const char* mount_path; + mode_t mode_reg; + mode_t mode_dir; + uid_t uid; + gid_t gid; + uint32_t block_size; + uint32_t inode_size; // TODO: Remove. + uint16_t bytes_per_sector; + uint16_t root_dirent_count; + uint32_t sectors_per_fat; + uint32_t root_inode_id; + uint32_t total_sectors; + uint32_t fat_sector; // TODO: Rename to lba + uint32_t root_sector; // TODO: Rename to lba + uint32_t data_sector; // TODO: Rename to lba + uint32_t cluster_count; + uint32_t cluster_size; + uint8_t fat_type; + uint32_t eio_cluster; + uint32_t eof_cluster; + uint32_t free_search; + uint32_t num_blocks; // TODO: Remove, probably. + uint32_t num_groups; // TODO: Remove. + uint32_t num_inodes; // TODO: Remove, probably. + Inode* mru_inode; + Inode* lru_inode; + Inode* dirty_inode; + Inode* hash_inodes[INODE_HASH_LENGTH]; + bool dirty; + +public: + BlockGroup* GetBlockGroup(uint32_t group_id); + Inode* GetInode(uint32_t inode_id, Block* dirent_block = NULL, + struct fat_dirent* dirent = NULL); + uint32_t AllocateCluster(); + void FreeCluster(uint32_t cluster); + uint32_t AllocateBlock(BlockGroup* preferred = NULL); + uint32_t AllocateInode(BlockGroup* preferred = NULL); + void FreeBlock(uint32_t block_id); + void FreeInode(uint32_t inode_id); + uint32_t ReadFAT(uint32_t cluster); + bool WriteFAT(uint32_t cluster, uint32_t value); + uint32_t CalculateFreeCount(); + void BeginWrite(); + void FinishWrite(); + void Sync(); + +}; + +#endif diff --git a/fat/fsmarshall.cpp b/fat/fsmarshall.cpp new file mode 100644 index 00000000..79d39709 --- /dev/null +++ b/fat/fsmarshall.cpp @@ -0,0 +1,888 @@ +/* + * Copyright (c) 2013, 2014, 2015, 2016, 2022, 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. + * + * fsmarshall.cpp + * Sortix fsmarshall frontend. + */ + +#if defined(__sortix__) + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "ext-constants.h" +#include "ext-structs.h" // TODO: Remove. + +#include "blockgroup.h" +#include "block.h" +#include "device.h" +#include "fat.h" +#include "fatfs.h" +#include "filesystem.h" +#include "fsmarshall.h" +#include "fuse.h" +#include "inode.h" + +bool RespondData(int chl, const void* ptr, size_t count) +{ + return writeall(chl, ptr, count) == count; +} + +bool RespondHeader(int chl, size_t type, size_t size) +{ + struct fsm_msg_header hdr; + hdr.msgtype = type; + hdr.msgsize = size; + return RespondData(chl, &hdr, sizeof(hdr)); +} + +bool RespondMessage(int chl, size_t type, const void* ptr, size_t count) +{ + return RespondHeader(chl, type, count) && + RespondData(chl, ptr, count); +} + +bool RespondError(int chl, int errnum) +{ + struct fsm_resp_error body; + body.errnum = errnum; + //fprintf(stderr, "fatfs: sending error %i (%s)\n", errnum, strerror(errnum)); + return RespondMessage(chl, FSM_RESP_ERROR, &body, sizeof(body)); +} + +bool RespondSuccess(int chl) +{ + struct fsm_resp_success body; + return RespondMessage(chl, FSM_RESP_SUCCESS, &body, sizeof(body)); +} + +bool RespondStat(int chl, struct stat* st) +{ + struct fsm_resp_stat body; + body.st = *st; + return RespondMessage(chl, FSM_RESP_STAT, &body, sizeof(body)); +} + +bool RespondStatVFS(int chl, struct statvfs* stvfs) +{ + struct fsm_resp_statvfs body; + body.stvfs = *stvfs; + return RespondMessage(chl, FSM_RESP_STATVFS, &body, sizeof(body)); +} + +bool RespondSeek(int chl, off_t offset) +{ + struct fsm_resp_lseek body; + body.offset = offset; + return RespondMessage(chl, FSM_RESP_LSEEK, &body, sizeof(body)); +} + +bool RespondRead(int chl, const uint8_t* buf, size_t count) +{ + struct fsm_resp_read body; + body.count = count; + return RespondMessage(chl, FSM_RESP_READ, &body, sizeof(body)) && + RespondData(chl, buf, count); +} + +bool RespondReadlink(int chl, const uint8_t* buf, size_t count) +{ + struct fsm_resp_readlink body; + body.targetlen = count; + return RespondMessage(chl, FSM_RESP_READLINK, &body, sizeof(body)) && + RespondData(chl, buf, count); +} + +bool RespondWrite(int chl, size_t count) +{ + struct fsm_resp_write body; + body.count = count; + return RespondMessage(chl, FSM_RESP_WRITE, &body, sizeof(body)); +} + +bool RespondOpen(int chl, ino_t ino, mode_t type) +{ + struct fsm_resp_open body; + body.ino = ino; + body.type = type; + return RespondMessage(chl, FSM_RESP_OPEN, &body, sizeof(body)); +} + +bool RespondMakeDir(int chl, ino_t ino) +{ + struct fsm_resp_mkdir body; + body.ino = ino; + return RespondMessage(chl, FSM_RESP_MKDIR, &body, sizeof(body)); +} + +bool RespondReadDir(int chl, struct dirent* dirent) +{ + struct fsm_resp_readdirents body; + body.ino = dirent->d_ino; + body.type = dirent->d_type; + body.namelen = dirent->d_namlen; + return RespondMessage(chl, FSM_RESP_READDIRENTS, &body, sizeof(body)) && + RespondData(chl, dirent->d_name, dirent->d_namlen); +} + +bool RespondTCGetBlob(int chl, const void* data, size_t data_size) +{ + struct fsm_resp_tcgetblob body; + body.count = data_size; + return RespondMessage(chl, FSM_RESP_TCGETBLOB, &body, sizeof(body)) && + RespondData(chl, data, data_size); +} + +Inode* SafeGetInode(Filesystem* fs, ino_t ino) +{ + if ( (uint32_t) ino != ino ) + return errno = EBADF, (Inode*) NULL; + // TODO: Should check if the inode is not deleted. + return fs->GetInode((uint32_t) ino); +} + +void HandleRefer(int chl, struct fsm_req_refer* msg, Filesystem* fs) +{ + (void) chl; + if ( Inode* inode = SafeGetInode(fs, (uint32_t) msg->ino) ) + { + inode->RemoteRefer(); + inode->Unref(); + } +} + +void HandleUnref(int chl, struct fsm_req_unref* msg, Filesystem* fs) +{ + (void) chl; + if ( Inode* inode = SafeGetInode(fs, (uint32_t) msg->ino) ) + { + inode->RemoteUnref(); + inode->Unref(); + } +} + +void HandleSync(int chl, struct fsm_req_sync* msg, Filesystem* fs) +{ + printf("ENOTSUP %s\n", __func__); + RespondError(chl, ENOTSUP); // TODO + return; + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + inode->Sync(); + inode->Unref(); + RespondSuccess(chl); +} + +void HandleStat(int chl, struct fsm_req_stat* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + struct stat st; + StatInode(inode, &st); + inode->Unref(); + RespondStat(chl, &st); +} + +void HandleChangeMode(int chl, struct fsm_req_chmod* msg, Filesystem* fs) +{ + printf("ENOTSUP %s\n", __func__); + RespondError(chl, ENOTSUP); // TODO + return; + if ( !fs->device->write ) { RespondError(chl, EROFS); return; } + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + uint32_t req_mode = ExtModeFromHostMode(msg->mode); + uint32_t old_mode = inode->Mode(); + uint32_t new_mode = (old_mode & ~S_SETABLE) | (req_mode & S_SETABLE); + inode->SetMode(new_mode); + inode->Unref(); + RespondSuccess(chl); +} + +void HandleChangeOwner(int chl, struct fsm_req_chown* msg, Filesystem* fs) +{ + printf("ENOTSUP %s\n", __func__); + RespondError(chl, ENOTSUP); // TODO + return; + if ( !fs->device->write ) { RespondError(chl, EROFS); return; } + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + if ( msg->uid != (uid_t) -1 ) + inode->SetUserId((uint32_t) msg->uid); + if ( msg->gid != (gid_t) -1 ) + inode->SetGroupId((uint32_t) msg->gid); + inode->Unref(); + RespondSuccess(chl); +} + +void HandleUTimens(int chl, struct fsm_req_utimens* msg, Filesystem* fs) +{ + printf("ENOTSUP %s\n", __func__); + RespondError(chl, ENOTSUP); // TODO + return; + if ( !fs->device->write ) { RespondError(chl, EROFS); return; } + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + if ( msg->times[0].tv_nsec != UTIME_OMIT || + msg->times[1].tv_nsec != UTIME_OMIT ) + { + time_t now = time(NULL); + inode->BeginWrite(); + if ( msg->times[0].tv_nsec == UTIME_NOW ) + inode->data->i_atime = now; + else if ( msg->times[0].tv_nsec != UTIME_OMIT ) + inode->data->i_atime = msg->times[0].tv_sec; + if ( msg->times[1].tv_nsec == UTIME_NOW ) + inode->data->i_mtime = now; + else if ( msg->times[1].tv_nsec != UTIME_OMIT ) + inode->data->i_mtime = msg->times[1].tv_sec; + inode->FinishWrite(); + } + inode->Unref(); + RespondSuccess(chl); +} + +void HandleTruncate(int chl, struct fsm_req_truncate* msg, Filesystem* fs) +{ + if ( !fs->device->write ) { RespondError(chl, EROFS); return; } + if ( msg->size < 0 ) { RespondError(chl, EINVAL); return; } + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + inode->Truncate((uint64_t) msg->size); + inode->Unref(); + RespondSuccess(chl); +} + +void HandleSeek(int chl, struct fsm_req_lseek* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + if ( msg->whence == SEEK_SET ) + RespondSeek(chl, msg->offset); + else if ( msg->whence == SEEK_END ) + { + off_t inode_size = inode->Size(); + if ( (msg->offset < 0 && inode_size + msg->offset < 0) || + (0 <= msg->offset && OFF_MAX - inode_size < msg->offset) ) + RespondError(chl, EOVERFLOW); + else + RespondSeek(chl, msg->offset + inode_size); + } + else + RespondError(chl, EINVAL); + inode->Unref(); +} + +void HandleReadAt(int chl, struct fsm_req_pread* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + uint8_t* buf = (uint8_t*) malloc(msg->count); + if ( !buf ) { inode->Unref(); RespondError(chl, errno); return; } + ssize_t amount = inode->ReadAt(buf, msg->count, msg->offset); + inode->Unref(); + if ( amount < 0 ) { free(buf); RespondError(chl, errno); return; } + RespondRead(chl, buf, amount); + free(buf); +} + +void HandleWriteAt(int chl, struct fsm_req_pwrite* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + const uint8_t* buf = (const uint8_t*) &msg[1]; + ssize_t amount = inode->WriteAt(buf, msg->count, msg->offset); + inode->Unref(); + if ( amount < 0 ) { RespondError(chl, errno); return; } + RespondWrite(chl, amount); +} + +void HandleOpen(int chl, struct fsm_req_open* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + Inode* result = inode->Open(path, msg->flags, msg->mode & 07777); + + free(path); + inode->Unref(); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondOpen(chl, result->inode_id, result->Mode() & S_IFMT); + // TODO: This leaks a reference so the Inode keeps existing with meta data + // since unfortunately the automatic Refer() in the kernel is not implied. + // idk how to best solve that just yet, maybe add a count to the inode + // that causes that many Unrefs() to happen on the next HandleRefer(). + //result->Unref(); +} + +void HandleMakeDir(int chl, struct fsm_req_mkdir* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + Inode* result = inode->CreateDirectory(path, msg->mode & 07777); + + free(path); + inode->Unref(); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondMakeDir(chl, result->inode_id); + result->Unref(); +} + +void HandleReadDir(int chl, struct fsm_req_readdirents* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + if ( !S_ISDIR(inode->Mode()) ) + { + inode->Unref(); + RespondError(chl, ENOTDIR); + return; + } + union + { + struct dirent kernel_entry; + // TODO: Adjust with LFN's real limit. + uint8_t padding[sizeof(struct dirent) + 256]; + }; + memset(&kernel_entry, 0, sizeof(kernel_entry)); + + if ( inode->inode_id == inode->filesystem->root_inode_id ) + { + if ( msg->rec_num < 2 ) + { + const char* name = msg->rec_num ? ".." : "."; + size_t name_len = strlen(name); + kernel_entry.d_reclen = sizeof(kernel_entry) + name_len; + kernel_entry.d_ino = inode->inode_id; + kernel_entry.d_dev = 0; + kernel_entry.d_type = DT_DIR; + kernel_entry.d_namlen = name_len; + memcpy(kernel_entry.d_name, name, name_len); + size_t dname_offset = offsetof(struct dirent, d_name); + padding[dname_offset + kernel_entry.d_namlen] = '\0'; + RespondReadDir(chl, &kernel_entry); + return; + } + msg->rec_num -= 2; + } + + uint32_t cluster = inode->first_cluster; + uint8_t sector = 0; + uint16_t offset = 0; + Block* block = NULL; + while ( inode->Iterate(&block, &cluster, §or, &offset) ) + { + const uint8_t* block_data = block->block_data + offset; + const struct fat_dirent* entry = (const struct fat_dirent*) block_data; + if ( !entry->name[0] ) + break; + if ( (unsigned char) entry->name[0] != 0xE5 && + !(entry->attributes & FAT_ATTRIBUTE_VOLUME_ID) && + !(msg->rec_num--) ) + { + char name[8 + 1 + 3 + 1]; + decode_8_3(entry->name, name); + size_t name_len = strnlen(entry->name, sizeof(entry->name)); + uint8_t file_type = + entry->attributes & FAT_ATTRIBUTE_DIRECTORY ? DT_DIR : DT_REG; + kernel_entry.d_reclen = sizeof(kernel_entry) + name_len; + kernel_entry.d_ino = entry->cluster_low | entry->cluster_high << 16; + kernel_entry.d_dev = 0; + kernel_entry.d_type = file_type; + kernel_entry.d_namlen = name_len; + memcpy(kernel_entry.d_name, name, name_len); + size_t dname_offset = offsetof(struct dirent, d_name); + padding[dname_offset + kernel_entry.d_namlen] = '\0'; + block->Unref(); + inode->Unref(); + RespondReadDir(chl, &kernel_entry); + return; + } + offset += sizeof(struct fat_dirent); + } + int errnum = errno; + if ( block ) + block->Unref(); + inode->Unref(); + if ( errnum ) + { + RespondError(chl, errnum); + return; + } + + kernel_entry.d_reclen = sizeof(kernel_entry); + RespondReadDir(chl, &kernel_entry); +} + +void HandleIsATTY(int chl, struct fsm_req_isatty* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + RespondError(chl, ENOTTY); + inode->Unref(); +} + +void HandleUnlink(int chl, struct fsm_req_unlink* msg, Filesystem* fs) +{ + printf("ENOTSUP %s\n", __func__); + RespondError(chl, ENOTSUP); // TODO + return; + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + bool result = inode->Unlink(path, false); + free(path); + inode->Unref(); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondSuccess(chl); +} + +void HandleRemoveDir(int chl, struct fsm_req_rmdir* msg, Filesystem* fs) +{ + printf("ENOTSUP %s\n", __func__); + RespondError(chl, ENOTSUP); // TODO + return; + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + bool result = inode->RemoveDirectory(path); + free(path); + inode->Unref(); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondSuccess(chl); +} + +void HandleLink(int chl, struct fsm_req_link* msg, Filesystem* fs) +{ + printf("ENOTSUP %s\n", __func__); + RespondError(chl, ENOTSUP); // TODO + return; + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + Inode* dest = SafeGetInode(fs, msg->linkino); + if ( !dest ) { inode->Unref(); RespondError(chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + bool result = inode->Link(path, dest, false); + + free(path); + dest->Unref(); + inode->Unref(); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondSuccess(chl); +} + +void HandleSymlink(int chl, struct fsm_req_symlink* msg, Filesystem* fs) +{ + printf("ENOTSUP %s\n", __func__); + RespondError(chl, ENOTSUP); // TODO + return; + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + + char* dest_raw = (char*) &(msg[1]); + char* dest = (char*) malloc(msg->targetlen + 1); + if ( !dest ) + { + RespondError(chl, errno); + inode->Unref(); + return; + } + memcpy(dest, dest_raw, msg->targetlen); + dest[msg->targetlen] = '\0'; + + char* path_raw = (char*) dest_raw + msg->targetlen; + char* path = (char*) malloc(msg->namelen + 1); + if ( !path ) + { + free(dest); + RespondError(chl, errno); + inode->Unref(); + return; + } + memcpy(path, path_raw, msg->namelen); + path[msg->namelen] = '\0'; + + bool result = inode->Symlink(path, dest); + + free(path); + free(dest); + inode->Unref(); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondSuccess(chl); +} + +void HandleReadlink(int chl, struct fsm_req_readlink* msg, Filesystem* fs) +{ + (void) msg; + (void) fs; + RespondError(chl, EINVAL); +} + +void HandleRename(int chl, struct fsm_req_rename* msg, Filesystem* fs) +{ + RespondError(chl, ENOTSUP); // TODO + return; + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->oldnamelen+1 + msg->newnamelen+1); + if ( !path ) { RespondError(chl, errno); return; } + memcpy(path, pathraw, msg->oldnamelen); + path[msg->oldnamelen] = '\0'; + memcpy(path + msg->oldnamelen + 1, pathraw + msg->oldnamelen, msg->newnamelen); + path[msg->oldnamelen + 1 + msg->newnamelen] = '\0'; + + const char* oldname = path; + const char* newname = path + msg->oldnamelen + 1; + + Inode* olddir = SafeGetInode(fs, msg->olddirino); + if ( !olddir ) { free(path); RespondError(chl, errno); return; } + Inode* newdir = SafeGetInode(fs, msg->newdirino); + if ( !newdir ) { olddir->Unref(); free(path); RespondError(chl, errno); return; } + + bool result = newdir->Rename(olddir, oldname, newname); + + newdir->Unref(); + olddir->Unref(); + free(path); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondSuccess(chl); +} + +void HandleStatVFS(int chl, struct fsm_req_statvfs* msg, Filesystem* fs) +{ + (void) msg; + struct statvfs stvfs; + stvfs.f_bsize = fs->cluster_size; + stvfs.f_frsize = fs->cluster_size; + stvfs.f_blocks = fs->cluster_count; + // TODO: Locate FsInfo and count on FAT12/FAT16. + stvfs.f_bfree = fs->CalculateFreeCount(); + stvfs.f_bavail = stvfs.f_bfree; + stvfs.f_files = stvfs.f_blocks; + stvfs.f_ffree = stvfs.f_bfree; + stvfs.f_favail = stvfs.f_files; + stvfs.f_fsid = 0; + stvfs.f_flag = 0; + if ( !fs->device->write ) + stvfs.f_flag |= ST_RDONLY; + stvfs.f_namemax = 8 + 3; // TODO: Long file name support. + RespondStatVFS(chl, &stvfs); +} + +void HandleTCGetBlob(int chl, struct fsm_req_tcgetblob* msg, Filesystem* fs) +{ + char* nameraw = (char*) &(msg[1]); + char* name = (char*) malloc(msg->namelen + 1); + if ( !name ) + return (void) RespondError(chl, errno); + memcpy(name, nameraw, msg->namelen); + name[msg->namelen] = '\0'; + + static const char index[] = "device-path\0filesystem-type\0filesystem-uuid\0mount-path\0"; + if ( !strcmp(name, "") ) + RespondTCGetBlob(chl, index, sizeof(index) - 1); + else if ( !strcmp(name, "device-path") ) + RespondTCGetBlob(chl, fs->device->path, strlen(fs->device->path)); + else if ( !strcmp(name, "filesystem-type") ) + RespondTCGetBlob(chl, "fat", strlen("fat")); + else if ( !strcmp(name, "filesystem-uuid") ) + { + unsigned char uuid[16]; + if ( fs->fat_type == 32 ) + { + memcpy(uuid, &fs->bpb->fat32_volume_id, 4); + memcpy(uuid + 4, &fs->bpb->fat32_volume_id, 11); + } + else + { + memcpy(uuid, &fs->bpb->fat12_volume_id, 4); + memcpy(uuid + 4, &fs->bpb->fat12_volume_id, 11); + } + uuid[15] = '\0'; + RespondTCGetBlob(chl, uuid, sizeof(uuid)); + } + else if ( !strcmp(name, "mount-path") ) + RespondTCGetBlob(chl, fs->mount_path, strlen(fs->mount_path)); + else + RespondError(chl, ENOENT); + + free(name); +} + +void HandleIncomingMessage(int chl, struct fsm_msg_header* hdr, Filesystem* fs) +{ + request_uid = hdr->uid; + request_gid = hdr->gid; + if ( (uint16_t) request_uid != request_uid || + (uint16_t) request_gid != request_gid ) + { + warn("id exceeded 16-bit: uid=%ju gid=%ju\n", + (uintmax_t) request_uid, (uintmax_t) request_gid); + RespondError(chl, EOVERFLOW); + return; + } + typedef void (*handler_t)(int, void*, Filesystem*); + handler_t handlers[FSM_MSG_NUM] = { NULL }; + handlers[FSM_REQ_SYNC] = (handler_t) HandleSync; + handlers[FSM_REQ_STAT] = (handler_t) HandleStat; + handlers[FSM_REQ_CHMOD] = (handler_t) HandleChangeMode; + handlers[FSM_REQ_CHOWN] = (handler_t) HandleChangeOwner; + handlers[FSM_REQ_TRUNCATE] = (handler_t) HandleTruncate; + handlers[FSM_REQ_LSEEK] = (handler_t) HandleSeek; + handlers[FSM_REQ_PREAD] = (handler_t) HandleReadAt; + handlers[FSM_REQ_OPEN] = (handler_t) HandleOpen; + handlers[FSM_REQ_READDIRENTS] = (handler_t) HandleReadDir; + handlers[FSM_REQ_PWRITE] = (handler_t) HandleWriteAt; + handlers[FSM_REQ_ISATTY] = (handler_t) HandleIsATTY; + handlers[FSM_REQ_UTIMENS] = (handler_t) HandleUTimens; + handlers[FSM_REQ_MKDIR] = (handler_t) HandleMakeDir; + handlers[FSM_REQ_RMDIR] = (handler_t) HandleRemoveDir; + handlers[FSM_REQ_UNLINK] = (handler_t) HandleUnlink; + handlers[FSM_REQ_LINK] = (handler_t) HandleLink; + handlers[FSM_REQ_SYMLINK] = (handler_t) HandleSymlink; + handlers[FSM_REQ_READLINK] = (handler_t) HandleReadlink; + handlers[FSM_REQ_RENAME] = (handler_t) HandleRename; + handlers[FSM_REQ_REFER] = (handler_t) HandleRefer; + handlers[FSM_REQ_UNREF] = (handler_t) HandleUnref; + handlers[FSM_REQ_STATVFS] = (handler_t) HandleStatVFS; + handlers[FSM_REQ_TCGETBLOB] = (handler_t) HandleTCGetBlob; + if ( FSM_MSG_NUM <= hdr->msgtype || !handlers[hdr->msgtype] ) + { + warn("message type %zu not supported\n", hdr->msgtype); + RespondError(chl, ENOTSUP); + return; + } + uint8_t body_buffer[65536]; + uint8_t* body = body_buffer; + if ( sizeof(body_buffer) < hdr->msgsize ) + { + body = (uint8_t*) mmap(NULL, hdr->msgsize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if ( (void*) body == MAP_FAILED ) + { + RespondError(chl, errno); + return; + } + } + if ( readall(chl, body, hdr->msgsize) == hdr->msgsize ) + handlers[hdr->msgtype](chl, body, fs); + else + RespondError(chl, errno); + if ( sizeof(body_buffer) < hdr->msgsize ) + munmap(body, hdr->msgsize); +} +static volatile bool should_terminate = false; + +void TerminationHandler(int) +{ + should_terminate = true; +} + +static void ready(void) +{ + const char* readyfd_env = getenv("READYFD"); + if ( !readyfd_env ) + return; + int readyfd = atoi(readyfd_env); + char c = '\n'; + write(readyfd, &c, 1); + close(readyfd); + unsetenv("READYFD"); +} + +int fsmarshall_main(const char* argv0, + const char* mount_path, + bool foreground, + Filesystem* fs, + Device* dev) +{ + (void) argv0; + + // Stat the root directory; + struct stat root_dir_st; + memset(&root_dir_st, 0, sizeof(root_dir_st)); + root_dir_st.st_ino = fs->root_inode_id; + root_dir_st.st_mode = S_IFDIR | 0755; + + // Create a filesystem server connected to the kernel that we'll listen on. + int serverfd = fsm_mountat(AT_FDCWD, mount_path, &root_dir_st, 0); + if ( serverfd < 0 ) + err(1, "%s", mount_path); + + // Make sure the server isn't unexpectedly killed and data is lost. + signal(SIGINT, TerminationHandler); + signal(SIGTERM, TerminationHandler); + signal(SIGQUIT, TerminationHandler); + + // Become a background process in its own process group by default. + if ( !foreground ) + { + pid_t child_pid = fork(); + if ( child_pid < 0 ) + err(1, "fork"); + if ( child_pid ) + exit(0); + setpgid(0, 0); + } + else + ready(); + + dev->SpawnSyncThread(); + + // Listen for filesystem messages and sync the filesystem every few seconds. + struct timespec last_sync_at; + clock_gettime(CLOCK_MONOTONIC, &last_sync_at); + int channel; + while ( 0 <= (channel = accept(serverfd, NULL, NULL)) ) + { + if ( should_terminate ) + break; + struct fsm_msg_header hdr; + size_t amount; + if ( (amount = readall(channel, &hdr, sizeof(hdr))) != sizeof(hdr) ) + { + //warn("incomplete header: got %zi of %zu bytes", amount, sizeof(hdr)); + errno = 0; + continue; + } + HandleIncomingMessage(channel, &hdr, fs); + close(channel); + + if ( dev->write && !dev->has_sync_thread ) + { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + if ( 5 <= timespec_sub(now, last_sync_at).tv_sec ) + { + fs->Sync(); + last_sync_at = now; + } + } + } + + // TODO: Replace with FAT concept. + // Garbage collect all open inode references. + while ( fs->mru_inode ) + { + Inode* inode = fs->mru_inode; + if ( inode->remote_reference_count ) + inode->RemoteUnref(); + else if ( inode->reference_count ) + inode->Unref(); + } + + // Sync the filesystem before shutting down. + if ( dev->write ) + fs->Sync(); + + close(serverfd); + + delete fs; + delete dev; + + return 0; +} + +#endif diff --git a/fat/fsmarshall.h b/fat/fsmarshall.h new file mode 100644 index 00000000..1d78d36a --- /dev/null +++ b/fat/fsmarshall.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015 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. + * + * fsmarshall.h + * Sortix fsmarshall frontend. + */ + +#ifndef FSMARSHALL_H +#define FSMARSHALL_H + +class Device; +class Filesystem; + +int fsmarshall_main(const char* argv0, + const char* mount_path, + bool foreground, + Filesystem* fs, + Device* dev); +#endif diff --git a/fat/fuse.cpp b/fat/fuse.cpp new file mode 100644 index 00000000..51ed71f0 --- /dev/null +++ b/fat/fuse.cpp @@ -0,0 +1,618 @@ +/* + * Copyright (c) 2013, 2014, 2015 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. + * + * fuse.cpp + * FUSE frontend. + */ + +#if !defined(__sortix__) + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define FUSE_USE_VERSION 26 +#include + +#include "ext-constants.h" +#include "ext-structs.h" + +#include "blockgroup.h" +#include "block.h" +#include "device.h" +#include "fatfs.h" +#include "filesystem.h" +#include "fuse.h" +#include "inode.h" + +struct fat_fuse_ctx +{ + Device* dev; + Filesystem* fs; +}; + +#ifndef S_SETABLE +#define S_SETABLE 02777 +#endif + +#define FUSE_FS (((struct fat_fuse_ctx*) (fuse_get_context()->private_data))->fs) + +void* fat_fuse_init(struct fuse_conn_info* /*conn*/) +{ + return fuse_get_context()->private_data; +} + +void fat_fuse_destroy(void* fs_private) +{ + struct fat_fuse_ctx* fat_fuse_ctx = (struct fat_fuse_ctx*) fs_private; + while ( fat_fuse_ctx->fs->mru_inode ) + { + Inode* inode = fat_fuse_ctx->fs->mru_inode; + if ( inode->remote_reference_count ) + inode->RemoteUnref(); + else if ( inode->reference_count ) + inode->Unref(); + } + fat_fuse_ctx->fs->Sync(); + fat_fuse_ctx->dev->Sync(); + delete fat_fuse_ctx->fs; fat_fuse_ctx->fs = NULL; + delete fat_fuse_ctx->dev; fat_fuse_ctx->dev = NULL; +} + +Inode* fat_fuse_resolve_path(const char* path) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode(FAT_ROOT_INO); + if ( !inode ) + return (Inode*) NULL; + while ( path[0] ) + { + if ( *path == '/' ) + { + if ( !FAT_S_ISDIR(inode->Mode()) ) + return inode->Unref(), errno = ENOTDIR, (Inode*) NULL; + path++; + continue; + } + size_t elem_len = strcspn(path, "/"); + char* elem = strndup(path, elem_len); + if ( !elem ) + return inode->Unref(), errno = ENOTDIR, (Inode*) NULL; + path += elem_len; + Inode* next = inode->Open(elem, O_RDONLY, 0); + free(elem); + inode->Unref(); + if ( !next ) + return NULL; + inode = next; + } + return inode; +} + +// Assumes that the path doesn't end with / unless it's the root directory. +Inode* fat_fuse_parent_dir(const char** path_ptr) +{ + const char* path = *path_ptr; + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode(FAT_ROOT_INO); + if ( !inode ) + return (Inode*) NULL; + while ( strchr(path, '/') ) + { + if ( *path == '/' ) + { + if ( !FAT_S_ISDIR(inode->Mode()) ) + return inode->Unref(), errno = ENOTDIR, (Inode*) NULL; + path++; + continue; + } + size_t elem_len = strcspn(path, "/"); + char* elem = strndup(path, elem_len); + if ( !elem ) + return inode->Unref(), errno = ENOTDIR, (Inode*) NULL; + path += elem_len; + Inode* next = inode->Open(elem, O_RDONLY, 0); + free(elem); + inode->Unref(); + if ( !next ) + return (Inode*) NULL; + inode = next; + } + *path_ptr = *path ? path : "."; + assert(!strchr(*path_ptr, '/')); + return inode; +} + +int fat_fuse_getattr(const char* path, struct stat* st) +{ + Inode* inode = fat_fuse_resolve_path(path); + if ( !inode ) + return -errno; + StatInode(inode, st); + inode->Unref(); + return 0; +} + +int fat_fuse_fgetattr(const char* /*path*/, struct stat* st, + struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + StatInode(inode, st); + inode->Unref(); + return 0; +} + +int fat_fuse_readlink(const char* path, char* buf, size_t bufsize) +{ + Inode* inode = fat_fuse_resolve_path(path); + if ( !inode ) + return -errno; + if ( !FAT_S_ISLNK(inode->Mode()) ) + return inode->Unref(), -(errno = EINVAL); + if ( !bufsize ) + return inode->Unref(), -(errno = EINVAL); + ssize_t amount = inode->ReadAt((uint8_t*) buf, bufsize, 0); + if ( amount < 0 ) + return inode->Unref(), -errno; + buf[(size_t) amount < bufsize ? (size_t) amount : bufsize - 1] = '\0'; + inode->Unref(); + return 0; +} + +int fat_fuse_mknod(const char* path, mode_t mode, dev_t dev) +{ + (void) path; + (void) mode; + (void) dev; + return -(errno = ENOSYS); +} + +int fat_fuse_mkdir(const char* path, mode_t mode) +{ + Inode* inode = fat_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + Inode* newdir = inode->CreateDirectory(path, ExtModeFromHostMode(mode)); + inode->Unref(); + if ( !newdir ) + return -errno; + newdir->Unref(); + return 0; +} + +int fat_fuse_unlink(const char* path) +{ + Inode* inode = fat_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + bool success = inode->Unlink(path, false); + inode->Unref(); + return success ? 0 : -errno; +} + +int fat_fuse_rmdir(const char* path) +{ + Inode* inode = fat_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + bool success = inode->RemoveDirectory(path); + inode->Unref(); + return success ? 0 : -errno; +} + +int fat_fuse_symlink(const char* oldname, const char* newname) +{ + Inode* newdir = fat_fuse_parent_dir(&newname); + if ( !newdir ) + return -errno; + bool success = newdir->Symlink(newname, oldname); + newdir->Unref(); + return success ? 0 : -errno; +} + +int fat_fuse_rename(const char* oldname, const char* newname) +{ + Inode* olddir = fat_fuse_parent_dir(&oldname); + if ( !olddir ) + return -errno; + Inode* newdir = fat_fuse_parent_dir(&newname); + if ( !newdir ) + return olddir->Unref(), -errno; + bool success = newdir->Rename(olddir, oldname, newname); + newdir->Unref(); + olddir->Unref(); + return success ? 0 : -errno; +} + +int fat_fuse_link(const char* oldname, const char* newname) +{ + Inode* inode = fat_fuse_resolve_path(oldname); + if ( !inode ) + return -errno; + Inode* newdir = fat_fuse_parent_dir(&newname); + if ( !newdir ) + return inode->Unref(), -errno; + bool success = inode->Link(newname, inode, false); + newdir->Unref(); + inode->Unref(); + return success ? 0 : -errno; +} + +int fat_fuse_chmod(const char* path, mode_t mode) +{ + Inode* inode = fat_fuse_resolve_path(path); + if ( !inode ) + return -errno; + if ( !FUSE_FS->device->write ) + return inode->Unref(), -(errno = EROFS); + uint32_t req_mode = ExtModeFromHostMode(mode); + uint32_t old_mode = inode->Mode(); + uint32_t new_mode = (old_mode & ~S_SETABLE) | (req_mode & S_SETABLE); + inode->SetMode(new_mode); + inode->Unref(); + return 0; +} + +int fat_fuse_chown(const char* path, uid_t owner, gid_t group) +{ + Inode* inode = fat_fuse_resolve_path(path); + if ( !inode ) + return -errno; + if ( !FUSE_FS->device->write ) + return inode->Unref(), -(errno = EROFS); + inode->SetUserId((uint32_t) owner); + inode->SetGroupId((uint32_t) group); + inode->Unref(); + return 0; +} + +int fat_fuse_truncate(const char* path, off_t size) +{ + Inode* inode = fat_fuse_resolve_path(path); + if ( !inode ) + return -errno; + if ( !FUSE_FS->device->write ) + return inode->Unref(), -(errno = EROFS); + inode->Truncate((uint64_t) size); + inode->Unref(); + return 0; +} + +int fat_fuse_ftruncate(const char* /*path*/, off_t size, + struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + if ( !FUSE_FS->device->write ) + return inode->Unref(), -(errno = EROFS); + inode->Truncate((uint64_t) size); + inode->Unref(); + return 0; +} + +int fat_fuse_open(const char* path, struct fuse_file_info* fi) +{ + int flags = fi->flags; + Inode* dir = fat_fuse_parent_dir(&path); + if ( !dir ) + return -errno; + Inode* result = dir->Open(path, flags, 0); + dir->Unref(); + if ( !result ) + return -errno; + fi->fh = (uint64_t) result->inode_id; + fi->keep_cache = 1; + result->RemoteRefer(); + result->Unref(); + return 0; +} + +int fat_fuse_access(const char* path, int mode) +{ + Inode* dir = fat_fuse_parent_dir(&path); + if ( !dir ) + return -errno; + Inode* result = dir->Open(path, O_RDONLY, 0); + dir->Unref(); + if ( !result ) + return -errno; + (void) mode; + result->Unref(); + return 0; +} + +int fat_fuse_create(const char* path, mode_t mode, struct fuse_file_info* fi) +{ + int flags = fi->flags | O_CREAT; + Inode* inode = fat_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + Inode* result = inode->Open(path, flags, ExtModeFromHostMode(mode)); + inode->Unref(); + if ( !result ) + return -errno; + fi->fh = (uint64_t) result->inode_id; + fi->keep_cache = 1; + result->RemoteRefer(); + result->Unref(); + return 0; +} + +int fat_fuse_opendir(const char* path, struct fuse_file_info* fi) +{ + return fat_fuse_open(path, fi); +} + +int fat_fuse_read(const char* /*path*/, char* buf, size_t count, off_t offset, + struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + if ( INT_MAX < count ) + count = INT_MAX; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + ssize_t result = inode->ReadAt((uint8_t*) buf, count, offset); + inode->Unref(); + return 0 <= result ? (int) result : -errno; +} + +int fat_fuse_write(const char* /*path*/, const char* buf, size_t count, + off_t offset, struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + if ( INT_MAX < count ) + count = INT_MAX; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + ssize_t result = inode->WriteAt((const uint8_t*) buf, count, offset); + inode->Unref(); + return 0 <= result ? (int) result : -errno; +} + +int fat_fuse_statfs(const char* /*path*/, struct statvfs* stvfs) +{ + memset(stvfs, 0, sizeof(*stvfs)); + Filesystem* fs = FUSE_FS; + stvfs->f_bsize = fs->block_size; + stvfs->f_frsize = fs->block_size; + stvfs->f_blocks = fs->num_blocks; + stvfs->f_bfree = fs->sb->s_free_blocks_count; + stvfs->f_bavail = fs->sb->s_free_blocks_count; + stvfs->f_files = fs->num_inodes; + stvfs->f_ffree = fs->sb->s_free_inodes_count; + stvfs->f_favail = fs->sb->s_free_inodes_count; + stvfs->f_ffree = fs->sb->s_free_inodes_count; + stvfs->f_fsid = 0; + stvfs->f_flag = 0; + if ( !fs->device->write ) + stvfs->f_flag |= ST_RDONLY; + stvfs->f_namemax = 255; + return 0; +} + +int fat_fuse_flush(const char* /*path*/, struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + inode->Sync(); + inode->Unref(); + return 0; +} + +int fat_fuse_release(const char* /*path*/, struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + inode->RemoteUnref(); + inode->Unref(); + return 0; +} + +int fat_fuse_releasedir(const char* path, struct fuse_file_info* fi) +{ + return fat_fuse_release(path, fi); +} + +int fat_fuse_fsync(const char* /*path*/, int data, struct fuse_file_info* fi) +{ + (void) data; + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + inode->Sync(); + inode->Unref(); + return 0; +} + +/*int fat_fuse_syncdir(const char* path, int data, struct fuse_file_info* fi) +{ + return fat_fuse_sync(path, data, fi); +}*/ + +/*int fat_fuse_setxattr(const char *, const char *, const char *, size_t, int) +{ + return -(errno = ENOSYS); +}*/ + +/*int fat_fuse_getxattr(const char *, const char *, char *, size_t) +{ + return -(errno = ENOSYS); +}*/ + +/*int fat_fuse_listxattr(const char *, char *, size_t) +{ + return -(errno = ENOSYS); +}*/ + +/*int fat_fuse_removexattr(const char *, const char *) +{ + return -(errno = ENOSYS); +}*/ + +int fat_fuse_readdir(const char* /*path*/, void* buf, fuse_fill_dir_t filler, + off_t rec_num, struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + if ( !S_ISDIR(inode->Mode()) ) + return inode->Unref(), -(errno = ENOTDIR); + + uint64_t file_size = inode->Size(); + uint64_t offset = 0; + Block* block = NULL; + uint64_t block_id = 0; + while ( offset < file_size ) + { + uint64_t entry_block_id = offset / fs->block_size; + uint64_t entry_block_offset = offset % fs->block_size; + if ( block && block_id != entry_block_id ) + block->Unref(), + block = NULL; + if ( !block && !(block = inode->GetBlock(block_id = entry_block_id)) ) + return inode->Unref(), -errno; + const uint8_t* block_data = block->block_data + entry_block_offset; + const struct ext_dirent* entry = (const struct ext_dirent*) block_data; + if ( entry->inode && entry->name_len && (!rec_num || !rec_num--) ) + { + char* entry_name = strndup(entry->name, entry->name_len); + if ( !entry_name ) + return block->Unref(), inode->Unref(), -errno; + memcpy(entry_name, entry->name, entry->name_len); + bool full = filler(buf, entry_name, NULL, 0); + free(entry_name); + if ( full ) + { + block->Unref(); + inode->Unref(); + return 0; + } + } + offset += entry->reclen; + } + if ( block ) + block->Unref(); + + inode->Unref(); + return 0; +} + +/*int fat_fuse_lock(const char*, struct fuse_file_info*, int, struct flock*) +{ + return -(errno = ENOSYS); +}*/ + +int fat_fuse_utimens(const char* path, const struct timespec tv[2]) +{ + Inode* inode = fat_fuse_resolve_path(path); + if ( !inode ) + return -errno; + if ( !FUSE_FS->device->write ) + return inode->Unref(), -(errno = EROFS); + inode->BeginWrite(); + inode->data->i_atime = tv[0].tv_sec; + inode->data->i_mtime = tv[1].tv_sec; + inode->FinishWrite(); + inode->Unref(); + return 0; +} + +/*int fat_fuse_bmap(const char*, size_t blocksize, uint64_t* idx) +{ + return -(errno = ENOSYS); +}*/ + +int fat_fuse_main(const char* argv0, + const char* mount_path, + bool foreground, + Filesystem* fs, + Device* dev) +{ + struct fuse_operations operations; + memset(&operations, 0, sizeof(operations)); + + operations.access = fat_fuse_access; + operations.chmod = fat_fuse_chmod; + operations.chown = fat_fuse_chown; + operations.create = fat_fuse_create; + operations.destroy = fat_fuse_destroy; + operations.fgetattr = fat_fuse_fgetattr; + operations.flush = fat_fuse_flush; + operations.fsync = fat_fuse_fsync; + operations.ftruncate = fat_fuse_ftruncate; + operations.getattr = fat_fuse_getattr; + operations.init = fat_fuse_init; + operations.link = fat_fuse_link; + operations.mkdir = fat_fuse_mkdir; + operations.mknod = fat_fuse_mknod; + operations.opendir = fat_fuse_opendir; + operations.open = fat_fuse_open; + operations.readdir = fat_fuse_readdir; + operations.read = fat_fuse_read; + operations.readlink = fat_fuse_readlink; + operations.releasedir = fat_fuse_releasedir; + operations.release = fat_fuse_release; + operations.rename = fat_fuse_rename; + operations.rmdir = fat_fuse_rmdir; + operations.statfs = fat_fuse_statfs; + operations.symlink = fat_fuse_symlink; + operations.truncate = fat_fuse_truncate; + operations.unlink = fat_fuse_unlink; + operations.utimens = fat_fuse_utimens; + operations.write = fat_fuse_write; + + operations.flag_nullpath_ok = 1; + operations.flag_nopath = 1; + + char* argv_fuse[] = + { + (char*) argv0, + (char*) "-s", + (char*) mount_path, + (char*) NULL, + }; + + int argc_fuse = sizeof(argv_fuse) / sizeof(argv_fuse[0]) - 1; + + struct fat_fuse_ctx fat_fuse_ctx; + fat_fuse_ctx.fs = fs; + fat_fuse_ctx.dev = dev; + + (void) foreground; + + return fuse_main(argc_fuse, argv_fuse, &operations, &fat_fuse_ctx); +} + +#endif diff --git a/fat/fuse.h b/fat/fuse.h new file mode 100644 index 00000000..1ba00d80 --- /dev/null +++ b/fat/fuse.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2015 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. + * + * fuse.h + * FUSE frontend. + */ + +#ifndef FUSE_H +#define FUSE_H + +class Device; +class Filesystem; + +int fat_fuse_main(const char* argv0, + const char* mount_path, + bool foreground, + Filesystem* fs, + Device* dev); + +#endif diff --git a/fat/inode.cpp b/fat/inode.cpp new file mode 100644 index 00000000..9057ffd8 --- /dev/null +++ b/fat/inode.cpp @@ -0,0 +1,1299 @@ +/* + * Copyright (c) 2013, 2014, 2015, 2018, 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. + * + * inode.cpp + * Filesystem inode. + */ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ext-constants.h" +#include "ext-structs.h" +#include "fat.h" + +#include "block.h" +#include "blockgroup.h" +#include "device.h" +#include "fatfs.h" +#include "filesystem.h" +#include "inode.h" +#include "util.h" + +#ifndef S_SETABLE +#define S_SETABLE 02777 +#endif +#ifndef O_WRITE +#define O_WRITE (O_WRONLY | O_RDWR) +#endif + +Inode::Inode(Filesystem* filesystem, uint32_t inode_id) +{ + this->prev_inode = NULL; + this->next_inode = NULL; + this->prev_hashed = NULL; + this->next_hashed = NULL; + this->prev_dirty = NULL; + this->next_dirty = NULL; + this->data_block = NULL; + this->data = NULL; + this->filesystem = filesystem; + this->reference_count = 1; + this->remote_reference_count = 0; + this->inode_id = inode_id; + this->dirty = false; +} + +Inode::~Inode() +{ + Sync(); + if ( data_block ) + data_block->Unref(); + Unlink(); +} + +uint32_t Inode::Mode() +{ + if ( inode_id == filesystem->root_inode_id ) + return filesystem->mode_dir; + mode_t mode = dirent->attributes & FAT_ATTRIBUTE_DIRECTORY ? + filesystem->mode_dir : filesystem->mode_reg; + if ( dirent->attributes & FAT_ATTRIBUTE_READ_ONLY ) + mode &= ~0222; + return mode; +} + +void Inode::SetMode(uint32_t mode) +{ + (void) mode; + assert(filesystem->device->write); + assert(false); // TODO: Unsupported. +} + +uint32_t Inode::UserId() +{ + return filesystem->uid; +} + +void Inode::SetUserId(uint32_t user) +{ + (void) user; + assert(filesystem->device->write); + assert(false); // TODO: Unsupported. +} + +uint32_t Inode::GroupId() +{ + return filesystem->gid; +} + +void Inode::SetGroupId(uint32_t group) +{ + (void) group; + assert(filesystem->device->write); + assert(false); // TODO: Unsupported. +} + +uint64_t Inode::Size() +{ + if ( inode_id == filesystem->root_inode_id ) + return 0; + if ( dirent->attributes & FAT_ATTRIBUTE_DIRECTORY ) + return 0; + return dirent->size; +} + +void Inode::SetSize(uint64_t new_size) +{ + assert(filesystem->device->write); + assert(false); // TODO: Not implemented. + bool largefile = filesystem->sb->s_feature_ro_compat & + FAT_FEATURE_RO_COMPAT_LARGE_FILE; + uint32_t lower = new_size & 0xFFFFFFFF; + uint32_t upper = new_size >> 32; + // TODO: Enforce filesize limit with no largefile support or non-files. + data->i_size = lower; + + // TODO: Figure out these i_blocks semantics and how stuff is reserved. + const uint64_t ENTRIES = filesystem->block_size / sizeof(uint32_t); + uint64_t block_direct = sizeof(data->i_block) / sizeof(uint32_t) - 3; + uint64_t block_singly = ENTRIES; + uint64_t block_doubly = block_singly * block_singly; + uint64_t max_direct = block_direct; + uint64_t max_singly = max_direct + block_singly; + uint64_t max_doubly = max_singly + block_doubly; + uint64_t logical_blocks = divup(new_size, (uint64_t) filesystem->block_size); + uint64_t actual_blocks = logical_blocks; + if ( max_direct <= logical_blocks ) + actual_blocks += divup(logical_blocks - max_direct, ENTRIES); + if ( max_singly <= logical_blocks ) + actual_blocks += divup(logical_blocks - max_singly, ENTRIES * ENTRIES); + if ( max_doubly <= logical_blocks ) + actual_blocks += divup(logical_blocks - max_doubly, ENTRIES * ENTRIES * ENTRIES); + + BeginWrite(); + data->i_blocks = (actual_blocks * filesystem->block_size) / 512; + if ( FAT_S_ISREG(data->i_mode) && largefile ) + data->i_dir_acl = upper; + FinishWrite(); + + Modified(); +} + +Block* Inode::GetBlockFromTable(Block* table, uint32_t index) +{ + if ( uint32_t block_id = ((uint32_t*) table->block_data)[index] ) + return filesystem->device->GetBlock(block_id); + // TODO: If in read only mode, then perhaps return a zero block here. + if ( !filesystem->device->write ) + return NULL; + uint32_t group_id = (inode_id - 1) / filesystem->sb->s_inodes_per_group; + assert(group_id < filesystem->num_groups); + BlockGroup* block_group = filesystem->GetBlockGroup(group_id); + uint32_t block_id = filesystem->AllocateBlock(block_group); + block_group->Unref(); + if ( block_id ) + { + Block* block = filesystem->device->GetBlockZeroed(block_id); + table->BeginWrite(); + ((uint32_t*) table->block_data)[index] = block_id; + table->FinishWrite(); + return block; + } + return NULL; +} + +Block* Inode::GetBlock(uint64_t offset) +{ + const uint64_t ENTRIES = filesystem->block_size / sizeof(uint32_t); + uint64_t block_direct = sizeof(data->i_block) / sizeof(uint32_t) - 3; + uint64_t block_singly = ENTRIES; + uint64_t block_doubly = block_singly * block_singly; + uint64_t block_triply = block_doubly * block_singly; + uint64_t max_direct = block_direct; + uint64_t max_singly = max_direct + block_singly; + uint64_t max_doubly = max_singly + block_doubly; + uint64_t max_triply = max_doubly + block_triply; + uint32_t index; + + Block* table = data_block; table->Refer(); + Block* block; + + uint32_t inode_offset = (uintptr_t) data - (uintptr_t) data_block->block_data; + uint32_t table_offset = offsetof(struct ext_inode, i_block); + uint32_t inode_block_table_offset = (inode_offset + table_offset) / sizeof(uint32_t); + + // TODO: It would seem that somebody needs a lesson in induction. :-) + if ( offset < max_direct ) + { + offset -= 0; + offset += inode_block_table_offset * 1; + read_direct: + index = offset; + offset %= 1; + block = GetBlockFromTable(table, index); + table->Unref(); + if ( !block ) + return NULL; + return block; + } + else if ( offset < max_singly ) + { + offset -= max_direct; + offset += (inode_block_table_offset + 12) * ENTRIES; + read_singly: + index = offset / ENTRIES; + offset = offset % ENTRIES; + block = GetBlockFromTable(table, index); + table->Unref(); + if ( !block ) + return NULL; + table = block; + goto read_direct; + } + else if ( offset < max_doubly ) + { + offset -= max_singly; + offset += (inode_block_table_offset + 13) * ENTRIES * ENTRIES; + read_doubly: + index = offset / (ENTRIES * ENTRIES); + offset = offset % (ENTRIES * ENTRIES); + block = GetBlockFromTable(table, index); + table->Unref(); + if ( !block ) + return NULL; + table = block; + goto read_singly; + } + else if ( offset < max_triply ) + { + offset -= max_doubly; + offset += (inode_block_table_offset + 14) * ENTRIES * ENTRIES * ENTRIES; + /*read_triply:*/ + index = offset / (ENTRIES * ENTRIES * ENTRIES); + offset = offset % (ENTRIES * ENTRIES * ENTRIES); + block = GetBlockFromTable(table, index); + table->Unref(); + if ( !block ) + return NULL; + table = block; + goto read_doubly; + } + + table->Unref(); + return NULL; +} + +Block* Inode::GetClusterSector(uint32_t cluster, uint8_t sector) +{ + uint32_t block_id; + if ( inode_id == filesystem->root_inode_id && filesystem->fat_type != 32 ) + block_id = filesystem->root_sector + cluster; + else + block_id = filesystem->data_sector + + (cluster - 2) * filesystem->bpb->sectors_per_cluster + + sector; + return filesystem->device->GetBlock(block_id); +} + +// TODO: Yes. Do review this function carefully. +bool Inode::Iterate(Block** block_ptr, uint32_t* cluster_ptr, + uint8_t* sector_ptr, uint16_t* offset_ptr) +{ + // TODO: Restructure to cache this. + if ( *block_ptr ) + { + (*block_ptr)->Unref(); + *block_ptr = NULL; + } + if ( *offset_ptr == filesystem->bytes_per_sector ) + { + *offset_ptr = 0; + if ( inode_id == filesystem->root_inode_id && + filesystem->fat_type != 32 ) + { + // TODO: This kinda assumes the root directory is sector sized. + uint32_t end = filesystem->root_dirent_count * + sizeof(struct fat_dirent); + uint32_t end_lba = end / filesystem->bytes_per_sector; + if ( end_lba <= *cluster_ptr ) + return errno = 0, false; + (*cluster_ptr)++; + } + else + { + (*sector_ptr)++; + if ( *sector_ptr == filesystem->bpb->sectors_per_cluster ) + { + *sector_ptr = 0; + *cluster_ptr = filesystem->ReadFAT(*cluster_ptr); + } + } + } + if ( inode_id != filesystem->root_inode_id || filesystem->fat_type == 32 ) + { + if ( *cluster_ptr < 2 ) + return errno = EIO, false; + if ( filesystem->eof_cluster <= *cluster_ptr ) + return errno = 0, false; + if ( filesystem->eio_cluster <= *cluster_ptr ) + return errno = EIO, false; + } + if ( !(*block_ptr = GetClusterSector(*cluster_ptr, *sector_ptr)) ) + return false; + return errno = 0, true; +} + +uint32_t Inode::SeekCluster(uint32_t cluster_id) +{ + // TODO: Cache. + uint32_t cluster = first_cluster; + while ( cluster_id-- ) + { + cluster = filesystem->ReadFAT(cluster); + if ( cluster < 2 ) + return errno = EIO, filesystem->eio_cluster; + if ( filesystem->eof_cluster <= cluster ) + return errno = EIO, filesystem->eio_cluster; + } + return cluster; +} + +bool Inode::FreeIndirect(uint64_t from, uint64_t offset, uint32_t block_id, + int indirection, uint64_t entry_span) +{ + assert(filesystem->device->write); + const uint64_t ENTRIES = filesystem->block_size / sizeof(uint32_t); + Block* block = filesystem->device->GetBlock(block_id); + uint32_t* table = (uint32_t*) block->block_data; + bool any_children = false; + bool begun_writing = false; + for ( uint64_t i = 0; i < ENTRIES; i++ ) + { + if ( !table[i] ) + continue; + uint64_t entry_offset = offset + entry_span * i; + if ( entry_offset < from || + (indirection && + FreeIndirect(from, entry_offset, table[i], indirection-1, + entry_span / ENTRIES)) ) + { + any_children = true; + continue; + } + filesystem->FreeBlock(table[i]); + if ( !begun_writing ) + { + block->BeginWrite(); + begun_writing = true; + } + table[i] = 0; + } + if ( begun_writing ) + block->FinishWrite(); + return any_children; +} + +bool Inode::Truncate(uint64_t new_size_64) +{ + assert(filesystem->device->write); + assert(S_ISREG(Mode())); + uint32_t new_size = (uint32_t) new_size_64; + if ( new_size_64 != new_size ) + return errno = E2BIG, false; + uint32_t old_size = dirent->size; + uint32_t pos = old_size < new_size ? old_size : new_size; + uint32_t bytes_per_sector = filesystem->bytes_per_sector; + uint32_t cluster_id = pos / filesystem->cluster_size; + uint32_t cluster_offset = pos % filesystem->cluster_size; + if ( cluster_id && !cluster_offset ) + { + cluster_id--; + cluster_offset = filesystem->cluster_size; + } + uint32_t cluster = SeekCluster(cluster_id); + if ( cluster_id == filesystem->eio_cluster ) + return errno = EIO, false; + if ( old_size < new_size ) + { + while ( old_size < new_size ) + { + if ( cluster_offset == filesystem->cluster_size ) + { + // TODO: Zero the new sectors since the old contents may leak + // if we were to implement mmap. + uint32_t next_cluster = filesystem->AllocateCluster(); + if ( !next_cluster ) + return false; + filesystem->WriteFAT(next_cluster, filesystem->eof_cluster); + filesystem->WriteFAT(cluster, next_cluster); + cluster_offset = 0; + cluster = next_cluster; + } + uint8_t sector = cluster_offset / bytes_per_sector; + uint16_t sector_offset = cluster_offset % bytes_per_sector; + Block* block = GetClusterSector(cluster, sector); + if ( !block ) + return false; + size_t left = new_size - old_size; + size_t available = bytes_per_sector - sector_offset; + size_t amount = left < available ? left : available; + block->BeginWrite(); + memset(block->block_data + sector_offset, 0, amount); + block->FinishWrite(); + old_size += amount; + cluster_offset += amount; + block->Unref(); + } + } + else if ( new_size < old_size ) + { + uint32_t marker = filesystem->eof_cluster; + while ( true ) + { + uint32_t next_cluster = filesystem->ReadFAT(cluster); + if ( next_cluster < 2 || filesystem->eio_cluster == next_cluster ) + return errno = EIO, false; + if ( next_cluster != marker ) + { + filesystem->WriteFAT(cluster, marker); + filesystem->FreeCluster(next_cluster); + } + if ( filesystem->eof_cluster <= next_cluster ) + break; + cluster = next_cluster; + cluster_id++; + marker = 0; + } + } + else + return true; + data_block->BeginWrite(); + dirent->size = new_size; + data_block->FinishWrite(); + return true; +} + +Inode* Inode::Open(const char* elem, int flags, mode_t mode) +{ + if ( !S_ISDIR(Mode()) ) + return errno = ENOTDIR, (Inode*) NULL; + size_t elem_length = strlen(elem); + if ( elem_length == 0 ) + return errno = ENOENT, (Inode*) NULL; + if ( inode_id == filesystem->root_inode_id ) + { + if ( !strcmp(elem, ".") || !strcmp(elem, "..") ) + { + if ( (flags & O_CREAT) && (flags & O_EXCL) ) + return errno = EEXIST, (Inode*) NULL; + if ( flags & O_WRITE && !filesystem->device->write ) + return errno = EROFS, (Inode*) NULL; + Refer(); + return this; + // TODO: Reopen the same inode. + } + } + uint32_t cluster = first_cluster; + uint8_t sector = 0; + uint16_t offset = 0; + Block* block = NULL; + bool found_free = false; + uint32_t free_cluster = 0; + uint8_t free_sector = 0; + uint16_t free_offset = 0; + uint32_t last_cluster = 0; + while ( Iterate(&block, &cluster, §or, &offset) ) + { + last_cluster = cluster; + uint8_t* block_data = block->block_data + offset; + struct fat_dirent* entry = (struct fat_dirent*) block_data; + if ( !found_free && + (!entry->name[0] || (unsigned char) entry->name[0] == 0xE5) ) + { + found_free = true; + free_cluster = cluster; + free_sector = sector; + free_offset = offset; + } + if ( !entry->name[0] ) + break; + char name[8 + 1 + 3 + 1]; + if ( (unsigned char) entry->name[0] != 0xE5 && + !(entry->attributes & FAT_ATTRIBUTE_VOLUME_ID) && + (decode_8_3(entry->name, name), !strcmp(elem, name)) ) + { + // TODO: Opening .. ends up with EIO failures. + if ( (flags & O_CREAT) && (flags & O_EXCL) ) + return block->Unref(), errno = EEXIST, (Inode*) NULL; + if ( (flags & O_DIRECTORY) && + !(entry->attributes & FAT_ATTRIBUTE_DIRECTORY) ) + return block->Unref(), errno = ENOTDIR, (Inode*) NULL; + uint32_t inode_id = entry->cluster_low | entry->cluster_high << 16; + // TODO: If the inode is a directory, keep a reference open to this + // parent directory so it can find the .. path back and keep track + // of where the directory records with metadata is. + Inode* inode = filesystem->GetInode(inode_id, block, entry); + block->Unref(); + if ( !inode ) + return (Inode*) NULL; + if ( flags & O_WRITE && !filesystem->device->write ) + return inode->Unref(), errno = EROFS, (Inode*) NULL; + if ( S_ISREG(inode->Mode()) && (flags & O_WRITE) && + (flags & O_TRUNC) && !inode->Truncate(0) ) + return (Inode*) NULL; + return inode; + } + offset += sizeof(struct fat_dirent); + } + if ( block ) + block->Unref(); + if ( errno ) + return (Inode*) NULL; + if ( flags & O_CREAT ) + { + if ( !filesystem->device->write ) + return errno = EROFS, (Inode*) NULL; + // TODO: Protect against . and .. + if ( !is_8_3(elem) ) + return errno = ENAMETOOLONG, (Inode*) NULL; + // TODO: Root directory support. + if ( inode_id == filesystem->root_inode_id && + filesystem->fat_type != 32 ) + return errno = ENOTSUP, (Inode*) NULL; + if ( !found_free ) + { + uint32_t new_cluster = filesystem->AllocateCluster(); + if ( !new_cluster ) + return (Inode*) NULL; + for ( size_t i = 0; i < filesystem->bpb->sectors_per_cluster; i++ ) + { + Block* block = GetClusterSector(new_cluster, i); + if ( !block ) + { + filesystem->FreeCluster(new_cluster); + return (Inode*) NULL; + } + block->BeginWrite(); + memset(block->block_data, 0, filesystem->bytes_per_sector); + block->FinishWrite(); + block->Unref(); + } + filesystem->WriteFAT(last_cluster, new_cluster); + found_free = true; + free_cluster = new_cluster; + free_sector = 0; + free_offset = 0; + } + // TODO: Avoid shadowing with the class member. + uint32_t inode_id = filesystem->AllocateCluster(); + // TODO: Actually zero this cluster entirely. + if ( !inode_id ) + return (Inode*) NULL; + mode_t attributes = mode & 0200 ? 0 : FAT_ATTRIBUTE_READ_ONLY; + if ( S_ISDIR(mode) ) + { + attributes |= FAT_ATTRIBUTE_DIRECTORY; + Block* block = GetClusterSector(inode_id, 0); + if ( !block ) + return filesystem->FreeCluster(inode_id), (Inode*) NULL; + block->BeginWrite(); + memset(block->block_data, 0, filesystem->bytes_per_sector); + struct fat_dirent* dirent = (struct fat_dirent*) block->block_data; + // TODO: Mirror modified times in here. + memcpy(dirent->name, ".", 1); + dirent->attributes = attributes; + dirent->cluster_high = htole16(inode_id >> 16); + dirent->cluster_low = htole16(inode_id & 0xFFFF); + dirent++; + memcpy(dirent->name, "..", 2); + dirent->attributes = FAT_ATTRIBUTE_DIRECTORY; + if ( this->inode_id == filesystem->root_inode_id ) + { + dirent->cluster_high = htole16(0); + dirent->cluster_low = htole16(0); + } + else + { + dirent->cluster_high = htole16(this->inode_id >> 16); + dirent->cluster_low = htole16(this->inode_id & 0xFFFF); + } + block->FinishWrite(); + block->Unref(); + } + Block* block = GetClusterSector(free_cluster, free_sector); + if ( !block ) + return filesystem->FreeCluster(inode_id), (Inode*) NULL; + filesystem->WriteFAT(inode_id, filesystem->eof_cluster); + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + block->BeginWrite(); + struct fat_dirent* dirent = + (struct fat_dirent*) (block->block_data + free_offset); + encode_8_3(elem, dirent->name); + dirent->attributes = attributes; + dirent->creation_tenths = timespec_to_fat_tenths(&now); + dirent->creation_time = htole16(timespec_to_fat_time(&now)); + dirent->creation_date = htole16(timespec_to_fat_date(&now)); + dirent->access_date = dirent->creation_date; + dirent->cluster_high = htole16(inode_id >> 16); + dirent->modified_time = dirent->creation_time; + dirent->modified_date = dirent->creation_date; + dirent->cluster_low = htole16(inode_id & 0xFFFF); + dirent->size = htole16(0); + block->FinishWrite(); + Inode* inode = filesystem->GetInode(inode_id, block, dirent); + block->Unref(); + return inode; + } + return errno = ENOENT, (Inode*) NULL; +} + +bool Inode::Link(const char* elem, Inode* dest, bool directories) +{ + if ( !FAT_S_ISDIR(Mode()) ) + return errno = ENOTDIR, false; + if ( directories && !FAT_S_ISDIR(dest->Mode()) ) + return errno = ENOTDIR, false; + if ( !directories && FAT_S_ISDIR(dest->Mode()) ) + return errno = EISDIR, false; + + // Search for a hole in which we can store the new directory entry and stop + // if we meet an existing link with the requested name. + size_t elem_length = strlen(elem); + if ( elem_length == 0 ) + return errno = ENOENT, false; + size_t new_entry_size = roundup(sizeof(struct ext_dirent) + elem_length, (size_t) 4); + uint64_t filesize = Size(); + uint64_t offset = 0; + Block* block = NULL; + uint64_t block_id = 0; + bool found_hole = false; + bool splitting = false; + uint64_t hole_block_id = 0; + uint64_t hole_block_offset = 0; + while ( offset < filesize ) + { + uint64_t entry_block_id = offset / filesystem->block_size; + uint64_t entry_block_offset = offset % filesystem->block_size; + if ( block && block_id != entry_block_id ) + block->Unref(), + block = NULL; + if ( !block && !(block = GetBlock(block_id = entry_block_id)) ) + return NULL; + const uint8_t* block_data = block->block_data + entry_block_offset; + const struct ext_dirent* entry = (const struct ext_dirent*) block_data; + if ( entry->name_len == elem_length && + memcmp(elem, entry->name, elem_length) == 0 ) + { + block->Unref(); + return errno = EEXIST, false; + } + if ( !found_hole ) + { + size_t entry_size = roundup(sizeof(struct ext_dirent) + entry->name_len, (size_t) 4); + if ( (!entry->inode || !entry->name[0]) && new_entry_size <= entry->reclen ) + { + hole_block_id = entry_block_id; + hole_block_offset = entry_block_offset; + new_entry_size = entry->reclen; + found_hole = true; + } + else if ( new_entry_size <= entry->reclen - entry_size ) + { + hole_block_id = entry_block_id; + hole_block_offset = entry_block_offset; + new_entry_size = entry->reclen - entry_size; + splitting = true; + found_hole = true; + } + } + offset += entry->reclen; + } + + if ( !filesystem->device->write ) + return errno = EROFS, false; + + if ( UINT16_MAX <= dest->data->i_links_count ) + return errno = EMLINK, false; + + if ( 255 < elem_length ) + return errno = ENAMETOOLONG, false; + + // We'll append another block if we failed to find a suitable hole. + if ( !found_hole ) + { + hole_block_id = filesize / filesystem->block_size; + hole_block_offset = filesize % filesystem->block_size; + new_entry_size = filesystem->block_size; + } + + if ( block && block_id != hole_block_id ) + block->Unref(), + block = NULL; + if ( !block && !(block = GetBlock(block_id = hole_block_id)) ) + return NULL; + + Modified(); + + block->BeginWrite(); + + uint8_t* block_data = block->block_data + hole_block_offset; + struct ext_dirent* entry = (struct ext_dirent*) block_data; + + // If we found a directory entry with room at the end, split it! + if ( splitting ) + { + entry->reclen = roundup(sizeof(struct ext_dirent) + entry->name_len, (size_t) 4); + assert(entry->reclen); + block_data += entry->reclen; + entry = (struct ext_dirent*) block_data; + } + + // Write the new directory entry. + entry->inode = dest->inode_id; + entry->reclen = new_entry_size; + entry->name_len = elem_length; + if ( filesystem->sb->s_feature_incompat & FAT_FEATURE_INCOMPAT_FILETYPE ) + entry->file_type = FAT_FT_OF_MODE(dest->Mode()); + else + entry->file_type = FAT_FT_UNKNOWN; + strncpy(entry->name, elem, new_entry_size - sizeof(struct ext_dirent)); + + block->FinishWrite(); + block->Unref(); + + dest->BeginWrite(); + dest->data->i_links_count++; + dest->FinishWrite(); + + if ( !found_hole ) + SetSize(Size() + filesystem->block_size); + + return true; +} + +Inode* Inode::UnlinkKeep(const char* elem, bool directories, bool force) +{ + if ( !FAT_S_ISDIR(Mode()) ) + return errno = ENOTDIR, (Inode*) NULL; + size_t elem_length = strlen(elem); + if ( elem_length == 0 ) + return errno = ENOENT, (Inode*) NULL; + uint32_t block_size = filesystem->block_size; + uint64_t filesize = Size(); + uint64_t num_blocks = divup(filesize, (uint64_t) block_size); + uint64_t offset = 0; + Block* block = NULL; + uint64_t block_id = 0; + struct ext_dirent* last_entry = NULL; + while ( offset < filesize ) + { + uint64_t entry_block_id = offset / block_size; + uint64_t entry_block_offset = offset % block_size; + if ( block && block_id != entry_block_id ) + last_entry = NULL, + block->Unref(), + block = NULL; + if ( !block && !(block = GetBlock(block_id = entry_block_id)) ) + return NULL; + uint8_t* block_data = block->block_data + entry_block_offset; + struct ext_dirent* entry = (struct ext_dirent*) block_data; + if ( entry->inode && + entry->name_len == elem_length && + memcmp(elem, entry->name, elem_length) == 0 ) + { + Inode* inode = filesystem->GetInode(entry->inode); + + if ( !force && directories && !FAT_S_ISDIR(inode->Mode()) ) + { + inode->Unref(); + block->Unref(); + return errno = ENOTDIR, (Inode*) NULL; + } + + if ( !force && directories && !inode->IsEmptyDirectory() ) + { + inode->Unref(); + block->Unref(); + return errno = ENOTEMPTY, (Inode*) NULL; + } + + if ( !force && !directories && FAT_S_ISDIR(inode->Mode()) ) + { + inode->Unref(); + block->Unref(); + return errno = EISDIR, (Inode*) NULL; + } + + if ( !filesystem->device->write ) + { + inode->Unref(); + block->Unref(); + return errno = EROFS, (Inode*) NULL; + } + + Modified(); + + inode->BeginWrite(); + inode->data->i_links_count--; + inode->FinishWrite(); + + block->BeginWrite(); + + entry->inode = 0; + entry->name_len = 0; + entry->file_type = 0; + + // Merge the current entry with the previous if any. + if ( last_entry ) + { + last_entry->reclen += entry->reclen; + memset(entry, 0, entry->reclen); + entry = last_entry; + } + + strncpy(entry->name + entry->name_len, "", + entry->reclen - sizeof(struct ext_dirent) - entry->name_len); + + // If the entire block is empty, we'll need to remove it. + if ( !entry->name[0] && entry->reclen == block_size ) + { + // If this is not the last block, we'll make it. This is faster + // than shifting the entire directory a single block. We don't + // actually copy this block to the end, since we'll truncate it + // regardless. + if ( entry_block_id + 1 != num_blocks ) + { + Block* last_block = GetBlock(num_blocks-1); + if ( last_block ) + { + memcpy(block->block_data, last_block->block_data, block_size); + last_block->Unref(); + Truncate(filesize - block_size); + } + } + else + { + Truncate(filesize - block_size); + } + } + + block->FinishWrite(); + + block->Unref(); + + return inode; + } + offset += entry->reclen; + last_entry = entry; + } + if ( block ) + block->Unref(); + return errno = ENOENT, (Inode*) NULL; +} + +bool Inode::Unlink(const char* elem, bool directories, bool force) +{ + Inode* result = UnlinkKeep(elem, directories, force); + if ( !result ) + return false; + result->Unref(); + return true; +} + +ssize_t Inode::ReadAt(uint8_t* buf, size_t s_count, off_t o_offset) +{ + if ( !FAT_S_ISREG(Mode()) ) + return errno = EISDIR, -1; + if ( o_offset < 0 ) + return errno = EINVAL, -1; + if ( SSIZE_MAX < s_count ) + s_count = SSIZE_MAX; + // TODO: Downgrade to 32-bit. + uint64_t sofar = 0; + uint64_t count = (uint64_t) s_count; + uint64_t offset = (uint64_t) o_offset; + uint32_t file_size = Size(); + if ( file_size <= offset ) + return 0; + if ( file_size - offset < count ) + count = file_size - offset; + if ( !count ) + return 0; + uint32_t cluster_id = offset / filesystem->cluster_size; + uint32_t cluster_offset = offset % filesystem->cluster_size; + uint32_t cluster = SeekCluster(cluster_id); + if ( filesystem->eio_cluster <= cluster ) + return -1; + while ( sofar < count ) + { + if ( filesystem->cluster_size <= cluster_offset ) + { + cluster = filesystem->ReadFAT(cluster); + // TODO: Better checks. + if ( filesystem->eio_cluster <= cluster ) + return sofar ? sofar : (errno = EIO, -1); + cluster_offset = 0; + cluster_id++; + } + uint8_t sector = cluster_offset / filesystem->bytes_per_sector; + uint16_t block_offset = cluster_offset % filesystem->bytes_per_sector; + uint32_t block_left = filesystem->bytes_per_sector - block_offset; + Block* block = GetClusterSector(cluster, sector); + if ( !block ) + return sofar ? sofar : -1; + size_t amount = count - sofar < block_left ? count - sofar : block_left; + memcpy(buf + sofar, block->block_data + block_offset, amount); + sofar += amount; + cluster_offset += amount; + block->Unref(); + } + return (ssize_t) sofar; +} + +ssize_t Inode::WriteAt(const uint8_t* buf, size_t s_count, off_t o_offset) +{ + if ( !FAT_S_ISREG(Mode()) ) + return errno = EISDIR, -1; + if ( o_offset < 0 ) + return errno = EINVAL, -1; + if ( !filesystem->device->write ) + return errno = EROFS, -1; + if ( SSIZE_MAX < s_count ) + s_count = SSIZE_MAX; + // TODO: Update modified time. + //Modified(); + // TODO: Downgrade to 32-bit. + uint64_t sofar = 0; + uint64_t count = (uint64_t) s_count; + uint64_t offset = (uint64_t) o_offset; + uint32_t file_size = Size(); + uint64_t end_at = offset + count; + if ( offset < end_at ) + /* TODO: Overflow! off_t overflow? */{}; + if ( file_size < end_at && !Truncate(end_at) ) + return -1; + uint32_t cluster_id = offset / filesystem->cluster_size; + uint32_t cluster_offset = offset % filesystem->cluster_size; + uint32_t cluster = SeekCluster(cluster_id); + if ( filesystem->eio_cluster <= cluster ) + return -1; + while ( sofar < count ) + { + if ( filesystem->cluster_size <= cluster_offset ) + { + cluster = filesystem->ReadFAT(cluster); + // TODO: Better checks. + if ( filesystem->eio_cluster <= cluster ) + return sofar ? sofar : (errno = EIO, -1); + cluster_offset = 0; + cluster_id++; + } + uint8_t sector = cluster_offset / filesystem->bytes_per_sector; + uint16_t block_offset = cluster_offset % filesystem->bytes_per_sector; + uint32_t block_left = filesystem->bytes_per_sector - block_offset; + Block* block = GetClusterSector(cluster, sector); + if ( !block ) + return sofar ? sofar : -1; + size_t amount = count - sofar < block_left ? count - sofar : block_left; + block->BeginWrite(); + memcpy(block->block_data + block_offset, buf + sofar, amount); + block->FinishWrite(); + sofar += amount; + cluster_offset += amount; + block->Unref(); + } + return (ssize_t) sofar; +} + +bool Inode::UnembedInInode() +{ + assert(data->i_blocks == 0 && 0 < data->i_size && data->i_size <= 60); + unsigned char* block_data = (unsigned char*) &data->i_block[0]; + size_t content_size = Size(); + unsigned char content[60]; + memcpy(content, block_data, content_size); + data_block->BeginWrite(); + memset(block_data, 0, 60); + data->i_size = 0; + data_block->FinishWrite(); + if ( WriteAt(content, content_size, 0) != (ssize_t) content_size ) + { + Truncate(0); + data_block->BeginWrite(); + memcpy(block_data, content, content_size); + data->i_size = content_size; + data->i_blocks = 0; + data_block->FinishWrite(); + return false; + } + return true; +} + +bool Inode::Rename(Inode* olddir, const char* oldname, const char* newname) +{ + if ( !strcmp(oldname, ".") || !strcmp(oldname, "..") || + !strcmp(newname, ".") || !strcmp(newname, "..") ) + return errno = EPERM, false; + Inode* src_inode = olddir->Open(oldname, O_RDONLY, 0); + if ( !src_inode ) + return false; + if ( Inode* dst_inode = Open(newname, O_RDONLY, 0) ) + { + bool same_inode = src_inode->inode_id == dst_inode->inode_id; + dst_inode->Unref(); + if ( same_inode ) + return src_inode->Unref(), true; + } + // TODO: Prove that this cannot fail and handle such a situation. + if ( FAT_S_ISDIR(src_inode->Mode()) ) + { + if ( !Unlink(newname, true) && errno != ENOENT ) + return src_inode->Unref(), false; + Link(newname, src_inode, true); + olddir->Unlink(oldname, true, true); + if ( olddir != this ) + { + src_inode->Unlink("..", true, true); + src_inode->Link("..", this, true); + } + } + else + { + if ( !Unlink(newname, false) && errno != ENOENT ) + return src_inode->Unref(), false; + Link(newname, src_inode, false); + olddir->Unlink(oldname, false); + } + + src_inode->Unref(); + return true; +} + +bool Inode::Symlink(const char* elem, const char* dest) +{ + if ( !filesystem->device->write ) + return errno = EROFS, false; + + // TODO: Preferred block group! + uint32_t result_inode_id = filesystem->AllocateInode(); + if ( !result_inode_id ) + return NULL; + + Inode* result = filesystem->GetInode(result_inode_id); + if ( !result ) + return filesystem->FreeInode(result_inode_id), false; + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + result->BeginWrite(); + memset(result->data, 0, sizeof(*result->data)); + result->data->i_atime = now.tv_sec; + result->data->i_ctime = now.tv_sec; + result->data->i_mtime = now.tv_sec; + // TODO: Set all the other inode properties! + result->FinishWrite(); + result->SetMode((0777 & S_SETABLE) | FAT_S_IFLNK); + result->SetUserId(request_uid); + result->SetGroupId(request_gid); + + size_t dest_length = strlen(dest); + if ( SSIZE_MAX < dest_length ) + return errno = EFBIG, -1; + if ( result->WriteAt((const uint8_t*) dest, dest_length, 0) < (ssize_t) dest_length ) + return result->Unref(), false; + + if ( !Link(elem, result, false) ) + return result->Truncate(0), result->Unref(), false; + + result->Unref(); + return true; +} + +Inode* Inode::CreateDirectory(const char* path, mode_t mode) +{ + return Open(path, O_CREAT | O_EXCL, mode | S_IFDIR); +} + +bool Inode::RemoveDirectory(const char* path) +{ + Inode* result = UnlinkKeep(path, true); + if ( !result ) + return false; + result->Unlink("..", true, true); + result->Unlink(".", true, true); + result->Truncate(0); + + // Decrease the directory count statistics. + uint32_t group_id = (result->inode_id - 1) / filesystem->sb->s_inodes_per_group; + assert(group_id < filesystem->num_groups); + BlockGroup* block_group = filesystem->GetBlockGroup(group_id); + if ( block_group ) + { + block_group->BeginWrite(); + block_group->data->bg_used_dirs_count--; + block_group->FinishWrite(); + block_group->Unref(); + } + + result->Unref(); + + return true; +} + +bool Inode::IsEmptyDirectory() +{ + if ( !FAT_S_ISDIR(Mode()) ) + return errno = ENOTDIR, false; + uint32_t block_size = filesystem->block_size; + uint64_t filesize = Size(); + uint64_t offset = 0; + Block* block = NULL; + uint64_t block_id = 0; + while ( offset < filesize ) + { + uint64_t entry_block_id = offset / block_size; + uint64_t entry_block_offset = offset % block_size; + if ( block && block_id != entry_block_id ) + block->Unref(), + block = NULL; + if ( !block && !(block = GetBlock(block_id = entry_block_id)) ) + return false; + uint8_t* block_data = block->block_data + entry_block_offset; + struct ext_dirent* entry = (struct ext_dirent*) block_data; + if ( entry->inode && + !((entry->name_len == 1 && entry->name[0] == '.') || + (entry->name_len == 2 && entry->name[0] == '.' && + entry->name[1] == '.' )) ) + { + block->Unref(); + return false; + } + offset += entry->reclen; + } + if ( block ) + block->Unref(); + return true; +} + +void Inode::Delete() +{ + assert(!data->i_links_count); + assert(!reference_count); + assert(!remote_reference_count); + + Truncate(0); + + uint32_t deleted_inode_id = inode_id; + + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + + BeginWrite(); + memset(data, 0, sizeof(*data)); + data->i_dtime = now.tv_sec; + FinishWrite(); + + filesystem->FreeInode(deleted_inode_id); +} + +void Inode::Refer() +{ + reference_count++; +} + +void Inode::Unref() +{ + assert(0 < reference_count); + reference_count--; + if ( !reference_count && !remote_reference_count ) + { + // TODO: Can data be NULL here? Should the other drives do this? + if ( data && !data->i_links_count ) + Delete(); + delete this; + } +} + +void Inode::RemoteRefer() +{ + remote_reference_count++; +} + +void Inode::RemoteUnref() +{ + assert(0 < remote_reference_count); + remote_reference_count--; + if ( !reference_count && !remote_reference_count ) + { + // TODO: Check if deleted. + //Delete(); + delete this; + } +} + +void Inode::Modified() +{ + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + BeginWrite(); + data->i_mtime = now.tv_sec; + FinishWrite(); +} + +void Inode::BeginWrite() +{ + data_block->BeginWrite(); +} + +void Inode::FinishWrite() +{ + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + data->i_ctime = now.tv_sec; + if ( !dirty ) + { + dirty = true; + prev_dirty = NULL; + next_dirty = filesystem->dirty_inode; + if ( next_dirty ) + next_dirty->prev_dirty = this; + filesystem->dirty_inode = this; + } + data_block->FinishWrite(); + Use(); +} + +void Inode::Sync() +{ + if ( !dirty ) + return; + data_block->Sync(); + // TODO: The inode contents needs to be sync'd as well! + (prev_dirty ? prev_dirty->next_dirty : filesystem->dirty_inode) = next_dirty; + if ( next_dirty ) + next_dirty->prev_dirty = prev_dirty; + prev_dirty = NULL; + next_dirty = NULL; + dirty = false; +} + +void Inode::Use() +{ + data_block->Use(); + Unlink(); + Prelink(); +} + +void Inode::Unlink() +{ + (prev_inode ? prev_inode->next_inode : filesystem->mru_inode) = next_inode; + (next_inode ? next_inode->prev_inode : filesystem->lru_inode) = prev_inode; + size_t bin = inode_id % INODE_HASH_LENGTH; + (prev_hashed ? prev_hashed->next_hashed : filesystem->hash_inodes[bin]) = next_hashed; + if ( next_hashed ) next_hashed->prev_hashed = prev_hashed; +} + +void Inode::Prelink() +{ + prev_inode = NULL; + next_inode = filesystem->mru_inode; + if ( filesystem->mru_inode ) + filesystem->mru_inode->prev_inode = this; + filesystem->mru_inode = this; + if ( !filesystem->lru_inode ) + filesystem->lru_inode = this; + size_t bin = inode_id % INODE_HASH_LENGTH; + prev_hashed = NULL; + next_hashed = filesystem->hash_inodes[bin]; + filesystem->hash_inodes[bin] = this; + if ( next_hashed ) + next_hashed->prev_hashed = this; +} diff --git a/fat/inode.h b/fat/inode.h new file mode 100644 index 00000000..e0d08580 --- /dev/null +++ b/fat/inode.h @@ -0,0 +1,94 @@ +/* + * 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. + * + * inode.h + * Filesystem inode. + */ + +#ifndef INODE_H +#define INODE_H + +class Block; +class Filesystem; + +class Inode +{ +public: + Inode(Filesystem* filesystem, uint32_t inode_id); + ~Inode(); + +public: + Inode* prev_inode; + Inode* next_inode; + Inode* prev_hashed; + Inode* next_hashed; + Inode* prev_dirty; + Inode* next_dirty; + Block* data_block; + struct fat_dirent* dirent; // TODO: Rename to data? + struct ext_inode* data; // TODO: Remove? + uint32_t first_cluster; + Filesystem* filesystem; + size_t reference_count; + size_t remote_reference_count; + uint32_t inode_id; + bool dirty; + +public: + uint32_t Mode(); + uint32_t UserId(); + uint32_t GroupId(); + uint64_t Size(); + void SetMode(uint32_t mode); + void SetUserId(uint32_t user); + void SetGroupId(uint32_t group); + void SetSize(uint64_t new_size); + bool Truncate(uint64_t new_size); + bool FreeIndirect(uint64_t from, uint64_t offset, uint32_t block_id, + int indirection, uint64_t entry_span); + Block* GetBlock(uint64_t offset); + Block* GetBlockFromTable(Block* table, uint32_t index); + Block* GetClusterSector(uint32_t cluster, uint8_t sector); + bool Iterate(Block** block_ptr, uint32_t* cluster_ptr, + uint8_t* sector_ptr, uint16_t* offset); + uint32_t SeekCluster(uint32_t cluster_id); + Inode* Open(const char* elem, int flags, mode_t mode); + bool Link(const char* elem, Inode* dest, bool directories); + bool Symlink(const char* elem, const char* dest); + bool Unlink(const char* elem, bool directories, bool force=false); + Inode* UnlinkKeep(const char* elem, bool directories, bool force=false); + ssize_t ReadAt(uint8_t* buffer, size_t count, off_t offset); + ssize_t WriteAt(const uint8_t* buffer, size_t count, off_t offset); + bool UnembedInInode(); + bool Rename(Inode* olddir, const char* oldname, const char* newname); + Inode* CreateDirectory(const char* path, mode_t mode); + bool RemoveDirectory(const char* path); + bool IsEmptyDirectory(); + void Refer(); + void Unref(); + void RemoteRefer(); + void RemoteUnref(); + void Sync(); + void BeginWrite(); + void FinishWrite(); + void Modified(); + void Use(); + void Unlink(); + void Prelink(); + void Delete(); + +}; + +#endif diff --git a/fat/ioleast.h b/fat/ioleast.h new file mode 100644 index 00000000..5648de48 --- /dev/null +++ b/fat/ioleast.h @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2012, 2013, 2015 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. + * + * ioleast.h + * Versions of {,p}{read,write} that don't return until it has returned as much + * data as requested, end of file, or an error occurs. This is sometimes needed + * as read(2) and write(2) is not always guaranteed to fill up the entire + * buffer or write it all. + */ + +#ifndef SORTIX_COMPATIBILITY_INCLUDE_IOLEAST_H +#define SORTIX_COMPATIBILITY_INCLUDE_IOLEAST_H + +#if defined(__sortix__) || defined(__sortix_libc__) + +#include_next + +#else + +#include + +#include +#include +#include +#include +#include + +#if !defined(EEOF) && defined(EIO) +#define EEOF EIO +#endif + +__attribute__((unused)) static inline +size_t readleast(int fd, void* buf_ptr, size_t least, size_t max) +{ + assert(least <= max); + unsigned char* buf = (unsigned char*) buf_ptr; + size_t done = 0; + do + { + ssize_t amount = read(fd, buf + done, max - done); + if ( amount < 0 ) + return done; + if ( !amount && done < least ) + return errno = EEOF, done; + if ( !amount ) + break; + done += amount; + } while ( done < least ); + return done; +} + +__attribute__((unused)) static inline +size_t writeleast(int fd, const void* buf_ptr, size_t least, size_t max) +{ + assert(least <= max); + const unsigned char* buf = (const unsigned char*) buf_ptr; + size_t done = 0; + do + { + ssize_t amount = write(fd, buf + done, max - done); + if ( amount < 0 ) + return done; + if ( !amount && done < least ) + return errno = EEOF, done; + if ( !amount ) + break; + done += amount; + } while ( done < least ); + return done; +} + +__attribute__((unused)) static inline +size_t preadleast(int fd, void* buf_ptr, size_t least, size_t max, off_t off) +{ + assert(least <= max); + unsigned char* buf = (unsigned char*) buf_ptr; + size_t done = 0; + do + { + ssize_t amount = pread(fd, buf + done, max - done, off + done); + if ( amount < 0 ) + return done; + if ( !amount && done < least ) + return errno = EEOF, done; + if ( !amount ) + break; + done += amount; + } while ( done < least ); + return done; +} + +__attribute__((unused)) static inline +size_t pwriteleast(int fd, const void* buf_ptr, size_t least, size_t max, off_t off) +{ + assert(least <= max); + const unsigned char* buf = (const unsigned char*) buf_ptr; + size_t done = 0; + do + { + ssize_t amount = pwrite(fd, buf + done, max - done, off + done); + if ( amount < 0 ) + return done; + if ( !amount && done < least ) + return errno = EEOF, done; + if ( !amount ) + break; + done += amount; + } while ( done < least ); + return done; +} + +__attribute__((unused)) static inline +size_t readall(int fd, void* buf, size_t count) +{ + return readleast(fd, buf, count, count); +} + +__attribute__((unused)) static inline +size_t writeall(int fd, const void* buf, size_t count) +{ + return writeleast(fd, buf, count, count); +} + +__attribute__((unused)) static inline +size_t preadall(int fd, void* buf, size_t count, off_t off) +{ + return preadleast(fd, buf, count, count, off); +} + +__attribute__((unused)) static inline +size_t pwriteall(int fd, const void* buf, size_t count, off_t off) +{ + return pwriteleast(fd, buf, count, count, off); +} + +#endif + +#endif diff --git a/fat/util.h b/fat/util.h new file mode 100644 index 00000000..e1f55adc --- /dev/null +++ b/fat/util.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013, 2015 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. + * + * util.h + * Utility functions for the filesystem implementation. + */ + +#ifndef UTIL_H +#define UTIL_H + +template T divup(T a, T b) +{ + return a/b + (a % b ? 1 : 0); +} + +template T roundup(T a, T b) +{ + return a % b ? a + b - a % b : a; +} + +static inline bool checkbit(const uint8_t* bitmap, size_t bit) +{ + uint8_t bits = bitmap[bit / 8UL]; + return bits & (1U << (bit % 8UL)); +} + +static inline void setbit(uint8_t* bitmap, size_t bit) +{ + bitmap[bit / 8UL] |= 1U << (bit % 8UL); +} + +static inline void clearbit(uint8_t* bitmap, size_t bit) +{ + bitmap[bit / 8UL] &= ~(1U << (bit % 8UL)); +} + +#endif diff --git a/libmount/Makefile b/libmount/Makefile index ca414efb..66829a5c 100644 --- a/libmount/Makefile +++ b/libmount/Makefile @@ -17,6 +17,7 @@ devices.o \ crc32.o \ ext2.o \ extended.o \ +fat.o \ filesystem.o \ gpt.o \ harddisk.o \ diff --git a/libmount/ext2.c b/libmount/ext2.c index b031e55a..f269d55f 100644 --- a/libmount/ext2.c +++ b/libmount/ext2.c @@ -99,6 +99,7 @@ static bool ext2_probe(struct blockdevice* bdev, const unsigned char* leading, size_t amount) { + // TODO: Strongly prefer a UUID indication before probing. (void) bdev; if ( amount < 1024 ) return false; diff --git a/libmount/fat.c b/libmount/fat.c new file mode 100644 index 00000000..841be801 --- /dev/null +++ b/libmount/fat.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 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. + * + * fat.c + * File Allocation Table filesystem. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "util.h" + +static size_t fat_probe_amount(struct blockdevice* bdev) +{ + (void) bdev; + return 512; +} + +static bool fat_probe(struct blockdevice* bdev, + const unsigned char* leading, + size_t amount) +{ + (void) leading; + (void) amount; + // TODO: Relax restriction that this must be a partition? At least for non-EFI. + struct partition* p = bdev->p; + if ( !p ) + return false; + // TODO: Test for a space padded FAT32 at 0x52 + 8 bytes. + if ( p->table_type == PARTITION_TABLE_TYPE_GPT ) + { + unsigned char bdp_uuid[16]; + uuid_from_string(bdp_uuid, BDP_GPT_TYPE_UUID); + unsigned char esp_uuid[16]; + uuid_from_string(esp_uuid, ESP_GPT_TYPE_UUID); + // TODO: Additional probing is needed to detect FAT vs NTFS. + return memcmp(p->gpt_type_guid, bdp_uuid, 16) == 0 || + memcmp(p->gpt_type_guid, esp_uuid, 16) == 0; + } + if ( p->table_type == PARTITION_TABLE_TYPE_MBR ) + return p->mbr_system_id == 0x01 || + p->mbr_system_id == 0x04 || + p->mbr_system_id == 0x06 || + p->mbr_system_id == 0x04 || + p->mbr_system_id == 0x0C || + p->mbr_system_id == 0x0E || + p->mbr_system_id == 0xEF; + return false; +} + +static void fat_release(struct filesystem* fs) +{ + if ( !fs ) + return; + free(fs); +} + +static enum filesystem_error fat_inspect(struct filesystem** fs_ptr, + struct blockdevice* bdev) +{ + *fs_ptr = NULL; + struct filesystem* fs = CALLOC_TYPE(struct filesystem); + if ( !fs ) + return FILESYSTEM_ERROR_ERRNO; + fs->bdev = bdev; + fs->handler = &fat_handler; + fs->handler_private = NULL; + unsigned char vbr[512]; + if ( blockdevice_preadall(bdev, vbr, sizeof(vbr), 0) != sizeof(vbr) ) + return fat_release(fs), FILESYSTEM_ERROR_ERRNO; + // TODO: Report efi instead if ESP. + fs->fstype_name = "fat"; + // TODO: Port a fat fsck. + fs->driver = "fatfs"; + fs->flags |= FILESYSTEM_FLAG_UUID; + // Use the serial number + label as the UUID. + // TODO: The location varies between FAT12/16 and FAT32. This is the wrong + // way to detect the FAT type. + if ( vbr[82 + 3] == '3' && vbr[82 + 4] == '2' ) + memcpy(fs->uuid, vbr + 67, 4 + 11); + else + memcpy(fs->uuid, vbr + 39, 4 + 11); + return *fs_ptr = fs, FILESYSTEM_ERROR_NONE; +} + +const struct filesystem_handler fat_handler = +{ + .handler_name = "fat", + .probe_amount = fat_probe_amount, + .probe = fat_probe, + .inspect = fat_inspect, + .release = fat_release, +}; diff --git a/libmount/filesystem.c b/libmount/filesystem.c index 9af9c130..3e6d939b 100644 --- a/libmount/filesystem.c +++ b/libmount/filesystem.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ static const struct filesystem_handler* filesystem_handlers[] = &extended_handler, &ext2_handler, &iso9660_handler, + &fat_handler, NULL, }; diff --git a/libmount/include/mount/fat.h b/libmount/include/mount/fat.h new file mode 100644 index 00000000..2b7e7729 --- /dev/null +++ b/libmount/include/mount/fat.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 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. + * + * mount/fat.h + * File Allocation Table filesystem. + */ + +#ifndef INCLUDE_MOUNT_FAT_H +#define INCLUDE_MOUNT_FAT_H + +#include + +#define BDP_GPT_TYPE_UUID "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7" +#define ESP_GPT_TYPE_UUID "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" + +#if defined(__cplusplus) +extern "C" { +#endif + +extern const struct filesystem_handler fat_handler; + +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +#endif