<commit_before>use utils::time;
use super::heartbeat_rpc::*;
use super::raft::*;
use super::*;
use raft::{RaftService, LogEntry, RaftMsg, Service as raft_svr_trait};
use raft::state_machine::StateMachineCtl;
use raft::state_machine::callback::server::{SMCallback, notify as cb_notify};
use rpc::Server;
use parking_lot::{RwLock};
use std::collections::{HashMap, HashSet, BTreeSet};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{thread, time as std_time};
use bifrost_hasher::hash_str;
use membership::client::{Group as ClientGroup, Member as ClientMember};

static MAX_TIMEOUT: i64 = 1000; //5 secs for 500ms heartbeat

struct HBStatus {
    online: bool,
    last_updated: i64,
}

pub struct HeartbeatService {
    status: RwLock<HashMap<u64, HBStatus>>,
    raft_service: Arc<RaftService>,
    closed: AtomicBool,
    was_leader: AtomicBool,
}

impl Service for HeartbeatService {
    fn ping(&self, id: u64) -> Result<(), ()> {
        let mut stat_map = self.status.write();
        let current_time = time::get_time();
        let mut stat = stat_map.entry(id).or_insert_with(|| HBStatus {
            online: false,
            last_updated: current_time,
            //orthodoxy info will trigger the watcher thread to update
        });
        stat.last_updated = current_time;
        // only update the timestamp, let the watcher thread to decide
        Ok(())
    }
}
impl HeartbeatService {
    fn update_raft(&self, online: Vec<u64>, offline: Vec<u64>) {
        let log = commands::hb_online_changed {
            online: online,
            offline: offline
        };
        let (fn_id, _, data) = log.encode();
        self.raft_service.c_command(LogEntry {
            id: 0,
            term: 0,
            sm_id: DEFAULT_SERVICE_ID,
            fn_id: fn_id,
            data: data
        });
    }
    fn transfer_leadership(&self) { //update timestamp for every alive server
        let mut stat_map = self.status.write();
        let current_time = time::get_time();
        for stat in stat_map.values_mut() {
            if stat.online {
                stat.last_updated = current_time;
            }
        }
    }
}
dispatch_rpc_service_functions!(HeartbeatService);

struct Member {
    pub id: u64,
    pub address: String,
    pub groups: HashSet<u64>,
}

struct MemberGroup {
    name: String,
    id: u64,
    members: BTreeSet<u64>
}

pub struct Membership {
    heartbeat: Arc<HeartbeatService>,
    groups: HashMap<u64, MemberGroup>,
    members: HashMap<u64, Member>,
    callback: Option<SMCallback>,
}
impl Drop for Membership {
    fn drop(&mut self) {
        self.heartbeat.closed.store(true, Ordering::Relaxed)
    }
}

impl Membership {
    pub fn new(server: &Arc<Server>, raft_service: &Arc<RaftService>) {
        let service = Arc::new(HeartbeatService {
            status: RwLock::new(HashMap::new()),
            closed: AtomicBool::new(false),
            raft_service: raft_service.clone(),
            was_leader: AtomicBool::new(false),
        });
        let service_clone = service.clone();
        thread::spawn(move || {
            while !service_clone.closed.load(Ordering::Relaxed) {
                let is_leader = service_clone.raft_service.is_leader();
                let was_leader = service_clone.was_leader.load(Ordering::Relaxed);
                if !was_leader && is_leader {service_clone.transfer_leadership()}
                if was_leader != is_leader {service_clone.was_leader.store(is_leader, Ordering::Relaxed);}
                if is_leader {
                    let current_time = time::get_time();
                    let mut outdated_members: Vec<u64> = Vec::new();
                    let mut backedin_members: Vec<u64> = Vec::new();
                    {
                        let mut status_map = service_clone.status.write();
                        let mut members_to_update: HashMap<u64, bool> = HashMap::new();
                        for (id, status) in status_map.iter() {
                            let alive = (current_time - status.last_updated) < MAX_TIMEOUT;
                            if status.online && !alive {
                                outdated_members.push(id.clone());
                                members_to_update.insert(id.clone(), alive);
                            }
                            if !status.online && alive {
                                backedin_members.push(id.clone());
                                members_to_update.insert(id.clone(), alive);
                            }
                        }
                        for (id, alive) in members_to_update.iter() {
                            let mut status = status_map.get_mut(&id).unwrap();
                            status.online = alive.clone();
                        }

                    }
                    service_clone.update_raft(backedin_members, outdated_members);
                }
                thread::sleep(std_time::Duration::from_secs(1));
            }
        });
        raft_service.register_state_machine(Box::new(Membership {
            heartbeat: service.clone(),
            groups: HashMap::new(),
            members: HashMap::new(),
            callback: None,
        }));
        server.register_service(DEFAULT_SERVICE_ID, service);
    }
    fn compose_client_member(&self, id: u64) -> ClientMember {
        let member = self.members.get(&id).unwrap();
        let stat_map = self.heartbeat.status.read();
        ClientMember {
            id: id,
            address: member.address.clone(),
            online: stat_map.get(&id).unwrap().online
        }
    }
    pub fn init_callback(&mut self, raft_service: &Arc<RaftService>) {
        self.callback = Some(SMCallback::new(self.id(), raft_service.clone()));
    }
    fn notify_for_member_online(&self, id: u64) {
        let client_member = self.compose_client_member(id);
        cb_notify(
            &self.callback,
            &commands::on_any_member_online{},
            || Ok(client_member.clone())
        );
        if let Some(ref member) = self.members.get(&id) {
            for group in &member.groups {
                cb_notify(
                    &self.callback,
                    &commands::on_group_member_online{group: group.clone()},
                    || Ok(client_member.clone())
                );
            }
        }
    }
    fn notify_for_member_offline(&self, id: u64) {
        let client_member = self.compose_client_member(id);
        cb_notify(
            &self.callback,
            &commands::on_any_member_offline{},
            || Ok(client_member.clone())
        );
        if let Some(ref member) = self.members.get(&id) {
            for group in &member.groups {
                cb_notify(
                    &self.callback,
                    &commands::on_group_member_offline{group: group.clone()},
                    || Ok(client_member.clone())
                );
            }
        }
    }
    fn notify_for_member_left(&self, id: u64) {
        let client_member = self.compose_client_member(id);
        cb_notify(
            &self.callback,
            &commands::on_any_member_left{},
            || Ok(client_member.clone())
        );
        if let Some(ref member) = self.members.get(&id) {
            for group in &member.groups {
                self.notify_for_group_member_left(group.clone(), &client_member)
            }
        }
    }
    fn notify_for_group_member_left(&self, group: u64, member: &ClientMember) {
        cb_notify(
            &self.callback,
            &commands::on_group_member_left{group: group},
            || Ok(member.clone())
        );
    }
    fn leave_group_(&mut self, group_id: u64, id: u64, need_notify: bool) -> Result<(), ()> {
        let mut success = false;
        let mut client_member: Option<ClientMember> = None;
        if let Some(ref mut group) = self.groups.get_mut(&group_id) {
            if let Some(ref mut member) = self.members.get_mut(&id) {
                group.members.remove(&id);
                member.groups.remove(&group_id);
                success = true;
            }
        }
        if success {
            if need_notify {
                self.notify_for_group_member_left(group_id, &self.compose_client_member(id));
            }
            return Ok(());
        } else {
            return Err(());
        }
    }
}

