#![forbid(unsafe_code)]
use std::str::FromStr;
use btoi::{btoi, btoi_radix};
use dur::Duration;
use libc::{gid_t, uid_t};
use memchr::arch::all::is_prefix;
use nix::{
errno::Errno,
sys::stat::SFlag,
unistd::{Gid, Uid},
};
use nom::{
branch::alt,
bytes::complete::{escaped_transform, is_not, tag, tag_no_case, take_while1},
character::complete::{char, digit1, one_of},
combinator::{all_consuming, map, opt, recognize, value},
error::{Error, ErrorKind},
multi::separated_list1,
sequence::preceded,
Finish, IResult, Parser,
};
use crate::{
confine::SydMountAttrFlags,
hash::SydHashSet,
landlock::{AccessFs, AccessNet},
landlock_policy::{LandlockPolicy, LANDLOCK_ACCESS_FS, LANDLOCK_ACCESS_NET},
mount::api::MountAttrFlags,
path::XPathBuf,
port::{parse_port_set, PortSet},
sandbox::{Action, Capability, SANDBOX_CAPS},
};
const NETLINK_FAMILIES: &[&str] = &[
"all",
"audit",
"connector",
"crypto",
"dnrtmsg",
"ecryptfs",
"fib_lookup",
"firewall",
"generic",
"inet_diag",
"ip6_fw",
"iscsi",
"kobject_uevent",
"netfilter",
"nflog",
"rdma",
"route",
"scsitransport",
"selinux",
"smc",
"sock_diag",
"usersock",
"xfrm",
];
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct BindCmd {
pub(crate) op: char,
pub(crate) src: Option<String>,
pub(crate) dst: Option<String>,
pub(crate) opt: MountAttrFlags,
pub(crate) dat: Option<String>,
pub(crate) r#try: bool,
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct ForceCmd {
pub(crate) op: char,
pub(crate) src: Option<String>,
pub(crate) alg: Option<String>,
pub(crate) key: Option<String>,
pub(crate) act: Option<Action>,
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct SetIdCmd {
pub(crate) id: char,
pub(crate) op: char,
pub(crate) src: Option<String>,
pub(crate) dst: Option<String>,
}
pub type PathSet = SydHashSet<XPathBuf>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LandlockRule {
Fs((AccessFs, String)),
Net((AccessNet, PortSet)),
}
pub type LandlockFilter = Vec<LandlockRule>;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LandlockOp {
Add,
Rem,
}
impl TryFrom<char> for LandlockOp {
type Error = Errno;
fn try_from(c: char) -> Result<Self, Self::Error> {
match c {
'+' => Ok(Self::Add),
'-' | '^' => Ok(Self::Rem),
_ => Err(Errno::EINVAL),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct FsCmd {
pub(crate) action: Action,
pub(crate) op: char,
pub(crate) fs_type: String,
}
#[derive(Debug, PartialEq, Eq)]
pub struct LandlockCmd {
pub filter: LandlockFilter,
pub op: LandlockOp,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ScmpPattern {
Path(String),
Addr(String),
Host(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ScmpCmd {
pub(crate) action: Action,
pub(crate) filter: Capability,
pub(crate) op: char,
pub(crate) pat: ScmpPattern,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum NetlinkOp {
Clear,
Add(Vec<String>),
Del(Vec<String>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct NetlinkCmd {
pub(crate) op: NetlinkOp,
}
impl NetlinkCmd {
pub fn new(op: NetlinkOp) -> Self {
NetlinkCmd { op }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct MaskCmd {
pub(crate) op: char,
pub(crate) pattern: String,
pub(crate) mask_all: Option<String>,
pub(crate) mask_dir: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct MknodCmd {
pub(crate) op: char,
pub(crate) kind: SFlag,
pub(crate) path: Option<String>,
pub(crate) mode: Option<String>,
pub(crate) r#try: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct LinkCmd {
pub(crate) op: char,
pub(crate) dst: Option<String>,
pub(crate) src: Option<String>,
pub(crate) sym: bool,
pub(crate) r#try: bool,
}
fn escaped_field(input: &str) -> IResult<&str, String> {
escaped_transform(
is_not(":\\"),
'\\',
alt((value(":", tag(":")), value("\\", tag("\\")))),
)
.parse(input)
.or_else(|_: nom::Err<Error<&str>>| Ok((input, String::new())))
}
pub(crate) fn parse_mask_cmd(input: &str) -> Result<MaskCmd, Errno> {
fn parse_clear(input: &str) -> IResult<&str, MaskCmd> {
map(char('^'), |op| MaskCmd {
op,
pattern: String::new(),
mask_all: None,
mask_dir: None,
})
.parse(input)
}
fn parse_del(input: &str) -> IResult<&str, MaskCmd> {
let (rem, (op, pat)) = (char('-'), take_while1(|_| true)).parse(input)?;
Ok((
rem,
MaskCmd {
op,
pattern: pat.to_string(),
mask_all: None,
mask_dir: None,
},
))
}
fn parse_add(input: &str) -> IResult<&str, MaskCmd> {
let (rem, op) = char('+').parse(input)?;
let (rem, pattern) = escaped_field(rem)?;
if pattern.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
let (rem, mask_all) = if let Some(after) = rem.strip_prefix(':') {
let (r, f) = escaped_field(after)?;
(r, Some(f))
} else {
(rem, None)
};
let mask_dir = if let Some(after) = rem.strip_prefix(':') {
let (r, f) = escaped_field(after)?;
if !r.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
Some(f)
} else if !rem.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
} else {
None
};
Ok((
"",
MaskCmd {
op,
pattern,
mask_all,
mask_dir,
},
))
}
match all_consuming(alt((parse_clear, parse_del, parse_add)))
.parse(input)
.finish()
{
Ok((_, cmd)) => Ok(cmd),
Err(_) => Err(Errno::EINVAL),
}
}
pub(crate) fn parse_mknod_cmd(input: &str) -> Result<MknodCmd, Errno> {
let (kind, r#try, suffix) = if let Some(s) = input.strip_prefix("mkdir-try") {
(SFlag::S_IFDIR, true, s)
} else if let Some(s) = input.strip_prefix("mkfile-try") {
(SFlag::S_IFREG, true, s)
} else if let Some(s) = input.strip_prefix("mkfifo-try") {
(SFlag::S_IFIFO, true, s)
} else if let Some(s) = input.strip_prefix("mkdir") {
(SFlag::S_IFDIR, false, s)
} else if let Some(s) = input.strip_prefix("mkfile") {
(SFlag::S_IFREG, false, s)
} else if let Some(s) = input.strip_prefix("mkfifo") {
(SFlag::S_IFIFO, false, s)
} else {
return Err(Errno::EINVAL);
};
fn parse_clear(input: &str) -> IResult<&str, MknodCmd> {
map(char('^'), |op| MknodCmd {
op,
kind: SFlag::empty(),
path: None,
mode: None,
r#try: false,
})
.parse(input)
}
fn parse_del(input: &str) -> IResult<&str, MknodCmd> {
let (rem, (op, path)) = (char('-'), take_while1(|_| true)).parse(input)?;
Ok((
rem,
MknodCmd {
op,
kind: SFlag::empty(),
path: Some(path.to_string()),
mode: None,
r#try: false,
},
))
}
fn parse_add(input: &str) -> IResult<&str, MknodCmd> {
let (rem, op) = char('+').parse(input)?;
let (rem, path) = escaped_field(rem)?;
if path.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
let mode = if let Some(after) = rem.strip_prefix(':') {
let (r, f) = escaped_field(after)?;
if !r.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
Some(f)
} else if !rem.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
} else {
None
};
Ok((
"",
MknodCmd {
op,
kind: SFlag::empty(),
path: Some(path),
mode,
r#try: false,
},
))
}
match all_consuming(alt((parse_clear, parse_del, parse_add)))
.parse(suffix)
.finish()
{
Ok((_, mut cmd)) => {
cmd.kind = kind;
cmd.r#try = r#try;
Ok(cmd)
}
Err(_) => Err(Errno::EINVAL),
}
}
pub(crate) fn parse_link_cmd(input: &str) -> Result<LinkCmd, Errno> {
let (sym, r#try, suffix) = if let Some(s) = input.strip_prefix("link-try") {
(false, true, s)
} else if let Some(s) = input.strip_prefix("link") {
(false, false, s)
} else if let Some(s) = input.strip_prefix("symlink-try") {
(true, true, s)
} else if let Some(s) = input.strip_prefix("symlink") {
(true, false, s)
} else {
return Err(Errno::EINVAL);
};
fn parse_clear(input: &str) -> IResult<&str, LinkCmd> {
map(char('^'), |op| LinkCmd {
op,
dst: None,
src: None,
sym: false,
r#try: false,
})
.parse(input)
}
fn parse_del(input: &str) -> IResult<&str, LinkCmd> {
let (rem, (_, dst)) = (char('-'), take_while1(|_| true)).parse(input)?;
Ok((
rem,
LinkCmd {
op: '-',
dst: Some(dst.to_string()),
src: None,
sym: false,
r#try: false,
},
))
}
fn parse_add(input: &str) -> IResult<&str, LinkCmd> {
let (rem, _) = char('+').parse(input)?;
let (rem, dst) = escaped_field(rem)?;
if dst.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
let rem = rem
.strip_prefix(':')
.ok_or_else(|| nom::Err::Error(Error::new(input, ErrorKind::Fail)))?;
let (rem, src) = escaped_field(rem)?;
if src.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
if !rem.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
Ok((
"",
LinkCmd {
op: '+',
dst: Some(dst),
src: Some(src),
sym: false,
r#try: false,
},
))
}
match all_consuming(alt((parse_clear, parse_del, parse_add)))
.parse(suffix)
.finish()
{
Ok((_, mut cmd)) => {
cmd.sym = sym;
cmd.r#try = r#try;
Ok(cmd)
}
Err(_) => Err(Errno::EINVAL),
}
}
pub(crate) fn parse_bind_cmd(command: &str) -> Result<BindCmd, Errno> {
let (r#try, suffix) = if let Some(s) = command.strip_prefix("bind-try") {
(true, s)
} else if let Some(s) = command.strip_prefix("bind") {
(false, s)
} else {
return Err(Errno::EINVAL);
};
fn parse_clear(input: &str) -> IResult<&str, BindCmd> {
map(char('^'), |op| BindCmd {
op,
src: None,
dst: None,
opt: MountAttrFlags::empty(),
dat: None,
r#try: false,
})
.parse(input)
}
fn parse_del(input: &str) -> IResult<&str, BindCmd> {
let (rem, _) = char('-').parse(input)?;
let (rem, src) = escaped_field(rem)?;
if src.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
let rem = rem
.strip_prefix(':')
.ok_or_else(|| nom::Err::Error(Error::new(input, ErrorKind::Fail)))?;
let (rem, dst) = escaped_field(rem)?;
if dst.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
if !rem.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
Ok((
"",
BindCmd {
op: '-',
src: Some(src),
dst: Some(dst),
opt: MountAttrFlags::empty(),
dat: None,
r#try: false,
},
))
}
fn parse_add(input: &str) -> IResult<&str, BindCmd> {
let (rem, _) = char('+').parse(input)?;
let (rem, src) = escaped_field(rem)?;
if src.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
let rem = rem
.strip_prefix(':')
.ok_or_else(|| nom::Err::Error(Error::new(input, ErrorKind::Fail)))?;
let (rem, dst) = escaped_field(rem)?;
if dst.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
let opt_part = if let Some(after) = rem.strip_prefix(':') {
if after.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
Some(after)
} else if !rem.is_empty() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
} else {
None
};
Ok((
"",
BindCmd {
op: '+',
src: Some(src),
dst: Some(dst),
opt: MountAttrFlags::empty(),
dat: opt_part.map(|s| s.to_string()),
r#try: false,
},
))
}
match all_consuming(alt((parse_clear, parse_del, parse_add)))
.parse(suffix)
.finish()
{
Ok((_, mut cmd)) => {
cmd.r#try = r#try;
if let Some(ref opt) = cmd.dat {
let mut flags = MountAttrFlags::empty();
let mut dat = Vec::new();
for flag in opt.split(',') {
if flag
.chars()
.next()
.map(|n| n.is_whitespace())
.unwrap_or(true)
{
return Err(Errno::EINVAL);
}
if flag
.chars()
.last()
.map(|n| n.is_whitespace())
.unwrap_or(true)
{
return Err(Errno::EINVAL);
}
if let Some(syd_flag) = SydMountAttrFlags::from_name(flag) {
flags |= syd_flag.0;
} else {
if !dat.is_empty() {
dat.push(b',');
}
dat.extend_from_slice(flag.as_bytes());
}
}
cmd.opt = flags;
cmd.dat = if dat.is_empty() {
None
} else {
Some(String::from_utf8(dat).or(Err(Errno::EINVAL))?)
};
}
Ok(cmd)
}
Err(_) => Err(Errno::EINVAL),
}
}
pub(crate) fn parse_force_cmd(input: &str) -> Result<ForceCmd, Errno> {
fn parse_clear(input: &str) -> IResult<&str, ForceCmd> {
map(tag("force^"), |_| ForceCmd {
op: '^',
src: None,
alg: None,
key: None,
act: None,
})
.parse(input)
}
fn parse_remove(input: &str) -> IResult<&str, ForceCmd> {
let (rem, (_, src)) = (tag("force-"), take_while1(|_| true)).parse(input)?;
Ok((
rem,
ForceCmd {
op: '-',
src: Some(src.to_string()),
alg: None,
key: None,
act: None,
},
))
}
fn parse_add(input: &str) -> IResult<&str, ForceCmd> {
let (rem, (_tag, src, _colon1, alg, _colon2, key, act)) = (
tag("force+"),
take_while1(|c: char| c != ':'), char(':'), take_while1(|c: char| c != ':'), char(':'), take_while1(|c: char| c != ':'), opt(preceded(char(':'), take_while1(|_| true))), )
.parse(input)?;
let act = if let Some(act) = act {
match Action::from_str(act) {
Ok(act) => Some(act),
Err(_) => {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
}
} else {
None
};
let fc = ForceCmd {
act,
op: '+',
src: Some(src.to_string()),
alg: Some(alg.to_string()),
key: Some(key.to_string()),
};
Ok((rem, fc))
}
match alt((parse_clear, parse_remove, parse_add))
.parse(input)
.finish()
{
Ok(("", cmd)) => Ok(cmd),
_ => Err(Errno::EINVAL),
}
}
pub(crate) fn parse_setid_cmd(input: &str) -> Result<SetIdCmd, Errno> {
fn parse_pm(input: &str) -> IResult<&str, SetIdCmd> {
let (rem, (_, id, _, op, src, _, dst)) = (
tag("set"),
one_of("ug"),
tag("id"),
one_of("+-"),
take_while1(|c| c != ':'),
char(':'),
take_while1(|c| c != ':'),
)
.parse(input)?;
Ok((
rem,
SetIdCmd {
id,
op,
src: Some(src.to_string()),
dst: Some(dst.to_string()),
},
))
}
fn parse_caret(input: &str) -> IResult<&str, SetIdCmd> {
let (rem, (_, id, _, _, src)) = (
tag("set"),
one_of("ug"),
tag("id"),
char('^'),
opt(take_while1(|c| c != ':')),
)
.parse(input)?;
Ok((
rem,
SetIdCmd {
id,
op: '^',
src: src.map(str::to_string),
dst: None,
},
))
}
match alt((parse_pm, parse_caret)).parse(input).finish() {
Ok(("", cmd)) => Ok(cmd),
_ => Err(Errno::EINVAL),
}
}
pub fn parse_landlock_cmd(input: &str) -> Result<LandlockCmd, Errno> {
fn lock_name(input: &str) -> IResult<&str, &str> {
for name in LANDLOCK_ACCESS_FS.keys().rev() {
if let Some(rest) = input.strip_prefix(name) {
if rest.is_empty() || rest.starts_with([',', '+', '-', '^']) {
return Ok((rest, *name));
}
}
}
for name in LANDLOCK_ACCESS_NET.keys().rev() {
if let Some(rest) = input.strip_prefix(name) {
if rest.is_empty() || rest.starts_with([',', '+', '-', '^']) {
return Ok((rest, *name));
}
}
}
Err(nom::Err::Error(Error::new(input, ErrorKind::Tag)))
}
fn inner(input: &str) -> IResult<&str, (&str, char, &str)> {
let (rem, (_, access, op, arg)) = (
tag("allow/lock/"),
recognize(separated_list1(char(','), lock_name)),
one_of("+-^"),
take_while1(|_| true), )
.parse(input)?;
Ok((rem, (access, op, arg)))
}
match inner(input).finish() {
Ok(("", (access, op, arg))) => {
let op = LandlockOp::try_from(op)?;
let (mut access_fs, access_net) = LandlockPolicy::access(access)?;
let ports = parse_port_set(arg).ok();
if access_net.contains(AccessNet::ConnectTcp) {
if ports.is_none() {
return Err(Errno::EINVAL);
}
if access_fs == AccessFs::MakeSock {
access_fs = AccessFs::EMPTY;
} else if !access_fs.is_empty() {
return Err(Errno::EINVAL);
}
}
let mut filter = LandlockFilter::new();
if access_net == AccessNet::BindTcp && access_fs == AccessFs::MakeSock {
let c = arg.chars().next().ok_or(Errno::EINVAL)?;
if matches!(c, '/' | '$') {
filter.push(LandlockRule::Fs((access_fs, arg.into())));
} else {
let ports = ports.ok_or(Errno::EINVAL)?;
filter.push(LandlockRule::Net((access_net, ports)));
}
} else if !access_fs.is_empty() {
filter.push(LandlockRule::Fs((access_fs, arg.into())));
} else if access_net.contains(AccessNet::ConnectTcp) {
let ports = ports.ok_or(Errno::EINVAL)?;
filter.push(LandlockRule::Net((access_net, ports)));
} else {
eprintln!("LO:4");
return Err(Errno::EINVAL);
}
Ok(LandlockCmd { filter, op })
}
_ => Err(Errno::EINVAL),
}
}
pub(crate) fn parse_fs_cmd(input: &str) -> Result<FsCmd, Errno> {
fn inner(input: &str) -> IResult<&str, (&str, char, &str)> {
(
take_while1(|c| c != '/'),
char('/'),
tag("fs"),
one_of("+-^"),
take_while1(|_| true), )
.map(|(act, _slash, _caps, op, fs_type)| (act, op, fs_type))
.parse(input)
}
match inner(input).finish() {
Ok(("", (act, op, fs_type))) => {
let action = Action::from_str(act).map_err(|_| Errno::EINVAL)?;
let fs_type = fs_type.to_string();
Ok(FsCmd {
action,
op,
fs_type,
})
}
_ => Err(Errno::EINVAL),
}
}
pub(crate) fn parse_scmp_cmd(input: &str) -> Result<ScmpCmd, Errno> {
fn cap_name(input: &str) -> IResult<&str, &str> {
for name in SANDBOX_CAPS.keys().rev() {
if let Some(rest) = input.strip_prefix(name) {
if rest.is_empty() || rest.starts_with([',', '+', '-', '^']) {
return Ok((rest, *name));
}
}
}
Err(nom::Err::Error(Error::new(input, ErrorKind::Tag)))
}
#[expect(clippy::type_complexity)]
fn inner(input: &str) -> IResult<&str, (&str, &str, char, &str)> {
(
take_while1(|c| c != '/'),
char('/'),
recognize(separated_list1(char(','), cap_name)),
one_of("+-^"),
take_while1(|_| true), )
.map(|(act, _slash, caps, op, pat)| (act, caps, op, pat))
.parse(input)
}
match inner(input).finish() {
Ok(("", (act, caps, op, pat))) => {
let action = Action::from_str(act).map_err(|_| Errno::EINVAL)?;
let mut filter = Capability::empty();
for cap in caps.split(',') {
if cap.is_empty() {
return Err(Errno::EINVAL);
}
let cap = Capability::from_str(cap)?;
if cap.contains(Capability::CAP_IOCTL) {
return Err(Errno::EINVAL);
}
filter.insert(cap);
}
if filter.is_empty() {
return Err(Errno::EINVAL);
}
let maybe_addr = filter.intersects(Capability::CAP_NET)
&& filter.difference(Capability::CAP_NET).is_empty();
let pat = if maybe_addr {
if let Ok((rem_host, host)) = host_parser(pat).finish() {
if rem_host.is_empty() {
ScmpPattern::Host(host.to_string())
} else {
if let Ok((rem_addr, addr)) = addr_parser(pat).finish() {
if rem_addr.is_empty() {
ScmpPattern::Addr(addr.to_string())
} else {
ScmpPattern::Path(addr.to_string())
}
} else {
ScmpPattern::Path(pat.to_string())
}
}
} else if let Ok((rem_addr, addr)) = addr_parser(pat).finish() {
if rem_addr.is_empty() {
ScmpPattern::Addr(addr.to_string())
} else {
ScmpPattern::Path(addr.to_string())
}
} else {
ScmpPattern::Path(pat.to_string())
}
} else if pat.is_empty() {
return Err(Errno::EINVAL);
} else {
ScmpPattern::Path(pat.to_string())
};
Ok(ScmpCmd {
action,
filter,
op,
pat,
})
}
_ => Err(Errno::EINVAL),
}
}
pub(crate) fn parse_netlink_cmd(input: &str) -> Result<NetlinkCmd, Errno> {
fn inner(input: &str) -> IResult<&str, NetlinkOp> {
alt((
map(char('^'), |_| NetlinkOp::Clear),
map((char('+'), netlink_parser), |(_, fams)| {
NetlinkOp::Add(fams)
}),
map((char('-'), netlink_parser), |(_, fams)| {
NetlinkOp::Del(fams)
}),
))
.parse(input)
}
let mut parser = preceded(tag("allow/net/link"), all_consuming(inner));
match parser
.parse(input)
.finish()
.map(|(_, op)| NetlinkCmd::new(op))
{
Ok(cmd) => Ok(cmd),
Err(_) => Err(Errno::EINVAL),
}
}
fn netlink_parser(input: &str) -> IResult<&str, Vec<String>> {
let fam_parser = nom::bytes::complete::take_while1(|c: char| c != ',');
let (rem, raw_list) = separated_list1(char(','), fam_parser).parse(input)?;
for &fam in &raw_list {
if NETLINK_FAMILIES.binary_search(&fam).is_err() {
return Err(nom::Err::Error(Error::new(input, ErrorKind::Fail)));
}
}
let vec = raw_list.iter().map(|s| s.to_string()).collect();
Ok((rem, vec))
}
fn addr_parser(input: &str) -> IResult<&str, &str> {
fn port_element(input: &str) -> IResult<&str, &str> {
recognize((digit1, nom::combinator::opt((char('-'), digit1)))).parse(input)
}
recognize(all_consuming((
take_while1(|c: char| c.is_ascii_hexdigit() || c == '.' || c == ':'),
nom::combinator::opt((char('/'), digit1)),
one_of("!@"),
separated_list1(char(','), port_element),
)))
.parse(input)
}
fn host_parser(input: &str) -> IResult<&str, &str> {
let alias_base = alt((
tag_no_case("any"),
tag_no_case("local"),
tag_no_case("loopback"),
tag_no_case("linklocal"),
tag_no_case("multicast"),
));
let alias_tuple = (
alias_base,
opt(one_of("46")),
one_of("!@"),
digit1,
opt((char('-'), digit1)),
);
recognize(all_consuming(alias_tuple)).parse(input)
}
pub fn str2i64(value: &[u8]) -> Result<i64, Errno> {
if is_prefix(value, b"0x") || is_prefix(value, b"0X") {
btoi_radix::<i64>(&value[2..], 16)
} else if is_prefix(value, b"0o") || is_prefix(value, b"0O") {
btoi_radix::<i64>(&value[2..], 8)
} else {
btoi::<i64>(value)
}
.or(Err(Errno::EINVAL))
}
pub fn str2u64(value: &[u8]) -> Result<u64, Errno> {
if is_prefix(value, b"0x") || is_prefix(value, b"0X") {
btoi_radix::<u64>(&value[2..], 16)
} else if is_prefix(value, b"0o") || is_prefix(value, b"0O") {
btoi_radix::<u64>(&value[2..], 8)
} else {
btoi::<u64>(value)
}
.or(Err(Errno::EINVAL))
}
pub fn str2secs(value: &str) -> Result<Duration, Errno> {
if let Ok(value) = str2u64(value.as_bytes()) {
Ok(Duration::from_secs(value.into()))
} else {
value.parse::<Duration>().or(Err(Errno::EINVAL))
}
}
pub fn str2micros(value: &str) -> Result<Duration, Errno> {
if let Ok(value) = str2u64(value.as_bytes()) {
Ok(Duration::from_micros(value.into()))
} else {
value.parse::<Duration>().or(Err(Errno::EINVAL))
}
}
pub fn str2u32(value: &[u8]) -> Result<u32, Errno> {
if is_prefix(value, b"0x") || is_prefix(value, b"0X") {
btoi_radix::<u32>(&value[2..], 16)
} else if is_prefix(value, b"0o") || is_prefix(value, b"0O") {
btoi_radix::<u32>(&value[2..], 8)
} else {
btoi::<u32>(value)
}
.or(Err(Errno::EINVAL))
}
pub fn str2uid(value: &[u8]) -> Result<Uid, Errno> {
if is_prefix(value, b"0x") || is_prefix(value, b"0X") {
btoi_radix::<uid_t>(&value[2..], 16)
} else if is_prefix(value, b"0o") || is_prefix(value, b"0O") {
btoi_radix::<uid_t>(&value[2..], 8)
} else {
btoi::<uid_t>(value)
}
.map(Uid::from_raw)
.or(Err(Errno::EINVAL))
}
pub fn str2gid(value: &[u8]) -> Result<Gid, Errno> {
if is_prefix(value, b"0x") || is_prefix(value, b"0X") {
btoi_radix::<gid_t>(&value[2..], 16)
} else if is_prefix(value, b"0o") || is_prefix(value, b"0O") {
btoi_radix::<gid_t>(&value[2..], 8)
} else {
btoi::<gid_t>(value)
}
.map(Gid::from_raw)
.or(Err(Errno::EINVAL))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::port::{Port, PortRange};
fn portset_from_ports(ports: impl IntoIterator<Item = Port>) -> PortSet {
let mut set = PortSet::empty();
for port in ports {
set.insert(port);
}
set
}
fn portset_from_ranges(
ranges: impl IntoIterator<Item = std::ops::RangeInclusive<Port>>,
) -> PortSet {
let mut set = PortSet::empty();
for range in ranges {
set.insert_range(PortRange::new(*range.start(), *range.end()));
}
set
}
#[test]
fn test_str2i64_1() {
assert_eq!(str2i64(b"0"), Ok(0));
assert_eq!(str2i64(b"123"), Ok(123));
assert_eq!(str2i64(b"-456"), Ok(-456));
assert_eq!(str2i64(b"9223372036854775807"), Ok(i64::MAX));
assert_eq!(str2i64(b"-9223372036854775808"), Ok(i64::MIN));
}
#[test]
fn test_str2i64_2() {
assert_eq!(str2i64(b"0x0"), Ok(0));
assert_eq!(str2i64(b"0x1a"), Ok(26));
assert_eq!(str2i64(b"0xFF"), Ok(255));
assert_eq!(str2i64(b"0XDeAdBeEf"), Ok(0xDEADBEEF));
}
#[test]
fn test_str2i64_3() {
assert_eq!(str2i64(b"0o0"), Ok(0));
assert_eq!(str2i64(b"0o17"), Ok(15));
assert_eq!(str2i64(b"0o777"), Ok(511));
assert_eq!(str2i64(b"0O755"), Ok(493));
}
#[test]
fn test_str2i64_4() {
assert_eq!(str2i64(b""), Err(Errno::EINVAL));
assert_eq!(str2i64(b"abc"), Err(Errno::EINVAL));
assert_eq!(str2i64(b"12.34"), Err(Errno::EINVAL));
}
#[test]
fn test_str2u64_1() {
assert_eq!(str2u64(b"0"), Ok(0));
assert_eq!(str2u64(b"123"), Ok(123));
assert_eq!(str2u64(b"18446744073709551615"), Ok(u64::MAX));
}
#[test]
fn test_str2u64_2() {
assert_eq!(str2u64(b"0x0"), Ok(0));
assert_eq!(str2u64(b"0xdeadbeef"), Ok(0xDEADBEEF));
assert_eq!(str2u64(b"0XCAFEBABE"), Ok(0xCAFEBABE));
}
#[test]
fn test_str2u64_3() {
assert_eq!(str2u64(b"0o0"), Ok(0));
assert_eq!(str2u64(b"0o777"), Ok(511));
assert_eq!(str2u64(b"0O644"), Ok(420));
}
#[test]
fn test_str2u64_4() {
assert_eq!(str2u64(b""), Err(Errno::EINVAL));
assert_eq!(str2u64(b"-1"), Err(Errno::EINVAL));
assert_eq!(str2u64(b"abc"), Err(Errno::EINVAL));
}
#[test]
fn test_str2u32_1() {
assert_eq!(str2u32(b"0"), Ok(0));
assert_eq!(str2u32(b"123"), Ok(123));
assert_eq!(str2u32(b"4294967295"), Ok(u32::MAX));
}
#[test]
fn test_str2u32_2() {
assert_eq!(str2u32(b"0x0"), Ok(0));
assert_eq!(str2u32(b"0xFFFFFFFF"), Ok(u32::MAX));
assert_eq!(str2u32(b"0xcafe"), Ok(0xCAFE));
}
#[test]
fn test_str2u32_3() {
assert_eq!(str2u32(b"0o0"), Ok(0));
assert_eq!(str2u32(b"0o777"), Ok(511));
}
#[test]
fn test_str2u32_4() {
assert_eq!(str2u32(b""), Err(Errno::EINVAL));
assert_eq!(str2u32(b"-1"), Err(Errno::EINVAL));
assert_eq!(str2u32(b"4294967296"), Err(Errno::EINVAL)); }
#[test]
fn test_str2secs_1() {
assert_eq!(str2secs("0"), Ok(Duration::from_secs(0)));
assert_eq!(str2secs("60"), Ok(Duration::from_secs(60)));
assert_eq!(str2secs("3600"), Ok(Duration::from_secs(3600)));
}
#[test]
fn test_str2secs_2() {
assert_eq!(str2secs("0x3c"), Ok(Duration::from_secs(60)));
}
#[test]
fn test_str2secs_3() {
assert_eq!(str2secs(""), Err(Errno::EINVAL));
assert_eq!(str2secs("abc"), Err(Errno::EINVAL));
}
#[test]
fn test_str2micros_1() {
assert_eq!(str2micros("0"), Ok(Duration::from_micros(0)));
assert_eq!(str2micros("1000"), Ok(Duration::from_micros(1000)));
assert_eq!(str2micros("1000000"), Ok(Duration::from_micros(1_000_000)));
}
#[test]
fn test_str2micros_2() {
assert_eq!(str2micros("0x3e8"), Ok(Duration::from_micros(1000)));
}
#[test]
fn test_str2micros_3() {
assert_eq!(str2micros(""), Err(Errno::EINVAL));
assert_eq!(str2micros("abc"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_bind_1() {
for prefix in ["bind", "bind-try"] {
let bc = parse_bind_cmd(&format!("{prefix}+/:/:ro")).unwrap();
assert_eq!(bc.op, '+');
assert_eq!(bc.src, Some("/".to_string()));
assert_eq!(bc.dst, Some("/".to_string()));
assert!(bc.opt.contains(MountAttrFlags::MOUNT_ATTR_RDONLY));
assert!(bc.dat.is_none());
assert_eq!(bc.r#try, prefix == "bind-try");
}
}
#[test]
fn test_parse_bind_2() {
for prefix in ["bind", "bind-try"] {
let bc = parse_bind_cmd(&format!("{prefix}-/mnt/data:/data")).unwrap();
assert_eq!(bc.op, '-');
assert_eq!(bc.src, Some("/mnt/data".to_string()));
assert_eq!(bc.dst, Some("/data".to_string()));
assert!(bc.opt.is_empty());
assert!(bc.dat.is_none());
assert_eq!(bc.r#try, prefix == "bind-try");
}
}
#[test]
fn test_parse_bind_3() {
for prefix in ["bind", "bind-try"] {
let bc = parse_bind_cmd(&format!("{prefix}+tmpfs:/tmp:ro,nosuid,size=10M")).unwrap();
assert_eq!(bc.op, '+');
assert_eq!(bc.src, Some("tmpfs".to_string()));
assert_eq!(bc.dst, Some("/tmp".to_string()));
assert!(bc.opt.contains(MountAttrFlags::MOUNT_ATTR_RDONLY));
assert!(bc.opt.contains(MountAttrFlags::MOUNT_ATTR_NOSUID));
assert_eq!(bc.dat.unwrap(), "size=10M");
assert_eq!(bc.r#try, prefix == "bind-try");
}
}
#[test]
fn test_parse_bind_4() {
for prefix in ["bind", "bind-try"] {
assert_eq!(
parse_bind_cmd(&format!("{prefix}^overlay:/tmp/target")),
Err(Errno::EINVAL)
);
assert_eq!(
parse_bind_cmd(&format!("{prefix}^overlay")),
Err(Errno::EINVAL)
);
assert_eq!(parse_bind_cmd(&format!("{prefix}+::")), Err(Errno::EINVAL));
assert_eq!(
parse_bind_cmd(&format!("{prefix}+/src::opt")),
Err(Errno::EINVAL)
);
assert_eq!(
parse_bind_cmd(&format!("{prefix}+:/dst:opt")),
Err(Errno::EINVAL)
);
assert_eq!(
parse_bind_cmd(&format!("{prefix}+src:/dst: ro")),
Err(Errno::EINVAL)
);
assert_eq!(
parse_bind_cmd(&format!("{prefix}+src:/dst:ro ")),
Err(Errno::EINVAL)
);
assert_eq!(
parse_bind_cmd(&format!("{prefix}+src:/dst:ro, nosuid")),
Err(Errno::EINVAL)
);
assert_eq!(
parse_bind_cmd(&format!("{prefix}+src:/dst:ro,nosuid ")),
Err(Errno::EINVAL)
);
}
assert_eq!(parse_bind_cmd("bind*src:/dst"), Err(Errno::EINVAL));
assert_eq!(parse_bind_cmd("bind=src:/dst"), Err(Errno::EINVAL));
assert_eq!(parse_bind_cmd("stat"), Err(Errno::EINVAL));
assert_eq!(parse_bind_cmd("bindsrc:/dst"), Err(Errno::EINVAL));
assert_eq!(parse_bind_cmd("bind-try*src:/dst"), Err(Errno::EINVAL));
assert_eq!(parse_bind_cmd("bind-try=src:/dst"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_bind_5() {
for prefix in ["bind", "bind-try"] {
let bc = parse_bind_cmd(&format!("{prefix}+/foo\\:bar:/dst")).unwrap();
assert_eq!(bc.op, '+');
assert_eq!(bc.src, Some("/foo:bar".to_string()));
assert_eq!(bc.dst, Some("/dst".to_string()));
assert_eq!(bc.r#try, prefix == "bind-try");
}
}
#[test]
fn test_parse_bind_6() {
for prefix in ["bind", "bind-try"] {
let bc = parse_bind_cmd(&format!("{prefix}+/src:/foo\\:bar")).unwrap();
assert_eq!(bc.src, Some("/src".to_string()));
assert_eq!(bc.dst, Some("/foo:bar".to_string()));
assert_eq!(bc.r#try, prefix == "bind-try");
}
}
#[test]
fn test_parse_bind_7() {
for prefix in ["bind", "bind-try"] {
let bc = parse_bind_cmd(&format!("{prefix}+/a\\\\b:/c\\\\d")).unwrap();
assert_eq!(bc.src, Some("/a\\b".to_string()));
assert_eq!(bc.dst, Some("/c\\d".to_string()));
assert_eq!(bc.r#try, prefix == "bind-try");
}
}
#[test]
fn test_parse_bind_8() {
for prefix in ["bind", "bind-try"] {
let bc = parse_bind_cmd(&format!("{prefix}+/a\\:b\\\\c:/dst")).unwrap();
assert_eq!(bc.src, Some("/a:b\\c".to_string()));
assert_eq!(bc.dst, Some("/dst".to_string()));
assert_eq!(bc.r#try, prefix == "bind-try");
}
}
#[test]
fn test_parse_bind_9() {
for prefix in ["bind", "bind-try"] {
let bc = parse_bind_cmd(&format!("{prefix}+/foo\\:bar:/dst:ro")).unwrap();
assert_eq!(bc.src, Some("/foo:bar".to_string()));
assert_eq!(bc.dst, Some("/dst".to_string()));
assert!(bc.opt.contains(MountAttrFlags::MOUNT_ATTR_RDONLY));
assert_eq!(bc.r#try, prefix == "bind-try");
}
}
#[test]
fn test_parse_bind_10() {
for prefix in ["bind", "bind-try"] {
let bc = parse_bind_cmd(&format!("{prefix}^")).unwrap();
assert_eq!(bc.op, '^');
assert_eq!(bc.src, None);
assert_eq!(bc.dst, None);
assert!(bc.opt.is_empty());
assert!(bc.dat.is_none());
assert_eq!(bc.r#try, prefix == "bind-try");
}
}
#[test]
fn test_parse_bind_11() {
for prefix in ["bind", "bind-try"] {
let bc = parse_bind_cmd(&format!("{prefix}-/foo\\:bar:/dst")).unwrap();
assert_eq!(bc.op, '-');
assert_eq!(bc.src, Some("/foo:bar".to_string()));
assert_eq!(bc.dst, Some("/dst".to_string()));
assert_eq!(bc.r#try, prefix == "bind-try");
}
}
#[test]
fn test_parse_bind_12() {
for prefix in ["bind", "bind-try"] {
assert_eq!(
parse_bind_cmd(&format!("{prefix}+/foo\\:/dst")),
Err(Errno::EINVAL)
);
}
}
#[test]
fn test_parse_bind_13() {
for bogus in ["mount", "mnt", "bnd", ""] {
for op in ["+/a:/b", "-/a:/b", "^"] {
assert_eq!(parse_bind_cmd(&format!("{bogus}{op}")), Err(Errno::EINVAL));
}
}
}
#[test]
fn test_parse_bind_14() {
for prefix in ["bind", "bind-try"] {
assert_eq!(
parse_bind_cmd(&format!("{prefix}+:/dst")),
Err(Errno::EINVAL)
);
assert_eq!(parse_bind_cmd(&format!("{prefix}+")), Err(Errno::EINVAL));
assert_eq!(parse_bind_cmd(&format!("{prefix}+:")), Err(Errno::EINVAL));
}
}
#[test]
fn test_parse_bind_15() {
for prefix in ["bind", "bind-try"] {
let bc = parse_bind_cmd(&format!("{prefix}+/a\\:b:/c\\:d:ro,noexec")).unwrap();
assert_eq!(bc.src, Some("/a:b".to_string()));
assert_eq!(bc.dst, Some("/c:d".to_string()));
assert!(bc.opt.contains(MountAttrFlags::MOUNT_ATTR_RDONLY));
assert!(bc.opt.contains(MountAttrFlags::MOUNT_ATTR_NOEXEC));
assert_eq!(bc.r#try, prefix == "bind-try");
}
}
#[test]
fn test_parse_force_1() {
let fc = parse_force_cmd("force^").unwrap();
assert_eq!(fc.op, '^');
assert_eq!(fc.src, None);
assert_eq!(fc.alg, None);
assert_eq!(fc.key, None);
assert_eq!(fc.act, None);
}
#[test]
fn test_parse_force_2() {
let fc = parse_force_cmd("force-/usr/bin/foo").unwrap();
assert_eq!(fc.op, '-');
assert_eq!(fc.src.unwrap(), "/usr/bin/foo");
assert_eq!(fc.alg, None);
assert_eq!(fc.key, None);
assert_eq!(fc.act, None);
}
#[test]
fn test_parse_force_3() {
let fc = parse_force_cmd("force+/usr/bin/bar:sha256:abcd1234").unwrap();
assert_eq!(fc.op, '+');
assert_eq!(fc.src.unwrap(), "/usr/bin/bar");
assert_eq!(fc.alg.unwrap(), "sha256");
assert_eq!(fc.key.unwrap(), "abcd1234");
assert_eq!(fc.act, None);
}
#[test]
fn test_parse_force_4() {
let fc = parse_force_cmd("force+/bin/prog:md5:0123456789abcdef:warn").unwrap();
assert_eq!(fc.op, '+');
assert_eq!(fc.src.unwrap(), "/bin/prog");
assert_eq!(fc.alg.unwrap(), "md5");
assert_eq!(fc.key.unwrap(), "0123456789abcdef");
assert_eq!(fc.act.unwrap(), Action::Warn);
}
#[test]
fn test_parse_force_5() {
let long_hash = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
let cmd = format!("force+/lib/x:sha512:{long_hash}:filter");
let fc = parse_force_cmd(&cmd).unwrap();
assert_eq!(fc.op, '+');
assert_eq!(fc.src.unwrap(), "/lib/x");
assert_eq!(fc.alg.unwrap(), "sha512");
assert_eq!(fc.key.unwrap(), long_hash);
assert_eq!(fc.act.unwrap(), Action::Filter);
}
#[test]
fn test_parse_force_6() {
assert_eq!(parse_force_cmd("force*=stuff"), Err(Errno::EINVAL));
assert_eq!(parse_force_cmd("force?"), Err(Errno::EINVAL));
assert_eq!(parse_force_cmd("force+"), Err(Errno::EINVAL));
assert_eq!(parse_force_cmd("force+/path"), Err(Errno::EINVAL));
assert_eq!(parse_force_cmd("force+/path:"), Err(Errno::EINVAL));
assert_eq!(parse_force_cmd("force+/path:sha256"), Err(Errno::EINVAL));
assert_eq!(parse_force_cmd("force+/path:sha256:"), Err(Errno::EINVAL));
assert_eq!(parse_force_cmd("force-"), Err(Errno::EINVAL));
assert_eq!(
parse_force_cmd("force+/x:sha256:abcd1234:invalid"),
Err(Errno::EINVAL)
);
assert_eq!(parse_force_cmd("force^extra"), Err(Errno::EINVAL));
assert_eq!(
parse_force_cmd("force+/path:sha256:abcd1234:warn:extra"),
Err(Errno::EINVAL)
);
}
#[test]
fn test_parse_setid_1() {
let cmd = parse_setid_cmd("setuid+alice:bob").unwrap();
assert_eq!(
cmd,
SetIdCmd {
id: 'u',
op: '+',
src: Some("alice".into()),
dst: Some("bob".into()),
}
);
}
#[test]
fn test_parse_setid_2() {
let cmd = parse_setid_cmd("setgid-john:doe").unwrap();
assert_eq!(
cmd,
SetIdCmd {
id: 'g',
op: '-',
src: Some("john".into()),
dst: Some("doe".into()),
}
);
}
#[test]
fn test_parse_setid_3() {
let cmd = parse_setid_cmd("setuid^").unwrap();
assert_eq!(
cmd,
SetIdCmd {
id: 'u',
op: '^',
src: None,
dst: None,
}
);
}
#[test]
fn test_parse_setid_4() {
let cmd = parse_setid_cmd("setgid^wheel").unwrap();
assert_eq!(
cmd,
SetIdCmd {
id: 'g',
op: '^',
src: Some("wheel".into()),
dst: None,
}
);
}
#[test]
fn test_parse_setid_5() {
assert_eq!(parse_setid_cmd("setxid+user:group"), Err(Errno::EINVAL));
assert_eq!(parse_setid_cmd("setuid*user:group"), Err(Errno::EINVAL));
assert_eq!(parse_setid_cmd("setuid+alice"), Err(Errno::EINVAL));
assert_eq!(parse_setid_cmd("setuid-alice"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_landlock_1() {
let cmd = parse_landlock_cmd("allow/lock/all+/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((LandlockPolicy::access_fs_from_set("all"), "/trusted".into(),))
);
}
#[test]
fn test_parse_landlock_2() {
let cmd = parse_landlock_cmd("allow/lock/all-/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((LandlockPolicy::access_fs_from_set("all"), "/trusted".into(),))
);
}
#[test]
fn test_parse_landlock_3() {
let cmd = parse_landlock_cmd("allow/lock/all^/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((LandlockPolicy::access_fs_from_set("all"), "/trusted".into(),))
);
}
#[test]
fn test_parse_landlock_4() {
let cmd = parse_landlock_cmd("allow/lock/all-x+/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("all-x"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_5() {
let cmd = parse_landlock_cmd("allow/lock/all-x-/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("all-x"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_6() {
let cmd = parse_landlock_cmd("allow/lock/all-x^/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("all-x"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_7() {
let all_x = LandlockPolicy::access_fs_from_set("all-x");
let all = LandlockPolicy::access_fs_from_set("all");
assert!(!all_x.contains(AccessFs::Execute));
assert!(all.contains(all_x));
assert!(!all_x.contains(all));
assert_eq!(all & !all_x, AccessFs::Execute);
}
#[test]
fn test_parse_landlock_8() {
let cmd = parse_landlock_cmd("allow/lock/all-x,exec+/bin").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((LandlockPolicy::access_fs_from_set("all"), "/bin".into(),))
);
}
#[test]
fn test_parse_landlock_9() {
let cmd = parse_landlock_cmd("allow/lock/all-x,read+/data").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
let rule = cmd.filter.first().cloned().unwrap();
if let LandlockRule::Fs((access, path)) = rule {
assert!(access.contains(AccessFs::ReadFile));
assert!(!access.contains(AccessFs::Execute));
assert_eq!(path, "/data");
} else {
panic!("Expected LandlockRule::Fs");
}
}
#[test]
fn test_parse_landlock_10() {
let cmd = parse_landlock_cmd("allow/lock/rpath+/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("rpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_11() {
let cmd = parse_landlock_cmd("allow/lock/rpath-/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("rpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_12() {
let cmd = parse_landlock_cmd("allow/lock/rpath^/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("rpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_13() {
let cmd = parse_landlock_cmd("allow/lock/wpath+/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("wpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_14() {
let cmd = parse_landlock_cmd("allow/lock/wpath-/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("wpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_15() {
let cmd = parse_landlock_cmd("allow/lock/wpath^/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("wpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_16() {
let cmd = parse_landlock_cmd("allow/lock/cpath+/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("cpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_17() {
let cmd = parse_landlock_cmd("allow/lock/cpath-/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("cpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_18() {
let cmd = parse_landlock_cmd("allow/lock/cpath^/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("cpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_19() {
let cmd = parse_landlock_cmd("allow/lock/dpath+/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("dpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_20() {
let cmd = parse_landlock_cmd("allow/lock/dpath-/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("dpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_21() {
let cmd = parse_landlock_cmd("allow/lock/dpath^/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("dpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_22() {
let cmd = parse_landlock_cmd("allow/lock/spath+/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("spath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_23() {
let cmd = parse_landlock_cmd("allow/lock/spath-/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("spath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_24() {
let cmd = parse_landlock_cmd("allow/lock/spath^/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("spath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_25() {
let cmd = parse_landlock_cmd("allow/lock/tpath+/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("tpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_26() {
let cmd = parse_landlock_cmd("allow/lock/tpath-/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("tpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_27() {
let cmd = parse_landlock_cmd("allow/lock/tpath^/trusted").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
LandlockPolicy::access_fs_from_set("tpath"),
"/trusted".into(),
))
);
}
#[test]
fn test_parse_landlock_28() {
let cmd = parse_landlock_cmd("allow/lock/inet+1024-65535").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Net((
LandlockPolicy::access_net_from_set("inet"),
portset_from_ranges([1024..=65535]),
))
);
}
#[test]
fn test_parse_landlock_29() {
let cmd = parse_landlock_cmd("allow/lock/inet-1024-65535").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Net((
LandlockPolicy::access_net_from_set("inet"),
portset_from_ranges([1024..=65535]),
))
);
}
#[test]
fn test_parse_landlock_30() {
let cmd = parse_landlock_cmd("allow/lock/inet^1024-65535").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Net((
LandlockPolicy::access_net_from_set("inet"),
portset_from_ranges([1024..=65535]),
))
);
}
#[test]
fn test_parse_landlock_31() {
let cmd = parse_landlock_cmd("allow/lock/read,write,exec-/var/log").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((
AccessFs::ReadFile | AccessFs::WriteFile | AccessFs::Execute,
"/var/log".into(),
))
);
}
#[test]
fn test_parse_landlock_32() {
let cmd = parse_landlock_cmd("allow/lock/bind,connect^1000-2000").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Net((
AccessNet::BindTcp | AccessNet::ConnectTcp,
portset_from_ranges([1000..=2000]),
))
);
}
#[test]
fn test_parse_landlock_33() {
let cmd = parse_landlock_cmd("allow/lock/connect+80,443,8080").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Net((AccessNet::ConnectTcp, portset_from_ports([80, 443, 8080]),))
);
}
#[test]
fn test_parse_landlock_34() {
let cmd = parse_landlock_cmd("allow/lock/bind,connect+80,443,8000-9000").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
let mut expected = portset_from_ports([80, 443]);
expected.union_with(&portset_from_ranges([8000..=9000]));
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Net((AccessNet::BindTcp | AccessNet::ConnectTcp, expected,))
);
}
#[test]
fn test_parse_landlock_35() {
let cmd = parse_landlock_cmd("allow/lock/inet-443").unwrap();
assert_eq!(cmd.op, LandlockOp::Rem);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Net((
LandlockPolicy::access_net_from_set("inet"),
portset_from_ports([443]),
))
);
}
#[test]
fn test_parse_landlock_36() {
let cmd = parse_landlock_cmd("allow/lock/write+tmp").unwrap();
assert_eq!(cmd.op, LandlockOp::Add);
assert_eq!(
cmd.filter.first().cloned().unwrap(),
LandlockRule::Fs((AccessFs::WriteFile, "tmp".into(),))
);
}
#[test]
fn test_parse_landlock_37() {
assert_eq!(
parse_landlock_cmd("allow/lockx/write+/tmp"),
Err(Errno::EINVAL)
);
assert_eq!(
parse_landlock_cmd("deny/lock/read+/tmp"),
Err(Errno::EINVAL)
);
assert_eq!(
parse_landlock_cmd("allow/lock/invalid+/tmp"),
Err(Errno::EINVAL)
);
assert_eq!(
parse_landlock_cmd("allow/lock/read,foo+/tmp"),
Err(Errno::EINVAL)
);
assert_eq!(parse_landlock_cmd("allow/lock/all"), Err(Errno::EINVAL));
assert_eq!(parse_landlock_cmd("allow/lock/all+"), Err(Errno::EINVAL));
assert_eq!(
parse_landlock_cmd("allow/lock/read,write-"),
Err(Errno::EINVAL)
);
}
#[test]
fn test_parse_scmp_1() {
let cmd = parse_scmp_cmd("allow/all+/usr/bin").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Allow,
filter: Capability::CAP_GLOB,
op: '+',
pat: ScmpPattern::Path("/usr/bin".into()),
}
);
}
#[test]
fn test_parse_scmp_2() {
let cmd = parse_scmp_cmd("allow/all-x+/home/***").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Allow,
filter: Capability::CAP_ALL_X,
op: '+',
pat: ScmpPattern::Path("/home/***".into()),
}
);
}
#[test]
fn test_parse_scmp_3() {
let cmd = parse_scmp_cmd("deny/all-x-/tmp/***").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Deny,
filter: Capability::CAP_ALL_X,
op: '-',
pat: ScmpPattern::Path("/tmp/***".into()),
}
);
}
#[test]
fn test_parse_scmp_4() {
let cmd = parse_scmp_cmd("filter/all-x^/var/***").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Filter,
filter: Capability::CAP_ALL_X,
op: '^',
pat: ScmpPattern::Path("/var/***".into()),
}
);
}
#[test]
fn test_parse_scmp_5() {
let cmd = parse_scmp_cmd("allow/all-x,read+/data").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Allow,
filter: Capability::CAP_ALL_X,
op: '+',
pat: ScmpPattern::Path("/data".into()),
}
);
}
#[test]
fn test_parse_scmp_6() {
let cmd = parse_scmp_cmd("warn/read,all-x-/secure").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Warn,
filter: Capability::CAP_ALL_X,
op: '-',
pat: ScmpPattern::Path("/secure".into()),
}
);
}
#[test]
fn test_parse_scmp_7() {
let cmd = parse_scmp_cmd("allow/all-x,exec+/bin").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Allow,
filter: Capability::CAP_GLOB,
op: '+',
pat: ScmpPattern::Path("/bin".into()),
}
);
}
#[test]
fn test_parse_scmp_8() {
let cmd = parse_scmp_cmd("allow/all+/path").unwrap();
assert_eq!(cmd.filter, Capability::CAP_GLOB);
let cmd = parse_scmp_cmd("allow/all-x+/path").unwrap();
assert_eq!(cmd.filter, Capability::CAP_ALL_X);
}
#[test]
fn test_parse_scmp_9() {
let cmd = parse_scmp_cmd("deny/all,read+/tmp").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Deny,
filter: Capability::CAP_GLOB,
op: '+',
pat: ScmpPattern::Path("/tmp".into())
}
);
let cmd = parse_scmp_cmd("allow/write,truncate,all-/tmp").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Allow,
filter: Capability::CAP_GLOB,
op: '-',
pat: ScmpPattern::Path("/tmp".into())
}
);
let cmd = parse_scmp_cmd("filter/all,chdir^/tmp").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Filter,
filter: Capability::CAP_GLOB,
op: '^',
pat: ScmpPattern::Path("/tmp".into())
}
);
}
#[test]
fn test_parse_scmp_10() {
let cmd = parse_scmp_cmd("deny/read,write-/var/log").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Deny,
filter: Capability::CAP_READ | Capability::CAP_WRITE,
op: '-',
pat: ScmpPattern::Path("/var/log".into()),
}
);
}
#[test]
fn test_parse_scmp_11() {
let cmd = parse_scmp_cmd("filter/net/bind+10.0.0.0/24!80-90").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Filter,
filter: Capability::CAP_NET_BIND,
op: '+',
pat: ScmpPattern::Addr("10.0.0.0/24!80-90".into()),
}
);
}
#[test]
fn test_parse_scmp_12() {
let cmd = parse_scmp_cmd("warn/net/bind+/some/dir").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Warn,
filter: Capability::CAP_NET_BIND,
op: '+',
pat: ScmpPattern::Path("/some/dir".into()),
}
);
}
#[test]
fn test_parse_scmp_13() {
let cmd = parse_scmp_cmd("warn/net/connect-2001:db8::1@22").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Warn,
filter: Capability::CAP_NET_CONNECT,
op: '-',
pat: ScmpPattern::Addr("2001:db8::1@22".into()),
}
);
}
#[test]
fn test_parse_scmp_14() {
let cmd = parse_scmp_cmd("exit/net/connect-/var/run/socket").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Exit,
filter: Capability::CAP_NET_CONNECT,
op: '-',
pat: ScmpPattern::Path("/var/run/socket".into()),
}
);
}
#[test]
fn test_parse_scmp_15() {
let cmd = parse_scmp_cmd("exit/net/sendfd+/tmp/socket").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Exit,
filter: Capability::CAP_NET_SENDFD,
op: '+',
pat: ScmpPattern::Path("/tmp/socket".into()),
}
);
}
#[test]
fn test_parse_scmp_16() {
assert_eq!(parse_scmp_cmd("block/all+/path"), Err(Errno::EINVAL));
assert_eq!(parse_scmp_cmd("allow/foo+/path"), Err(Errno::EINVAL));
assert_eq!(parse_scmp_cmd("deny/read,foo+/path"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_scmp_17() {
let cmd = parse_scmp_cmd("allow/net/bind,read+/file").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Allow,
filter: Capability::CAP_NET_BIND | Capability::CAP_READ,
op: '+',
pat: ScmpPattern::Path("/file".into()),
}
);
let cmd = parse_scmp_cmd("kill/read,net/connect-/file").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Kill,
filter: Capability::CAP_NET_CONNECT | Capability::CAP_READ,
op: '-',
pat: ScmpPattern::Path("/file".into()),
}
);
let cmd = parse_scmp_cmd("panic/read,net/sendfd,write^/file").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Panic,
filter: Capability::CAP_NET_SENDFD | Capability::CAP_READ | Capability::CAP_WRITE,
op: '^',
pat: ScmpPattern::Path("/file".into()),
}
);
let cmd =
parse_scmp_cmd("filter/net/bind,read,net/sendfd,write,net/connect+/file").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Filter,
filter: Capability::CAP_NET | Capability::CAP_READ | Capability::CAP_WRITE,
op: '+',
pat: ScmpPattern::Path("/file".into()),
}
);
}
#[test]
fn test_parse_scmp_18() {
let cmd = parse_scmp_cmd("allow/net/bind,net/connect+1.2.3.4!80").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Allow,
filter: Capability::CAP_NET_BIND | Capability::CAP_NET_CONNECT,
op: '+',
pat: ScmpPattern::Addr("1.2.3.4!80".into()),
}
);
let cmd = parse_scmp_cmd("abort/read,net/bind,net/connect-1.2.3.4!80").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Abort,
filter: Capability::CAP_NET_BIND
| Capability::CAP_NET_CONNECT
| Capability::CAP_READ,
op: '-',
pat: ScmpPattern::Path("1.2.3.4!80".into()),
}
);
let cmd = parse_scmp_cmd("stop/net/bind,net/connect,net/sendfd^1.2.3.4!80").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Stop,
filter: Capability::CAP_NET,
op: '^',
pat: ScmpPattern::Addr("1.2.3.4!80".into()),
}
);
}
#[test]
fn test_parse_scmp_19() {
assert_eq!(
parse_scmp_cmd("allow/net/bind+not_ip"),
Ok(ScmpCmd {
action: Action::Allow,
filter: Capability::CAP_NET_BIND,
op: '+',
pat: ScmpPattern::Path("not_ip".into()),
})
);
assert_eq!(
parse_scmp_cmd("allow/net/connect+1.2.3.4!port"),
Ok(ScmpCmd {
action: Action::Allow,
filter: Capability::CAP_NET_CONNECT,
op: '+',
pat: ScmpPattern::Path("1.2.3.4!port".into()),
})
);
}
#[test]
fn test_parse_scmp_20() {
let cmd = parse_scmp_cmd("filter/net/bind+10.0.0.0/24!80,443,8000-9000").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Filter,
filter: Capability::CAP_NET_BIND,
op: '+',
pat: ScmpPattern::Addr("10.0.0.0/24!80,443,8000-9000".into()),
}
);
}
#[test]
fn test_parse_scmp_21() {
let cmd = parse_scmp_cmd("warn/net/connect-2001:db8::1@22,80,443").unwrap();
assert_eq!(
cmd,
ScmpCmd {
action: Action::Warn,
filter: Capability::CAP_NET_CONNECT,
op: '-',
pat: ScmpPattern::Addr("2001:db8::1@22,80,443".into()),
}
);
}
#[test]
fn test_parse_scmp_22() {
assert_eq!(parse_scmp_cmd("allow/all"), Err(Errno::EINVAL));
assert_eq!(parse_scmp_cmd("deny/net/bind+"), Err(Errno::EINVAL));
assert_eq!(parse_scmp_cmd("warn/stat,path+/file"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_scmp_23() {
let cmd = parse_netlink_cmd("allow/net/link^").unwrap();
assert_eq!(cmd.op, NetlinkOp::Clear);
}
#[test]
fn test_parse_netlink_1() {
let cmd = parse_netlink_cmd("allow/net/link+route").unwrap();
assert_eq!(cmd.op, NetlinkOp::Add(vec!["route".into()]));
}
#[test]
fn test_parse_netlink_2() {
let cmd = parse_netlink_cmd("allow/net/link+route,usersock,firewall").unwrap();
assert_eq!(
cmd.op,
NetlinkOp::Add(vec!["route".into(), "usersock".into(), "firewall".into()])
);
}
#[test]
fn test_parse_netlink_3() {
let cmd = parse_netlink_cmd("allow/net/link-fib_lookup").unwrap();
assert_eq!(cmd.op, NetlinkOp::Del(vec!["fib_lookup".into()]));
}
#[test]
fn test_parse_netlink_4() {
let cmd = parse_netlink_cmd("allow/net/link-selinux,sock_diag,crypto").unwrap();
assert_eq!(
cmd.op,
NetlinkOp::Del(vec!["selinux".into(), "sock_diag".into(), "crypto".into()])
);
}
#[test]
fn test_parse_netlink_5() {
assert_eq!(parse_netlink_cmd("allow/net/link+foo"), Err(Errno::EINVAL));
assert_eq!(
parse_netlink_cmd("allow/net/link-bar,unknown"),
Err(Errno::EINVAL)
);
}
#[test]
fn test_parse_netlink_6() {
assert_eq!(parse_netlink_cmd("allow/net/link"), Err(Errno::EINVAL));
assert_eq!(parse_netlink_cmd("allow/net/link "), Err(Errno::EINVAL));
assert_eq!(
parse_netlink_cmd("allow/net/link^extra"),
Err(Errno::EINVAL)
);
assert_eq!(
parse_netlink_cmd("allow/net/link+route "),
Err(Errno::EINVAL)
);
assert_eq!(parse_netlink_cmd("allow/net/link+"), Err(Errno::EINVAL));
assert_eq!(parse_netlink_cmd("allow/net/link-"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_fs_1() {
let cmd = parse_fs_cmd("allow/fs+ext4").unwrap();
assert_eq!(cmd.action, Action::Allow);
assert_eq!(cmd.op, '+');
assert_eq!(cmd.fs_type, "ext4");
}
#[test]
fn test_parse_fs_2() {
let cmd = parse_fs_cmd("deny/fs-tmpfs").unwrap();
assert_eq!(cmd.action, Action::Deny);
assert_eq!(cmd.op, '-');
assert_eq!(cmd.fs_type, "tmpfs");
}
#[test]
fn test_parse_fs_3() {
let cmd = parse_fs_cmd("filter/fs^btrfs").unwrap();
assert_eq!(cmd.action, Action::Filter);
assert_eq!(cmd.op, '^');
assert_eq!(cmd.fs_type, "btrfs");
}
#[test]
fn test_parse_fs_4() {
let actions = [
("allow", Action::Allow),
("deny", Action::Deny),
("filter", Action::Filter),
("warn", Action::Warn),
("stop", Action::Stop),
("abort", Action::Abort),
("kill", Action::Kill),
("panic", Action::Panic),
("exit", Action::Exit),
];
for (act_str, act_enum) in actions {
let input = format!("{}/fs+xfs", act_str);
let cmd = parse_fs_cmd(&input).unwrap();
assert_eq!(cmd.action, act_enum);
}
}
#[test]
fn test_parse_fs_5() {
assert_eq!(parse_fs_cmd("block/fs+ext4"), Err(Errno::EINVAL));
assert_eq!(parse_fs_cmd("reject/fs-tmpfs"), Err(Errno::EINVAL));
assert_eq!(parse_fs_cmd("allow/fs+"), Err(Errno::EINVAL));
assert_eq!(parse_fs_cmd("deny/fs-"), Err(Errno::EINVAL));
assert_eq!(parse_fs_cmd("allow/fs*ext4"), Err(Errno::EINVAL));
assert_eq!(parse_fs_cmd("allow/fs=tmpfs"), Err(Errno::EINVAL));
assert_eq!(parse_fs_cmd("allow+ext4"), Err(Errno::EINVAL));
assert_eq!(parse_fs_cmd("fs+ext4"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_mask_1() {
let cmd = parse_mask_cmd("^").unwrap();
assert_eq!(cmd.op, '^');
assert_eq!(cmd.pattern, "");
assert_eq!(cmd.mask_all, None);
assert_eq!(cmd.mask_dir, None);
}
#[test]
fn test_parse_mask_2() {
assert_eq!(parse_mask_cmd("^/"), Err(Errno::EINVAL));
assert_eq!(parse_mask_cmd("^/foo"), Err(Errno::EINVAL));
assert_eq!(parse_mask_cmd("^:"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_mask_3() {
let cmd = parse_mask_cmd("-/proc/cmdline").unwrap();
assert_eq!(cmd.op, '-');
assert_eq!(cmd.pattern, "/proc/cmdline");
assert_eq!(cmd.mask_all, None);
assert_eq!(cmd.mask_dir, None);
}
#[test]
fn test_parse_mask_4() {
let cmd = parse_mask_cmd("-/foo:bar:baz").unwrap();
assert_eq!(cmd.pattern, "/foo:bar:baz");
}
#[test]
fn test_parse_mask_5() {
assert_eq!(parse_mask_cmd("-"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_mask_6() {
let cmd = parse_mask_cmd("+/proc/**").unwrap();
assert_eq!(cmd.op, '+');
assert_eq!(cmd.pattern, "/proc/**");
assert_eq!(cmd.mask_all, None);
assert_eq!(cmd.mask_dir, None);
}
#[test]
fn test_parse_mask_7() {
let cmd = parse_mask_cmd("+/***:/dev/null").unwrap();
assert_eq!(cmd.pattern, "/***");
assert_eq!(cmd.mask_all, Some("/dev/null".to_string()));
assert_eq!(cmd.mask_dir, None);
}
#[test]
fn test_parse_mask_8() {
let cmd = parse_mask_cmd("+/***:/dev/null:/dev/zero").unwrap();
assert_eq!(cmd.pattern, "/***");
assert_eq!(cmd.mask_all, Some("/dev/null".to_string()));
assert_eq!(cmd.mask_dir, Some("/dev/zero".to_string()));
}
#[test]
fn test_parse_mask_9() {
assert_eq!(parse_mask_cmd("+"), Err(Errno::EINVAL));
assert_eq!(parse_mask_cmd("+:"), Err(Errno::EINVAL));
assert_eq!(parse_mask_cmd("+::"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_mask_10() {
let cmd = parse_mask_cmd("+/foo:").unwrap();
assert_eq!(cmd.pattern, "/foo");
assert_eq!(cmd.mask_all, Some("".to_string()));
}
#[test]
fn test_parse_mask_11() {
let cmd = parse_mask_cmd("+/foo:/bar:").unwrap();
assert_eq!(cmd.pattern, "/foo");
assert_eq!(cmd.mask_all, Some("/bar".to_string()));
assert_eq!(cmd.mask_dir, Some("".to_string()));
}
#[test]
fn test_parse_mask_12() {
let cmd = parse_mask_cmd("+/foo\\:bar").unwrap();
assert_eq!(cmd.pattern, "/foo:bar");
assert_eq!(cmd.mask_all, None);
}
#[test]
fn test_parse_mask_13() {
let cmd = parse_mask_cmd("+/foo:/bar\\:baz").unwrap();
assert_eq!(cmd.pattern, "/foo");
assert_eq!(cmd.mask_all, Some("/bar:baz".to_string()));
}
#[test]
fn test_parse_mask_14() {
let cmd = parse_mask_cmd("+/***:/dev/null:/d\\:ev/zero").unwrap();
assert_eq!(cmd.pattern, "/***");
assert_eq!(cmd.mask_all, Some("/dev/null".to_string()));
assert_eq!(cmd.mask_dir, Some("/d:ev/zero".to_string()));
}
#[test]
fn test_parse_mask_15() {
let cmd = parse_mask_cmd("+/a\\:b\\:c").unwrap();
assert_eq!(cmd.pattern, "/a:b:c");
}
#[test]
fn test_parse_mask_16() {
assert_eq!(parse_mask_cmd("+/foo\\nbar"), Err(Errno::EINVAL));
assert_eq!(parse_mask_cmd("+/foo\\a"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_mask_17() {
let cmd = parse_mask_cmd("+/foo\\\\bar").unwrap();
assert_eq!(cmd.pattern, "/foo\\bar");
}
#[test]
fn test_parse_mask_18() {
assert_eq!(parse_mask_cmd("+/foo\\"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_mask_19() {
let cmd = parse_mask_cmd("+/foo\\\\").unwrap();
assert_eq!(cmd.pattern, "/foo\\");
}
#[test]
fn test_parse_mask_20() {
assert_eq!(parse_mask_cmd("!/foo"), Err(Errno::EINVAL));
assert_eq!(parse_mask_cmd("=/foo"), Err(Errno::EINVAL));
assert_eq!(parse_mask_cmd("*/foo"), Err(Errno::EINVAL));
assert_eq!(parse_mask_cmd(""), Err(Errno::EINVAL));
}
#[test]
fn test_parse_mask_21() {
assert_eq!(parse_mask_cmd("+/a:/b:/c:/d"), Err(Errno::EINVAL));
}
#[test]
fn test_parse_mask_22() {
let cmd = parse_mask_cmd("+/proc/*/cmdline").unwrap();
assert_eq!(cmd.pattern, "/proc/*/cmdline");
let cmd = parse_mask_cmd("+/*/*msg").unwrap();
assert_eq!(cmd.pattern, "/*/*msg");
let cmd = parse_mask_cmd("+/home/**").unwrap();
assert_eq!(cmd.pattern, "/home/**");
}
const MKNOD_CMD: &[(&str, SFlag, bool)] = &[
("mkdir", SFlag::S_IFDIR, false),
("mkfile", SFlag::S_IFREG, false),
("mkfifo", SFlag::S_IFIFO, false),
("mkdir-try", SFlag::S_IFDIR, true),
("mkfile-try", SFlag::S_IFREG, true),
("mkfifo-try", SFlag::S_IFIFO, true),
];
#[test]
fn test_parse_mknod_1() {
for (cmd, kind, r#try) in MKNOD_CMD {
let cmd = parse_mknod_cmd(&format!("{cmd}^")).unwrap();
assert_eq!(cmd.op, '^');
assert_eq!(cmd.kind, *kind);
assert_eq!(cmd.path, None);
assert_eq!(cmd.mode, None);
assert_eq!(cmd.r#try, *r#try);
}
}
#[test]
fn test_parse_mknod_2() {
for (cmd, _, _) in MKNOD_CMD {
assert_eq!(parse_mknod_cmd(&format!("{cmd}^/")), Err(Errno::EINVAL));
assert_eq!(parse_mknod_cmd(&format!("{cmd}^/foo")), Err(Errno::EINVAL));
assert_eq!(parse_mknod_cmd(&format!("{cmd}^:")), Err(Errno::EINVAL));
}
}
#[test]
fn test_parse_mknod_3() {
for (cmd, kind, r#try) in MKNOD_CMD {
let cmd = parse_mknod_cmd(&format!("{cmd}-/dev/mynode")).unwrap();
assert_eq!(cmd.op, '-');
assert_eq!(cmd.kind, *kind);
assert_eq!(cmd.path, Some("/dev/mynode".to_string()));
assert_eq!(cmd.mode, None);
assert_eq!(cmd.r#try, *r#try);
}
}
#[test]
fn test_parse_mknod_4() {
for (cmd, _, _) in MKNOD_CMD {
let cmd = parse_mknod_cmd(&format!("{cmd}-/foo:bar:baz")).unwrap();
assert_eq!(cmd.path, Some("/foo:bar:baz".to_string()));
}
}
#[test]
fn test_parse_mknod_5() {
for (cmd, _, _) in MKNOD_CMD {
assert_eq!(parse_mknod_cmd(&format!("{cmd}-")), Err(Errno::EINVAL));
}
}
#[test]
fn test_parse_mknod_6() {
for (cmd, kind, r#try) in MKNOD_CMD {
let cmd = parse_mknod_cmd(&format!("{cmd}+/dev/mynode")).unwrap();
assert_eq!(cmd.op, '+');
assert_eq!(cmd.kind, *kind);
assert_eq!(cmd.path, Some("/dev/mynode".to_string()));
assert_eq!(cmd.mode, None);
assert_eq!(cmd.r#try, *r#try);
}
}
#[test]
fn test_parse_mknod_7() {
for (cmd, kind, r#try) in MKNOD_CMD {
let cmd = parse_mknod_cmd(&format!("{cmd}+/dev/mynode:0600")).unwrap();
assert_eq!(cmd.op, '+');
assert_eq!(cmd.kind, *kind);
assert_eq!(cmd.path, Some("/dev/mynode".to_string()));
assert_eq!(cmd.mode, Some("0600".to_string()));
assert_eq!(cmd.r#try, *r#try);
}
}
#[test]
fn test_parse_mknod_8() {
for (cmd, _, _) in MKNOD_CMD {
assert_eq!(parse_mknod_cmd(&format!("{cmd}+")), Err(Errno::EINVAL));
assert_eq!(parse_mknod_cmd(&format!("{cmd}+:")), Err(Errno::EINVAL));
assert_eq!(parse_mknod_cmd(&format!("{cmd}+::")), Err(Errno::EINVAL));
}
}
#[test]
fn test_parse_mknod_9() {
for (cmd, _, _) in MKNOD_CMD {
let cmd = parse_mknod_cmd(&format!("{cmd}+/dev/foo\\:bar")).unwrap();
assert_eq!(cmd.path, Some("/dev/foo:bar".to_string()));
assert_eq!(cmd.mode, None);
}
}
#[test]
fn test_parse_mknod_10() {
for (cmd, _, _) in MKNOD_CMD {
let cmd = parse_mknod_cmd(&format!("{cmd}+/dev/foo:04\\:00")).unwrap();
assert_eq!(cmd.path, Some("/dev/foo".to_string()));
assert_eq!(cmd.mode, Some("04:00".to_string()));
}
}
#[test]
fn test_parse_mknod_11() {
for (cmd, _, _) in MKNOD_CMD {
let cmd = parse_mknod_cmd(&format!("{cmd}+/dev/foo\\\\bar")).unwrap();
assert_eq!(cmd.path, Some("/dev/foo\\bar".to_string()));
}
}
#[test]
fn test_parse_mknod_12() {
for (cmd, _, _) in MKNOD_CMD {
assert_eq!(
parse_mknod_cmd(&format!("{cmd}+/dev/foo\\")),
Err(Errno::EINVAL)
);
assert_eq!(parse_mknod_cmd(&format!("{cmd}!/foo")), Err(Errno::EINVAL));
assert_eq!(parse_mknod_cmd(&format!("{cmd}=/foo")), Err(Errno::EINVAL));
assert_eq!(parse_mknod_cmd(&format!("{cmd}*/foo")), Err(Errno::EINVAL));
assert_eq!(
parse_mknod_cmd(&format!("{cmd}+/a:/b:/c")),
Err(Errno::EINVAL)
);
}
assert_eq!(parse_mknod_cmd(""), Err(Errno::EINVAL));
}
#[test]
fn test_parse_mknod_13() {
for (cmd, _, _) in MKNOD_CMD {
let cmd = parse_mknod_cmd(&format!("{cmd}+/dev/foo:")).unwrap();
assert_eq!(cmd.path, Some("/dev/foo".to_string()));
assert_eq!(cmd.mode, Some("".to_string()));
}
}
#[test]
fn test_parse_mknod_14() {
for (cmd, _, _) in MKNOD_CMD {
let cmd = parse_mknod_cmd(&format!("{cmd}+/dev/foo\\\\")).unwrap();
assert_eq!(cmd.path, Some("/dev/foo\\".to_string()));
}
}
#[test]
fn test_parse_mknod_15() {
for (cmd, _, _) in MKNOD_CMD {
assert_eq!(
parse_mknod_cmd(&format!("{cmd}+/dev/foo\\nbar")),
Err(Errno::EINVAL)
);
assert_eq!(
parse_mknod_cmd(&format!("{cmd}+/dev/foo\\a")),
Err(Errno::EINVAL)
);
}
}
#[test]
fn test_parse_mknod_16() {
for (cmd, _, _) in MKNOD_CMD {
let cmd = parse_mknod_cmd(&format!("{cmd}+/a\\:b\\:c")).unwrap();
assert_eq!(cmd.path, Some("/a:b:c".to_string()));
}
}
#[test]
fn test_parse_mknod_17() {
assert_eq!(parse_mknod_cmd("mknod+/foo"), Err(Errno::EINVAL));
assert_eq!(parse_mknod_cmd("mkfil+/foo"), Err(Errno::EINVAL));
assert_eq!(parse_mknod_cmd("mk+/foo"), Err(Errno::EINVAL));
let cmd = parse_mknod_cmd("mkfile-tr+/foo").unwrap();
assert_eq!(cmd.op, '-');
assert_eq!(cmd.kind, SFlag::S_IFREG);
assert_eq!(cmd.path, Some("tr+/foo".to_string()));
assert!(!cmd.r#try);
}
#[test]
fn test_parse_mknod_18() {
for (cmd, kind, r#try) in MKNOD_CMD {
let c = parse_mknod_cmd(&format!("{cmd}+/foo\\:bar")).unwrap();
assert_eq!(c.op, '+');
assert_eq!(c.kind, *kind);
assert_eq!(c.path, Some("/foo:bar".to_string()));
assert_eq!(c.mode, None);
assert_eq!(c.r#try, *r#try);
}
}
#[test]
fn test_parse_mknod_19() {
for (cmd, kind, r#try) in MKNOD_CMD {
let c = parse_mknod_cmd(&format!("{cmd}+/a\\\\b")).unwrap();
assert_eq!(c.op, '+');
assert_eq!(c.kind, *kind);
assert_eq!(c.path, Some("/a\\b".to_string()));
assert_eq!(c.mode, None);
assert_eq!(c.r#try, *r#try);
}
}
#[test]
fn test_parse_mknod_20() {
for (cmd, kind, r#try) in MKNOD_CMD {
let c = parse_mknod_cmd(&format!("{cmd}+/foo\\:bar:0755")).unwrap();
assert_eq!(c.op, '+');
assert_eq!(c.kind, *kind);
assert_eq!(c.path, Some("/foo:bar".to_string()));
assert_eq!(c.mode, Some("0755".to_string()));
assert_eq!(c.r#try, *r#try);
}
}
#[test]
fn test_parse_mknod_21() {
for (cmd, kind, r#try) in MKNOD_CMD {
let c = parse_mknod_cmd(&format!("{cmd}+/a\\:b\\\\c:0600")).unwrap();
assert_eq!(c.op, '+');
assert_eq!(c.kind, *kind);
assert_eq!(c.path, Some("/a:b\\c".to_string()));
assert_eq!(c.mode, Some("0600".to_string()));
assert_eq!(c.r#try, *r#try);
}
}
#[test]
fn test_parse_mknod_22() {
for (cmd, _, _) in MKNOD_CMD {
assert_eq!(
parse_mknod_cmd(&format!("{cmd}+/foo\\")),
Err(Errno::EINVAL)
);
}
}
#[test]
fn test_parse_link_1() {
for (prefix, sym) in [
("link", false),
("link-try", false),
("symlink", true),
("symlink-try", true),
] {
let cmd = parse_link_cmd(&format!("{prefix}^")).unwrap();
assert_eq!(cmd.op, '^');
assert_eq!(cmd.dst, None);
assert_eq!(cmd.src, None);
assert_eq!(cmd.sym, sym);
assert_eq!(cmd.r#try, prefix == "symlink-try" || prefix == "link-try");
}
}
#[test]
fn test_parse_link_2() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
assert_eq!(parse_link_cmd(&format!("{prefix}^/")), Err(Errno::EINVAL));
assert_eq!(
parse_link_cmd(&format!("{prefix}^/foo")),
Err(Errno::EINVAL)
);
assert_eq!(parse_link_cmd(&format!("{prefix}^:")), Err(Errno::EINVAL));
}
}
#[test]
fn test_parse_link_3() {
for (prefix, sym) in [
("link", false),
("link-try", false),
("symlink", true),
("symlink-try", true),
] {
let cmd = parse_link_cmd(&format!("{prefix}-/lib/libfoo.so")).unwrap();
assert_eq!(cmd.op, '-');
assert_eq!(cmd.dst, Some("/lib/libfoo.so".to_string()));
assert_eq!(cmd.src, None);
assert_eq!(cmd.sym, sym);
assert_eq!(cmd.r#try, prefix == "symlink-try" || prefix == "link-try");
}
}
#[test]
fn test_parse_link_4() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
assert_eq!(parse_link_cmd(&format!("{prefix}-")), Err(Errno::EINVAL));
}
}
#[test]
fn test_parse_link_5() {
for (prefix, sym) in [
("link", false),
("link-try", false),
("symlink", true),
("symlink-try", true),
] {
let cmd = parse_link_cmd(&format!("{prefix}-/foo:bar")).unwrap();
assert_eq!(cmd.op, '-');
assert_eq!(cmd.dst, Some("/foo:bar".to_string()));
assert_eq!(cmd.src, None);
assert_eq!(cmd.sym, sym);
}
}
#[test]
fn test_parse_link_6() {
for (prefix, sym) in [
("link", false),
("link-try", false),
("symlink", true),
("symlink-try", true),
] {
let cmd =
parse_link_cmd(&format!("{prefix}+/lib/libfoo.so:/usr/lib/libfoo.so")).unwrap();
assert_eq!(cmd.op, '+');
assert_eq!(cmd.dst, Some("/lib/libfoo.so".to_string()));
assert_eq!(cmd.src, Some("/usr/lib/libfoo.so".to_string()));
assert_eq!(cmd.sym, sym);
assert_eq!(cmd.r#try, prefix == "symlink-try" || prefix == "link-try");
}
}
#[test]
fn test_parse_link_7() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
assert_eq!(
parse_link_cmd(&format!("{prefix}+/dst")),
Err(Errno::EINVAL)
);
}
}
#[test]
fn test_parse_link_8() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
assert_eq!(
parse_link_cmd(&format!("{prefix}+/dst:")),
Err(Errno::EINVAL)
);
}
}
#[test]
fn test_parse_link_9() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
assert_eq!(
parse_link_cmd(&format!("{prefix}+:/src")),
Err(Errno::EINVAL)
);
}
}
#[test]
fn test_parse_link_10() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
assert_eq!(parse_link_cmd(&format!("{prefix}+:")), Err(Errno::EINVAL));
assert_eq!(parse_link_cmd(&format!("{prefix}+")), Err(Errno::EINVAL));
}
}
#[test]
fn test_parse_link_11() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
assert_eq!(
parse_link_cmd(&format!("{prefix}+/dst:/src:/extra")),
Err(Errno::EINVAL)
);
}
}
#[test]
fn test_parse_link_12() {
for (prefix, sym) in [
("link", false),
("link-try", false),
("symlink", true),
("symlink-try", true),
] {
let cmd = parse_link_cmd(&format!("{prefix}+/foo\\:bar:/src")).unwrap();
assert_eq!(cmd.op, '+');
assert_eq!(cmd.dst, Some("/foo:bar".to_string()));
assert_eq!(cmd.src, Some("/src".to_string()));
assert_eq!(cmd.sym, sym);
}
}
#[test]
fn test_parse_link_13() {
for (prefix, sym) in [
("link", false),
("link-try", false),
("symlink", true),
("symlink-try", true),
] {
let cmd = parse_link_cmd(&format!("{prefix}+/dst:/foo\\:bar")).unwrap();
assert_eq!(cmd.dst, Some("/dst".to_string()));
assert_eq!(cmd.src, Some("/foo:bar".to_string()));
assert_eq!(cmd.sym, sym);
}
}
#[test]
fn test_parse_link_14() {
for (prefix, sym) in [
("link", false),
("link-try", false),
("symlink", true),
("symlink-try", true),
] {
let cmd = parse_link_cmd(&format!("{prefix}+/foo\\\\bar:/src\\\\baz")).unwrap();
assert_eq!(cmd.dst, Some("/foo\\bar".to_string()));
assert_eq!(cmd.src, Some("/src\\baz".to_string()));
assert_eq!(cmd.sym, sym);
}
}
#[test]
fn test_parse_link_15() {
for (prefix, sym) in [
("link", false),
("link-try", false),
("symlink", true),
("symlink-try", true),
] {
let cmd = parse_link_cmd(&format!("{prefix}+/a\\:b\\\\c:/src")).unwrap();
assert_eq!(cmd.dst, Some("/a:b\\c".to_string()));
assert_eq!(cmd.src, Some("/src".to_string()));
assert_eq!(cmd.sym, sym);
}
}
#[test]
fn test_parse_link_16() {
for bogus in ["sym", "ln", "hardlink", "lnk", ""] {
for op in ["+/a:/b", "-/a", "^"] {
assert_eq!(parse_link_cmd(&format!("{bogus}{op}")), Err(Errno::EINVAL));
}
}
}
#[test]
fn test_parse_link_17() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
assert_eq!(
parse_link_cmd(&format!("{prefix}!/a:/b")),
Err(Errno::EINVAL)
);
assert_eq!(
parse_link_cmd(&format!("{prefix}=/a:/b")),
Err(Errno::EINVAL)
);
assert_eq!(parse_link_cmd(&format!("{prefix}*")), Err(Errno::EINVAL));
}
}
#[test]
fn test_parse_link_18() {
for (prefix, sym, r#try) in [
("link", false, false),
("link-try", false, true),
("symlink", true, false),
("symlink-try", true, true),
] {
let cmd = parse_link_cmd(&format!("{prefix}+/dst:/src")).unwrap();
assert_eq!(cmd.sym, sym);
assert_eq!(cmd.r#try, r#try);
assert_eq!(cmd.op, '+');
}
}
#[test]
fn test_parse_link_19() {
for (prefix, sym, r#try) in [
("link", false, false),
("link-try", false, true),
("symlink", true, false),
("symlink-try", true, true),
] {
let cmd = parse_link_cmd(&format!("{prefix}-/some/path")).unwrap();
assert_eq!(cmd.op, '-');
assert_eq!(cmd.dst, Some("/some/path".to_string()));
assert_eq!(cmd.src, None);
assert_eq!(cmd.sym, sym);
assert_eq!(cmd.r#try, r#try);
}
}
#[test]
fn test_parse_link_20() {
for (prefix, sym, r#try) in [
("link", false, false),
("link-try", false, true),
("symlink", true, false),
("symlink-try", true, true),
] {
let cmd = parse_link_cmd(&format!("{prefix}^")).unwrap();
assert_eq!(cmd.op, '^');
assert_eq!(cmd.sym, sym);
assert_eq!(cmd.r#try, r#try);
}
}
#[test]
fn test_parse_link_21() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
let cmd = parse_link_cmd(&format!("{prefix}-/path/with spaces/and:colons")).unwrap();
assert_eq!(cmd.dst, Some("/path/with spaces/and:colons".to_string()));
assert_eq!(cmd.src, None);
}
}
#[test]
fn test_parse_link_22() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
assert_eq!(parse_link_cmd(&format!("{prefix}+::")), Err(Errno::EINVAL));
}
}
#[test]
fn test_parse_link_23() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
assert_eq!(parse_link_cmd(&format!("{prefix}+:::")), Err(Errno::EINVAL));
}
}
#[test]
fn test_parse_link_24() {
for prefix in ["link", "link-try", "symlink", "symlink-try"] {
assert_eq!(
parse_link_cmd(&format!("{prefix}+/foo\\:/src")),
Err(Errno::EINVAL)
);
}
}
}