src/tools.rs: implement file_set_contents_full()
This commit is contained in:
parent
0f778e0652
commit
eea8131952
44
src/tools.rs
44
src/tools.rs
|
@ -149,13 +149,25 @@ pub fn file_get_json<P: AsRef<Path>>(path: P, default: Option<Value>) -> Result<
|
||||||
}).map_err(|err: Error| format_err!("unable to parse json from {:?} - {}", path, err))
|
}).map_err(|err: Error| format_err!("unable to parse json from {:?} - {}", path, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Atomically write a file. We first create a temporary file, which
|
/// Atomically write a file
|
||||||
/// is then renamed.
|
///
|
||||||
|
/// We first create a temporary file, which is then renamed.
|
||||||
pub fn file_set_contents<P: AsRef<Path>>(
|
pub fn file_set_contents<P: AsRef<Path>>(
|
||||||
path: P,
|
path: P,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
perm: Option<stat::Mode>,
|
perm: Option<stat::Mode>,
|
||||||
) -> Result<(), Error> {
|
) -> 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<P: AsRef<Path>>(
|
||||||
|
path: P,
|
||||||
|
data: &[u8],
|
||||||
|
perm: Option<stat::Mode>,
|
||||||
|
owner: Option<nix::unistd::Uid>,
|
||||||
|
group: Option<nix::unistd::Gid>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
@ -180,6 +192,13 @@ pub fn file_set_contents<P: AsRef<Path>>(
|
||||||
bail!("fchmod {:?} failed: {}", tmp_path, err);
|
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;
|
use std::os::unix::io::FromRawFd;
|
||||||
let mut file = unsafe { File::from_raw_fd(fd) };
|
let mut file = unsafe { File::from_raw_fd(fd) };
|
||||||
|
|
||||||
|
@ -320,7 +339,7 @@ pub fn file_chunker<C, R>(
|
||||||
Ok(())
|
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> {
|
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()) };
|
let info = unsafe { libc::getpwnam(std::ffi::CString::new(username).unwrap().as_ptr()) };
|
||||||
if info == std::ptr::null_mut() {
|
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))
|
Ok((info.pw_uid, info.pw_gid))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change ownership of an open file handle
|
||||||
|
pub fn fchown(
|
||||||
|
fd: RawFd,
|
||||||
|
owner: Option<nix::unistd::Uid>,
|
||||||
|
group: Option<nix::unistd::Gid>
|
||||||
|
) -> 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)
|
// Returns the hosts node name (UTS node name)
|
||||||
pub fn nodename() -> &'static str {
|
pub fn nodename() -> &'static str {
|
||||||
|
|
||||||
|
|
|
@ -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<Mutex<ProcessLocker>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Mutex<ProcessLocker>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Arc<Mutex<Self>>, 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<Mutex<Self>>) -> Result<SharedLock, Error> {
|
||||||
|
|
||||||
|
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<Mutex<Self>>) -> Result<ExclusiveLock, Error> {
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
|
@ -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<ProcFsPidStat, Error> {
|
||||||
|
|
||||||
|
let statstr = tools::file_read_firstline(format!("/proc/{}/stat", pid))?;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref REGEX: Regex = Regex::new(r"^(?P<pid>\d+) \(?:.*\) (?P<status>\S) -?\d+ -?\d+ -?\d+ -?\d+ -?\d+ \d+ \d+ \d+ \d+ \d+ (?P<utime>\d+) (?P<stime>\d+) -?\d+ -?\d+ -?\d+ -?\d+ -?\d+ 0 (?P<starttime>\d+) (?P<vsize>\d+) (?P<rss>-?\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::<i32>().unwrap()) {
|
||||||
|
bail!("unable to read pid stat for process '{}' - got wrong pid", pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(ProcFsPidStat {
|
||||||
|
status: cap["status"].parse::<u8>().unwrap(),
|
||||||
|
utime: cap["utime"].parse::<u64>().unwrap(),
|
||||||
|
stime: cap["stime"].parse::<u64>().unwrap(),
|
||||||
|
starttime: cap["starttime"].parse::<u64>().unwrap(),
|
||||||
|
vsize: cap["vsize"].parse::<u64>().unwrap(),
|
||||||
|
rss: cap["rss"].parse::<i64>().unwrap() * 4096,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("unable to read pid stat for process '{}'", pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_proc_starttime(pid: nix::unistd::Pid) -> Result<u64, Error> {
|
||||||
|
|
||||||
|
let info = read_proc_pid_stat(pid)?;
|
||||||
|
|
||||||
|
Ok(info.starttime)
|
||||||
|
}
|
Loading…
Reference in New Issue