impl StateMachineCmds for Membership {
    fn hb_online_changed(&mut self, online: Vec<u64>, offline: Vec<u64>) -> Result<(), ()> {
        {
            let mut stat_map = self.heartbeat.status.write();
            for id in &online {
                if let Some(ref mut stat) = stat_map.get_mut(&id) {
                    stat.online = true;
                }
            }
            for id in &offline {
                if let Some(ref mut stat) = stat_map.get_mut(&id) {
                    stat.online = false;
                }
            }
        }
        for id in online {
            self.notify_for_member_online(id);
        }
        for id in offline {
            self.notify_for_member_offline(id);
        }
        Ok(())
    }
    fn join(&mut self, address: String) -> Result<u64, ()> {
        let id = hash_str(&address);
        let mut stat_map = self.heartbeat.status.write();
        let mut joined = false;
        self.members.entry(id).or_insert_with(|| {
            let current_time = time::get_time();
            let mut stat = stat_map.entry(id).or_insert_with(|| HBStatus {
                online: true,
                last_updated: current_time
            });
            stat.online = true;
            stat.last_updated = current_time;
            joined = true;
            Member {
                id: id,
                address: address.clone(),
                groups: HashSet::new(),
            }
        });
        if joined {
            cb_notify(
                &self.callback,
                &commands::on_any_member_joined{},
                || Ok(self.compose_client_member(id))
            );
            Ok(id)
        } else {
            Err(())
        }
    }
    fn leave(&mut self, id: u64) -> Result<(), ()> {
        if !self.members.contains_key(&id) {return Err(())};
        let mut groups:Vec<u64> = Vec::new();
        if let Some(member) = self.members.get(&id) {
            for group in &member.groups {
                groups.push(group.clone());
            }
        }
        self.notify_for_member_left(id);
        for group_id in groups {
            self.leave_group_(group_id, id, false);
        }
        {
            let mut stat_map = self.heartbeat.status.write();
            stat_map.remove(&id);
        }
        self.members.remove(&id);
        Ok(())
    }
    fn join_group(&mut self, group_id: u64, id: u64) -> Result<(), ()> {
        let mut success = false;
        if let Some(ref mut group) = self.groups.get_mut(&group_id) {
            if let Some(ref mut member) = self.members.get_mut(&id) {
                group.members.insert(id);
                member.groups.insert(group_id);
                success = true;
            }
        }
        if success {
            cb_notify(
                &self.callback,
                &commands::on_group_member_joined{group: group_id},
                || Ok(self.compose_client_member(id))
            );
            return Ok(());
        } else {
            return Err(());
        }
    }
    fn leave_group(&mut self, group_id: u64, id: u64) -> Result<(), ()> {
        self.leave_group_(group_id, id, true)
    }
    fn new_group(&mut self, name: String) -> Result<u64, u64> {
        let id = hash_str(&name);
        let mut inserted = false;
        self.groups.entry(id).or_insert_with(|| {
            inserted = true;
            MemberGroup {
                name: name,
                id: id,
                members: BTreeSet::new(),
            }
        });
        if inserted {
            Ok(id)
        } else {
            Err(id)
        }
    }
    fn del_group(&mut self, id: u64) -> Result<(), ()> {
        let mut members: Option<BTreeSet<u64>> = None;
        if let Some(group) = self.groups.get(&id) {
            members = Some(group.members.clone());
        }
        if let Some(members) = members {
            for member_id in members {
                if let Some(ref mut member) = self.members.get_mut(&member_id) {
                    member.groups.remove(&id);
                }
            }
            self.groups.remove(&id);
            return Ok(())
        } else {
            return Err(());
        }
    }
    fn group_leader(&self, group: u64) -> Result<Option<ClientMember>, ()> {
        if let Some(group) = self.groups.get(&group) {
            let first_member = group.members.iter().next();
            if let Some(first_member) = first_member {
                Ok(Some(self.compose_client_member(first_member.clone())))
            } else {
                Ok(None)
            }
        } else {
            Err(())
        }
    }
    fn group_members(&self, group: u64, online_only: bool) -> Result<Vec<ClientMember>, ()> {
        if let Some(group) = self.groups.get(&group) {
            Ok(group.members.iter()
                .map(|id| self.compose_client_member(id.clone()))
                .filter(|member| !online_only || member.online)
                .collect())
        } else {
            Err(())
        }
    }
    fn all_members(&self, online_only: bool) -> Result<Vec<ClientMember>, ()> {
        Ok(self.members.iter()
            .map(|(id, _)| self.compose_client_member(id.clone()))
            .filter(|member| !online_only || member.online)
            .collect())
    }
}
impl StateMachineCtl for Membership {
    raft_sm_complete!();
    fn snapshot(&self) -> Option<Vec<u8>> {
        //Some(serialize!(&self.map))
        None // TODO: Backup members
    }
    fn recover(&mut self, data: Vec<u8>) {
        //self.map = deserialize!(&data);
    }
    fn id(&self) -> u64 {DEFAULT_SERVICE_ID}
}<commit_msg>fix group leader<commit_after>use utils::time;
use super::heartbeat_rpc::*;
use super::raft::*;
use super::*;
use raft::{RaftService, LogEntry, RaftMsg, Service as raft_svr_trait};
use raft::state_machine::StateMachineCtl;
use raft::state_machine::callback::server::{SMCallback, notify as cb_notify};
use rpc::Server;
use parking_lot::{RwLock};
use std::collections::{HashMap, HashSet, BTreeSet};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{thread, time as std_time};
use bifrost_hasher::hash_str;
use membership::client::{Group as ClientGroup, Member as ClientMember};

static MAX_TIMEOUT: i64 = 1000; //5 secs for 500ms heartbeat

struct HBStatus {
    online: bool,
    last_updated: i64,
}

pub struct HeartbeatService {
    status: RwLock<HashMap<u64, HBStatus>>,
    raft_service: Arc<RaftService>,
    closed: AtomicBool,
    was_leader: AtomicBool,
}

impl Service for HeartbeatService {
    fn ping(&self, id: u64) -> Result<(), ()> {
        let mut stat_map = self.status.write();
        let current_time = time::get_time();
        let mut stat = stat_map.entry(id).or_insert_with(|| HBStatus {
            online: false,
            last_updated: current_time,
            //orthodoxy info will trigger the watcher thread to update
        });
        stat.last_updated = current_time;
        // only update the timestamp, let the watcher thread to decide
        Ok(())
    }
}
impl HeartbeatService {
    fn update_raft(&self, online: Vec<u64>, offline: Vec<u64>) {
        let log = commands::hb_online_changed {
            online: online,
            offline: offline
        };
        let (fn_id, _, data) = log.encode();
        self.raft_service.c_command(LogEntry {
            id: 0,
            term: 0,
            sm_id: DEFAULT_SERVICE_ID,
            fn_id: fn_id,
            data: data
        });
    }
    fn transfer_leadership(&self) { //update timestamp for every alive server
        let mut stat_map = self.status.write();
        let current_time = time::get_time();
        for stat in stat_map.values_mut() {
            if stat.online {
                stat.last_updated = current_time;
            }
        }
    }
}
dispatch_rpc_service_functions!(HeartbeatService);

struct Member {
    pub id: u64,
    pub address: String,
    pub groups: HashSet<u64>,
}

struct MemberGroup {
    name: String,
    id: u64,
    members: BTreeSet<u64>
}

pub struct Membership {
    heartbeat: Arc<HeartbeatService>,
    groups: HashMap<u64, MemberGroup>,
    members: HashMap<u64, Member>,
    callback: Option<SMCallback>,
}
impl Drop for Membership {
    fn drop(&mut self) {
        self.heartbeat.closed.store(true, Ordering::Relaxed)
    }
}

impl Membership {
    pub fn new(server: &Arc<Server>, raft_service: &Arc<RaftService>) {
        let service = Arc::new(HeartbeatService {
            status: RwLock::new(HashMap::new()),
            closed: AtomicBool::new(false),
            raft_service: raft_service.clone(),
            was_leader: AtomicBool::new(false),
        });
        let service_clone = service.clone();
        thread::spawn(move || {
            while !service_clone.closed.load(Ordering::Relaxed) {
                let is_leader = service_clone.raft_service.is_leader();
                let was_leader = service_clone.was_leader.load(Ordering::Relaxed);
                if !was_leader && is_leader {service_clone.transfer_leadership()}
                if was_leader != is_leader {service_clone.was_leader.store(is_leader, Ordering::Relaxed);}
                if is_leader {
                    let current_time = time::get_time();
                    let mut outdated_members: Vec<u64> = Vec::new();
                    let mut backedin_members: Vec<u64> = Vec::new();
                    {
                        let mut status_map = service_clone.status.write();
                        let mut members_to_update: HashMap<u64, bool> = HashMap::new();
                        for (id, status) in status_map.iter() {
                            let alive = (current_time - status.last_updated) < MAX_TIMEOUT;
                            if status.online && !alive {
                                outdated_members.push(*id);
                                members_to_update.insert(*id, alive);
                            }
                            if !status.online && alive {
                                backedin_members.push(*id);
                                members_to_update.insert(*id, alive);
                            }
                        }
                        for (id, alive) in members_to_update.iter() {
                            let mut status = status_map.get_mut(&id).unwrap();
                            status.online = *alive;
                        }

                    }
                    service_clone.update_raft(backedin_members, outdated_members);
                }
                thread::sleep(std_time::Duration::from_secs(1));
            }
        });
        raft_service.register_state_machine(Box::new(Membership {
            heartbeat: service.clone(),
            groups: HashMap::new(),
            members: HashMap::new(),
            callback: None,
        }));
        server.register_service(DEFAULT_SERVICE_ID, service);
    }
    fn compose_client_member(&self, id: u64) -> ClientMember {
        let member = self.members.get(&id).unwrap();
        let stat_map = self.heartbeat.status.read();
        ClientMember {
            id: id,
            address: member.address.clone(),
            online: stat_map.get(&id).unwrap().online
        }
    }
    pub fn init_callback(&mut self, raft_service: &Arc<RaftService>) {
        self.callback = Some(SMCallback::new(self.id(), raft_service.clone()));
    }
    fn notify_for_member_online(&self, id: u64) {
        let client_member = self.compose_client_member(id);
        cb_notify(
            &self.callback,
            &commands::on_any_member_online{},
            || Ok(client_member.clone())
        );
        if let Some(ref member) = self.members.get(&id) {
            for group in &member.groups {
                cb_notify(
                    &self.callback,
                    &commands::on_group_member_online{group: *group},
                    || Ok(client_member.clone())
                );
            }
        }
    }
    fn notify_for_member_offline(&self, id: u64) {
        let client_member = self.compose_client_member(id);
        cb_notify(
            &self.callback,
            &commands::on_any_member_offline{},
            || Ok(client_member.clone())
        );
        if let Some(ref member) = self.members.get(&id) {
            for group in &member.groups {
                cb_notify(
                    &self.callback,
                    &commands::on_group_member_offline{group: *group},
                    || Ok(client_member.clone())
                );
            }
        }
    }
    fn notify_for_member_left(&self, id: u64) {
        let client_member = self.compose_client_member(id);
        cb_notify(
            &self.callback,
            &commands::on_any_member_left{},
            || Ok(client_member.clone())
        );
        if let Some(ref member) = self.members.get(&id) {
            for group in &member.groups {
                self.notify_for_group_member_left(*group, &client_member)
            }
        }
    }
    fn notify_for_group_member_left(&self, group: u64, member: &ClientMember) {
        cb_notify(
            &self.callback,
            &commands::on_group_member_left{group: group},
            || Ok(member.clone())
        );
    }
    fn leave_group_(&mut self, group_id: u64, id: u64, need_notify: bool) -> Result<(), ()> {
        let mut success = false;
        let mut client_member: Option<ClientMember> = None;
        if let Some(ref mut group) = self.groups.get_mut(&group_id) {
            if let Some(ref mut member) = self.members.get_mut(&id) {
                group.members.remove(&id);
                member.groups.remove(&group_id);
                success = true;
            }
        }
        if success {
            if need_notify {
                self.notify_for_group_member_left(group_id, &self.compose_client_member(id));
            }
            return Ok(());
        } else {
            return Err(());
        }
    }
    fn group_leader_id(&self, group: u64) -> Result<Option<u64>, ()> {
        if let Some(group) = self.groups.get(&group) {
            let stat_map = self.heartbeat.status.read();
            for member in group.members.iter() {
                if let Some(member_stat) = stat_map.get(&member) {
                    if member_stat.online {
                        return Ok(Some(*member))
                    }
                }
            }
            Ok(None)
        } else {
            Err(())
        }
    }
}

