diff --git a/Makefile b/Makefile index 2dc4746e..9c268f20 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ games \ hostname \ ifconfig \ init \ +iso9660 \ kblayout \ kblayout-compiler \ login \ diff --git a/iso9660/.gitignore b/iso9660/.gitignore new file mode 100644 index 00000000..58ee62de --- /dev/null +++ b/iso9660/.gitignore @@ -0,0 +1,2 @@ +iso9660fs +*.o diff --git a/iso9660/Makefile b/iso9660/Makefile new file mode 100644 index 00000000..6f420670 --- /dev/null +++ b/iso9660/Makefile @@ -0,0 +1,33 @@ +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/version.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=$(DEFAULT_OPTLEVEL) +CXXFLAGS?=$(OPTLEVEL) + +CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\" +CXXFLAGS:=$(CXXFLAGS) -Wall -Wextra -fno-exceptions -fno-rtti -fcheck-new + +LIBS:=$(LIBS) + +ifeq ($(HOST_IS_SORTIX),0) + LIBS:=$(LIBS) -lfuse + CPPFLAGS:=$(CPPFLAGS) -D_FILE_OFFSET_BITS=64 +endif + +BINARIES:=iso9660fs + +all: $(BINARIES) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(SBINDIR) + install $(BINARIES) $(DESTDIR)$(SBINDIR) + +iso9660fs: *.cpp *.h + $(CXX) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) *.cpp -o $@ $(LIBS) + +clean: + rm -f $(BINARIES) *.o diff --git a/iso9660/block.cpp b/iso9660/block.cpp new file mode 100644 index 00000000..0cae1b27 --- /dev/null +++ b/iso9660/block.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2013, 2014, 2015, 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. + * + * block.cpp + * Blocks in the filesystem. + */ + +#include + +#include +#include +#include + +#include "block.h" +#include "device.h" +#include "ioleast.h" + +Block::Block() +{ + this->block_data = NULL; +} + +Block::Block(Device* device, uint32_t block_id) +{ + Construct(device, block_id); +} + +void Block::Construct(Device* device, uint32_t block_id) +{ + this->prev_block = NULL; + this->next_block = NULL; + this->prev_hashed = NULL; + this->next_hashed = NULL; + this->device = device; + this->reference_count = 1; + this->block_id = block_id; +} + +Block::~Block() +{ + Destruct(); + delete[] block_data; +} + +void Block::Destruct() +{ + Unlink(); +} + +void Block::Refer() +{ + reference_count++; +} + +void Block::Unref() +{ + if ( !--reference_count ) + { +#if 0 + device->block_count--; + delete this; +#endif + } +} + +void Block::Use() +{ + Unlink(); + Prelink(); +} + +void Block::Unlink() +{ + (prev_block ? prev_block->next_block : device->mru_block) = next_block; + (next_block ? next_block->prev_block : device->lru_block) = prev_block; + size_t bin = block_id % DEVICE_HASH_LENGTH; + (prev_hashed ? prev_hashed->next_hashed : device->hash_blocks[bin]) = next_hashed; + if ( next_hashed ) next_hashed->prev_hashed = prev_hashed; +} + +void Block::Prelink() +{ + prev_block = NULL; + next_block = device->mru_block; + if ( device->mru_block ) + device->mru_block->prev_block = this; + device->mru_block = this; + if ( !device->lru_block ) + device->lru_block = this; + size_t bin = block_id % DEVICE_HASH_LENGTH; + prev_hashed = NULL; + next_hashed = device->hash_blocks[bin]; + device->hash_blocks[bin] = this; + if ( next_hashed ) + next_hashed->prev_hashed = this; +} diff --git a/iso9660/block.h b/iso9660/block.h new file mode 100644 index 00000000..7d1da665 --- /dev/null +++ b/iso9660/block.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013, 2014, 2015, 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. + * + * block.h + * Blocks in the filesystem. + */ + +#ifndef BLOCK_H +#define BLOCK_H + +class Device; + +class Block +{ +public: + Block(); + Block(Device* device, uint32_t block_id); + ~Block(); + void Construct(Device* device, uint32_t block_id); + void Destruct(); + +public: + Block* prev_block; + Block* next_block; + Block* prev_hashed; + Block* next_hashed; + Device* device; + size_t reference_count; + uint32_t block_id; + uint8_t* block_data; + +public: + void Refer(); + void Unref(); + void Use(); + void Unlink(); + void Prelink(); + +}; + +#endif diff --git a/iso9660/device.cpp b/iso9660/device.cpp new file mode 100644 index 00000000..84db8750 --- /dev/null +++ b/iso9660/device.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2013, 2014, 2015, 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. + * + * device.cpp + * Block device. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "block.h" +#include "device.h" +#include "ioleast.h" + +Device::Device(int fd, const char* path, uint32_t block_size) +{ + this->mru_block = NULL; + this->lru_block = NULL; + for ( size_t i = 0; i < DEVICE_HASH_LENGTH; i++ ) + hash_blocks[i] = NULL; + struct stat st; + fstat(fd, &st); + this->device_size = st.st_size; + this->path = path; + this->block_size = block_size; + this->fd = fd; + this->block_count = 0; +#ifdef __sortix__ + // TODO: This isn't scaleable if there's multiple filesystems mounted. + size_t memory; + memstat(NULL, &memory); + this->block_limit = (memory / 10) / block_size; +#else + this->block_limit = 32768; +#endif +} + +Device::~Device() +{ + while ( mru_block ) + delete mru_block; + close(fd); +} + +Block* Device::AllocateBlock() +{ + if ( block_limit <= block_count ) + { + for ( Block* block = lru_block; block; block = block->prev_block ) + { + if ( block->reference_count ) + continue; + block->Destruct(); // Syncs. + return block; + } + } + uint8_t* data = new uint8_t[block_size]; + if ( !data ) // TODO: Use operator new nothrow! + return NULL; + Block* block = new Block(); + if ( !block ) // TODO: Use operator new nothrow! + return delete[] data, (Block*) NULL; + block->block_data = data; + block_count++; + return block; +} + +Block* Device::GetBlock(uint32_t block_id) +{ + if ( Block* block = GetCachedBlock(block_id) ) + return block; + Block* block = AllocateBlock(); + if ( !block ) + return NULL; + block->Construct(this, block_id); + off_t file_offset = (off_t) block_size * (off_t) block_id; + preadall(fd, block->block_data, block_size, file_offset); + block->Prelink(); + return block; +} + +Block* Device::GetCachedBlock(uint32_t block_id) +{ + size_t bin = block_id % DEVICE_HASH_LENGTH; + for ( Block* iter = hash_blocks[bin]; iter; iter = iter->next_hashed ) + if ( iter->block_id == block_id ) + return iter->Refer(), iter; + return NULL; +} diff --git a/iso9660/device.h b/iso9660/device.h new file mode 100644 index 00000000..b5555fb7 --- /dev/null +++ b/iso9660/device.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013, 2014, 2015, 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. + * + * device.h + * Block device. + */ + +#ifndef DEVICE_H +#define DEVICE_H + +class Block; + +static const size_t DEVICE_HASH_LENGTH = 1 << 16; + +class Device +{ +public: + Device(int fd, const char* path, uint32_t block_size); + ~Device(); + +public: + Block* mru_block; + Block* lru_block; + Block* hash_blocks[DEVICE_HASH_LENGTH]; + off_t device_size; + const char* path; + uint32_t block_size; + int fd; + size_t block_count; + size_t block_limit; + +public: + Block* AllocateBlock(); + Block* GetBlock(uint32_t block_id); + Block* GetCachedBlock(uint32_t block_id); + +}; + +#endif diff --git a/iso9660/filesystem.cpp b/iso9660/filesystem.cpp new file mode 100644 index 00000000..92231538 --- /dev/null +++ b/iso9660/filesystem.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013, 2014, 2015, 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. + * + * filesystem.cpp + * ISO 9660 filesystem implementation. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include // DEBUG + +#include "block.h" +#include "device.h" +#include "filesystem.h" +#include "inode.h" +#include "util.h" + +Filesystem::Filesystem(Device* device, const char* mount_path, + uint64_t pvd_offset) +{ + // TODO: This should be replaced by the . entry within the root directory + // so the Rock Ridge extensions are available. + uint32_t pvd_block_id = pvd_offset / device->block_size; + this->pvd_block = device->GetBlock(pvd_block_id); + assert(pvd_block); // TODO: This can fail. + this->pvd = (struct iso9660_pvd*) + (pvd_block->block_data + pvd_offset % device->block_size); + this->root_ino = pvd_offset + offsetof(struct iso9660_pvd, root_dirent); + this->device = device; + this->mount_path = mount_path; + this->block_size = device->block_size; + this->mru_inode = NULL; + this->lru_inode = NULL; + for ( size_t i = 0; i < INODE_HASH_LENGTH; i++ ) + this->hash_inodes[i] = NULL; +} + +Filesystem::~Filesystem() +{ + while ( mru_inode ) + delete mru_inode; + pvd_block->Unref(); +} + +Inode* Filesystem::GetInode(iso9660_ino_t inode_id) +{ + size_t bin = inode_id % INODE_HASH_LENGTH; + for ( Inode* iter = hash_inodes[bin]; iter; iter = iter->next_hashed ) + if ( iter->inode_id == inode_id ) + return iter->Refer(), iter; + + uint32_t block_id = inode_id / block_size; + uint32_t offset = inode_id % block_size; + + Block* block = device->GetBlock(block_id); + if ( !block ) + return (Inode*) NULL; + Inode* inode = new Inode(this, inode_id); + if ( !inode ) + return block->Unref(), (Inode*) NULL; + inode->data_block = block; + uint8_t* buf = inode->data_block->block_data + offset; + inode->data = (struct iso9660_dirent*) buf; + inode->Prelink(); + inode->Parse(); + + return inode; +} diff --git a/iso9660/filesystem.h b/iso9660/filesystem.h new file mode 100644 index 00000000..584450b7 --- /dev/null +++ b/iso9660/filesystem.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013, 2014, 2015, 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. + * + * filesystem.h + * ISO 9660 filesystem implementation. + */ + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include "iso9660.h" + +class Device; +class Inode; + +static const size_t INODE_HASH_LENGTH = 1 << 16; + +class Filesystem +{ +public: + Filesystem(Device* device, const char* mount_path, uint64_t pvd_offset); + ~Filesystem(); + +public: + Block* pvd_block; + struct iso9660_pvd* pvd; + Device* device; + const char* mount_path; + iso9660_ino_t root_ino; + uint32_t block_size; + Inode* mru_inode; + Inode* lru_inode; + Inode* hash_inodes[INODE_HASH_LENGTH]; + +public: + Inode* GetInode(iso9660_ino_t inode_id); + +}; + +#endif diff --git a/iso9660/fsmarshall.cpp b/iso9660/fsmarshall.cpp new file mode 100644 index 00000000..a5c24b35 --- /dev/null +++ b/iso9660/fsmarshall.cpp @@ -0,0 +1,701 @@ +/* + * Copyright (c) 2013, 2014, 2015, 2016, 2022, 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. + * + * fsmarshall.cpp + * Sortix fsmarshall frontend. + */ + +#if defined(__sortix__) + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "block.h" +#include "device.h" +#include "filesystem.h" +#include "fsmarshall.h" +#include "fuse.h" +#include "inode.h" +#include "iso9660fs.h" + +bool RespondData(int chl, const void* ptr, size_t count) +{ + return writeall(chl, ptr, count) == count; +} + +bool RespondHeader(int chl, size_t type, size_t size) +{ + struct fsm_msg_header hdr; + hdr.msgtype = type; + hdr.msgsize = size; + return RespondData(chl, &hdr, sizeof(hdr)); +} + +bool RespondMessage(int chl, size_t type, const void* ptr, size_t count) +{ + return RespondHeader(chl, type, count) && + RespondData(chl, ptr, count); +} + +bool RespondError(int chl, int errnum) +{ + struct fsm_resp_error body; + body.errnum = errnum; + return RespondMessage(chl, FSM_RESP_ERROR, &body, sizeof(body)); +} + +bool RespondSuccess(int chl) +{ + struct fsm_resp_success body; + return RespondMessage(chl, FSM_RESP_SUCCESS, &body, sizeof(body)); +} + +bool RespondStat(int chl, struct stat* st) +{ + struct fsm_resp_stat body; + body.st = *st; + return RespondMessage(chl, FSM_RESP_STAT, &body, sizeof(body)); +} + +bool RespondStatVFS(int chl, struct statvfs* stvfs) +{ + struct fsm_resp_statvfs body; + body.stvfs = *stvfs; + return RespondMessage(chl, FSM_RESP_STATVFS, &body, sizeof(body)); +} + +bool RespondSeek(int chl, off_t offset) +{ + struct fsm_resp_lseek body; + body.offset = offset; + return RespondMessage(chl, FSM_RESP_LSEEK, &body, sizeof(body)); +} + +bool RespondRead(int chl, const uint8_t* buf, size_t count) +{ + struct fsm_resp_read body; + body.count = count; + return RespondMessage(chl, FSM_RESP_READ, &body, sizeof(body)) && + RespondData(chl, buf, count); +} + +bool RespondReadlink(int chl, const uint8_t* buf, size_t count) +{ + struct fsm_resp_readlink body; + body.targetlen = count; + return RespondMessage(chl, FSM_RESP_READLINK, &body, sizeof(body)) && + RespondData(chl, buf, count); +} + +bool RespondWrite(int chl, size_t count) +{ + struct fsm_resp_write body; + body.count = count; + return RespondMessage(chl, FSM_RESP_WRITE, &body, sizeof(body)); +} + +bool RespondOpen(int chl, ino_t ino, mode_t type) +{ + struct fsm_resp_open body; + body.ino = ino; + body.type = type; + return RespondMessage(chl, FSM_RESP_OPEN, &body, sizeof(body)); +} + +bool RespondMakeDir(int chl, ino_t ino) +{ + struct fsm_resp_mkdir body; + body.ino = ino; + return RespondMessage(chl, FSM_RESP_MKDIR, &body, sizeof(body)); +} + +bool RespondReadDir(int chl, struct dirent* dirent) +{ + struct fsm_resp_readdirents body; + body.ino = dirent->d_ino; + body.type = dirent->d_type; + body.namelen = dirent->d_namlen; + return RespondMessage(chl, FSM_RESP_READDIRENTS, &body, sizeof(body)) && + RespondData(chl, dirent->d_name, dirent->d_namlen); +} + +bool RespondTCGetBlob(int chl, const void* data, size_t data_size) +{ + struct fsm_resp_tcgetblob body; + body.count = data_size; + return RespondMessage(chl, FSM_RESP_TCGETBLOB, &body, sizeof(body)) && + RespondData(chl, data, data_size); +} + +Inode* SafeGetInode(Filesystem* fs, ino_t ino) +{ + if ( (iso9660_ino_t) ino != ino ) + return errno = EBADF, (Inode*) ino; + return fs->GetInode((iso9660_ino_t) ino); +} + +void HandleRefer(int chl, struct fsm_req_refer* msg, Filesystem* fs) +{ + (void) chl; + if ( Inode* inode = SafeGetInode(fs, msg->ino) ) + { + inode->RemoteRefer(); + inode->Unref(); + } +} + +void HandleUnref(int chl, struct fsm_req_unref* msg, Filesystem* fs) +{ + (void) chl; + if ( Inode* inode = SafeGetInode(fs, msg->ino) ) + { + inode->RemoteUnref(); + inode->Unref(); + } +} + +void HandleSync(int chl, struct fsm_req_sync* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + inode->Unref(); + RespondSuccess(chl); +} + +void HandleStat(int chl, struct fsm_req_stat* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + struct stat st; + StatInode(inode, &st); + inode->Unref(); + RespondStat(chl, &st); +} + +void HandleChangeMode(int chl, struct fsm_req_chmod* /*msg*/, Filesystem* /*fs*/) +{ + RespondError(chl, EROFS); +} + +void HandleChangeOwner(int chl, struct fsm_req_chown* /*msg*/, Filesystem* /*fs*/) +{ + RespondError(chl, EROFS); +} + +void HandleUTimens(int chl, struct fsm_req_utimens* /*msg*/, Filesystem* /*fs*/) +{ + RespondError(chl, EROFS); +} + +void HandleTruncate(int chl, struct fsm_req_truncate* /*msg*/, Filesystem* /*fs*/) +{ + RespondError(chl, EROFS); +} + +void HandleSeek(int chl, struct fsm_req_lseek* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + if ( msg->whence == SEEK_SET ) + RespondSeek(chl, msg->offset); + else if ( msg->whence == SEEK_END ) + { + off_t inode_size = inode->Size(); + if ( (msg->offset < 0 && inode_size + msg->offset < 0) || + (0 <= msg->offset && OFF_MAX - inode_size < msg->offset) ) + RespondError(chl, EOVERFLOW); + else + RespondSeek(chl, msg->offset + inode_size); + } + else + RespondError(chl, EINVAL); + inode->Unref(); +} + +void HandleReadAt(int chl, struct fsm_req_pread* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + uint8_t* buf = (uint8_t*) malloc(msg->count); + if ( !buf ) { inode->Unref(); RespondError(chl, errno); return; } + ssize_t amount = inode->ReadAt(buf, msg->count, msg->offset); + inode->Unref(); + if ( amount < 0 ) { free(buf); RespondError(chl, errno); return; } + RespondRead(chl, buf, amount); + free(buf); +} + +void HandleWriteAt(int chl, struct fsm_req_pwrite* /*msg*/, Filesystem* /*fs*/) +{ + RespondError(chl, EROFS); +} + +void HandleOpen(int chl, struct fsm_req_open* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + Inode* result = inode->Open(path, msg->flags, FsModeFromHostMode(msg->mode)); + + free(path); + inode->Unref(); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondOpen(chl, result->inode_id, result->Mode() & S_IFMT); + result->Unref(); +} + +void HandleMakeDir(int chl, struct fsm_req_mkdir* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + inode->Unref(); + RespondError(chl, EROFS); +} + +void HandleReadDir(int chl, struct fsm_req_readdirents* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + if ( !S_ISDIR(inode->Mode()) ) + { + inode->Unref(); + RespondError(chl, ENOTDIR); + return; + } + union + { + struct dirent kernel_entry; + uint8_t padding[sizeof(struct dirent) + 256]; + }; + memset(&kernel_entry, 0, sizeof(kernel_entry)); + + 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 ( inode->ReadDirectory(&offset, &block, &block_id, + msg->rec_num ? NULL : name, &file_type, + &inode_id) ) + { + if ( !(msg->rec_num--) ) + { + size_t name_len = strlen(name); + kernel_entry.d_reclen = sizeof(kernel_entry) + name_len; + kernel_entry.d_ino = inode_id; + kernel_entry.d_dev = 0; + kernel_entry.d_type = HostDTFromFsDT(file_type); + kernel_entry.d_namlen = name_len; + memcpy(kernel_entry.d_name, name, name_len); + size_t dname_offset = offsetof(struct dirent, d_name); + padding[dname_offset + kernel_entry.d_namlen] = '\0'; + block->Unref(); + inode->Unref(); + RespondReadDir(chl, &kernel_entry); + return; + } + } + int errnum = errno; + if ( block ) + block->Unref(); + inode->Unref(); + + if ( errnum ) + { + RespondError(chl, errnum); + return; + } + + kernel_entry.d_reclen = sizeof(kernel_entry); + RespondReadDir(chl, &kernel_entry); +} + +void HandleIsATTY(int chl, struct fsm_req_isatty* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + RespondError(chl, ENOTTY); + inode->Unref(); +} + +void HandleUnlink(int chl, struct fsm_req_unlink* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + bool result = inode->Unlink(path, false); + free(path); + inode->Unref(); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondSuccess(chl); +} + +void HandleRemoveDir(int chl, struct fsm_req_rmdir* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + bool result = inode->RemoveDirectory(path); + free(path); + inode->Unref(); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondSuccess(chl); +} + +void HandleLink(int chl, struct fsm_req_link* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + Inode* dest = SafeGetInode(fs, msg->linkino); + if ( !dest ) { inode->Unref(); RespondError(chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + bool result = inode->Link(path, dest); + + free(path); + dest->Unref(); + inode->Unref(); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondSuccess(chl); +} + +void HandleSymlink(int chl, struct fsm_req_symlink* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->dirino); + if ( !inode ) { RespondError(chl, errno); return; } + inode->Unref(); + RespondError(chl, EROFS); +} + +void HandleReadlink(int chl, struct fsm_req_readlink* msg, Filesystem* fs) +{ + Inode* inode = SafeGetInode(fs, msg->ino); + if ( !inode ) { RespondError(chl, errno); return; } + if ( !ISO9660_S_ISLNK(inode->Mode()) ) { inode->Unref(); RespondError(chl, EINVAL); return; } + size_t count = inode->Size(); + uint8_t* buf = (uint8_t*) malloc(count); + if ( !buf ) { inode->Unref(); RespondError(chl, errno); return; } + ssize_t amount = inode->ReadLink(buf, count); + inode->Unref(); + if ( amount < 0 ) { RespondError(chl, errno); return; } + RespondReadlink(chl, buf, amount); + free(buf); +} + +void HandleRename(int chl, struct fsm_req_rename* msg, Filesystem* fs) +{ + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->oldnamelen+1 + msg->newnamelen+1); + if ( !path ) { RespondError(chl, errno); return; } + memcpy(path, pathraw, msg->oldnamelen); + path[msg->oldnamelen] = '\0'; + memcpy(path + msg->oldnamelen + 1, pathraw + msg->oldnamelen, msg->newnamelen); + path[msg->oldnamelen + 1 + msg->newnamelen] = '\0'; + + const char* oldname = path; + const char* newname = path + msg->oldnamelen + 1; + + Inode* olddir = SafeGetInode(fs, msg->olddirino); + if ( !olddir ) { free(path); RespondError(chl, errno); return; } + Inode* newdir = SafeGetInode(fs, msg->newdirino); + if ( !newdir ) { olddir->Unref(); free(path); RespondError(chl, errno); return; } + + bool result = newdir->Rename(olddir, oldname, newname); + + newdir->Unref(); + olddir->Unref(); + free(path); + + if ( !result ) { RespondError(chl, errno); return; } + + RespondSuccess(chl); +} + +void HandleStatVFS(int chl, struct fsm_req_statvfs* msg, Filesystem* fs) +{ + (void) msg; + struct statvfs stvfs; + stvfs.f_bsize = fs->block_size; + stvfs.f_frsize = fs->block_size; + stvfs.f_blocks = fs->device->device_size / fs->block_size; + stvfs.f_bfree = 0; + stvfs.f_bavail = 0; + stvfs.f_files = 0; + stvfs.f_ffree = 0; + stvfs.f_favail = 0; + stvfs.f_ffree = 0; + stvfs.f_fsid = 0; + stvfs.f_flag = ST_RDONLY; + stvfs.f_namemax = 255; + RespondStatVFS(chl, &stvfs); +} + +void HandleTCGetBlob(int chl, struct fsm_req_tcgetblob* msg, Filesystem* fs) +{ + char* nameraw = (char*) &(msg[1]); + char* name = (char*) malloc(msg->namelen + 1); + if ( !name ) + return (void) RespondError(chl, errno); + memcpy(name, nameraw, msg->namelen); + name[msg->namelen] = '\0'; + + //static const char index[] = "device-path\0filesystem-type\0filesystem-uuid\0mount-path\0"; + static const char index[] = "device-path\0filesystem-type\0mount-path\0"; + if ( !strcmp(name, "") ) + RespondTCGetBlob(chl, index, sizeof(index) - 1); + else if ( !strcmp(name, "device-path") ) + RespondTCGetBlob(chl, fs->device->path, strlen(fs->device->path)); + else if ( !strcmp(name, "filesystem-type") ) + RespondTCGetBlob(chl, "iso9660", strlen("iso9660")); + // TODO: Some kind of unique id. + //else if ( !strcmp(name, "filesystem-uuid") ) + // RespondTCGetBlob(chl, fs->sb->s_uuid, sizeof(fs->sb->s_uuid)); + else if ( !strcmp(name, "mount-path") ) + RespondTCGetBlob(chl, fs->mount_path, strlen(fs->mount_path)); + else + RespondError(chl, ENOENT); + + free(name); +} + +void HandleIncomingMessage(int chl, struct fsm_msg_header* hdr, Filesystem* fs) +{ + request_uid = hdr->uid; + request_gid = hdr->gid; + if ( (uint16_t) request_uid != request_uid || + (uint16_t) request_gid != request_gid ) + { + fprintf(stderr, "extfs: id exceeded 16-bit: uid=%ju gid=%ju\n", + (uintmax_t) request_uid, (uintmax_t) request_gid); + RespondError(chl, EOVERFLOW); + return; + } + typedef void (*handler_t)(int, void*, Filesystem*); + handler_t handlers[FSM_MSG_NUM] = { NULL }; + handlers[FSM_REQ_SYNC] = (handler_t) HandleSync; + handlers[FSM_REQ_STAT] = (handler_t) HandleStat; + handlers[FSM_REQ_CHMOD] = (handler_t) HandleChangeMode; + handlers[FSM_REQ_CHOWN] = (handler_t) HandleChangeOwner; + handlers[FSM_REQ_TRUNCATE] = (handler_t) HandleTruncate; + handlers[FSM_REQ_LSEEK] = (handler_t) HandleSeek; + handlers[FSM_REQ_PREAD] = (handler_t) HandleReadAt; + handlers[FSM_REQ_OPEN] = (handler_t) HandleOpen; + handlers[FSM_REQ_READDIRENTS] = (handler_t) HandleReadDir; + handlers[FSM_REQ_PWRITE] = (handler_t) HandleWriteAt; + handlers[FSM_REQ_ISATTY] = (handler_t) HandleIsATTY; + handlers[FSM_REQ_UTIMENS] = (handler_t) HandleUTimens; + handlers[FSM_REQ_MKDIR] = (handler_t) HandleMakeDir; + handlers[FSM_REQ_RMDIR] = (handler_t) HandleRemoveDir; + handlers[FSM_REQ_UNLINK] = (handler_t) HandleUnlink; + handlers[FSM_REQ_LINK] = (handler_t) HandleLink; + handlers[FSM_REQ_SYMLINK] = (handler_t) HandleSymlink; + handlers[FSM_REQ_READLINK] = (handler_t) HandleReadlink; + handlers[FSM_REQ_RENAME] = (handler_t) HandleRename; + handlers[FSM_REQ_REFER] = (handler_t) HandleRefer; + handlers[FSM_REQ_UNREF] = (handler_t) HandleUnref; + handlers[FSM_REQ_STATVFS] = (handler_t) HandleStatVFS; + handlers[FSM_REQ_TCGETBLOB] = (handler_t) HandleTCGetBlob; + if ( FSM_MSG_NUM <= hdr->msgtype || !handlers[hdr->msgtype] ) + { + fprintf(stderr, "extfs: message type %zu not supported\n", hdr->msgtype); + RespondError(chl, ENOTSUP); + return; + } + uint8_t body_buffer[65536]; + uint8_t* body = body_buffer; + if ( sizeof(body_buffer) < hdr->msgsize ) + { + body = (uint8_t*) mmap(NULL, hdr->msgsize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if ( (void*) body == MAP_FAILED ) + { + RespondError(chl, errno); + return; + } + } + if ( readall(chl, body, hdr->msgsize) == hdr->msgsize ) + handlers[hdr->msgtype](chl, body, fs); + else + RespondError(chl, errno); + if ( sizeof(body_buffer) < hdr->msgsize ) + munmap(body, hdr->msgsize); +} +static volatile bool should_terminate = false; + +void TerminationHandler(int) +{ + should_terminate = true; +} + +static void ready(void) +{ + const char* readyfd_env = getenv("READYFD"); + if ( !readyfd_env ) + return; + int readyfd = atoi(readyfd_env); + char c = '\n'; + write(readyfd, &c, 1); + close(readyfd); + unsetenv("READYFD"); +} + +int fsmarshall_main(const char* argv0, + const char* mount_path, + bool foreground, + Filesystem* fs, + Device* dev) +{ + (void) argv0; + + // Stat the root inode. + struct stat root_inode_st; + Inode* root_inode = fs->GetInode(fs->root_ino); + if ( !root_inode ) + err(1, "GetInode"); + StatInode(root_inode, &root_inode_st); + root_inode->Unref(); + + // Create a filesystem server connected to the kernel that we'll listen on. + int serverfd = fsm_mountat(AT_FDCWD, mount_path, &root_inode_st, 0); + if ( serverfd < 0 ) + err(1, "%s", mount_path); + + // Make sure the server isn't unexpectedly killed and data is lost. + signal(SIGINT, TerminationHandler); + signal(SIGTERM, TerminationHandler); + signal(SIGQUIT, TerminationHandler); + + // Become a background process in its own process group by default. + if ( !foreground ) + { + pid_t child_pid = fork(); + if ( child_pid < 0 ) + err(1, "fork"); + if ( child_pid ) + exit(0); + setpgid(0, 0); + } + else + ready(); + + // Listen for filesystem messages. + int channel; + while ( 0 <= (channel = accept(serverfd, NULL, NULL)) ) + { + if ( should_terminate ) + break; + struct fsm_msg_header hdr; + size_t amount; + if ( (amount = readall(channel, &hdr, sizeof(hdr))) != sizeof(hdr) ) + { + //warn("incomplete header: got %zi of %zu bytes", amount, sizeof(hdr)); + errno = 0; + continue; + } + HandleIncomingMessage(channel, &hdr, fs); + close(channel); + } + + // Garbage collect all open inode references. + while ( fs->mru_inode ) + { + Inode* inode = fs->mru_inode; + if ( inode->remote_reference_count ) + inode->RemoteUnref(); + else if ( inode->reference_count ) + inode->Unref(); + } + + close(serverfd); + + delete fs; + delete dev; + + return 0; +} + +#endif diff --git a/iso9660/fsmarshall.h b/iso9660/fsmarshall.h new file mode 100644 index 00000000..1d78d36a --- /dev/null +++ b/iso9660/fsmarshall.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015 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. + * + * fsmarshall.h + * Sortix fsmarshall frontend. + */ + +#ifndef FSMARSHALL_H +#define FSMARSHALL_H + +class Device; +class Filesystem; + +int fsmarshall_main(const char* argv0, + const char* mount_path, + bool foreground, + Filesystem* fs, + Device* dev); +#endif diff --git a/iso9660/fuse.cpp b/iso9660/fuse.cpp new file mode 100644 index 00000000..03878c16 --- /dev/null +++ b/iso9660/fuse.cpp @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2013, 2014, 2015, 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. + * + * fuse.cpp + * FUSE frontend. + */ + +#if !defined(__sortix__) + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSE_USE_VERSION 26 +#include + +#include "block.h" +#include "device.h" +#include "filesystem.h" +#include "fuse.h" +#include "inode.h" +#include "iso9660fs.h" + +#include // DEBUG + +struct iso9660_fuse_ctx +{ + Device* dev; + Filesystem* fs; +}; + +#ifndef S_SETABLE +#define S_SETABLE 02777 +#endif + +#define FUSE_FS (((struct iso9660_fuse_ctx*) (fuse_get_context()->private_data))->fs) + +void* iso9660_fuse_init(struct fuse_conn_info* /*conn*/) +{ + return fuse_get_context()->private_data; +} + +void iso9660_fuse_destroy(void* fs_private) +{ + struct iso9660_fuse_ctx* iso9660_fuse_ctx = + (struct iso9660_fuse_ctx*) fs_private; + while ( iso9660_fuse_ctx->fs->mru_inode ) + { + Inode* inode = iso9660_fuse_ctx->fs->mru_inode; + if ( inode->remote_reference_count ) + inode->RemoteUnref(); + else if ( inode->reference_count ) + inode->Unref(); + } + delete iso9660_fuse_ctx->fs; iso9660_fuse_ctx->fs = NULL; + delete iso9660_fuse_ctx->dev; iso9660_fuse_ctx->dev = NULL; +} + +Inode* iso9660_fuse_resolve_path(const char* path) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode(fs->root_ino); + if ( !inode ) + return (Inode*) NULL; + while ( path[0] ) + { + if ( *path == '/' ) + { + if ( !ISO9660_S_ISDIR(inode->Mode()) ) + return inode->Unref(), errno = ENOTDIR, (Inode*) NULL; + path++; + continue; + } + size_t elem_len = strcspn(path, "/"); + char* elem = strndup(path, elem_len); + if ( !elem ) + return inode->Unref(), errno = ENOTDIR, (Inode*) NULL; + path += elem_len; + Inode* next = inode->Open(elem, O_RDONLY, 0); + free(elem); + inode->Unref(); + if ( !next ) + return NULL; + inode = next; + } + return inode; +} + +// Assumes that the path doesn't end with / unless it's the root directory. +Inode* iso9660_fuse_parent_dir(const char** path_ptr) +{ + const char* path = *path_ptr; + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode(fs->root_ino); + if ( !inode ) + return (Inode*) NULL; + while ( strchr(path, '/') ) + { + if ( *path == '/' ) + { + if ( !ISO9660_S_ISDIR(inode->Mode()) ) + return inode->Unref(), errno = ENOTDIR, (Inode*) NULL; + path++; + continue; + } + size_t elem_len = strcspn(path, "/"); + char* elem = strndup(path, elem_len); + if ( !elem ) + return inode->Unref(), errno = ENOTDIR, (Inode*) NULL; + path += elem_len; + Inode* next = inode->Open(elem, O_RDONLY, 0); + free(elem); + inode->Unref(); + if ( !next ) + return (Inode*) NULL; + inode = next; + } + *path_ptr = *path ? path : "."; + assert(!strchr(*path_ptr, '/')); + return inode; +} + +int iso9660_fuse_getattr(const char* path, struct stat* st) +{ + Inode* inode = iso9660_fuse_resolve_path(path); + if ( !inode ) + return -errno; + StatInode(inode, st); + inode->Unref(); + return 0; +} + +int iso9660_fuse_fgetattr(const char* /*path*/, struct stat* st, + struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + StatInode(inode, st); + inode->Unref(); + return 0; +} + +int iso9660_fuse_readlink(const char* path, char* buf, size_t bufsize) +{ + Inode* inode = iso9660_fuse_resolve_path(path); + if ( !inode ) + return -errno; + if ( !ISO9660_S_ISLNK(inode->Mode()) ) + return inode->Unref(), -(errno = EINVAL); + if ( !bufsize ) + return inode->Unref(), -(errno = EINVAL); + ssize_t amount = inode->ReadLink((uint8_t*) buf, bufsize); + if ( amount < 0 ) + return inode->Unref(), -errno; + buf[(size_t) amount < bufsize ? (size_t) amount : bufsize - 1] = '\0'; + inode->Unref(); + return 0; +} + +int iso9660_fuse_mknod(const char* path, mode_t mode, dev_t dev) +{ + (void) path; + (void) mode; + (void) dev; + return -(errno = ENOSYS); +} + +int iso9660_fuse_mkdir(const char* path, mode_t /*mode*/) +{ + Inode* inode = iso9660_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + inode->Unref(); + return -(errno = EROFS); +} + +int iso9660_fuse_unlink(const char* path) +{ + Inode* inode = iso9660_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + bool success = inode->Unlink(path, false); + inode->Unref(); + return success ? 0 : -errno; +} + +int iso9660_fuse_rmdir(const char* path) +{ + Inode* inode = iso9660_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + bool success = inode->RemoveDirectory(path); + inode->Unref(); + return success ? 0 : -errno; +} + +int iso9660_fuse_symlink(const char* /*oldname*/, const char* newname) +{ + Inode* newdir = iso9660_fuse_parent_dir(&newname); + if ( !newdir ) + return -errno; + newdir->Unref(); + return -(errno = EROFS); +} + +int iso9660_fuse_rename(const char* oldname, const char* newname) +{ + Inode* olddir = iso9660_fuse_parent_dir(&oldname); + if ( !olddir ) + return -errno; + Inode* newdir = iso9660_fuse_parent_dir(&newname); + if ( !newdir ) + return olddir->Unref(), -errno; + bool success = newdir->Rename(olddir, oldname, newname); + newdir->Unref(); + olddir->Unref(); + return success ? 0 : -errno; +} + +int iso9660_fuse_link(const char* oldname, const char* newname) +{ + Inode* inode = iso9660_fuse_resolve_path(oldname); + if ( !inode ) + return -errno; + Inode* newdir = iso9660_fuse_parent_dir(&newname); + if ( !newdir ) + return inode->Unref(), -errno; + bool success = inode->Link(newname, inode); + newdir->Unref(); + inode->Unref(); + return success ? 0 : -errno; +} + +int iso9660_fuse_chmod(const char* path, mode_t mode) +{ + Inode* inode = iso9660_fuse_resolve_path(path); + if ( !inode ) + return -errno; + (void) mode; + return inode->Unref(), -(errno = EROFS); +} + +int iso9660_fuse_chown(const char* path, uid_t owner, gid_t group) +{ + Inode* inode = iso9660_fuse_resolve_path(path); + if ( !inode ) + return -errno; + (void) owner; + (void) group; + return inode->Unref(), -(errno = EROFS); +} + +int iso9660_fuse_truncate(const char* path, off_t size) +{ + Inode* inode = iso9660_fuse_resolve_path(path); + if ( !inode ) + return -errno; + (void) size; + return inode->Unref(), -(errno = EROFS); +} + +int iso9660_fuse_ftruncate(const char* /*path*/, off_t size, + struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + (void) size; + return inode->Unref(), -(errno = EROFS); +} + +int iso9660_fuse_open(const char* path, struct fuse_file_info* fi) +{ + int flags = fi->flags; + Inode* dir = iso9660_fuse_parent_dir(&path); + if ( !dir ) + return -errno; + Inode* result = dir->Open(path, flags, 0); + dir->Unref(); + if ( !result ) + return -errno; + fi->fh = (uint64_t) result->inode_id; + fi->keep_cache = 1; + result->RemoteRefer(); + result->Unref(); + return 0; +} + +int iso9660_fuse_access(const char* path, int mode) +{ + Inode* dir = iso9660_fuse_parent_dir(&path); + if ( !dir ) + return -errno; + Inode* result = dir->Open(path, O_RDONLY, 0); + dir->Unref(); + if ( !result ) + return -errno; + (void) mode; + result->Unref(); + return 0; +} + +int iso9660_fuse_create(const char* path, mode_t mode, struct fuse_file_info* fi) +{ + int flags = fi->flags | O_CREAT; + Inode* inode = iso9660_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + Inode* result = inode->Open(path, flags, FsModeFromHostMode(mode)); + inode->Unref(); + if ( !result ) + return -errno; + fi->fh = (uint64_t) result->inode_id; + fi->keep_cache = 1; + result->RemoteRefer(); + result->Unref(); + return 0; +} + +int iso9660_fuse_opendir(const char* path, struct fuse_file_info* fi) +{ + return iso9660_fuse_open(path, fi); +} + +int iso9660_fuse_read(const char* /*path*/, char* buf, size_t count, + off_t offset, struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + if ( INT_MAX < count ) + count = INT_MAX; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + ssize_t result = inode->ReadAt((uint8_t*) buf, count, offset); + inode->Unref(); + return 0 <= result ? (int) result : -errno; +} + +int iso9660_fuse_write(const char* /*path*/, const char* /*buf*/, + size_t /*count*/, off_t /*offset*/, + struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + inode->Unref(); + return -(errno = EROFS); +} + +int iso9660_fuse_statfs(const char* /*path*/, struct statvfs* stvfs) +{ + memset(stvfs, 0, sizeof(*stvfs)); + Filesystem* fs = FUSE_FS; + stvfs->f_bsize = fs->block_size; + stvfs->f_frsize = fs->block_size; + stvfs->f_blocks = fs->device->device_size / fs->block_size; + stvfs->f_bfree = 0; + stvfs->f_bavail = 0; + stvfs->f_files = 0; + stvfs->f_ffree = 0; + stvfs->f_favail = 0; + stvfs->f_ffree = 0; + stvfs->f_fsid = 0; + stvfs->f_flag = ST_RDONLY; + stvfs->f_namemax = 255; + return 0; +} + +int iso9660_fuse_flush(const char* /*path*/, struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + inode->Unref(); + return 0; +} + +int iso9660_fuse_release(const char* /*path*/, struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + inode->RemoteUnref(); + inode->Unref(); + return 0; +} + +int iso9660_fuse_releasedir(const char* path, struct fuse_file_info* fi) +{ + return iso9660_fuse_release(path, fi); +} + +int iso9660_fuse_fsync(const char* /*path*/, int data, struct fuse_file_info* fi) +{ + (void) data; + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + inode->Unref(); + return 0; +} + +/*int iso9660_fuse_syncdir(const char* path, int data, struct fuse_file_info* fi) +{ + return iso9660_fuse_sync(path, data, fi); +}*/ + +/*int iso9660_fuse_setxattr(const char *, const char *, const char *, size_t, int) +{ + return -(errno = ENOSYS); +}*/ + +/*int iso9660_fuse_getxattr(const char *, const char *, char *, size_t) +{ + return -(errno = ENOSYS); +}*/ + +/*int iso9660_fuse_listxattr(const char *, char *, size_t) +{ + return -(errno = ENOSYS); +}*/ + +/*int iso9660_fuse_removexattr(const char *, const char *) +{ + return -(errno = ENOSYS); +}*/ + +int iso9660_fuse_readdir(const char* /*path*/, void* buf, + fuse_fill_dir_t filler, off_t rec_num, + struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((iso9660_ino_t) fi->fh); + if ( !inode ) + return -errno; + if ( !S_ISDIR(inode->Mode()) ) + return inode->Unref(), -(errno = ENOTDIR); + 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 ( inode->ReadDirectory(&offset, &block, &block_id, + rec_num ? NULL : name, &file_type, &inode_id) ) + { + if ( !rec_num || !rec_num-- ) + { + if ( filler(buf, name, NULL, 0) ) + { + block->Unref(); + inode->Unref(); + return 0; + } + } + } + int errnum = errno; + if ( block ) + block->Unref(); + inode->Unref(); + if ( errnum ) + return -errnum; + return 0; +} + +/*int iso9660_fuse_lock(const char*, struct fuse_file_info*, int, struct flock*) +{ + return -(errno = ENOSYS); +}*/ + +int iso9660_fuse_utimens(const char* path, const struct timespec tv[2]) +{ + Inode* inode = iso9660_fuse_resolve_path(path); + if ( !inode ) + return -errno; + (void) tv; + return inode->Unref(), -(errno = EROFS); +} + +/*int iso9660_fuse_bmap(const char*, size_t blocksize, uint64_t* idx) +{ + return -(errno = ENOSYS); +}*/ + +int iso9660_fuse_main(const char* argv0, + const char* mount_path, + bool foreground, + Filesystem* fs, + Device* dev) +{ + struct fuse_operations operations; + memset(&operations, 0, sizeof(operations)); + + operations.access = iso9660_fuse_access; + operations.chmod = iso9660_fuse_chmod; + operations.chown = iso9660_fuse_chown; + operations.create = iso9660_fuse_create; + operations.destroy = iso9660_fuse_destroy; + operations.fgetattr = iso9660_fuse_fgetattr; + operations.flush = iso9660_fuse_flush; + operations.fsync = iso9660_fuse_fsync; + operations.ftruncate = iso9660_fuse_ftruncate; + operations.getattr = iso9660_fuse_getattr; + operations.init = iso9660_fuse_init; + operations.link = iso9660_fuse_link; + operations.mkdir = iso9660_fuse_mkdir; + operations.mknod = iso9660_fuse_mknod; + operations.opendir = iso9660_fuse_opendir; + operations.open = iso9660_fuse_open; + operations.readdir = iso9660_fuse_readdir; + operations.read = iso9660_fuse_read; + operations.readlink = iso9660_fuse_readlink; + operations.releasedir = iso9660_fuse_releasedir; + operations.release = iso9660_fuse_release; + operations.rename = iso9660_fuse_rename; + operations.rmdir = iso9660_fuse_rmdir; + operations.statfs = iso9660_fuse_statfs; + operations.symlink = iso9660_fuse_symlink; + operations.truncate = iso9660_fuse_truncate; + operations.unlink = iso9660_fuse_unlink; + operations.utimens = iso9660_fuse_utimens; + operations.write = iso9660_fuse_write; + + operations.flag_nullpath_ok = 1; + operations.flag_nopath = 1; + + char* argv_fuse[] = + { + (char*) argv0, + (char*) "-s", + (char*) (foreground ? "-f" : mount_path), + (char*) (foreground ? mount_path : NULL), + (char*) NULL, + }; + + int argc_fuse = sizeof(argv_fuse) / sizeof(argv_fuse[0]) - 1; + + struct iso9660_fuse_ctx iso9660_fuse_ctx; + iso9660_fuse_ctx.fs = fs; + iso9660_fuse_ctx.dev = dev; + + return fuse_main(argc_fuse, argv_fuse, &operations, &iso9660_fuse_ctx); +} + +#endif diff --git a/iso9660/fuse.h b/iso9660/fuse.h new file mode 100644 index 00000000..6cf51a7f --- /dev/null +++ b/iso9660/fuse.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2015 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. + * + * fuse.h + * FUSE frontend. + */ + +#ifndef FUSE_H +#define FUSE_H + +class Device; +class Filesystem; + +int iso9660_fuse_main(const char* argv0, + const char* mount_path, + bool foreground, + Filesystem* fs, + Device* dev); + +#endif diff --git a/iso9660/inode.cpp b/iso9660/inode.cpp new file mode 100644 index 00000000..affb95a1 --- /dev/null +++ b/iso9660/inode.cpp @@ -0,0 +1,614 @@ +/* + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + // TODO: 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; +} diff --git a/iso9660/inode.h b/iso9660/inode.h new file mode 100644 index 00000000..689bb67c --- /dev/null +++ b/iso9660/inode.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2013, 2014, 2015, 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.h + * Filesystem inode. + */ + +#ifndef INODE_H +#define INODE_H + +#include "iso9660.h" + +class Block; +class Filesystem; + +class Inode +{ +public: + Inode(Filesystem* filesystem, iso9660_ino_t inode_id); + ~Inode(); + +public: + Inode* prev_inode; + Inode* next_inode; + Inode* prev_hashed; + Inode* next_hashed; + Block* data_block; + struct iso9660_dirent* data; + Filesystem* filesystem; + size_t reference_count; + size_t remote_reference_count; + iso9660_ino_t inode_id; + iso9660_ino_t parent_inode_id; + uint32_t uid; + uint32_t gid; + uint64_t size; + uint64_t time; + uint32_t mode; + uint32_t nlink; + +public: + void Parse(); + uint32_t Mode(); + uint32_t UserId(); + uint32_t GroupId(); + uint64_t Size(); + uint64_t Time(); + Block* GetBlock(uint32_t offset); + bool FindParentInode(uint64_t parent_lba, uint64_t parent_size); + bool 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); + Inode* Open(const char* elem, int flags, mode_t mode); + bool Link(const char* elem, Inode* dest); + bool Unlink(const char* elem, bool directories, bool force=false); + Inode* UnlinkKeep(const char* elem, bool directories, bool force=false); + ssize_t ReadLink(uint8_t* buf, size_t bufsize); + ssize_t ReadAt(uint8_t* buffer, size_t count, off_t offset); + bool Rename(Inode* olddir, const char* oldname, const char* newname); + bool RemoveDirectory(const char* path); + void Refer(); + void Unref(); + void RemoteRefer(); + void RemoteUnref(); + void Use(); + void Unlink(); + void Prelink(); + +}; + +#endif diff --git a/iso9660/ioleast.h b/iso9660/ioleast.h new file mode 100644 index 00000000..5648de48 --- /dev/null +++ b/iso9660/ioleast.h @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2012, 2013, 2015 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. + * + * ioleast.h + * Versions of {,p}{read,write} that don't return until it has returned as much + * data as requested, end of file, or an error occurs. This is sometimes needed + * as read(2) and write(2) is not always guaranteed to fill up the entire + * buffer or write it all. + */ + +#ifndef SORTIX_COMPATIBILITY_INCLUDE_IOLEAST_H +#define SORTIX_COMPATIBILITY_INCLUDE_IOLEAST_H + +#if defined(__sortix__) || defined(__sortix_libc__) + +#include_next + +#else + +#include + +#include +#include +#include +#include +#include + +#if !defined(EEOF) && defined(EIO) +#define EEOF EIO +#endif + +__attribute__((unused)) static inline +size_t readleast(int fd, void* buf_ptr, size_t least, size_t max) +{ + assert(least <= max); + unsigned char* buf = (unsigned char*) buf_ptr; + size_t done = 0; + do + { + ssize_t amount = read(fd, buf + done, max - done); + if ( amount < 0 ) + return done; + if ( !amount && done < least ) + return errno = EEOF, done; + if ( !amount ) + break; + done += amount; + } while ( done < least ); + return done; +} + +__attribute__((unused)) static inline +size_t writeleast(int fd, const void* buf_ptr, size_t least, size_t max) +{ + assert(least <= max); + const unsigned char* buf = (const unsigned char*) buf_ptr; + size_t done = 0; + do + { + ssize_t amount = write(fd, buf + done, max - done); + if ( amount < 0 ) + return done; + if ( !amount && done < least ) + return errno = EEOF, done; + if ( !amount ) + break; + done += amount; + } while ( done < least ); + return done; +} + +__attribute__((unused)) static inline +size_t preadleast(int fd, void* buf_ptr, size_t least, size_t max, off_t off) +{ + assert(least <= max); + unsigned char* buf = (unsigned char*) buf_ptr; + size_t done = 0; + do + { + ssize_t amount = pread(fd, buf + done, max - done, off + done); + if ( amount < 0 ) + return done; + if ( !amount && done < least ) + return errno = EEOF, done; + if ( !amount ) + break; + done += amount; + } while ( done < least ); + return done; +} + +__attribute__((unused)) static inline +size_t pwriteleast(int fd, const void* buf_ptr, size_t least, size_t max, off_t off) +{ + assert(least <= max); + const unsigned char* buf = (const unsigned char*) buf_ptr; + size_t done = 0; + do + { + ssize_t amount = pwrite(fd, buf + done, max - done, off + done); + if ( amount < 0 ) + return done; + if ( !amount && done < least ) + return errno = EEOF, done; + if ( !amount ) + break; + done += amount; + } while ( done < least ); + return done; +} + +__attribute__((unused)) static inline +size_t readall(int fd, void* buf, size_t count) +{ + return readleast(fd, buf, count, count); +} + +__attribute__((unused)) static inline +size_t writeall(int fd, const void* buf, size_t count) +{ + return writeleast(fd, buf, count, count); +} + +__attribute__((unused)) static inline +size_t preadall(int fd, void* buf, size_t count, off_t off) +{ + return preadleast(fd, buf, count, count, off); +} + +__attribute__((unused)) static inline +size_t pwriteall(int fd, const void* buf, size_t count, off_t off) +{ + return pwriteleast(fd, buf, count, count, off); +} + +#endif + +#endif diff --git a/iso9660/iso9660.h b/iso9660/iso9660.h new file mode 100644 index 00000000..5721554c --- /dev/null +++ b/iso9660/iso9660.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 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. + * + * iso9660.h + * Data structures for the ISO 9660 filesystem. + */ + +#ifndef ISO9660_H +#define ISO9660_H + +#include + +const uint8_t TYPE_BOOT_RECORD = 0x00; +const uint8_t TYPE_PRIMARY_VOLUME_DESCRIPTOR = 0x01; +const uint8_t TYPE_VOLUME_DESCRIPTOR_SET_TERMINATOR = 0xFF; + +struct iso9660_pvd /* primary volume descriptor */ +{ + uint8_t type; + char standard_identifier[5]; + uint8_t version; + uint8_t unused1; + char system_identifier[32]; + char volume_identifier[32]; + uint8_t unused2[8]; + uint32_t volume_space_size_le; + uint32_t volume_space_size_be; + uint8_t unused3[32]; + uint16_t volume_set_size_le; + uint16_t volume_set_size_be; + uint16_t volume_sequence_number_le; + uint16_t volume_sequence_number_be; + uint16_t logical_block_size_le; + uint16_t logical_block_size_be; + uint32_t path_table_size_le; + uint32_t path_table_size_be; + uint32_t path_table_lba_le; + uint32_t path_table_opt_lba_le; + uint32_t path_table_lba_be; + uint32_t path_table_opt_lba_be; + uint8_t root_dirent[34]; + char volume_set_identifier[128]; + char publisher_identifier[128]; + char data_preparer_identifier[128]; + char application_identifier[128]; + char copyright_file_identifier[37]; + char abstract_file_identifier[37]; + char bibliographic_file_identifier[37]; + char creation_datetime[17]; + char modification_datetime[17]; + char expiration_datetime[17]; + char effective_datetime[17]; + uint8_t file_structure_version; + uint8_t unused4; + uint8_t application_use[512]; + uint8_t reserved[653]; +}; + +static_assert(sizeof(struct iso9660_pvd) == 2048, + "sizeof(struct iso9660_pvd) == 2048"); + +typedef uint64_t iso9660_ino_t; + +struct iso9660_dirent +{ + uint8_t unused; +}; + +#define ISO9660_DIRENT_FLAG_DIR (1 << 1) + +#define ISO9660_S_IFMT (0170000) +#define ISO9660_S_IFIFO (0010000) +#define ISO9660_S_IFCHR (0020000) +#define ISO9660_S_IFDIR (0040000) +#define ISO9660_S_IFBLK (0060000) +#define ISO9660_S_IFREG (0100000) +#define ISO9660_S_IFLNK (0120000) +#define ISO9660_S_IFSOCK (0140000) + +#define ISO9660_S_ISSOCK(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFSOCK) +#define ISO9660_S_ISLNK(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFLNK) +#define ISO9660_S_ISREG(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFREG) +#define ISO9660_S_ISBLK(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFBLK) +#define ISO9660_S_ISDIR(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFDIR) +#define ISO9660_S_ISCHR(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFCHR) +#define ISO9660_S_ISFIFO(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFIFO) + +static const uint8_t ISO9660_FT_UNKNOWN = 0; +static const uint8_t ISO9660_FT_REG_FILE = 1; +static const uint8_t ISO9660_FT_DIR = 2; +static const uint8_t ISO9660_FT_CHRDEV = 3; +static const uint8_t ISO9660_FT_BLKDEV = 4; +static const uint8_t ISO9660_FT_FIFO = 5; +static const uint8_t ISO9660_FT_SOCK = 6; +static const uint8_t ISO9660_FT_SYMLINK = 7; + +static inline uint8_t ISO9660_FT_OF_MODE(mode_t mode) +{ + if ( ISO9660_S_ISREG(mode) ) + return ISO9660_FT_REG_FILE; + if ( ISO9660_S_ISDIR(mode) ) + return ISO9660_FT_DIR; + if ( ISO9660_S_ISCHR(mode) ) + return ISO9660_FT_CHRDEV; + if ( ISO9660_S_ISBLK(mode) ) + return ISO9660_FT_BLKDEV; + if ( ISO9660_S_ISFIFO(mode) ) + return ISO9660_FT_FIFO; + if ( ISO9660_S_ISSOCK(mode) ) + return ISO9660_FT_SOCK; + if ( ISO9660_S_ISLNK(mode) ) + return ISO9660_FT_SYMLINK; + return ISO9660_FT_UNKNOWN; +} + +#endif diff --git a/iso9660/iso9660fs.cpp b/iso9660/iso9660fs.cpp new file mode 100644 index 00000000..e19de3eb --- /dev/null +++ b/iso9660/iso9660fs.cpp @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2013, 2014, 2015, 2016, 2022, 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. + * + * iso9660fs.cpp + * Implementation of the ISO 9660 filesystem. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__sortix__) +#include "fsmarshall.h" +#else +#include "fuse.h" +#endif + +#include "block.h" +#include "device.h" +#include "filesystem.h" +#include "inode.h" +#include "ioleast.h" +#include "iso9660.h" +#include "iso9660fs.h" +#include "util.h" + +uid_t request_uid; +uid_t request_gid; + +mode_t HostModeFromFsMode(uint32_t mode) +{ + mode_t hostmode = mode & 07777; + if ( ISO9660_S_ISSOCK(mode) ) hostmode |= S_IFSOCK; + if ( ISO9660_S_ISLNK(mode) ) hostmode |= S_IFLNK; + if ( ISO9660_S_ISREG(mode) ) hostmode |= S_IFREG; + if ( ISO9660_S_ISBLK(mode) ) hostmode |= S_IFBLK; + if ( ISO9660_S_ISDIR(mode) ) hostmode |= S_IFDIR; + if ( ISO9660_S_ISCHR(mode) ) hostmode |= S_IFCHR; + if ( ISO9660_S_ISFIFO(mode) ) hostmode |= S_IFIFO; + return hostmode; +} + +uint32_t FsModeFromHostMode(mode_t hostmode) +{ + uint32_t mode = hostmode & 07777; + if ( S_ISSOCK(hostmode) ) mode |= ISO9660_S_IFSOCK; + if ( S_ISLNK(hostmode) ) mode |= ISO9660_S_IFLNK; + if ( S_ISREG(hostmode) ) mode |= ISO9660_S_IFREG; + if ( S_ISBLK(hostmode) ) mode |= ISO9660_S_IFBLK; + if ( S_ISDIR(hostmode) ) mode |= ISO9660_S_IFDIR; + if ( S_ISCHR(hostmode) ) mode |= ISO9660_S_IFCHR; + if ( S_ISFIFO(hostmode) ) mode |= ISO9660_S_IFIFO; + return mode; +} + +uint8_t HostDTFromFsDT(uint8_t fsdt) +{ + switch ( fsdt ) + { + case ISO9660_FT_UNKNOWN: return DT_UNKNOWN; + case ISO9660_FT_REG_FILE: return DT_REG; + case ISO9660_FT_DIR: return DT_DIR; + case ISO9660_FT_CHRDEV: return DT_CHR; + case ISO9660_FT_BLKDEV: return DT_BLK; + case ISO9660_FT_FIFO: return DT_FIFO; + case ISO9660_FT_SOCK: return DT_SOCK; + case ISO9660_FT_SYMLINK: return DT_LNK; + } + return DT_UNKNOWN; +} + +void StatInode(Inode* inode, struct stat* st) +{ + memset(st, 0, sizeof(*st)); + st->st_ino = inode->inode_id; + st->st_mode = HostModeFromFsMode(inode->Mode()); + st->st_nlink = inode->nlink; + st->st_uid = inode->uid; + st->st_gid = inode->gid; + st->st_size = inode->size; + // TODO: Rock Ridge. + st->st_atim.tv_sec = inode->Time(); + st->st_atim.tv_nsec = 0; + // TODO: Rock Ridge. + st->st_ctim.tv_sec = inode->Time(); + st->st_ctim.tv_nsec = 0; + // TODO: Rock Ridge. + st->st_mtim.tv_sec = inode->Time(); + st->st_mtim.tv_nsec = 0; + st->st_blksize = inode->filesystem->block_size; + st->st_blocks = divup(st->st_size, (off_t) 512); +} + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + { + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } + } +} + +static void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... DEVICE [MOUNT-POINT]\n", argv0); +} + +static void version(FILE* fp, const char* argv0) +{ + fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR); +} + +int main(int argc, char* argv[]) +{ + const char* argv0 = argv[0]; + const char* pretend_mount_path = NULL; + bool foreground = false; + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' || !arg[1] ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + case 'r': break; + default: + fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); + help(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) + help(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--version") ) + version(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--background") ) + foreground = false; + else if ( !strcmp(arg, "--foreground") ) + foreground = true; + else if ( !strcmp(arg, "--read") ) + { + } + else if ( !strncmp(arg, "--pretend-mount-path=", strlen("--pretend-mount-path=")) ) + pretend_mount_path = arg + strlen("--pretend-mount-path="); + else if ( !strcmp(arg, "--pretend-mount-path") ) + { + if ( i+1 == argc ) + { + fprintf(stderr, "%s: --pretend-mount-path: Missing operand\n", argv0); + exit(1); + } + pretend_mount_path = argv[++i]; + argv[i] = NULL; + } + else + { + fprintf(stderr, "%s: unknown option: %s\n", argv0, arg); + help(stderr, argv0); + exit(1); + } + } + + if ( argc == 1 ) + { + help(stdout, argv0); + exit(0); + } + + compact_arguments(&argc, &argv); + + const char* device_path = 2 <= argc ? argv[1] : NULL; + const char* mount_path = 3 <= argc ? argv[2] : NULL; + + if ( !device_path ) + { + help(stderr, argv0); + exit(1); + } + + if ( !pretend_mount_path ) + pretend_mount_path = mount_path; + + int fd = open(device_path, O_RDONLY); + if ( fd < 0 ) + err(1, "%s", device_path); + + // Search for the Primary Volume Descriptor. + off_t block_size = 2048; + struct iso9660_pvd pvd; + off_t pvd_offset; + for ( uint32_t i = 16; true; i++ ) + { + pvd_offset = block_size * i; + if ( preadall(fd, &pvd, sizeof(pvd), pvd_offset) != sizeof(pvd) ) + { + if ( errno == EEOF ) + errx(1, "Not a valid ISO 9660 filesystem: %s", device_path); + else + err(1, "read: %s", device_path); + } + if ( memcmp(pvd.standard_identifier, "CD001", 5) != 0 ) + errx(1, "Not a valid ISO 9660 filesystem: %s", device_path); + if ( pvd.type == TYPE_PRIMARY_VOLUME_DESCRIPTOR ) + break; + if ( pvd.type == TYPE_VOLUME_DESCRIPTOR_SET_TERMINATOR ) + errx(1, "Not a valid ISO 9660 filesystem: %s", device_path); + } + + if ( pvd.version != 1 || pvd.file_structure_version != 1 ) + errx(1, "Unsupported ISO 9660 filesystem version: %s", device_path); + // TODO: Test if appropriate power of two and allow. + if ( le32toh(pvd.logical_block_size_le) != block_size ) + errx(1, "Unsupported ISO 9660 block size: %s", device_path); + block_size = le32toh(pvd.logical_block_size_le); + + Device* dev = new Device(fd, device_path, block_size); + if ( !dev ) + err(1, "malloc"); + Filesystem* fs = new Filesystem(dev, pretend_mount_path, pvd_offset); + if ( !fs ) + err(1, "%s", device_path); + + // Change the root inode to be its own . entry instead of the pvd record, so + // parent directories correctly .. to it, and so Rock Ridge extensions work. + Inode* root = fs->GetInode(fs->root_ino); + if ( !root ) + err(1, "%s: GetInode", device_path); + fs->root_ino = 0; + uint32_t root_lba; + uint32_t root_size; + memcpy(&root_lba, pvd.root_dirent + 2, sizeof(root_lba)); + memcpy(&root_size, pvd.root_dirent + 10, sizeof(root_size)); + root->FindParentInode(root_lba, root_size); + fs->root_ino = root->parent_inode_id; + root->Unref(); + + if ( !mount_path ) + return 0; + +#if defined(__sortix__) + return fsmarshall_main(argv0, mount_path, foreground, fs, dev); +#else + return iso9660_fuse_main(argv0, mount_path, foreground, fs, dev); +#endif +} diff --git a/iso9660/iso9660fs.h b/iso9660/iso9660fs.h new file mode 100644 index 00000000..0e0dab86 --- /dev/null +++ b/iso9660/iso9660fs.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 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. + * + * iso9660fs.h + * Implementation of the ISO 9660 filesystem. + */ + +#ifndef ISO9660FS_H +#define ISO9660FS_H + +extern uid_t request_uid; +extern gid_t request_gid; + +class Inode; + +mode_t HostModeFromFsMode(uint32_t fsmode); +uint32_t FsModeFromHostMode(mode_t hostmode); +uint8_t HostDTFromFsDT(uint8_t fsdt); +void StatInode(Inode* inode, struct stat* st); + +#endif diff --git a/iso9660/util.h b/iso9660/util.h new file mode 100644 index 00000000..e1f55adc --- /dev/null +++ b/iso9660/util.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013, 2015 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. + * + * util.h + * Utility functions for the filesystem implementation. + */ + +#ifndef UTIL_H +#define UTIL_H + +template T divup(T a, T b) +{ + return a/b + (a % b ? 1 : 0); +} + +template T roundup(T a, T b) +{ + return a % b ? a + b - a % b : a; +} + +static inline bool checkbit(const uint8_t* bitmap, size_t bit) +{ + uint8_t bits = bitmap[bit / 8UL]; + return bits & (1U << (bit % 8UL)); +} + +static inline void setbit(uint8_t* bitmap, size_t bit) +{ + bitmap[bit / 8UL] |= 1U << (bit % 8UL); +} + +static inline void clearbit(uint8_t* bitmap, size_t bit) +{ + bitmap[bit / 8UL] &= ~(1U << (bit % 8UL)); +} + +#endif diff --git a/libmount/Makefile b/libmount/Makefile index 43fca2ac..ca414efb 100644 --- a/libmount/Makefile +++ b/libmount/Makefile @@ -20,6 +20,7 @@ extended.o \ filesystem.o \ gpt.o \ harddisk.o \ +iso9660.o \ mbr.o \ partition.o \ uuid.o \ diff --git a/libmount/filesystem.c b/libmount/filesystem.c index 30c2bbfd..9af9c130 100644 --- a/libmount/filesystem.c +++ b/libmount/filesystem.c @@ -32,6 +32,7 @@ #include #include #include +#include #include const char* filesystem_error_string(enum filesystem_error error) @@ -55,6 +56,7 @@ static const struct filesystem_handler* filesystem_handlers[] = &biosboot_handler, &extended_handler, &ext2_handler, + &iso9660_handler, NULL, }; diff --git a/libmount/include/mount/iso9660.h b/libmount/include/mount/iso9660.h new file mode 100644 index 00000000..566a86d8 --- /dev/null +++ b/libmount/include/mount/iso9660.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 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. + * + * mount/iso9660.h + * ISO 9660 filesystem. + */ + +#ifndef INCLUDE_MOUNT_ISO9660_H +#define INCLUDE_MOUNT_ISO9660_H + +#include + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +extern const struct filesystem_handler iso9660_handler; + +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +#endif diff --git a/libmount/iso9660.c b/libmount/iso9660.c new file mode 100644 index 00000000..bf290dab --- /dev/null +++ b/libmount/iso9660.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 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. + * + * iso9660.c + * iso9660 filesystem. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "util.h" + +static size_t iso9660_probe_amount(struct blockdevice* bdev) +{ + (void) bdev; + size_t block_size = 2048 /* TODO: Actual block size? */; + return 17 * block_size; +} + +static bool iso9660_probe(struct blockdevice* bdev, + const unsigned char* leading, + size_t amount) +{ + (void) bdev; + size_t block_size = 2048 /* TODO: Actual block size? */; + if ( amount < 17 * block_size ) + return false; + size_t offset = 16 * block_size; + return !memcmp(leading + offset + 1, "CD001", 5); +} + +static void iso9660_release(struct filesystem* fs) +{ + if ( !fs ) + return; + free(fs); +} + +static enum filesystem_error iso9660_inspect(struct filesystem** fs_ptr, + struct blockdevice* bdev) +{ + *fs_ptr = NULL; + struct filesystem* fs = CALLOC_TYPE(struct filesystem); + if ( !fs ) + return FILESYSTEM_ERROR_ERRNO; + fs->bdev = bdev; + fs->handler = &iso9660_handler; + fs->handler_private = NULL; + fs->fstype_name = "iso9660"; + fs->driver = "iso9660fs"; + size_t block_size = 2048 /* TODO: Actual block size? */; + size_t offset = 16 * block_size; + uint8_t pvd[2048]; + if ( blockdevice_preadall(bdev, pvd, sizeof(pvd), offset) != sizeof(pvd) ) + return iso9660_release(fs), FILESYSTEM_ERROR_ERRNO; + // TODO: LABEL support using the volume name. + char vol_set[128 + 1]; + memcpy(vol_set, pvd + 190, 128); + vol_set[128] = '\0'; + for ( size_t i = 128; i && vol_set[i-1] == ' '; i-- ) + vol_set[i-1] = '\0'; + if ( uuid_validate(vol_set) ) + { + fs->flags |= FILESYSTEM_FLAG_UUID; + uuid_from_string(fs->uuid, vol_set); + } + return *fs_ptr = fs, FILESYSTEM_ERROR_NONE; +} + +const struct filesystem_handler iso9660_handler = +{ + .handler_name = "iso9660", + .probe_amount = iso9660_probe_amount, + .probe = iso9660_probe, + .inspect = iso9660_inspect, + .release = iso9660_release, +}; diff --git a/libmount/partition.c b/libmount/partition.c index 0c2054fb..e329bad0 100644 --- a/libmount/partition.c +++ b/libmount/partition.c @@ -163,6 +163,9 @@ bool blockdevice_probe_partition_table_type(enum partition_table_type* result_ou memcpy(&mbr, leading, sizeof(mbr)); if ( mbr.signature[0] != 0x55 && mbr.signature[1] != 0xAA ) break; + // Ignore the partition table on live cdrom images. + if ( mbr.partitions[0][4 /*system_id*/] == 0xCD ) + break; result = PARTITION_TABLE_TYPE_MBR; goto out; } while ( 0 ); diff --git a/share/man/man7/user-guide.7 b/share/man/man7/user-guide.7 index ffa01ae7..84536296 100644 --- a/share/man/man7/user-guide.7 +++ b/share/man/man7/user-guide.7 @@ -127,6 +127,9 @@ ext2 extensions. You can make a compatible filesystem with: .Pp .Dl $ mkfs.ext2 -O none,large_file,filetype +.Pp +You can mount ISO 9660 filesystems for e.g. CD-ROMs using +.Xr iso9660fs 8 . .Ss Networking Internet Protocol version 4 .Pq Xr ip 4 diff --git a/sysinstall/sysinstall.c b/sysinstall/sysinstall.c index f38c12bd..3bfe8242 100644 --- a/sysinstall/sysinstall.c +++ b/sysinstall/sysinstall.c @@ -157,6 +157,12 @@ static bool should_install_bootloader_path(const char* mnt, return result; } +static bool should_ignore_bootloader_on_filesystem(struct blockdevice* bdev) +{ + return bdev->fs && bdev->fs->driver && + !strcmp(bdev->fs->driver, "iso9660fs"); +} + static bool should_install_bootloader_bdev(struct blockdevice* bdev) { if ( !bdev->fs ) @@ -196,13 +202,16 @@ static bool should_install_bootloader(void) { for ( size_t n = 0; n < hd->bdev.pt->partitions_count; n++ ) { - any_systems = true; struct partition* p = hd->bdev.pt->partitions[n]; + if ( should_ignore_bootloader_on_filesystem(&p->bdev) ) + continue; + any_systems = true; if ( should_install_bootloader_bdev(&p->bdev) ) return true; } } - else if ( hd->bdev.fs ) + else if ( hd->bdev.fs && + !should_ignore_bootloader_on_filesystem(&hd->bdev) ) { any_systems = true; if ( should_install_bootloader_bdev(&hd->bdev) ) diff --git a/utils/command-not-found.c b/utils/command-not-found.c index f0fda838..058148ce 100644 --- a/utils/command-not-found.c +++ b/utils/command-not-found.c @@ -80,6 +80,7 @@ struct command commands[] = {LOGOUT, "logout", NULL, suggest_logout}, {MOUNT, "extfs", "system", NULL}, + {MOUNT, "iso9660fs", "system", NULL}, {MOUNT, "mount", NULL, NULL}, {PAGER, "less", NULL, NULL}, diff --git a/utils/unmount.8 b/utils/unmount.8 index a3e6c5e9..48fb2579 100644 --- a/utils/unmount.8 +++ b/utils/unmount.8 @@ -33,4 +33,5 @@ It is not safe to shut down the computer directly after using this option. will exit 0 on success and non-zero otherwise. .Sh SEE ALSO .Xr unmount 2 , -.Xr extfs 8 +.Xr extfs 8 , +.Xr iso9660fs 8