1pub use nix::sys::signal::Signal;
42use number_prefix::{NumberPrefix, Prefix};
43use std::collections::HashMap;
44use std::ffi::{CStr, CString, NulError};
45use std::fmt;
46use std::io;
47use std::num;
48use std::str;
49
50use sysctl::Sysctl;
51use thiserror::Error;
52
53mod ffi;
54
55const RCTL_DEFAULT_BUFSIZE: usize = 128 * 1024;
57
58#[derive(Debug, Error, PartialEq, Clone)]
59pub enum ParseError {
60 #[error("Unknown subject type: {0}")]
61 UnknownSubjectType(String),
62
63 #[error("No such user: {0}")]
64 UnknownUser(String),
65
66 #[error("Unknown resource: {0}")]
67 UnknownResource(String),
68
69 #[error("Unknown action: {0}")]
70 UnknownAction(String),
71
72 #[error("Bogus data at end of limit: {0}")]
73 LimitBogusData(String),
74
75 #[error("Invalid limit literal: {0}")]
76 InvalidLimitLiteral(String),
77
78 #[error("Invalid numeric value: {0}")]
79 InvalidNumeral(num::ParseIntError),
80
81 #[error("No subject specified")]
82 NoSubjectGiven,
83
84 #[error("Bogus data at end of subject: {0}")]
85 SubjectBogusData(String),
86
87 #[error("Invalid Rule syntax: '{0}'")]
88 InvalidRuleSyntax(String),
89}
90
91#[derive(Debug, Error)]
92pub enum Error {
93 #[error("Parse Error: {0}")]
94 ParseError(ParseError),
95
96 #[error("OS Error: {0}")]
97 OsError(io::Error),
98
99 #[error("An interior Nul byte was found while attempting to construct a CString: {0}")]
100 CStringError(NulError),
101
102 #[error("The statistics returned by the kernel were invalid.")]
103 InvalidStatistics,
104
105 #[error("Invalid RCTL / RACCT kernel state: {0}")]
106 InvalidKernelState(State),
107}
108
109mod subject {
111 use super::ParseError;
112 use nix::unistd::{self, Uid};
113 use std::fmt;
114
115 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
117 pub struct User(pub Uid);
118
119 impl User {
120 pub fn from_uid(uid: libc::uid_t) -> User {
121 User(Uid::from_raw(uid))
122 }
123
124 pub fn from_name(name: &str) -> Result<User, ParseError> {
125 let uid = unistd::User::from_name(name)
126 .unwrap_or(None)
130 .ok_or_else(|| ParseError::UnknownUser(name.into()))?
131 .uid;
132
133 Ok(User(uid))
134 }
135 }
136
137 impl fmt::Display for User {
138 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139 match unistd::User::from_uid(self.0) {
140 Err(e) => write!(f, "user: <{e}>"),
141 Ok(Some(user)) => write!(f, "user:{}", user.name),
142 Ok(None) => write!(f, "user:{}", self.0),
143 }
144 }
145 }
146
147 #[cfg(feature = "serialize")]
148 impl serde::Serialize for User {
149 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
150 where
151 S: serde::Serializer,
152 {
153 self.0.as_raw().serialize(serializer)
154 }
155 }
156
157 impl<'a> From<&'a User> for String {
158 fn from(user: &'a User) -> String {
159 format!("user:{}", user.0)
160 }
161 }
162
163 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
165 #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
166 pub struct Process(pub libc::pid_t);
167
168 impl fmt::Display for Process {
169 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170 write!(f, "process:{}", self.0)
171 }
172 }
173
174 impl<'a> From<&'a Process> for String {
175 fn from(proc: &'a Process) -> String {
176 format!("{proc}")
177 }
178 }
179
180 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
182 #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
183 pub struct Jail(pub String);
184
185 impl fmt::Display for Jail {
186 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187 write!(f, "jail:{}", self.0)
188 }
189 }
190
191 impl<'a> From<&'a Jail> for String {
192 fn from(jail: &'a Jail) -> String {
193 format!("{jail}")
194 }
195 }
196
197 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
199 #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
200 pub struct LoginClass(pub String);
201
202 impl fmt::Display for LoginClass {
203 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
204 write!(f, "loginclass:{}", self.0)
205 }
206 }
207
208 impl<'a> From<&'a LoginClass> for String {
209 fn from(login_class: &'a LoginClass) -> String {
210 format!("{login_class}")
211 }
212 }
213
214 #[cfg(test)]
215 mod tests {
216 use super::*;
217
218 #[test]
219 fn display_jail_name() {
220 assert_eq!(
221 format!("{}", Jail("testjail_rctl_name".into())),
222 "jail:testjail_rctl_name".to_string()
223 );
224 }
225
226 #[test]
227 fn display_user() {
228 assert_eq!(
229 format!("{}", User::from_name("nobody").expect("no nobody user")),
230 "user:nobody".to_string()
231 );
232
233 assert_eq!(format!("{}", User::from_uid(4242)), "user:4242".to_string());
234 }
235
236 #[test]
237 fn display_loginclass() {
238 assert_eq!(
239 format!("{}", LoginClass("test".into())),
240 "loginclass:test".to_string()
241 );
242 }
243 }
244}
245
246#[derive(Clone, Debug, PartialEq, Eq, Hash)]
257#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
258pub enum Subject {
259 Process(subject::Process),
260 Jail(subject::Jail),
261 User(subject::User),
262 LoginClass(subject::LoginClass),
263}
264
265impl Subject {
266 pub fn process_id(pid: libc::pid_t) -> Self {
267 Subject::Process(subject::Process(pid))
268 }
269
270 pub fn user_name(name: &str) -> Result<Self, ParseError> {
271 Ok(Subject::User(subject::User::from_name(name)?))
272 }
273
274 pub fn user_id(uid: libc::uid_t) -> Self {
275 Subject::User(subject::User::from_uid(uid))
276 }
277
278 pub fn login_class<S: Into<String>>(name: S) -> Self {
279 Subject::LoginClass(subject::LoginClass(name.into()))
280 }
281
282 pub fn jail_name<S: Into<String>>(name: S) -> Self {
283 Subject::Jail(subject::Jail(name.into()))
284 }
285
286 pub fn usage(&self) -> Result<HashMap<Resource, usize>, Error> {
307 let filter = Filter::new().subject(self);
308
309 let rusage = rctl_api_wrapper(ffi::rctl_get_racct, &filter)?;
310
311 let mut map: HashMap<Resource, usize> = HashMap::new();
312
313 for statistic in rusage.split(',') {
314 let mut kv = statistic.split('=');
315
316 let resource = kv
317 .next()
318 .ok_or(Error::InvalidStatistics)?
319 .parse::<Resource>()
320 .map_err(Error::ParseError)?;
321
322 let value = kv
323 .next()
324 .ok_or(Error::InvalidStatistics)?
325 .parse::<usize>()
326 .map_err(ParseError::InvalidNumeral)
327 .map_err(Error::ParseError)?;
328
329 map.insert(resource, value);
330 }
331
332 Ok(map)
333 }
334
335 pub fn limits(&self) -> Result<RuleParsingIntoIter<String>, Error> {
337 let outbuf = rctl_api_wrapper(ffi::rctl_get_limits, self)?;
338
339 Ok(RuleParsingIntoIter { inner: outbuf })
340 }
341}
342
343impl fmt::Display for Subject {
344 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
345 match self {
346 Subject::Process(p) => write!(f, "{p}"),
347 Subject::User(u) => write!(f, "{u}"),
348 Subject::Jail(j) => write!(f, "{j}"),
349 Subject::LoginClass(c) => write!(f, "{c}"),
350 }
351 }
352}
353
354impl<'a> From<&'a Subject> for String {
355 fn from(subject: &'a Subject) -> String {
356 match &subject {
357 Subject::Process(p) => p.into(),
358 Subject::User(u) => u.into(),
359 Subject::Jail(j) => j.into(),
360 Subject::LoginClass(c) => c.into(),
361 }
362 }
363}
364
365fn parse_process(s: &str) -> Result<Subject, ParseError> {
366 s.parse::<libc::pid_t>()
367 .map_err(ParseError::InvalidNumeral)
368 .map(Subject::process_id)
369}
370
371fn parse_user(s: &str) -> Result<Subject, ParseError> {
372 match s.parse::<libc::uid_t>() {
373 Ok(uid) => Ok(Subject::user_id(uid)),
374 Err(_) => Ok(Subject::user_name(s)?),
375 }
376}
377
378fn parse_jail(s: &str) -> Subject {
379 Subject::jail_name(s)
380}
381
382fn parse_login_class(s: &str) -> Subject {
383 Subject::login_class(s)
384}
385
386impl str::FromStr for Subject {
387 type Err = ParseError;
388
389 fn from_str(s: &str) -> Result<Self, Self::Err> {
390 let parts: Vec<_> = s.split(':').collect();
391
392 let subject_type = parts[0].parse::<SubjectType>()?;
393
394 match parts.len() {
395 1 => Err(ParseError::NoSubjectGiven),
396 2 => match subject_type {
397 SubjectType::Process => parse_process(parts[1]),
398 SubjectType::User => parse_user(parts[1]),
399 SubjectType::LoginClass => Ok(parse_login_class(parts[1])),
400 SubjectType::Jail => Ok(parse_jail(parts[1])),
401 },
402 _ => Err(ParseError::SubjectBogusData(format!(
403 ":{}",
404 parts[2..].join(":")
405 ))),
406 }
407 }
408}
409
410#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
412#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
413pub enum SubjectType {
414 Process,
415 Jail,
416 User,
417 LoginClass,
418}
419
420impl str::FromStr for SubjectType {
421 type Err = ParseError;
422
423 fn from_str(s: &str) -> Result<Self, Self::Err> {
424 match s {
425 "process" => Ok(SubjectType::Process),
426 "jail" => Ok(SubjectType::Jail),
427 "user" => Ok(SubjectType::User),
428 "loginclass" => Ok(SubjectType::LoginClass),
429 _ => Err(ParseError::UnknownSubjectType(s.into())),
430 }
431 }
432}
433
434impl<'a> From<&'a Subject> for SubjectType {
435 fn from(subject: &'a Subject) -> Self {
436 match subject {
437 Subject::Process(_) => SubjectType::Process,
438 Subject::Jail(_) => SubjectType::Jail,
439 Subject::User(_) => SubjectType::User,
440 Subject::LoginClass(_) => SubjectType::LoginClass,
441 }
442 }
443}
444
445impl SubjectType {
446 pub fn as_str(&self) -> &'static str {
447 match self {
448 SubjectType::Process => "process",
449 SubjectType::Jail => "jail",
450 SubjectType::User => "user",
451 SubjectType::LoginClass => "loginclass",
452 }
453 }
454}
455
456impl<'a> From<&'a SubjectType> for &'static str {
457 fn from(subject_type: &'a SubjectType) -> &'static str {
458 subject_type.as_str()
459 }
460}
461
462impl<'a> From<&'a SubjectType> for String {
463 fn from(subject_type: &'a SubjectType) -> String {
464 subject_type.as_str().into()
465 }
466}
467
468impl fmt::Display for SubjectType {
469 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
470 let r: &'static str = self.into();
471 write!(f, "{r}")
472 }
473}
474
475#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
477#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
478pub enum Resource {
479 CpuTime,
481
482 DataSize,
484
485 StackSize,
495
496 CoreDumpSize,
509
510 MemoryUse,
512
513 MemoryLocked,
528
529 MaxProcesses,
531
532 OpenFiles,
534
535 VMemoryUse,
537
538 PseudoTerminals,
540
541 SwapUse,
543
544 NThreads,
546
547 MsgqQueued,
549
550 MsgqSize,
552
553 NMsgq,
555
556 Nsem,
558
559 NSemop,
561
562 NShm,
564
565 ShmSize,
567
568 Wallclock,
570
571 PercentCpu,
573
574 ReadBps,
576
577 WriteBps,
579
580 ReadIops,
582
583 WriteIops,
585}
586
587impl Resource {
588 pub fn as_str(&self) -> &'static str {
596 match self {
597 Resource::CpuTime => "cputime",
598 Resource::DataSize => "datasize",
599 Resource::StackSize => "stacksize",
600 Resource::CoreDumpSize => "coredumpsize",
601 Resource::MemoryUse => "memoryuse",
602 Resource::MemoryLocked => "memorylocked",
603 Resource::MaxProcesses => "maxproc",
604 Resource::OpenFiles => "openfiles",
605 Resource::VMemoryUse => "vmemoryuse",
606 Resource::PseudoTerminals => "pseudoterminals",
607 Resource::SwapUse => "swapuse",
608 Resource::NThreads => "nthr",
609 Resource::MsgqQueued => "msgqqueued",
610 Resource::MsgqSize => "msgqsize",
611 Resource::NMsgq => "nmsgq",
612 Resource::Nsem => "nsem",
613 Resource::NSemop => "nsemop",
614 Resource::NShm => "nshm",
615 Resource::ShmSize => "shmsize",
616 Resource::Wallclock => "wallclock",
617 Resource::PercentCpu => "pcpu",
618 Resource::ReadBps => "readbps",
619 Resource::WriteBps => "writebps",
620 Resource::ReadIops => "readiops",
621 Resource::WriteIops => "writeiops",
622 }
623 }
624}
625
626impl str::FromStr for Resource {
627 type Err = ParseError;
628
629 fn from_str(s: &str) -> Result<Self, Self::Err> {
630 match s {
631 "cputime" => Ok(Resource::CpuTime),
632 "datasize" => Ok(Resource::DataSize),
633 "stacksize" => Ok(Resource::StackSize),
634 "coredumpsize" => Ok(Resource::CoreDumpSize),
635 "memoryuse" => Ok(Resource::MemoryUse),
636 "memorylocked" => Ok(Resource::MemoryLocked),
637 "maxproc" => Ok(Resource::MaxProcesses),
638 "openfiles" => Ok(Resource::OpenFiles),
639 "vmemoryuse" => Ok(Resource::VMemoryUse),
640 "pseudoterminals" => Ok(Resource::PseudoTerminals),
641 "swapuse" => Ok(Resource::SwapUse),
642 "nthr" => Ok(Resource::NThreads),
643 "msgqqueued" => Ok(Resource::MsgqQueued),
644 "msgqsize" => Ok(Resource::MsgqSize),
645 "nmsgq" => Ok(Resource::NMsgq),
646 "nsem" => Ok(Resource::Nsem),
647 "nsemop" => Ok(Resource::NSemop),
648 "nshm" => Ok(Resource::NShm),
649 "shmsize" => Ok(Resource::ShmSize),
650 "wallclock" => Ok(Resource::Wallclock),
651 "pcpu" => Ok(Resource::PercentCpu),
652 "readbps" => Ok(Resource::ReadBps),
653 "writebps" => Ok(Resource::WriteBps),
654 "readiops" => Ok(Resource::ReadIops),
655 "writeiops" => Ok(Resource::WriteIops),
656 _ => Err(ParseError::UnknownResource(s.into())),
657 }
658 }
659}
660
661impl<'a> From<&'a Resource> for &'a str {
662 fn from(resource: &'a Resource) -> &'a str {
663 resource.as_str()
664 }
665}
666
667impl<'a> From<&'a Resource> for String {
668 fn from(resource: &'a Resource) -> String {
669 resource.as_str().to_owned()
670 }
671}
672
673impl fmt::Display for Resource {
674 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
675 write!(f, "{}", self.as_str())
676 }
677}
678
679#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
681#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
682pub enum Action {
683 Deny,
696
697 Log,
699
700 DevCtl,
705
706 #[cfg_attr(feature = "serialize", serde(serialize_with = "signal_serialize"))]
719 Signal(Signal),
720
721 Throttle,
732}
733
734impl Action {
735 pub fn as_str(&self) -> &'static str {
753 match self {
754 Action::Deny => "deny",
755 Action::Log => "log",
756 Action::DevCtl => "devctl",
757 Action::Throttle => "throttle",
758 Action::Signal(sig) => match sig {
759 Signal::SIGHUP => "sighup",
760 Signal::SIGINT => "sigint",
761 Signal::SIGQUIT => "sigquit",
762 Signal::SIGILL => "sigill",
763 Signal::SIGTRAP => "sigtrap",
764 Signal::SIGABRT => "sigabrt",
765 Signal::SIGBUS => "sigbus",
766 Signal::SIGFPE => "sigfpe",
767 Signal::SIGKILL => "sigkill",
768 Signal::SIGUSR1 => "sigusr1",
769 Signal::SIGSEGV => "sigsegv",
770 Signal::SIGUSR2 => "sigusr2",
771 Signal::SIGPIPE => "sigpipe",
772 Signal::SIGALRM => "sigalrm",
773 Signal::SIGTERM => "sigterm",
774 Signal::SIGCHLD => "sigchld",
775 Signal::SIGCONT => "sigcont",
776 Signal::SIGSTOP => "sigstop",
777 Signal::SIGTSTP => "sigtstp",
778 Signal::SIGTTIN => "sigttin",
779 Signal::SIGTTOU => "sigttou",
780 Signal::SIGURG => "sigurg",
781 Signal::SIGXCPU => "sigxcpu",
782 Signal::SIGXFSZ => "sigxfsz",
783 Signal::SIGVTALRM => "sigvtalrm",
784 Signal::SIGPROF => "sigprof",
785 Signal::SIGWINCH => "sigwinch",
786 Signal::SIGIO => "sigio",
787 Signal::SIGSYS => "sigsys",
788 Signal::SIGEMT => "sigemt",
789 Signal::SIGINFO => "siginfo",
790 _ => "unknown",
791 },
792 }
793 }
794}
795
796impl str::FromStr for Action {
797 type Err = ParseError;
798
799 fn from_str(s: &str) -> Result<Self, Self::Err> {
800 match s {
801 "deny" => Ok(Action::Deny),
802 "log" => Ok(Action::Log),
803 "devctl" => Ok(Action::DevCtl),
804 "throttle" => Ok(Action::Throttle),
805 "sighup" => Ok(Action::Signal(Signal::SIGHUP)),
806 "sigint" => Ok(Action::Signal(Signal::SIGINT)),
807 "sigquit" => Ok(Action::Signal(Signal::SIGQUIT)),
808 "sigill" => Ok(Action::Signal(Signal::SIGILL)),
809 "sigtrap" => Ok(Action::Signal(Signal::SIGTRAP)),
810 "sigabrt" => Ok(Action::Signal(Signal::SIGABRT)),
811 "sigbus" => Ok(Action::Signal(Signal::SIGBUS)),
812 "sigfpe" => Ok(Action::Signal(Signal::SIGFPE)),
813 "sigkill" => Ok(Action::Signal(Signal::SIGKILL)),
814 "sigusr1" => Ok(Action::Signal(Signal::SIGUSR1)),
815 "sigsegv" => Ok(Action::Signal(Signal::SIGSEGV)),
816 "sigusr2" => Ok(Action::Signal(Signal::SIGUSR2)),
817 "sigpipe" => Ok(Action::Signal(Signal::SIGPIPE)),
818 "sigalrm" => Ok(Action::Signal(Signal::SIGALRM)),
819 "sigterm" => Ok(Action::Signal(Signal::SIGTERM)),
820 "sigchld" => Ok(Action::Signal(Signal::SIGCHLD)),
821 "sigcont" => Ok(Action::Signal(Signal::SIGCONT)),
822 "sigstop" => Ok(Action::Signal(Signal::SIGSTOP)),
823 "sigtstp" => Ok(Action::Signal(Signal::SIGTSTP)),
824 "sigttin" => Ok(Action::Signal(Signal::SIGTTIN)),
825 "sigttou" => Ok(Action::Signal(Signal::SIGTTOU)),
826 "sigurg" => Ok(Action::Signal(Signal::SIGURG)),
827 "sigxcpu" => Ok(Action::Signal(Signal::SIGXCPU)),
828 "sigxfsz" => Ok(Action::Signal(Signal::SIGXFSZ)),
829 "sigvtalrm" => Ok(Action::Signal(Signal::SIGVTALRM)),
830 "sigprof" => Ok(Action::Signal(Signal::SIGPROF)),
831 "sigwinch" => Ok(Action::Signal(Signal::SIGWINCH)),
832 "sigio" => Ok(Action::Signal(Signal::SIGIO)),
833 "sigsys" => Ok(Action::Signal(Signal::SIGSYS)),
834 "sigemt" => Ok(Action::Signal(Signal::SIGEMT)),
835 "siginfo" => Ok(Action::Signal(Signal::SIGINFO)),
836 _ => Err(ParseError::UnknownAction(s.into())),
837 }
838 }
839}
840
841impl<'a> From<&'a Action> for &'a str {
842 fn from(action: &'a Action) -> &'a str {
843 action.as_str()
844 }
845}
846
847impl<'a> From<&'a Action> for String {
848 fn from(action: &'a Action) -> String {
849 action.as_str().into()
850 }
851}
852
853impl fmt::Display for Action {
854 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
855 write!(f, "{}", self.as_str())
856 }
857}
858
859#[cfg(feature = "serialize")]
860fn signal_serialize<S>(signal: &Signal, s: S) -> Result<S::Ok, S::Error>
861where
862 S: serde::Serializer,
863{
864 let sig_str = format!("{signal:?}");
865 s.serialize_str(&sig_str)
866}
867
868#[derive(Debug, Clone, PartialEq, Eq, Hash)]
871#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
872pub struct Limit {
873 amount: usize,
874 per: Option<SubjectType>,
875}
876
877impl Limit {
878 pub fn amount(amount: usize) -> Limit {
884 Limit { amount, per: None }
885 }
886
887 pub fn amount_per(amount: usize, per: SubjectType) -> Limit {
916 Limit {
917 amount,
918 per: Some(per),
919 }
920 }
921}
922
923fn parse_limit_with_suffix(s: &str) -> Result<usize, ParseError> {
924 let s = s.trim().to_lowercase();
925
926 if let Ok(v) = s.parse::<usize>() {
927 return Ok(v);
928 }
929
930 let suffixes = ["k", "m", "g", "t", "p", "e", "z", "y"];
931
932 for (i, suffix) in suffixes.iter().enumerate() {
933 match s
934 .split(suffix)
935 .next()
936 .expect("could not split the suffix off")
937 .parse::<usize>()
938 {
939 Err(_) => continue,
940 Ok(v) => return Ok(v * 1024usize.pow((i + 1) as u32)),
941 };
942 }
943
944 Err(ParseError::InvalidLimitLiteral(s))
945}
946
947impl str::FromStr for Limit {
948 type Err = ParseError;
949
950 fn from_str(s: &str) -> Result<Self, Self::Err> {
951 let parts: Vec<_> = s.split('/').collect();
952
953 let val = parse_limit_with_suffix(parts[0])?;
954
955 match parts.len() {
956 1 => Ok(Limit::amount(val)),
957 2 => Ok(Limit::amount_per(val, parts[1].parse::<SubjectType>()?)),
958 _ => Err(ParseError::LimitBogusData(format!(
959 "/{}",
960 parts[2..].join("/")
961 ))),
962 }
963 }
964}
965
966impl fmt::Display for Limit {
967 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
968 let amount = match NumberPrefix::binary(self.amount as f64) {
969 NumberPrefix::Standalone(amt) => format!("{amt}"),
970 NumberPrefix::Prefixed(prefix, amt) => {
971 let prefix = match prefix {
972 Prefix::Kibi => "k",
973 Prefix::Mebi => "m",
974 Prefix::Gibi => "g",
975 Prefix::Tebi => "t",
976 Prefix::Pebi => "p",
977 Prefix::Exbi => "e",
978 Prefix::Zebi => "z",
979 Prefix::Yobi => "y",
980 _ => panic!("called binary_prefix but got decimal prefix"),
981 };
982
983 format!("{amt}{prefix}")
984 }
985 };
986
987 let per = match &self.per {
988 Some(s) => format!("/{s}"),
989 None => "".to_string(),
990 };
991
992 write!(f, "{amount}{per}")
993 }
994}
995
996impl<'a> From<&'a Limit> for String {
997 fn from(limit: &'a Limit) -> String {
998 let per = match &limit.per {
999 Some(s) => format!("/{s}"),
1000 None => "".to_string(),
1001 };
1002 format!("{}{}", limit.amount, per)
1003 }
1004}
1005
1006#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1026#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
1027pub struct Rule {
1028 pub subject: Subject,
1029 pub resource: Resource,
1030 pub limit: Limit,
1031 pub action: Action,
1032}
1033
1034impl Rule {
1035 pub fn apply(&self) -> Result<(), Error> {
1057 rctl_api_wrapper(ffi::rctl_add_rule, self)?;
1058
1059 Ok(())
1060 }
1061
1062 pub fn remove(&self) -> Result<(), Error> {
1084 let filter: Filter = self.into();
1085 filter.remove_rules()
1086 }
1087}
1088
1089impl fmt::Display for Rule {
1090 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1091 write!(
1092 f,
1093 "{}:{}:{}={}",
1094 self.subject, self.resource, self.action, self.limit
1095 )
1096 }
1097}
1098
1099impl<'a> From<&'a Rule> for String {
1100 fn from(rule: &'a Rule) -> String {
1101 let subject: String = (&rule.subject).into();
1102 let resource: &str = (&rule.resource).into();
1103 let action: &str = (&rule.action).into();
1104 let limit: String = (&rule.limit).into();
1105 format!("{subject}:{resource}:{action}={limit}")
1106 }
1107}
1108
1109impl str::FromStr for Rule {
1110 type Err = ParseError;
1111
1112 fn from_str(s: &str) -> Result<Self, Self::Err> {
1113 let parts: Vec<_> = s.split(':').collect();
1115
1116 if parts.len() != 4 {
1117 return Err(ParseError::InvalidRuleSyntax(s.into()));
1118 }
1119
1120 let subject = format!("{}:{}", parts[0], parts[1]).parse::<Subject>()?;
1121 let resource = parts[2].parse::<Resource>()?;
1122
1123 let parts: Vec<_> = parts[3].split('=').collect();
1124
1125 if parts.len() != 2 {
1126 return Err(ParseError::InvalidRuleSyntax(s.into()));
1127 }
1128
1129 let action = parts[0].parse::<Action>()?;
1130 let limit = parts[1].parse::<Limit>()?;
1131
1132 Ok(Rule {
1133 subject,
1134 resource,
1135 action,
1136 limit,
1137 })
1138 }
1139}
1140
1141#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1143pub struct RuleParserAdapter<I> {
1144 inner: I,
1145}
1146
1147impl<'a, I> Iterator for RuleParserAdapter<I>
1148where
1149 I: Iterator<Item = &'a str>,
1150{
1151 type Item = Rule;
1152
1153 fn next(&mut self) -> Option<Rule> {
1154 match self.inner.next() {
1155 Some(item) => item.parse::<Rule>().ok(),
1156 None => None,
1157 }
1158 }
1159}
1160
1161#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1163pub struct RuleParsingIntoIter<S> {
1164 inner: S,
1165}
1166
1167impl<'a> IntoIterator for &'a RuleParsingIntoIter<String> {
1168 type Item = Rule;
1169 type IntoIter = RuleParserAdapter<str::Split<'a, char>>;
1170
1171 fn into_iter(self) -> Self::IntoIter {
1172 RuleParserAdapter {
1173 inner: self.inner.split(','),
1174 }
1175 }
1176}
1177
1178#[derive(Debug, Default, Clone, PartialEq)]
1180pub struct Filter {
1181 subject_type: Option<SubjectType>,
1182 subject: Option<Subject>,
1183
1184 resource: Option<Resource>,
1185 limit: Option<Limit>,
1186
1187 action: Option<Action>,
1188 limit_per: Option<SubjectType>,
1189}
1190
1191impl Filter {
1192 pub fn new() -> Filter {
1202 Filter::default()
1203 }
1204
1205 pub fn subject_type(mut self: Filter, subject_type: &SubjectType) -> Filter {
1218 if self.subject.is_none() {
1219 self.subject_type = Some(*subject_type);
1220 }
1221 self
1222 }
1223
1224 pub fn subject(mut self: Filter, subject: &Subject) -> Filter {
1237 self.subject = Some(subject.clone());
1238 self.subject_type = None;
1239 self
1240 }
1241
1242 pub fn resource(mut self: Filter, resource: &Resource) -> Filter {
1253 self.resource = Some(*resource);
1254 self
1255 }
1256
1257 pub fn action(mut self: Filter, action: &Action) -> Filter {
1268 self.action = Some(*action);
1269 self
1270 }
1271
1272 pub fn deny(self: Filter) -> Filter {
1283 self.action(&Action::Deny)
1284 }
1285
1286 pub fn log(self: Filter) -> Filter {
1297 self.action(&Action::Log)
1298 }
1299
1300 pub fn devctl(self: Filter) -> Filter {
1311 self.action(&Action::DevCtl)
1312 }
1313
1314 pub fn signal(self: Filter, signal: Signal) -> Filter {
1326 self.action(&Action::Signal(signal))
1327 }
1328
1329 pub fn limit(mut self: Filter, limit: &Limit) -> Filter {
1349 let mut limit = limit.clone();
1350
1351 if let (Some(limit_per), None) = (self.limit_per, limit.per) {
1354 limit.per = Some(limit_per);
1355 }
1356
1357 self.limit_per = None;
1358 self.limit = Some(limit);
1359 self
1360 }
1361
1362 fn sanity(&self) {
1363 if let (Some(subject), Some(subject_type)) = (&self.subject, &self.subject_type) {
1364 let actual_type: SubjectType = subject.into();
1365 assert_eq!(&actual_type, subject_type);
1366 }
1367 }
1368
1369 pub fn rules(&self) -> Result<RuleParsingIntoIter<String>, Error> {
1386 let outbuf = rctl_api_wrapper(ffi::rctl_get_rules, self)?;
1387
1388 Ok(RuleParsingIntoIter { inner: outbuf })
1389 }
1390
1391 pub fn remove_rules(&self) -> Result<(), Error> {
1434 rctl_api_wrapper(ffi::rctl_remove_rule, self)?;
1435
1436 Ok(())
1437 }
1438}
1439
1440impl fmt::Display for Filter {
1441 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1442 self.sanity();
1443
1444 match self {
1445 Filter {
1446 subject_type: Some(s),
1447 ..
1448 } => write!(f, "{s}:"),
1449 Filter {
1450 subject_type: None,
1451 subject: Some(s),
1452 ..
1453 } => write!(f, "{s}"),
1454 Filter {
1455 subject_type: None,
1456 subject: None,
1457 ..
1458 } => write!(f, ":"),
1459 }?;
1460
1461 if let Filter {
1463 resource: None,
1464 action: None,
1465 limit: None,
1466 limit_per: None,
1467 ..
1468 } = self
1469 {
1470 return Ok(());
1471 }
1472
1473 match &self.resource {
1474 Some(resource) => write!(f, ":{resource}"),
1475 None => write!(f, ":"),
1476 }?;
1477
1478 if let Filter {
1480 action: None,
1481 limit: None,
1482 limit_per: None,
1483 ..
1484 } = self
1485 {
1486 return Ok(());
1487 }
1488
1489 match &self.action {
1490 Some(action) => write!(f, ":{action}"),
1491 None => write!(f, ":"),
1492 }?;
1493
1494 if let Filter {
1496 limit: None,
1497 limit_per: None,
1498 ..
1499 } = self
1500 {
1501 return Ok(());
1502 }
1503
1504 match &self.limit {
1505 Some(limit) => write!(f, "={limit}"),
1506 None => write!(
1507 f,
1508 "=/{}",
1509 self.limit_per.expect("could not unwrap limit_per")
1510 ),
1511 }
1512 }
1513}
1514
1515impl<'a> From<&'a Filter> for String {
1516 fn from(filter: &'a Filter) -> String {
1517 let subject: String = match filter.subject {
1518 Some(ref s) => s.into(),
1519 None => ":".into(),
1520 };
1521
1522 let resource: &str = match filter.resource {
1523 Some(ref r) => r.into(),
1524 None => "",
1525 };
1526
1527 let action: &str = match filter.action {
1528 Some(ref a) => a.into(),
1529 None => "",
1530 };
1531
1532 let limit: String = match filter.limit {
1533 Some(ref l) => l.into(),
1534 None => "".into(),
1535 };
1536
1537 format!("{subject}:{resource}:{action}={limit}")
1538 }
1539}
1540
1541impl From<Rule> for Filter {
1542 fn from(rule: Rule) -> Self {
1543 Filter {
1544 subject_type: None,
1545 subject: Some(rule.subject),
1546 resource: Some(rule.resource),
1547 limit: Some(rule.limit),
1548 limit_per: None,
1549 action: Some(rule.action),
1550 }
1551 }
1552}
1553
1554impl<'a> From<&'a Rule> for Filter {
1555 fn from(rule: &'a Rule) -> Self {
1556 let rule = rule.clone();
1557 Filter {
1558 subject_type: None,
1559 subject: Some(rule.subject),
1560 resource: Some(rule.resource),
1561 limit: Some(rule.limit),
1562 limit_per: None,
1563 action: Some(rule.action),
1564 }
1565 }
1566}
1567
1568impl<'a> From<&'a Subject> for Filter {
1569 fn from(subject: &'a Subject) -> Self {
1570 Filter::new().subject(subject)
1571 }
1572}
1573
1574impl From<Subject> for Filter {
1575 fn from(subject: Subject) -> Self {
1576 Filter::new().subject(&subject)
1577 }
1578}
1579
1580impl<'a> From<&'a SubjectType> for Filter {
1581 fn from(subject_type: &'a SubjectType) -> Self {
1582 Filter::new().subject_type(subject_type)
1583 }
1584}
1585
1586impl From<SubjectType> for Filter {
1587 fn from(subject_type: SubjectType) -> Self {
1588 Filter::new().subject_type(&subject_type)
1589 }
1590}
1591
1592impl<'a> From<&'a Action> for Filter {
1593 fn from(action: &'a Action) -> Self {
1594 Filter::new().action(action)
1595 }
1596}
1597
1598impl From<Action> for Filter {
1599 fn from(action: Action) -> Self {
1600 Filter::new().action(&action)
1601 }
1602}
1603
1604impl<'a> From<&'a Limit> for Filter {
1605 fn from(limit: &'a Limit) -> Self {
1606 Filter::new().limit(limit)
1607 }
1608}
1609
1610impl From<Limit> for Filter {
1611 fn from(limit: Limit) -> Self {
1612 Filter::new().limit(&limit)
1613 }
1614}
1615
1616#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
1618pub enum State {
1619 Disabled,
1622
1623 Enabled,
1625
1626 NotPresent,
1637
1638 Jailed,
1640}
1641
1642impl State {
1643 pub fn check() -> State {
1656 let jailed = sysctl::Ctl::new("security.jail.jailed");
1658
1659 if jailed.is_err() {
1661 return State::Jailed;
1662 }
1663
1664 match jailed.unwrap().value() {
1665 Ok(sysctl::CtlValue::Int(0)) => {}
1666 _ => return State::Jailed,
1667 };
1668
1669 let enable_racct = sysctl::Ctl::new("kern.racct.enable");
1671
1672 if enable_racct.is_err() {
1674 return State::NotPresent;
1675 }
1676
1677 match enable_racct.unwrap().value() {
1678 Ok(value) => match value {
1679 sysctl::CtlValue::U8(1) => State::Enabled,
1681
1682 sysctl::CtlValue::Uint(1) => State::Enabled,
1685
1686 _ => State::Disabled,
1688 },
1689
1690 _ => State::NotPresent,
1692 }
1693 }
1694
1695 pub fn is_enabled(&self) -> bool {
1708 matches!(self, State::Enabled)
1709 }
1710
1711 pub fn is_present(&self) -> bool {
1722 !matches!(self, State::NotPresent)
1723 }
1724}
1725
1726impl fmt::Display for State {
1727 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1728 match self {
1729 State::Enabled => write!(f, "enabled"),
1730 State::Disabled => write!(f, "disabled"),
1731 State::NotPresent => write!(f, "not present"),
1732 State::Jailed => write!(f, "not available in a jail"),
1733 }
1734 }
1735}
1736
1737fn rctl_api_wrapper<S: Into<String>>(
1738 api: unsafe extern "C" fn(
1739 *const libc::c_char,
1740 libc::size_t,
1741 *mut libc::c_char,
1742 libc::size_t,
1743 ) -> libc::c_int,
1744 input: S,
1745) -> Result<String, Error> {
1746 let input: String = input.into();
1748 let inputlen = input.len() + 1;
1749 let inbuf = CString::new(input).map_err(Error::CStringError)?;
1750
1751 let mut outbuf: Vec<i8> = vec![0; RCTL_DEFAULT_BUFSIZE];
1753
1754 loop {
1755 if unsafe {
1757 api(
1758 inbuf.as_ptr(),
1759 inputlen,
1760 outbuf.as_mut_ptr() as *mut libc::c_char,
1761 outbuf.len(),
1762 )
1763 } != 0
1764 {
1765 let err = io::Error::last_os_error();
1766
1767 match err.raw_os_error() {
1768 Some(libc::ERANGE) => {
1769 let current_len = outbuf.len();
1771 outbuf.resize(current_len + RCTL_DEFAULT_BUFSIZE, 0);
1772 continue;
1773 }
1774 Some(libc::EPERM) => {
1775 let state = State::check();
1776 break match state.is_enabled() {
1777 true => Err(Error::OsError(err)),
1778 false => Err(Error::InvalidKernelState(State::check())),
1779 };
1780 }
1781 Some(libc::ENOSYS) => break Err(Error::InvalidKernelState(State::check())),
1782 Some(libc::ESRCH) => break Ok("".into()),
1783 _ => break Err(Error::OsError(err)),
1784 }
1785 }
1786
1787 break Ok(
1790 unsafe { CStr::from_ptr(outbuf.as_ptr() as *mut libc::c_char) }
1791 .to_string_lossy()
1792 .into(),
1793 );
1794 }
1795}
1796
1797#[cfg(test)]
1798pub mod tests {
1799 use super::*;
1800 use std::collections::HashSet;
1801
1802 #[test]
1803 fn parse_subject_type() {
1804 assert_eq!(
1805 "user"
1806 .parse::<SubjectType>()
1807 .expect("could not parse user subject type"),
1808 SubjectType::User,
1809 );
1810
1811 assert_eq!(
1812 "jail"
1813 .parse::<SubjectType>()
1814 .expect("could not parse jail subject type"),
1815 SubjectType::Jail,
1816 );
1817
1818 assert!("bogus".parse::<SubjectType>().is_err());
1819 }
1820
1821 #[test]
1822 fn parse_subject() {
1823 assert_eq!(
1824 "user:42"
1825 .parse::<Subject>()
1826 .expect("Could not parse 'user:42' as Subject"),
1827 Subject::user_id(42)
1828 );
1829
1830 assert_eq!(
1831 "user:nobody"
1832 .parse::<Subject>()
1833 .expect("Could not parse 'user:nobody' as Subject"),
1834 Subject::user_name("nobody").expect("no user 'nobody'")
1835 );
1836
1837 assert_eq!(
1838 "process:42"
1839 .parse::<Subject>()
1840 .expect("Could not parse 'process:42' as Subject"),
1841 Subject::process_id(42)
1842 );
1843
1844 assert_eq!(
1845 "jail:www"
1846 .parse::<Subject>()
1847 .expect("Could not parse 'jail:www' as Subject"),
1848 Subject::jail_name("www")
1849 );
1850
1851 assert_eq!(
1852 "loginclass:test"
1853 .parse::<Subject>()
1854 .expect("Could not parse 'loginclass:test' as Subject"),
1855 Subject::login_class("test")
1856 );
1857
1858 assert!("".parse::<Subject>().is_err());
1859 assert!(":".parse::<Subject>().is_err());
1860 assert!(":1234".parse::<Subject>().is_err());
1861 assert!("bogus".parse::<Subject>().is_err());
1862 assert!("user".parse::<Subject>().is_err());
1863 assert!("process:bogus".parse::<Subject>().is_err());
1864 assert!("process:".parse::<Subject>().is_err());
1865 assert!("user:test:bogus".parse::<Subject>().is_err());
1866 }
1867
1868 #[test]
1869 fn parse_resource() {
1870 assert_eq!(
1871 "vmemoryuse"
1872 .parse::<Resource>()
1873 .expect("could not parse vmemoryuse resource"),
1874 Resource::VMemoryUse,
1875 );
1876
1877 assert!("bogus".parse::<Resource>().is_err());
1878 }
1879
1880 #[test]
1881 fn parse_action() {
1882 assert_eq!(
1883 "deny"
1884 .parse::<Action>()
1885 .expect("could not parse deny action"),
1886 Action::Deny
1887 );
1888
1889 assert_eq!(
1890 "log".parse::<Action>().expect("could not parse log action"),
1891 Action::Log
1892 );
1893
1894 assert_eq!(
1895 "throttle"
1896 .parse::<Action>()
1897 .expect("could not parse throttle action"),
1898 Action::Throttle
1899 );
1900
1901 assert_eq!(
1902 "devctl"
1903 .parse::<Action>()
1904 .expect("could not parse devctl action"),
1905 Action::DevCtl
1906 );
1907
1908 assert_eq!(
1909 "sigterm"
1910 .parse::<Action>()
1911 .expect("could not parse sigterm action"),
1912 Action::Signal(Signal::SIGTERM)
1913 );
1914
1915 assert!("bogus".parse::<Action>().is_err());
1916 }
1917
1918 #[test]
1919 fn display_limit() {
1920 assert_eq!(
1921 Limit {
1922 amount: 100 * 1024 * 1024,
1923 per: None
1924 }
1925 .to_string(),
1926 "100m".to_string()
1927 );
1928
1929 assert_eq!(
1930 Limit {
1931 amount: 100 * 1024 * 1024,
1932 per: Some(SubjectType::User)
1933 }
1934 .to_string(),
1935 "100m/user".to_string()
1936 );
1937
1938 assert_eq!(
1939 Limit {
1940 amount: 42,
1941 per: Some(SubjectType::LoginClass)
1942 }
1943 .to_string(),
1944 "42/loginclass".to_string()
1945 );
1946 }
1947
1948 #[test]
1949 fn parse_limit() {
1950 assert_eq!(
1951 "100m"
1952 .parse::<Limit>()
1953 .expect("Could not parse '100m' as Limit"),
1954 Limit::amount(100 * 1024 * 1024),
1955 );
1956
1957 assert_eq!(
1958 "100m/user"
1959 .parse::<Limit>()
1960 .expect("Could not parse '100m/user' as Limit"),
1961 Limit::amount_per(100 * 1024 * 1024, SubjectType::User),
1962 );
1963
1964 assert!("100m/bogus".parse::<Limit>().is_err());
1965 assert!("100m/userbogus".parse::<Limit>().is_err());
1966 assert!("100q".parse::<Limit>().is_err());
1967 assert!("-42".parse::<Limit>().is_err());
1968 assert!("".parse::<Limit>().is_err());
1969 assert!("bogus".parse::<Limit>().is_err());
1970 }
1971
1972 #[test]
1973 fn parse_rule() {
1974 assert_eq!(
1975 "user:nobody:vmemoryuse:deny=1g"
1976 .parse::<Rule>()
1977 .expect("Could not parse 'user:nobody:vmemoryuse:deny=1g' as Rule"),
1978 Rule {
1979 subject: Subject::user_name("nobody").expect("no user 'nobody'"),
1980 resource: Resource::VMemoryUse,
1981 action: Action::Deny,
1982 limit: Limit::amount(1 << 30),
1983 }
1984 );
1985
1986 assert!(":::=/".parse::<Rule>().is_err());
1987 assert!("user:missing_resource:=100m/user".parse::<Rule>().is_err());
1988 assert!("user:missing_resource=100m/user".parse::<Rule>().is_err());
1989 assert!(
1990 "user:too:many:colons:vmemoryuse:deny=100m/user"
1991 .parse::<Rule>()
1992 .is_err()
1993 );
1994 assert!(
1995 "loginclass:nolimit:vmemoryuse:deny="
1996 .parse::<Rule>()
1997 .is_err()
1998 );
1999 assert!(
2000 "loginclass:nolimit:vmemoryuse:deny"
2001 .parse::<Rule>()
2002 .is_err()
2003 );
2004 assert!(
2005 "loginclass:equals:vmemoryuse:deny=123=456"
2006 .parse::<Rule>()
2007 .is_err()
2008 );
2009 assert!("-42".parse::<Rule>().is_err());
2010 assert!("".parse::<Rule>().is_err());
2011 assert!("bogus".parse::<Rule>().is_err());
2012 }
2013
2014 #[test]
2015 fn display_filter() {
2016 assert_eq!(Filter::new().to_string(), ":".to_string());
2017
2018 assert_eq!(
2019 Filter::new()
2020 .subject_type(&SubjectType::LoginClass)
2021 .to_string(),
2022 "loginclass:".to_string()
2023 );
2024
2025 assert_eq!(
2026 Filter::new().subject(&Subject::user_id(42)).to_string(),
2027 "user:42".to_string()
2028 );
2029
2030 assert_eq!(
2031 Filter::new().resource(&Resource::MaxProcesses).to_string(),
2032 "::maxproc".to_string()
2033 );
2034
2035 assert_eq!(
2036 Filter::new()
2037 .subject(&Subject::user_id(42))
2038 .resource(&Resource::MemoryUse)
2039 .to_string(),
2040 "user:42:memoryuse".to_string()
2041 );
2042
2043 assert_eq!(Filter::new().deny().to_string(), ":::deny".to_string());
2044 }
2045
2046 #[test]
2047 fn iterate_rules() {
2048 if !State::check().is_enabled() {
2049 return;
2050 }
2051
2052 let common_subject = Subject::jail_name("testjail_rctl_rules");
2053 let rule1 = Rule {
2054 subject: common_subject.clone(),
2055 resource: Resource::VMemoryUse,
2056 action: Action::Log,
2057 limit: Limit::amount(100 * 1024 * 1024),
2058 };
2059 rule1.apply().expect("Could not apply rule 1");
2060
2061 let rule2 = Rule {
2062 subject: common_subject.clone(),
2063 resource: Resource::StackSize,
2064 action: Action::Log,
2065 limit: Limit::amount(100 * 1024 * 1024),
2066 };
2067 rule2.apply().expect("Could not apply rule 2");
2068
2069 let filter = Filter::new().subject(&common_subject);
2070
2071 let rules: HashSet<_> = filter
2072 .rules()
2073 .expect("Could not get rules matching filter")
2074 .into_iter()
2075 .collect();
2076
2077 assert!(rules.contains(&rule1));
2078 assert!(rules.contains(&rule2));
2079
2080 filter.remove_rules().expect("Could not remove rules");
2081 }
2082
2083 #[cfg(feature = "serialize")]
2084 #[test]
2085 fn serialize_rule() {
2086 let rule = "process:23:vmemoryuse:sigterm=100m"
2087 .parse::<Rule>()
2088 .expect("Could not parse rule");
2089
2090 let serialized = serde_json::to_string(&rule).expect("Could not serialize rule");
2091
2092 let rule_map: serde_json::Value =
2093 serde_json::from_str(&serialized).expect("Could not load serialized rule");
2094
2095 assert_eq!(rule_map["subject"]["Process"], 23);
2096 assert_eq!(rule_map["resource"], "VMemoryUse");
2097 assert_eq!(rule_map["action"]["Signal"], "SIGTERM");
2098 assert_eq!(rule_map["limit"]["amount"], 100 * 1024 * 1024)
2099 }
2100}