impl StateMachineCmds for Membership {
    fn hb_online_changed(&mut self, online: Vec<u64>, offline: Vec<u64>) -> Result<(), ()> {
        {
            let mut stat_map = self.heartbeat.status.write();
            for id in &online {
                if let Some(ref mut stat) = stat_map.get_mut(&id) {
                    stat.online = true;
                }
            }
            for id in &offline {
                if let Some(ref mut stat) = stat_map.get_mut(&id) {
                    stat.online = false;
                }
            }
        }
        for id in online {
            self.notify_for_member_online(id);
        }
        for id in offline {
            self.notify_for_member_offline(id);
        }
        Ok(())
    }
    fn join(&mut self, address: String) -> Result<u64, ()> {
        let id = hash_str(&address);
        let mut stat_map = self.heartbeat.status.write();
        let mut joined = false;
        self.members.entry(id).or_insert_with(|| {
            let current_time = time::get_time();
            let mut stat = stat_map.entry(id).or_insert_with(|| HBStatus {
                online: true,
                last_updated: current_time
            });
            stat.online = true;
            stat.last_updated = current_time;
            joined = true;
            Member {
                id: id,
                address: address.clone(),
                groups: HashSet::new(),
            }
        });
        if joined {
            cb_notify(
                &self.callback,
                &commands::on_any_member_joined{},
                || Ok(self.compose_client_member(id))
            );
            Ok(id)
        } else {
            Err(())
        }
    }
    fn leave(&mut self, id: u64) -> Result<(), ()> {
        if !self.members.contains_key(&id) {return Err(())};
        let mut groups:Vec<u64> = Vec::new();
        if let Some(member) = self.members.get(&id) {
            for group in &member.groups {
                groups.push(*group);
            }
        }
        self.notify_for_member_left(id);
        for group_id in groups {
            self.leave_group_(group_id, id, false);
        }
        {
            let mut stat_map = self.heartbeat.status.write();
            stat_map.remove(&id);
        }
        self.members.remove(&id);
        Ok(())
    }
    fn join_group(&mut self, group_id: u64, id: u64) -> Result<(), ()> {
        let mut success = false;
        if let Some(ref mut group) = self.groups.get_mut(&group_id) {
            if let Some(ref mut member) = self.members.get_mut(&id) {
                group.members.insert(id);
                member.groups.insert(group_id);
                success = true;
            }
        }
        if success {
            cb_notify(
                &self.callback,
                &commands::on_group_member_joined{group: group_id},
                || Ok(self.compose_client_member(id))
            );
            return Ok(());
        } else {
            return Err(());
        }
    }
    fn leave_group(&mut self, group_id: u64, id: u64) -> Result<(), ()> {
        self.leave_group_(group_id, id, true)
    }
    fn new_group(&mut self, name: String) -> Result<u64, u64> {
        let id = hash_str(&name);
        let mut inserted = false;
        self.groups.entry(id).or_insert_with(|| {
            inserted = true;
            MemberGroup {
                name: name,
                id: id,
                members: BTreeSet::new(),
            }
        });
        if inserted {
            Ok(id)
        } else {
            Err(id)
        }
    }
    fn del_group(&mut self, id: u64) -> Result<(), ()> {
        let mut members: Option<BTreeSet<u64>> = None;
        if let Some(group) = self.groups.get(&id) {
            members = Some(group.members.clone());
        }
        if let Some(members) = members {
            for member_id in members {
                if let Some(ref mut member) = self.members.get_mut(&member_id) {
                    member.groups.remove(&id);
                }
            }
            self.groups.remove(&id);
            return Ok(())
        } else {
            return Err(());
        }
    }
    fn group_leader(&self, group: u64) -> Result<Option<ClientMember>, ()> {
        match self.group_leader_id(group) {
            Ok(Some(id)) => Ok(Some(self.compose_client_member(id))),
            Err(_) => Err(()),
            _ => Ok(None)
        }
    }
    fn group_members(&self, group: u64, online_only: bool) -> Result<Vec<ClientMember>, ()> {
        if let Some(group) = self.groups.get(&group) {
            Ok(group.members.iter()
                .map(|id| self.compose_client_member(*id))
                .filter(|member| !online_only || member.online)
                .collect())
        } else {
            Err(())
        }
    }
    fn all_members(&self, online_only: bool) -> Result<Vec<ClientMember>, ()> {
        Ok(self.members.iter()
            .map(|(id, _)| self.compose_client_member(*id))
            .filter(|member| !online_only || member.online)
            .collect())
    }
}
impl StateMachineCtl for Membership {
    raft_sm_complete!();
    fn snapshot(&self) -> Option<Vec<u8>> {
        //Some(serialize!(&self.map))
        None // TODO: Backup members
    }
    fn recover(&mut self, data: Vec<u8>) {
        //self.map = deserialize!(&data);
    }
    fn id(&self) -> u64 {DEFAULT_SERVICE_ID}
}<|endoftext|>


<commit_before>//
// 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
//

#![deny(
    dead_code,
    non_camel_case_types,
    non_snake_case,
    path_statements,
    trivial_numeric_casts,
    unstable_features,
    unused_allocation,
    unused_import_braces,
    unused_imports,
    unused_must_use,
    unused_mut,
    unused_qualifications,
    while_true,
)]

#[macro_use] extern crate log;
extern crate semver;
extern crate toml;

extern crate libimagrt;
#[macro_use] extern crate libimagstore;
#[macro_use] extern crate libimagerror;
extern crate libimagentryedit;
extern crate libimagentrytag;

pub static MODULE_ENTRY_PATH_NAME: &'static str = "notes";

module_entry_path_mod!(MODULE_ENTRY_PATH_NAME);

pub mod error;
pub mod note;
pub mod result;

<commit_msg>Revert "Export module entry path string"<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
//

#![deny(
    dead_code,
    non_camel_case_types,
    non_snake_case,
    path_statements,
    trivial_numeric_casts,
    unstable_features,
    unused_allocation,
    unused_import_braces,
    unused_imports,
    unused_must_use,
    unused_mut,
    unused_qualifications,
    while_true,
)]

#[macro_use] extern crate log;
extern crate semver;
extern crate toml;

extern crate libimagrt;
#[macro_use] extern crate libimagstore;
#[macro_use] extern crate libimagerror;
extern crate libimagentryedit;
extern crate libimagentrytag;

module_entry_path_mod!("notes");

pub mod error;
pub mod note;
pub mod result;

<|endoftext|>


<commit_before>use nom::{ConsumerState, FileProducer, Producer};
use std::io;
use std::io::{Error, Result};
use std::u32;
use std::fs::File;

use utility::{ErrorKind, ReadStream};

use metadata::{
  Metadata, Data,
  StreamInfo, CueSheet, VorbisComment, Picture,
  PictureType,
};
use metadata::types::Consumer;

// Will return true when the unwrapped value of `option` and `other` match
// or `option` is `Option::None`, otherwise false.
#[inline]
pub fn optional_eq<T: Eq>(option: Option<T>, other: T) -> bool {
  option.map_or(true, |value| value == other)
}

// With the given filename, return all metadata blocks available.
//
// This function expects a flac file, but will return a proper `Result::Err`
// when things go wrong.
//
// # Failures
//
// * `ErrorKind::NotFound` is returned when the given filename isn't found.
// * `ErrorKind::InvalidData` is returned when the data within the file
//   isn't valid FLAC data.
pub fn get_metadata(filename: &str) -> Result<Vec<Metadata>> {
  File::open(filename).and_then(|file| {
    let mut stream   = ReadStream::new(file);
    let mut consumer = Consumer::new();
    let mut is_error = false;

    loop {
      match consumer.handle(&mut stream) {
        Ok(_)                         => break,
        Err(ErrorKind::Consumed(_))   => continue,
        Err(ErrorKind::Incomplete(_)) => continue,
        Err(_)                        => {
          is_error = true;

          break;
        }
      }
    }

    if is_error {
      let error_str = "parser: couldn't find any metadata";

      Err(Error::new(io::ErrorKind::InvalidData, error_str))
    } else {
      Ok(consumer.data)
    }
  })
}

