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} 339