|
| 1 | +/* |
| 2 | + * Copyright (c) 2023, Liav A. <[email protected]> |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: BSD-2-Clause |
| 5 | + */ |
| 6 | + |
| 7 | +#include "DeviceEventLoop.h" |
| 8 | +#include <AK/Debug.h> |
| 9 | +#include <LibCore/DirIterator.h> |
| 10 | +#include <LibCore/System.h> |
| 11 | +#include <LibIPC/MultiServer.h> |
| 12 | +#include <fcntl.h> |
| 13 | +#include <stdio.h> |
| 14 | +#include <unistd.h> |
| 15 | + |
| 16 | +namespace DeviceMapper { |
| 17 | + |
| 18 | +DeviceEventLoop::DeviceEventLoop(NonnullOwnPtr<Core::File> devctl_file) |
| 19 | + : m_devctl_file(move(devctl_file)) |
| 20 | +{ |
| 21 | +} |
| 22 | + |
| 23 | +using MinorNumberAllocationType = DeviceEventLoop::MinorNumberAllocationType; |
| 24 | + |
| 25 | +static constexpr DeviceEventLoop::DeviceNodeMatch s_matchers[] = { |
| 26 | + { "audio"sv, "audio"sv, "audio/%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 116, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0220 }, |
| 27 | + { {}, "render"sv, "gpu/render%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 28, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 }, |
| 28 | + { "window"sv, "gpu-connector"sv, "gpu/connector%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 226, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0660 }, |
| 29 | + { {}, "virtio-console"sv, "hvc0p%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 229, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 }, |
| 30 | + { "phys"sv, "hid-mouse"sv, "input/mouse/%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 10, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 }, |
| 31 | + { "phys"sv, "hid-keyboard"sv, "input/keyboard/%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 85, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0666 }, |
| 32 | + { {}, "storage"sv, "hd%letter"sv, DeviceNodeFamily::Type::BlockDevice, 3, MinorNumberAllocationType::SequentialUnlimited, 0, 0, 0600 }, |
| 33 | + { "tty"sv, "console"sv, "tty%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 4, MinorNumberAllocationType::SequentialLimited, 0, 63, 0620 }, |
| 34 | + { "tty"sv, "console"sv, "ttyS%digit"sv, DeviceNodeFamily::Type::CharacterDevice, 4, MinorNumberAllocationType::SequentialLimited, 64, 127, 0620 }, |
| 35 | +}; |
| 36 | + |
| 37 | +static bool is_in_minor_number_range(DeviceEventLoop::DeviceNodeMatch const& matcher, MinorNumber minor_number) |
| 38 | +{ |
| 39 | + if (matcher.minor_number_allocation_type == MinorNumberAllocationType::SequentialUnlimited) |
| 40 | + return true; |
| 41 | + |
| 42 | + return matcher.minor_number_start <= minor_number && static_cast<MinorNumber>(matcher.minor_number_start.value() + matcher.minor_number_range_size) >= minor_number; |
| 43 | +} |
| 44 | + |
| 45 | +static Optional<DeviceEventLoop::DeviceNodeMatch const&> device_node_family_to_match_type(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) |
| 46 | +{ |
| 47 | + for (auto& matcher : s_matchers) { |
| 48 | + if (matcher.major_number == major_number |
| 49 | + && unix_device_type == matcher.unix_device_type |
| 50 | + && is_in_minor_number_range(matcher, minor_number)) |
| 51 | + return matcher; |
| 52 | + } |
| 53 | + return {}; |
| 54 | +} |
| 55 | + |
| 56 | +static bool is_in_family_minor_number_range(DeviceNodeFamily const& family, MinorNumber minor_number) |
| 57 | +{ |
| 58 | + return family.base_minor_number() <= minor_number && static_cast<MinorNumber>(family.base_minor_number().value() + family.devices_symbol_suffix_allocation_map().size()) >= minor_number; |
| 59 | +} |
| 60 | + |
| 61 | +Optional<DeviceNodeFamily&> DeviceEventLoop::find_device_node_family(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) const |
| 62 | +{ |
| 63 | + for (auto const& family : m_device_node_families) { |
| 64 | + if (family->major_number() == major_number && family->type() == unix_device_type && is_in_family_minor_number_range(*family, minor_number)) |
| 65 | + return *family.ptr(); |
| 66 | + } |
| 67 | + return {}; |
| 68 | +} |
| 69 | + |
| 70 | +ErrorOr<NonnullRefPtr<DeviceNodeFamily>> DeviceEventLoop::find_or_register_new_device_node_family(DeviceNodeMatch const& match, DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) |
| 71 | +{ |
| 72 | + if (auto possible_family = find_device_node_family(unix_device_type, major_number, minor_number); possible_family.has_value()) |
| 73 | + return possible_family.release_value(); |
| 74 | + unsigned allocation_map_size = 1024; |
| 75 | + if (match.minor_number_allocation_type == MinorNumberAllocationType::SequentialLimited) |
| 76 | + allocation_map_size = match.minor_number_range_size; |
| 77 | + auto bitmap = TRY(Bitmap::create(allocation_map_size, false)); |
| 78 | + auto node = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) DeviceNodeFamily(move(bitmap), |
| 79 | + match.family_type_literal, |
| 80 | + unix_device_type, |
| 81 | + major_number, |
| 82 | + minor_number))); |
| 83 | + TRY(m_device_node_families.try_append(node)); |
| 84 | + |
| 85 | + return node; |
| 86 | +} |
| 87 | + |
| 88 | +static ErrorOr<String> build_suffix_with_letters(size_t allocation_index) |
| 89 | +{ |
| 90 | + auto base_string = TRY(String::from_utf8(""sv)); |
| 91 | + while (true) { |
| 92 | + base_string = TRY(String::formatted("{:c}{}", 'a' + (allocation_index % 26), base_string)); |
| 93 | + allocation_index = (allocation_index / 26); |
| 94 | + if (allocation_index == 0) |
| 95 | + break; |
| 96 | + allocation_index = allocation_index - 1; |
| 97 | + } |
| 98 | + return base_string; |
| 99 | +} |
| 100 | + |
| 101 | +static ErrorOr<String> build_suffix_with_numbers(size_t allocation_index) |
| 102 | +{ |
| 103 | + return String::number(allocation_index); |
| 104 | +} |
| 105 | + |
| 106 | +static ErrorOr<void> prepare_permissions_after_populating_devtmpfs(StringView path, DeviceEventLoop::DeviceNodeMatch const& match) |
| 107 | +{ |
| 108 | + if (match.permission_group.is_null()) |
| 109 | + return {}; |
| 110 | + auto group = TRY(Core::System::getgrnam(match.permission_group)); |
| 111 | + VERIFY(group.has_value()); |
| 112 | + TRY(Core::System::endgrent()); |
| 113 | + TRY(Core::System::chown(path, 0, group.value().gr_gid)); |
| 114 | + return {}; |
| 115 | +} |
| 116 | + |
| 117 | +ErrorOr<void> DeviceEventLoop::register_new_device(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) |
| 118 | +{ |
| 119 | + auto possible_match = device_node_family_to_match_type(unix_device_type, major_number, minor_number); |
| 120 | + if (!possible_match.has_value()) |
| 121 | + return {}; |
| 122 | + auto const& match = possible_match.release_value(); |
| 123 | + auto device_node_family = TRY(find_or_register_new_device_node_family(match, unix_device_type, major_number, minor_number)); |
| 124 | + static constexpr StringView devtmpfs_base_path = "/dev/"sv; |
| 125 | + auto path_pattern = TRY(String::from_utf8(match.path_pattern)); |
| 126 | + auto& allocation_map = device_node_family->devices_symbol_suffix_allocation_map(); |
| 127 | + auto possible_allocated_suffix_index = allocation_map.find_first_unset(); |
| 128 | + if (!possible_allocated_suffix_index.has_value()) { |
| 129 | + // FIXME: Make the allocation map bigger? |
| 130 | + return Error::from_errno(ERANGE); |
| 131 | + } |
| 132 | + auto allocated_suffix_index = possible_allocated_suffix_index.release_value(); |
| 133 | + |
| 134 | + auto path = TRY(String::from_utf8(path_pattern)); |
| 135 | + if (match.path_pattern.contains("%digit"sv)) { |
| 136 | + auto replacement = TRY(build_suffix_with_numbers(allocated_suffix_index)); |
| 137 | + path = TRY(path.replace("%digit"sv, replacement, ReplaceMode::All)); |
| 138 | + } |
| 139 | + if (match.path_pattern.contains("%letter"sv)) { |
| 140 | + auto replacement = TRY(build_suffix_with_letters(allocated_suffix_index)); |
| 141 | + path = TRY(path.replace("%letter"sv, replacement, ReplaceMode::All)); |
| 142 | + } |
| 143 | + VERIFY(!path.is_empty()); |
| 144 | + path = TRY(String::formatted("{}{}", devtmpfs_base_path, path)); |
| 145 | + mode_t old_mask = umask(0); |
| 146 | + if (unix_device_type == DeviceNodeFamily::Type::BlockDevice) |
| 147 | + TRY(Core::System::create_block_device(path.bytes_as_string_view(), match.create_mode, major_number.value(), minor_number.value())); |
| 148 | + else |
| 149 | + TRY(Core::System::create_char_device(path.bytes_as_string_view(), match.create_mode, major_number.value(), minor_number.value())); |
| 150 | + umask(old_mask); |
| 151 | + TRY(prepare_permissions_after_populating_devtmpfs(path.bytes_as_string_view(), match)); |
| 152 | + |
| 153 | + auto result = TRY(device_node_family->registered_nodes().try_set(RegisteredDeviceNode { move(path), minor_number }, AK::HashSetExistingEntryBehavior::Keep)); |
| 154 | + VERIFY(result != HashSetResult::ReplacedExistingEntry); |
| 155 | + if (result == HashSetResult::KeptExistingEntry) { |
| 156 | + // FIXME: Handle this case properly. |
| 157 | + return Error::from_errno(EEXIST); |
| 158 | + } |
| 159 | + allocation_map.set(allocated_suffix_index, true); |
| 160 | + return {}; |
| 161 | +} |
| 162 | + |
| 163 | +ErrorOr<void> DeviceEventLoop::unregister_device(DeviceNodeFamily::Type unix_device_type, MajorNumber major_number, MinorNumber minor_number) |
| 164 | +{ |
| 165 | + if (!device_node_family_to_match_type(unix_device_type, major_number, minor_number).has_value()) |
| 166 | + return {}; |
| 167 | + auto possible_family = find_device_node_family(unix_device_type, major_number, minor_number); |
| 168 | + if (!possible_family.has_value()) { |
| 169 | + // FIXME: Handle cases where we can't remove a device node. |
| 170 | + // This could happen when the DeviceMapper program was restarted |
| 171 | + // so the previous state was not preserved and a device was removed. |
| 172 | + return Error::from_errno(ENODEV); |
| 173 | + } |
| 174 | + auto& family = possible_family.release_value(); |
| 175 | + for (auto& node : family.registered_nodes()) { |
| 176 | + if (node.minor_number() == minor_number) |
| 177 | + TRY(Core::System::unlink(node.device_path())); |
| 178 | + } |
| 179 | + auto removed_anything = family.registered_nodes().remove_all_matching([minor_number](auto& device) { return device.minor_number() == minor_number; }); |
| 180 | + if (!removed_anything) { |
| 181 | + // FIXME: Handle cases where we can't remove a device node. |
| 182 | + // This could happen when the DeviceMapper program was restarted |
| 183 | + // so the previous state was not preserved and a device was removed. |
| 184 | + return Error::from_errno(ENODEV); |
| 185 | + } |
| 186 | + return {}; |
| 187 | +} |
| 188 | + |
| 189 | +static ErrorOr<void> create_kcov_device_node() |
| 190 | +{ |
| 191 | + mode_t old_mask = umask(0); |
| 192 | + ScopeGuard umask_guard([old_mask] { umask(old_mask); }); |
| 193 | + TRY(Core::System::create_char_device("/dev/kcov"sv, 0666, 30, 0)); |
| 194 | + return {}; |
| 195 | +} |
| 196 | + |
| 197 | +ErrorOr<void> DeviceEventLoop::read_one_or_eof(DeviceEvent& event) |
| 198 | +{ |
| 199 | + if (m_devctl_file->read_until_filled({ bit_cast<u8*>(&event), sizeof(DeviceEvent) }).is_error()) { |
| 200 | + // Bad! Kernel and SystemServer apparently disagree on the record size, |
| 201 | + // which means that previous data is likely to be invalid. |
| 202 | + return Error::from_string_view("File ended after incomplete record? /dev/devctl seems broken!"sv); |
| 203 | + } |
| 204 | + return {}; |
| 205 | +} |
| 206 | + |
| 207 | +ErrorOr<void> DeviceEventLoop::drain_events_from_devctl() |
| 208 | +{ |
| 209 | + for (;;) { |
| 210 | + DeviceEvent event; |
| 211 | + TRY(read_one_or_eof(event)); |
| 212 | + // NOTE: Ignore any event related to /dev/devctl device node - normally |
| 213 | + // it should never disappear from the system and we already use it in this |
| 214 | + // code. |
| 215 | + if (event.major_number == 2 && event.minor_number == 10 && !event.is_block_device) |
| 216 | + continue; |
| 217 | + |
| 218 | + if (event.state == DeviceEvent::State::Inserted) { |
| 219 | + // NOTE: We have a special function for the KCOV device, because we don't |
| 220 | + // want to create a new MinorNumberAllocationType (e.g. SingleInstance). |
| 221 | + // Instead, just blindly create that device node and assume we will never |
| 222 | + // have to worry about it, so we don't need to register that! |
| 223 | + if (event.major_number == 30 && event.minor_number == 0 && !event.is_block_device) { |
| 224 | + TRY(create_kcov_device_node()); |
| 225 | + continue; |
| 226 | + } |
| 227 | + VERIFY(event.is_block_device == 1 || event.is_block_device == 0); |
| 228 | + TRY(register_new_device(event.is_block_device ? DeviceNodeFamily::Type::BlockDevice : DeviceNodeFamily::Type::CharacterDevice, event.major_number, event.minor_number)); |
| 229 | + } else if (event.state == DeviceEvent::State::Removed) { |
| 230 | + if (event.major_number == 30 && event.minor_number == 0 && !event.is_block_device) { |
| 231 | + dbgln("DeviceMapper: unregistering device failed: kcov tried to de-register itself!?"); |
| 232 | + continue; |
| 233 | + } |
| 234 | + 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()) |
| 235 | + dbgln("DeviceMapper: unregistering device failed: {}", error_or_void.error()); |
| 236 | + } else { |
| 237 | + dbgln("DeviceMapper: Unhandled device event ({:x})!", event.state); |
| 238 | + } |
| 239 | + } |
| 240 | + VERIFY_NOT_REACHED(); |
| 241 | +} |
| 242 | + |
| 243 | +} |
0 commit comments