Skip to main content

pciid_parser/
lib.rs

1#![warn(clippy::pedantic)]
2#![doc = include_str!("../README.md")]
3mod error;
4mod parser;
5pub mod schema;
6
7use crate::parser::Parser;
8use error::Error;
9use parser::Event;
10use schema::{Class, Device, DeviceInfo, SubClass, SubDeviceId, Vendor};
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13use std::{
14    collections::HashMap,
15    fs::File,
16    io::{BufReader, Read},
17    path::{Path, PathBuf},
18};
19
20const DB_PATHS: &[&str] = &[
21    "/usr/share/hwdata/pci.ids",
22    "/usr/share/misc/pci.ids",
23    "/run/current-system/sw/share/pci.ids", // NixOS
24    "@hwdata@/share/hwdata/pci.ids",
25];
26#[cfg(feature = "online")]
27const URL: &str = "https://pci-ids.ucw.cz/v2.2/pci.ids";
28
29#[derive(Debug)]
30pub enum VendorDataError {
31    MissingIdsFile,
32}
33
34#[derive(Debug)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36pub struct Database {
37    pub vendors: HashMap<u16, Vendor>,
38    pub classes: HashMap<u8, Class>,
39}
40
41impl Database {
42    /// Attempt to read the database from a list of known file paths
43    ///
44    /// # Errors
45    /// Returns an error when either no file could be found or the parsing fails.
46    pub fn read() -> Result<Self, Error> {
47        let file = Self::open_file()?;
48        Self::parse_db(file)
49    }
50
51    /// Read the database from a given path
52    ///
53    /// # Errors
54    /// Returns an error when the file can't be read or when parsing fails
55    pub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
56        let file = File::open(path)?;
57        Self::parse_db(file)
58    }
59
60    /// Fetch a database from an online source
61    ///
62    /// # Errors
63    /// Returns an error when the database either can't be fetched or parsed
64    #[cfg(feature = "online")]
65    pub fn get_online() -> Result<Self, Error> {
66        let response = ureq::get(URL).call()?;
67
68        Self::parse_db(response.into_body().into_reader())
69    }
70
71    /// Parse a database from the given reader
72    ///
73    /// # Errors
74    /// Returns an error whenever there's a parsing error
75    #[allow(clippy::too_many_lines)] // todo
76    pub fn parse_db<R: Read>(reader: R) -> Result<Self, Error> {
77        let reader = BufReader::new(reader);
78        let mut parser = Parser::new(reader);
79
80        let mut current_vendor: Option<(u16, Vendor)> = None;
81        let mut current_device: Option<(u16, Device)> = None;
82
83        let mut current_class: Option<(u8, Class)> = None;
84        let mut current_subclass: Option<(u8, SubClass)> = None;
85
86        let mut vendors: HashMap<u16, Vendor> = HashMap::with_capacity(2500);
87        let mut classes: HashMap<u8, Class> = HashMap::with_capacity(200);
88
89        while let Some(event) = parser.next_event()? {
90            match event {
91                Event::Vendor { id, name } => {
92                    // The vendor section is complete so it needs to be pushed to the main list
93                    if let Some((device_id, device)) = current_device.take() {
94                        let (_, vendor) = current_vendor
95                            .as_mut()
96                            .ok_or_else(Error::no_current_vendor)?;
97                        vendor.devices.insert(device_id, device);
98                    }
99                    if let Some((vendor_id, vendor)) = current_vendor.take() {
100                        vendors.insert(vendor_id, vendor);
101                    }
102
103                    let vendor = Vendor {
104                        name: name.to_owned(),
105                        devices: HashMap::new(),
106                    };
107                    current_vendor = Some((
108                        u16::from_str_radix(id, 16).map_err(|_| Error::invalid_int(id))?,
109                        vendor,
110                    ));
111                }
112                Event::Device { id, name } => {
113                    // Device section is over, write to vendor
114                    if let Some((device_id, device)) = current_device.take() {
115                        let (_, current_vendor) = current_vendor
116                            .as_mut()
117                            .ok_or_else(Error::no_current_vendor)?;
118
119                        current_vendor.devices.insert(device_id, device);
120                    }
121
122                    let device = Device {
123                        name: name.to_owned(),
124                        subdevices: HashMap::new(),
125                    };
126
127                    current_device = Some((
128                        u16::from_str_radix(id, 16).map_err(|_| Error::invalid_int(id))?,
129                        device,
130                    ));
131                }
132                Event::Subdevice {
133                    subvendor,
134                    subdevice,
135                    subsystem_name,
136                } => {
137                    let (_, current_device) = current_device
138                        .as_mut()
139                        .ok_or_else(Error::no_current_device)?;
140
141                    let subdevice_id = SubDeviceId {
142                        subvendor: u16::from_str_radix(subvendor, 16)
143                            .map_err(|_| Error::invalid_int(subvendor))?,
144                        subdevice: u16::from_str_radix(subdevice, 16)
145                            .map_err(|_| Error::invalid_int(subdevice))?,
146                    };
147                    current_device
148                        .subdevices
149                        .insert(subdevice_id, subsystem_name.to_owned());
150                }
151                Event::Class { id, name } => {
152                    if let Some((subclass_id, subclass)) = current_subclass.take() {
153                        let (_, class) =
154                            current_class.as_mut().ok_or_else(Error::no_current_class)?;
155
156                        class.subclasses.insert(subclass_id, subclass);
157                    }
158                    if let Some((class_id, class)) = current_class.take() {
159                        classes.insert(class_id, class);
160                    }
161
162                    let class = Class {
163                        name: name.to_owned(),
164                        subclasses: HashMap::new(),
165                    };
166                    current_class = Some((
167                        u8::from_str_radix(id, 16).map_err(|_| Error::invalid_int(id))?,
168                        class,
169                    ));
170                }
171                Event::SubClass { id, name } => {
172                    if let Some((subclass_id, subclass)) = current_subclass {
173                        let (_, class) =
174                            current_class.as_mut().ok_or_else(Error::no_current_class)?;
175
176                        class.subclasses.insert(subclass_id, subclass);
177                    }
178
179                    let subclass = SubClass {
180                        name: name.to_owned(),
181                        prog_ifs: HashMap::new(),
182                    };
183                    current_subclass = Some((
184                        u8::from_str_radix(id, 16).map_err(|_| Error::invalid_int(id))?,
185                        subclass,
186                    ));
187                }
188                Event::ProgIf { id, name } => {
189                    let (_, subclass) = current_subclass
190                        .as_mut()
191                        .ok_or_else(Error::no_current_subclass)?;
192
193                    subclass.prog_ifs.insert(
194                        u8::from_str_radix(id, 16).map_err(|_| Error::invalid_int(id))?,
195                        name.to_owned(),
196                    );
197                }
198            }
199        }
200        // Finish writing the last vendor and class
201        if let Some((device_id, device)) = current_device.take() {
202            let (_, vendor) = current_vendor
203                .as_mut()
204                .ok_or_else(Error::no_current_vendor)?;
205            vendor.devices.insert(device_id, device);
206        }
207        if let Some((vendor_id, vendor)) = current_vendor.take() {
208            vendors.insert(vendor_id, vendor);
209        }
210
211        if let Some((subclass_id, subclass)) = current_subclass.take() {
212            let (_, class) = current_class.as_mut().ok_or_else(Error::no_current_class)?;
213
214            class.subclasses.insert(subclass_id, subclass);
215        }
216        if let Some((class_id, class)) = current_class.take() {
217            classes.insert(class_id, class);
218        }
219
220        vendors.shrink_to_fit();
221        classes.shrink_to_fit();
222
223        Ok(Self { vendors, classes })
224    }
225
226    fn open_file() -> Result<File, Error> {
227        if let Some(path) = DB_PATHS
228            .iter()
229            .find(|path| Path::exists(&PathBuf::from(path)))
230        {
231            Ok(File::open(path)?)
232        } else {
233            Err(Error::FileNotFound)
234        }
235    }
236
237    #[must_use]
238    pub fn get_device_info(
239        &self,
240        vendor_id: u16,
241        model_id: u16,
242        subsys_vendor_id: u16,
243        subsys_model_id: u16,
244    ) -> DeviceInfo<'_> {
245        let mut vendor_name = None;
246        let mut device_name = None;
247        let mut subvendor_name = None;
248        let mut subdevice_name = None;
249
250        if let Some(vendor) = self.vendors.get(&vendor_id) {
251            vendor_name = Some(vendor.name.as_str());
252
253            if let Some(device) = vendor.devices.get(&model_id) {
254                device_name = Some(device.name.as_str());
255
256                if let Some(subvendor) = self.vendors.get(&subsys_vendor_id) {
257                    subvendor_name = Some(subvendor.name.as_str());
258                }
259
260                let subdevice_id = SubDeviceId {
261                    subvendor: subsys_vendor_id,
262                    subdevice: subsys_model_id,
263                };
264
265                subdevice_name = device.subdevices.get(&subdevice_id).map(String::as_str);
266            }
267        }
268
269        DeviceInfo {
270            vendor_name,
271            device_name,
272            subvendor_name,
273            subdevice_name,
274        }
275    }
276}
277
278/// Try to find the name of a vendor by its id.
279/// This will search the database from one of the known file paths for the name.
280///
281/// # Errors
282/// Returns an error when the file can't be read or when parsing fails
283pub fn find_vendor_name(vendor_id: u16) -> Result<Option<String>, Error> {
284    let reader = Database::open_file()?;
285    find_vendor_name_with_reader(reader, vendor_id)
286}
287
288/// Try to find the name of a vendor by its id.
289/// This will search the database from the given reader for the name.
290///
291/// # Errors
292/// Returns an error when parsing fails
293pub fn find_vendor_name_with_reader<R: Read>(
294    reader: R,
295    vendor_id: u16,
296) -> Result<Option<String>, Error> {
297    let vendor_id = format!("{vendor_id:x?}");
298
299    let mut parser = Parser::new(BufReader::new(reader));
300
301    while let Some(event) = parser.next_event()? {
302        if let Event::Vendor { id, name } = event {
303            if id == vendor_id {
304                return Ok(Some(name.to_owned()));
305            }
306        }
307    }
308
309    Ok(None)
310}
311
312/// Try to find the name of a device by its vendor and device id.
313/// This will search the database from one of the known file paths for the name.
314///
315/// # Errors
316/// Returns an error when the file can't be read or when parsing fails
317pub fn find_device_name(vendor_id: u16, device_id: u16) -> Result<Option<String>, Error> {
318    let reader = Database::open_file()?;
319    find_device_name_with_reader(reader, vendor_id, device_id)
320}
321
322/// Try to find the name of a device by its vendor and device id.
323/// This will search the database from the given reader for the name.
324///
325/// # Errors
326/// Returns an error when parsing fails
327pub fn find_device_name_with_reader<R: Read>(
328    reader: R,
329    vendor_id: u16,
330    device_id: u16,
331) -> Result<Option<String>, Error> {
332    let vendor_id = format!("{vendor_id:x?}");
333    let device_id = format!("{device_id:x?}");
334
335    let mut parser = Parser::new(BufReader::new(reader));
336
337    while let Some(event) = parser.next_event()? {
338        if let Event::Vendor { id, .. } = event {
339            if id == vendor_id {
340                while let Some(event) = parser.next_event()? {
341                    match event {
342                        Event::Device { id, name } => {
343                            if id == device_id {
344                                return Ok(Some(name.to_owned()));
345                            }
346                        }
347                        Event::Vendor { .. } => break,
348                        _ => (),
349                    }
350                }
351
352                break;
353            }
354        }
355    }
356
357    Ok(None)
358}
359
360/// Try to find the name of a subdevice by its ids.
361/// This will search the database from the given reader for the name.
362///
363/// # Errors
364/// Returns an error when parsing fails
365pub fn find_subdevice_name(
366    parent_vendor_id: u16,
367    parent_device_id: u16,
368    subvendor_id: u16,
369    subdevice_id: u16,
370) -> Result<Option<String>, Error> {
371    let reader = Database::open_file()?;
372    find_subdevice_name_with_reader(
373        reader,
374        parent_vendor_id,
375        parent_device_id,
376        subvendor_id,
377        subdevice_id,
378    )
379}
380
381/// Try to find the name of a subdevice by its ids.
382/// This will search the database from the given reader for the name.
383///
384/// # Errors
385/// Returns an error when parsing fails
386pub fn find_subdevice_name_with_reader<R: Read>(
387    reader: R,
388    parent_vendor_id: u16,
389    parent_device_id: u16,
390    subvendor_id: u16,
391    subdevice_id: u16,
392) -> Result<Option<String>, Error> {
393    let parent_vendor_id = format!("{parent_vendor_id:x?}");
394    let parent_device_id = format!("{parent_device_id:x?}");
395    let subvendor_id = format!("{subvendor_id:x?}");
396    let subdevice_id = format!("{subdevice_id:x?}");
397
398    let mut parser = Parser::new(BufReader::new(reader));
399
400    while let Some(event) = parser.next_event()? {
401        if let Event::Vendor { id, .. } = event {
402            if id == parent_vendor_id {
403                while let Some(event) = parser.next_event()? {
404                    match event {
405                        Event::Device { id, .. } => {
406                            if id == parent_device_id {
407                                while let Some(event) = parser.next_event()? {
408                                    match event {
409                                        Event::Subdevice {
410                                            subvendor,
411                                            subdevice,
412                                            subsystem_name,
413                                        } => {
414                                            if subvendor == subvendor_id
415                                                && subdevice == subdevice_id
416                                            {
417                                                return Ok(Some(subsystem_name.to_owned()));
418                                            }
419                                        }
420                                        _ => break,
421                                    }
422                                }
423
424                                break;
425                            }
426                        }
427                        Event::Vendor { .. } => break,
428                        _ => (),
429                    }
430                }
431
432                break;
433            }
434        }
435    }
436
437    Ok(None)
438}