<commit_before>use super::TimerUpdateWidget;
use text::{Attributes, Text};

use std::time::Duration;

use xcb;
use xcb_util;

pub struct Pager {
    conn: xcb_util::ewmh::Connection,
    screen_idx: i32,
    update_interval: Duration,
    active_attr: Attributes,
    inactive_attr: Attributes,
}

impl Pager {
    pub fn new(active_attr: Attributes, inactive_attr: Attributes) -> Pager {
        let (conn, screen_idx) = xcb::Connection::connect_with_xlib_display().unwrap();
        let ewmh_conn = xcb_util::ewmh::Connection::connect(conn)
            .map_err(|_| ())
            .unwrap();

        Pager {
            conn: ewmh_conn,
            screen_idx: screen_idx,
            update_interval: Duration::from_secs(1),
            active_attr: active_attr,
            inactive_attr: inactive_attr,
        }
    }

    fn get_desktops_info(&self) -> Vec<(bool, String)> {
        let number = xcb_util::ewmh::get_number_of_desktops(&self.conn, self.screen_idx)
            .get_reply()
            .unwrap() as usize;
        let current = xcb_util::ewmh::get_current_desktop(&self.conn, self.screen_idx)
            .get_reply()
            .unwrap() as usize;
        let names_reply = xcb_util::ewmh::get_desktop_names(&self.conn, self.screen_idx)
            .get_reply()
            .unwrap();
        let mut names = names_reply.strings();

        // EWMH states that `number` may not equal `names.len()`, as there may
        // be unnamed desktops, or more desktops than are currently in use.
        if names.len() > number {
            names.truncate(number);
        } else if number > names.len() {
            for i in 0..(number - names.len()) {
                names.push("?");
            }
        }

        names
            .into_iter()
            .enumerate()
            .map(|(i, name)| (i == current, name.to_owned()))
            .collect()
    }
}

impl TimerUpdateWidget for Pager {
    fn update_interval(&self) -> Duration {
        self.update_interval
    }

    fn tick(&self) -> Vec<Text> {
        let desktops = self.get_desktops_info();
        self.get_desktops_info()
            .into_iter()
            .map(|(active, name)| {
                Text {
                    attr: if active {
                        self.active_attr.clone()
                    } else {
                        self.inactive_attr.clone()
                    },
                    text: name,
                    stretch: false,
                }
            })
            .collect()
    }
}
<commit_msg>Remove unused variable.<commit_after>use super::TimerUpdateWidget;
use text::{Attributes, Text};

use std::time::Duration;

use xcb;
use xcb_util;

pub struct Pager {
    conn: xcb_util::ewmh::Connection,
    screen_idx: i32,
    update_interval: Duration,
    active_attr: Attributes,
    inactive_attr: Attributes,
}

impl Pager {
    pub fn new(active_attr: Attributes, inactive_attr: Attributes) -> Pager {
        let (conn, screen_idx) = xcb::Connection::connect_with_xlib_display().unwrap();
        let ewmh_conn = xcb_util::ewmh::Connection::connect(conn)
            .map_err(|_| ())
            .unwrap();

        Pager {
            conn: ewmh_conn,
            screen_idx: screen_idx,
            update_interval: Duration::from_secs(1),
            active_attr: active_attr,
            inactive_attr: inactive_attr,
        }
    }

    fn get_desktops_info(&self) -> Vec<(bool, String)> {
        let number = xcb_util::ewmh::get_number_of_desktops(&self.conn, self.screen_idx)
            .get_reply()
            .unwrap() as usize;
        let current = xcb_util::ewmh::get_current_desktop(&self.conn, self.screen_idx)
            .get_reply()
            .unwrap() as usize;
        let names_reply = xcb_util::ewmh::get_desktop_names(&self.conn, self.screen_idx)
            .get_reply()
            .unwrap();
        let mut names = names_reply.strings();

        // EWMH states that `number` may not equal `names.len()`, as there may
        // be unnamed desktops, or more desktops than are currently in use.
        if names.len() > number {
            names.truncate(number);
        } else if number > names.len() {
            for i in 0..(number - names.len()) {
                names.push("?");
            }
        }

        names
            .into_iter()
            .enumerate()
            .map(|(i, name)| (i == current, name.to_owned()))
            .collect()
    }
}

impl TimerUpdateWidget for Pager {
    fn update_interval(&self) -> Duration {
        self.update_interval
    }

    fn tick(&self) -> Vec<Text> {
        self.get_desktops_info()
            .into_iter()
            .map(|(active, name)| {
                Text {
                    attr: if active {
                        self.active_attr.clone()
                    } else {
                        self.inactive_attr.clone()
                    },
                    text: name,
                    stretch: false,
                }
            })
            .collect()
    }
}
<|endoftext|>


<commit_before>//
// Sysinfo
//
// Copyright (c) 2018 Guillaume Gomez
//

use crate::DiskType;

use crate::sys::disk::{new_disk, Disk};
use crate::sys::processor::{self, Processor, Query};

use std::collections::HashMap;
use std::ffi::OsStr;
use std::mem::{size_of, zeroed};

use winapi::ctypes::c_void;

use winapi::shared::minwindef::{BYTE, DWORD, MAX_PATH, TRUE};
use winapi::um::fileapi::{
    CreateFileW, GetDriveTypeW, GetLogicalDrives, GetVolumeInformationW, OPEN_EXISTING,
};
use winapi::um::handleapi::CloseHandle;
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::ioapiset::DeviceIoControl;
use winapi::um::sysinfoapi::{GetSystemInfo, SYSTEM_INFO};
use winapi::um::winbase::DRIVE_FIXED;
use winapi::um::winioctl::{
    DISK_GEOMETRY, IOCTL_DISK_GET_DRIVE_GEOMETRY, IOCTL_STORAGE_QUERY_PROPERTY,
};
use winapi::um::winnt::{BOOLEAN, FILE_SHARE_READ, FILE_SHARE_WRITE, HANDLE};

pub struct KeyHandler {
    pub unique_id: String,
    pub win_key: Vec<u16>,
}

impl KeyHandler {
    pub fn new(unique_id: String, win_key: Vec<u16>) -> KeyHandler {
        KeyHandler { unique_id, win_key }
    }
}

pub fn init_processors() -> (Vec<Processor>, String, String) {
    unsafe {
        let mut sys_info: SYSTEM_INFO = zeroed();
        GetSystemInfo(&mut sys_info);
        let (vendor_id, brand) = processor::get_vendor_id_and_brand(&sys_info);
        let frequencies = processor::get_frequencies(sys_info.dwNumberOfProcessors as usize);
        let mut ret = Vec::with_capacity(sys_info.dwNumberOfProcessors as usize + 1);
        for nb in 0..sys_info.dwNumberOfProcessors {
            ret.push(Processor::new_with_values(
                &format!("CPU {}", nb + 1),
                vendor_id.clone(),
                brand.clone(),
                frequencies[nb as usize],
            ));
        }
        (ret, vendor_id, brand)
    }
}

pub unsafe fn open_drive(drive_name: &[u16], open_rights: DWORD) -> HANDLE {
    CreateFileW(
        drive_name.as_ptr(),
        open_rights,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        std::ptr::null_mut(),
        OPEN_EXISTING,
        0,
        std::ptr::null_mut(),
    )
}

pub unsafe fn get_drive_size(handle: HANDLE) -> u64 {
    let mut pdg: DISK_GEOMETRY = std::mem::zeroed();
    let mut junk = 0;
    let result = DeviceIoControl(
        handle,
        IOCTL_DISK_GET_DRIVE_GEOMETRY,
        std::ptr::null_mut(),
        0,
        &mut pdg as *mut DISK_GEOMETRY as *mut c_void,
        size_of::<DISK_GEOMETRY>() as DWORD,
        &mut junk,
        std::ptr::null_mut(),
    );
    if result == TRUE {
        *pdg.Cylinders.QuadPart() as u64
            * pdg.TracksPerCylinder as u64
            * pdg.SectorsPerTrack as u64
            * pdg.BytesPerSector as u64
    } else {
        0
    }
}

