/******************************************************************************* Copyright(C) Jonas 'Sortie' Termansen 2012, 2013, 2014. This file is part of Sortix. Sortix 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. Sortix 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 Sortix. If not, see . fs/kram.cpp Kernel RAM filesystem. *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kram.h" namespace Sortix { namespace KRAMFS { File::File(InodeType inode_type, mode_t type, dev_t dev, ino_t ino, uid_t owner, gid_t group, mode_t mode) { this->inode_type = inode_type; if ( !dev ) dev = (dev_t) this; if ( !ino ) ino = (ino_t) this; this->type = type; this->stat_uid = owner; this->stat_gid = group; this->stat_mode = (mode & S_SETABLE) | this->type; this->stat_size = 0; this->stat_blksize = 1; this->dev = dev; this->ino = ino; } File::~File() { } int File::truncate(ioctx_t* ctx, off_t length) { int ret = fcache.truncate(ctx, length); if ( ret == 0 ) { ScopedLock lock(&metalock); stat_size = fcache.GetFileSize(); stat_mtim = Time::Get(CLOCK_REALTIME); } return ret; } off_t File::lseek(ioctx_t* ctx, off_t offset, int whence) { return fcache.lseek(ctx, offset, whence); } ssize_t File::pread(ioctx_t* ctx, uint8_t* dest, size_t count, off_t off) { return fcache.pread(ctx, dest, count, off); } ssize_t File::pwrite(ioctx_t* ctx, const uint8_t* src, size_t count, off_t off) { ssize_t ret = fcache.pwrite(ctx, src, count, off); if ( 0 < ret ) { ScopedLock lock(&metalock); stat_size = fcache.GetFileSize(); stat_mtim = Time::Get(CLOCK_REALTIME); } return ret; } ssize_t File::readlink(ioctx_t* ctx, char* buf, size_t bufsize) { if ( !S_ISLNK(type) ) return errno = EINVAL, -1; if ( (size_t) SSIZE_MAX < bufsize ) bufsize = SSIZE_MAX; return fcache.pread(ctx, (uint8_t*) buf, bufsize, 0); } Dir::Dir(dev_t dev, ino_t ino, uid_t owner, gid_t group, mode_t mode) { inode_type = INODE_TYPE_DIR; if ( !dev ) dev = (dev_t) this; if ( !ino ) ino = (ino_t) this; dir_lock = KTHREAD_MUTEX_INITIALIZER; this->stat_gid = owner; this->stat_gid = group; this->type = S_IFDIR; this->stat_mode = (mode & S_SETABLE) | this->type; this->dev = dev; this->ino = ino; children_used = 0; children_length = 0; children = NULL; shut_down = false; } Dir::~Dir() { // We must not be deleted or garbage collected if we are still used by // someone. In that case the deleter should either delete our children or // simply forget about us. assert(!children_used); delete[] children; } ssize_t Dir::readdirents(ioctx_t* ctx, struct kernel_dirent* dirent, size_t size, off_t start, size_t /*maxcount*/) { ScopedLock lock(&dir_lock); if ( children_used <= (uintmax_t) start ) return 0; struct kernel_dirent retdirent; memset(&retdirent, 0, sizeof(retdirent)); const char* name = children[start].name; size_t namelen = strlen(name); size_t needed = sizeof(*dirent) + namelen + 1; ssize_t ret = -1; if ( size < needed ) { errno = ERANGE; retdirent.d_namlen = namelen; } else { Ref inode = children[start].inode; ret = needed; retdirent.d_reclen = needed; retdirent.d_nextoff = 0; retdirent.d_namlen = namelen; retdirent.d_ino = inode->ino; retdirent.d_dev = inode->dev; retdirent.d_type = ModeToDT(inode->type); } if ( !ctx->copy_to_dest(dirent, &retdirent, sizeof(retdirent)) ) return -1; if ( 0 <= ret && !ctx->copy_to_dest(dirent->d_name, name, namelen+1) ) return -1; return ret; } size_t Dir::FindChild(const char* filename) { for ( size_t i = 0; i < children_used; i++ ) if ( !strcmp(filename, children[i].name) ) return i; return SIZE_MAX; } bool Dir::AddChild(const char* filename, Ref inode) { if ( children_used == children_length ) { size_t new_children_length = children_length ? 2 * children_length : 4; DirEntry* new_children = new DirEntry[new_children_length]; if ( !new_children ) return false; for ( size_t i = 0; i < children_used; i++ ) { new_children[i].inode = children[i].inode; new_children[i].name = children[i].name; children[i].inode.Reset(); } delete[] children; children = new_children; children_length = new_children_length; } char* filename_copy = String::Clone(filename); if ( !filename_copy ) return false; inode->linked(); DirEntry* dirent = &children[children_used++]; dirent->inode = inode; dirent->name = filename_copy; return true; } void Dir::RemoveChild(size_t index) { assert(index < children_used); if ( index != children_used-1 ) { DirEntry tmp = children[index]; children[index] = children[children_used-1]; children[children_used-1] = tmp; index = children_used-1; } children[index].inode.Reset(); delete[] children[index].name; children_used--; } Ref Dir::open(ioctx_t* ctx, const char* filename, int flags, mode_t mode) { ScopedLock lock(&dir_lock); if ( shut_down ) return errno = ENOENT, Ref(NULL); size_t child_index = FindChild(filename); if ( child_index != SIZE_MAX ) { if ( (flags & O_CREATE) && (flags & O_EXCL) ) return errno = EEXIST, Ref(NULL); return children[child_index].inode; } if ( !(flags & O_CREATE) ) return errno = ENOENT, Ref(NULL); Ref file(new File(INODE_TYPE_FILE, S_IFREG, dev, 0, ctx->uid, ctx->gid, mode)); if ( !file ) return Ref(NULL); if ( !AddChild(filename, file) ) return Ref(NULL); return file; } int Dir::mkdir(ioctx_t* ctx, const char* filename, mode_t mode) { ScopedLock lock(&dir_lock); if ( shut_down ) return errno = ENOENT, -1; size_t child_index = FindChild(filename); if ( child_index != SIZE_MAX ) return errno = EEXIST, -1; Ref dir(new Dir(dev, 0, ctx->uid, ctx->gid, mode)); if ( !dir ) goto cleanup_done; if ( dir->link_raw(ctx, ".", dir) ) goto cleanup_done; if ( dir->link_raw(ctx, "..", Ref(this)) ) goto cleanup_dot; if ( !AddChild(filename, dir) ) goto cleanup_dotdot; return 0; cleanup_dotdot: dir->unlink_raw(ctx, ".."); cleanup_dot: dir->unlink_raw(ctx, "."); cleanup_done: return -1; } int Dir::rmdir(ioctx_t* ctx, const char* filename) { if ( IsDotOrDotDot(filename) ) return errno = ENOTEMPTY, -1; ScopedLock lock(&dir_lock); if ( shut_down ) return errno = ENOENT, -1; size_t child_index = FindChild(filename); if ( child_index == SIZE_MAX ) return errno = ENOENT, -1; Inode* child = children[child_index].inode.Get(); if ( !S_ISDIR(child->type) ) return errno = ENOTDIR, -1; if ( child->rmdir_me(ctx) < 0 ) return -1; RemoveChild(child_index); return 0; } int Dir::rmdir_me(ioctx_t* /*ctx*/) { ScopedLock lock(&dir_lock); if ( shut_down ) return errno = ENOENT, -1; for ( size_t i = 0; i < children_used; i++ ) if ( !IsDotOrDotDot(children[i].name) ) return errno = ENOTEMPTY, -1; shut_down = true; for ( size_t i = 0; i < children_used; i++ ) { children[i].inode->unlinked(); children[i].inode.Reset(); delete[] children[i].name; } delete[] children; children = NULL; children_used = children_length = 0; return 0; } int Dir::link(ioctx_t* /*ctx*/, const char* filename, Ref node) { if ( S_ISDIR(node->type) ) return errno = EPERM, -1; // TODO: Is this needed? This may protect against file descriptors to // deleted directories being used to corrupt kernel state, or something. if ( IsDotOrDotDot(filename) ) return errno = EEXIST, -1; if ( node->dev != dev ) return errno = EXDEV, -1; ScopedLock lock(&dir_lock); if ( shut_down ) return errno = ENOENT, -1; size_t child_index = FindChild(filename); if ( child_index != SIZE_MAX ) return errno = EEXIST, -1; if ( !AddChild(filename, node) ) return -1; return 0; } int Dir::link_raw(ioctx_t* /*ctx*/, const char* filename, Ref node) { if ( node->dev != dev ) return errno = EXDEV, -1; ScopedLock lock(&dir_lock); size_t child_index = FindChild(filename); if ( child_index != SIZE_MAX ) { children[child_index].inode->unlinked(); children[child_index].inode = node; children[child_index].inode->linked(); } else { if ( !AddChild(filename, node) ) return -1; } return 0; } int Dir::unlink(ioctx_t* /*ctx*/, const char* filename) { ScopedLock lock(&dir_lock); if ( shut_down ) return errno = ENOENT, -1; size_t child_index = FindChild(filename); if ( child_index == SIZE_MAX ) return errno = ENOENT, -1; Inode* child = children[child_index].inode.Get(); if ( S_ISDIR(child->type) ) return errno = EISDIR, -1; RemoveChild(child_index); return 0; } int Dir::unlink_raw(ioctx_t* /*ctx*/, const char* filename) { ScopedLock lock(&dir_lock); size_t child_index = FindChild(filename); if ( child_index == SIZE_MAX ) return errno = ENOENT, -1; RemoveChild(child_index); return 0; } int Dir::symlink(ioctx_t* ctx, const char* oldname, const char* filename) { ScopedLock lock(&dir_lock); if ( shut_down ) return errno = ENOENT, -1; if ( FindChild(filename) != SIZE_MAX ) return errno = EEXIST, -1; Ref file(new File(INODE_TYPE_SYMLINK, S_IFLNK, dev, 0, ctx->uid, ctx->gid, 0777)); if ( !file ) return Ref(NULL); ioctx_t kctx; SetupKernelIOCtx(&kctx); size_t oldname_length = strlen(oldname); size_t so_far = 0; while ( so_far < oldname_length ) { #if OFF_MAX < SIZE_MAX if ( (uintmax_t) OFF_MAX < (uintmax_t) so_far ) return Ref(NULL); #endif ssize_t amount = file->pwrite(&kctx, (const uint8_t*) oldname + so_far, oldname_length - so_far, (off_t) so_far); if ( amount <= 0 ) return Ref(NULL); so_far += (size_t) amount; } if ( !AddChild(filename, file) ) return Ref(NULL); return 0; } int Dir::rename_here(ioctx_t* ctx, Ref from, const char* oldname, const char* newname) { if ( IsDotOrDotDot(oldname) || IsDotOrDotDot(newname) ) return errno = EINVAL, -1; // TODO: Check whether oldpath is an ancestor of newpath. // Avoid deadlocks by locking directories in the right order. Dir* from_dir = (Dir*) from.Get(); kthread_mutex_t* mutex_ptr1; kthread_mutex_t* mutex_ptr2; if ( from_dir->ino < this->ino ) { mutex_ptr1 = &from_dir->dir_lock; mutex_ptr2 = &this->dir_lock; } else if ( from_dir->ino == this->ino ) { mutex_ptr1 = &this->dir_lock, mutex_ptr2 = NULL; if ( !strcmp(oldname, newname) ) return 0; } else { mutex_ptr1 = &this->dir_lock; mutex_ptr2 = &from_dir->dir_lock; } ScopedLock lock1(mutex_ptr1); ScopedLock lock2(mutex_ptr2); size_t from_index = from_dir->FindChild(oldname); if ( from_index == SIZE_MAX ) return errno = ENOENT, -1; Ref the_inode = from_dir->children[from_index].inode; size_t to_index = this->FindChild(newname); if ( to_index != SIZE_MAX ) { Ref existing = this->children[to_index].inode; if ( existing->dev == the_inode->dev && existing->ino == the_inode->ino ) return 0; if ( S_ISDIR(existing->type) ) { Dir* existing_dir = (Dir*) existing.Get(); if ( !S_ISDIR(the_inode->type) ) return errno = EISDIR, -1; assert(&existing_dir->dir_lock != mutex_ptr1); assert(&existing_dir->dir_lock != mutex_ptr2); if ( existing_dir->rmdir_me(ctx) != 0 ) return -1; } this->children[to_index].inode = the_inode; } else { if ( !this->AddChild(newname, the_inode) ) return -1; } from_dir->RemoveChild(from_index); if ( S_ISDIR(the_inode->type) ) the_inode->link_raw(ctx, "..", Ref(this)); return 0; } } // namespace KRAMFS } // namespace Sortix