forked from extern/nushell
# Description 1. Make table to be a subtype of `list<any>`, so some input_output_types of filter commands are unnecessary 2. Change some commands which accept an input type, but generates different output types. In this case, delete duplicate entry, and change relative output type to `<any>` Yeah it makes some commands more permissive, but I think it's better to run into strange issue that why my script runs to failed during parse time. Fixes #11193 # User-Facing Changes NaN # Tests + Formatting NaN # After Submitting NaN
608 lines
17 KiB
Rust
608 lines
17 KiB
Rust
use nix::sys::resource::{rlim_t, Resource, RLIM_INFINITY};
|
|
use nu_engine::CallExt;
|
|
use nu_protocol::{
|
|
ast::Call,
|
|
engine::{Command, EngineState, Stack},
|
|
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
|
|
SyntaxShape, Type, Value,
|
|
};
|
|
use once_cell::sync::Lazy;
|
|
|
|
/// An object contains resource related parameters
|
|
struct ResourceInfo<'a> {
|
|
name: &'a str,
|
|
desc: &'a str,
|
|
flag: char,
|
|
multiplier: rlim_t,
|
|
resource: Resource,
|
|
}
|
|
|
|
impl<'a> ResourceInfo<'a> {
|
|
/// Create a `ResourceInfo` object
|
|
fn new(
|
|
name: &'a str,
|
|
desc: &'a str,
|
|
flag: char,
|
|
multiplier: rlim_t,
|
|
resource: Resource,
|
|
) -> Self {
|
|
Self {
|
|
name,
|
|
desc,
|
|
flag,
|
|
multiplier,
|
|
resource,
|
|
}
|
|
}
|
|
|
|
/// Get unit
|
|
fn get_unit(&self) -> &str {
|
|
if self.resource == Resource::RLIMIT_CPU {
|
|
"(seconds, "
|
|
} else if self.multiplier == 1 {
|
|
"("
|
|
} else {
|
|
"(kB, "
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Default for ResourceInfo<'a> {
|
|
fn default() -> Self {
|
|
Self {
|
|
name: "file-size",
|
|
desc: "Maximum size of files created by the shell",
|
|
flag: 'f',
|
|
multiplier: 1024,
|
|
resource: Resource::RLIMIT_FSIZE,
|
|
}
|
|
}
|
|
}
|
|
|
|
static RESOURCE_ARRAY: Lazy<Vec<ResourceInfo>> = Lazy::new(|| {
|
|
let resources = [
|
|
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
|
|
(
|
|
"socket-buffers",
|
|
"Maximum size of socket buffers",
|
|
'b',
|
|
1024,
|
|
Resource::RLIMIT_SBSIZE,
|
|
),
|
|
(
|
|
"core-size",
|
|
"Maximum size of core files created",
|
|
'c',
|
|
1024,
|
|
Resource::RLIMIT_CORE,
|
|
),
|
|
(
|
|
"data-size",
|
|
"Maximum size of a process's data segment",
|
|
'd',
|
|
1024,
|
|
Resource::RLIMIT_DATA,
|
|
),
|
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
|
(
|
|
"nice",
|
|
"Controls of maximum nice priority",
|
|
'e',
|
|
1,
|
|
Resource::RLIMIT_NICE,
|
|
),
|
|
(
|
|
"file-size",
|
|
"Maximum size of files created by the shell",
|
|
'f',
|
|
1024,
|
|
Resource::RLIMIT_FSIZE,
|
|
),
|
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
|
(
|
|
"pending-signals",
|
|
"Maximum number of pending signals",
|
|
'i',
|
|
1,
|
|
Resource::RLIMIT_SIGPENDING,
|
|
),
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "openbsd",
|
|
target_os = "linux",
|
|
target_os = "netbsd"
|
|
))]
|
|
(
|
|
"lock-size",
|
|
"Maximum size that may be locked into memory",
|
|
'l',
|
|
1024,
|
|
Resource::RLIMIT_MEMLOCK,
|
|
),
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_os = "linux",
|
|
target_os = "aix",
|
|
))]
|
|
(
|
|
"resident-set-size",
|
|
"Maximum resident set size",
|
|
'm',
|
|
1024,
|
|
Resource::RLIMIT_RSS,
|
|
),
|
|
(
|
|
"file-descriptor-count",
|
|
"Maximum number of open file descriptors",
|
|
'n',
|
|
1,
|
|
Resource::RLIMIT_NOFILE,
|
|
),
|
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
|
(
|
|
"queue-size",
|
|
"Maximum bytes in POSIX message queues",
|
|
'q',
|
|
1024,
|
|
Resource::RLIMIT_MSGQUEUE,
|
|
),
|
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
|
(
|
|
"realtime-priority",
|
|
"Maximum realtime scheduling priority",
|
|
'r',
|
|
1,
|
|
Resource::RLIMIT_RTPRIO,
|
|
),
|
|
(
|
|
"stack-size",
|
|
"Maximum stack size",
|
|
's',
|
|
1024,
|
|
Resource::RLIMIT_STACK,
|
|
),
|
|
(
|
|
"cpu-time",
|
|
"Maximum amount of CPU time in seconds",
|
|
't',
|
|
1,
|
|
Resource::RLIMIT_CPU,
|
|
),
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd",
|
|
target_os = "linux",
|
|
target_os = "aix",
|
|
))]
|
|
(
|
|
"process-count",
|
|
"Maximum number of processes available to the current user",
|
|
'u',
|
|
1,
|
|
Resource::RLIMIT_NPROC,
|
|
),
|
|
#[cfg(not(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd")))]
|
|
(
|
|
"virtual-memory-size",
|
|
"Maximum amount of virtual memory available to each process",
|
|
'v',
|
|
1024,
|
|
Resource::RLIMIT_AS,
|
|
),
|
|
#[cfg(target_os = "freebsd")]
|
|
(
|
|
"swap-size",
|
|
"Maximum swap space",
|
|
'w',
|
|
1024,
|
|
Resource::RLIMIT_SWAP,
|
|
),
|
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
|
(
|
|
"file-locks",
|
|
"Maximum number of file locks",
|
|
'x',
|
|
1,
|
|
Resource::RLIMIT_LOCKS,
|
|
),
|
|
#[cfg(target_os = "linux")]
|
|
(
|
|
"realtime-maxtime",
|
|
"Maximum contiguous realtime CPU time",
|
|
'y',
|
|
1,
|
|
Resource::RLIMIT_RTTIME,
|
|
),
|
|
#[cfg(target_os = "freebsd")]
|
|
(
|
|
"kernel-queues",
|
|
"Maximum number of kqueues",
|
|
'K',
|
|
1,
|
|
Resource::RLIMIT_KQUEUES,
|
|
),
|
|
#[cfg(target_os = "freebsd")]
|
|
(
|
|
"ptys",
|
|
"Maximum number of pseudo-terminals",
|
|
'P',
|
|
1,
|
|
Resource::RLIMIT_NPTS,
|
|
),
|
|
];
|
|
|
|
let mut resource_array = Vec::new();
|
|
for (name, desc, flag, multiplier, res) in resources {
|
|
resource_array.push(ResourceInfo::new(name, desc, flag, multiplier, res));
|
|
}
|
|
|
|
resource_array
|
|
});
|
|
|
|
/// Convert `rlim_t` to `Value` representation
|
|
fn limit_to_value(limit: rlim_t, multiplier: rlim_t, span: Span) -> Result<Value, ShellError> {
|
|
if limit == RLIM_INFINITY {
|
|
return Ok(Value::string("unlimited", span));
|
|
}
|
|
|
|
let val = match i64::try_from(limit / multiplier) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
return Err(ShellError::CantConvert {
|
|
to_type: "i64".into(),
|
|
from_type: "rlim_t".into(),
|
|
span,
|
|
help: Some(e.to_string()),
|
|
});
|
|
}
|
|
};
|
|
|
|
Ok(Value::int(val, span))
|
|
}
|
|
|
|
/// Get maximum length of all flag descriptions
|
|
fn max_desc_len(
|
|
call: &Call,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
print_all: bool,
|
|
) -> Result<usize, ShellError> {
|
|
let mut desc_len = 0;
|
|
let mut unit_len = 0;
|
|
|
|
for res in RESOURCE_ARRAY.iter() {
|
|
if !print_all && !call.has_flag(engine_state, stack, res.name)? {
|
|
continue;
|
|
}
|
|
|
|
desc_len = res.desc.len().max(desc_len);
|
|
unit_len = res.get_unit().len().max(unit_len);
|
|
}
|
|
|
|
// Use `RLIMIT_FSIZE` limit if no resource flag provided.
|
|
if desc_len == 0 {
|
|
let res = ResourceInfo::default();
|
|
desc_len = res.desc.len().max(desc_len);
|
|
unit_len = res.get_unit().len().max(unit_len);
|
|
}
|
|
|
|
// desc.len() + unit.len() + '-X)'.len()
|
|
Ok(desc_len + unit_len + 3)
|
|
}
|
|
|
|
/// Fill `ResourceInfo` to the record entry
|
|
fn fill_record(
|
|
res: &ResourceInfo,
|
|
max_len: usize,
|
|
soft: bool,
|
|
hard: bool,
|
|
span: Span,
|
|
) -> Result<Record, ShellError> {
|
|
let mut record = Record::new();
|
|
let mut desc = String::new();
|
|
|
|
desc.push_str(res.desc);
|
|
|
|
debug_assert!(res.desc.len() + res.get_unit().len() + 3 <= max_len);
|
|
let width = max_len - res.desc.len() - res.get_unit().len() - 3;
|
|
if width == 0 {
|
|
desc.push_str(format!(" {}-{})", res.get_unit(), res.flag).as_str());
|
|
} else {
|
|
desc.push_str(format!("{:>width$} {}-{})", ' ', res.get_unit(), res.flag).as_str());
|
|
}
|
|
|
|
record.push("description", Value::string(desc, span));
|
|
|
|
let (soft_limit, hard_limit) = getrlimit(res.resource)?;
|
|
|
|
if soft {
|
|
let soft_limit = limit_to_value(soft_limit, res.multiplier, span)?;
|
|
record.push("soft", soft_limit);
|
|
}
|
|
|
|
if hard {
|
|
let hard_limit = limit_to_value(hard_limit, res.multiplier, span)?;
|
|
record.push("hard", hard_limit);
|
|
}
|
|
|
|
Ok(record)
|
|
}
|
|
|
|
/// Set limits
|
|
fn set_limits(
|
|
limit_value: &Value,
|
|
res: &ResourceInfo,
|
|
soft: bool,
|
|
hard: bool,
|
|
call_span: Span,
|
|
) -> Result<(), ShellError> {
|
|
let (mut soft_limit, mut hard_limit) = getrlimit(res.resource)?;
|
|
let new_limit = parse_limit(limit_value, res, soft, soft_limit, hard_limit, call_span)?;
|
|
|
|
if hard {
|
|
hard_limit = new_limit;
|
|
}
|
|
|
|
if soft {
|
|
soft_limit = new_limit;
|
|
|
|
// Do not attempt to set the soft limit higher than the hard limit.
|
|
if (new_limit > hard_limit || new_limit == RLIM_INFINITY) && hard_limit != RLIM_INFINITY {
|
|
soft_limit = hard_limit;
|
|
}
|
|
}
|
|
|
|
setrlimit(res.resource, soft_limit, hard_limit)
|
|
}
|
|
|
|
/// Print limits
|
|
fn print_limits(
|
|
call: &Call,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
print_all: bool,
|
|
soft: bool,
|
|
hard: bool,
|
|
) -> Result<PipelineData, ShellError> {
|
|
let mut output = Vec::new();
|
|
let mut print_default_limit = true;
|
|
let max_len = max_desc_len(call, engine_state, stack, print_all)?;
|
|
|
|
for res in RESOURCE_ARRAY.iter() {
|
|
if !print_all {
|
|
// Print specified limit.
|
|
if !call.has_flag(engine_state, stack, res.name)? {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
let record = fill_record(res, max_len, soft, hard, call.head)?;
|
|
output.push(Value::record(record, call.head));
|
|
|
|
if print_default_limit {
|
|
print_default_limit = false;
|
|
}
|
|
}
|
|
|
|
// Print `RLIMIT_FSIZE` limit if no resource flag provided.
|
|
if print_default_limit {
|
|
let res = ResourceInfo::default();
|
|
let record = fill_record(&res, max_len, soft, hard, call.head)?;
|
|
output.push(Value::record(record, call.head));
|
|
}
|
|
|
|
Ok(Value::list(output, call.head).into_pipeline_data())
|
|
}
|
|
|
|
/// Wrap `nix::sys::resource::getrlimit`
|
|
fn setrlimit(res: Resource, soft_limit: rlim_t, hard_limit: rlim_t) -> Result<(), ShellError> {
|
|
nix::sys::resource::setrlimit(res, soft_limit, hard_limit).map_err(|e| {
|
|
ShellError::GenericError {
|
|
error: e.to_string(),
|
|
msg: String::new(),
|
|
span: None,
|
|
help: None,
|
|
inner: vec![],
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Wrap `nix::sys::resource::setrlimit`
|
|
fn getrlimit(res: Resource) -> Result<(rlim_t, rlim_t), ShellError> {
|
|
nix::sys::resource::getrlimit(res).map_err(|e| ShellError::GenericError {
|
|
error: e.to_string(),
|
|
msg: String::new(),
|
|
span: None,
|
|
help: None,
|
|
inner: vec![],
|
|
})
|
|
}
|
|
|
|
/// Parse user input
|
|
fn parse_limit(
|
|
limit_value: &Value,
|
|
res: &ResourceInfo,
|
|
soft: bool,
|
|
soft_limit: rlim_t,
|
|
hard_limit: rlim_t,
|
|
call_span: Span,
|
|
) -> Result<rlim_t, ShellError> {
|
|
match limit_value {
|
|
Value::Int { val, internal_span } => {
|
|
let value = rlim_t::try_from(*val).map_err(|e| ShellError::CantConvert {
|
|
to_type: "rlim_t".into(),
|
|
from_type: "i64".into(),
|
|
span: *internal_span,
|
|
help: Some(e.to_string()),
|
|
})?;
|
|
|
|
let (limit, overflow) = value.overflowing_mul(res.multiplier);
|
|
if overflow {
|
|
Ok(RLIM_INFINITY)
|
|
} else {
|
|
Ok(limit)
|
|
}
|
|
}
|
|
Value::Filesize { val, internal_span } => {
|
|
if res.multiplier != 1024 {
|
|
return Err(ShellError::TypeMismatch {
|
|
err_message: format!(
|
|
"filesize is not compatible with resource {:?}",
|
|
res.resource
|
|
),
|
|
span: *internal_span,
|
|
});
|
|
}
|
|
|
|
rlim_t::try_from(*val).map_err(|e| ShellError::CantConvert {
|
|
to_type: "rlim_t".into(),
|
|
from_type: "i64".into(),
|
|
span: *internal_span,
|
|
help: Some(e.to_string()),
|
|
})
|
|
}
|
|
Value::String { val, internal_span } => {
|
|
if val == "unlimited" {
|
|
Ok(RLIM_INFINITY)
|
|
} else if val == "soft" {
|
|
if soft {
|
|
Ok(hard_limit)
|
|
} else {
|
|
Ok(soft_limit)
|
|
}
|
|
} else if val == "hard" {
|
|
Ok(hard_limit)
|
|
} else {
|
|
return Err(ShellError::IncorrectValue {
|
|
msg: "Only unlimited, soft and hard are supported for strings".into(),
|
|
val_span: *internal_span,
|
|
call_span,
|
|
});
|
|
}
|
|
}
|
|
_ => Err(ShellError::TypeMismatch {
|
|
err_message: format!(
|
|
"string, int or filesize required, you provide {}",
|
|
limit_value.get_type()
|
|
),
|
|
span: limit_value.span(),
|
|
}),
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct ULimit;
|
|
|
|
impl Command for ULimit {
|
|
fn name(&self) -> &str {
|
|
"ulimit"
|
|
}
|
|
|
|
fn usage(&self) -> &str {
|
|
"Set or get resource usage limits."
|
|
}
|
|
|
|
fn signature(&self) -> Signature {
|
|
let mut sig = Signature::build("ulimit")
|
|
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
|
.switch("soft", "Sets soft resource limit", Some('S'))
|
|
.switch("hard", "Sets hard resource limit", Some('H'))
|
|
.switch("all", "Prints all current limits", Some('a'))
|
|
.optional("limit", SyntaxShape::Any, "Limit value.")
|
|
.category(Category::Platform);
|
|
|
|
for res in RESOURCE_ARRAY.iter() {
|
|
sig = sig.switch(res.name, res.desc, Some(res.flag));
|
|
}
|
|
|
|
sig
|
|
}
|
|
|
|
fn run(
|
|
&self,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
call: &Call,
|
|
_input: PipelineData,
|
|
) -> Result<PipelineData, ShellError> {
|
|
let mut soft = call.has_flag(engine_state, stack, "soft")?;
|
|
let mut hard = call.has_flag(engine_state, stack, "hard")?;
|
|
let all = call.has_flag(engine_state, stack, "all")?;
|
|
|
|
if !hard && !soft {
|
|
// Set both hard and soft limits if neither was specified.
|
|
hard = true;
|
|
soft = true;
|
|
}
|
|
|
|
if let Some(limit_value) = call.opt::<Value>(engine_state, stack, 0)? {
|
|
let mut set_default_limit = true;
|
|
|
|
for res in RESOURCE_ARRAY.iter() {
|
|
if call.has_flag(engine_state, stack, res.name)? {
|
|
set_limits(&limit_value, res, soft, hard, call.head)?;
|
|
|
|
if set_default_limit {
|
|
set_default_limit = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set `RLIMIT_FSIZE` limit if no resource flag provided.
|
|
if set_default_limit {
|
|
let res = ResourceInfo::default();
|
|
set_limits(&limit_value, &res, hard, soft, call.head)?;
|
|
}
|
|
|
|
Ok(PipelineData::Empty)
|
|
} else {
|
|
print_limits(call, engine_state, stack, all, soft, hard)
|
|
}
|
|
}
|
|
|
|
fn examples(&self) -> Vec<Example> {
|
|
vec![
|
|
Example {
|
|
description: "Print all current limits",
|
|
example: "ulimit -a",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Print specified limits",
|
|
example: "ulimit --core-size --data-size --file-size",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Set limit",
|
|
example: "ulimit --core-size 102400",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Set stack size soft limit",
|
|
example: "ulimit -s -S 10240",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Set virtual memory size hard limit",
|
|
example: "ulimit -v -H 10240",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Set core size limit to unlimited",
|
|
example: "ulimit -c unlimited",
|
|
result: None,
|
|
},
|
|
]
|
|
}
|
|
|
|
fn search_terms(&self) -> Vec<&str> {
|
|
vec!["resource", "limits"]
|
|
}
|
|
}
|