POSIX allows <fcntl.h> to include <sys/stat.h>, but doesn't require it. There's little reason to do that, since they are separate headers, and <fcntl.h> just needs the mode_t constants. Fix the code accidentally relying on <fcntl.h> including <sys/stat.h>. The mode_t constants are now provided in their own kernel header <sortix/mode.h>. Additionally fix <sys/stat.h> pulling in all of <sys/types.h>, which is not allowed by POSIX, which only requires a few types to be declared. Fix the code accidentally relying on <sys/stat.h> including <sys/types.h>. Finally fix <dirent.h> pulling in <stdint.h> through <sortix/dirent.h>. The <sortix/__/dt.h> and <sortix/__/stat.h> headers are no longer required and their contents have been merged into <sortix/__/dirent.h>.
1195 lines
32 KiB
C++
1195 lines
32 KiB
C++
/*
|
|
* Copyright (c) 2013, 2014, 2015, 2018 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 <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 "block.h"
|
|
#include "blockgroup.h"
|
|
#include "device.h"
|
|
#include "extfs.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)
|
|
{
|
|
assert(filesystem->device->write);
|
|
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)
|
|
{
|
|
assert(filesystem->device->write);
|
|
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)
|
|
{
|
|
assert(filesystem->device->write);
|
|
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)
|
|
{
|
|
assert(filesystem->device->write);
|
|
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);
|
|
// 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void Inode::Truncate(uint64_t new_size)
|
|
{
|
|
assert(filesystem->device->write);
|
|
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++ )
|
|
{
|
|
if ( !data->i_block[i] )
|
|
continue;
|
|
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 = EXT2_FT_UNKNOWN;
|
|
if ( filesystem->sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE )
|
|
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 ( !inode )
|
|
return (Inode*) NULL;
|
|
if ( flags & O_DIRECTORY &&
|
|
!EXT2_S_ISDIR(inode->Mode()) &&
|
|
!EXT2_S_ISLNK(inode->Mode()) )
|
|
{
|
|
inode->Unref();
|
|
return errno = ENOTDIR, (Inode*) NULL;
|
|
}
|
|
if ( flags & O_WRITE && !filesystem->device->write )
|
|
{
|
|
inode->Unref();
|
|
return errno = EROFS, (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 )
|
|
{
|
|
if ( !filesystem->device->write )
|
|
return errno = EROFS, (Inode*) NULL;
|
|
// 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), (Inode*) NULL;
|
|
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((mode & S_SETABLE) | S_IFREG);
|
|
result->SetUserId(request_uid);
|
|
result->SetGroupId(request_gid);
|
|
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;
|
|
|
|
// 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 & EXT2_FEATURE_INCOMPAT_FILETYPE )
|
|
entry->file_type = EXT2_FT_OF_MODE(dest->Mode());
|
|
else
|
|
entry->file_type = EXT2_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 ( !EXT2_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 && !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;
|
|
}
|
|
|
|
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 ( !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 ( !filesystem->device->write )
|
|
return errno = EROFS, -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)
|
|
{
|
|
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) | EXT2_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)
|
|
{
|
|
if ( !filesystem->device->write )
|
|
return errno = EROFS, (Inode*) NULL;
|
|
|
|
// 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), (Inode*) NULL;
|
|
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((mode & S_SETABLE) | EXT2_S_IFDIR);
|
|
result->SetUserId(request_uid);
|
|
result->SetGroupId(request_gid);
|
|
|
|
// 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);
|
|
if ( !block_group )
|
|
return result->Unref(), (Inode*) NULL;
|
|
block_group->BeginWrite();
|
|
block_group->data->bg_used_dirs_count++;
|
|
block_group->FinishWrite();
|
|
block_group->Unref();
|
|
|
|
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);
|
|
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 ( !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;
|
|
}
|