mirror of
https://github.com/SerenityOS/serenity
synced 2024-09-06 17:06:31 +00:00
![Liav A](/assets/img/avatar_default.png)
This is done by merging all scattered pieces of derived classes from the ProcFSInode class into that one class, so we don't use inheritance but rather simplistic checks to determine the proper code for each ProcFS inode with its specific characteristics.
343 lines
14 KiB
C++
343 lines
14 KiB
C++
/*
|
|
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/JsonArraySerializer.h>
|
|
#include <AK/JsonObjectSerializer.h>
|
|
#include <AK/JsonValue.h>
|
|
#include <Kernel/FileSystem/Custody.h>
|
|
#include <Kernel/FileSystem/ProcFS/Inode.h>
|
|
#include <Kernel/InterruptDisabler.h>
|
|
#include <Kernel/KBufferBuilder.h>
|
|
#include <Kernel/Memory/AnonymousVMObject.h>
|
|
#include <Kernel/Memory/MemoryManager.h>
|
|
#include <Kernel/Process.h>
|
|
#include <Kernel/ProcessExposed.h>
|
|
#include <Kernel/TTY/TTY.h>
|
|
|
|
namespace Kernel {
|
|
|
|
ErrorOr<void> Process::procfs_get_thread_stack(ThreadID thread_id, KBufferBuilder& builder) const
|
|
{
|
|
auto array = TRY(JsonArraySerializer<>::try_create(builder));
|
|
auto thread = Thread::from_tid(thread_id);
|
|
if (!thread)
|
|
return ESRCH;
|
|
auto current_process_credentials = Process::current().credentials();
|
|
bool show_kernel_addresses = current_process_credentials->is_superuser();
|
|
bool kernel_address_added = false;
|
|
for (auto address : TRY(Processor::capture_stack_trace(*thread, 1024))) {
|
|
if (!show_kernel_addresses && !Memory::is_user_address(VirtualAddress { address })) {
|
|
if (kernel_address_added)
|
|
continue;
|
|
address = 0xdeadc0de;
|
|
kernel_address_added = true;
|
|
}
|
|
TRY(array.add(address));
|
|
}
|
|
|
|
TRY(array.finish());
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> Process::traverse_stacks_directory(FileSystemID fsid, Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> callback) const
|
|
{
|
|
TRY(callback({ "."sv, { fsid, SegmentedProcFSIndex::build_segmented_index_for_main_property(pid(), SegmentedProcFSIndex::ProcessSubDirectory::Stacks, SegmentedProcFSIndex::MainProcessProperty::Reserved) }, 0 }));
|
|
TRY(callback({ ".."sv, { fsid, m_procfs_traits->component_index() }, 0 }));
|
|
|
|
return thread_list().with([&](auto& list) -> ErrorOr<void> {
|
|
for (auto const& thread : list) {
|
|
int tid = thread.tid().value();
|
|
InodeIdentifier identifier = { fsid, SegmentedProcFSIndex::build_segmented_index_for_thread_stack(pid(), thread.tid()) };
|
|
auto name = TRY(KString::number(tid));
|
|
TRY(callback({ name->view(), identifier, 0 }));
|
|
}
|
|
return {};
|
|
});
|
|
}
|
|
|
|
ErrorOr<NonnullLockRefPtr<Inode>> Process::lookup_stacks_directory(ProcFS const& procfs, StringView name) const
|
|
{
|
|
auto maybe_needle = name.to_uint();
|
|
if (!maybe_needle.has_value())
|
|
return ENOENT;
|
|
auto needle = maybe_needle.release_value();
|
|
|
|
ErrorOr<NonnullLockRefPtr<ProcFSInode>> thread_stack_inode { ENOENT };
|
|
for_each_thread([&](Thread const& thread) {
|
|
int tid = thread.tid().value();
|
|
VERIFY(!(tid < 0));
|
|
if (needle == (unsigned)tid) {
|
|
thread_stack_inode = ProcFSInode::try_create_as_thread_stack_inode(procfs, thread.tid(), pid());
|
|
return IterationDecision::Break;
|
|
}
|
|
return IterationDecision::Continue;
|
|
});
|
|
|
|
if (thread_stack_inode.is_error())
|
|
return thread_stack_inode.release_error();
|
|
return thread_stack_inode.release_value();
|
|
}
|
|
|
|
ErrorOr<void> Process::traverse_children_directory(FileSystemID fsid, Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> callback) const
|
|
{
|
|
TRY(callback({ "."sv, { fsid, SegmentedProcFSIndex::build_segmented_index_for_sub_directory(pid(), SegmentedProcFSIndex::ProcessSubDirectory::Children) }, 0 }));
|
|
TRY(callback({ ".."sv, { fsid, m_procfs_traits->component_index() }, 0 }));
|
|
return Process::for_each_in_same_jail([&](Process& process) -> ErrorOr<void> {
|
|
if (process.ppid() == pid()) {
|
|
StringBuilder builder;
|
|
builder.appendff("{}", process.pid());
|
|
TRY(callback({ builder.string_view(), { fsid, SegmentedProcFSIndex::build_segmented_index_for_children(pid(), process.pid()) }, DT_LNK }));
|
|
}
|
|
return {};
|
|
});
|
|
}
|
|
|
|
ErrorOr<NonnullLockRefPtr<Inode>> Process::lookup_children_directory(ProcFS const& procfs, StringView name) const
|
|
{
|
|
auto maybe_pid = name.to_uint();
|
|
if (!maybe_pid.has_value())
|
|
return ENOENT;
|
|
|
|
auto child_process = Process::from_pid_in_same_jail(*maybe_pid);
|
|
if (!child_process || child_process->ppid() != pid())
|
|
return ENOENT;
|
|
|
|
return TRY(ProcFSInode::try_create_as_child_process_link_inode(procfs, *maybe_pid, pid()));
|
|
}
|
|
|
|
ErrorOr<size_t> Process::procfs_get_child_proccess_link(ProcessID child_pid, KBufferBuilder& builder) const
|
|
{
|
|
TRY(builder.appendff("../../{}", child_pid.value()));
|
|
return builder.length();
|
|
}
|
|
|
|
ErrorOr<size_t> Process::procfs_get_file_description_link(unsigned fd, KBufferBuilder& builder) const
|
|
{
|
|
auto file_description = TRY(open_file_description(fd));
|
|
// Note: These links are not guaranteed to point to actual VFS paths, just like in other kernels.
|
|
auto data = TRY(file_description->pseudo_path());
|
|
TRY(builder.append(data->view()));
|
|
return data->length();
|
|
}
|
|
|
|
ErrorOr<void> Process::traverse_file_descriptions_directory(FileSystemID fsid, Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> callback) const
|
|
{
|
|
TRY(callback({ "."sv, { fsid, m_procfs_traits->component_index() }, 0 }));
|
|
TRY(callback({ ".."sv, { fsid, m_procfs_traits->component_index() }, 0 }));
|
|
size_t count = 0;
|
|
fds().with_shared([&](auto& fds) {
|
|
fds.enumerate([&](auto& file_description_metadata) {
|
|
if (!file_description_metadata.is_valid()) {
|
|
count++;
|
|
return;
|
|
}
|
|
StringBuilder builder;
|
|
builder.appendff("{}", count);
|
|
// FIXME: Propagate errors from callback.
|
|
(void)callback({ builder.string_view(), { fsid, SegmentedProcFSIndex::build_segmented_index_for_file_description(pid(), count) }, DT_LNK });
|
|
count++;
|
|
});
|
|
});
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<NonnullLockRefPtr<Inode>> Process::lookup_file_descriptions_directory(ProcFS const& procfs, StringView name) const
|
|
{
|
|
auto maybe_index = name.to_uint();
|
|
if (!maybe_index.has_value())
|
|
return ENOENT;
|
|
|
|
if (!m_fds.with_shared([&](auto& fds) { return fds.get_if_valid(*maybe_index); }))
|
|
return ENOENT;
|
|
|
|
return TRY(ProcFSInode::try_create_as_file_description_link_inode(procfs, *maybe_index, pid()));
|
|
}
|
|
|
|
ErrorOr<void> Process::procfs_get_pledge_stats(KBufferBuilder& builder) const
|
|
{
|
|
auto obj = TRY(JsonObjectSerializer<>::try_create(builder));
|
|
#define __ENUMERATE_PLEDGE_PROMISE(x) \
|
|
if (has_promised(Pledge::x)) { \
|
|
if (!promises_builder.is_empty()) \
|
|
TRY(promises_builder.try_append(' ')); \
|
|
TRY(promises_builder.try_append(#x##sv)); \
|
|
}
|
|
if (has_promises()) {
|
|
StringBuilder promises_builder;
|
|
ENUMERATE_PLEDGE_PROMISES
|
|
TRY(obj.add("promises"sv, promises_builder.string_view()));
|
|
}
|
|
#undef __ENUMERATE_PLEDGE_PROMISE
|
|
TRY(obj.finish());
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> Process::procfs_get_unveil_stats(KBufferBuilder& builder) const
|
|
{
|
|
auto array = TRY(JsonArraySerializer<>::try_create(builder));
|
|
TRY(m_unveil_data.with([&](auto& unveil_data) -> ErrorOr<void> {
|
|
TRY(unveil_data.paths.for_each_node_in_tree_order([&](auto const& unveiled_path) -> ErrorOr<IterationDecision> {
|
|
if (!unveiled_path.was_explicitly_unveiled())
|
|
return IterationDecision::Continue;
|
|
auto obj = TRY(array.add_object());
|
|
TRY(obj.add("path"sv, unveiled_path.path()));
|
|
StringBuilder permissions_builder;
|
|
if (unveiled_path.permissions() & UnveilAccess::Read)
|
|
permissions_builder.append('r');
|
|
if (unveiled_path.permissions() & UnveilAccess::Write)
|
|
permissions_builder.append('w');
|
|
if (unveiled_path.permissions() & UnveilAccess::Execute)
|
|
permissions_builder.append('x');
|
|
if (unveiled_path.permissions() & UnveilAccess::CreateOrRemove)
|
|
permissions_builder.append('c');
|
|
if (unveiled_path.permissions() & UnveilAccess::Browse)
|
|
permissions_builder.append('b');
|
|
TRY(obj.add("permissions"sv, permissions_builder.string_view()));
|
|
TRY(obj.finish());
|
|
return IterationDecision::Continue;
|
|
}));
|
|
return {};
|
|
}));
|
|
TRY(array.finish());
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> Process::procfs_get_perf_events(KBufferBuilder& builder) const
|
|
{
|
|
InterruptDisabler disabler;
|
|
if (!perf_events()) {
|
|
dbgln("ProcFS: No perf events for {}", pid());
|
|
return Error::from_errno(ENOBUFS);
|
|
}
|
|
return perf_events()->to_json(builder);
|
|
}
|
|
|
|
ErrorOr<void> Process::procfs_get_fds_stats(KBufferBuilder& builder) const
|
|
{
|
|
auto array = TRY(JsonArraySerializer<>::try_create(builder));
|
|
|
|
return fds().with_shared([&](auto& fds) -> ErrorOr<void> {
|
|
if (fds.open_count() == 0) {
|
|
TRY(array.finish());
|
|
return {};
|
|
}
|
|
|
|
size_t count = 0;
|
|
TRY(fds.try_enumerate([&](auto& file_description_metadata) -> ErrorOr<void> {
|
|
if (!file_description_metadata.is_valid()) {
|
|
count++;
|
|
return {};
|
|
}
|
|
bool cloexec = file_description_metadata.flags() & FD_CLOEXEC;
|
|
LockRefPtr<OpenFileDescription> description = file_description_metadata.description();
|
|
auto description_object = TRY(array.add_object());
|
|
TRY(description_object.add("fd"sv, count));
|
|
// TODO: Better OOM handling.
|
|
auto pseudo_path_or_error = description->pseudo_path();
|
|
TRY(description_object.add("absolute_path"sv, pseudo_path_or_error.is_error() ? "???"sv : pseudo_path_or_error.value()->view()));
|
|
TRY(description_object.add("seekable"sv, description->file().is_seekable()));
|
|
TRY(description_object.add("class"sv, description->file().class_name()));
|
|
TRY(description_object.add("offset"sv, description->offset()));
|
|
TRY(description_object.add("cloexec"sv, cloexec));
|
|
TRY(description_object.add("blocking"sv, description->is_blocking()));
|
|
TRY(description_object.add("can_read"sv, description->can_read()));
|
|
TRY(description_object.add("can_write"sv, description->can_write()));
|
|
Inode* inode = description->inode();
|
|
if (inode != nullptr) {
|
|
auto inode_object = TRY(description_object.add_object("inode"sv));
|
|
TRY(inode_object.add("fsid"sv, inode->fsid().value()));
|
|
TRY(inode_object.add("index"sv, inode->index().value()));
|
|
TRY(inode_object.finish());
|
|
}
|
|
TRY(description_object.finish());
|
|
count++;
|
|
return {};
|
|
}));
|
|
|
|
TRY(array.finish());
|
|
return {};
|
|
});
|
|
}
|
|
|
|
ErrorOr<void> Process::procfs_get_virtual_memory_stats(KBufferBuilder& builder) const
|
|
{
|
|
auto array = TRY(JsonArraySerializer<>::try_create(builder));
|
|
TRY(address_space().with([&](auto& space) -> ErrorOr<void> {
|
|
for (auto const& region : space->region_tree().regions()) {
|
|
auto current_process_credentials = Process::current().credentials();
|
|
if (!region.is_user() && !current_process_credentials->is_superuser())
|
|
continue;
|
|
auto region_object = TRY(array.add_object());
|
|
TRY(region_object.add("readable"sv, region.is_readable()));
|
|
TRY(region_object.add("writable"sv, region.is_writable()));
|
|
TRY(region_object.add("executable"sv, region.is_executable()));
|
|
TRY(region_object.add("stack"sv, region.is_stack()));
|
|
TRY(region_object.add("shared"sv, region.is_shared()));
|
|
TRY(region_object.add("syscall"sv, region.is_syscall_region()));
|
|
TRY(region_object.add("purgeable"sv, region.vmobject().is_anonymous()));
|
|
if (region.vmobject().is_anonymous()) {
|
|
TRY(region_object.add("volatile"sv, static_cast<Memory::AnonymousVMObject const&>(region.vmobject()).is_volatile()));
|
|
}
|
|
TRY(region_object.add("cacheable"sv, region.is_cacheable()));
|
|
TRY(region_object.add("address"sv, region.vaddr().get()));
|
|
TRY(region_object.add("size"sv, region.size()));
|
|
TRY(region_object.add("amount_resident"sv, region.amount_resident()));
|
|
TRY(region_object.add("amount_dirty"sv, region.amount_dirty()));
|
|
TRY(region_object.add("cow_pages"sv, region.cow_pages()));
|
|
TRY(region_object.add("name"sv, region.name()));
|
|
TRY(region_object.add("vmobject"sv, region.vmobject().class_name()));
|
|
|
|
StringBuilder pagemap_builder;
|
|
for (size_t i = 0; i < region.page_count(); ++i) {
|
|
auto page = region.physical_page(i);
|
|
if (!page)
|
|
pagemap_builder.append('N');
|
|
else if (page->is_shared_zero_page() || page->is_lazy_committed_page())
|
|
pagemap_builder.append('Z');
|
|
else
|
|
pagemap_builder.append('P');
|
|
}
|
|
TRY(region_object.add("pagemap"sv, pagemap_builder.string_view()));
|
|
TRY(region_object.finish());
|
|
}
|
|
return {};
|
|
}));
|
|
TRY(array.finish());
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> Process::procfs_get_current_work_directory_link(KBufferBuilder& builder) const
|
|
{
|
|
return builder.append(TRY(const_cast<Process&>(*this).current_directory()->try_serialize_absolute_path())->view());
|
|
}
|
|
|
|
ErrorOr<void> Process::procfs_get_command_line(KBufferBuilder& builder) const
|
|
{
|
|
auto array = TRY(JsonArraySerializer<>::try_create(builder));
|
|
for (auto const& arg : arguments()) {
|
|
TRY(array.add(arg.view()));
|
|
}
|
|
TRY(array.finish());
|
|
return {};
|
|
}
|
|
|
|
mode_t Process::binary_link_required_mode() const
|
|
{
|
|
if (!executable())
|
|
return 0;
|
|
return m_procfs_traits->required_mode();
|
|
}
|
|
|
|
ErrorOr<void> Process::procfs_get_binary_link(KBufferBuilder& builder) const
|
|
{
|
|
auto custody = executable();
|
|
if (!custody)
|
|
return Error::from_errno(ENOEXEC);
|
|
return builder.append(TRY(custody->try_serialize_absolute_path())->view());
|
|
}
|
|
|
|
}
|