/// Reads and returns the `StreamInfo` metadata block of the given FLAC
/// file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `StreamInfo` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
///
/// # Examples
///
/// Handling errors might look something like this:
///
/// ```
/// use flac::metadata;
///
/// match metadata::get_stream_info("path/to/file.flac") {
///   Ok(stream_info) => {
///     // Use the stream_info variable...
///   }
///   Err(error)      => println!("{}", error),
/// }
/// ```
///
/// Or just ignore the errors:
///
/// ```no_run
/// use flac::metadata;
///
/// let stream_info = metadata::get_stream_info("path/to/file.flac").unwrap();
/// ```
pub fn get_stream_info(filename: &str) -> Result<StreamInfo> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find StreamInfo";
    let mut result = Err(Error::new(io::ErrorKind::NotFound, error_str));

    for block in blocks {
      if let Data::StreamInfo(stream_info) = block.data {
        result = Ok(stream_info);
        break;
      }
    }

    result
  })
}

/// Reads and returns the `VorbisComment` metadata block of the given FLAC
/// file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `VorbisComment` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
///
/// # Examples
///
/// Handling errors might look something like this:
///
/// ```
/// use flac::metadata;
///
/// match metadata::get_vorbis_comment("path/to/file.flac") {
///   Ok(vorbis_comment) => {
///     // Use the vorbis_comment variable...
///   }
///   Err(error)         => println!("{}", error),
/// }
/// ```
///
/// Or just ignore the errors:
///
/// ```no_run
/// use flac::metadata;
///
/// let vorbis_comment =
///   metadata::get_vorbis_comment("path/to/file.flac").unwrap();
/// ```
pub fn get_vorbis_comment(filename: &str) -> Result<VorbisComment> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find VorbisComment";
    let mut result = Err(Error::new(io::ErrorKind::NotFound, error_str));

    for block in blocks {
      if let Data::VorbisComment(vorbis_comment) = block.data {
        result = Ok(vorbis_comment);
        break;
      }
    }

    result
  })
}

/// Reads and returns the `CueSheet` metadata block of the given FLAC file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `CueSheet` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
///
/// # Examples
///
/// Handling errors might look something like this:
///
/// ```
/// use flac::metadata;
///
/// match metadata::get_cue_sheet("path/to/file.flac") {
///   Ok(cue_sheet) => {
///     // Use the cue_sheet variable...
///   }
///   Err(error)    => println!("{}", error),
/// }
/// ```
///
/// Or just ignore the errors
///
/// ```no_run
/// use flac::metadata;
///
/// let cue_sheet = metadata::get_cue_sheet("path/to/file.flac").unwrap();
/// ```
pub fn get_cue_sheet(filename: &str) -> Result<CueSheet> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find CueSheet";
    let mut result = Err(Error::new(io::ErrorKind::NotFound, error_str));

    for block in blocks {
      if let Data::CueSheet(cue_sheet) = block.data {
        result = Ok(cue_sheet);
        break;
      }
    }

    result
  })
}

/// Reads and returns a `Picture` metadata block of the given FLAC file.
///
/// There can be more than one `Picture` block in a file and this function
/// takes optional, that being `Option<T>`, parameters that act as
/// constraints to search within. The `Picture` with the largest area
/// matching all constraints will be returned.
///
/// Putting `None` into any of the optional constraints conveys that you
/// want any of that parameter. Otherwise it will try to look for the image
/// that matches within the given constraints.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found,
///   there is no `Picture` within the file, or no `Picture` that fits the
///   given constraints.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
///
/// # Examples
///
/// Handling errors might look something like this:
///
/// ```
/// use flac::metadata;
/// use flac::metadata::PictureType;
///
/// let result = metadata::get_picture("path/to/file.flac",
///                                    Some(PictureType::FileIcon),
///                                    Some("image/gif"),
///                                    None, None, None,
///                                    None, None);
///
/// match result {
///   Ok(picture) => {
///     // Use the picture variable...
///   }
///   Err(error)  => println!("{}", error),
/// }
/// ```
///
/// Or just ignore the errors:
///
/// ```no_run
/// use flac::metadata;
/// use flac::metadata::PictureType;
///
/// let picture = metadata::get_picture("path/to/file.flac",
///                                     Some(PictureType::FileIcon),
///                                     Some("image/gif"),
///                                     None, None, None,
///                                     None, None).unwrap();
/// ```
pub fn get_picture(filename: &str,
                   picture_type: Option<PictureType>,
                   mime_type: Option<&str>,
                   description: Option<&str>,
                   max_width: Option<u32>,
                   max_height: Option<u32>,
                   max_depth: Option<u32>,
                   max_colors: Option<u32>)
                   -> Result<Picture> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find any Picture";
    let mut result = Err(Error::new(io::ErrorKind::NotFound, error_str));

    let mut max_area_seen  = 0;
    let mut max_depth_seen = 0;

    let max_value      = u32::max_value();
    let max_width_num  = max_width.unwrap_or(max_value);
    let max_height_num = max_height.unwrap_or(max_value);
    let max_depth_num  = max_depth.unwrap_or(max_value);
    let max_colors_num = max_colors.unwrap_or(max_value);

    for block in blocks {
      if let Data::Picture(picture) = block.data {
        let area = (picture.width as u64) * (picture.height as u64);

        if optional_eq(picture_type, picture.picture_type) &&
           optional_eq(mime_type, &picture.mime_type) &&
           optional_eq(description, &picture.description) &&
           picture.width <= max_width_num &&
           picture.height <= max_height_num &&
           picture.depth <= max_depth_num &&
           picture.colors <= max_colors_num &&
           (area > max_area_seen || (area == max_area_seen &&
                                     picture.depth > max_depth_seen)) {
          max_area_seen  = area;
          max_depth_seen = picture.depth;
          result         = Ok(picture);
        }
      }
    }

    result
  })
}

#[cfg(test)]
mod tests {
  use super::*;
  use std::io::ErrorKind;

  #[test]
  #[should_panic]
  fn test_panic_optional_eq() {
    assert!(optional_eq(Some(1), 0));
  }

  #[test]
  fn test_optional_eq() {
    assert!(optional_eq(None, 0), "Should always return true when None");
    assert!(optional_eq(Some(0), 0), "Should return true (Some(0) == 0)");
  }

  #[test]
  fn test_get_metadata() {
    let not_found    = get_metadata("non-existent/file.txt");
    let invalid_data = get_metadata("README.md");
    let result       = get_metadata("tests/assets/input-SVAUP.flac");

    assert_eq!(not_found.unwrap_err().kind(), ErrorKind::NotFound);
    assert_eq!(invalid_data.unwrap_err().kind(), ErrorKind::InvalidData);
    assert!(result.is_ok());
  }
}
<commit_msg>Remove the unneeded imports<commit_after>use std::io;
use std::io::{Error, Result};
use std::u32;
use std::fs::File;

use utility::{ErrorKind, ReadStream};

use metadata::{
  Metadata, Data,
  StreamInfo, CueSheet, VorbisComment, Picture,
  PictureType,
};
use metadata::types::Consumer;

// Will return true when the unwrapped value of `option` and `other` match
// or `option` is `Option::None`, otherwise false.
#[inline]
pub fn optional_eq<T: Eq>(option: Option<T>, other: T) -> bool {
  option.map_or(true, |value| value == other)
}

// With the given filename, return all metadata blocks available.
//
// This function expects a flac file, but will return a proper `Result::Err`
// when things go wrong.
//
// # Failures
//
// * `ErrorKind::NotFound` is returned when the given filename isn't found.
// * `ErrorKind::InvalidData` is returned when the data within the file
//   isn't valid FLAC data.
pub fn get_metadata(filename: &str) -> Result<Vec<Metadata>> {
  File::open(filename).and_then(|file| {
    let mut stream   = ReadStream::new(file);
    let mut consumer = Consumer::new();
    let mut is_error = false;

    loop {
      match consumer.handle(&mut stream) {
        Ok(_)                         => break,
        Err(ErrorKind::Consumed(_))   => continue,
        Err(ErrorKind::Incomplete(_)) => continue,
        Err(_)                        => {
          is_error = true;

          break;
        }
      }
    }

    if is_error {
      let error_str = "parser: couldn't find any metadata";

      Err(Error::new(io::ErrorKind::InvalidData, error_str))
    } else {
      Ok(consumer.data)
    }
  })
}

/// Reads and returns the `StreamInfo` metadata block of the given FLAC
/// file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `StreamInfo` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
///
/// # Examples
///
/// Handling errors might look something like this:
///
/// ```
/// use flac::metadata;
///
/// match metadata::get_stream_info("path/to/file.flac") {
///   Ok(stream_info) => {
///     // Use the stream_info variable...
///   }
///   Err(error)      => println!("{}", error),
/// }
/// ```
///
/// Or just ignore the errors:
///
/// ```no_run
/// use flac::metadata;
///
/// let stream_info = metadata::get_stream_info("path/to/file.flac").unwrap();
/// ```
pub fn get_stream_info(filename: &str) -> Result<StreamInfo> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find StreamInfo";
    let mut result = Err(Error::new(io::ErrorKind::NotFound, error_str));

    for block in blocks {
      if let Data::StreamInfo(stream_info) = block.data {
        result = Ok(stream_info);
        break;
      }
    }

    result
  })
}

