use core::convert::TryFrom;
use core::fmt;
use core::hint::unreachable_unchecked as unreach;
use core::mem;
use core::mem::MaybeUninit as MU;
use core::ops::*;
use fallible::*;
use libc;
use null_terminated::Nul;
use io::*;
use rand::*;
use void::Void;
use {Error, Str};
use time::*;
use util::*;
#[derive(Debug)]
pub struct File {
fd: isize
}
impl File {
#[inline]
pub fn chmod(&self, mode: Mode) -> Result<(), Error> {
unsafe { esyscall_!(FCHMOD, self.fd, mode.bits) }
}
#[inline]
pub fn stat(&self) -> Result<Stat, Error> { unsafe {
let mut st = MU::<libc::stat>::uninit();
esyscall!(FSTAT, self.fd, st.as_mut_ptr())?;
Ok(Stat::from(st.assume_init()))
} }
#[inline]
pub fn sync(&self, metadata: bool) -> Result<(), Error> {
unsafe { if !metadata { esyscall_!(FDATASYNC, self.fd) }
else { esyscall_!(FSYNC, self.fd) } }
}
#[inline]
pub fn truncate(&self, length: u64) -> Result<(), Error> {
unsafe { esyscall_!(FTRUNCATE, self.fd, try_to_usize(length)?) }
}
#[cfg(target_os = "linux")]
#[inline]
pub fn exec(&self, argv: &Nul<&Str>, envp: &Nul<&Str>) -> Result<Void, Error> {
exec_at(Some(self), str0!(""), argv, envp, AtFlags::Follow)
}
#[cfg(not(target_os = "linux"))]
#[inline]
pub fn exec(&self, argv: &Nul<&Str>, envp: &Nul<&Str>) -> Result<Void, Error> {
unsafe { esyscall!(FEXECVE, self.fd, argv as *const _, envp as *const _).map(|_| unreach()) }
}
#[inline]
pub fn fd(&self) -> isize { self.fd }
#[inline]
pub fn new(fd: isize) -> Option<Self> {
if unsafe {
syscall!(FCNTL, fd, ::libc::F_GETFD) as isize
} >= 0 { Some(File { fd }) } else { None }
}
#[inline]
pub const fn new_unchecked(fd: isize) -> Self { File { fd } }
}
#[inline]
pub fn new_pipe(flags: OpenFlags) -> Result<(File, File), Error> { unsafe {
let mut fds = MU::<[::libc::c_int; 2]>::uninit().assume_init();
esyscall!(PIPE2, &mut fds as *mut _, flags.bits)?;
Ok((File { fd: fds[0] as _ }, File { fd: fds[1] as _ }))
} }
#[deprecated(since = "0.3.0", note = "use `new_pipe`")]
pub use self::new_pipe as mk_pipe;
#[inline]
pub fn open_at(opt_dir: Option<&File>, path: &Str, o_mode: OpenMode,
f_mode: Option<Mode>) -> Result<File, Error> {
unsafe { match f_mode {
None => esyscall!(OPENAT, from_opt_dir(opt_dir), path.as_ptr(), o_mode.0),
Some(f_mode) => esyscall!(OPENAT, from_opt_dir(opt_dir), path.as_ptr(),
o_mode.0 | ::libc::O_CREAT as usize, f_mode.bits),
} }.map(|fd| File { fd: fd as isize })
}
#[inline]
pub fn rename_at(opt_old_dir: Option<&File>, old_path: &Str,
opt_new_dir: Option<&File>, new_path: &Str) -> Result<(), Error> {
unsafe { esyscall_!(RENAMEAT, from_opt_dir(opt_old_dir), old_path.as_ptr(),
from_opt_dir(opt_new_dir), new_path.as_ptr()) }
}
#[inline]
pub fn link_at(opt_old_dir: Option<&File>, old_path: &Str,
opt_new_dir: Option<&File>, new_path: &Str,
at_flags: AtFlags) -> Result<(), Error> {
unsafe { esyscall_!(LINKAT, from_opt_dir(opt_old_dir), old_path.as_ptr(),
from_opt_dir(opt_new_dir), new_path.as_ptr(),
if at_flags.contains(AtFlags::Follow) { ::libc::AT_SYMLINK_FOLLOW }
else { 0 } | AT_EMPTY_PATH) }
}
#[inline]
pub fn unlink_at(opt_dir: Option<&File>, path: &Str) -> Result<(), Error> {
unsafe { esyscall_!(UNLINKAT, from_opt_dir(opt_dir), path.as_ptr(), 0) }
}
#[inline]
pub fn chmod_at(opt_dir: Option<&File>, path: &Str,
mode: Mode, at_flags: AtFlags) -> Result<(), Error> {
unsafe { esyscall_!(FCHMODAT, from_opt_dir(opt_dir), path.as_ptr(), mode.bits,
if at_flags.contains(AtFlags::Follow) { 0 }
else { libc::AT_SYMLINK_NOFOLLOW }) }
}
#[inline]
pub fn stat_at(opt_dir: Option<&File>, path: &Str,
at_flags: AtFlags) -> Result<Stat, Error> { unsafe {
let fl = if at_flags.contains(AtFlags::Follow) { 0 } else { libc::AT_SYMLINK_NOFOLLOW }
| AT_EMPTY_PATH;
let fd = from_opt_dir(opt_dir);
let mut st = MU::<libc::stat>::uninit();
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
esyscall_!(NEWFSTATAT, fd, path.as_ptr(), st.as_mut_ptr(), fl)?;
#[cfg(all(target_os = "linux", not(target_arch = "x86_64")))]
esyscall_!(FSTATAT64, fd, path.as_ptr(), st.as_mut_ptr(), fl)?;
#[cfg(not(target_os = "linux"))]
esyscall_!(FSTATAT, fd, path.as_ptr(), st.as_mut_ptr(), fl)?;
Ok(Stat::from(st.assume_init()))
} }
#[cfg(target_os = "linux")]
#[inline]
pub fn exec_at(opt_dir: Option<&File>, path: &Str,
argv: &Nul<&Str>, env: &Nul<&Str>, at_flags: AtFlags) -> Result<Void, Error> {
unsafe { esyscall!(EXECVEAT, from_opt_dir(opt_dir), path.as_ptr(),
argv.as_ptr(), env.as_ptr(),
if at_flags.contains(AtFlags::Follow) { 0 }
else { libc::AT_SYMLINK_NOFOLLOW } | AT_EMPTY_PATH).map(|_| unreach()) }
}
#[inline]
pub(crate) fn from_opt_dir(opt_dir: Option<&File>) -> isize {
match opt_dir {
None => libc::AT_FDCWD as isize,
Some(dir) => dir.fd,
}
}
pub fn mktemp_at<R: Clone, TheRng: Rng>
(opt_dir: Option<&File>, templ: &mut Str, range: R, rng: &mut TheRng, flags: OpenFlags) ->
Result<File, Error> where [u8]: IndexMut<R, Output = [u8]> {
mktemp_helper(|path| open_at(opt_dir, path, OpenMode::RdWr | flags | O_EXCL, Some((Perm::Read | Perm::Write) << USR)),
templ, range, rng)
}
pub(crate) fn mktemp_helper<R: Clone, TheRng: Rng, F: Fn(&Str) -> Result<A, Error>, A>
(f: F, templ: &mut Str, range: R, rng: &mut TheRng) -> Result<A, Error>
where [u8]: IndexMut<R, Output = [u8]> {
let tries = 0x100;
for _ in 0..tries {
randname(rng, &mut templ[range.clone()]);
match f(templ) {
Err(Error::EEXIST) => (),
r => return r,
}
}
Err(Error::EEXIST)
}
fn randname<TheRng: Rng>(rng: &mut TheRng, bs: &mut [u8]) {
let base = 'Z' as u64 - '@' as u64;
let mut n: u64 = rng.gen();
for p in bs.iter_mut() {
*p = (n % base + 'A' as u64) as u8;
n /= base;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Clobber {
NoClobber,
Clobber,
ClobberSavingPerms,
}
pub use self::Clobber::*;
pub fn atomic_write_file_at<F: FnOnce(File) -> Result<T, Error>, T>
(opt_dir: Option<&File>, path: &Str,
clobber: Clobber, mode: Mode, writer: F) -> Result<T, Error> {
let mut rng = ::rand::rngs::SmallRng::from_rng(::random::OsRandom::new())
.map_err(|_| Error::EIO)?;
let mut temp_path = [b' '; 13];
temp_path[temp_path.len() - 1] = 0;
let temp_path_ref = <&mut Str>::try_from(&mut temp_path[..]).unwrap();
let f = mktemp_at(opt_dir, temp_path_ref, 0..12, &mut rng, OpenFlags::empty())?;
struct Rm<'a, 'b> { opt_dir: Option<&'a File>, path: &'b Str };
impl<'a, 'b> Drop for Rm<'a, 'b> {
#[inline]
fn drop(&mut self) { unlink_at(self.opt_dir, self.path).unwrap_or(()) }
}
let rm = Rm { opt_dir, path: temp_path_ref };
f.chmod(match clobber {
NoClobber | Clobber => mode,
ClobberSavingPerms => match stat_at(opt_dir, path, AtFlags::empty()) {
Ok(st) => st.mode,
Err(Error::ENOENT) => mode,
Err(e) => return Err(e),
},
})?;
let m = writer(f)?;
match clobber {
NoClobber => link_at(opt_dir, temp_path_ref, opt_dir, path, AtFlags::empty())?,
Clobber | ClobberSavingPerms => {
rename_at(opt_dir, temp_path_ref, opt_dir, path)?;
mem::forget(rm);
},
}
Ok(m)
}
impl TryClone for File {
type Error = Error;
#[inline]
fn try_clone(&self) -> Result<Self, Error> {
unsafe { esyscall!(DUP, self.fd) }.map(|fd| File { fd: fd as _ })
}
#[inline]
fn try_clone_from(&mut self, other: &Self) -> Result<(), Error> {
unsafe { esyscall_!(DUP2, other.fd, self.fd) }
}
}
impl Drop for File {
#[inline]
fn drop(&mut self) { unsafe { syscall!(CLOSE, self.fd) }; }
}
impl Read<u8> for File {
type Err = Error;
#[inline]
fn readv(&mut self, bufs: &mut [&mut [u8]]) -> Result<usize, Self::Err> {
unsafe { esyscall!(READV, self.fd, bufs.as_mut_ptr(), bufs.len()) }
}
}
impl Write<u8> for File {
type Err = Error;
#[inline]
fn writev(&mut self, bufs: &[&[u8]]) -> Result<usize, Self::Err> {
unsafe { esyscall!(WRITEV, self.fd, bufs.as_ptr(), bufs.len()) }
}
#[inline]
fn flush(&mut self) -> Result<(), Self::Err> { self.sync(false) }
}
impl fmt::Write for File {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.write_all(s.as_bytes()).map_err(|_| fmt::Error)
}
}
bitflags! {
pub struct Mode: u16 {
const SUID = 0o4000;
const SGID = 0o2000;
const SVTX = 0o1000;
#[doc(hidden)]
const ____ = 0o0777;
}
}
#[deprecated(since = "0.6.11", note = "Rather use `Mode`")]
pub use self::Mode as FileMode;
#[allow(missing_docs)]
mod file_permission { bitflags! {
pub struct Permission: u8 {
const Read = 4;
const Write = 2;
const Exec = 1;
}
} } pub use self::file_permission::*;
pub(crate) use self::Permission as Perm;
#[deprecated(since = "0.6.11", note = "Rather use `Permission`")]
pub use self::Permission as FilePermission;
impl Shl<ModeSection> for Permission {
type Output = Mode;
#[inline]
fn shl(self, sect: ModeSection) -> Mode {
Mode::from_bits_truncate((self.bits() as u16) << sect.pos())
}
}
impl Shr<ModeSection> for Mode {
type Output = Permission;
#[inline]
fn shr(self, sect: ModeSection) -> Permission {
Permission::from_bits_truncate((self.bits >> sect.pos() & 3) as _)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ModeSection {
USR,
GRP,
OTH,
}
pub use self::ModeSection::*;
#[deprecated(since = "0.6.11", note = "Rather use `ModeSection`")]
pub use self::ModeSection as FileModeSection;
impl ModeSection {
#[inline]
fn pos(self) -> u32 { match self { USR => 6, GRP => 3, OTH => 0 } }
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct OpenMode(usize);
impl OpenMode {
pub const RdOnly: Self = OpenMode(libc::O_RDONLY as _);
pub const WrOnly: Self = OpenMode(libc::O_WRONLY as _);
pub const RdWr : Self = OpenMode(libc::O_RDWR as _);
}
impl BitOr<OpenFlags> for OpenMode {
type Output = Self;
#[inline]
fn bitor(self, flags: OpenFlags) -> Self { OpenMode(self.0 | flags.bits) }
}
impl BitAnd<OpenFlags> for OpenMode {
type Output = Self;
#[inline]
fn bitand(self, flags: OpenFlags) -> Self { OpenMode(self.0 & flags.bits) }
}
impl BitXor<OpenFlags> for OpenMode {
type Output = Self;
#[inline]
fn bitxor(self, flags: OpenFlags) -> Self { OpenMode(self.0 ^ flags.bits) }
}
impl BitOrAssign<OpenFlags> for OpenMode {
#[inline]
fn bitor_assign(&mut self, flags: OpenFlags) { self.0 |= flags.bits }
}
impl BitAndAssign<OpenFlags> for OpenMode {
#[inline]
fn bitand_assign(&mut self, flags: OpenFlags) { self.0 &= flags.bits }
}
impl BitXorAssign<OpenFlags> for OpenMode {
#[inline]
fn bitxor_assign(&mut self, flags: OpenFlags) { self.0 ^= flags.bits }
}
bitflags! {
pub struct OpenFlags: usize {
const O_CLOEXEC = libc::O_CLOEXEC as usize;
const O_EXCL = libc::O_EXCL as usize;
const O_NONBLOCK = libc::O_NONBLOCK as usize;
const O_NOCTTY = libc::O_NOCTTY as usize;
}
}
#[allow(missing_docs)] pub const O_CLOEXEC : OpenFlags = OpenFlags::O_CLOEXEC;
#[allow(missing_docs)] pub const O_EXCL : OpenFlags = OpenFlags::O_EXCL;
#[allow(missing_docs)] pub const O_NONBLOCK: OpenFlags = OpenFlags::O_NONBLOCK;
#[allow(missing_docs)] pub const O_NOCTTY : OpenFlags = OpenFlags::O_NOCTTY;
bitflags! {
pub struct AtFlags: usize {
const Follow = libc::AT_SYMLINK_FOLLOW as usize;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Stat {
pub dev: libc::dev_t,
pub ino: libc::ino_t,
pub mode: Mode,
pub nlink: libc::nlink_t,
pub uid: libc::uid_t,
pub gid: libc::gid_t,
pub size: libc::off_t,
pub blksize: libc::blksize_t,
pub blocks: libc::blkcnt_t,
pub atime: EpochTime,
pub mtime: EpochTime,
pub ctime: EpochTime,
}
impl From<libc::stat> for Stat {
#[inline(always)]
fn from(st: libc::stat) -> Self {
Stat {
dev: st.st_dev,
ino: st.st_ino,
mode: Mode::from_bits_truncate(st.st_mode as _),
nlink: st.st_nlink,
uid: st.st_uid,
gid: st.st_gid,
size: st.st_size,
blksize: st.st_blksize,
blocks: st.st_blocks,
atime: EpochTime::from_s_ns(st.st_atime, st.st_atime_nsec as _),
mtime: EpochTime::from_s_ns(st.st_mtime, st.st_mtime_nsec as _),
ctime: EpochTime::from_s_ns(st.st_ctime, st.st_ctime_nsec as _),
}
}
}
#[cfg(target_os = "linux")]
const AT_EMPTY_PATH: libc::c_int = libc::AT_EMPTY_PATH;
#[cfg(not(target_os = "linux"))]
const AT_EMPTY_PATH: libc::c_int = 0;