From 446200d6f3bcf3a828e23902e9dbbb44702135d0 Mon Sep 17 00:00:00 2001 From: Liav A Date: Sat, 29 Apr 2023 21:17:20 +0300 Subject: [PATCH] Kernel+Services: Enable barebones hot-plug handling capabilities Userspace initially didn't have any sort of mechanism to handle device hotplug (either removing or inserting a device). This meant that after a short term of scanning all known devices, by fetching device events (DeviceEvent packets) from /dev/devctl, we basically never try to read it again after SystemServer initialization code. To accommodate hotplug needs, we change SystemServer by ensuring it will generate a known set of device nodes at their location during the its main initialization code. This includes devices like /dev/mem, /dev/zero and /dev/full, etc. The actual responsible userspace program to handle hotplug events is a new userspace program called DeviceMapper, with following key points: - Its current task is to to constantly read the /dev/devctl device node. Because we already created generic devices, we only handle devices that are dynamically-generated in nature, like storage devices, audio channels, etc. - Since dynamically-generated device nodes could have an infinite minor numbers, but major numbers are decoded to a device type, we create an internal registry based on two structures - DeviceNodeFamily, and RegisteredDeviceNode. DeviceNodeFamily objects are attached in the main logic code, when handling a DeviceEvent device insertion packet. A DeviceNodeFamily object has an internal HashTable to hold objects of RegisteredDeviceNode class. - Because some device nodes could still share the same major number (TTY and serial TTY devices), we have two modes of allocation - limited allocation (so a range is defined for a major number), or infinite range. Therefore, two (or more) separate DeviceNodeFamily objects can can exist albeit sharing the same major number, but they are required to allocate from a different minor numbers' range to ensure there are no collisions. - As for KCOV, we handle this device differently. In case the user compiled the kernel with such support - this happens to be a singular device node that we usually don't need, so it's dynamically-generated too, and because it has only one instance, we don't register it in our internal registry to not make it complicated needlessly. The Kernel code is modified to allow proper blocking in case of no events in the DeviceControlDevice class, because otherwise we will need to poll periodically the device to check if a new event is available, which would waste CPU time for no good reason. --- Kernel/Devices/DeviceManagement.cpp | 29 +- Kernel/Devices/DeviceManagement.h | 8 +- .../Devices/Generic/DeviceControlDevice.cpp | 30 +- Userland/Services/CMakeLists.txt | 1 + Userland/Services/DeviceMapper/CMakeLists.txt | 13 + .../Services/DeviceMapper/DeviceEventLoop.cpp | 243 ++++++++++++ .../Services/DeviceMapper/DeviceEventLoop.h | 61 +++ .../Services/DeviceMapper/DeviceNodeFamily.h | 52 +++ .../DeviceMapper/RegisteredDeviceNode.h | 49 +++ Userland/Services/DeviceMapper/main.cpp | 49 +++ Userland/Services/SystemServer/Service.cpp | 1 - Userland/Services/SystemServer/main.cpp | 347 ++---------------- 12 files changed, 535 insertions(+), 348 deletions(-) create mode 100644 Userland/Services/DeviceMapper/CMakeLists.txt create mode 100644 Userland/Services/DeviceMapper/DeviceEventLoop.cpp create mode 100644 Userland/Services/DeviceMapper/DeviceEventLoop.h create mode 100644 Userland/Services/DeviceMapper/DeviceNodeFamily.h create mode 100644 Userland/Services/DeviceMapper/RegisteredDeviceNode.h create mode 100644 Userland/Services/DeviceMapper/main.cpp diff --git a/Kernel/Devices/DeviceManagement.cpp b/Kernel/Devices/DeviceManagement.cpp index 134f67c847..e48c296054 100644 --- a/Kernel/Devices/DeviceManagement.cpp +++ b/Kernel/Devices/DeviceManagement.cpp @@ -52,14 +52,6 @@ Device* DeviceManagement::get_device(MajorNumber major, MinorNumber minor) }); } -Optional DeviceManagement::dequeue_top_device_event(Badge) -{ - SpinlockLocker locker(m_event_queue_lock); - if (m_event_queue.is_empty()) - return {}; - return m_event_queue.dequeue(); -} - void DeviceManagement::before_device_removal(Badge, Device& device) { u64 device_id = encoded_device(device.major(), device.minor()); @@ -68,15 +60,20 @@ void DeviceManagement::before_device_removal(Badge, Device& device) map.remove(encoded_device(device.major(), device.minor())); }); - { + m_event_queue.with([&](auto& queue) { DeviceEvent event { DeviceEvent::State::Removed, device.is_block_device(), device.major().value(), device.minor().value() }; - SpinlockLocker locker(m_event_queue_lock); - m_event_queue.enqueue(event); - } + queue.enqueue(event); + }); + if (m_device_control_device) m_device_control_device->evaluate_block_conditions(); } +SpinlockProtected, LockRank::None>& DeviceManagement::event_queue(Badge) +{ + return m_event_queue; +} + void DeviceManagement::after_inserting_device(Badge, Device& device) { u64 device_id = encoded_device(device.major(), device.minor()); @@ -92,11 +89,11 @@ void DeviceManagement::after_inserting_device(Badge, Device& device) } }); - { + m_event_queue.with([&](auto& queue) { DeviceEvent event { DeviceEvent::State::Inserted, device.is_block_device(), device.major().value(), device.minor().value() }; - SpinlockLocker locker(m_event_queue_lock); - m_event_queue.enqueue(event); - } + queue.enqueue(event); + }); + if (m_device_control_device) m_device_control_device->evaluate_block_conditions(); } diff --git a/Kernel/Devices/DeviceManagement.h b/Kernel/Devices/DeviceManagement.h index 91502fe3a3..180e160c45 100644 --- a/Kernel/Devices/DeviceManagement.h +++ b/Kernel/Devices/DeviceManagement.h @@ -36,8 +36,6 @@ public: bool is_console_device_attached() const { return !m_console_device.is_null(); } void attach_console_device(ConsoleDevice const&); - Optional dequeue_top_device_event(Badge); - void after_inserting_device(Badge, Device&); void before_device_removal(Badge, Device&); @@ -68,14 +66,14 @@ public: return device; } + SpinlockProtected, LockRank::None>& event_queue(Badge); + private: LockRefPtr m_null_device; LockRefPtr m_console_device; LockRefPtr m_device_control_device; SpinlockProtected, LockRank::None> m_devices {}; - - mutable Spinlock m_event_queue_lock {}; - CircularQueue m_event_queue; + SpinlockProtected, LockRank::None> m_event_queue {}; }; } diff --git a/Kernel/Devices/Generic/DeviceControlDevice.cpp b/Kernel/Devices/Generic/DeviceControlDevice.cpp index 9d6b2c273e..d06ce6b929 100644 --- a/Kernel/Devices/Generic/DeviceControlDevice.cpp +++ b/Kernel/Devices/Generic/DeviceControlDevice.cpp @@ -19,7 +19,9 @@ UNMAP_AFTER_INIT NonnullLockRefPtr DeviceControlDevice::mus bool DeviceControlDevice::can_read(OpenFileDescription const&, u64) const { - return true; + return DeviceManagement::the().event_queue({}).with([](auto& queue) -> bool { + return !queue.is_empty(); + }); } UNMAP_AFTER_INIT DeviceControlDevice::DeviceControlDevice() @@ -29,18 +31,24 @@ UNMAP_AFTER_INIT DeviceControlDevice::DeviceControlDevice() UNMAP_AFTER_INIT DeviceControlDevice::~DeviceControlDevice() = default; -ErrorOr DeviceControlDevice::read(OpenFileDescription&, u64, UserOrKernelBuffer& buffer, size_t size) +ErrorOr DeviceControlDevice::read(OpenFileDescription&, u64 offset, UserOrKernelBuffer& buffer, size_t size) { - auto device_event = DeviceManagement::the().dequeue_top_device_event({}); - if (!device_event.has_value()) - return 0; - - if (size < sizeof(DeviceEvent)) + if (offset != 0) + return Error::from_errno(EINVAL); + if ((size % sizeof(DeviceEvent)) != 0) return Error::from_errno(EOVERFLOW); - size_t nread = 0; - TRY(buffer.write(&device_event.value(), nread, sizeof(DeviceEvent))); - nread += sizeof(DeviceEvent); - return nread; + + return DeviceManagement::the().event_queue({}).with([&](auto& queue) -> ErrorOr { + size_t nread = 0; + for (size_t event_index = 0; event_index < (size / sizeof(DeviceEvent)); event_index++) { + if (queue.is_empty()) + break; + auto event = queue.dequeue(); + TRY(buffer.write(&event, nread, sizeof(DeviceEvent))); + nread += sizeof(DeviceEvent); + } + return nread; + }); } ErrorOr DeviceControlDevice::ioctl(OpenFileDescription&, unsigned, Userspace) diff --git a/Userland/Services/CMakeLists.txt b/Userland/Services/CMakeLists.txt index cbdea1d640..a77b49c2f1 100644 --- a/Userland/Services/CMakeLists.txt +++ b/Userland/Services/CMakeLists.txt @@ -10,6 +10,7 @@ if (SERENITYOS) add_subdirectory(ChessEngine) add_subdirectory(Clipboard) add_subdirectory(CrashDaemon) + add_subdirectory(DeviceMapper) add_subdirectory(DHCPClient) add_subdirectory(FileSystemAccessServer) add_subdirectory(KeyboardPreferenceLoader) diff --git a/Userland/Services/DeviceMapper/CMakeLists.txt b/Userland/Services/DeviceMapper/CMakeLists.txt new file mode 100644 index 0000000000..b35586bc27 --- /dev/null +++ b/Userland/Services/DeviceMapper/CMakeLists.txt @@ -0,0 +1,13 @@ +serenity_component( + DeviceMapper + REQUIRED + TARGETS DeviceMapper +) + +set(SOURCES + main.cpp + DeviceEventLoop.cpp +) + +serenity_bin(DeviceMapper) +target_link_libraries(DeviceMapper PRIVATE LibCore LibFileSystem LibMain) diff --git a/Userland/Services/DeviceMapper/DeviceEventLoop.cpp b/Userland/Services/DeviceMapper/DeviceEventLoop.cpp new file mode 100644 index 0000000000..ef0620f540 --- /dev/null +++ b/Userland/Services/DeviceMapper/DeviceEventLoop.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2023, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "DeviceEventLoop.h" +#include +#include +#include +#include +#include +#include +#include + +namespace DeviceMapper { + +DeviceEventLoop::DeviceEventLoop(NonnullOwnPtr devctl_file) + : m_devctl_file(move(devctl_file)) +{ +} + +using MinorNumberAllocationType = DeviceEventLoop::MinorNumberAllocationType; + +static constexpr DeviceEventLoop::DeviceNodeMatch s_matchers[] = { + { "audio"sv, "audio"sv, "audio/%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 116, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0220 }, + { {}, "render"sv, "gpu/render%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 28, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 }, + { "window"sv, "gpu-connector"sv, "gpu/connector%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 226, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0660 }, + { {}, "virtio-console"sv, "hvc0p%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 229, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 }, + { "phys"sv, "hid-mouse"sv, "input/mouse/%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 10, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 }, + { "phys"sv, "hid-keyboard"sv, "input/keyboard/%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 85, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 }, + { {}, "storage"sv, "hd%letter"sv, DeviceNodeFamily::Type::BlockDevice, 3, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0600 }, + { "tty"sv, "console"sv, "tty%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 4, MinorNumberAllocationType::SequentialLimited, 0, 63, 0620 }, + { "tty"sv, "console"sv, "ttyS%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 4, MinorNumberAllocationType::SequentialLimited, 64, 127, 0620 }, +}; + +static bool is_in_minor_number_range(DeviceEventLoop::DeviceNodeMatch const& matcher, MinorNumber minor_number) +{ + if (matcher.minor_number_allocation_type == MinorNumberAllocationType::SequentialUnlimited) + return true; + + return matcher.minor_number_start <= minor_number && static_cast(matcher.minor_number_start.value() + matcher.minor_number_range_size) >= minor_number; +} + +static Optional device_node_family_to_match_type(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) +{ + for (auto& matcher : s_matchers) { + if (matcher.major_number == major_number + && unix_device_type == matcher.unix_device_type + && is_in_minor_number_range(matcher, minor_number)) + return matcher; + } + return {}; +} + +static bool is_in_family_minor_number_range(DeviceNodeFamily const& family, MinorNumber minor_number) +{ + return family.base_minor_number() <= minor_number && static_cast(family.base_minor_number().value() + family.devices_symbol_suffix_allocation_map().size()) >= minor_number; +} + +Optional DeviceEventLoop::find_device_node_family(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) const +{ + for (auto const& family : m_device_node_families) { + if (family->major_number() == major_number && family->type() == unix_device_type && is_in_family_minor_number_range(*family, minor_number)) + return *family.ptr(); + } + return {}; +} + +ErrorOr> DeviceEventLoop::find_or_register_new_device_node_family(DeviceNodeMatch const& match, DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) +{ + if (auto possible_family = find_device_node_family(unix_device_type, major_number, minor_number); possible_family.has_value()) + return possible_family.release_value(); + unsigned allocation_map_size = 1024; + if (match.minor_number_allocation_type == MinorNumberAllocationType::SequentialLimited) + allocation_map_size = match.minor_number_range_size; + auto bitmap = TRY(Bitmap::create(allocation_map_size, false)); + auto node = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) DeviceNodeFamily(move(bitmap), + match.family_type_literal, + unix_device_type, + major_number, + minor_number))); + TRY(m_device_node_families.try_append(node)); + + return node; +} + +static ErrorOr build_suffix_with_letters(size_t allocation_index) +{ + auto base_string = TRY(String::from_utf8(""sv)); + while (true) { + base_string = TRY(String::formatted("{:c}{}", 'a' + (allocation_index % 26), base_string)); + allocation_index = (allocation_index / 26); + if (allocation_index == 0) + break; + allocation_index = allocation_index - 1; + } + return base_string; +} + +static ErrorOr build_suffix_with_numbers(size_t allocation_index) +{ + return String::number(allocation_index); +} + +static ErrorOr prepare_permissions_after_populating_devtmpfs(StringView path, DeviceEventLoop::DeviceNodeMatch const& match) +{ + if (match.permission_group.is_null()) + return {}; + auto group = TRY(Core::System::getgrnam(match.permission_group)); + VERIFY(group.has_value()); + TRY(Core::System::endgrent()); + TRY(Core::System::chown(path, 0, group.value().gr_gid)); + return {}; +} + +ErrorOr DeviceEventLoop::register_new_device(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) +{ + auto possible_match = device_node_family_to_match_type(unix_device_type, major_number, minor_number); + if (!possible_match.has_value()) + return {}; + auto const& match = possible_match.release_value(); + auto device_node_family = TRY(find_or_register_new_device_node_family(match, unix_device_type, major_number, minor_number)); + static constexpr StringView devtmpfs_base_path = "/dev/"sv; + auto path_pattern = TRY(String::from_utf8(match.path_pattern)); + auto& allocation_map = device_node_family->devices_symbol_suffix_allocation_map(); + auto possible_allocated_suffix_index = allocation_map.find_first_unset(); + if (!possible_allocated_suffix_index.has_value()) { + // FIXME: Make the allocation map bigger? + return Error::from_errno(ERANGE); + } + auto allocated_suffix_index = possible_allocated_suffix_index.release_value(); + + auto path = TRY(String::from_utf8(path_pattern)); + if (match.path_pattern.contains("%digit"sv)) { + auto replacement = TRY(build_suffix_with_numbers(allocated_suffix_index)); + path = TRY(path.replace("%digit"sv, replacement, ReplaceMode::All)); + } + if (match.path_pattern.contains("%letter"sv)) { + auto replacement = TRY(build_suffix_with_letters(allocated_suffix_index)); + path = TRY(path.replace("%letter"sv, replacement, ReplaceMode::All)); + } + VERIFY(!path.is_empty()); + path = TRY(String::formatted("{}{}", devtmpfs_base_path, path)); + mode_t old_mask = umask(0); + if (unix_device_type == DeviceNodeFamily::Type::BlockDevice) + TRY(Core::System::create_block_device(path.bytes_as_string_view(), match.create_mode, major_number.value(), minor_number.value())); + else + TRY(Core::System::create_char_device(path.bytes_as_string_view(), match.create_mode, major_number.value(), minor_number.value())); + umask(old_mask); + TRY(prepare_permissions_after_populating_devtmpfs(path.bytes_as_string_view(), match)); + + auto result = TRY(device_node_family->registered_nodes().try_set(RegisteredDeviceNode { move(path), minor_number }, AK::HashSetExistingEntryBehavior::Keep)); + VERIFY(result != HashSetResult::ReplacedExistingEntry); + if (result == HashSetResult::KeptExistingEntry) { + // FIXME: Handle this case properly. + return Error::from_errno(EEXIST); + } + allocation_map.set(allocated_suffix_index, true); + return {}; +} + +ErrorOr DeviceEventLoop::unregister_device(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) +{ + if (!device_node_family_to_match_type(unix_device_type, major_number, minor_number).has_value()) + return {}; + auto possible_family = find_device_node_family(unix_device_type, major_number, minor_number); + if (!possible_family.has_value()) { + // FIXME: Handle cases where we can't remove a device node. + // This could happen when the DeviceMapper program was restarted + // so the previous state was not preserved and a device was removed. + return Error::from_errno(ENODEV); + } + auto& family = possible_family.release_value(); + for (auto& node : family.registered_nodes()) { + if (node.minor_number() == minor_number) + TRY(Core::System::unlink(node.device_path())); + } + auto removed_anything = family.registered_nodes().remove_all_matching([minor_number](auto& device) { return device.minor_number() == minor_number; }); + if (!removed_anything) { + // FIXME: Handle cases where we can't remove a device node. + // This could happen when the DeviceMapper program was restarted + // so the previous state was not preserved and a device was removed. + return Error::from_errno(ENODEV); + } + return {}; +} + +static ErrorOr create_kcov_device_node() +{ + mode_t old_mask = umask(0); + ScopeGuard umask_guard([old_mask] { umask(old_mask); }); + TRY(Core::System::create_char_device("/dev/kcov"sv, 0666, 30, 0)); + return {}; +} + +ErrorOr DeviceEventLoop::read_one_or_eof(DeviceEvent& event) +{ + if (m_devctl_file->read_until_filled({ bit_cast(&event), sizeof(DeviceEvent) }).is_error()) { + // Bad! Kernel and SystemServer apparently disagree on the record size, + // which means that previous data is likely to be invalid. + return Error::from_string_view("File ended after incomplete record? /dev/devctl seems broken!"sv); + } + return {}; +} + +ErrorOr DeviceEventLoop::drain_events_from_devctl() +{ + for (;;) { + DeviceEvent event; + TRY(read_one_or_eof(event)); + // NOTE: Ignore any event related to /dev/devctl device node - normally + // it should never disappear from the system and we already use it in this + // code. + if (event.major_number == 2 && event.minor_number == 10 && !event.is_block_device) + continue; + + if (event.state == DeviceEvent::State::Inserted) { + // NOTE: We have a special function for the KCOV device, because we don't + // want to create a new MinorNumberAllocationType (e.g. SingleInstance). + // Instead, just blindly create that device node and assume we will never + // have to worry about it, so we don't need to register that! + if (event.major_number == 30 && event.minor_number == 0 && !event.is_block_device) { + TRY(create_kcov_device_node()); + continue; + } + VERIFY(event.is_block_device == 1 || event.is_block_device == 0); + TRY(register_new_device(event.is_block_device ? DeviceNodeFamily::Type::BlockDevice : DeviceNodeFamily::Type::CharacterDevice, event.major_number, event.minor_number)); + } else if (event.state == DeviceEvent::State::Removed) { + if (event.major_number == 30 && event.minor_number == 0 && !event.is_block_device) { + dbgln("DeviceMapper: unregistering device failed: kcov tried to de-register itself!?"); + continue; + } + if (auto error_or_void = unregister_device(event.is_block_device ? DeviceNodeFamily::Type::BlockDevice : DeviceNodeFamily::Type::CharacterDevice, event.major_number, event.minor_number); error_or_void.is_error()) + dbgln("DeviceMapper: unregistering device failed: {}", error_or_void.error()); + } else { + dbgln("DeviceMapper: Unhandled device event ({:x})!", event.state); + } + } + VERIFY_NOT_REACHED(); +} + +} diff --git a/Userland/Services/DeviceMapper/DeviceEventLoop.h b/Userland/Services/DeviceMapper/DeviceEventLoop.h new file mode 100644 index 0000000000..2d6c948a58 --- /dev/null +++ b/Userland/Services/DeviceMapper/DeviceEventLoop.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "DeviceNodeFamily.h" +#include +#include +#include +#include +#include +#include + +namespace DeviceMapper { + +class DeviceEventLoop { +public: + enum class MinorNumberAllocationType { + SequentialUnlimited, + SequentialLimited, + }; + + enum class UnixDeviceType { + BlockDevice, + CharacterDevice, + }; + + struct DeviceNodeMatch { + StringView permission_group; + StringView family_type_literal; + StringView path_pattern; + DeviceNodeFamily::Type unix_device_type; + MajorNumber major_number; + MinorNumberAllocationType minor_number_allocation_type; + MinorNumber minor_number_start; + size_t minor_number_range_size; + mode_t create_mode; + }; + + DeviceEventLoop(NonnullOwnPtr); + virtual ~DeviceEventLoop() = default; + + ErrorOr drain_events_from_devctl(); + +private: + Optional find_device_node_family(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) const; + ErrorOr> find_or_register_new_device_node_family(DeviceNodeMatch const& match, DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number); + + ErrorOr register_new_device(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number); + ErrorOr unregister_device(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number); + + ErrorOr read_one_or_eof(DeviceEvent& event); + + Vector> m_device_node_families; + NonnullOwnPtr const m_devctl_file; +}; + +} diff --git a/Userland/Services/DeviceMapper/DeviceNodeFamily.h b/Userland/Services/DeviceMapper/DeviceNodeFamily.h new file mode 100644 index 0000000000..2ba58ecf96 --- /dev/null +++ b/Userland/Services/DeviceMapper/DeviceNodeFamily.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "RegisteredDeviceNode.h" +#include +#include +#include +#include + +namespace DeviceMapper { + +class DeviceNodeFamily : public RefCounted { +public: + enum class Type { + BlockDevice, + CharacterDevice, + }; + + DeviceNodeFamily(Bitmap devices_symbol_suffix_allocation_map, StringView literal_device_family, Type type, MajorNumber major, MinorNumber base_minor) + : m_literal_device_family(literal_device_family) + , m_type(type) + , m_major(major) + , m_base_minor(base_minor) + , m_devices_symbol_suffix_allocation_map(move(devices_symbol_suffix_allocation_map)) + { + } + + StringView literal_device_family() const { return m_literal_device_family; } + MajorNumber major_number() const { return m_major; } + MinorNumber base_minor_number() const { return m_base_minor; } + Type type() const { return m_type; } + + HashTable& registered_nodes() { return m_registered_nodes; } + Bitmap& devices_symbol_suffix_allocation_map() { return m_devices_symbol_suffix_allocation_map; } + Bitmap const& devices_symbol_suffix_allocation_map() const { return m_devices_symbol_suffix_allocation_map; } + +private: + StringView m_literal_device_family; + Type m_type { Type::CharacterDevice }; + MajorNumber m_major { 0 }; + MinorNumber m_base_minor { 0 }; + + HashTable m_registered_nodes; + Bitmap m_devices_symbol_suffix_allocation_map; +}; + +} diff --git a/Userland/Services/DeviceMapper/RegisteredDeviceNode.h b/Userland/Services/DeviceMapper/RegisteredDeviceNode.h new file mode 100644 index 0000000000..c04eab53ff --- /dev/null +++ b/Userland/Services/DeviceMapper/RegisteredDeviceNode.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace DeviceMapper { + +class RegisteredDeviceNode { +public: + RegisteredDeviceNode(String device_path, MinorNumber minor) + : m_device_path(move(device_path)) + , m_minor(minor) + { + } + + StringView device_path() const { return m_device_path.bytes_as_string_view(); } + MinorNumber minor_number() const { return m_minor; } + +private: + String m_device_path; + MinorNumber m_minor { 0 }; +}; + +} + +namespace AK { + +template<> +struct Traits : public GenericTraits { + static unsigned hash(DeviceMapper::RegisteredDeviceNode const& node) + { + return int_hash(node.minor_number().value()); + } + + static bool equals(DeviceMapper::RegisteredDeviceNode const& a, DeviceMapper::RegisteredDeviceNode const& b) + { + return a.minor_number() == b.minor_number(); + } +}; + +} diff --git a/Userland/Services/DeviceMapper/main.cpp b/Userland/Services/DeviceMapper/main.cpp new file mode 100644 index 0000000000..6a80c6a7da --- /dev/null +++ b/Userland/Services/DeviceMapper/main.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "DeviceEventLoop.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ErrorOr serenity_main(Main::Arguments) +{ + TRY(Core::System::unveil("/dev/"sv, "rwc"sv)); + TRY(Core::System::unveil("/etc/group"sv, "rw"sv)); + TRY(Core::System::unveil(nullptr, nullptr)); + TRY(Core::System::pledge("stdio rpath dpath wpath cpath chown fattr")); + + auto file_or_error = Core::File::open("/dev/devctl"sv, Core::File::OpenMode::Read); + if (file_or_error.is_error()) { + warnln("Failed to open /dev/devctl - {}", file_or_error.error()); + VERIFY_NOT_REACHED(); + } + auto file = file_or_error.release_value(); + DeviceMapper::DeviceEventLoop device_event_loop(move(file)); + if (auto result = device_event_loop.drain_events_from_devctl(); result.is_error()) + dbgln("DeviceMapper: Fatal error: {}", result.release_error()); + // NOTE: If we return from drain_events_from_devctl, it must be an error + // so we should always return 1! + return 1; +} diff --git a/Userland/Services/SystemServer/Service.cpp b/Userland/Services/SystemServer/Service.cpp index 1491b3c3d5..68b6e1e016 100644 --- a/Userland/Services/SystemServer/Service.cpp +++ b/Userland/Services/SystemServer/Service.cpp @@ -364,7 +364,6 @@ ErrorOr> Service::try_create(Core::ConfigFile const& conf bool Service::is_enabled_for_system_mode(StringView mode) const { - extern DeprecatedString g_system_mode; return m_system_modes.contains_slow(mode); } diff --git a/Userland/Services/SystemServer/main.cpp b/Userland/Services/SystemServer/main.cpp index 58862d3a39..465725d3ad 100644 --- a/Userland/Services/SystemServer/main.cpp +++ b/Userland/Services/SystemServer/main.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2018-2021, Andreas Kling * Copyright (c) 2021, Peter Elliott + * Copyright (c) 2023, Liav A. * * SPDX-License-Identifier: BSD-2-Clause */ @@ -17,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +69,8 @@ static void sigchld_handler(int) } } +namespace SystemServer { + static ErrorOr determine_system_mode() { ArmedScopeGuard declare_text_mode_on_failure([&] { @@ -93,280 +97,6 @@ static ErrorOr determine_system_mode() declare_text_mode_on_failure.disarm(); dbgln("Read system_mode: {}", g_system_mode); - - struct stat file_state; - int rc = lstat("/dev/gpu/connector0", &file_state); - if (rc != 0 && g_system_mode == "graphical") { - dbgln("WARNING: No device nodes at /dev/gpu/ directory. This is probably a sign of disabled graphics functionality."); - dbgln("To cope with this, I'll turn off graphical mode."); - g_system_mode = "text"; - } - return {}; -} - -static ErrorOr chown_all_matching_device_nodes_under_specific_directory(StringView directory, group const& group) -{ - struct stat cur_file_stat; - - Core::DirIterator di(directory, Core::DirIterator::SkipParentAndBaseDir); - if (di.has_error()) - VERIFY_NOT_REACHED(); - while (di.has_next()) { - auto entry_name = di.next_full_path(); - auto rc = stat(entry_name.characters(), &cur_file_stat); - if (rc < 0) - continue; - TRY(Core::System::chown(entry_name, 0, group.gr_gid)); - } - return {}; -} - -static ErrorOr chown_all_matching_device_nodes(group const& group, unsigned major_number) -{ - struct stat cur_file_stat; - - Core::DirIterator di("/dev/", Core::DirIterator::SkipParentAndBaseDir); - if (di.has_error()) - VERIFY_NOT_REACHED(); - while (di.has_next()) { - auto entry_name = di.next_full_path(); - auto rc = stat(entry_name.characters(), &cur_file_stat); - if (rc < 0) - continue; - if (major(cur_file_stat.st_rdev) != major_number) - continue; - TRY(Core::System::chown(entry_name, 0, group.gr_gid)); - } - return {}; -} - -inline char offset_character_with_number(char base_char, u8 offset) -{ - char offsetted_char = base_char; - VERIFY(static_cast(offsetted_char) + static_cast(offset) < 256); - offsetted_char += offset; - return offsetted_char; -} - -static ErrorOr create_devtmpfs_block_device(StringView name, mode_t mode, unsigned major, unsigned minor) -{ - return Core::System::mknod(name, mode | S_IFBLK, makedev(major, minor)); -} - -static ErrorOr create_devtmpfs_char_device(StringView name, mode_t mode, unsigned major, unsigned minor) -{ - return Core::System::mknod(name, mode | S_IFCHR, makedev(major, minor)); -} - -static ErrorOr read_one_or_eof(NonnullOwnPtr& file, DeviceEvent& event) -{ - auto const read_buf = TRY(file->read_some({ (u8*)&event, sizeof(DeviceEvent) })); - if (read_buf.size() == sizeof(DeviceEvent)) { - // Good! We could read another DeviceEvent. - return true; - } - if (file->is_eof()) { - // Good! We have reached the "natural" end of the file. - return false; - } - // Bad! Kernel and SystemServer apparently disagree on the record size, - // which means that previous data is likely to be invalid. - return Error::from_string_view("File ended after incomplete record? /dev/devctl seems broken!"sv); -} - -static ErrorOr populate_devtmpfs_devices_based_on_devctl() -{ - auto file_or_error = Core::File::open("/dev/devctl"sv, Core::File::OpenMode::Read); - if (file_or_error.is_error()) { - warnln("Failed to open /dev/devctl - {}", file_or_error.error()); - VERIFY_NOT_REACHED(); - } - auto file = file_or_error.release_value(); - - DeviceEvent event; - while (TRY(read_one_or_eof(file, event))) { - if (event.state != DeviceEvent::Inserted) - continue; - auto major_number = event.major_number; - auto minor_number = event.minor_number; - bool is_block_device = (event.is_block_device == 1); - switch (major_number) { - case 116: { - if (!is_block_device) { - auto name = TRY(String::formatted("/dev/audio/{}", minor_number)); - TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0220, 116, minor_number)); - break; - } - break; - } - case 28: { - auto name = TRY(String::formatted("/dev/gpu/render{}", minor_number)); - TRY(create_devtmpfs_block_device(name.bytes_as_string_view(), 0666, 28, minor_number)); - break; - } - case 226: { - auto name = TRY(String::formatted("/dev/gpu/connector{}", minor_number)); - TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0666, 226, minor_number)); - break; - } - case 229: { - if (!is_block_device) { - auto name = TRY(String::formatted("/dev/hvc0p{}", minor_number)); - TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0666, 229, minor_number)); - } - break; - } - case 10: { - if (!is_block_device) { - switch (minor_number) { - case 0: { - TRY(create_devtmpfs_char_device("/dev/input/mouse/0"sv, 0666, 10, 0)); - break; - } - default: - warnln("Unknown character device {}:{}", major_number, minor_number); - } - } - break; - } - case 85: { - if (!is_block_device) { - switch (minor_number) { - case 0: { - TRY(create_devtmpfs_char_device("/dev/input/keyboard/0"sv, 0666, 85, 0)); - break; - } - default: - warnln("Unknown character device {}:{}", major_number, minor_number); - } - } - break; - } - case 1: { - if (!is_block_device) { - switch (minor_number) { - case 5: { - TRY(create_devtmpfs_char_device("/dev/zero"sv, 0666, 1, 5)); - break; - } - case 1: { - TRY(create_devtmpfs_char_device("/dev/mem"sv, 0666, 1, 1)); - break; - } - case 3: { - TRY(create_devtmpfs_char_device("/dev/null"sv, 0666, 1, 3)); - break; - } - case 7: { - TRY(create_devtmpfs_char_device("/dev/full"sv, 0666, 1, 7)); - break; - } - case 8: { - TRY(create_devtmpfs_char_device("/dev/random"sv, 0666, 1, 8)); - break; - } - default: - warnln("Unknown character device {}:{}", major_number, minor_number); - break; - } - } - break; - } - case 30: { - if (!is_block_device) { - auto name = TRY(String::formatted("/dev/kcov{}", minor_number)); - TRY(create_devtmpfs_char_device(name.bytes_as_string_view(), 0666, 30, minor_number)); - } - break; - } - case 3: { - if (is_block_device) { - auto name = TRY(String::formatted("/dev/hd{}", offset_character_with_number('a', minor_number))); - TRY(create_devtmpfs_block_device(name.bytes_as_string_view(), 0600, 3, minor_number)); - } - break; - } - case 5: { - if (!is_block_device) { - switch (minor_number) { - case 1: { - TRY(create_devtmpfs_char_device("/dev/console"sv, 0666, 5, 1)); - break; - } - case 2: { - TRY(create_devtmpfs_char_device("/dev/ptmx"sv, 0666, 5, 2)); - break; - } - case 0: { - TRY(create_devtmpfs_char_device("/dev/tty"sv, 0666, 5, 0)); - break; - } - default: - warnln("Unknown character device {}:{}", major_number, minor_number); - } - } - break; - } - case 4: { - if (!is_block_device) { - switch (minor_number) { - case 0: { - TRY(create_devtmpfs_char_device("/dev/tty0"sv, 0620, 4, 0)); - break; - } - case 1: { - TRY(create_devtmpfs_char_device("/dev/tty1"sv, 0620, 4, 1)); - break; - } - case 2: { - TRY(create_devtmpfs_char_device("/dev/tty2"sv, 0620, 4, 2)); - break; - } - case 3: { - TRY(create_devtmpfs_char_device("/dev/tty3"sv, 0620, 4, 3)); - break; - } - case 64: { - TRY(create_devtmpfs_char_device("/dev/ttyS0"sv, 0620, 4, 64)); - break; - } - case 65: { - TRY(create_devtmpfs_char_device("/dev/ttyS1"sv, 0620, 4, 65)); - break; - } - case 66: { - TRY(create_devtmpfs_char_device("/dev/ttyS2"sv, 0620, 4, 66)); - break; - } - case 67: { - TRY(create_devtmpfs_char_device("/dev/ttyS3"sv, 0666, 4, 67)); - break; - } - default: - warnln("Unknown character device {}:{}", major_number, minor_number); - } - } - break; - } - default: - if (!is_block_device) - warnln("Unknown character device {}:{}", major_number, minor_number); - else - warnln("Unknown block device {}:{}", major_number, minor_number); - break; - } - } - return {}; -} - -static ErrorOr populate_devtmpfs() -{ - mode_t old_mask = umask(0); - printf("Changing umask %#o\n", old_mask); - TRY(create_devtmpfs_char_device("/dev/devctl"sv, 0660, 2, 10)); - TRY(populate_devtmpfs_devices_based_on_devctl()); - umask(old_mask); - TRY(Core::System::symlink("/dev/random"sv, "/dev/urandom"sv)); return {}; } @@ -395,40 +125,26 @@ static ErrorOr prepare_bare_minimum_devtmpfs_directory_structure() TRY(Core::System::mkdir("/dev/gpu"sv, 0755)); TRY(Core::System::mkdir("/dev/pts"sv, 0755)); TRY(Core::System::mount(-1, "/dev/pts"sv, "devpts"sv, 0)); + + mode_t old_mask = umask(0); + TRY(Core::System::create_char_device("/dev/devctl"sv, 0660, 2, 10)); + TRY(Core::System::create_char_device("/dev/zero"sv, 0666, 1, 5)); + TRY(Core::System::create_char_device("/dev/mem"sv, 0600, 1, 1)); + TRY(Core::System::create_char_device("/dev/null"sv, 0666, 3, 0)); + TRY(Core::System::create_char_device("/dev/full"sv, 0666, 7, 0)); + TRY(Core::System::create_char_device("/dev/random"sv, 0666, 8, 0)); + TRY(Core::System::create_char_device("/dev/console"sv, 0666, 5, 1)); + TRY(Core::System::create_char_device("/dev/ptmx"sv, 0666, 5, 2)); + TRY(Core::System::create_char_device("/dev/tty"sv, 0666, 5, 0)); + umask(old_mask); + TRY(Core::System::symlink("/dev/random"sv, "/dev/urandom"sv)); + TRY(Core::System::chmod("/dev/urandom"sv, 0666)); return {}; } -static ErrorOr prepare_permissions_after_populating_devtmpfs() +static ErrorOr spawn_device_mapper_process() { - TRY(Core::System::chmod("/dev/urandom"sv, 0666)); - - auto phys_group = TRY(Core::System::getgrnam("phys"sv)); - VERIFY(phys_group.has_value()); - // FIXME: Try to find a way to not hardcode the major number of display connector device nodes. - TRY(chown_all_matching_device_nodes(phys_group.value(), 29)); - - auto const filter_chown_ENOENT = [](ErrorOr result) -> ErrorOr { - auto const chown_enoent = Error::from_syscall("chown"sv, -ENOENT); - if (result.is_error() && result.error() == chown_enoent) { - dbgln("{}", result.release_error()); - return {}; - } - return result; - }; - - TRY(filter_chown_ENOENT(Core::System::chown("/dev/input/keyboard/0"sv, 0, phys_group.value().gr_gid))); - TRY(filter_chown_ENOENT(Core::System::chown("/dev/input/mouse/0"sv, 0, phys_group.value().gr_gid))); - - auto tty_group = TRY(Core::System::getgrnam("tty"sv)); - VERIFY(tty_group.has_value()); - // FIXME: Try to find a way to not hardcode the major number of tty nodes. - TRY(chown_all_matching_device_nodes(tty_group.release_value(), 4)); - - auto audio_group = TRY(Core::System::getgrnam("audio"sv)); - VERIFY(audio_group.has_value()); - TRY(Core::System::chown("/dev/audio"sv, 0, audio_group->gr_gid)); - TRY(chown_all_matching_device_nodes_under_specific_directory("/dev/audio"sv, audio_group.release_value())); - TRY(Core::System::endgrent()); + TRY(Core::Process::spawn("/bin/DeviceMapper"sv, ReadonlySpan {}, {}, Core::Process::KeepAsChild::No)); return {}; } @@ -436,8 +152,7 @@ static ErrorOr prepare_synthetic_filesystems() { TRY(prepare_bare_minimum_filesystem_mounts()); TRY(prepare_bare_minimum_devtmpfs_directory_structure()); - TRY(populate_devtmpfs()); - TRY(prepare_permissions_after_populating_devtmpfs()); + TRY(spawn_device_mapper_process()); return {}; } @@ -557,6 +272,8 @@ static ErrorOr activate_user_services_based_on_system_mode() return {}; } +}; + ErrorOr serenity_main(Main::Arguments arguments) { bool user = false; @@ -565,18 +282,18 @@ ErrorOr serenity_main(Main::Arguments arguments) args_parser.parse(arguments); if (!user) { - TRY(mount_all_filesystems()); - TRY(prepare_synthetic_filesystems()); - TRY(reopen_base_file_descriptors()); + TRY(SystemServer::mount_all_filesystems()); + TRY(SystemServer::prepare_synthetic_filesystems()); + TRY(SystemServer::reopen_base_file_descriptors()); } TRY(Core::System::pledge("stdio proc exec tty accept unix rpath wpath cpath chown fattr id sigaction")); if (!user) { - TRY(create_tmp_coredump_directory()); - TRY(set_default_coredump_directory()); - TRY(create_tmp_semaphore_directory()); - TRY(determine_system_mode()); + TRY(SystemServer::create_tmp_coredump_directory()); + TRY(SystemServer::set_default_coredump_directory()); + TRY(SystemServer::create_tmp_semaphore_directory()); + TRY(SystemServer::determine_system_mode()); } Core::EventLoop event_loop; @@ -585,9 +302,9 @@ ErrorOr serenity_main(Main::Arguments arguments) event_loop.register_signal(SIGTERM, sigterm_handler); if (!user) { - TRY(activate_base_services_based_on_system_mode()); + TRY(SystemServer::activate_base_services_based_on_system_mode()); } else { - TRY(activate_user_services_based_on_system_mode()); + TRY(SystemServer::activate_user_services_based_on_system_mode()); } return event_loop.exec();