Skip to content
scanner.rs 11.1 KiB
Newer Older
Luker's avatar
Luker committed
/*
 * Copyright (c) 2021, Luca Fulchir <luker@fenrirproject.org>
 *
 * This file is part of dfim.
 *
 * dfim is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * dfim is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with dfim.  If not, see <https://www.gnu.org/licenses/>.
 */

use super::DeviceMsg;
Luker's avatar
Luker committed
use ::std::sync::{mpsc, Arc, Condvar};
Luker's avatar
Luker committed

pub async fn scanner(
    logger: Arc<::slog::Logger>,
Luker's avatar
Luker committed
    cfg: Arc<dfim::state::cfg::Cfg>,
Luker's avatar
Luker committed
    tx: mpsc::Sender<DeviceMsg>,
    cvar: Arc<Condvar>,
) -> Result<(), ::std::io::Error> {
    // First setup the udev monitors, then enumerate all devices one, then just
    // wait for udev events
Luker's avatar
Luker committed
    ::slog::debug!(logger, "Spawning local udev monitors");
    let mut mon_vec = Vec::with_capacity(cfg.devices.len());
Luker's avatar
Luker committed
    for dev in &cfg.devices {
Luker's avatar
Luker committed
        //let mut mon_builder = ::udev::MonitorBuilder::new().unwrap();
        let mut mon_builder = ::tokio_udev::MonitorBuilder::new().unwrap();
Luker's avatar
Luker committed
        match &dev.subsystem {
            None => {}
            Some(ss) => match &dev.devtype {
                None => {
                    mon_builder = match mon_builder.match_subsystem(ss.to_str())
                    {
                        Ok(new_mon_builder) => new_mon_builder,
                        Err(e) => {
                            ::slog::error!(
                                logger,
                                "Can't monitor subsystem \"{}\"",
                                ss
                            );
                            return Err(e);
                        }
                    };
                }
                Some(dt) => {
                    mon_builder = match mon_builder
                        .match_subsystem_devtype(ss.to_str(), dt.to_str())
                    {
                        Ok(new_mon_builder) => new_mon_builder,
                        Err(e) => {
                            ::slog::error!(
                                logger,
                                "Can't monitor subsystem \"{}\" with devtype \
                                 \"{}\"",
                                ss,
                                dt
                            );
                            return Err(e);
                        }
                    };
                }
            },
        }
        for tag in &dev.tags {
            mon_builder = match mon_builder.match_tag(&tag.id) {
                Ok(new_mon_builder) => new_mon_builder,
                Err(e) => {
                    ::slog::error!(
                        logger,
                        "Can't filter with tag \"{}\"",
                        &tag.id
                    );
                    return Err(e);
                }
            }
        }
Luker's avatar
Luker committed
        let mon_sock = match mon_builder.listen() {
Luker's avatar
Luker committed
            Ok(sock) => sock,
            Err(e) => {
                ::slog::error!(logger, "Can't create socket to monitor dev");
                return Err(e);
            }
        };
Luker's avatar
Luker committed
        let async_mon =
            ::tokio_udev::AsyncMonitorSocket::new(mon_sock).unwrap();
        // let th_join = ::tokio::spawn(monitor_udev(
        let th_join = ::tokio::task::spawn_local(monitor_udev(
            logger.clone(),
Luker's avatar
Luker committed
            cfg.clone(),
Luker's avatar
Luker committed
            async_mon,
            tx.clone(),
            cvar.clone(),
        ));
        mon_vec.push(th_join);
Luker's avatar
Luker committed
    }
Luker's avatar
Luker committed

    // enumerate all devices
Luker's avatar
Luker committed
    let udev_ctx = ::udev::Udev::new().unwrap();
Luker's avatar
Luker committed
    let mut devices = ::udev::Enumerator::with_udev(udev_ctx.clone())?;
    devices.match_is_initialized().unwrap();

    let dev_list = devices.scan_devices()?;

Luker's avatar
Luker committed
    ::slog::debug!(logger, "Scanning all devices...");
Luker's avatar
Luker committed
    for dev in dev_list {
        match filter_devices(&logger, cfg.clone(), dev) {
Luker's avatar
Luker committed
            Ok(dev) => {
Luker's avatar
Luker committed
                match tx.send(DeviceMsg {
Luker's avatar
Luker committed
                    id: dev.0,
                    path: dev.1.syspath().into(),
Luker's avatar
Luker committed
                }) {
Luker's avatar
Luker committed
                    Ok(()) => {}
                    Err(_) => {
                        // the receiver has been closed
                        // it's pointless to do any more work
                        return Err(::std::io::Error::new(
                            ::std::io::ErrorKind::ConnectionReset,
                            "work queue receiver has been closed",
                        ));
                    }
                }
                cvar.notify_one();
            }
            Err(()) => {}
        }
Luker's avatar
Luker committed
    }
Luker's avatar
Luker committed
    for th in mon_vec {
        th.await;
    }

    Ok(())
}
Luker's avatar
Luker committed
async fn monitor_udev(
    logger: Arc<::slog::Logger>,
Luker's avatar
Luker committed
    cfg: Arc<dfim::state::cfg::Cfg>,
Luker's avatar
Luker committed
    mut async_mon: ::tokio_udev::AsyncMonitorSocket,
    tx: mpsc::Sender<DeviceMsg>,
    cvar: Arc<Condvar>,
) -> Result<(), ::std::io::Error> {
    loop {
        match super::WORK_STATE.load(::std::sync::atomic::Ordering::Relaxed) {
            super::WorkState::KeepWorking => {}
            super::WorkState::ShutdownImmediate
            | super::WorkState::ShutdownAllGraceful
            | super::WorkState::ShutdownScannerGraceful => break,
        }
        use ::tokio_stream::StreamExt;
        while let Some(res_event) = async_mon.next().await {
            match res_event {
                Ok(event) => {
                    match filter_devices(&logger, cfg.clone(), event.device()) {
Luker's avatar
Luker committed
                        Ok(dev) => {
                            match tx.send(DeviceMsg {
                                id: dev.0,
                                path: dev.1.syspath().into(),
                            }) {
                                Ok(()) => {}
                                Err(_) => {
                                    // the receiver has been closed
                                    // it's pointless to do any more work
                                    return Err(::std::io::Error::new(
                                        ::std::io::ErrorKind::ConnectionReset,
                                        "work queue receiver has been closed",
                                    ));
                                }
                            }
                            cvar.notify_one();
Luker's avatar
Luker committed
                        }
Luker's avatar
Luker committed
                        Err(_) => {}
Luker's avatar
Luker committed
                    }
                }
                Err(_) => {
                    // the receiver has been closed
                    // it's pointless to do any more work
                    return Err(::std::io::Error::new(
                        ::std::io::ErrorKind::ConnectionReset,
                        "udev socket reset",
                    ));
                }
            }
        }
    }
Luker's avatar
Luker committed
    Ok(())
}
Luker's avatar
Luker committed

