libscu 3.0.2

crate for fetching software/hardware info on Unix-like OSs
Documentation
// test pci identifier 8086:5916 103C:82B9 (intel hd graphics 620)

use std::{
    fs::File,
    io::{self, BufRead},
    path::{Path, PathBuf},
};

const POSSIBLE_PCI_IDS_PATHS: [&str; 3] = [
    "/usr/share/hwdata/pci.ids",
    "/usr/share/misc/pci.ids",
    "/var/lib/pciutils/pci.ids",
];

const PCI_IDS_FILE_NOT_FOUND_ERROR: (io::ErrorKind, &str) =
    (io::ErrorKind::NotFound, "file pci.ids not found in system");
const PCI_IDENTIFIER_NOT_FOUND_ERROR: (io::ErrorKind, &str) =
    (io::ErrorKind::NotFound, "identifier not found");

fn find_pci_ids() -> io::Result<PathBuf> {
    for possible_path in POSSIBLE_PCI_IDS_PATHS {
        if Path::new(possible_path).is_file() {
            return Ok(PathBuf::from(possible_path));
        }
    }

    Err(io::Error::new(
        PCI_IDS_FILE_NOT_FOUND_ERROR.0,
        PCI_IDS_FILE_NOT_FOUND_ERROR.1,
    ))
}

fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
    P: AsRef<Path>,
{
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}

fn extract_model_from_line(line: &str, model_buf: &mut String, splitter: &str) {
    *model_buf = line
        .split(splitter)
        .nth(1)
        .unwrap_or_default()
        .trim()
        .to_string();
}

pub fn lookup_for_pci_id_value(pci_id: &str) -> io::Result<String> {
    let pci_ids_file_path = find_pci_ids()?;

    let pieces = pci_id
        .trim()
        .to_ascii_lowercase()
        .replace(":", " ")
        .splitn(3, " ")
        .filter(|s| !s.is_empty())
        .map(String::from)
        .collect::<Vec<String>>();
    let pieces_len = pieces.len();
    let (mut vendor, mut device, mut _subsys) = (false, false, false);

    let mut latest_known_model = String::default();

    let (vendor_must_start_with, device_must_start_with, subsys_must_start_with) = (
        pieces.first().cloned().unwrap_or_default().to_string(),
        format!("\t{}", pieces.get(1).cloned().unwrap_or_default()),
        format!("\t\t{}", pieces.get(2).cloned().unwrap_or_default()),
    );

    if let Ok(lines) = read_lines(pci_ids_file_path) {
        for line in lines.flatten() {
            if pieces_len >= 1 && !vendor && line.starts_with(&vendor_must_start_with) {
                vendor = true;
                extract_model_from_line(&line, &mut latest_known_model, &vendor_must_start_with);
            } else if pieces_len >= 2
                && (!device && vendor)
                && line.starts_with(&device_must_start_with)
            {
                device = true;
                extract_model_from_line(&line, &mut latest_known_model, &device_must_start_with);
            } else if pieces_len == 3
                && (!_subsys && vendor && device)
                && line.starts_with(&subsys_must_start_with)
            {
                _subsys = true;
                extract_model_from_line(&line, &mut latest_known_model, &subsys_must_start_with);
                break;
            }

            if [vendor, device, _subsys].iter().filter(|b| **b).count() == pieces_len {
                break;
            }
        }
    }

    if latest_known_model.is_empty() {
        return Err(io::Error::new(
            PCI_IDENTIFIER_NOT_FOUND_ERROR.0,
            PCI_IDENTIFIER_NOT_FOUND_ERROR.1,
        ));
    }

    Ok(latest_known_model)
}