1use nix::sys::resource::{RLIM_INFINITY, Resource, rlim_t};
2use nu_engine::command_prelude::*;
3
4use std::sync::LazyLock;
5
6struct 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 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 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
246fn 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
267fn 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 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 Ok(desc_len + unit_len + 3)
295}
296
297fn 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
335fn 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 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
362fn 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 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 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
401fn 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
414fn 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
425fn 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 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 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}