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").unwrap();
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).unwrap();
14//! ```
15use crate::errno::Errno;
16use crate::{NixPath, Result};
17use libc::{self, c_char, c_int};
18use std::default::Default;
19use std::{mem, ptr};
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 =
124            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
125        if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) {
126            Some(self.0.dqb_bhardlimit)
127        } else {
128            None
129        }
130    }
131
132    /// Set the absolute limit on disk quota blocks allocated.
133    pub fn set_blocks_hard_limit(&mut self, limit: u64) {
134        self.0.dqb_bhardlimit = limit;
135    }
136
137    /// Preferred limit on disk quota blocks
138    pub fn blocks_soft_limit(&self) -> Option<u64> {
139        let valid_fields =
140            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
141        if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) {
142            Some(self.0.dqb_bsoftlimit)
143        } else {
144            None
145        }
146    }
147
148    /// Set the preferred limit on disk quota blocks allocated.
149    pub fn set_blocks_soft_limit(&mut self, limit: u64) {
150        self.0.dqb_bsoftlimit = limit;
151    }
152
153    /// Current occupied space (bytes).
154    pub fn occupied_space(&self) -> Option<u64> {
155        let valid_fields =
156            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
157        if valid_fields.contains(QuotaValidFlags::QIF_SPACE) {
158            Some(self.0.dqb_curspace)
159        } else {
160            None
161        }
162    }
163
164    /// Maximum number of allocated inodes.
165    pub fn inodes_hard_limit(&self) -> Option<u64> {
166        let valid_fields =
167            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
168        if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) {
169            Some(self.0.dqb_ihardlimit)
170        } else {
171            None
172        }
173    }
174
175    /// Set the maximum number of allocated inodes.
176    pub fn set_inodes_hard_limit(&mut self, limit: u64) {
177        self.0.dqb_ihardlimit = limit;
178    }
179
180    /// Preferred inode limit
181    pub fn inodes_soft_limit(&self) -> Option<u64> {
182        let valid_fields =
183            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
184        if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) {
185            Some(self.0.dqb_isoftlimit)
186        } else {
187            None
188        }
189    }
190
191    /// Set the preferred limit of allocated inodes.
192    pub fn set_inodes_soft_limit(&mut self, limit: u64) {
193        self.0.dqb_isoftlimit = limit;
194    }
195
196    /// Current number of allocated inodes.
197    pub fn allocated_inodes(&self) -> Option<u64> {
198        let valid_fields =
199            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
200        if valid_fields.contains(QuotaValidFlags::QIF_INODES) {
201            Some(self.0.dqb_curinodes)
202        } else {
203            None
204        }
205    }
206
207    /// Time limit for excessive disk use.
208    pub fn block_time_limit(&self) -> Option<u64> {
209        let valid_fields =
210            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
211        if valid_fields.contains(QuotaValidFlags::QIF_BTIME) {
212            Some(self.0.dqb_btime)
213        } else {
214            None
215        }
216    }
217
218    /// Set the time limit for excessive disk use.
219    pub fn set_block_time_limit(&mut self, limit: u64) {
220        self.0.dqb_btime = limit;
221    }
222
223    /// Time limit for excessive files.
224    pub fn inode_time_limit(&self) -> Option<u64> {
225        let valid_fields =
226            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
227        if valid_fields.contains(QuotaValidFlags::QIF_ITIME) {
228            Some(self.0.dqb_itime)
229        } else {
230            None
231        }
232    }
233
234    /// Set the time limit for excessive files.
235    pub fn set_inode_time_limit(&mut self, limit: u64) {
236        self.0.dqb_itime = limit;
237    }
238}
239
240fn quotactl<P: ?Sized + NixPath>(
241    cmd: QuotaCmd,
242    special: Option<&P>,
243    id: c_int,
244    addr: *mut c_char,
245) -> Result<()> {
246    unsafe {
247        Errno::clear();
248        let res = match special {
249            Some(dev) => dev.with_nix_path(|path| {
250                libc::quotactl(cmd.as_int(), path.as_ptr(), id, addr)
251            }),
252            None => Ok(libc::quotactl(cmd.as_int(), ptr::null(), id, addr)),
253        }?;
254
255        Errno::result(res).map(drop)
256    }
257}
258
259/// Turn on disk quotas for a block device.
260pub fn quotactl_on<P: ?Sized + NixPath>(
261    which: QuotaType,
262    special: &P,
263    format: QuotaFmt,
264    quota_file: &P,
265) -> Result<()> {
266    quota_file.with_nix_path(|path| {
267        let mut path_copy = path.to_bytes_with_nul().to_owned();
268        let p: *mut c_char = path_copy.as_mut_ptr() as *mut c_char;
269        quotactl(
270            QuotaCmd(QuotaSubCmd::Q_QUOTAON, which),
271            Some(special),
272            format as c_int,
273            p,
274        )
275    })?
276}
277
278/// Disable disk quotas for a block device.
279pub fn quotactl_off<P: ?Sized + NixPath>(
280    which: QuotaType,
281    special: &P,
282) -> Result<()> {
283    quotactl(
284        QuotaCmd(QuotaSubCmd::Q_QUOTAOFF, which),
285        Some(special),
286        0,
287        ptr::null_mut(),
288    )
289}
290
291/// Update the on-disk copy of quota usages for a filesystem.
292///
293/// If `special` is `None`, then all file systems with active quotas are sync'd.
294pub fn quotactl_sync<P: ?Sized + NixPath>(
295    which: QuotaType,
296    special: Option<&P>,
297) -> Result<()> {
298    quotactl(
299        QuotaCmd(QuotaSubCmd::Q_SYNC, which),
300        special,
301        0,
302        ptr::null_mut(),
303    )
304}
305
306/// Get disk quota limits and current usage for the given user/group id.
307pub fn quotactl_get<P: ?Sized + NixPath>(
308    which: QuotaType,
309    special: &P,
310    id: c_int,
311) -> Result<Dqblk> {
312    let mut dqblk = mem::MaybeUninit::uninit();
313    quotactl(
314        QuotaCmd(QuotaSubCmd::Q_GETQUOTA, which),
315        Some(special),
316        id,
317        dqblk.as_mut_ptr() as *mut c_char,
318    )?;
319    Ok(unsafe { Dqblk(dqblk.assume_init()) })
320}
321
322/// Configure quota values for the specified fields for a given user/group id.
323pub fn quotactl_set<P: ?Sized + NixPath>(
324    which: QuotaType,
325    special: &P,
326    id: c_int,
327    dqblk: &Dqblk,
328    fields: QuotaValidFlags,
329) -> Result<()> {
330    let mut dqblk_copy = *dqblk;
331    dqblk_copy.0.dqb_valid = fields.bits();
332    quotactl(
333        QuotaCmd(QuotaSubCmd::Q_SETQUOTA, which),
334        Some(special),
335        id,
336        &mut dqblk_copy as *mut _ as *mut c_char,
337    )
338}