/* * 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 "fat.h" #include "block.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->filesystem = filesystem; this->reference_count = 1; this->remote_reference_count = 0; this->implied_reference = 0; this->inode_id = inode_id; this->dirty = false; this->deleted = 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; } bool Inode::ChangeMode(mode_t mode) { assert(filesystem->device->write); if ( inode_id == filesystem->root_inode_id ) return errno = EPERM, false; mode_t base_mode = (dirent->attributes & FAT_ATTRIBUTE_DIRECTORY ? filesystem->mode_dir : filesystem->mode_reg) & 0777; uint8_t new_attributes = dirent->attributes; if ( mode == (base_mode & ~0222) ) new_attributes |= FAT_ATTRIBUTE_READ_ONLY; else if ( mode == (base_mode | (base_mode & 0222)) ) new_attributes &= ~FAT_ATTRIBUTE_READ_ONLY; else return errno = EPERM, false; if ( new_attributes == dirent->attributes ) return true; if ( data_block ) data_block->BeginWrite(); dirent->attributes = new_attributes; if ( data_block ) data_block->FinishWrite(); return true; } uint32_t Inode::UserId() { return filesystem->uid; } bool Inode::ChangeOwner(uid_t uid, gid_t gid) { assert(filesystem->device->write); if ( inode_id == filesystem->root_inode_id ) return errno = EPERM, false; if ( (uid != (uid_t) -1 && uid != filesystem->uid) || (gid != (gid_t) -1 && gid != filesystem->gid) ) return errno = EPERM, false; return true; } uint32_t Inode::GroupId() { return filesystem->gid; } void Inode::UTimens(const struct timespec times[2]) { if ( inode_id == filesystem->root_inode_id ) return; if ( times[0].tv_nsec != UTIME_OMIT || times[1].tv_nsec != UTIME_OMIT ) { struct timespec now; clock_gettime(CLOCK_REALTIME, &now); uint8_t tenths; uint16_t time; if ( data_block ) data_block->BeginWrite(); if ( times[0].tv_nsec == UTIME_NOW ) timespec_to_fat(&now, &dirent->access_date, &time, &tenths); else if ( times[0].tv_nsec != UTIME_OMIT ) timespec_to_fat(×[0], &dirent->access_date, &time, &tenths); if ( times[1].tv_nsec == UTIME_NOW ) timespec_to_fat(&now, &dirent->modified_date, &dirent->modified_time, &tenths); else if ( times[1].tv_nsec != UTIME_OMIT ) timespec_to_fat(×[1], &dirent->modified_date, &dirent->modified_time, &tenths); if ( data_block ) data_block->FinishWrite(); } } uint64_t Inode::Size() { if ( inode_id == filesystem->root_inode_id ) return 0; if ( dirent->attributes & FAT_ATTRIBUTE_DIRECTORY ) return 0; return dirent->size; } 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::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; if ( data_block ) data_block->BeginWrite(); dirent->size = new_size; if ( data_block ) 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; if ( deleted ) return errno = ENOENT, (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; // TODO: Protect against . and .. // TODO: Switch to use Link() 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(new_cluster, filesystem->eof_cluster); 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, ". ", 11); dirent->attributes = attributes; dirent->cluster_high = htole16(inode_id >> 16); dirent->cluster_low = htole16(inode_id & 0xFFFF); dirent++; memcpy(dirent->name, ".. ", 11); 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 ( !S_ISDIR(Mode()) ) return errno = ENOTDIR, false; if ( deleted ) return errno = ENOENT, false; if ( directories && !S_ISDIR(dest->Mode()) ) return errno = ENOTDIR, false; if ( !directories && S_ISDIR(dest->Mode()) ) return errno = EISDIR, false; if ( !filesystem->device->write ) return errno = EROFS, false; size_t elem_length = strlen(elem); if ( elem_length == 0 ) return errno = ENOENT, false; 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)) ) return block->Unref(), errno = EEXIST, false; offset += sizeof(struct fat_dirent); } if ( block ) block->Unref(); if ( errno ) return false; // Files can only have a single link. if ( !dest->deleted && !directories ) return errno = EPERM, false; if ( !is_8_3(elem) ) return errno = ENAMETOOLONG, false; // TODO: Root directory support. if ( inode_id == filesystem->root_inode_id && filesystem->fat_type != 32 ) return errno = ENOTSUP, false; 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 false; } block->BeginWrite(); memset(block->block_data, 0, filesystem->bytes_per_sector); block->FinishWrite(); block->Unref(); } filesystem->WriteFAT(new_cluster, filesystem->eof_cluster); filesystem->WriteFAT(last_cluster, new_cluster); found_free = true; free_cluster = new_cluster; free_sector = 0; free_offset = 0; } block = GetClusterSector(free_cluster, free_sector); if ( !block ) return false; block->BeginWrite(); struct fat_dirent* dirent = (struct fat_dirent*) (block->block_data + free_offset); if ( strcmp(elem, ".") != 0 && strcmp(elem, "..") != 0 ) { assert(dest->deleted); memcpy(dirent, dest->dirent, sizeof(*dirent)); dest->dirent = dirent; dest->data_block = block; block->Refer(); dest->deleted = false; } else { memset(dirent, 0, sizeof(*dirent)); dirent->attributes = FAT_ATTRIBUTE_DIRECTORY; } encode_8_3(elem, dirent->name); dirent->cluster_high = htole16(dest->inode_id >> 16); dirent->cluster_low = htole16(dest->inode_id & 0xFFFF); block->FinishWrite(); Modified(); return true; } Inode* Inode::UnlinkKeep(const char* elem, bool directories, bool force) { // TODO: It looks like it's assumed . and .. will never get here. Except // that can happen with force during rename? Inode* inode = Open(elem, O_WRITE, 0); if ( !inode ) return NULL; if ( !force && directories && !S_ISDIR(inode->Mode()) ) return inode->Unref(), errno = ENOTDIR, (Inode*) NULL; if ( !force && directories && !inode->IsEmptyDirectory() ) return inode->Unref(), errno = ENOTEMPTY, (Inode*) NULL; if ( !force && !directories && S_ISDIR(inode->Mode()) ) return inode->Unref(), errno = EISDIR, (Inode*) NULL; if ( !filesystem->device->write ) return inode->Unref(), errno = EROFS, (Inode*) NULL; if ( strcmp(elem, ".") != 0 && strcmp(elem, "..") != 0 ) { assert(!inode->deleted); inode->data_block->BeginWrite(); inode->dirent->name[0] = (char) 0xE5; memcpy(&inode->deleted_dirent, inode->dirent, sizeof(struct fat_dirent)); inode->dirent = &inode->deleted_dirent; inode->data_block->FinishWrite(); inode->data_block->Unref(); inode->data_block = NULL; inode->deleted = true; } // TODO: If dirs keep ref to their parent dir alive, unref it here. Modified(); return inode; } 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 ( !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 ( !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; 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::Rename(Inode* olddir, const char* oldname, const char* newname) { if ( deleted ) return errno = ENOENT, false; 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; // TODO: Verify src_inode is not a subdir of this dir. 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 ( S_ISDIR(src_inode->Mode()) ) { if ( !Unlink(newname, true) && errno != ENOENT ) return src_inode->Unref(), false; olddir->Unlink(oldname, true, true); Link(newname, src_inode, 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; olddir->Unlink(oldname, false); Link(newname, src_inode, false); } src_inode->Unref(); return true; } bool Inode::Symlink(const char* elem, const char* dest) { (void) elem; (void) dest; if ( !filesystem->device->write ) return errno = EROFS, false; return errno = EPERM, false; } 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; // There is no need to remove the . and .. and entries since there is no // link count and the directory is empty. We can just discard the data. result->Unref(); return true; } bool Inode::IsEmptyDirectory() { if ( !S_ISDIR(Mode()) ) return errno = ENOTDIR, false; if ( deleted ) return errno = ENOENT, false; if ( inode_id == filesystem->root_inode_id ) return false; uint32_t cluster = first_cluster; uint8_t sector = 0; uint16_t offset = 0; Block* block = NULL; while ( Iterate(&block, &cluster, §or, &offset) ) { uint8_t* block_data = block->block_data + offset; struct fat_dirent* entry = (struct fat_dirent*) block_data; 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(name, ".") != 0 && strcmp(name, "..") != 0) ) return block->Unref(), false; offset += sizeof(struct fat_dirent); } if ( block ) block->Unref(); if ( errno ) return false; return true; } void Inode::Delete() { assert(deleted); assert(dirent->name[0] == 0x00 || (unsigned char) dirent->name[0] == 0xE5); assert(!reference_count); assert(!remote_reference_count); uint32_t cluster = first_cluster; while ( true ) { if ( cluster < 2 || filesystem->eio_cluster == cluster ) break; if ( filesystem->eof_cluster <= cluster ) break; uint32_t next_cluster = filesystem->ReadFAT(cluster); filesystem->WriteFAT(cluster, 0); filesystem->FreeCluster(cluster); cluster = next_cluster; } } void Inode::Refer() { reference_count++; } void Inode::Unref() { assert(0 < reference_count); reference_count--; if ( !reference_count && !remote_reference_count ) { if ( deleted ) 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 ) { if ( deleted ) Delete(); delete this; } } void Inode::Modified() { if ( inode_id == filesystem->root_inode_id ) return; struct timespec now; clock_gettime(CLOCK_REALTIME, &now); if ( data_block ) data_block->BeginWrite(); uint8_t tenths; timespec_to_fat(&now, &dirent->modified_date, &dirent->modified_time, &tenths); if ( data_block ) data_block->FinishWrite(); } void Inode::BeginWrite() { if ( data_block ) data_block->BeginWrite(); } // TODO: Uh. Use these? 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; } if ( data_block ) data_block->FinishWrite(); Use(); } void Inode::Sync() { if ( !dirty ) return; if ( data_block ) 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() { if ( data_block ) 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; }