From 01cd474930d8f8fc262647fe069bed63ddc60744 Mon Sep 17 00:00:00 2001 From: Jesse Buhagiar Date: Thu, 10 Jun 2021 00:24:04 +1000 Subject: [PATCH] Userland/Libraries: Add LibUSBDB library Simple clone of LibPCIDB to support USB IDs instead of PCI ones. The format is basically identical, besides a few changes of the double tab fields. --- CMakeLists.txt | 18 ++ Userland/Libraries/CMakeLists.txt | 1 + Userland/Libraries/LibUSBDB/CMakeLists.txt | 6 + Userland/Libraries/LibUSBDB/Database.cpp | 208 +++++++++++++++++++++ Userland/Libraries/LibUSBDB/Database.h | 86 +++++++++ Userland/Utilities/CMakeLists.txt | 1 + Userland/Utilities/lsusb.cpp | 20 +- 7 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 Userland/Libraries/LibUSBDB/CMakeLists.txt create mode 100644 Userland/Libraries/LibUSBDB/Database.cpp create mode 100644 Userland/Libraries/LibUSBDB/Database.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 032e1e4b5c..5a58dbed2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ option(ENABLE_ALL_THE_DEBUG_MACROS "Enable all debug macros to validate they sti option(ENABLE_ALL_DEBUG_FACILITIES "Enable all noisy debug symbols and options. Not recommended for normal developer use" OFF) option(ENABLE_COMPILETIME_FORMAT_CHECK "Enable compiletime format string checks" ON) option(ENABLE_PCI_IDS_DOWNLOAD "Enable download of the pci.ids database at build time" ON) +option(ENABLE_USB_IDS_DOWNLOAD "Enable download of the usb.ids database at build time" ON) option(BUILD_LAGOM "Build parts of the system targeting the host OS for fuzzing/testing" OFF) option(ENABLE_KERNEL_LTO "Build the kernel with link-time optimization" OFF) @@ -293,3 +294,20 @@ if(EXISTS ${PCI_IDS_GZ_PATH} AND NOT EXISTS ${PCI_IDS_INSTALL_PATH}) file(MAKE_DIRECTORY ${CMAKE_INSTALL_DATAROOTDIR}) file(RENAME ${PCI_IDS_PATH} ${PCI_IDS_INSTALL_PATH}) endif() + +set(USB_IDS_GZ_URL http://www.linux-usb.org/usb.ids.gz) +set(USB_IDS_GZ_PATH ${CMAKE_BINARY_DIR}/usb.ids.gz) +set(USB_IDS_PATH ${CMAKE_BINARY_DIR}/usb.ids) +set(USB_IDS_INSTALL_PATH ${CMAKE_INSTALL_DATAROOTDIR}/usb.ids) + +if(ENABLE_USB_IDS_DOWNLOAD AND NOT EXISTS ${USB_IDS_GZ_PATH}) + message(STATUS "Downloading USB ID database from ${USB_IDS_GZ_URL}...") + file(DOWNLOAD ${USB_IDS_GZ_URL} ${USB_IDS_GZ_PATH} INACTIVITY_TIMEOUT 10) +endif() + +if(EXISTS ${USB_IDS_GZ_PATH} AND NOT EXISTS ${USB_IDS_INSTALL_PATH}) + message(STATUS "Extracting USB ID database from ${USB_IDS_GZ_PATH}...") + execute_process(COMMAND gzip -k -d ${USB_IDS_GZ_PATH}) + file(MAKE_DIRECTORY ${CMAKE_INSTALL_DATAROOTDIR}) + file(RENAME ${USB_IDS_PATH} ${USB_IDS_INSTALL_PATH}) +endif() diff --git a/Userland/Libraries/CMakeLists.txt b/Userland/Libraries/CMakeLists.txt index a51cf9b709..e023df3bc0 100644 --- a/Userland/Libraries/CMakeLists.txt +++ b/Userland/Libraries/CMakeLists.txt @@ -42,6 +42,7 @@ add_subdirectory(LibTextCodec) add_subdirectory(LibThreading) add_subdirectory(LibTLS) add_subdirectory(LibTTF) +add_subdirectory(LibUSBDB) add_subdirectory(LibVideo) add_subdirectory(LibVT) add_subdirectory(LibWasm) diff --git a/Userland/Libraries/LibUSBDB/CMakeLists.txt b/Userland/Libraries/LibUSBDB/CMakeLists.txt new file mode 100644 index 0000000000..5dd0ea9933 --- /dev/null +++ b/Userland/Libraries/LibUSBDB/CMakeLists.txt @@ -0,0 +1,6 @@ +set(SOURCES + Database.cpp +) + +serenity_lib(LibUSBDB usbdb) +target_link_libraries(LibUSBDB LibC) diff --git a/Userland/Libraries/LibUSBDB/Database.cpp b/Userland/Libraries/LibUSBDB/Database.cpp new file mode 100644 index 0000000000..952423a144 --- /dev/null +++ b/Userland/Libraries/LibUSBDB/Database.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#include "Database.h" + +namespace USBDB { + +RefPtr Database::open(const String& filename) +{ + auto file_or_error = MappedFile::map(filename); + if (file_or_error.is_error()) + return nullptr; + auto res = adopt_ref(*new Database(file_or_error.release_value())); + if (res->init() != 0) + return nullptr; + return res; +} + +const StringView Database::get_vendor(u16 vendor_id) const +{ + const auto& vendor = m_vendors.get(vendor_id); + if (!vendor.has_value()) + return ""; + return vendor.value()->name; +} + +const StringView Database::get_device(u16 vendor_id, u16 device_id) const +{ + const auto& vendor = m_vendors.get(vendor_id); + if (!vendor.has_value()) { + return ""; + } + const auto& device = vendor.value()->devices.get(device_id); + if (!device.has_value()) + return ""; + return device.value()->name; +} + +const StringView Database::get_interface(u16 vendor_id, u16 device_id, u16 interface_id) const +{ + const auto& vendor = m_vendors.get(vendor_id); + if (!vendor.has_value()) + return ""; + const auto& device = vendor.value()->devices.get(device_id); + if (!device.has_value()) + return ""; + const auto& interface = device.value()->interfaces.get(interface_id); + if (!interface.has_value()) + return ""; + return interface.value()->name; +} + +const StringView Database::get_class(u8 class_id) const +{ + const auto& xclass = m_classes.get(class_id); + if (!xclass.has_value()) + return ""; + return xclass.value()->name; +} + +const StringView Database::get_subclass(u8 class_id, u8 subclass_id) const +{ + const auto& xclass = m_classes.get(class_id); + if (!xclass.has_value()) + return ""; + const auto& subclass = xclass.value()->subclasses.get(subclass_id); + if (!subclass.has_value()) + return ""; + return subclass.value()->name; +} + +const StringView Database::get_protocol(u8 class_id, u8 subclass_id, u8 protocol_id) const +{ + const auto& xclass = m_classes.get(class_id); + if (!xclass.has_value()) + return ""; + const auto& subclass = xclass.value()->subclasses.get(subclass_id); + if (!subclass.has_value()) + return ""; + const auto& protocol = subclass.value()->protocols.get(protocol_id); + if (!protocol.has_value()) + return ""; + return protocol.value()->name; +} + +int Database::init() +{ + if (m_ready) + return 0; + + m_view = StringView { m_file->bytes() }; + + ParseMode mode = ParseMode::UnknownMode; + + OwnPtr current_vendor {}; + OwnPtr current_device {}; + OwnPtr current_class {}; + OwnPtr current_subclass {}; + + auto commit_device = [&]() { + if (current_device && current_vendor) { + auto id = current_device->id; + current_vendor->devices.set(id, current_device.release_nonnull()); + } + }; + + auto commit_vendor = [&]() { + commit_device(); + if (current_vendor) { + auto id = current_vendor->id; + m_vendors.set(id, current_vendor.release_nonnull()); + } + }; + + auto commit_subclass = [&]() { + if (current_subclass && current_class) { + auto id = current_subclass->id; + current_class->subclasses.set(id, current_subclass.release_nonnull()); + } + }; + + auto commit_class = [&]() { + commit_subclass(); + if (current_class) { + auto id = current_class->id; + m_classes.set(id, current_class.release_nonnull()); + } + }; + + auto commit_all = [&]() { + commit_vendor(); + commit_class(); + }; + + auto lines = m_view.split_view('\n'); + + for (auto& line : lines) { + if (line.length() < 2 || line[0] == '#') + continue; + + if (line[0] == 'C') { + mode = ParseMode::ClassMode; + commit_all(); + } else if ((line[0] >= '0' && line[0] <= '9') || (line[0] >= 'a' && line[0] <= 'f')) { + mode = ParseMode::VendorMode; + commit_all(); + } else if (line[0] != '\t') { + mode = ParseMode::UnknownMode; + continue; + } + + switch (mode) { + case ParseMode::VendorMode: + if (line[0] != '\t') { + commit_vendor(); + current_vendor = make(); + current_vendor->id = AK::StringUtils::convert_to_uint_from_hex(line).value_or(0); + current_vendor->name = line.substring_view(6, line.length() - 6); + } else if (line[0] == '\t' && line[1] != '\t') { + commit_device(); + current_device = make(); + current_device->id = AK::StringUtils::convert_to_uint_from_hex((line.substring_view(1, line.length() - 1))).value_or(0); + current_device->name = line.substring_view(7, line.length() - 7); + } else if (line[0] == '\t' && line[1] == '\t') { + auto interface = make(); + interface->interface = AK::StringUtils::convert_to_uint_from_hex((line.substring_view(2, 4))).value_or(0); + interface->name = line.substring_view(7, line.length() - 7); + current_device->interfaces.set(interface->interface, move(interface)); + } + break; + case ParseMode::ClassMode: + if (line[0] != '\t') { + commit_class(); + current_class = make(); + current_class->id = AK::StringUtils::convert_to_uint_from_hex((line.substring_view(2, 2))).value_or(0); + current_class->name = line.substring_view(6, line.length() - 6); + } else if (line[0] == '\t' && line[1] != '\t') { + commit_subclass(); + current_subclass = make(); + current_subclass->id = AK::StringUtils::convert_to_uint_from_hex((line.substring_view(1, 2))).value_or(0); + current_subclass->name = line.substring_view(5, line.length() - 5); + } else if (line[0] == '\t' && line[1] == '\t') { + auto protocol = make(); + protocol->id = AK::StringUtils::convert_to_uint_from_hex((line.substring_view(2, 2))).value_or(0); + protocol->name = line.substring_view(6, line.length() - 6); + current_subclass->protocols.set(protocol->id, move(protocol)); + } + break; + default: + break; + } + } + + commit_all(); + + m_ready = true; + + return 0; +} + +} diff --git a/Userland/Libraries/LibUSBDB/Database.h b/Userland/Libraries/LibUSBDB/Database.h new file mode 100644 index 0000000000..c1fa0cc9f1 --- /dev/null +++ b/Userland/Libraries/LibUSBDB/Database.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace USBDB { + +struct Interface { + u16 interface; + StringView name; +}; + +struct Device { + u16 id; + StringView name; + HashMap> interfaces; +}; + +struct Vendor { + u16 id; + StringView name; + HashMap> devices; +}; + +struct Protocol { + u8 id { 0 }; + StringView name {}; +}; + +struct Subclass { + u8 id { 0 }; + StringView name {}; + HashMap> protocols; +}; + +struct Class { + u8 id { 0 }; + StringView name {}; + HashMap> subclasses; +}; + +class Database : public RefCounted { +public: + static RefPtr open(const String& filename); + static RefPtr open() { return open("/res/usb.ids"); }; + + const StringView get_vendor(u16 vendor_id) const; + const StringView get_device(u16 vendor_id, u16 device_id) const; + const StringView get_interface(u16 vendor_id, u16 device_id, u16 interface_id) const; + const StringView get_class(u8 class_id) const; + const StringView get_subclass(u8 class_id, u8 subclass_id) const; + const StringView get_protocol(u8 class_id, u8 subclass_id, u8 protocol_id) const; + +private: + explicit Database(NonnullRefPtr file) + : m_file(move(file)) + { + } + + int init(); + + enum ParseMode { + UnknownMode, + VendorMode, + ClassMode, + }; + + NonnullRefPtr m_file; + StringView m_view {}; + HashMap> m_vendors; + HashMap> m_classes; + bool m_ready { false }; +}; + +} diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt index f9d804806f..0468aa9dde 100644 --- a/Userland/Utilities/CMakeLists.txt +++ b/Userland/Utilities/CMakeLists.txt @@ -70,6 +70,7 @@ target_link_libraries(gzip LibCompress) target_link_libraries(js LibJS LibLine) target_link_libraries(keymap LibKeyboard) target_link_libraries(lspci LibPCIDB) +target_link_libraries(lsusb LibUSBDB) target_link_libraries(man LibMarkdown) target_link_libraries(matroska LibVideo) target_link_libraries(md LibMarkdown) diff --git a/Userland/Utilities/lsusb.cpp b/Userland/Utilities/lsusb.cpp index 3859e064c7..da7d7f3ad1 100644 --- a/Userland/Utilities/lsusb.cpp +++ b/Userland/Utilities/lsusb.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,11 @@ int main(int argc, char** argv) return 1; } + if (unveil("/res/usb.ids", "r") < 0) { + perror("unveil"); + return 1; + } + if (unveil(nullptr, nullptr) < 0) { perror("unveil"); return 1; @@ -38,6 +44,11 @@ int main(int argc, char** argv) Core::DirIterator usb_devices("/proc/bus/usb", Core::DirIterator::SkipDots); + RefPtr usb_db = USBDB::Database::open(); + if (!usb_db) { + warnln("Failed to open usb.ids"); + } + while (usb_devices.has_next()) { auto full_path = LexicalPath(usb_devices.next_full_path()); @@ -52,13 +63,18 @@ int main(int argc, char** argv) auto json = JsonValue::from_string(contents); VERIFY(json.has_value()); - json.value().as_array().for_each([bus_id](auto& value) { + json.value().as_array().for_each([bus_id, usb_db](auto& value) { auto& device_descriptor = value.as_object(); auto vendor_id = device_descriptor.get("vendor_id").to_u32(); auto product_id = device_descriptor.get("product_id").to_u32(); - outln("Device {}: ID {:04x}:{:04x}", bus_id, vendor_id, product_id); + StringView vendor_string = usb_db->get_vendor(vendor_id); + StringView device_string = usb_db->get_device(vendor_id, product_id); + if (device_string.is_empty()) + device_string = "Unknown Device"; + + outln("Device {}: ID {:04x}:{:04x} {} {}", bus_id, vendor_id, product_id, vendor_string, device_string); }); }