network-manager 0.11.0

Rust NetworkManager bindings
Documentation
extern crate dbus;
extern crate futures;
extern crate futures_cpupool;
extern crate tokio_timer;

use std::str::FromStr;
use std::time::Duration;
use self::dbus::{BusType, Connection, ConnectionItem, Interface, Member, Message, Path, Props};
use self::dbus::arg::{Dict, Iter, Variant};
use self::futures::Future;
use self::futures_cpupool::CpuPool;
use self::tokio_timer::Timer;

use errors::*;

pub const SD_SERVICE_MANAGER: &str = "org.freedesktop.systemd1";
pub const SD_SERVICE_PATH: &str = "/org/freedesktop/systemd1";
pub const SD_MANAGER_INTERFACE: &str = "org.freedesktop.systemd1.Manager";
pub const SD_UNIT_INTERFACE: &str = "org.freedesktop.systemd1.Unit";

pub fn start_service(timeout: u64) -> Result<ServiceState> {
    let state = get_service_state()?;
    match state {
        ServiceState::Active => Ok(state),
        ServiceState::Activating => handler(timeout, ServiceState::Active),
        ServiceState::Failed => bail!(ErrorKind::Service),
        _ => {
            let message = Message::new_method_call(
                SD_SERVICE_MANAGER,
                SD_SERVICE_PATH,
                SD_MANAGER_INTERFACE,
                "StartUnit",
            ).map_err(|_| ErrorKind::Service)?
                .append2("NetworkManager.service", "fail");

            let connection =
                Connection::get_private(BusType::System).map_err(|_| ErrorKind::Service)?;

            connection
                .send_with_reply_and_block(message, 2000)
                .map_err(|_| ErrorKind::Service)?;

            handler(timeout, ServiceState::Active)
        },
    }
}

pub fn stop_service(timeout: u64) -> Result<ServiceState> {
    let state = get_service_state()?;
    match state {
        ServiceState::Inactive => Ok(state),
        ServiceState::Deactivating => handler(timeout, ServiceState::Inactive),
        ServiceState::Failed => bail!(ErrorKind::Service),
        _ => {
            let message = Message::new_method_call(
                SD_SERVICE_MANAGER,
                SD_SERVICE_PATH,
                SD_MANAGER_INTERFACE,
                "StopUnit",
            ).map_err(|_| ErrorKind::Service)?
                .append2("NetworkManager.service", "fail");

            let connection =
                Connection::get_private(BusType::System).map_err(|_| ErrorKind::Service)?;

            connection
                .send_with_reply_and_block(message, 2000)
                .map_err(|_| ErrorKind::Service)?;

            handler(timeout, ServiceState::Inactive)
        },
    }
}

pub fn get_service_state() -> Result<ServiceState> {
    let message = Message::new_method_call(
        SD_SERVICE_MANAGER,
        SD_SERVICE_PATH,
        SD_MANAGER_INTERFACE,
        "GetUnit",
    ).map_err(|_| ErrorKind::Service)?
        .append1("NetworkManager.service");

    let connection = Connection::get_private(BusType::System).map_err(|_| ErrorKind::Service)?;

    let response = connection
        .send_with_reply_and_block(message, 2000)
        .map_err(|_| ErrorKind::Service)?;

    let path = response.get1::<Path>().ok_or(ErrorKind::Service)?;

    let response = Props::new(
        &connection,
        SD_SERVICE_MANAGER,
        path,
        SD_UNIT_INTERFACE,
        2000,
    ).get("ActiveState")
        .map_err(|_| ErrorKind::Service)?;

    response
        .inner::<&str>()
        .ok()
        .ok_or(ErrorKind::Service)?
        .parse()
}

fn handler(timeout: u64, target_state: ServiceState) -> Result<ServiceState> {
    if timeout == 0 {
        return get_service_state();
    }

    let timer = Timer::default()
        .sleep(Duration::from_secs(timeout))
        .then(|_| bail!(ErrorKind::Service));

    let process = CpuPool::new_num_cpus().spawn_fn(|| {
        let connection = Connection::get_private(BusType::System).map_err(|_| ErrorKind::Service)?;
        connection
            .add_match(
                "type='signal', sender='org.freedesktop.systemd1', \
                 interface='org.freedesktop.DBus.Properties', \
                 member='PropertiesChanged', \
                 path='/org/freedesktop/systemd1/unit/NetworkManager_2eservice'",
            )
            .map_err(|_| ErrorKind::Service)?;

        if get_service_state()? == target_state {
            return Ok(target_state);
        }

        for item in connection.iter(0) {
            let response = if let ConnectionItem::Signal(ref signal) = item {
                signal
            } else {
                continue;
            };

            if response.interface().ok_or(ErrorKind::Service)?
                != Interface::from("org.freedesktop.DBus.Properties")
                || response.member().ok_or(ErrorKind::Service)? != Member::from("PropertiesChanged")
                || response.path().ok_or(ErrorKind::Service)?
                    != Path::from("/org/freedesktop/systemd1/unit/NetworkManager_2eservice")
            {
                continue;
            }

            let (interface, dictionary) = response.get2::<&str, Dict<&str, Variant<Iter>, _>>();

            if interface.ok_or(ErrorKind::Service)? != "org.freedesktop.systemd1.Unit" {
                continue;
            }

            for (k, mut v) in dictionary.ok_or(ErrorKind::Service)? {
                if k == "ActiveState" {
                    let response = v.0.get::<&str>().ok_or(ErrorKind::Service)?;
                    let state: ServiceState = response.parse()?;
                    if state == target_state {
                        return Ok(target_state);
                    }
                }
            }
        }
        bail!(ErrorKind::Service)
    });

    match timer.select(process).map(|(result, _)| result).wait() {
        Ok(val) => Ok(val),
        Err(val) => Err(val.0),
    }
}

#[derive(Debug, Eq, PartialEq)]
pub enum ServiceState {
    Active,
    Reloading,
    Inactive,
    Failed,
    Activating,
    Deactivating,
}

impl FromStr for ServiceState {
    type Err = Error;
    fn from_str(s: &str) -> Result<ServiceState> {
        match s {
            "active" => Ok(ServiceState::Active),
            "reloading" => Ok(ServiceState::Reloading),
            "inactive" => Ok(ServiceState::Inactive),
            "failed" => Ok(ServiceState::Failed),
            "activating" => Ok(ServiceState::Activating),
            "deactivating" => Ok(ServiceState::Deactivating),
            _ => bail!(ErrorKind::Service),
        }
    }
}