pub unsafe fn get_disks() -> Vec<Disk> {
    let drives = GetLogicalDrives();
    if drives == 0 {
        return Vec::new();
    }

    #[cfg(feature = "multithread")]
    use rayon::iter::ParallelIterator;

    crate::utils::into_iter(0..size_of::<DWORD>() * 8)
        .filter_map(|x| {
            if (drives >> x) & 1 == 0 {
                return None;
            }
            let mount_point = [b'A' as u16 + x as u16, b':' as u16, b'\\' as u16, 0];
            if GetDriveTypeW(mount_point.as_ptr()) != DRIVE_FIXED {
                return None;
            }
            let mut name = [0u16; MAX_PATH + 1];
            let mut file_system = [0u16; 32];
            if GetVolumeInformationW(
                mount_point.as_ptr(),
                name.as_mut_ptr(),
                name.len() as DWORD,
                std::ptr::null_mut(),
                std::ptr::null_mut(),
                std::ptr::null_mut(),
                file_system.as_mut_ptr(),
                file_system.len() as DWORD,
            ) == 0
            {
                return None;
            }
            let mut pos = 0;
            for x in name.iter() {
                if *x == 0 {
                    break;
                }
                pos += 1;
            }
            let name = String::from_utf16_lossy(&name[..pos]);
            let name = OsStr::new(&name);

            pos = 0;
            for x in file_system.iter() {
                if *x == 0 {
                    break;
                }
                pos += 1;
            }
            let file_system: Vec<u8> = file_system[..pos].iter().map(|x| *x as u8).collect();

            let drive_name = [
                b'\\' as u16,
                b'\\' as u16,
                b'.' as u16,
                b'\\' as u16,
                b'A' as u16 + x as u16,
                b':' as u16,
                0,
            ];
            let handle = open_drive(&drive_name, 0);
            if handle == INVALID_HANDLE_VALUE {
                CloseHandle(handle);
                return new_disk(name, &mount_point, &file_system, DiskType::Unknown(-1), 0);
            }
            let disk_size = get_drive_size(handle);
            /*let mut spq_trim: ffi::STORAGE_PROPERTY_QUERY = std::mem::zeroed();
            spq_trim.PropertyId = ffi::StorageDeviceTrimProperty;
            spq_trim.QueryType = ffi::PropertyStandardQuery;
            let mut dtd: ffi::DEVICE_TRIM_DESCRIPTOR = std::mem::zeroed();*/
            #[allow(non_snake_case)]
            #[repr(C)]
            struct STORAGE_PROPERTY_QUERY {
                PropertyId: i32,
                QueryType: i32,
                AdditionalParameters: [BYTE; 1],
            }
            #[allow(non_snake_case)]
            #[repr(C)]
            struct DEVICE_TRIM_DESCRIPTOR {
                Version: DWORD,
                Size: DWORD,
                TrimEnabled: BOOLEAN,
            }
            let mut spq_trim = STORAGE_PROPERTY_QUERY {
                PropertyId: 8i32,
                QueryType: 0i32,
                AdditionalParameters: [0],
            };
            let mut dtd: DEVICE_TRIM_DESCRIPTOR = std::mem::zeroed();

            let mut dw_size = 0;
            if DeviceIoControl(
                handle,
                IOCTL_STORAGE_QUERY_PROPERTY,
                &mut spq_trim as *mut STORAGE_PROPERTY_QUERY as *mut c_void,
                size_of::<STORAGE_PROPERTY_QUERY>() as DWORD,
                &mut dtd as *mut DEVICE_TRIM_DESCRIPTOR as *mut c_void,
                size_of::<DEVICE_TRIM_DESCRIPTOR>() as DWORD,
                &mut dw_size,
                std::ptr::null_mut(),
            ) == 0
                || dw_size != size_of::<DEVICE_TRIM_DESCRIPTOR>() as DWORD
            {
                CloseHandle(handle);
                return new_disk(
                    name,
                    &mount_point,
                    &file_system,
                    DiskType::Unknown(-1),
                    disk_size,
                );
            }
            let is_ssd = dtd.TrimEnabled != 0;
            CloseHandle(handle);
            new_disk(
                name,
                &mount_point,
                &file_system,
                if is_ssd { DiskType::SSD } else { DiskType::HDD },
                disk_size,
            )
        })
        .collect::<Vec<_>>()
}

#[allow(non_snake_case)]
pub unsafe fn load_symbols() -> HashMap<String, u32> {
    use winapi::um::winreg::{RegQueryValueExA, HKEY_PERFORMANCE_DATA};

    let mut cbCounters = 0;
    let mut dwType = 0;
    let mut ret = HashMap::new();

    let _dwStatus = RegQueryValueExA(
        HKEY_PERFORMANCE_DATA,
        b"Counter 009\0".as_ptr() as *const _,
        std::ptr::null_mut(),
        &mut dwType as *mut i32 as *mut _,
        std::ptr::null_mut(),
        &mut cbCounters as *mut i32 as *mut _,
    );

    let mut lpmszCounters = Vec::with_capacity(cbCounters as usize);
    lpmszCounters.set_len(cbCounters as usize);
    let _dwStatus = RegQueryValueExA(
        HKEY_PERFORMANCE_DATA,
        b"Counter 009\0".as_ptr() as *const _,
        std::ptr::null_mut(),
        &mut dwType as *mut i32 as *mut _,
        lpmszCounters.as_mut_ptr(),
        &mut cbCounters as *mut i32 as *mut _,
    );
    for (pos, s) in lpmszCounters
        .split(|x| *x == 0)
        .filter(|x| !x.is_empty())
        .collect::<Vec<_>>()
        .chunks(2)
        .filter(|&x| x.len() == 2)
        .filter_map(
            |x| match (std::str::from_utf8(x[0]), String::from_utf8(x[1].to_vec())) {
                (Ok(n), Ok(s)) => {
                    if let Ok(n) = u32::from_str_radix(n, 10) {
                        Some((n, s))
                    } else {
                        None
                    }
                }
                _ => None,
            },
        )
    {
        ret.insert(s, pos as u32);
    }
    ret
}

#[allow(clippy::ptr_arg)]
pub fn get_translation(s: &String, map: &HashMap<String, u32>) -> Option<String> {
    use winapi::um::pdh::PdhLookupPerfNameByIndexW;

    if let Some(index) = map.get(s) {
        let mut size: usize = 0;
        unsafe {
            let _res = PdhLookupPerfNameByIndexW(
                std::ptr::null(),
                *index,
                std::ptr::null_mut(),
                &mut size as *mut usize as *mut _,
            );
            if size == 0 {
                return Some(String::new());
            } else {
                let mut v = Vec::with_capacity(size);
                v.set_len(size);
                let _res = PdhLookupPerfNameByIndexW(
                    std::ptr::null(),
                    *index,
                    v.as_mut_ptr() as *mut _,
                    &mut size as *mut usize as *mut _,
                );
                return Some(String::from_utf16(&v[..size - 1]).expect("invalid utf16"));
            }
        }
    }
    None
}

pub fn add_counter(
    s: String,
    query: &mut Query,
    keys: &mut Option<KeyHandler>,
    counter_name: String,
) {
    let mut full = s.encode_utf16().collect::<Vec<_>>();
    full.push(0);
    if query.add_counter(&counter_name, full.clone()) {
        *keys = Some(KeyHandler::new(counter_name, full));
    }
}
<commit_msg>Also show removable drives<commit_after>//
// Sysinfo
//
// Copyright (c) 2018 Guillaume Gomez
//

use crate::DiskType;

use crate::sys::disk::{new_disk, Disk};
use crate::sys::processor::{self, Processor, Query};

use std::collections::HashMap;
use std::ffi::OsStr;
use std::mem::{size_of, zeroed};

use winapi::{ctypes::c_void, um::winbase::DRIVE_REMOVABLE};

use winapi::shared::minwindef::{BYTE, DWORD, MAX_PATH, TRUE};
use winapi::um::fileapi::{
    CreateFileW, GetDriveTypeW, GetLogicalDrives, GetVolumeInformationW, OPEN_EXISTING,
};
use winapi::um::handleapi::CloseHandle;
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::ioapiset::DeviceIoControl;
use winapi::um::sysinfoapi::{GetSystemInfo, SYSTEM_INFO};
use winapi::um::winbase::DRIVE_FIXED;
use winapi::um::winioctl::{
    DISK_GEOMETRY, IOCTL_DISK_GET_DRIVE_GEOMETRY, IOCTL_STORAGE_QUERY_PROPERTY,
};
use winapi::um::winnt::{BOOLEAN, FILE_SHARE_READ, FILE_SHARE_WRITE, HANDLE};

pub struct KeyHandler {
    pub unique_id: String,
    pub win_key: Vec<u16>,
}

impl KeyHandler {
    pub fn new(unique_id: String, win_key: Vec<u16>) -> KeyHandler {
        KeyHandler { unique_id, win_key }
    }
}

pub fn init_processors() -> (Vec<Processor>, String, String) {
    unsafe {
        let mut sys_info: SYSTEM_INFO = zeroed();
        GetSystemInfo(&mut sys_info);
        let (vendor_id, brand) = processor::get_vendor_id_and_brand(&sys_info);
        let frequencies = processor::get_frequencies(sys_info.dwNumberOfProcessors as usize);
        let mut ret = Vec::with_capacity(sys_info.dwNumberOfProcessors as usize + 1);
        for nb in 0..sys_info.dwNumberOfProcessors {
            ret.push(Processor::new_with_values(
                &format!("CPU {}", nb + 1),
                vendor_id.clone(),
                brand.clone(),
                frequencies[nb as usize],
            ));
        }
        (ret, vendor_id, brand)
    }
}

