use std::io;
use std::mem;
use std::ffi::{OsString, CStr};
use std::fs::{File, read_link};
use std::os::unix::io::{AsRawFd, RawFd, FromRawFd, IntoRawFd};
use std::os::unix::ffi::{OsStringExt};
use std::path::{PathBuf};
use libc;
use crate::metadata::{self, Metadata};
use crate::list::{DirIter, open_dir, open_dirfd};
use crate::{Dir, AsPath};
#[cfg(target_os="linux")]
const BASE_OPEN_FLAGS: libc::c_int = libc::O_PATH|libc::O_CLOEXEC;
#[cfg(target_os="freebsd")]
const BASE_OPEN_FLAGS: libc::c_int = libc::O_DIRECTORY|libc::O_CLOEXEC;
#[cfg(not(any(target_os="linux", target_os="freebsd")))]
const BASE_OPEN_FLAGS: libc::c_int = libc::O_CLOEXEC;
impl Dir {
#[deprecated(since="0.1.15", note="\
Use `Dir::open(\".\")` instead. \
Dir::cwd() doesn't open actual file descriptor and uses magic value \
instead which resolves to current dir on any syscall invocation. \
This is usually counter-intuitive and yields a broken \
file descriptor when using `Dir::as_raw_fd`. \
Will be removed in version v0.2 of the library.")]
pub fn cwd() -> Dir {
Dir(libc::AT_FDCWD)
}
pub fn open<P: AsPath>(path: P) -> io::Result<Dir> {
Dir::_open(to_cstr(path)?.as_ref())
}
fn _open(path: &CStr) -> io::Result<Dir> {
let fd = unsafe {
libc::open(path.as_ptr(), BASE_OPEN_FLAGS)
};
if fd < 0 {
Err(io::Error::last_os_error())
} else {
Ok(Dir(fd))
}
}
pub fn list_dir<P: AsPath>(&self, path: P) -> io::Result<DirIter> {
open_dir(self, to_cstr(path)?.as_ref())
}
pub fn list_self(&self) -> io::Result<DirIter> {
unsafe {
open_dirfd(libc::dup(self.0))
}
}
pub fn sub_dir<P: AsPath>(&self, path: P) -> io::Result<Dir> {
self._sub_dir(to_cstr(path)?.as_ref())
}
fn _sub_dir(&self, path: &CStr) -> io::Result<Dir> {
let fd = unsafe {
libc::openat(self.0,
path.as_ptr(),
BASE_OPEN_FLAGS|libc::O_NOFOLLOW)
};
if fd < 0 {
Err(io::Error::last_os_error())
} else {
Ok(Dir(fd))
}
}
pub fn read_link<P: AsPath>(&self, path: P) -> io::Result<PathBuf> {
self._read_link(to_cstr(path)?.as_ref())
}
fn _read_link(&self, path: &CStr) -> io::Result<PathBuf> {
let mut buf = vec![0u8; 4096];
let res = unsafe {
libc::readlinkat(self.0,
path.as_ptr(),
buf.as_mut_ptr() as *mut libc::c_char, buf.len())
};
if res < 0 {
Err(io::Error::last_os_error())
} else {
buf.truncate(res as usize);
Ok(OsString::from_vec(buf).into())
}
}
pub fn open_file<P: AsPath>(&self, path: P) -> io::Result<File> {
self._open_file(to_cstr(path)?.as_ref(),
libc::O_RDONLY, 0)
}
pub fn write_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
-> io::Result<File>
{
self._open_file(to_cstr(path)?.as_ref(),
libc::O_CREAT|libc::O_WRONLY|libc::O_TRUNC,
mode)
}
pub fn append_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
-> io::Result<File>
{
self._open_file(to_cstr(path)?.as_ref(),
libc::O_CREAT|libc::O_WRONLY|libc::O_APPEND,
mode)
}
#[deprecated(since="0.1.7", note="please use `write_file` instead")]
pub fn create_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
-> io::Result<File>
{
self._open_file(to_cstr(path)?.as_ref(),
libc::O_CREAT|libc::O_WRONLY|libc::O_TRUNC,
mode)
}
#[cfg(target_os="linux")]
pub fn new_unnamed_file(&self, mode: libc::mode_t)
-> io::Result<File>
{
self._open_file(unsafe { CStr::from_bytes_with_nul_unchecked(b".\0") },
libc::O_TMPFILE|libc::O_WRONLY,
mode)
}
#[cfg(not(target_os="linux"))]
pub fn new_unnamed_file<P: AsPath>(&self, _mode: libc::mode_t)
-> io::Result<File>
{
Err(io::Error::new(io::ErrorKind::Other,
"creating unnamed tmpfiles is only supported on linux"))
}
#[cfg(target_os="linux")]
pub fn link_file_at<F: AsRawFd, P: AsPath>(&self, file: &F, path: P)
-> io::Result<()>
{
let fd_path = format!("/proc/self/fd/{}", file.as_raw_fd());
_hardlink(&Dir(libc::AT_FDCWD), to_cstr(fd_path)?.as_ref(),
&self, to_cstr(path)?.as_ref(),
libc::AT_SYMLINK_FOLLOW)
}
#[cfg(not(target_os="linux"))]
pub fn link_file_at<F: AsRawFd, P: AsPath>(&self, _file: F, _path: P)
-> io::Result<()>
{
Err(io::Error::new(io::ErrorKind::Other,
"linking unnamed fd to directories is only supported on linux"))
}
pub fn new_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
-> io::Result<File>
{
self._open_file(to_cstr(path)?.as_ref(),
libc::O_CREAT|libc::O_EXCL|libc::O_WRONLY,
mode)
}
pub fn update_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
-> io::Result<File>
{
self._open_file(to_cstr(path)?.as_ref(),
libc::O_CREAT|libc::O_RDWR,
mode)
}
fn _open_file(&self, path: &CStr, flags: libc::c_int, mode: libc::mode_t)
-> io::Result<File>
{
unsafe {
let res = libc::openat(self.0, path.as_ptr(),
flags|libc::O_CLOEXEC|libc::O_NOFOLLOW,
mode as libc::c_uint);
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(File::from_raw_fd(res))
}
}
}
pub fn symlink<P: AsPath, R: AsPath>(&self, path: P, value: R)
-> io::Result<()>
{
self._symlink(to_cstr(path)?.as_ref(), to_cstr(value)?.as_ref())
}
fn _symlink(&self, path: &CStr, link: &CStr) -> io::Result<()> {
unsafe {
let res = libc::symlinkat(link.as_ptr(),
self.0, path.as_ptr());
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
pub fn create_dir<P: AsPath>(&self, path: P, mode: libc::mode_t)
-> io::Result<()>
{
self._create_dir(to_cstr(path)?.as_ref(), mode)
}
fn _create_dir(&self, path: &CStr, mode: libc::mode_t) -> io::Result<()> {
unsafe {
let res = libc::mkdirat(self.0, path.as_ptr(), mode);
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
pub fn local_rename<P: AsPath, R: AsPath>(&self, old: P, new: R)
-> io::Result<()>
{
rename(self, to_cstr(old)?.as_ref(), self, to_cstr(new)?.as_ref())
}
#[cfg(target_os="linux")]
pub fn local_exchange<P: AsPath, R: AsPath>(&self, old: P, new: R)
-> io::Result<()>
{
let flags = libc::RENAME_EXCHANGE as libc::c_int;
rename_flags(self, to_cstr(old)?.as_ref(),
self, to_cstr(new)?.as_ref(),
flags)
}
pub fn remove_dir<P: AsPath>(&self, path: P)
-> io::Result<()>
{
self._unlink(to_cstr(path)?.as_ref(), libc::AT_REMOVEDIR)
}
pub fn remove_file<P: AsPath>(&self, path: P)
-> io::Result<()>
{
self._unlink(to_cstr(path)?.as_ref(), 0)
}
fn _unlink(&self, path: &CStr, flags: libc::c_int) -> io::Result<()> {
unsafe {
let res = libc::unlinkat(self.0, path.as_ptr(), flags);
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
pub fn recover_path(&self) -> io::Result<PathBuf> {
let fd = self.0;
if fd != libc::AT_FDCWD {
read_link(format!("/proc/self/fd/{}", fd))
} else {
read_link("/proc/self/cwd")
}
}
pub fn metadata<P: AsPath>(&self, path: P) -> io::Result<Metadata> {
self._stat(to_cstr(path)?.as_ref(), libc::AT_SYMLINK_NOFOLLOW)
}
fn _stat(&self, path: &CStr, flags: libc::c_int) -> io::Result<Metadata> {
unsafe {
let mut stat = mem::zeroed();
let res = libc::fstatat(self.0, path.as_ptr(),
&mut stat, flags);
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(metadata::new(stat))
}
}
}
pub fn self_metadata(&self) -> io::Result<Metadata> {
unsafe {
let mut stat = mem::zeroed();
let res = libc::fstat(self.0, &mut stat);
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(metadata::new(stat))
}
}
}
pub unsafe fn from_raw_fd_checked(fd: RawFd) -> io::Result<Self> {
let mut stat = mem::zeroed();
let res = libc::fstat(fd, &mut stat);
if res < 0 {
Err(io::Error::last_os_error())
} else {
match stat.st_mode & libc::S_IFMT {
libc::S_IFDIR => Ok(Dir(fd)),
_ => Err(io::Error::from_raw_os_error(libc::ENOTDIR))
}
}
}
pub fn try_clone(&self) -> io::Result<Self> {
let fd = unsafe { libc::dup(self.0) };
if fd == -1 {
Err(io::Error::last_os_error())
} else {
unsafe { Self::from_raw_fd_checked(fd) }
}
}
}
pub fn rename<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R)
-> io::Result<()>
where P: AsPath, R: AsPath,
{
_rename(old_dir, to_cstr(old)?.as_ref(), new_dir, to_cstr(new)?.as_ref())
}
fn _rename(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr)
-> io::Result<()>
{
unsafe {
let res = libc::renameat(old_dir.0, old.as_ptr(),
new_dir.0, new.as_ptr());
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
pub fn hardlink<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R)
-> io::Result<()>
where P: AsPath, R: AsPath,
{
_hardlink(old_dir, to_cstr(old)?.as_ref(),
new_dir, to_cstr(new)?.as_ref(),
0)
}
fn _hardlink(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr,
flags: libc::c_int)
-> io::Result<()>
{
unsafe {
let res = libc::linkat(old_dir.0, old.as_ptr(),
new_dir.0, new.as_ptr(), flags);
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
#[cfg(target_os="linux")]
pub fn rename_flags<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R,
flags: libc::c_int)
-> io::Result<()>
where P: AsPath, R: AsPath,
{
_rename_flags(old_dir, to_cstr(old)?.as_ref(),
new_dir, to_cstr(new)?.as_ref(),
flags)
}
#[cfg(target_os="linux")]
fn _rename_flags(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr,
flags: libc::c_int)
-> io::Result<()>
{
unsafe {
let res = libc::syscall(
libc::SYS_renameat2,
old_dir.0, old.as_ptr(),
new_dir.0, new.as_ptr(), flags);
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
impl AsRawFd for Dir {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.0
}
}
impl FromRawFd for Dir {
#[inline]
unsafe fn from_raw_fd(fd: RawFd) -> Dir {
Dir(fd)
}
}
impl IntoRawFd for Dir {
#[inline]
fn into_raw_fd(self) -> RawFd {
let result = self.0;
mem::forget(self);
return result;
}
}
impl Drop for Dir {
fn drop(&mut self) {
let fd = self.0;
if fd != libc::AT_FDCWD {
unsafe {
libc::close(fd);
}
}
}
}
fn to_cstr<P: AsPath>(path: P) -> io::Result<P::Buffer> {
path.to_path()
.ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput,
"nul byte in file name")
})
}
#[cfg(test)]
mod test {
use std::io::{Read};
use std::path::Path;
use std::os::unix::io::{FromRawFd, IntoRawFd};
use crate::{Dir};
#[test]
fn test_open_ok() {
assert!(Dir::open("src").is_ok());
}
#[test]
#[cfg_attr(target_os="freebsd", should_panic(expected="Not a directory"))]
fn test_open_file() {
Dir::open("src/lib.rs").unwrap();
}
#[test]
fn test_read_file() {
let dir = Dir::open("src").unwrap();
let mut buf = String::new();
dir.open_file("lib.rs").unwrap()
.read_to_string(&mut buf).unwrap();
assert!(buf.find("extern crate libc;").is_some());
}
#[test]
fn test_from_into() {
let dir = Dir::open("src").unwrap();
let dir = unsafe { Dir::from_raw_fd(dir.into_raw_fd()) };
let mut buf = String::new();
dir.open_file("lib.rs").unwrap()
.read_to_string(&mut buf).unwrap();
assert!(buf.find("extern crate libc;").is_some());
}
#[test]
#[should_panic(expected="No such file or directory")]
fn test_open_no_dir() {
Dir::open("src/some-non-existent-file").unwrap();
}
#[test]
fn test_list() {
let dir = Dir::open("src").unwrap();
let me = dir.list_dir(".").unwrap();
assert!(me.collect::<Result<Vec<_>, _>>().unwrap()
.iter().find(|x| {
x.file_name() == Path::new("lib.rs").as_os_str()
})
.is_some());
}
#[test]
fn test_from_raw_fd_checked() {
let fd = Dir::open(".").unwrap().into_raw_fd();
let dir = unsafe { Dir::from_raw_fd_checked(fd) }.unwrap();
let filefd = dir.open_file("src/lib.rs").unwrap().into_raw_fd();
match unsafe { Dir::from_raw_fd_checked(filefd) } {
Ok(_) => assert!(false, "from_raw_fd_checked succeeded on a non-directory fd!"),
Err(e) => assert_eq!(e.raw_os_error().unwrap(), libc::ENOTDIR)
}
}
#[test]
fn test_try_clone() {
let d = Dir::open(".").unwrap();
let d2 = d.try_clone().unwrap();
drop(d);
let _file = d2.open_file("src/lib.rs").unwrap();
}
}