1use std::{io::Seek, io::SeekFrom, path::PathBuf};
2
3#[allow(unused_imports)]
4use log::{debug, error, info, trace, warn};
5use std::fs::{File, OpenOptions};
6use std::io::Read;
7use std::io::Write;
8use std::ops::Deref;
9
10#[derive(Debug)]
11pub struct Files {
12 pub passwd: Option<PathBuf>,
13 pub shadow: Option<PathBuf>,
14 pub group: Option<PathBuf>,
15}
16
17impl Default for Files {
18 fn default() -> Self {
20 Self {
21 passwd: Some(PathBuf::from("/etc/passwd")),
22 shadow: Some(PathBuf::from("/etc/shadow")),
23 group: Some(PathBuf::from("/etc/group")),
24 }
25 }
26}
27
28impl Files {
29 #[must_use]
31 pub const fn is_virtual(&self) -> bool {
32 !(self.group.is_some() & self.passwd.is_some() & self.shadow.is_some())
33 }
34 pub fn lock_and_get_passwd(&self) -> Result<LockedFileGuard, crate::UserLibError> {
35 let path = self.passwd.as_ref();
36 match path {
37 Some(p) => LockedFileGuard::new(p),
38 None => Err(crate::UserLibError::FilesRequired),
39 }
40 }
41 pub fn lock_and_get_shadow(&self) -> Result<LockedFileGuard, crate::UserLibError> {
42 let path = self.shadow.as_ref();
43 match path {
44 Some(p) => LockedFileGuard::new(p),
45 None => Err(crate::UserLibError::FilesRequired),
46 }
47 }
48 pub fn lock_and_get_group(&self) -> Result<LockedFileGuard, crate::UserLibError> {
49 let path = self.group.as_ref();
50 match path {
51 Some(p) => LockedFileGuard::new(p),
52 None => Err(crate::UserLibError::FilesRequired),
53 }
54 }
55
56 pub fn lock_all_get(
57 &self,
58 ) -> Result<(LockedFileGuard, LockedFileGuard, LockedFileGuard), crate::UserLibError> {
59 let pwd = self.lock_and_get_passwd()?;
60 let shd = self.lock_and_get_shadow()?;
61 let grp = self.lock_and_get_group()?;
62 Ok((pwd, shd, grp))
63 }
64}
65
66#[derive(Debug)]
67pub struct LockedFileGuard {
68 lockfile: PathBuf,
69 path: PathBuf,
70 pub(crate) file: File,
71}
72
73#[derive(Debug)]
74struct TempLockFile {
75 tlf: PathBuf,
76}
77
78impl Drop for TempLockFile {
79 fn drop(&mut self) {
80 info!("removing temporary lockfile {}", self.tlf.to_str().unwrap());
81 std::fs::remove_file(&self.tlf).unwrap();
82 }
83}
84impl Deref for TempLockFile {
85 type Target = PathBuf;
86 fn deref(&self) -> &PathBuf {
87 &self.tlf
88 }
89}
90impl LockedFileGuard {
91 pub fn new(path: &PathBuf) -> Result<Self, crate::UserLibError> {
92 let locked = Self::try_to_lock_file(path);
93 match locked {
94 Ok((lockfile, file)) => Ok(Self {
95 lockfile,
96 path: path.to_owned(),
97 file,
98 }),
99 Err(e) => Err(e),
100 }
101 }
102
103 pub fn replace_contents(&mut self, new_content: String) -> Result<(), crate::UserLibError> {
104 self.file = match File::create(&self.path) {
105 Ok(file) => file,
106 Err(e) => return Err(("Failed to truncate file.".to_owned(), e).into()),
107 };
108 match self.file.write_all(&new_content.into_bytes()) {
109 Ok(_) => (),
110 Err(e) => return Err(("Could not write (all) users. ".to_owned(), e).into()),
111 };
112 let _ = self.file.write(b"\n");
113 Ok(())
114 }
115
116 pub fn append(&mut self, appendee: String) -> Result<(), crate::UserLibError> {
117 self.file.seek(SeekFrom::End(-1)).map_or_else(
119 |e| Err(format!("Failed to append to file {}", e)),
120 |_| Ok(()),
121 )?;
122 let mut b = [0_u8; 1];
124 self.file.read_exact(&mut b)?;
125 if &b != b"\n" {
127 self.file.write_all(b"\n")?;
129 }
130 self.file.write_all(&appendee.into_bytes()).map_or_else(
132 |e| Err(("Failed to append to file".to_owned(), e).into()),
133 Ok,
134 )
135 }
136
137 fn try_to_lock_file(path: &PathBuf) -> Result<(PathBuf, File), crate::UserLibError> {
161 info!("locking file {}", path.to_string_lossy());
162 let mut tempfilepath_const = path.clone();
163 let pid = std::process::id();
165 debug!("using pid {}", std::process::id());
166 let filename = tempfilepath_const.file_name().unwrap().to_owned();
168 tempfilepath_const.pop();
170 let mut lockfilepath = tempfilepath_const.clone();
171 tempfilepath_const.push(format!("{}.{}", filename.to_str().unwrap(), pid));
173 let tempfilepath = TempLockFile {
174 tlf: tempfilepath_const,
175 };
176 lockfilepath.push(format!("{}.lock", filename.to_str().unwrap()));
177 debug!(
178 "Lockfile paths: {:?} (temporary) {:?} (final)",
179 *tempfilepath, lockfilepath
180 );
181 {
183 let mut tempfile = File::create(&*tempfilepath).unwrap_or_else(|e| {
184 panic!("Failed to open {} error: {}", filename.to_str().unwrap(), e)
185 });
186 write!(tempfile, "{}", pid).or_else(|e| {
187 let error_msg = format!(
188 "could not write to {} error {}",
189 filename.to_string_lossy(),
190 e
191 );
192 error!("{}", error_msg);
193 let err: crate::UserLibError = error_msg.into();
194 Err(err)
195 })?;
196 }
197
198 let linkresult = std::fs::hard_link(&*tempfilepath, &lockfilepath);
200 match linkresult {
201 Ok(()) => {
202 debug!("successfully locked");
203
204 let resfile = OpenOptions::new().read(true).write(true).open(&path);
206 return match resfile {
207 Ok(file) => Ok((lockfilepath, file)),
208 Err(e) => {
209 let _ = std::fs::remove_file(&lockfilepath);
211 let ret: crate::UserLibError = format!(
212 "Failed to open the file: {}, error: {}",
213 path.to_str().unwrap(),
214 e
215 )
216 .into();
217 Err(ret)
218 }
219 };
220 }
221 Err(e) => match e.kind() {
222 std::io::ErrorKind::AlreadyExists => {
224 warn!("The file is already locked by another process! – testing the validity of the lock");
225 {
226 let mut lf = match File::open(&lockfilepath) {
227 Ok(file) => file,
228 Err(e) => {
229 panic!("failed to open the lockfile: {}", e);
230 }
231 };
232 let mut content = String::new();
233 lf.read_to_string(&mut content)
234 .unwrap_or_else(|e| panic!("failed to read the lockfile{}", e));
235
236 let content = content.trim().trim_matches(char::from(0));
237 let lock_pid = content.parse::<u32>();
238 match lock_pid {
239 Ok(pid) => {
240 warn!(
241 "found a pid: {}, checking if this process is still running",
242 pid
243 );
244 error!("The file could not be locked");
245 todo!("Validate the lock and delete the file if the process does not exist anymore");
246 }
251 Err(e) => error!(
252 "existing lock file {} with an invalid PID '{}' Error: {}",
253 lockfilepath.to_str().unwrap(),
254 content,
255 e
256 ),
257 }
258 }
259 }
260
261 _ => {
262 panic!("failed to lock the file: {}", e);
263 }
264 },
265 }
266 Err("was not able to lock!".into())
267 }
268}
269
270impl Drop for LockedFileGuard {
271 fn drop(&mut self) {
272 info!("removing lock");
273 std::fs::remove_file(&self.lockfile).unwrap();
274 }
275}