1use super::{Command, ResourceExt as _, SetLimitType, SetLimitValue, ShowLimitType};
20use crate::common::syntax::{Mode, OptionSpec, ParseError, parse_arguments};
21use std::num::ParseIntError;
22use std::str::FromStr;
23use thiserror::Error;
24use yash_env::Env;
25use yash_env::semantics::Field;
26use yash_env::source::Location;
27use yash_env::source::pretty::{Report, ReportType, Snippet, Span, SpanRole, add_span};
28use yash_env::system::resource::Resource;
29
30#[derive(Clone, Debug, Eq, Error, PartialEq)]
31#[non_exhaustive]
32pub enum Error {
33 #[error(transparent)]
35 CommonError(#[from] ParseError<'static>),
36
37 #[error("cannot set limit for -a")]
39 AllWithOperand(Field),
40
41 #[error("cannot show both hard and soft limits at once")]
44 ShowingBoth { soft: Location, hard: Location },
45
46 #[error("cannot specify more than one resource")]
48 TooManyResources(Location),
49
50 #[error("too many operands")]
54 TooManyOperands(Vec<Field>),
55
56 #[error("invalid limit")]
58 InvalidLimit(Field, ParseIntError),
59}
60
61impl Error {
62 #[must_use]
64 pub fn to_report(&self) -> Report<'_> {
65 let snippets = match self {
66 Self::CommonError(e) => return e.to_report(),
67 Self::AllWithOperand(field) => Snippet::with_primary_span(
68 &field.origin,
69 format!("{field}: unexpected operand").into(),
70 ),
71 Self::ShowingBoth { soft, hard } => {
72 let mut snippets =
73 Snippet::with_primary_span(soft, "soft limit requested here".into());
74 add_span(
75 &hard.code,
76 Span {
77 range: hard.byte_range(),
78 role: SpanRole::Primary {
79 label: "hard limit requested here".into(),
80 },
81 },
82 &mut snippets,
83 );
84 snippets
85 }
86 Self::TooManyResources(location) => {
87 Snippet::with_primary_span(location, "unexpected option".into())
88 }
89 Self::TooManyOperands(fields) => Snippet::with_primary_span(
90 &fields[1].origin,
91 format!("{}: unexpected operand", fields[1].value).into(),
92 ),
93 Self::InvalidLimit(operand, parse_int_error) => Snippet::with_primary_span(
94 &operand.origin,
95 format!("{operand}: invalid limit ({parse_int_error})").into(),
96 ),
97 };
98 let mut report = Report::new();
99 report.r#type = ReportType::Error;
100 report.title = self.to_string().into();
101 report.snippets = snippets;
102 report
103 }
104}
105
106impl<'a> From<&'a Error> for Report<'a> {
107 #[inline]
108 fn from(error: &'a Error) -> Self {
109 error.to_report()
110 }
111}
112
113pub type Result = std::result::Result<Command, Error>;
115
116const OPTION_SPECS: &[OptionSpec] = &[
118 OptionSpec::new().short('H').long("hard"),
119 OptionSpec::new().short('S').long("soft"),
120 OptionSpec::new().short('a').long("all"),
121 OptionSpec::new().short('v').long("as"),
122 OptionSpec::new().short('c').long("core"),
123 OptionSpec::new().short('t').long("cpu"),
124 OptionSpec::new().short('d').long("data"),
125 OptionSpec::new().short('f').long("fsize"),
126 OptionSpec::new().short('k').long("kqueues"),
127 OptionSpec::new().short('x').long("locks"),
128 OptionSpec::new().short('l').long("memlock"),
129 OptionSpec::new().short('q').long("msgqueue"),
130 OptionSpec::new().short('e').long("nice"),
131 OptionSpec::new().short('n').long("nofile"),
132 OptionSpec::new().short('u').long("nproc"),
133 OptionSpec::new().short('m').long("rss"),
134 OptionSpec::new().short('r').long("rtprio"),
135 OptionSpec::new().short('R').long("rttime"),
136 OptionSpec::new().short('b').long("sbsize"),
137 OptionSpec::new().short('i').long("sigpending"),
138 OptionSpec::new().short('s').long("stack"),
139 OptionSpec::new().short('w').long("swap"),
140];
141
142pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result {
144 let (options, operands) = parse_arguments(OPTION_SPECS, Mode::with_env(env), args)?;
145
146 let mut resource_option = None;
147 let mut hard = None;
148 let mut soft = None;
149
150 for option in options {
151 match option.spec.get_short().unwrap() {
152 'H' => hard = Some(option.location),
153 'S' => soft = Some(option.location),
154 c => {
155 if resource_option.is_some_and(|c2| c2 != c) {
156 return Err(Error::TooManyResources(option.location));
157 }
158 resource_option = Some(c);
159 }
160 }
161 }
162
163 let resource = match resource_option {
164 Some('a') => {
165 return if let Some(operand) = operands.into_iter().next() {
166 Err(Error::AllWithOperand(operand))
167 } else {
168 Ok(Command::ShowAll(show_limit_type(hard, soft)?))
169 };
170 }
171
172 Some(option_char) => Resource::ALL
173 .iter()
174 .copied()
175 .find(|r| r.option() == option_char)
176 .unwrap(),
177
178 None => Resource::FSIZE,
179 };
180
181 if operands.len() > 1 {
182 return Err(Error::TooManyOperands(operands));
183 }
184
185 if let Some(operand) = { operands }.pop() {
186 let limit_type = set_limit_type(hard, soft);
187 let value = parse_value(operand)?;
188 return Ok(Command::Set(resource, limit_type, value));
189 }
190
191 Ok(Command::ShowOne(resource, show_limit_type(hard, soft)?))
192}
193
194fn show_limit_type(
195 hard: Option<Location>,
196 soft: Option<Location>,
197) -> std::result::Result<ShowLimitType, Error> {
198 match (hard, soft) {
199 (None, _) => Ok(ShowLimitType::Soft),
200 (Some(_), None) => Ok(ShowLimitType::Hard),
201 (Some(hard), Some(soft)) => Err(Error::ShowingBoth { soft, hard }),
202 }
203}
204
205fn set_limit_type(hard: Option<Location>, soft: Option<Location>) -> SetLimitType {
206 match (hard, soft) {
207 (None, Some(_)) => SetLimitType::Soft,
208 (Some(_), None) => SetLimitType::Hard,
209 (None, None) | (Some(_), Some(_)) => SetLimitType::Both,
210 }
211}
212
213fn parse_value(operand: Field) -> std::result::Result<SetLimitValue, Error> {
214 operand
215 .value
216 .parse()
217 .map_err(|e| Error::InvalidLimit(operand, e))
218}
219
220impl FromStr for SetLimitValue {
221 type Err = ParseIntError;
222
223 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
224 match s {
225 "unlimited" => Ok(Self::Unlimited),
226 "soft" => Ok(Self::CurrentSoft),
227 "hard" => Ok(Self::CurrentHard),
228 _ => Ok(Self::Number(s.parse()?)),
229 }
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn show_default_soft_default_fsize() {
239 let env = Env::new_virtual();
240 let result = parse(&env, vec![]);
241 assert_eq!(
242 result,
243 Ok(Command::ShowOne(Resource::FSIZE, ShowLimitType::Soft))
244 );
245 }
246
247 #[test]
248 fn show_explicit_soft_default_fsize() {
249 let env = Env::new_virtual();
250 let result = parse(&env, Field::dummies(["-S"]));
251 assert_eq!(
252 result,
253 Ok(Command::ShowOne(Resource::FSIZE, ShowLimitType::Soft))
254 );
255 }
256
257 #[test]
258 fn show_explicit_hard_default_fsize() {
259 let env = Env::new_virtual();
260 let result = parse(&env, Field::dummies(["-H"]));
261 assert_eq!(
262 result,
263 Ok(Command::ShowOne(Resource::FSIZE, ShowLimitType::Hard))
264 );
265 }
266
267 #[test]
268 fn show_cpu_default_soft() {
269 let env = Env::new_virtual();
270 let result = parse(&env, Field::dummies(["-t"]));
271 assert_eq!(
272 result,
273 Ok(Command::ShowOne(Resource::CPU, ShowLimitType::Soft))
274 );
275 }
276
277 #[test]
278 fn show_cpu_explicit_hard() {
279 let env = Env::new_virtual();
280 let result = parse(&env, Field::dummies(["-t", "-H"]));
281 assert_eq!(
282 result,
283 Ok(Command::ShowOne(Resource::CPU, ShowLimitType::Hard))
284 );
285 }
286
287 #[test]
288 fn show_all_default_soft() {
289 let env = Env::new_virtual();
290 let result = parse(&env, Field::dummies(["-a"]));
291 assert_eq!(result, Ok(Command::ShowAll(ShowLimitType::Soft)));
292 }
293
294 #[test]
295 fn show_all_explicit_soft() {
296 let env = Env::new_virtual();
297 let result = parse(&env, Field::dummies(["-Sa"]));
298 assert_eq!(result, Ok(Command::ShowAll(ShowLimitType::Soft)));
299 }
300
301 #[test]
302 fn show_all_explicit_hard() {
303 let env = Env::new_virtual();
304 let result = parse(&env, Field::dummies(["-aH"]));
305 assert_eq!(result, Ok(Command::ShowAll(ShowLimitType::Hard)));
306 }
307
308 #[test]
309 fn set_default_both_default_fsize() {
310 let env = Env::new_virtual();
311 let result = parse(&env, Field::dummies(["0"]));
312 assert_eq!(
313 result,
314 Ok(Command::Set(
315 Resource::FSIZE,
316 SetLimitType::Both,
317 SetLimitValue::Number(0)
318 ))
319 );
320 }
321
322 #[test]
323 fn set_explicit_soft_default_fsize() {
324 let env = Env::new_virtual();
325 let result = parse(&env, Field::dummies(["-S", "0"]));
326 assert_eq!(
327 result,
328 Ok(Command::Set(
329 Resource::FSIZE,
330 SetLimitType::Soft,
331 SetLimitValue::Number(0)
332 ))
333 );
334 }
335
336 #[test]
337 fn set_explicit_hard_default_fsize() {
338 let env = Env::new_virtual();
339 let result = parse(&env, Field::dummies(["-H", "0"]));
340 assert_eq!(
341 result,
342 Ok(Command::Set(
343 Resource::FSIZE,
344 SetLimitType::Hard,
345 SetLimitValue::Number(0)
346 ))
347 );
348 }
349
350 #[test]
351 fn set_default_both_explicit_data() {
352 let env = Env::new_virtual();
353 let result = parse(&env, Field::dummies(["-d", "0"]));
354 assert_eq!(
355 result,
356 Ok(Command::Set(
357 Resource::DATA,
358 SetLimitType::Both,
359 SetLimitValue::Number(0)
360 ))
361 );
362 }
363
364 #[test]
365 fn set_explicit_soft_explicit_data() {
366 let env = Env::new_virtual();
367 let result = parse(&env, Field::dummies(["-Sd", "0"]));
368 assert_eq!(
369 result,
370 Ok(Command::Set(
371 Resource::DATA,
372 SetLimitType::Soft,
373 SetLimitValue::Number(0)
374 ))
375 );
376 }
377
378 #[test]
379 fn set_explicit_hard_explicit_data() {
380 let env = Env::new_virtual();
381 let result = parse(&env, Field::dummies(["-Hd", "0"]));
382 assert_eq!(
383 result,
384 Ok(Command::Set(
385 Resource::DATA,
386 SetLimitType::Hard,
387 SetLimitValue::Number(0)
388 ))
389 );
390 }
391
392 #[test]
393 fn set_unlimited() {
394 let env = Env::new_virtual();
395 let result = parse(&env, Field::dummies(["unlimited"]));
396 assert_eq!(
397 result,
398 Ok(Command::Set(
399 Resource::FSIZE,
400 SetLimitType::Both,
401 SetLimitValue::Unlimited
402 ))
403 );
404 }
405
406 #[test]
407 fn set_all() {
408 let env = Env::new_virtual();
409 let result = parse(&env, Field::dummies(["-a", "0"]));
410 assert_eq!(result, Err(Error::AllWithOperand(Field::dummy("0"))));
411 }
412
413 #[test]
414 fn show_hard_and_soft() {
415 let env = Env::new_virtual();
416 let result = parse(&env, Field::dummies(["-H", "-S"]));
417 assert_eq!(
418 result,
419 Err(Error::ShowingBoth {
420 soft: Location::dummy("-S"),
421 hard: Location::dummy("-H")
422 })
423 );
424 }
425
426 #[test]
427 fn set_hard_and_soft() {
428 let env = Env::new_virtual();
429 let result = parse(&env, Field::dummies(["-HS", "0"]));
430 assert_eq!(
431 result,
432 Ok(Command::Set(
433 Resource::FSIZE,
434 SetLimitType::Both,
435 SetLimitValue::Number(0)
436 ))
437 );
438 }
439
440 #[test]
441 fn redundant_limit_type_options() {
442 let env = Env::new_virtual();
443 let result = parse(&env, Field::dummies(["-H", "-H", "0"]));
444 assert_eq!(
445 result,
446 Ok(Command::Set(
447 Resource::FSIZE,
448 SetLimitType::Hard,
449 SetLimitValue::Number(0)
450 ))
451 );
452 }
453
454 #[test]
455 fn more_than_one_resource() {
456 let env = Env::new_virtual();
457 let result = parse(&env, Field::dummies(["-d", "-f"]));
458 assert_eq!(result, Err(Error::TooManyResources(Location::dummy("-f"))));
459 }
460
461 #[test]
462 fn redundant_resource_options() {
463 let env = Env::new_virtual();
464 let result = parse(&env, Field::dummies(["-dd", "-d", "0"]));
465 assert_eq!(
466 result,
467 Ok(Command::Set(
468 Resource::DATA,
469 SetLimitType::Both,
470 SetLimitValue::Number(0)
471 ))
472 );
473 }
474
475 #[test]
476 fn too_many_operands() {
477 let env = Env::new_virtual();
478 let args = Field::dummies(["0", "1"]);
479 let result = parse(&env, args.clone());
480 assert_eq!(result, Err(Error::TooManyOperands(args)));
481 }
482
483 #[test]
484 fn set_limit_value_from_str_number() {
485 assert_eq!("0".parse(), Ok(SetLimitValue::Number(0)));
486 assert_eq!("1".parse(), Ok(SetLimitValue::Number(1)));
487 assert_eq!("100".parse(), Ok(SetLimitValue::Number(100)));
488 }
489
490 #[test]
491 fn set_limit_value_from_str_unlimited() {
492 assert_eq!("unlimited".parse(), Ok(SetLimitValue::Unlimited));
493 }
494
495 #[test]
496 fn set_limit_value_from_str_soft() {
497 assert_eq!("soft".parse(), Ok(SetLimitValue::CurrentSoft));
498 }
499
500 #[test]
501 fn set_limit_value_from_str_hard() {
502 assert_eq!("hard".parse(), Ok(SetLimitValue::CurrentHard));
503 }
504}