/* * Copyright (c) 2021, Liav A. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include namespace Kernel { ErrorOr 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 Process::traverse_stacks_directory(FileSystemID fsid, Function(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 { 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> 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> 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 = ProcFSProcessPropertyInode::try_create_for_thread_stack(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 Process::traverse_children_directory(FileSystemID fsid, Function(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 { 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> 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(ProcFSProcessPropertyInode::try_create_for_child_process_link(procfs, *maybe_pid, pid())); } ErrorOr Process::procfs_get_child_proccess_link(ProcessID child_pid, KBufferBuilder& builder) const { TRY(builder.appendff("../../{}", child_pid.value())); return builder.length(); } ErrorOr 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 Process::traverse_file_descriptions_directory(FileSystemID fsid, Function(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> 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(ProcFSProcessPropertyInode::try_create_for_file_description_link(procfs, *maybe_index, pid())); } ErrorOr 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 Process::procfs_get_unveil_stats(KBufferBuilder& builder) const { auto array = TRY(JsonArraySerializer<>::try_create(builder)); TRY(m_unveil_data.with([&](auto& unveil_data) -> ErrorOr { TRY(unveil_data.paths.for_each_node_in_tree_order([&](auto const& unveiled_path) -> ErrorOr { 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 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 Process::procfs_get_fds_stats(KBufferBuilder& builder) const { auto array = TRY(JsonArraySerializer<>::try_create(builder)); return fds().with_shared([&](auto& fds) -> ErrorOr { if (fds.open_count() == 0) { TRY(array.finish()); return {}; } size_t count = 0; TRY(fds.try_enumerate([&](auto& file_description_metadata) -> ErrorOr { if (!file_description_metadata.is_valid()) { count++; return {}; } bool cloexec = file_description_metadata.flags() & FD_CLOEXEC; LockRefPtr 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 Process::procfs_get_virtual_memory_stats(KBufferBuilder& builder) const { auto array = TRY(JsonArraySerializer<>::try_create(builder)); TRY(address_space().with([&](auto& space) -> ErrorOr { 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(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 Process::procfs_get_current_work_directory_link(KBufferBuilder& builder) const { return builder.append(TRY(const_cast(*this).current_directory()->try_serialize_absolute_path())->view()); } ErrorOr 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 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()); } }