fn filter_devices(
    logger: &::slog::Logger,
Luker's avatar
Luker committed
    cfg: Arc<dfim::state::cfg::Cfg>,
Luker's avatar
Luker committed
    dev: ::udev::Device,
Luker's avatar
Luker committed
) -> Result<(String, ::udev::Device), ()> {
    if !dev.is_initialized() {
        return Err(());
    }

    use ::std::ptr::NonNull;
    use dfim::config::device::Device;

    let mut last_matched: Option<&Device> = None;
Luker's avatar
Luker committed
    'devloop: for cfg_dev in &cfg.devices {
Luker's avatar
Luker committed
        match &cfg_dev.subsystem {
            None => {}
            Some(s) => {
                let os_s: ::std::ffi::OsString = s.to_str().into();
                if dev.subsystem() != Some(os_s.as_os_str()) {
Luker's avatar
Luker committed
                    continue 'devloop;
Luker's avatar
Luker committed
                }
            }
        }
        match &cfg_dev.devtype {
            None => {}
            Some(dt) => {
                let os_dt: ::std::ffi::OsString = dt.to_str().into();
                if dev.devtype() != Some(os_dt.as_os_str()) {
Luker's avatar
Luker committed
                    continue 'devloop;
Luker's avatar
Luker committed
        let path = match dev.devpath().to_str() {
            Some(path) => path,
Luker's avatar
Luker committed
            None => continue 'devloop,
Luker's avatar
Luker committed
        };
        if !cfg_dev.path_regex.is_match(path) {
Luker's avatar
Luker committed
            continue 'devloop;
Luker's avatar
Luker committed
        }
        for attr in &cfg_dev.attributes {
            let os_name: ::std::ffi::OsString = attr.name.clone().into();
            match dev.attribute_value(os_name) {
                Some(val) => {
                    let os_value: ::std::ffi::OsString =
                        attr.value.clone().into();
                    if val != os_value {
Luker's avatar
Luker committed
                        continue 'devloop;
Luker's avatar
Luker committed
                    }
                }
                None => {
Luker's avatar
Luker committed
                    continue 'devloop;
Luker's avatar
Luker committed
                }
            }
        }
        for attr in &cfg_dev.exclude_attributes {
            let os_name: ::std::ffi::OsString = attr.name.clone().into();
            match dev.attribute_value(os_name) {
                Some(val) => {
                    let os_value: ::std::ffi::OsString =
                        attr.value.clone().into();
                    if val == os_value {
Luker's avatar
Luker committed
                        continue 'devloop;
Luker's avatar
Luker committed
                    }
                }
                None => {}
            }
        }
        match &cfg_dev.sysname {
            None => {}
            Some(sn) => {
                let os_sn: ::std::ffi::OsString = sn.as_str().into();
                if dev.sysname() != os_sn.as_os_str() {
Luker's avatar
Luker committed
                    continue 'devloop;
Luker's avatar
Luker committed
                }
            }
        }
        for prop in &cfg_dev.properties {
            let os_name: ::std::ffi::OsString = prop.name.clone().into();
            match dev.property_value(os_name) {
                Some(val) => {
                    let os_value: ::std::ffi::OsString =
                        prop.value.clone().into();
                    if val != os_value {
Luker's avatar
Luker committed
                        continue 'devloop;
Luker's avatar
Luker committed
                    }
                }
                None => {
Luker's avatar
Luker committed
                    continue 'devloop;
Luker's avatar
Luker committed
                }
            }
        }
        // TODO: TAGS
        if let Some(old_match) = last_matched {
            ::slog::error!(
                logger,
                "device \"{:?}\" has multiple matches: \"{}\" - \"{}\"",
                dev.devpath(),
                old_match.id,
                cfg_dev.id
            );
            return Err(());
        }
        last_matched = Some(&cfg_dev);
    }
    match last_matched {
        None => Err(()),
        Some(cfg_dev) => {
            ::slog::debug!(
                logger,
                "FOUND: {:?} - {:?} - {:?} - {:?} - {:?} - {:?} - {:?} - {:?} \
                 - {:?} - {:?}",
                dev.devpath(),
                dev.is_initialized(),
                dev.devnum(),
                dev.syspath(),
                dev.devnode(),
                dev.subsystem(),
                dev.sysname(),
                dev.sysnum(),
                dev.devtype(),
                dev.driver()
            );
            for a in dev.attributes() {
                ::slog::debug!(logger, "A: {:?} - {:?}", a.name(), a.value());
            }
            for p in dev.properties() {
                ::slog::debug!(logger, "P: {:?} - {:?}", p.name(), p.value());
            }
            Ok((cfg_dev.id.to_owned(), dev))
        }