#![allow(clippy::unnecessary_cast)]
use std::ffi::CString;
use std::fs;
use std::fs::File;
use std::io;
use std::io::Write;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
use std::ptr;
use std::result;
use std::thread;
use std::time::Duration;
use libc::{c_char, group, passwd};
use crate::pwm::Polarity;
pub type Result<T> = result::Result<T, io::Error>;
pub fn user_to_uid(name: &str) -> Option<u32> {
if let Ok(name_cstr) = CString::new(name) {
let buf = &mut [0 as c_char; 4096];
let mut res: *mut passwd = ptr::null_mut();
let mut pwd = passwd {
pw_name: ptr::null_mut(),
pw_passwd: ptr::null_mut(),
pw_uid: 0,
pw_gid: 0,
pw_gecos: ptr::null_mut(),
pw_dir: ptr::null_mut(),
pw_shell: ptr::null_mut(),
};
unsafe {
if libc::getpwnam_r(
name_cstr.as_ptr(),
&mut pwd,
buf.as_mut_ptr(),
buf.len(),
&mut res,
) == 0
&& res as usize > 0
{
return Some((*res).pw_uid);
}
}
}
None
}
pub fn group_to_gid(name: &str) -> Option<u32> {
if let Ok(name_cstr) = CString::new(name) {
let buf = &mut [0 as c_char; 4096];
let mut res: *mut group = ptr::null_mut();
let mut grp = group {
gr_name: ptr::null_mut(),
gr_passwd: ptr::null_mut(),
gr_gid: 0,
gr_mem: ptr::null_mut(),
};
unsafe {
if libc::getgrnam_r(
name_cstr.as_ptr(),
&mut grp,
buf.as_mut_ptr(),
buf.len(),
&mut res,
) == 0
&& res as usize > 0
{
return Some((*res).gr_gid);
}
}
}
None
}
fn check_permissions(path: &str, gid: u32) -> bool {
if let Ok(metadata) = fs::metadata(path) {
if metadata.permissions().mode() != 0o040_770 && metadata.permissions().mode() != 0o100_770
{
return false;
}
if metadata.gid() == gid {
return true;
}
}
false
}
pub fn export(channel: u8) -> Result<()> {
if !Path::new(&format!("/sys/class/pwm/pwmchip0/pwm{}", channel)).exists() {
File::create("/sys/class/pwm/pwmchip0/export")?.write_fmt(format_args!("{}", channel))?;
}
if let Some(root_uid) = user_to_uid("root") {
unsafe {
if libc::getuid() == root_uid || libc::geteuid() == root_uid {
return Ok(());
}
}
}
let gid_gpio = if let Some(gid) = group_to_gid("gpio") {
gid
} else {
0
};
let paths = &[
format!("/sys/class/pwm/pwmchip0/pwm{}", channel),
format!("/sys/class/pwm/pwmchip0/pwm{}/period", channel),
format!("/sys/class/pwm/pwmchip0/pwm{}/duty_cycle", channel),
format!("/sys/class/pwm/pwmchip0/pwm{}/polarity", channel),
format!("/sys/class/pwm/pwmchip0/pwm{}/enable", channel),
];
let mut counter = 0;
'counter: while counter < 25 {
for path in paths {
if !check_permissions(path, gid_gpio) {
thread::sleep(Duration::from_millis(40));
counter += 1;
continue 'counter;
}
}
break;
}
Ok(())
}
pub fn unexport(channel: u8) -> Result<()> {
if Path::new(&format!("/sys/class/pwm/pwmchip0/pwm{}", channel)).exists() {
File::create("/sys/class/pwm/pwmchip0/unexport")?.write_fmt(format_args!("{}", channel))?;
}
Ok(())
}
pub fn period(channel: u8) -> Result<u64> {
let period = fs::read_to_string(format!("/sys/class/pwm/pwmchip0/pwm{}/period", channel))?;
if let Ok(period) = period.trim().parse() {
Ok(period)
} else {
Ok(0)
}
}
pub fn set_period(channel: u8, period: u64) -> Result<()> {
File::create(format!("/sys/class/pwm/pwmchip0/pwm{}/period", channel))?
.write_fmt(format_args!("{}", period))?;
Ok(())
}
pub fn pulse_width(channel: u8) -> Result<u64> {
let duty_cycle =
fs::read_to_string(format!("/sys/class/pwm/pwmchip0/pwm{}/duty_cycle", channel))?;
if let Ok(duty_cycle) = duty_cycle.trim().parse() {
Ok(duty_cycle)
} else {
Ok(0)
}
}
pub fn set_pulse_width(channel: u8, pulse_width: u64) -> Result<()> {
File::create(format!("/sys/class/pwm/pwmchip0/pwm{}/duty_cycle", channel))?
.write_fmt(format_args!("{}", pulse_width))?;
Ok(())
}
pub fn polarity(channel: u8) -> Result<Polarity> {
let polarity = fs::read_to_string(format!("/sys/class/pwm/pwmchip0/pwm{}/polarity", channel))?;
match polarity.trim() {
"normal" => Ok(Polarity::Normal),
_ => Ok(Polarity::Inverse),
}
}
pub fn set_polarity(channel: u8, polarity: Polarity) -> Result<()> {
let b_polarity: &[u8] = match polarity {
Polarity::Normal => b"normal",
Polarity::Inverse => b"inversed",
};
File::create(format!("/sys/class/pwm/pwmchip0/pwm{}/polarity", channel))?
.write_all(b_polarity)?;
Ok(())
}
pub fn enabled(channel: u8) -> Result<bool> {
let enabled = fs::read_to_string(format!("/sys/class/pwm/pwmchip0/pwm{}/enable", channel))?;
match enabled.trim() {
"0" => Ok(false),
_ => Ok(true),
}
}
pub fn set_enabled(channel: u8, enabled: bool) -> Result<()> {
File::create(format!("/sys/class/pwm/pwmchip0/pwm{}/enable", channel))?
.write_fmt(format_args!("{}", enabled as u8))
.map_err(|e| {
if e.kind() == io::ErrorKind::InvalidInput {
io::Error::new(
io::ErrorKind::InvalidInput,
"Make sure you have set either a period or frequency before enabling PWM",
)
} else {
e
}
})?;
Ok(())
}