umanux/userlib/
files.rs

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    /// use the default Linux `/etc/` paths
19    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    /// Check if all the files are defined. Because some operations require the files to be present
30    #[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        // Seek to the last character.
118        self.file.seek(SeekFrom::End(-1)).map_or_else(
119            |e| Err(format!("Failed to append to file {}", e)),
120            |_| Ok(()),
121        )?;
122        // Read the last character
123        let mut b = [0_u8; 1];
124        self.file.read_exact(&mut b)?;
125        // Verify it is '\n' else append '\n' so in any case the file ends with with a newline now
126        if &b != b"\n" {
127            //self.file.write_all(&b)?;
128            self.file.write_all(b"\n")?;
129        }
130        // write the new line.
131        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    /// This function tries to lock a file in the way other passwd locking mechanisms work.
138    ///
139    /// * get the pid
140    /// * create the temporary lockfilepath "/etc/passwd.12397"
141    /// * create the lockfilepath "/etc/passwd.lock"
142    /// * open the temporary file
143    /// * write the pid to the tempfile
144    /// * try to make a link from the temporary file created to the lockfile
145    /// * ensure that the file has been linked successfully
146    ///
147    /// when the link could not be created:
148    ///
149    /// * Open the lockfile
150    /// * read the contents of the lockfile
151    /// * check if the lockfile contains a pid if not error out
152    /// * check if the containing pid is in a valid format. If not create a matching error
153    ///
154    /// not implemented yet:
155    ///
156    /// * test if this process could be killed. If so disclose the pid in the error.
157    /// * try to delete the lockfile as it is apparently not used by the process anmore. (cleanup)
158    /// * try to lock again now that the old logfile has been safely removed.
159    /// * remove the original file and only keep the lock hardlink
160    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        // get the pid
164        let pid = std::process::id();
165        debug!("using pid {}", std::process::id());
166        // get the filename
167        let filename = tempfilepath_const.file_name().unwrap().to_owned();
168        // and the base path which is the base for tempfile and lockfile.
169        tempfilepath_const.pop();
170        let mut lockfilepath = tempfilepath_const.clone();
171        // push the filenames to the paths
172        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        // write the pid into the tempfile
182        {
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        // try to make a hardlink from the lockfile to the tempfile
199        let linkresult = std::fs::hard_link(&*tempfilepath, &lockfilepath);
200        match linkresult {
201            Ok(()) => {
202                debug!("successfully locked");
203
204                // open the file
205                let resfile = OpenOptions::new().read(true).write(true).open(&path);
206                return match resfile {
207                    Ok(file) => Ok((lockfilepath, file)),
208                    Err(e) => {
209                        // failed to open the file undo the locks
210                        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                // analyze the error further
223                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                                /*let sent = nix::sys::signal::kill(
247                                    nix::unistd::Pid::from_raw(pid as i32),
248                                    nix::sys::signal::Signal::from(0),
249                                );*/
250                            }
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}