use super::*;
#[cfg(unix)]
use ext::ToHex;
#[cfg(unix)]
use nix::{errno, fcntl, sys::stat, unistd};
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "ios",
target_os = "freebsd"
))]
use std::convert::TryInto;
#[cfg(unix)]
use std::{
convert::Infallible, ffi::{CStr, CString, OsString}, fs, iter, os::unix::ffi::OsStringExt, os::unix::io::AsRawFd, os::unix::io::FromRawFd
};
use std::{
fmt, io::{self, Read, Write}, path
};
#[doc(inline)]
#[cfg(unix)]
pub use fcntl::{FdFlag, OFlag};
#[cfg(unix)]
pub fn move_fds(fds: &mut [(Fd, Fd)], flags: Option<FdFlag>, allow_nonexistent: bool) {
loop {
#[allow(clippy::never_loop)]
let i = 'a: loop {
for (i, &(from, to)) in fds.iter().enumerate() {
if from == to {
continue;
}
if fds.iter().position(|&(from, _)| from == to).is_none() {
break 'a i;
}
}
for &mut (from, to) in fds {
assert_eq!(from, to);
}
return;
};
let (from, to) = fds[i];
move_fd(from, to, flags, allow_nonexistent).unwrap();
fds[i].0 = to;
}
}
#[cfg(unix)]
pub fn seal_fd(fd: Fd) {
let fd2 = fcntl::open(&fd_path(fd).unwrap(), OFlag::O_RDONLY, stat::Mode::empty()).unwrap();
let fd_flags = FdFlag::from_bits(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD).unwrap()).unwrap();
let fl_flags = OFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL).unwrap())
& !(OFlag::O_WRONLY | OFlag::O_RDWR)
| OFlag::O_RDONLY;
let err = fcntl::fcntl(fd2, fcntl::FcntlArg::F_SETFL(fl_flags)).unwrap();
assert_eq!(err, 0);
move_fd(fd2, fd, Some(fd_flags), false).unwrap();
}
#[cfg(unix)]
pub fn dup_fd(oldfd: Fd, flags: Option<FdFlag>) -> nix::Result<Fd> {
let flags = flags.unwrap_or_else(|| {
FdFlag::from_bits(fcntl::fcntl(oldfd, fcntl::FcntlArg::F_GETFD).unwrap()).unwrap()
});
fcntl::fcntl(
oldfd,
if flags.contains(FdFlag::FD_CLOEXEC) {
fcntl::FcntlArg::F_DUPFD_CLOEXEC(oldfd)
} else {
fcntl::FcntlArg::F_DUPFD(oldfd)
},
)
.map(|newfd| {
assert_ne!(oldfd, newfd);
newfd
})
}
#[cfg(unix)]
pub fn move_fd(
oldfd: Fd, newfd: Fd, flags: Option<FdFlag>, allow_nonexistent: bool,
) -> nix::Result<()> {
copy_fd(oldfd, newfd, flags, allow_nonexistent).and_then(|()| unistd::close(oldfd))
}
#[cfg(unix)]
pub fn copy_fd(
oldfd: Fd, newfd: Fd, flags: Option<FdFlag>, allow_nonexistent: bool,
) -> nix::Result<()> {
if !allow_nonexistent {
let _ = fcntl::fcntl(newfd, fcntl::FcntlArg::F_GETFD).unwrap();
}
if oldfd == newfd {
return Err(nix::Error::Sys(errno::Errno::EINVAL));
}
let flags = flags.unwrap_or_else(|| {
FdFlag::from_bits(fcntl::fcntl(oldfd, fcntl::FcntlArg::F_GETFD).unwrap()).unwrap()
});
let flags = if flags.contains(FdFlag::FD_CLOEXEC) {
OFlag::O_CLOEXEC
} else {
OFlag::empty()
};
#[cfg_attr(
not(any(target_os = "android", target_os = "linux")),
allow(clippy::never_loop)
)]
loop {
match unistd::dup3(oldfd, newfd, flags) {
#[cfg(any(target_os = "android", target_os = "linux"))]
Err(nix::Error::Sys(errno::Errno::EBUSY)) => continue,
a => break a,
}
}
.map(|fd| assert_eq!(fd, newfd))
}
#[cfg(unix)]
pub fn pipe(flags: OFlag) -> nix::Result<(Fd, Fd)> {
#[cfg(any(
target_os = "android",
target_os = "dragonfly",
target_os = "emscripten",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd"
))]
{
unistd::pipe2(flags)
}
#[cfg(not(any(
target_os = "android",
target_os = "dragonfly",
target_os = "emscripten",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd"
)))]
{
unistd::pipe().map(|(read, write)| {
fn apply(fd: Fd, new_flags: OFlag) {
let fs_flags =
OFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL).unwrap());
let new_fs_flags = fs_flags | (new_flags & !OFlag::O_CLOEXEC);
if fs_flags != new_fs_flags {
let err = fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFL(new_fs_flags)).unwrap();
assert_eq!(err, 0);
}
let fd_flags =
FdFlag::from_bits(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD).unwrap()).unwrap();
let mut new_fd_flags = fd_flags;
new_fd_flags.set(FdFlag::FD_CLOEXEC, new_flags.contains(OFlag::O_CLOEXEC));
if fd_flags != new_fd_flags {
let err = fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_fd_flags)).unwrap();
assert_eq!(err, 0);
}
}
apply(read, flags);
apply(write, flags);
(read, write)
})
}
}
#[cfg(unix)]
pub fn memfd_create(name: &CStr, cloexec: bool) -> nix::Result<Fd> {
let ret = {
#[cfg(any(target_os = "android", target_os = "linux"))]
{
use nix::sys::memfd;
let mut flags = memfd::MemFdCreateFlag::empty();
flags.set(memfd::MemFdCreateFlag::MFD_CLOEXEC, cloexec);
memfd::memfd_create(name, flags)
}
#[cfg(target_os = "freebsd")]
{
let _ = name;
let flags = if cloexec {
OFlag::O_RDWR | OFlag::O_CLOEXEC
} else {
OFlag::O_RDWR
};
errno::Errno::result(unsafe {
libc::shm_open(libc::SHM_ANON, flags.bits(), stat::Mode::S_IRWXU.bits())
})
}
#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "freebsd")))]
{
let _ = name;
Err(nix::Error::Sys(errno::Errno::ENOSYS))
}
};
#[cfg(all(unix, not(any(target_os = "ios", target_os = "macos"))))]
let ret = ret.or_else(|_e| {
use nix::sys::mman;
let mut name = tmpfile(&"/".into());
let name = heapless_string_to_cstr(&mut name);
mman::shm_open(
name,
OFlag::O_RDWR | OFlag::O_CREAT | OFlag::O_EXCL,
stat::Mode::S_IRWXU,
)
.map(|fd| {
if !cloexec {
let mut flags_ =
FdFlag::from_bits(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD).unwrap()).unwrap();
flags_.remove(FdFlag::FD_CLOEXEC);
let _ = fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(flags_)).unwrap();
}
mman::shm_unlink(name).unwrap();
fd
})
});
#[cfg(unix)]
{
ret.or_else(|_e| {
let mut name = tmpfile(&"/tmp/".into());
let name = heapless_string_to_cstr(&mut name);
fcntl::open(
name,
OFlag::O_RDWR
| OFlag::O_CREAT | OFlag::O_EXCL
| if cloexec {
OFlag::O_CLOEXEC
} else {
OFlag::empty()
},
stat::Mode::S_IRWXU,
)
.map(|fd| {
unistd::unlink(name).unwrap();
fd
})
})
}
#[cfg(windows)]
{
ret.or_else(|_e| {
unimplemented!()
})
}
}
#[cfg(unix)]
pub fn execve(path: &CStr, args: &[&CStr], vars: &[&CStr]) -> nix::Result<Infallible> {
let args: heapless::Vec<*const libc::c_char, heapless::consts::U256> = args
.iter()
.map(|arg| arg.as_ptr())
.chain(iter::once(std::ptr::null()))
.collect();
let vars: heapless::Vec<*const libc::c_char, heapless::consts::U256> = vars
.iter()
.map(|arg| arg.as_ptr())
.chain(iter::once(std::ptr::null()))
.collect();
let _ = unsafe { libc::execve(path.as_ptr(), args.as_ptr(), vars.as_ptr()) };
Err(nix::Error::Sys(nix::errno::Errno::last()))
}
#[cfg(unix)]
fn heapless_string_to_cstr<N>(string: &mut heapless::String<N>) -> &CStr
where
N: heapless::ArrayLength<u8>,
{
string.push('\0').unwrap();
CStr::from_bytes_with_nul(string.as_bytes()).unwrap()
}
#[cfg(unix)]
fn tmpfile(
prefix: &heapless::String<heapless::consts::U6>,
) -> heapless::String<typenum::operator_aliases::Sum<heapless::consts::U6, heapless::consts::U32>> {
let mut random: [u8; 16] = [0; 16];
let rand = nix::fcntl::open(
"/dev/urandom",
OFlag::O_RDONLY,
nix::sys::stat::Mode::empty(),
)
.expect("Couldn't open /dev/urandom");
let rand = unsafe { fs::File::from_raw_fd(rand) };
(&rand).read_exact(&mut random).unwrap();
drop(rand);
let mut ret = heapless::String::new();
std::fmt::Write::write_fmt(&mut ret, format_args!("{}{}", prefix, random.to_hex())).unwrap();
ret
}
#[cfg(unix)]
pub fn fexecve(fd: Fd, args: &[&CStr], vars: &[&CStr]) -> nix::Result<Infallible> {
let mut res = Err(nix::Error::Sys(nix::errno::Errno::ENOSYS));
#[cfg(any(
target_os = "android",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "illumos",
target_os = "linux",
target_os = "solaris"
))]
{
res = res.or_else(|_| {
let args: heapless::Vec<*const libc::c_char, heapless::consts::U256> = args
.iter()
.map(|arg| arg.as_ptr())
.chain(iter::once(std::ptr::null()))
.collect();
let vars: heapless::Vec<*const libc::c_char, heapless::consts::U256> = vars
.iter()
.map(|arg| arg.as_ptr())
.chain(iter::once(std::ptr::null()))
.collect();
let _ = unsafe { libc::fexecve(fd, args.as_ptr(), vars.as_ptr()) };
Err(nix::Error::Sys(nix::errno::Errno::last()))
});
}
if res == Err(nix::Error::Sys(nix::errno::Errno::ENOSYS)) {
let mut path = fd_path_heapless(fd).unwrap();
let path = heapless_string_to_cstr(&mut path);
res = execve(&path, args, vars);
if res.is_err() {
res = Err(nix::Error::Sys(nix::errno::Errno::ENOSYS));
}
}
if res == Err(nix::Error::Sys(nix::errno::Errno::ENOSYS)) {
res = fexecve_fallback(fd, args, vars);
}
res
}
#[cfg(unix)]
fn fexecve_fallback(fd: Fd, args: &[&CStr], vars: &[&CStr]) -> nix::Result<Infallible> {
use std::hash::Hasher;
struct HashWriter<T: Hasher, W: Write>(T, W);
impl<T: Hasher, W: Write> Write for HashWriter<T, W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.1.write(buf).map(|written| {
self.0.write(&buf[..written]);
written
})
}
fn flush(&mut self) -> io::Result<()> {
self.1.flush()
}
}
let tmp =
fcntl::open("/tmp", OFlag::O_CLOEXEC, stat::Mode::empty()).expect("couldn't open /tmp");
let mut to_path = tmpfile(&"".into());
let to_path = heapless_string_to_cstr(&mut to_path);
let to = fcntl::openat(
tmp,
to_path,
OFlag::O_RDWR | OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_CLOEXEC,
stat::Mode::S_IRWXU,
)
.unwrap();
let mut from = unsafe { fs::File::from_raw_fd(fd) };
let mut to = unsafe { fs::File::from_raw_fd(to) };
let pos = io::Seek::seek(&mut from, io::SeekFrom::Current(0)).unwrap();
let x = io::Seek::seek(&mut from, io::SeekFrom::Start(0)).unwrap();
assert_eq!(x, 0);
let mut hasher = twox_hash::XxHash::with_seed(0);
let _ = io::copy(&mut from, &mut HashWriter(&mut hasher, &mut to)).unwrap();
let x = io::Seek::seek(&mut from, io::SeekFrom::Start(pos)).unwrap();
assert_eq!(x, pos);
assert_eq!(from.metadata().unwrap().len(), to.metadata().unwrap().len());
let mut hash: [u8; 16] = [0; 16];
hash[..8].copy_from_slice(&hasher.finish().to_ne_bytes());
hasher.write_u8(0);
hash[8..].copy_from_slice(&hasher.finish().to_ne_bytes());
let mut to_path2: heapless::String<heapless::consts::U33> = heapless::String::new();
std::fmt::Write::write_fmt(&mut to_path2, format_args!("{}", hash.to_hex())).unwrap();
let to_path2 = heapless_string_to_cstr(&mut to_path2);
fcntl::renameat(Some(tmp), to_path, Some(tmp), to_path2).unwrap();
let to_path = to_path2;
let mut to_path_full: heapless::String<
typenum::operator_aliases::Sum<heapless::consts::U6, heapless::consts::U32>,
> = "/tmp/".into();
to_path_full.push_str(to_path.to_str().unwrap()).unwrap();
let to_path_full = heapless_string_to_cstr(&mut to_path_full);
let (read, write) = pipe(OFlag::O_CLOEXEC).unwrap();
if let unistd::ForkResult::Parent { .. } = unistd::fork().expect("Fork failed") {
unistd::close(read).unwrap();
execve(to_path_full, args, vars).map_err(|e| {
let _ = unistd::write(write, &[0]).unwrap();
unistd::close(write).unwrap();
unistd::unlinkat(Some(tmp), to_path, unistd::UnlinkatFlags::NoRemoveDir).unwrap();
unistd::close(tmp).unwrap();
e
})
} else {
unistd::close(write).unwrap();
match unistd::read(read, &mut [0, 0]) {
Ok(1) => unsafe {
unistd::close(tmp).unwrap();
libc::_exit(0)
},
Ok(0) => {
unistd::close(tmp).unwrap();
unsafe { libc::_exit(0) }
}
e => {
unistd::close(tmp).unwrap();
panic!("{:?}", e)
}
}
}
}
pub fn copy<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W, len: u64) -> io::Result<()>
where
R: Read,
W: Write,
{
io::copy(&mut reader.take(len), writer).and_then(|written| {
if written == len {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"copy couldn't finish",
))
}
})
}
#[cfg(unix)]
pub fn copy_sendfile<O: AsRawFd, I: AsRawFd>(in_: &I, out: &O, len: u64) -> nix::Result<()> {
#[cfg(any(target_os = "android", target_os = "linux"))]
{
use nix::sys::sendfile;
let mut offset: u64 = 0;
while offset != len {
let n = sendfile::sendfile(
out.as_raw_fd(),
in_.as_raw_fd(),
None,
(len - offset).try_into().unwrap(),
)?;
let n: u64 = n.try_into().unwrap();
assert!(n <= len - offset);
if n == 0 {
return Err(nix::Error::Sys(nix::errno::Errno::EIO));
}
offset += n;
}
Ok(())
}
#[cfg(any(target_os = "ios", target_os = "macos"))]
{
use nix::sys::sendfile;
let mut offset = 0;
while offset != len {
let (result, n) = sendfile::sendfile(
in_.as_raw_fd(),
out.as_raw_fd(),
0,
Some((len - offset).try_into().unwrap()),
None,
None,
);
result?;
let n: u64 = n.try_into().unwrap();
assert!(n <= len - offset);
if n == 0 {
return Err(nix::Error::Sys(nix::errno::Errno::EIO));
}
offset += n;
}
Ok(())
}
#[cfg(target_os = "freebsd")]
{
use nix::sys::sendfile;
let mut offset = 0;
while offset != len {
let (result, n) = sendfile::sendfile(
in_.as_raw_fd(),
out.as_raw_fd(),
0,
Some((len - offset).try_into().unwrap()),
None,
None,
sendfile::SfFlags::empty(),
0,
);
result?;
let n: u64 = n.try_into().unwrap();
assert!(n <= len - offset);
if n == 0 {
return Err(nix::Error::Sys(nix::errno::Errno::EIO));
}
offset += n;
}
Ok(())
}
#[cfg(not(any(
target_os = "android",
target_os = "linux",
target_os = "ios",
target_os = "macos",
target_os = "freebsd"
)))]
{
let _ = (in_, out, len);
unimplemented!()
}
}
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn copy_splice<O: AsRawFd, I: AsRawFd>(in_: &I, out: &O, len: u64) -> nix::Result<()> {
let mut offset = 0;
while offset != len {
let n = fcntl::splice(
in_.as_raw_fd(),
None,
out.as_raw_fd(),
None,
(len - offset).try_into().unwrap(),
fcntl::SpliceFFlags::empty(),
)?;
let n: u64 = n.try_into().unwrap();
assert!(n <= len - offset);
if n == 0 {
return Err(nix::Error::Sys(nix::errno::Errno::EIO));
}
offset += n;
}
Ok(())
}
pub fn fd_dir() -> io::Result<path::PathBuf> {
#[cfg(any(target_os = "android", target_os = "linux"))]
{
Ok(path::PathBuf::from("/proc/self/fd"))
}
#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "ios"))]
{
Ok(path::PathBuf::from("/dev/fd"))
}
#[cfg(not(any(
target_os = "android",
target_os = "linux",
target_os = "freebsd",
target_os = "macos",
target_os = "ios"
)))]
{
Err(io::Error::new(
io::ErrorKind::NotFound,
"no known /proc/self/fd equivalent for OS",
))
}
}
pub fn fd_path(fd: Fd) -> io::Result<path::PathBuf> {
#[cfg(any(target_os = "android", target_os = "linux"))]
{
Ok(path::PathBuf::from(format!("/proc/self/fd/{}", fd)))
}
#[cfg(any(
target_os = "freebsd",
target_os = "netbsd",
target_os = "macos",
target_os = "ios"
))]
{
Ok(path::PathBuf::from(format!("/dev/fd/{}", fd)))
}
#[cfg(not(any(
target_os = "android",
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "macos",
target_os = "ios"
)))]
{
let _ = fd;
Err(io::Error::new(
io::ErrorKind::NotFound,
"no known /proc/self/fd equivalent for OS",
))
}
}
#[doc(hidden)]
pub fn fd_path_heapless(fd: Fd) -> io::Result<heapless::String<heapless::consts::U24>> {
let mut ret = heapless::String::new();
#[cfg(any(target_os = "android", target_os = "linux"))]
{
use std::fmt::Write;
ret.write_fmt(format_args!("/proc/self/fd/{}", fd)).unwrap();
}
#[cfg(any(
target_os = "freebsd",
target_os = "netbsd",
target_os = "macos",
target_os = "ios"
))]
{
use std::fmt::Write;
ret.write_fmt(format_args!("/dev/fd/{}", fd)).unwrap();
}
#[cfg(not(any(
target_os = "android",
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "macos",
target_os = "ios"
)))]
{
let _ = (fd, &mut ret);
return Err(io::Error::new(
io::ErrorKind::NotFound,
"no known /proc/self/fd equivalent for OS",
));
}
#[allow(unreachable_code)]
Ok(ret)
}
pub struct FdIter(#[cfg(unix)] *mut libc::DIR);
impl FdIter {
pub fn new() -> Result<Self, io::Error> {
let dir = fd_dir()?;
#[cfg(unix)]
{
let dir =
CString::new(<path::PathBuf as Into<OsString>>::into(dir).into_vec()).unwrap();
let dirp: *mut libc::DIR = unsafe { libc::opendir(dir.as_ptr()) };
assert!(!dirp.is_null());
Ok(Self(dirp))
}
#[cfg(windows)]
{
let _ = dir;
Err(io::Error::new(
io::ErrorKind::NotFound,
"can't iterate dir?",
))
}
}
}
impl Iterator for FdIter {
type Item = Fd;
fn next(&mut self) -> Option<Self::Item> {
#[cfg(unix)]
{
let mut dent;
while {
dent = unsafe { libc::readdir(self.0) };
!dent.is_null()
} {
let name = unsafe { CStr::from_ptr((*dent).d_name.as_ptr()) };
if name == CStr::from_bytes_with_nul(b".\0").unwrap()
|| name == CStr::from_bytes_with_nul(b"..\0").unwrap()
{
continue;
}
let fd = name
.to_str()
.map_err(|_| ())
.and_then(|fd| fd.parse::<Fd>().map_err(|_| ()));
if fd.is_err() || fd.unwrap() == unsafe { libc::dirfd(self.0) } {
continue;
}
return Some(fd.unwrap());
}
None
}
#[cfg(windows)]
{
unreachable!()
}
}
}
impl fmt::Debug for FdIter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("FdIter").finish()
}
}
impl Drop for FdIter {
fn drop(&mut self) {
#[cfg(unix)]
{
let ret = unsafe { libc::closedir(self.0) };
assert_eq!(ret, 0);
}
}
}