use std::{
borrow::{Borrow, Cow},
cmp::Ordering,
collections::VecDeque,
ffi::{CStr, OsStr, OsString},
ops::{Deref, RangeBounds},
os::{
fd::RawFd,
unix::ffi::{OsStrExt, OsStringExt},
},
path::{Component, Path, PathBuf},
sync::{Arc, LazyLock},
};
use bitflags::bitflags;
use btoi::btoi;
use itoa::Integer;
use memchr::{
arch::all::{is_equal, is_prefix, is_suffix, memchr::One},
memchr, memmem, memrchr,
};
use nix::{
errno::Errno,
fcntl::{OFlag, AT_FDCWD},
libc::pid_t,
unistd::Pid,
NixPath,
};
use tinyvec::TinyVec;
use crate::{
compat::{openat2, OpenHow, ResolveFlag},
config::{MAGIC_PREFIX, MAXSYMLINKS},
fs::tgkill,
log::log_untrusted_buf,
lookup::FileType,
retry::retry_on_eintr,
sandbox::{Flags, Options},
};
#[macro_export]
macro_rules! xpath {
($($arg:tt)*) => {
XPathBuf::from(format!($($arg)*))
};
}
pub const PATH_MAX: usize = 4096;
pub const PATH_MIN: usize = 64;
pub const PATH_CAP: usize = 128;
static EMPATH: LazyLock<u64> = LazyLock::new(|| {
let path: &'static [u8] = Box::leak(Box::new([0u8]));
path.as_ptr() as u64
});
static EMARGV: LazyLock<u64> = LazyLock::new(|| {
let empty_str: &'static [u8] = Box::leak(Box::new([0u8]));
let empty_ptr = empty_str.as_ptr() as *const libc::c_char;
let argv: &'static [*const libc::c_char; 2] =
Box::leak(Box::new([empty_ptr, std::ptr::null()]));
argv.as_ptr() as u64
});
static EMENVP: LazyLock<u64> = LazyLock::new(|| {
let envp: &'static [*const libc::c_char; 1] = Box::leak(Box::new([std::ptr::null()]));
envp.as_ptr() as u64
});
static DOTDOT: LazyLock<u64> = LazyLock::new(|| {
let path: &'static [u8] = Box::leak(Box::new(*b"..\0"));
path.as_ptr() as u64
});
#[inline(always)]
pub(crate) fn empty_path() -> u64 {
*EMPATH
}
#[inline(always)]
pub(crate) fn empty_argv() -> u64 {
*EMARGV
}
#[inline(always)]
pub(crate) fn empty_envp() -> u64 {
*EMENVP
}
#[inline(always)]
pub(crate) fn dotdot_with_nul() -> u64 {
*DOTDOT
}
#[expect(clippy::derived_hash_with_manual_eq)]
#[derive(Clone, Default, Hash, Ord, PartialOrd)]
pub struct XPathBuf(pub(crate) TinyVec<[u8; PATH_CAP]>);
impl Eq for XPathBuf {}
impl PartialEq for XPathBuf {
fn eq(&self, other: &Self) -> bool {
is_equal(&self.0, &other.0)
}
}
impl PartialEq<XPath> for XPathBuf {
fn eq(&self, other: &XPath) -> bool {
is_equal(self.as_bytes(), other.as_bytes())
}
}
impl PartialEq<XPathBuf> for XPath {
fn eq(&self, other: &XPathBuf) -> bool {
is_equal(self.as_bytes(), other.as_bytes())
}
}
impl Deref for XPathBuf {
type Target = XPath;
fn deref(&self) -> &XPath {
XPath::from_bytes(&self.0)
}
}
impl Borrow<XPath> for XPathBuf {
fn borrow(&self) -> &XPath {
self.deref()
}
}
impl Borrow<XPath> for Arc<XPathBuf> {
fn borrow(&self) -> &XPath {
self.deref()
}
}
#[expect(clippy::derived_hash_with_manual_eq)]
#[repr(transparent)]
#[derive(Hash, Ord, PartialOrd)]
pub struct XPath(OsStr);
impl Eq for XPath {}
impl PartialEq for XPath {
fn eq(&self, other: &Self) -> bool {
is_equal(self.0.as_bytes(), other.0.as_bytes())
}
}
impl ToOwned for XPath {
type Owned = XPathBuf;
fn to_owned(&self) -> Self::Owned {
XPathBuf::from(self.as_bytes())
}
}
impl AsRef<XPath> for XPathBuf {
fn as_ref(&self) -> &XPath {
self.as_xpath()
}
}
impl AsRef<Path> for XPathBuf {
fn as_ref(&self) -> &Path {
self.as_path()
}
}
impl AsRef<OsStr> for XPathBuf {
fn as_ref(&self) -> &OsStr {
self.as_os_str()
}
}
impl From<&XPath> for XPathBuf {
fn from(path: &XPath) -> Self {
path.as_bytes().into()
}
}
impl From<PathBuf> for XPathBuf {
fn from(pbuf: PathBuf) -> Self {
pbuf.into_os_string().into()
}
}
impl From<&OsStr> for XPathBuf {
fn from(ostr: &OsStr) -> Self {
ostr.as_bytes().into()
}
}
impl From<OsString> for XPathBuf {
fn from(os: OsString) -> Self {
if os.as_bytes().len() <= PATH_CAP {
os.as_bytes().into()
} else {
Self(TinyVec::Heap(os.into_vec()))
}
}
}
impl From<String> for XPathBuf {
fn from(s: String) -> Self {
if s.len() <= PATH_CAP {
s.as_bytes().into()
} else {
Self(TinyVec::Heap(s.into_bytes()))
}
}
}
impl From<&str> for XPathBuf {
fn from(s: &str) -> Self {
let mut tv = TinyVec::new();
tv.extend_from_slice(s.as_bytes());
Self(tv)
}
}
impl From<Cow<'_, str>> for XPathBuf {
fn from(cow: Cow<'_, str>) -> Self {
if cow.len() <= PATH_CAP {
return cow.as_bytes().into();
}
match cow {
Cow::Borrowed(s) => Self::from(s),
Cow::Owned(s) => Self::from(s),
}
}
}
impl From<&[u8]> for XPathBuf {
fn from(bytes: &[u8]) -> Self {
let mut tv = TinyVec::new();
tv.extend_from_slice(bytes);
Self(tv)
}
}
impl From<Vec<u8>> for XPathBuf {
fn from(vec: Vec<u8>) -> Self {
if vec.len() <= PATH_CAP {
vec.as_slice().into()
} else {
Self(TinyVec::Heap(vec))
}
}
}
impl From<VecDeque<u8>> for XPathBuf {
fn from(mut vec: VecDeque<u8>) -> Self {
if vec.len() <= PATH_CAP {
vec.make_contiguous();
vec.as_slices().0.into()
} else {
Self(TinyVec::Heap(Vec::from(vec)))
}
}
}
impl From<pid_t> for XPathBuf {
fn from(pid: pid_t) -> Self {
let mut buf = itoa::Buffer::new();
buf.format(pid).into()
}
}
impl std::ops::Deref for XPath {
type Target = Path;
fn deref(&self) -> &Self::Target {
self.as_path()
}
}
impl AsRef<Path> for XPath {
fn as_ref(&self) -> &Path {
self.as_path()
}
}
impl AsRef<OsStr> for XPath {
fn as_ref(&self) -> &OsStr {
self.as_os_str()
}
}
impl AsRef<XPath> for &XPath {
fn as_ref(&self) -> &XPath {
self
}
}
impl std::fmt::Display for XPathBuf {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", mask_path(self.as_path()))
}
}
impl std::fmt::Debug for XPathBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", mask_path(self.as_path()))
}
}
impl serde::Serialize for XPathBuf {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{self}"))
}
}
impl NixPath for XPathBuf {
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn len(&self) -> usize {
self.0.len()
}
fn with_nix_path<T, F>(&self, f: F) -> Result<T, Errno>
where
F: FnOnce(&CStr) -> T,
{
self.as_os_str().with_nix_path(f)
}
}
impl std::fmt::Display for XPath {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", mask_path(self.as_path()))
}
}
impl std::fmt::Debug for XPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", mask_path(self.as_path()))
}
}
impl serde::Serialize for XPath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{self}"))
}
}
impl NixPath for XPath {
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn len(&self) -> usize {
self.0.len()
}
fn with_nix_path<T, F>(&self, f: F) -> Result<T, Errno>
where
F: FnOnce(&CStr) -> T,
{
self.as_os_str().with_nix_path(f)
}
}
impl redix::RaxKey for XPathBuf {
type Output = XPathBuf;
fn encode(self) -> Self::Output {
self
}
fn to_buf(&self) -> (*const u8, usize) {
let bytes = self.as_bytes();
(bytes.as_ptr(), bytes.len())
}
unsafe fn from_buf(ptr: *const u8, len: usize) -> Self::Output {
if ptr.is_null() || len == 0 {
Self::default()
} else {
std::slice::from_raw_parts(ptr, len).to_vec().into()
}
}
}
bitflags! {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct XPathCheckFlags: u8 {
const SAFE_NAME = 1 << 0;
const RESTRICT_MKBDEV = 1 << 1;
const RESTRICT_MAGICLINKS = 1 << 2;
}
}
impl XPathCheckFlags {
pub fn safe_name(self) -> bool {
self.contains(Self::SAFE_NAME)
}
pub fn restrict_mkbdev(self) -> bool {
self.contains(Self::RESTRICT_MKBDEV)
}
pub fn restrict_magiclinks(self) -> bool {
self.contains(Self::RESTRICT_MAGICLINKS)
}
}
impl From<(Flags, Options)> for XPathCheckFlags {
fn from((flags, options): (Flags, Options)) -> Self {
let mut check = Self::empty();
if !flags.allow_unsafe_filename() {
check.insert(Self::SAFE_NAME);
}
if !options.allow_unsafe_mkbdev() {
check.insert(Self::RESTRICT_MKBDEV);
}
if flags.force_no_magiclinks() {
check.insert(Self::RESTRICT_MAGICLINKS);
}
check
}
}
impl XPath {
pub fn check(
&self,
pid: Pid,
file_type: Option<&FileType>,
dir_entry: Option<&XPath>,
flags: XPathCheckFlags,
) -> Result<(), Errno> {
if file_type == Some(&FileType::Unk)
|| (flags.restrict_mkbdev() && file_type == Some(&FileType::Blk))
{
return Err(Errno::ENOENT);
}
let is_mfd = matches!(file_type, Some(FileType::Mfd));
let is_proc_dir = self.starts_with(b"/proc");
if flags.safe_name() && !is_mfd && !is_proc_dir {
self.check_name()?;
}
let (is_proc, proc_pid) = if is_proc_dir {
const LEN: usize = b"/proc".len();
let mut proc_pid = None;
let is_proc = self.len() == LEN;
if is_proc {
if let Some(p) = dir_entry {
proc_pid = btoi::<libc::pid_t>(p.as_bytes()).ok();
}
}
if proc_pid.is_none()
&& self
.get(LEN + 1)
.map(|c| c.is_ascii_digit())
.unwrap_or(false)
{
let path = self.as_bytes();
let path = &path[LEN + 1..];
let pidx = memchr(b'/', path).unwrap_or(path.len());
proc_pid = btoi::<libc::pid_t>(&path[..pidx]).ok();
}
(is_proc, proc_pid)
} else {
return Ok(());
};
if flags.restrict_magiclinks()
&& matches!(file_type, Some(t) if t.is_link())
&& !self.is_proc_self(false)
&& !self.is_proc_self(true)
{
return Err(Errno::ENOENT);
}
let proc_pid = if let Some(pid) = proc_pid {
Pid::from_raw(pid)
} else {
return Ok(());
};
if proc_pid == pid {
return Ok(());
}
if is_proc {
return Err(Errno::ENOENT);
}
let syd_pid = Pid::this();
if proc_pid == syd_pid {
return Err(Errno::ENOENT);
}
if tgkill(syd_pid, proc_pid, 0).is_ok() {
return Err(Errno::ENOENT);
}
Ok(())
}
#[expect(clippy::arithmetic_side_effects)]
pub fn check_name(&self) -> Result<(), Errno> {
let (_, name) = self.split();
let name = name.as_bytes();
let len = name.len();
if len == 0 {
return Err(Errno::EILSEQ);
}
let name_utf8 = std::str::from_utf8(name).or(Err(Errno::EILSEQ))?;
if name_utf8
.chars()
.nth(0)
.map(|c| c.is_whitespace())
.unwrap_or(false)
{
return Err(Errno::EILSEQ);
}
if name_utf8
.chars()
.last()
.map(|c| c.is_whitespace())
.unwrap_or(false)
{
return Err(Errno::EILSEQ);
}
let first_byte = name[0];
let last_byte = name[len - 1];
if !is_permitted_initial(first_byte) {
return Err(Errno::EILSEQ);
}
match len {
2 => {
let middle_byte = name[1];
if !is_permitted_middle(middle_byte) {
return Err(Errno::EILSEQ);
}
}
n if n > 2 => {
for &b in &name[1..len - 1] {
if !is_permitted_middle(b) {
return Err(Errno::EILSEQ);
}
}
}
_ => {}
}
if !is_permitted_final(last_byte) {
return Err(Errno::EILSEQ);
}
Ok(())
}
pub fn replace_proc_self<'a>(&'a self, pid: Pid) -> Cow<'a, Self> {
let p = if let Some(p) = self.split_prefix(b"/proc") {
p
} else {
return Cow::Borrowed(self);
};
let mut buf = itoa::Buffer::new();
let pid = buf.format(pid.as_raw());
let p = if let Some(p) = p.split_prefix(pid.as_bytes()) {
p
} else {
return Cow::Borrowed(self);
};
let mut pdir = XPathBuf::from("/proc/self");
pdir.append_byte(b'/');
pdir.append_bytes(p.as_bytes());
Cow::Owned(pdir)
}
#[expect(clippy::arithmetic_side_effects)]
pub fn split_prefix(&self, base: &[u8]) -> Option<&Self> {
let mut len = base.len();
if len == 0 {
return None;
} else if base == b"/" {
return Some(self);
}
let base = if base[len - 1] == b'/' {
len -= 1;
&base[..len]
} else {
base
};
if !self.starts_with(base) {
return None;
}
let raw = self.as_bytes();
let len_raw = raw.len();
if len == len_raw {
Some(XPath::from_bytes(b""))
} else if len_raw < len + 1 || raw[len] != b'/' {
None
} else {
Some(XPath::from_bytes(&raw[len + 1..]))
}
}
#[expect(clippy::arithmetic_side_effects)]
pub fn split(&self) -> (&Self, &Self) {
let bytes = match self.get(0) {
None => return (XPath::from_bytes(b""), XPath::from_bytes(b"")),
Some(b'/') if self.0.len() == 1 => {
return (
XPath::from_bytes(&self.as_bytes()[..1]),
XPath::from_bytes(&self.as_bytes()[..1]),
)
}
_ => self.as_bytes(),
};
let has_trailing_slash = bytes[bytes.len() - 1] == b'/';
let effective_length = if has_trailing_slash && bytes.len() > 1 {
bytes.len() - 1
} else {
bytes.len()
};
let last_slash_index = memrchr(b'/', &bytes[..effective_length]);
if let Some(idx) = last_slash_index {
let parent_path = if idx == 0 {
XPath::from_bytes(b"/")
} else {
XPath::from_bytes(&bytes[..idx])
};
let filename_start = idx + 1;
let filename_end = if has_trailing_slash {
bytes.len()
} else {
effective_length
};
let filename_path = XPath::from_bytes(&bytes[filename_start..filename_end]);
return (parent_path, filename_path);
}
(XPath::from_bytes(b""), self)
}
pub fn extension(&self) -> Option<&Self> {
let dot = memrchr(b'.', self.as_bytes())?;
#[expect(clippy::arithmetic_side_effects)]
if dot < self.0.len() - 1 {
Some(Self::from_bytes(&self.as_bytes()[dot + 1..]))
} else {
None
}
}
pub fn parent(&self) -> &Self {
Self::from_bytes(&self.as_bytes()[..self.parent_len()])
}
#[expect(clippy::arithmetic_side_effects)]
pub fn parent_len(&self) -> usize {
let bytes = match self.get(0) {
None => return 0,
Some(b'/') if self.len() == 1 => return 1,
_ => self.as_bytes(),
};
let has_trailing_slash = bytes[bytes.len() - 1] == b'/';
let effective_length = if has_trailing_slash && bytes.len() > 1 {
bytes.len() - 1
} else {
bytes.len()
};
let last_slash_index = memrchr(b'/', &bytes[..effective_length]);
if let Some(idx) = last_slash_index {
return if idx == 0 {
1
} else {
idx
};
}
0
}
pub fn depth(&self) -> usize {
One::new(b'/').count(self.as_bytes())
}
pub fn descendant_of(&self, root: &[u8]) -> bool {
if is_equal(root, b"/") {
return true;
} else if !self.starts_with(root) {
return false;
}
let slen = self.len();
let rlen = root.len();
match slen.cmp(&rlen) {
Ordering::Less => false,
Ordering::Equal => true,
Ordering::Greater => self.get(rlen) == Some(b'/'),
}
}
pub fn strip_root(&self) -> XPathBuf {
let src = self.as_path();
let mut dst = XPathBuf::new();
for comp in src.components() {
if matches!(
comp,
Component::RootDir | Component::Prefix(_) | Component::CurDir
) {
continue;
}
if !dst.is_empty() {
dst.append_byte(b'/');
}
dst.append_bytes(comp.as_os_str().as_bytes());
}
dst
}
pub fn strip_prefix(&self, base: &[u8]) -> Option<&Self> {
if !self.starts_with(base) {
return None;
}
let remainder = &self.as_bytes()[base.len()..];
if remainder.is_empty() {
Some(Self::from_bytes(b""))
} else if remainder[0] == b'/' {
Some(Self::from_bytes(&remainder[1..]))
} else {
None
}
}
pub fn ends_with_slash(&self) -> bool {
self.last() == Some(b'/') && !self.is_root()
}
pub fn has_parent_dot(&self) -> bool {
let bytes = self.as_bytes();
#[expect(clippy::arithmetic_side_effects)]
for index in memmem::Finder::new(b"..").find_iter(bytes) {
let is_dotdot = if index == 0 {
true
} else {
bytes[index - 1] == b'/'
} && (index + 2 == bytes.len() || bytes[index + 2] == b'/');
if is_dotdot {
return true;
}
}
false
}
pub fn is_glob(&self) -> bool {
match self.first() {
Some(b'/') => true, Some(b'@') => true, Some(b'!') => self.is_special(),
_ => {
false
}
}
}
#[inline]
pub fn is_special(&self) -> bool {
self.is_equal(b"!unnamed")
|| self.starts_with(b"!memfd:")
|| self.starts_with(b"!memfd-hugetlb:")
|| self.is_equal(b"!secretmem")
}
pub fn is_magic(&self) -> bool {
self.starts_with(MAGIC_PREFIX)
}
pub fn is_root(&self) -> bool {
self.is_equal(b"/")
}
pub fn is_procfs(&self) -> bool {
const PROC_LEN: usize = b"/proc".len();
const PROC_DIR_LEN: usize = b"/proc/".len();
match self.len() {
PROC_LEN if self.is_equal(b"/proc") => true,
PROC_DIR_LEN if self.is_equal(b"/proc/") => true,
_ => false,
}
}
pub fn is_dev(&self) -> bool {
self.starts_with(b"/dev/")
}
pub fn is_proc(&self) -> bool {
self.starts_with(b"/proc/")
}
pub fn is_kcov(&self) -> bool {
cfg!(feature = "kcov") && self.is_equal(b"/dev/kcov")
|| self.is_equal(b"/sys/kernel/debug/kcov")
}
#[cfg(feature = "kcov")]
pub fn is_kcov_mfd(&self) -> bool {
self.is_equal(b"!memfd:syd-kcov")
}
pub fn is_proc_pid(&self) -> bool {
if !self.is_proc() {
return false;
}
self.get("/proc/".len())
.map(|b| b.is_ascii_digit())
.unwrap_or(false)
}
pub fn is_proc_version(&self) -> bool {
self.is_equal(b"/proc/version")
}
pub fn is_proc_osrelease(&self) -> bool {
self.is_equal(b"/proc/sys/kernel/osrelease")
}
pub fn is_machine_id(&self) -> bool {
const MACHINE_ID: &[&[u8]] = &[
b"/etc/machine-id",
b"/etc/hostid",
b"/var/adm/hostid",
b"/sys/class/dmi/id/product_uuid",
b"/sys/devices/virtual/dmi/id/product_uuid",
];
MACHINE_ID.iter().any(|f| self.is_equal(f))
}
pub fn is_proc_status(&self) -> bool {
self.starts_with(b"/proc") && self.ends_with(b"/status")
}
pub fn is_proc_self(&self, thread: bool) -> bool {
if thread {
is_equal(self.as_bytes(), b"/proc/thread-self")
} else {
is_equal(self.as_bytes(), b"/proc/self")
}
}
#[expect(clippy::disallowed_methods)]
pub fn exists(&self, follow: bool) -> bool {
let flags = if self.is_empty() {
return false;
} else if !follow {
OFlag::O_NOFOLLOW
} else {
OFlag::empty()
};
let mut how = OpenHow::new().flags(flags | OFlag::O_PATH | OFlag::O_CLOEXEC);
if !follow {
how =
how.resolve(ResolveFlag::RESOLVE_NO_MAGICLINKS | ResolveFlag::RESOLVE_NO_SYMLINKS);
}
retry_on_eintr(|| openat2(AT_FDCWD, self, how))
.map(drop)
.is_ok()
}
pub fn is_symlink(&self) -> bool {
self.as_path().is_symlink()
}
pub fn is_dir(&self) -> bool {
self.as_path().is_dir()
}
pub fn is_file(&self) -> bool {
self.as_path().is_file()
}
pub fn is_absolute(&self) -> bool {
self.first() == Some(b'/')
}
pub fn is_relative(&self) -> bool {
!self.is_absolute()
}
pub fn is_dot(&self) -> bool {
self.is_equal(b".")
}
pub fn is_equal(&self, s: &[u8]) -> bool {
is_equal(self.as_bytes(), s)
}
pub fn starts_with(&self, base: &[u8]) -> bool {
is_prefix(self.as_bytes(), base)
}
pub fn ends_with(&self, base: &[u8]) -> bool {
is_suffix(self.as_bytes(), base)
}
pub fn contains(&self, sub: &[u8]) -> bool {
memmem::find(self.as_bytes(), sub).is_some()
}
pub fn contains_char(&self, c: u8) -> bool {
memchr(c, self.as_bytes()).is_some()
}
pub fn find(&self, sub: &[u8]) -> Option<usize> {
memmem::find(self.as_bytes(), sub)
}
pub fn find_char(&self, c: u8) -> Option<usize> {
memchr(c, self.as_bytes())
}
pub fn first(&self) -> Option<u8> {
self.as_bytes().first().copied()
}
pub fn last(&self) -> Option<u8> {
self.as_bytes().last().copied()
}
pub fn get(&self, index: usize) -> Option<u8> {
self.as_bytes().get(index).copied()
}
pub fn as_path(&self) -> &Path {
Path::new(self.as_os_str())
}
pub fn join(&self, path: &[u8]) -> XPathBuf {
let mut owned = self.to_owned();
owned.push(path);
owned
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn as_os_str(&self) -> &OsStr {
&self.0
}
pub fn from_bytes_until_nul(slice: &[u8]) -> &XPath {
let nullx = memchr(0, slice).unwrap_or(slice.len());
let slice = &slice[..nullx];
unsafe { std::mem::transmute(slice) }
}
pub const fn from_bytes(slice: &[u8]) -> &XPath {
unsafe { std::mem::transmute(slice) }
}
pub fn dotdot() -> &'static XPath {
XPath::from_bytes(b"..")
}
pub fn dot() -> &'static XPath {
XPath::from_bytes(b".")
}
pub fn root() -> &'static XPath {
XPath::from_bytes(b"/")
}
pub fn empty() -> &'static XPath {
XPath::from_bytes(b"")
}
pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &XPath {
unsafe { &*(s.as_ref() as *const OsStr as *const XPath) }
}
}
impl XPathBuf {
pub fn clean_consecutive_slashes(&mut self) {
let len = match self.len() {
0 | 1 => return,
n => n,
};
let mut write_pos = 0;
let mut read_pos = 0;
#[expect(clippy::arithmetic_side_effects)]
while read_pos < len {
if self.0[read_pos] == b'/' {
self.0[write_pos] = b'/';
write_pos += 1;
read_pos += 1;
while read_pos < len && self.0[read_pos] == b'/' {
read_pos += 1;
}
} else {
let next_slash = memchr(b'/', &self.0[read_pos..])
.map(|pos| pos + read_pos)
.unwrap_or(len);
let segment_len = next_slash - read_pos;
if read_pos != write_pos {
self.0.copy_within(read_pos..next_slash, write_pos);
}
write_pos += segment_len;
read_pos = next_slash;
}
}
self.0.truncate(write_pos);
}
pub fn replace_prefix(&mut self, old: &[u8], new: &[u8]) -> Result<(), Errno> {
if old.is_empty() {
return Err(Errno::EINVAL);
}
let s = self.as_bytes();
if is_prefix(s, new) || !is_prefix(s, old) || old == new {
return Ok(());
}
let old_len = old.len();
let new_len = new.len();
let len = self.0.len();
#[expect(clippy::arithmetic_side_effects)]
match new_len.cmp(&old_len) {
Ordering::Equal => {
self.0[..new_len].copy_from_slice(new);
}
Ordering::Less => {
let diff = old_len - new_len;
if old_len <= len {
self.0.copy_within(old_len..len, new_len);
let new_total = len.saturating_sub(diff);
self.0.truncate(new_total);
self.0[..new_len].copy_from_slice(new);
} else {
return Err(Errno::EINVAL);
}
}
Ordering::Greater => {
let add = new_len - old_len;
let new_total = len.checked_add(add).ok_or(Errno::EOVERFLOW)?;
self.try_reserve(add)?;
self.0.resize(new_total, 0);
self.0
.copy_within(old_len..(new_total - add), old_len + add);
self.0[..new_len].copy_from_slice(new);
}
}
Ok(())
}
pub fn drain<R: RangeBounds<usize>>(&mut self, range: R) {
self.0.drain(range);
}
pub fn extend(&mut self, other: &[u8]) {
self.0.extend_from_slice(other)
}
pub fn from_root(pid: Pid) -> Result<Self, Errno> {
let mut buf = itoa::Buffer::new();
let mut pfd = Vec::new();
let len = pid_t::MAX_STR_LEN
.checked_add("/root".len())
.ok_or(Errno::EOVERFLOW)?;
pfd.try_reserve(len).or(Err(Errno::ENOMEM))?;
pfd.extend_from_slice(buf.format(pid.as_raw()).as_bytes());
pfd.extend_from_slice(b"/root");
pfd.shrink_to_fit();
Ok(pfd.into())
}
pub fn from_exe(pid: Pid) -> Result<Self, Errno> {
let mut buf = itoa::Buffer::new();
let mut pfd = Vec::new();
let len = pid_t::MAX_STR_LEN
.checked_add("/exe".len())
.ok_or(Errno::EOVERFLOW)?;
pfd.try_reserve(len).or(Err(Errno::ENOMEM))?;
pfd.extend_from_slice(buf.format(pid.as_raw()).as_bytes());
pfd.extend_from_slice(b"/exe");
pfd.shrink_to_fit();
Ok(pfd.into())
}
pub fn from_cwd(pid: Pid) -> Result<Self, Errno> {
let mut buf = itoa::Buffer::new();
let mut pfd = Vec::new();
let len = pid_t::MAX_STR_LEN
.checked_add("/cwd".len())
.ok_or(Errno::EOVERFLOW)?;
pfd.try_reserve(len).or(Err(Errno::ENOMEM))?;
pfd.extend_from_slice(buf.format(pid.as_raw()).as_bytes());
pfd.extend_from_slice(b"/cwd");
pfd.shrink_to_fit();
Ok(pfd.into())
}
pub fn from_pid(pid: Pid) -> Result<Self, Errno> {
let mut buf = itoa::Buffer::new();
let mut pfd = Vec::new();
pfd.try_reserve(pid_t::MAX_STR_LEN).or(Err(Errno::ENOMEM))?;
pfd.extend_from_slice(buf.format(pid.as_raw()).as_bytes());
pfd.shrink_to_fit();
Ok(pfd.into())
}
pub fn from_task(tgid: Pid, tid: Pid) -> Result<Self, Errno> {
let mut buf = itoa::Buffer::new();
let mut pfd = Vec::new();
let len = pid_t::MAX_STR_LEN
.checked_mul(2)
.ok_or(Errno::EOVERFLOW)?
.checked_add("/task/".len())
.ok_or(Errno::EOVERFLOW)?;
pfd.try_reserve(len).or(Err(Errno::ENOMEM))?;
pfd.extend_from_slice(buf.format(tgid.as_raw()).as_bytes());
pfd.extend_from_slice(b"/task/");
pfd.extend_from_slice(buf.format(tid.as_raw()).as_bytes());
pfd.shrink_to_fit();
Ok(pfd.into())
}
pub fn from_fd(fd: RawFd) -> Result<Self, Errno> {
let mut buf = itoa::Buffer::new();
let mut pfd = Vec::new();
pfd.try_reserve(RawFd::MAX_STR_LEN).or(Err(Errno::ENOMEM))?;
pfd.extend_from_slice(buf.format(fd).as_bytes());
Ok(pfd.into())
}
pub fn from_pid_fd(pid: Pid, fd: RawFd) -> Result<Self, Errno> {
let mut buf = itoa::Buffer::new();
let mut pfd = Vec::new();
let len = pid_t::MAX_STR_LEN
.checked_add(RawFd::MAX_STR_LEN)
.ok_or(Errno::EOVERFLOW)?
.checked_add("/fd/".len())
.ok_or(Errno::EOVERFLOW)?;
pfd.try_reserve(len).or(Err(Errno::ENOMEM))?;
pfd.extend_from_slice(buf.format(pid.as_raw()).as_bytes());
pfd.extend_from_slice(b"/fd/");
pfd.extend_from_slice(buf.format(fd).as_bytes());
pfd.shrink_to_fit();
Ok(pfd.into())
}
pub fn from_self_fd(fd: RawFd) -> Result<Self, Errno> {
const LEN: usize = "thread-self/fd".len() + 1 + RawFd::MAX_STR_LEN;
let mut pfd = Vec::new();
pfd.try_reserve(LEN).or(Err(Errno::ENOMEM))?;
pfd.extend_from_slice(b"thread-self/fd");
let mut pfd: Self = pfd.into();
pfd.push_fd(fd);
pfd.shrink_to_fit();
Ok(pfd)
}
pub fn push_pid(&mut self, pid: Pid) {
let mut buf = itoa::Buffer::new();
self.push(buf.format(pid.as_raw()).as_bytes())
}
pub fn push_fd(&mut self, fd: RawFd) {
let mut buf = itoa::Buffer::new();
self.push(buf.format(fd).as_bytes())
}
pub fn push(&mut self, path: &[u8]) {
let path = XPath::from_bytes(path);
match path.find_char(b'/') {
None | Some(0) => {}
Some(n) if n == path.len().saturating_sub(1) => {}
_ => unreachable!("BUG: Path traversal detected for `{path}'"),
}
assert!(
!path.has_parent_dot(),
"BUG: Path traversal detected for `{path}'"
);
if path.first() == Some(b'/') {
self.0.clear();
} else if self.last().map(|c| c != b'/').unwrap_or(true) {
self.append_byte(b'/');
}
self.append_bytes(path.as_bytes());
}
pub fn pop(&mut self) {
self.truncate(self.parent_len());
}
pub unsafe fn pop_unchecked(&mut self) {
#[expect(clippy::arithmetic_side_effects)]
if let Some(idx) = memrchr(b'/', &self.as_bytes()[1..]) {
self.0.truncate(idx + 1);
} else if self.0.len() > 1 {
self.0.truncate(1);
}
}
pub fn append_bytes(&mut self, bytes: &[u8]) {
self.0.extend_from_slice(bytes)
}
pub fn append_byte(&mut self, byte: u8) {
self.0.push(byte)
}
pub fn pop_last(&mut self) -> Option<u8> {
self.0.pop()
}
pub fn clear(&mut self) {
self.0.clear()
}
pub fn into_vec(self) -> Vec<u8> {
self.0.to_vec()
}
pub fn into_os_string(self) -> OsString {
OsString::from_vec(self.0.to_vec())
}
pub fn truncate(&mut self, len: usize) {
self.0.truncate(len)
}
pub fn remove(&mut self, index: usize) -> u8 {
self.0.remove(index)
}
pub fn shrink_to_fit(&mut self) {
self.0.shrink_to_fit()
}
pub fn try_clone(&self) -> Result<Self, Errno> {
let mut vec = Vec::new();
vec.try_reserve(self.len()).or(Err(Errno::ENOMEM))?;
vec.extend_from_slice(self.as_bytes());
Ok(vec.into())
}
pub fn try_reserve(&mut self, additional: usize) -> Result<(), Errno> {
self.0.try_reserve(additional).or(Err(Errno::ENOMEM))
}
pub fn resize(&mut self, new_len: usize, value: u8) {
self.0.resize(new_len, value)
}
pub fn join(&self, path: &[u8]) -> XPathBuf {
let mut owned = self.clone();
owned.push(path);
owned
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn as_os_str(&self) -> &OsStr {
OsStr::from_bytes(&self.0)
}
pub fn as_path(&self) -> &Path {
Path::new(self.as_os_str())
}
pub fn as_xpath(&self) -> &XPath {
XPath::new(self.as_os_str())
}
pub fn is_symlink(&self) -> bool {
self.as_path().is_symlink()
}
pub fn is_dir(&self) -> bool {
self.as_path().is_dir()
}
pub fn is_file(&self) -> bool {
self.as_path().is_file()
}
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
self.0.as_mut_slice()
}
pub fn as_ptr(&self) -> *const u8 {
self.0.as_ptr()
}
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.0.as_mut_ptr()
}
pub fn set(&mut self, idx: usize, val: u8) {
self.0[idx] = val;
}
pub fn with_capacity(n: usize) -> Self {
Self(TinyVec::with_capacity(n))
}
pub fn capacity(&self) -> usize {
self.0.capacity()
}
pub fn empty() -> Self {
Self::new()
}
pub fn new() -> XPathBuf {
Self(TinyVec::new())
}
}
pub fn mask_path<P: AsRef<Path> + ?Sized>(path: &P) -> String {
log_untrusted_buf(path.as_ref().as_os_str().as_bytes()).0
}
fn is_permitted_initial(b: u8) -> bool {
is_permitted_byte(b) && !matches!(b, b'-' | b' ' | b'~')
}
fn is_permitted_middle(b: u8) -> bool {
is_permitted_byte(b)
}
fn is_permitted_final(b: u8) -> bool {
is_permitted_byte(b) && b != b' '
}
fn is_permitted_byte(b: u8) -> bool {
match b {
b'*' | b'?' | b'[' | b']' | b'"' | b'<' | b'>' | b'|' | b'(' | b')' | b'&' | b'\''
| b'!' | b'\\' | b';' | b'$' | b'`' => false,
0x20..=0x7E => true,
0x80..=0xFE => true,
_ => false,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct XPathComponent {
start: usize,
end: usize,
parent: bool,
}
impl XPathComponent {
pub fn is_parent_dir(self) -> bool {
self.parent
}
fn new_parent() -> Self {
Self {
start: 0,
end: 0,
parent: true,
}
}
fn new_normal(start: usize, end: usize) -> Self {
Self {
start,
end,
parent: false,
}
}
}
#[derive(Clone, Copy)]
struct XPathFrame {
buf_end: usize,
off: usize,
remaining: usize,
}
bitflags! {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct XPathTrailingFlags: u8 {
const SLASH = 1 << 0;
const DOT = 1 << 1;
const DOTDOT = 1 << 2;
}
}
fn xpath_scan(path: &[u8]) -> Result<(usize, XPathTrailingFlags), Errno> {
let mut count = 0usize;
let mut off = 0usize;
let mut dot_last = false;
let mut dotdot_last = false;
for pos in One::new(b'/').iter(path) {
let len = pos.checked_sub(off).ok_or(Errno::EOVERFLOW)?;
let seg = &path[off..pos];
match len {
0 => {}
1 if seg[0] == b'.' => {
dot_last = true;
dotdot_last = false;
}
2 if seg[0] == b'.' && seg[1] == b'.' => {
count = count.checked_add(1).ok_or(Errno::EOVERFLOW)?;
dot_last = false;
dotdot_last = true;
}
1..PATH_MAX => {
count = count.checked_add(1).ok_or(Errno::EOVERFLOW)?;
dot_last = false;
dotdot_last = false;
}
_ => return Err(Errno::ENAMETOOLONG),
}
off = pos.checked_add(1).ok_or(Errno::EOVERFLOW)?;
}
if off < path.len() {
let seg = &path[off..];
match seg.len() {
1 if seg[0] == b'.' => {
let has_slash = off.checked_sub(1).is_some_and(|prev| path[prev] == b'/');
let mut trailing = XPathTrailingFlags::DOT;
if has_slash {
trailing.insert(XPathTrailingFlags::SLASH);
}
Ok((count, trailing))
}
2 if seg[0] == b'.' && seg[1] == b'.' => {
count = count.checked_add(1).ok_or(Errno::EOVERFLOW)?;
let trailing = XPathTrailingFlags::SLASH | XPathTrailingFlags::DOTDOT;
Ok((count, trailing))
}
1..PATH_MAX => {
count = count.checked_add(1).ok_or(Errno::EOVERFLOW)?;
Ok((count, XPathTrailingFlags::empty()))
}
_ => Err(Errno::ENAMETOOLONG),
}
} else {
let mut trailing = XPathTrailingFlags::SLASH;
if dot_last {
trailing.insert(XPathTrailingFlags::DOT);
}
if dotdot_last {
trailing.insert(XPathTrailingFlags::DOTDOT);
}
Ok((count, trailing))
}
}
fn xpath_step(buf: &[u8], frame: &mut XPathFrame) -> Option<Result<XPathComponent, Errno>> {
loop {
if frame.off >= frame.buf_end {
return None;
}
let seg = &buf[frame.off..frame.buf_end];
let end = One::new(b'/').find(seg).unwrap_or(seg.len());
let comp_start = frame.off;
let comp_end = match frame.off.checked_add(end) {
Some(v) => v,
None => return Some(Err(Errno::EOVERFLOW)),
};
frame.off = match comp_end.checked_add(1) {
Some(v) => v,
None => return Some(Err(Errno::EOVERFLOW)),
};
match end {
0 => continue,
1 if seg[0] == b'.' => continue,
2 if seg[0] == b'.' && seg[1] == b'.' => {
frame.remaining = match frame.remaining.checked_sub(1) {
Some(v) => v,
None => return Some(Err(Errno::EOVERFLOW)),
};
return Some(Ok(XPathComponent::new_parent()));
}
1..PATH_MAX => {
frame.remaining = match frame.remaining.checked_sub(1) {
Some(v) => v,
None => return Some(Err(Errno::EOVERFLOW)),
};
return Some(Ok(XPathComponent::new_normal(comp_start, comp_end)));
}
_ => return Some(Err(Errno::ENAMETOOLONG)),
}
}
}
pub struct XPathComponents {
buf: Vec<u8>,
frames: [XPathFrame; MAXSYMLINKS as usize + 1],
depth: usize,
last: XPathComponent,
}
impl XPathComponents {
pub fn new(path: &XPath) -> Result<(Self, XPathTrailingFlags), Errno> {
let bytes = path.as_bytes();
let (count, trailing) = xpath_scan(bytes)?;
let mut buf = Vec::new();
buf.try_reserve(bytes.len()).or(Err(Errno::ENOMEM))?;
buf.extend_from_slice(bytes);
let empty = XPathFrame {
buf_end: 0,
off: 0,
remaining: 0,
};
let mut frames = [empty; MAXSYMLINKS as usize + 1];
frames[0] = XPathFrame {
buf_end: bytes.len(),
off: 0,
remaining: count,
};
Ok((
Self {
buf,
frames,
depth: 1,
last: XPathComponent::new_parent(),
},
trailing,
))
}
pub fn try_next(&mut self) -> Result<Option<XPathComponent>, Errno> {
match self.next().transpose()? {
Some(comp) => {
self.last = comp;
Ok(Some(comp))
}
None => Ok(None),
}
}
pub fn as_bytes(&self) -> Result<&[u8], Errno> {
if self.depth == 0 && self.last.start == self.last.end {
return Err(Errno::ENOENT);
}
Ok(&self.buf[self.last.start..self.last.end])
}
pub fn push_symlink(&mut self, target: XPathBuf) -> Result<(), Errno> {
if self.depth > MAXSYMLINKS as usize {
return Err(Errno::ELOOP);
}
let target_bytes = target.as_bytes();
let (count, _) = xpath_scan(target_bytes)?;
let start = self.buf.len();
self.buf
.try_reserve(target_bytes.len())
.or(Err(Errno::ENOMEM))?;
self.buf.extend_from_slice(target_bytes);
let end = self.buf.len();
self.frames[self.depth] = XPathFrame {
buf_end: end,
off: start,
remaining: count,
};
self.depth = self.depth.checked_add(1).ok_or(Errno::EOVERFLOW)?;
Ok(())
}
pub fn is_empty(&self) -> bool {
self.frames[..self.depth].iter().all(|f| f.remaining == 0)
}
pub fn remaining(&self) -> usize {
self.frames[..self.depth].iter().map(|f| f.remaining).sum()
}
}
impl Iterator for XPathComponents {
type Item = Result<XPathComponent, Errno>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let idx = self.depth.checked_sub(1)?;
let frame = &mut self.frames[idx];
match xpath_step(&self.buf, frame) {
Some(item) => return Some(item),
None => {
self.depth = idx;
}
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let n = self.remaining();
(n, Some(n))
}
}
impl std::iter::FusedIterator for XPathComponents {}
#[cfg(test)]
mod tests {
use std::{sync::mpsc, thread};
use nix::unistd::{gettid, pause};
use super::*;
use crate::magic::ProcMagic;
#[test]
fn test_xpath_1() {
assert_eq!(XPath::from_bytes(b"").depth(), 0);
assert_eq!(XPath::from_bytes(b"foo").depth(), 0);
assert_eq!(XPath::from_bytes(b"/").depth(), 1);
assert_eq!(XPath::from_bytes(b"/foo").depth(), 1);
assert_eq!(XPath::from_bytes(b"/foo/bar").depth(), 2);
assert_eq!(XPath::from_bytes(b"/foo/bar/baz").depth(), 3);
assert_eq!(XPath::from_bytes(b"a/b/c/d").depth(), 3);
}
#[test]
fn test_xpath_2() {
assert_eq!(
XPath::from_bytes(b"/foo/bar.rs")
.extension()
.unwrap()
.as_bytes(),
b"rs"
);
assert_eq!(
XPath::from_bytes(b"archive.tar.gz")
.extension()
.unwrap()
.as_bytes(),
b"gz"
);
assert!(XPath::from_bytes(b"/foo/bar").extension().is_none());
assert!(XPath::from_bytes(b"noext").extension().is_none());
assert!(XPath::from_bytes(b"trailing.").extension().is_none());
assert_eq!(
XPath::from_bytes(b".hidden")
.extension()
.unwrap()
.as_bytes(),
b"hidden"
);
}
#[test]
fn test_xpath_3() {
assert_eq!(XPath::from_bytes(b"/foo/bar").parent().as_bytes(), b"/foo");
assert_eq!(XPath::from_bytes(b"/foo").parent().as_bytes(), b"/");
assert_eq!(XPath::from_bytes(b"/").parent().as_bytes(), b"/");
assert_eq!(XPath::from_bytes(b"").parent().as_bytes(), b"");
assert_eq!(XPath::from_bytes(b"foo").parent().as_bytes(), b"");
assert_eq!(XPath::from_bytes(b"/foo/bar/").parent().as_bytes(), b"/foo");
}
#[test]
fn test_xpath_4() {
assert_eq!(XPath::from_bytes(b"").parent_len(), 0);
assert_eq!(XPath::from_bytes(b"/").parent_len(), 1);
assert_eq!(XPath::from_bytes(b"/foo").parent_len(), 1);
assert_eq!(XPath::from_bytes(b"/foo/bar").parent_len(), 4);
assert_eq!(XPath::from_bytes(b"foo").parent_len(), 0);
assert_eq!(XPath::from_bytes(b"/a/b/c/").parent_len(), 4);
}
#[test]
fn test_xpath_5() {
assert!(XPath::from_bytes(b"/").is_root());
assert!(!XPath::from_bytes(b"/foo").is_root());
assert!(!XPath::from_bytes(b"").is_root());
assert!(!XPath::from_bytes(b"//").is_root());
}
#[test]
fn test_xpath_6() {
assert!(XPath::from_bytes(b"/").is_absolute());
assert!(XPath::from_bytes(b"/foo").is_absolute());
assert!(!XPath::from_bytes(b"foo").is_absolute());
assert!(!XPath::from_bytes(b"").is_absolute());
assert!(!XPath::from_bytes(b"relative/path").is_absolute());
}
#[test]
fn test_xpath_7() {
assert!(XPath::from_bytes(b"foo").is_relative());
assert!(XPath::from_bytes(b"").is_relative());
assert!(!XPath::from_bytes(b"/foo").is_relative());
assert!(!XPath::from_bytes(b"/").is_relative());
}
#[test]
fn test_xpath_8() {
assert!(XPath::from_bytes(b".").is_dot());
assert!(!XPath::from_bytes(b"..").is_dot());
assert!(!XPath::from_bytes(b"").is_dot());
assert!(!XPath::from_bytes(b"./").is_dot());
}
#[test]
fn test_xpath_9() {
assert!(XPath::from_bytes(b"/proc").is_procfs());
assert!(XPath::from_bytes(b"/proc/").is_procfs());
assert!(!XPath::from_bytes(b"/proc/1").is_procfs());
assert!(!XPath::from_bytes(b"/pro").is_procfs());
assert!(!XPath::from_bytes(b"").is_procfs());
}
#[test]
fn test_xpath_10() {
assert!(XPath::from_bytes(b"/proc/1").is_proc());
assert!(XPath::from_bytes(b"/proc/self").is_proc());
assert!(!XPath::from_bytes(b"/proc").is_proc());
assert!(!XPath::from_bytes(b"/procedure").is_proc());
}
#[test]
fn test_xpath_11() {
assert!(XPath::from_bytes(b"/dev/null").is_dev());
assert!(XPath::from_bytes(b"/dev/sda").is_dev());
assert!(!XPath::from_bytes(b"/dev").is_dev());
assert!(!XPath::from_bytes(b"/device").is_dev());
}
#[test]
fn test_xpath_12() {
assert!(XPath::from_bytes(b"/proc/self").is_proc_self(false));
assert!(!XPath::from_bytes(b"/proc/self").is_proc_self(true));
assert!(XPath::from_bytes(b"/proc/thread-self").is_proc_self(true));
assert!(!XPath::from_bytes(b"/proc/thread-self").is_proc_self(false));
assert!(!XPath::from_bytes(b"/proc/1").is_proc_self(false));
}
#[test]
fn test_xpath_13() {
assert!(XPath::from_bytes(b"/proc/version").is_proc_version());
assert!(!XPath::from_bytes(b"/proc/versions").is_proc_version());
assert!(!XPath::from_bytes(b"/proc").is_proc_version());
}
#[test]
fn test_xpath_14() {
assert!(XPath::from_bytes(b"/proc/sys/kernel/osrelease").is_proc_osrelease());
assert!(!XPath::from_bytes(b"/proc/sys/kernel").is_proc_osrelease());
}
#[test]
fn test_xpath_15() {
assert!(XPath::from_bytes(b"/proc/self/status").is_proc_status());
assert!(XPath::from_bytes(b"/proc/1/status").is_proc_status());
assert!(!XPath::from_bytes(b"/proc/self/stat").is_proc_status());
assert!(!XPath::from_bytes(b"/tmp/status").is_proc_status());
}
#[test]
fn test_xpath_16() {
assert!(XPath::from_bytes(b"/etc/machine-id").is_machine_id());
assert!(XPath::from_bytes(b"/etc/hostid").is_machine_id());
assert!(XPath::from_bytes(b"/var/adm/hostid").is_machine_id());
assert!(XPath::from_bytes(b"/sys/class/dmi/id/product_uuid").is_machine_id());
assert!(!XPath::from_bytes(b"/etc/hostname").is_machine_id());
}
#[test]
fn test_xpath_17() {
assert!(XPath::from_bytes(b"/foo/bar").is_glob());
assert!(XPath::from_bytes(b"@abstract").is_glob());
assert!(XPath::from_bytes(b"!unnamed").is_glob());
assert!(XPath::from_bytes(b"!memfd:").is_glob());
assert!(XPath::from_bytes(b"!secretmem").is_glob());
assert!(!XPath::from_bytes(b"relative").is_glob());
assert!(!XPath::from_bytes(b"").is_glob());
assert!(!XPath::from_bytes(b"!other").is_glob());
}
#[test]
fn test_xpath_18() {
assert!(XPath::from_bytes(b"!unnamed").is_special());
assert!(XPath::from_bytes(b"!memfd:").is_special());
assert!(XPath::from_bytes(b"!memfd-hugetlb:x").is_special());
assert!(XPath::from_bytes(b"!secretmem").is_special());
assert!(!XPath::from_bytes(b"!other").is_special());
assert!(!XPath::from_bytes(b"/foo").is_special());
}
#[test]
fn test_xpath_19() {
assert!(XPath::from_bytes(b"/foo/").ends_with_slash());
assert!(XPath::from_bytes(b"/foo/bar/").ends_with_slash());
assert!(!XPath::from_bytes(b"/").ends_with_slash());
assert!(!XPath::from_bytes(b"/foo").ends_with_slash());
assert!(!XPath::from_bytes(b"").ends_with_slash());
}
#[test]
fn test_xpath_20() {
let p = XPath::from_bytes(b"/foo");
assert_eq!(p.first(), Some(b'/'));
assert_eq!(p.last(), Some(b'o'));
assert_eq!(p.get(0), Some(b'/'));
assert_eq!(p.get(1), Some(b'f'));
assert_eq!(p.get(99), None);
let empty = XPath::from_bytes(b"");
assert_eq!(empty.first(), None);
assert_eq!(empty.last(), None);
assert_eq!(empty.get(0), None);
}
#[test]
fn test_xpath_21() {
let p = XPath::from_bytes(b"/foo/bar");
assert!(p.starts_with(b"/foo"));
assert!(p.starts_with(b"/"));
assert!(!p.starts_with(b"foo"));
assert!(p.ends_with(b"bar"));
assert!(p.ends_with(b"/bar"));
assert!(!p.ends_with(b"foo"));
}
#[test]
fn test_xpath_22() {
assert!(XPath::from_bytes(b"/foo").is_equal(b"/foo"));
assert!(!XPath::from_bytes(b"/foo").is_equal(b"/bar"));
assert!(!XPath::from_bytes(b"/foo").is_equal(b"/foo/"));
}
#[test]
fn test_xpath_23() {
let p = XPath::from_bytes(b"/foo/bar/baz");
assert!(p.contains(b"bar"));
assert!(p.contains(b"/foo"));
assert!(!p.contains(b"xyz"));
}
#[test]
fn test_xpath_24() {
let p = XPath::from_bytes(b"/foo");
assert!(p.contains_char(b'/'));
assert!(p.contains_char(b'f'));
assert!(!p.contains_char(b'x'));
}
#[test]
fn test_xpath_25() {
let p = XPath::from_bytes(b"/foo/bar");
assert_eq!(p.find(b"foo"), Some(1));
assert_eq!(p.find(b"bar"), Some(5));
assert_eq!(p.find(b"baz"), None);
}
#[test]
fn test_xpath_26() {
let p = XPath::from_bytes(b"/foo/bar");
assert_eq!(p.find_char(b'/'), Some(0));
assert_eq!(p.find_char(b'f'), Some(1));
assert_eq!(p.find_char(b'z'), None);
}
#[test]
fn test_xpath_27() {
assert_eq!(XPath::from_bytes_until_nul(b"foo\0bar").as_bytes(), b"foo");
assert_eq!(XPath::from_bytes_until_nul(b"\0bar").as_bytes(), b"");
assert_eq!(XPath::from_bytes_until_nul(b"no_nul").as_bytes(), b"no_nul");
assert_eq!(XPath::from_bytes_until_nul(b"").as_bytes(), b"");
}
#[test]
fn test_xpath_28() {
assert_eq!(XPath::dotdot().as_bytes(), b"..");
assert_eq!(XPath::dot().as_bytes(), b".");
assert_eq!(XPath::root().as_bytes(), b"/");
assert_eq!(XPath::empty().as_bytes(), b"");
}
#[test]
fn test_xpath_29() {
let p = XPath::from_bytes(b"/foo");
assert_eq!(p.join(b"bar"), XPathBuf::from("/foo/bar"));
assert_eq!(p.join(b"/etc"), XPathBuf::from("/etc"));
}
#[test]
fn test_xpath_30() {
let p = XPathBuf::from("/foo");
assert_eq!(p.join(b"bar"), XPathBuf::from("/foo/bar"));
assert_eq!(p.join(b"/etc"), XPathBuf::from("/etc"));
}
#[test]
fn test_xpath_31() {
let p = XPath::from_bytes(b"/foo/bar/baz");
assert_eq!(p.strip_prefix(b"/foo").unwrap().as_bytes(), b"bar/baz");
assert_eq!(p.strip_prefix(b"/foo/bar/baz").unwrap().as_bytes(), b"");
assert!(p.strip_prefix(b"/xyz").is_none());
assert!(p.strip_prefix(b"/foobar").is_none());
}
#[test]
fn test_xpath_32() {
let p = XPathBuf::from("/foo/bar");
assert_eq!(format!("{p}"), "/foo/bar");
let x = XPath::from_bytes(b"/hello");
assert_eq!(format!("{x}"), "/hello");
}
#[test]
fn test_xpath_33() {
let p = XPathBuf::from("/foo");
assert_eq!(format!("{p:?}"), "/foo");
let x = XPath::from_bytes(b"/bar");
assert_eq!(format!("{x:?}"), "/bar");
}
#[test]
fn test_xpath_34() {
let pb = PathBuf::from("/some/path");
let xp = XPathBuf::from(pb);
assert_eq!(xp.as_bytes(), b"/some/path");
}
#[test]
fn test_xpath_35() {
let os = OsString::from("/test");
let xp = XPathBuf::from(os);
assert_eq!(xp.as_bytes(), b"/test");
}
#[test]
fn test_xpath_36() {
let os = OsStr::new("/test");
let xp = XPathBuf::from(os);
assert_eq!(xp.as_bytes(), b"/test");
}
#[test]
fn test_xpath_37() {
let cow: Cow<'_, str> = Cow::Borrowed("/borrowed");
let xp = XPathBuf::from(cow);
assert_eq!(xp.as_bytes(), b"/borrowed");
let cow: Cow<'_, str> = Cow::Owned(String::from("/owned"));
let xp = XPathBuf::from(cow);
assert_eq!(xp.as_bytes(), b"/owned");
}
#[test]
fn test_xpath_38() {
let v = vec![b'/', b'a'];
let xp = XPathBuf::from(v);
assert_eq!(xp.as_bytes(), b"/a");
}
#[test]
fn test_xpath_39() {
let mut vd = VecDeque::new();
vd.push_back(b'/');
vd.push_back(b'x');
let xp = XPathBuf::from(vd);
assert_eq!(xp.as_bytes(), b"/x");
}
#[test]
fn test_xpath_40() {
let xp = XPathBuf::from(42 as pid_t);
assert_eq!(xp.as_bytes(), b"42");
}
#[test]
fn test_xpath_41() {
let x = XPath::from_bytes(b"/ref");
let xp = XPathBuf::from(x);
assert_eq!(xp.as_bytes(), b"/ref");
}
#[test]
fn test_xpath_42() {
let p = XPathBuf::with_capacity(256);
assert!(p.capacity() >= 256);
assert!(p.is_empty());
}
#[test]
fn test_xpath_43() {
let p = XPathBuf::from("/test");
assert_eq!(p.into_vec(), b"/test");
}
#[test]
fn test_xpath_44() {
let p = XPathBuf::from("/foo");
let os = p.into_os_string();
assert_eq!(os, OsString::from("/foo"));
}
#[test]
fn test_xpath_45() {
let mut p = XPathBuf::from("/foo");
p.clear();
assert!(p.is_empty());
assert_eq!(p.as_bytes(), b"");
}
#[test]
fn test_xpath_46() {
let mut p = XPathBuf::from("/foo/bar");
p.truncate(4);
assert_eq!(p.as_bytes(), b"/foo");
}
#[test]
fn test_xpath_47() {
let mut p = XPathBuf::from("/ab");
assert_eq!(p.pop_last(), Some(b'b'));
assert_eq!(p.pop_last(), Some(b'a'));
assert_eq!(p.pop_last(), Some(b'/'));
assert_eq!(p.pop_last(), None);
}
#[test]
fn test_xpath_48() {
let mut p = XPathBuf::from("abc");
assert_eq!(p.remove(1), b'b');
assert_eq!(p.as_bytes(), b"ac");
}
#[test]
fn test_xpath_49() {
let p = XPathBuf::from("/foo/bar");
let c = p.try_clone().unwrap();
assert_eq!(p, c);
}
#[test]
fn test_xpath_50() {
let mut p = XPathBuf::from("abc");
p.set(1, b'X');
assert_eq!(p.as_bytes(), b"aXc");
}
#[test]
fn test_xpath_51() {
let mut p = XPathBuf::from("ab");
p.resize(5, b'x');
assert_eq!(p.as_bytes(), b"abxxx");
p.resize(2, 0);
assert_eq!(p.as_bytes(), b"ab");
}
#[test]
fn test_xpath_52() {
let p = XPathBuf::from("/foo");
assert_eq!(p.as_slice(), b"/foo");
}
#[test]
fn test_xpath_53() {
let p = XPathBuf::from("/foo");
assert_eq!(p.as_xpath().as_bytes(), b"/foo");
}
#[test]
fn test_xpath_54() {
let p = XPathBuf::from("/foo");
assert_eq!(p.as_path(), Path::new("/foo"));
assert_eq!(p.as_xpath().as_path(), Path::new("/foo"));
}
#[test]
fn test_xpath_55() {
let p = XPathBuf::from("/foo");
assert_eq!(p.as_os_str(), OsStr::new("/foo"));
}
#[test]
fn test_xpath_56() {
let buf = XPathBuf::from("/foo");
let path = XPath::from_bytes(b"/foo");
assert_eq!(buf, *path);
assert_eq!(*path, buf);
assert_ne!(buf, *XPath::from_bytes(b"/bar"));
}
#[test]
fn test_xpath_57() {
let buf = XPathBuf::from("/foo");
let xpath: &XPath = &buf;
assert_eq!(xpath.as_bytes(), b"/foo");
let borrowed: &XPath = std::borrow::Borrow::borrow(&buf);
assert_eq!(borrowed.as_bytes(), b"/foo");
}
#[test]
fn test_xpath_58() {
let path = XPath::from_bytes(b"/foo");
let owned: XPathBuf = path.to_owned();
assert_eq!(owned.as_bytes(), b"/foo");
}
#[test]
fn test_xpath_59() {
assert_eq!(mask_path(Path::new("/foo/bar")), "/foo/bar");
}
#[test]
fn test_xpath_60() {
let f = XPathCheckFlags::SAFE_NAME;
assert!(f.safe_name());
assert!(!f.restrict_mkbdev());
assert!(!f.restrict_magiclinks());
let f = XPathCheckFlags::RESTRICT_MKBDEV;
assert!(!f.safe_name());
assert!(f.restrict_mkbdev());
let f = XPathCheckFlags::RESTRICT_MAGICLINKS;
assert!(f.restrict_magiclinks());
let f = XPathCheckFlags::empty();
assert!(!f.safe_name());
assert!(!f.restrict_mkbdev());
assert!(!f.restrict_magiclinks());
}
#[test]
fn test_xpath_61() {
let p = xpath!("/foo/{}", "bar");
assert_eq!(p.as_bytes(), b"/foo/bar");
}
#[test]
fn test_xpath_62() {
let a = XPathBuf::new();
let b = XPathBuf::empty();
let c = XPathBuf::default();
assert_eq!(a, b);
assert_eq!(b, c);
assert!(a.is_empty());
}
#[test]
fn test_xpath_63() {
let mut p = XPathBuf::from("/foo/bar");
p.drain(4..);
assert_eq!(p.as_bytes(), b"/foo");
}
#[test]
fn test_xpath_64() {
let mut p = XPathBuf::from("/foo");
p.extend(b"/bar");
assert_eq!(p.as_bytes(), b"/foo/bar");
}
#[test]
fn test_xpath_65() {
let pid = Pid::from_raw(42);
let p = XPath::from_bytes(b"/proc/42/mem");
let r = p.replace_proc_self(pid);
assert_eq!(r.as_bytes(), b"/proc/self/mem");
let p2 = XPath::from_bytes(b"/tmp/foo");
let r2 = p2.replace_proc_self(pid);
assert_eq!(r2.as_bytes(), b"/tmp/foo");
let p3 = XPath::from_bytes(b"/proc/99/mem");
let r3 = p3.replace_proc_self(pid);
assert_eq!(r3.as_bytes(), b"/proc/99/mem");
}
#[test]
fn test_xpath_66() {
use std::collections::HashSet;
let a = XPathBuf::from("/a");
let b = XPathBuf::from("/b");
assert!(a < b);
let mut set = HashSet::new();
set.insert(XPathBuf::from("/foo"));
assert!(set.contains(&XPathBuf::from("/foo")));
assert!(!set.contains(&XPathBuf::from("/bar")));
}
#[test]
fn test_xpath_67() {
let buf = XPathBuf::from("/foo");
assert!(!NixPath::is_empty(&buf));
assert_eq!(NixPath::len(&buf), 4);
let empty = XPathBuf::new();
assert!(NixPath::is_empty(&empty));
assert_eq!(NixPath::len(&empty), 0);
let xpath = XPath::from_bytes(b"/bar");
assert!(!NixPath::is_empty(xpath));
assert_eq!(NixPath::len(xpath), 4);
}
#[test]
fn test_xpath_68() {
let large = "x".repeat(PATH_CAP + 1);
let xp = XPathBuf::from(large.clone());
assert_eq!(xp.as_bytes(), large.as_bytes());
let os = OsString::from(large.clone());
let xp2 = XPathBuf::from(os);
assert_eq!(xp2.as_bytes(), large.as_bytes());
}
#[test]
fn test_xpath_69() {
let large = vec![b'y'; PATH_CAP + 1];
let xp = XPathBuf::from(large.clone());
assert_eq!(xp.as_bytes(), large.as_slice());
let mut vd: VecDeque<u8> = VecDeque::new();
for &b in &large {
vd.push_back(b);
}
let xp2 = XPathBuf::from(vd);
assert_eq!(xp2.as_bytes(), large.as_slice());
}
#[test]
fn test_xpath_70() {
let large = "z".repeat(PATH_CAP + 1);
let cow: Cow<'_, str> = Cow::Owned(large.clone());
let xp = XPathBuf::from(cow);
assert_eq!(xp.as_bytes(), large.as_bytes());
}
#[test]
fn test_xpath_71() {
let mut p = XPathBuf::with_capacity(1024);
p.append_bytes(b"/x");
let before = p.capacity();
p.shrink_to_fit();
assert!(p.capacity() <= before);
assert_eq!(p.as_bytes(), b"/x");
}
#[test]
fn test_xpath_72() {
let buf = XPathBuf::from("/foo");
let _: &XPath = buf.as_ref();
let _: &Path = buf.as_ref();
let _: &OsStr = buf.as_ref();
let xpath = XPath::from_bytes(b"/bar");
let _: &Path = xpath.as_ref();
let _: &OsStr = xpath.as_ref();
}
#[test]
fn test_xpath_73() {
assert!(!XPath::from_bytes(b"/tmp/foo").is_kcov());
assert!(XPath::from_bytes(b"/sys/kernel/debug/kcov").is_kcov());
}
#[test]
fn test_xpath_74() {
let prefix = MAGIC_PREFIX;
let mut magic = Vec::from(prefix);
magic.extend_from_slice(b"test");
assert!(XPath::from_bytes(&magic).is_magic());
assert!(!XPath::from_bytes(b"/foo").is_magic());
}
#[test]
fn test_xpath_75() {
let pid = Pid::from_raw(123);
let p = XPathBuf::from_pid(pid).unwrap();
assert_eq!(p.as_bytes(), b"123");
}
#[test]
fn test_xpath_76() {
let p = XPathBuf::from_fd(7).unwrap();
assert_eq!(p.as_bytes(), b"7");
}
#[test]
fn test_xpath_77() {
let pid = Pid::from_raw(1);
let p = XPathBuf::from_root(pid).unwrap();
assert_eq!(p.as_bytes(), b"1/root");
}
#[test]
fn test_xpath_78() {
let pid = Pid::from_raw(1);
let p = XPathBuf::from_exe(pid).unwrap();
assert_eq!(p.as_bytes(), b"1/exe");
}
#[test]
fn test_xpath_79() {
let pid = Pid::from_raw(1);
let p = XPathBuf::from_cwd(pid).unwrap();
assert_eq!(p.as_bytes(), b"1/cwd");
}
#[test]
fn test_xpath_80() {
let tgid = Pid::from_raw(10);
let tid = Pid::from_raw(11);
let p = XPathBuf::from_task(tgid, tid).unwrap();
assert_eq!(p.as_bytes(), b"10/task/11");
}
#[test]
fn test_xpath_81() {
let pid = Pid::from_raw(5);
let p = XPathBuf::from_pid_fd(pid, 3).unwrap();
assert_eq!(p.as_bytes(), b"5/fd/3");
}
#[test]
fn test_xpath_82() {
let mut p = XPathBuf::from("/proc");
p.push_pid(Pid::from_raw(42));
assert_eq!(p.as_bytes(), b"/proc/42");
}
#[test]
fn test_xpath_83() {
let mut p = XPathBuf::from("fd");
p.push_fd(3);
assert_eq!(p.as_bytes(), b"fd/3");
}
#[test]
fn test_is_permitted_byte() {
assert!(is_permitted_initial(b'a'));
assert!(is_permitted_initial(b'Z'));
assert!(is_permitted_initial(b'.'));
assert!(!is_permitted_initial(b'-'));
assert!(!is_permitted_initial(b' '));
assert!(!is_permitted_initial(b'~'));
assert!(!is_permitted_initial(b'*'));
assert!(is_permitted_middle(b'a'));
assert!(is_permitted_middle(b'-'));
assert!(is_permitted_middle(b' '));
assert!(is_permitted_middle(b'~'));
assert!(!is_permitted_middle(b'*'));
assert!(!is_permitted_middle(b'\0'));
assert!(is_permitted_final(b'a'));
assert!(is_permitted_final(b'-'));
assert!(is_permitted_final(b'~'));
assert!(!is_permitted_final(b' '));
assert!(!is_permitted_final(b'*'));
}
struct CCSTestCase<'a> {
src: &'a str,
dst: &'a str,
}
const CCS_TESTS: &[CCSTestCase] = &[
CCSTestCase { src: "/", dst: "/" },
CCSTestCase {
src: "///",
dst: "/",
},
CCSTestCase {
src: "////",
dst: "/",
},
CCSTestCase {
src: "//home/alip///",
dst: "/home/alip/",
},
CCSTestCase {
src: "//home/alip///.config///",
dst: "/home/alip/.config/",
},
CCSTestCase {
src: "//home/alip///.config///htop////",
dst: "/home/alip/.config/htop/",
},
CCSTestCase {
src: "//home/alip///.config///htop////htoprc",
dst: "/home/alip/.config/htop/htoprc",
},
];
#[test]
fn test_clean_consecutive_slashes() {
for (idx, test) in CCS_TESTS.iter().enumerate() {
let mut path = XPathBuf::from(test.src);
path.clean_consecutive_slashes();
assert_eq!(
path,
XPathBuf::from(test.dst),
"Test {idx}: {} -> {path} != {}",
test.src,
test.dst
);
}
}
#[test]
fn test_descendant_of() {
let cases = [
("/", "/", true),
("/foo", "/", true),
("/foo/bar", "/", true),
("/foo", "/foo", true),
("/foo/bar", "/foo", true),
("/foo2", "/foo", false),
("/foot", "/foo", false),
("/fo", "/foo", false),
("/", "/foo", false),
("/foo/bar", "/foo/bar", true),
("/foo/bar/baz", "/foo/bar", true),
("/foo/barbaz", "/foo/bar", false),
("/foo", "/foo/bar", false),
];
for &(path, root, expected) in &cases {
let path = XPath::from_bytes(path.as_bytes());
assert_eq!(
path.descendant_of(root.as_bytes()),
expected,
"Failed on input: {path:?} of {root}!"
);
}
}
#[test]
fn test_has_parent_dot() {
const TEST_CASES: &[(&[u8], bool)] = &[
(b"/home/user/..", true),
(b"/home/user/abc/..", true),
(b"/path/to/dir/..", true),
(b"/home/user/abc/xyz/..", true),
(b"/dir/..", true),
(b"/dir/..abc", false),
(b"/../", true),
(b"..", true),
(b"/..", true),
(b"/../../", true),
(b"/..file/..", true),
(b"/home/user/abc", false),
(b"/home/user/abc/xyz", false),
(b"/dir/abc/xyz/..file", false),
(b"abc/..file", false),
(b"dir/abc/..xyz", false),
(b"dir/abc/xyz/..xyz", false),
(b"/../", true),
(b"/abc/../", true),
(b"/abc/../xyz", true),
(b"/..", true),
(b"/dir/..file", false),
(b"/..file", false),
(b"abc/..xyz", false),
(b"file/..name", false),
(b"/..", true),
(b"", false),
(b".", false),
(b".a", false),
(b"..", true),
(b"...", false),
(b"/long/path/with/some/other/..component", false),
(b"/long/path/with/..other", false),
(b"/this/is/a/path/to/..", true),
(b"/home/abc/def/..", true),
(b"/dir/..abc/..", true),
(b"/path/to/../../..", true),
(b"/path/to/abc/../../../xyz", true),
(b"/../file", true),
(b"/file/..", true),
(b"/..test", false),
(b"/..test/file", false),
(b"/some/dir/..test/file", false),
(b"/path/with/..file", false),
(b"/home/user", false),
(b"/usr/local/bin", false),
(b"/test/dir", false),
(b"/..", true),
(b"/../../", true),
(b"/..file/..", true),
(b"../dir/abc/..", true),
(b"dir/..abc/xyz/..", true),
(b"/path/to/..test/dir/../file/..", true),
(b"/path/..to/../file", true),
(b"/..test/file", false),
(b"/test/dir/..file/..", true),
(b"/some/dir/..test/file/..", true),
(b"/path/with/..file", false),
];
for (idx, &(path, expected)) in TEST_CASES.iter().enumerate() {
let path = XPath::from_bytes(path);
assert_eq!(
path.has_parent_dot(),
expected,
"FAIL: `{path}' at index:{idx} expected has_parent_dot:{expected}"
);
}
}
#[test]
fn test_strip_root() {
let cases = [
("", ""),
(".", ""),
("./", ""),
("./.", ""),
("././", ""),
("foo", "foo"),
("foo/bar", "foo/bar"),
("foo/./bar", "foo/bar"),
("./foo/bar", "foo/bar"),
("/", ""),
("//", ""),
("///", ""),
("/.", ""),
("/./", ""),
("/./.", ""),
("/././", ""),
("/foo", "foo"),
("/foo/", "foo"),
("//foo", "foo"),
("///foo", "foo"),
("/./foo", "foo"),
("/././foo", "foo"),
("/foo/bar", "foo/bar"),
("/foo/bar/", "foo/bar"),
("//foo//bar///", "foo/bar"),
("/./foo/./bar/.", "foo/bar"),
("///.//././foo/bar", "foo/bar"),
("/foo.bar", "foo.bar"),
("/foo..bar", "foo..bar"),
("../foo", "../foo"),
("../../foo", "../../foo"),
("foo/../bar", "foo/../bar"),
("/..", ".."),
("/../", ".."),
("/../foo", "../foo"),
("/foo/..", "foo/.."),
("/foo/../bar", "foo/../bar"),
("/foo/../../bar", "foo/../../bar"),
("/../../etc/passwd", "../../etc/passwd"),
("/foo/../../../etc/passwd", "foo/../../../etc/passwd"),
("/ foo", " foo"),
("/\u{00A0}foo", "\u{00A0}foo"),
];
for &(input, expected) in &cases {
let path = XPath::from_bytes(input.as_bytes());
let stripped = path.strip_root();
let expected_path = XPath::from_bytes(expected.as_bytes());
assert_eq!(
stripped.as_xpath(),
expected_path,
"strip_root failed: input={input:?}, expected={expected:?}, got={:?}",
stripped
);
}
}
#[test]
fn test_path_check_1() {
xpath!("/proc")
.check(
Pid::from_raw(1),
Some(&FileType::Dir),
None,
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc")
.check(
Pid::from_raw(1),
Some(&FileType::Dir),
Some(&XPath::from_bytes(b"self")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc")
.check(
Pid::from_raw(1),
Some(&FileType::Reg),
Some(&XPath::from_bytes(b"uptime")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/dev/null")
.check(
Pid::from_raw(1),
Some(&FileType::Chr),
None,
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/dev/log")
.check(
Pid::from_raw(1),
Some(&FileType::Sock),
None,
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/dev/fifo")
.check(
Pid::from_raw(1),
Some(&FileType::Fifo),
None,
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/dev/sda1")
.check(
Pid::from_raw(1),
Some(&FileType::Blk),
None,
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap_err();
xpath!("/dev/lmao")
.check(
Pid::from_raw(1),
Some(&FileType::Unk),
None,
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap_err();
}
#[test]
fn test_path_check_2() {
let this = Pid::from_raw(128);
let that = Pid::from_raw(256);
xpath!("/proc")
.check(
this,
Some(&FileType::Dir),
Some(&xpath!("{this}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc/{this}")
.check(
this,
Some(&FileType::Reg),
Some(&XPath::from_bytes(b"mem")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc/{this}")
.check(
this,
Some(&FileType::Dir),
Some(&XPath::from_bytes(b"")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc/{this}/task")
.check(
this,
Some(&FileType::Dir),
Some(&xpath!("{this}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc")
.check(
this,
Some(&FileType::Dir),
Some(&xpath!("{that}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap_err();
xpath!("/proc/{that}")
.check(
this,
Some(&FileType::Reg),
Some(&XPath::from_bytes(b"")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc/{that}")
.check(
this,
Some(&FileType::Dir),
Some(&XPath::from_bytes(b"")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc/{that}/task")
.check(
this,
Some(&FileType::Dir),
Some(&xpath!("{that}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
}
#[test]
fn test_path_check_3() {
let syd = Pid::this();
let pid = Pid::from_raw(syd.as_raw() + 1);
xpath!("/proc")
.check(
syd,
Some(&FileType::Dir),
Some(&xpath!("{syd}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc")
.check(
pid,
Some(&FileType::Dir),
Some(&xpath!("{syd}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap_err();
xpath!("/proc/{syd}")
.check(
syd,
Some(&FileType::Reg),
Some(&XPath::from_bytes(b"")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc/{syd}")
.check(
pid,
Some(&FileType::Reg),
Some(&XPath::from_bytes(b"")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap_err();
xpath!("/proc/{syd}")
.check(
pid,
Some(&FileType::Dir),
Some(&XPath::from_bytes(b"")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap_err();
xpath!("/proc/{syd}/task")
.check(
syd,
Some(&FileType::Dir),
Some(&xpath!("{syd}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc/{syd}/task")
.check(
pid,
Some(&FileType::Dir),
Some(&xpath!("{syd}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap_err();
}
#[test]
fn test_path_check_4() {
let pid = Pid::this();
let tid = {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(gettid()).unwrap();
pause();
});
rx.recv().unwrap()
};
xpath!("/proc")
.check(
tid,
Some(&FileType::Dir),
Some(&xpath!("{tid}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc/{tid}")
.check(
tid,
Some(&FileType::Reg),
Some(&XPath::from_bytes(b"")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc/{tid}")
.check(
tid,
Some(&FileType::Dir),
Some(&XPath::from_bytes(b"")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc/{tid}/task")
.check(
tid,
Some(&FileType::Dir),
Some(&xpath!("{tid}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap();
xpath!("/proc")
.check(
pid,
Some(&FileType::Dir),
Some(&xpath!("{tid}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap_err();
xpath!("/proc/{tid}")
.check(
pid,
Some(&FileType::Reg),
Some(&XPath::from_bytes(b"")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap_err();
xpath!("/proc/{tid}")
.check(
pid,
Some(&FileType::Dir),
Some(&XPath::from_bytes(b"")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap_err();
xpath!("/proc/{tid}/task")
.check(
pid,
Some(&FileType::Dir),
Some(&xpath!("{tid}")),
XPathCheckFlags::SAFE_NAME | XPathCheckFlags::RESTRICT_MKBDEV,
)
.unwrap_err();
}
#[test]
fn test_path_check_5() {
let pid = Pid::from_raw(1);
assert_eq!(
xpath!("/proc/1/exe").check(
pid,
Some(&FileType::Lnk),
None,
XPathCheckFlags::RESTRICT_MAGICLINKS
),
Err(Errno::ENOENT),
);
assert_eq!(
xpath!("/proc/1/exe").check(
pid,
Some(&FileType::MagicLnk(ProcMagic::Exe { pid })),
None,
XPathCheckFlags::RESTRICT_MAGICLINKS
),
Err(Errno::ENOENT),
);
assert_eq!(
xpath!("/proc/self").check(
pid,
Some(&FileType::Lnk),
None,
XPathCheckFlags::RESTRICT_MAGICLINKS
),
Ok(()),
);
assert_eq!(
xpath!("/proc/thread-self").check(
pid,
Some(&FileType::Lnk),
None,
XPathCheckFlags::RESTRICT_MAGICLINKS
),
Ok(()),
);
assert_eq!(
xpath!("/proc/1").check(
pid,
Some(&FileType::Dir),
None,
XPathCheckFlags::RESTRICT_MAGICLINKS
),
Ok(()),
);
assert_eq!(
xpath!("/tmp/link").check(
pid,
Some(&FileType::Lnk),
None,
XPathCheckFlags::RESTRICT_MAGICLINKS
),
Ok(()),
);
assert_eq!(
xpath!("/proc/1/exe").check(pid, Some(&FileType::Lnk), None, XPathCheckFlags::empty()),
Ok(()),
);
}
#[test]
fn test_path_split_1() {
let path = XPathBuf::from("/tmp/foo/bar/baz");
assert_eq!(path.split_prefix(b"/").unwrap().as_bytes(), path.as_bytes());
assert!(path.split_prefix(b"/tm").is_none());
assert_eq!(
path.split_prefix(b"/tmp").unwrap().as_bytes(),
b"foo/bar/baz"
);
assert!(path.split_prefix(b"/tmp/f").is_none());
assert_eq!(
path.split_prefix(b"/tmp/foo/").unwrap().as_bytes(),
b"bar/baz"
);
assert_eq!(
path.split_prefix(b"/tmp/foo/bar/baz").unwrap().as_bytes(),
b""
);
}
#[test]
fn test_path_split_2() {
let path = XPathBuf::from("tmp/foo/bar/baz");
assert!(path.split_prefix(b"t").is_none());
assert!(path.split_prefix(b"tm").is_none());
assert_eq!(
path.split_prefix(b"tmp").unwrap().as_bytes(),
b"foo/bar/baz"
);
assert_eq!(
path.split_prefix(b"tmp/").unwrap().as_bytes(),
b"foo/bar/baz"
);
assert_eq!(
path.split_prefix(b"tmp/foo/bar/baz").unwrap().as_bytes(),
b""
);
}
#[test]
fn test_path_split_3() {
let path = XPathBuf::from("/tmp/fob/secret");
assert!(path.split_prefix(b"/tmp/foo/").is_none());
let path = XPathBuf::from("/ac/secret");
assert!(path.split_prefix(b"/ab/").is_none());
let path = XPathBuf::from("/prot/self/maps");
assert!(path.split_prefix(b"/proc/").is_none());
}
#[test]
fn test_path_pop_unchecked() {
let mut path = XPathBuf::from("/usr/host/bin/id");
unsafe { path.pop_unchecked() };
assert_eq!(path, XPathBuf::from("/usr/host/bin"));
unsafe { path.pop_unchecked() };
assert_eq!(path, XPathBuf::from("/usr/host"));
unsafe { path.pop_unchecked() };
assert_eq!(path, XPathBuf::from("/usr"));
unsafe { path.pop_unchecked() };
assert_eq!(path, XPathBuf::from("/"));
unsafe { path.pop_unchecked() };
assert_eq!(path, XPathBuf::from("/"));
}
#[test]
fn test_path_pop() {
let mut path = XPathBuf::from("/spirited/away.rs");
path.pop();
assert_eq!(path, XPathBuf::from("/spirited"));
path.pop();
assert_eq!(path, XPathBuf::from("/"));
path.pop();
assert_eq!(path, XPathBuf::from("/"));
}
#[test]
fn test_path_push_1() {
let mut path = XPathBuf::from("/tmp");
path.push(b"file.bk");
assert_eq!(path, XPathBuf::from("/tmp/file.bk"));
let mut path = XPathBuf::from("/tmp");
path.push(b"/etc");
assert_eq!(path, XPathBuf::from("/etc"));
let mut path = XPathBuf::from("/tmp/bar");
path.push(b"baz/");
assert_eq!(path, XPathBuf::from("/tmp/bar/baz/"));
let mut path = XPathBuf::from("/tmp");
path.push(b"");
assert_eq!(path, XPathBuf::from("/tmp/"));
assert_eq!(path.as_os_str().as_bytes(), b"/tmp/");
}
#[test]
#[should_panic]
fn test_path_push_2() {
let mut path = XPathBuf::from("/tmp");
path.push(b"..");
}
#[test]
#[should_panic]
fn test_path_push_3() {
let mut path = XPathBuf::from("/tmp");
path.push(b"../");
}
#[test]
fn test_path_split() {
let path = XPathBuf::from("/foo/bar/baz");
let (parent, file_name) = path.split();
assert_eq!(parent, XPath::from_bytes(b"/foo/bar"));
assert_eq!(file_name, XPath::from_bytes(b"baz"));
let path = XPathBuf::from("/foo/bar/baz/");
let (parent, file_name) = path.split();
assert_eq!(parent, XPath::from_bytes(b"/foo/bar"));
assert_eq!(file_name, XPath::from_bytes(b"baz/"));
let path = XPathBuf::from("/");
let (parent, file_name) = path.split();
assert_eq!(parent, XPath::from_bytes(b"/"));
assert_eq!(file_name, XPath::from_bytes(b"/"));
let path = XPathBuf::from("/foo");
let (parent, file_name) = path.split();
assert_eq!(parent, XPath::from_bytes(b"/"));
assert_eq!(file_name, XPath::from_bytes(b"foo"));
let path = XPathBuf::from("/foo/");
let (parent, file_name) = path.split();
assert_eq!(parent, XPath::from_bytes(b"/"));
assert_eq!(file_name, XPath::from_bytes(b"foo/"));
}
#[test]
fn test_path_is_proc_pid() {
const TEST_CASES: &[(&str, bool)] = &[
("/pro", false),
("/pro/", false),
("/pro/1", false),
("/proc", false),
("/proc/", false),
("/proc/acpi", false),
("/proc/keys", false),
("/proc/0keys", true),
("/proc/1", true),
("/proc/1/", true),
("/proc/123456789", true),
("/proc/123456789/task", true),
];
for (path, is_proc_pid) in TEST_CASES {
assert_eq!(
*is_proc_pid,
XPathBuf::from(*path).is_proc_pid(),
"{path}:{is_proc_pid}"
);
}
}
#[test]
fn test_check_name_1() {
let valid_filenames = [
"valid_filename.txt",
"hello_world",
"File123",
"Makefile",
"こんにちは", "文件", "emoji😀", "valid~name", "name~", "a",
"normal",
"test-file",
"test_file",
"file name",
"file☃name", "name\u{0080}", "name\u{00FE}", "😀name", "name😀", "😀", "name😀name", "na~me", "name-", "name_", "name.", "a\u{0020}b", "a\u{00A0}b", "a\u{1680}b", "a\u{2007}b", "a\u{202F}b", "a\u{3000}b", ];
for (idx, name) in valid_filenames.iter().enumerate() {
let name = XPath::new(name);
assert!(
name.check_name().is_ok(),
"Filename {idx} '{name}' should be valid"
);
}
}
#[test]
fn test_check_name_2() {
let invalid_filenames: &[&[u8]] = &[
b"", b"-", b"*", b"?", b"!", b"$", b"`", b" -", b"~home", b"*home", b"?home", b"!home", b"$home", b"`home", b"file ", b"file*", b"file?", b"file!", b"file$", b"file`", b"bad*name", b"bad?name", b"bad!name", b"bad$name", b"bad`name", b"bad\nname", b"\0", b"bad\0name", b"bad\x7Fname", b"bad\xFFname", b"\x1Fcontrol", b"name\x1F", b"name\x7F", b"name\xFF", b"name ", b"-name", b" name", b"~name", b"*name", b"?name", b"!name", b"$name", b"`name", b"name\x19", b"name\n", b"\nname", b"na\nme", b"name\t", b"name\r", b"name\x1B", b"name\x00", b"name\x7F", b"name\xFF", b"\xFF", b"name\x80\xFF", b"name\xC0\xAF", b"\xF0\x28\x8C\xBC", b"\xF0\x90\x28\xBC", b"\xF0\x28\x8C\x28", b"name\xFFname", b"name\xC3\x28", b"name\xA0\xA1", b"\xE2\x28\xA1", b"\xE2\x82\x28", b"\xF0\x28\x8C\xBC", b"\xF0\x90\x28\xBC", b"\xF0\x28\x8C\x28", b"\xC2\xA0", b"\x20file", b"file\x20", b"\xC2\xA0file", b"file\xE3\x80\x80", b"\xE2\x80\xAFfile", b"\xE2\x81\x9Ffile\xE2\x81\x9F", ];
for (idx, name) in invalid_filenames.iter().enumerate() {
let name = XPath::from_bytes(name);
assert!(
name.check_name().is_err(),
"Filename {idx} '{name}' should not be valid"
);
}
}
#[test]
fn test_check_name_3() {
for b in 0x00..=0x1F {
if let Some(c) = char::from_u32(b as u32) {
let name = format!("name{c}char");
let name = XPath::new(&name);
assert!(
name.check_name().is_err(),
"Filename with control character '\\x{b:02X}' should be invalid",
);
}
}
}
#[test]
fn test_check_name_4() {
for b in 0x80..=0xFE {
if b == 0xFF {
continue; }
let mut bytes = b"name".to_vec();
bytes.push(b);
bytes.extend_from_slice(b"char");
let name = OsStr::from_bytes(&bytes);
let name = XPath::new(name);
let result = name.check_name();
if std::str::from_utf8(&bytes).is_ok() {
assert!(result.is_ok(), "Filename with byte 0x{b:X} should be valid",);
} else {
assert!(
result.is_err(),
"Filename with invalid UTF-8 byte 0x{b:X} should be invalid",
);
}
}
}
#[test]
fn test_check_name_5() {
let valid_single_chars = [
"a", "b", "Z", "9", "_", ".", "😀", ];
for (idx, name) in valid_single_chars.iter().enumerate() {
let name = XPath::new(name);
assert!(
name.check_name().is_ok(),
"Single-character filename {idx} '{name}' should be valid",
);
}
let invalid_single_chars: &[&[u8]] = &[
b"-", b" ", b"~", b"*", b"?", b"\n", b"\r", b"\x7F", b"\x1F", b"\xFF", b"\0", b"\xC2\xA0", b"\x20", b"\xC2\xA0", b"\xE1\x9A\x80", b"\xE2\x80\x87", b"\xE2\x80\xAF", b"\xE3\x80\x80", ];
for (idx, name) in invalid_single_chars.iter().enumerate() {
let name = XPath::from_bytes(name);
assert!(
name.check_name().is_err(),
"Single-character filename {idx} '{name}' should be invalid",
);
}
}
fn xp(bytes: &[u8]) -> XPathBuf {
bytes.into()
}
#[test]
fn test_push_bytes() {
let mut base = XPathBuf::from("./");
assert_eq!(base.as_bytes(), b"./");
base.append_bytes(b"sigpipe.sock");
base.append_byte(0);
let v = base.into_vec();
assert_eq!(v, b"./sigpipe.sock\0");
}
#[test]
fn test_from_string() {
let s = String::from("test/path");
let xb = XPathBuf::from(s);
assert_eq!(xb.as_bytes(), b"test/path");
let v = xb.into_vec();
assert_eq!(v, b"test/path");
}
#[test]
fn test_replace_prefix_1() -> Result<(), Errno> {
let mut x = xp(b"/ordinary/path");
x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
assert_eq!(x.as_bytes(), b"/ordinary/path");
Ok(())
}
#[test]
fn test_replace_prefix_2() -> Result<(), Errno> {
let mut x = xp(b"abc:/tail");
x.replace_prefix(b"abc:", b"XYZ:")?;
assert_eq!(x.as_bytes(), b"XYZ:/tail");
Ok(())
}
#[test]
fn test_replace_prefix_3() -> Result<(), Errno> {
let mut x = xp(b"!memfd:/some/path");
x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
assert_eq!(x.as_bytes(), b"!memfd-hugetlb:/some/path");
Ok(())
}
#[test]
fn test_replace_prefix_4() -> Result<(), Errno> {
let mut x = xp(b"!memfd:");
x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
assert_eq!(x.as_bytes(), b"!memfd-hugetlb:");
Ok(())
}
#[test]
fn test_replace_prefix_5() -> Result<(), Errno> {
let mut x = xp(b"!memfd-hugetlb:/already");
x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
assert_eq!(x.as_bytes(), b"!memfd-hugetlb:/already");
x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
assert_eq!(x.as_bytes(), b"!memfd-hugetlb:/already");
Ok(())
}
#[test]
fn test_replace_prefix_6() -> Result<(), Errno> {
let mut x = xp(b"prefix-long:/rest");
x.replace_prefix(b"prefix-long:", b"p:")?;
assert_eq!(x.as_bytes(), b"p:/rest");
Ok(())
}
#[test]
fn test_replace_prefix_7() -> Result<(), Errno> {
let mut x = xp(b"foobar-long:/zzz");
x.replace_prefix(b"foobar-long:", b"foobar:")?;
assert_eq!(x.as_bytes(), b"foobar:/zzz");
Ok(())
}
#[test]
fn test_replace_prefix_8() -> Result<(), Errno> {
let mut x = xp(b"!memfd:/a/b/c/d/e");
x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
assert_eq!(x.as_bytes(), b"!memfd-hugetlb:/a/b/c/d/e");
Ok(())
}
#[test]
fn test_replace_prefix_9() -> Result<(), Errno> {
let mut x = xp(b"longerprefix:/a/b/c");
x.replace_prefix(b"longerprefix:", b"lp:")?;
assert_eq!(x.as_bytes(), b"lp:/a/b/c");
Ok(())
}
#[test]
fn test_replace_prefix_10() -> Result<(), Errno> {
let mut x = XPathBuf::with_capacity(1);
x.append_bytes(b"!memfd:/x/y/z");
let cap_before = x.capacity();
x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
let cap_after = x.capacity();
assert!(cap_after >= cap_before);
assert_eq!(x.as_bytes(), b"!memfd-hugetlb:/x/y/z");
Ok(())
}
#[test]
fn test_replace_prefix_11() -> Result<(), Errno> {
let mut tail = Vec::new();
for _ in 0..512 {
tail.extend_from_slice(b"/component");
}
let mut p = b"!memfd:".to_vec();
p.extend_from_slice(&tail);
let mut x = xp(&p);
x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
let mut expected = b"!memfd-hugetlb:".to_vec();
expected.extend_from_slice(&tail);
assert_eq!(x.as_bytes(), expected.as_slice());
Ok(())
}
#[test]
fn test_replace_prefix_12() -> Result<(), Errno> {
let mut x = xp(b"\xF0\x9F\x92\xA9prefix-long:\xFF\x00tail");
x.replace_prefix(b"\xF0\x9F\x92\xA9prefix-long:", b"\xF0\x9F\x92\xA9p:")?;
assert_eq!(x.as_bytes(), b"\xF0\x9F\x92\xA9p:\xFF\x00tail");
Ok(())
}
#[test]
fn test_replace_prefix_13() {
let mut x = xp(b"!memfd:/whatever");
let res = x.replace_prefix(b"", b"!memfd-hugetlb:");
assert!(matches!(res, Err(Errno::EINVAL)));
}
fn collect(parts: &mut XPathComponents) -> Result<Vec<Option<Vec<u8>>>, Errno> {
let mut out = Vec::new();
while let Some(c) = parts.try_next()? {
if c.is_parent_dir() {
out.push(None);
} else {
out.push(Some(parts.as_bytes()?.to_vec()));
}
}
Ok(out)
}
#[test]
fn test_xpath_scan_1() -> Result<(), Errno> {
let (count, _) = xpath_scan(b"a/b/./c/../d")?;
assert_eq!(count, 5);
Ok(())
}
#[test]
fn test_xpath_scan_2() -> Result<(), Errno> {
let (_, trailing) = xpath_scan(b"foo/bar/")?;
assert!(trailing.contains(XPathTrailingFlags::SLASH));
let (_, trailing) = xpath_scan(b"foo/bar")?;
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_scan_3() {
let long = vec![b'a'; PATH_MAX + 1];
let result = xpath_scan(&long);
assert!(matches!(result, Err(Errno::ENAMETOOLONG)));
}
#[test]
fn test_xpath_scan_4() -> Result<(), Errno> {
let (_, trailing) = xpath_scan(b"foo/.")?;
assert!(trailing.contains(XPathTrailingFlags::SLASH));
assert!(trailing.contains(XPathTrailingFlags::DOT));
let (_, trailing) = xpath_scan(b"foo")?;
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
assert!(!trailing.contains(XPathTrailingFlags::DOT));
let (_, trailing) = xpath_scan(b".")?;
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
assert!(trailing.contains(XPathTrailingFlags::DOT));
let (_, trailing) = xpath_scan(b"./")?;
assert!(trailing.contains(XPathTrailingFlags::SLASH));
assert!(trailing.contains(XPathTrailingFlags::DOT));
let (_, trailing) = xpath_scan(b"/foo/bar/.")?;
assert!(trailing.contains(XPathTrailingFlags::SLASH));
assert!(trailing.contains(XPathTrailingFlags::DOT));
let (_, trailing) = xpath_scan(b"foo/..")?;
assert!(trailing.contains(XPathTrailingFlags::SLASH));
assert!(!trailing.contains(XPathTrailingFlags::DOT));
let (_, trailing) = xpath_scan(b"foo/")?;
assert!(trailing.contains(XPathTrailingFlags::SLASH));
assert!(!trailing.contains(XPathTrailingFlags::DOT));
let (_, trailing) = xpath_scan(b"dir/.//")?;
assert!(trailing.contains(XPathTrailingFlags::SLASH));
assert!(trailing.contains(XPathTrailingFlags::DOT));
let (_, trailing) = xpath_scan(b"dir/./")?;
assert!(trailing.contains(XPathTrailingFlags::SLASH));
assert!(trailing.contains(XPathTrailingFlags::DOT));
let (_, trailing) = xpath_scan(b"foo/./././/.//")?;
assert!(trailing.contains(XPathTrailingFlags::SLASH));
assert!(trailing.contains(XPathTrailingFlags::DOT));
let (_, trailing) = xpath_scan(b"././/")?;
assert!(trailing.contains(XPathTrailingFlags::SLASH));
assert!(trailing.contains(XPathTrailingFlags::DOT));
Ok(())
}
#[test]
fn test_xpath_components_1() -> Result<(), Errno> {
let path = XPath::from_bytes(b"");
let (mut parts, trailing) = XPathComponents::new(&path)?;
assert!(collect(&mut parts)?.is_empty());
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_2() -> Result<(), Errno> {
let path = XPath::from_bytes(b"..");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(collect(&mut p)?, vec![None]);
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_3() -> Result<(), Errno> {
let path = XPath::from_bytes(b"../..");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(collect(&mut p)?, vec![None, None]);
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_4() -> Result<(), Errno> {
let path = XPath::from_bytes(b"../foo/../bar");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(
collect(&mut p)?,
vec![None, Some(b"foo".to_vec()), None, Some(b"bar".to_vec())]
);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_5() -> Result<(), Errno> {
let path = XPath::from_bytes(b"../foo/..");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(collect(&mut p)?, vec![None, Some(b"foo".to_vec()), None]);
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_6() -> Result<(), Errno> {
let path = XPath::from_bytes(b"////..////bar");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(collect(&mut p)?, vec![None, Some(b"bar".to_vec())]);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_7() -> Result<(), Errno> {
let path = XPath::from_bytes(b"../foo/../bar/");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(
collect(&mut p)?,
vec![None, Some(b"foo".to_vec()), None, Some(b"bar".to_vec())]
);
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_8() -> Result<(), Errno> {
let path = XPath::from_bytes(b"../foo/bar/../../baz/..");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(
collect(&mut p)?,
vec![
None,
Some(b"foo".to_vec()),
Some(b"bar".to_vec()),
None,
None,
Some(b"baz".to_vec()),
None,
]
);
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_9() -> Result<(), Errno> {
let path = XPath::from_bytes(b"/..");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(collect(&mut p)?, vec![None]);
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_10() -> Result<(), Errno> {
let path = XPath::from_bytes(b".");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert!(collect(&mut p)?.is_empty());
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_11() -> Result<(), Errno> {
let path = XPath::from_bytes(b"./");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert!(collect(&mut p)?.is_empty());
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_12() -> Result<(), Errno> {
let path = XPath::from_bytes(b"..foo");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(collect(&mut p)?, vec![Some(b"..foo".to_vec())]);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_13() -> Result<(), Errno> {
let path = XPath::from_bytes(b"...");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(collect(&mut p)?, vec![Some(b"...".to_vec())]);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_14() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a/./b");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(
collect(&mut p)?,
vec![Some(b"a".to_vec()), Some(b"b".to_vec())]
);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_15() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a/.");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(collect(&mut p)?, vec![Some(b"a".to_vec())]);
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_16() -> Result<(), Errno> {
let path = XPath::from_bytes(b"///");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert!(collect(&mut p)?.is_empty());
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_17() -> Result<(), Errno> {
let path = XPath::from_bytes(b"/");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert!(collect(&mut p)?.is_empty());
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_18() -> Result<(), Errno> {
let path = XPath::from_bytes(b"hello");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(collect(&mut p)?, vec![Some(b"hello".to_vec())]);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_19() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a//b");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(
collect(&mut p)?,
vec![Some(b"a".to_vec()), Some(b"b".to_vec())]
);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_20() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a/b/../c");
let (mut p, _) = XPathComponents::new(&path)?;
assert_eq!(p.size_hint(), (4, Some(4)));
assert_eq!(p.remaining(), 4);
assert!(!p.is_empty());
let _ = p.next();
assert_eq!(p.remaining(), 3);
while p.next().is_some() {}
assert_eq!(p.remaining(), 0);
assert!(p.is_empty());
Ok(())
}
#[test]
fn test_xpath_components_21() -> Result<(), Errno> {
let path = XPath::from_bytes(b"x");
let (mut p, _) = XPathComponents::new(&path)?;
assert!(p.next().is_some());
assert!(p.next().is_none());
assert!(p.next().is_none());
assert!(p.next().is_none());
Ok(())
}
#[test]
fn test_xpath_components_22() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a/b");
let (mut p, _) = XPathComponents::new(&path)?;
let c = p.try_next()?.ok_or(Errno::ENOENT)?;
assert!(!c.is_parent_dir());
assert_eq!(p.as_bytes()?, b"a");
p.push_symlink(XPathBuf::from("x/y"))?;
assert_eq!(
collect(&mut p)?,
vec![
Some(b"x".to_vec()),
Some(b"y".to_vec()),
Some(b"b".to_vec()),
]
);
Ok(())
}
#[test]
fn test_xpath_components_23() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a/b/c");
let (mut p, _) = XPathComponents::new(&path)?;
p.next();
p.push_symlink(XPathBuf::from("s1/s2"))?;
p.next();
p.push_symlink(XPathBuf::from("deep"))?;
assert_eq!(
collect(&mut p)?,
vec![
Some(b"deep".to_vec()),
Some(b"s2".to_vec()),
Some(b"b".to_vec()),
Some(b"c".to_vec()),
]
);
Ok(())
}
#[test]
fn test_xpath_components_24() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a/b");
let (mut p, _) = XPathComponents::new(&path)?;
p.next();
p.push_symlink(XPathBuf::from("../x"))?;
assert_eq!(
collect(&mut p)?,
vec![None, Some(b"x".to_vec()), Some(b"b".to_vec())]
);
Ok(())
}
#[test]
fn test_xpath_components_25() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a");
let (mut p, _) = XPathComponents::new(&path)?;
p.try_next()?; p.push_symlink(XPathBuf::from("foo/bar"))?;
let c1 = p.try_next()?.ok_or(Errno::ENOENT)?;
assert!(!c1.is_parent_dir());
assert_eq!(p.as_bytes()?, b"foo");
let c2 = p.try_next()?.ok_or(Errno::ENOENT)?;
assert!(!c2.is_parent_dir());
assert_eq!(p.as_bytes()?, b"bar");
assert!(p.try_next()?.is_none());
Ok(())
}
#[test]
fn test_xpath_components_26() -> Result<(), Errno> {
let path = XPath::from_bytes(b"/usr/bin/syd");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(
collect(&mut p)?,
vec![
Some(b"usr".to_vec()),
Some(b"bin".to_vec()),
Some(b"syd".to_vec()),
]
);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_27() -> Result<(), Errno> {
let path = XPath::from_bytes(b"src/main.rs");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(
collect(&mut p)?,
vec![Some(b"src".to_vec()), Some(b"main.rs".to_vec())]
);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_28() -> Result<(), Errno> {
let path = XPath::from_bytes(b"/proc/self/fd/3");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(
collect(&mut p)?,
vec![
Some(b"proc".to_vec()),
Some(b"self".to_vec()),
Some(b"fd".to_vec()),
Some(b"3".to_vec()),
]
);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_29() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a/b/c/d/e/f/g/h");
let (mut p, trailing) = XPathComponents::new(&path)?;
let c = collect(&mut p)?;
assert_eq!(c.len(), 8);
assert_eq!(c[0], Some(b"a".to_vec()));
assert_eq!(c[7], Some(b"h".to_vec()));
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_30() -> Result<(), Errno> {
let path = XPath::from_bytes(b"/foo/bar/..");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(
collect(&mut p)?,
vec![Some(b"foo".to_vec()), Some(b"bar".to_vec()), None]
);
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_31() -> Result<(), Errno> {
let path = XPath::from_bytes(b"..../....");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(
collect(&mut p)?,
vec![Some(b"....".to_vec()), Some(b"....".to_vec())]
);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_32() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a/b/c");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(
collect(&mut p)?,
vec![
Some(b"a".to_vec()),
Some(b"b".to_vec()),
Some(b"c".to_vec()),
]
);
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_33() -> Result<(), Errno> {
let name = vec![b'x'; PATH_MAX - 1];
let (mut p, trailing) = XPathComponents::new(XPath::from_bytes(&name))?;
let c = collect(&mut p)?;
assert_eq!(c.len(), 1);
assert_eq!(c[0].as_ref().map(|v| v.len()), Some(PATH_MAX - 1));
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_34() {
let name = vec![b'x'; PATH_MAX];
let result = XPathComponents::new(XPath::from_bytes(&name));
assert!(matches!(result, Err(Errno::ENAMETOOLONG)));
}
#[test]
fn test_xpath_components_35() -> Result<(), Errno> {
let path = XPath::from_bytes(b"../././../.");
let (mut p, trailing) = XPathComponents::new(&path)?;
assert_eq!(collect(&mut p)?, vec![None, None]);
assert!(trailing.contains(XPathTrailingFlags::SLASH));
Ok(())
}
#[test]
fn test_xpath_components_36() {
let path = XPath::from_bytes(b"x");
let (mut p, _) = XPathComponents::new(&path).unwrap();
while p.next().is_some() {}
assert_eq!(p.as_bytes(), Err(Errno::ENOENT));
}
#[test]
fn test_xpath_components_37() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a/b");
let (mut p, _) = XPathComponents::new(&path)?;
assert_eq!(p.remaining(), 2);
p.next(); assert_eq!(p.remaining(), 1);
p.push_symlink(XPathBuf::from("x/y/z"))?;
assert_eq!(p.remaining(), 4);
while p.next().is_some() {}
assert_eq!(p.remaining(), 0);
assert!(p.is_empty());
Ok(())
}
#[test]
fn test_xpath_components_38() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a/b");
let (mut p, _) = XPathComponents::new(&path)?;
p.next();
p.push_symlink(XPathBuf::from(""))?;
assert_eq!(collect(&mut p)?, vec![Some(b"b".to_vec())]);
Ok(())
}
#[test]
fn test_xpath_components_39() -> Result<(), Errno> {
let path = XPath::from_bytes(b"a/b/c");
let (mut p, _) = XPathComponents::new(&path)?;
assert_eq!(p.size_hint(), (3, Some(3)));
p.next();
assert_eq!(p.size_hint(), (2, Some(2)));
p.next();
p.next();
assert_eq!(p.size_hint(), (0, Some(0)));
Ok(())
}
#[test]
fn test_xpath_components_40() -> Result<(), Errno> {
let path = XPath::from_bytes(b"foo/.");
let (_, trailing) = XPathComponents::new(&path)?;
assert!(trailing.contains(XPathTrailingFlags::SLASH));
assert!(trailing.contains(XPathTrailingFlags::DOT));
let path = XPath::from_bytes(b"foo/bar");
let (_, trailing) = XPathComponents::new(&path)?;
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
assert!(!trailing.contains(XPathTrailingFlags::DOT));
let path = XPath::from_bytes(b".");
let (_, trailing) = XPathComponents::new(&path)?;
assert!(!trailing.contains(XPathTrailingFlags::SLASH));
assert!(trailing.contains(XPathTrailingFlags::DOT));
Ok(())
}
}