Introduce directory traversal

This commit is contained in:
topjohnwu
2023-06-09 02:00:37 -07:00
parent e13775ec2c
commit 40f25f4d56
9 changed files with 412 additions and 134 deletions

View File

@@ -1,53 +1,25 @@
use mem::MaybeUninit;
use std::cmp::min;
use std::ffi::CStr;
use std::fs::File;
use std::io::{BufRead, Read, Seek, SeekFrom, Write};
use std::marker::PhantomData;
use std::ops::Deref;
use std::os::fd::{AsFd, BorrowedFd, IntoRawFd};
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::{io, mem};
use std::{io, mem, slice};
use libc::{c_char, c_uint, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH};
use libc::{c_char, c_uint, dirent, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH, O_RDONLY};
use crate::{bfmt_cstr, errno, error, xopen};
use crate::{bfmt_cstr, copy_cstr, cstr, errno, error};
pub mod unsafe_impl {
use std::ffi::CStr;
use libc::c_char;
use crate::slice_from_ptr_mut;
pub unsafe fn readlink(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
let r = libc::readlink(path, buf.cast(), bufsz - 1);
if r >= 0 {
*buf.offset(r) = b'\0';
}
r
}
#[no_mangle]
unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
super::realpath(CStr::from_ptr(path), slice_from_ptr_mut(buf, bufsz))
}
}
pub fn __open_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> Option<OwnedFd> {
pub fn __open_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> io::Result<OwnedFd> {
unsafe {
let fd = libc::open(path.as_ptr(), flags, mode as c_uint);
if fd >= 0 {
Some(OwnedFd::from_raw_fd(fd))
Ok(OwnedFd::from_raw_fd(fd))
} else {
None
}
}
}
pub fn __xopen_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> Option<OwnedFd> {
unsafe {
let fd = xopen(path.as_ptr(), flags, mode);
if fd >= 0 {
Some(OwnedFd::from_raw_fd(fd))
} else {
None
Err(io::Error::last_os_error())
}
}
}
@@ -62,78 +34,74 @@ macro_rules! open_fd {
};
}
#[macro_export]
macro_rules! xopen_fd {
($path:expr, $flags:expr) => {
$crate::__xopen_fd_impl($path, $flags, 0)
};
($path:expr, $flags:expr, $mode:expr) => {
$crate::__xopen_fd_impl($path, $flags, $mode)
};
pub unsafe fn readlink_unsafe(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
let r = libc::readlink(path, buf.cast(), bufsz - 1);
if r >= 0 {
*buf.offset(r) = b'\0';
}
r
}
pub fn readlink(path: &CStr, data: &mut [u8]) -> isize {
unsafe { unsafe_impl::readlink(path.as_ptr(), data.as_mut_ptr(), data.len()) }
pub fn readlink(path: &CStr, data: &mut [u8]) -> io::Result<usize> {
let r = unsafe { readlink_unsafe(path.as_ptr(), data.as_mut_ptr(), data.len()) };
if r < 0 {
return Err(io::Error::last_os_error());
}
Ok(r as usize)
}
pub fn fd_path(fd: RawFd, buf: &mut [u8]) -> isize {
let mut fd_buf: [u8; 40] = [0; 40];
pub fn fd_path(fd: RawFd, buf: &mut [u8]) -> io::Result<usize> {
let mut fd_buf = [0_u8; 40];
let fd_path = bfmt_cstr!(&mut fd_buf, "/proc/self/fd/{}", fd);
readlink(fd_path, buf)
}
// Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp
pub fn realpath(path: &CStr, buf: &mut [u8]) -> isize {
if let Some(fd) = open_fd!(path, O_PATH | O_CLOEXEC) {
let mut st1: libc::stat;
let mut st2: libc::stat;
let mut skip_check = false;
unsafe {
st1 = mem::zeroed();
if libc::fstat(fd.as_raw_fd(), &mut st1) < 0 {
// This shall only fail on Linux < 3.6
skip_check = true;
}
pub fn realpath(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
let fd = open_fd!(path, O_PATH | O_CLOEXEC)?;
let mut st1: libc::stat;
let mut st2: libc::stat;
let mut skip_check = false;
unsafe {
st1 = mem::zeroed();
if libc::fstat(fd.as_raw_fd(), &mut st1) < 0 {
// This shall only fail on Linux < 3.6
skip_check = true;
}
let len = fd_path(fd.as_raw_fd(), buf);
unsafe {
st2 = mem::zeroed();
if libc::stat(buf.as_ptr().cast(), &mut st2) < 0
|| (!skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino))
{
*errno() = ENOENT;
return -1;
}
}
len
} else {
*errno() = ENOENT;
-1
}
let len = fd_path(fd.as_raw_fd(), buf)?;
unsafe {
st2 = mem::zeroed();
if libc::stat(buf.as_ptr().cast(), &mut st2) < 0
|| (!skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino))
{
*errno() = ENOENT;
return Err(io::Error::last_os_error());
}
}
Ok(len)
}
extern "C" {
fn strscpy(dst: *mut c_char, src: *const c_char, size: usize) -> usize;
}
#[no_mangle]
pub unsafe extern "C" fn mkdirs(path: *const c_char, mode: mode_t) -> i32 {
pub fn mkdirs(path: &CStr, mode: mode_t) -> io::Result<()> {
let mut buf = [0_u8; 4096];
let ptr: *mut c_char = buf.as_mut_ptr().cast();
let len = strscpy(ptr, path, buf.len());
let mut curr = &mut buf[1..len];
while let Some(p) = curr.iter().position(|c| *c == b'/') {
curr[p] = b'\0';
if libc::mkdir(ptr, mode) < 0 && *errno() != EEXIST {
return -1;
let len = copy_cstr(&mut buf, path);
let buf = &mut buf[..len];
let mut off = 1;
unsafe {
while let Some(p) = buf[off..].iter().position(|c| *c == b'/') {
buf[off + p] = b'\0';
if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST {
return Err(io::Error::last_os_error());
}
buf[off + p] = b'/';
off += p + 1;
}
if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST {
return Err(io::Error::last_os_error());
}
curr[p] = b'/';
curr = &mut curr[(p + 1)..];
}
if libc::mkdir(ptr, mode) < 0 && *errno() != EEXIST {
return -1;
}
0
*errno() = 0;
Ok(())
}
pub trait ReadExt {
@@ -221,3 +189,276 @@ impl<T: Write> WriteExt for T {
Ok(())
}
}
pub struct DirEntry<'a> {
dir: &'a Directory<'a>,
entry: &'a dirent,
name_len: usize,
}
impl DirEntry<'_> {
pub fn d_name(&self) -> &CStr {
unsafe {
CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(
self.d_name.as_ptr().cast(),
self.name_len,
))
}
}
pub fn path(&self, buf: &mut [u8]) -> io::Result<usize> {
let mut len = self.dir.path(buf)?;
buf[len] = b'/';
len += 1;
len += copy_cstr(&mut buf[len..], self.d_name());
Ok(len)
}
pub fn is_dir(&self) -> bool {
self.d_type == libc::DT_DIR
}
pub fn is_file(&self) -> bool {
self.d_type == libc::DT_REG
}
pub fn is_lnk(&self) -> bool {
self.d_type == libc::DT_LNK
}
pub fn unlink(&self) -> io::Result<()> {
let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 };
unsafe {
if libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag) < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
pub fn open_as_dir(&self) -> io::Result<Directory> {
if !self.is_dir() {
return Err(io::Error::from(io::ErrorKind::NotADirectory));
}
unsafe {
let fd = libc::openat(
self.dir.as_raw_fd(),
self.d_name.as_ptr(),
O_RDONLY | O_CLOEXEC,
);
if fd < 0 {
return Err(io::Error::last_os_error());
}
Directory::try_from(OwnedFd::from_raw_fd(fd))
}
}
pub fn open_as_file(&self, flags: i32) -> io::Result<File> {
if self.is_dir() {
return Err(io::Error::from(io::ErrorKind::IsADirectory));
}
unsafe {
let fd = libc::openat(
self.dir.as_raw_fd(),
self.d_name.as_ptr(),
flags | O_CLOEXEC,
);
if fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(File::from_raw_fd(fd))
}
}
}
impl Deref for DirEntry<'_> {
type Target = dirent;
fn deref(&self) -> &dirent {
self.entry
}
}
pub struct Directory<'a> {
dirp: *mut libc::DIR,
_phantom: PhantomData<&'a libc::DIR>,
}
pub enum WalkResult {
Continue,
Abort,
Skip,
}
impl<'a> Directory<'a> {
pub fn open(path: &CStr) -> io::Result<Directory> {
let dirp = unsafe { libc::opendir(path.as_ptr()) };
if dirp.is_null() {
return Err(io::Error::last_os_error());
}
Ok(Directory {
dirp,
_phantom: PhantomData,
})
}
pub fn read(&mut self) -> io::Result<Option<DirEntry<'_>>> {
*errno() = 0;
let e = unsafe { libc::readdir(self.dirp) };
if e.is_null() {
return if *errno() != 0 {
Err(io::Error::last_os_error())
} else {
Ok(None)
};
}
// Skip both "." and ".."
unsafe {
let entry = &*e;
let d_name = CStr::from_ptr(entry.d_name.as_ptr());
return if d_name == cstr!(".") || d_name == cstr!("..") {
self.read()
} else {
let e = DirEntry {
dir: self,
entry,
name_len: d_name.to_bytes().len(),
};
Ok(Some(e))
};
}
}
pub fn rewind(&mut self) {
unsafe { libc::rewinddir(self.dirp) }
}
pub fn path(&self, buf: &mut [u8]) -> io::Result<usize> {
fd_path(self.as_raw_fd(), buf)
}
pub fn post_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
mut f: F,
) -> io::Result<WalkResult> {
self.post_order_walk_impl(&mut f)
}
pub fn pre_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
mut f: F,
) -> io::Result<WalkResult> {
self.pre_order_walk_impl(&mut f)
}
pub fn remove_all(&mut self) -> io::Result<()> {
self.post_order_walk(|e| {
e.unlink()?;
Ok(WalkResult::Continue)
})?;
Ok(())
}
}
impl Directory<'_> {
fn post_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
f: &mut F,
) -> io::Result<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
None => return Ok(Continue),
Some(ref e) => {
if e.is_dir() {
let mut dir = e.open_as_dir()?;
if let Abort = dir.post_order_walk_impl(f)? {
return Ok(Abort);
}
}
match f(e)? {
Abort => return Ok(Abort),
Skip => return Ok(Continue),
Continue => {}
}
}
}
}
}
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
f: &mut F,
) -> io::Result<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
None => return Ok(Continue),
Some(ref e) => match f(e)? {
Abort => return Ok(Abort),
Skip => return Ok(Continue),
Continue => {
if e.is_dir() {
let mut dir = e.open_as_dir()?;
if let Abort = dir.pre_order_walk_impl(f)? {
return Ok(Abort);
}
}
}
},
}
}
}
}
impl TryFrom<OwnedFd> for Directory<'_> {
type Error = io::Error;
fn try_from(fd: OwnedFd) -> io::Result<Self> {
let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) };
if dirp.is_null() {
return Err(io::Error::last_os_error());
}
Ok(Directory {
dirp,
_phantom: PhantomData,
})
}
}
impl AsRawFd for Directory<'_> {
fn as_raw_fd(&self) -> RawFd {
unsafe { libc::dirfd(self.dirp) }
}
}
impl<'a> AsFd for Directory<'a> {
fn as_fd(&self) -> BorrowedFd<'a> {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
impl Drop for Directory<'_> {
fn drop(&mut self) {
unsafe {
libc::closedir(self.dirp);
}
}
}
pub fn rm_rf(path: &CStr) -> io::Result<()> {
unsafe {
let mut stat: libc::stat = mem::zeroed();
if libc::lstat(path.as_ptr(), &mut stat) < 0 {
return Err(io::Error::last_os_error());
}
if (stat.st_mode & libc::S_IFMT as u32) == libc::S_IFDIR as u32 {
let mut dir = Directory::open(path)?;
dir.remove_all()?;
}
if libc::remove(path.as_ptr()) < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}