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();