#![forbid(unsafe_code)]
use std::os::fd::AsFd;
use btoi::btoi;
use memchr::{memchr, memmem};
use nix::{errno::Errno, sys::socket::UnixAddr};
use crate::{
cookie::safe_read, io::write_all, path::XPath, retry::retry_on_eintr, unix::unix_path_bytes,
};
struct Patch {
needle: &'static [u8],
repl: &'static [u8],
}
type PatchMask = u8; type PatchStep = Option<(usize, PatchMask)>;
const TRACERPID: &[u8] = b"TracerPid:";
const NONEWPRIVS: &[u8] = b"NoNewPrivs:";
const SECCOMP: &[u8] = b"Seccomp:";
const SECCOMP_FILTERS: &[u8] = b"Seccomp_filters:";
const SPEC_SSB: &[u8] = b"Speculation_Store_Bypass:";
const SPEC_SIB: &[u8] = b"SpeculationIndirectBranch:";
const SPEC_SSB_PATCHES: &[Patch] = &[
Patch {
needle: b"unknown",
repl: b"vulnerable",
},
Patch {
needle: b"unsupported",
repl: b"vulnerable",
},
Patch {
needle: b"thread ",
repl: b"",
},
Patch {
needle: b"force ",
repl: b"",
},
Patch {
needle: b"mitigated",
repl: b"vulnerable",
},
];
const SPEC_SIB_PATCHES: &[Patch] = &[
Patch {
needle: b"unknown",
repl: b"always enabled",
},
Patch {
needle: b"unsupported",
repl: b"always enabled",
},
Patch {
needle: b"conditional",
repl: b"always",
},
Patch {
needle: b"force ",
repl: b"",
},
Patch {
needle: b"disabled",
repl: b"enabled",
},
];
const PF_TRACERPID: u8 = 1 << 0;
const PF_NONEWPRIVS: u8 = 1 << 1;
const PF_SECCOMP: u8 = 1 << 2;
const PF_SECCOMP_FILTERS: u8 = 1 << 3;
const PF_SPEC_SSB: u8 = 1 << 4;
const PF_SPEC_SIB: u8 = 1 << 5;
const PNU_BUF: usize = 0x200; const PPS_BUF: usize = 0x1000;
fn filter_lines<S, D, F>(src: S, dst: D, buf: &mut [u8], mut emit: F) -> Result<(), Errno>
where
S: AsFd,
D: AsFd + Copy,
F: FnMut(D, &[u8]) -> Result<(), Errno>,
{
let mut fill = 0usize;
let mut overlong = false;
loop {
let mut pos = 0usize;
loop {
let rest = buf.get(pos..fill).ok_or(Errno::EOVERFLOW)?;
let Some(rel) = memchr(b'\n', rest) else {
if !overlong && pos == 0 && fill == buf.len() {
overlong = true;
}
break;
};
let end = pos
.checked_add(rel)
.and_then(|i| i.checked_add(1))
.ok_or(Errno::EOVERFLOW)?;
let line = buf.get(pos..end).ok_or(Errno::EOVERFLOW)?;
if overlong {
write_all(dst, line)?;
overlong = false;
} else {
emit(dst, line)?;
}
pos = end;
}
if overlong {
write_all(dst, buf.get(..fill).ok_or(Errno::EOVERFLOW)?)?;
fill = 0;
} else {
buf.copy_within(pos..fill, 0);
fill = fill.checked_sub(pos).ok_or(Errno::EOVERFLOW)?;
}
let n = retry_on_eintr(|| safe_read(&src, buf.get_mut(fill..).ok_or(Errno::EOVERFLOW)?))?;
if n == 0 {
break; }
fill = fill.checked_add(n).ok_or(Errno::EOVERFLOW)?;
}
if fill > 0 {
let line = buf.get(..fill).ok_or(Errno::EOVERFLOW)?;
if overlong {
write_all(dst, line)?;
} else {
emit(dst, line)?;
}
}
Ok(())
}
struct ProcPidStatusMasker {
prefix_mask: u8,
}
impl ProcPidStatusMasker {
fn new() -> Self {
Self { prefix_mask: 0 }
}
fn try_emit_zero_field<Fd: AsFd + Copy>(
&mut self,
out: Fd,
line: &[u8],
) -> Result<bool, Errno> {
if (self.prefix_mask & PF_TRACERPID) == 0 && line.starts_with(TRACERPID) {
self.prefix_mask |= PF_TRACERPID;
Self::emit_zero_field(out, line, TRACERPID)?;
return Ok(true);
}
if (self.prefix_mask & PF_NONEWPRIVS) == 0 && line.starts_with(NONEWPRIVS) {
self.prefix_mask |= PF_NONEWPRIVS;
Self::emit_zero_field(out, line, NONEWPRIVS)?;
return Ok(true);
}
if (self.prefix_mask & PF_SECCOMP_FILTERS) == 0 && line.starts_with(SECCOMP_FILTERS) {
self.prefix_mask |= PF_SECCOMP_FILTERS;
Self::emit_zero_field(out, line, SECCOMP_FILTERS)?;
return Ok(true);
}
if (self.prefix_mask & PF_SECCOMP) == 0 && line.starts_with(SECCOMP) {
self.prefix_mask |= PF_SECCOMP;
Self::emit_zero_field(out, line, SECCOMP)?;
return Ok(true);
}
Ok(false)
}
fn try_emit_patch_group<Fd: AsFd + Copy>(
&mut self,
out: Fd,
line: &[u8],
) -> Result<bool, Errno> {
if (self.prefix_mask & PF_SPEC_SSB) == 0 && line.starts_with(SPEC_SSB) {
self.prefix_mask |= PF_SPEC_SSB;
let (head, value) = line.split_at(SPEC_SSB.len());
write_all(out, head)?;
return Self::emit_patch_group_value(out, value, SPEC_SSB_PATCHES);
}
if (self.prefix_mask & PF_SPEC_SIB) == 0 && line.starts_with(SPEC_SIB) {
self.prefix_mask |= PF_SPEC_SIB;
let (head, value) = line.split_at(SPEC_SIB.len());
write_all(out, head)?;
return Self::emit_patch_group_value(out, value, SPEC_SIB_PATCHES);
}
Ok(false)
}
fn emit_zero_field<Fd: AsFd + Copy>(out: Fd, line: &[u8], field: &[u8]) -> Result<(), Errno> {
let mut i = field.len();
while i < line.len() {
let b = line[i];
if b == b' ' || b == b'\t' {
i = i.checked_add(1).ok_or(Errno::EOVERFLOW)?;
} else {
break;
}
}
let start = i;
while i < line.len() && line[i].is_ascii_digit() {
i = i.checked_add(1).ok_or(Errno::EOVERFLOW)?;
}
let end = i;
let digits_len = end.checked_sub(start).ok_or(Errno::EOVERFLOW)?;
if digits_len == 0 || (digits_len == 1 && line[start] == b'0') {
write_all(out, line)?;
return Ok(());
}
write_all(out, &line[..start])?;
write_all(out, b"0\n")?;
Ok(())
}
fn apply_patch_step<Fd: AsFd + Copy>(
out: Fd,
value: &[u8],
patches: &[Patch],
applied: PatchMask,
) -> Result<PatchStep, Errno> {
if value.is_empty() || patches.is_empty() {
return Ok(None);
}
let mut best_pos: Option<usize> = None;
let mut best_idx: usize = 0;
#[expect(clippy::cast_possible_truncation)]
for (idx, p) in patches.iter().enumerate() {
if ((applied >> (idx as u32)) & 1) != 0 {
continue;
}
if p.needle.is_empty() {
continue;
}
if let Some(pos) = memmem::find(value, p.needle) {
match best_pos {
None => {
best_pos = Some(pos);
best_idx = idx;
}
Some(cur) if pos < cur => {
best_pos = Some(pos);
best_idx = idx;
}
_ => {}
}
}
}
let pos = match best_pos {
Some(pos) => pos,
None => return Ok(None),
};
let (left, after_left) = value.split_at(pos);
let needle_len = patches[best_idx].needle.len();
let (_, rest) = after_left.split_at(needle_len);
write_all(out, left)?;
write_all(out, patches[best_idx].repl)?;
let consumed = value
.len()
.checked_sub(rest.len())
.ok_or(Errno::EOVERFLOW)?;
if best_idx >= (u8::BITS as usize) {
return Err(Errno::EOVERFLOW);
}
#[expect(clippy::cast_possible_truncation)]
let bit: PatchMask = 1u8 << (best_idx as u32);
let new_mask: PatchMask = applied | bit;
Ok(Some((consumed, new_mask)))
}
fn emit_patch_group_value<Fd: AsFd + Copy>(
out: Fd,
mut value: &[u8],
patches: &[Patch],
) -> Result<bool, Errno> {
let mut applied: PatchMask = 0;
let mut any = false;
loop {
match Self::apply_patch_step(out, value, patches, applied)? {
None => {
write_all(out, value)?;
return Ok(any || !value.is_empty());
}
Some((consumed, new_mask)) => {
any = true;
if consumed > value.len() {
return Err(Errno::EOVERFLOW);
}
let (_, rest) = value.split_at(consumed);
value = rest;
applied = new_mask;
}
}
}
}
fn emit_line<Fd: AsFd + Copy>(&mut self, out: Fd, line: &[u8]) -> Result<(), Errno> {
if self.try_emit_zero_field(out, line)? {
return Ok(());
}
if self.try_emit_patch_group(out, line)? {
return Ok(());
}
write_all(out, line)
}
fn run<S: AsFd, D: AsFd>(&mut self, src: S, dst: D) -> Result<(), Errno> {
let mut buf = [0u8; PPS_BUF];
filter_lines(src, &dst, &mut buf, |out, line| self.emit_line(out, line))
}
}
fn emit_unix_line<Fd, L>(out: Fd, line: &[u8], lookup: &mut L) -> Result<(), Errno>
where
Fd: AsFd + Copy,
L: FnMut(u64) -> Option<UnixAddr>,
{
let has_nl = line.last() == Some(&b'\n');
let content = if has_nl {
&line[..line.len().saturating_sub(1)]
} else {
line
};
if let Some((inode, off)) = parse_proc_net_unix(content) {
if let Some(path) = content.get(off..) {
if let Some(addr) = lookup(inode) {
if let Some(addr) = unix_path_bytes(&addr) {
if path == XPath::from_bytes(addr).split().1.as_bytes() {
write_all(out, content.get(..off).ok_or(Errno::EOVERFLOW)?)?;
write_all(out, addr)?;
if has_nl {
write_all(out, b"\n")?;
}
return Ok(());
}
}
}
}
}
write_all(out, line)
}
fn parse_proc_net_unix(line: &[u8]) -> Option<(u64, usize)> {
let mut idx = 0usize;
let mut inode = 0u64;
for field in 0..7 {
while idx < line.len() && line[idx] == b' ' {
idx = idx.checked_add(1)?;
}
let start = idx;
while idx < line.len() && line[idx] != b' ' {
idx = idx.checked_add(1)?;
}
if start == idx {
return None; }
if field == 6 {
inode = btoi::<u64>(line.get(start..idx)?).ok()?;
}
}
if line.get(idx) == Some(&b' ') {
let off = idx.checked_add(1)?;
if off < line.len() {
return Some((inode, off));
}
}
None
}
pub(crate) fn mask_proc_pid_status<S: AsFd, D: AsFd>(src: S, dst: D) -> Result<(), Errno> {
ProcPidStatusMasker::new().run(src, dst)
}
pub(crate) fn mask_proc_net_unix<S, D, L>(src: S, dst: D, mut lookup: L) -> Result<(), Errno>
where
S: AsFd,
D: AsFd,
L: FnMut(u64) -> Option<UnixAddr>,
{
let mut buf = [0u8; PNU_BUF];
filter_lines(src, &dst, &mut buf, |out, line| {
emit_unix_line(out, line, &mut lookup)
})
}
#[cfg(test)]
mod tests {
use nix::{
fcntl::OFlag,
unistd::{pipe2, read, write},
};
use super::*;
const PROC_NET_UNIX_HDR: &[u8] = b"Num RefCount Protocol Flags Type St Inode Path\n";
fn run_mask(input: &[u8]) -> Result<Vec<u8>, Errno> {
let (in_rd, in_wr) = pipe2(OFlag::O_CLOEXEC)?;
let (out_rd, out_wr) = pipe2(OFlag::O_CLOEXEC)?;
let mut off = 0usize;
while off < input.len() {
match write(&in_wr, &input[off..]) {
Ok(0) => break,
Ok(n) => {
off = off.checked_add(n).ok_or(Errno::EOVERFLOW)?;
}
Err(e) => return Err(e),
}
}
drop(in_wr);
mask_proc_pid_status(&in_rd, &out_wr)?;
drop(out_wr);
let mut out = Vec::new();
let mut buf = [0u8; 1024];
loop {
match retry_on_eintr(|| read(&out_rd, &mut buf)) {
Ok(0) => break,
Ok(n) => out.extend_from_slice(&buf[..n]),
Err(e) => return Err(e),
}
}
Ok(out)
}
fn run_unix_mask(input: &[u8], map: &[(u64, &[u8])]) -> Result<Vec<u8>, Errno> {
let (in_rd, in_wr) = pipe2(OFlag::O_CLOEXEC)?;
let (out_rd, out_wr) = pipe2(OFlag::O_CLOEXEC)?;
let mut off = 0usize;
while off < input.len() {
match write(&in_wr, &input[off..]) {
Ok(0) => break,
Ok(n) => off = off.checked_add(n).ok_or(Errno::EOVERFLOW)?,
Err(e) => return Err(e),
}
}
drop(in_wr);
mask_proc_net_unix(&in_rd, &out_wr, |inode| {
map.iter()
.find(|(i, _)| *i == inode)
.map(|(_, p)| UnixAddr::new(std::path::Path::new(std::str::from_utf8(p).unwrap())))
.transpose()
.unwrap()
})?;
drop(out_wr);
let mut out = Vec::new();
let mut buf = [0u8; 1024];
loop {
match retry_on_eintr(|| read(&out_rd, &mut buf)) {
Ok(0) => break,
Ok(n) => out.extend_from_slice(&buf[..n]),
Err(e) => return Err(e),
}
}
Ok(out)
}
#[test]
fn test_proc_pid_status_1() {
let input = b"TracerPid:\t123\nNoNewPrivs:\t1\nSeccomp:\t2\nSeccomp_filters:\t7\n";
let out = run_mask(input).unwrap();
let expected = b"TracerPid:\t0\nNoNewPrivs:\t0\nSeccomp:\t0\nSeccomp_filters:\t0\n";
assert_eq!(&out, expected);
}
#[test]
fn test_proc_pid_status_2() {
let input = b"TracerPid:\t 456\nSeccomp:\t\t 2\n";
let out = run_mask(input).unwrap();
let expected = b"TracerPid:\t 0\nSeccomp:\t\t 0\n";
assert_eq!(&out, expected);
}
#[test]
fn test_proc_pid_status_3() {
let input = b"TracerPid:\t0\nNoNewPrivs:\t0\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, input);
}
#[test]
fn test_proc_pid_status_4() {
let input = b"TracerPid:\t123 extra_garbage\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, b"TracerPid:\t0\n");
}
#[test]
fn test_proc_pid_status_5() {
let input = b"Speculation_Store_Bypass: \t\tthread mitigated\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, b"Speculation_Store_Bypass: \t\tvulnerable\n");
}
#[test]
fn test_proc_pid_status_6() {
let input = b"Speculation_Store_Bypass:\t force mitigated\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, b"Speculation_Store_Bypass:\t vulnerable\n");
}
#[test]
fn test_proc_pid_status_7() {
let input = b"Speculation_Store_Bypass:\tthread force mitigated\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, b"Speculation_Store_Bypass:\tvulnerable\n");
}
#[test]
fn test_proc_pid_status_8() {
let input = b"SpeculationIndirectBranch:\t conditional force disabled\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, b"SpeculationIndirectBranch:\t always enabled\n");
}
#[test]
fn test_proc_pid_status_9() {
let input = b"SpeculationIndirectBranch: \talways force disabled\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, b"SpeculationIndirectBranch: \talways enabled\n");
}
#[test]
fn test_proc_pid_status_10() {
let input = b"SpeculationIndirectBranch:\t \tconditional enabled\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, b"SpeculationIndirectBranch:\t \talways enabled\n");
}
#[test]
fn test_proc_pid_status_11() {
let input = concat!(
"NoNewPrivs:\t1\n",
"TracerPid:\t42\n",
"Seccomp:\t2\n",
"Seccomp_filters:\t3\n",
)
.as_bytes();
let expected = concat!(
"NoNewPrivs:\t0\n",
"TracerPid:\t0\n",
"Seccomp:\t0\n",
"Seccomp_filters:\t0\n",
)
.as_bytes();
let out = run_mask(input).unwrap();
assert_eq!(&out, expected);
}
#[test]
fn test_proc_pid_status_12() {
let input = concat!(
"SpeculationIndirectBranch: \t conditional enabled\n",
"NoNewPrivs:\t1\n",
"Speculation_Store_Bypass: \t\t thread force mitigated\n",
"Seccomp:\t 2\n",
"TracerPid: \t42\n",
"Seccomp_filters: \t\t 3\n",
)
.as_bytes();
let expected = concat!(
"SpeculationIndirectBranch: \t always enabled\n",
"NoNewPrivs:\t0\n",
"Speculation_Store_Bypass: \t\t vulnerable\n",
"Seccomp:\t 0\n",
"TracerPid: \t0\n",
"Seccomp_filters: \t\t 0\n",
)
.as_bytes();
let out = run_mask(input).unwrap();
assert_eq!(&out, expected);
}
#[test]
fn test_proc_pid_status_13() {
let input = b"TracerPid:\t \t \t 999\nNoNewPrivs:\t\t\t3\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, b"TracerPid:\t \t \t 0\nNoNewPrivs:\t\t\t0\n");
}
#[test]
fn test_proc_pid_status_14() {
let input = b"Seccomp:\t\t\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, input);
}
#[test]
fn test_proc_pid_status_15() {
let input = b"Name:\tcat\nState:\tS (sleeping)\nThreads:\t4\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, input);
}
#[test]
fn test_proc_pid_status_16() {
let input = b"Name:\tSeccomp:\t2 (not a header)\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, input);
}
#[test]
fn test_proc_pid_status_17() {
let input = b"TracerPid:\t4194304\n";
let out = run_mask(input).unwrap();
assert_eq!(&out, b"TracerPid:\t0\n");
}
#[test]
fn test_proc_pid_status_18() {
let mut line = vec![b'A'; 10000];
line.push(b'\n');
let out = run_mask(&line).unwrap();
assert_eq!(&out, &line);
}
#[test]
fn test_proc_pid_status_19() {
let input = concat!(
"Name:\tmyproc\n",
"TracerPid:\t42\n",
"Speculation_Store_Bypass:\tthread force mitigated\n",
"NoNewPrivs:\t1\n",
"SpeculationIndirectBranch:\t conditional force disabled\n",
"Seccomp:\t2\n",
"Threads:\t5\n",
"Seccomp_filters:\t3\n",
)
.as_bytes();
let expected = concat!(
"Name:\tmyproc\n",
"TracerPid:\t0\n",
"Speculation_Store_Bypass:\tvulnerable\n",
"NoNewPrivs:\t0\n",
"SpeculationIndirectBranch:\t always enabled\n",
"Seccomp:\t0\n",
"Threads:\t5\n",
"Seccomp_filters:\t0\n",
)
.as_bytes();
let out = run_mask(input).unwrap();
assert_eq!(&out, expected);
}
#[test]
fn test_proc_pid_status_20() {
let input = b"Name:\tno_nl_at_end";
let out = run_mask(input).unwrap();
assert_eq!(&out, input);
}
#[test]
fn test_proc_pid_status_21() {
let mut long = vec![b'X'; 4096];
long.push(b'\n');
let input = [
b"Name:\tmyproc\n".as_ref(),
b"State:\tS (sleeping)\n".as_ref(),
b"TracerPid:\t 456\n".as_ref(),
b"NoNewPrivs:\t1\n".as_ref(),
b"Speculation_Store_Bypass: \tthread force mitigated\n".as_ref(),
b"SpeculationIndirectBranch:\t conditional force disabled\n".as_ref(),
b"Seccomp:\t2\n".as_ref(),
b"Threads:\t5\n".as_ref(),
b"Seccomp_filters:\t3\n".as_ref(),
b"Note:\tSeccomp:\t2 (not a header)\n".as_ref(),
&long,
b"Name:\tno_nl_at_end".as_ref(),
]
.concat();
let expected = [
b"Name:\tmyproc\n".as_ref(),
b"State:\tS (sleeping)\n".as_ref(),
b"TracerPid:\t 0\n".as_ref(),
b"NoNewPrivs:\t0\n".as_ref(),
b"Speculation_Store_Bypass: \tvulnerable\n".as_ref(),
b"SpeculationIndirectBranch:\t always enabled\n".as_ref(),
b"Seccomp:\t0\n".as_ref(),
b"Threads:\t5\n".as_ref(),
b"Seccomp_filters:\t0\n".as_ref(),
b"Note:\tSeccomp:\t2 (not a header)\n".as_ref(),
&long,
b"Name:\tno_nl_at_end".as_ref(),
]
.concat();
let out = run_mask(&input).unwrap();
assert_eq!(&out, &expected);
}
#[test]
fn test_proc_net_unix_1() {
let out = run_unix_mask(PROC_NET_UNIX_HDR, &[]).unwrap();
assert_eq!(&out, PROC_NET_UNIX_HDR);
}
#[test]
fn test_proc_net_unix_2() {
let input = b"0000000000000000: 00000002 00000000 00000000 0001 01 16993800 foo.sock\n";
let out = run_unix_mask(input, &[(16993800, b"/tmp/dir/foo.sock")]).unwrap();
assert_eq!(
&out,
b"0000000000000000: 00000002 00000000 00000000 0001 01 16993800 /tmp/dir/foo.sock\n"
);
}
#[test]
fn test_proc_net_unix_3() {
let input = b"0000000000000000: 00000002 00000000 00000000 0001 01 4242 foo.sock\n";
let out = run_unix_mask(input, &[]).unwrap();
assert_eq!(&out, input);
}
#[test]
fn test_proc_net_unix_4() {
let input = b"0000000000000000: 00000002 00000000 00000000 0001 01 7 foo.sock\n";
let out = run_unix_mask(input, &[(7, b"/tmp/bar.sock")]).unwrap();
assert_eq!(&out, input);
}
#[test]
fn test_proc_net_unix_5() {
let input = b"0000000000000000: 00000002 00000000 00010000 0001 01 9 @abstract\n";
let out = run_unix_mask(input, &[]).unwrap();
assert_eq!(&out, input);
}
#[test]
fn test_proc_net_unix_6() {
let input = b"0000000000000000: 00000002 00000000 00000000 0001 01 55 @foo\n";
let out = run_unix_mask(input, &[(55, b"/tmp/@foo")]).unwrap();
assert_eq!(
&out,
b"0000000000000000: 00000002 00000000 00000000 0001 01 55 /tmp/@foo\n"
);
}
#[test]
fn test_proc_net_unix_7() {
let input = b"0000000000000000: 00000002 00000000 00000000 0001 01 12345\n";
let out = run_unix_mask(input, &[(12345, b"/tmp/x.sock")]).unwrap();
assert_eq!(&out, input);
}
#[test]
fn test_proc_net_unix_8() {
let input = b"0000000000000000: 00000002 00000000 00000000 0001 01 42 foo.sock\n";
let out = run_unix_mask(input, &[(42, b"/run/foo.sock")]).unwrap();
assert_eq!(
&out,
b"0000000000000000: 00000002 00000000 00000000 0001 01 42 /run/foo.sock\n"
);
}
#[test]
fn test_proc_net_unix_9() {
let input = [
PROC_NET_UNIX_HDR,
b"0000000000000000: 00000002 00000000 00000000 0001 01 100 a.sock\n".as_ref(),
b"0000000000000000: 00000002 00000000 00010000 0001 01 101 @abs\n".as_ref(),
b"0000000000000000: 00000003 00000000 00000000 0001 03 102 b.sock".as_ref(),
]
.concat();
let expected = [
PROC_NET_UNIX_HDR,
b"0000000000000000: 00000002 00000000 00000000 0001 01 100 /x/a.sock\n".as_ref(),
b"0000000000000000: 00000002 00000000 00010000 0001 01 101 @abs\n".as_ref(),
b"0000000000000000: 00000003 00000000 00000000 0001 03 102 /y/b.sock".as_ref(),
]
.concat();
let out = run_unix_mask(&input, &[(100, b"/x/a.sock"), (102, b"/y/b.sock")]).unwrap();
assert_eq!(&out, &expected);
}
#[test]
fn test_proc_net_unix_10() {
let line = b"0000000000000000: 00000002 00000000 00000000 0001 01 555 p.sock";
let (inode, off) = parse_proc_net_unix(line).unwrap();
assert_eq!(inode, 555);
assert_eq!(&line[off..], b"p.sock");
assert!(
parse_proc_net_unix(b"Num RefCount Protocol Flags Type St Inode Path")
.is_none()
);
assert!(
parse_proc_net_unix(b"0000000000000000: 00000002 00000000 00000000 0001 01 12345")
.is_none()
);
}
}