pub unsafe fn open_drive(drive_name: &[u16], open_rights: DWORD) -> HANDLE {
    CreateFileW(
        drive_name.as_ptr(),
        open_rights,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        std::ptr::null_mut(),
        OPEN_EXISTING,
        0,
        std::ptr::null_mut(),
    )
}

pub unsafe fn get_drive_size(handle: HANDLE) -> u64 {
    let mut pdg: DISK_GEOMETRY = std::mem::zeroed();
    let mut junk = 0;
    let result = DeviceIoControl(
        handle,
        IOCTL_DISK_GET_DRIVE_GEOMETRY,
        std::ptr::null_mut(),
        0,
        &mut pdg as *mut DISK_GEOMETRY as *mut c_void,
        size_of::<DISK_GEOMETRY>() as DWORD,
        &mut junk,
        std::ptr::null_mut(),
    );
    if result == TRUE {
        *pdg.Cylinders.QuadPart() as u64
            * pdg.TracksPerCylinder as u64
            * pdg.SectorsPerTrack as u64
            * pdg.BytesPerSector as u64
    } else {
        0
    }
}

pub unsafe fn get_disks() -> Vec<Disk> {
    let drives = GetLogicalDrives();
    if drives == 0 {
        return Vec::new();
    }

    #[cfg(feature = "multithread")]
    use rayon::iter::ParallelIterator;

    crate::utils::into_iter(0..size_of::<DWORD>() * 8)
        .filter_map(|x| {
            if (drives >> x) & 1 == 0 {
                return None;
            }
            let mount_point = [b'A' as u16 + x as u16, b':' as u16, b'\\' as u16, 0];

            let drive_type = GetDriveTypeW(mount_point.as_ptr());

            if drive_type != DRIVE_FIXED && drive_type != DRIVE_REMOVABLE {
                return None;
            }
            let mut name = [0u16; MAX_PATH + 1];
            let mut file_system = [0u16; 32];
            if GetVolumeInformationW(
                mount_point.as_ptr(),
                name.as_mut_ptr(),
                name.len() as DWORD,
                std::ptr::null_mut(),
                std::ptr::null_mut(),
                std::ptr::null_mut(),
                file_system.as_mut_ptr(),
                file_system.len() as DWORD,
            ) == 0
            {
                return None;
            }
            let mut pos = 0;
            for x in name.iter() {
                if *x == 0 {
                    break;
                }
                pos += 1;
            }
            let name = String::from_utf16_lossy(&name[..pos]);
            let name = OsStr::new(&name);

            pos = 0;
            for x in file_system.iter() {
                if *x == 0 {
                    break;
                }
                pos += 1;
            }
            let file_system: Vec<u8> = file_system[..pos].iter().map(|x| *x as u8).collect();

            let drive_name = [
                b'\\' as u16,
                b'\\' as u16,
                b'.' as u16,
                b'\\' as u16,
                b'A' as u16 + x as u16,
                b':' as u16,
                0,
            ];
            let handle = open_drive(&drive_name, 0);
            if handle == INVALID_HANDLE_VALUE {
                CloseHandle(handle);
                return new_disk(name, &mount_point, &file_system, DiskType::Unknown(-1), 0);
            }
            let disk_size = get_drive_size(handle);
            /*let mut spq_trim: ffi::STORAGE_PROPERTY_QUERY = std::mem::zeroed();
            spq_trim.PropertyId = ffi::StorageDeviceTrimProperty;
            spq_trim.QueryType = ffi::PropertyStandardQuery;
            let mut dtd: ffi::DEVICE_TRIM_DESCRIPTOR = std::mem::zeroed();*/
            #[allow(non_snake_case)]
            #[repr(C)]
            struct STORAGE_PROPERTY_QUERY {
                PropertyId: i32,
                QueryType: i32,
                AdditionalParameters: [BYTE; 1],
            }
            #[allow(non_snake_case)]
            #[repr(C)]
            struct DEVICE_TRIM_DESCRIPTOR {
                Version: DWORD,
                Size: DWORD,
                TrimEnabled: BOOLEAN,
            }
            let mut spq_trim = STORAGE_PROPERTY_QUERY {
                PropertyId: 8i32,
                QueryType: 0i32,
                AdditionalParameters: [0],
            };
            let mut dtd: DEVICE_TRIM_DESCRIPTOR = std::mem::zeroed();

            let mut dw_size = 0;
            if DeviceIoControl(
                handle,
                IOCTL_STORAGE_QUERY_PROPERTY,
                &mut spq_trim as *mut STORAGE_PROPERTY_QUERY as *mut c_void,
                size_of::<STORAGE_PROPERTY_QUERY>() as DWORD,
                &mut dtd as *mut DEVICE_TRIM_DESCRIPTOR as *mut c_void,
                size_of::<DEVICE_TRIM_DESCRIPTOR>() as DWORD,
                &mut dw_size,
                std::ptr::null_mut(),
            ) == 0
                || dw_size != size_of::<DEVICE_TRIM_DESCRIPTOR>() as DWORD
            {
                CloseHandle(handle);
                return new_disk(
                    name,
                    &mount_point,
                    &file_system,
                    DiskType::Unknown(-1),
                    disk_size,
                );
            }
            let is_ssd = dtd.TrimEnabled != 0;
            CloseHandle(handle);
            new_disk(
                name,
                &mount_point,
                &file_system,
                if is_ssd { DiskType::SSD } else { DiskType::HDD },
                disk_size,
            )
        })
        .collect::<Vec<_>>()
}

#[allow(non_snake_case)]
pub unsafe fn load_symbols() -> HashMap<String, u32> {
    use winapi::um::winreg::{RegQueryValueExA, HKEY_PERFORMANCE_DATA};

    let mut cbCounters = 0;
    let mut dwType = 0;
    let mut ret = HashMap::new();

    let _dwStatus = RegQueryValueExA(
        HKEY_PERFORMANCE_DATA,
        b"Counter 009\0".as_ptr() as *const _,
        std::ptr::null_mut(),
        &mut dwType as *mut i32 as *mut _,
        std::ptr::null_mut(),
        &mut cbCounters as *mut i32 as *mut _,
    );

    let mut lpmszCounters = Vec::with_capacity(cbCounters as usize);
    lpmszCounters.set_len(cbCounters as usize);
    let _dwStatus = RegQueryValueExA(
        HKEY_PERFORMANCE_DATA,
        b"Counter 009\0".as_ptr() as *const _,
        std::ptr::null_mut(),
        &mut dwType as *mut i32 as *mut _,
        lpmszCounters.as_mut_ptr(),
        &mut cbCounters as *mut i32 as *mut _,
    );
    for (pos, s) in lpmszCounters
        .split(|x| *x == 0)
        .filter(|x| !x.is_empty())
        .collect::<Vec<_>>()
        .chunks(2)
        .filter(|&x| x.len() == 2)
        .filter_map(
            |x| match (std::str::from_utf8(x[0]), String::from_utf8(x[1].to_vec())) {
                (Ok(n), Ok(s)) => {
                    if let Ok(n) = u32::from_str_radix(n, 10) {
                        Some((n, s))
                    } else {
                        None
                    }
                }
                _ => None,
            },
        )
    {
        ret.insert(s, pos as u32);
    }
    ret
}

#[allow(clippy::ptr_arg)]
pub fn get_translation(s: &String, map: &HashMap<String, u32>) -> Option<String> {
    use winapi::um::pdh::PdhLookupPerfNameByIndexW;

    if let Some(index) = map.get(s) {
        let mut size: usize = 0;
        unsafe {
            let _res = PdhLookupPerfNameByIndexW(
                std::ptr::null(),
                *index,
                std::ptr::null_mut(),
                &mut size as *mut usize as *mut _,
            );
            if size == 0 {
                return Some(String::new());
            } else {
                let mut v = Vec::with_capacity(size);
                v.set_len(size);
                let _res = PdhLookupPerfNameByIndexW(
                    std::ptr::null(),
                    *index,
                    v.as_mut_ptr() as *mut _,
                    &mut size as *mut usize as *mut _,
                );
                return Some(String::from_utf16(&v[..size - 1]).expect("invalid utf16"));
            }
        }
    }
    None
}

pub fn add_counter(
    s: String,
    query: &mut Query,
    keys: &mut Option<KeyHandler>,
    counter_name: String,
) {
    let mut full = s.encode_utf16().collect::<Vec<_>>();
    full.push(0);
    if query.add_counter(&counter_name, full.clone()) {
        *keys = Some(KeyHandler::new(counter_name, full));
    }
}
<|endoftext|>


