use crate::{
extensions::FileExt,
util::{DEV_PATH, SYSFS_PATH},
};
use bitflags::bitflags;
use displaydoc::Display;
use nix::sys::stat;
use std::{
convert::TryInto,
fs,
fs::DirEntry,
io,
io::prelude::*,
ops::Range,
os::{linux::fs::MetadataExt, unix::fs::FileTypeExt},
path::{Path, PathBuf},
time::Duration,
};
use thiserror::Error;
#[derive(Debug, Display, Error)]
pub enum Error {
Io(#[from] io::Error),
InvalidArg(&'static str),
Invalid,
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
fn parse_dev(path: &Path) -> Result<(u64, u64)> {
let i = fs::read_to_string(path.join("dev"))?;
let mut i = i.trim().split(':');
let major = i.next().ok_or_else(|| Error::Invalid)?;
let minor = i.next().ok_or_else(|| Error::Invalid)?;
let major = major.parse::<u64>().map_err(|_| Error::Invalid)?;
let minor = minor.parse::<u64>().map_err(|_| Error::Invalid)?;
Ok((major, minor))
}
fn find_from_major_minor(major: u64, minor: u64) -> Result<Option<PathBuf>> {
for dev in fs::read_dir(DEV_PATH)? {
let dev: DirEntry = dev?;
if !dev.file_type()?.is_block_device() {
continue;
}
let meta = dev.metadata()?;
let dev_id = meta.st_rdev();
if (major, minor) == (stat::major(dev_id), stat::minor(dev_id)) {
return Ok(Some(dev.path()));
}
}
Ok(None)
}
fn dev_size(path: &Path) -> Result<u64> {
fs::read_to_string(path.join("size"))?
.trim()
.parse::<u64>()
.map(|b| b * 512)
.map_err(|_| Error::Invalid)
}
bitflags! {
pub struct BlockCap: u32 {
const REMOVABLE = 1;
const MEDIA_CHANGE_NOTIFY = 4;
const CD = 8;
const UP = 16;
const SUPPRESS_PARTITION_INFO = 32;
const EXT_DEVT = 64;
const NATIVE_CAPACITY = 128;
const BLOCK_EVENTS_ON_EXCL_WRITE = 256;
const NO_PART_SCAN = 512;
const HIDDEN = 1024;
}
}
#[derive(Debug, Clone)]
pub struct Block {
name: String,
path: PathBuf,
major: u64,
minor: u64,
}
impl Block {
pub fn get_connected() -> Result<Vec<Self>> {
let sysfs = Path::new(SYSFS_PATH);
let mut devices = Vec::new();
let mut paths = vec![sysfs.join("subsystem/block/devices")];
if !paths[0].exists() {
paths = vec![sysfs.join("class/block"), sysfs.join("block")];
}
for path in paths {
if !path.exists() {
continue;
}
for dev in path.read_dir()? {
let dev: DirEntry = dev?;
if dev.path().join("partition").exists() {
continue;
}
devices.push(Self::new(dev.path().canonicalize()?)?);
}
}
devices.sort_unstable_by(|a, b| a.name.cmp(&b.name));
devices.dedup_by(|a, b| a.name == b.name);
Ok(devices)
}
pub fn from_dev(path: &Path) -> Result<Self> {
let sysfs = Path::new(SYSFS_PATH);
let meta = path.metadata()?;
if !meta.file_type().is_block_device() {
return Err(Error::InvalidArg("path"));
}
let dev_id = meta.st_rdev();
let (major, minor) = (stat::major(dev_id), stat::minor(dev_id));
let path = sysfs.join("dev/block").join(format!("{}:{}", major, minor));
let path = path.canonicalize()?;
if path.join("partition").exists() {
return Err(Error::InvalidArg("path"));
}
Self::new(path)
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn dev_path(&self) -> Result<Option<PathBuf>> {
find_from_major_minor(self.major, self.minor)
}
pub fn name(&self) -> &str {
&self.name
}
pub fn partitions(&self) -> Result<Vec<Partition>> {
let mut devices = Vec::new();
for dir in fs::read_dir(&self.path)? {
let dir: DirEntry = dir?;
let path = dir.path();
if !dir.file_type()?.is_dir() || !path.join("partition").exists() {
continue;
}
devices.push(Partition::new(path)?);
}
Ok(devices)
}
pub fn open(&self) -> Result<Option<fs::File>> {
let path = find_from_major_minor(self.major, self.minor)?;
match path {
Some(path) => Ok(Some(
fs::OpenOptions::new().read(true).write(true).open(path)?,
)),
None => Ok(None),
}
}
pub fn major(&self) -> u64 {
self.major
}
pub fn minor(&self) -> u64 {
self.minor
}
pub fn size(&self) -> Result<u64> {
dev_size(&self.path)
}
pub fn capability(&self) -> Result<BlockCap> {
Ok(unsafe {
BlockCap::from_bits_unchecked(
std::fs::read_to_string(self.path.join("capability"))?
.trim()
.parse()
.map_err(|_| Error::Invalid)?,
)
})
}
pub fn power(&self) -> Power {
Power::new(&self.path)
}
pub fn add_partition(&mut self, num: u64, start_end: Range<i64>) -> Result<()> {
let f = self.open()?.ok_or_else(|| Error::Invalid)?;
f.add_partition(
num.try_into()
.map_err(|_| Error::InvalidArg("Partition number was too large"))?,
start_end.start,
start_end.end,
)
.map_err(|_| Error::Invalid)?;
Ok(())
}
pub fn remove_partition(&mut self, num: u64) -> Result<()> {
let f = self.open()?.ok_or_else(|| Error::Invalid)?;
f.remove_partition(
num.try_into()
.map_err(|_| Error::InvalidArg("Partition number was too large"))?,
)
.map_err(|_| Error::Invalid)?;
Ok(())
}
pub fn remove_existing_partitions(&mut self) -> Result<()> {
let f = self.open()?.ok_or_else(|| Error::Invalid)?;
let parts = self.partitions()?;
for part in parts {
f.remove_partition(
part.number()?
.try_into()
.map_err(|_| Error::InvalidArg("Partition number was too large"))?,
)
.map_err(|_| Error::Invalid)?;
}
Ok(())
}
pub fn model(&self) -> Result<Option<String>> {
let path = self.path.parent().unwrap().parent().unwrap().join("model");
if !path.exists() {
return Ok(None);
}
Ok(Some(fs::read_to_string(path).map(|s| s.trim().to_owned())?))
}
pub fn logical_block_size(&self) -> Result<u64> {
Ok(
fs::read_to_string(self.path.join("queue/logical_block_size"))?
.trim()
.parse::<u64>()
.map_err(|_| Error::Invalid)?,
)
}
}
impl Block {
fn new(path: PathBuf) -> Result<Self> {
let (major, minor) = parse_dev(&path)?;
Ok(Self {
name: path
.file_name()
.and_then(|s| s.to_str())
.map(Into::into)
.unwrap(),
path,
major,
minor,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Partition {
name: String,
path: PathBuf,
major: u64,
minor: u64,
}
impl Partition {
pub fn open(&self) -> Result<Option<fs::File>> {
let path = find_from_major_minor(self.major, self.minor)?;
match path {
Some(path) => Ok(Some(
fs::OpenOptions::new().read(true).write(true).open(path)?,
)),
None => Ok(None),
}
}
pub fn size(&self) -> Result<u64> {
dev_size(&self.path)
}
pub fn start(&self) -> Result<u64> {
fs::read_to_string(self.path.join("start"))?
.trim()
.parse::<u64>()
.map(|i| i * 512)
.map_err(|_| Error::Invalid)
}
pub fn name(&self) -> &str {
&self.name
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn dev_path(&self) -> Result<Option<PathBuf>> {
find_from_major_minor(self.major, self.minor)
}
pub fn number(&self) -> Result<u64> {
fs::read_to_string(self.path.join("partition"))?
.trim()
.parse::<u64>()
.map_err(|_| Error::Invalid)
}
}
impl Partition {
fn new(path: PathBuf) -> Result<Self> {
let (major, minor) = parse_dev(&path)?;
Ok(Self {
name: path
.file_name()
.and_then(|s| s.to_str())
.map(Into::into)
.unwrap(),
path,
major,
minor,
})
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Status {
Suspended,
Suspending,
Resuming,
Active,
FatalError,
Unsupported,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Control {
Auto,
On,
}
#[derive(Debug, Copy, Clone)]
pub struct Power<'a> {
path: &'a Path,
}
impl Power<'_> {
pub fn control(&self) -> Result<Control> {
match fs::read_to_string(self.path.join("power/control"))?.trim() {
"auto" => Ok(Control::Auto),
"on" => Ok(Control::On),
_ => Err(Error::Invalid),
}
}
pub fn set_control(&mut self, control: Control) -> Result<()> {
if self.control()? == control {
return Ok(());
}
let mut file = fs::OpenOptions::new()
.write(true)
.open(self.path.join("power/control"))?;
match control {
Control::Auto => file.write_all(b"auto")?,
Control::On => file.write_all(b"on")?,
};
Ok(())
}
pub fn status(&self) -> Result<Status> {
match fs::read_to_string(self.path.join("power/runtime_status"))?.trim() {
"suspended" => Ok(Status::Suspended),
"suspending" => Ok(Status::Suspending),
"resuming" => Ok(Status::Resuming),
"active" => Ok(Status::Active),
"error" => Ok(Status::FatalError),
"unsupported" => Ok(Status::Unsupported),
_ => Err(Error::Invalid),
}
}
pub fn autosuspend_delay(&self) -> Result<Option<Duration>> {
let f = fs::read_to_string(self.path.join("power/autosuspend_delay_ms"));
if let Err(Some(5)) = f.as_ref().map_err(|e| e.raw_os_error()) {
return Ok(None);
}
let s = f?;
Ok(Some(Duration::from_millis(
s.trim().parse().map_err(|_| Error::Invalid)?,
)))
}
pub fn set_autosuspend_delay(&mut self, delay: Duration) -> Result<Option<()>> {
let mut f = fs::OpenOptions::new()
.write(true)
.open(self.path.join("power/autosuspend_delay_ms"))?;
let f = write!(f, "{}", delay.as_millis());
if let nix::Error::Sys(nix::errno::Errno::EIO) = nix::Error::last() {
return Ok(None);
}
f?;
Ok(Some(()))
}
pub fn async_(&self) -> Result<bool> {
match fs::read_to_string(self.path.join("power/async"))?.trim() {
"enabled" => Ok(true),
"disabled" => Ok(false),
_ => Err(Error::Invalid),
}
}
pub fn wakeup(&self) -> Option<Wakeup> {
let path = self.path.join("power/wakeup");
if !path.exists() {
return None;
}
Some(Wakeup::new(self.path))
}
}
impl<'a> Power<'a> {
fn new(path: &'a Path) -> Self {
Self { path }
}
}
#[derive(Debug)]
pub struct Wakeup<'a> {
path: &'a Path,
}
impl Wakeup<'_> {
pub fn enabled(&self) -> Result<bool> {
match fs::read_to_string(self.path.join("power/wakeup"))?.trim() {
"enabled" => Ok(true),
"disabled" => Ok(false),
_ => Err(Error::Invalid),
}
}
pub fn set_enabled(&mut self, enabled: bool) -> Result<()> {
if enabled == self.enabled()? {
return Ok(());
}
let mut f = fs::OpenOptions::new()
.write(true)
.open(self.path.join("power/wakeup"))?;
if enabled {
write!(&mut f, "enabled")?;
} else {
write!(&mut f, "disabled")?;
};
Ok(())
}
}
impl<'a> Wakeup<'a> {
fn new(path: &'a Path) -> Self {
Self { path }
}
}
macro_rules! wakeup_helper {
($(#[$outer:meta])* $name:ident, $file:literal) => {
impl Wakeup<'_> {
pub fn $name(&self) -> Result<u32> {
Ok(
fs::read_to_string(self.path.join(concat!("power/", $file)))?
.trim()
.parse::<u32>()
.map_err(|_| Error::Invalid)?,
)
}
}
};
}
macro_rules! wakeup_helper_d {
( $(#[$outer:meta])*
$name:ident, $file:literal) => {
impl Wakeup<'_> {
pub fn $name(&self) -> Result<Duration> {
Ok(Duration::from_millis(
fs::read_to_string(self.path.join(concat!("power/", $file)))?
.trim()
.parse::<u64>()
.map_err(|_| Error::Invalid)?,
))
}
}
};
}
wakeup_helper!(
count,
"wakeup_count"
);
wakeup_helper!(
count_active,
"wakeup_active_count"
);
wakeup_helper!(
count_abort,
"wakeup_abort_count"
);
wakeup_helper!(
count_expired,
"wakeup_expire_count"
);
wakeup_helper!(
active,
"wakeup_active"
);
wakeup_helper_d!(
total_time,
"wakeup_total_time_ms"
);
wakeup_helper_d!(
max_time,
"wakeup_max_time_ms"
);
wakeup_helper_d!(
last_time,
"wakeup_last_time_ms"
);
wakeup_helper_d!(
prevent_sleep_time,
"wakeup_prevent_sleep_time_ms"
);