<commit_before>// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::io;
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::Path;
use std::thread;

use base::{error, Event, EventType, PollToken, RawDescriptor, WaitContext};
use data_model::{DataInit, Le32};
use vm_memory::GuestMemory;

use crate::virtio::descriptor_utils::Error as DescriptorUtilsError;
use crate::virtio::{copy_config, Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_VHOST_USER};

use remain::sorted;
use thiserror::Error as ThisError;

// Proxy device i.e. this device's configuration.
const NUM_PROXY_DEVICE_QUEUES: usize = 2;
const PROXY_DEVICE_QUEUE_SIZE: u16 = 256;
const PROXY_DEVICE_QUEUE_SIZES: &[u16] = &[PROXY_DEVICE_QUEUE_SIZE; NUM_PROXY_DEVICE_QUEUES];

#[sorted]
#[derive(ThisError, Debug)]
pub enum Error {
    /// Failed to accept connection on a socket.
    #[error("failed to accept connection on a socket: {0}")]
    AcceptConnection(std::io::Error),
    /// Failed to create a listener.
    #[error("failed to create a listener: {0}")]
    CreateListener(std::io::Error),
    /// Failed to create a wait context object.
    #[error("failed to create a wait context object: {0}")]
    CreateWaitContext(base::Error),
    /// There are no more available descriptors to receive into.
    #[error("no rx descriptors available")]
    RxDescriptorsExhausted,
    /// Removing read event from the VhostVmmSocket fd events failed.
    #[error("failed to disable EPOLLIN on VhostVmmSocket fd: {0}")]
    WaitContextDisableVhostVmmSocket(base::Error),
    /// Adding read event to the VhostVmmSocket fd events failed.
    #[error("failed to enable EPOLLIN on VhostVmmSocket fd: {0}")]
    WaitContextEnableVhostVmmSocket(base::Error),
    /// Failed to wait for events.
    #[error("failed to wait for events: {0}")]
    WaitError(base::Error),
    /// Writing to a buffer in the guest failed.
    #[error("failed to write to guest buffer: {0}")]
    WriteBuffer(std::io::Error),
    /// Failed to create a Writer.
    #[error("failed to create a Writer: {0}")]
    WriterCreation(DescriptorUtilsError),
}

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
struct VirtioVhostUserConfig {
    status: Le32,
    max_vhost_queues: Le32,
    uuid: [u8; 16],
}

// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for VirtioVhostUserConfig {}

struct Worker {
    mem: GuestMemory,
    interrupt: Interrupt,
    rx_queue: Queue,
    tx_queue: Queue,
    vhost_vmm_socket: UnixStream,
}

impl Worker {
    fn run(&mut self, rx_queue_evt: Event, tx_queue_evt: Event, kill_evt: Event) -> Result<()> {
        #[derive(PollToken, Debug, Clone)]
        pub enum Token {
            // Data is available on the vhost vmm socket.
            VhostVmmSocket,
            // The vhost-device has made a read buffer available.
            RxQueue,
            // The vhost-device has sent a buffer to the |Worker::tx_queue|.
            TxQueue,
            // crosvm has requested the device to shut down.
            Kill,
        }

        let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
            (&self.vhost_vmm_socket, Token::VhostVmmSocket),
            (&rx_queue_evt, Token::RxQueue),
            (&tx_queue_evt, Token::TxQueue),
            (&kill_evt, Token::Kill),
        ])
        .map_err(Error::CreateWaitContext)?;

        let mut vhost_vmm_socket_polling_enabled = true;
        'wait: loop {
            let events = wait_ctx.wait().map_err(Error::WaitError)?;
            for event in events.iter().filter(|e| e.is_readable) {
                match event.token {
                    Token::VhostVmmSocket => match self.process_rx() {
                        Ok(()) => {}
                        Err(Error::RxDescriptorsExhausted) => {
                            wait_ctx
                                .modify(
                                    &self.vhost_vmm_socket,
                                    EventType::None,
                                    Token::VhostVmmSocket,
                                )
                                .map_err(Error::WaitContextDisableVhostVmmSocket)?;
                            vhost_vmm_socket_polling_enabled = false;
                        }
                        Err(e) => return Err(e),
                    },
                    Token::RxQueue => {
                        if let Err(e) = rx_queue_evt.read() {
                            error!("net: error reading rx queue Event: {}", e);
                            break 'wait;
                        }
                        if !vhost_vmm_socket_polling_enabled {
                            wait_ctx
                                .modify(
                                    &self.vhost_vmm_socket,
                                    EventType::Read,
                                    Token::VhostVmmSocket,
                                )
                                .map_err(Error::WaitContextEnableVhostVmmSocket)?;
                            vhost_vmm_socket_polling_enabled = true;
                        }
                    }
                    Token::TxQueue => {
                        if let Err(e) = tx_queue_evt.read() {
                            error!("error reading rx queue event: {}", e);
                            break 'wait;
                        }
                        self.process_tx();
                    }
                    Token::Kill => {
                        let _ = kill_evt.read();
                        break 'wait;
                    }
                }
            }
        }
        Ok(())
    }

    fn process_rx(&mut self) -> Result<()> {
        let mut exhausted_queue = false;

        // Read as many frames as possible.
        loop {
            let desc_chain = match self.rx_queue.peek(&self.mem) {
                Some(desc) => desc,
                None => {
                    exhausted_queue = true;
                    break;
                }
            };

            let index = desc_chain.index;
            let bytes_written = match Writer::new(self.mem.clone(), desc_chain) {
                Ok(mut writer) => {
                    match writer.write_from(&mut self.vhost_vmm_socket, writer.available_bytes()) {
                        Ok(_) => {}
                        Err(ref e) if e.kind() == io::ErrorKind::WriteZero => {
                            error!("rx: buffer is too small to hold frame");
                            break;
                        }
                        Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
                            // No more to read.
                            break;
                        }
                        Err(e) => {
                            error!("rx: failed to write slice: {}", e);
                            return Err(Error::WriteBuffer(e));
                        }
                    };

                    writer.bytes_written() as u32
                }
                Err(e) => {
                    error!("failed to create Writer: {}", e);
                    0
                }
            };

            // The driver is able to deal with a descriptor with 0 bytes written.
            self.rx_queue.pop_peeked(&self.mem);
            self.rx_queue.add_used(&self.mem, index, bytes_written);
            self.rx_queue.trigger_interrupt(&self.mem, &self.interrupt);
        }

        if exhausted_queue {
            Err(Error::RxDescriptorsExhausted)
        } else {
            Ok(())
        }
    }

    fn process_tx(&mut self) {
        while let Some(desc_chain) = self.tx_queue.pop(&self.mem) {
            let index = desc_chain.index;
            match Reader::new(self.mem.clone(), desc_chain) {
                Ok(mut reader) => {
                    let expected_count = reader.available_bytes();
                    match reader.read_to(&mut self.vhost_vmm_socket, expected_count) {
                        Ok(count) => {
                            // Datagram messages should be sent as whole.
                            // TODO: Should this be a panic! as it will violate the Linux API.
                            if count != expected_count {
                                error!("wrote only {} bytes of {}", count, expected_count);
                            }
                        }
                        Err(e) => error!("failed to write message to vhost-vmm: {}", e),
                    }
                }
                Err(e) => error!("failed to create Reader: {}", e),
            }
            self.tx_queue.add_used(&self.mem, index, 0);
            self.tx_queue.trigger_interrupt(&self.mem, &self.interrupt);
        }
    }
}

pub struct VirtioVhostUser {
    vhost_vmm_socket: Option<UnixStream>,
    config: VirtioVhostUserConfig,
    kill_evt: Option<Event>,
    worker_thread: Option<thread::JoinHandle<Worker>>,
}

impl VirtioVhostUser {
    pub fn new(vhost_vmm_socket_path: &Path) -> Result<VirtioVhostUser> {
        let listener = UnixListener::bind(vhost_vmm_socket_path).map_err(Error::CreateListener)?;
        let (socket, _) = listener.accept().map_err(Error::AcceptConnection)?;
        Ok(VirtioVhostUser {
            vhost_vmm_socket: Some(socket),
            config: Default::default(),
            kill_evt: None,
            worker_thread: None,
        })
    }
}

impl Drop for VirtioVhostUser {
    fn drop(&mut self) {
        if let Some(kill_evt) = self.kill_evt.take() {
            match kill_evt.write(1) {
                Ok(()) => {
                    if let Some(worker_thread) = self.worker_thread.take() {
                        // Ignore the result because there is nothing we can do about it.
                        let _ = worker_thread.join();
                    }
                }
                Err(e) => error!("failed to write kill event: {}", e),
            }
        }
    }
}

impl VirtioDevice for VirtioVhostUser {
    fn keep_rds(&self) -> Vec<RawDescriptor> {
        vec![]
    }

    fn device_type(&self) -> u32 {
        TYPE_VHOST_USER
    }

    fn queue_max_sizes(&self) -> &[u16] {
        PROXY_DEVICE_QUEUE_SIZES
    }

    fn read_config(&self, offset: u64, data: &mut [u8]) {
        copy_config(
            data,
            0, /* dst_offset */
            self.config.as_slice(),
            offset,
        );
    }

    fn write_config(&mut self, offset: u64, data: &[u8]) {
        // TODO: Confirm if |data| is guaranteed to be sent in LE.
        copy_config(
            self.config.as_mut_slice(),
            offset,
            data,
            0, /* src_offset */
        );
    }

    fn activate(
        &mut self,
        mem: GuestMemory,
        interrupt: Interrupt,
        mut queues: Vec<Queue>,
        mut queue_evts: Vec<Event>,
    ) {
        if queues.len() != NUM_PROXY_DEVICE_QUEUES || queue_evts.len() != NUM_PROXY_DEVICE_QUEUES {
            error!("bad queue length: {} {}", queues.len(), queue_evts.len());
            return;
        }

        let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
            Ok(v) => v,
            Err(e) => {
                error!("failed creating kill Event pair: {}", e);
                return;
            }
        };
        self.kill_evt = Some(self_kill_evt);

        // The socket will be moved to the worker thread. Guaranteed to be valid as a connection is
        // ensured in |VirtioVhostUser::new|.
        let vhost_vmm_socket = self
            .vhost_vmm_socket
            .take()
            .expect("socket connection missing");

        let worker_result = thread::Builder::new()
            .name("virtio_vhost_user".to_string())
            .spawn(move || {
                let rx_queue = queues.remove(0);
                let tx_queue = queues.remove(0);
                let mut worker = Worker {
                    mem,
                    interrupt,
                    rx_queue,
                    tx_queue,
                    vhost_vmm_socket,
                };
                let rx_queue_evt = queue_evts.remove(0);
                let tx_queue_evt = queue_evts.remove(0);
                let _ = worker.run(rx_queue_evt, tx_queue_evt, kill_evt);
                worker
            });

