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