feat: clone from https://github.com/miquels/webdav-server-rs
This commit is contained in:
7
fs_quota/.gitignore
vendored
Normal file
7
fs_quota/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
|
||||
src/rquota.h
|
||||
src/rquota_xdr.c
|
||||
30
fs_quota/Cargo.toml
Normal file
30
fs_quota/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "fs-quota"
|
||||
|
||||
# When releasing to crates.io:
|
||||
# - Update html_root_url in src/lib.rs
|
||||
# - Update CHANGELOG.md.
|
||||
# - Run: cargo readme > README.md
|
||||
# - Create git tag fs-quota-0.x.y
|
||||
version = "0.1.0"
|
||||
|
||||
readme = "README.md"
|
||||
documentation = "https://docs.rs/fs-quota"
|
||||
repository = "https://github.com/miquels/webdav-server-rs"
|
||||
homepage = "https://github.com/miquels/webdav-server-rs/tree/master/fs_quota"
|
||||
authors = ["Miquel van Smoorenburg <mike@langeraar.net>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
keywords = ["quota"]
|
||||
categories = ["filesystem"]
|
||||
|
||||
[features]
|
||||
nfs = []
|
||||
default = ["nfs"]
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.66"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.82"
|
||||
log = "0.4.13"
|
||||
36
fs_quota/README.md
Normal file
36
fs_quota/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
# fs-quota
|
||||
|
||||
Get filesystem disk space used and available for a unix user.
|
||||
|
||||
This crate has support for:
|
||||
|
||||
- the Linux quota system
|
||||
- NFS quotas (via SUNRPC).
|
||||
- `libc::vfsstat` lookups (like `df`).
|
||||
|
||||
Both the `quota` systemcall and `vfsstat` systemcall are different
|
||||
on every system. That functionality is only implemented on Linux
|
||||
right now. NFS quota support _should_ work everywhere.
|
||||
|
||||
NFS quota support can be left out by disabling the `nfs` feature.
|
||||
|
||||
Example application:
|
||||
```rust
|
||||
use fs_quota::*;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 2 {
|
||||
println!("usage: fs_quota <path>");
|
||||
return;
|
||||
}
|
||||
println!("{:#?}", FsQuota::check(&args[1], None));
|
||||
}
|
||||
```
|
||||
|
||||
### Copyright and License.
|
||||
|
||||
* © 2018, 2019 XS4ALL Internet bv
|
||||
* © 2018, 2019 Miquel van Smoorenburg
|
||||
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
11
fs_quota/README.tpl
Normal file
11
fs_quota/README.tpl
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
# {{crate}}
|
||||
|
||||
{{readme}}
|
||||
|
||||
### Copyright and License.
|
||||
|
||||
* © 2018, 2019 XS4ALL Internet bv
|
||||
* © 2018, 2019 Miquel van Smoorenburg
|
||||
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
68
fs_quota/build.rs
Normal file
68
fs_quota/build.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
extern crate cc;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
fn run_rpcgen() {
|
||||
let res = Command::new("rpcgen")
|
||||
.arg("-c")
|
||||
.arg("src/rquota.x")
|
||||
.output()
|
||||
.expect("failed to run rpcgen");
|
||||
let csrc = String::from_utf8_lossy(&res.stdout);
|
||||
let mut f = File::create("src/rquota_xdr.c").expect("src/rquota_xdr.c");
|
||||
f.write_all(
|
||||
csrc.replace("/usr/include/rpcsvc/rquota.h", "./rquota.h")
|
||||
.replace("src/rquota.h", "./rquota.h")
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = Command::new("rpcgen")
|
||||
.arg("-h")
|
||||
.arg("src/rquota.x")
|
||||
.output()
|
||||
.expect("failed to run rpcgen");
|
||||
let hdr = String::from_utf8_lossy(&res.stdout);
|
||||
let mut f = File::create("src/rquota.h").expect("src/rquota.h");
|
||||
f.write_all(hdr.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "nfs")]
|
||||
run_rpcgen();
|
||||
|
||||
let mut builder = cc::Build::new();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
builder.file("src/quota-linux.c");
|
||||
|
||||
#[cfg(feature = "nfs")]
|
||||
{
|
||||
if Path::new("/usr/include/tirpc").exists() {
|
||||
// Fedora does not include RPC support in glibc anymore, so use tirpc instead.
|
||||
builder.include("/usr/include/tirpc");
|
||||
}
|
||||
builder.file("src/quota-nfs.c").file("src/rquota_xdr.c");
|
||||
}
|
||||
builder
|
||||
.flag_if_supported("-Wno-unused-variable")
|
||||
.compile("fs_quota");
|
||||
|
||||
if Path::new("/usr/include/tirpc").exists() {
|
||||
println!("cargo:rustc-link-lib=tirpc");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=rpcsvc");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
println!("cargo:rerun-if-changed=src/quota-linux.c");
|
||||
|
||||
#[cfg(feature = "nfs")]
|
||||
{
|
||||
println!("cargo:rerun-if-changed=src/rquota.x");
|
||||
println!("cargo:rerun-if-changed=src/quota-nfs.c");
|
||||
}
|
||||
}
|
||||
11
fs_quota/examples/fs_quota.rs
Normal file
11
fs_quota/examples/fs_quota.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
extern crate fs_quota;
|
||||
use fs_quota::*;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 2 {
|
||||
println!("usage: fs_quota <path>");
|
||||
return;
|
||||
}
|
||||
println!("{:#?}", FsQuota::check(&args[1], None));
|
||||
}
|
||||
26
fs_quota/src/Makefile
Normal file
26
fs_quota/src/Makefile
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
RPCGEN = rpcgen
|
||||
CFLAGS = -Wall
|
||||
|
||||
rquota.a: quota-nfs.o rquota_xdr.o
|
||||
rm -f rquota.a
|
||||
ar r rquota.a quota-nfs.o rquota_xdr.o
|
||||
|
||||
rquota_xdr.c: rquota.h
|
||||
(echo '#include <rpc/rpc.h>'; \
|
||||
$(RPCGEN) -c rquota.x | \
|
||||
sed -e 's/IXDR_PUT/(void)IXDR_PUT/g' \
|
||||
-e 's,/usr/include/rpcsvc/rquota.h,rquota.h,' \
|
||||
-e 's/^static char rcsid.*//' ) > rquota_xdr.c
|
||||
|
||||
rquota.h: Makefile rquota.x
|
||||
$(RPCGEN) -h rquota.x > rquota.h
|
||||
|
||||
rquota_xdr.o: rquota_xdr.c
|
||||
cc -c rquota_xdr.c
|
||||
|
||||
quota-nfs.o: rquota.h
|
||||
|
||||
clean:
|
||||
rm -f *.a *.o rquota.h rquota_clnt.c rquota_svc.c rquota_xdr.c
|
||||
|
||||
15
fs_quota/src/generic_os.rs
Normal file
15
fs_quota/src/generic_os.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// No-op implementations.
|
||||
//
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{FqError, FsQuota, Mtab};
|
||||
|
||||
pub(crate) fn get_quota(_device: impl AsRef<Path>, _uid: u32) -> Result<FsQuota, FqError> {
|
||||
Err(FqError::NoQuota)
|
||||
}
|
||||
|
||||
pub(crate) fn read_mtab() -> io::Result<Vec<Mtab>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
282
fs_quota/src/lib.rs
Normal file
282
fs_quota/src/lib.rs
Normal file
@@ -0,0 +1,282 @@
|
||||
#![doc(html_root_url = "https://docs.rs/fs-quota/0.1.0")]
|
||||
//! Get filesystem disk space used and available for a unix user.
|
||||
//!
|
||||
//! This crate has support for:
|
||||
//!
|
||||
//! - the Linux quota system
|
||||
//! - NFS quotas (via SUNRPC).
|
||||
//! - `libc::vfsstat` lookups (like `df`).
|
||||
//!
|
||||
//! Both the `quota` systemcall and `vfsstat` systemcall are different
|
||||
//! on every system. That functionality is only implemented on Linux
|
||||
//! right now. NFS quota support _should_ work everywhere.
|
||||
//!
|
||||
//! NFS quota support can be left out by disabling the `nfs` feature.
|
||||
//!
|
||||
//! Example application:
|
||||
//! ```no_run
|
||||
//! use fs_quota::*;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let args: Vec<String> = std::env::args().collect();
|
||||
//! if args.len() < 2 {
|
||||
//! println!("usage: fs_quota <path>");
|
||||
//! return;
|
||||
//! }
|
||||
//! println!("{:#?}", FsQuota::check(&args[1], None));
|
||||
//! }
|
||||
//! ```
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate libc;
|
||||
|
||||
use std::ffi::{CStr, CString, OsStr};
|
||||
use std::io;
|
||||
use std::os::raw::c_char;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[cfg(feature = "nfs")]
|
||||
mod quota_nfs;
|
||||
|
||||
// Linux specific code lives in linux.rs.
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "linux")]
|
||||
use linux::{get_quota, read_mtab};
|
||||
|
||||
// Unsupported OS.
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
mod generic_os;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
use generic_os::{get_quota, read_mtab};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum FsType {
|
||||
LinuxExt,
|
||||
LinuxXfs,
|
||||
Nfs,
|
||||
Other,
|
||||
}
|
||||
|
||||
// return filesystem major type.
|
||||
fn fstype(tp: &str) -> FsType {
|
||||
match tp {
|
||||
"ext2" | "ext3" | "ext4" => FsType::LinuxExt,
|
||||
"xfs" => FsType::LinuxXfs,
|
||||
"nfs" | "nfs4" => FsType::Nfs,
|
||||
_ => FsType::Other,
|
||||
}
|
||||
}
|
||||
|
||||
/// quota / vfsstat lookup result.
|
||||
#[derive(Debug)]
|
||||
pub struct FsQuota {
|
||||
/// number of bytes used.
|
||||
pub bytes_used: u64,
|
||||
/// maximum number of bytes (available - used).
|
||||
pub bytes_limit: Option<u64>,
|
||||
/// number of files (inodes) in use.
|
||||
pub files_used: u64,
|
||||
/// maximum number of files (available - used).
|
||||
pub files_limit: Option<u64>,
|
||||
}
|
||||
|
||||
/// Error result.
|
||||
#[derive(Debug)]
|
||||
pub enum FqError {
|
||||
/// Permission denied.
|
||||
PermissionDenied,
|
||||
/// Filesystem does not have quotas enabled.
|
||||
NoQuota,
|
||||
/// An I/O error occured.
|
||||
IoError(io::Error),
|
||||
/// Some other error.
|
||||
Other,
|
||||
}
|
||||
|
||||
impl FsQuota {
|
||||
/// Get the filesystem quota for a `uid` on the filesystem where `path` is on.
|
||||
///
|
||||
/// If `uid` is `None`, get it for the current real user-id.
|
||||
pub fn user(path: impl AsRef<Path>, uid: Option<u32>) -> Result<FsQuota, FqError> {
|
||||
let id = uid.unwrap_or(unsafe { libc::getuid() as u32 });
|
||||
let entry = get_mtab_entry(path)?;
|
||||
|
||||
#[cfg(feature = "nfs")]
|
||||
{
|
||||
let fst = fstype(&entry.fstype);
|
||||
if fst == FsType::Nfs {
|
||||
return quota_nfs::get_quota(&entry, id);
|
||||
}
|
||||
}
|
||||
|
||||
get_quota(&entry.device, id)
|
||||
}
|
||||
|
||||
/// Get used and available disk space of the filesystem indicated by `path`.
|
||||
///
|
||||
/// This is not really a quota call; it simply calls `libc::vfsstat` (`df`).
|
||||
pub fn system(path: impl AsRef<Path>) -> Result<FsQuota, FqError> {
|
||||
// Call libc::vfsstat(). It's POSIX so should be supported everywhere.
|
||||
let cpath = CString::new(path.as_ref().as_os_str().as_bytes())?;
|
||||
let mut vfs = unsafe { std::mem::zeroed::<libc::statvfs>() };
|
||||
let rc = unsafe { libc::statvfs(cpath.as_ptr(), &mut vfs) };
|
||||
if rc != 0 {
|
||||
return Err(FqError::IoError(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(FsQuota {
|
||||
bytes_used: ((vfs.f_blocks - vfs.f_bfree) * vfs.f_frsize) as u64,
|
||||
bytes_limit: Some(((vfs.f_blocks - (vfs.f_bfree - vfs.f_bavail)) * vfs.f_frsize) as u64),
|
||||
files_used: (vfs.f_files - vfs.f_ffree) as u64,
|
||||
files_limit: Some((vfs.f_files - (vfs.f_ffree - vfs.f_favail)) as u64),
|
||||
})
|
||||
}
|
||||
|
||||
/// Lookup used and available disk space for a `uid`. First check user's quota,
|
||||
/// if quotas are not enabled check the filesystem disk space usage.
|
||||
///
|
||||
/// This is the equivalent of
|
||||
///
|
||||
/// ```no_run
|
||||
/// # let path = "/";
|
||||
/// # let uid = None;
|
||||
/// # use fs_quota::*;
|
||||
/// FsQuota::user(path, uid)
|
||||
/// .or_else(|e| if e == FqError::NoQuota { FsQuota::system(path) } else { Err(e) })
|
||||
/// # ;
|
||||
/// ```
|
||||
///
|
||||
pub fn check(path: impl AsRef<Path>, uid: Option<u32>) -> Result<FsQuota, FqError> {
|
||||
let path = path.as_ref();
|
||||
FsQuota::user(path, uid).or_else(|e| {
|
||||
if e == FqError::NoQuota {
|
||||
FsQuota::system(path)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The libc realpath() function.
|
||||
fn realpath<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
|
||||
let cpath = CString::new(path.as_ref().as_os_str().as_bytes())?;
|
||||
let nullptr: *mut c_char = std::ptr::null_mut();
|
||||
unsafe {
|
||||
let r = libc::realpath(cpath.as_ptr(), nullptr);
|
||||
if r == nullptr {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
let osstr = OsStr::from_bytes(CStr::from_ptr(r).to_bytes());
|
||||
let p = PathBuf::from(osstr);
|
||||
libc::free(r as *mut libc::c_void);
|
||||
Ok(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Mtab {
|
||||
host: Option<String>,
|
||||
device: String,
|
||||
directory: String,
|
||||
fstype: String,
|
||||
}
|
||||
|
||||
// find an entry in the mtab.
|
||||
fn get_mtab_entry(path: impl AsRef<Path>) -> Result<Mtab, FqError> {
|
||||
let path = path.as_ref();
|
||||
let meta = std::fs::symlink_metadata(path)?;
|
||||
|
||||
// get all eligible entries.
|
||||
let ents = read_mtab()?
|
||||
.into_iter()
|
||||
.filter(|e| fstype(&e.fstype) != FsType::Other)
|
||||
.filter(|e| {
|
||||
match std::fs::metadata(&e.directory) {
|
||||
Ok(ref m) => m.dev() == meta.dev(),
|
||||
Err(_) => false,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Mtab>>();
|
||||
|
||||
// 0 matches, error. 1 match, fine. >1 match, need to look closer.
|
||||
let entry = match ents.len() {
|
||||
0 => return Err(FqError::NoQuota),
|
||||
1 => ents[0].clone(),
|
||||
_ => {
|
||||
// multiple matching entries.. happens on NFS.
|
||||
|
||||
// get "realpath" of the path that was passed in.
|
||||
let rp = match realpath(path) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
// realpath the remaining entries as well..
|
||||
let mut v = Vec::new();
|
||||
for mut e in ents.into_iter() {
|
||||
match realpath(&e.directory) {
|
||||
Ok(p) => {
|
||||
let c = String::from_utf8_lossy(p.as_os_str().as_bytes());
|
||||
e.directory = c.to_string();
|
||||
v.push(e);
|
||||
},
|
||||
Err(_) => {},
|
||||
}
|
||||
}
|
||||
if v.len() == 0 {
|
||||
return Err(FqError::NoQuota);
|
||||
}
|
||||
|
||||
// find longest match.
|
||||
v.sort_by_key(|e| e.directory.clone());
|
||||
v.reverse();
|
||||
match v.iter().position(|ref x| rp.starts_with(&x.directory)) {
|
||||
Some(p) => v[p].clone(),
|
||||
None => {
|
||||
return Err(FqError::NoQuota);
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
impl From<io::Error> for FqError {
|
||||
fn from(e: io::Error) -> Self {
|
||||
FqError::IoError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::ffi::NulError> for FqError {
|
||||
fn from(e: std::ffi::NulError) -> Self {
|
||||
FqError::IoError(e.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_num(e: &FqError) -> u32 {
|
||||
match e {
|
||||
&FqError::PermissionDenied => 1,
|
||||
&FqError::NoQuota => 2,
|
||||
&FqError::IoError(_) => 3,
|
||||
&FqError::Other => 4,
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for FqError {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match self {
|
||||
&FqError::IoError(ref e) => {
|
||||
if let &FqError::IoError(ref o) = other {
|
||||
e.kind() == o.kind()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
e => to_num(e) == to_num(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
101
fs_quota/src/linux.rs
Normal file
101
fs_quota/src/linux.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// Linux specific systemcalls for quota.
|
||||
//
|
||||
use std::ffi::CString;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{FqError, FsQuota, Mtab};
|
||||
|
||||
// The actual implementation is done in C, and imported here.
|
||||
extern "C" {
|
||||
fn fs_quota_linux(
|
||||
device: *const c_char,
|
||||
id: c_int,
|
||||
do_group: c_int,
|
||||
bytes_used: *mut u64,
|
||||
bytes_limit: *mut u64,
|
||||
files_used: *mut u64,
|
||||
files_limit: *mut u64,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
// wrapper for the C functions.
|
||||
pub(crate) fn get_quota(device: impl AsRef<Path>, uid: u32) -> Result<FsQuota, FqError> {
|
||||
let id = uid as c_int;
|
||||
let device = device.as_ref();
|
||||
|
||||
let mut bytes_used = 0u64;
|
||||
let mut bytes_limit = 0u64;
|
||||
let mut files_used = 0u64;
|
||||
let mut files_limit = 0u64;
|
||||
|
||||
let path = CString::new(device.as_os_str().as_bytes())?;
|
||||
let rc = unsafe {
|
||||
fs_quota_linux(
|
||||
path.as_ptr(),
|
||||
id,
|
||||
0,
|
||||
&mut bytes_used as *mut u64,
|
||||
&mut bytes_limit as *mut u64,
|
||||
&mut files_used as *mut u64,
|
||||
&mut files_limit as *mut u64,
|
||||
)
|
||||
};
|
||||
|
||||
// Error mapping.
|
||||
match rc {
|
||||
0 => {
|
||||
let m = |v| if v == 0xffffffffffffffff { None } else { Some(v) };
|
||||
Ok(FsQuota {
|
||||
bytes_used: bytes_used,
|
||||
bytes_limit: m(bytes_limit),
|
||||
files_used: files_used,
|
||||
files_limit: m(files_limit),
|
||||
})
|
||||
},
|
||||
1 => Err(FqError::NoQuota),
|
||||
_ => Err(FqError::IoError(io::Error::last_os_error())),
|
||||
}
|
||||
}
|
||||
|
||||
// read /etc/mtab.
|
||||
pub(crate) fn read_mtab() -> io::Result<Vec<Mtab>> {
|
||||
let f = File::open("/etc/mtab")?;
|
||||
let reader = BufReader::new(f);
|
||||
let mut result = Vec::new();
|
||||
for l in reader.lines() {
|
||||
let l2 = l?;
|
||||
let line = l2.trim();
|
||||
if line.len() == 0 || line.starts_with("#") {
|
||||
continue;
|
||||
}
|
||||
let words = line.split_whitespace().collect::<Vec<_>>();
|
||||
if words.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
let (host, device) = if words[2].starts_with("nfs") {
|
||||
if !words[0].contains(":") {
|
||||
continue;
|
||||
}
|
||||
let mut s = words[0].splitn(2, ':');
|
||||
let host = s.next().unwrap();
|
||||
let path = s.next().unwrap();
|
||||
(Some(host.to_string()), path)
|
||||
} else {
|
||||
(None, words[2])
|
||||
};
|
||||
result.push(Mtab {
|
||||
host: host,
|
||||
device: device.to_string(),
|
||||
directory: words[1].to_string(),
|
||||
fstype: words[2].to_string(),
|
||||
});
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
44
fs_quota/src/quota-linux.c
Normal file
44
fs_quota/src/quota-linux.c
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/quota.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef HAVE_STRUCT_DQBLK_CURSPACE
|
||||
# define dqb_curblocks dqb_curspace
|
||||
#endif
|
||||
|
||||
int fs_quota_linux(char *path, int id, int do_group,
|
||||
uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
|
||||
uint64_t *count_value_r, uint64_t *count_limit_r)
|
||||
{
|
||||
int type = do_group ? GRPQUOTA : USRQUOTA;
|
||||
|
||||
struct dqblk dqblk;
|
||||
if (quotactl(QCMD(Q_GETQUOTA, type), path, id, (caddr_t)&dqblk) < 0) {
|
||||
if (errno == ESRCH || errno == ENOENT) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if _LINUX_QUOTA_VERSION == 1
|
||||
*bytes_value_r = dqblk.dqb_curblocks * 1024;
|
||||
#else
|
||||
*bytes_value_r = dqblk.dqb_curspace;
|
||||
#endif
|
||||
*bytes_limit_r = dqblk.dqb_bsoftlimit * 1024;
|
||||
if (*bytes_limit_r == 0) {
|
||||
*bytes_limit_r = dqblk.dqb_bhardlimit * 1024;
|
||||
}
|
||||
*count_value_r = dqblk.dqb_curinodes;
|
||||
*count_limit_r = dqblk.dqb_isoftlimit;
|
||||
if (*count_limit_r == 0) {
|
||||
*count_limit_r = dqblk.dqb_ihardlimit;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
185
fs_quota/src/quota-nfs.c
Normal file
185
fs_quota/src/quota-nfs.c
Normal file
@@ -0,0 +1,185 @@
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "./rquota.h"
|
||||
#define RQUOTA_GETQUOTA_TIMEOUT_SECS 2
|
||||
|
||||
static const uint32_t unlimited32 = 0xffffffff;
|
||||
static const uint64_t unlimited64 = 0xffffffffffffffff;
|
||||
|
||||
#define E_CLNT_CALL 0x00100000
|
||||
#define E_CLNT_CREATE 0x00000001
|
||||
#define E_NOQUOTA 0x00000002
|
||||
#define E_PERM 0x00000003
|
||||
#define E_UNKNOWN 0x0000f000
|
||||
|
||||
static void
|
||||
rquota_get_result(const rquota *rq,
|
||||
uint64_t *bytes_used_r, uint64_t *bytes_limit_r,
|
||||
uint64_t *files_used_r, uint64_t *files_limit_r)
|
||||
{
|
||||
*bytes_used_r = (uint64_t)rq->rq_curblocks *
|
||||
(uint64_t)rq->rq_bsize;
|
||||
*bytes_limit_r = unlimited64;
|
||||
if (rq->rq_bsoftlimit != 0 && rq->rq_bsoftlimit != unlimited32) {
|
||||
*bytes_limit_r = (uint64_t)rq->rq_bsoftlimit *
|
||||
(uint64_t)rq->rq_bsize;
|
||||
} else if (rq->rq_bhardlimit != unlimited32) {
|
||||
*bytes_limit_r = (uint64_t)rq->rq_bhardlimit *
|
||||
(uint64_t)rq->rq_bsize;
|
||||
}
|
||||
|
||||
*files_used_r = rq->rq_curfiles;
|
||||
*files_limit_r = unlimited64;
|
||||
if (rq->rq_fsoftlimit != 0 && rq->rq_fsoftlimit != unlimited32)
|
||||
*files_limit_r = rq->rq_fsoftlimit;
|
||||
else if (rq->rq_fhardlimit != unlimited32)
|
||||
*files_limit_r = rq->rq_fhardlimit;
|
||||
}
|
||||
|
||||
int fs_quota_nfs_user(char *host, char *path, int uid,
|
||||
uint64_t *bytes_used_r, uint64_t *bytes_limit_r,
|
||||
uint64_t *files_used_r, uint64_t *files_limit_r)
|
||||
{
|
||||
struct getquota_rslt result;
|
||||
struct getquota_args args;
|
||||
struct timeval timeout;
|
||||
enum clnt_stat call_status;
|
||||
CLIENT *cl;
|
||||
|
||||
/* clnt_create() polls for a while to establish a connection */
|
||||
cl = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp");
|
||||
if (cl == NULL) {
|
||||
return E_CLNT_CREATE;
|
||||
}
|
||||
|
||||
/* Establish some RPC credentials */
|
||||
auth_destroy(cl->cl_auth);
|
||||
cl->cl_auth = authunix_create_default();
|
||||
|
||||
/* make the rquota call on the remote host */
|
||||
args.gqa_pathp = path;
|
||||
args.gqa_uid = uid;
|
||||
|
||||
timeout.tv_sec = RQUOTA_GETQUOTA_TIMEOUT_SECS;
|
||||
timeout.tv_usec = 0;
|
||||
call_status = clnt_call(cl, RQUOTAPROC_GETQUOTA,
|
||||
(xdrproc_t)xdr_getquota_args, (char *)&args,
|
||||
(xdrproc_t)xdr_getquota_rslt, (char *)&result,
|
||||
timeout);
|
||||
|
||||
/* the result has been deserialized, let the client go */
|
||||
auth_destroy(cl->cl_auth);
|
||||
clnt_destroy(cl);
|
||||
|
||||
if (call_status != RPC_SUCCESS) {
|
||||
return E_CLNT_CALL | call_status;
|
||||
}
|
||||
|
||||
switch (result.status) {
|
||||
case Q_OK: {
|
||||
rquota_get_result(&result.getquota_rslt_u.gqr_rquota,
|
||||
bytes_used_r, bytes_limit_r,
|
||||
files_used_r, files_limit_r);
|
||||
return 0;
|
||||
}
|
||||
case Q_NOQUOTA:
|
||||
return E_NOQUOTA;
|
||||
case Q_EPERM:
|
||||
return E_PERM;
|
||||
default:
|
||||
return E_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
int fs_quota_nfs_ext(char *host, char *path, int id, int do_group,
|
||||
uint64_t *bytes_used_r, uint64_t *bytes_limit_r,
|
||||
uint64_t *files_used_r, uint64_t *files_limit_r)
|
||||
{
|
||||
#if defined(EXT_RQUOTAVERS) && defined(GRPQUOTA)
|
||||
struct getquota_rslt result;
|
||||
ext_getquota_args args;
|
||||
struct timeval timeout;
|
||||
enum clnt_stat call_status;
|
||||
CLIENT *cl;
|
||||
|
||||
/* clnt_create() polls for a while to establish a connection */
|
||||
cl = clnt_create(host, RQUOTAPROG, EXT_RQUOTAVERS, "udp");
|
||||
if (cl == NULL) {
|
||||
return E_CLNT_CREATE;
|
||||
}
|
||||
|
||||
/* Establish some RPC credentials */
|
||||
auth_destroy(cl->cl_auth);
|
||||
cl->cl_auth = authunix_create_default();
|
||||
|
||||
/* make the rquota call on the remote host */
|
||||
args.gqa_pathp = path;
|
||||
args.gqa_id = id;
|
||||
args.gqa_type = do_group ? GRPQUOTA : USRQUOTA;
|
||||
timeout.tv_sec = RQUOTA_GETQUOTA_TIMEOUT_SECS;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
call_status = clnt_call(cl, RQUOTAPROC_GETQUOTA,
|
||||
(xdrproc_t)xdr_ext_getquota_args, (char *)&args,
|
||||
(xdrproc_t)xdr_getquota_rslt, (char *)&result,
|
||||
timeout);
|
||||
|
||||
/* the result has been deserialized, let the client go */
|
||||
auth_destroy(cl->cl_auth);
|
||||
clnt_destroy(cl);
|
||||
|
||||
if (call_status != RPC_SUCCESS) {
|
||||
return E_CLNT_CALL | call_status;
|
||||
}
|
||||
|
||||
switch (result.status) {
|
||||
case Q_OK: {
|
||||
rquota_get_result(&result.getquota_rslt_u.gqr_rquota,
|
||||
bytes_used_r, bytes_limit_r,
|
||||
files_used_r, files_limit_r);
|
||||
return 0;
|
||||
}
|
||||
case Q_NOQUOTA:
|
||||
return E_NOQUOTA;
|
||||
case Q_EPERM:
|
||||
return E_PERM;
|
||||
default:
|
||||
return E_UNKNOWN;
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
(void)host; (void)path; (void)id; (void)do_group;
|
||||
(void)bytes_used_r; (void)bytes_limit_r;
|
||||
(void)files_used_r; (void)files_limit_r;
|
||||
return E_NOQUOTA;
|
||||
#endif
|
||||
}
|
||||
|
||||
int fs_quota_nfs(char *host, char *path, char *nfsvers, int id, int do_group,
|
||||
uint64_t *bytes_used_r, uint64_t *bytes_limit_r,
|
||||
uint64_t *files_used_r, uint64_t *files_limit_r)
|
||||
{
|
||||
/* For NFSv4, we send the filesystem path without initial /. Server
|
||||
prepends proper NFS pseudoroot automatically and uses this for
|
||||
detection of NFSv4 mounts. */
|
||||
if (strcmp(nfsvers, "nfs4") == 0) {
|
||||
while (*path == '/')
|
||||
path++;
|
||||
}
|
||||
|
||||
if (do_group)
|
||||
return fs_quota_nfs_ext(host, path, id, 1,
|
||||
bytes_used_r, bytes_limit_r,
|
||||
files_used_r, files_limit_r);
|
||||
else
|
||||
return fs_quota_nfs_user(host, path, id,
|
||||
bytes_used_r, bytes_limit_r,
|
||||
files_used_r, files_limit_r);
|
||||
}
|
||||
|
||||
92
fs_quota/src/quota_nfs.rs
Normal file
92
fs_quota/src/quota_nfs.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use crate::{FqError, FsQuota, Mtab};
|
||||
|
||||
extern "C" {
|
||||
fn fs_quota_nfs(
|
||||
host: *const c_char,
|
||||
path: *const c_char,
|
||||
nfsvers: *const c_char,
|
||||
id: c_int,
|
||||
do_group: c_int,
|
||||
bytes_used: *mut u64,
|
||||
bytes_limit: *mut u64,
|
||||
files_used: *mut u64,
|
||||
files_limit: *mut u64,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
mod ffi {
|
||||
use super::*;
|
||||
extern "C" {
|
||||
pub(crate) fn clnt_sperrno(e: c_int) -> *const c_char;
|
||||
}
|
||||
}
|
||||
|
||||
// The rpcsvc clnt_sperrno function.
|
||||
fn clnt_sperrno(e: c_int) -> &'static str {
|
||||
unsafe {
|
||||
let msg = ffi::clnt_sperrno(e);
|
||||
std::str::from_utf8(CStr::from_ptr(msg).to_bytes()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_quota(entry: &Mtab, uid: u32) -> Result<FsQuota, FqError> {
|
||||
let host = CString::new(entry.host.as_ref().unwrap().as_bytes())?;
|
||||
let path = CString::new(entry.device.as_bytes())?;
|
||||
let fstype = CString::new(entry.fstype.as_bytes())?;
|
||||
|
||||
let mut bytes_used = 0u64;
|
||||
let mut bytes_limit = 0u64;
|
||||
let mut files_used = 0u64;
|
||||
let mut files_limit = 0u64;
|
||||
|
||||
let rc = unsafe {
|
||||
fs_quota_nfs(
|
||||
host.as_ptr(),
|
||||
path.as_ptr(),
|
||||
fstype.as_ptr(),
|
||||
uid as c_int,
|
||||
0,
|
||||
&mut bytes_used as *mut u64,
|
||||
&mut bytes_limit as *mut u64,
|
||||
&mut files_used as *mut u64,
|
||||
&mut files_limit as *mut u64,
|
||||
)
|
||||
};
|
||||
|
||||
// Error mapping.
|
||||
match rc {
|
||||
0 => {},
|
||||
0x00000001 => {
|
||||
debug!("nfs: clnt_create error");
|
||||
return Err(FqError::Other);
|
||||
},
|
||||
0x00000002 => {
|
||||
return Err(FqError::NoQuota);
|
||||
},
|
||||
0x00000003 => {
|
||||
debug!("nfs: permission denied");
|
||||
return Err(FqError::PermissionDenied);
|
||||
},
|
||||
c @ 0x00100000..=0x001fffff => {
|
||||
let e = c & 0x000fffff;
|
||||
debug!("nfs: clnt_call error: {}", clnt_sperrno(e));
|
||||
return Err(FqError::Other);
|
||||
},
|
||||
e => {
|
||||
debug!("nfs: unknown error {}", e);
|
||||
return Err(FqError::Other);
|
||||
},
|
||||
}
|
||||
|
||||
let m = |v| if v == 0xffffffffffffffff { None } else { Some(v) };
|
||||
let res = FsQuota {
|
||||
bytes_used: bytes_used,
|
||||
bytes_limit: m(bytes_limit),
|
||||
files_used: files_used,
|
||||
files_limit: m(files_limit),
|
||||
};
|
||||
return Ok(res);
|
||||
}
|
||||
139
fs_quota/src/rquota.x
Normal file
139
fs_quota/src/rquota.x
Normal file
@@ -0,0 +1,139 @@
|
||||
/* @(#)rquota.x 2.1 88/08/01 4.0 RPCSRC */
|
||||
/* @(#)rquota.x 1.2 87/09/20 Copyr 1987 Sun Micro */
|
||||
|
||||
/*
|
||||
* Remote quota protocol
|
||||
* Requires unix authentication
|
||||
*/
|
||||
|
||||
const RQ_PATHLEN = 1024;
|
||||
|
||||
struct sq_dqblk {
|
||||
unsigned int rq_bhardlimit; /* absolute limit on disk blks alloc */
|
||||
unsigned int rq_bsoftlimit; /* preferred limit on disk blks */
|
||||
unsigned int rq_curblocks; /* current block count */
|
||||
unsigned int rq_fhardlimit; /* absolute limit on allocated files */
|
||||
unsigned int rq_fsoftlimit; /* preferred file limit */
|
||||
unsigned int rq_curfiles; /* current # allocated files */
|
||||
unsigned int rq_btimeleft; /* time left for excessive disk use */
|
||||
unsigned int rq_ftimeleft; /* time left for excessive files */
|
||||
};
|
||||
|
||||
struct getquota_args {
|
||||
string gqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
|
||||
int gqa_uid; /* Inquire about quota for uid */
|
||||
};
|
||||
|
||||
struct setquota_args {
|
||||
int sqa_qcmd;
|
||||
string sqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
|
||||
int sqa_id; /* Set quota for uid */
|
||||
sq_dqblk sqa_dqblk;
|
||||
};
|
||||
|
||||
struct ext_getquota_args {
|
||||
string gqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
|
||||
int gqa_type; /* Type of quota info is needed about */
|
||||
int gqa_id; /* Inquire about quota for id */
|
||||
};
|
||||
|
||||
struct ext_setquota_args {
|
||||
int sqa_qcmd;
|
||||
string sqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
|
||||
int sqa_id; /* Set quota for id */
|
||||
int sqa_type; /* Type of quota to set */
|
||||
sq_dqblk sqa_dqblk;
|
||||
};
|
||||
|
||||
/*
|
||||
* remote quota structure
|
||||
*/
|
||||
struct rquota {
|
||||
int rq_bsize; /* block size for block counts */
|
||||
bool rq_active; /* indicates whether quota is active */
|
||||
unsigned int rq_bhardlimit; /* absolute limit on disk blks alloc */
|
||||
unsigned int rq_bsoftlimit; /* preferred limit on disk blks */
|
||||
unsigned int rq_curblocks; /* current block count */
|
||||
unsigned int rq_fhardlimit; /* absolute limit on allocated files */
|
||||
unsigned int rq_fsoftlimit; /* preferred file limit */
|
||||
unsigned int rq_curfiles; /* current # allocated files */
|
||||
unsigned int rq_btimeleft; /* time left for excessive disk use */
|
||||
unsigned int rq_ftimeleft; /* time left for excessive files */
|
||||
};
|
||||
|
||||
enum qr_status {
|
||||
Q_OK = 1, /* quota returned */
|
||||
Q_NOQUOTA = 2, /* noquota for uid */
|
||||
Q_EPERM = 3 /* no permission to access quota */
|
||||
};
|
||||
|
||||
union getquota_rslt switch (qr_status status) {
|
||||
case Q_OK:
|
||||
rquota gqr_rquota; /* valid if status == Q_OK */
|
||||
case Q_NOQUOTA:
|
||||
void;
|
||||
case Q_EPERM:
|
||||
void;
|
||||
};
|
||||
|
||||
union setquota_rslt switch (qr_status status) {
|
||||
case Q_OK:
|
||||
rquota sqr_rquota; /* valid if status == Q_OK */
|
||||
case Q_NOQUOTA:
|
||||
void;
|
||||
case Q_EPERM:
|
||||
void;
|
||||
};
|
||||
|
||||
program RQUOTAPROG {
|
||||
version RQUOTAVERS {
|
||||
/*
|
||||
* Get all quotas
|
||||
*/
|
||||
getquota_rslt
|
||||
RQUOTAPROC_GETQUOTA(getquota_args) = 1;
|
||||
|
||||
/*
|
||||
* Get active quotas only
|
||||
*/
|
||||
getquota_rslt
|
||||
RQUOTAPROC_GETACTIVEQUOTA(getquota_args) = 2;
|
||||
|
||||
/*
|
||||
* Set all quotas
|
||||
*/
|
||||
setquota_rslt
|
||||
RQUOTAPROC_SETQUOTA(setquota_args) = 3;
|
||||
|
||||
/*
|
||||
* Get active quotas only
|
||||
*/
|
||||
setquota_rslt
|
||||
RQUOTAPROC_SETACTIVEQUOTA(setquota_args) = 4;
|
||||
} = 1;
|
||||
version EXT_RQUOTAVERS {
|
||||
/*
|
||||
* Get all quotas
|
||||
*/
|
||||
getquota_rslt
|
||||
RQUOTAPROC_GETQUOTA(ext_getquota_args) = 1;
|
||||
|
||||
/*
|
||||
* Get active quotas only
|
||||
*/
|
||||
getquota_rslt
|
||||
RQUOTAPROC_GETACTIVEQUOTA(ext_getquota_args) = 2;
|
||||
|
||||
/*
|
||||
* Set all quotas
|
||||
*/
|
||||
setquota_rslt
|
||||
RQUOTAPROC_SETQUOTA(ext_setquota_args) = 3;
|
||||
|
||||
/*
|
||||
* Set active quotas only
|
||||
*/
|
||||
setquota_rslt
|
||||
RQUOTAPROC_SETACTIVEQUOTA(ext_setquota_args) = 4;
|
||||
} = 2;
|
||||
} = 100011;
|
||||
Reference in New Issue
Block a user