├── clippy.toml ├── .gitignore ├── scripts ├── codegen.sh └── download-libc.sh ├── tests └── it │ ├── main.rs │ ├── utils.rs │ ├── windows.rs │ ├── unix.rs │ └── linux.rs ├── codegen ├── Cargo.toml └── src │ ├── main.rs │ ├── bindings.rs │ ├── build.rs │ ├── resource.md │ └── resource.rs ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── ci.yml ├── CONTRIBUTING.md ├── Cargo.toml ├── README.md ├── justfile ├── LICENSE ├── examples ├── nofile.rs └── sys_limits.rs ├── src ├── tools.rs ├── windows.rs ├── resource │ ├── mod.rs │ └── generated.rs ├── lib.rs ├── unix.rs ├── proc_limits.rs └── sys_limits.rs ├── CHANGELOG.md └── Cargo.lock /clippy.toml: -------------------------------------------------------------------------------- 1 | doc-valid-idents = ["FreeBSD", "NetBSD", "macOS"] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | 4 | /.vscode 5 | /temp 6 | 7 | __pycache__ 8 | -------------------------------------------------------------------------------- /scripts/codegen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | ./scripts/download-libc.sh 3 | cargo run -p rlimit-codegen 4 | rustfmt src/bindings.rs \ 5 | src/resource/generated.rs \ 6 | build.rs 7 | cargo fmt 8 | echo "done" 9 | -------------------------------------------------------------------------------- /tests/it/main.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | use self::utils::*; 3 | 4 | #[cfg(any(target_os = "linux", target_os = "android"))] 5 | mod linux; 6 | 7 | #[cfg(unix)] 8 | mod unix; 9 | 10 | #[cfg(windows)] 11 | mod windows; 12 | -------------------------------------------------------------------------------- /scripts/download-libc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | if [ ! -d "temp/libc" ]; then 4 | mkdir -p temp 5 | 6 | pushd temp 7 | git clone https://github.com/rust-lang/libc.git -b main --depth=1 8 | popd 9 | fi 10 | -------------------------------------------------------------------------------- /codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rlimit-codegen" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | bool-logic = "0.3.3" 9 | scoped-writer = "0.3.0" 10 | std-next = "0.1.5" 11 | libc-cfg = "0.3.3" 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | --- 13 | 14 | *By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.* 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Development Guide 2 | 3 | Toolchain 4 | 5 | + [Rust 1.65.0 or newer](https://rustup.rs/) 6 | + [just](https://github.com/casey/just) 7 | 8 | Get the source code 9 | 10 | ```bash 11 | git clone https://github.com/Nugine/rlimit.git 12 | cd rlimit 13 | ``` 14 | 15 | #### Run basic checks and tests 16 | 17 | ```bash 18 | just dev 19 | ``` 20 | 21 | #### Run the codegen 22 | 23 | ```bash 24 | just codegen 25 | ``` 26 | 27 | #### Open documentation 28 | 29 | ```bash 30 | just doc 31 | ``` 32 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rlimit" 3 | version = "0.10.2" 4 | authors = ["Nugine "] 5 | edition = "2021" 6 | description = "Resource limits" 7 | repository = "https://github.com/Nugine/rlimit/" 8 | license = "MIT" 9 | readme = "README.md" 10 | keywords = ["rlimit", "unix", "syscall"] 11 | categories = ["os::unix-apis"] 12 | documentation = "https://docs.rs/rlimit" 13 | rust-version = "1.65.0" 14 | 15 | [package.metadata.docs.rs] 16 | all-features = true 17 | rustdoc-args = ["--cfg", "docsrs"] 18 | 19 | [dependencies] 20 | libc = "0.2.178" 21 | 22 | [dev-dependencies] 23 | once_cell = "1.21.3" 24 | 25 | [workspace] 26 | members = ["codegen"] 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rlimit 2 | 3 | [![Latest Version]][crates.io] 4 | [![Documentation]][docs.rs] 5 | [![License]](LICENSE) 6 | [![Downloads]][downloads] 7 | 8 | Resource limits. 9 | 10 | [crates.io]: https://crates.io/crates/rlimit 11 | [Latest Version]: https://img.shields.io/crates/v/rlimit.svg 12 | [Documentation]: https://docs.rs/rlimit/badge.svg 13 | [docs.rs]: https://docs.rs/rlimit 14 | [License]: https://img.shields.io/crates/l/rlimit.svg 15 | [downloads]: https://img.shields.io/crates/d/rlimit 16 | 17 | Documentation: 18 | 19 | ## Contributing 20 | 21 | + [Development Guide](./CONTRIBUTING.md) 22 | 23 | ## Sponsor 24 | 25 | If my open-source work has been helpful to you, please [sponsor me](https://github.com/Nugine#sponsor). 26 | 27 | Every little bit helps. Thank you! 28 | -------------------------------------------------------------------------------- /tests/it/utils.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | #[track_caller] 4 | pub fn expect_ok(result: io::Result<()>) { 5 | assert!( 6 | result.is_ok(), 7 | "result = {}, should be OK()", 8 | result.as_ref().unwrap_err(), 9 | ); 10 | } 11 | 12 | #[track_caller] 13 | pub fn expect_err(result: io::Result<()>, kind: io::ErrorKind) { 14 | assert_eq!(result.unwrap_err().kind(), kind); 15 | } 16 | 17 | pub fn atomically(f: impl FnOnce() -> R) -> R { 18 | use once_cell::sync::Lazy; 19 | use std::sync::Mutex; 20 | 21 | static LOCK: Lazy> = Lazy::new(|| Mutex::new(())); 22 | let _guard = LOCK.lock().unwrap(); 23 | f() 24 | } 25 | 26 | #[test] 27 | #[ignore] 28 | fn tools_nofile() { 29 | let lim = rlimit::increase_nofile_limit(u64::MAX).unwrap(); 30 | dbg!(lim); 31 | } 32 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # https://github.com/casey/just 2 | 3 | dev: 4 | just fmt 5 | just check 6 | just test 7 | 8 | fmt: 9 | cargo fmt --all 10 | 11 | check: 12 | cargo check 13 | cargo clippy -- -D warnings 14 | 15 | test: 16 | cargo test --all-features -- --test-threads=1 --nocapture 17 | cargo run --example nofile 18 | 19 | doc: 20 | RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --no-deps --open --all-features 21 | 22 | codegen: 23 | #!/bin/bash -e 24 | cd {{justfile_directory()}} 25 | ./scripts/codegen.sh 26 | 27 | sync-version: 28 | cargo set-version -p rlimit 0.10.2 29 | 30 | publish: 31 | cargo publish -p rlimit 32 | 33 | ci: 34 | just fmt 35 | cargo check 36 | cargo +nightly clippy -- -D warnings 37 | cargo +stable clippy -- -D warnings -A unknown-lints 38 | -------------------------------------------------------------------------------- /tests/it/windows.rs: -------------------------------------------------------------------------------- 1 | use super::atomically; 2 | 3 | #[test] 4 | fn maxstdio() { 5 | atomically(|| { 6 | assert_eq!(rlimit::getmaxstdio(), 512); 7 | assert_eq!(rlimit::setmaxstdio(2048).unwrap(), 2048); 8 | assert_eq!(rlimit::getmaxstdio(), 2048); 9 | }); 10 | } 11 | 12 | #[test] 13 | fn maxstdio_overflow() { 14 | // Test that u32::MAX is rejected (would overflow to negative when cast to c_int) 15 | let result = rlimit::setmaxstdio(u32::MAX); 16 | assert!(result.is_err()); 17 | assert_eq!(result.unwrap_err().kind(), std::io::ErrorKind::InvalidInput); 18 | } 19 | 20 | #[test] 21 | fn maxstdio_large_value() { 22 | // Test that values larger than c_int::MAX are rejected 23 | let test_value = (std::os::raw::c_int::MAX as u32) + 1; 24 | let result = rlimit::setmaxstdio(test_value); 25 | assert!(result.is_err()); 26 | assert_eq!(result.unwrap_err().kind(), std::io::ErrorKind::InvalidInput); 27 | } 28 | -------------------------------------------------------------------------------- /tests/it/unix.rs: -------------------------------------------------------------------------------- 1 | use std::io::ErrorKind; 2 | 3 | use rlimit::{getrlimit, setrlimit, Resource}; 4 | 5 | use super::{atomically, expect_err, expect_ok}; 6 | 7 | #[test] 8 | fn resource_set_get() { 9 | const SOFT: u64 = 4 * 1024 * 1024; 10 | const HARD: u64 = 8 * 1024 * 1024; 11 | 12 | atomically(|| { 13 | expect_ok(Resource::FSIZE.set(SOFT - 1, HARD)); 14 | 15 | expect_ok(setrlimit(Resource::FSIZE, SOFT, HARD)); 16 | 17 | assert_eq!(Resource::FSIZE.get().unwrap(), (SOFT, HARD)); 18 | 19 | // FIXME: why does this line succeed on freebsd? 20 | #[cfg(not(target_os = "freebsd"))] 21 | { 22 | expect_err(Resource::FSIZE.set(HARD, SOFT), ErrorKind::InvalidInput); 23 | } 24 | 25 | expect_err( 26 | Resource::FSIZE.set(HARD, HARD + 1), 27 | ErrorKind::PermissionDenied, 28 | ); 29 | }); 30 | } 31 | 32 | #[test] 33 | fn resource_infinity() { 34 | assert_eq!( 35 | getrlimit(Resource::CPU).unwrap(), 36 | (rlimit::INFINITY, rlimit::INFINITY) 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Nugine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/nofile.rs: -------------------------------------------------------------------------------- 1 | #[cfg(unix)] 2 | mod unix_limits { 3 | use std::cmp; 4 | use std::io; 5 | 6 | use rlimit::Resource; 7 | 8 | const DEFAULT_NOFILE_LIMIT: u64 = 16384; // or another number 9 | 10 | /// Try to increase NOFILE limit and return the current soft limit. 11 | pub fn increase_nofile_limit() -> io::Result { 12 | let (soft, hard) = Resource::NOFILE.get()?; 13 | println!("Before increasing: soft = {soft}, hard = {hard}"); 14 | 15 | let target = cmp::min(DEFAULT_NOFILE_LIMIT, hard); 16 | println!("Try to increase: target = {target}"); 17 | Resource::NOFILE.set(target, hard)?; 18 | 19 | let (soft, hard) = Resource::NOFILE.get()?; 20 | println!("After increasing: soft = {soft}, hard = {hard}"); 21 | Ok(soft) 22 | } 23 | } 24 | 25 | fn main() { 26 | #[cfg(unix)] 27 | { 28 | match unix_limits::increase_nofile_limit() { 29 | Ok(soft) => println!("NOFILE limit: soft = {soft}"), 30 | Err(err) => println!("Failed to increase NOFILE limit: {err}"), 31 | } 32 | } 33 | #[cfg(not(unix))] 34 | { 35 | println!("Do nothing on non-Unix systems"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /codegen/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::all)] 2 | 3 | mod bindings; 4 | mod build; 5 | mod resource; 6 | 7 | use std::fs::File; 8 | use std::io::BufWriter; 9 | 10 | use bool_logic::cfg::ast::Expr; 11 | use bool_logic::cfg::ast::{Pred, Var}; 12 | use bool_logic::visit_mut::{walk_mut_expr, VisitMut}; 13 | use libc_cfg::{search, simplified_expr, CfgItem, RegexSet}; 14 | 15 | fn patch_cfg_expr(expr: &mut Expr) { 16 | struct Visitor; 17 | 18 | impl VisitMut for Visitor { 19 | fn visit_mut_expr(&mut self, expr: &mut Expr) { 20 | walk_mut_expr(self, expr); 21 | 22 | if let Expr::Var(Var(Pred { key, value: None })) = expr { 23 | // ignore `linux_time_bits64` 24 | if key.as_str() == "linux_time_bits64" { 25 | *expr = Expr::Const(true) 26 | } 27 | } 28 | } 29 | } 30 | 31 | Visitor.visit_mut_expr(expr); 32 | } 33 | 34 | fn collect_item_list() -> Vec { 35 | let libc_path = "temp/libc"; 36 | let re = RegexSet::new([ 37 | "RLIM", 38 | "rlimit", 39 | "RLIMIT_", 40 | "^CTL_KERN$", 41 | "^KERN_MAXFILESPERPROC$", 42 | "^sysctl$", 43 | ]) 44 | .unwrap(); 45 | 46 | let mut item_list = search(libc_path, &re).unwrap(); 47 | 48 | for item in &mut item_list { 49 | patch_cfg_expr(&mut item.cfg); 50 | item.cfg = simplified_expr(item.cfg.clone()); 51 | } 52 | 53 | item_list 54 | } 55 | 56 | fn write_file(path: &str, f: impl FnOnce()) { 57 | let mut writer = BufWriter::new(File::create(path).unwrap()); 58 | scoped_writer::scoped(&mut writer, f) 59 | } 60 | 61 | fn main() { 62 | let item_list = collect_item_list(); 63 | let resources = self::resource::collect_resources(&item_list); 64 | 65 | { 66 | let path = "src/bindings.rs"; 67 | write_file(path, || self::bindings::codegen(&item_list)); 68 | } 69 | 70 | { 71 | let path = "src/resource/generated.rs"; 72 | write_file(path, || self::resource::codegen(&resources)); 73 | } 74 | 75 | { 76 | let path = "build.rs"; 77 | write_file(path, || self::build::codegen(&item_list)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/tools.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | /// Returns the value of `kern.maxfilesperproc` by sysctl. 4 | /// # Errors 5 | /// Returns an error if any syscall failed. 6 | #[cfg(rlimit__get_kern_max_files_per_proc)] 7 | fn get_kern_max_files_per_proc() -> io::Result { 8 | use std::mem; 9 | use std::ptr; 10 | 11 | let mut mib = [libc::CTL_KERN, libc::KERN_MAXFILESPERPROC]; 12 | let mut max_files_per_proc: libc::c_int = 0; 13 | let mut oldlen = mem::size_of::(); 14 | let ret = unsafe { 15 | libc::sysctl( 16 | mib.as_mut_ptr(), 17 | 2, 18 | &mut max_files_per_proc as *mut libc::c_int as *mut libc::c_void, 19 | &mut oldlen, 20 | ptr::null_mut(), 21 | 0, 22 | ) 23 | }; 24 | 25 | if ret < 0 { 26 | return Err(io::Error::last_os_error()); 27 | } 28 | 29 | // SAFETY: sysctl should return a non-negative value for KERN_MAXFILESPERPROC. 30 | // Casting a non-negative c_int to u64 is always safe (widening conversion). 31 | debug_assert!(max_files_per_proc >= 0); 32 | Ok(max_files_per_proc as u64) 33 | } 34 | 35 | /// Try to increase NOFILE limit and return the current soft limit. 36 | /// 37 | /// `lim` is the expected limit which can be up to [`u64::MAX`]. 38 | /// 39 | /// This function does nothing and returns `Ok(lim)` 40 | /// if `RLIMIT_NOFILE` does not exist on current platform. 41 | /// 42 | /// # Errors 43 | /// Returns an error if any syscall failed. 44 | pub fn increase_nofile_limit(lim: u64) -> io::Result { 45 | #[cfg(unix)] 46 | { 47 | use crate::Resource; 48 | 49 | if !Resource::NOFILE.is_supported() { 50 | return Ok(lim); 51 | } 52 | 53 | let (soft, hard) = Resource::NOFILE.get()?; 54 | 55 | if soft >= hard { 56 | return Ok(hard); 57 | } 58 | 59 | if soft >= lim { 60 | return Ok(soft); 61 | } 62 | 63 | let mut lim = lim; 64 | 65 | lim = lim.min(hard); 66 | 67 | #[cfg(rlimit__get_kern_max_files_per_proc)] 68 | { 69 | lim = lim.min(get_kern_max_files_per_proc()?) 70 | } 71 | 72 | Resource::NOFILE.set(lim, hard)?; 73 | 74 | Ok(lim) 75 | } 76 | #[cfg(windows)] 77 | { 78 | Ok(lim) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::os::raw::c_int; 3 | 4 | extern "C" { 5 | fn _setmaxstdio(new_max: c_int) -> c_int; 6 | fn _getmaxstdio() -> c_int; 7 | } 8 | 9 | /// Sets a maximum for the number of simultaneously open files at the stream I/O level. 10 | /// 11 | /// The maximum allowed value is platform-dependent, typically 8192 on modern systems. 12 | /// Values that would overflow when converted to `c_int` or exceed the platform's 13 | /// maximum will result in an error. 14 | /// 15 | /// See 16 | /// 17 | /// # Errors 18 | /// Returns an error if: 19 | /// - `new_max` exceeds `c_int::MAX` (typically 2,147,483,647) 20 | /// - `new_max` is below the minimum (typically 20) or above the platform maximum (typically 8192) 21 | /// - The underlying `_setmaxstdio` call fails 22 | #[cfg_attr(docsrs, doc(cfg(windows)))] 23 | pub fn setmaxstdio(new_max: u32) -> io::Result { 24 | // Validate that new_max fits in c_int to prevent overflow. 25 | // SAFETY: c_int::MAX (i32::MAX = 2147483647) fits in u32, so this cast is safe. 26 | if new_max > c_int::MAX as u32 { 27 | return Err(io::Error::new( 28 | io::ErrorKind::InvalidInput, 29 | "new_max exceeds maximum allowed value", 30 | )); 31 | } 32 | 33 | // SAFETY: We've validated that new_max fits in c_int, so the cast is safe 34 | // (though clippy warns about possible wrapping). The cast back to u32 is 35 | // safe because we've checked that ret >= 0. 36 | #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] 37 | unsafe { 38 | let ret = _setmaxstdio(new_max as c_int); 39 | if ret < 0 { 40 | return Err(io::Error::last_os_error()); 41 | } 42 | Ok(ret as u32) 43 | } 44 | } 45 | 46 | /// Returns the number of simultaneously open files permitted at the stream I/O level. 47 | /// 48 | /// See 49 | #[cfg_attr(docsrs, doc(cfg(windows)))] 50 | #[must_use] 51 | pub fn getmaxstdio() -> u32 { 52 | // SAFETY: A negative `ret` should never appear. 53 | // It is safe even if the return value is wrong. 54 | #[allow(clippy::cast_sign_loss)] 55 | unsafe { 56 | let ret = _getmaxstdio(); 57 | debug_assert!(ret >= 0); 58 | ret as u32 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/sys_limits.rs: -------------------------------------------------------------------------------- 1 | //! Example demonstrating how to read system-wide file descriptor limits. 2 | //! 3 | //! This example shows the difference between: 4 | //! - System-wide limits from /proc/sys/fs/ 5 | //! - Per-process limits from getrlimit() 6 | //! 7 | //! Run with: cargo run --example sys_limits 8 | 9 | #[cfg(any(target_os = "linux", target_os = "android"))] 10 | fn main() { 11 | use rlimit::{Resource, SysLimits}; 12 | 13 | println!("=== System-wide File Descriptor Limits ===\n"); 14 | 15 | // Read system-wide limits 16 | match SysLimits::read() { 17 | Ok(limits) => { 18 | if let Some(file_max) = limits.file_max { 19 | println!("System file-max: {}", file_max); 20 | println!(" (Maximum number of file descriptors system-wide)"); 21 | } 22 | 23 | if let Some(file_nr) = &limits.file_nr { 24 | println!("\nCurrent file descriptor usage:"); 25 | println!(" Allocated: {}", file_nr.allocated); 26 | println!(" Free: {}", file_nr.free); 27 | println!(" Maximum: {}", file_nr.maximum); 28 | 29 | let usage_percent = if file_nr.maximum > 0 { 30 | (file_nr.allocated as f64 / file_nr.maximum as f64) * 100.0 31 | } else { 32 | 0.0 33 | }; 34 | println!(" Usage: {:.2}%", usage_percent); 35 | } 36 | 37 | if let Some(nr_open) = limits.nr_open { 38 | println!("\nPer-process nr_open: {}", nr_open); 39 | println!(" (Maximum NOFILE limit that can be set per-process)"); 40 | } 41 | } 42 | Err(e) => { 43 | eprintln!("Failed to read system limits: {}", e); 44 | return; 45 | } 46 | } 47 | 48 | println!("\n=== Per-process NOFILE Limit ===\n"); 49 | 50 | // Compare with per-process NOFILE limit 51 | match Resource::NOFILE.get() { 52 | Ok((soft, hard)) => { 53 | println!("NOFILE soft limit: {}", soft); 54 | println!("NOFILE hard limit: {}", hard); 55 | println!(" (Limits for this process only)"); 56 | } 57 | Err(e) => { 58 | eprintln!("Failed to get NOFILE limit: {}", e); 59 | } 60 | } 61 | 62 | println!("\n=== Key Differences ===\n"); 63 | println!("• file-max: System-wide limit on total file descriptors"); 64 | println!("• nr_open: Maximum value for per-process NOFILE limit"); 65 | println!("• NOFILE: Actual limit for this specific process"); 66 | println!(); 67 | println!("Hierarchy: file-max >= sum(all process NOFILE) and NOFILE <= nr_open"); 68 | } 69 | 70 | #[cfg(not(any(target_os = "linux", target_os = "android")))] 71 | fn main() { 72 | println!("This example only works on Linux and Android systems."); 73 | println!("System-wide limits are not available on this platform."); 74 | } 75 | -------------------------------------------------------------------------------- /codegen/src/bindings.rs: -------------------------------------------------------------------------------- 1 | use bool_logic::cfg::ast::*; 2 | use libc_cfg::{simplified_expr, CfgItem}; 3 | use scoped_writer::g; 4 | 5 | pub fn codegen(item_list: &[CfgItem]) { 6 | g([ 7 | "#![allow(clippy::cast_possible_truncation)]", 8 | "#![allow(clippy::unnecessary_cast)]", 9 | "", 10 | ]); 11 | 12 | codegen_64(item_list); 13 | codegen_inf(item_list); 14 | codegen_resources(item_list); 15 | } 16 | 17 | fn codegen_64(item_list: &[CfgItem]) { 18 | for name in ["rlimit", "getrlimit", "setrlimit", "prlimit"] { 19 | let name64 = format!("{name}64"); 20 | let item64 = item_list.iter().find(|item| item.name == name64).unwrap(); 21 | let cfg64 = item64.cfg.clone(); 22 | 23 | let item = item_list.iter().find(|item| item.name == name).unwrap(); 24 | let cfg = item.cfg.clone(); 25 | 26 | g!("#[cfg({cfg64})]"); 27 | g!("pub use libc::{name64} as {name};"); 28 | g!(); 29 | 30 | let otherwise = simplified_expr(all((not(cfg64), cfg))); 31 | if otherwise.is_const_false() { 32 | assert_eq!(name, "prlimit"); 33 | } else { 34 | g!("#[cfg({otherwise})]"); 35 | g!("pub use libc::{name};"); 36 | g!(); 37 | } 38 | } 39 | } 40 | 41 | fn codegen_inf(item_list: &[CfgItem]) { 42 | let name = "RLIM_INFINITY"; 43 | let item = item_list.iter().find(|item| item.name == name).unwrap(); 44 | let cfg = &item.cfg; 45 | 46 | g!("#[cfg({cfg})]"); 47 | g!("pub const {name}: u64 = libc::{name} as u64;"); 48 | g!(); 49 | 50 | g!("#[cfg(not({cfg}))]"); 51 | g!("pub const {name}: u64 = u64::MAX;"); 52 | g!(); 53 | } 54 | 55 | fn codegen_resources(item_list: &[CfgItem]) { 56 | let resources = { 57 | let mut ans = Vec::new(); 58 | for item in item_list { 59 | let name = item.name.as_str(); 60 | 61 | if name == "RLIMIT_NLIMITS" { 62 | continue; 63 | } 64 | 65 | // FIXME: https://github.com/rust-lang/libc/pull/3325#pullrequestreview-1663168123 66 | if name == "RLIMIT_OFILE" { 67 | continue; 68 | } 69 | 70 | if name.starts_with("RLIMIT_") { 71 | ans.push(item); 72 | } 73 | } 74 | ans 75 | }; 76 | 77 | for item in &resources { 78 | let name = item.name.as_str(); 79 | let cfg = &item.cfg; 80 | 81 | g!("#[cfg({cfg})]"); 82 | g!("pub const {name}: u8 = libc::{name} as u8;"); 83 | g!(); 84 | 85 | g!("#[cfg(not({cfg}))]"); 86 | g!("pub const {name}: u8 = u8::MAX;"); 87 | g!(); 88 | } 89 | 90 | g!("#[cfg(test)]"); 91 | g!("mod tests {{"); 92 | 93 | g!("#[allow(clippy::too_many_lines)]"); 94 | g!("#[test]"); 95 | g!("fn resource_range() {{"); 96 | 97 | for item in &resources { 98 | let name = item.name.as_str(); 99 | let cfg = &item.cfg; 100 | 101 | g!("#[cfg({cfg})]"); 102 | g!("assert!((0..128).contains(&libc::{name}));"); 103 | g!(); 104 | } 105 | 106 | g!("}}"); 107 | 108 | g!("}}"); 109 | } 110 | -------------------------------------------------------------------------------- /src/resource/mod.rs: -------------------------------------------------------------------------------- 1 | mod generated; 2 | 3 | use crate::{getrlimit, setrlimit}; 4 | 5 | use std::fmt; 6 | use std::io; 7 | 8 | /// A kind of resource. 9 | /// 10 | /// All resource constants are available on all unix platforms. 11 | /// Passing an unsupported resource to `[set|get|p]rlimit` will 12 | /// result in a custom IO error. 13 | /// 14 | /// **Be careful**: The documentation of [`Resource`](Resource) constants are based on a few systems. 15 | /// 16 | /// It may be inconsistent with other platforms. 17 | /// 18 | /// ## References 19 | /// 20 | /// Linux: 21 | /// 22 | /// FreeBSD: 23 | /// 24 | /// NetBSD: 25 | /// 26 | /// AIX: 27 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] 28 | pub struct Resource { 29 | tag: u8, 30 | value: u8, 31 | } 32 | 33 | impl fmt::Debug for Resource { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 35 | match Self::find_ident_by_tag(self.tag) { 36 | Some(ident) => write!(f, "Resource::{ident}"), 37 | None => unreachable!(), 38 | } 39 | } 40 | } 41 | 42 | /// An error returned when `Resource::from_str` fails 43 | #[derive(Debug, Clone, PartialEq, Eq)] 44 | pub struct ParseResourceError(()); 45 | 46 | impl fmt::Display for ParseResourceError { 47 | #[inline] 48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 49 | write!(f, "Failed to parse Resource") 50 | } 51 | } 52 | 53 | impl std::error::Error for ParseResourceError {} 54 | 55 | impl Resource { 56 | /// Set resource limits. 57 | /// # Errors 58 | /// See [`setrlimit`] 59 | #[inline] 60 | pub fn set(self, soft: u64, hard: u64) -> io::Result<()> { 61 | setrlimit(self, soft, hard) 62 | } 63 | 64 | /// Get resource limits. 65 | /// # Errors 66 | /// See [`getrlimit`] 67 | #[inline] 68 | pub fn get(self) -> io::Result<(u64, u64)> { 69 | getrlimit(self) 70 | } 71 | 72 | /// Get soft resource limit (`rlim_cur`) 73 | /// # Errors 74 | /// See [`getrlimit`] 75 | pub fn get_soft(self) -> io::Result { 76 | self.get().map(|(soft, _)| soft) 77 | } 78 | 79 | /// Get hard resource limit (`rlim_max`) 80 | /// # Errors 81 | /// See [`getrlimit`] 82 | pub fn get_hard(self) -> io::Result { 83 | self.get().map(|(_, hard)| hard) 84 | } 85 | 86 | /// Returns the name of the resource. 87 | /// 88 | /// # Example 89 | /// ``` 90 | /// # #[cfg(unix)] 91 | /// # { 92 | /// # use rlimit::Resource; 93 | /// assert_eq!(Resource::NOFILE.as_name(), "RLIMIT_NOFILE"); 94 | /// # } 95 | /// ``` 96 | #[must_use] 97 | pub fn as_name(self) -> &'static str { 98 | match Self::find_name_by_tag(self.tag) { 99 | Some(name) => name, 100 | None => unreachable!(), 101 | } 102 | } 103 | 104 | /// Returns true if the current platform supports this resource. 105 | #[must_use] 106 | pub const fn is_supported(self) -> bool { 107 | self.value != u8::MAX 108 | } 109 | 110 | /// `u8::MAX` indicates unsupported resource. 111 | #[inline] 112 | #[must_use] 113 | pub(crate) const fn as_raw(self) -> u8 { 114 | self.value 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /codegen/src/build.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write as _; 2 | 3 | use bool_logic::cfg::ast::{all, All, Any, Expr, Not, Var}; 4 | use libc_cfg::{simplified_expr, CfgItem}; 5 | use scoped_writer::g; 6 | 7 | fn find<'a>(item_list: &'a [CfgItem], name: &str) -> &'a CfgItem { 8 | item_list.iter().find(|item| item.name == name).unwrap() 9 | } 10 | 11 | fn find_many_cfg(item_list: &[CfgItem], items: &[&str]) -> Vec { 12 | items 13 | .iter() 14 | .map(|name| find(item_list, name).cfg.clone()) 15 | .collect() 16 | } 17 | 18 | fn cfg_eval(s: &mut String, depth: usize, cfg: &Expr) { 19 | match cfg { 20 | Expr::Any(Any(any)) => { 21 | if depth > 0 { 22 | write!(s, "(").unwrap(); 23 | } 24 | let (first, xs) = any.split_first().unwrap(); 25 | cfg_eval(s, depth + 1, first); 26 | for x in xs { 27 | write!(s, " || ").unwrap(); 28 | cfg_eval(s, depth + 1, x); 29 | } 30 | if depth > 0 { 31 | write!(s, ")").unwrap(); 32 | } 33 | } 34 | Expr::All(All(all)) => { 35 | if depth > 0 { 36 | write!(s, "(").unwrap(); 37 | } 38 | let (first, xs) = all.split_first().unwrap(); 39 | cfg_eval(s, depth + 1, first); 40 | for x in xs { 41 | write!(s, " && ").unwrap(); 42 | cfg_eval(s, depth + 1, x); 43 | } 44 | if depth > 0 { 45 | write!(s, ")").unwrap(); 46 | } 47 | } 48 | Expr::Not(Not(expr)) => { 49 | let pred = &expr.as_var().unwrap().0; 50 | let val = pred.value.as_ref().unwrap(); 51 | write!(s, "{} != {:?}", pred.key, val).unwrap(); 52 | } 53 | Expr::Var(Var(pred)) => { 54 | let val = pred.value.as_ref().unwrap(); 55 | write!(s, "{} == {:?}", pred.key, val).unwrap(); 56 | } 57 | Expr::Const(_) => unimplemented!(), 58 | } 59 | } 60 | 61 | fn set_cfg_if(key: &str, cfg: &Expr) { 62 | let cfg = { 63 | let mut s = String::new(); 64 | cfg_eval(&mut s, 0, cfg); 65 | s 66 | }; 67 | 68 | g!("let {key} = {cfg};"); 69 | g!(r#"println!("cargo:rustc-check-cfg=cfg(rlimit__{key})");"#); 70 | g!("if {key} {{"); 71 | g!(r#"println!("cargo:rustc-cfg=rlimit__{key}");"#); 72 | g!("}}"); 73 | g!(); 74 | } 75 | 76 | fn forward_item_cfg(item_list: &[CfgItem], name: &str) { 77 | let item = find(item_list, name); 78 | let key = format!("has_{name}"); 79 | set_cfg_if(&key, &item.cfg); 80 | } 81 | 82 | pub fn codegen(item_list: &[CfgItem]) { 83 | g!("fn main() {{"); 84 | 85 | { 86 | g!("let target_os = std::env::var(\"CARGO_CFG_TARGET_OS\").unwrap();"); 87 | g!("let target_env = std::env::var(\"CARGO_CFG_TARGET_ENV\").unwrap();"); 88 | g!(); 89 | } 90 | 91 | { 92 | let extra_os = ["switch"]; 93 | let values = extra_os.join("\",\""); 94 | g!(r#"println!("cargo:rustc-check-cfg=cfg(target_os, values(\"{values}\"))");"#) 95 | } 96 | 97 | forward_item_cfg(item_list, "prlimit64"); 98 | 99 | { 100 | let cfg = simplified_expr(all(find_many_cfg( 101 | item_list, 102 | &["CTL_KERN", "KERN_MAXFILESPERPROC", "sysctl"], 103 | ))); 104 | set_cfg_if("get_kern_max_files_per_proc", &cfg); 105 | } 106 | 107 | g!("}}") 108 | } 109 | -------------------------------------------------------------------------------- /codegen/src/resource.md: -------------------------------------------------------------------------------- 1 | # Resource 2 | 3 | # RLIMIT_AS 4 | 5 | The maximum size (in bytes) of the process's virtual memory (address space). 6 | 7 | # RLIMIT_CORE 8 | 9 | The maximum size (in bytes) of a core file that the process may dump. 10 | 11 | # RLIMIT_CPU 12 | 13 | A limit (in seconds) on the amount of CPU time that the process can consume. 14 | 15 | # RLIMIT_DATA 16 | 17 | The maximum size (in bytes) of the process's data segment (initialized data, uninitialized data, and heap). 18 | 19 | # RLIMIT_FSIZE 20 | 21 | The maximum size (in bytes) of files that the process may create. 22 | 23 | # RLIMIT_KQUEUES 24 | 25 | The maximum number of kqueues this user id is allowed to create. 26 | 27 | # RLIMIT_LOCKS 28 | 29 | (early Linux 2.4 only) 30 | 31 | A limit on the combined number of `flock(2)` locks and `fcntl(2)` leases that this process may establish. 32 | 33 | # RLIMIT_MEMLOCK 34 | 35 | The maximum number (in bytes) of memory that may be locked into RAM. 36 | 37 | # RLIMIT_MSGQUEUE 38 | 39 | A limit on the number of bytes that can be allocated for POSIX message queues for the real user ID of the calling process. 40 | 41 | # RLIMIT_NICE 42 | 43 | This specifies a ceiling to which the process's nice value can be raised using `setpriority(2)` or `nice(2)`. 44 | 45 | # RLIMIT_NOFILE 46 | 47 | This specifies a value one greater than the maximum file descriptor number that can be opened by this process. 48 | 49 | # RLIMIT_NOVMON 50 | 51 | The number of open vnode monitors. 52 | 53 | # RLIMIT_NPROC 54 | 55 | A limit on the number of extant process (or, more precisely on Linux, threads) for the real user ID of the calling process. 56 | 57 | # RLIMIT_NPTS 58 | 59 | The maximum number of pseudo-terminals this user id is allowed to create. 60 | 61 | # RLIMIT_NTHR 62 | 63 | The maximum number of simultaneous threads (Lightweight Processes) for this user id. 64 | Kernel threads and the first thread of each process are not counted against this limit. 65 | 66 | # RLIMIT_POSIXLOCKS 67 | 68 | The maximum number of POSIX-type advisory-mode locks available to this user. 69 | 70 | # RLIMIT_RSS 71 | 72 | A limit (in bytes) on the process's resident set (the number of virtual pages resident in RAM). 73 | 74 | # RLIMIT_RTPRIO 75 | 76 | This specifies a ceiling on the real-time priority that may be set for this process using `sched_setscheduler(2)` and `sched_setparam(2)`. 77 | 78 | # RLIMIT_RTTIME 79 | 80 | 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. 81 | 82 | # RLIMIT_SBSIZE 83 | 84 | The maximum size (in bytes) of socket buffer usage for this user. This limits the amount of network memory, and hence the amount of mbufs, that this user may hold at any time. 85 | 86 | # RLIMIT_SIGPENDING 87 | 88 | A limit on the number of signals that may be queued for the real user ID ofthe calling process. 89 | 90 | # RLIMIT_STACK 91 | 92 | The maximum size (in bytes) of the process stack. 93 | 94 | # RLIMIT_SWAP 95 | 96 | The maximum size (in bytes) of the swap space that may be reserved or used by all of this user id's processes. 97 | 98 | # RLIMIT_THREADS 99 | 100 | **AIX**: The maximum number of threads each process can create. This limit is enforced by the kernel and the pthread debug library. 101 | 102 | # RLIMIT_UMTXP 103 | 104 | The number of shared locks a given user may create simultaneously. 105 | 106 | # RLIMIT_VMEM 107 | 108 | An alias for [`RLIMIT_AS`](Resource::AS). The maximum size of a process's mapped address space in bytes. 109 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | [Unreleased]: https://github.com/Nugine/rlimit/compare/v0.10.2...HEAD 10 | 11 | ## [0.10.2] - 2024-09-01 12 | 13 | [0.10.2]: https://github.com/Nugine/rlimit/compare/v0.10.1...v0.10.2 14 | 15 | + Update libc bindings. 16 | 17 | ## [0.10.1] - 2023-07-20 18 | 19 | [0.10.1]: https://github.com/Nugine/rlimit/compare/v0.10.0...v0.10.1 20 | 21 | + Update libc bindings. 22 | + Fix incorrect build script. ([Issue #55](https://github.com/Nugine/rlimit/issues/55)) 23 | 24 | ## [0.10.0] - 2023-07-04 25 | 26 | [0.10.0]: https://github.com/Nugine/rlimit/compare/v0.9.1...v0.10.0 27 | 28 | + Update libc bindings. 29 | + Add `Resource::get_soft` and `Resource::get_hard`. 30 | 31 | The MSRV of v0.10.* is explicitly guaranteed to be 1.60.0. 32 | 33 | If you have any idea that can help `rlimit` reach v1.0, please leave your comments in [this issue](https://github.com/Nugine/rlimit/issues/27). 34 | 35 | ## [0.9.1] - 2023-01-30 36 | 37 | [0.9.1]: https://github.com/Nugine/rlimit/compare/v0.9.0...v0.9.1 38 | 39 | + [PR #46](https://github.com/Nugine/rlimit/pull/46): `prlimit` and `ProcLimits` are available on Android. 40 | 41 | ## [0.9.0] - 2022-12-28 42 | 43 | [0.9.0]: https://github.com/Nugine/rlimit/compare/v0.8.3...v0.9.0 44 | 45 | + rlimit v0.9.0 follows the latest libc definitions. 46 | + The MSRV of v0.9.* is explicitly guaranteed to be 1.59.0. 47 | 48 | ## [0.8.3] - 2022-04-06 49 | 50 | [0.8.3]: https://github.com/Nugine/rlimit/compare/v0.8.2...v0.8.3 51 | 52 | [PR #43](https://github.com/Nugine/rlimit/pull/43): Downgrade MSRV 53 | 54 | ## [0.8.2] - 2022-04-06 55 | 56 | [0.8.2]: https://github.com/Nugine/rlimit/compare/v0.8.1...v0.8.2 57 | 58 | rlimit v0.8.2 uses libc definitions again instead of incorrect custom bindings. 59 | 60 | rlimit v0.8.0 and v0.8.1 are yanked now. 61 | 62 | ## ~~[0.8.1] - 2022-04-01~~ 63 | 64 | [0.8.1]: https://github.com/Nugine/rlimit/compare/v0.8.0...v0.8.1 65 | 66 | [PR #36](https://github.com/Nugine/rlimit/pull/36): Fix the bindings for aarch64-apple-darwin. 67 | 68 | ## ~~[0.8.0] - 2022-03-31~~ 69 | 70 | [0.8.0]: https://github.com/Nugine/rlimit/compare/v0.7.0...v0.8.0 71 | 72 | rlimit v0.8.0 uses custom ffi bindings instead of libc for rlimit symbols and constants. The custom bindings are kept in sync with system headers automatically. 73 | 74 | All resource constants are available on all unix platforms. 75 | Passing an unsupported resource to `[set|get|p]rlimit` will result in a custom IO error. 76 | 77 | ### Added 78 | 79 | + `Resource::is_supported` 80 | 81 | ### Changed 82 | 83 | + `Resource::as_raw` is a private method now. 84 | 85 | ### Removed 86 | 87 | + `Resource::available_names` 88 | + `Resource::available_resources` 89 | + `RawResource` 90 | 91 | ## [0.7.0] - 2022-02-13 92 | 93 | [0.7.0]: https://github.com/Nugine/rlimit/compare/v0.6.2...v0.7.0 94 | 95 | ### Added 96 | 97 | + Windows support 98 | + [rlimit::getmaxstdio](https://docs.rs/rlimit/0.7.0/rlimit/fn.getmaxstdio.html) 99 | + [rlimit::setmaxstdio](https://docs.rs/rlimit/0.7.0/rlimit/fn.stdmaxstdio.html) 100 | 101 | ### Changed 102 | 103 | + [rlimit::utils::increase_nofile_limit] in v0.6.2 has been moved to [rlimit::increase_nofile_limit]. 104 | 105 | [rlimit::utils::increase_nofile_limit]: https://docs.rs/rlimit/0.6.2/rlimit/utils/fn.increase_nofile_limit.html 106 | 107 | [rlimit::increase_nofile_limit]: https://docs.rs/rlimit/0.7.0/rlimit/fn.increase_nofile_limit.html 108 | 109 | ### Removed 110 | 111 | + [rlimit::utils::get_kern_max_files_per_proc] has been removed from public interfaces. 112 | 113 | [rlimit::utils::get_kern_max_files_per_proc]: https://docs.rs/rlimit/0.6.2/x86_64-apple-darwin/rlimit/utils/fn.get_kern_max_files_per_proc.html 114 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! rlimit - Resource limits. 2 | //! 3 | //! ## Set resource limit 4 | //! ```no_run 5 | //! # #[cfg(unix)] 6 | //! # { 7 | //! use rlimit::{setrlimit, Resource}; 8 | //! 9 | //! const DEFAULT_SOFT_LIMIT: u64 = 4 * 1024 * 1024; 10 | //! const DEFAULT_HARD_LIMIT: u64 = 8 * 1024 * 1024; 11 | //! assert!(Resource::FSIZE.set(DEFAULT_SOFT_LIMIT, DEFAULT_HARD_LIMIT).is_ok()); 12 | //! 13 | //! let soft = 16384; 14 | //! let hard = soft * 2; 15 | //! assert!(setrlimit(Resource::NOFILE, soft, hard).is_ok()); 16 | //! # } 17 | //! ``` 18 | //! 19 | //! ## Get resource limit 20 | //! ```no_run 21 | //! # #[cfg(unix)] 22 | //! # { 23 | //! use rlimit::{getrlimit, Resource}; 24 | //! 25 | //! assert!(Resource::NOFILE.get().is_ok()); 26 | //! assert_eq!(getrlimit(Resource::CPU).unwrap(), (rlimit::INFINITY, rlimit::INFINITY)); 27 | //! # } 28 | //! ``` 29 | //! 30 | //! ## Windows 31 | //! 32 | //! Windows does not have Unix-like resource limits. 33 | //! It only supports changing the number of simultaneously open files currently permitted at the stdio level. 34 | //! 35 | //! See the official documentation of 36 | //! [`_getmaxstdio`](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/getmaxstdio?view=msvc-170) 37 | //! and 38 | //! [`_setmaxstdio`](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio?view=msvc-170). 39 | //! 40 | //! ```no_run 41 | //! # #[cfg(windows)] 42 | //! # { 43 | //! println!("{}", rlimit::getmaxstdio()); // 512 44 | //! rlimit::setmaxstdio(2048).unwrap(); 45 | //! println!("{}", rlimit::getmaxstdio()); // 2048 46 | //! # } 47 | //! ``` 48 | //! 49 | //! ## Increase NOFILE limit 50 | //! See the example [nofile](https://github.com/Nugine/rlimit/blob/main/examples/nofile.rs). 51 | //! 52 | //! You can also use the tool function [`rlimit::increase_nofile_limit`][`crate::increase_nofile_limit`] 53 | //! 54 | //! ```no_run 55 | //! rlimit::increase_nofile_limit(10240).unwrap(); 56 | //! rlimit::increase_nofile_limit(u64::MAX).unwrap(); 57 | //! ``` 58 | //! 59 | //! # Troubleshoot 60 | //! 61 | //! ## Failed to increase NOFILE to hard limit on macOS 62 | //! On macOS, getrlimit by default reports that the hard limit is 63 | //! unlimited, but there is usually a stricter hard limit discoverable 64 | //! via sysctl (`kern.maxfilesperproc`). Failing to discover this secret stricter hard limit will 65 | //! cause the call to setrlimit to fail. 66 | //! 67 | //! [`rlimit::increase_nofile_limit`][`crate::increase_nofile_limit`] 68 | //! respects `kern.maxfilesperproc`. 69 | //! 70 | 71 | #![cfg_attr(docsrs, feature(doc_cfg))] 72 | #![deny( 73 | missing_docs, 74 | missing_debug_implementations, 75 | clippy::all, 76 | clippy::pedantic, 77 | clippy::cargo 78 | )] 79 | #![allow( 80 | clippy::option_if_let_else, // I don't like it. The match expression is more readable. 81 | )] 82 | 83 | #[allow(unused_macros)] 84 | macro_rules! group { 85 | ($($item:item)*) => { 86 | $($item)* 87 | } 88 | } 89 | 90 | #[cfg(any(doc, windows))] 91 | group! { 92 | mod windows; 93 | 94 | #[doc(inline)] 95 | pub use self::windows::*; 96 | } 97 | 98 | #[cfg(any(doc, unix))] 99 | group! { 100 | mod bindings; 101 | 102 | mod unix; 103 | mod resource; 104 | 105 | #[doc(inline)] 106 | pub use self::unix::*; 107 | 108 | #[doc(inline)] 109 | pub use self::resource::Resource; 110 | } 111 | 112 | #[cfg(any(doc, target_os = "linux", target_os = "android"))] 113 | group! { 114 | mod proc_limits; 115 | 116 | #[doc(inline)] 117 | pub use self::proc_limits::*; 118 | 119 | mod sys_limits; 120 | 121 | #[doc(inline)] 122 | pub use self::sys_limits::*; 123 | } 124 | 125 | mod tools; 126 | #[doc(inline)] 127 | pub use self::tools::*; 128 | 129 | #[cfg(test)] 130 | mod tests { 131 | #[allow(clippy::assertions_on_constants)] 132 | #[test] 133 | fn build_cfg() { 134 | if cfg!(target_os = "linux") || cfg!(target_os = "android") { 135 | assert!(cfg!(rlimit__has_prlimit64)); 136 | assert!(cfg!(not(rlimit__get_kern_max_files_per_proc))); 137 | } 138 | 139 | if cfg!(target_os = "macos") { 140 | assert!(cfg!(not(rlimit__has_prlimit64))); 141 | assert!(cfg!(rlimit__get_kern_max_files_per_proc)); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /codegen/src/resource.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use libc_cfg::CfgItem; 4 | use scoped_writer::g; 5 | use stdx::default::default; 6 | 7 | pub struct Resource { 8 | ident: String, 9 | name: String, 10 | tag: usize, 11 | } 12 | 13 | pub fn collect_resources(item_list: &[CfgItem]) -> Vec { 14 | let mut ans: Vec = default(); 15 | 16 | for item in item_list { 17 | let name = item.name.as_str(); 18 | 19 | if name == "RLIMIT_NLIMITS" { 20 | continue; 21 | } 22 | 23 | // FIXME: https://github.com/rust-lang/libc/pull/3325#pullrequestreview-1663168123 24 | if name == "RLIMIT_OFILE" { 25 | continue; 26 | } 27 | 28 | if let Some(ident) = name.strip_prefix("RLIMIT_") { 29 | ans.push(Resource { 30 | ident: ident.to_owned(), 31 | name: name.to_owned(), 32 | tag: 0, 33 | }); 34 | } 35 | } 36 | 37 | ans.sort_by(|lhs, rhs| Ord::cmp(&lhs.ident, &rhs.ident)); 38 | ans.iter_mut().enumerate().for_each(|(i, r)| r.tag = i + 1); 39 | 40 | ans 41 | } 42 | 43 | fn load_docs() -> HashMap<&'static str, Vec<&'static str>> { 44 | let content = include_str!("./resource.md"); 45 | 46 | let mut paragraphs: HashMap<&str, Vec<&str>> = default(); 47 | let mut iter = content.lines().peekable(); 48 | 49 | while let Some(line) = iter.next() { 50 | let heading = line.strip_prefix("# ").unwrap(); 51 | 52 | while let Some(s) = iter.peek() { 53 | if s.is_empty() { 54 | iter.next(); 55 | } else { 56 | break; 57 | } 58 | } 59 | 60 | let mut lines: Vec<&str> = default(); 61 | while let Some(s) = iter.peek() { 62 | if s.starts_with("# ") { 63 | break; 64 | } else { 65 | lines.push(iter.next().unwrap()); 66 | } 67 | } 68 | 69 | while let Some(s) = lines.last() { 70 | if s.is_empty() { 71 | lines.pop(); 72 | } else { 73 | break; 74 | } 75 | } 76 | 77 | assert!(paragraphs.insert(heading, lines).is_none()); 78 | } 79 | 80 | paragraphs 81 | } 82 | 83 | pub fn codegen(resources: &[Resource]) { 84 | let docs = load_docs(); 85 | 86 | g([ 87 | "use super::Resource;", 88 | "use super::ParseResourceError;", 89 | "use crate::bindings as C;", 90 | "", 91 | ]); 92 | 93 | { 94 | g!("impl Resource {{"); 95 | 96 | for res in resources { 97 | for doc in &docs[res.name.as_str()] { 98 | g!("/// {doc}"); 99 | } 100 | 101 | g!( 102 | "pub const {}: Self = Self {{ tag: {}, value: C::{} }};", 103 | res.ident, 104 | res.tag, 105 | res.name 106 | ); 107 | g!(); 108 | } 109 | 110 | g!("}}"); 111 | g!(); 112 | } 113 | 114 | { 115 | g!("impl Resource {{"); 116 | 117 | g!("pub(super) const fn find_name_by_tag(tag: u8) -> Option<&'static str> {{"); 118 | g!(" match tag {{"); 119 | for res in resources { 120 | g!(" {} => Some({:?}),", res.tag, res.name); 121 | } 122 | g!(" _ => None,"); 123 | g!(" }}"); 124 | g!("}}"); 125 | g!(); 126 | 127 | g!("pub(super) const fn find_ident_by_tag(tag: u8) -> Option<&'static str> {{"); 128 | g!(" match tag {{"); 129 | for res in resources { 130 | g!(" {} => Some({:?}),", res.tag, res.ident); 131 | } 132 | g!(" _ => None,"); 133 | g!(" }}"); 134 | g!("}}"); 135 | g!(); 136 | 137 | g!("}}"); 138 | } 139 | 140 | { 141 | g!("impl std::str::FromStr for Resource {{"); 142 | g!(" type Err = ParseResourceError;"); 143 | g!(); 144 | g!(" fn from_str(s: &str) -> Result {{"); 145 | g!(" match s {{"); 146 | for res in resources { 147 | g!(" {:?} => Ok(Self::{}),", res.name, res.ident); 148 | } 149 | g!(" _ => Err(ParseResourceError(())),"); 150 | g!(" }}"); 151 | g!(" }}"); 152 | g!("}}"); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/unix.rs: -------------------------------------------------------------------------------- 1 | use crate::bindings as C; 2 | use crate::resource::Resource; 3 | 4 | use std::{io, mem}; 5 | 6 | /// A value indicating no limit. 7 | /// 8 | /// This constant is the minimum of the platform's `RLIM_INFINITY` and `u64::MAX`. 9 | /// On most platforms, `RLIM_INFINITY` is already `u64`, making this cast lossless. 10 | #[allow(clippy::unnecessary_cast)] 11 | pub const INFINITY: u64 = C::RLIM_INFINITY as u64; 12 | 13 | fn check_supported(resource: Resource) -> io::Result<()> { 14 | let raw_resource = resource.as_raw(); 15 | if raw_resource == u8::MAX { 16 | return Err(io::Error::new(io::ErrorKind::Other, "unsupported resource")); 17 | } 18 | Ok(()) 19 | } 20 | 21 | /// Set resource limits. 22 | /// # Errors 23 | /// \[Linux\] See 24 | #[allow(clippy::unnecessary_min_or_max)] 25 | #[inline] 26 | pub fn setrlimit(resource: Resource, soft: u64, hard: u64) -> io::Result<()> { 27 | check_supported(resource)?; 28 | // SAFETY: Values are clamped to INFINITY, which is the maximum value representable 29 | // by the platform's rlim_t type. On platforms where rlim_t is u64, this is a no-op. 30 | // On platforms where rlim_t is smaller (e.g., u32), the min() ensures we don't 31 | // exceed the platform's maximum value before truncating. 32 | let rlim = C::rlimit { 33 | rlim_cur: soft.min(INFINITY) as _, 34 | rlim_max: hard.min(INFINITY) as _, 35 | }; 36 | #[allow(clippy::cast_lossless)] 37 | let ret = unsafe { C::setrlimit(resource.as_raw() as _, &rlim) }; 38 | if ret == 0 { 39 | Ok(()) 40 | } else { 41 | Err(io::Error::last_os_error()) 42 | } 43 | } 44 | 45 | /// Get resource limits. 46 | /// # Errors 47 | /// \[Linux\] See 48 | #[allow(clippy::unnecessary_min_or_max)] 49 | #[inline] 50 | pub fn getrlimit(resource: Resource) -> io::Result<(u64, u64)> { 51 | check_supported(resource)?; 52 | let mut rlim = unsafe { mem::zeroed() }; 53 | #[allow(clippy::cast_lossless)] 54 | let ret = unsafe { C::getrlimit(resource.as_raw() as _, &mut rlim) }; 55 | 56 | #[allow(clippy::unnecessary_cast)] 57 | if ret == 0 { 58 | // SAFETY: On platforms where rlim_t is u64, this cast is lossless (no-op). 59 | // On platforms where rlim_t is smaller (e.g., u32), this is a widening cast 60 | // which is always safe. The min(INFINITY) clamps to our portable maximum. 61 | let soft = (rlim.rlim_cur as u64).min(INFINITY); 62 | let hard = (rlim.rlim_max as u64).min(INFINITY); 63 | Ok((soft, hard)) 64 | } else { 65 | Err(io::Error::last_os_error()) 66 | } 67 | } 68 | 69 | /// The type of a process ID 70 | #[allow(non_camel_case_types)] 71 | #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))] 72 | #[cfg(any(doc, target_os = "linux", target_os = "android"))] 73 | pub type pid_t = i32; 74 | 75 | /// Set and get the resource limits of an arbitrary process. 76 | /// # Errors 77 | /// See 78 | #[allow(clippy::unnecessary_min_or_max)] 79 | #[inline] 80 | #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))] 81 | #[cfg(any(doc, rlimit__has_prlimit64))] 82 | pub fn prlimit( 83 | pid: pid_t, 84 | resource: Resource, 85 | new_limit: Option<(u64, u64)>, 86 | old_limit: Option<(&mut u64, &mut u64)>, 87 | ) -> io::Result<()> { 88 | check_supported(resource)?; 89 | 90 | // SAFETY: Values are clamped to INFINITY before casting to rlim_t. 91 | // See setrlimit() for detailed explanation of truncation safety. 92 | let new_rlim: Option = new_limit.map(|(soft, hard)| C::rlimit { 93 | rlim_cur: soft.min(INFINITY) as _, 94 | rlim_max: hard.min(INFINITY) as _, 95 | }); 96 | 97 | let new_rlimit_ptr: *const C::rlimit = match new_rlim { 98 | Some(ref rlim) => rlim, 99 | None => std::ptr::null(), 100 | }; 101 | 102 | let mut old_rlim: C::rlimit = unsafe { mem::zeroed() }; 103 | 104 | let old_rlimit_ptr: *mut C::rlimit = if old_limit.is_some() { 105 | &mut old_rlim 106 | } else { 107 | std::ptr::null_mut() 108 | }; 109 | 110 | #[allow(clippy::cast_lossless)] 111 | let ret = unsafe { C::prlimit(pid, resource.as_raw() as _, new_rlimit_ptr, old_rlimit_ptr) }; 112 | 113 | if ret == 0 { 114 | #[allow(clippy::unnecessary_cast)] 115 | if let Some((soft, hard)) = old_limit { 116 | // SAFETY: See getrlimit() for detailed explanation of cast safety. 117 | *soft = (old_rlim.rlim_cur as u64).min(INFINITY); 118 | *hard = (old_rlim.rlim_max as u64).min(INFINITY); 119 | } 120 | 121 | Ok(()) 122 | } else { 123 | Err(io::Error::last_os_error()) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /tests/it/linux.rs: -------------------------------------------------------------------------------- 1 | use std::io::ErrorKind; 2 | use std::ops::Not; 3 | 4 | use rlimit::{prlimit, Resource}; 5 | 6 | use super::{atomically, expect_err, expect_ok}; 7 | 8 | #[test] 9 | fn linux_prlimit() { 10 | const SOFT: u64 = 4 * 1024 * 1024; 11 | const HARD: u64 = 8 * 1024 * 1024; 12 | 13 | atomically(|| { 14 | let res = Resource::CORE; 15 | 16 | expect_ok(prlimit(0, res, Some((SOFT, HARD)), None)); 17 | 18 | let mut soft = 0; 19 | let mut hard = 0; 20 | 21 | expect_ok(prlimit(0, res, None, Some((&mut soft, &mut hard)))); 22 | 23 | assert_eq!((soft, hard), (SOFT, HARD)); 24 | 25 | expect_err( 26 | prlimit(0, res, Some((HARD, SOFT)), None), 27 | ErrorKind::InvalidInput, 28 | ); 29 | 30 | expect_err( 31 | prlimit(0, res, Some((HARD, HARD + 1)), None), 32 | ErrorKind::PermissionDenied, 33 | ); 34 | }); 35 | } 36 | 37 | #[test] 38 | fn linux_proc_limits() { 39 | use rlimit::ProcLimits; 40 | 41 | atomically(|| { 42 | let self_limits = ProcLimits::read_self().unwrap(); 43 | assert!(self_limits.max_cpu_time.is_some()); 44 | assert!(self_limits.max_file_size.is_some()); 45 | assert!(self_limits.max_data_size.is_some()); 46 | assert!(self_limits.max_stack_size.is_some()); 47 | assert!(self_limits.max_core_file_size.is_some()); 48 | assert!(self_limits.max_resident_set.is_some()); 49 | assert!(self_limits.max_processes.is_some()); 50 | assert!(self_limits.max_open_files.is_some()); 51 | assert!(self_limits.max_locked_memory.is_some()); 52 | assert!(self_limits.max_address_space.is_some()); 53 | assert!(self_limits.max_file_locks.is_some()); 54 | assert!(self_limits.max_pending_signals.is_some()); 55 | assert!(self_limits.max_msgqueue_size.is_some()); 56 | assert!(self_limits.max_nice_priority.is_some()); 57 | assert!(self_limits.max_realtime_priority.is_some()); 58 | assert!(self_limits.max_realtime_timeout.is_some()); 59 | 60 | let self_pid = unsafe { libc::getpid() }; 61 | let process_limits = ProcLimits::read_process(self_pid).unwrap(); 62 | 63 | macro_rules! assert_limit_eq{ 64 | {$lhs:expr, $rhs:expr, [$($field:tt,)+]} => { 65 | $( 66 | assert_eq!($lhs.$field, $rhs.$field, stringify!($field)); 67 | )+ 68 | } 69 | } 70 | 71 | assert_limit_eq!( 72 | self_limits, 73 | process_limits, 74 | [ 75 | max_cpu_time, 76 | max_file_size, 77 | max_data_size, 78 | max_stack_size, 79 | max_core_file_size, 80 | max_resident_set, 81 | max_processes, 82 | max_open_files, 83 | max_locked_memory, 84 | max_address_space, 85 | max_file_locks, 86 | max_pending_signals, 87 | max_msgqueue_size, 88 | max_nice_priority, 89 | max_realtime_priority, 90 | max_realtime_timeout, 91 | ] 92 | ); 93 | }); 94 | } 95 | 96 | #[test] 97 | fn linux_sys_limits() { 98 | use rlimit::SysLimits; 99 | 100 | atomically(|| { 101 | // Test reading system limits 102 | let limits = SysLimits::read().unwrap(); 103 | 104 | // file_max should be present 105 | assert!(limits.file_max.is_some(), "file_max should be readable"); 106 | let file_max = limits.file_max.unwrap(); 107 | println!("file_max: {file_max}"); 108 | 109 | // file_nr should be present and valid 110 | assert!(limits.file_nr.is_some(), "file_nr should be readable"); 111 | let file_nr = limits.file_nr.as_ref().unwrap(); 112 | println!( 113 | "file_nr: allocated={}, free={}, maximum={}", 114 | file_nr.allocated, file_nr.free, file_nr.maximum 115 | ); 116 | 117 | // file_nr.maximum should match file_max 118 | assert_eq!( 119 | file_nr.maximum, file_max, 120 | "file_nr.maximum should match file_max" 121 | ); 122 | 123 | // nr_open should be present 124 | assert!(limits.nr_open.is_some(), "nr_open should be readable"); 125 | let nr_open = limits.nr_open.unwrap(); 126 | println!("nr_open: {nr_open}"); 127 | 128 | // nr_open should be greater than 0 129 | assert!(nr_open > 0, "nr_open should be positive"); 130 | 131 | // Note: We don't test writing to system limits here because it requires root privileges 132 | // and would affect the entire system 133 | }); 134 | } 135 | 136 | #[test] 137 | fn unsupported() { 138 | assert!(Resource::UMTXP.is_supported().not()); 139 | let err = Resource::UMTXP.get().unwrap_err(); 140 | assert!(err.kind() == std::io::ErrorKind::Other); 141 | } 142 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.100" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 19 | 20 | [[package]] 21 | name = "bool-logic" 22 | version = "0.3.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "9368c11a5d246c73eef95ebeb5dec1957e159be9b18f5367c35b7920427bb00b" 25 | 26 | [[package]] 27 | name = "camino" 28 | version = "1.2.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" 31 | 32 | [[package]] 33 | name = "libc" 34 | version = "0.2.178" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" 37 | 38 | [[package]] 39 | name = "libc-cfg" 40 | version = "0.3.3" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "8d88eb454f5f9ae5422156917277e4553dd3db1bc2bed916b1a18e89351dbc8c" 43 | dependencies = [ 44 | "anyhow", 45 | "bool-logic", 46 | "camino", 47 | "log", 48 | "proc-macro2", 49 | "quote", 50 | "regex", 51 | "std-next", 52 | "syn", 53 | ] 54 | 55 | [[package]] 56 | name = "log" 57 | version = "0.4.28" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 60 | 61 | [[package]] 62 | name = "memchr" 63 | version = "2.7.4" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 66 | 67 | [[package]] 68 | name = "once_cell" 69 | version = "1.21.3" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 72 | 73 | [[package]] 74 | name = "proc-macro2" 75 | version = "1.0.103" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 78 | dependencies = [ 79 | "unicode-ident", 80 | ] 81 | 82 | [[package]] 83 | name = "quote" 84 | version = "1.0.42" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 87 | dependencies = [ 88 | "proc-macro2", 89 | ] 90 | 91 | [[package]] 92 | name = "regex" 93 | version = "1.12.2" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 96 | dependencies = [ 97 | "aho-corasick", 98 | "memchr", 99 | "regex-automata", 100 | "regex-syntax", 101 | ] 102 | 103 | [[package]] 104 | name = "regex-automata" 105 | version = "0.4.13" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 108 | dependencies = [ 109 | "aho-corasick", 110 | "memchr", 111 | "regex-syntax", 112 | ] 113 | 114 | [[package]] 115 | name = "regex-syntax" 116 | version = "0.8.5" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 119 | 120 | [[package]] 121 | name = "rlimit" 122 | version = "0.10.2" 123 | dependencies = [ 124 | "libc", 125 | "once_cell", 126 | ] 127 | 128 | [[package]] 129 | name = "rlimit-codegen" 130 | version = "0.0.0" 131 | dependencies = [ 132 | "bool-logic", 133 | "libc-cfg", 134 | "scoped-writer", 135 | "std-next", 136 | ] 137 | 138 | [[package]] 139 | name = "scoped-writer" 140 | version = "0.3.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "653b66e4bd74f651b52a450281d228f6c9a279e966884847bf49f377e7639d23" 143 | 144 | [[package]] 145 | name = "simdutf8" 146 | version = "0.1.5" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" 149 | 150 | [[package]] 151 | name = "std-next" 152 | version = "0.1.9" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "04082e93ed1a06debd9148c928234b46d2cf260bc65f44e1d1d3fa594c5beebc" 155 | dependencies = [ 156 | "simdutf8", 157 | "thiserror", 158 | ] 159 | 160 | [[package]] 161 | name = "syn" 162 | version = "2.0.109" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" 165 | dependencies = [ 166 | "proc-macro2", 167 | "quote", 168 | "unicode-ident", 169 | ] 170 | 171 | [[package]] 172 | name = "thiserror" 173 | version = "2.0.12" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 176 | dependencies = [ 177 | "thiserror-impl", 178 | ] 179 | 180 | [[package]] 181 | name = "thiserror-impl" 182 | version = "2.0.12" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 185 | dependencies = [ 186 | "proc-macro2", 187 | "quote", 188 | "syn", 189 | ] 190 | 191 | [[package]] 192 | name = "unicode-ident" 193 | version = "1.0.18" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 196 | -------------------------------------------------------------------------------- /src/proc_limits.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | 3 | use crate::unix::pid_t; 4 | 5 | use std::fs; 6 | use std::io::{self, BufRead}; 7 | use std::num::ParseIntError; 8 | use std::path::Path; 9 | 10 | /// A process's resource limits. It is parsed from the **proc** filesystem. 11 | /// 12 | /// See . 13 | /// 14 | #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))] 15 | #[derive(Debug, Clone, Default)] 16 | #[non_exhaustive] 17 | pub struct ProcLimits { 18 | /// Max cpu time. See also [`Resource::CPU`](struct.Resource.html#associatedconstant.CPU). 19 | pub max_cpu_time: Option, 20 | /// Max file size. See also [`Resource::FSIZE`](struct.Resource.html#associatedconstant.FSIZE). 21 | pub max_file_size: Option, 22 | /// Max data size. See also [`Resource::DATA`](struct.Resource.html#associatedconstant.DATA). 23 | pub max_data_size: Option, 24 | /// Max stack size. See also [`Resource::STACK`](struct.Resource.html#associatedconstant.STACK). 25 | pub max_stack_size: Option, 26 | /// Max core file size. See also [`Resource::CORE`](struct.Resource.html#associatedconstant.CORE). 27 | pub max_core_file_size: Option, 28 | /// Max resident set. See also [`Resource::RSS`](struct.Resource.html#associatedconstant.RSS). 29 | pub max_resident_set: Option, 30 | /// Max processes. See also [`Resource::NPROC`](struct.Resource.html#associatedconstant.NPROC). 31 | pub max_processes: Option, 32 | /// Max open files. See also [`Resource::NOFILE`](struct.Resource.html#associatedconstant.NOFILE). 33 | pub max_open_files: Option, 34 | /// Max locked memory. See also [`Resource::MEMLOCK`](struct.Resource.html#associatedconstant.MEMLOCK). 35 | pub max_locked_memory: Option, 36 | /// Max address space. See also [`Resource::AS`](struct.Resource.html#associatedconstant.AS). 37 | pub max_address_space: Option, 38 | /// Max file locks. See also [`Resource::LOCKS`](struct.Resource.html#associatedconstant.LOCKS). 39 | pub max_file_locks: Option, 40 | /// Max pending signals. See also [`Resource::SIGPENDING`](struct.Resource.html#associatedconstant.SIGPENDING). 41 | pub max_pending_signals: Option, 42 | /// Max msgqueue size. See also [`Resource::MSGQUEUE`](struct.Resource.html#associatedconstant.MSGQUEUE). 43 | pub max_msgqueue_size: Option, 44 | /// Max nice priority. See also [`Resource::NICE`](struct.Resource.html#associatedconstant.NICE). 45 | pub max_nice_priority: Option, 46 | /// Max realtime priority. See also [`Resource::RTPRIO`](struct.Resource.html#associatedconstant.RTPRIO). 47 | pub max_realtime_priority: Option, 48 | /// Max realtime timeout. See also [`Resource::RTTIME`](struct.Resource.html#associatedconstant.RTTIME). 49 | pub max_realtime_timeout: Option, 50 | } 51 | 52 | /// A process's resource limit field. 53 | #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))] 54 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 55 | pub struct ProcLimit { 56 | /// Soft limit. `None` indicates `unlimited`. 57 | pub soft_limit: Option, 58 | /// Hard limit. `None` indicates `unlimited`. 59 | pub hard_limit: Option, 60 | } 61 | 62 | impl ProcLimits { 63 | /// Reads the current process's resource limits from `/proc/self/limits`. 64 | /// 65 | /// # Errors 66 | /// Returns an error if any IO operation failed. 67 | /// 68 | /// Returns an error if the file format is invalid. 69 | /// 70 | pub fn read_self() -> io::Result { 71 | Self::read_proc_fs("/proc/self/limits") 72 | } 73 | 74 | /// Reads a process's resource limits from `/proc/[pid]/limits`. 75 | /// 76 | /// # Errors 77 | /// Returns an error if `pid` is negative. 78 | /// 79 | /// Returns an error if any IO operation failed. 80 | /// 81 | /// Returns an error if the file format is invalid. 82 | /// 83 | pub fn read_process(pid: pid_t) -> io::Result { 84 | if pid < 0 { 85 | return Err(io::Error::new( 86 | io::ErrorKind::InvalidInput, 87 | "ProcLimits: pid must be non-negative", 88 | )); 89 | } 90 | Self::read_proc_fs(format!("/proc/{pid}/limits")) 91 | } 92 | 93 | fn read_proc_fs(limits_path: impl AsRef) -> io::Result { 94 | fn parse_head(head: &str) -> Option<(usize, usize, usize)> { 95 | let s_idx = head.find('S')?; 96 | let h_idx = head[s_idx..].find('H')?; 97 | let u_idx = head[s_idx + h_idx..].find('U')?; 98 | Some((s_idx, h_idx, u_idx)) 99 | } 100 | 101 | fn parse_limit_number(s: &str) -> Result, ParseIntError> { 102 | match s { 103 | "unlimited" => Ok(None), 104 | _ => match s.parse::() { 105 | Ok(n) => Ok(Some(n)), 106 | Err(e) => Err(e), 107 | }, 108 | } 109 | } 110 | 111 | fn io_error_other(s: impl Into) -> io::Error { 112 | io::Error::new(io::ErrorKind::Other, s.into()) 113 | } 114 | 115 | let error_missing_table_head = || io_error_other("ProcLimits: missing table head"); 116 | 117 | let error_invalid_table_head = || io_error_other("ProcLimits: invalid table head"); 118 | 119 | let error_invalid_limit_number = 120 | |e| io_error_other(format!("ProcLimits: invalid limit number: {e}")); 121 | 122 | let error_duplicate_limit_field = || io_error_other("ProcLimits: duplicate limit field"); 123 | 124 | let error_unknown_limit_field = 125 | |s: &str| io_error_other(format!("ProcLimits: unknown limit field: {s:?}")); 126 | 127 | let reader = io::BufReader::new(fs::File::open(limits_path)?); 128 | let mut lines = reader.lines(); 129 | 130 | let head = lines.next().ok_or_else(error_missing_table_head)??; 131 | 132 | let (name_len, soft_len, hard_len) = 133 | parse_head(&head).ok_or_else(error_invalid_table_head)?; 134 | 135 | let mut ans = Self::default(); 136 | 137 | let sorted_table: [(&str, &mut Option); 16] = [ 138 | ("max address space", &mut ans.max_address_space), 139 | ("max core file size", &mut ans.max_core_file_size), 140 | ("max cpu time", &mut ans.max_cpu_time), 141 | ("max data size", &mut ans.max_data_size), 142 | ("max file locks", &mut ans.max_file_locks), 143 | ("max file size", &mut ans.max_file_size), 144 | ("max locked memory", &mut ans.max_locked_memory), 145 | ("max msgqueue size", &mut ans.max_msgqueue_size), 146 | ("max nice priority", &mut ans.max_nice_priority), 147 | ("max open files", &mut ans.max_open_files), 148 | ("max pending signals", &mut ans.max_pending_signals), 149 | ("max processes", &mut ans.max_processes), 150 | ("max realtime priority", &mut ans.max_realtime_priority), 151 | ("max realtime timeout", &mut ans.max_realtime_timeout), 152 | ("max resident set", &mut ans.max_resident_set), 153 | ("max stack size", &mut ans.max_stack_size), 154 | ]; 155 | 156 | for line in lines { 157 | let line = line?; 158 | 159 | let (name, line) = line.split_at(name_len); 160 | let (soft, line) = line.split_at(soft_len); 161 | let (hard, _) = line.split_at(hard_len); 162 | 163 | let name = name.trim().to_lowercase(); 164 | let soft_limit = parse_limit_number(soft.trim()).map_err(error_invalid_limit_number)?; 165 | let hard_limit = parse_limit_number(hard.trim()).map_err(error_invalid_limit_number)?; 166 | let limit = ProcLimit { 167 | soft_limit, 168 | hard_limit, 169 | }; 170 | 171 | match sorted_table.binary_search_by_key(&name.as_str(), |&(s, _)| s) { 172 | Ok(idx) => { 173 | let field = &mut *sorted_table[idx].1; 174 | if field.is_some() { 175 | return Err(error_duplicate_limit_field()); 176 | } 177 | *field = Some(limit); 178 | } 179 | Err(_) => return Err(error_unknown_limit_field(&name)), 180 | } 181 | } 182 | 183 | Ok(ans) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/sys_limits.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | 3 | use std::fs; 4 | use std::io; 5 | use std::path::Path; 6 | 7 | /// System-wide file descriptor limits and statistics. 8 | /// 9 | /// These values are read from and written to the **proc** filesystem under `/proc/sys/fs/`. 10 | /// 11 | /// See and 12 | /// . 13 | /// 14 | #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))] 15 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 16 | #[non_exhaustive] 17 | pub struct SysLimits { 18 | /// System-wide limit on the total number of file descriptors that can be allocated. 19 | /// 20 | /// This corresponds to `/proc/sys/fs/file-max`. 21 | pub file_max: Option, 22 | 23 | /// Current file descriptor usage statistics. 24 | /// 25 | /// This corresponds to `/proc/sys/fs/file-nr` and contains three values: 26 | /// - `allocated`: Number of allocated file descriptors 27 | /// - `free`: Number of free file descriptors (deprecated, always 0 in modern kernels) 28 | /// - `maximum`: Maximum number of file descriptors (same as `file_max`) 29 | /// 30 | /// This field is read-only. 31 | pub file_nr: Option, 32 | 33 | /// Per-process maximum number of file descriptors that can be allocated. 34 | /// 35 | /// This corresponds to `/proc/sys/fs/nr_open`. This is the ceiling value that 36 | /// can be set for the per-process NOFILE limit (both hard and soft). 37 | pub nr_open: Option, 38 | } 39 | 40 | /// File descriptor usage statistics from `/proc/sys/fs/file-nr`. 41 | /// 42 | /// **Note:** The "free" field has been deprecated since Linux 2.6 and is always 0 in modern kernels. 43 | #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))] 44 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 45 | pub struct FileNr { 46 | /// Number of allocated file descriptors. 47 | pub allocated: u64, 48 | /// Number of free file descriptors. 49 | /// 50 | /// **Deprecated:** This field has been deprecated since Linux 2.6 and is always 0 in modern kernels. 51 | /// It does not contain meaningful data. 52 | pub free: u64, 53 | /// Maximum number of file descriptors (same as `file_max`). 54 | pub maximum: u64, 55 | } 56 | 57 | impl SysLimits { 58 | /// Reads system-wide file descriptor limits from `/proc/sys/fs/`. 59 | /// 60 | /// # Errors 61 | /// Returns an error if any IO operation failed or if the file format is invalid. 62 | /// 63 | /// # Examples 64 | /// ```no_run 65 | /// # #[cfg(any(target_os = "linux", target_os = "android"))] 66 | /// # { 67 | /// use rlimit::SysLimits; 68 | /// 69 | /// let limits = SysLimits::read().unwrap(); 70 | /// println!("System file-max: {:?}", limits.file_max); 71 | /// println!("Current usage: {:?}", limits.file_nr); 72 | /// println!("Per-process nr_open: {:?}", limits.nr_open); 73 | /// # } 74 | /// ``` 75 | pub fn read() -> io::Result { 76 | let file_max = read_u64_from_file("/proc/sys/fs/file-max").ok(); 77 | let file_nr = read_file_nr("/proc/sys/fs/file-nr").ok(); 78 | let nr_open = read_u64_from_file("/proc/sys/fs/nr_open").ok(); 79 | 80 | Ok(Self { 81 | file_max, 82 | file_nr, 83 | nr_open, 84 | }) 85 | } 86 | 87 | /// Writes the `file_max` value to `/proc/sys/fs/file-max`. 88 | /// 89 | /// This operation typically requires root privileges or `CAP_SYS_ADMIN` capability. 90 | /// 91 | /// # Errors 92 | /// Returns an error if the write operation failed, which can happen if: 93 | /// - The process lacks sufficient privileges 94 | /// - The value is invalid 95 | /// - The filesystem is read-only 96 | /// 97 | /// # Examples 98 | /// ```no_run 99 | /// # #[cfg(any(target_os = "linux", target_os = "android"))] 100 | /// # { 101 | /// use rlimit::SysLimits; 102 | /// 103 | /// // This typically requires root privileges 104 | /// SysLimits::set_file_max(1048576).unwrap(); 105 | /// # } 106 | /// ``` 107 | pub fn set_file_max(value: u64) -> io::Result<()> { 108 | write_u64_to_file("/proc/sys/fs/file-max", value) 109 | } 110 | 111 | /// Writes the `nr_open` value to `/proc/sys/fs/nr_open`. 112 | /// 113 | /// This operation typically requires root privileges or `CAP_SYS_ADMIN` capability. 114 | /// 115 | /// # Errors 116 | /// Returns an error if the write operation failed, which can happen if: 117 | /// - The process lacks sufficient privileges 118 | /// - The value is invalid 119 | /// - The filesystem is read-only 120 | /// 121 | /// # Examples 122 | /// ```no_run 123 | /// # #[cfg(any(target_os = "linux", target_os = "android"))] 124 | /// # { 125 | /// use rlimit::SysLimits; 126 | /// 127 | /// // This typically requires root privileges 128 | /// SysLimits::set_nr_open(1048576).unwrap(); 129 | /// # } 130 | /// ``` 131 | pub fn set_nr_open(value: u64) -> io::Result<()> { 132 | write_u64_to_file("/proc/sys/fs/nr_open", value) 133 | } 134 | } 135 | 136 | /// Reads a u64 value from a file in `/proc/sys/fs/`. 137 | fn read_u64_from_file(path: impl AsRef) -> io::Result { 138 | let content = fs::read_to_string(path)?; 139 | content 140 | .trim() 141 | .parse::() 142 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) 143 | } 144 | 145 | /// Reads file descriptor statistics from `/proc/sys/fs/file-nr`. 146 | /// 147 | /// The file contains three whitespace-separated values: allocated, free, and maximum. 148 | fn read_file_nr(path: impl AsRef) -> io::Result { 149 | let content = fs::read_to_string(path)?; 150 | let parts: Vec<&str> = content.split_whitespace().collect(); 151 | 152 | if parts.len() != 3 { 153 | return Err(io::Error::new( 154 | io::ErrorKind::InvalidData, 155 | format!("Expected 3 values in file-nr, got {}", parts.len()), 156 | )); 157 | } 158 | 159 | let allocated = parts[0] 160 | .parse::() 161 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; 162 | let free = parts[1] 163 | .parse::() 164 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; 165 | let maximum = parts[2] 166 | .parse::() 167 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; 168 | 169 | Ok(FileNr { 170 | allocated, 171 | free, 172 | maximum, 173 | }) 174 | } 175 | 176 | /// Writes a u64 value to a file in `/proc/sys/fs/`. 177 | fn write_u64_to_file(path: impl AsRef, value: u64) -> io::Result<()> { 178 | fs::write(path, value.to_string()) 179 | } 180 | 181 | #[cfg(test)] 182 | mod tests { 183 | use super::*; 184 | 185 | #[test] 186 | fn test_read_system_limits() { 187 | // This test only verifies that the function doesn't crash 188 | // The actual values depend on the system configuration 189 | let result = SysLimits::read(); 190 | 191 | // Should succeed on Linux/Android systems 192 | if cfg!(any(target_os = "linux", target_os = "android")) { 193 | let limits = result.unwrap(); 194 | 195 | // At least one of the fields should be populated 196 | assert!( 197 | limits.file_max.is_some() || limits.file_nr.is_some() || limits.nr_open.is_some(), 198 | "At least one limit should be readable" 199 | ); 200 | 201 | // If file_nr is present, validate its structure 202 | if let Some(file_nr) = limits.file_nr { 203 | // Maximum should match file_max if both are present 204 | if let Some(file_max) = limits.file_max { 205 | assert_eq!(file_nr.maximum, file_max); 206 | } 207 | } 208 | } 209 | } 210 | 211 | #[test] 212 | fn test_file_nr_parsing() { 213 | // Test the internal parsing logic with mock data 214 | use std::io::Write; 215 | 216 | let temp_dir = std::env::temp_dir(); 217 | let test_file = temp_dir.join(format!( 218 | "test_file_nr_{:?}.txt", 219 | std::thread::current().id() 220 | )); 221 | 222 | // Write test data 223 | let mut file = fs::File::create(&test_file).unwrap(); 224 | write!(file, "2208\t0\t9223372036854775807").unwrap(); 225 | file.flush().unwrap(); 226 | drop(file); 227 | 228 | // Read and verify 229 | let result = read_file_nr(&test_file).unwrap(); 230 | assert_eq!(result.allocated, 2208); 231 | assert_eq!(result.free, 0); 232 | assert_eq!(result.maximum, 9_223_372_036_854_775_807); 233 | 234 | // Clean up 235 | fs::remove_file(&test_file).ok(); 236 | } 237 | 238 | #[test] 239 | fn test_u64_parsing() { 240 | use std::io::Write; 241 | 242 | let temp_dir = std::env::temp_dir(); 243 | let test_file = temp_dir.join(format!( 244 | "test_u64_{:?}.txt", 245 | std::thread::current().id() 246 | )); 247 | 248 | // Write test data 249 | let mut file = fs::File::create(&test_file).unwrap(); 250 | writeln!(file, "1048576").unwrap(); 251 | file.flush().unwrap(); 252 | drop(file); 253 | 254 | // Read and verify 255 | let result = read_u64_from_file(&test_file).unwrap(); 256 | assert_eq!(result, 1_048_576); 257 | 258 | // Clean up 259 | fs::remove_file(&test_file).ok(); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/resource/generated.rs: -------------------------------------------------------------------------------- 1 | use super::ParseResourceError; 2 | use super::Resource; 3 | use crate::bindings as C; 4 | 5 | impl Resource { 6 | /// The maximum size (in bytes) of the process's virtual memory (address space). 7 | pub const AS: Self = Self { 8 | tag: 1, 9 | value: C::RLIMIT_AS, 10 | }; 11 | 12 | /// The maximum size (in bytes) of a core file that the process may dump. 13 | pub const CORE: Self = Self { 14 | tag: 2, 15 | value: C::RLIMIT_CORE, 16 | }; 17 | 18 | /// A limit (in seconds) on the amount of CPU time that the process can consume. 19 | pub const CPU: Self = Self { 20 | tag: 3, 21 | value: C::RLIMIT_CPU, 22 | }; 23 | 24 | /// The maximum size (in bytes) of the process's data segment (initialized data, uninitialized data, and heap). 25 | pub const DATA: Self = Self { 26 | tag: 4, 27 | value: C::RLIMIT_DATA, 28 | }; 29 | 30 | /// The maximum size (in bytes) of files that the process may create. 31 | pub const FSIZE: Self = Self { 32 | tag: 5, 33 | value: C::RLIMIT_FSIZE, 34 | }; 35 | 36 | /// The maximum number of kqueues this user id is allowed to create. 37 | pub const KQUEUES: Self = Self { 38 | tag: 6, 39 | value: C::RLIMIT_KQUEUES, 40 | }; 41 | 42 | /// (early Linux 2.4 only) 43 | /// 44 | /// A limit on the combined number of `flock(2)` locks and `fcntl(2)` leases that this process may establish. 45 | pub const LOCKS: Self = Self { 46 | tag: 7, 47 | value: C::RLIMIT_LOCKS, 48 | }; 49 | 50 | /// The maximum number (in bytes) of memory that may be locked into RAM. 51 | pub const MEMLOCK: Self = Self { 52 | tag: 8, 53 | value: C::RLIMIT_MEMLOCK, 54 | }; 55 | 56 | /// A limit on the number of bytes that can be allocated for POSIX message queues for the real user ID of the calling process. 57 | pub const MSGQUEUE: Self = Self { 58 | tag: 9, 59 | value: C::RLIMIT_MSGQUEUE, 60 | }; 61 | 62 | /// This specifies a ceiling to which the process's nice value can be raised using `setpriority(2)` or `nice(2)`. 63 | pub const NICE: Self = Self { 64 | tag: 10, 65 | value: C::RLIMIT_NICE, 66 | }; 67 | 68 | /// This specifies a value one greater than the maximum file descriptor number that can be opened by this process. 69 | pub const NOFILE: Self = Self { 70 | tag: 11, 71 | value: C::RLIMIT_NOFILE, 72 | }; 73 | 74 | /// The number of open vnode monitors. 75 | pub const NOVMON: Self = Self { 76 | tag: 12, 77 | value: C::RLIMIT_NOVMON, 78 | }; 79 | 80 | /// A limit on the number of extant process (or, more precisely on Linux, threads) for the real user ID of the calling process. 81 | pub const NPROC: Self = Self { 82 | tag: 13, 83 | value: C::RLIMIT_NPROC, 84 | }; 85 | 86 | /// The maximum number of pseudo-terminals this user id is allowed to create. 87 | pub const NPTS: Self = Self { 88 | tag: 14, 89 | value: C::RLIMIT_NPTS, 90 | }; 91 | 92 | /// The maximum number of simultaneous threads (Lightweight Processes) for this user id. 93 | /// Kernel threads and the first thread of each process are not counted against this limit. 94 | pub const NTHR: Self = Self { 95 | tag: 15, 96 | value: C::RLIMIT_NTHR, 97 | }; 98 | 99 | /// The maximum number of POSIX-type advisory-mode locks available to this user. 100 | pub const POSIXLOCKS: Self = Self { 101 | tag: 16, 102 | value: C::RLIMIT_POSIXLOCKS, 103 | }; 104 | 105 | /// A limit (in bytes) on the process's resident set (the number of virtual pages resident in RAM). 106 | pub const RSS: Self = Self { 107 | tag: 17, 108 | value: C::RLIMIT_RSS, 109 | }; 110 | 111 | /// This specifies a ceiling on the real-time priority that may be set for this process using `sched_setscheduler(2)` and `sched_setparam(2)`. 112 | pub const RTPRIO: Self = Self { 113 | tag: 18, 114 | value: C::RLIMIT_RTPRIO, 115 | }; 116 | 117 | /// 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. 118 | pub const RTTIME: Self = Self { 119 | tag: 19, 120 | value: C::RLIMIT_RTTIME, 121 | }; 122 | 123 | /// The maximum size (in bytes) of socket buffer usage for this user. This limits the amount of network memory, and hence the amount of mbufs, that this user may hold at any time. 124 | pub const SBSIZE: Self = Self { 125 | tag: 20, 126 | value: C::RLIMIT_SBSIZE, 127 | }; 128 | 129 | /// A limit on the number of signals that may be queued for the real user ID ofthe calling process. 130 | pub const SIGPENDING: Self = Self { 131 | tag: 21, 132 | value: C::RLIMIT_SIGPENDING, 133 | }; 134 | 135 | /// The maximum size (in bytes) of the process stack. 136 | pub const STACK: Self = Self { 137 | tag: 22, 138 | value: C::RLIMIT_STACK, 139 | }; 140 | 141 | /// The maximum size (in bytes) of the swap space that may be reserved or used by all of this user id's processes. 142 | pub const SWAP: Self = Self { 143 | tag: 23, 144 | value: C::RLIMIT_SWAP, 145 | }; 146 | 147 | /// **AIX**: The maximum number of threads each process can create. This limit is enforced by the kernel and the pthread debug library. 148 | pub const THREADS: Self = Self { 149 | tag: 24, 150 | value: C::RLIMIT_THREADS, 151 | }; 152 | 153 | /// The number of shared locks a given user may create simultaneously. 154 | pub const UMTXP: Self = Self { 155 | tag: 25, 156 | value: C::RLIMIT_UMTXP, 157 | }; 158 | 159 | /// An alias for [`RLIMIT_AS`](Resource::AS). The maximum size of a process's mapped address space in bytes. 160 | pub const VMEM: Self = Self { 161 | tag: 26, 162 | value: C::RLIMIT_VMEM, 163 | }; 164 | } 165 | 166 | impl Resource { 167 | pub(super) const fn find_name_by_tag(tag: u8) -> Option<&'static str> { 168 | match tag { 169 | 1 => Some("RLIMIT_AS"), 170 | 2 => Some("RLIMIT_CORE"), 171 | 3 => Some("RLIMIT_CPU"), 172 | 4 => Some("RLIMIT_DATA"), 173 | 5 => Some("RLIMIT_FSIZE"), 174 | 6 => Some("RLIMIT_KQUEUES"), 175 | 7 => Some("RLIMIT_LOCKS"), 176 | 8 => Some("RLIMIT_MEMLOCK"), 177 | 9 => Some("RLIMIT_MSGQUEUE"), 178 | 10 => Some("RLIMIT_NICE"), 179 | 11 => Some("RLIMIT_NOFILE"), 180 | 12 => Some("RLIMIT_NOVMON"), 181 | 13 => Some("RLIMIT_NPROC"), 182 | 14 => Some("RLIMIT_NPTS"), 183 | 15 => Some("RLIMIT_NTHR"), 184 | 16 => Some("RLIMIT_POSIXLOCKS"), 185 | 17 => Some("RLIMIT_RSS"), 186 | 18 => Some("RLIMIT_RTPRIO"), 187 | 19 => Some("RLIMIT_RTTIME"), 188 | 20 => Some("RLIMIT_SBSIZE"), 189 | 21 => Some("RLIMIT_SIGPENDING"), 190 | 22 => Some("RLIMIT_STACK"), 191 | 23 => Some("RLIMIT_SWAP"), 192 | 24 => Some("RLIMIT_THREADS"), 193 | 25 => Some("RLIMIT_UMTXP"), 194 | 26 => Some("RLIMIT_VMEM"), 195 | _ => None, 196 | } 197 | } 198 | 199 | pub(super) const fn find_ident_by_tag(tag: u8) -> Option<&'static str> { 200 | match tag { 201 | 1 => Some("AS"), 202 | 2 => Some("CORE"), 203 | 3 => Some("CPU"), 204 | 4 => Some("DATA"), 205 | 5 => Some("FSIZE"), 206 | 6 => Some("KQUEUES"), 207 | 7 => Some("LOCKS"), 208 | 8 => Some("MEMLOCK"), 209 | 9 => Some("MSGQUEUE"), 210 | 10 => Some("NICE"), 211 | 11 => Some("NOFILE"), 212 | 12 => Some("NOVMON"), 213 | 13 => Some("NPROC"), 214 | 14 => Some("NPTS"), 215 | 15 => Some("NTHR"), 216 | 16 => Some("POSIXLOCKS"), 217 | 17 => Some("RSS"), 218 | 18 => Some("RTPRIO"), 219 | 19 => Some("RTTIME"), 220 | 20 => Some("SBSIZE"), 221 | 21 => Some("SIGPENDING"), 222 | 22 => Some("STACK"), 223 | 23 => Some("SWAP"), 224 | 24 => Some("THREADS"), 225 | 25 => Some("UMTXP"), 226 | 26 => Some("VMEM"), 227 | _ => None, 228 | } 229 | } 230 | } 231 | impl std::str::FromStr for Resource { 232 | type Err = ParseResourceError; 233 | 234 | fn from_str(s: &str) -> Result { 235 | match s { 236 | "RLIMIT_AS" => Ok(Self::AS), 237 | "RLIMIT_CORE" => Ok(Self::CORE), 238 | "RLIMIT_CPU" => Ok(Self::CPU), 239 | "RLIMIT_DATA" => Ok(Self::DATA), 240 | "RLIMIT_FSIZE" => Ok(Self::FSIZE), 241 | "RLIMIT_KQUEUES" => Ok(Self::KQUEUES), 242 | "RLIMIT_LOCKS" => Ok(Self::LOCKS), 243 | "RLIMIT_MEMLOCK" => Ok(Self::MEMLOCK), 244 | "RLIMIT_MSGQUEUE" => Ok(Self::MSGQUEUE), 245 | "RLIMIT_NICE" => Ok(Self::NICE), 246 | "RLIMIT_NOFILE" => Ok(Self::NOFILE), 247 | "RLIMIT_NOVMON" => Ok(Self::NOVMON), 248 | "RLIMIT_NPROC" => Ok(Self::NPROC), 249 | "RLIMIT_NPTS" => Ok(Self::NPTS), 250 | "RLIMIT_NTHR" => Ok(Self::NTHR), 251 | "RLIMIT_POSIXLOCKS" => Ok(Self::POSIXLOCKS), 252 | "RLIMIT_RSS" => Ok(Self::RSS), 253 | "RLIMIT_RTPRIO" => Ok(Self::RTPRIO), 254 | "RLIMIT_RTTIME" => Ok(Self::RTTIME), 255 | "RLIMIT_SBSIZE" => Ok(Self::SBSIZE), 256 | "RLIMIT_SIGPENDING" => Ok(Self::SIGPENDING), 257 | "RLIMIT_STACK" => Ok(Self::STACK), 258 | "RLIMIT_SWAP" => Ok(Self::SWAP), 259 | "RLIMIT_THREADS" => Ok(Self::THREADS), 260 | "RLIMIT_UMTXP" => Ok(Self::UMTXP), 261 | "RLIMIT_VMEM" => Ok(Self::VMEM), 262 | _ => Err(ParseResourceError(())), 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | branches: 7 | - main 8 | schedule: # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#onschedule 9 | - cron: '0 0 * * 0' # at midnight of each sunday 10 | 11 | 12 | name: CI 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | develop: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: dtolnay/rust-toolchain@nightly 23 | with: 24 | components: rustfmt, clippy 25 | - uses: Swatinem/rust-cache@v2 26 | - run: cargo fmt --all -- --check 27 | - run: cargo clippy -- -D warnings 28 | - run: cargo test --all-features 29 | 30 | msrv: 31 | runs-on: ubuntu-latest 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | toolchain: 36 | - 1.65.0 # MSRV 37 | - stable 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: taiki-e/install-action@just 41 | - uses: dtolnay/rust-toolchain@master 42 | with: 43 | toolchain: ${{ matrix.toolchain }} 44 | - uses: Swatinem/rust-cache@v2 45 | - run: cargo test -p rlimit --all-features 46 | 47 | cross: 48 | runs-on: ubuntu-latest 49 | strategy: 50 | fail-fast: false 51 | matrix: 52 | target: 53 | # copied from `rustup target list` 54 | # - aarch64-apple-darwin # (build error) 55 | # - aarch64-apple-ios # (build error) 56 | # - aarch64-apple-ios-sim # (build error) 57 | # - aarch64-fuchsia # (fuchsia does not have [sg]etrlimit) 58 | - aarch64-linux-android 59 | # - aarch64-pc-windows-msvc # (missing docker image) 60 | - aarch64-unknown-linux-gnu 61 | - aarch64-unknown-linux-musl 62 | # - aarch64-unknown-none # (no std) 63 | # - aarch64-unknown-none-softfloat # (no std) 64 | - arm-linux-androideabi 65 | - arm-unknown-linux-gnueabi # (libc mismatch) 66 | - arm-unknown-linux-gnueabihf # (libc mismatch) 67 | - arm-unknown-linux-musleabi 68 | - arm-unknown-linux-musleabihf 69 | # - armebv7r-none-eabi # (no std) 70 | # - armebv7r-none-eabihf # (no std) 71 | - armv5te-unknown-linux-gnueabi # (libc mismatch) 72 | - armv5te-unknown-linux-musleabi 73 | - armv7-linux-androideabi 74 | # - armv7-unknown-linux-gnueabi # (missing docker image) 75 | - armv7-unknown-linux-gnueabihf # (libc mismatch) 76 | # - armv7-unknown-linux-musleabi # (missing docker image) 77 | - armv7-unknown-linux-musleabihf 78 | # - armv7a-none-eabi # (no std) 79 | # - armv7r-none-eabi # (no std) 80 | # - armv7r-none-eabihf # (no std) 81 | # - asmjs-unknown-emscripten # (build error) 82 | # - i586-pc-windows-msvc # (missing docker image) 83 | - i586-unknown-linux-gnu # (libc mismatch) 84 | - i586-unknown-linux-musl 85 | - i686-linux-android 86 | # - i686-pc-windows-gnu # (missing docker image) 87 | # - i686-pc-windows-msvc # (missing docker image) 88 | # - i686-unknown-freebsd # (missing docker image) 89 | - i686-unknown-linux-gnu # (libc mismatch) 90 | - i686-unknown-linux-musl 91 | # - mips-unknown-linux-gnu # (rust-std unavailable) 92 | # - mips-unknown-linux-musl # (rust-std unavailable) 93 | # - mips64-unknown-linux-gnuabi64 # (rust-std unavailable) 94 | # - mips64-unknown-linux-muslabi64 # (missing docker image) 95 | # - mips64el-unknown-linux-gnuabi64 # (rust-std unavailable) 96 | # - mips64el-unknown-linux-muslabi64 # (missing docker image) 97 | # - mipsel-unknown-linux-gnu # (rust-std unavailable) 98 | # - mipsel-unknown-linux-musl # (rust-std unavailable) 99 | # - nvptx64-nvidia-cuda # (no std) 100 | - powerpc-unknown-linux-gnu # (libc mismatch) 101 | # - powerpc64-unknown-linux-gnu # (missing docker image) 102 | - powerpc64le-unknown-linux-gnu 103 | # - riscv32i-unknown-none-elf # (no std) 104 | # - riscv32imac-unknown-none-elf # (no std) 105 | # - riscv32imc-unknown-none-elf # (no std) 106 | - riscv64gc-unknown-linux-gnu 107 | # - riscv64gc-unknown-none-elf # (no std) 108 | # - riscv64imac-unknown-none-elf # (no std) 109 | - s390x-unknown-linux-gnu 110 | # - sparc64-unknown-linux-gnu # (missing docker image) 111 | # - sparcv9-sun-solaris # (build error) 112 | # - thumbv6m-none-eabi # (no std) 113 | # - thumbv7em-none-eabi # (no std) 114 | # - thumbv7em-none-eabihf # (no std) 115 | # - thumbv7m-none-eabi # (no std) 116 | # - thumbv7neon-linux-androideabi # (missing docker image) 117 | # - thumbv7neon-unknown-linux-gnueabihf # (missing docker image) 118 | # - thumbv8m.base-none-eabi # (no std) 119 | # - thumbv8m.main-none-eabi # (no std) 120 | # - thumbv8m.main-none-eabihf # (no std) 121 | # - wasm32-unknown-emscripten # (build error) 122 | # - wasm32-unknown-unknown # (tester error) 123 | # - wasm32-wasi # (tester error) 124 | # - x86_64-apple-darwin # (build error) 125 | # - x86_64-apple-ios # (build error) 126 | # - x86_64-fortanix-unknown-sgx # (tester error) 127 | # - x86_64-fuchsia # (fuchsia does not have [sg]etrlimit) 128 | - x86_64-linux-android 129 | # - x86_64-pc-solaris # (missing docker image) 130 | # - x86_64-pc-windows-gnu # (other) 131 | # - x86_64-pc-windows-msvc # (missing docker image) 132 | # - x86_64-sun-solaris # (build error) 133 | # - x86_64-unknown-freebsd # (missing docker image) 134 | # - x86_64-unknown-illumos # (build error) 135 | - x86_64-unknown-linux-gnu 136 | # - x86_64-unknown-linux-gnux32 # (missing docker image) 137 | - x86_64-unknown-linux-musl 138 | # - x86_64-unknown-netbsd # (tester error) 139 | # - x86_64-unknown-redox # (nightly) 140 | 141 | steps: 142 | - uses: actions/checkout@v4 143 | - uses: dtolnay/rust-toolchain@stable 144 | with: 145 | targets: ${{ matrix.target }} 146 | - uses: taiki-e/install-action@v2 147 | with: 148 | tool: cross 149 | - name: cross test 150 | run: | 151 | # TODO: remove this when `cross` updates 152 | # See https://github.com/cross-rs/cross/issues/1217 153 | export CROSS_TARGET_AARCH64_LINUX_ANDROID_IMAGE="ghcr.io/cross-rs/aarch64-linux-android:main" 154 | export CROSS_TARGET_I686_LINUX_ANDROID_IMAGE="ghcr.io/cross-rs/i686-linux-android:main" 155 | export CROSS_TARGET_X86_64_LINUX_ANDROID_IMAGE="ghcr.io/cross-rs/x86_64-linux-android:main" 156 | 157 | cross test --all-features --target=${{ matrix.target }} 158 | 159 | macos: 160 | strategy: 161 | fail-fast: false 162 | matrix: 163 | include: 164 | - runner: macos-latest 165 | - runner: macos-15-intel 166 | runs-on: ${{ matrix.runner }} 167 | steps: 168 | - uses: actions/checkout@v4 169 | - uses: dtolnay/rust-toolchain@stable 170 | - run: | 171 | cargo test --all-features 172 | 173 | windows: 174 | runs-on: windows-latest 175 | steps: 176 | - uses: actions/checkout@v4 177 | - uses: dtolnay/rust-toolchain@stable 178 | - run: | 179 | cargo test --all-features 180 | 181 | ubuntu: 182 | runs-on: ubuntu-latest 183 | steps: 184 | - uses: actions/checkout@v4 185 | - uses: dtolnay/rust-toolchain@stable 186 | - run: | 187 | cargo test --all-features 188 | 189 | diff-codegen: 190 | runs-on: ubuntu-latest 191 | steps: 192 | - uses: actions/checkout@v4 193 | - uses: dtolnay/rust-toolchain@stable 194 | - name: diff 195 | run: | 196 | ./scripts/codegen.sh 197 | [[ -z $(git status -s) ]] # Fail if changed. See https://stackoverflow.com/a/9393642 198 | 199 | bsd: 200 | runs-on: ubuntu-latest 201 | permissions: 202 | contents: read 203 | strategy: 204 | fail-fast: false 205 | matrix: 206 | os: 207 | - name: freebsd 208 | version: '14.3' 209 | - name: netbsd 210 | version: '10.1' 211 | # - name: openbsd 212 | # version: '7.8' 213 | steps: 214 | - uses: actions/checkout@v4 215 | - name: Test on ${{ matrix.os.name }} 216 | uses: cross-platform-actions/action@v0.30.0 217 | with: 218 | operating_system: ${{ matrix.os.name }} 219 | version: ${{ matrix.os.version }} 220 | run: | 221 | uname -a 222 | # Install curl if needed (NetBSD may not have it by default) 223 | if ! command -v curl > /dev/null 2>&1; then 224 | if command -v pkgin > /dev/null 2>&1; then 225 | sudo pkgin -y install curl 226 | elif command -v pkg_add > /dev/null 2>&1; then 227 | sudo pkg_add -v curl 228 | elif command -v pkg > /dev/null 2>&1; then 229 | sudo pkg install -y curl 230 | fi 231 | fi 232 | # Install rustup 233 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal 234 | . "$HOME/.cargo/env" 235 | rustc --version 236 | cargo --version 237 | cargo test --all-features 238 | --------------------------------------------------------------------------------