/******************************************************************************* Copyright(C) Jonas 'Sortie' Termansen 2013. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . extfs.cpp Implementation of the extended filesystem. *******************************************************************************/ #define __STDC_CONSTANT_MACROS #define __STDC_LIMIT_MACROS #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__OFF_MAX) && !defined(OFF_MAX) #define OFF_MAX __OFF_MAX #endif #if defined(__sortix__) #include #endif #if defined(__sortix__) #include #else struct timespec timespec_make(time_t sec, long nsec) { struct timespec ret; ret.tv_sec = sec; ret.tv_nsec = nsec; return ret; } #endif #if defined(__linux__) #define FUSE_USE_VERSION 26 #include #endif #if defined(__sortix__) #include #endif #include "ext-constants.h" #include "ext-structs.h" #include "blockgroup.h" #include "block.h" #include "device.h" #include "filesystem.h" #include "inode.h" #include "ioleast.h" #include "util.h" const uint32_t EXT2_FEATURE_COMPAT_SUPPORTED = 0; const uint32_t EXT2_FEATURE_INCOMPAT_SUPPORTED = \ EXT2_FEATURE_INCOMPAT_FILETYPE; const uint32_t EXT2_FEATURE_RO_COMPAT_SUPPORTED = \ EXT2_FEATURE_RO_COMPAT_LARGE_FILE; // TODO: Inode 0 is not legal, but a lot of functions here accept it! mode_t HostModeFromExtMode(uint32_t extmode) { mode_t hostmode = extmode & 0777; if ( extmode & EXT2_S_ISVTX ) hostmode |= S_ISVTX; #if defined(S_ISGID) // Not on Sortix. if ( extmode & EXT2_S_ISGID ) hostmode |= S_ISGID; #endif #if defined(S_ISUID) // Not on Sortix. if ( extmode & EXT2_S_ISUID ) hostmode |= S_ISUID; #endif if ( EXT2_S_ISSOCK(extmode) ) hostmode |= S_IFSOCK; if ( EXT2_S_ISLNK(extmode) ) hostmode |= S_IFLNK; if ( EXT2_S_ISREG(extmode) ) hostmode |= S_IFREG; if ( EXT2_S_ISBLK(extmode) ) hostmode |= S_IFBLK; if ( EXT2_S_ISDIR(extmode) ) hostmode |= S_IFDIR; if ( EXT2_S_ISCHR(extmode) ) hostmode |= S_IFCHR; if ( EXT2_S_ISFIFO(extmode) ) hostmode |= S_IFIFO; return hostmode; } uint32_t ExtModeFromHostMode(mode_t hostmode) { uint32_t extmode = hostmode & 0777; if ( hostmode & S_ISVTX ) extmode |= EXT2_S_ISVTX; #if defined(S_ISGID) // Not on Sortix. if ( hostmode & S_ISGID ) extmode |= EXT2_S_ISGID; #endif #if defined(S_ISUID) // Not on Sortix. if ( hostmode & EXT2_S_ISUID ) extmode |= EXT2_S_ISUID; #endif if ( S_ISSOCK(hostmode) ) extmode |= EXT2_S_IFSOCK; if ( S_ISLNK(hostmode) ) extmode |= EXT2_S_IFLNK; if ( S_ISREG(hostmode) ) extmode |= EXT2_S_IFREG; if ( S_ISBLK(hostmode) ) extmode |= EXT2_S_IFBLK; if ( S_ISDIR(hostmode) ) extmode |= EXT2_S_IFDIR; if ( S_ISCHR(hostmode) ) extmode |= EXT2_S_IFCHR; if ( S_ISFIFO(hostmode) ) extmode |= EXT2_S_IFIFO; return extmode; } void StatInode(Inode* inode, struct stat* st) { memset(st, 0, sizeof(*st)); st->st_ino = inode->inode_id; st->st_mode = HostModeFromExtMode(inode->Mode()); st->st_nlink = inode->data->i_links_count; st->st_uid = inode->UserId(); st->st_gid = inode->GroupId(); st->st_size = inode->Size(); st->st_atim = timespec_make(inode->data->i_atime, 0); st->st_ctim = timespec_make(inode->data->i_ctime, 0); st->st_mtim = timespec_make(inode->data->i_mtime, 0); st->st_blksize = inode->filesystem->block_size; st->st_blocks = inode->data->i_blocks; } #if defined(__sortix__) bool RespondData(int svr, int chl, const void* ptr, size_t count) { return fsm_send(svr, chl, ptr, count) == (ssize_t) count; } bool RespondHeader(int svr, int chl, size_t type, size_t size) { struct fsm_msg_header hdr; hdr.msgtype = type; hdr.msgsize = size; return RespondData(svr, chl, &hdr, sizeof(hdr)); } bool RespondMessage(int svr, int chl, unsigned int type, const void* ptr, size_t count) { return RespondHeader(svr, chl, type, count) && RespondData(svr, chl, ptr, count); } bool RespondError(int svr, int chl, int errnum) { struct fsm_resp_error body; body.errnum = errnum; //fprintf(stderr, "extfs: sending error %i (%s)\n", errnum, strerror(errnum)); return RespondMessage(svr, chl, FSM_RESP_ERROR, &body, sizeof(body)); } bool RespondSuccess(int svr, int chl) { struct fsm_resp_success body; return RespondMessage(svr, chl, FSM_RESP_SUCCESS, &body, sizeof(body)); } bool RespondStat(int svr, int chl, struct stat* st) { struct fsm_resp_stat body; body.st = *st; return RespondMessage(svr, chl, FSM_RESP_STAT, &body, sizeof(body)); } bool RespondSeek(int svr, int chl, off_t offset) { struct fsm_resp_lseek body; body.offset = offset; return RespondMessage(svr, chl, FSM_RESP_LSEEK, &body, sizeof(body)); } bool RespondRead(int svr, int chl, const uint8_t* buf, size_t count) { struct fsm_resp_read body; body.count = count; return RespondMessage(svr, chl, FSM_RESP_READ, &body, sizeof(body)) && RespondData(svr, chl, buf, count); } bool RespondWrite(int svr, int chl, size_t count) { struct fsm_resp_write body; body.count = count; return RespondMessage(svr, chl, FSM_RESP_WRITE, &body, sizeof(body)); } bool RespondOpen(int svr, int chl, ino_t ino, mode_t type) { struct fsm_resp_open body; body.ino = ino; body.type = type; return RespondMessage(svr, chl, FSM_RESP_OPEN, &body, sizeof(body)); } bool RespondMakeDir(int svr, int chl, ino_t ino) { struct fsm_resp_mkdir body; body.ino = ino; return RespondMessage(svr, chl, FSM_RESP_MKDIR, &body, sizeof(body)); } bool RespondReadDir(int svr, int chl, struct kernel_dirent* dirent) { struct fsm_resp_readdirents body; body.ino = dirent->d_ino; body.type = dirent->d_type; body.namelen = dirent->d_namelen; return RespondMessage(svr, chl, FSM_RESP_READDIRENTS, &body, sizeof(body)) && RespondData(svr, chl, dirent->d_name, dirent->d_namelen); } void HandleRefer(int svr, int chl, struct fsm_req_refer* msg, Filesystem* fs) { (void) svr; (void) chl; if ( fs->num_inodes <= msg->ino ) return; if ( Inode* inode = fs->GetInode((uint32_t) msg->ino) ) inode->RemoteRefer(); } void HandleUnref(int svr, int chl, struct fsm_req_unref* msg, Filesystem* fs) { (void) svr; (void) chl; if ( fs->num_inodes <= msg->ino ) return; if ( Inode* inode = fs->GetInode((uint32_t) msg->ino) ) inode->RemoteUnref(); } void HandleSync(int svr, int chl, struct fsm_req_sync* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->ino); if ( !inode ) { RespondError(svr, chl, errno); return; } inode->Sync(); inode->Unref(); RespondSuccess(svr, chl); } void HandleStat(int svr, int chl, struct fsm_req_stat* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->ino); if ( !inode ) { RespondError(svr, chl, errno); return; } struct stat st; StatInode(inode, &st); inode->Unref(); RespondStat(svr, chl, &st); } void HandleChangeMode(int svr, int chl, struct fsm_req_chmod* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->ino); if ( !inode ) { RespondError(svr, chl, errno); return; } uint32_t req_mode = ExtModeFromHostMode(msg->mode); uint32_t old_mode = inode->Mode(); uint32_t new_mode = (old_mode & ~S_SETABLE) | (req_mode & S_SETABLE); inode->SetMode(new_mode); inode->Unref(); } void HandleChangeOwner(int svr, int chl, struct fsm_req_chown* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->ino); if ( !inode ) { RespondError(svr, chl, errno); return; } inode->SetUserId((uint32_t) msg->uid); inode->SetGroupId((uint32_t) msg->gid); inode->Unref(); RespondSuccess(svr, chl); } void HandleUTimens(int svr, int chl, struct fsm_req_utimens* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->ino); if ( !inode ) { RespondError(svr, chl, errno); return; } inode->data->i_atime = msg->times[0].tv_sec; inode->data->i_mtime = msg->times[1].tv_sec; inode->Dirty(); inode->Unref(); RespondSuccess(svr, chl); } void HandleTruncate(int svr, int chl, struct fsm_req_truncate* msg, Filesystem* fs) { if( msg->size < 0 ) { RespondError(svr, chl, EINVAL); return; } if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->ino); if ( !inode ) { RespondError(svr, chl, errno); return; } inode->Truncate((uint64_t) msg->size); inode->Unref(); RespondSuccess(svr, chl); } void HandleSeek(int svr, int chl, struct fsm_req_lseek* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->ino); if ( !inode ) { RespondError(svr, chl, errno); return; } if ( msg->whence == SEEK_SET ) RespondSeek(svr, 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(svr, chl, EOVERFLOW); else RespondSeek(svr, chl, msg->offset + inode_size); } else RespondError(svr, chl, EINVAL); inode->Unref(); } void HandleReadAt(int svr, int chl, struct fsm_req_pread* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->ino); if ( !inode ) { RespondError(svr, chl, errno); return; } uint8_t* buf = (uint8_t*) malloc(msg->count); if ( !buf ) { inode->Unref(); RespondError(svr, chl, errno); return; } ssize_t amount = inode->ReadAt(buf, msg->count, msg->offset); RespondRead(svr, chl, buf, amount); inode->Unref(); free(buf); } void HandleWriteAt(int svr, int chl, struct fsm_req_pread* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->ino); if ( !inode ) { RespondError(svr, chl, errno); return; } const uint8_t* buf = (const uint8_t*) &msg[1]; ssize_t amount = inode->WriteAt(buf, msg->count, msg->offset); RespondWrite(svr, chl, amount); inode->Unref(); } void HandleOpen(int svr, int chl, struct fsm_req_open* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->dirino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->dirino); if ( !inode ) { RespondError(svr, chl, errno); return; } char* pathraw = (char*) &(msg[1]); char* path = (char*) malloc(msg->namelen+1); if ( !path ) { RespondError(svr, chl, errno); inode->Unref(); return; } memcpy(path, pathraw, msg->namelen); path[msg->namelen] = '\0'; Inode* result = inode->Open(path, msg->flags, ExtModeFromHostMode(msg->mode)); free(path); inode->Unref(); if ( !result ) { RespondError(svr, chl, errno); return; } RespondOpen(svr, chl, result->inode_id, result->Mode() & S_IFMT); result->Unref(); } void HandleMakeDir(int svr, int chl, struct fsm_req_open* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->dirino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->dirino); if ( !inode ) { RespondError(svr, chl, errno); return; } char* pathraw = (char*) &(msg[1]); char* path = (char*) malloc(msg->namelen+1); if ( !path ) { RespondError(svr, chl, errno); inode->Unref(); return; } memcpy(path, pathraw, msg->namelen); path[msg->namelen] = '\0'; Inode* result = inode->CreateDirectory(path, ExtModeFromHostMode(msg->mode)); free(path); inode->Unref(); if ( !result ) { RespondError(svr, chl, errno); return; } RespondMakeDir(svr, chl, result->inode_id); result->Unref(); } void HandleReadDir(int svr, int chl, struct fsm_req_readdirents* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->ino); if ( !inode ) { RespondError(svr, chl, errno); return; } if ( !S_ISDIR(inode->Mode()) ) { inode->Unref(); RespondError(svr, chl, ENOTDIR); return; } union { struct kernel_dirent kernel_entry; uint8_t padding[sizeof(struct kernel_dirent) + 256]; }; memset(&kernel_entry, 0, sizeof(kernel_entry)); uint64_t file_size = inode->Size(); uint64_t offset = 0; Block* block = NULL; uint64_t block_id = 0; while ( offset < file_size ) { uint64_t entry_block_id = offset / fs->block_size; uint64_t entry_block_offset = offset % fs->block_size; if ( block && block_id != entry_block_id ) block->Unref(), block = NULL; if ( !block && !(block = inode->GetBlock(block_id = entry_block_id)) ) { inode->Unref(); RespondError(svr, chl, errno); return; } const uint8_t* block_data = block->block_data + entry_block_offset; const struct ext_dirent* entry = (const struct ext_dirent*) block_data; if ( entry->inode && entry->name_len && !(msg->rec_num--) ) { kernel_entry.d_reclen = sizeof(kernel_entry) + entry->name_len; kernel_entry.d_off = 0; kernel_entry.d_ino = entry->inode; kernel_entry.d_dev = 0; kernel_entry.d_type = 0; // TODO: Support this! kernel_entry.d_namelen = entry->name_len; memcpy(kernel_entry.d_name, entry->name, entry->name_len); size_t dname_offset = offsetof(struct kernel_dirent, d_name); padding[dname_offset + kernel_entry.d_namelen] = '\0'; block->Unref(); inode->Unref(); RespondReadDir(svr, chl, &kernel_entry); return; } offset += entry->reclen; } if ( block ) block->Unref(); kernel_entry.d_reclen = sizeof(kernel_entry); RespondReadDir(svr, chl, &kernel_entry); } void HandleIsATTY(int svr, int chl, struct fsm_req_isatty* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->ino); if ( !inode ) { RespondError(svr, chl, errno); return; } RespondError(svr, chl, ENOTTY); inode->Unref(); } void HandleUnlink(int svr, int chl, struct fsm_req_unlink* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->dirino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->dirino); if ( !inode ) { RespondError(svr, chl, errno); return; } char* pathraw = (char*) &(msg[1]); char* path = (char*) malloc(msg->namelen+1); if ( !path ) { RespondError(svr, chl, errno); inode->Unref(); return; } memcpy(path, pathraw, msg->namelen); path[msg->namelen] = '\0'; Inode* result = inode->Unlink(path, false); free(path); inode->Unref(); if ( !result ) { RespondError(svr, chl, errno); return; } result->Unref(); RespondSuccess(svr, chl); } void HandleRemoveDir(int svr, int chl, struct fsm_req_unlink* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->dirino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->dirino); if ( !inode ) { RespondError(svr, chl, errno); return; } char* pathraw = (char*) &(msg[1]); char* path = (char*) malloc(msg->namelen+1); if ( !path ) { RespondError(svr, chl, errno); inode->Unref(); return; } memcpy(path, pathraw, msg->namelen); path[msg->namelen] = '\0'; if ( inode->RemoveDirectory(path) ) RespondSuccess(svr, chl); else RespondError(svr, chl, errno); free(path); inode->Unref(); } void HandleLink(int svr, int chl, struct fsm_req_link* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->dirino ) { RespondError(svr, chl, EBADF); return; } if ( fs->num_inodes <= msg->linkino ) { RespondError(svr, chl, EBADF); return; } Inode* inode = fs->GetInode((uint32_t) msg->dirino); if ( !inode ) { RespondError(svr, chl, errno); return; } Inode* dest = fs->GetInode((uint32_t) msg->linkino); if ( !dest ) { inode->Unref(); RespondError(svr, chl, errno); return; } char* pathraw = (char*) &(msg[1]); char* path = (char*) malloc(msg->namelen+1); if ( !path ) { RespondError(svr, chl, errno); inode->Unref(); return; } memcpy(path, pathraw, msg->namelen); path[msg->namelen] = '\0'; if ( inode->Link(path, dest, false) ) RespondSuccess(svr, chl); else RespondError(svr, chl, errno); free(path); dest->Unref(); inode->Unref(); } void HandleRename(int svr, int chl, struct fsm_req_rename* msg, Filesystem* fs) { if ( fs->num_inodes <= msg->olddirino ) { RespondError(svr, chl, EBADF); return; } if ( fs->num_inodes <= msg->newdirino ) { RespondError(svr, chl, EBADF); return; } char* pathraw = (char*) &(msg[1]); char* path = (char*) malloc(msg->oldnamelen+1 + msg->newnamelen+1); if ( !path ) { RespondError(svr, 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 = fs->GetInode((uint32_t) msg->newdirino); if ( !olddir ) { free(path); RespondError(svr, chl, errno); return; } Inode* newdir = fs->GetInode((uint32_t) msg->newdirino); if ( !newdir ) { olddir->Unref(); free(path); RespondError(svr, chl, errno); return; } if ( newdir->Rename(olddir, oldname, newname) ) RespondSuccess(svr, chl); else RespondError(svr, chl, errno); newdir->Unref(); olddir->Unref(); free(path); } void HandleIncomingMessage(int svr, int chl, struct fsm_msg_header* hdr, Filesystem* fs) { typedef void (*handler_t)(int, 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_RENAME] = (handler_t) HandleRename; // TODO: symlink // TODO: readlink handlers[FSM_REQ_REFER] = (handler_t) HandleRefer; handlers[FSM_REQ_UNREF] = (handler_t) HandleUnref; if ( FSM_MSG_NUM <= hdr->msgtype || !handlers[hdr->msgtype] ) { fprintf(stderr, "extfs: message %zu not supported!\n", hdr->msgtype); RespondError(svr, chl, ENOTSUP); return; } uint8_t* body = (uint8_t*) malloc(hdr->msgsize); if ( !body ) { fprintf(stderr, "extfs: message too large: %zu bytes\n", hdr->msgsize); RespondError(svr, chl, errno); return; } if ( fsm_recv(svr, chl, body, hdr->msgsize) == (ssize_t) hdr->msgsize ) handlers[hdr->msgtype](svr, chl, body, fs); else { fprintf(stderr, "extfs: incomplete message: expected %zu bytes\n", hdr->msgsize); RespondError(svr, chl, errno); } free(body); } void AlarmHandler(int) { } static volatile bool should_terminate = false; void TerminationHandler(int) { should_terminate = true; } #endif #if defined(__linux__) struct ext2_fuse_ctx { Device* dev; Filesystem* fs; }; #ifndef S_SETABLE #define S_SETABLE 02777 #endif #define FUSE_FS (((struct ext2_fuse_ctx*) (fuse_get_context()->private_data))->fs) void* ext2_fuse_init(struct fuse_conn_info* /*conn*/) { return fuse_get_context()->private_data; } void ext2_fuse_destroy(void* fs_private) { struct ext2_fuse_ctx* ext2_fuse_ctx = (struct ext2_fuse_ctx*) fs_private; ext2_fuse_ctx->fs->Sync(); ext2_fuse_ctx->dev->Sync(); delete ext2_fuse_ctx->fs; ext2_fuse_ctx->fs = NULL; delete ext2_fuse_ctx->dev; ext2_fuse_ctx->dev = NULL; } Inode* ext2_fuse_resolve_path(const char* path) { Filesystem* fs = FUSE_FS; Inode* inode = fs->GetInode(EXT2_ROOT_INO); assert(inode); while ( path[0] ) { if ( *path == '/' ) { if ( !EXT2_S_ISDIR(inode->Mode()) ) return errno = ENOTDIR, (Inode*) NULL; path++; continue; } size_t elem_len = strcspn(path, "/"); char* elem = new char[elem_len+1]; memcpy(elem, path, elem_len); elem[elem_len] = '\0'; path += elem_len; Inode* next = inode->Open(elem, O_RDONLY, 0); delete[] 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* ext2_fuse_parent_dir(const char** path_ptr) { const char* path = *path_ptr; Filesystem* fs = FUSE_FS; Inode* inode = fs->GetInode(EXT2_ROOT_INO); assert(inode); while ( strchr(path, '/') ) { if ( *path == '/' ) { if ( !EXT2_S_ISDIR(inode->Mode()) ) return errno = ENOTDIR, (Inode*) NULL; path++; continue; } size_t elem_len = strcspn(path, "/"); char* elem = new char[elem_len+1]; memcpy(elem, path, elem_len); elem[elem_len] = '\0'; path += elem_len; Inode* next = inode->Open(elem, O_RDONLY, 0); delete[] elem; inode->Unref(); if ( !next ) return NULL; inode = next; } *path_ptr = *path ? path : "."; assert(!strchr(*path_ptr, '/')); return inode; } int ext2_fuse_getattr(const char* path, struct stat* st) { Inode* inode = ext2_fuse_resolve_path(path); if ( !inode ) return -errno; StatInode(inode, st); inode->Unref(); return 0; } int ext2_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 ext2_fuse_readlink(const char* path, char* buf, size_t bufsize) { (void) path; (void) buf; (void) bufsize; return -(errno = ENOSYS); } int ext2_fuse_mknod(const char* path, mode_t mode, dev_t dev) { (void) path; (void) mode; (void) dev; return -(errno = ENOSYS); } int ext2_fuse_mkdir(const char* path, mode_t mode) { Inode* inode = ext2_fuse_parent_dir(&path); if ( !inode ) return -errno; Inode* newdir = inode->CreateDirectory(path, ExtModeFromHostMode(mode)); inode->Unref(); if ( !newdir ) return -errno; newdir->Unref(); return 0; } int ext2_fuse_unlink(const char* path) { Inode* inode = ext2_fuse_parent_dir(&path); if ( !inode ) return -errno; Inode* result = inode->Unlink(path, false); inode->Unref(); if ( !result ) return -errno; result->Unref(); return 0; } int ext2_fuse_rmdir(const char* path) { Inode* inode = ext2_fuse_parent_dir(&path); if ( !inode ) return -errno; bool success = inode->RemoveDirectory(path); inode->Unref(); return success ? 0 : -errno; } int ext2_fuse_symlink(const char* oldname, const char* newname) { (void) oldname; (void) newname; return -(errno = ENOSYS); } int ext2_fuse_rename(const char* oldname, const char* newname) { Inode* olddir = ext2_fuse_parent_dir(&oldname); if ( !olddir ) return -errno; Inode* newdir = ext2_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 ext2_fuse_link(const char* oldname, const char* newname) { Inode* inode = ext2_fuse_resolve_path(oldname); if ( !inode ) return -errno; Inode* newdir = ext2_fuse_parent_dir(&newname); if ( !newdir ) return inode->Unref(), -errno; bool success = inode->Link(newname, inode, false); newdir->Unref(); inode->Unref(); return success ? 0 : -errno; } int ext2_fuse_chmod(const char* path, mode_t mode) { Inode* inode = ext2_fuse_resolve_path(path); if ( !inode ) return -errno; uint32_t req_mode = ExtModeFromHostMode(mode); uint32_t old_mode = inode->Mode(); uint32_t new_mode = (old_mode & ~S_SETABLE) | (req_mode & S_SETABLE); inode->SetMode(new_mode); inode->Unref(); return 0; } int ext2_fuse_chown(const char* path, uid_t owner, gid_t group) { Inode* inode = ext2_fuse_resolve_path(path); if ( !inode ) return -errno; inode->SetUserId((uint32_t) owner); inode->SetGroupId((uint32_t) group); inode->Unref(); return 0; } int ext2_fuse_truncate(const char* path, off_t size) { Inode* inode = ext2_fuse_resolve_path(path); if ( !inode ) return -errno; inode->Truncate((uint64_t) size); inode->Unref(); return 0; } int ext2_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; inode->Truncate((uint64_t) size); inode->Unref(); return 0; } int ext2_fuse_open(const char* path, struct fuse_file_info* fi) { int flags = fi->flags; Inode* dir = ext2_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 ext2_fuse_access(const char* path, int mode) { Inode* dir = ext2_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 ext2_fuse_create(const char* path, mode_t mode, struct fuse_file_info* fi) { int flags = fi->flags | O_CREAT; Inode* inode = ext2_fuse_parent_dir(&path); if ( !inode ) return -errno; Inode* result = inode->Open(path, flags, ExtModeFromHostMode(mode)); inode->Unref(); if ( !result ) return -errno; fi->fh = (uint64_t) result->inode_id; fi->keep_cache = 1; result->Unref(); return 0; } int ext2_fuse_opendir(const char* path, struct fuse_file_info* fi) { return ext2_fuse_open(path, fi); } int ext2_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 ext2_fuse_write(const char* /*path*/, const 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->WriteAt((const uint8_t*) buf, count, offset); inode->Unref(); return 0 <= result ? (int) result : -errno; } int ext2_fuse_statfs(const char* /*path*/, struct statvfs* stvfs) { (void) stvfs; return errno = -ENOSYS, -1; } int ext2_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->Sync(); inode->Unref(); return 0; } int ext2_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 ext2_fuse_releasedir(const char* path, struct fuse_file_info* fi) { return ext2_fuse_release(path, fi); } int ext2_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->Sync(); inode->Unref(); return 0; } /*int ext2_fuse_syncdir(const char* path, int data, struct fuse_file_info* fi) { return ext2_fuse_sync(path, data, fi); }*/ /*int ext2_fuse_setxattr(const char *, const char *, const char *, size_t, int) { return -(errno = ENOSYS); }*/ /*int ext2_fuse_getxattr(const char *, const char *, char *, size_t) { return -(errno = ENOSYS); }*/ /*int ext2_fuse_listxattr(const char *, char *, size_t) { return -(errno = ENOSYS); }*/ /*int ext2_fuse_removexattr(const char *, const char *) { return -(errno = ENOSYS); }*/ int ext2_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((uint32_t) fi->fh); if ( !inode ) return -errno; if ( !S_ISDIR(inode->Mode()) ) return inode->Unref(), -(errno = ENOTDIR); uint64_t file_size = inode->Size(); uint64_t offset = 0; Block* block = NULL; uint64_t block_id = 0; while ( offset < file_size ) { uint64_t entry_block_id = offset / fs->block_size; uint64_t entry_block_offset = offset % fs->block_size; if ( block && block_id != entry_block_id ) block->Unref(), block = NULL; if ( !block && !(block = inode->GetBlock(block_id = entry_block_id)) ) return inode->Unref(), -errno; const uint8_t* block_data = block->block_data + entry_block_offset; const struct ext_dirent* entry = (const struct ext_dirent*) block_data; if ( entry->inode && entry->name_len && (!rec_num || !rec_num--) ) { char* entry_name = new char[entry->name_len+1]; memcpy(entry_name, entry->name, entry->name_len); entry_name[entry->name_len] = '\0'; bool full = filler(buf, entry_name, NULL, 0); delete[] entry_name; if ( full ) { block->Unref(); inode->Unref(); return 0; } } offset += entry->reclen; } if ( block ) block->Unref(); inode->Sync(); inode->Unref(); return 0; } /*int ext2_fuse_lock(const char*, struct fuse_file_info*, int, struct flock*) { return -(errno = ENOSYS); }*/ int ext2_fuse_utimens(const char* path, const struct timespec tv[2]) { Inode* inode = ext2_fuse_resolve_path(path); if ( !inode ) return -errno; inode->data->i_atime = tv[0].tv_sec; inode->data->i_mtime = tv[1].tv_sec; inode->Dirty(); inode->Unref(); return 0; } /*int ext2_fuse_bmap(const char*, size_t blocksize, uint64_t* idx) { return -(errno = ENOSYS); }*/ #endif void CompactArguments(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)--; } } void Usage(FILE* fp, const char* argv0) { fprintf(fp, "Usage: %s [--probe] [--test-uuid UUID] DEVICE [MOUNT-POINT]\n", argv0); } void Help(FILE* fp, const char* argv0) { Usage(fp, argv0); } void Version(FILE* fp, const char* argv0) { Usage(fp, argv0); } int main(int argc, char* argv[]) { const char* argv0 = argv[0]; const char* test_uuid = NULL; bool foreground = false; bool probe = false; bool read = false; bool write = false; for ( int i = 1; i < argc; i++ ) { const char* arg = argv[i]; if ( arg[0] != '-' ) continue; argv[i] = NULL; if ( !strcmp(arg, "--") ) break; if ( arg[1] != '-' ) { while ( char c = *++arg ) switch ( c ) { case 'r': read = true; break; case 'w': write = true; break; default: fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); Usage(stderr, argv0); exit(1); } } else if ( !strcmp(arg, "--help") ) { Help(stdout, argv0); exit(0); } else if ( !strcmp(arg, "--usage") ) { Usage(stdout, argv0); exit(0); } else if ( !strcmp(arg, "--version") ) { Version(stdout, argv0); exit(0); } else if ( !strcmp(arg, "--foreground") ) foreground = true; else if ( !strcmp(arg, "--probe") ) probe = true; else if ( !strcmp(arg, "--read") ) read = true; else if ( !strcmp(arg, "--write") ) write = true; else if ( !strcmp(arg, "--test-uuid") ) { if ( i+1 == argc ) { fprintf(stderr, "%s: --test-uuid: Missing operand\n", argv0); exit(1); } test_uuid = argv[++i], argv[i] = NULL; } else { fprintf(stderr, "%s: unknown option: %s\n", argv0, arg); Usage(stderr, argv0); exit(1); } } // It doesn't make sense to have a write-only filesystem. read = read || write; // Default to read and write filesystem access. bool default_access = !read && !write ? read = write = true : false; if ( argc == 1 ) { Usage(stderr, argv0); exit(0); } CompactArguments(&argc, &argv); const char* device_path = 2 <= argc ? argv[1] : NULL; const char* mount_path = 2 <= argc ? argv[2] : NULL; if ( !device_path ) { Usage(stderr, argv0); exit(1); } int fd = open(device_path, write ? O_RDWR : O_RDONLY); if ( fd < 0 ) error(1, errno, "`%s'", device_path); // Read the super block from the filesystem so we can verify it. struct ext_superblock sb; if ( preadall(fd, &sb, sizeof(sb), 1024) != sizeof(sb) ) { if ( probe ) exit(1); else error(1, errno, "read: `%s'", device_path); } // Verify the magic value to detect a compatible filesystem. if ( !probe && sb.s_magic != EXT2_SUPER_MAGIC ) error(1, 0, "`%s' isn't a valid extended filesysten", device_path); if ( probe && sb.s_magic != EXT2_SUPER_MAGIC ) exit(1); // Test whether this was the filesystem the user was looking for. if ( test_uuid ) { // TODO: Test uuid! } // Test whether this revision of the extened filesystem is supported. if ( probe && sb.s_rev_level == EXT2_GOOD_OLD_REV ) exit(1); if ( !probe && sb.s_rev_level == EXT2_GOOD_OLD_REV ) error(1, 0, "`%s' is formatted with an obsolete filesystem revision", device_path); // Verify that no incompatible features are in use. if ( probe && sb.s_feature_compat & ~EXT2_FEATURE_INCOMPAT_SUPPORTED ) exit(1); if ( !probe && sb.s_feature_incompat & ~EXT2_FEATURE_INCOMPAT_SUPPORTED ) error(1, 0, "`%s' uses unsupported and incompatible features", device_path); // Verify that no incompatible features are in use if opening for write. if ( probe && default_access && write && sb.s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED ) exit(1); if ( !probe && default_access && write && sb.s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED ) error(1, 0, "`%s uses unsupported and incompatible features, " "read-only access is possible, but write-access was " "requested", device_path); if ( write && sb.s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED ) { if ( !probe ) fprintf(stderr, "Warning: `%s' uses unsupported and incompatible " "features, falling back to read-only access\n", device_path); // TODO: Modify the file descriptor such that writing fails! read = false; } // Check whether any features are in use that we can safely disregard. if ( !probe && sb.s_feature_compat & ~EXT2_FEATURE_COMPAT_SUPPORTED ) fprintf(stderr, "Note: filesystem uses unsupported but compatible " "features\n"); // We have found no critical problems, so let the caller know that this // filesystem satisfies the probe request. if ( probe ) exit(0); // Check whether the filesystem was unmounted cleanly. if ( !probe && sb.s_state != EXT2_VALID_FS ) fprintf(stderr, "Warning: `%s' wasn't unmounted cleanly\n", device_path); uint32_t block_size = 1024U << sb.s_log_block_size; Device* dev = new Device(fd, block_size, write); Filesystem* fs = new Filesystem(dev); fs->block_groups = new BlockGroup*[fs->num_groups]; for ( size_t i = 0; i < fs->num_groups; i++ ) fs->block_groups[i] = NULL; #if defined(__sortix__) if ( !mount_path ) return 0; // Open the mount point. int mountfd = open(mount_path, O_RDWR | O_DIRECTORY); if ( mountfd < 0 ) error(1, errno, "%s", mount_path); // Create a filesystem server connected to the kernel that we'll listen on. int serverfd = fsm_mkserver(); if ( serverfd < 0 ) error(1, errno, "fsm_mkserver"); // Create a file descriptor for our root directory. The kernel won't send // a message to this file descriptor so we can mount it before starting to // listen for messages. int rootfd = fsm_bootstraprootfd(serverfd, EXT2_ROOT_INO, O_RDWR | O_CREAT, S_IFDIR); if ( rootfd < 0 ) error(1, errno, "fsm_bootstraprootfd"); if ( fsm_fsbind(rootfd, mountfd, 0) < 0 ) error(1, errno, "fsm_fsbind"); close(mountfd); // 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 ) { if ( fork() ) exit(0); setpgid(0, 0); } // Listen for filesystem messages and sync the filesystem every few seconds. struct timespec last_sync_at; clock_gettime(CLOCK_MONOTONIC, &last_sync_at); int channel; while ( 0 <= (channel = fsm_listen(serverfd)) ) { if ( should_terminate ) break; struct fsm_msg_header hdr; ssize_t amount; if ( (amount = fsm_recv(serverfd, channel, &hdr, sizeof(hdr))) != sizeof(hdr) ) { error(0, errno, "got %zi bytes, expected %zu", amount, sizeof(hdr)); break; } HandleIncomingMessage(serverfd, channel, &hdr, fs); fsm_closechannel(serverfd, channel); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); if ( write && 5 <= timespec_sub(now, last_sync_at).tv_sec) { fs->Sync(); last_sync_at = now; } } // Sync the filesystem before shutting down. if ( write ) { fprintf(stderr, "%s: filesystem server shutting down, syncing...", argv0); fflush(stderr); fs->Sync(); fprintf(stderr, " done.\n"); } fsm_closeserver(serverfd); #elif defined(__linux__) (void) foreground; struct fuse_operations operations; memset(&operations, 0, sizeof(operations)); operations.access = ext2_fuse_access; operations.chmod = ext2_fuse_chmod; operations.chown = ext2_fuse_chown; operations.create = ext2_fuse_create; operations.destroy = ext2_fuse_destroy; operations.fgetattr = ext2_fuse_fgetattr; operations.flush = ext2_fuse_flush; operations.fsync = ext2_fuse_fsync; operations.ftruncate = ext2_fuse_ftruncate; operations.getattr = ext2_fuse_getattr; operations.init = ext2_fuse_init; operations.link = ext2_fuse_link; operations.mkdir = ext2_fuse_mkdir; operations.mknod = ext2_fuse_mknod; operations.opendir = ext2_fuse_opendir; operations.open = ext2_fuse_open; operations.readdir = ext2_fuse_readdir; operations.read = ext2_fuse_read; operations.readlink = ext2_fuse_readlink; operations.releasedir = ext2_fuse_releasedir; operations.release = ext2_fuse_release; operations.rename = ext2_fuse_rename; operations.rmdir = ext2_fuse_rmdir; operations.statfs = ext2_fuse_statfs; operations.symlink = ext2_fuse_symlink; operations.truncate = ext2_fuse_truncate; operations.unlink = ext2_fuse_unlink; operations.utimens = ext2_fuse_utimens; operations.write = ext2_fuse_write; operations.flag_nullpath_ok = 1; operations.flag_nopath = 1; char* argv_fuse[] = { (char*) argv[0], (char*) "-s", (char*) mount_path, (char*) NULL, }; int argc_fuse = sizeof(argv_fuse) / sizeof(argv_fuse[0]) - 1; struct ext2_fuse_ctx ext2_fuse_ctx; ext2_fuse_ctx.fs = fs; ext2_fuse_ctx.dev = dev; return fuse_main(argc_fuse, argv_fuse, &operations, &ext2_fuse_ctx); #else (void) foreground; (void) mount_path; #endif delete fs; delete dev; close(fd); return 0; }