use crate::udev_list; use crate::udev_list::udev_list_entry; use std::io::BufRead; use std::{ffi, fs, io, ptr, sync}; const USB_IDS_PATH: &str = env!("LIBUDEV_ZERO_USB_IDS_PATH"); #[allow(non_camel_case_types)] #[repr(C)] pub struct udev { _private: [u8; 0], } #[derive(Default)] struct Hwdb { head: udev_list::ListEntry, } #[allow(non_camel_case_types)] #[repr(C)] pub struct udev_hwdb { _private: [u8; 0], } type UdevLogFn = Option< unsafe extern "C" fn( *mut udev, ffi::c_int, *const ffi::c_char, ffi::c_int, *const ffi::c_char, *const ffi::c_char, *mut ffi::c_void, ), >; #[unsafe(no_mangle)] pub extern "C" fn udev_new() -> *mut udev { sync::Arc::into_raw(sync::Arc::new(udev { _private: [] })) .cast_mut() .cast() } #[unsafe(no_mangle)] pub extern "C" fn udev_ref(udev: *mut udev) -> *mut udev { if !udev.is_null() { unsafe { sync::Arc::increment_strong_count(udev.cast_const()) }; } udev } #[unsafe(no_mangle)] pub extern "C" fn udev_unref(udev: *mut udev) -> *mut udev { if udev.is_null() { return ptr::null_mut(); } let raw = udev; let udev = unsafe { sync::Arc::from_raw(udev.cast_const()) }; if sync::Arc::into_inner(udev).is_some() { ptr::null_mut() } else { raw } } #[unsafe(no_mangle)] pub extern "C" fn udev_set_log_fn(_udev: *mut udev, _log_fn: UdevLogFn) {} #[unsafe(no_mangle)] pub extern "C" fn udev_set_log_priority(_udev: *mut udev, _priority: ffi::c_int) {} #[unsafe(no_mangle)] pub extern "C" fn udev_get_log_priority(_udev: *mut udev) -> ffi::c_int { 0 } #[unsafe(no_mangle)] pub extern "C" fn udev_hwdb_new(_udev: *mut udev) -> *mut udev_hwdb { sync::Arc::into_raw(sync::Arc::new(Hwdb::default())) .cast::() .cast_mut() } #[unsafe(no_mangle)] pub extern "C" fn udev_hwdb_ref(hwdb: *mut udev_hwdb) -> *mut udev_hwdb { if !hwdb.is_null() { unsafe { sync::Arc::increment_strong_count(hwdb.cast::()) }; } hwdb } #[unsafe(no_mangle)] pub extern "C" fn udev_hwdb_unref(hwdb: *mut udev_hwdb) -> *mut udev_hwdb { if !hwdb.is_null() { unsafe { drop(sync::Arc::from_raw(hwdb.cast::())); } } ptr::null_mut() } #[unsafe(no_mangle)] pub extern "C" fn udev_hwdb_get_properties_list_entry( hwdb: *mut udev_hwdb, modalias: *const ffi::c_char, _flags: ffi::c_uint, ) -> *mut udev_list_entry { if hwdb.is_null() || modalias.is_null() { return ptr::null_mut(); } let hwdb = unsafe { &mut *hwdb.cast::() }; if let Some(next) = hwdb.head.next.take() { drop(next); } let modalias = unsafe { ffi::CStr::from_ptr(modalias) }.to_string_lossy(); match parse_usb_modalias(&modalias) { Some((vendor_id, Some(product_id))) => { if let Some(name) = usb_ids_lookup_product(vendor_id, product_id) { hwdb.head.add_entry( ffi::CString::new("ID_MODEL_FROM_DATABASE").unwrap(), Some(name), ); } } Some((vendor_id, None)) => { if let Some(name) = usb_ids_lookup_vendor(vendor_id) { hwdb.head.add_entry( ffi::CString::new("ID_VENDOR_FROM_DATABASE").unwrap(), Some(name), ); } } None => {} } hwdb.head .next .as_deref_mut() .map_or(ptr::null_mut(), |next| ptr::from_mut(next).cast()) } fn usb_ids_lookup_product(vendor_id: u16, product_id: u16) -> Option { let file = fs::File::open(USB_IDS_PATH).ok()?; let reader = io::BufReader::new(file); let mut in_vendor = false; for line in reader.lines().map_while(Result::ok) { if line.is_empty() || line.starts_with('#') { continue; } if in_vendor { let Some(product_line) = line.strip_prefix('\t') else { break; }; if product_line.starts_with('\t') { continue; } let Some(id_text) = product_line.split_whitespace().next() else { continue; }; let Ok(id) = u16::from_str_radix(id_text, 16) else { continue; }; if id != product_id { continue; } let Some((_, name)) = product_line.split_once(' ') else { break; }; let name = name.trim_start_matches(' ').trim_end_matches('\r'); return ffi::CString::new(name).ok(); } if line.starts_with('\t') { continue; } let Some(id_text) = line.split_whitespace().next() else { continue; }; let Ok(id) = u16::from_str_radix(id_text, 16) else { continue; }; if id == vendor_id { in_vendor = true; } } None } fn usb_ids_lookup_vendor(vendor_id: u16) -> Option { let file = fs::File::open(USB_IDS_PATH).ok()?; let reader = io::BufReader::new(file); for line in reader.lines().map_while(Result::ok) { if line.is_empty() || line.starts_with('#') || line.starts_with('\t') { continue; } let Some(id_text) = line.split_whitespace().next() else { continue; }; let Ok(id) = u16::from_str_radix(id_text, 16) else { continue; }; if id != vendor_id { continue; } let Some((_, name)) = line.split_once(' ') else { break; }; let name = name.trim_start_matches(' ').trim_end_matches('\r'); if name.is_empty() { break; } return ffi::CString::new(name).ok(); } None } fn parse_usb_modalias(modalias: &str) -> Option<(u16, Option)> { let rest = modalias.strip_prefix("usb:v")?; let vendor_id = u16::from_str_radix(rest.get(..4)?, 16).ok()?; if let Some(product) = rest.get(4..)?.strip_prefix('p') { let product_id = u16::from_str_radix(product.get(..4)?, 16).ok()?; Some((vendor_id, Some(product_id))) } else { Some((vendor_id, None)) } }