        match worker_result {
            Err(e) => {
                error!("failed to spawn virtio_vhost_user worker: {}", e);
            }
            Ok(join_handle) => {
                self.worker_thread = Some(join_handle);
            }
        }
    }

    fn reset(&mut self) -> bool {
        // TODO
        true
    }
}
<commit_msg>crosvm: vvu: proxy: Allocate bar and add handler stubs<commit_after>// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::io;
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::Path;
use std::thread;

use base::{error, info, Event, EventType, PollToken, RawDescriptor, WaitContext};
use data_model::{DataInit, Le32};
use resources::Alloc;
use vm_memory::GuestMemory;

use crate::pci::{PciBarConfiguration, PciBarIndex, PciBarPrefetchable, PciBarRegionType};
use crate::virtio::descriptor_utils::Error as DescriptorUtilsError;
use crate::virtio::{copy_config, Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_VHOST_USER};
use crate::PciAddress;

use remain::sorted;
use thiserror::Error as ThisError;

// Proxy device i.e. this device's configuration.
const NUM_PROXY_DEVICE_QUEUES: usize = 2;
const PROXY_DEVICE_QUEUE_SIZE: u16 = 256;
const PROXY_DEVICE_QUEUE_SIZES: &[u16] = &[PROXY_DEVICE_QUEUE_SIZE; NUM_PROXY_DEVICE_QUEUES];
const BAR_INDEX: u8 = 2;
// Bar size represents the amount of memory to be mapped for a sibling VM. Each
// Virtio Vhost User Slave implementation requires access to the entire sibling
// memory. It's assumed that sibling VM memory would be <= 8GB, hence this
// constant value.
//
// TODO(abhishekbh): Understand why shared memory region size and overall bar
// size differ in the QEMU implementation. The metadata required to map sibling
// memory is about 16 MB per GB of sibling memory per device. Therefore, it is
// in our interest to not waste space here and correlate it tightly to the
// actual maximum memory a sibling VM can have.
const BAR_SIZE: u64 = 1 << 33;

// Bar configuration.
// All offsets are from the starting of bar |BAR_INDEX|.
const DOORBELL_OFFSET: u64 = 0;
// TODO(abhishekbh): Copied from lspci in qemu with VVU support.
const DOORBELL_SIZE: u64 = 0x2000;
const NOTIFICATIONS_OFFSET: u64 = DOORBELL_OFFSET + DOORBELL_SIZE;
const NOTIFICATIONS_SIZE: u64 = 0x1000;
const SHARED_MEMORY_OFFSET: u64 = NOTIFICATIONS_OFFSET + NOTIFICATIONS_SIZE;

#[sorted]
#[derive(ThisError, Debug)]
pub enum Error {
    /// Failed to accept connection on a socket.
    #[error("failed to accept connection on a socket: {0}")]
    AcceptConnection(std::io::Error),
    /// Failed to create a listener.
    #[error("failed to create a listener: {0}")]
    CreateListener(std::io::Error),
    /// Failed to create a wait context object.
    #[error("failed to create a wait context object: {0}")]
    CreateWaitContext(base::Error),
    /// There are no more available descriptors to receive into.
    #[error("no rx descriptors available")]
    RxDescriptorsExhausted,
    /// Removing read event from the VhostVmmSocket fd events failed.
    #[error("failed to disable EPOLLIN on VhostVmmSocket fd: {0}")]
    WaitContextDisableVhostVmmSocket(base::Error),
    /// Adding read event to the VhostVmmSocket fd events failed.
    #[error("failed to enable EPOLLIN on VhostVmmSocket fd: {0}")]
    WaitContextEnableVhostVmmSocket(base::Error),
    /// Failed to wait for events.
    #[error("failed to wait for events: {0}")]
    WaitError(base::Error),
    /// Writing to a buffer in the guest failed.
    #[error("failed to write to guest buffer: {0}")]
    WriteBuffer(std::io::Error),
    /// Failed to create a Writer.
    #[error("failed to create a Writer: {0}")]
    WriterCreation(DescriptorUtilsError),
}

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
struct VirtioVhostUserConfig {
    status: Le32,
    max_vhost_queues: Le32,
    uuid: [u8; 16],
}

// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for VirtioVhostUserConfig {}

struct Worker {
    mem: GuestMemory,
    interrupt: Interrupt,
    rx_queue: Queue,
    tx_queue: Queue,
    vhost_vmm_socket: UnixStream,
}

impl Worker {
    fn run(&mut self, rx_queue_evt: Event, tx_queue_evt: Event, kill_evt: Event) -> Result<()> {
        #[derive(PollToken, Debug, Clone)]
        pub enum Token {
            // Data is available on the vhost vmm socket.
            VhostVmmSocket,
            // The vhost-device has made a read buffer available.
            RxQueue,
            // The vhost-device has sent a buffer to the |Worker::tx_queue|.
            TxQueue,
            // crosvm has requested the device to shut down.
            Kill,
        }

        let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
            (&self.vhost_vmm_socket, Token::VhostVmmSocket),
            (&rx_queue_evt, Token::RxQueue),
            (&tx_queue_evt, Token::TxQueue),
            (&kill_evt, Token::Kill),
        ])
        .map_err(Error::CreateWaitContext)?;

        let mut vhost_vmm_socket_polling_enabled = true;
        'wait: loop {
            let events = wait_ctx.wait().map_err(Error::WaitError)?;
            for event in events.iter().filter(|e| e.is_readable) {
                match event.token {
                    Token::VhostVmmSocket => match self.process_rx() {
                        Ok(()) => {}
                        Err(Error::RxDescriptorsExhausted) => {
                            wait_ctx
                                .modify(
                                    &self.vhost_vmm_socket,
                                    EventType::None,
                                    Token::VhostVmmSocket,
                                )
                                .map_err(Error::WaitContextDisableVhostVmmSocket)?;
                            vhost_vmm_socket_polling_enabled = false;
                        }
                        Err(e) => return Err(e),
                    },
                    Token::RxQueue => {
                        if let Err(e) = rx_queue_evt.read() {
                            error!("net: error reading rx queue Event: {}", e);
                            break 'wait;
                        }
                        if !vhost_vmm_socket_polling_enabled {
                            wait_ctx
                                .modify(
                                    &self.vhost_vmm_socket,
                                    EventType::Read,
                                    Token::VhostVmmSocket,
                                )
                                .map_err(Error::WaitContextEnableVhostVmmSocket)?;
                            vhost_vmm_socket_polling_enabled = true;
                        }
                    }
                    Token::TxQueue => {
                        if let Err(e) = tx_queue_evt.read() {
                            error!("error reading rx queue event: {}", e);
                            break 'wait;
                        }
                        self.process_tx();
                    }
                    Token::Kill => {
                        let _ = kill_evt.read();
                        break 'wait;
                    }
                }
            }
        }
        Ok(())
    }

    fn process_rx(&mut self) -> Result<()> {
        let mut exhausted_queue = false;

        // Read as many frames as possible.
        loop {
            let desc_chain = match self.rx_queue.peek(&self.mem) {
                Some(desc) => desc,
                None => {
                    exhausted_queue = true;
                    break;
                }
            };

            let index = desc_chain.index;
            let bytes_written = match Writer::new(self.mem.clone(), desc_chain) {
                Ok(mut writer) => {
                    match writer.write_from(&mut self.vhost_vmm_socket, writer.available_bytes()) {
                        Ok(_) => {}
                        Err(ref e) if e.kind() == io::ErrorKind::WriteZero => {
                            error!("rx: buffer is too small to hold frame");
                            break;
                        }
                        Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
                            // No more to read.
                            break;
                        }
                        Err(e) => {
                            error!("rx: failed to write slice: {}", e);
                            return Err(Error::WriteBuffer(e));
                        }
                    };

                    writer.bytes_written() as u32
                }
                Err(e) => {
                    error!("failed to create Writer: {}", e);
                    0
                }
            };

            // The driver is able to deal with a descriptor with 0 bytes written.
            self.rx_queue.pop_peeked(&self.mem);
            self.rx_queue.add_used(&self.mem, index, bytes_written);
            self.rx_queue.trigger_interrupt(&self.mem, &self.interrupt);
        }

        if exhausted_queue {
            Err(Error::RxDescriptorsExhausted)
        } else {
            Ok(())
        }
    }

    fn process_tx(&mut self) {
        while let Some(desc_chain) = self.tx_queue.pop(&self.mem) {
            let index = desc_chain.index;
            match Reader::new(self.mem.clone(), desc_chain) {
                Ok(mut reader) => {
                    let expected_count = reader.available_bytes();
                    match reader.read_to(&mut self.vhost_vmm_socket, expected_count) {
                        Ok(count) => {
                            // Datagram messages should be sent as whole.
                            // TODO: Should this be a panic! as it will violate the Linux API.
                            if count != expected_count {
                                error!("wrote only {} bytes of {}", count, expected_count);
                            }
                        }
                        Err(e) => error!("failed to write message to vhost-vmm: {}", e),
                    }
                }
                Err(e) => error!("failed to create Reader: {}", e),
            }
            self.tx_queue.add_used(&self.mem, index, 0);
            self.tx_queue.trigger_interrupt(&self.mem, &self.interrupt);
        }
    }
}

pub struct VirtioVhostUser {
    vhost_vmm_socket: Option<UnixStream>,
    config: VirtioVhostUserConfig,
    kill_evt: Option<Event>,
    worker_thread: Option<thread::JoinHandle<Worker>>,
    pci_bar: Option<Alloc>,
}

impl VirtioVhostUser {
    pub fn new(vhost_vmm_socket_path: &Path) -> Result<VirtioVhostUser> {
        let listener = UnixListener::bind(vhost_vmm_socket_path).map_err(Error::CreateListener)?;
        let (socket, _) = listener.accept().map_err(Error::AcceptConnection)?;
        Ok(VirtioVhostUser {
            vhost_vmm_socket: Some(socket),
            config: Default::default(),
            kill_evt: None,
            worker_thread: None,
            pci_bar: None,
        })
    }

    // Implement writing to the notifications bar as per the VVU spec.
    fn write_bar_notifications(&mut self, _offset: u64, _data: &[u8]) {
        // TODO(abhishekbh): Implement write notifications.
        info!("notifications write");
    }

    // Implement reading from the notifications bar as per the VVU spec.
    fn read_bar_notifications(&mut self, _offset: u64, _data: &mut [u8]) {
        // TODO(abhishekbh): Implement read notifications.
        info!("notifications read");
    }
}

impl Drop for VirtioVhostUser {
    fn drop(&mut self) {
        if let Some(kill_evt) = self.kill_evt.take() {
            match kill_evt.write(1) {
                Ok(()) => {
                    if let Some(worker_thread) = self.worker_thread.take() {
                        // Ignore the result because there is nothing we can do about it.
                        let _ = worker_thread.join();
                    }
                }
                Err(e) => error!("failed to write kill event: {}", e),
            }
        }
    }
}

