├── .github ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── clippy.toml ├── codegen ├── Cargo.toml └── src │ ├── bindings.rs │ ├── build.rs │ ├── main.rs │ ├── resource.md │ └── resource.rs ├── examples └── nofile.rs ├── justfile ├── scripts ├── codegen.sh └── download-libc.sh ├── src ├── bindings.rs ├── lib.rs ├── proc_limits.rs ├── resource │ ├── generated.rs │ └── mod.rs ├── tools.rs ├── unix.rs └── windows.rs └── tests └── it ├── linux.rs ├── main.rs ├── unix.rs ├── utils.rs └── windows.rs /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | jobs: 15 | develop: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: dtolnay/rust-toolchain@nightly 20 | with: 21 | components: rustfmt, clippy 22 | - uses: Swatinem/rust-cache@v2 23 | - run: cargo fmt --all -- --check 24 | - run: cargo clippy -- -D warnings 25 | - run: cargo test --all-features 26 | 27 | msrv: 28 | runs-on: ubuntu-latest 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | toolchain: 33 | - 1.65.0 # MSRV 34 | - stable 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: taiki-e/install-action@just 38 | - uses: dtolnay/rust-toolchain@master 39 | with: 40 | toolchain: ${{ matrix.toolchain }} 41 | - uses: Swatinem/rust-cache@v2 42 | - run: cargo test -p rlimit --all-features 43 | 44 | cross: 45 | runs-on: ubuntu-latest 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | target: 50 | # copied from `rustup target list` 51 | # - aarch64-apple-darwin # (build error) 52 | # - aarch64-apple-ios # (build error) 53 | # - aarch64-apple-ios-sim # (build error) 54 | # - aarch64-fuchsia # (fuchsia does not have [sg]etrlimit) 55 | - aarch64-linux-android 56 | # - aarch64-pc-windows-msvc # (missing docker image) 57 | - aarch64-unknown-linux-gnu 58 | - aarch64-unknown-linux-musl 59 | # - aarch64-unknown-none # (no std) 60 | # - aarch64-unknown-none-softfloat # (no std) 61 | - arm-linux-androideabi 62 | - arm-unknown-linux-gnueabi # (libc mismatch) 63 | - arm-unknown-linux-gnueabihf # (libc mismatch) 64 | - arm-unknown-linux-musleabi 65 | - arm-unknown-linux-musleabihf 66 | # - armebv7r-none-eabi # (no std) 67 | # - armebv7r-none-eabihf # (no std) 68 | - armv5te-unknown-linux-gnueabi # (libc mismatch) 69 | - armv5te-unknown-linux-musleabi 70 | - armv7-linux-androideabi 71 | # - armv7-unknown-linux-gnueabi # (missing docker image) 72 | - armv7-unknown-linux-gnueabihf # (libc mismatch) 73 | # - armv7-unknown-linux-musleabi # (missing docker image) 74 | - armv7-unknown-linux-musleabihf 75 | # - armv7a-none-eabi # (no std) 76 | # - armv7r-none-eabi # (no std) 77 | # - armv7r-none-eabihf # (no std) 78 | # - asmjs-unknown-emscripten # (build error) 79 | # - i586-pc-windows-msvc # (missing docker image) 80 | - i586-unknown-linux-gnu # (libc mismatch) 81 | - i586-unknown-linux-musl 82 | - i686-linux-android 83 | # - i686-pc-windows-gnu # (missing docker image) 84 | # - i686-pc-windows-msvc # (missing docker image) 85 | # - i686-unknown-freebsd # (missing docker image) 86 | - i686-unknown-linux-gnu # (libc mismatch) 87 | - i686-unknown-linux-musl 88 | # - mips-unknown-linux-gnu # (rust-std unavailable) 89 | # - mips-unknown-linux-musl # (rust-std unavailable) 90 | # - mips64-unknown-linux-gnuabi64 # (rust-std unavailable) 91 | # - mips64-unknown-linux-muslabi64 # (missing docker image) 92 | # - mips64el-unknown-linux-gnuabi64 # (rust-std unavailable) 93 | # - mips64el-unknown-linux-muslabi64 # (missing docker image) 94 | # - mipsel-unknown-linux-gnu # (rust-std unavailable) 95 | # - mipsel-unknown-linux-musl # (rust-std unavailable) 96 | # - nvptx64-nvidia-cuda # (no std) 97 | - powerpc-unknown-linux-gnu # (libc mismatch) 98 | # - powerpc64-unknown-linux-gnu # (missing docker image) 99 | - powerpc64le-unknown-linux-gnu 100 | # - riscv32i-unknown-none-elf # (no std) 101 | # - riscv32imac-unknown-none-elf # (no std) 102 | # - riscv32imc-unknown-none-elf # (no std) 103 | - riscv64gc-unknown-linux-gnu 104 | # - riscv64gc-unknown-none-elf # (no std) 105 | # - riscv64imac-unknown-none-elf # (no std) 106 | - s390x-unknown-linux-gnu 107 | # - sparc64-unknown-linux-gnu # (missing docker image) 108 | # - sparcv9-sun-solaris # (build error) 109 | # - thumbv6m-none-eabi # (no std) 110 | # - thumbv7em-none-eabi # (no std) 111 | # - thumbv7em-none-eabihf # (no std) 112 | # - thumbv7m-none-eabi # (no std) 113 | # - thumbv7neon-linux-androideabi # (missing docker image) 114 | # - thumbv7neon-unknown-linux-gnueabihf # (missing docker image) 115 | # - thumbv8m.base-none-eabi # (no std) 116 | # - thumbv8m.main-none-eabi # (no std) 117 | # - thumbv8m.main-none-eabihf # (no std) 118 | # - wasm32-unknown-emscripten # (build error) 119 | # - wasm32-unknown-unknown # (tester error) 120 | # - wasm32-wasi # (tester error) 121 | # - x86_64-apple-darwin # (build error) 122 | # - x86_64-apple-ios # (build error) 123 | # - x86_64-fortanix-unknown-sgx # (tester error) 124 | # - x86_64-fuchsia # (fuchsia does not have [sg]etrlimit) 125 | - x86_64-linux-android 126 | # - x86_64-pc-solaris # (missing docker image) 127 | # - x86_64-pc-windows-gnu # (other) 128 | # - x86_64-pc-windows-msvc # (missing docker image) 129 | # - x86_64-sun-solaris # (build error) 130 | # - x86_64-unknown-freebsd # (missing docker image) 131 | # - x86_64-unknown-illumos # (build error) 132 | - x86_64-unknown-linux-gnu 133 | # - x86_64-unknown-linux-gnux32 # (missing docker image) 134 | - x86_64-unknown-linux-musl 135 | # - x86_64-unknown-netbsd # (tester error) 136 | # - x86_64-unknown-redox # (nightly) 137 | 138 | steps: 139 | - uses: actions/checkout@v4 140 | - uses: dtolnay/rust-toolchain@stable 141 | with: 142 | targets: ${{ matrix.target }} 143 | - uses: taiki-e/install-action@v2 144 | with: 145 | tool: cross 146 | - name: cross test 147 | run: | 148 | # TODO: remove this when `cross` updates 149 | # See https://github.com/cross-rs/cross/issues/1217 150 | export CROSS_TARGET_AARCH64_LINUX_ANDROID_IMAGE="ghcr.io/cross-rs/aarch64-linux-android:main" 151 | export CROSS_TARGET_I686_LINUX_ANDROID_IMAGE="ghcr.io/cross-rs/i686-linux-android:main" 152 | export CROSS_TARGET_X86_64_LINUX_ANDROID_IMAGE="ghcr.io/cross-rs/x86_64-linux-android:main" 153 | 154 | cross test --all-features --target=${{ matrix.target }} 155 | 156 | macos: 157 | runs-on: macos-latest 158 | steps: 159 | - uses: actions/checkout@v4 160 | - uses: dtolnay/rust-toolchain@stable 161 | - run: | 162 | cargo test --all-features 163 | 164 | windows: 165 | runs-on: windows-latest 166 | steps: 167 | - uses: actions/checkout@v4 168 | - uses: dtolnay/rust-toolchain@stable 169 | - run: | 170 | cargo test --all-features 171 | 172 | ubuntu: 173 | runs-on: ubuntu-latest 174 | steps: 175 | - uses: actions/checkout@v4 176 | - uses: dtolnay/rust-toolchain@stable 177 | with: 178 | targets: x86_64-apple-darwin 179 | - run: | 180 | cargo test --all-features 181 | - run: | 182 | cargo check --target x86_64-apple-darwin 183 | 184 | diff-codegen: 185 | runs-on: ubuntu-latest 186 | steps: 187 | - uses: actions/checkout@v4 188 | - uses: dtolnay/rust-toolchain@stable 189 | - name: diff 190 | run: | 191 | ./scripts/codegen.sh 192 | [[ -z $(git status -s) ]] # Fail if changed. See https://stackoverflow.com/a/9393642 193 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /Cargo.lock 4 | 5 | /.vscode 6 | /temp 7 | 8 | __pycache__ 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.170" 21 | 22 | [dev-dependencies] 23 | once_cell = "1.20.3" 24 | 25 | [workspace] 26 | members = ["codegen"] 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); 3 | let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap(); 4 | 5 | println!("cargo:rustc-check-cfg=cfg(target_os, values(\"switch\"))"); 6 | let has_prlimit64 = (target_os == "android" && target_env != "newlib") 7 | || ((target_env == "gnu" || target_env == "musl" || target_env == "ohos") 8 | && (target_os == "l4re" || target_os == "linux")); 9 | println!("cargo:rustc-check-cfg=cfg(rlimit__has_prlimit64)"); 10 | if has_prlimit64 { 11 | println!("cargo:rustc-cfg=rlimit__has_prlimit64"); 12 | } 13 | 14 | let get_kern_max_files_per_proc = (target_os == "dragonfly" 15 | || target_os == "freebsd" 16 | || target_os == "ios" 17 | || target_os == "macos" 18 | || target_os == "tvos" 19 | || target_os == "visionos" 20 | || target_os == "watchos") 21 | && target_env != "newlib"; 22 | println!("cargo:rustc-check-cfg=cfg(rlimit__get_kern_max_files_per_proc)"); 23 | if get_kern_max_files_per_proc { 24 | println!("cargo:rustc-cfg=rlimit__get_kern_max_files_per_proc"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | doc-valid-idents = ["FreeBSD", "NetBSD", "macOS"] 2 | -------------------------------------------------------------------------------- /codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rlimit-codegen" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | codegen-cfg = "0.2.0" 9 | codegen-libc = "0.2.1" 10 | scoped-writer = "0.3.0" 11 | std-next = "0.1.5" 12 | -------------------------------------------------------------------------------- /codegen/src/bindings.rs: -------------------------------------------------------------------------------- 1 | use codegen_cfg::ast::*; 2 | use codegen_libc::{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!("{}64", name); 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 | -------------------------------------------------------------------------------- /codegen/src/build.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write as _; 2 | 3 | use codegen_cfg::ast::{all, All, Any, Expr, Not, Var}; 4 | use codegen_libc::{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/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 codegen_cfg::ast::{Pred, Var}; 11 | use codegen_cfg::bool_logic::ast::Expr; 12 | use codegen_cfg::bool_logic::visit_mut::{walk_mut_expr, VisitMut}; 13 | use codegen_libc::{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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /codegen/src/resource.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use codegen_libc::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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 120 | mod tools; 121 | #[doc(inline)] 122 | pub use self::tools::*; 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | #[test] 127 | fn build_cfg() { 128 | if cfg!(target_os = "linux") || cfg!(target_os = "android") { 129 | assert!(cfg!(rlimit__has_prlimit64)); 130 | assert!(cfg!(not(rlimit__get_kern_max_files_per_proc))); 131 | } 132 | 133 | if cfg!(target_os = "macos") { 134 | assert!(cfg!(not(rlimit__has_prlimit64))); 135 | assert!(cfg!(rlimit__get_kern_max_files_per_proc)); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | debug_assert!(max_files_per_proc >= 0); 30 | Ok(max_files_per_proc as u64) 31 | } 32 | 33 | /// Try to increase NOFILE limit and return the current soft limit. 34 | /// 35 | /// `lim` is the expected limit which can be up to [`u64::MAX`]. 36 | /// 37 | /// This function does nothing and returns `Ok(lim)` 38 | /// if `RLIMIT_NOFILE` does not exist on current platform. 39 | /// 40 | /// # Errors 41 | /// Returns an error if any syscall failed. 42 | pub fn increase_nofile_limit(lim: u64) -> io::Result { 43 | #[cfg(unix)] 44 | { 45 | use crate::Resource; 46 | 47 | if !Resource::NOFILE.is_supported() { 48 | return Ok(lim); 49 | } 50 | 51 | let (soft, hard) = Resource::NOFILE.get()?; 52 | 53 | if soft >= hard { 54 | return Ok(hard); 55 | } 56 | 57 | if soft >= lim { 58 | return Ok(soft); 59 | } 60 | 61 | let mut lim = lim; 62 | 63 | lim = lim.min(hard); 64 | 65 | #[cfg(rlimit__get_kern_max_files_per_proc)] 66 | { 67 | lim = lim.min(get_kern_max_files_per_proc()?) 68 | } 69 | 70 | Resource::NOFILE.set(lim, hard)?; 71 | 72 | Ok(lim) 73 | } 74 | #[cfg(windows)] 75 | { 76 | Ok(lim) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /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 | #[allow(clippy::unnecessary_cast)] 8 | pub const INFINITY: u64 = C::RLIM_INFINITY as u64; 9 | 10 | fn check_supported(resource: Resource) -> io::Result<()> { 11 | let raw_resource = resource.as_raw(); 12 | if raw_resource == u8::MAX { 13 | return Err(io::Error::new(io::ErrorKind::Other, "unsupported resource")); 14 | } 15 | Ok(()) 16 | } 17 | 18 | /// Set resource limits. 19 | /// # Errors 20 | /// \[Linux\] See 21 | #[allow(clippy::unnecessary_min_or_max)] 22 | #[inline] 23 | pub fn setrlimit(resource: Resource, soft: u64, hard: u64) -> io::Result<()> { 24 | check_supported(resource)?; 25 | let rlim = C::rlimit { 26 | rlim_cur: soft.min(INFINITY) as _, 27 | rlim_max: hard.min(INFINITY) as _, 28 | }; 29 | #[allow(clippy::cast_lossless)] 30 | let ret = unsafe { C::setrlimit(resource.as_raw() as _, &rlim) }; 31 | if ret == 0 { 32 | Ok(()) 33 | } else { 34 | Err(io::Error::last_os_error()) 35 | } 36 | } 37 | 38 | /// Get resource limits. 39 | /// # Errors 40 | /// \[Linux\] See 41 | #[allow(clippy::unnecessary_min_or_max)] 42 | #[inline] 43 | pub fn getrlimit(resource: Resource) -> io::Result<(u64, u64)> { 44 | check_supported(resource)?; 45 | let mut rlim = unsafe { mem::zeroed() }; 46 | #[allow(clippy::cast_lossless)] 47 | let ret = unsafe { C::getrlimit(resource.as_raw() as _, &mut rlim) }; 48 | 49 | #[allow(clippy::unnecessary_cast)] 50 | if ret == 0 { 51 | let soft = (rlim.rlim_cur as u64).min(INFINITY); 52 | let hard = (rlim.rlim_max as u64).min(INFINITY); 53 | Ok((soft, hard)) 54 | } else { 55 | Err(io::Error::last_os_error()) 56 | } 57 | } 58 | 59 | /// The type of a process ID 60 | #[allow(non_camel_case_types)] 61 | #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))] 62 | #[cfg(any(doc, target_os = "linux", target_os = "android"))] 63 | pub type pid_t = i32; 64 | 65 | /// Set and get the resource limits of an arbitrary process. 66 | /// # Errors 67 | /// See 68 | #[allow(clippy::unnecessary_min_or_max)] 69 | #[inline] 70 | #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))] 71 | #[cfg(any(doc, rlimit__has_prlimit64))] 72 | pub fn prlimit( 73 | pid: pid_t, 74 | resource: Resource, 75 | new_limit: Option<(u64, u64)>, 76 | old_limit: Option<(&mut u64, &mut u64)>, 77 | ) -> io::Result<()> { 78 | check_supported(resource)?; 79 | 80 | let new_rlim: Option = new_limit.map(|(soft, hard)| C::rlimit { 81 | rlim_cur: soft.min(INFINITY) as _, 82 | rlim_max: hard.min(INFINITY) as _, 83 | }); 84 | 85 | let new_rlimit_ptr: *const C::rlimit = match new_rlim { 86 | Some(ref rlim) => rlim, 87 | None => std::ptr::null(), 88 | }; 89 | 90 | let mut old_rlim: C::rlimit = unsafe { mem::zeroed() }; 91 | 92 | let old_rlimit_ptr: *mut C::rlimit = if old_limit.is_some() { 93 | &mut old_rlim 94 | } else { 95 | std::ptr::null_mut() 96 | }; 97 | 98 | #[allow(clippy::cast_lossless)] 99 | let ret = unsafe { C::prlimit(pid, resource.as_raw() as _, new_rlimit_ptr, old_rlimit_ptr) }; 100 | 101 | if ret == 0 { 102 | #[allow(clippy::unnecessary_cast)] 103 | if let Some((soft, hard)) = old_limit { 104 | *soft = (old_rlim.rlim_cur as u64).min(INFINITY); 105 | *hard = (old_rlim.rlim_max as u64).min(INFINITY); 106 | } 107 | 108 | Ok(()) 109 | } else { 110 | Err(io::Error::last_os_error()) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /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 | /// See 12 | /// 13 | /// # Errors 14 | /// See the official documentation 15 | #[cfg_attr(docsrs, doc(cfg(windows)))] 16 | pub fn setmaxstdio(new_max: u32) -> io::Result { 17 | // A negative `new_max` will cause EINVAL. 18 | // A negative `ret` should never appear. 19 | // It is safe even if the return value is wrong. 20 | #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] 21 | unsafe { 22 | let ret = _setmaxstdio(new_max as c_int); 23 | if ret < 0 { 24 | return Err(io::Error::last_os_error()); 25 | } 26 | Ok(ret as u32) 27 | } 28 | } 29 | 30 | /// Returns the number of simultaneously open files permitted at the stream I/O level. 31 | /// 32 | /// See 33 | #[cfg_attr(docsrs, doc(cfg(windows)))] 34 | #[must_use] 35 | pub fn getmaxstdio() -> u32 { 36 | // A negative `ret` should never appear. 37 | // It is safe even if the return value is wrong. 38 | #[allow(clippy::cast_sign_loss)] 39 | unsafe { 40 | let ret = _getmaxstdio(); 41 | debug_assert!(ret >= 0); 42 | ret as u32 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /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 unsupported() { 98 | assert!(Resource::UMTXP.is_supported().not()); 99 | let err = Resource::UMTXP.get().unwrap_err(); 100 | assert!(err.kind() == std::io::ErrorKind::Other); 101 | } 102 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------