Skip to content
Closed
2 changes: 2 additions & 0 deletions src/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub mod quota;
#[cfg(any(target_os = "linux"))]
pub mod reboot;

pub mod resource;

pub mod select;

#[cfg(any(target_os = "android",
Expand Down
258 changes: 258 additions & 0 deletions src/sys/resource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
//! Configure the process resource limits.
use std::mem;

#[cfg(not(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "macos")))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't use a negative config check here. Nix could add a new platform at any time, and it's easier to maintain checks like this if they're all written as positive checks.

use libc::{self, rlimit, __rlimit_resource_t, RLIM_INFINITY};

#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "macos"))]
use libc::{self, rlimit, c_int, RLIM_INFINITY};

pub use libc::rlim_t;

use {Errno, Result};

#[cfg(target_os = "linux")]
libc_enum!{
/// A resource that limits apply to
#[repr(u32)]
pub enum Resource {
// POSIX
/// This is the maximum size of the process's virtual memory (address space). The limit is
/// specified in bytes, and is rounded down to the system page size.
RLIMIT_AS,
/// This is the maximum size of a core file (see core(5)) in bytes that the process may
/// dump.
RLIMIT_CORE,
/// This is a limit, in seconds, on the amount of CPU time that the process can consume.
RLIMIT_CPU,
/// This is the maximum size of the process's data segment (initialized data, uninitialized
/// data, and heap). The limit is specified in bytes, and is rounded down to the system
/// page size.
RLIMIT_DATA,
/// This is the maximum size in bytes of files that the process may create. Attempts to
/// extend a file beyond this limit result in delivery of a SIGXFSZ signal.
RLIMIT_FSIZE,
/// This specifies a value one greater than the maximum file descriptor number that can be
/// opened by this process.
RLIMIT_NOFILE,
/// This is the maximum size of the process stack, in bytes. Upon reaching this limit, a
/// SIGSEGV signal is generated.
RLIMIT_STACK,

// BSDs and Linux
/// This is the maximum number of bytes of memory that may be locked into RAM. This limit
/// is in effect rounded down to the nearest multiple of the system page size.
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
RLIMIT_MEMLOCK,
/// This is a limit on the number of extant process (or, more precisely on Linux, threads)
/// for the real user ID of the calling process.
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
RLIMIT_NPROC,
/// This is a limit (in bytes) on the process's resident set (the number of virtual pages resident in RAM).
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
RLIMIT_RSS,

// Android and Linux only
/// This is a limit on the combined number of flock(2) locks and fcntl(2) leases that this process may establish.
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_LOCKS,
/// This is a limit on the number of bytes that can be allocated for POSIX message queues
/// for the real user ID of the calling process.
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_MSGQUEUE,
/// This specifies a ceiling to which the process's nice value can be raised using
/// setpriority(2) or nice(2). The actual ceiling for the nice value is calculated as 20 -
/// rlim_cur.
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_NICE,
/// This specifies a ceiling on the real-time priority that may be set for this process
/// using sched_setscheduler(2) and sched_setparam(2).
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_RTPRIO,
/// This is a limit (in microseconds) on the amount of CPU time that a process scheduled
/// under a real-time scheduling policy may consume without making a blocking system call.
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_RTTIME,
/// This is a limit on the number of signals that may be queued for the real user ID of the
/// calling process. Both standard and real-time signals are counted for the purpose of
/// checking this limit.
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_SIGPENDING,

// Available on some BSD
/// The maximum number of kqueues this user id is allowed to create.
#[cfg(target_os = "freebsd")]
RLIMIT_KQUEUES,
/// The maximum number of pseudo-terminals this user id is allowed to create.
#[cfg(target_os = "freebsd")]
RLIMIT_NPTS,
/// The maximum size (in bytes) of socket buffer usage for this user.
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
RLIMIT_SBSIZE,
/// The maximum size (in bytes) of the swap space that may be reserved or used by all of this user id's processes.
#[cfg(target_os = "freebsd")]
RLIMIT_SWAP,
}
}