impl VirtioDevice for VirtioVhostUser {
    fn keep_rds(&self) -> Vec<RawDescriptor> {
        vec![]
    }

    fn device_type(&self) -> u32 {
        TYPE_VHOST_USER
    }

    fn queue_max_sizes(&self) -> &[u16] {
        PROXY_DEVICE_QUEUE_SIZES
    }

    fn read_config(&self, offset: u64, data: &mut [u8]) {
        copy_config(
            data,
            0, /* dst_offset */
            self.config.as_slice(),
            offset,
        );
    }

    fn write_config(&mut self, offset: u64, data: &[u8]) {
        // TODO: Confirm if |data| is guaranteed to be sent in LE.
        copy_config(
            self.config.as_mut_slice(),
            offset,
            data,
            0, /* src_offset */
        );
    }

    fn get_device_bars(&mut self, address: PciAddress) -> Vec<PciBarConfiguration> {
        self.pci_bar = Some(Alloc::PciBar {
            bus: address.bus,
            dev: address.dev,
            func: address.func,
            bar: BAR_INDEX,
        });

        vec![PciBarConfiguration::new(
            BAR_INDEX as usize,
            BAR_SIZE as u64,
            PciBarRegionType::Memory64BitRegion,
            PciBarPrefetchable::NotPrefetchable,
        )]
    }

    fn activate(
        &mut self,
        mem: GuestMemory,
        interrupt: Interrupt,
        mut queues: Vec<Queue>,
        mut queue_evts: Vec<Event>,
    ) {
        if queues.len() != NUM_PROXY_DEVICE_QUEUES || queue_evts.len() != NUM_PROXY_DEVICE_QUEUES {
            error!("bad queue length: {} {}", queues.len(), queue_evts.len());
            return;
        }

        let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
            Ok(v) => v,
            Err(e) => {
                error!("failed creating kill Event pair: {}", e);
                return;
            }
        };
        self.kill_evt = Some(self_kill_evt);

        // The socket will be moved to the worker thread. Guaranteed to be valid as a connection is
        // ensured in |VirtioVhostUser::new|.
        let vhost_vmm_socket = self
            .vhost_vmm_socket
            .take()
            .expect("socket connection missing");

        let worker_result = thread::Builder::new()
            .name("virtio_vhost_user".to_string())
            .spawn(move || {
                let rx_queue = queues.remove(0);
                let tx_queue = queues.remove(0);
                let mut worker = Worker {
                    mem,
                    interrupt,
                    rx_queue,
                    tx_queue,
                    vhost_vmm_socket,
                };
                let rx_queue_evt = queue_evts.remove(0);
                let tx_queue_evt = queue_evts.remove(0);
                let _ = worker.run(rx_queue_evt, tx_queue_evt, kill_evt);
                worker
            });

        match worker_result {
            Err(e) => {
                error!("failed to spawn virtio_vhost_user worker: {}", e);
            }
            Ok(join_handle) => {
                self.worker_thread = Some(join_handle);
            }
        }
    }

    fn read_bar(&mut self, bar_index: PciBarIndex, offset: u64, data: &mut [u8]) {
        if self.pci_bar.is_none() {
            error!("PCI bar is not allocated");
            return;
        }

        if bar_index != BAR_INDEX as PciBarIndex {
            error!("wrong PCI bar: {}", bar_index);
            return;
        }

        if (NOTIFICATIONS_OFFSET..SHARED_MEMORY_OFFSET).contains(&offset) {
            self.read_bar_notifications(offset - NOTIFICATIONS_OFFSET, data);
        } else {
            error!("addr is outside known region for reads");
        }
    }

    fn write_bar(&mut self, bar_index: PciBarIndex, offset: u64, data: &[u8]) {
        if self.pci_bar.is_none() {
            error!("PCI bar is not allocated");
            return;
        }

        if bar_index != BAR_INDEX as PciBarIndex {
            error!("wrong PCI bar: {}", bar_index);
            return;
        }

        if (DOORBELL_OFFSET..NOTIFICATIONS_OFFSET).contains(&offset) {
            // TODO(abhishekbh): Implement doorbell writes.
            unimplemented!();
        } else if (NOTIFICATIONS_OFFSET..SHARED_MEMORY_OFFSET).contains(&offset) {
            self.write_bar_notifications(offset - NOTIFICATIONS_OFFSET, data);
        } else {
            error!("addr is outside known region for writes");
        }
    }

    fn reset(&mut self) -> bool {
        // TODO
        true
    }
}
<|endoftext|>


<commit_before>extern crate crossbeam;
#[macro_use] extern crate version;
extern crate walkdir;

use std::env;
use std::process::exit;
use std::process::Command;
use std::process::Stdio;
use std::io::ErrorKind;

use walkdir::WalkDir;
use crossbeam::*;

