nix/sys/
quota.rs

1//! Set and configure disk quotas for users, groups, or projects.
2//!
3//! # Examples
4//!
5//! Enabling and setting a quota:
6//!
7//! ```rust,no_run
8//! # use nix::sys::quota::{Dqblk, quotactl_on, quotactl_set, QuotaFmt, QuotaType, QuotaValidFlags};
9//! quotactl_on(QuotaType::USRQUOTA, "/dev/sda1", QuotaFmt::QFMT_VFS_V1, "aquota.user");
10//! let mut dqblk: Dqblk = Default::default();
11//! dqblk.set_blocks_hard_limit(10000);
12//! dqblk.set_blocks_soft_limit(8000);
13//! quotactl_set(QuotaType::USRQUOTA, "/dev/sda1", 50, &dqblk, QuotaValidFlags::QIF_BLIMITS);
14//! ```
15use std::default::Default;
16use std::{mem, ptr};
17use libc::{self, c_int, c_char};
18use crate::{Result, NixPath};
19use crate::errno::Errno;
20
21struct QuotaCmd(QuotaSubCmd, QuotaType);
22
23impl QuotaCmd {
24    #[allow(unused_unsafe)]
25    fn as_int(&self) -> c_int {
26        unsafe { libc::QCMD(self.0 as i32, self.1 as i32) }
27    }
28}
29
30// linux quota version >= 2
31libc_enum!{
32    #[repr(i32)]
33    enum QuotaSubCmd {
34        Q_SYNC,
35        Q_QUOTAON,
36        Q_QUOTAOFF,
37        Q_GETQUOTA,
38        Q_SETQUOTA,
39    }
40}
41
42libc_enum!{
43    /// The scope of the quota.
44    #[repr(i32)]
45    #[non_exhaustive]
46    pub enum QuotaType {
47        /// Specify a user quota
48        USRQUOTA,
49        /// Specify a group quota
50        GRPQUOTA,
51    }
52}
53
54libc_enum!{
55    /// The type of quota format to use.
56    #[repr(i32)]
57    #[non_exhaustive]
58    pub enum QuotaFmt {
59        /// Use the original quota format.
60        QFMT_VFS_OLD,
61        /// Use the standard VFS v0 quota format.
62        ///
63        /// Handles 32-bit UIDs/GIDs and quota limits up to 2<sup>32</sup> bytes/2<sup>32</sup> inodes.
64        QFMT_VFS_V0,
65        /// Use the VFS v1 quota format.
66        ///
67        /// Handles 32-bit UIDs/GIDs and quota limits of 2<sup>64</sup> bytes/2<sup>64</sup> inodes.
68        QFMT_VFS_V1,
69    }
70}
71
72libc_bitflags!(
73    /// Indicates the quota fields that are valid to read from.
74    #[derive(Default)]
75    pub struct QuotaValidFlags: u32 {
76        /// The block hard & soft limit fields.
77        QIF_BLIMITS;
78        /// The current space field.
79        QIF_SPACE;
80        /// The inode hard & soft limit fields.
81        QIF_ILIMITS;
82        /// The current inodes field.
83        QIF_INODES;
84        /// The disk use time limit field.
85        QIF_BTIME;
86        /// The file quote time limit field.
87        QIF_ITIME;
88        /// All block & inode limits.
89        QIF_LIMITS;
90        /// The space & inodes usage fields.
91        QIF_USAGE;
92        /// The time limit fields.
93        QIF_TIMES;
94        /// All fields.
95        QIF_ALL;
96    }
97);
98
99/// Wrapper type for `if_dqblk`
100#[repr(transparent)]
101#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
102pub struct Dqblk(libc::dqblk);
103
104impl Default for Dqblk {
105    fn default() -> Dqblk {
106        Dqblk(libc::dqblk {
107            dqb_bhardlimit: 0,
108            dqb_bsoftlimit: 0,
109            dqb_curspace: 0,
110            dqb_ihardlimit: 0,
111            dqb_isoftlimit: 0,
112            dqb_curinodes: 0,
113            dqb_btime: 0,
114            dqb_itime: 0,
115            dqb_valid: 0,
116        })
117    }
118}
119
120impl Dqblk {
121    /// The absolute limit on disk quota blocks allocated.
122    pub fn blocks_hard_limit(&self) -> Option<u64> {
123        let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
124        if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) {
125            Some(self.0.dqb_bhardlimit)
126        } else {
127            None
128        }
129    }
130
131    /// Set the absolute limit on disk quota blocks allocated.
132    pub fn set_blocks_hard_limit(&mut self, limit: u64) {
133        self.0.dqb_bhardlimit = limit;
134    }
135
136    /// Preferred limit on disk quota blocks
137    pub fn blocks_soft_limit(&self) -> Option<u64> {
138        let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
139        if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) {
140            Some(self.0.dqb_bsoftlimit)
141        } else {
142            None
143        }
144    }
145
146    /// Set the preferred limit on disk quota blocks allocated.
147    pub fn set_blocks_soft_limit(&mut self, limit: u64) {
148        self.0.dqb_bsoftlimit = limit;
149    }
150
151    /// Current occupied space (bytes).
152    pub fn occupied_space(&self) -> Option<u64> {
153        let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
154        if valid_fields.contains(QuotaValidFlags::QIF_SPACE) {
155            Some(self.0.dqb_curspace)
156        } else {
157            None
158        }
159    }
160
161    /// Maximum number of allocated inodes.
162    pub fn inodes_hard_limit(&self) -> Option<u64> {
163        let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
164        if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) {
165            Some(self.0.dqb_ihardlimit)
166        } else {
167            None
168        }
169    }
170
171    /// Set the maximum number of allocated inodes.
172    pub fn set_inodes_hard_limit(&mut self, limit: u64) {
173        self.0.dqb_ihardlimit = limit;
174    }
175
176    /// Preferred inode limit
177    pub fn inodes_soft_limit(&self) -> Option<u64> {
178        let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
179        if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) {
180            Some(self.0.dqb_isoftlimit)
181        } else {
182            None
183        }
184    }
185
186    /// Set the preferred limit of allocated inodes.
187    pub fn set_inodes_soft_limit(&mut self, limit: u64) {
188        self.0.dqb_isoftlimit = limit;
189    }
190
191    /// Current number of allocated inodes.
192    pub fn allocated_inodes(&self) -> Option<u64> {
193        let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
194        if valid_fields.contains(QuotaValidFlags::QIF_INODES) {
195            Some(self.0.dqb_curinodes)
196        } else {
197            None
198        }
199    }
200
201    /// Time limit for excessive disk use.
202    pub fn block_time_limit(&self) -> Option<u64> {
203        let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
204        if valid_fields.contains(QuotaValidFlags::QIF_BTIME) {
205            Some(self.0.dqb_btime)
206        } else {
207            None
208        }
209    }
210
211    /// Set the time limit for excessive disk use.
212    pub fn set_block_time_limit(&mut self, limit: u64) {
213        self.0.dqb_btime = limit;
214    }
215
216    /// Time limit for excessive files.
217    pub fn inode_time_limit(&self) -> Option<u64> {
218        let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
219        if valid_fields.contains(QuotaValidFlags::QIF_ITIME) {
220            Some(self.0.dqb_itime)
221        } else {
222            None
223        }
224    }
225
226    /// Set the time limit for excessive files.
227    pub fn set_inode_time_limit(&mut self, limit: u64) {
228        self.0.dqb_itime = limit;
229    }
230}
231
232fn quotactl<P: ?Sized + NixPath>(cmd: QuotaCmd, special: Option<&P>, id: c_int, addr: *mut c_char) -> Result<()> {
233    unsafe {
234        Errno::clear();
235        let res = match special {
236            Some(dev) => dev.with_nix_path(|path| libc::quotactl(cmd.as_int(), path.as_ptr(), id, addr)),
237            None => Ok(libc::quotactl(cmd.as_int(), ptr::null(), id, addr)),
238        }?;
239
240        Errno::result(res).map(drop)
241    }
242}
243
244/// Turn on disk quotas for a block device.
245pub fn quotactl_on<P: ?Sized + NixPath>(which: QuotaType, special: &P, format: QuotaFmt, quota_file: &P) -> Result<()> {
246    quota_file.with_nix_path(|path| {
247        let mut path_copy = path.to_bytes_with_nul().to_owned();
248        let p: *mut c_char = path_copy.as_mut_ptr() as *mut c_char;
249        quotactl(QuotaCmd(QuotaSubCmd::Q_QUOTAON, which), Some(special), format as c_int, p)
250    })?
251}
252
253/// Disable disk quotas for a block device.
254pub fn quotactl_off<P: ?Sized + NixPath>(which: QuotaType, special: &P) -> Result<()> {
255    quotactl(QuotaCmd(QuotaSubCmd::Q_QUOTAOFF, which), Some(special), 0, ptr::null_mut())
256}
257
258/// Update the on-disk copy of quota usages for a filesystem.
259///
260/// If `special` is `None`, then all file systems with active quotas are sync'd.
261pub fn quotactl_sync<P: ?Sized + NixPath>(which: QuotaType, special: Option<&P>) -> Result<()> {
262    quotactl(QuotaCmd(QuotaSubCmd::Q_SYNC, which), special, 0, ptr::null_mut())
263}
264
265/// Get disk quota limits and current usage for the given user/group id.
266pub fn quotactl_get<P: ?Sized + NixPath>(which: QuotaType, special: &P, id: c_int) -> Result<Dqblk> {
267    let mut dqblk = mem::MaybeUninit::uninit();
268    quotactl(QuotaCmd(QuotaSubCmd::Q_GETQUOTA, which), Some(special), id, dqblk.as_mut_ptr() as *mut c_char)?;
269    Ok(unsafe{ Dqblk(dqblk.assume_init())})
270}
271
272/// Configure quota values for the specified fields for a given user/group id.
273pub fn quotactl_set<P: ?Sized + NixPath>(which: QuotaType, special: &P, id: c_int, dqblk: &Dqblk, fields: QuotaValidFlags) -> Result<()> {
274    let mut dqblk_copy = *dqblk;
275    dqblk_copy.0.dqb_valid = fields.bits();
276    quotactl(QuotaCmd(QuotaSubCmd::Q_SETQUOTA, which), Some(special), id, &mut dqblk_copy as *mut _ as *mut c_char)
277}