/// Reads and returns the `VorbisComment` metadata block of the given FLAC
/// file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `VorbisComment` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
///
/// # Examples
///
/// Handling errors might look something like this:
///
/// ```
/// use flac::metadata;
///
/// match metadata::get_vorbis_comment("path/to/file.flac") {
///   Ok(vorbis_comment) => {
///     // Use the vorbis_comment variable...
///   }
///   Err(error)         => println!("{}", error),
/// }
/// ```
///
/// Or just ignore the errors:
///
/// ```no_run
/// use flac::metadata;
///
/// let vorbis_comment =
///   metadata::get_vorbis_comment("path/to/file.flac").unwrap();
/// ```
pub fn get_vorbis_comment(filename: &str) -> Result<VorbisComment> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find VorbisComment";
    let mut result = Err(Error::new(io::ErrorKind::NotFound, error_str));

    for block in blocks {
      if let Data::VorbisComment(vorbis_comment) = block.data {
        result = Ok(vorbis_comment);
        break;
      }
    }

    result
  })
}

/// Reads and returns the `CueSheet` metadata block of the given FLAC file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `CueSheet` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
///
/// # Examples
///
/// Handling errors might look something like this:
///
/// ```
/// use flac::metadata;
///
/// match metadata::get_cue_sheet("path/to/file.flac") {
///   Ok(cue_sheet) => {
///     // Use the cue_sheet variable...
///   }
///   Err(error)    => println!("{}", error),
/// }
/// ```
///
/// Or just ignore the errors
///
/// ```no_run
/// use flac::metadata;
///
/// let cue_sheet = metadata::get_cue_sheet("path/to/file.flac").unwrap();
/// ```
pub fn get_cue_sheet(filename: &str) -> Result<CueSheet> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find CueSheet";
    let mut result = Err(Error::new(io::ErrorKind::NotFound, error_str));

    for block in blocks {
      if let Data::CueSheet(cue_sheet) = block.data {
        result = Ok(cue_sheet);
        break;
      }
    }

    result
  })
}

/// Reads and returns a `Picture` metadata block of the given FLAC file.
///
/// There can be more than one `Picture` block in a file and this function
/// takes optional, that being `Option<T>`, parameters that act as
/// constraints to search within. The `Picture` with the largest area
/// matching all constraints will be returned.
///
/// Putting `None` into any of the optional constraints conveys that you
/// want any of that parameter. Otherwise it will try to look for the image
/// that matches within the given constraints.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found,
///   there is no `Picture` within the file, or no `Picture` that fits the
///   given constraints.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
///
/// # Examples
///
/// Handling errors might look something like this:
///
/// ```
/// use flac::metadata;
/// use flac::metadata::PictureType;
///
/// let result = metadata::get_picture("path/to/file.flac",
///                                    Some(PictureType::FileIcon),
///                                    Some("image/gif"),
///                                    None, None, None,
///                                    None, None);
///
/// match result {
///   Ok(picture) => {
///     // Use the picture variable...
///   }
///   Err(error)  => println!("{}", error),
/// }
/// ```
///
/// Or just ignore the errors:
///
/// ```no_run
/// use flac::metadata;
/// use flac::metadata::PictureType;
///
/// let picture = metadata::get_picture("path/to/file.flac",
///                                     Some(PictureType::FileIcon),
///                                     Some("image/gif"),
///                                     None, None, None,
///                                     None, None).unwrap();
/// ```
pub fn get_picture(filename: &str,
                   picture_type: Option<PictureType>,
                   mime_type: Option<&str>,
                   description: Option<&str>,
                   max_width: Option<u32>,
                   max_height: Option<u32>,
                   max_depth: Option<u32>,
                   max_colors: Option<u32>)
                   -> Result<Picture> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find any Picture";
    let mut result = Err(Error::new(io::ErrorKind::NotFound, error_str));

    let mut max_area_seen  = 0;
    let mut max_depth_seen = 0;

    let max_value      = u32::max_value();
    let max_width_num  = max_width.unwrap_or(max_value);
    let max_height_num = max_height.unwrap_or(max_value);
    let max_depth_num  = max_depth.unwrap_or(max_value);
    let max_colors_num = max_colors.unwrap_or(max_value);

    for block in blocks {
      if let Data::Picture(picture) = block.data {
        let area = (picture.width as u64) * (picture.height as u64);

        if optional_eq(picture_type, picture.picture_type) &&
           optional_eq(mime_type, &picture.mime_type) &&
           optional_eq(description, &picture.description) &&
           picture.width <= max_width_num &&
           picture.height <= max_height_num &&
           picture.depth <= max_depth_num &&
           picture.colors <= max_colors_num &&
           (area > max_area_seen || (area == max_area_seen &&
                                     picture.depth > max_depth_seen)) {
          max_area_seen  = area;
          max_depth_seen = picture.depth;
          result         = Ok(picture);
        }
      }
    }

    result
  })
}

#[cfg(test)]
mod tests {
  use super::*;
  use std::io::ErrorKind;

  #[test]
  #[should_panic]
  fn test_panic_optional_eq() {
    assert!(optional_eq(Some(1), 0));
  }

  #[test]
  fn test_optional_eq() {
    assert!(optional_eq(None, 0), "Should always return true when None");
    assert!(optional_eq(Some(0), 0), "Should return true (Some(0) == 0)");
  }

  #[test]
  fn test_get_metadata() {
    let not_found    = get_metadata("non-existent/file.txt");
    let invalid_data = get_metadata("README.md");
    let result       = get_metadata("tests/assets/input-SVAUP.flac");

    assert_eq!(not_found.unwrap_err().kind(), ErrorKind::NotFound);
    assert_eq!(invalid_data.unwrap_err().kind(), ErrorKind::InvalidData);
    assert!(result.is_ok());
  }
}
<|endoftext|>


<commit_before>//! # The Rust Core Library
//!
//! The Rust Core Library is the dependency-free[^free] foundation of [The
//! Rust Standard Library](../std/index.html). It is the portable glue
//! between the language and its libraries, defining the intrinsic and
//! primitive building blocks of all Rust code. It links to no
//! upstream libraries, no system libraries, and no libc.
//!
//! [^free]: Strictly speaking, there are some symbols which are needed but
//!          they aren't always necessary.
//!
//! The core library is *minimal*: it isn't even aware of heap allocation,
//! nor does it provide concurrency or I/O. These things require
//! platform integration, and this library is platform-agnostic.
//!
//! # How to use the core library
//!
//! Please note that all of these details are currently not considered stable.
//!
// FIXME: Fill me in with more detail when the interface settles
//! This library is built on the assumption of a few existing symbols:
//!
//! * `memcpy`, `memcmp`, `memset` - These are core memory routines which are
//!   often generated by LLVM. Additionally, this library can make explicit
//!   calls to these functions. Their signatures are the same as found in C.
//!   These functions are often provided by the system libc, but can also be
//!   provided by the [compiler-builtins crate](https://crates.io/crates/compiler_builtins).
//!
//! * `rust_begin_panic` - This function takes four arguments, a
//!   `fmt::Arguments`, a `&'static str`, and two `u32`'s. These four arguments
//!   dictate the panic message, the file at which panic was invoked, and the
//!   line and column inside the file. It is up to consumers of this core
//!   library to define this panic function; it is only required to never
//!   return. This requires a `lang` attribute named `panic_impl`.
//!
//! * `rust_eh_personality` - is used by the failure mechanisms of the
//!    compiler. This is often mapped to GCC's personality function, but crates
//!    which do not trigger a panic can be assured that this function is never
//!    called. The `lang` attribute is called `eh_personality`.

