This commit is contained in:
2022-12-30 20:55:14 +08:00
parent 5a9c09d673
commit 118d6a5a1d
53 changed files with 4720 additions and 1 deletions

7
fs_quota/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
/target/
**/*.rs.bk
Cargo.lock
src/rquota.h
src/rquota_xdr.c

30
fs_quota/Cargo.toml Normal file
View 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
View 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
View 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
View 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");
}
}

View 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
View 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

View 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
View 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
View 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)
}

View 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
View 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
View 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
View 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;