1
0
Fork 0
mirror of https://gitlab.com/sortix/sortix.git synced 2023-02-13 20:55:38 -05:00
sortix--sortix/ext/inode.cpp
Jonas 'Sortie' Termansen bc928e99a4 Fix extfs coding style and general issues.
This cleans up constructors so fields are initialized in the same order they
are declared in. This makes it trivial to spot accidentally uninitialized
fields.

This fixes a minor argument parsing bug when the mount path isn't set, but
fortunately argv[argc] is NULL and we wanted to set it to NULL anyway.

This prevents excessively large block sizes from being used.

This improves inode value range checks in the fsmarshall code. Inode 0 is
not a valid inode. The new code for this is also simpler.

This prevents creating links with names larger than 255 bytes.

This adds a check to ensure inodes don't overflow the hardlink count.

This ensures the dirent filetype is only set if supported.
2015-08-27 22:12:11 +02:00

1125 lines
30 KiB
C++

/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013, 2014, 2015.
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
inode.cpp
Filesystem inode.
*******************************************************************************/
#define __STDC_CONSTANT_MACROS
#define __STDC_LIMIT_MACROS
#include <sys/stat.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.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 "block.h"
#include "blockgroup.h"
#include "device.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()
{
// TODO: Use i_mode_high.
return data->i_mode;
}
void Inode::SetMode(uint32_t mode)
{
BeginWrite();
// TODO: Use i_mode_high.
data->i_mode = (uint16_t) mode;
FinishWrite();
}
uint32_t Inode::UserId()
{
// TODO: Use i_uid_high.
return data->i_uid;
}
void Inode::SetUserId(uint32_t user)
{
BeginWrite();
// TODO: Use i_uid_high.
data->i_uid = (uint16_t) user;
FinishWrite();
}
uint32_t Inode::GroupId()
{
// TODO: Use i_gid_high.
return data->i_gid;
}
void Inode::SetGroupId(uint32_t group)
{
BeginWrite();
// TODO: Use i_gid_high.
data->i_gid = (uint16_t) group;
FinishWrite();
}
uint64_t Inode::Size()
{
bool largefile = filesystem->sb->s_feature_ro_compat &
EXT2_FEATURE_RO_COMPAT_LARGE_FILE;
if ( !EXT2_S_ISREG(data->i_mode) || !largefile )
return data->i_size;
uint64_t lower = data->i_size;
uint64_t upper = data->i_dir_acl;
uint64_t size = lower | (upper << 32ULL);
return size;
}
void Inode::SetSize(uint64_t new_size)
{
bool largefile = filesystem->sb->s_feature_ro_compat &
EXT2_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 ( EXT2_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);
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;
}
bool Inode::FreeIndirect(uint64_t from, uint64_t offset, uint32_t block_id,
int indirection, uint64_t entry_span)
{
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;
}
void Inode::Truncate(uint64_t new_size)
{
uint64_t old_size = Size();
bool could_be_embedded = old_size == 0 && EXT2_S_ISLNK(Mode()) && !data->i_blocks;
bool is_embedded = 0 < old_size && old_size <= 60 && !data->i_blocks;
if ( could_be_embedded || is_embedded )
{
if ( new_size <= 60 )
{
data_block->BeginWrite();
unsigned char* block_data = (unsigned char*) &data->i_block[0];
if ( old_size < new_size )
memset(block_data + old_size, 0, new_size - old_size);
if ( new_size < old_size )
memset(block_data + new_size, 0, old_size - new_size);
data->i_size = new_size;
data_block->FinishWrite();
return;
}
if ( is_embedded && !UnembedInInode() )
{
// Truncate() can't fail ATM so lose data instead of worse stuff.
data_block->BeginWrite();
unsigned char* block_data = (unsigned char*) &data->i_block[0];
memset(block_data, 0, 60);
data->i_size = 0;
data_block->FinishWrite();
}
}
// TODO: Enforce a filesize limit!
SetSize(new_size);
if ( old_size <= new_size )
return;
uint64_t old_num_blocks = divup(old_size, (uint64_t) filesystem->block_size);
uint64_t new_num_blocks = divup(new_size, (uint64_t) filesystem->block_size);
// Zero out the rest of the last partial block, if any.
uint32_t partial = new_size % filesystem->block_size;
if ( partial )
{
Block* partial_block = GetBlock(new_num_blocks-1);
uint8_t* data = partial_block->block_data;
partial_block->BeginWrite();
memset(data + partial, 0, filesystem->block_size - partial);
partial_block->FinishWrite();
}
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;
BeginWrite();
for ( uint64_t i = new_num_blocks; i < old_num_blocks && i < 12; i++ )
{
filesystem->FreeBlock(data->i_block[i]);
data->i_block[i] = 0;
}
if ( data->i_block[12] && !FreeIndirect(new_num_blocks, max_direct, data->i_block[12], 0, 1) )
{
filesystem->FreeBlock(data->i_block[12]);
data->i_block[12] = 0;
}
if ( data->i_block[13] && !FreeIndirect(new_num_blocks, max_singly, data->i_block[13], 1, ENTRIES) )
{
filesystem->FreeBlock(data->i_block[13]);
data->i_block[13] = 0;
}
if ( data->i_block[14] && !FreeIndirect(new_num_blocks, max_doubly, data->i_block[14], 2, ENTRIES * ENTRIES) )
{
filesystem->FreeBlock(data->i_block[14]);
data->i_block[14] = 0;
}
(void) max_triply;
FinishWrite();
}
Inode* Inode::Open(const char* elem, int flags, mode_t mode)
{
if ( !EXT2_S_ISDIR(Mode()) )
return errno = ENOTDIR, (Inode*) NULL;
size_t elem_length = strlen(elem);
if ( elem_length == 0 )
return errno = ENOENT, (Inode*) NULL;
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 / 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->inode &&
entry->name_len == elem_length &&
memcmp(elem, entry->name, elem_length) == 0 )
{
uint8_t file_type = entry->file_type;
uint32_t inode_id = entry->inode;
block->Unref();
if ( (flags & O_CREAT) && (flags & O_EXCL) )
return errno = EEXIST, (Inode*) NULL;
if ( (flags & O_DIRECTORY) &&
file_type != EXT2_FT_UNKNOWN &&
file_type != EXT2_FT_DIR &&
file_type != EXT2_FT_SYMLINK )
return errno = ENOTDIR, (Inode*) NULL;
Inode* inode = filesystem->GetInode(inode_id);
if ( flags & O_DIRECTORY &&
!EXT2_S_ISDIR(inode->Mode()) &&
!EXT2_S_ISLNK(inode->Mode()) )
{
inode->Unref();
return errno = ENOTDIR, (Inode*) NULL;
}
if ( S_ISREG(inode->Mode()) && flags & O_WRITE && flags & O_TRUNC )
inode->Truncate(0);
return inode;
}
offset += entry->reclen;
}
if ( block )
block->Unref();
if ( flags & O_CREAT )
{
// TODO: Preferred block group!
uint32_t result_inode_id = filesystem->AllocateInode();
if ( !result_inode_id )
return NULL;
Inode* result = filesystem->GetInode(result_inode_id);
memset(result->data, 0, sizeof(*result->data));
result->SetMode((mode & S_SETABLE) | S_IFREG);
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
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!
if ( !Link(elem, result, false) )
{
result->Unref();
return NULL;
}
return result;
}
return errno = ENOENT, (Inode*) NULL;
}
bool Inode::Link(const char* elem, Inode* dest, bool directories)
{
if ( !EXT2_S_ISDIR(Mode()) )
return errno = ENOTDIR, false;
if ( directories && !EXT2_S_ISDIR(dest->Mode()) )
return errno = ENOTDIR, false;
if ( !directories && EXT2_S_ISDIR(dest->Mode()) )
return errno = EISDIR, false;
Modified();
// 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 ( 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;
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 & EXT2_FEATURE_INCOMPAT_FILETYPE )
entry->file_type = EXT2_FT_OF_MODE(dest->Mode());
else
entry->file_type = 0;
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 ( !EXT2_S_ISDIR(Mode()) )
return errno = ENOTDIR, (Inode*) NULL;
Modified();
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 && !EXT2_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 && EXT2_S_ISDIR(inode->Mode()) )
{
inode->Unref();
block->Unref();
return errno = EISDIR, (Inode*) NULL;
}
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);
memcpy(block->block_data, last_block->block_data, block_size);
last_block->Unref();
}
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 ( !EXT2_S_ISREG(Mode()) && !EXT2_S_ISLNK(Mode()) )
return errno = EISDIR, -1;
if ( o_offset < 0 )
return errno = EINVAL, -1;
if ( SSIZE_MAX < s_count )
s_count = SSIZE_MAX;
uint64_t sofar = 0;
uint64_t count = (uint64_t) s_count;
uint64_t offset = (uint64_t) o_offset;
uint64_t file_size = Size();
if ( file_size <= offset )
return 0;
if ( file_size - offset < count )
count = file_size - offset;
// TODO: This case also needs to be handled in SetSize, Truncate, WriteAt,
// and so on.
if ( 0 < file_size && file_size <= 60 && !data->i_blocks )
{
assert(offset + count <= 60);
unsigned char* block_data = (unsigned char*) &data->i_block[0];
memcpy(buf, block_data + offset, count);
return (ssize_t) count;
}
while ( sofar < count )
{
uint64_t block_id = offset / filesystem->block_size;
uint32_t block_offset = offset % filesystem->block_size;
uint32_t block_left = filesystem->block_size - block_offset;
Block* block = GetBlock(block_id);
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;
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 ( !EXT2_S_ISREG(Mode()) && !EXT2_S_ISLNK(Mode()) )
return errno = EISDIR, -1;
if ( o_offset < 0 )
return errno = EINVAL, -1;
if ( SSIZE_MAX < s_count )
s_count = SSIZE_MAX;
Modified();
uint64_t sofar = 0;
uint64_t count = (uint64_t) s_count;
uint64_t offset = (uint64_t) o_offset;
uint64_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);
if ( 0 < end_at && end_at <= 60 && !data->i_blocks )
{
data_block->BeginWrite();
unsigned char* block_data = (unsigned char*) &data->i_block[0];
memcpy(block_data + offset, buf, count);
if ( data->i_size < end_at )
data->i_size = end_at;
data_block->FinishWrite();
return (ssize_t) count;
}
while ( sofar < count )
{
uint64_t block_id = offset / filesystem->block_size;
uint32_t block_offset = offset % filesystem->block_size;
uint32_t block_left = filesystem->block_size - block_offset;
Block* block = GetBlock(block_id);
if ( !block )
return sofar ? (ssize_t) 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();
block->Unref();
sofar += amount;
offset += amount;
}
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 ( EXT2_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)
{
// TODO: Preferred block group!
uint32_t result_inode_id = filesystem->AllocateInode();
if ( !result_inode_id )
return NULL;
Inode* result = filesystem->GetInode(result_inode_id);
memset(result->data, 0, sizeof(*result->data));
result->SetMode((0777 & S_SETABLE) | EXT2_S_IFLNK);
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
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!
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)
{
// TODO: Preferred block group!
uint32_t result_inode_id = filesystem->AllocateInode();
if ( !result_inode_id )
return NULL;
Inode* result = filesystem->GetInode(result_inode_id);
memset(result->data, 0, sizeof(*result->data));
result->SetMode((mode & S_SETABLE) | EXT2_S_IFDIR);
// Increase 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);
block_group->BeginWrite();
block_group->data->bg_used_dirs_count++;
block_group->FinishWrite();
block_group->Unref();
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
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!
if ( !Link(path, result, true) )
return result->Unref(), (Inode*) NULL;
if ( !result->Link(".", result, true) )
{
Unlink(path, true, true);
return result->Unref(), (Inode*) NULL;
}
if ( !result->Link("..", this, true) )
{
result->Unlink(".", true, true);
Unlink(path, true, true);
return result->Unref(), (Inode*) NULL;
}
return result;
}
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);
block_group->BeginWrite();
block_group->data->bg_used_dirs_count--;
block_group->FinishWrite();
block_group->Unref();
result->Unref();
return true;
}
bool Inode::IsEmptyDirectory()
{
if ( !EXT2_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 )
{
if ( !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 )
{
if ( !data->i_links_count )
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;
}