use std::{io::Seek, io::SeekFrom, path::PathBuf};
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use std::fs::{File, OpenOptions};
use std::io::Read;
use std::io::Write;
use std::ops::Deref;
#[derive(Debug)]
pub struct Files {
pub passwd: Option<PathBuf>,
pub shadow: Option<PathBuf>,
pub group: Option<PathBuf>,
}
impl Default for Files {
fn default() -> Self {
Self {
passwd: Some(PathBuf::from("/etc/passwd")),
shadow: Some(PathBuf::from("/etc/shadow")),
group: Some(PathBuf::from("/etc/group")),
}
}
}
impl Files {
#[must_use]
pub const fn is_virtual(&self) -> bool {
!(self.group.is_some() & self.passwd.is_some() & self.shadow.is_some())
}
pub fn lock_and_get_passwd(&self) -> Result<LockedFileGuard, crate::UserLibError> {
let path = self.passwd.as_ref();
match path {
Some(p) => LockedFileGuard::new(p),
None => Err(crate::UserLibError::FilesRequired),
}
}
pub fn lock_and_get_shadow(&self) -> Result<LockedFileGuard, crate::UserLibError> {
let path = self.shadow.as_ref();
match path {
Some(p) => LockedFileGuard::new(p),
None => Err(crate::UserLibError::FilesRequired),
}
}
pub fn lock_and_get_group(&self) -> Result<LockedFileGuard, crate::UserLibError> {
let path = self.group.as_ref();
match path {
Some(p) => LockedFileGuard::new(p),
None => Err(crate::UserLibError::FilesRequired),
}
}
pub fn lock_all_get(
&self,
) -> Result<(LockedFileGuard, LockedFileGuard, LockedFileGuard), crate::UserLibError> {
let pwd = self.lock_and_get_passwd()?;
let shd = self.lock_and_get_shadow()?;
let grp = self.lock_and_get_group()?;
Ok((pwd, shd, grp))
}
}
#[derive(Debug)]
pub struct LockedFileGuard {
lockfile: PathBuf,
path: PathBuf,
pub(crate) file: File,
}
#[derive(Debug)]
struct TempLockFile {
tlf: PathBuf,
}
impl Drop for TempLockFile {
fn drop(&mut self) {
info!("removing temporary lockfile {}", self.tlf.to_str().unwrap());
std::fs::remove_file(&self.tlf).unwrap();
}
}
impl Deref for TempLockFile {
type Target = PathBuf;
fn deref(&self) -> &PathBuf {
&self.tlf
}
}
impl LockedFileGuard {
pub fn new(path: &PathBuf) -> Result<Self, crate::UserLibError> {
let locked = Self::try_to_lock_file(path);
match locked {
Ok((lockfile, file)) => Ok(Self {
lockfile,
path: path.to_owned(),
file,
}),
Err(e) => Err(e),
}
}
pub fn replace_contents(&mut self, new_content: String) -> Result<(), crate::UserLibError> {
self.file = match File::create(&self.path) {
Ok(file) => file,
Err(e) => return Err(("Failed to truncate file.".to_owned(), e).into()),
};
match self.file.write_all(&new_content.into_bytes()) {
Ok(_) => (),
Err(e) => return Err(("Could not write (all) users. ".to_owned(), e).into()),
};
let _ = self.file.write(b"\n");
Ok(())
}
pub fn append(&mut self, appendee: String) -> Result<(), crate::UserLibError> {
self.file.seek(SeekFrom::End(-1)).map_or_else(
|e| Err(format!("Failed to append to file {}", e)),
|_| Ok(()),
)?;
let mut b = [0_u8; 1];
self.file.read_exact(&mut b)?;
if &b != b"\n" {
self.file.write_all(b"\n")?;
}
self.file.write_all(&appendee.into_bytes()).map_or_else(
|e| Err(("Failed to append to file".to_owned(), e).into()),
Ok,
)
}
fn try_to_lock_file(path: &PathBuf) -> Result<(PathBuf, File), crate::UserLibError> {
info!("locking file {}", path.to_string_lossy());
let mut tempfilepath_const = path.clone();
let pid = std::process::id();
debug!("using pid {}", std::process::id());
let filename = tempfilepath_const.file_name().unwrap().to_owned();
tempfilepath_const.pop();
let mut lockfilepath = tempfilepath_const.clone();
tempfilepath_const.push(format!("{}.{}", filename.to_str().unwrap(), pid));
let tempfilepath = TempLockFile {
tlf: tempfilepath_const,
};
lockfilepath.push(format!("{}.lock", filename.to_str().unwrap()));
debug!(
"Lockfile paths: {:?} (temporary) {:?} (final)",
*tempfilepath, lockfilepath
);
{
let mut tempfile = File::create(&*tempfilepath).unwrap_or_else(|e| {
panic!("Failed to open {} error: {}", filename.to_str().unwrap(), e)
});
write!(tempfile, "{}", pid).or_else(|e| {
let error_msg = format!(
"could not write to {} error {}",
filename.to_string_lossy(),
e
);
error!("{}", error_msg);
let err: crate::UserLibError = error_msg.into();
Err(err)
})?;
}
let linkresult = std::fs::hard_link(&*tempfilepath, &lockfilepath);
match linkresult {
Ok(()) => {
debug!("successfully locked");
let resfile = OpenOptions::new().read(true).write(true).open(&path);
return match resfile {
Ok(file) => Ok((lockfilepath, file)),
Err(e) => {
let _ = std::fs::remove_file(&lockfilepath);
let ret: crate::UserLibError = format!(
"Failed to open the file: {}, error: {}",
path.to_str().unwrap(),
e
)
.into();
Err(ret)
}
};
}
Err(e) => match e.kind() {
std::io::ErrorKind::AlreadyExists => {
warn!("The file is already locked by another process! – testing the validity of the lock");
{
let mut lf = match File::open(&lockfilepath) {
Ok(file) => file,
Err(e) => {
panic!("failed to open the lockfile: {}", e);
}
};
let mut content = String::new();
lf.read_to_string(&mut content)
.unwrap_or_else(|e| panic!("failed to read the lockfile{}", e));
let content = content.trim().trim_matches(char::from(0));
let lock_pid = content.parse::<u32>();
match lock_pid {
Ok(pid) => {
warn!(
"found a pid: {}, checking if this process is still running",
pid
);
error!("The file could not be locked");
todo!("Validate the lock and delete the file if the process does not exist anymore");
}
Err(e) => error!(
"existing lock file {} with an invalid PID '{}' Error: {}",
lockfilepath.to_str().unwrap(),
content,
e
),
}
}
}
_ => {
panic!("failed to lock the file: {}", e);
}
},
}
Err("was not able to lock!".into())
}
}
impl Drop for LockedFileGuard {
fn drop(&mut self) {
info!("removing lock");
std::fs::remove_file(&self.lockfile).unwrap();
}
}