13da5c369Sopenharmony_ci//! Set and configure disk quotas for users, groups, or projects.
23da5c369Sopenharmony_ci//!
33da5c369Sopenharmony_ci//! # Examples
43da5c369Sopenharmony_ci//!
53da5c369Sopenharmony_ci//! Enabling and setting a quota:
63da5c369Sopenharmony_ci//!
73da5c369Sopenharmony_ci//! ```rust,no_run
83da5c369Sopenharmony_ci//! # use nix::sys::quota::{Dqblk, quotactl_on, quotactl_set, QuotaFmt, QuotaType, QuotaValidFlags};
93da5c369Sopenharmony_ci//! quotactl_on(QuotaType::USRQUOTA, "/dev/sda1", QuotaFmt::QFMT_VFS_V1, "aquota.user").unwrap();
103da5c369Sopenharmony_ci//! let mut dqblk: Dqblk = Default::default();
113da5c369Sopenharmony_ci//! dqblk.set_blocks_hard_limit(10000);
123da5c369Sopenharmony_ci//! dqblk.set_blocks_soft_limit(8000);
133da5c369Sopenharmony_ci//! quotactl_set(QuotaType::USRQUOTA, "/dev/sda1", 50, &dqblk, QuotaValidFlags::QIF_BLIMITS).unwrap();
143da5c369Sopenharmony_ci//! ```
153da5c369Sopenharmony_ciuse crate::errno::Errno;
163da5c369Sopenharmony_ciuse crate::{NixPath, Result};
173da5c369Sopenharmony_ciuse libc::{self, c_char, c_int};
183da5c369Sopenharmony_ciuse std::default::Default;
193da5c369Sopenharmony_ciuse std::{mem, ptr};
203da5c369Sopenharmony_ci
213da5c369Sopenharmony_cistruct QuotaCmd(QuotaSubCmd, QuotaType);
223da5c369Sopenharmony_ci
233da5c369Sopenharmony_ciimpl QuotaCmd {
243da5c369Sopenharmony_ci    #[allow(unused_unsafe)]
253da5c369Sopenharmony_ci    fn as_int(&self) -> c_int {
263da5c369Sopenharmony_ci        unsafe { libc::QCMD(self.0 as i32, self.1 as i32) }
273da5c369Sopenharmony_ci    }
283da5c369Sopenharmony_ci}
293da5c369Sopenharmony_ci
303da5c369Sopenharmony_ci// linux quota version >= 2
313da5c369Sopenharmony_cilibc_enum! {
323da5c369Sopenharmony_ci    #[repr(i32)]
333da5c369Sopenharmony_ci    enum QuotaSubCmd {
343da5c369Sopenharmony_ci        Q_SYNC,
353da5c369Sopenharmony_ci        Q_QUOTAON,
363da5c369Sopenharmony_ci        Q_QUOTAOFF,
373da5c369Sopenharmony_ci        Q_GETQUOTA,
383da5c369Sopenharmony_ci        Q_SETQUOTA,
393da5c369Sopenharmony_ci    }
403da5c369Sopenharmony_ci}
413da5c369Sopenharmony_ci
423da5c369Sopenharmony_cilibc_enum! {
433da5c369Sopenharmony_ci    /// The scope of the quota.
443da5c369Sopenharmony_ci    #[repr(i32)]
453da5c369Sopenharmony_ci    #[non_exhaustive]
463da5c369Sopenharmony_ci    pub enum QuotaType {
473da5c369Sopenharmony_ci        /// Specify a user quota
483da5c369Sopenharmony_ci        USRQUOTA,
493da5c369Sopenharmony_ci        /// Specify a group quota
503da5c369Sopenharmony_ci        GRPQUOTA,
513da5c369Sopenharmony_ci    }
523da5c369Sopenharmony_ci}
533da5c369Sopenharmony_ci
543da5c369Sopenharmony_cilibc_enum! {
553da5c369Sopenharmony_ci    /// The type of quota format to use.
563da5c369Sopenharmony_ci    #[repr(i32)]
573da5c369Sopenharmony_ci    #[non_exhaustive]
583da5c369Sopenharmony_ci    pub enum QuotaFmt {
593da5c369Sopenharmony_ci        /// Use the original quota format.
603da5c369Sopenharmony_ci        QFMT_VFS_OLD,
613da5c369Sopenharmony_ci        /// Use the standard VFS v0 quota format.
623da5c369Sopenharmony_ci        ///
633da5c369Sopenharmony_ci        /// Handles 32-bit UIDs/GIDs and quota limits up to 2<sup>32</sup> bytes/2<sup>32</sup> inodes.
643da5c369Sopenharmony_ci        QFMT_VFS_V0,
653da5c369Sopenharmony_ci        /// Use the VFS v1 quota format.
663da5c369Sopenharmony_ci        ///
673da5c369Sopenharmony_ci        /// Handles 32-bit UIDs/GIDs and quota limits of 2<sup>64</sup> bytes/2<sup>64</sup> inodes.
683da5c369Sopenharmony_ci        QFMT_VFS_V1,
693da5c369Sopenharmony_ci    }
703da5c369Sopenharmony_ci}
713da5c369Sopenharmony_ci
723da5c369Sopenharmony_cilibc_bitflags!(
733da5c369Sopenharmony_ci    /// Indicates the quota fields that are valid to read from.
743da5c369Sopenharmony_ci    #[derive(Default)]
753da5c369Sopenharmony_ci    pub struct QuotaValidFlags: u32 {
763da5c369Sopenharmony_ci        /// The block hard & soft limit fields.
773da5c369Sopenharmony_ci        QIF_BLIMITS;
783da5c369Sopenharmony_ci        /// The current space field.
793da5c369Sopenharmony_ci        QIF_SPACE;
803da5c369Sopenharmony_ci        /// The inode hard & soft limit fields.
813da5c369Sopenharmony_ci        QIF_ILIMITS;
823da5c369Sopenharmony_ci        /// The current inodes field.
833da5c369Sopenharmony_ci        QIF_INODES;
843da5c369Sopenharmony_ci        /// The disk use time limit field.
853da5c369Sopenharmony_ci        QIF_BTIME;
863da5c369Sopenharmony_ci        /// The file quote time limit field.
873da5c369Sopenharmony_ci        QIF_ITIME;
883da5c369Sopenharmony_ci        /// All block & inode limits.
893da5c369Sopenharmony_ci        QIF_LIMITS;
903da5c369Sopenharmony_ci        /// The space & inodes usage fields.
913da5c369Sopenharmony_ci        QIF_USAGE;
923da5c369Sopenharmony_ci        /// The time limit fields.
933da5c369Sopenharmony_ci        QIF_TIMES;
943da5c369Sopenharmony_ci        /// All fields.
953da5c369Sopenharmony_ci        QIF_ALL;
963da5c369Sopenharmony_ci    }
973da5c369Sopenharmony_ci);
983da5c369Sopenharmony_ci
993da5c369Sopenharmony_ci/// Wrapper type for `if_dqblk`
1003da5c369Sopenharmony_ci#[repr(transparent)]
1013da5c369Sopenharmony_ci#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1023da5c369Sopenharmony_cipub struct Dqblk(libc::dqblk);
1033da5c369Sopenharmony_ci
1043da5c369Sopenharmony_ciimpl Default for Dqblk {
1053da5c369Sopenharmony_ci    fn default() -> Dqblk {
1063da5c369Sopenharmony_ci        Dqblk(libc::dqblk {
1073da5c369Sopenharmony_ci            dqb_bhardlimit: 0,
1083da5c369Sopenharmony_ci            dqb_bsoftlimit: 0,
1093da5c369Sopenharmony_ci            dqb_curspace: 0,
1103da5c369Sopenharmony_ci            dqb_ihardlimit: 0,
1113da5c369Sopenharmony_ci            dqb_isoftlimit: 0,
1123da5c369Sopenharmony_ci            dqb_curinodes: 0,
1133da5c369Sopenharmony_ci            dqb_btime: 0,
1143da5c369Sopenharmony_ci            dqb_itime: 0,
1153da5c369Sopenharmony_ci            dqb_valid: 0,
1163da5c369Sopenharmony_ci        })
1173da5c369Sopenharmony_ci    }
1183da5c369Sopenharmony_ci}
1193da5c369Sopenharmony_ci
1203da5c369Sopenharmony_ciimpl Dqblk {
1213da5c369Sopenharmony_ci    /// The absolute limit on disk quota blocks allocated.
1223da5c369Sopenharmony_ci    pub fn blocks_hard_limit(&self) -> Option<u64> {
1233da5c369Sopenharmony_ci        let valid_fields =
1243da5c369Sopenharmony_ci            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
1253da5c369Sopenharmony_ci        if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) {
1263da5c369Sopenharmony_ci            Some(self.0.dqb_bhardlimit)
1273da5c369Sopenharmony_ci        } else {
1283da5c369Sopenharmony_ci            None
1293da5c369Sopenharmony_ci        }
1303da5c369Sopenharmony_ci    }
1313da5c369Sopenharmony_ci
1323da5c369Sopenharmony_ci    /// Set the absolute limit on disk quota blocks allocated.
1333da5c369Sopenharmony_ci    pub fn set_blocks_hard_limit(&mut self, limit: u64) {
1343da5c369Sopenharmony_ci        self.0.dqb_bhardlimit = limit;
1353da5c369Sopenharmony_ci    }
1363da5c369Sopenharmony_ci
1373da5c369Sopenharmony_ci    /// Preferred limit on disk quota blocks
1383da5c369Sopenharmony_ci    pub fn blocks_soft_limit(&self) -> Option<u64> {
1393da5c369Sopenharmony_ci        let valid_fields =
1403da5c369Sopenharmony_ci            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
1413da5c369Sopenharmony_ci        if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) {
1423da5c369Sopenharmony_ci            Some(self.0.dqb_bsoftlimit)
1433da5c369Sopenharmony_ci        } else {
1443da5c369Sopenharmony_ci            None
1453da5c369Sopenharmony_ci        }
1463da5c369Sopenharmony_ci    }
1473da5c369Sopenharmony_ci
1483da5c369Sopenharmony_ci    /// Set the preferred limit on disk quota blocks allocated.
1493da5c369Sopenharmony_ci    pub fn set_blocks_soft_limit(&mut self, limit: u64) {
1503da5c369Sopenharmony_ci        self.0.dqb_bsoftlimit = limit;
1513da5c369Sopenharmony_ci    }
1523da5c369Sopenharmony_ci
1533da5c369Sopenharmony_ci    /// Current occupied space (bytes).
1543da5c369Sopenharmony_ci    pub fn occupied_space(&self) -> Option<u64> {
1553da5c369Sopenharmony_ci        let valid_fields =
1563da5c369Sopenharmony_ci            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
1573da5c369Sopenharmony_ci        if valid_fields.contains(QuotaValidFlags::QIF_SPACE) {
1583da5c369Sopenharmony_ci            Some(self.0.dqb_curspace)
1593da5c369Sopenharmony_ci        } else {
1603da5c369Sopenharmony_ci            None
1613da5c369Sopenharmony_ci        }
1623da5c369Sopenharmony_ci    }
1633da5c369Sopenharmony_ci
1643da5c369Sopenharmony_ci    /// Maximum number of allocated inodes.
1653da5c369Sopenharmony_ci    pub fn inodes_hard_limit(&self) -> Option<u64> {
1663da5c369Sopenharmony_ci        let valid_fields =
1673da5c369Sopenharmony_ci            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
1683da5c369Sopenharmony_ci        if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) {
1693da5c369Sopenharmony_ci            Some(self.0.dqb_ihardlimit)
1703da5c369Sopenharmony_ci        } else {
1713da5c369Sopenharmony_ci            None
1723da5c369Sopenharmony_ci        }
1733da5c369Sopenharmony_ci    }
1743da5c369Sopenharmony_ci
1753da5c369Sopenharmony_ci    /// Set the maximum number of allocated inodes.
1763da5c369Sopenharmony_ci    pub fn set_inodes_hard_limit(&mut self, limit: u64) {
1773da5c369Sopenharmony_ci        self.0.dqb_ihardlimit = limit;
1783da5c369Sopenharmony_ci    }
1793da5c369Sopenharmony_ci
1803da5c369Sopenharmony_ci    /// Preferred inode limit
1813da5c369Sopenharmony_ci    pub fn inodes_soft_limit(&self) -> Option<u64> {
1823da5c369Sopenharmony_ci        let valid_fields =
1833da5c369Sopenharmony_ci            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
1843da5c369Sopenharmony_ci        if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) {
1853da5c369Sopenharmony_ci            Some(self.0.dqb_isoftlimit)
1863da5c369Sopenharmony_ci        } else {
1873da5c369Sopenharmony_ci            None
1883da5c369Sopenharmony_ci        }
1893da5c369Sopenharmony_ci    }
1903da5c369Sopenharmony_ci
1913da5c369Sopenharmony_ci    /// Set the preferred limit of allocated inodes.
1923da5c369Sopenharmony_ci    pub fn set_inodes_soft_limit(&mut self, limit: u64) {
1933da5c369Sopenharmony_ci        self.0.dqb_isoftlimit = limit;
1943da5c369Sopenharmony_ci    }
1953da5c369Sopenharmony_ci
1963da5c369Sopenharmony_ci    /// Current number of allocated inodes.
1973da5c369Sopenharmony_ci    pub fn allocated_inodes(&self) -> Option<u64> {
1983da5c369Sopenharmony_ci        let valid_fields =
1993da5c369Sopenharmony_ci            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
2003da5c369Sopenharmony_ci        if valid_fields.contains(QuotaValidFlags::QIF_INODES) {
2013da5c369Sopenharmony_ci            Some(self.0.dqb_curinodes)
2023da5c369Sopenharmony_ci        } else {
2033da5c369Sopenharmony_ci            None
2043da5c369Sopenharmony_ci        }
2053da5c369Sopenharmony_ci    }
2063da5c369Sopenharmony_ci
2073da5c369Sopenharmony_ci    /// Time limit for excessive disk use.
2083da5c369Sopenharmony_ci    pub fn block_time_limit(&self) -> Option<u64> {
2093da5c369Sopenharmony_ci        let valid_fields =
2103da5c369Sopenharmony_ci            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
2113da5c369Sopenharmony_ci        if valid_fields.contains(QuotaValidFlags::QIF_BTIME) {
2123da5c369Sopenharmony_ci            Some(self.0.dqb_btime)
2133da5c369Sopenharmony_ci        } else {
2143da5c369Sopenharmony_ci            None
2153da5c369Sopenharmony_ci        }
2163da5c369Sopenharmony_ci    }
2173da5c369Sopenharmony_ci
2183da5c369Sopenharmony_ci    /// Set the time limit for excessive disk use.
2193da5c369Sopenharmony_ci    pub fn set_block_time_limit(&mut self, limit: u64) {
2203da5c369Sopenharmony_ci        self.0.dqb_btime = limit;
2213da5c369Sopenharmony_ci    }
2223da5c369Sopenharmony_ci
2233da5c369Sopenharmony_ci    /// Time limit for excessive files.
2243da5c369Sopenharmony_ci    pub fn inode_time_limit(&self) -> Option<u64> {
2253da5c369Sopenharmony_ci        let valid_fields =
2263da5c369Sopenharmony_ci            QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
2273da5c369Sopenharmony_ci        if valid_fields.contains(QuotaValidFlags::QIF_ITIME) {
2283da5c369Sopenharmony_ci            Some(self.0.dqb_itime)
2293da5c369Sopenharmony_ci        } else {
2303da5c369Sopenharmony_ci            None
2313da5c369Sopenharmony_ci        }
2323da5c369Sopenharmony_ci    }
2333da5c369Sopenharmony_ci
2343da5c369Sopenharmony_ci    /// Set the time limit for excessive files.
2353da5c369Sopenharmony_ci    pub fn set_inode_time_limit(&mut self, limit: u64) {
2363da5c369Sopenharmony_ci        self.0.dqb_itime = limit;
2373da5c369Sopenharmony_ci    }
2383da5c369Sopenharmony_ci}
2393da5c369Sopenharmony_ci
2403da5c369Sopenharmony_cifn quotactl<P: ?Sized + NixPath>(
2413da5c369Sopenharmony_ci    cmd: QuotaCmd,
2423da5c369Sopenharmony_ci    special: Option<&P>,
2433da5c369Sopenharmony_ci    id: c_int,
2443da5c369Sopenharmony_ci    addr: *mut c_char,
2453da5c369Sopenharmony_ci) -> Result<()> {
2463da5c369Sopenharmony_ci    unsafe {
2473da5c369Sopenharmony_ci        Errno::clear();
2483da5c369Sopenharmony_ci        let res = match special {
2493da5c369Sopenharmony_ci            Some(dev) => dev.with_nix_path(|path| {
2503da5c369Sopenharmony_ci                libc::quotactl(cmd.as_int(), path.as_ptr(), id, addr)
2513da5c369Sopenharmony_ci            }),
2523da5c369Sopenharmony_ci            None => Ok(libc::quotactl(cmd.as_int(), ptr::null(), id, addr)),
2533da5c369Sopenharmony_ci        }?;
2543da5c369Sopenharmony_ci
2553da5c369Sopenharmony_ci        Errno::result(res).map(drop)
2563da5c369Sopenharmony_ci    }
2573da5c369Sopenharmony_ci}
2583da5c369Sopenharmony_ci
2593da5c369Sopenharmony_ci/// Turn on disk quotas for a block device.
2603da5c369Sopenharmony_cipub fn quotactl_on<P: ?Sized + NixPath>(
2613da5c369Sopenharmony_ci    which: QuotaType,
2623da5c369Sopenharmony_ci    special: &P,
2633da5c369Sopenharmony_ci    format: QuotaFmt,
2643da5c369Sopenharmony_ci    quota_file: &P,
2653da5c369Sopenharmony_ci) -> Result<()> {
2663da5c369Sopenharmony_ci    quota_file.with_nix_path(|path| {
2673da5c369Sopenharmony_ci        let mut path_copy = path.to_bytes_with_nul().to_owned();
2683da5c369Sopenharmony_ci        let p: *mut c_char = path_copy.as_mut_ptr() as *mut c_char;
2693da5c369Sopenharmony_ci        quotactl(
2703da5c369Sopenharmony_ci            QuotaCmd(QuotaSubCmd::Q_QUOTAON, which),
2713da5c369Sopenharmony_ci            Some(special),
2723da5c369Sopenharmony_ci            format as c_int,
2733da5c369Sopenharmony_ci            p,
2743da5c369Sopenharmony_ci        )
2753da5c369Sopenharmony_ci    })?
2763da5c369Sopenharmony_ci}
2773da5c369Sopenharmony_ci
2783da5c369Sopenharmony_ci/// Disable disk quotas for a block device.
2793da5c369Sopenharmony_cipub fn quotactl_off<P: ?Sized + NixPath>(
2803da5c369Sopenharmony_ci    which: QuotaType,
2813da5c369Sopenharmony_ci    special: &P,
2823da5c369Sopenharmony_ci) -> Result<()> {
2833da5c369Sopenharmony_ci    quotactl(
2843da5c369Sopenharmony_ci        QuotaCmd(QuotaSubCmd::Q_QUOTAOFF, which),
2853da5c369Sopenharmony_ci        Some(special),
2863da5c369Sopenharmony_ci        0,
2873da5c369Sopenharmony_ci        ptr::null_mut(),
2883da5c369Sopenharmony_ci    )
2893da5c369Sopenharmony_ci}
2903da5c369Sopenharmony_ci
2913da5c369Sopenharmony_ci/// Update the on-disk copy of quota usages for a filesystem.
2923da5c369Sopenharmony_ci///
2933da5c369Sopenharmony_ci/// If `special` is `None`, then all file systems with active quotas are sync'd.
2943da5c369Sopenharmony_cipub fn quotactl_sync<P: ?Sized + NixPath>(
2953da5c369Sopenharmony_ci    which: QuotaType,
2963da5c369Sopenharmony_ci    special: Option<&P>,
2973da5c369Sopenharmony_ci) -> Result<()> {
2983da5c369Sopenharmony_ci    quotactl(
2993da5c369Sopenharmony_ci        QuotaCmd(QuotaSubCmd::Q_SYNC, which),
3003da5c369Sopenharmony_ci        special,
3013da5c369Sopenharmony_ci        0,
3023da5c369Sopenharmony_ci        ptr::null_mut(),
3033da5c369Sopenharmony_ci    )
3043da5c369Sopenharmony_ci}
3053da5c369Sopenharmony_ci
3063da5c369Sopenharmony_ci/// Get disk quota limits and current usage for the given user/group id.
3073da5c369Sopenharmony_cipub fn quotactl_get<P: ?Sized + NixPath>(
3083da5c369Sopenharmony_ci    which: QuotaType,
3093da5c369Sopenharmony_ci    special: &P,
3103da5c369Sopenharmony_ci    id: c_int,
3113da5c369Sopenharmony_ci) -> Result<Dqblk> {
3123da5c369Sopenharmony_ci    let mut dqblk = mem::MaybeUninit::uninit();
3133da5c369Sopenharmony_ci    quotactl(
3143da5c369Sopenharmony_ci        QuotaCmd(QuotaSubCmd::Q_GETQUOTA, which),
3153da5c369Sopenharmony_ci        Some(special),
3163da5c369Sopenharmony_ci        id,
3173da5c369Sopenharmony_ci        dqblk.as_mut_ptr() as *mut c_char,
3183da5c369Sopenharmony_ci    )?;
3193da5c369Sopenharmony_ci    Ok(unsafe { Dqblk(dqblk.assume_init()) })
3203da5c369Sopenharmony_ci}
3213da5c369Sopenharmony_ci
3223da5c369Sopenharmony_ci/// Configure quota values for the specified fields for a given user/group id.
3233da5c369Sopenharmony_cipub fn quotactl_set<P: ?Sized + NixPath>(
3243da5c369Sopenharmony_ci    which: QuotaType,
3253da5c369Sopenharmony_ci    special: &P,
3263da5c369Sopenharmony_ci    id: c_int,
3273da5c369Sopenharmony_ci    dqblk: &Dqblk,
3283da5c369Sopenharmony_ci    fields: QuotaValidFlags,
3293da5c369Sopenharmony_ci) -> Result<()> {
3303da5c369Sopenharmony_ci    let mut dqblk_copy = *dqblk;
3313da5c369Sopenharmony_ci    dqblk_copy.0.dqb_valid = fields.bits();
3323da5c369Sopenharmony_ci    quotactl(
3333da5c369Sopenharmony_ci        QuotaCmd(QuotaSubCmd::Q_SETQUOTA, which),
3343da5c369Sopenharmony_ci        Some(special),
3353da5c369Sopenharmony_ci        id,
3363da5c369Sopenharmony_ci        &mut dqblk_copy as *mut _ as *mut c_char,
3373da5c369Sopenharmony_ci    )
3383da5c369Sopenharmony_ci}
339