• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
24     #[allow(unused_unsafe)]
as_int(&self) -> c_int25     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
31 libc_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 
42 libc_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 
54 libc_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 
72 libc_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)]
102 pub struct Dqblk(libc::dqblk);
103 
104 impl Default for Dqblk {
default() -> Dqblk105     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 
120 impl Dqblk {
121     /// The absolute limit on disk quota blocks allocated.
blocks_hard_limit(&self) -> Option<u64>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.
set_blocks_hard_limit(&mut self, limit: u64)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
blocks_soft_limit(&self) -> Option<u64>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.
set_blocks_soft_limit(&mut self, limit: u64)149     pub fn set_blocks_soft_limit(&mut self, limit: u64) {
150         self.0.dqb_bsoftlimit = limit;
151     }
152 
153     /// Current occupied space (bytes).
occupied_space(&self) -> Option<u64>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.
inodes_hard_limit(&self) -> Option<u64>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.
set_inodes_hard_limit(&mut self, limit: u64)176     pub fn set_inodes_hard_limit(&mut self, limit: u64) {
177         self.0.dqb_ihardlimit = limit;
178     }
179 
180     /// Preferred inode limit
inodes_soft_limit(&self) -> Option<u64>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.
set_inodes_soft_limit(&mut self, limit: u64)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.
allocated_inodes(&self) -> Option<u64>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.
block_time_limit(&self) -> Option<u64>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.
set_block_time_limit(&mut self, limit: u64)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.
inode_time_limit(&self) -> Option<u64>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.
set_inode_time_limit(&mut self, limit: u64)235     pub fn set_inode_time_limit(&mut self, limit: u64) {
236         self.0.dqb_itime = limit;
237     }
238 }
239 
quotactl<P: ?Sized + NixPath>( cmd: QuotaCmd, special: Option<&P>, id: c_int, addr: *mut c_char, ) -> Result<()>240 fn 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.
quotactl_on<P: ?Sized + NixPath>( which: QuotaType, special: &P, format: QuotaFmt, quota_file: &P, ) -> Result<()>260 pub 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.
quotactl_off<P: ?Sized + NixPath>( which: QuotaType, special: &P, ) -> Result<()>279 pub 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.
quotactl_sync<P: ?Sized + NixPath>( which: QuotaType, special: Option<&P>, ) -> Result<()>294 pub 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.
quotactl_get<P: ?Sized + NixPath>( which: QuotaType, special: &P, id: c_int, ) -> Result<Dqblk>307 pub 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.
quotactl_set<P: ?Sized + NixPath>( which: QuotaType, special: &P, id: c_int, dqblk: &Dqblk, fields: QuotaValidFlags, ) -> Result<()>323 pub 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 }
339