use core::pin::Pin;
use core::task::Context;
use core::task::Poll;
use std::ffi::CStr;
use std::io;
use std::io::Read;
use std::io::Write;
use std::os::fd::OwnedFd;
use std::os::unix::io::AsRawFd;
use std::os::unix::io::FromRawFd;
use std::os::unix::io::IntoRawFd;
use std::os::unix::io::RawFd;
use std::path::Path;
use syscalls::Errno;
use tokio::io::AsyncRead;
use tokio::io::AsyncWrite;
use tokio::io::Interest;
use tokio::io::ReadBuf;
use tokio::io::unix::AsyncFd as TokioAsyncFd;
use super::util;
#[derive(Debug)]
pub struct Fd(i32);
#[derive(Debug)]
pub struct AsyncFd(TokioAsyncFd<Fd>);
impl Fd {
pub fn new(fd: i32) -> Self {
assert_ne!(fd, -1);
Self(fd)
}
#[allow(dead_code)]
pub fn open<P: AsRef<Path>>(path: P, flags: i32) -> Result<Self, Errno> {
let path = util::to_cstring(path.as_ref());
Self::open_c(path.as_ptr(), flags)
}
pub fn open_c(path: *const libc::c_char, flags: i32) -> Result<Self, Errno> {
let fd = Errno::result(unsafe { libc::open(path, flags) })?;
Ok(Self(fd))
}
pub fn create_c(
path: *const libc::c_char,
flags: i32,
mode: libc::mode_t,
) -> Result<Self, Errno> {
let fd = Errno::result(unsafe { libc::open(path, flags | libc::O_CREAT, mode) })?;
Ok(Self(fd))
}
pub fn null(readable: bool) -> Result<Self, Errno> {
Self::open_c(
c"/dev/null".as_ptr(),
if readable {
libc::O_RDONLY
} else {
libc::O_WRONLY
},
)
}
pub fn socket(domain: i32, ty: i32, protocol: i32) -> Result<Self, Errno> {
Errno::result(unsafe { libc::socket(domain, ty, protocol) }).map(Self::new)
}
pub fn pidfd_open(pid: libc::pid_t, flags: u32) -> Result<Self, Errno> {
unsafe { syscalls::syscall2(syscalls::Sysno::pidfd_open, pid as usize, flags as usize) }
.map(|fd| Self::new(fd as i32))
}
pub fn pidfd_getfd(&self, targetfd: i32, flags: u32) -> Result<Self, Errno> {
unsafe {
syscalls::syscall3(
syscalls::Sysno::pidfd_getfd,
self.as_raw_fd() as usize,
targetfd as usize,
flags as usize,
)
}
.map(|fd| Self::new(fd as i32))
}
pub fn set_nonblocking(&self) -> Result<(), Errno> {
let fd = self.as_raw_fd();
let flags = Errno::result(unsafe { libc::fcntl(fd, libc::F_GETFL) })?;
Errno::result(unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) })?;
Ok(())
}
#[allow(unused)]
pub fn is_nonblocking(&self) -> Result<bool, Errno> {
let fd = self.as_raw_fd();
let flags = Errno::result(unsafe { libc::fcntl(fd, libc::F_GETFL) })?;
Ok(flags & libc::O_NONBLOCK == libc::O_NONBLOCK)
}
pub fn dup(&self) -> Result<Fd, Errno> {
let fd = Errno::result(unsafe { libc::dup(self.0) })?;
Ok(Self(fd))
}
pub fn dup2(&self, newfd: RawFd) -> Result<Fd, Errno> {
let fd = Errno::result(unsafe { libc::dup2(self.0, newfd) })?;
Ok(Self(fd))
}
#[allow(unused)]
pub fn close(self) -> Result<(), Errno> {
let fd = self.0;
core::mem::forget(self);
Errno::result(unsafe { libc::close(fd) })?;
Ok(())
}
pub fn leave_open(self) {
core::mem::forget(self);
}
}
impl IntoRawFd for Fd {
fn into_raw_fd(self) -> RawFd {
let fd = self.as_raw_fd();
core::mem::forget(self);
fd
}
}
impl Drop for Fd {
fn drop(&mut self) {
let _ = unsafe { libc::close(self.0) };
}
}
impl Read for Fd {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let res = Errno::result(unsafe {
libc::read(
self.0,
buf.as_mut_ptr() as *mut libc::c_void,
buf.len() as libc::size_t,
)
})?;
Ok(res as usize)
}
}
impl Write for Fd {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let res = Errno::result(unsafe {
libc::write(
self.0,
buf.as_ptr() as *const libc::c_void,
buf.len() as libc::size_t,
)
})?;
Ok(res as usize)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl AsRawFd for Fd {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl FromRawFd for Fd {
unsafe fn from_raw_fd(fd: i32) -> Self {
Self::new(fd)
}
}
impl From<Fd> for std::fs::File {
fn from(fd: Fd) -> Self {
unsafe { std::fs::File::from_raw_fd(fd.into_raw_fd()) }
}
}
impl From<OwnedFd> for Fd {
fn from(fd: OwnedFd) -> Self {
Self(fd.into_raw_fd())
}
}
impl AsyncFd {
pub fn new(fd: Fd) -> Result<Self, Errno> {
fd.set_nonblocking()?;
Ok(Self(
TokioAsyncFd::with_interest(fd, Interest::READABLE | Interest::WRITABLE).unwrap(),
))
}
pub fn readable(fd: Fd) -> Result<Self, Errno> {
fd.set_nonblocking()?;
Ok(Self(
TokioAsyncFd::with_interest(fd, Interest::READABLE).unwrap(),
))
}
pub fn writable(fd: Fd) -> Result<Self, Errno> {
fd.set_nonblocking()?;
Ok(Self(
TokioAsyncFd::with_interest(fd, Interest::WRITABLE).unwrap(),
))
}
}
impl AsRawFd for AsyncFd {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl AsyncRead for AsyncFd {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
loop {
let mut guard = futures::ready!(self.0.poll_read_ready_mut(cx))?;
match guard.try_io(|inner| {
let n = inner.get_mut().read(buf.initialize_unfilled())?;
buf.advance(n);
Ok(())
}) {
Ok(result) => return Poll::Ready(result),
Err(_would_block) => continue,
}
}
}
}
impl AsyncWrite for AsyncFd {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
loop {
let mut guard = futures::ready!(self.0.poll_write_ready_mut(cx))?;
match guard.try_io(|inner| inner.get_mut().write(buf)) {
Ok(result) => return Poll::Ready(result),
Err(_would_block) => continue,
}
}
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
}
pub fn pipe() -> Result<(Fd, Fd), Errno> {
let mut fds = [0; 2];
Errno::result(unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) })?;
Ok((Fd(fds[0]), Fd(fds[1])))
}
pub fn write_bytes(path: &'static [u8], bytes: &[u8]) -> Result<(), Errno> {
let path = unsafe { CStr::from_bytes_with_nul_unchecked(path) };
Fd::open_c(path.as_ptr(), libc::O_WRONLY)?
.write_all(bytes)
.map_err(|err| Errno::new(err.raw_os_error().unwrap()))
}
pub fn touch(path: *const libc::c_char, mode: libc::mode_t) -> Result<(), Errno> {
Fd::create_c(path, libc::O_CLOEXEC, mode).map(drop)
}
pub fn lstat(path: *const libc::c_char) -> Result<libc::stat64, Errno> {
let mut buf: libc::stat64 = unsafe { core::mem::zeroed() };
Errno::result(unsafe { libc::lstat64(path, &mut buf) })?;
Ok(buf)
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct FileType(libc::mode_t);
impl FileType {
pub fn new(path: *const libc::c_char) -> Result<Self, Errno> {
Ok(Self::from(lstat(path)?))
}
pub fn is_dir(&self) -> bool {
self.0 & libc::S_IFMT == libc::S_IFDIR
}
#[allow(unused)]
pub fn is_file(&self) -> bool {
self.0 & libc::S_IFMT == libc::S_IFREG
}
}
impl From<libc::stat64> for FileType {
fn from(stat: libc::stat64) -> Self {
Self(stat.st_mode)
}
}
pub fn is_dir(path: *const libc::c_char) -> bool {
match FileType::new(path) {
Ok(ft) => ft.is_dir(),
Err(_) => false,
}
}
fn copy_cstr_to_slice<'a>(
s: &CStr,
buf: &'a mut [libc::c_char],
) -> Result<&'a mut [libc::c_char], Errno> {
let bytes = s.to_bytes_with_nul();
if bytes.len() > buf.len() {
return Err(Errno::ENAMETOOLONG);
}
unsafe {
core::ptr::copy_nonoverlapping(
bytes.as_ptr() as *const libc::c_char,
buf.as_mut_ptr(),
bytes.len(),
)
};
Ok(&mut buf[0..bytes.len()])
}
pub fn create_dir_all(path: &CStr, mode: libc::mode_t) -> Result<(), Errno> {
let mut buf = ['\0' as libc::c_char; libc::PATH_MAX as usize];
let path = copy_cstr_to_slice(path, &mut buf)?;
create_dir_all_(path, mode)
}
fn create_dir_all_(path: &mut [libc::c_char], mode: libc::mode_t) -> Result<(), Errno> {
if path.len() == 1 {
return Ok(());
}
match Errno::result(unsafe { libc::mkdir(path.as_ptr(), mode) }) {
Ok(_) => return Ok(()),
Err(Errno::ENOENT) => {}
Err(_) if is_dir(path.as_ptr()) => return Ok(()),
Err(e) => return Err(e),
}
with_parent(path, |parent| {
match parent {
Some(p) => create_dir_all_(p, mode),
None => {
Err(Errno::EPERM)
}
}
})?;
match Errno::result(unsafe { libc::mkdir(path.as_ptr(), mode) }) {
Ok(_) => Ok(()),
Err(_) if is_dir(path.as_ptr()) => Ok(()),
Err(e) => Err(e),
}
}
pub fn touch_path(
path: &CStr,
file_mode: libc::mode_t,
dir_mode: libc::mode_t,
) -> Result<(), Errno> {
let mut buf = ['\0' as libc::c_char; libc::PATH_MAX as usize];
let path = copy_cstr_to_slice(path, &mut buf)?;
touch_path_(path, file_mode, dir_mode)
}
fn touch_path_(
path: &mut [libc::c_char],
file_mode: libc::mode_t,
dir_mode: libc::mode_t,
) -> Result<(), Errno> {
match touch(path.as_ptr(), file_mode) {
Ok(_) => return Ok(()),
Err(Errno::ENOENT) => {}
Err(e) => return Err(e),
}
with_parent(path, |parent| match parent {
Some(p) => create_dir_all_(p, dir_mode),
None => Err(Errno::ENOENT),
})?;
touch(path.as_ptr(), file_mode)
}
fn with_parent<F, T>(path: &mut [libc::c_char], mut f: F) -> T
where
F: FnMut(Option<&mut [libc::c_char]>) -> T,
{
if let Some(parent_index) = path
.iter()
.rev()
.position(|c| *c == b'/' as libc::c_char)
.map(|i| path.len() - i)
{
path[parent_index - 1] = 0;
let result = f(Some(&mut path[..parent_index]));
path[parent_index - 1] = b'/' as libc::c_char;
result
} else {
f(None)
}
}
#[cfg(test)]
mod tests {
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
use super::*;
#[test]
fn test_copy_cstr_to_slice() {
const BYTES: &[u8] = b"/foo/bar\0";
let s = CStr::from_bytes_with_nul(BYTES).unwrap();
let mut buf = [0; BYTES.len()];
assert_eq!(
copy_cstr_to_slice(s, &mut buf).unwrap().len(),
s.to_bytes_with_nul().len()
);
let mut buf = [0; BYTES.len() - 1];
assert_eq!(copy_cstr_to_slice(s, &mut buf), Err(Errno::ENAMETOOLONG));
}
#[test]
fn test_is_dir() {
assert!(is_dir(c"/".as_ptr()));
assert!(is_dir(c"/dev".as_ptr()));
assert!(!is_dir(c"/dev/null".as_ptr()));
}
#[test]
fn test_file_type() {
assert!(FileType::new(c"/".as_ptr()).unwrap().is_dir());
assert!(FileType::new(c"/dev".as_ptr()).unwrap().is_dir());
assert!(!FileType::new(c"/dev/null".as_ptr()).unwrap().is_file());
}
#[test]
fn test_create_dir_all() {
if crate::test_runs_in_own_process() {
return;
}
let tempdir = tempfile::TempDir::new().unwrap();
let path = CString::new(
tempdir
.path()
.join("some/path/to/a/dir")
.into_os_string()
.as_bytes(),
)
.unwrap();
create_dir_all(&path, 0o777).unwrap();
assert!(is_dir(path.as_ptr()));
}
#[test]
fn test_touch_path() {
if crate::test_runs_in_own_process() {
return;
}
let tempdir = tempfile::TempDir::new().unwrap();
let path = CString::new(
tempdir
.path()
.join("some/path/to/a/file")
.into_os_string()
.as_bytes(),
)
.unwrap();
touch_path(&path, 0o666, 0o777).unwrap();
assert!(FileType::new(path.as_ptr()).unwrap().is_file());
}
#[test]
fn test_nonblocking() -> Result<(), Errno> {
if crate::test_runs_in_own_process() {
return Ok(());
}
let (r, w) = pipe()?;
assert!(!r.is_nonblocking()?);
assert!(!w.is_nonblocking()?);
let f = w.dup()?;
assert!(!f.is_nonblocking()?);
w.set_nonblocking()?;
assert!(!r.is_nonblocking()?);
assert!(w.is_nonblocking()?);
assert!(f.is_nonblocking()?);
Ok(())
}
}