#[cfg(not(target_os = "linux"))]
libc_enum!{
/// A resource that limits apply to
#[repr(i32)]
pub enum Resource {
// POSIX
/// This is the maximum size of the process's virtual memory (address space). The limit is
/// specified in bytes, and is rounded down to the system page size.
RLIMIT_AS,
/// This is the maximum size of a core file (see core(5)) in bytes that the process may
/// dump.
RLIMIT_CORE,
/// This is a limit, in seconds, on the amount of CPU time that the process can consume.
RLIMIT_CPU,
/// This is the maximum size of the process's data segment (initialized data, uninitialized
/// data, and heap). The limit is specified in bytes, and is rounded down to the system
/// page size.
RLIMIT_DATA,
/// This is the maximum size in bytes of files that the process may create. Attempts to
/// extend a file beyond this limit result in delivery of a SIGXFSZ signal.
RLIMIT_FSIZE,
/// This specifies a value one greater than the maximum file descriptor number that can be
/// opened by this process.
RLIMIT_NOFILE,
/// This is the maximum size of the process stack, in bytes. Upon reaching this limit, a
/// SIGSEGV signal is generated.
RLIMIT_STACK,

// BSDs and Linux
/// This is the maximum number of bytes of memory that may be locked into RAM. This limit
/// is in effect rounded down to the nearest multiple of the system page size.
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
RLIMIT_MEMLOCK,
/// This is a limit on the number of extant process (or, more precisely on Linux, threads)
/// for the real user ID of the calling process.
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
RLIMIT_NPROC,
/// This is a limit (in bytes) on the process's resident set (the number of virtual pages resident in RAM).
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
RLIMIT_RSS,

// Android and Linux only
/// This is a limit on the combined number of flock(2) locks and fcntl(2) leases that this process may establish.
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_LOCKS,
/// This is a limit on the number of bytes that can be allocated for POSIX message queues
/// for the real user ID of the calling process.
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_MSGQUEUE,
/// This specifies a ceiling to which the process's nice value can be raised using
/// setpriority(2) or nice(2). The actual ceiling for the nice value is calculated as 20 -
/// rlim_cur.
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_NICE,
/// This specifies a ceiling on the real-time priority that may be set for this process
/// using sched_setscheduler(2) and sched_setparam(2).
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_RTPRIO,
/// This is a limit (in microseconds) on the amount of CPU time that a process scheduled
/// under a real-time scheduling policy may consume without making a blocking system call.
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_RTTIME,
/// This is a limit on the number of signals that may be queued for the real user ID of the
/// calling process. Both standard and real-time signals are counted for the purpose of
/// checking this limit.
#[cfg(any(target_os = "android", target_os = "linux"))]
RLIMIT_SIGPENDING,

// Available on some BSD
/// The maximum number of kqueues this user id is allowed to create.
#[cfg(target_os = "freebsd")]
RLIMIT_KQUEUES,
/// The maximum number of pseudo-terminals this user id is allowed to create.
#[cfg(target_os = "freebsd")]
RLIMIT_NPTS,
/// The maximum size (in bytes) of socket buffer usage for this user.
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
RLIMIT_SBSIZE,
/// The maximum size (in bytes) of the swap space that may be reserved or used by all of this user id's processes.
#[cfg(target_os = "freebsd")]
RLIMIT_SWAP,
}
}

/// Get the current processes resource limits
///
/// A value of None indicates that there's no limit.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None should be in backticks

///
/// # Parameters
///
/// * `resource`: The [`Resource`] that we want to get the limits of.
///
/// # Examples
///
/// ```
/// # use nix::sys::resource::{getrlimit, Resource};
///
/// let (soft_limit, hard_limit) = getrlimit(Resource::RLIMIT_NOFILE).unwrap();
/// println!("current soft_limit: {:?}", soft_limit);
/// println!("current hard_limit: {:?}", hard_limit);
/// ```
///
/// # References
///
/// [getrlimit(2)](https://linux.die.net/man/2/getrlimit)
///
/// [`Resource`]: enum.Resource.html
pub fn getrlimit(resource: Resource) -> Result<(Option<rlim_t>, Option<rlim_t>)> {
let mut rlim = mem::MaybeUninit::<&rlimit>::uninit();

#[cfg(not(any(target_os = "freebsd", target_os = "macos", target_os = "openbsd", target_os = "netbsd")))]
let res = unsafe { libc::getrlimit(resource as __rlimit_resource_t, rlim.as_mut_ptr() as *mut _) };
#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "openbsd", target_os = "netbsd"))]
let res = unsafe { libc::getrlimit(resource as c_int, rlim.as_mut_ptr() as *mut _) };

let rlim = unsafe { rlim.assume_init() };
// TODO: use Option::filter after it has been stabilized
Errno::result(res).map(|_| {
(if rlim.rlim_cur != RLIM_INFINITY { Some(rlim.rlim_cur) } else { None },
if rlim.rlim_max != RLIM_INFINITY { Some(rlim.rlim_max) } else { None })
})
}

