#[cfg(feature = "alloc")]
use alloc::string::String;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::mem::MaybeUninit;
use rusl::error::Errno;
pub use rusl::platform::Mode;
use rusl::platform::{Dirent, OpenFlags, Stat, NULL_BYTE};
use rusl::string::strlen::{buf_strlen, strlen};
use rusl::string::unix_str::{AsUnixStr, UnixStr};
use rusl::unistd::UnlinkFlags;
use crate::error::Error;
use crate::error::Result;
use crate::io::{Read, Write};
use crate::unix::fd::{AsRawFd, BorrowedFd, OwnedFd, RawFd};
#[cfg(test)]
mod test;
pub struct File(OwnedFd);
impl File {
#[inline]
pub fn open(path: impl AsUnixStr) -> Result<Self> {
Self::open_with_options(path, OpenOptions::new().read(true))
}
#[inline]
fn open_with_options(path: impl AsUnixStr, opts: &OpenOptions) -> Result<Self> {
let flags =
OpenFlags::O_CLOEXEC | opts.get_access_mode()? | opts.get_creation_mode()? | opts.flags;
let fd = rusl::unistd::open_mode(path, flags, opts.mode)?;
Ok(File(OwnedFd(fd)))
}
fn open_at(dir_fd: RawFd, path: impl AsUnixStr) -> Result<Self> {
let mut opts = OpenOptions::new();
opts.read(true);
let flags =
OpenFlags::O_CLOEXEC | opts.get_access_mode()? | opts.get_creation_mode()? | opts.flags;
let fd = rusl::unistd::open_at_mode(dir_fd, path, flags, opts.mode)?;
Ok(File(OwnedFd(fd)))
}
#[must_use]
pub const unsafe fn from_raw_fd(fd: RawFd) -> Self {
Self(OwnedFd::from_raw(fd))
}
#[inline]
pub fn set_nonblocking(&self) -> Result<()> {
self.0.set_nonblocking()
}
#[inline]
pub fn metadata(&self) -> Result<Metadata> {
let stat = rusl::unistd::stat_fd(self.as_raw_fd())?;
Ok(Metadata(stat))
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn copy<P: AsUnixStr>(&self, dest: P) -> Result<Self> {
let this_metadata = self.metadata()?;
let dest = OpenOptions::new()
.create(true)
.write(true)
.mode(this_metadata.mode())
.open(dest)?;
let mut offset = 0;
let mut remaining = this_metadata.0.st_size as u64 - offset;
while remaining > 0 {
let w = rusl::unistd::copy_file_range(
self.as_raw_fd(),
offset,
dest.as_raw_fd(),
offset,
remaining as usize,
)?;
if w == 0 {
return Ok(dest);
}
offset += w as u64;
remaining = this_metadata.0.st_size as u64 - offset;
}
Ok(dest)
}
}
impl File {
#[inline]
pub(crate) fn into_inner(self) -> OwnedFd {
self.0
}
}
impl AsRawFd for File {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl Read for File {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
Ok(rusl::unistd::read(self.0 .0, buf)?)
}
}
#[cfg(feature = "alloc")]
pub fn read<P: AsUnixStr>(path: P) -> Result<Vec<u8>> {
let mut file = File::open(path)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
Ok(bytes)
}
#[cfg(feature = "alloc")]
pub fn read_to_string<P: AsUnixStr>(path: P) -> Result<String> {
let mut file = File::open(path)?;
let mut string = String::new();
file.read_to_string(&mut string)?;
Ok(string)
}
pub fn write<P: AsUnixStr>(path: P, buf: &[u8]) -> Result<()> {
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)?;
file.write_all(buf)?;
Ok(())
}
#[derive(Debug, Clone)]
pub struct Metadata(Stat);
impl Metadata {
#[inline]
#[must_use]
pub fn is_dir(&self) -> bool {
Mode::from(self.0.st_mode) & Mode::S_IFMT == Mode::S_IFDIR
}
#[inline]
#[must_use]
pub fn is_file(&self) -> bool {
Mode::from(self.0.st_mode) & Mode::S_IFMT == Mode::S_IFREG
}
#[inline]
#[must_use]
pub fn is_symlink(&self) -> bool {
Mode::from(self.0.st_mode) & Mode::S_IFMT == Mode::S_IFLNK
}
#[inline]
#[must_use]
pub fn mode(&self) -> Mode {
Mode::from(self.0.st_mode)
}
#[inline]
#[must_use]
#[allow(clippy::len_without_is_empty)]
#[allow(clippy::cast_sign_loss)]
pub fn len(&self) -> u64 {
self.0.st_size as u64
}
}
#[inline]
pub fn metadata<P: AsUnixStr>(path: P) -> Result<Metadata> {
let res = rusl::unistd::stat(path)?;
Ok(Metadata(res))
}
#[inline]
pub fn rename(src: impl AsUnixStr, dest: impl AsUnixStr) -> Result<()> {
rusl::unistd::rename(src, dest)?;
Ok(())
}
#[inline]
pub fn copy_file(src: impl AsUnixStr, dest: impl AsUnixStr) -> Result<File> {
let src_file = File::open(&src)?;
src_file.copy(dest)
}
pub fn exists<P: AsUnixStr>(path: P) -> Result<bool> {
match rusl::unistd::stat(path) {
Ok(_) => Ok(true),
Err(e) => {
if matches!(e.code, Some(Errno::ENOENT)) {
return Ok(false);
}
Err(Error::from(e))
}
}
}
#[inline]
pub fn remove_file<P: AsUnixStr>(path: P) -> Result<()> {
rusl::unistd::unlink(path)?;
Ok(())
}
#[inline]
pub fn create_dir<P: AsUnixStr>(path: P) -> Result<()> {
create_dir_mode(path, Mode::from(0o755))
}
#[inline]
pub fn create_dir_mode<P: AsUnixStr>(path: P, mode: Mode) -> Result<()> {
rusl::unistd::mkdir(path, mode)?;
Ok(())
}
#[inline]
pub fn create_dir_all<P: AsUnixStr>(path: P) -> Result<()> {
const NO_ALLOC_MAX_LEN: usize = 512;
const EMPTY: [MaybeUninit<u8>; NO_ALLOC_MAX_LEN] = [MaybeUninit::uninit(); NO_ALLOC_MAX_LEN];
path.exec_with_self_as_ptr(|ptr| unsafe {
let len = strlen(ptr);
#[cfg(feature = "alloc")]
if len > NO_ALLOC_MAX_LEN {
let mut owned: Vec<u8> = Vec::with_capacity(len);
ptr.copy_to(owned.as_mut_ptr(), len);
return write_all_sub_paths(owned.as_mut_slice(), ptr);
}
#[cfg(not(feature = "alloc"))]
if len > NO_ALLOC_MAX_LEN {
return Err(rusl::Error::no_code(
"Supplied path larger than 512 without an allocator present",
));
}
let mut copied = EMPTY;
ptr.copy_to(copied.as_mut_ptr().cast(), len);
let initialized_section = copied[..len].as_mut_ptr().cast();
let buf: &mut [u8] = core::slice::from_raw_parts_mut(initialized_section, len);
write_all_sub_paths(buf, ptr)
})?;
Ok(())
}
#[inline]
fn write_all_sub_paths(buf: &mut [u8], raw: *const u8) -> core::result::Result<(), rusl::Error> {
let len = buf.len();
let mut it = 1;
loop {
let ind = len - it;
if ind == 0 {
break;
}
let byte = buf[ind];
if byte == b'/' {
buf[ind] = NULL_BYTE;
return match rusl::unistd::mkdir(&buf[..=ind], Mode::from(0o755)) {
Ok(_) => {
buf[ind] = b'/';
for i in ind + 1..len {
if buf[i] == b'/' {
buf[i] = NULL_BYTE;
rusl::unistd::mkdir(&buf[..=i], Mode::from(0o755))?;
buf[i] = b'/';
}
}
rusl::unistd::mkdir(
unsafe { core::slice::from_raw_parts(raw, len + 1) },
Mode::from(0o755),
)?;
Ok(())
}
Err(e) => {
if let Some(code) = e.code {
if code == Errno::ENOENT {
it += 1;
buf[ind] = b'/';
continue;
} else if code == Errno::EEXIST {
return Ok(());
}
}
Err(e)
}
};
}
it += 1;
}
Ok(())
}
pub struct Directory(OwnedFd);
impl Directory {
#[inline]
pub fn open<P: AsUnixStr>(path: P) -> Result<Directory> {
let fd = rusl::unistd::open(path, OpenFlags::O_CLOEXEC | OpenFlags::O_RDONLY)?;
Ok(Directory(OwnedFd(fd)))
}
#[inline]
fn open_at<P: AsUnixStr>(dir_fd: RawFd, path: P) -> Result<Directory> {
let fd = rusl::unistd::open_at(dir_fd, path, OpenFlags::O_CLOEXEC | OpenFlags::O_RDONLY)?;
Ok(Directory(OwnedFd(fd)))
}
#[must_use]
pub fn read<'a>(&self) -> ReadDir<'a> {
let buf = [0u8; 512];
ReadDir {
fd: BorrowedFd::new(self.0 .0),
filled_buf: buf,
offset: 0,
read_size: 0,
eod: false,
}
}
pub fn remove_all(&self) -> Result<()> {
for sub_dir in self.read() {
let sub_dir = sub_dir?;
if FileType::Directory == sub_dir.file_type() {
if sub_dir.is_relative_reference() {
continue;
}
let fname = sub_dir.file_unix_name()?;
let next = Self::open_at(self.0 .0, fname)?;
next.remove_all()?;
rusl::unistd::unlink_at(self.0 .0, fname, UnlinkFlags::at_removedir())?;
} else {
rusl::unistd::unlink_at(
self.0 .0,
sub_dir.file_unix_name()?,
UnlinkFlags::empty(),
)?;
}
}
Ok(())
}
}
pub struct ReadDir<'a> {
fd: BorrowedFd<'a>,
filled_buf: [u8; 512],
offset: usize,
read_size: usize,
eod: bool,
}
impl<'a> Iterator for ReadDir<'a> {
type Item = Result<DirEntry<'a>>;
fn next(&mut self) -> Option<Self::Item> {
if self.read_size == self.offset {
if self.eod {
return None;
}
match rusl::unistd::get_dents(self.fd.fd, &mut self.filled_buf) {
Ok(read) => {
if read == 0 {
self.eod = true;
return None;
}
self.read_size = read;
self.offset = 0;
}
Err(e) => {
self.eod = true;
return Some(Err(e.into()));
}
}
}
unsafe {
Dirent::try_from_bytes(&self.filled_buf[self.offset..])
.map(|d| {
self.offset += d.d_reclen as usize;
d
})
.map(|de| {
Ok(DirEntry {
inner: de,
fd: self.fd,
})
})
}
}
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum FileType {
Fifo,
CharDevice,
Directory,
BlockDevice,
RegularFile,
Symlink,
Socket,
Unknown,
}
pub struct DirEntry<'a> {
inner: Dirent,
fd: BorrowedFd<'a>,
}
impl<'a> DirEntry<'a> {
#[must_use]
#[inline]
pub fn is_relative_reference(&self) -> bool {
&self.inner.d_name[..2] == b".\0" || &self.inner.d_name[..3] == b"..\0"
}
#[must_use]
pub fn file_type(&self) -> FileType {
match self.inner.d_type {
rusl::platform::DirType::DT_FIFO => FileType::Fifo,
rusl::platform::DirType::DT_CHR => FileType::CharDevice,
rusl::platform::DirType::DT_DIR => FileType::Directory,
rusl::platform::DirType::DT_BLK => FileType::BlockDevice,
rusl::platform::DirType::DT_REG => FileType::RegularFile,
rusl::platform::DirType::DT_LNK => FileType::Symlink,
rusl::platform::DirType::DT_SOCK => FileType::Socket,
_ => FileType::Unknown,
}
}
pub fn file_name(&self) -> Result<&str> {
let len = buf_strlen(&self.inner.d_name)?;
let as_str = unsafe { core::str::from_utf8(self.inner.d_name.get_unchecked(..len)) }
.map_err(|_| Error::no_code("File name not utf8"))?;
Ok(as_str)
}
pub fn file_unix_name(&self) -> Result<&UnixStr> {
let len = buf_strlen(&self.inner.d_name)?;
unsafe {
let tgt = self.inner.d_name.get_unchecked(..=len);
Ok(&*(tgt as *const [u8] as *const UnixStr))
}
}
#[inline]
pub fn open_file(&self) -> Result<File> {
let ft = self.file_type();
if ft == FileType::RegularFile || ft == FileType::CharDevice {
Ok(File::open_at(self.fd.fd, self.file_unix_name()?)?)
} else {
Err(Error::no_code("Tried to open non-file as file"))
}
}
#[inline]
pub fn open_dir(&self) -> Result<Directory> {
if self.file_type() == FileType::Directory {
Ok(Directory::open_at(self.fd.fd, self.file_unix_name()?)?)
} else {
Err(Error::no_code("Tried to open non-file as file"))
}
}
}
#[inline]
pub fn remove_dir<P: AsUnixStr>(path: P) -> Result<()> {
rusl::unistd::unlink_flags(path, UnlinkFlags::at_removedir())?;
Ok(())
}
pub fn remove_dir_all<P: AsUnixStr>(path: P) -> Result<()> {
let dir = Directory::open(&path)?;
dir.remove_all()?;
remove_dir(path)
}
impl Write for File {
#[inline]
fn write(&mut self, buf: &[u8]) -> Result<usize> {
Ok(rusl::unistd::write(self.0 .0, buf)?)
}
#[inline]
fn flush(&mut self) -> Result<()> {
Ok(())
}
}
#[derive(Clone, Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct OpenOptions {
read: bool,
write: bool,
append: bool,
truncate: bool,
create: bool,
create_new: bool,
flags: OpenFlags,
mode: Mode,
}
impl Default for OpenOptions {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl OpenOptions {
#[must_use]
pub fn new() -> OpenOptions {
OpenOptions {
read: false,
write: false,
append: false,
truncate: false,
create: false,
create_new: false,
flags: OpenFlags::empty(),
mode: Mode::from(0o0_000_666),
}
}
pub fn read(&mut self, read: bool) -> &mut Self {
self.read = read;
self
}
pub fn write(&mut self, write: bool) -> &mut Self {
self.write = write;
self
}
pub fn append(&mut self, append: bool) -> &mut Self {
self.append = append;
self
}
pub fn truncate(&mut self, truncate: bool) -> &mut Self {
self.truncate = truncate;
self
}
pub fn create(&mut self, create: bool) -> &mut Self {
self.create = create;
self
}
pub fn create_new(&mut self, create_new: bool) -> &mut Self {
self.create_new = create_new;
self
}
pub fn custom_flags(&mut self, flags: OpenFlags) -> &mut Self {
self.flags = flags;
self
}
pub fn mode(&mut self, mode: Mode) -> &mut Self {
self.mode = mode;
self
}
#[inline]
pub fn open(&self, path: impl AsUnixStr) -> Result<File> {
File::open_with_options(path, self)
}
fn get_access_mode(&self) -> Result<OpenFlags> {
match (self.read, self.write, self.append) {
(true, false, false) => Ok(OpenFlags::O_RDONLY),
(false, true, false) => Ok(OpenFlags::O_WRONLY),
(true, true, false) => Ok(OpenFlags::O_RDWR),
(false, _, true) => Ok(OpenFlags::O_WRONLY | OpenFlags::O_APPEND),
(true, _, true) => Ok(OpenFlags::O_RDWR | OpenFlags::O_APPEND),
(false, false, false) => Err(Error::no_code(
"Bad OpenOptions, no access mode (read, write, append)",
)),
}
}
fn get_creation_mode(&self) -> crate::error::Result<OpenFlags> {
match (self.write, self.append) {
(true, false) => {}
(false, false) => {
if self.truncate || self.create || self.create_new {
return Err(Error::no_code("Bad OpenOptions, used truncate, create, or create_new without access mode write or append"));
}
}
(_, true) => {
if self.truncate && !self.create_new {
return Err(Error::no_code(
"Bad OpenOptions, used truncate without create_new with access mode append",
));
}
}
}
Ok(match (self.create, self.truncate, self.create_new) {
(false, false, false) => OpenFlags::empty(),
(true, false, false) => OpenFlags::O_CREAT,
(false, true, false) => OpenFlags::O_TRUNC,
(true, true, false) => OpenFlags::O_CREAT | OpenFlags::O_TRUNC,
(_, _, true) => OpenFlags::O_CREAT | OpenFlags::O_EXCL,
})
}
}