Skip to main content

starnix_uapi/
resource_limits.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::errors::{Errno, error};
6use crate::{
7    FUTEX_TID_MASK, RLIM_INFINITY, RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE,
8    RLIMIT_LOCKS, RLIMIT_MEMLOCK, RLIMIT_MSGQUEUE, RLIMIT_NICE, RLIMIT_NOFILE, RLIMIT_NPROC,
9    RLIMIT_RSS, RLIMIT_RTPRIO, RLIMIT_RTTIME, RLIMIT_SIGPENDING, RLIMIT_STACK, rlimit, uapi,
10};
11use std::collections::HashMap;
12
13/// A description of a resource.
14pub struct ResourceDesc {
15    /// The name of the resource.
16    pub name: &'static str,
17
18    /// The units in which limits on the resource are expressed.
19    pub unit: &'static str,
20}
21
22impl ResourceDesc {
23    fn new(name: &'static str, unit: &'static str) -> ResourceDesc {
24        ResourceDesc { name, unit }
25    }
26}
27
28#[allow(clippy::upper_case_acronyms)]
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum Resource {
31    CPU,
32    FSIZE,
33    DATA,
34    STACK,
35    CORE,
36    RSS,
37    NPROC,
38    NOFILE,
39    MEMLOCK,
40    AS,
41    LOCKS,
42    SIGPENDING,
43    MSGQUEUE,
44    NICE,
45    RTPRIO,
46    RTTIME,
47}
48
49impl Resource {
50    pub const ALL: [Resource; 16] = [
51        Resource::CPU,
52        Resource::FSIZE,
53        Resource::DATA,
54        Resource::STACK,
55        Resource::CORE,
56        Resource::RSS,
57        Resource::NPROC,
58        Resource::NOFILE,
59        Resource::MEMLOCK,
60        Resource::AS,
61        Resource::LOCKS,
62        Resource::SIGPENDING,
63        Resource::MSGQUEUE,
64        Resource::NICE,
65        Resource::RTPRIO,
66        Resource::RTTIME,
67    ];
68
69    pub fn from_raw(raw: u32) -> Result<Resource, Errno> {
70        Ok(match raw {
71            RLIMIT_CPU => Resource::CPU,
72            RLIMIT_FSIZE => Resource::FSIZE,
73            RLIMIT_DATA => Resource::DATA,
74            RLIMIT_STACK => Resource::STACK,
75            RLIMIT_CORE => Resource::CORE,
76            RLIMIT_RSS => Resource::RSS,
77            RLIMIT_NPROC => Resource::NPROC,
78            RLIMIT_NOFILE => Resource::NOFILE,
79            RLIMIT_MEMLOCK => Resource::MEMLOCK,
80            RLIMIT_AS => Resource::AS,
81            RLIMIT_LOCKS => Resource::LOCKS,
82            RLIMIT_SIGPENDING => Resource::SIGPENDING,
83            RLIMIT_MSGQUEUE => Resource::MSGQUEUE,
84            RLIMIT_NICE => Resource::NICE,
85            RLIMIT_RTPRIO => Resource::RTPRIO,
86            RLIMIT_RTTIME => Resource::RTTIME,
87            _ => return error!(EINVAL),
88        })
89    }
90
91    pub fn desc(&self) -> ResourceDesc {
92        match self {
93            Resource::CPU => ResourceDesc::new("Max cpu time", "seconds"),
94            Resource::FSIZE => ResourceDesc::new("Max file size", "bytes"),
95            Resource::DATA => ResourceDesc::new("Max data size", "bytes"),
96            Resource::STACK => ResourceDesc::new("Max stack size", "bytes"),
97            Resource::CORE => ResourceDesc::new("Max core file size", "bytes"),
98            Resource::RSS => ResourceDesc::new("Max resident set", "bytes"),
99            Resource::NPROC => ResourceDesc::new("Max processes", "processes"),
100            Resource::NOFILE => ResourceDesc::new("Max open files", "files"),
101            Resource::MEMLOCK => ResourceDesc::new("Max locked memory", "bytes"),
102            Resource::AS => ResourceDesc::new("Max address space", "bytes"),
103            Resource::LOCKS => ResourceDesc::new("Max file locks", "bytes"),
104            Resource::SIGPENDING => ResourceDesc::new("Max pending signals", "signals"),
105            Resource::MSGQUEUE => ResourceDesc::new("Max msgqueue size", "bytes"),
106            Resource::NICE => ResourceDesc::new("Max nice priority", ""),
107            Resource::RTPRIO => ResourceDesc::new("Max realtime priority", ""),
108            Resource::RTTIME => ResourceDesc::new("Max realtime timeout", "us"),
109        }
110    }
111}
112
113impl std::str::FromStr for Resource {
114    type Err = Errno;
115
116    fn from_str(name: &str) -> Result<Self, Self::Err> {
117        Ok(match name {
118            "CPU" => Resource::CPU,
119            "FSIZE" => Resource::FSIZE,
120            "DATA" => Resource::DATA,
121            "STACK" => Resource::STACK,
122            "CORE" => Resource::CORE,
123            "RSS" => Resource::RSS,
124            "NPROC" => Resource::NPROC,
125            "NOFILE" => Resource::NOFILE,
126            "MEMLOCK" => Resource::MEMLOCK,
127            "AS" => Resource::AS,
128            "LOCKS" => Resource::LOCKS,
129            "SIGPENDING" => Resource::SIGPENDING,
130            "MSGQUEUE" => Resource::MSGQUEUE,
131            "NICE" => Resource::NICE,
132            "RTPRIO" => Resource::RTPRIO,
133            "RTTIME" => Resource::RTTIME,
134            _ => return error!(EINVAL),
135        })
136    }
137}
138
139#[derive(Debug, Clone)]
140pub struct ResourceLimits {
141    values: HashMap<Resource, rlimit>,
142}
143
144const INFINITE_LIMIT: rlimit =
145    rlimit { rlim_cur: RLIM_INFINITY as u64, rlim_max: RLIM_INFINITY as u64 };
146
147/// Default hard limit for [`RLIMIT_NOFILE`] (rather than [`uapi::INR_OPEN_MAX`]).
148const DEFAULT_NOFILE_MAX: u64 = 524288;
149
150/// Calculates system default for `/proc/sys/kernel/threads-max` from total RAM in bytes.
151///
152/// Per `proc_sys_kernel(5)`, `/proc/sys/kernel/threads-max`:
153///   "This file specifies the system-wide limit on the number of threads (tasks)
154///   that can be created on the system."
155///   ...
156///   "If the thread structures would occupy too much (more than 1/8th) of the
157///   available RAM pages, threads-max is reduced accordingly."
158///
159/// Align with Linux, which budgets 16 KiB per thread stack regardless of the
160/// system page size, limiting total thread stacks to 1/8th of available RAM and
161/// clamping `threads-max` to `[20, `[`FUTEX_TID_MASK`]`]`.
162pub fn default_threads_max(total_ram_bytes: u64) -> u64 {
163    const MIN_THREADS: u64 = 20;
164    const THREAD_STACK_SIZE: u64 = 16384;
165    (total_ram_bytes / (8 * THREAD_STACK_SIZE)).clamp(MIN_THREADS, FUTEX_TID_MASK as u64)
166}
167
168const DEFAULT_LIMITS: [(Resource, rlimit); 7] = [
169    (Resource::STACK, rlimit { rlim_cur: uapi::_STK_LIM as u64, rlim_max: RLIM_INFINITY as u64 }),
170    (Resource::CORE, rlimit { rlim_cur: 0, rlim_max: RLIM_INFINITY as u64 }),
171    (
172        Resource::NOFILE,
173        rlimit { rlim_cur: uapi::INR_OPEN_CUR as u64, rlim_max: DEFAULT_NOFILE_MAX },
174    ),
175    (
176        Resource::MEMLOCK,
177        rlimit { rlim_cur: uapi::MLOCK_LIMIT as u64, rlim_max: uapi::MLOCK_LIMIT as u64 },
178    ),
179    (
180        Resource::MSGQUEUE,
181        rlimit { rlim_cur: uapi::MQ_BYTES_MAX as u64, rlim_max: uapi::MQ_BYTES_MAX as u64 },
182    ),
183    (Resource::NICE, rlimit { rlim_cur: 0, rlim_max: 0 }),
184    (Resource::RTPRIO, rlimit { rlim_cur: 0, rlim_max: 0 }),
185];
186
187// NB: ResourceLimits objects are accessed using a lock.  To avoid the risk of
188// deadlock, we try to avoid lock acquisition in ResourceLimits.
189impl ResourceLimits {
190    /// Initializes default resource limits, scaling [`RLIMIT_NPROC`] and [`RLIMIT_SIGPENDING`]
191    /// to half of [`default_threads_max`] for `total_ram_bytes`.
192    pub fn from_total_ram(total_ram_bytes: u64) -> Self {
193        let mut limits = Self { values: Default::default() };
194        for (resource, value) in DEFAULT_LIMITS {
195            limits.set(resource, value);
196        }
197        let scaled_limit = default_threads_max(total_ram_bytes) / 2;
198        limits.set(Resource::NPROC, rlimit { rlim_cur: scaled_limit, rlim_max: scaled_limit });
199        limits.set(Resource::SIGPENDING, rlimit { rlim_cur: scaled_limit, rlim_max: scaled_limit });
200        limits
201    }
202
203    pub fn get(&self, resource: Resource) -> rlimit {
204        *self.values.get(&resource).unwrap_or(&INFINITE_LIMIT)
205    }
206
207    pub fn set(&mut self, resource: Resource, value: rlimit) {
208        self.values.insert(resource, value);
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[::fuchsia::test]
217    fn test_resource_from_str() {
218        for resource in Resource::ALL {
219            let name = format!("{resource:?}");
220            assert_eq!(name.parse::<Resource>(), Ok(resource));
221        }
222        assert!("".parse::<Resource>().is_err());
223        assert!("RLIMIT_NOFILE".parse::<Resource>().is_err());
224        assert!("UNKNOWN".parse::<Resource>().is_err());
225    }
226
227    #[::fuchsia::test]
228    fn test_threads_max_and_scaled_limits() {
229        // 4 GiB RAM / (8 * 16 KiB per thread) = 32,768 threads-max.
230        // nproc / sigpending = 32,768 / 2 = 16,384.
231        let ram_4gb = 4 * 1024 * 1024 * 1024;
232        assert_eq!(default_threads_max(ram_4gb), 32768);
233
234        let limits = ResourceLimits::from_total_ram(ram_4gb);
235        let nproc = limits.get(Resource::NPROC);
236        assert_eq!(nproc.rlim_cur, 16384);
237        assert_eq!(nproc.rlim_max, 16384);
238
239        let sigpending = limits.get(Resource::SIGPENDING);
240        assert_eq!(sigpending.rlim_cur, 16384);
241        assert_eq!(sigpending.rlim_max, 16384);
242
243        let nofile = limits.get(Resource::NOFILE);
244        assert_eq!(nofile.rlim_cur, 1024);
245        assert_eq!(nofile.rlim_max, 524288);
246
247        let memlock = limits.get(Resource::MEMLOCK);
248        assert_eq!(memlock.rlim_cur, 8 * 1024 * 1024);
249        assert_eq!(memlock.rlim_max, 8 * 1024 * 1024);
250
251        let stack = limits.get(Resource::STACK);
252        assert_eq!(stack.rlim_cur, 8 * 1024 * 1024);
253        assert_eq!(stack.rlim_max, RLIM_INFINITY as u64);
254    }
255
256    #[::fuchsia::test]
257    fn test_threads_max_clamping() {
258        assert_eq!(default_threads_max(0), 20);
259        assert_eq!(default_threads_max(1024), 20);
260        assert_eq!(default_threads_max(u64::MAX), FUTEX_TID_MASK as u64);
261    }
262}