sortix-mirror/fat/inode.cpp

1016 lines
28 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 "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(&times[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(&times[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, &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;
// 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, &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)) )
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, &sector, &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;
}