/// Set the current processes resource limits
///
/// A value of None indicates that there's no limit.
///
/// # Parameters
///
/// * `resource`: The [`Resource`] that we want to set the limits of.
/// * `soft_limit`: The value that the kernel enforces for the corresponding resource.
/// * `hard_limit`: The ceiling for the soft limit. Must be lower or equal to the current hard limit
/// for non-root users.
///
/// # Examples
///
/// ```no_run
/// # use nix::sys::resource::{setrlimit, Resource};
///
/// let soft_limit = Some(1024);
/// let hard_limit = None;
/// setrlimit(Resource::RLIMIT_NOFILE, soft_limit, hard_limit).unwrap();
/// ```
///
/// # References
///
/// [setrlimit(2)](https://linux.die.net/man/2/setrlimit)
///
/// [`Resource`]: enum.Resource.html
pub fn setrlimit(resource: Resource, soft_limit: Option<rlim_t>, hard_limit: Option<rlim_t>) -> Result<()> {
let mut rlim: rlimit = unsafe { mem::zeroed() };
rlim.rlim_cur = soft_limit.unwrap_or(RLIM_INFINITY);
rlim.rlim_max = hard_limit.unwrap_or(RLIM_INFINITY);

#[cfg(not(any(target_os = "freebsd", target_os = "macos", target_os = "openbsd", target_os = "netbsd")))]
let res = unsafe { libc::setrlimit(resource as __rlimit_resource_t, &rlim as *const _) };
#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "openbsd", target_os = "netbsd"))]
let res = unsafe { libc::setrlimit(resource as c_int, &rlim as *const _) };

Errno::result(res).map(|_| ())
}
1 change: 1 addition & 0 deletions test/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ mod test_kmod;
mod test_mq;
mod test_net;
mod test_nix_path;
mod test_resource;
mod test_poll;
mod test_pty;
#[cfg(any(target_os = "android",
Expand Down
49 changes: 49 additions & 0 deletions test/test_resource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use nix::sys::resource::{Resource, getrlimit, setrlimit};

/// Tests the RLIMIT_NOFILE functionality of getrlimit(), where the resource RLIMIT_NOFILE refers
/// to the maximum file descriptor number that can be opened by the process (aka the maximum number
/// of file descriptors that the process can open, since Linux 4.5).
///
/// We first fetch the existing file descriptor maximum values using getrlimit(), then edit the
/// soft limit to make sure it has a new and distinct value to the hard limit. We then setrlimit()
/// to put the new soft limit in effect, and then getrlimit() once more to ensure the limits have
/// been updated.
#[test]
pub fn test_resource_limits_nofile() {
let (soft_limit, hard_limit) = getrlimit(Resource::RLIMIT_NOFILE).unwrap();

// make sure the soft limit and hard limit are not equal
let soft_limit = match soft_limit {
Some(nofile) => Some(nofile - 1),
None => Some(1024),
};
setrlimit(Resource::RLIMIT_NOFILE, soft_limit, hard_limit).unwrap();

let (new_soft_limit, _new_hard_limit) = getrlimit(Resource::RLIMIT_NOFILE).unwrap();
assert_eq!(new_soft_limit, soft_limit);
}

/// Tests the RLIMIT_STACK functionality of getrlimit(), where the resource RLIMIT_STACK refers to
/// the maximum stack size that can be spawned by the current process before SIGSEGV is generated.
///
/// We first save the current stack limits, then newly set the soft limit to the same size as the
/// hard limit. We check to make sure these limits have been updated properly. We then set the
/// stack limits back to the original values, and make sure they have been updated properly.
#[test]
pub fn test_resource_limits_stack() {
let (mut soft_limit, hard_limit) = getrlimit(Resource::RLIMIT_STACK).unwrap();
let orig_limit = (soft_limit, hard_limit);

soft_limit = hard_limit;
setrlimit(Resource::RLIMIT_STACK, soft_limit, hard_limit).unwrap();

let limit2 = getrlimit(Resource::RLIMIT_STACK).unwrap();
assert_eq!(soft_limit, limit2.0);
assert_eq!(hard_limit, limit2.1);

setrlimit(Resource::RLIMIT_STACK, orig_limit.0, orig_limit.1).unwrap();

let final_limit = getrlimit(Resource::RLIMIT_STACK).unwrap();
assert_eq!(orig_limit.0, final_limit.0);
assert_eq!(orig_limit.1, final_limit.1);
}