1016 lines
28 KiB
C++
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(×[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;
|
||
|
}
|