// Since libcore defines many fundamental lang items, all tests live in a
// separate crate, libcoretest, to avoid bizarre issues.
//
// Here we explicitly #[cfg]-out this whole crate when testing. If we don't do
// this, both the generated test artifact and the linked libtest (which
// transitively includes libcore) will both define the same set of lang items,
// and this will cause the E0152 "found duplicate lang item" error. See
// discussion in #50466 for details.
//
// This cfg won't affect doc tests.
#![cfg(not(test))]
#![stable(feature = "core", since = "1.6.0")]
#![doc(
    html_root_url = "https://doc.rust-lang.org/nightly/",
    html_playground_url = "https://play.rust-lang.org/",
    issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",
    test(no_crate_inject, attr(deny(warnings))),
    test(attr(allow(dead_code, deprecated, unused_variables, unused_mut)))
)]
#![no_core]
#![warn(deprecated_in_future)]
#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![allow(explicit_outlives_requirements)]
#![allow(incomplete_features)]
#![feature(allow_internal_unstable)]
#![feature(arbitrary_self_types)]
#![feature(asm)]
#![feature(cfg_target_has_atomic)]
#![feature(const_alloc_layout)]
#![feature(const_discriminant)]
#![feature(const_checked_int_methods)]
#![feature(const_euclidean_int_methods)]
#![feature(const_float_classify)]
#![feature(const_float_bits_conv)]
#![feature(const_overflowing_int_methods)]
#![feature(const_int_unchecked_arith)]
#![feature(const_mut_refs)]
#![feature(const_int_pow)]
#![feature(constctlz)]
#![feature(const_panic)]
#![feature(const_pin)]
#![feature(const_fn)]
#![feature(const_fn_union)]
#![feature(const_assume)]
#![cfg_attr(not(bootstrap), feature(const_fn_floating_point_arithmetic))]
#![cfg_attr(not(bootstrap), feature(const_fn_fn_ptr_basics))]
#![feature(const_generics)]
#![feature(const_option)]
#![feature(const_precise_live_drops)]
#![feature(const_ptr_offset)]
#![feature(const_ptr_offset_from)]
#![feature(const_raw_ptr_comparison)]
#![feature(const_slice_from_raw_parts)]
#![feature(const_slice_ptr_len)]
#![feature(const_size_of_val)]
#![feature(const_align_of_val)]
#![feature(const_type_id)]
#![feature(const_type_name)]
#![feature(const_likely)]
#![feature(const_unreachable_unchecked)]
#![feature(custom_inner_attributes)]
#![feature(decl_macro)]
#![feature(doc_cfg)]
#![feature(doc_spotlight)]
#![feature(duration_consts_2)]
#![feature(duration_saturating_ops)]
#![feature(extern_types)]
#![feature(fundamental)]
#![feature(intrinsics)]
#![feature(lang_items)]
#![feature(link_llvm_intrinsics)]
#![feature(llvm_asm)]
#![feature(negative_impls)]
#![feature(never_type)]
#![feature(nll)]
#![feature(exhaustive_patterns)]
#![feature(no_core)]
#![feature(optin_builtin_traits)]
#![feature(or_patterns)]
#![feature(prelude_import)]
#![feature(repr_simd, platform_intrinsics)]
#![feature(rustc_attrs)]
#![feature(simd_ffi)]
#![feature(min_specialization)]
#![feature(staged_api)]
#![feature(std_internals)]
#![feature(stmt_expr_attributes)]
#![feature(transparent_unions)]
#![feature(unboxed_closures)]
#![feature(unsized_locals)]
#![feature(untagged_unions)]
#![feature(unwind_attributes)]
#![feature(variant_count)]
#![cfg_attr(bootstrap, feature(doc_alias))]
#![feature(tbm_target_feature)]
#![feature(sse4a_target_feature)]
#![feature(arm_target_feature)]
#![feature(powerpc_target_feature)]
#![feature(mips_target_feature)]
#![feature(aarch64_target_feature)]
#![feature(wasm_target_feature)]
#![feature(avx512_target_feature)]
#![feature(cmpxchg16b_target_feature)]
#![feature(rtm_target_feature)]
#![feature(f16c_target_feature)]
#![feature(hexagon_target_feature)]
#![feature(const_fn_transmute)]
#![feature(abi_unadjusted)]
#![feature(adx_target_feature)]
#![feature(external_doc)]
#![feature(associated_type_bounds)]
#![feature(const_caller_location)]
#![feature(slice_ptr_get)]
#![feature(no_niche)] // rust-lang/rust#68303
#![feature(unsafe_block_in_unsafe_fn)]
#![deny(unsafe_op_in_unsafe_fn)]

#[prelude_import]
#[allow(unused)]
use prelude::v1::*;

#[cfg(not(test))] // See #65860
#[macro_use]
mod macros;

#[macro_use]
mod internal_macros;

#[path = "num/shells/int_macros.rs"]
#[macro_use]
mod int_macros;

#[path = "num/shells/i128.rs"]
pub mod i128;
#[path = "num/shells/i16.rs"]
pub mod i16;
#[path = "num/shells/i32.rs"]
pub mod i32;
#[path = "num/shells/i64.rs"]
pub mod i64;
#[path = "num/shells/i8.rs"]
pub mod i8;
#[path = "num/shells/isize.rs"]
pub mod isize;

#[path = "num/shells/u128.rs"]
pub mod u128;
#[path = "num/shells/u16.rs"]
pub mod u16;
#[path = "num/shells/u32.rs"]
pub mod u32;
#[path = "num/shells/u64.rs"]
pub mod u64;
#[path = "num/shells/u8.rs"]
pub mod u8;
#[path = "num/shells/usize.rs"]
pub mod usize;

#[path = "num/f32.rs"]
pub mod f32;
#[path = "num/f64.rs"]
pub mod f64;

#[macro_use]
pub mod num;

/* The libcore prelude, not as all-encompassing as the libstd prelude */

pub mod prelude;

/* Core modules for ownership management */

pub mod hint;
pub mod intrinsics;
pub mod mem;
pub mod ptr;

/* Core language traits */

pub mod borrow;
pub mod clone;
pub mod cmp;
pub mod convert;
pub mod default;
pub mod marker;
pub mod ops;

/* Core types and methods on primitives */

pub mod any;
pub mod array;
pub mod ascii;
pub mod cell;
pub mod char;
pub mod ffi;
pub mod iter;
#[unstable(feature = "once_cell", issue = "74465")]
pub mod lazy;
pub mod option;
pub mod panic;
pub mod panicking;
pub mod pin;
pub mod raw;
pub mod result;
pub mod sync;

pub mod fmt;
pub mod hash;
pub mod slice;
pub mod str;
pub mod time;

pub mod unicode;

/* Async */
pub mod future;
pub mod task;

/* Heap memory allocator trait */
#[allow(missing_docs)]
pub mod alloc;

// note: does not need to be public
mod bool;
mod tuple;
mod unit;

#[stable(feature = "core_primitive", since = "1.43.0")]
pub mod primitive;

// Pull in the `core_arch` crate directly into libcore. The contents of
// `core_arch` are in a different repository: rust-lang/stdarch.
//
// `core_arch` depends on libcore, but the contents of this module are
// set up in such a way that directly pulling it here works such that the
// crate uses the this crate as its libcore.
#[path = "../../stdarch/crates/core_arch/src/mod.rs"]
#[allow(
    missing_docs,
    missing_debug_implementations,
    dead_code,
    unused_imports,
    unsafe_op_in_unsafe_fn
)]
// FIXME: This annotation should be moved into rust-lang/stdarch after clashing_extern_declarations is
// merged. It currently cannot because bootstrap fails as the lint hasn't been defined yet.
#[allow(clashing_extern_declarations)]
#[unstable(feature = "stdsimd", issue = "48556")]
mod core_arch;

#[stable(feature = "simd_arch", since = "1.27.0")]
pub use core_arch::arch;
<commit_msg>Add requisite feature gates in the standard library<commit_after>//! # The Rust Core Library
//!
//! The Rust Core Library is the dependency-free[^free] foundation of [The
//! Rust Standard Library](../std/index.html). It is the portable glue
//! between the language and its libraries, defining the intrinsic and
//! primitive building blocks of all Rust code. It links to no
//! upstream libraries, no system libraries, and no libc.
//!
//! [^free]: Strictly speaking, there are some symbols which are needed but
//!          they aren't always necessary.
//!
//! The core library is *minimal*: it isn't even aware of heap allocation,
//! nor does it provide concurrency or I/O. These things require
//! platform integration, and this library is platform-agnostic.
//!
//! # How to use the core library
//!
//! Please note that all of these details are currently not considered stable.
//!
// FIXME: Fill me in with more detail when the interface settles
//! This library is built on the assumption of a few existing symbols:
//!
//! * `memcpy`, `memcmp`, `memset` - These are core memory routines which are
//!   often generated by LLVM. Additionally, this library can make explicit
//!   calls to these functions. Their signatures are the same as found in C.
//!   These functions are often provided by the system libc, but can also be
//!   provided by the [compiler-builtins crate](https://crates.io/crates/compiler_builtins).
//!
//! * `rust_begin_panic` - This function takes four arguments, a
//!   `fmt::Arguments`, a `&'static str`, and two `u32`'s. These four arguments
//!   dictate the panic message, the file at which panic was invoked, and the
//!   line and column inside the file. It is up to consumers of this core
//!   library to define this panic function; it is only required to never
//!   return. This requires a `lang` attribute named `panic_impl`.
//!
//! * `rust_eh_personality` - is used by the failure mechanisms of the
//!    compiler. This is often mapped to GCC's personality function, but crates
//!    which do not trigger a panic can be assured that this function is never
//!    called. The `lang` attribute is called `eh_personality`.