<commit_before>use std::collections::HashSet;
use std::fs::{read_dir, read_link, remove_file, metadata};
use std::io::{self, stdout, stderr};
use std::ffi::OsStr;
use std::path::Path;
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH, Duration};
use std::os::unix::fs::MetadataExt;

use argparse::{ArgumentParser, PushConst, StoreTrue, StoreOption};
use scan_dir::ScanDir;
use humantime;

use super::setup;
use super::Wrapper;
use container::util::clean_dir;
use config::volumes::Volume::Persistent;
use config::command::MainCommand::{Supervise, Command, CapsuleCommand};
use file_util::{read_visible_entries, Lock};
use wrapper::build::get_version_hash;

#[derive(Clone, Copy)]
enum Action {
    Temporary,
    Old,
    Unused,
    Everything,
    Transient,
    Volumes,
    UnusedVolumes,
}

pub fn clean_cmd(wrapper: &Wrapper, cmdline: Vec<String>)
    -> Result<i32, String>
{
    let mut global = false;
    let mut dry_run = false;
    let mut actions = vec!();
    let mut duration = None::<humantime::Duration>;
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("
            Performs various cleanup tasks
            ");
        ap.refer(&mut actions)
          .add_option(&["--tmp", "--tmp-folders"],
                PushConst(Action::Temporary),
                "Clean temporary containers (failed builds)")
          .add_option(&["--old", "--old-containers"], PushConst(Action::Old), "
                Clean old versions of containers (those which doesn't have a \
                symlink in .vagga)")
          .add_option(&["--unused"], PushConst(Action::Unused), "
                Clean unused containers, or versions thereof. (This is not \
                `--old` for historical reasons, we will probably merge the \
                commands later on)")
          .add_option(&["--transient"], PushConst(Action::Transient),
                "Clean unneeded transient folders (left from containers with
                 `write-mode` set to transient-something). The pid of process
                 is checked for liveness first.")
          .add_option(&["--everything"], PushConst(Action::Everything),
                "Clean whole `.vagga` folder. Useful when deleting a project.
                 With ``--global`` cleans whole storage-dir and cache-dir")
          .add_option(&["--unused-volumes"], PushConst(Action::UnusedVolumes),
                "Remove `!Persistent` volumes that are not used by any \
                 command or container of the current config")
          .add_option(&["--volumes"], PushConst(Action::Volumes),
                "Remove all `!Persistent` volumes. So they are reinitialized \
                 on the next start of the command")
          .required();
        ap.refer(&mut global)
          .add_option(&["--global"], StoreTrue,
                "Apply cleanup command to all containers. Works only \
                if `storage-dir` is configured in settings");
        ap.refer(&mut dry_run)
          .add_option(&["-n", "--dry-run"], StoreTrue,
                "Dry run. Don't delete everything, just print");
        ap.refer(&mut duration)
          .add_option(&["--at-least"], StoreOption, "
            Only in combination with `--unused`. Treat as unused \
            containers that are unused for specified time, rather than \
            the ones not used by current version of config");
        match ap.parse(cmdline, &mut stdout(), &mut stderr()) {
            Ok(()) => {}
            Err(0) => return Ok(0),
            Err(_) => {
                return Ok(1);
            }
        }
    }
    if global && wrapper.ext_settings.storage_dir.is_none() {
        error!("The --global flag is only meaningful if you configure \
            storage-dir in settings");
        return Ok(2);
    }
    let duration = duration.map(|x| x.into());
    for action in actions.iter() {
        let res = match *action {
            Action::Temporary => clean_temporary(wrapper, global, dry_run),
            Action::Old => clean_old(wrapper, global, dry_run),
            Action::Unused => {
                clean_unused(wrapper, global, duration, dry_run)
            }
            Action::Transient => clean_transient(wrapper, global, dry_run),
            Action::Everything => clean_everything(wrapper, global, dry_run),
            Action::UnusedVolumes => {
                clean_volumes(wrapper, global, dry_run, false)
            }
            Action::Volumes => {
                clean_volumes(wrapper, global, dry_run, true)
            }
        };
        match res {
            Ok(()) => {}
            Err(err) => {
                error!("Error cleaning up: {}", err);
                return Ok(3);
            }
        }
    }
    return Ok(0);
}

fn clean_dir_wrapper(path: &Path,
        remove_dir_itself: bool, dry_run: bool) -> Result<(), String> {
    // TODO(tailhook) chroot to dir for removing
    if dry_run {
        println!("Would remove {:?}", path);
    } else {
        let mut n = path.to_path_buf().into_os_string();
        n.push(".lock");
        let lock_name = Path::new(&n);
        let lock_guard = if lock_name.exists() {
            match Lock::exclusive(&lock_name) {
                Ok(x) => Some(x),
                Err(e) => {
                    error!("Failed to lock {:?}: {}, skipping", lock_name, e);
                    return Ok(());
                }
            }
        } else {
            None
        };
        debug!("Removing {:?}", path);
        clean_dir(path, remove_dir_itself)?;
        if let Some(_lock) = lock_guard {
            remove_file(lock_name)
                .map_err(|e| format!("Error removing lock file {:?}: {}",
                    lock_name, e))?;
        }
    }
    Ok(())
}

fn clean_everything(wrapper: &Wrapper, global: bool, dry_run: bool)
    -> Result<(), String>
{
    if global {
        if let Some(ref cache_dir) = wrapper.ext_settings.cache_dir {
            clean_dir_wrapper(&cache_dir, false, dry_run)?;
        }
        if let Some(ref storage_dir) = wrapper.ext_settings.storage_dir {
            clean_dir_wrapper(&storage_dir, false, dry_run)?;
        }
    } else {
        let base = match setup::get_vagga_base(
            wrapper.project_root, wrapper.ext_settings)?
        {
            Some(base) => base,
            None => {
                warn!("No vagga directory exists");
                return Ok(());
            }
        };
        clean_dir_wrapper(&base, true, dry_run)?;
        let inner = wrapper.project_root.join(".vagga");
        if base != inner {
            clean_dir_wrapper(&inner, true, dry_run)?;
        }

    }
    return Ok(());
}

fn clean_temporary(wrapper: &Wrapper, global: bool, dry_run: bool)
    -> Result<(), String>
{
    if global {
        panic!("Global cleanup is not implemented yet");
    }
    let base = match setup::get_vagga_base(
        wrapper.project_root, wrapper.ext_settings)?
    {
        Some(base) => base,
        None => {
            warn!("No vagga directory exists");
            return Ok(());
        }
    };
    let roots = base.join(".roots");
    for entry in try_msg!(read_dir(&roots),
        "Can't read dir {r:?}: {err}", r=roots)
    {
        let entry = try_msg!(entry, "Can't read dir {r:?}: {err}", r=roots);
        let typ = try_msg!(entry.file_type(),
            "Can't stat {p:?}: {err}", p=entry.path());
        if typ.is_dir() &&
           entry.file_name()[..].to_str().map(|n| n.starts_with(".tmp"))
                                         .unwrap_or(false)
        {
            clean_dir_wrapper(&entry.path(), true, dry_run)?;
        }
    }

    return Ok(());
}

fn clean_old(wrapper: &Wrapper, global: bool, dry_run: bool)
    -> Result<(), String>
{
    if global {
        panic!("Global cleanup is not implemented yet");
    }
    let base = match setup::get_vagga_base(
        wrapper.project_root, wrapper.ext_settings)?
    {
        Some(base) => base,
        None => {
            warn!("No vagga directory exists");
            return Ok(());
        }
    };
    let useful: HashSet<String> = try_msg!(
        read_visible_entries(&wrapper.project_root.join(".vagga")),
            "Can't read vagga directory: {err}")
        .into_iter()
        .filter_map(|path| read_link(&path)
             .map_err(|e| warn!("Can't readlink {:?}: {}", path, e))
             .ok()
             .and_then(|f| {
                 // The container name is next to the last component
                 f.iter().rev().nth(1)
                 .and_then(|x| x.to_str()).map(ToString::to_string)
             }))
        .collect();

    info!("Useful images {:?}", useful);
    clean_dirs_except(&base.join(".roots"), &useful, dry_run)?;

    return Ok(());
}

fn clean_transient(wrapper: &Wrapper, global: bool, dry_run: bool)
    -> Result<(), String>
{
    if global {
        panic!("Global cleanup is not implemented yet");
    }
    let base = match setup::get_vagga_base(
        wrapper.project_root, wrapper.ext_settings)?
    {
        Some(base) => base,
        None => {
            warn!("No vagga directory exists");
            return Ok(());
        }
    };
    let procfs = Path::new("/proc");
    for entry in try_msg!(read_dir(&base.join(".transient")),
                        "Can't read .vagga/.transient dir: {err}")
    {
        let entry = try_msg!(entry, "Error reading .vagga/transient: {err}");
        if let Some(fname) = entry.file_name()[..].to_str() {
            if let Some(idx) = fname.find('.') {
                if u32::from_str(&fname[idx+1..]).is_ok() &&
                    procfs.join(&fname[idx+1..]).exists()
                {
                    continue;
                }
            }
        }
        clean_dir_wrapper(&entry.path(), true, dry_run)?;
    }

    return Ok(());
}

fn clean_dirs_except<P: AsRef<Path>>(roots: P, useful: &HashSet<String>,
    dry_run: bool)
    -> Result<(), String>
{
    let roots = roots.as_ref();
    for entry in try_msg!(read_dir(&roots),
                         "Can't read dir {dir:?}: {err}", dir=roots)
    {
        let entry = try_msg!(entry,
                             "Can't read dir {dir:?}: {err}", dir=roots);
        let path = entry.path();
        let typ = try_msg!(entry.file_type(),
            "Can't stat {p:?}: {err}", p=path);
        if !typ.is_dir() {
            if path.extension() == Some(OsStr::new("lock")) &&
               path.with_extension("").is_dir()
            {
                debug!("Skipping lock file {:?}", path);
            } else {
                match remove_file(&path) {
                    Ok(()) => {}
                    // File is deleted while we were scanning
                    Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
                    Err(ref e) => {
                        return Err(
                            format!("Can't remove file {:?}: {}", path, e));
                    }
                }
            }
        } else if !typ.is_dir() || entry.file_name()[..].to_str()
            .map(|n| !useful.contains(&n.to_string()))
            .unwrap_or(false)
        {
            clean_dir_wrapper(&entry.path(), true, dry_run)?;
        }
    }
    Ok(())
}

fn clean_unused(wrapper: &Wrapper, global: bool, duration: Option<Duration>,
    dry_run: bool)
    -> Result<(), String>
{
    if global {
        panic!("Global cleanup is not implemented yet");
    }

    setup::setup_base_filesystem(
        wrapper.project_root, wrapper.ext_settings)?;

    let mut useful: HashSet<String> = HashSet::new();
    if let Some(duration) = duration {
        let unixtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
        let cut_off = (unixtime - duration).as_secs() as i64;
        ScanDir::dirs().skip_hidden(false).read("/vagga/base/.roots", |iter| {
            for (entry, name) in iter {
                let luse_path = entry.path().join("last_use");
                match metadata(&luse_path) {
                    Ok(ref meta) if meta.mtime() > cut_off => {
                        useful.insert(name);
                    }
                    Ok(_) => {}
                    Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
                    Err(e) => {
                        error!("Error trying to stat {:?}: {}", luse_path, e);
                    }
                }
            }
        }).map_err(|e| {
            error!("Error reading `.vagga/.roots`: {}", e);
        }).ok();
    } else {
        for (name, _) in &wrapper.config.containers {
            if let Some(version) = get_version_hash(name, wrapper)? {
                useful.insert(format!("{}.{}", name, &version[..8]));
            }
        }
    }
    info!("Useful images {:?}", useful);
    clean_dirs_except("/vagga/base/.roots", &useful, dry_run)?;

    return Ok(());
}

fn clean_volumes(wrapper: &Wrapper, global: bool, dry_run: bool, all: bool)
    -> Result<(), String>
{
    if global {
        panic!("Global cleanup is not implemented yet");
    }
    let base = match setup::get_vagga_base(
        wrapper.project_root, wrapper.ext_settings)?
    {
        Some(base) => base,
        None => {
            warn!("No vagga directory exists");
            return Ok(());
        }
    };
    let volume_dir = base.join(".volumes");
    let mut useful = HashSet::new();
    if !all {
        for (_, container) in &wrapper.config.containers {
            for (_, vol) in &container.volumes {
                if let Persistent(ref p) = *vol {
                    useful.insert(p.name.clone());
                }
            }
        }
        for (_, command) in &wrapper.config.commands {
            match *command {
                Command(ref cmd) => {
                    for (_, vol) in &cmd.volumes {
                        if let Persistent(ref p) = *vol {
                            useful.insert(p.name.clone());
                        }
                    }
                }
                CapsuleCommand(_) => {
                    // novolumes
                },
                Supervise(ref cmd) => {
                    for (_, child) in &cmd.children {
                        for (_, vol) in child.get_volumes() {
                            if let Persistent(ref p) = *vol {
                                useful.insert(p.name.clone());
                            }
                        }
                    }
                }
            }
        }
    }
    info!("Useful volumes {:?}", useful);
    clean_dirs_except(volume_dir, &useful, dry_run)?;

    return Ok(());
}
<commit_msg>Implements `vagga _clean --unused --global --at-least=..`<commit_after>use std::collections::HashSet;
use std::fs::{read_dir, read_link, remove_file, metadata};
use std::io::{self, stdout, stderr};
use std::ffi::OsStr;
use std::path::Path;
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH, Duration};
use std::os::unix::fs::MetadataExt;

use argparse::{ArgumentParser, PushConst, StoreTrue, StoreOption};
use scan_dir::ScanDir;
use humantime;

use super::setup;
use super::Wrapper;
use container::util::clean_dir;
use config::volumes::Volume::Persistent;
use config::command::MainCommand::{Supervise, Command, CapsuleCommand};
use file_util::{read_visible_entries, Lock};
use wrapper::build::get_version_hash;

#[derive(Clone, Copy)]
enum Action {
    Temporary,
    Old,
    Unused,
    Everything,
    Transient,
    Volumes,
    UnusedVolumes,
}

pub fn clean_cmd(wrapper: &Wrapper, cmdline: Vec<String>)
    -> Result<i32, String>
{
    let mut global = false;
    let mut dry_run = false;
    let mut actions = vec!();
    let mut duration = None::<humantime::Duration>;
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("
            Performs various cleanup tasks
            ");
        ap.refer(&mut actions)
          .add_option(&["--tmp", "--tmp-folders"],
                PushConst(Action::Temporary),
                "Clean temporary containers (failed builds)")
          .add_option(&["--old", "--old-containers"], PushConst(Action::Old), "
                Clean old versions of containers (those which doesn't have a \
                symlink in .vagga)")
          .add_option(&["--unused"], PushConst(Action::Unused), "
                Clean unused containers, or versions thereof. (This is not \
                `--old` for historical reasons, we will probably merge the \
                commands later on)")
          .add_option(&["--transient"], PushConst(Action::Transient),
                "Clean unneeded transient folders (left from containers with
                 `write-mode` set to transient-something). The pid of process
                 is checked for liveness first.")
          .add_option(&["--everything"], PushConst(Action::Everything),
                "Clean whole `.vagga` folder. Useful when deleting a project.
                 With ``--global`` cleans whole storage-dir and cache-dir")
          .add_option(&["--unused-volumes"], PushConst(Action::UnusedVolumes),
                "Remove `!Persistent` volumes that are not used by any \
                 command or container of the current config")
          .add_option(&["--volumes"], PushConst(Action::Volumes),
                "Remove all `!Persistent` volumes. So they are reinitialized \
                 on the next start of the command")
          .required();
        ap.refer(&mut global)
          .add_option(&["--global"], StoreTrue,
                "Apply cleanup command to all containers. Works only \
                if `storage-dir` is configured in settings");
        ap.refer(&mut dry_run)
          .add_option(&["-n", "--dry-run"], StoreTrue,
                "Dry run. Don't delete everything, just print");
        ap.refer(&mut duration)
          .add_option(&["--at-least"], StoreOption, "
            Only in combination with `--unused`. Treat as unused \
            containers that are unused for specified time, rather than \
            the ones not used by current version of config");
        match ap.parse(cmdline, &mut stdout(), &mut stderr()) {
            Ok(()) => {}
            Err(0) => return Ok(0),
            Err(_) => {
                return Ok(1);
            }
        }
    }
    if global && wrapper.ext_settings.storage_dir.is_none() {
        error!("The --global flag is only meaningful if you configure \
            storage-dir in settings");
        return Ok(2);
    }
    let duration = duration.map(|x| x.into());
    for action in actions.iter() {
        let res = match *action {
            Action::Temporary => clean_temporary(wrapper, global, dry_run),
            Action::Old => clean_old(wrapper, global, dry_run),
            Action::Unused => {
                if global {
                    if let Some(duration) = duration {
                        global_clean_unused(wrapper, duration, dry_run)
                    } else {
                        panic!("no global cleanup without --at-least");
                    }
                } else {
                    clean_unused(wrapper, duration, dry_run)
                }
            }
            Action::Transient => clean_transient(wrapper, global, dry_run),
            Action::Everything => clean_everything(wrapper, global, dry_run),
            Action::UnusedVolumes => {
                clean_volumes(wrapper, global, dry_run, false)
            }
            Action::Volumes => {
                clean_volumes(wrapper, global, dry_run, true)
            }
        };
        match res {
            Ok(()) => {}
            Err(err) => {
                error!("Error cleaning up: {}", err);
                return Ok(3);
            }
        }
    }
    return Ok(0);
}

fn clean_dir_wrapper(path: &Path,
        remove_dir_itself: bool, dry_run: bool) -> Result<(), String> {
    // TODO(tailhook) chroot to dir for removing
    if dry_run {
        println!("Would remove {:?}", path);
    } else {
        let mut n = path.to_path_buf().into_os_string();
        n.push(".lock");
        let lock_name = Path::new(&n);
        let lock_guard = if lock_name.exists() {
            match Lock::exclusive(&lock_name) {
                Ok(x) => Some(x),
                Err(e) => {
                    error!("Failed to lock {:?}: {}, skipping", lock_name, e);
                    return Ok(());
                }
            }
        } else {
            None
        };
        debug!("Removing {:?}", path);
        clean_dir(path, remove_dir_itself)?;
        if let Some(_lock) = lock_guard {
            remove_file(lock_name)
                .map_err(|e| format!("Error removing lock file {:?}: {}",
                    lock_name, e))?;
        }
    }
    Ok(())
}

fn clean_everything(wrapper: &Wrapper, global: bool, dry_run: bool)
    -> Result<(), String>
{
    if global {
        if let Some(ref cache_dir) = wrapper.ext_settings.cache_dir {
            clean_dir_wrapper(&cache_dir, false, dry_run)?;
        }
        if let Some(ref storage_dir) = wrapper.ext_settings.storage_dir {
            clean_dir_wrapper(&storage_dir, false, dry_run)?;
        }
    } else {
        let base = match setup::get_vagga_base(
            wrapper.project_root, wrapper.ext_settings)?
        {
            Some(base) => base,
            None => {
                warn!("No vagga directory exists");
                return Ok(());
            }
        };
        clean_dir_wrapper(&base, true, dry_run)?;
        let inner = wrapper.project_root.join(".vagga");
        if base != inner {
            clean_dir_wrapper(&inner, true, dry_run)?;
        }

    }
    return Ok(());
}

fn clean_temporary(wrapper: &Wrapper, global: bool, dry_run: bool)
    -> Result<(), String>
{
    if global {
        panic!("Global cleanup is not implemented yet");
    }
    let base = match setup::get_vagga_base(
        wrapper.project_root, wrapper.ext_settings)?
    {
        Some(base) => base,
        None => {
            warn!("No vagga directory exists");
            return Ok(());
        }
    };
    let roots = base.join(".roots");
    for entry in try_msg!(read_dir(&roots),
        "Can't read dir {r:?}: {err}", r=roots)
    {
        let entry = try_msg!(entry, "Can't read dir {r:?}: {err}", r=roots);
        let typ = try_msg!(entry.file_type(),
            "Can't stat {p:?}: {err}", p=entry.path());
        if typ.is_dir() &&
           entry.file_name()[..].to_str().map(|n| n.starts_with(".tmp"))
                                         .unwrap_or(false)
        {
            clean_dir_wrapper(&entry.path(), true, dry_run)?;
        }
    }

    return Ok(());
}

fn clean_old(wrapper: &Wrapper, global: bool, dry_run: bool)
    -> Result<(), String>
{
    if global {
        panic!("Global cleanup is not implemented yet");
    }
    let base = match setup::get_vagga_base(
        wrapper.project_root, wrapper.ext_settings)?
    {
        Some(base) => base,
        None => {
            warn!("No vagga directory exists");
            return Ok(());
        }
    };
    let useful: HashSet<String> = try_msg!(
        read_visible_entries(&wrapper.project_root.join(".vagga")),
            "Can't read vagga directory: {err}")
        .into_iter()
        .filter_map(|path| read_link(&path)
             .map_err(|e| warn!("Can't readlink {:?}: {}", path, e))
             .ok()
             .and_then(|f| {
                 // The container name is next to the last component
                 f.iter().rev().nth(1)
                 .and_then(|x| x.to_str()).map(ToString::to_string)
             }))
        .collect();

    info!("Useful images {:?}", useful);
    clean_dirs_except(&base.join(".roots"), &useful, dry_run)?;

    return Ok(());
}

fn clean_transient(wrapper: &Wrapper, global: bool, dry_run: bool)
    -> Result<(), String>
{
    if global {
        panic!("Global cleanup is not implemented yet");
    }
    let base = match setup::get_vagga_base(
        wrapper.project_root, wrapper.ext_settings)?
    {
        Some(base) => base,
        None => {
            warn!("No vagga directory exists");
            return Ok(());
        }
    };
    let procfs = Path::new("/proc");
    for entry in try_msg!(read_dir(&base.join(".transient")),
                        "Can't read .vagga/.transient dir: {err}")
    {
        let entry = try_msg!(entry, "Error reading .vagga/transient: {err}");
        if let Some(fname) = entry.file_name()[..].to_str() {
            if let Some(idx) = fname.find('.') {
                if u32::from_str(&fname[idx+1..]).is_ok() &&
                    procfs.join(&fname[idx+1..]).exists()
                {
                    continue;
                }
            }
        }
        clean_dir_wrapper(&entry.path(), true, dry_run)?;
    }

    return Ok(());
}

fn clean_dirs_except<P: AsRef<Path>>(roots: P, useful: &HashSet<String>,
    dry_run: bool)
    -> Result<(), String>
{
    let roots = roots.as_ref();
    for entry in try_msg!(read_dir(&roots),
                         "Can't read dir {dir:?}: {err}", dir=roots)
    {
        let entry = try_msg!(entry,
                             "Can't read dir {dir:?}: {err}", dir=roots);
        let path = entry.path();
        let typ = try_msg!(entry.file_type(),
            "Can't stat {p:?}: {err}", p=path);
        if !typ.is_dir() {
            if path.extension() == Some(OsStr::new("lock")) &&
               path.with_extension("").is_dir()
            {
                debug!("Skipping lock file {:?}", path);
            } else {
                match remove_file(&path) {
                    Ok(()) => {}
                    // File is deleted while we were scanning
                    Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
                    Err(ref e) => {
                        return Err(
                            format!("Can't remove file {:?}: {}", path, e));
                    }
                }
            }
        } else if !typ.is_dir() || entry.file_name()[..].to_str()
            .map(|n| !useful.contains(&n.to_string()))
            .unwrap_or(false)
        {
            clean_dir_wrapper(&entry.path(), true, dry_run)?;
        }
    }
    Ok(())
}

fn global_clean_unused(wrapper: &Wrapper, duration: Duration,
    dry_run: bool)
    -> Result<(), String>
{
    let unixtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
    let cut_off = (unixtime - duration).as_secs() as i64;
    let is_cache_dir = |p: &Path| {
        wrapper.ext_settings.cache_dir.as_ref()
        .map(|x| x == p)
        .unwrap_or(false)
    };
    let storage_dir = wrapper.ext_settings.storage_dir.as_ref().unwrap();
    let mut proj_num = 0;
    let mut to_remove = 0;
    let mut to_keep = 0;
    ScanDir::dirs().read(&storage_dir, |iter| {
        for (entry, name) in iter {
            let path = entry.path();
            if is_cache_dir(&path) {
                continue;
            }
            proj_num += 1;
            info!("Scanning project {}", name);

            let mut useful: HashSet<String> = HashSet::new();
            let roots = path.join(".roots");
            ScanDir::dirs().skip_hidden(false).read(&roots, |iter| {
                for (entry, name) in iter {
                    let luse_path = entry.path().join("last_use");
                    match metadata(&luse_path) {
                        Ok(ref meta) if meta.mtime() > cut_off => {
                            useful.insert(name);
                            to_keep += 1;
                        }
                        Ok(_) => {
                            to_remove += 1;
                        }
                        Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
                        Err(e) => {
                            error!("Error trying to stat {:?}: {}",
                                luse_path, e);
                        }
                    }
                }
            }).map_err(|e| {
                error!("Error reading {:?}: {}", roots, e);
            }).ok();

            info!("Useful images {:?}", useful);
            clean_dirs_except(&roots, &useful, dry_run)
            .map_err(|e| error!("Error cleaning {:?}: {}", roots, e))
            .ok(); // TODO(tailhook) propagate the errorneous exit code?
        }
    }).map_err(|e| {
        format!("Error reading storage dir {:?}: {}", storage_dir, e)
    })?;
    info!("Scanned {} projects, keeping {} images, removed {}",
        proj_num, to_keep, to_remove);
    Ok(())
}

fn clean_unused(wrapper: &Wrapper, duration: Option<Duration>,
    dry_run: bool)
    -> Result<(), String>
{

    setup::setup_base_filesystem(
        wrapper.project_root, wrapper.ext_settings)?;

    let mut useful: HashSet<String> = HashSet::new();
    if let Some(duration) = duration {
        let unixtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
        let cut_off = (unixtime - duration).as_secs() as i64;
        ScanDir::dirs().skip_hidden(false).read("/vagga/base/.roots", |iter| {
            for (entry, name) in iter {
                let luse_path = entry.path().join("last_use");
                match metadata(&luse_path) {
                    Ok(ref meta) if meta.mtime() > cut_off => {
                        useful.insert(name);
                    }
                    Ok(_) => {}
                    Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
                    Err(e) => {
                        error!("Error trying to stat {:?}: {}", luse_path, e);
                    }
                }
            }
        }).map_err(|e| {
            error!("Error reading `.vagga/.roots`: {}", e);
        }).ok();
    } else {
        for (name, _) in &wrapper.config.containers {
            if let Some(version) = get_version_hash(name, wrapper)? {
                useful.insert(format!("{}.{}", name, &version[..8]));
            }
        }
    }
    info!("Useful images {:?}", useful);
    clean_dirs_except("/vagga/base/.roots", &useful, dry_run)?;

    return Ok(());
}

fn clean_volumes(wrapper: &Wrapper, global: bool, dry_run: bool, all: bool)
    -> Result<(), String>
{
    if global {
        panic!("Global cleanup is not implemented yet");
    }
    let base = match setup::get_vagga_base(
        wrapper.project_root, wrapper.ext_settings)?
    {
        Some(base) => base,
        None => {
            warn!("No vagga directory exists");
            return Ok(());
        }
    };
    let volume_dir = base.join(".volumes");
    let mut useful = HashSet::new();
    if !all {
        for (_, container) in &wrapper.config.containers {
            for (_, vol) in &container.volumes {
                if let Persistent(ref p) = *vol {
                    useful.insert(p.name.clone());
                }
            }
        }
        for (_, command) in &wrapper.config.commands {
            match *command {
                Command(ref cmd) => {
                    for (_, vol) in &cmd.volumes {
                        if let Persistent(ref p) = *vol {
                            useful.insert(p.name.clone());
                        }
                    }
                }
                CapsuleCommand(_) => {
                    // novolumes
                },
                Supervise(ref cmd) => {
                    for (_, child) in &cmd.children {
                        for (_, vol) in child.get_volumes() {
                            if let Persistent(ref p) = *vol {
                                useful.insert(p.name.clone());
                            }
                        }
                    }
                }
            }
        }
    }
    info!("Useful volumes {:?}", useful);
    clean_dirs_except(volume_dir, &useful, dry_run)?;

    return Ok(());
}
<|endoftext|>


<commit_before>extern crate gimli;
extern crate memmap;
extern crate object;

use object::Object;
use std::cell::Cell;
use std::env;
use std::fs;

fn main() {
    for file_path in env::args().skip(1) {
        println!("{}", file_path);
        println!("");

        let file = fs::File::open(&file_path).expect("Should open file");
        let file = memmap::Mmap::open(&file, memmap::Protection::Read)
            .expect("Should create a mmap for file");
        let file = object::File::parse(unsafe { file.as_slice() });

        if file.is_little_endian() {
            dump_file::<gimli::LittleEndian>(file);
        } else {
            dump_file::<gimli::BigEndian>(file);
        }
    }
}

fn dump_file<Endian>(file: object::File)
    where Endian: gimli::Endianity
{
    let debug_abbrev = file.get_section(".debug_abbrev")
        .expect("Does not have .debug_abbrev section");
    let debug_abbrev = gimli::DebugAbbrev::<Endian>::new(debug_abbrev);
    let debug_str = file.get_section(".debug_str")
        .expect("Does not have .debug_str section");
    let debug_str = gimli::DebugStr::<Endian>::new(debug_str);

    dump_info(&file, debug_abbrev, debug_str);
    dump_types(&file, debug_abbrev, debug_str);
    dump_line(&file, debug_abbrev);
    dump_aranges::<Endian>(&file);
}

fn dump_info<Endian>(file: &object::File,
                     debug_abbrev: gimli::DebugAbbrev<Endian>,
                     debug_str: gimli::DebugStr<Endian>)
    where Endian: gimli::Endianity
{
    if let Some(debug_info) = file.get_section(".debug_info") {
        println!(".debug_info");
        println!("");

        let debug_info = gimli::DebugInfo::<Endian>::new(&debug_info);

        for unit in debug_info.units() {
            let unit = unit.expect("Should parse the unit OK");

            let abbrevs = unit.abbreviations(debug_abbrev)
                .expect("Error parsing abbreviations");

            dump_entries(unit.entries(&abbrevs), debug_str);
        }
    }
}

fn dump_types<Endian>(file: &object::File,
                      debug_abbrev: gimli::DebugAbbrev<Endian>,
                      debug_str: gimli::DebugStr<Endian>)
    where Endian: gimli::Endianity
{
    if let Some(debug_types) = file.get_section(".debug_types") {
        println!(".debug_types");
        println!("");

        let debug_types = gimli::DebugTypes::<Endian>::new(&debug_types);

        for unit in debug_types.units() {
            let unit = unit.expect("Should parse the unit OK");

            let abbrevs = unit.abbreviations(debug_abbrev)
                .expect("Error parsing abbreviations");

            dump_entries(unit.entries(&abbrevs), debug_str);
        }
    }
}

fn dump_entries<Endian>(mut entries: gimli::EntriesCursor<Endian>,
                        debug_str: gimli::DebugStr<Endian>)
    where Endian: gimli::Endianity
{
    let depth = Cell::new(0);
    while let Some((delta_depth, entry)) = entries.next_dfs().expect("Should parse next dfs") {
        depth.set(depth.get() + delta_depth);
        let indent = || {
            for _ in 0..(depth.get() as usize) {
                print!("        ");
            }
        };

        indent();
        println!("<{}> <{}>", entry.offset(), entry.tag());

        let mut attrs = entry.attrs();
        while let Some(attr) = attrs.next().expect("Should parse attribute OK") {
            indent();
            let mut value = attr.value();
            if let gimli::AttributeValue::DebugStrRef(o) = value {
                let s = debug_str.get_str(o).expect("Should have valid str offset");
                value = gimli::AttributeValue::String(s)
            }
            println!("    {} = {:?}", attr.name(), value);
        }
    }
}

fn dump_line<Endian>(file: &object::File, debug_abbrev: gimli::DebugAbbrev<Endian>)
    where Endian: gimli::Endianity
{
    let debug_line = file.get_section(".debug_line");
    let debug_info = file.get_section(".debug_info");

    if let (Some(debug_line), Some(debug_info)) = (debug_line, debug_info) {
        println!(".debug_line");
        println!("");

        let debug_line = gimli::DebugLine::<Endian>::new(&debug_line);
        let debug_info = gimli::DebugInfo::<Endian>::new(&debug_info);

        for unit in debug_info.units() {
            let unit = unit.expect("Should parse unit header OK");

            let abbrevs = unit.abbreviations(debug_abbrev)
                .expect("Error parsing abbreviations");

            let mut cursor = unit.entries(&abbrevs);
            cursor.next_dfs().expect("Should parse next dfs");

            let root = cursor.current().expect("Should have a root DIE");
            let value = root.attr_value(gimli::DW_AT_stmt_list);
            let offset = gimli::DebugLineOffset(match value {
                Some(gimli::AttributeValue::Data(data)) if data.len() == 4 => {
                    Endian::read_u32(data.into()) as u64
                }
                Some(gimli::AttributeValue::Data(data)) if data.len() == 8 => {
                    Endian::read_u64(data.into())
                }
                Some(gimli::AttributeValue::SecOffset(offset)) => offset,
                _ => continue,
            });

            let header =
                gimli::LineNumberProgramHeader::new(debug_line, offset, unit.address_size());
            if let Ok(header) = header {
                println!("");
                println!("Offset:                             0x{:x}", offset.0);
                println!("Length:                             {}",
                         header.unit_length());
                println!("DWARF version:                      {}", header.version());
                println!("Prologue length:                    {}",
                         header.header_length());
                println!("Minimum instruction length:         {}",
                         header.minimum_instruction_length());
                println!("Maximum operations per instruction: {}",
                         header.maximum_operations_per_instruction());
                println!("Default is_stmt:                    {}",
                         header.default_is_stmt());
                println!("Line base:                          {}", header.line_base());
                println!("Line range:                         {}",
                         header.line_range());
                println!("Opcode base:                        {}",
                         header.opcode_base());

                println!("");
                println!("Opcodes:");
                for (i, length) in header.standard_opcode_lengths().iter().enumerate() {
                    println!("  Opcode {} as {} args", i + 1, length);
                }

                println!("");
                println!("The Directory Table:");
                for (i, dir) in header.include_directories().iter().enumerate() {
                    println!("  {} {}", i + 1, dir.to_string_lossy());
                }

                println!("");
                println!("The File Name Table");
                println!("  Entry\tDir\tTime\tSize\tName");
                for (i, file) in header.file_names().iter().enumerate() {
                    println!("  {}\t{}\t{}\t{}\t{}",
                             i + 1,
                             file.directory_index(),
                             file.last_modification(),
                             file.length(),
                             file.path_name().to_string_lossy());
                }

                println!("");
                println!("Line Number Statements:");
                let mut opcodes = header.opcodes();
                while let Some(opcode) = opcodes.next_opcode(&header)
                    .expect("Should parse opcode OK") {
                    println!("  {}", opcode);
                }
            }
        }
    }
}

fn dump_aranges<Endian>(file: &object::File)
    where Endian: gimli::Endianity
{
    if let Some(debug_aranges) = file.get_section(".debug_aranges") {
        println!(".debug_aranges");
        let debug_aranges = gimli::DebugAranges::<Endian>::new(debug_aranges);

        let mut aranges = debug_aranges.items();
        while let Some(arange) = aranges.next_entry().expect("Should parse arange OK") {
            println!("arange starts at {}, length of {}, cu_die_offset = {:?}",
                     arange.start(),
                     arange.len(),
                     arange.debug_info_offset());
        }
    }
}
<commit_msg>dwarfdump: fix dump_line()<commit_after>extern crate gimli;
extern crate memmap;
extern crate object;

use object::Object;
use std::cell::Cell;
use std::env;
use std::fs;

fn main() {
    for file_path in env::args().skip(1) {
        println!("{}", file_path);
        println!("");

        let file = fs::File::open(&file_path).expect("Should open file");
        let file = memmap::Mmap::open(&file, memmap::Protection::Read)
            .expect("Should create a mmap for file");
        let file = object::File::parse(unsafe { file.as_slice() });

        if file.is_little_endian() {
            dump_file::<gimli::LittleEndian>(file);
        } else {
            dump_file::<gimli::BigEndian>(file);
        }
    }
}

fn dump_file<Endian>(file: object::File)
    where Endian: gimli::Endianity
{
    let debug_abbrev = file.get_section(".debug_abbrev")
        .expect("Does not have .debug_abbrev section");
    let debug_abbrev = gimli::DebugAbbrev::<Endian>::new(debug_abbrev);
    let debug_str = file.get_section(".debug_str")
        .expect("Does not have .debug_str section");
    let debug_str = gimli::DebugStr::<Endian>::new(debug_str);

    dump_info(&file, debug_abbrev, debug_str);
    dump_types(&file, debug_abbrev, debug_str);
    dump_line(&file, debug_abbrev);
    dump_aranges::<Endian>(&file);
}

fn dump_info<Endian>(file: &object::File,
                     debug_abbrev: gimli::DebugAbbrev<Endian>,
                     debug_str: gimli::DebugStr<Endian>)
    where Endian: gimli::Endianity
{
    if let Some(debug_info) = file.get_section(".debug_info") {
        println!(".debug_info");
        println!("");

        let debug_info = gimli::DebugInfo::<Endian>::new(&debug_info);

        for unit in debug_info.units() {
            let unit = unit.expect("Should parse the unit OK");

            let abbrevs = unit.abbreviations(debug_abbrev)
                .expect("Error parsing abbreviations");

            dump_entries(unit.entries(&abbrevs), debug_str);
        }
    }
}

fn dump_types<Endian>(file: &object::File,
                      debug_abbrev: gimli::DebugAbbrev<Endian>,
                      debug_str: gimli::DebugStr<Endian>)
    where Endian: gimli::Endianity
{
    if let Some(debug_types) = file.get_section(".debug_types") {
        println!(".debug_types");
        println!("");

        let debug_types = gimli::DebugTypes::<Endian>::new(&debug_types);

        for unit in debug_types.units() {
            let unit = unit.expect("Should parse the unit OK");

            let abbrevs = unit.abbreviations(debug_abbrev)
                .expect("Error parsing abbreviations");

            dump_entries(unit.entries(&abbrevs), debug_str);
        }
    }
}

fn dump_entries<Endian>(mut entries: gimli::EntriesCursor<Endian>,
                        debug_str: gimli::DebugStr<Endian>)
    where Endian: gimli::Endianity
{
    let depth = Cell::new(0);
    while let Some((delta_depth, entry)) = entries.next_dfs().expect("Should parse next dfs") {
        depth.set(depth.get() + delta_depth);
        let indent = || {
            for _ in 0..(depth.get() as usize) {
                print!("        ");
            }
        };

        indent();
        println!("<{}> <{}>", entry.offset(), entry.tag());

        let mut attrs = entry.attrs();
        while let Some(attr) = attrs.next().expect("Should parse attribute OK") {
            indent();
            let mut value = attr.value();
            if let gimli::AttributeValue::DebugStrRef(o) = value {
                let s = debug_str.get_str(o).expect("Should have valid str offset");
                value = gimli::AttributeValue::String(s)
            }
            println!("    {} = {:?}", attr.name(), value);
        }
    }
}

fn dump_line<Endian>(file: &object::File, debug_abbrev: gimli::DebugAbbrev<Endian>)
    where Endian: gimli::Endianity
{
    let debug_line = file.get_section(".debug_line");
    let debug_info = file.get_section(".debug_info");

    if let (Some(debug_line), Some(debug_info)) = (debug_line, debug_info) {
        println!(".debug_line");
        println!("");

        let debug_line = gimli::DebugLine::<Endian>::new(&debug_line);
        let debug_info = gimli::DebugInfo::<Endian>::new(&debug_info);

        for unit in debug_info.units() {
            let unit = unit.expect("Should parse unit header OK");

            let abbrevs = unit.abbreviations(debug_abbrev)
                .expect("Error parsing abbreviations");

            let mut cursor = unit.entries(&abbrevs);
            cursor.next_dfs().expect("Should parse next dfs");

            let root = cursor.current().expect("Should have a root DIE");
            let offset = match root.attr_value(gimli::DW_AT_stmt_list) {
                Some(gimli::AttributeValue::DebugLineRef(offset)) => offset,
                _ => continue,
            };

            let header =
                gimli::LineNumberProgramHeader::new(debug_line, offset, unit.address_size());
            if let Ok(header) = header {
                println!("");
                println!("Offset:                             0x{:x}", offset.0);
                println!("Length:                             {}",
                         header.unit_length());
                println!("DWARF version:                      {}", header.version());
                println!("Prologue length:                    {}",
                         header.header_length());
                println!("Minimum instruction length:         {}",
                         header.minimum_instruction_length());
                println!("Maximum operations per instruction: {}",
                         header.maximum_operations_per_instruction());
                println!("Default is_stmt:                    {}",
                         header.default_is_stmt());
                println!("Line base:                          {}", header.line_base());
                println!("Line range:                         {}",
                         header.line_range());
                println!("Opcode base:                        {}",
                         header.opcode_base());

                println!("");
                println!("Opcodes:");
                for (i, length) in header.standard_opcode_lengths().iter().enumerate() {
                    println!("  Opcode {} as {} args", i + 1, length);
                }

                println!("");
                println!("The Directory Table:");
                for (i, dir) in header.include_directories().iter().enumerate() {
                    println!("  {} {}", i + 1, dir.to_string_lossy());
                }

                println!("");
                println!("The File Name Table");
                println!("  Entry\tDir\tTime\tSize\tName");
                for (i, file) in header.file_names().iter().enumerate() {
                    println!("  {}\t{}\t{}\t{}\t{}",
                             i + 1,
                             file.directory_index(),
                             file.last_modification(),
                             file.length(),
                             file.path_name().to_string_lossy());
                }

                println!("");
                println!("Line Number Statements:");
                let mut opcodes = header.opcodes();
                while let Some(opcode) = opcodes.next_opcode(&header)
                    .expect("Should parse opcode OK") {
                    println!("  {}", opcode);
                }
            }
        }
    }
}

fn dump_aranges<Endian>(file: &object::File)
    where Endian: gimli::Endianity
{
    if let Some(debug_aranges) = file.get_section(".debug_aranges") {
        println!(".debug_aranges");
        let debug_aranges = gimli::DebugAranges::<Endian>::new(debug_aranges);

        let mut aranges = debug_aranges.items();
        while let Some(arange) = aranges.next_entry().expect("Should parse arange OK") {
            println!("arange starts at {}, length of {}, cu_die_offset = {:?}",
                     arange.start(),
                     arange.len(),
                     arange.debug_info_offset());
        }
    }
}
<|endoftext|>