use std::path::Path;
use std::{ffi::CString, os::fd::AsRawFd};
use std::fs::File;
use std::io;
use crate::os::process::cmd::spec::CmdSpec;
use crate::os::process::cmd::std_cmd;
use crate::os::unistd::{gid_by_groupname, uid_by_username};
use super::{Uid, Gid};
pub fn chown(path: impl AsRef<Path>, uid: Option<Uid>, gid: Option<Gid>) -> io::Result<()> {
use std::os::unix::ffi::OsStrExt;
let path = path.as_ref();
let bytes = path.as_os_str().as_bytes();
let c_path = CString::new(bytes).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contains interior NUL"))?;
let uid_raw: libc::uid_t = uid.map(|u| u.0).unwrap_or(!0 as libc::uid_t);
let gid_raw: libc::gid_t = gid.map(|g| g.0).unwrap_or(!0 as libc::gid_t);
let ret = unsafe { libc::chown(c_path.as_ptr(), uid_raw, gid_raw) };
if ret == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub fn fchown(file: &File, uid: Option<Uid>, gid: Option<Gid>) -> io::Result<()> {
let fd = file.as_raw_fd();
let uid_raw: libc::uid_t = uid.map(|u| u.0).unwrap_or(!0 as libc::uid_t);
let gid_raw: libc::gid_t = gid.map(|g| g.0).unwrap_or(!0 as libc::gid_t);
let ret = unsafe { libc::fchown(fd, uid_raw, gid_raw) };
if ret == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub fn lchown(path: impl AsRef<Path>, uid: Option<Uid>, gid: Option<Gid>) -> io::Result<()> {
use std::os::unix::ffi::OsStrExt;
let path = path.as_ref();
let bytes = path.as_os_str().as_bytes();
let c_path = CString::new(bytes).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contains interior NUL"))?;
let uid_raw: libc::uid_t = uid.map(|u| u.0).unwrap_or(!0 as libc::uid_t);
let gid_raw: libc::gid_t = gid.map(|g| g.0).unwrap_or(!0 as libc::gid_t);
let ret = unsafe { libc::lchown(c_path.as_ptr(), uid_raw, gid_raw) };
if ret == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub fn sudo_chown(path: impl AsRef<std::path::Path>, uid: Option<Uid>, gid: Option<Gid>) -> io::Result<()> {
match chown(&path, uid, gid) {
Ok(()) => return Ok(()),
Err(err) => {
if err.kind() != io::ErrorKind::PermissionDenied || super::is_root() {
return Err(err);
}
}
}
let owner_arg = match (uid, gid) {
(Some(u), Some(g)) => format!("{}:{}", u.as_raw(), g.as_raw()),
(Some(u), None) => format!("{}", u.as_raw()),
(None, Some(g)) => format!(":{}", g.as_raw()),
(None, None) => {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "both uid and gid are None"));
}
};
let path_str = path.as_ref().to_string_lossy().into_owned();
let spec = CmdSpec::new("chown").arg(owner_arg).arg(path_str).sudo();
match std_cmd::status_ok(spec) {
Ok(true) => Ok(()),
Ok(false) => Err(io::Error::new(io::ErrorKind::Other, "sudo chown failed")),
Err(e) => Err(e),
}
}
pub fn chown_by_names(
path: impl AsRef<Path>,
username: Option<&str>,
groupname: Option<&str>,
) -> io::Result<()> {
let uid_opt = match username {
Some(name) => match uid_by_username(name) {
Some(u) => Some(u),
None => return Err(io::Error::new(io::ErrorKind::NotFound, format!("user not found: {}", name))),
},
None => None,
};
let gid_opt = match groupname {
Some(name) => match gid_by_groupname(name) {
Some(g) => Some(g),
None => return Err(io::Error::new(io::ErrorKind::NotFound, format!("group not found: {}", name))),
},
None => None,
};
chown(path, uid_opt, gid_opt)
}
pub fn fchown_by_names(
file: &File,
username: Option<&str>,
groupname: Option<&str>,
) -> io::Result<()> {
let uid_opt = match username {
Some(name) => match uid_by_username(name) {
Some(u) => Some(u),
None => return Err(io::Error::new(io::ErrorKind::NotFound, format!("user not found: {}", name))),
},
None => None,
};
let gid_opt = match groupname {
Some(name) => match gid_by_groupname(name) {
Some(g) => Some(g),
None => return Err(io::Error::new(io::ErrorKind::NotFound, format!("group not found: {}", name))),
},
None => None,
};
fchown(file, uid_opt, gid_opt)
}
pub fn lchown_by_names(
path: impl AsRef<Path>,
username: Option<&str>,
groupname: Option<&str>,
) -> io::Result<()> {
let uid_opt = match username {
Some(name) => match uid_by_username(name) {
Some(u) => Some(u),
None => return Err(io::Error::new(io::ErrorKind::NotFound, format!("user not found: {}", name))),
},
None => None,
};
let gid_opt = match groupname {
Some(name) => match gid_by_groupname(name) {
Some(g) => Some(g),
None => return Err(io::Error::new(io::ErrorKind::NotFound, format!("group not found: {}", name))),
},
None => None,
};
lchown(path, uid_opt, gid_opt)
}