From eea8131952dce17b6726dad8fd4b4a2827232a92 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Sat, 6 Apr 2019 13:53:43 +0200 Subject: [PATCH] src/tools.rs: implement file_set_contents_full() --- src/tools.rs | 44 ++++++++- src/tools/process_locker.rs~ | 185 +++++++++++++++++++++++++++++++++++ src/tools/procfs.rs~ | 47 +++++++++ 3 files changed, 273 insertions(+), 3 deletions(-) create mode 100644 src/tools/process_locker.rs~ create mode 100644 src/tools/procfs.rs~ diff --git a/src/tools.rs b/src/tools.rs index f63102cb..0696d3b0 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -149,13 +149,25 @@ pub fn file_get_json>(path: P, default: Option) -> Result< }).map_err(|err: Error| format_err!("unable to parse json from {:?} - {}", path, err)) } -/// Atomically write a file. We first create a temporary file, which -/// is then renamed. +/// Atomically write a file +/// +/// We first create a temporary file, which is then renamed. pub fn file_set_contents>( path: P, data: &[u8], perm: Option, ) -> Result<(), Error> { + file_set_contents_full(path, data, perm, None, None) +} + +/// Atomically write a file with owner and group +pub fn file_set_contents_full>( + path: P, + data: &[u8], + perm: Option, + owner: Option, + group: Option, +) -> Result<(), Error> { let path = path.as_ref(); @@ -180,6 +192,13 @@ pub fn file_set_contents>( bail!("fchmod {:?} failed: {}", tmp_path, err); } + if owner != None || group != None { + if let Err(err) = fchown(fd, owner, group) { + let _ = unistd::unlink(tmp_path); + bail!("fchown {:?} failed: {}", tmp_path, err); + } + } + use std::os::unix::io::FromRawFd; let mut file = unsafe { File::from_raw_fd(fd) }; @@ -320,7 +339,7 @@ pub fn file_chunker( Ok(()) } -// Returns the Unix uid/gid for the sepcified system user. +/// Returns the Unix uid/gid for the sepcified system user. pub fn getpwnam_ugid(username: &str) -> Result<(libc::uid_t,libc::gid_t), Error> { let info = unsafe { libc::getpwnam(std::ffi::CString::new(username).unwrap().as_ptr()) }; if info == std::ptr::null_mut() { @@ -332,6 +351,25 @@ pub fn getpwnam_ugid(username: &str) -> Result<(libc::uid_t,libc::gid_t), Error> Ok((info.pw_uid, info.pw_gid)) } +/// Change ownership of an open file handle +pub fn fchown( + fd: RawFd, + owner: Option, + group: Option +) -> Result<(), Error> { + + // According to the POSIX specification, -1 is used to indicate that owner and group + // are not to be changed. Since uid_t and gid_t are unsigned types, we have to wrap + // around to get -1 (copied fron nix crate). + let uid = owner.map(Into::into).unwrap_or((0 as libc::uid_t).wrapping_sub(1)); + let gid = group.map(Into::into).unwrap_or((0 as libc::gid_t).wrapping_sub(1)); + + let res = unsafe { libc::fchown(fd, uid, gid) }; + nix::errno::Errno::result(res)?; + + Ok(()) +} + // Returns the hosts node name (UTS node name) pub fn nodename() -> &'static str { diff --git a/src/tools/process_locker.rs~ b/src/tools/process_locker.rs~ new file mode 100644 index 00000000..f1cf57c0 --- /dev/null +++ b/src/tools/process_locker.rs~ @@ -0,0 +1,185 @@ +use failure::*; +use std::thread; +use std::sync::{Arc, Mutex}; +use std::os::unix::io::AsRawFd; + +//use proxmox_backup::tools; + +// fixme: use F_OFD_ locks when implemented with nix::fcntl + +// flock lock conversion is not atomic, so we need to use fcntl + +struct ProcessLocker { + file: std::fs::File, + exclusive: bool, + writers: usize, +} + +struct SharedLock { + locker: Arc>, +} + +impl Drop for SharedLock { + fn drop(&mut self) { + let tid = std::thread::current().id(); + let pid = std::process::id(); + + println!("{:?}:{:?}: Drop Writerlock", pid, tid); + + let mut data = self.locker.lock().unwrap(); + + if data.writers == 0 { panic!("unexpected ProcessLocker state"); } + + if data.writers == 1 && !data.exclusive { + + let op = libc::flock { + l_type: libc::F_UNLCK as i16, + l_whence: libc::SEEK_SET as i16, + l_start: 0, + l_len: 0, + l_pid: 0, + }; + + if let Err(err) = nix::fcntl::fcntl(data.file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLKW(&op)) { + panic!("unable to drop writer lock - {}", err); + } + data.writers = 0; + } + } +} + +struct ExclusiveLock { + locker: Arc>, +} + +impl Drop for ExclusiveLock { + fn drop(&mut self) { + let tid = std::thread::current().id(); + let pid = std::process::id(); + + println!("{:?}:{:?}: Drop Exclusive lock", pid, tid); + + let mut data = self.locker.lock().unwrap(); + + if !data.exclusive { panic!("unexpected ProcessLocker state"); } + + let ltype = if data.writers != 0 { libc::F_RDLCK } else { libc::F_UNLCK }; + let op = libc::flock { + l_type: ltype as i16, + l_whence: libc::SEEK_SET as i16, + l_start: 0, + l_len: 0, + l_pid: 0, + }; + + if let Err(err) = nix::fcntl::fcntl(data.file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLKW(&op)) { + panic!("unable to drop exclusive lock - {}", err); + } + + data.exclusive = false; + } +} + +impl ProcessLocker { + + pub fn new(lockfile: &str) -> Result>, Error> { + + let file = std::fs::OpenOptions::new() + .create(true) + .read(true) + .write(true) + .open(lockfile)?; + + Ok(Arc::new(Mutex::new(Self { + file: file, + exclusive: false, + writers: 0, + }))) + } + + pub fn try_shared_lock(locker: Arc>) -> Result { + + let mut data = locker.lock().unwrap(); + + if data.writers == 0 && !data.exclusive { + let op = libc::flock { + l_type: libc::F_RDLCK as i16, + l_whence: libc::SEEK_SET as i16, + l_start: 0, + l_len: 0, + l_pid: 0, + }; + + if let Err(err) = nix::fcntl::fcntl(data.file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLK(&op)) { + bail!("unable to get shared lock - {}", err); + } + } + + data.writers += 1; + + Ok(SharedLock { locker: locker.clone() }) + } + + pub fn try_exclusive_lock(locker: Arc>) -> Result { + + let mut data = locker.lock().unwrap(); + + if data.exclusive { + bail!("already locked exclusively"); + + } + + let op = libc::flock { + l_type: libc::F_WRLCK as i16, + l_whence: libc::SEEK_SET as i16, + l_start: 0, + l_len: 0, + l_pid: 0, + }; + + if let Err(err) = nix::fcntl::fcntl(data.file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLK(&op)) { + bail!("unable to get exclusive lock - {}", err); + } + + data.exclusive = true; + + return Ok(ExclusiveLock { locker: locker.clone() }); + + } +} + +fn run_test() -> Result<(), Error> +{ + + let tid = std::thread::current().id(); + let pid = std::process::id(); + + let locker = ProcessLocker::new("test.flock")?; + + println!("{:?}:{:?}: try to get shared lock", pid, tid); + let l1 = ProcessLocker::try_shared_lock(locker.clone())?; + println!("{:?}:{:?}: got shared lock", pid, tid); + + println!("{:?}:{:?}: try to get exclusive lock", pid, tid); + let l2 = ProcessLocker::try_exclusive_lock(locker.clone())?; + println!("{:?}:{:?}: got exclusive lock", pid, tid); + + println!("{:?}:{:?}: try to get shared lock", pid, tid); + let l3 = ProcessLocker::try_shared_lock(locker.clone())?; + println!("{:?}:{:?}: got shared lock", pid, tid); + + Ok(()) +} + +fn main() -> Result<(), Error> +{ + if let Err(err) = run_test() { + println!("ERROR: {}", err); + } + + let tid = std::thread::current().id(); + let pid = std::process::id(); + println!("{:?}:{:?}: Exit", pid, tid); + + Ok(()) +} diff --git a/src/tools/procfs.rs~ b/src/tools/procfs.rs~ new file mode 100644 index 00000000..5b20addb --- /dev/null +++ b/src/tools/procfs.rs~ @@ -0,0 +1,47 @@ +use failure::*; + +use crate::tools; +use lazy_static::lazy_static; +use regex::Regex; + +pub struct ProcFsPidStat { + pub status: u8, + pub utime: u64, + pub stime: u64, + pub starttime: u64, + pub vsize: u64, + pub rss: i64, +} + +pub fn read_proc_pid_stat(pid: nix::unistd::Pid) -> Result { + + let statstr = tools::file_read_firstline(format!("/proc/{}/stat", pid))?; + + lazy_static! { + static ref REGEX: Regex = Regex::new(r"^(?P\d+) \(?:.*\) (?P\S) -?\d+ -?\d+ -?\d+ -?\d+ -?\d+ \d+ \d+ \d+ \d+ \d+ (?P\d+) (?P\d+) -?\d+ -?\d+ -?\d+ -?\d+ -?\d+ 0 (?P\d+) (?P\d+) (?P-?\d+) \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ -?\d+ -?\d+ \d+ \d+ \d+").unwrap(); + } + + if let Some(cap) = REGEX.captures(&statstr) { + if pid != nix::unistd::Pid::from_raw(cap["pid"].parse::().unwrap()) { + bail!("unable to read pid stat for process '{}' - got wrong pid", pid); + } + + return Ok(ProcFsPidStat { + status: cap["status"].parse::().unwrap(), + utime: cap["utime"].parse::().unwrap(), + stime: cap["stime"].parse::().unwrap(), + starttime: cap["starttime"].parse::().unwrap(), + vsize: cap["vsize"].parse::().unwrap(), + rss: cap["rss"].parse::().unwrap() * 4096, + }); + } + + bail!("unable to read pid stat for process '{}'", pid); +} + +pub fn read_proc_starttime(pid: nix::unistd::Pid) -> Result { + + let info = read_proc_pid_stat(pid)?; + + Ok(info.starttime) +}