diff --git a/Kernel/FileSystem/DevPtsFS/Inode.cpp b/Kernel/FileSystem/DevPtsFS/Inode.cpp index 30f7afc0388..6c74b034572 100644 --- a/Kernel/FileSystem/DevPtsFS/Inode.cpp +++ b/Kernel/FileSystem/DevPtsFS/Inode.cpp @@ -104,6 +104,11 @@ ErrorOr DevPtsFSInode::remove_child(StringView) return EROFS; } +ErrorOr DevPtsFSInode::replace_child(StringView, Inode&) +{ + return EROFS; +} + ErrorOr DevPtsFSInode::chmod(mode_t) { return EROFS; diff --git a/Kernel/FileSystem/DevPtsFS/Inode.h b/Kernel/FileSystem/DevPtsFS/Inode.h index 4d67ec6ba24..4466aa00152 100644 --- a/Kernel/FileSystem/DevPtsFS/Inode.h +++ b/Kernel/FileSystem/DevPtsFS/Inode.h @@ -35,6 +35,7 @@ private: virtual ErrorOr> create_child(StringView name, mode_t, dev_t, UserID, GroupID) override; virtual ErrorOr add_child(Inode&, StringView name, mode_t) override; virtual ErrorOr remove_child(StringView name) override; + virtual ErrorOr replace_child(StringView name, Inode& child) override; virtual ErrorOr chmod(mode_t) override; virtual ErrorOr chown(UserID, GroupID) override; diff --git a/Kernel/FileSystem/Ext2FS/Inode.cpp b/Kernel/FileSystem/Ext2FS/Inode.cpp index a08a35d2d36..9ac62cb7d6e 100644 --- a/Kernel/FileSystem/Ext2FS/Inode.cpp +++ b/Kernel/FileSystem/Ext2FS/Inode.cpp @@ -892,6 +892,63 @@ ErrorOr Ext2FSInode::remove_child(StringView name) return {}; } +ErrorOr Ext2FSInode::replace_child(StringView name, Inode& child) +{ + MutexLocker locker(m_inode_lock); + dbgln_if(EXT2_DEBUG, "Ext2FSInode[{}]::replace_child(): Replacing '{}' with inode {}", identifier(), name, child.index()); + VERIFY(is_directory()); + + TRY(populate_lookup_cache()); + + if (name.length() > EXT2_NAME_LEN) + return ENAMETOOLONG; + + Vector entries; + + Optional old_child_index; + TRY(traverse_as_directory([&](auto& entry) -> ErrorOr { + auto is_replacing_this_inode = name == entry.name; + auto inode_index = is_replacing_this_inode ? child.index() : entry.inode.index(); + + auto entry_name = TRY(KString::try_create(entry.name)); + TRY(entries.try_empend(move(entry_name), inode_index, to_ext2_file_type(child.mode()))); + if (is_replacing_this_inode) + old_child_index = entry.inode.index(); + + return {}; + })); + + if (!old_child_index.has_value()) + return ENOENT; + + auto old_child = TRY(fs().get_inode({ fsid(), *old_child_index })); + + auto old_index_it = m_lookup_cache.find(name); + VERIFY(old_index_it != m_lookup_cache.end()); + old_index_it->value = child.index(); + + // NOTE: Between this line and the write_directory line, all operations must + // be atomic. Any changes made should be reverted. + TRY(child.increment_link_count()); + + auto maybe_decrement_error = old_child->decrement_link_count(); + if (maybe_decrement_error.is_error()) { + old_index_it->value = *old_child_index; + MUST(child.decrement_link_count()); + return maybe_decrement_error; + } + + // FIXME: The filesystem is left in an inconsistent state if this fails. + // Revert the changes made above if we can't write_directory. + // Ideally, decrement should be the last operation, but we currently + // can't "un-write" a directory entry list. + TRY(write_directory(entries)); + + // TODO: Emit a did_replace_child event. + + return {}; +} + ErrorOr Ext2FSInode::populate_lookup_cache() { VERIFY(m_inode_lock.is_exclusively_locked_by_current_thread()); diff --git a/Kernel/FileSystem/Ext2FS/Inode.h b/Kernel/FileSystem/Ext2FS/Inode.h index 320e7bcd56d..fd0e4990d0a 100644 --- a/Kernel/FileSystem/Ext2FS/Inode.h +++ b/Kernel/FileSystem/Ext2FS/Inode.h @@ -36,6 +36,7 @@ private: virtual ErrorOr> create_child(StringView name, mode_t, dev_t, UserID, GroupID) override; virtual ErrorOr add_child(Inode& child, StringView name, mode_t) override; virtual ErrorOr remove_child(StringView name) override; + virtual ErrorOr replace_child(StringView name, Inode& child) override; virtual ErrorOr update_timestamps(Optional