sortix-mirror/fat/inode.cpp

1300 lines
36 KiB
C++

/*
* 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 <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <endian.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#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, &sector, &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;
}