nu_command/platform/
ulimit.rs

1use nix::sys::resource::{RLIM_INFINITY, Resource, rlim_t};
2use nu_engine::command_prelude::*;
3
4use std::sync::LazyLock;
5
6/// An object contains resource related parameters
7struct ResourceInfo<'a> {
8    name: &'a str,
9    desc: &'a str,
10    flag: char,
11    multiplier: rlim_t,
12    resource: Resource,
13}
14
15impl<'a> ResourceInfo<'a> {
16    /// Create a `ResourceInfo` object
17    fn new(
18        name: &'a str,
19        desc: &'a str,
20        flag: char,
21        multiplier: rlim_t,
22        resource: Resource,
23    ) -> Self {
24        Self {
25            name,
26            desc,
27            flag,
28            multiplier,
29            resource,
30        }
31    }
32
33    /// Get unit
34    fn get_unit(&self) -> &str {
35        if self.resource == Resource::RLIMIT_CPU {
36            "(seconds, "
37        } else if self.multiplier == 1 {
38            "("
39        } else {
40            "(kB, "
41        }
42    }
43}
44
45impl Default for ResourceInfo<'_> {
46    fn default() -> Self {
47        Self {
48            name: "file-size",
49            desc: "Maximum size of files created by the shell",
50            flag: 'f',
51            multiplier: 1024,
52            resource: Resource::RLIMIT_FSIZE,
53        }
54    }
55}
56
57static RESOURCE_ARRAY: LazyLock<Vec<ResourceInfo>> = LazyLock::new(|| {
58    let resources = [
59        #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
60        (
61            "socket-buffers",
62            "Maximum size of socket buffers",
63            'b',
64            1024,
65            Resource::RLIMIT_SBSIZE,
66        ),
67        (
68            "core-size",
69            "Maximum size of core files created",
70            'c',
71            1024,
72            Resource::RLIMIT_CORE,
73        ),
74        (
75            "data-size",
76            "Maximum size of a process's data segment",
77            'd',
78            1024,
79            Resource::RLIMIT_DATA,
80        ),
81        #[cfg(any(target_os = "android", target_os = "linux"))]
82        (
83            "nice",
84            "Controls of maximum nice priority",
85            'e',
86            1,
87            Resource::RLIMIT_NICE,
88        ),
89        (
90            "file-size",
91            "Maximum size of files created by the shell",
92            'f',
93            1024,
94            Resource::RLIMIT_FSIZE,
95        ),
96        #[cfg(any(target_os = "android", target_os = "linux"))]
97        (
98            "pending-signals",
99            "Maximum number of pending signals",
100            'i',
101            1,
102            Resource::RLIMIT_SIGPENDING,
103        ),
104        #[cfg(any(
105            target_os = "android",
106            target_os = "freebsd",
107            target_os = "openbsd",
108            target_os = "linux",
109            target_os = "freebsd",
110            target_os = "netbsd"
111        ))]
112        (
113            "lock-size",
114            "Maximum size that may be locked into memory",
115            'l',
116            1024,
117            Resource::RLIMIT_MEMLOCK,
118        ),
119        #[cfg(any(
120            target_os = "android",
121            target_os = "freebsd",
122            target_os = "netbsd",
123            target_os = "openbsd",
124            target_os = "linux",
125            target_os = "freebsd",
126            target_os = "aix",
127        ))]
128        (
129            "resident-set-size",
130            "Maximum resident set size",
131            'm',
132            1024,
133            Resource::RLIMIT_RSS,
134        ),
135        (
136            "file-descriptor-count",
137            "Maximum number of open file descriptors",
138            'n',
139            1,
140            Resource::RLIMIT_NOFILE,
141        ),
142        #[cfg(any(target_os = "android", target_os = "linux"))]
143        (
144            "queue-size",
145            "Maximum bytes in POSIX message queues",
146            'q',
147            1024,
148            Resource::RLIMIT_MSGQUEUE,
149        ),
150        #[cfg(any(target_os = "android", target_os = "linux"))]
151        (
152            "realtime-priority",
153            "Maximum realtime scheduling priority",
154            'r',
155            1,
156            Resource::RLIMIT_RTPRIO,
157        ),
158        (
159            "stack-size",
160            "Maximum stack size",
161            's',
162            1024,
163            Resource::RLIMIT_STACK,
164        ),
165        (
166            "cpu-time",
167            "Maximum amount of CPU time in seconds",
168            't',
169            1,
170            Resource::RLIMIT_CPU,
171        ),
172        #[cfg(any(
173            target_os = "android",
174            target_os = "freebsd",
175            target_os = "netbsd",
176            target_os = "openbsd",
177            target_os = "linux",
178            target_os = "freebsd",
179            target_os = "aix",
180        ))]
181        (
182            "process-count",
183            "Maximum number of processes available to the current user",
184            'u',
185            1,
186            Resource::RLIMIT_NPROC,
187        ),
188        #[cfg(not(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd")))]
189        (
190            "virtual-memory-size",
191            "Maximum amount of virtual memory available to each process",
192            'v',
193            1024,
194            Resource::RLIMIT_AS,
195        ),
196        #[cfg(target_os = "freebsd")]
197        (
198            "swap-size",
199            "Maximum swap space",
200            'w',
201            1024,
202            Resource::RLIMIT_SWAP,
203        ),
204        #[cfg(any(target_os = "android", target_os = "linux"))]
205        (
206            "file-locks",
207            "Maximum number of file locks",
208            'x',
209            1,
210            Resource::RLIMIT_LOCKS,
211        ),
212        #[cfg(target_os = "linux")]
213        (
214            "realtime-maxtime",
215            "Maximum contiguous realtime CPU time",
216            'y',
217            1,
218            Resource::RLIMIT_RTTIME,
219        ),
220        #[cfg(target_os = "freebsd")]
221        (
222            "kernel-queues",
223            "Maximum number of kqueues",
224            'K',
225            1,
226            Resource::RLIMIT_KQUEUES,
227        ),
228        #[cfg(target_os = "freebsd")]
229        (
230            "ptys",
231            "Maximum number of pseudo-terminals",
232            'P',
233            1,
234            Resource::RLIMIT_NPTS,
235        ),
236    ];
237
238    let mut resource_array = Vec::new();
239    for (name, desc, flag, multiplier, res) in resources {
240        resource_array.push(ResourceInfo::new(name, desc, flag, multiplier, res));
241    }
242
243    resource_array
244});
245
246/// Convert `rlim_t` to `Value` representation
247fn limit_to_value(limit: rlim_t, multiplier: rlim_t, span: Span) -> Result<Value, ShellError> {
248    if limit == RLIM_INFINITY {
249        return Ok(Value::string("unlimited", span));
250    }
251
252    let val = match i64::try_from(limit / multiplier) {
253        Ok(v) => v,
254        Err(e) => {
255            return Err(ShellError::CantConvert {
256                to_type: "i64".into(),
257                from_type: "rlim_t".into(),
258                span,
259                help: Some(e.to_string()),
260            });
261        }
262    };
263
264    Ok(Value::int(val, span))
265}
266
267/// Get maximum length of all flag descriptions
268fn max_desc_len(
269    call: &Call,
270    engine_state: &EngineState,
271    stack: &mut Stack,
272    print_all: bool,
273) -> Result<usize, ShellError> {
274    let mut desc_len = 0;
275    let mut unit_len = 0;
276
277    for res in RESOURCE_ARRAY.iter() {
278        if !print_all && !call.has_flag(engine_state, stack, res.name)? {
279            continue;
280        }
281
282        desc_len = res.desc.len().max(desc_len);
283        unit_len = res.get_unit().len().max(unit_len);
284    }
285
286    // Use `RLIMIT_FSIZE` limit if no resource flag provided.
287    if desc_len == 0 {
288        let res = ResourceInfo::default();
289        desc_len = res.desc.len().max(desc_len);
290        unit_len = res.get_unit().len().max(unit_len);
291    }
292
293    // desc.len() + unit.len() + '-X)'.len()
294    Ok(desc_len + unit_len + 3)
295}
296
297/// Fill `ResourceInfo` to the record entry
298fn fill_record(
299    res: &ResourceInfo,
300    max_len: usize,
301    soft: bool,
302    hard: bool,
303    span: Span,
304) -> Result<Record, ShellError> {
305    let mut record = Record::new();
306    let mut desc = String::new();
307
308    desc.push_str(res.desc);
309
310    debug_assert!(res.desc.len() + res.get_unit().len() + 3 <= max_len);
311    let width = max_len - res.desc.len() - res.get_unit().len() - 3;
312    if width == 0 {
313        desc.push_str(format!(" {}-{})", res.get_unit(), res.flag).as_str());
314    } else {
315        desc.push_str(format!("{:>width$} {}-{})", ' ', res.get_unit(), res.flag).as_str());
316    }
317
318    record.push("description", Value::string(desc, span));
319
320    let (soft_limit, hard_limit) = getrlimit(res.resource)?;
321
322    if soft {
323        let soft_limit = limit_to_value(soft_limit, res.multiplier, span)?;
324        record.push("soft", soft_limit);
325    }
326
327    if hard {
328        let hard_limit = limit_to_value(hard_limit, res.multiplier, span)?;
329        record.push("hard", hard_limit);
330    }
331
332    Ok(record)
333}
334
335/// Set limits
336fn set_limits(
337    limit_value: &Value,
338    res: &ResourceInfo,
339    soft: bool,
340    hard: bool,
341    call_span: Span,
342) -> Result<(), ShellError> {
343    let (mut soft_limit, mut hard_limit) = getrlimit(res.resource)?;
344    let new_limit = parse_limit(limit_value, res, soft, soft_limit, hard_limit, call_span)?;
345
346    if hard {
347        hard_limit = new_limit;
348    }
349
350    if soft {
351        soft_limit = new_limit;
352
353        // Do not attempt to set the soft limit higher than the hard limit.
354        if (new_limit > hard_limit || new_limit == RLIM_INFINITY) && hard_limit != RLIM_INFINITY {
355            soft_limit = hard_limit;
356        }
357    }
358
359    setrlimit(res.resource, soft_limit, hard_limit)
360}
361
362/// Print limits
363fn print_limits(
364    call: &Call,
365    engine_state: &EngineState,
366    stack: &mut Stack,
367    print_all: bool,
368    soft: bool,
369    hard: bool,
370) -> Result<PipelineData, ShellError> {
371    let mut output = Vec::new();
372    let mut print_default_limit = true;
373    let max_len = max_desc_len(call, engine_state, stack, print_all)?;
374
375    for res in RESOURCE_ARRAY.iter() {
376        if !print_all {
377            // Print specified limit.
378            if !call.has_flag(engine_state, stack, res.name)? {
379                continue;
380            }
381        }
382
383        let record = fill_record(res, max_len, soft, hard, call.head)?;
384        output.push(Value::record(record, call.head));
385
386        if print_default_limit {
387            print_default_limit = false;
388        }
389    }
390
391    // Print `RLIMIT_FSIZE` limit if no resource flag provided.
392    if print_default_limit {
393        let res = ResourceInfo::default();
394        let record = fill_record(&res, max_len, soft, hard, call.head)?;
395        output.push(Value::record(record, call.head));
396    }
397
398    Ok(Value::list(output, call.head).into_pipeline_data())
399}
400
401/// Wrap `nix::sys::resource::getrlimit`
402fn setrlimit(res: Resource, soft_limit: rlim_t, hard_limit: rlim_t) -> Result<(), ShellError> {
403    nix::sys::resource::setrlimit(res, soft_limit, hard_limit).map_err(|e| {
404        ShellError::GenericError {
405            error: e.to_string(),
406            msg: String::new(),
407            span: None,
408            help: None,
409            inner: vec![],
410        }
411    })
412}
413
414/// Wrap `nix::sys::resource::setrlimit`
415fn getrlimit(res: Resource) -> Result<(rlim_t, rlim_t), ShellError> {
416    nix::sys::resource::getrlimit(res).map_err(|e| ShellError::GenericError {
417        error: e.to_string(),
418        msg: String::new(),
419        span: None,
420        help: None,
421        inner: vec![],
422    })
423}
424
425/// Parse user input
426fn parse_limit(
427    limit_value: &Value,
428    res: &ResourceInfo,
429    soft: bool,
430    soft_limit: rlim_t,
431    hard_limit: rlim_t,
432    call_span: Span,
433) -> Result<rlim_t, ShellError> {
434    let val_span = limit_value.span();
435    match limit_value {
436        Value::Int { val, .. } => {
437            let value = rlim_t::try_from(*val).map_err(|e| ShellError::CantConvert {
438                to_type: "rlim_t".into(),
439                from_type: "i64".into(),
440                span: val_span,
441                help: Some(e.to_string()),
442            })?;
443
444            let (limit, overflow) = value.overflowing_mul(res.multiplier);
445            if overflow {
446                Ok(RLIM_INFINITY)
447            } else {
448                Ok(limit)
449            }
450        }
451        Value::Filesize { val, .. } => {
452            if res.multiplier != 1024 {
453                return Err(ShellError::TypeMismatch {
454                    err_message: format!(
455                        "filesize is not compatible with resource {:?}",
456                        res.resource
457                    ),
458                    span: val_span,
459                });
460            }
461
462            rlim_t::try_from(*val).map_err(|e| ShellError::CantConvert {
463                to_type: "rlim_t".into(),
464                from_type: "i64".into(),
465                span: val_span,
466                help: Some(e.to_string()),
467            })
468        }
469        Value::String { val, .. } => {
470            if val == "unlimited" {
471                Ok(RLIM_INFINITY)
472            } else if val == "soft" {
473                if soft { Ok(hard_limit) } else { Ok(soft_limit) }
474            } else if val == "hard" {
475                Ok(hard_limit)
476            } else {
477                Err(ShellError::IncorrectValue {
478                    msg: "Only unlimited, soft and hard are supported for strings".into(),
479                    val_span,
480                    call_span,
481                })
482            }
483        }
484        _ => Err(ShellError::TypeMismatch {
485            err_message: format!(
486                "string, int or filesize required, you provide {}",
487                limit_value.get_type()
488            ),
489            span: limit_value.span(),
490        }),
491    }
492}
493
494#[derive(Clone)]
495pub struct ULimit;
496
497impl Command for ULimit {
498    fn name(&self) -> &str {
499        "ulimit"
500    }
501
502    fn description(&self) -> &str {
503        "Set or get resource usage limits."
504    }
505
506    fn signature(&self) -> Signature {
507        let mut sig = Signature::build("ulimit")
508            .input_output_types(vec![(Type::Nothing, Type::Any)])
509            .switch("soft", "Sets soft resource limit", Some('S'))
510            .switch("hard", "Sets hard resource limit", Some('H'))
511            .switch("all", "Prints all current limits", Some('a'))
512            .optional("limit", SyntaxShape::Any, "Limit value.")
513            .category(Category::Platform);
514
515        for res in RESOURCE_ARRAY.iter() {
516            sig = sig.switch(res.name, res.desc, Some(res.flag));
517        }
518
519        sig
520    }
521
522    fn run(
523        &self,
524        engine_state: &EngineState,
525        stack: &mut Stack,
526        call: &Call,
527        _input: PipelineData,
528    ) -> Result<PipelineData, ShellError> {
529        let mut soft = call.has_flag(engine_state, stack, "soft")?;
530        let mut hard = call.has_flag(engine_state, stack, "hard")?;
531        let all = call.has_flag(engine_state, stack, "all")?;
532
533        if !hard && !soft {
534            // Set both hard and soft limits if neither was specified.
535            hard = true;
536            soft = true;
537        }
538
539        if let Some(limit_value) = call.opt::<Value>(engine_state, stack, 0)? {
540            let mut set_default_limit = true;
541
542            for res in RESOURCE_ARRAY.iter() {
543                if call.has_flag(engine_state, stack, res.name)? {
544                    set_limits(&limit_value, res, soft, hard, call.head)?;
545
546                    if set_default_limit {
547                        set_default_limit = false;
548                    }
549                }
550            }
551
552            // Set `RLIMIT_FSIZE` limit if no resource flag provided.
553            if set_default_limit {
554                let res = ResourceInfo::default();
555                set_limits(&limit_value, &res, hard, soft, call.head)?;
556            }
557
558            Ok(PipelineData::empty())
559        } else {
560            print_limits(call, engine_state, stack, all, soft, hard)
561        }
562    }
563
564    fn examples(&self) -> Vec<Example<'_>> {
565        vec![
566            Example {
567                description: "Print all current limits",
568                example: "ulimit -a",
569                result: None,
570            },
571            Example {
572                description: "Print specified limits",
573                example: "ulimit --core-size --data-size --file-size",
574                result: None,
575            },
576            Example {
577                description: "Set limit",
578                example: "ulimit --core-size 102400",
579                result: None,
580            },
581            Example {
582                description: "Set stack size soft limit",
583                example: "ulimit -s -S 10240",
584                result: None,
585            },
586            Example {
587                description: "Set virtual memory size hard limit",
588                example: "ulimit -v -H 10240",
589                result: None,
590            },
591            Example {
592                description: "Set core size limit to unlimited",
593                example: "ulimit -c unlimited",
594                result: None,
595            },
596        ]
597    }
598
599    fn search_terms(&self) -> Vec<&str> {
600        vec!["resource", "limits"]
601    }
602}