#![forbid(unsafe_code)]
use crate::{
error::{Error, ErrorImpl, ErrorKind},
flags::{OpenFlags, ResolverFlags},
syscalls,
utils::FdExt,
Handle,
};
use std::{
fs::File,
io::Error as IOError,
os::unix::io::{AsFd, OwnedFd},
path::{Path, PathBuf},
rc::Rc,
};
pub(crate) mod opath {
mod imp;
pub(crate) use imp::*;
mod symlink_stack;
pub(crate) use symlink_stack::{SymlinkStack, SymlinkStackError};
}
pub(crate) mod openat2;
pub(crate) mod procfs;
const MAX_SYMLINK_TRAVERSALS: usize = 128;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub(crate) enum ResolverBackend {
KernelOpenat2,
EmulatedOpath,
}
impl Default for ResolverBackend {
fn default() -> Self {
if syscalls::openat2::saw_openat2_failure() {
ResolverBackend::EmulatedOpath
} else {
ResolverBackend::KernelOpenat2
}
}
}
impl ResolverBackend {
#[cfg(test)]
pub(crate) fn supported(self) -> bool {
match self {
ResolverBackend::KernelOpenat2 => !syscalls::openat2::openat2_is_not_supported(),
ResolverBackend::EmulatedOpath => true,
}
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Resolver {
pub(crate) backend: ResolverBackend,
pub flags: ResolverFlags,
}
#[derive(Debug)]
pub(crate) enum PartialLookup<H, E = Error> {
Complete(H),
Partial {
handle: H,
remaining: PathBuf,
last_error: E,
},
}
impl<H> AsRef<H> for PartialLookup<H> {
fn as_ref(&self) -> &H {
match self {
Self::Complete(handle) => handle,
Self::Partial { handle, .. } => handle,
}
}
}
impl TryInto<Handle> for PartialLookup<Handle> {
type Error = Error;
fn try_into(self) -> Result<Handle, Self::Error> {
match self {
Self::Complete(handle) => Ok(handle),
Self::Partial { last_error, .. } => Err(last_error),
}
}
}
impl TryInto<Handle> for PartialLookup<Rc<OwnedFd>> {
type Error = Error;
fn try_into(self) -> Result<Handle, Self::Error> {
PartialLookup::<Handle>::from(self).try_into()
}
}
impl TryInto<(Handle, Option<PathBuf>)> for PartialLookup<Handle> {
type Error = Error;
fn try_into(self) -> Result<(Handle, Option<PathBuf>), Self::Error> {
match self {
Self::Complete(handle) => Ok((handle, None)),
Self::Partial {
handle,
remaining,
last_error,
} => match last_error.kind() {
ErrorKind::OsError(Some(libc::ENOENT)) => Ok((handle, Some(remaining))),
_ => Err(last_error),
},
}
}
}
impl From<PartialLookup<Rc<OwnedFd>>> for PartialLookup<Handle> {
fn from(result: PartialLookup<Rc<OwnedFd>>) -> Self {
let (rc, partial) = match result {
PartialLookup::Complete(rc) => (rc, None),
PartialLookup::Partial {
handle,
remaining,
last_error,
} => (handle, Some((remaining, last_error))),
};
let handle = Handle::from_fd(
Rc::try_unwrap(rc)
.expect("current handle in lookup should only have a single Rc reference"),
);
match partial {
None => Self::Complete(handle),
Some((remaining, last_error)) => Self::Partial {
handle,
remaining,
last_error,
},
}
}
}
impl Resolver {
fn generic_open(
&self,
root: impl AsFd,
path: impl AsRef<Path>,
flags: impl Into<OpenFlags>,
) -> Result<File, Error> {
let flags = flags.into();
let handle = self.resolve(root, path, flags.contains(OpenFlags::O_NOFOLLOW))?;
if handle.metadata()?.is_symlink() {
if flags.contains(OpenFlags::O_DIRECTORY) {
Err(ErrorImpl::OsError {
operation: "emulated openat2".into(),
source: IOError::from_raw_os_error(libc::ENOTDIR),
})?;
}
if flags.contains(OpenFlags::O_PATH) {
return Ok(OwnedFd::from(handle).into());
}
Err(ErrorImpl::OsError {
operation: "emulated openat2".into(),
source: IOError::from_raw_os_error(libc::ELOOP),
})?;
}
handle.reopen(flags)
}
pub(crate) fn open(
&self,
root: impl AsFd,
path: impl AsRef<Path>,
flags: impl Into<OpenFlags>,
) -> Result<File, Error> {
let flags = flags.into();
if flags.intersects(OpenFlags::O_CREAT | OpenFlags::O_EXCL) {
Err(ErrorImpl::InvalidArgument {
name: "oflags".into(),
description: "open flags to one-shot open cannot contain O_CREAT or O_EXCL".into(),
})?
}
match self.backend {
ResolverBackend::KernelOpenat2 => {
let root = root.as_fd();
let path = path.as_ref();
openat2::open(root, path, self.flags, flags).or_else(|err| {
if syscalls::openat2::openat2_is_not_supported() {
self.generic_open(root, path, flags)
} else {
Err(err)
}
})
}
_ => self.generic_open(root, path, flags),
}
}
#[inline]
pub(crate) fn resolve(
&self,
root: impl AsFd,
path: impl AsRef<Path>,
no_follow_trailing: bool,
) -> Result<Handle, Error> {
match self.backend {
ResolverBackend::KernelOpenat2 => {
let root = root.as_fd();
let path = path.as_ref();
openat2::resolve(root, path, self.flags, no_follow_trailing).or_else(|err| {
if syscalls::openat2::openat2_is_not_supported() {
opath::resolve(root, path, self.flags, no_follow_trailing)
} else {
Err(err)
}
})
}
ResolverBackend::EmulatedOpath => {
opath::resolve(root, path, self.flags, no_follow_trailing)
}
}
}
#[inline]
pub(crate) fn resolve_partial(
&self,
root: impl AsFd,
path: impl AsRef<Path>,
no_follow_trailing: bool,
) -> Result<PartialLookup<Handle>, Error> {
match self.backend {
ResolverBackend::KernelOpenat2 => {
let root = root.as_fd();
let path = path.as_ref();
openat2::resolve_partial(root, path, self.flags, no_follow_trailing).or_else(
|err| {
if syscalls::openat2::openat2_is_not_supported() {
opath::resolve_partial(root, path, self.flags, no_follow_trailing)
.map(Into::into)
} else {
Err(err)
}
},
)
}
ResolverBackend::EmulatedOpath => {
opath::resolve_partial(root, path.as_ref(), self.flags, no_follow_trailing)
.map(Into::into)
}
}
}
}