rctl/
lib.rs

1// Copyright 2019 Fabian Freyer <fabian.freyer@physik.tu-berlin.de>
2// Copyright 2018 David O'Rourke <david.orourke@gmail.com>
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice,
8//    this list of conditions and the following disclaimer.
9//
10// 2. Redistributions in binary form must reproduce the above copyright notice,
11//    this list of conditions and the following disclaimer in the documentation
12//    and/or other materials provided with the distribution.
13//
14// 3. Neither the name of the copyright holder nor the names of its contributors
15//    may be used to endorse or promote products derived from this software
16//    without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28// POSSIBILITY OF SUCH DAMAGE.
29
30// Note:
31// The rctl_api_wrapper function, the usage() function as well as the state
32// check are based on code from phyber/jail_exporter:
33// https://github.com/phyber/jail_exporter/blob/6498d628143399fc365fad4217ad85db12348e65/src/rctl/mod.rs
34
35//! Resource limits and accounting with `RCTL` and `RACCT`
36//!
37//! Large parts of this documentation are adapted from the [`rctl(8)`] manpage.
38//!
39//! [`rctl(8)`]: https://www.freebsd.org/cgi/man.cgi?query=rctl&sektion=8&manpath=FreeBSD+11.2-stable
40
41pub 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
55// Set to the same value as found in rctl.c in FreeBSD 11.1
56const 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
109/// Helper module containing enums representing [Subjects](Subject)
110mod subject {
111    use super::ParseError;
112    use nix::unistd::{self, Uid};
113    use std::fmt;
114
115    /// Represents a user subject
116    #[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                // Note: the only documented error that getpwnam_r may return is
127                // ERANGE, and Nix is supposed to handle that one, so it should
128                // "never" return Err
129                .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    /// Represents a process subject
164    #[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    /// Represents a jail subject
181    #[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    /// Represents a login class subject
198    #[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/// A struct representing an RCTL subject.
247///
248/// From [`rctl(8)`]:
249/// > Subject defines the kind of entity the rule applies to.  It can be either
250/// > process, user, login class, or jail.
251/// >
252/// > Subject ID identifies the subject. It can be user name, numerical user ID
253/// > login class name, or jail name.
254///
255/// [`rctl(8)`]: https://www.freebsd.org/cgi/man.cgi?query=rctl&sektion=8&manpath=FreeBSD+11.2-stable
256#[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    /// Get the resource usage for a specific [Subject].
287    ///
288    /// # Example
289    ///
290    /// ```
291    /// # extern crate rctl;
292    /// # use rctl;
293    /// # if !rctl::State::check().is_enabled() {
294    /// #     return;
295    /// # }
296    /// extern crate libc;
297    ///
298    /// let uid = unsafe { libc::getuid() };
299    /// let subject = rctl::Subject::user_id(uid);
300    ///
301    /// let usage = subject.usage()
302    ///     .expect("Could not get RCTL usage");
303    ///
304    /// println!("{:#?}", usage);
305    /// ```
306    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    /// Get an IntoIterator over the rules that apply to this subject.
336    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/// The type of a [Subject].
411#[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/// An Enum representing a resource type
476#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
477#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
478pub enum Resource {
479    /// CPU time, in seconds
480    CpuTime,
481
482    /// datasize, in bytes
483    DataSize,
484
485    /// stack size, in bytes
486    ///
487    /// from the [FreeBSD Handbook Chapter 13.13 - Resource Limits]:
488    /// > The maximum size of a process stack. This alone is not sufficient to
489    /// > limit the amount of memory a program may use, so it should be used in
490    /// > conjunction with other limits.
491    ///
492    /// [FreeBSD Handbook Chapter 13.13 - Resource Limits]:
493    ///   https://www.freebsd.org/doc/handbook/security-resourcelimits.html#resource-limits
494    StackSize,
495
496    /// coredump size, in bytes
497    ///
498    /// from the [FreeBSD Handbook Chapter 13.13 - Resource Limits]:
499    /// > The limit on the size of a core file generated by a program is
500    /// > subordinate to other limits on disk usage, such as filesize or disk
501    /// > quotas. This limit is often used as a less severe method of
502    /// > controlling disk space consumption. Since users do not generate core
503    /// > files and often do not delete them, this setting may save them from
504    /// > running out of disk space should a large program crash.
505    ///
506    /// [FreeBSD Handbook Chapter 13.13 - Resource Limits]:
507    ///   https://www.freebsd.org/doc/handbook/security-resourcelimits.html#resource-limits
508    CoreDumpSize,
509
510    /// resident setsize, in bytes
511    MemoryUse,
512
513    /// locked memory, in bytes
514    ///
515    /// from the [FreeBSD Handbook Chapter 13.13 - Resource Limits]:
516    /// > The maximum amount of memory a process may request to be locked into
517    /// > main memory using [`mlock(2)`]. Some system-critical programs, such as
518    /// > [`amd(8)`], lock into main memory so that if the system begins to
519    /// > swap, they do not contribute to disk thrashing.
520    ///
521    /// [`mlock(2)`]:
522    ///   https://www.freebsd.org/cgi/man.cgi?query=mlock&sektion=2&manpath=freebsd-release-ports
523    /// [`amd(8)`]:
524    ///   https://www.freebsd.org/cgi/man.cgi?query=amd&sektion=8&manpath=freebsd-release-ports
525    /// [FreeBSD Handbook Chapter 13.13 - Resource Limits]:
526    ///   https://www.freebsd.org/doc/handbook/security-resourcelimits.html#resource-limits
527    MemoryLocked,
528
529    /// number of processes
530    MaxProcesses,
531
532    /// File descriptor table size
533    OpenFiles,
534
535    /// address space limit, in bytes
536    VMemoryUse,
537
538    /// number of PTYs
539    PseudoTerminals,
540
541    /// swapspace that may be reserved or used, in bytes
542    SwapUse,
543
544    /// number of threads
545    NThreads,
546
547    /// number of queued SysV messages
548    MsgqQueued,
549
550    /// SysVmessagequeue size, in bytes
551    MsgqSize,
552
553    /// number of SysV message queues
554    NMsgq,
555
556    /// number of SysV semaphores
557    Nsem,
558
559    /// number of SysV semaphores modified in a single `semop(2)` call
560    NSemop,
561
562    /// number of SysV shared memorysegments
563    NShm,
564
565    /// SysVshared memory size, in bytes
566    ShmSize,
567
568    /// wallclock time, in seconds
569    Wallclock,
570
571    /// %CPU, in percents of a single CPU core
572    PercentCpu,
573
574    /// filesystem reads, in bytes per second
575    ReadBps,
576
577    /// filesystem writes, in bytes per second
578    WriteBps,
579
580    /// filesystem reads, inoperations per second
581    ReadIops,
582
583    /// filesystem writes, in operations persecond
584    WriteIops,
585}
586
587impl Resource {
588    /// Return the string representation of the resource
589    ///
590    /// # Examples
591    /// ```
592    /// use rctl::Resource;
593    /// assert_eq!(Resource::CpuTime.as_str(), "cputime");
594    /// ```
595    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/// Represents the action to be taken when a [Subject] offends against a Rule.
680#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
681#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
682pub enum Action {
683    /// Deny the resource allocation
684    ///
685    /// Not supported for the following [Resources]: [`CpuTime`], [`Wallclock`],
686    /// [`ReadBps`], [`WriteBps`], [`ReadIops`], [`WriteIops`]
687    ///
688    /// [`CpuTime`]: Resource::CpuTime
689    /// [`WallClock`]: Resource::Wallclock
690    /// [`ReadBps`]: Resource::ReadBps
691    /// [`WriteBps`]: Resource::WriteBps
692    /// [`ReadIops`]: Resource::ReadIops
693    /// [`WriteIops`]: Resource::WriteIops
694    /// [Resources]: Resource
695    Deny,
696
697    /// Log a warning to the console
698    Log,
699
700    /// Send a notification to [`devd(8)`] using `system = "RCTL"`,
701    /// `subsystem = "rule"`, `type = "matched"`
702    ///
703    /// [`devd(8)`]: https://www.freebsd.org/cgi/man.cgi?query=devd&sektion=8&manpath=FreeBSD+11.2-stable
704    DevCtl,
705
706    /// Send a [signal] to the offending process.
707    ///
708    /// # Example
709    /// ```
710    /// # extern crate rctl;
711    /// use rctl::Signal;
712    /// use rctl::Action;
713    ///
714    /// let action = Action::Signal(Signal::SIGTERM);
715    /// ```
716    ///
717    /// [signal]: Signal
718    #[cfg_attr(feature = "serialize", serde(serialize_with = "signal_serialize"))]
719    Signal(Signal),
720
721    /// Slow down process execution
722    ///
723    /// Only supported for the following [Resources]:
724    /// [`ReadBps`], [`WriteBps`], [`ReadIops`], [`WriteIops`]
725    ///
726    /// [`ReadBps`]: Resource::ReadBps
727    /// [`WriteBps`]: Resource::WriteBps
728    /// [`ReadIops`]: Resource::ReadIops
729    /// [`WriteIops`]: Resource::WriteIops
730    /// [Resources]: Resource
731    Throttle,
732}
733
734impl Action {
735    /// Return the string representation of the Action according to [`rctl(8)`]
736    ///
737    /// # Examples
738    /// ```
739    /// use rctl::Action;
740    /// assert_eq!(Action::Deny.as_str(), "deny");
741    /// ```
742    ///
743    /// Signals are handled by `rctl::Signal`:
744    /// ```
745    /// # extern crate rctl;
746    /// # use rctl::Action;
747    /// use rctl::Signal;
748    /// assert_eq!(Action::Signal(Signal::SIGKILL).as_str(), "sigkill");
749    /// ```
750    ///
751    /// [`rctl(8)`]: https://www.freebsd.org/cgi/man.cgi?query=rctl&sektion=8&manpath=FreeBSD+11.2-stable
752    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/// Defines how much of a [Resource] a process can use beofore the defined
869/// [Action] triggers.
870#[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    /// Construct a limit representing the amount used before an [Action]
879    /// triggers.
880    ///
881    /// The entity the amount gets accounted for defaults to the type of the
882    /// [Subject] of the respective [Rule].
883    pub fn amount(amount: usize) -> Limit {
884        Limit { amount, per: None }
885    }
886
887    /// Limit the amount per [SubjectType].
888    ///
889    /// This defines what entity the amount gets accounted for.
890    ///
891    /// # Examples
892    ///
893    /// For example the following [Rule] means that each process of any user
894    /// belonging to the login class "users" may allocate up to 100 MiB of
895    /// virtual memory:
896    /// ```rust
897    /// # use rctl::{Subject, SubjectType, Resource, Action, Limit, Rule};
898    /// Rule {
899    ///     subject: Subject::login_class("users"),
900    ///     resource: Resource::VMemoryUse,
901    ///     action: Action::Deny,
902    ///     limit: Limit::amount_per(100*1024*1024, SubjectType::Process),
903    /// }
904    /// # ;
905    /// ```
906    ///
907    /// Setting `per: Some(SubjectType::User)` on the above [Rule] would mean
908    /// that for each user belonging to the login class "users", the sum of
909    /// virtual memory allocated by all the processes of that user will not
910    /// exceed 100 MiB.
911    ///
912    /// Setting `per: Some(SubjectType::LoginClass)` on the above [Rule] would
913    /// mean that the sum of virtual memory allocated by all processes of all
914    /// users belonging to that login class will not exceed 100 MiB.
915    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/// A rule represents an [Action] to be taken when a particular [Subject] hits
1007/// a [Limit] for a [Resource].
1008///
1009/// Syntax for the string representation of a rule is
1010/// `subject:subject-id:resource:action=amount/per`.
1011///
1012/// # Examples
1013///
1014/// ```rust
1015/// use rctl::{Subject, SubjectType, Resource, Action, Limit, Rule};
1016/// let rule = Rule {
1017///     subject: Subject::user_name("nobody").expect("no user 'nobody'"),
1018///     resource: Resource::VMemoryUse,
1019///     action: Action::Deny,
1020///     limit: Limit::amount(1024*1024*1024),
1021/// };
1022///
1023/// assert_eq!(rule.to_string(), "user:nobody:vmemoryuse:deny=1g".to_string());
1024/// ```
1025#[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    /// Add this rule to the resource limits database.
1036    ///
1037    /// # Example
1038    ///
1039    /// ```
1040    /// # extern crate rctl;
1041    /// # if !rctl::State::check().is_enabled() {
1042    /// #     return;
1043    /// # }
1044    /// use rctl::{Rule, Subject, Resource, Action, Limit};
1045    ///
1046    /// let rule = Rule {
1047    ///     subject: Subject::jail_name("testjail_rctl_rule_apply_method"),
1048    ///     resource: Resource::VMemoryUse,
1049    ///     action: Action::Log,
1050    ///     limit: Limit::amount(100*1024*1024),
1051    /// };
1052    ///
1053    /// rule.apply();
1054    /// # rule.remove();
1055    /// ```
1056    pub fn apply(&self) -> Result<(), Error> {
1057        rctl_api_wrapper(ffi::rctl_add_rule, self)?;
1058
1059        Ok(())
1060    }
1061
1062    /// Attempt to remove this rule from the resource limits database.
1063    ///
1064    /// # Example
1065    ///
1066    /// ```
1067    /// # extern crate rctl;
1068    /// # if !rctl::State::check().is_enabled() {
1069    /// #     return;
1070    /// # }
1071    /// use rctl::{Rule, Subject, Resource, Action, Limit};
1072    ///
1073    /// let rule = Rule {
1074    ///     subject: Subject::jail_name("testjail_rctl_rule_remove_method"),
1075    ///     resource: Resource::VMemoryUse,
1076    ///     action: Action::Log,
1077    ///     limit: Limit::amount(100*1024*1024),
1078    /// };
1079    ///
1080    /// # rule.apply();
1081    /// rule.remove();
1082    /// ```
1083    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        // subject:subject-id:resource:action=amount/per
1114        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/// Adapter over objects parseable into a [Rule]
1142#[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/// Owning struct implementing IntoIterator, returning a [RuleParserAdapter].
1162#[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/// A filter can match a set of [Rules](Rule).
1179#[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    /// Return the filter that matches all rules
1193    ///
1194    /// # Example
1195    ///
1196    /// ```
1197    /// # use rctl::Filter;
1198    /// let filter = Filter::new();
1199    /// assert_eq!(filter.to_string(), ":".to_string());
1200    /// ```
1201    pub fn new() -> Filter {
1202        Filter::default()
1203    }
1204
1205    /// Constrain the filter to a specific [SubjectType]
1206    ///
1207    /// If the filter is already constrained to a subject, this is a No-Op.
1208    ///
1209    /// # Example
1210    ///
1211    /// ```
1212    /// # use rctl::{Filter, SubjectType};
1213    /// let filter = Filter::new()
1214    ///     .subject_type(&SubjectType::LoginClass);
1215    /// assert_eq!(filter.to_string(), "loginclass:".to_string());
1216    /// ```
1217    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    /// Constrain the filter to a specific [Subject]
1225    ///
1226    /// Resets the subject type.
1227    ///
1228    /// # Example
1229    ///
1230    /// ```
1231    /// # use rctl::{Filter, Subject};
1232    /// let filter = Filter::new()
1233    ///     .subject(&Subject::user_name("nobody").expect("no user 'nobody'"));
1234    /// assert_eq!(filter.to_string(), "user:nobody".to_string());
1235    /// ```
1236    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    /// Constrain the filter to a specific [Resource]
1243    ///
1244    /// # Example
1245    ///
1246    /// ```
1247    /// # use rctl::{Filter, Resource};
1248    /// let filter = Filter::new()
1249    ///     .resource(&Resource::MemoryLocked);
1250    /// assert_eq!(filter.to_string(), "::memorylocked".to_string());
1251    /// ```
1252    pub fn resource(mut self: Filter, resource: &Resource) -> Filter {
1253        self.resource = Some(*resource);
1254        self
1255    }
1256
1257    /// Constrain the filter to a specific [Action]
1258    ///
1259    /// # Example
1260    ///
1261    /// ```
1262    /// # use rctl::{Filter, Action};
1263    /// let filter = Filter::new()
1264    ///     .action(&Action::Deny);
1265    /// assert_eq!(filter.to_string(), ":::deny".to_string());
1266    /// ```
1267    pub fn action(mut self: Filter, action: &Action) -> Filter {
1268        self.action = Some(*action);
1269        self
1270    }
1271
1272    /// Constrain the filter to the [Deny](Action::Deny) [Action]
1273    ///
1274    /// # Example
1275    ///
1276    /// ```
1277    /// # use rctl::{Filter, Action};
1278    /// let filter = Filter::new()
1279    ///     .deny();
1280    /// assert_eq!(filter, Filter::new().action(&Action::Deny));
1281    /// ```
1282    pub fn deny(self: Filter) -> Filter {
1283        self.action(&Action::Deny)
1284    }
1285
1286    /// Constrain the filter to the [Log](Action::Log) [Action]
1287    ///
1288    /// # Example
1289    ///
1290    /// ```
1291    /// # use rctl::{Filter, Action};
1292    /// let filter = Filter::new()
1293    ///     .log();
1294    /// assert_eq!(filter, Filter::new().action(&Action::Log));
1295    /// ```
1296    pub fn log(self: Filter) -> Filter {
1297        self.action(&Action::Log)
1298    }
1299
1300    /// Constrain the filter to the [DevCtl](Action::DevCtl) [Action]
1301    ///
1302    /// # Example
1303    ///
1304    /// ```
1305    /// # use rctl::{Filter, Action};
1306    /// let filter = Filter::new()
1307    ///     .devctl();
1308    /// assert_eq!(filter, Filter::new().action(&Action::DevCtl));
1309    /// ```
1310    pub fn devctl(self: Filter) -> Filter {
1311        self.action(&Action::DevCtl)
1312    }
1313
1314    /// Constrain the filter to the [Signal](Action::Signal) [Action]
1315    ///
1316    /// # Example
1317    ///
1318    /// ```
1319    /// # extern crate rctl;
1320    /// # use rctl::{Filter, Action, Signal};
1321    /// let filter = Filter::new()
1322    ///     .signal(Signal::SIGTERM);
1323    /// assert_eq!(filter.to_string(), ":::sigterm".to_string());
1324    /// ```
1325    pub fn signal(self: Filter, signal: Signal) -> Filter {
1326        self.action(&Action::Signal(signal))
1327    }
1328
1329    /// Constrain the filter to a particular [Limit]
1330    ///
1331    /// Resets any limit_per, if the given limit has a `per` set.
1332    ///
1333    /// # Examples
1334    ///
1335    /// ```
1336    /// # use rctl::{Filter, Limit};
1337    /// let filter = Filter::new()
1338    ///     .limit(&Limit::amount(100*1024*1024));
1339    /// assert_eq!(filter.to_string(), ":::=100m".to_string());
1340    /// ```
1341    ///
1342    /// ```
1343    /// # use rctl::{Filter, Limit, SubjectType};
1344    /// let filter = Filter::new()
1345    ///     .limit(&Limit::amount_per(100*1024*1024, SubjectType::Process));
1346    /// assert_eq!(filter.to_string(), ":::=100m/process".to_string());
1347    /// ```
1348    pub fn limit(mut self: Filter, limit: &Limit) -> Filter {
1349        let mut limit = limit.clone();
1350
1351        // if the limit given doesn't have a `per` set, but we do, move it
1352        // into the limit.
1353        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    /// Return an [IntoIterator] over all [Rules] matching this [Filter].
1370    ///
1371    /// # Example
1372    ///
1373    /// List all rules:
1374    ///
1375    /// ```
1376    /// use rctl;
1377    ///
1378    /// let filter = rctl::Filter::new();
1379    /// for rule in filter.rules() {
1380    ///     println!("{:?}", rule);
1381    /// }
1382    /// ```
1383    ///
1384    /// [Rules]: Rule
1385    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    /// Remove all matching [Rules] from the resource limits database.
1392    ///
1393    /// # Examples
1394    ///
1395    /// ```
1396    /// # extern crate rctl;
1397    /// # if !rctl::State::check().is_enabled() {
1398    /// #     return;
1399    /// # }
1400    /// # use rctl::{Rule, Resource, Action, Limit};
1401    /// use rctl::{Filter, Subject};
1402    /// # let rule = Rule {
1403    /// #     subject: Subject::jail_name("testjail_rctl_filter_remove"),
1404    /// #     resource: Resource::VMemoryUse,
1405    /// #     action: Action::Log,
1406    /// #     limit: Limit::amount(100*1024*1024),
1407    /// # };
1408    /// # rule.apply();
1409    /// # let rule = Rule {
1410    /// #     subject: Subject::jail_name("testjail_rctl_filter_remove"),
1411    /// #     resource: Resource::StackSize,
1412    /// #     action: Action::Log,
1413    /// #     limit: Limit::amount(100*1024*1024),
1414    /// # };
1415    /// # rule.apply();
1416    /// let filter = Filter::new()
1417    ///     .subject(&Subject::jail_name("testjail_rctl_filter_remove"))
1418    ///     .remove_rules()
1419    ///     .expect("Could not remove rules");
1420    /// ```
1421    ///
1422    /// Remove all rules, clearing the resource limits database by using the
1423    /// default (`':'`) [Filter]:
1424    /// ```no_run
1425    /// # extern crate rctl;
1426    /// # use rctl::{Rule, Subject, Resource, Action, Limit};
1427    /// use rctl;
1428    ///
1429    /// rctl::Filter::new().remove_rules().expect("Could not remove all rules");
1430    /// ```
1431    ///
1432    /// [Rules]: Rule
1433    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 Resource, Action, and Limit are unset, leave it at this.
1462        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 action, and limit are unset, leave it at this.
1479        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 limit is unset, leave it at this
1495        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/// Enum representing the state of `RACCT`/`RCTL` in the kernel.
1617#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
1618pub enum State {
1619    /// `RCTL` / `RACCT` is present in the kernel, but is not enabled via the
1620    /// `kern.racct.enable` tunable.
1621    Disabled,
1622
1623    /// `RCTL` / `RACCT` is enabled.
1624    Enabled,
1625
1626    /// `RCTL` / `RACCT` is disabled.
1627    ///
1628    /// The kernel does not support `RCTL` / `RACCT`. The following options have
1629    /// to be set in the kernel configuration when compiling the kernel to
1630    /// add support for `RCTL` / `RACCT`:
1631    ///
1632    /// ```ignore
1633    /// options         RACCT
1634    /// options         RCTL
1635    /// ```
1636    NotPresent,
1637
1638    /// `RCTL` is not available within a Jail
1639    Jailed,
1640}
1641
1642impl State {
1643    /// Check the state of the `RCTL` / `RACCT` support.
1644    ///
1645    /// This queries the `kern.racct.enable` sysctl. If this fails in any way,
1646    /// (most probably by the sysctl not being present), the kernel is assumed
1647    /// to be compiled without the `RCTL` / `RACCT` options.
1648    ///
1649    /// # Example
1650    ///
1651    /// ```
1652    /// # use rctl;
1653    /// let state = rctl::State::check();
1654    /// ```
1655    pub fn check() -> State {
1656        // RCTL is not available in a jail
1657        let jailed = sysctl::Ctl::new("security.jail.jailed");
1658
1659        // If the sysctl call fails (unlikely), we assume we're in a Jail.
1660        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        // Check the kern.racct.enable sysctl.
1670        let enable_racct = sysctl::Ctl::new("kern.racct.enable");
1671
1672        // If the sysctl call fails, we assume it to be disabled.
1673        if enable_racct.is_err() {
1674            return State::NotPresent;
1675        }
1676
1677        match enable_racct.unwrap().value() {
1678            Ok(value) => match value {
1679                // FreeBSD 13 returns a U8 as the kernel variable is a bool.
1680                sysctl::CtlValue::U8(1) => State::Enabled,
1681
1682                // FreeBSD older than 13 returns a Uint as the kernel variable
1683                // is an int.
1684                sysctl::CtlValue::Uint(1) => State::Enabled,
1685
1686                // Other values we assume means RACCT is disabled.
1687                _ => State::Disabled,
1688            },
1689
1690            // If we could not get the sysctl value, assume it to be disabled.
1691            _ => State::NotPresent,
1692        }
1693    }
1694
1695    /// Return `true` if the `RCTL` / `RACCT` support is [Enabled].
1696    ///
1697    /// # Examples
1698    ///
1699    /// ```
1700    /// # use rctl;
1701    /// if rctl::State::check().is_enabled() {
1702    ///     // do things requiring `RCTL` / `RACCT` support.
1703    /// }
1704    /// ```
1705    ///
1706    /// [Enabled]: State::Enabled
1707    pub fn is_enabled(&self) -> bool {
1708        matches!(self, State::Enabled)
1709    }
1710
1711    /// Return `true` if the kernel has `RCTL` / `RACCT` support compiled in.
1712    ///
1713    /// # Examples
1714    ///
1715    /// ```
1716    /// # use rctl;
1717    /// if ! rctl::State::check().is_present() {
1718    ///     println!("The kernel does not have RCTL / RACCT support");
1719    /// }
1720    /// ```
1721    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    // Get the input buffer as a C string.
1747    let input: String = input.into();
1748    let inputlen = input.len() + 1;
1749    let inbuf = CString::new(input).map_err(Error::CStringError)?;
1750
1751    // C compatible output buffer.
1752    let mut outbuf: Vec<i8> = vec![0; RCTL_DEFAULT_BUFSIZE];
1753
1754    loop {
1755        // Unsafe C call to get the jail resource usage.
1756        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                    // if the error code is ERANGE, retry with a larger buffer
1770                    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        // If everything went well, convert the return C string in the outbuf
1788        // back into an easily usable Rust string and return.
1789        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}