// Since libcore defines many fundamental lang items, all tests live in a
// separate crate, libcoretest, to avoid bizarre issues.
//
// Here we explicitly #[cfg]-out this whole crate when testing. If we don't do
// this, both the generated test artifact and the linked libtest (which
// transitively includes libcore) will both define the same set of lang items,
// and this will cause the E0152 "found duplicate lang item" error. See
// discussion in #50466 for details.
//
// This cfg won't affect doc tests.
#![cfg(not(test))]
#![stable(feature = "core", since = "1.6.0")]
#![doc(
    html_root_url = "https://doc.rust-lang.org/nightly/",
    html_playground_url = "https://play.rust-lang.org/",
    issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",
    test(no_crate_inject, attr(deny(warnings))),
    test(attr(allow(dead_code, deprecated, unused_variables, unused_mut)))
)]
#![no_core]
#![warn(deprecated_in_future)]
#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![allow(explicit_outlives_requirements)]
#![allow(incomplete_features)]
#![feature(allow_internal_unstable)]
#![feature(arbitrary_self_types)]
#![feature(asm)]
#![feature(cfg_target_has_atomic)]
#![feature(const_alloc_layout)]
#![feature(const_discriminant)]
#![feature(const_checked_int_methods)]
#![feature(const_euclidean_int_methods)]
#![feature(const_float_classify)]
#![feature(const_float_bits_conv)]
#![feature(const_overflowing_int_methods)]
#![feature(const_int_unchecked_arith)]
#![feature(const_mut_refs)]
#![feature(const_int_pow)]
#![feature(constctlz)]
#![feature(const_panic)]
#![feature(const_pin)]
#![feature(const_fn)]
#![feature(const_fn_union)]
#![feature(const_assume)]
#![cfg_attr(not(bootstrap), feature(const_fn_impl_trait))]
#![cfg_attr(not(bootstrap), feature(const_fn_floating_point_arithmetic))]
#![cfg_attr(not(bootstrap), feature(const_fn_fn_ptr_basics))]
#![feature(const_generics)]
#![feature(const_option)]
#![feature(const_precise_live_drops)]
#![feature(const_ptr_offset)]
#![feature(const_ptr_offset_from)]
#![feature(const_raw_ptr_comparison)]
#![feature(const_slice_from_raw_parts)]
#![feature(const_slice_ptr_len)]
#![feature(const_size_of_val)]
#![feature(const_align_of_val)]
#![feature(const_type_id)]
#![feature(const_type_name)]
#![feature(const_likely)]
#![feature(const_unreachable_unchecked)]
#![feature(custom_inner_attributes)]
#![feature(decl_macro)]
#![feature(doc_cfg)]
#![feature(doc_spotlight)]
#![feature(duration_consts_2)]
#![feature(duration_saturating_ops)]
#![feature(extern_types)]
#![feature(fundamental)]
#![feature(intrinsics)]
#![feature(lang_items)]
#![feature(link_llvm_intrinsics)]
#![feature(llvm_asm)]
#![feature(negative_impls)]
#![feature(never_type)]
#![feature(nll)]
#![feature(exhaustive_patterns)]
#![feature(no_core)]
#![feature(optin_builtin_traits)]
#![feature(or_patterns)]
#![feature(prelude_import)]
#![feature(repr_simd, platform_intrinsics)]
#![feature(rustc_attrs)]
#![feature(simd_ffi)]
#![feature(min_specialization)]
#![feature(staged_api)]
#![feature(std_internals)]
#![feature(stmt_expr_attributes)]
#![feature(transparent_unions)]
#![feature(unboxed_closures)]
#![feature(unsized_locals)]
#![feature(untagged_unions)]
#![feature(unwind_attributes)]
#![feature(variant_count)]
#![cfg_attr(bootstrap, feature(doc_alias))]
#![feature(tbm_target_feature)]
#![feature(sse4a_target_feature)]
#![feature(arm_target_feature)]
#![feature(powerpc_target_feature)]
#![feature(mips_target_feature)]
#![feature(aarch64_target_feature)]
#![feature(wasm_target_feature)]
#![feature(avx512_target_feature)]
#![feature(cmpxchg16b_target_feature)]
#![feature(rtm_target_feature)]
#![feature(f16c_target_feature)]
#![feature(hexagon_target_feature)]
#![feature(const_fn_transmute)]
#![feature(abi_unadjusted)]
#![feature(adx_target_feature)]
#![feature(external_doc)]
#![feature(associated_type_bounds)]
#![feature(const_caller_location)]
#![feature(slice_ptr_get)]
#![feature(no_niche)] // rust-lang/rust#68303
#![feature(unsafe_block_in_unsafe_fn)]
#![deny(unsafe_op_in_unsafe_fn)]

#[prelude_import]
#[allow(unused)]
use prelude::v1::*;

#[cfg(not(test))] // See #65860
#[macro_use]
mod macros;

#[macro_use]
mod internal_macros;

#[path = "num/shells/int_macros.rs"]
#[macro_use]
mod int_macros;

#[path = "num/shells/i128.rs"]
pub mod i128;
#[path = "num/shells/i16.rs"]
pub mod i16;
#[path = "num/shells/i32.rs"]
pub mod i32;
#[path = "num/shells/i64.rs"]
pub mod i64;
#[path = "num/shells/i8.rs"]
pub mod i8;
#[path = "num/shells/isize.rs"]
pub mod isize;

#[path = "num/shells/u128.rs"]
pub mod u128;
#[path = "num/shells/u16.rs"]
pub mod u16;
#[path = "num/shells/u32.rs"]
pub mod u32;
#[path = "num/shells/u64.rs"]
pub mod u64;
#[path = "num/shells/u8.rs"]
pub mod u8;
#[path = "num/shells/usize.rs"]
pub mod usize;

#[path = "num/f32.rs"]
pub mod f32;
#[path = "num/f64.rs"]
pub mod f64;

#[macro_use]
pub mod num;

/* The libcore prelude, not as all-encompassing as the libstd prelude */

pub mod prelude;

/* Core modules for ownership management */

pub mod hint;
pub mod intrinsics;
pub mod mem;
pub mod ptr;

/* Core language traits */

pub mod borrow;
pub mod clone;
pub mod cmp;
pub mod convert;
pub mod default;
pub mod marker;
pub mod ops;

/* Core types and methods on primitives */

pub mod any;
pub mod array;
pub mod ascii;
pub mod cell;
pub mod char;
pub mod ffi;
pub mod iter;
#[unstable(feature = "once_cell", issue = "74465")]
pub mod lazy;
pub mod option;
pub mod panic;
pub mod panicking;
pub mod pin;
pub mod raw;
pub mod result;
pub mod sync;

pub mod fmt;
pub mod hash;
pub mod slice;
pub mod str;
pub mod time;

pub mod unicode;

/* Async */
pub mod future;
pub mod task;

/* Heap memory allocator trait */
#[allow(missing_docs)]
pub mod alloc;

// note: does not need to be public
mod bool;
mod tuple;
mod unit;

#[stable(feature = "core_primitive", since = "1.43.0")]
pub mod primitive;

// Pull in the `core_arch` crate directly into libcore. The contents of
// `core_arch` are in a different repository: rust-lang/stdarch.
//
// `core_arch` depends on libcore, but the contents of this module are
// set up in such a way that directly pulling it here works such that the
// crate uses the this crate as its libcore.
#[path = "../../stdarch/crates/core_arch/src/mod.rs"]
#[allow(
    missing_docs,
    missing_debug_implementations,
    dead_code,
    unused_imports,
    unsafe_op_in_unsafe_fn
)]
// FIXME: This annotation should be moved into rust-lang/stdarch after clashing_extern_declarations is
// merged. It currently cannot because bootstrap fails as the lint hasn't been defined yet.
#[allow(clashing_extern_declarations)]
#[unstable(feature = "stdsimd", issue = "48556")]
mod core_arch;

#[stable(feature = "simd_arch", since = "1.27.0")]
pub use core_arch::arch;
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

//! A handler and associated types for the filesystems action.
//!
//! The filesystems action lists all mounted filesystems on the client,
//! collecting device name, mount point, filesystem type and its options.
//! Current implementation works only in Linux systems.

use rrg_proto::{Filesystem, KeyValue, AttributedDict};
use crate::session::{self, Session};

use log::error;
use std::fmt::{Display, Formatter};

/// Enum of possible errors, which can occur during collecting filesystems data.
#[derive(Debug)]
enum Error {
    /// Missing mtab-like file error.
    MissingFile(std::io::Error),
    /// Parsing mtab-like file error.
    MountInfoParse(std::io::Error),
}

impl std::error::Error for Error {

    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        use Error::*;

        match *self {
            MissingFile(ref error) => Some(error),
            MountInfoParse(ref error) => Some(error),
        }
    }
}

impl Display for Error {

    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
        use Error::*;

        match *self {
            MissingFile(ref error) => {
                write!(fmt, "missed file error: {}", error)
            },
            MountInfoParse(ref error) => {
                write!(fmt, "failed to obtain mount information: {}", error)
            },
        }
    }
}

impl From<Error> for session::Error {

    fn from(error: Error) -> session::Error {
        session::Error::action(error)
    }
}

/// A response type for the filesystems action.
pub struct Response {
    /// Information about the filesystem.
    mount_info: proc_mounts::MountInfo,
}

/// Handles requests for the filesystems action.
///
/// Initially searches in `/proc/mounts`. If it's missing, falls back to
/// `/etc/mtab`.
pub fn handle<S: Session>(session: &mut S, _: ()) -> session::Result<()> {
    use proc_mounts::MountIter;

    // Check whether `/proc/mounts` exists.
    let mount_iter = match MountIter::new() {
        Ok(mount_iter) => mount_iter,
        Err(_) => {
            // `/proc/mounts` doesn't exist. Try to fall back to `/etc/mtab`.
            MountIter::new_from_file("/etc/mtab").map_err(Error::MissingFile)?
        },
    };

    for mount_info in mount_iter {
        let mount_info = mount_info.map_err(Error::MountInfoParse)?;
        session.reply(Response {
           mount_info,
        })?;
    }

    Ok(())
}

/// Converts filesystem mount option in `String` representation to
/// GRR's `KeyValue` protobuf struct representation.
fn option_to_key_value(option: String) -> KeyValue {
    match &option.split('=').collect::<Vec<&str>>()[..] {
        &[key] => KeyValue::key(String::from(key)),
        &[key, value] => KeyValue::pair(String::from(key), String::from(value)),
        _ => {
            error!("invalid mount option syntax: {}", option);
            // TODO: It's better not to send any key-value in this case.
            KeyValue::empty()
        },
    }
}

impl super::Response for Response {

    const RDF_NAME: Option<&'static str> = Some("Filesystem");

    type Proto = rrg_proto::Filesystem;

