sortix-mirror/iso9660/inode.cpp

615 lines
16 KiB
C++

/*
* Copyright (c) 2013, 2014, 2015, 2018, 2022 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 <ctype.h>
#include <errno.h>
#include <endian.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "block.h"
#include "device.h"
#include "filesystem.h"
#include "inode.h"
#include "iso9660fs.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, iso9660_ino_t inode_id)
{
this->prev_inode = NULL;
this->next_inode = NULL;
this->prev_hashed = NULL;
this->next_hashed = 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->parent_inode_id = 0;
}
Inode::~Inode()
{
if ( data_block )
data_block->Unref();
Unlink();
}
void Inode::Parse()
{
const uint8_t* block_data = (const uint8_t*) data;
uid = 0;
gid = 0;
time = 0;
uint8_t file_flags = block_data[25];
bool is_directory = file_flags & ISO9660_DIRENT_FLAG_DIR;
mode = 0555 | (is_directory ? ISO9660_S_IFDIR : ISO9660_S_IFREG);
uint32_t u32;
memcpy(&u32, block_data + 10, sizeof(u32));
size = le32toh(u32);
nlink = 1;
const uint8_t* time_bytes = block_data + 18;
struct tm tm;
memset(&tm, 0, sizeof(tm));
tm.tm_year = time_bytes[0];
tm.tm_mon = time_bytes[1] - 1;
tm.tm_mday = time_bytes[2];
tm.tm_hour = time_bytes[3];
tm.tm_min = time_bytes[4];
tm.tm_sec = time_bytes[5];
// TODO: Is this accurate?
time_t tz_offset = (-48 + time_bytes[6]) * 15 * 60;
// TODO: The timezone offset should've been mixed in with mktime, somehow.
time = mktime(&tm) + tz_offset;
uint8_t dirent_len = block_data[0];
uint8_t name_len = block_data[32];
size_t extended_off = 33 + name_len + !(name_len & 1);
for ( size_t i = extended_off; i < dirent_len && 3 <= dirent_len - i; )
{
const uint8_t* field = block_data + i;
uint8_t len = field[2];
if ( !len || dirent_len - i < len )
break;
i += len;
if ( len == 36 && field[0] == 'P' && field[1] == 'X' && field[3] == 1 )
{
uint32_t bits;
memcpy(&bits, field + 4, sizeof(bits));
mode = le32toh(bits) & 0xffff;
memcpy(&bits, field + 12, sizeof(bits));
nlink = le32toh(bits);
memcpy(&bits, field + 20, sizeof(bits));
uid = le32toh(bits);
memcpy(&bits, field + 28, sizeof(bits));
gid = le32toh(bits);
}
}
if ( ISO9660_S_ISLNK(mode) )
{
uint8_t buf[256];
size = ReadLink(buf, sizeof(buf));
}
}
uint32_t Inode::Mode()
{
return mode;
}
uint32_t Inode::UserId()
{
return uid;
}
uint32_t Inode::GroupId()
{
return gid;
}
uint64_t Inode::Size()
{
return size;
}
uint64_t Inode::Time()
{
return time;
}
Block* Inode::GetBlock(uint32_t offset)
{
uint32_t lba;
memcpy(&lba, (uint8_t*) data + 2, sizeof(lba));
lba = le32toh(lba);
uint32_t block_id = lba + offset;
return filesystem->device->GetBlock(block_id);
}
bool Inode::FindParentInode(uint64_t parent_lba, uint64_t parent_size)
{
if ( inode_id == filesystem->root_ino )
return parent_inode_id = inode_id, true;
Block* block = NULL;
uint32_t block_size = filesystem->block_size;
uint64_t parent_offset = parent_lba * block_size;
uint64_t block_id = 0;
uint64_t offset = 0;
while ( offset < parent_size )
{
uint64_t entry_block_id = (parent_offset + offset) / block_size;
uint64_t entry_block_offset = (parent_offset + offset) % block_size;
if ( block && block_id != entry_block_id )
{
block->Unref();
block = NULL;
}
if ( !block && !(block = filesystem->device->GetBlock(entry_block_id)) )
return false;
const uint8_t* block_data = block->block_data + entry_block_offset;
uint8_t dirent_len = block_data[0];
if ( !dirent_len )
{
offset = (entry_block_id + 1) * block_size;
continue;
}
uint8_t name_len = block_data[32];
const uint8_t* name_data = block_data + 33;
if ( name_len == 0 || !name_data[0] )
{
parent_inode_id = parent_offset + offset;
return true;
}
// TODO: Can dirent_len be misaligned?
uint64_t reclen = dirent_len + (dirent_len & 1);
if ( !reclen )
return errno = EINVAL, false;
offset += reclen;
}
return errno = EINVAL, false;
}
bool Inode::ReadDirectory(uint64_t* offset_inout,
Block** block_inout,
uint64_t* block_id_inout,
char* name,
uint8_t* file_type_out,
iso9660_ino_t* inode_id_out)
{
uint64_t offset = *offset_inout;
next_block:
uint64_t filesize = Size();
if ( filesize <= offset )
return errno = 0, false;
uint64_t entry_block_id = offset / filesystem->block_size;
uint64_t entry_block_offset = offset % filesystem->block_size;
if ( *block_inout && *block_id_inout != entry_block_id )
{
(*block_inout)->Unref();
(*block_inout) = NULL;
}
if ( !*block_inout &&
!(*block_inout = GetBlock(*block_id_inout = entry_block_id)) )
return false;
const uint8_t* block_data = (*block_inout)->block_data + entry_block_offset;
uint8_t dirent_len = block_data[0];
if ( !dirent_len )
{
offset = (entry_block_id + 1) * filesystem->block_size;
goto next_block;
}
if ( name )
{
uint8_t name_len = block_data[32];
const uint8_t* name_data = block_data + 33;
size_t extended_off = 33 + name_len + !(name_len & 1);
iso9660_ino_t entry_inode_id =
((*block_inout)->block_id * filesystem->block_size) +
entry_block_offset;
// TODO: The root directory inode should be that of its . entry.
if ( name_len == 0 || !name_data[0] )
{
name[0] = '.';
name[1] = '\0';
name_len = 1;
entry_inode_id = inode_id;
}
else if ( name_len == 1 && name_data[0] == 1 )
{
name[0] = '.';
name[1] = '.';
name[2] = '\0';
name_len = 2;
if ( !parent_inode_id )
{
uint32_t parent_lba;
memcpy(&parent_lba, block_data + 2, sizeof(parent_lba));
parent_lba = le32toh(parent_lba);
uint32_t parent_size;
memcpy(&parent_size, block_data + 10, sizeof(parent_size));
parent_size = le32toh(parent_size);
if ( !FindParentInode(parent_lba, parent_size) )
return false;
}
entry_inode_id = parent_inode_id;
}
else
{
for ( size_t i = 0; i < name_len; i++ )
{
if ( name_data[i] == ';' )
{
name_len = i;
break;
}
name[i] = tolower(name_data[i]);
}
name[name_len] = '\0';
}
for ( size_t i = extended_off; i < dirent_len && 3 <= dirent_len - i; )
{
const uint8_t* field = block_data + i;
uint8_t len = field[2];
if ( !len || dirent_len - i < len )
break;
i += len;
if ( 5 <= len && field[0] == 'N' && field[1] == 'M' &&
field[3] == 1 )
{
uint8_t nm_flags = field[4];
if ( nm_flags & (1 << 0) ) // TODO: Continued names.
break;
name_len = len - 5;
memcpy(name, field + 5, name_len);
name[name_len] = '\0';
}
// TODO: Other extensions.
}
uint8_t file_flags = block_data[25];
// TODO: Rock Ridge.
bool is_directory = file_flags & ISO9660_DIRENT_FLAG_DIR;
uint8_t file_type = is_directory ? ISO9660_FT_DIR : ISO9660_FT_REG_FILE;
*file_type_out = file_type;
*inode_id_out = entry_inode_id;
}
// TODO: Can dirent_len be misaligned?
uint64_t reclen = dirent_len + (dirent_len & 1);
if ( !reclen )
return errno = EINVAL, false;
offset += reclen;
*offset_inout = offset;
return true;
}
Inode* Inode::Open(const char* elem, int flags, mode_t mode)
{
if ( !ISO9660_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 offset = 0;
Block* block = NULL;
uint64_t block_id = 0;
char name[256];
uint8_t file_type;
iso9660_ino_t inode_id;
while ( ReadDirectory(&offset, &block, &block_id, name, &file_type,
&inode_id) )
{
size_t name_len = strlen(name);
if ( name_len == elem_length && memcmp(elem, name, elem_length) == 0 )
{
block->Unref();
if ( (flags & O_CREAT) && (flags & O_EXCL) )
return errno = EEXIST, (Inode*) NULL;
if ( (flags & O_DIRECTORY) &&
file_type != ISO9660_FT_UNKNOWN &&
file_type != ISO9660_FT_DIR &&
file_type != ISO9660_FT_SYMLINK )
return errno = ENOTDIR, (Inode*) NULL;
Inode* inode = filesystem->GetInode(inode_id);
if ( !inode )
return (Inode*) NULL;
if ( flags & O_DIRECTORY &&
!ISO9660_S_ISDIR(inode->Mode()) &&
!ISO9660_S_ISLNK(inode->Mode()) )
{
inode->Unref();
return errno = ENOTDIR, (Inode*) NULL;
}
if ( flags & O_WRITE )
{
inode->Unref();
return errno = EROFS, (Inode*) NULL;
}
return inode;
}
}
if ( block )
block->Unref();
if ( flags & O_CREAT )
{
(void) mode;
return errno = EROFS, (Inode*) NULL;
}
return errno = ENOENT, (Inode*) NULL;
}
bool Inode::Link(const char* elem, Inode* dest)
{
if ( !ISO9660_S_ISDIR(Mode()) )
return errno = ENOTDIR, false;
if ( ISO9660_S_ISDIR(dest->Mode()) )
return errno = EISDIR, false;
size_t elem_length = strlen(elem);
if ( elem_length == 0 )
return errno = ENOENT, false;
uint64_t offset = 0;
Block* block = NULL;
uint64_t block_id = 0;
char name[256];
uint8_t file_type;
iso9660_ino_t inode_id;
while ( ReadDirectory(&offset, &block, &block_id, name, &file_type,
&inode_id) )
{
size_t name_len = strlen(name);
if ( name_len == elem_length && memcmp(elem, name, elem_length) == 0 )
{
block->Unref();
return errno = EEXIST, false;
}
}
if ( block )
block->Unref();
return errno = EROFS, false;
}
Inode* Inode::UnlinkKeep(const char* elem, bool directories, bool force)
{
if ( !ISO9660_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 offset = 0;
Block* block = NULL;
uint64_t block_id = 0;
char name[256];
uint8_t file_type;
iso9660_ino_t inode_id;
while ( ReadDirectory(&offset, &block, &block_id, name, &file_type,
&inode_id) )
{
size_t name_len = strlen(name);
if ( name_len == elem_length && memcmp(elem, name, elem_length) == 0 )
{
(void) directories;
(void) force;
block->Unref();
return errno = EROFS, (Inode*) NULL;
}
}
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::ReadLink(uint8_t* buf, size_t bufsize)
{
size_t result = 0;
const uint8_t* block_data = (const uint8_t*) data;
uint8_t dirent_len = block_data[0];
uint8_t name_len = block_data[32];
size_t extended_off = 33 + name_len + !(name_len & 1);
bool continued = true;
for ( size_t i = extended_off; i < dirent_len && 3 <= dirent_len - i; )
{
const uint8_t* field = block_data + i;
uint8_t len = field[2];
if ( !len || dirent_len - i < len )
break;
i += len;
if ( 5 <= len && field[0] == 'S' && field[1] == 'L' && field[3] == 1 )
{
for ( size_t n = 5; n < len && 2 <= len - n; )
{
uint8_t comp_flags = field[n + 0];
uint8_t comp_len = field[n + 1];
if ( len - (n + 2) < comp_len )
break;
const char* data = (const char*) (field + n + 2);
size_t datalen = comp_len;
// TDOO: How is a trailing slash encoded?
if ( !continued || (comp_flags & (1 << 3) /* root */) )
{
buf[result++] = '/';
if ( result == bufsize )
return (ssize_t) result;
}
if ( comp_flags & (1 << 1) )
{
data = ".";
datalen = 1;
}
else if ( comp_flags & (1 << 2) )
{
data = "..";
datalen = 2;
}
size_t possible = bufsize - result;
size_t count = datalen < possible ? datalen : possible;
memcpy(buf + result, data, count);
result += count;
if ( result == bufsize )
return (ssize_t) result;
continued = comp_flags & (1 << 0);
n += 2 + comp_len;
}
}
}
return (ssize_t) result;
}
ssize_t Inode::ReadAt(uint8_t* buf, size_t s_count, off_t o_offset)
{
if ( !ISO9660_S_ISREG(Mode()) && !ISO9660_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;
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;
}
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;
}
src_inode->Unref();
return errno = EROFS, false;
}
bool Inode::RemoveDirectory(const char* path)
{
return UnlinkKeep(path, true);
}
void Inode::Refer()
{
reference_count++;
}
void Inode::Unref()
{
assert(0 < reference_count);
reference_count--;
if ( !reference_count && !remote_reference_count )
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 )
delete this;
}
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;
}