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