    fn into_proto(self) -> Filesystem {
        let options = self.mount_info.options.into_iter()
            .map(option_to_key_value)
            .collect();

        // TODO: Remove lossy conversion of `PathBuf` to `String`
        // when `mount_point` and `device` fields of `Filesystem` message
        // will have `bytes` type instead of `string`.
        Filesystem {
            device: Some(self.mount_info.source.to_string_lossy()
                .into_owned()),
            mount_point: Some(self.mount_info.dest.to_string_lossy()
                .into_owned()),
            r#type: Some(self.mount_info.fstype),
            label: None,
            options: Some(options),
        }
    }
}

#[cfg(test)]
mod tests {

    use super::*;
    use std::path::PathBuf;

    #[test]
    fn test_if_any_filesystem_exists() {
        let mut session = session::test::Fake::new();
        assert!(handle(&mut session, ()).is_ok());
        assert_ne!(session.reply_count(), 0);
    }

    /// Unit-like struct, representing a filesystem for testing with `fuse`.
    struct FuseFilesystem;

    impl fuse::Filesystem for FuseFilesystem {}

    #[test]
    fn test_fuse_filesystem() {
        use users::{get_current_uid, get_current_gid};

        let fs_name = PathBuf::from("fuse-test-fs");

        let tmp_dir = tempfile::tempdir().unwrap();
        let mountpoint = tmp_dir.path();

        let fs_name_option = format!("fsname={}", fs_name.to_str().unwrap());
        let options = [
            "-o", "ro",
            "-o", "nosuid",
            "-o", "nodev",
            "-o", "relatime",
            "-o", "subtype=custom-type",
            "-o", &fs_name_option,
        ];
        let options = options.iter().map(|opt| opt.as_ref())
            .collect::<Vec<&std::ffi::OsStr>>();

        // Spawn a background thread to handle filesystem operations.
        // When `_background_session` is dropped, filesystem will be unmounted.
        let background_session = unsafe {
            fuse::spawn_mount(FuseFilesystem, &mountpoint, &options).unwrap()
        };

        let mut session = session::test::Fake::new();
        assert!(handle(&mut session, ()).is_ok());

        let fuse_mounted_fs = session.replies::<Response>()
            .find(|reply| reply.mount_info.source == fs_name)
            .expect("no reply with a mounted FUSE filesystem");

        let mount_info = &fuse_mounted_fs.mount_info;
        assert_eq!(mount_info.source, fs_name);
        assert_eq!(mount_info.dest, tmp_dir.path());
        assert_eq!(mount_info.fstype, "fuse.custom-type");

        let options = &mount_info.options;
        assert!(options.iter().any(|opt| opt == "ro"));
        assert!(options.iter().any(|opt| opt == "nosuid"));
        assert!(options.iter().any(|opt| opt == "nodev"));
        assert!(options.iter().any(|opt| opt == "relatime"));

        // `user_id` and `group_id` are set by libfuse.
        // http://man7.org/linux/man-pages/man8/mount.fuse.8.html
        let current_uid_option = format!("user_id={}", get_current_uid());
        assert!(options.iter().any(|opt| *opt == current_uid_option));

        let current_gid_option = format!("group_id={}", get_current_gid());
        assert!(options.iter().any(|opt| *opt == current_gid_option));

        drop(background_session);
    }
}
<commit_msg>Remove an unused import.<commit_after>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

//! A handler and associated types for the filesystems action.
//!
//! The filesystems action lists all mounted filesystems on the client,
//! collecting device name, mount point, filesystem type and its options.
//! Current implementation works only in Linux systems.

use rrg_proto::{Filesystem, KeyValue};
use crate::session::{self, Session};

use log::error;
use std::fmt::{Display, Formatter};

/// Enum of possible errors, which can occur during collecting filesystems data.
#[derive(Debug)]
enum Error {
    /// Missing mtab-like file error.
    MissingFile(std::io::Error),
    /// Parsing mtab-like file error.
    MountInfoParse(std::io::Error),
}

impl std::error::Error for Error {

    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        use Error::*;

        match *self {
            MissingFile(ref error) => Some(error),
            MountInfoParse(ref error) => Some(error),
        }
    }
}

impl Display for Error {

    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
        use Error::*;

        match *self {
            MissingFile(ref error) => {
                write!(fmt, "missed file error: {}", error)
            },
            MountInfoParse(ref error) => {
                write!(fmt, "failed to obtain mount information: {}", error)
            },
        }
    }
}

impl From<Error> for session::Error {

    fn from(error: Error) -> session::Error {
        session::Error::action(error)
    }
}

/// A response type for the filesystems action.
pub struct Response {
    /// Information about the filesystem.
    mount_info: proc_mounts::MountInfo,
}

/// Handles requests for the filesystems action.
///
/// Initially searches in `/proc/mounts`. If it's missing, falls back to
/// `/etc/mtab`.
pub fn handle<S: Session>(session: &mut S, _: ()) -> session::Result<()> {
    use proc_mounts::MountIter;

    // Check whether `/proc/mounts` exists.
    let mount_iter = match MountIter::new() {
        Ok(mount_iter) => mount_iter,
        Err(_) => {
            // `/proc/mounts` doesn't exist. Try to fall back to `/etc/mtab`.
            MountIter::new_from_file("/etc/mtab").map_err(Error::MissingFile)?
        },
    };

    for mount_info in mount_iter {
        let mount_info = mount_info.map_err(Error::MountInfoParse)?;
        session.reply(Response {
           mount_info,
        })?;
    }

    Ok(())
}

/// Converts filesystem mount option in `String` representation to
/// GRR's `KeyValue` protobuf struct representation.
fn option_to_key_value(option: String) -> KeyValue {
    match &option.split('=').collect::<Vec<&str>>()[..] {
        &[key] => KeyValue::key(String::from(key)),
        &[key, value] => KeyValue::pair(String::from(key), String::from(value)),
        _ => {
            error!("invalid mount option syntax: {}", option);
            // TODO: It's better not to send any key-value in this case.
            KeyValue::empty()
        },
    }
}

impl super::Response for Response {

    const RDF_NAME: Option<&'static str> = Some("Filesystem");

    type Proto = rrg_proto::Filesystem;

    fn into_proto(self) -> Filesystem {
        let options = self.mount_info.options.into_iter()
            .map(option_to_key_value)
            .collect();

        // TODO: Remove lossy conversion of `PathBuf` to `String`
        // when `mount_point` and `device` fields of `Filesystem` message
        // will have `bytes` type instead of `string`.
        Filesystem {
            device: Some(self.mount_info.source.to_string_lossy()
                .into_owned()),
            mount_point: Some(self.mount_info.dest.to_string_lossy()
                .into_owned()),
            r#type: Some(self.mount_info.fstype),
            label: None,
            options: Some(options),
        }
    }
}

#[cfg(test)]
mod tests {

    use super::*;
    use std::path::PathBuf;

    #[test]
    fn test_if_any_filesystem_exists() {
        let mut session = session::test::Fake::new();
        assert!(handle(&mut session, ()).is_ok());
        assert_ne!(session.reply_count(), 0);
    }

    /// Unit-like struct, representing a filesystem for testing with `fuse`.
    struct FuseFilesystem;

    impl fuse::Filesystem for FuseFilesystem {}

    #[test]
    fn test_fuse_filesystem() {
        use users::{get_current_uid, get_current_gid};

        let fs_name = PathBuf::from("fuse-test-fs");

        let tmp_dir = tempfile::tempdir().unwrap();
        let mountpoint = tmp_dir.path();

        let fs_name_option = format!("fsname={}", fs_name.to_str().unwrap());
        let options = [
            "-o", "ro",
            "-o", "nosuid",
            "-o", "nodev",
            "-o", "relatime",
            "-o", "subtype=custom-type",
            "-o", &fs_name_option,
        ];
        let options = options.iter().map(|opt| opt.as_ref())
            .collect::<Vec<&std::ffi::OsStr>>();

        // Spawn a background thread to handle filesystem operations.
        // When `_background_session` is dropped, filesystem will be unmounted.
        let background_session = unsafe {
            fuse::spawn_mount(FuseFilesystem, &mountpoint, &options).unwrap()
        };

        let mut session = session::test::Fake::new();
        assert!(handle(&mut session, ()).is_ok());

        let fuse_mounted_fs = session.replies::<Response>()
            .find(|reply| reply.mount_info.source == fs_name)
            .expect("no reply with a mounted FUSE filesystem");

        let mount_info = &fuse_mounted_fs.mount_info;
        assert_eq!(mount_info.source, fs_name);
        assert_eq!(mount_info.dest, tmp_dir.path());
        assert_eq!(mount_info.fstype, "fuse.custom-type");

        let options = &mount_info.options;
        assert!(options.iter().any(|opt| opt == "ro"));
        assert!(options.iter().any(|opt| opt == "nosuid"));
        assert!(options.iter().any(|opt| opt == "nodev"));
        assert!(options.iter().any(|opt| opt == "relatime"));

        // `user_id` and `group_id` are set by libfuse.
        // http://man7.org/linux/man-pages/man8/mount.fuse.8.html
        let current_uid_option = format!("user_id={}", get_current_uid());
        assert!(options.iter().any(|opt| *opt == current_uid_option));

        let current_gid_option = format!("group_id={}", get_current_gid());
        assert!(options.iter().any(|opt| *opt == current_gid_option));

        drop(background_session);
    }
}
<|endoftext|>