fn help(cmds: Vec<String>) {
    println!(r#"

     _
    (_)_ __ ___   __ _  __ _
    | | '_ \` _ \/ _\`|/ _\`|
    | | | | | | | (_| | (_| |
    |_|_| |_| |_|\__,_|\__, |
                       |___/
    -------------------------

    Usage: imag [--version | --versions | -h | --help] <command> <args...>

    imag - the personal information management suite for the commandline

    imag is a PIM suite for the commandline. It consists of several commands,
    called "modules". Each module implements one PIM aspect and all of these
    modules can be used independently.

    Available commands:
    "#);

    for cmd in cmds.iter() {
        println!("\t{}", cmd);
    }

    println!(r#"

    Call a command with 'imag <command> <args>'
    Each command can be called with "--help" to get the respective helptext.

    Please visit https://github.com/matthiasbeyer/imag to view the source code,
    follow the development of imag or maybe even contribute to imag.

    imag is free software. It is released under the terms of LGPLv2.1

    (c) 2016 Matthias Beyer and contributors"#);
}

fn get_commands() -> Vec<String> {
    let path = env::var("PATH");
    if path.is_err() {
        println!("PATH error: {:?}", path);
        exit(1);
    }
    let pathelements = path.unwrap();
    let pathelements = pathelements.split(":");

    let joinhandles : Vec<ScopedJoinHandle<Vec<String>>> = pathelements
        .map(|elem| {
            crossbeam::scope(|scope| {
                scope.spawn(|| {
                    WalkDir::new(elem)
                        .max_depth(1)
                        .into_iter()
                        .filter(|path| {
                            match path {
                                &Ok(ref path) => path.path().starts_with(format!("{}/imag-", elem)),
                                &Err(_)   => false,
                            }
                        })
                        .filter_map(|x| x.ok())
                        .map(|path| {
                            path.path()
                                .to_str()
                                .map(String::from)
                        })
                        .filter_map(|x| x)
                        .collect()
                })
            })
        })
        .collect();

    let mut execs = vec![];
    for joinhandle in joinhandles.into_iter() {
        let mut v = joinhandle.join();
        execs.append(&mut v);
    }

    execs
}

fn find_command() -> Option<String> {
    env::args().skip(1).filter(|x| !x.starts_with("-")).next()
}

fn main() {
    let commands  = get_commands();
    let mut args  = env::args();
    let _         = args.next();
    let first_arg = match find_command() {
        Some(s) => s,
        None    => {
            help(commands);
            exit(0);
        },
    };

    match &first_arg[..] {
        "--help" | "-h" => {
            help(commands);
            exit(0);
        },

        "--version"  => println!("imag {}", &version!()[..]),

        "--versions" => {
            let mut result = vec![];
            for command in commands.iter() {
                result.push(crossbeam::scope(|scope| {
                    scope.spawn(|| {
                        let v = Command::new(command).arg("--version").output();
                        match v {
                            Ok(v) => match String::from_utf8(v.stdout) {
                                Ok(s) => format!("{} -> {}", command, s),
                                Err(e) => format!("Failed calling {} -> {:?}", command, e),
                            },
                            Err(e) => format!("Failed calling {} -> {:?}", command, e),
                        }
                    })
                }))
            }

            for versionstring in result.into_iter().map(|handle| handle.join()) {
                println!("{}", versionstring);
            }
        },

        s => {
            match Command::new(format!("imag-{}", s))
                .stdin(Stdio::inherit())
                .stdout(Stdio::inherit())
                .stderr(Stdio::inherit())
                .spawn()
                .and_then(|mut handle| handle.wait())
            {
                Ok(exit_status) => {
                    if !exit_status.success() {
                        println!("{} exited with non-zero exit code", s);
                        exit(exit_status.code().unwrap_or(42));
                    }
                },

                Err(e) => {
                    match e.kind() {
                        ErrorKind::NotFound => {
                            println!("No such command: 'imag-{}'", s);
                            exit(2);
                        },
                        ErrorKind::PermissionDenied => {
                            println!("No permission to execute: 'imag-{}'", s);
                            exit(1);
                        },
                        _ => {
                            println!("Error spawning: {:?}", e);
                            exit(1337);
                        }
                    }
                }
            }

        },
    }
}
<commit_msg>Add argument finder helper function<commit_after>extern crate crossbeam;
#[macro_use] extern crate version;
extern crate walkdir;

use std::env;
use std::process::exit;
use std::process::Command;
use std::process::Stdio;
use std::io::ErrorKind;

use walkdir::WalkDir;
use crossbeam::*;

fn help(cmds: Vec<String>) {
    println!(r#"

     _
    (_)_ __ ___   __ _  __ _
    | | '_ \` _ \/ _\`|/ _\`|
    | | | | | | | (_| | (_| |
    |_|_| |_| |_|\__,_|\__, |
                       |___/
    -------------------------

    Usage: imag [--version | --versions | -h | --help] <command> <args...>

    imag - the personal information management suite for the commandline

    imag is a PIM suite for the commandline. It consists of several commands,
    called "modules". Each module implements one PIM aspect and all of these
    modules can be used independently.

    Available commands:
    "#);

    for cmd in cmds.iter() {
        println!("\t{}", cmd);
    }

    println!(r#"

    Call a command with 'imag <command> <args>'
    Each command can be called with "--help" to get the respective helptext.

    Please visit https://github.com/matthiasbeyer/imag to view the source code,
    follow the development of imag or maybe even contribute to imag.

    imag is free software. It is released under the terms of LGPLv2.1

    (c) 2016 Matthias Beyer and contributors"#);
}

fn get_commands() -> Vec<String> {
    let path = env::var("PATH");
    if path.is_err() {
        println!("PATH error: {:?}", path);
        exit(1);
    }
    let pathelements = path.unwrap();
    let pathelements = pathelements.split(":");

    let joinhandles : Vec<ScopedJoinHandle<Vec<String>>> = pathelements
        .map(|elem| {
            crossbeam::scope(|scope| {
                scope.spawn(|| {
                    WalkDir::new(elem)
                        .max_depth(1)
                        .into_iter()
                        .filter(|path| {
                            match path {
                                &Ok(ref path) => path.path().starts_with(format!("{}/imag-", elem)),
                                &Err(_)   => false,
                            }
                        })
                        .filter_map(|x| x.ok())
                        .map(|path| {
                            path.path()
                                .to_str()
                                .map(String::from)
                        })
                        .filter_map(|x| x)
                        .collect()
                })
            })
        })
        .collect();

    let mut execs = vec![];
    for joinhandle in joinhandles.into_iter() {
        let mut v = joinhandle.join();
        execs.append(&mut v);
    }

    execs
}

fn find_command() -> Option<String> {
    env::args().skip(1).filter(|x| !x.starts_with("-")).next()
}

fn find_args(command: &str) -> Vec<String> {
    env::args()
        .skip(1)
        .position(|e| e == command)
        .map(|pos| env::args().skip(pos + 2).collect::<Vec<String>>())
        .unwrap_or(vec![])
}

fn main() {
    let commands  = get_commands();
    let mut args  = env::args();
    let _         = args.next();
    let first_arg = match find_command() {
        Some(s) => s,
        None    => {
            help(commands);
            exit(0);
        },
    };

    match &first_arg[..] {
        "--help" | "-h" => {
            help(commands);
            exit(0);
        },

        "--version"  => println!("imag {}", &version!()[..]),

        "--versions" => {
            let mut result = vec![];
            for command in commands.iter() {
                result.push(crossbeam::scope(|scope| {
                    scope.spawn(|| {
                        let v = Command::new(command).arg("--version").output();
                        match v {
                            Ok(v) => match String::from_utf8(v.stdout) {
                                Ok(s) => format!("{} -> {}", command, s),
                                Err(e) => format!("Failed calling {} -> {:?}", command, e),
                            },
                            Err(e) => format!("Failed calling {} -> {:?}", command, e),
                        }
                    })
                }))
            }

            for versionstring in result.into_iter().map(|handle| handle.join()) {
                println!("{}", versionstring);
            }
        },

        s => {
            match Command::new(format!("imag-{}", s))
                .stdin(Stdio::inherit())
                .stdout(Stdio::inherit())
                .stderr(Stdio::inherit())
                .args(&find_args(s)[..])
                .spawn()
                .and_then(|mut handle| handle.wait())
            {
                Ok(exit_status) => {
                    if !exit_status.success() {
                        println!("{} exited with non-zero exit code", s);
                        exit(exit_status.code().unwrap_or(42));
                    }
                },

                Err(e) => {
                    match e.kind() {
                        ErrorKind::NotFound => {
                            println!("No such command: 'imag-{}'", s);
                            exit(2);
                        },
                        ErrorKind::PermissionDenied => {
                            println!("No permission to execute: 'imag-{}'", s);
                            exit(1);
                        },
                        _ => {
                            println!("Error spawning: {:?}", e);
                            exit(1337);
                        }
                    }
                }
            }

        },
    }
}
<|endoftext|>


<commit_before>extern crate crossbeam;
extern crate clap;
#[macro_use] extern crate version;
#[macro_use] extern crate log;
extern crate walkdir;

extern crate libimagrt;
extern crate libimagerror;

use std::env;
use std::process::exit;
use std::process::Command;
use std::process::Stdio;
use std::io::ErrorKind;

use walkdir::WalkDir;
use crossbeam::*;
use clap::{Arg, AppSettings, SubCommand};

use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error;

/// Returns the helptext, putting the Strings in cmds as possible
/// subcommands into it
fn help_text(cmds: Vec<String>) -> String {
    let text = format!(r#"

     _
    (_)_ __ ___   __ _  __ _
    | | '_ \` _ \/ _\`|/ _\`|
    | | | | | | | (_| | (_| |
    |_|_| |_| |_|\__,_|\__, |
                       |___/
    -------------------------

    Usage: imag [--version | --versions | -h | --help] <command> <args...>

    imag - the personal information management suite for the commandline

    imag is a PIM suite for the commandline. It consists of several commands,
    called "modules". Each module implements one PIM aspect and all of these
    modules can be used independently.

    Available commands:

    {imagbins}

    Call a command with 'imag <command> <args>'
    Each command can be called with "--help" to get the respective helptext.

    Please visit https://github.com/matthiasbeyer/imag to view the source code,
    follow the development of imag or maybe even contribute to imag.

    imag is free software. It is released under the terms of LGPLv2.1

    (c) 2016 Matthias Beyer and contributors"#, imagbins = cmds.into_iter()
        .map(|cmd| format!("\t{}\n", cmd))
        .fold(String::new(), |s, c| {
            let s = s + c.as_str();
            s
        }));
    text
}

/// Returns the list of imag-* executables found in $PATH
fn get_commands() -> Vec<String> {
    let path = env::var("PATH");
    if path.is_err() {
        println!("PATH error: {:?}", path);
        exit(1);
    }
    let pathelements = path.unwrap();
    let pathelements = pathelements.split(":");

    let joinhandles : Vec<ScopedJoinHandle<Vec<String>>> = pathelements
        .map(|elem| {
            crossbeam::scope(|scope| {
                scope.spawn(|| {
                    WalkDir::new(elem)
                        .max_depth(1)
                        .into_iter()
                        .filter(|path| {
                            match path {
                                &Ok(ref p) => p.file_name()
                                    .to_str()
                                    .map_or(false, |filename| filename.starts_with("imag-")),
                                &Err(_)   => false,
                            }
                        })
                        .filter_map(|x| x.ok())
                        .filter_map(|path| {
                           path.file_name()
                               .to_str()
                               .and_then(|s| s.splitn(2, "-").nth(1).map(String::from))
                        })
                        .collect()
                })
            })
        })
        .collect();

    let mut execs = vec![];
    for joinhandle in joinhandles.into_iter() {
        let mut v = joinhandle.join();
        execs.append(&mut v);
    }

    execs
}

fn main() {
    // Initialize the Runtime and build the CLI
    let appname  = "imag";
    let version  = &version!();
    let about    = "imag - the PIM suite for the commandline";
    let commands = get_commands();
    let helptext = help_text(commands.clone());
    let app      = Runtime::get_default_cli_builder(appname, version, about)
        .settings(&[AppSettings::AllowExternalSubcommands, AppSettings::ArgRequiredElseHelp])
        .arg(Arg::with_name("version")
             .long("version")
             .takes_value(false)
             .required(false)
             .multiple(false)
             .help("Get the version of imag"))
        .arg(Arg::with_name("versions")
             .long("versions")
             .takes_value(false)
             .required(false)
             .multiple(false)
             .help("Get the versions of the imag commands"))
        .subcommand(SubCommand::with_name("help").help("Show help"))
        .help(helptext.as_str());
    let rt = Runtime::new(app)
        .unwrap_or_else(|e| {
            println!("Runtime couldn't be setup. Exiting");
            trace_error(&e);
            exit(1);
        });
    let matches = rt.cli();

    debug!("matches: {:?}", matches);

    // Begin checking for arguments

    if matches.is_present("version") {
        debug!("Showing version");
        println!("imag {}", &version!()[..]);
        exit(0);
    }

    if matches.is_present("versions") {
        debug!("Showing versions");
        let mut result = vec![];
        for command in commands.iter() {
            result.push(crossbeam::scope(|scope| {
                scope.spawn(|| {
                    let v = Command::new(format!("imag-{}",command)).arg("--version").output();
                    match v {
                        Ok(v) => match String::from_utf8(v.stdout) {
                            Ok(s) => format!("{:10} -> {}", command, s),
                            Err(e) => format!("Failed calling {} -> {:?}", command, e),
                        },
                        Err(e) => format!("Failed calling {} -> {:?}", command, e),
                    }
                })
            }))
        }

        for versionstring in result.into_iter().map(|handle| handle.join()) {
            // The amount of newlines may differ depending on the subprocess
            println!("{}", versionstring.trim());
        }
    }

    // Matches any subcommand given
    match matches.subcommand() {
        (subcommand, Some(scmd)) => {
            // Get all given arguments and further subcommands to pass to
            // the imag-<> binary
            // Providing no arguments is OK, and is therefore ignored here
            let subcommand_args : Vec<&str> = match scmd.values_of("") {
                Some(values) => values.collect(),
                None => Vec::new()
            };

            // Typos happen, so check if the given subcommand is one found in $PATH
            if !commands.contains(&String::from(subcommand)) {
                println!("No such command: 'imag-{}'", subcommand);
                println!("See 'imag --help' for available subcommands");
                exit(2);
            }

            debug!("Calling 'imag-{}' with args: {:?}", subcommand, subcommand_args);

            // Create a Command, and pass it the gathered arguments
            match Command::new(format!("imag-{}", subcommand))
                .stdin(Stdio::inherit())
                .stdout(Stdio::inherit())
                .stderr(Stdio::inherit())
                .args(&subcommand_args[..])
                .spawn()
                .and_then(|mut handle| handle.wait())
            {
                Ok(exit_status) => {
                    if !exit_status.success() {
                        debug!("{} exited with non-zero exit code: {:?}", subcommand, exit_status);
                        println!("{} exited with non-zero exit code", subcommand);
                        exit(exit_status.code().unwrap_or(1));
                    }
                    debug!("Successful exit!");
                },

                Err(e) => {
                    debug!("Error calling the subcommand");
                    match e.kind() {
                        ErrorKind::NotFound => {
                            // With the check above, this absolutely should not happen.
                            // Keeping it to be safe
                            println!("No such command: 'imag-{}'", subcommand);
                            println!("See 'imag --help' for available subcommands");
                            exit(2);
                        },
                        ErrorKind::PermissionDenied => {
                            println!("No permission to execute: 'imag-{}'", subcommand);
                            exit(1);
                        },
                        _ => {
                            println!("Error spawning: {:?}", e);
                            exit(1);
                        }
                    }
                }
            }
        },
        // Calling for example 'imag --versions' will lead here, as this option does not exit.
        // There's nothing to do in such a case
        _ => {},
    }
}
<commit_msg>bin: Add copyright notice to all files<commit_after>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

extern crate crossbeam;
extern crate clap;
#[macro_use] extern crate version;
#[macro_use] extern crate log;
extern crate walkdir;

extern crate libimagrt;
extern crate libimagerror;

use std::env;
use std::process::exit;
use std::process::Command;
use std::process::Stdio;
use std::io::ErrorKind;

use walkdir::WalkDir;
use crossbeam::*;
use clap::{Arg, AppSettings, SubCommand};

use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error;

/// Returns the helptext, putting the Strings in cmds as possible
/// subcommands into it
fn help_text(cmds: Vec<String>) -> String {
    let text = format!(r#"

     _
    (_)_ __ ___   __ _  __ _
    | | '_ \` _ \/ _\`|/ _\`|
    | | | | | | | (_| | (_| |
    |_|_| |_| |_|\__,_|\__, |
                       |___/
    -------------------------

    Usage: imag [--version | --versions | -h | --help] <command> <args...>

    imag - the personal information management suite for the commandline

    imag is a PIM suite for the commandline. It consists of several commands,
    called "modules". Each module implements one PIM aspect and all of these
    modules can be used independently.

    Available commands:

    {imagbins}

    Call a command with 'imag <command> <args>'
    Each command can be called with "--help" to get the respective helptext.

    Please visit https://github.com/matthiasbeyer/imag to view the source code,
    follow the development of imag or maybe even contribute to imag.

    imag is free software. It is released under the terms of LGPLv2.1

    (c) 2016 Matthias Beyer and contributors"#, imagbins = cmds.into_iter()
        .map(|cmd| format!("\t{}\n", cmd))
        .fold(String::new(), |s, c| {
            let s = s + c.as_str();
            s
        }));
    text
}

/// Returns the list of imag-* executables found in $PATH
fn get_commands() -> Vec<String> {
    let path = env::var("PATH");
    if path.is_err() {
        println!("PATH error: {:?}", path);
        exit(1);
    }
    let pathelements = path.unwrap();
    let pathelements = pathelements.split(":");

    let joinhandles : Vec<ScopedJoinHandle<Vec<String>>> = pathelements
        .map(|elem| {
            crossbeam::scope(|scope| {
                scope.spawn(|| {
                    WalkDir::new(elem)
                        .max_depth(1)
                        .into_iter()
                        .filter(|path| {
                            match path {
                                &Ok(ref p) => p.file_name()
                                    .to_str()
                                    .map_or(false, |filename| filename.starts_with("imag-")),
                                &Err(_)   => false,
                            }
                        })
                        .filter_map(|x| x.ok())
                        .filter_map(|path| {
                           path.file_name()
                               .to_str()
                               .and_then(|s| s.splitn(2, "-").nth(1).map(String::from))
                        })
                        .collect()
                })
            })
        })
        .collect();

    let mut execs = vec![];
    for joinhandle in joinhandles.into_iter() {
        let mut v = joinhandle.join();
        execs.append(&mut v);
    }

    execs
}

fn main() {
    // Initialize the Runtime and build the CLI
    let appname  = "imag";
    let version  = &version!();
    let about    = "imag - the PIM suite for the commandline";
    let commands = get_commands();
    let helptext = help_text(commands.clone());
    let app      = Runtime::get_default_cli_builder(appname, version, about)
        .settings(&[AppSettings::AllowExternalSubcommands, AppSettings::ArgRequiredElseHelp])
        .arg(Arg::with_name("version")
             .long("version")
             .takes_value(false)
             .required(false)
             .multiple(false)
             .help("Get the version of imag"))
        .arg(Arg::with_name("versions")
             .long("versions")
             .takes_value(false)
             .required(false)
             .multiple(false)
             .help("Get the versions of the imag commands"))
        .subcommand(SubCommand::with_name("help").help("Show help"))
        .help(helptext.as_str());
    let rt = Runtime::new(app)
        .unwrap_or_else(|e| {
            println!("Runtime couldn't be setup. Exiting");
            trace_error(&e);
            exit(1);
        });
    let matches = rt.cli();

    debug!("matches: {:?}", matches);

    // Begin checking for arguments

    if matches.is_present("version") {
        debug!("Showing version");
        println!("imag {}", &version!()[..]);
        exit(0);
    }

    if matches.is_present("versions") {
        debug!("Showing versions");
        let mut result = vec![];
        for command in commands.iter() {
            result.push(crossbeam::scope(|scope| {
                scope.spawn(|| {
                    let v = Command::new(format!("imag-{}",command)).arg("--version").output();
                    match v {
                        Ok(v) => match String::from_utf8(v.stdout) {
                            Ok(s) => format!("{:10} -> {}", command, s),
                            Err(e) => format!("Failed calling {} -> {:?}", command, e),
                        },
                        Err(e) => format!("Failed calling {} -> {:?}", command, e),
                    }
                })
            }))
        }

        for versionstring in result.into_iter().map(|handle| handle.join()) {
            // The amount of newlines may differ depending on the subprocess
            println!("{}", versionstring.trim());
        }
    }

    // Matches any subcommand given
    match matches.subcommand() {
        (subcommand, Some(scmd)) => {
            // Get all given arguments and further subcommands to pass to
            // the imag-<> binary
            // Providing no arguments is OK, and is therefore ignored here
            let subcommand_args : Vec<&str> = match scmd.values_of("") {
                Some(values) => values.collect(),
                None => Vec::new()
            };

            // Typos happen, so check if the given subcommand is one found in $PATH
            if !commands.contains(&String::from(subcommand)) {
                println!("No such command: 'imag-{}'", subcommand);
                println!("See 'imag --help' for available subcommands");
                exit(2);
            }

            debug!("Calling 'imag-{}' with args: {:?}", subcommand, subcommand_args);

            // Create a Command, and pass it the gathered arguments
            match Command::new(format!("imag-{}", subcommand))
                .stdin(Stdio::inherit())
                .stdout(Stdio::inherit())
                .stderr(Stdio::inherit())
                .args(&subcommand_args[..])
                .spawn()
                .and_then(|mut handle| handle.wait())
            {
                Ok(exit_status) => {
                    if !exit_status.success() {
                        debug!("{} exited with non-zero exit code: {:?}", subcommand, exit_status);
                        println!("{} exited with non-zero exit code", subcommand);
                        exit(exit_status.code().unwrap_or(1));
                    }
                    debug!("Successful exit!");
                },

                Err(e) => {
                    debug!("Error calling the subcommand");
                    match e.kind() {
                        ErrorKind::NotFound => {
                            // With the check above, this absolutely should not happen.
                            // Keeping it to be safe
                            println!("No such command: 'imag-{}'", subcommand);
                            println!("See 'imag --help' for available subcommands");
                            exit(2);
                        },
                        ErrorKind::PermissionDenied => {
                            println!("No permission to execute: 'imag-{}'", subcommand);
                            exit(1);
                        },
                        _ => {
                            println!("Error spawning: {:?}", e);
                            exit(1);
                        }
                    }
                }
            }
        },
        // Calling for example 'imag --versions' will lead here, as this option does not exit.
        // There's nothing to do in such a case
        _ => {},
    }
}
<|endoftext|>


<commit_before>use libc;

use std::borrow::Cow;
use std::ffi::CStr;
use std::str;

#[cfg(feature = "c-library")]
use glib::translate::*;

/// Converts a `char *` which is known to be valid UTF-8 into a `&str`
///
/// The usual `from_glib_none(s)` allocates an owned String.  The
/// purpose of `utf8_cstr()` is to get a temporary string slice into a
/// C string which is already known to be valid UTF-8; for example,
/// as for strings which come from `libxml2`.
pub unsafe fn utf8_cstr<'a>(s: *const libc::c_char) -> &'a str {
    assert!(!s.is_null());

    str::from_utf8_unchecked(CStr::from_ptr(s).to_bytes())
}

/// Error-tolerant C string import
pub unsafe fn cstr<'a>(s: *const libc::c_char) -> Cow<'a, str> {
    if s.is_null() {
        return Cow::Borrowed("(null)");
    }
    CStr::from_ptr(s).to_string_lossy()
}

pub fn clamp<T: PartialOrd>(val: T, low: T, high: T) -> T {
    if val < low {
        low
    } else if val > high {
        high
    } else {
        val
    }
}

#[cfg(feature = "c-library")]
pub fn rsvg_g_warning(msg: &str) {
    unsafe {
        extern "C" {
            fn rsvg_g_warning_from_c(msg: *const libc::c_char);
        }

        rsvg_g_warning_from_c(msg.to_glib_none().0);
    }
}

#[cfg(not(feature = "c-library"))]
pub fn rsvg_g_warning(_msg: &str) {
    // The only callers of this are in handle.rs. When those functions
    // are called from the Rust API, they are able to return a
    // meaningful error code, but the C API isn't - so they issues a
    // g_warning() instead.
}

#[cfg(feature = "c-library")]
pub fn rsvg_g_critical(msg: &str) {
    unsafe {
        extern "C" {
            fn rsvg_g_critical_from_c(msg: *const libc::c_char);
        }

        rsvg_g_critical_from_c(msg.to_glib_none().0);
    }
}

#[cfg(not(feature = "c-library"))]
pub fn rsvg_g_critical(_msg: &str) {
}
<commit_msg>Clarify the comment in rsvg_g_warning()<commit_after>use libc;

use std::borrow::Cow;
use std::ffi::CStr;
use std::str;

#[cfg(feature = "c-library")]
use glib::translate::*;

/// Converts a `char *` which is known to be valid UTF-8 into a `&str`
///
/// The usual `from_glib_none(s)` allocates an owned String.  The
/// purpose of `utf8_cstr()` is to get a temporary string slice into a
/// C string which is already known to be valid UTF-8; for example,
/// as for strings which come from `libxml2`.
pub unsafe fn utf8_cstr<'a>(s: *const libc::c_char) -> &'a str {
    assert!(!s.is_null());

    str::from_utf8_unchecked(CStr::from_ptr(s).to_bytes())
}

/// Error-tolerant C string import
pub unsafe fn cstr<'a>(s: *const libc::c_char) -> Cow<'a, str> {
    if s.is_null() {
        return Cow::Borrowed("(null)");
    }
    CStr::from_ptr(s).to_string_lossy()
}

pub fn clamp<T: PartialOrd>(val: T, low: T, high: T) -> T {
    if val < low {
        low
    } else if val > high {
        high
    } else {
        val
    }
}

#[cfg(feature = "c-library")]
pub fn rsvg_g_warning(msg: &str) {
    unsafe {
        extern "C" {
            fn rsvg_g_warning_from_c(msg: *const libc::c_char);
        }

        rsvg_g_warning_from_c(msg.to_glib_none().0);
    }
}

#[cfg(not(feature = "c-library"))]
pub fn rsvg_g_warning(_msg: &str) {
    // This, and rsvg_g_critical() below, are intended to be called
    // from Rust code which is directly called from the C API.  When
    // this crate is being built as part of the c-library, the purpose
    // of the functions is to call the corresponding glib macros to
    // print a warning or a critical error message.  When this crate
    // is being built as part of the standalone Rust crate, they do
    // nothing, since the Rust code is able to propagate an error code
    // all the way to the crate's public API.
}

#[cfg(feature = "c-library")]
pub fn rsvg_g_critical(msg: &str) {
    unsafe {
        extern "C" {
            fn rsvg_g_critical_from_c(msg: *const libc::c_char);
        }

        rsvg_g_critical_from_c(msg.to_glib_none().0);
    }
}

#[cfg(not(feature = "c-library"))]
pub fn rsvg_g_critical(_msg: &str) {
}
<|endoftext|>


<commit_before>use slack_api as slack;
use tokio_tungstenite as tungstenite;

use futures_util::{SinkExt, StreamExt};

use tokio::sync::mpsc;

use serde::Deserialize;
use serde_json::json;

use atomic_counter::AtomicCounter;
use atomic_counter::RelaxedCounter;

use std::sync::Arc;
use std::default::Default;
use std::env;
use std::result::Result;

use chrono::prelude::{Utc, DateTime};
use std::time::Duration;
use humantime::format_duration;

// SQlite worker thread
use std::thread;
use futures::executor::block_on_stream;

// Use of a mod or pub mod is not actually necessary.
pub mod build_info {
   // The file has been placed there by the build script.
   include!(concat!(env!("OUT_DIR"), "/built.rs"));
}

// Type alias for msg_id
type MsgId = Arc<RelaxedCounter>;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Shared integer counter for message ids
    let msg_id = Arc::new(RelaxedCounter::new(0));

    // Uptime of program start
    let start_time: DateTime<Utc> = Utc::now();

    let token = env::var("SLACK_API_TOKEN").map_err(|_| "SLACK_API_TOKEN env var must be set")?;
    let client = slack::default_client().map_err(|e| format!("Could not get default_client, {:?}", e))?;

    // Post a message
    let msg = slack::chat::PostMessageRequest {
        channel: "karmator-devs",
        text: "Pogger, i'm starting up!",
        as_user: Some(true),
        ..Default::default()
    };
    slack::chat::post_message(&client, &token, &msg).await;

    // Work to establish the WS connection
    let response = slack::rtm::connect(&client, &token).await.map_err(|e| format!("Control - {:?}", e))?;
    println!("Control - Got an ok reply");

    let ws_url = response.url.ok_or(format!("Control - \tNo Ws url"))?;
    println!("Control - \tGot a WS url: {:?}", ws_url);

    let (ws_stream, _) = tungstenite::connect_async(ws_url).await.map_err(|e| format!("Control - \t\t{:?}", e))?;
    println!("Control - \t\tWS connection established");

    // Setup the mpsc channel
    let (tx, mut rx) = mpsc::channel(32);

    // Split the stream
    let (mut ws_write, mut ws_read) = ws_stream.split();

    // Setup the mpsc channel for sqlite and spawn the sqlite worker thread
    // TODO: define a data format for asking the db to query something on your behalf, for now
    // pass in the raw query string?
    let (sql_tx, sql_rx) = mpsc::channel(32);

    let sql_worker = thread::spawn(move || {
        println!("Sql Worker - Launching");
        process_queries(sql_rx);
        println!("Sql Worker - Exiting");
    });

    // Spawn the inbound ws stream processor
    let inbound = tokio::spawn(async move {
        loop {
            if let Some(Ok(ws_msg)) = ws_read.next().await {
                let msg_id = msg_id.clone();
                let tx2 = tx.clone();
                let sql_tx2 = sql_tx.clone();

                tokio::spawn(async move {
                    process_inbound_message(msg_id, ws_msg, tx2, sql_tx2, start_time).await;
                });

            }
        }
    });

    // Spawn outbound ws processor
    // TODO: need to make sure to wait on sending till we recieve the hello event
    // could be a oneshot or some sort of flag which it waits till inbound has
    // gotten the hello
    let outbound = tokio::spawn(async move {
        loop {
            while let Some(message) = rx.recv().await {
                println!("Outbound - {:?}", message);

                // TODO: present some way to do plain vs fancy message, and if
                // fancy do a webapi post, otherwise dump into the WS
                //
                // TODO: look into tracking the sent message with a confirmation
                // that it was sent (via msg id) and if it fails, resend with
                // backoff
                //
                // TODO: find a way to handle the ping/pong cycle and monitor
                ws_write.send(message).await;
            }
        }
    });

    // Wait till either exits (error) then begin recovery
    match tokio::try_join!(inbound, outbound) {
        Ok((first, second)) => println!("Control - \t\t\tBoth exited fine"),
        Err(err) => println!("Control - \t\t\tSomething failed: {:?}", err),
    }

    // The sql_tx got moved into the inbound tokio async, so when that dies....
    let res = sql_worker.join();
    println!("Control - \t\t\tSql worker: {:?}", res);

    Ok(())
}

fn process_queries(
    sql_rx: mpsc::Receiver<String>
) {
    let mut block_sql_rx = block_on_stream(sql_rx);

    while let Some(query) = block_sql_rx.next() {
        println!("Sql Worker - Query {:?}", query);
    }
}

async fn process_inbound_message(
    msg_id: MsgId,
    msg: tungstenite::tungstenite::Message,
    mut tx: mpsc::Sender<tungstenite::tungstenite::Message>,
    mut sql_tx: mpsc::Sender<String>,
    start_time: DateTime<Utc>
) -> Result<(), Box<dyn std::error::Error>>
{
    let id = msg_id.inc();

    println!("Inbound - id = {:?}", id);

    // Parse incoming message
    let raw_msg = match msg {
        tungstenite::tungstenite::Message::Text(x) => Some(x),
        _ => {
            println!("Inbound - \tUnsupported ws message type - {:?}", msg);
            None
        },
    };
    println!("Inbound - raw event = {:?}", raw_msg);

    if let Some(e) = raw_msg.and_then(parse_event) {
        match e {
            Event::UserEvent(event) => {
                // Check if its a message/certain string, if so, reply
                match event {
                    // !version
                    UserEvent::Message {
                        channel: Some(c),
                        text: t,
                        subtype: _,
                        hidden: _,
                        user: u,
                        ts: _,
                    } if c == "CAF6S4TRT".to_string() => {
                        if t == "!version".to_string() {
                            let ver = build_info::PKG_VERSION;
                            let dat = build_info::BUILT_TIME_UTC;
                            let sha = build_info::GIT_COMMIT_HASH.unwrap_or("Unknown");

                            let ws_msg = json!({
                                "id": id,
                                "type": "message",
                                "channel": "CAF6S4TRT",
                                "text": format!("<@{}>: Cargo Version: {} - Build Date: {}, - Build SHA: {}", u, ver, dat, sha),
                            }).to_string();

                            tx.send(tungstenite::tungstenite::Message::from(ws_msg)).await;
                            println!("Inbound - \t\t!version");

                        } else if t == "!uptime".to_string() {
                            let end_time: DateTime<Utc> = Utc::now();
                            let durt = end_time.signed_duration_since(start_time).to_std().unwrap_or(
                                Duration::from_secs(3122064000));

                            let ws_msg = json!({
                                "id": id,
                                "type": "message",
                                "channel": "CAF6S4TRT",
                                "text": format!("<@{}>: {}", u, format_duration(durt).to_string()),
                            }).to_string();

                            tx.send(tungstenite::tungstenite::Message::from(ws_msg)).await;
                            println!("Inbound - \t\t!uptime");

                        } else if t == "!help".to_string() {
                            let help = "Available commands: !uptime !version !github !sidevotes !karma !givers !rank !ranksidevote";
                            let ws_msg = json!({
                                "id": id,
                                "type": "message",
                                "channel": "CAF6S4TRT",
                                "text": format!("<@{}>: {}", u, help),
                            }).to_string();

                            tx.send(tungstenite::tungstenite::Message::from(ws_msg)).await;
                            println!("Inbound - \t\t!help");

                        } else if t == "!github".to_string() {
                            let github = "Github repo: https://github.com/pharaun/Karmator";
                            let ws_msg = json!({
                                "id": id,
                                "type": "message",
                                "channel": "CAF6S4TRT",
                                "text": format!("<@{}>: {}", u, github),
                            }).to_string();

                            tx.send(tungstenite::tungstenite::Message::from(ws_msg)).await;
                            println!("Inbound - \t\t!github");

                        } else if t == "!run_sql_query".to_string() {
                            sql_tx.send("select * from votes;".to_string()).await;

                            let ws_msg = json!({
                                "id": id,
                                "type": "message",
                                "channel": "CAF6S4TRT",
                                "text": format!("<@{}>: Ok running query", u),
                            }).to_string();
                            tx.send(tungstenite::tungstenite::Message::from(ws_msg)).await;

                            println!("Inbound - \t\t!run_sql_query");
                        }
                    },

                    _ => println!("Inbound - \t\tNo action taken"),
                }
            },

            Event::SystemControl(sc) => (),
            Event::MessageControl(mc) => (),
        }
    }
    Ok(())
}

fn parse_event(s: String) -> Option<Event> {
    // Return User Events first, then the other two
    serde_json::from_str(&s).and_then(|ue| {
        println!("Inbound - \tUser Event = {:?}", ue);
        Ok(Event::UserEvent(ue))
    }).or_else(|_| {
        serde_json::from_str(&s).and_then(|sc| {
            println!("Inbound - \tSystem Control = {:?}", sc);
            Ok(Event::SystemControl(sc))
        }).or_else(|_| {
            serde_json::from_str(&s).and_then(|mc| {
                println!("Inbound - \tMessage Control = {:?}", mc);
                Ok(Event::MessageControl(mc))
            }).or_else(|x| {
                // TODO: for now print to stderr what didn't get deserialized
                // later can have config option to log to file the error or not
                eprintln!("Inbound - \tFail parse = {:?}", s);
                Err(x)
            })
        })
    }).ok()
}

#[derive(Debug)]
enum Event {
    UserEvent(UserEvent),
    SystemControl(SystemControl),
    MessageControl(MessageControl),
}

#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
enum UserEvent {
    // These events are what plugins will care for:
    //
    // Slack messages
    // TODO: more involved here for now basics
    Message {
        subtype: Option<String>,
        hidden: Option<bool>,
        channel: Option<String>,
        user: String,
        text: String,
        ts: String,
    },

    // TODO: consider looking at slack_api for types to reuse here
    ReactionAdded {
        user: String,
        reaction: String,
        item_user: Option<String>,
        // item: ListResponseItem
        event_ts: String,
    },
    ReactionRemoved {
        user: String,
        reaction: String,
        item_user: Option<String>,
        // item: ListResponseItem
        event_ts: String,
    },
}

#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
enum SystemControl {
    // First message upon successful connection establishment
    Hello,

    // Client should reconnect after getting such message
    Goodbye,

    // Reply to Ping = { type: ping, id: num }
    Pong { reply_to: usize },
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum MessageControl {
    // Reply to message sent
    MessageSent {
        ok: bool,
        reply_to: usize,
        text: String,
        ts: String
    },

    // Reply to failed message sent
    MessageError {
        ok: bool,
        reply_to: usize,
        error: ErrorDetail,
    },
}

#[derive(Debug, Deserialize)]
struct ErrorDetail {
    code: usize,
    msg: String,
}
<commit_msg>working query engine, need to setup the oneshot back to record result<commit_after>use slack_api as slack;
use tokio_tungstenite as tungstenite;

use futures_util::{SinkExt, StreamExt};

use tokio::sync::mpsc;

use serde::Deserialize;
use serde_json::json;

use atomic_counter::AtomicCounter;
use atomic_counter::RelaxedCounter;

use std::sync::Arc;
use std::default::Default;
use std::env;
use std::result::Result;

use chrono::prelude::{Utc, DateTime};
use std::time::Duration;
use humantime::format_duration;

// SQlite worker thread
use std::thread;
use futures::executor::block_on_stream;

use rusqlite as rs;
use std::path::Path;

// Use of a mod or pub mod is not actually necessary.
pub mod build_info {
   // The file has been placed there by the build script.
   include!(concat!(env!("OUT_DIR"), "/built.rs"));
}

// Type alias for msg_id
type MsgId = Arc<RelaxedCounter>;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Shared integer counter for message ids
    let msg_id = Arc::new(RelaxedCounter::new(0));

    // Uptime of program start
    let start_time: DateTime<Utc> = Utc::now();

    let token = env::var("SLACK_API_TOKEN").map_err(|_| "SLACK_API_TOKEN env var must be set")?;
    let client = slack::default_client().map_err(|e| format!("Could not get default_client, {:?}", e))?;

    // Post a message
    let msg = slack::chat::PostMessageRequest {
        channel: "karmator-devs",
        text: "Pogger, i'm starting up!",
        as_user: Some(true),
        ..Default::default()
    };
    slack::chat::post_message(&client, &token, &msg).await;

    // Work to establish the WS connection
    let response = slack::rtm::connect(&client, &token).await.map_err(|e| format!("Control - {:?}", e))?;
    println!("Control - Got an ok reply");

    let ws_url = response.url.ok_or(format!("Control - \tNo Ws url"))?;
    println!("Control - \tGot a WS url: {:?}", ws_url);

    let (ws_stream, _) = tungstenite::connect_async(ws_url).await.map_err(|e| format!("Control - \t\t{:?}", e))?;
    println!("Control - \t\tWS connection established");

    // Setup the mpsc channel
    let (tx, mut rx) = mpsc::channel(32);

    // Split the stream
    let (mut ws_write, mut ws_read) = ws_stream.split();

    // Setup the mpsc channel for sqlite and spawn the sqlite worker thread
    // TODO: define a data format for asking the db to query something on your behalf, for now
    // pass in the raw query string?
    let (sql_tx, sql_rx) = mpsc::channel(32);

    let sql_worker = thread::spawn(move || {
        println!("Sql Worker - Launching");
        process_queries(sql_rx);
        println!("Sql Worker - Exiting");
    });

    // Spawn the inbound ws stream processor
    let inbound = tokio::spawn(async move {
        loop {
            if let Some(Ok(ws_msg)) = ws_read.next().await {
                let msg_id = msg_id.clone();
                let tx2 = tx.clone();
                let sql_tx2 = sql_tx.clone();

                tokio::spawn(async move {
                    process_inbound_message(msg_id, ws_msg, tx2, sql_tx2, start_time).await;
                });

            }
        }
    });

    // Spawn outbound ws processor
    // TODO: need to make sure to wait on sending till we recieve the hello event
    // could be a oneshot or some sort of flag which it waits till inbound has
    // gotten the hello
    let outbound = tokio::spawn(async move {
        loop {
            while let Some(message) = rx.recv().await {
                println!("Outbound - {:?}", message);

                // TODO: present some way to do plain vs fancy message, and if
                // fancy do a webapi post, otherwise dump into the WS
                //
                // TODO: look into tracking the sent message with a confirmation
                // that it was sent (via msg id) and if it fails, resend with
                // backoff
                //
                // TODO: find a way to handle the ping/pong cycle and monitor
                ws_write.send(message).await;
            }
        }
    });

    // Wait till either exits (error) then begin recovery
    match tokio::try_join!(inbound, outbound) {
        Ok((first, second)) => println!("Control - \t\t\tBoth exited fine"),
        Err(err) => println!("Control - \t\t\tSomething failed: {:?}", err),
    }

    // The sql_tx got moved into the inbound tokio async, so when that dies....
    let res = sql_worker.join();
    println!("Control - \t\t\tSql worker: {:?}", res);

    Ok(())
}

fn process_queries(
    sql_rx: mpsc::Receiver<runQuery>
) {
    let mut block_sql_rx = block_on_stream(sql_rx);

    //let filename = "db.sqlite";
    //let conn = rs::Connection::open_with_flags(
    //    Path::new(filename),
    //    rs::OpenFlags::SQLITE_OPEN_READ_WRITE
    //).expect(&format!("Connection error: {}", filename));
    let conn = rs::Connection::open_in_memory().expect("Failure");

    // Just create a db table
    let res = conn.execute(
        "CREATE TABLE votes (
            for TEXT NOT NULL,
            what TEXT NOT NULL
        )",
        rs::params![],
    );
    println!("Sql Worker - Table - {:?}", res);

    // Listen for inbound query
    while let Some(query) = block_sql_rx.next() {
        match query {
            runQuery::Query => {
                println!("Sql Worker - Query");
                let mut stmt = conn.prepare("SELECT * FROM votes").unwrap();

                let mut vote_iter = stmt.query_map(rs::params![], |row| {
                    let a: String = row.get(0).unwrap();
                    let b: String = row.get(1).unwrap();

                    Ok(format!("{} {}", a, b))
                }).unwrap();

                // Result
                if let Some(Ok(res)) = vote_iter.next() {
                    println!("Sql Worker - Query - {:?}", res);
                } else {
                    println!("Sql Worker - Query - Fail");
                }
            },
            runQuery::Insert(f, w) => {
                println!("Sql Worker - Insert");
                let mut stmt = conn.prepare("INSERT INTO votes (for, what) VALUES (?, ?)").unwrap();
                let res = stmt.execute(&[f, w]);

                println!("Sql Worker - Insert - {:?}", res);
            }
        }
    }
}

#[derive(Debug)]
enum runQuery {
    Insert(String, String),
    Query,
}

#[derive(Debug)]
enum resQuery {
    Insert(usize),
    Query(String),
}

async fn process_inbound_message(
    msg_id: MsgId,
    msg: tungstenite::tungstenite::Message,
    mut tx: mpsc::Sender<tungstenite::tungstenite::Message>,
    mut sql_tx: mpsc::Sender<runQuery>,
    start_time: DateTime<Utc>
) -> Result<(), Box<dyn std::error::Error>>
{
    let id = msg_id.inc();

    println!("Inbound - id = {:?}", id);

    // Parse incoming message
    let raw_msg = match msg {
        tungstenite::tungstenite::Message::Text(x) => Some(x),
        _ => {
            println!("Inbound - \tUnsupported ws message type - {:?}", msg);
            None
        },
    };
    println!("Inbound - raw event = {:?}", raw_msg);

    if let Some(e) = raw_msg.and_then(parse_event) {
        match e {
            Event::UserEvent(event) => {
                // Check if its a message/certain string, if so, reply
                match event {
                    // !version
                    UserEvent::Message {
                        channel: Some(c),
                        text: t,
                        subtype: _,
                        hidden: _,
                        user: u,
                        ts: _,
                    } if c == "CAF6S4TRT".to_string() => {
                        if t == "!version".to_string() {
                            let ver = build_info::PKG_VERSION;
                            let dat = build_info::BUILT_TIME_UTC;
                            let sha = build_info::GIT_COMMIT_HASH.unwrap_or("Unknown");

                            let ws_msg = json!({
                                "id": id,
                                "type": "message",
                                "channel": "CAF6S4TRT",
                                "text": format!("<@{}>: Cargo Version: {} - Build Date: {}, - Build SHA: {}", u, ver, dat, sha),
                            }).to_string();

                            tx.send(tungstenite::tungstenite::Message::from(ws_msg)).await;
                            println!("Inbound - \t\t!version");

                        } else if t == "!uptime".to_string() {
                            let end_time: DateTime<Utc> = Utc::now();
                            let durt = end_time.signed_duration_since(start_time).to_std().unwrap_or(
                                Duration::from_secs(3122064000));

                            let ws_msg = json!({
                                "id": id,
                                "type": "message",
                                "channel": "CAF6S4TRT",
                                "text": format!("<@{}>: {}", u, format_duration(durt).to_string()),
                            }).to_string();

                            tx.send(tungstenite::tungstenite::Message::from(ws_msg)).await;
                            println!("Inbound - \t\t!uptime");

                        } else if t == "!help".to_string() {
                            let help = "Available commands: !uptime !version !github !sidevotes !karma !givers !rank !ranksidevote";
                            let ws_msg = json!({
                                "id": id,
                                "type": "message",
                                "channel": "CAF6S4TRT",
                                "text": format!("<@{}>: {}", u, help),
                            }).to_string();

                            tx.send(tungstenite::tungstenite::Message::from(ws_msg)).await;
                            println!("Inbound - \t\t!help");

                        } else if t == "!github".to_string() {
                            let github = "Github repo: https://github.com/pharaun/Karmator";
                            let ws_msg = json!({
                                "id": id,
                                "type": "message",
                                "channel": "CAF6S4TRT",
                                "text": format!("<@{}>: {}", u, github),
                            }).to_string();

                            tx.send(tungstenite::tungstenite::Message::from(ws_msg)).await;
                            println!("Inbound - \t\t!github");

                        } else if t == "!run_sql_query".to_string() {
                            sql_tx.send(runQuery::Insert("hi".to_string(), "bye".to_string())).await;
                            sql_tx.send(runQuery::Query).await;

                            let ws_msg = json!({
                                "id": id,
                                "type": "message",
                                "channel": "CAF6S4TRT",
                                "text": format!("<@{}>: Ok running query", u),
                            }).to_string();
                            tx.send(tungstenite::tungstenite::Message::from(ws_msg)).await;

                            println!("Inbound - \t\t!run_sql_query");
                        }
                    },

                    _ => println!("Inbound - \t\tNo action taken"),
                }
            },

            Event::SystemControl(sc) => (),
            Event::MessageControl(mc) => (),
        }
    }
    Ok(())
}

fn parse_event(s: String) -> Option<Event> {
    // Return User Events first, then the other two
    serde_json::from_str(&s).and_then(|ue| {
        println!("Inbound - \tUser Event = {:?}", ue);
        Ok(Event::UserEvent(ue))
    }).or_else(|_| {
        serde_json::from_str(&s).and_then(|sc| {
            println!("Inbound - \tSystem Control = {:?}", sc);
            Ok(Event::SystemControl(sc))
        }).or_else(|_| {
            serde_json::from_str(&s).and_then(|mc| {
                println!("Inbound - \tMessage Control = {:?}", mc);
                Ok(Event::MessageControl(mc))
            }).or_else(|x| {
                // TODO: for now print to stderr what didn't get deserialized
                // later can have config option to log to file the error or not
                eprintln!("Inbound - \tFail parse = {:?}", s);
                Err(x)
            })
        })
    }).ok()
}

#[derive(Debug)]
enum Event {
    UserEvent(UserEvent),
    SystemControl(SystemControl),
    MessageControl(MessageControl),
}

#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
enum UserEvent {
    // These events are what plugins will care for:
    //
    // Slack messages
    // TODO: more involved here for now basics
    Message {
        subtype: Option<String>,
        hidden: Option<bool>,
        channel: Option<String>,
        user: String,
        text: String,
        ts: String,
    },

    // TODO: consider looking at slack_api for types to reuse here
    ReactionAdded {
        user: String,
        reaction: String,
        item_user: Option<String>,
        // item: ListResponseItem
        event_ts: String,
    },
    ReactionRemoved {
        user: String,
        reaction: String,
        item_user: Option<String>,
        // item: ListResponseItem
        event_ts: String,
    },
}

#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
enum SystemControl {
    // First message upon successful connection establishment
    Hello,

    // Client should reconnect after getting such message
    Goodbye,

    // Reply to Ping = { type: ping, id: num }
    Pong { reply_to: usize },
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum MessageControl {
    // Reply to message sent
    MessageSent {
        ok: bool,
        reply_to: usize,
        text: String,
        ts: String
    },

    // Reply to failed message sent
    MessageError {
        ok: bool,
        reply_to: usize,
        error: ErrorDetail,
    },
}

#[derive(Debug, Deserialize)]
struct ErrorDetail {
    code: usize,
    msg: String,
}
<|endoftext|>


<commit_before>//! Basic interface traits for state-machine/automaton implementations.

use std::fmt::Debug;

use util::set;
use util::graph::interface::Id;
use pattern::Atom;

/// Trait bounds for any type used for input to an automaton state
pub trait Input: Clone + Debug + Eq + Ord + set::Contains<<Self as Input>::Atom> + set::IsSubsetOf<Self> {
    /// Smallest transition unit used.
    type Atom: Atom;
}

/// Metadata-bearing transition from one automaton state to another.
pub trait Transition: Clone + Debug + Eq {
    /// Input type that triggers this transition.
    type Input: Input;

    /// Fetch the input on which the transition is followed.
    fn input(&self) -> &Self::Input;
}

/// Trait for types that contain some specification of actions to be performed.
pub trait Actions {
    /// Action type
    type Action;

    /// Fetch a slice over the contained actions.
    fn actions(&self) -> &[Self::Action];
}

/// Interface for automaton implementations
pub trait Automaton<'a, A: 'a + Atom> {
    /// Concrete type used to represent automaton states
    type State;

    /// Type used to identify individual states.
    type StateId: Id;

    /// Iterator over the IDs of states in the automaton.
    type StateIdsIter: Iterator<Item=Self::StateId>;

    /// Concrete type used to represent a single input to a given state.
    type Input: 'a + Input<Atom=A>;

    /// Iterator over the possible inputs for a state.
    type InputsIter: Iterator<Item=&'a Self::Input>;

    /// Type used to store the input and any relevant metadata for a transition
    /// from one state to another.
    type Transition: Transition<Input=Self::Input>;

    /// Type used to identify individual transitions.
    type TransitionId: Id;

    /// Iterator over the IDs of transitions in the automaton.
    type TransitionIdsIter: Iterator<Item=Self::TransitionId>;

    /// Iterator over the possible outgoing transitions for a state.
    type TransitionsIter: Iterator<Item=(Self::TransitionId, Self::StateId)>;

    // --------------------------------

    /// Get the number of states in the automaton.
    fn state_count(&self) -> usize;

    /// Get an iterator over the IDs of all states in the automaton.
    fn state_ids(&self) -> Self::StateIdsIter;

    /// Get the ID of the automaton's initial state.
    fn initial_state(&self) -> Self::StateId;

    /// Get a reference to a specific state.
    fn state(&self, s: Self::StateId) -> &Self::State;

    /// Fetch an iterator over the valid inputs for a given state.
    fn state_inputs(&'a self, s: Self::StateId) -> Self::InputsIter;

    /// Fetch an iterator over the `(transition_id, next_state_id)` pairs for
    /// a given state.
    fn state_transitions(&'a self, s: Self::StateId) -> Self::TransitionsIter;

    // --------------------------------

    /// Get the number of transitions in the automaton.
    fn transition_count(&self) -> usize;

    /// Get an iterator over the IDs of all transitions in the automaton
    fn transition_ids(&self) -> Self::TransitionIdsIter;

    /// Get a reference to a specific transition.
    fn transition(&self, id: Self::TransitionId) -> &Self::Transition;
}

/// Interface for deterministic automatons.
pub trait DeterministicAutomaton<'a, A: 'a + Atom>: Automaton<'a, A> {
    /// Determine the next state to which the DFA will transition given its
    /// current state and the input token.
    fn next_state(&self, current_state: Self::StateId, input: A) -> Option<Self::StateId>;
}

/// Interface for non-deterministic automatons.
pub trait NondeterministicAutomaton<'a,A: 'a + Atom>: Automaton<'a, A> {
    /// Iterator type returned by `next_states`.
    type NextStatesIter: Iterator<Item=Self::StateId>;

    /// Fetch an iterator over the set of states to which the automaton
    /// transitions on the given input.
    fn next_states(&'a self, current_state: Self::StateId, input: A) -> Self::NextStatesIter;
}

<commit_msg>automaton/interface: improve expressiveness of automaton traits<commit_after>//! Basic interface traits for state-machine/automaton implementations.

use std::fmt::Debug;

use util::{graph, set};
use util::graph::interface::Id;
use pattern::Atom;

/// Trait for types used to describe possible inputs that trigger
/// a given transition.
pub trait Input: Clone + Debug + Eq + Ord + set::Contains<<Self as Input>::Atom> + set::IsSubsetOf<Self> {
    /// Smallest transition unit used.
    type Atom: Atom;
}

/// Trait for types that contain some specification of actions to be performed.
pub trait Actions {
    /// Action type
    type Action;

    /// Fetch a slice over the contained actions.
    fn actions(&self) -> &[Self::Action];
}

/// Trait for types that support adding actions.
pub trait ActionsMut: Actions {
    /// Add an action to the object.
    fn add_action(&mut self, action: Self::Action);
}

/// An automaton state.
pub trait State: Actions {
    /// Check if the state is "accepting", i.e. is valid as an exit node for
    /// the automaton.
    fn is_accept(&self) -> bool;

    /// Check if the state is a valid entry node for the automaton.
    fn is_entry(&self) -> bool;
}

/// Mutation methods for automaton states.
pub trait StateMut: State + ActionsMut {
    /// Set whether the state is an accepting state.
    fn set_accept(&mut self, accept: bool);

    /// Set whether the state is an accepting state.
    fn set_entry(&mut self, entry: bool);
}

/// Metadata-bearing transition from one automaton state to another.
pub trait Transition: Actions + Clone + Debug + PartialEq {
    /// Input type that triggers this transition.
    type Input: Input;

    /// Fetch the input on which the transition is followed.
    fn input(&self) -> &Self::Input;
}

/// Interface for automaton implementations
pub trait Automaton<'a, A: 'a + Atom> {
    /// Action type used by [`Actions`](trait.Actions.html) implementations on
    /// [`State`](trait.State.html) and [`Transition`](state.Transition.html)
    /// associated types.
    type Action;

    /// Concrete type used to represent automaton states.
    type State: StateMut<Action=Self::Action>;

    /// Type used to identify individual states.
    type StateId: Id;

    /// Iterator over the IDs of states in the automaton.
    type StateIdsIter: Iterator<Item=Self::StateId>;

    /// Concrete type used to represent the possible atomic inputs that can
    /// trigger a transition.
    type Input: 'a + Input<Atom=A>;

    /// Iterator over the possible inputs for a state.
    type InputsIter: Iterator<Item=&'a Self::Input>;

    /// Type used to store the input and any relevant metadata for a transition
    /// from one state to another.
    type Transition: Transition<Input=Self::Input, Action=Self::Action>;

    /// Type used to identify individual transitions.
    type TransitionId: Id;

    /// Iterator over the IDs of transitions in the automaton.
    type TransitionIdsIter: 'a + Iterator<Item=Self::TransitionId>;

    /// Iterator over the possible outgoing transitions for a state.
    type TransitionsIter: Iterator<Item=(Self::TransitionId, Self::StateId)>;

    // --------------------------------

    /// Get the number of states in the automaton.
    fn state_count(&self) -> usize;

    /// Get an iterator over the IDs of all states in the automaton.
    fn state_ids(&self) -> Self::StateIdsIter;

    /// Get the ID of the automaton's initial state.
    fn initial_state(&self) -> Self::StateId;

    /// Get a reference to a specific state.
    fn state(&self, s: Self::StateId) -> &Self::State;

    /// Fetch an iterator over the valid inputs for a given state.
    fn state_inputs(&'a self, s: Self::StateId) -> Self::InputsIter;

    /// Fetch an iterator over the `(transition_id, next_state_id)` pairs for
    /// a given state.
    fn state_transitions(&'a self, s: Self::StateId) -> Self::TransitionsIter;

    // --------------------------------

    /// Get the number of transitions in the automaton.
    fn transition_count(&self) -> usize;

    /// Get an iterator over the IDs of all transitions in the automaton
    fn transition_ids(&self) -> Self::TransitionIdsIter;

    /// Get a reference to a specific transition.
    fn transition(&self, id: Self::TransitionId) -> &Self::Transition;
}

/// Interface for deterministic automatons.
pub trait DeterministicAutomaton<'a, A: 'a + Atom>: Automaton<'a, A> {
    /// Determine the next state to which the DFA will transition given its
    /// current state and the input token.
    fn next_state(&self, current_state: Self::StateId, input: A) -> Option<Self::StateId>;
}

/// Interface for non-deterministic automatons.
pub trait NondeterministicAutomaton<'a,A: 'a + Atom>: Automaton<'a, A> {
    /// Iterator type returned by `next_states`.
    type NextStatesIter: Iterator<Item=Self::StateId>;

    /// Fetch an iterator over the set of states to which the automaton
    /// transitions on the given input.
    fn next_states(&'a self, current_state: Self::StateId, input: A) -> Self::NextStatesIter;
}

/// Interface for graph-based automata that support exposing their internal
/// graph representations.
pub trait Graph {
    /// Concrete graph type used by the automaton.
    type Graph: graph::DirectedGraph;

    /// Fetch a reference to the automaton's internal graph.
    fn graph(&self) -> &Self::Graph;
}
<|endoftext|>