use std::path::Path;
use std::time::{Duration, SystemTime};
use std::ffi::{CStr, CString, OsStr, OsString};
use std::fs;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::*;
use crate::types::*;
use libc::{c_char, c_void, timespec};
#[cfg(target_os = "linux")]
pub(crate) mod linux_fs;
#[cfg(target_os = "linux")]
use linux_fs as unix_impl;
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
pub(crate) mod bsd_like_fs;
#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
pub(crate) mod bsd_fs;
#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
use bsd_fs as unix_impl;
#[cfg(target_os = "macos")]
pub(crate) mod macos_fs;
#[cfg(target_os = "macos")]
use macos_fs as unix_impl;
pub(crate) use unix_impl::get_errno;
pub use unix_impl::{copy_file_range, statfs};
pub fn convert_filetype(f: fs::FileType) -> FileKind {
match f {
f if f.is_file() => FileKind::RegularFile,
f if f.is_dir() => FileKind::Directory,
f if f.is_symlink() => FileKind::Symlink,
f if f.is_block_device() => FileKind::BlockDevice,
f if f.is_char_device() => FileKind::CharDevice,
f if f.is_fifo() => FileKind::NamedPipe,
f if f.is_socket() => FileKind::Socket,
_ => panic!("Unknown FileKind"), }
}
pub fn convert_fileattribute(metadata: fs::Metadata) -> FileAttribute {
FileAttribute {
size: metadata.size(),
blocks: metadata.blocks(),
atime: SystemTime::UNIX_EPOCH + Duration::new(metadata.atime() as u64, 0),
mtime: SystemTime::UNIX_EPOCH + Duration::new(metadata.mtime() as u64, 0),
ctime: SystemTime::UNIX_EPOCH + Duration::new(metadata.ctime() as u64, 0),
crtime: SystemTime::UNIX_EPOCH + Duration::new(metadata.mtime() as u64, 0),
kind: convert_filetype(metadata.file_type()),
perm: (metadata.mode() & 0o777) as u16,
nlink: metadata.nlink() as u32,
uid: metadata.uid(),
gid: metadata.gid(),
rdev: metadata.rdev() as u32,
blksize: metadata.blksize() as u32,
flags: 0, ttl: None,
generation: None,
}
}
fn convert_stat_struct(statbuf: libc::stat) -> Option<FileAttribute> {
let atime = SystemTime::UNIX_EPOCH + Duration::new(statbuf.st_atime as u64, 0);
let mtime = SystemTime::UNIX_EPOCH + Duration::new(statbuf.st_mtime as u64, 0);
let ctime = SystemTime::UNIX_EPOCH + Duration::new(statbuf.st_ctime as u64, 0);
let perm = (statbuf.st_mode & (libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO)) as u16;
let flags = 0;
Some(FileAttribute {
size: statbuf.st_size as u64,
blocks: statbuf.st_blocks as u64,
atime,
mtime,
ctime,
crtime: mtime,
kind: stat_to_kind(statbuf)?,
perm: perm,
nlink: statbuf.st_nlink as u32,
uid: statbuf.st_uid as u32,
gid: statbuf.st_gid as u32,
rdev: statbuf.st_rdev as u32,
blksize: statbuf.st_blksize as u32,
flags: flags,
ttl: None,
generation: None,
})
}
fn stat_to_kind(statbuf: libc::stat) -> Option<FileKind> {
use libc::*;
Some(match statbuf.st_mode & S_IFMT {
S_IFREG => FileKind::RegularFile,
S_IFDIR => FileKind::Directory,
S_IFCHR => FileKind::CharDevice,
S_IFBLK => FileKind::BlockDevice,
S_IFIFO => FileKind::NamedPipe,
S_IFLNK => FileKind::Symlink,
S_IFSOCK => FileKind::Socket,
_ => return None, })
}
fn system_time_to_timespec(time: SystemTime) -> Result<timespec, PosixError> {
let duration = time.duration_since(std::time::UNIX_EPOCH).map_err(|_| {
PosixError::new(
ErrorKind::InvalidArgument,
"System time could not be converted to TimeSpec",
)
})?;
Ok(timespec {
tv_sec: duration.as_secs() as i64,
tv_nsec: duration.subsec_nanos() as i64,
})
}
fn cstring_from_path(path: &Path) -> Result<CString, PosixError> {
CString::new(path.as_os_str().as_bytes()).map_err(|_| {
PosixError::new(
ErrorKind::InvalidArgument,
format!("{}: Cstring conversion failed", path.display()),
)
})
}
pub fn lookup(path: &Path) -> Result<FileAttribute, PosixError> {
let c_path = cstring_from_path(path)?;
let mut statbuf: libc::stat = unsafe { std::mem::zeroed() };
let result = unsafe { libc::lstat(c_path.as_ptr(), &mut statbuf) };
if result == -1 {
return Err(PosixError::last_error(format!(
"{}: lstat failed in lookup",
path.display()
)));
}
Ok(convert_stat_struct(statbuf).ok_or(PosixError::new(
ErrorKind::InvalidArgument,
format!(
"{}: statbuf conversion failed {:?}",
path.display(),
statbuf
),
))?)
}
pub fn getattr(fd: BorrowedFd) -> Result<FileAttribute, PosixError> {
let mut statbuf: libc::stat = unsafe { std::mem::zeroed() };
let result = unsafe { libc::fstat(fd.as_raw_fd(), &mut statbuf) };
if result == -1 {
return Err(PosixError::last_error(format!(
"{:?}: fstat failed in getattr",
fd
)));
}
Ok(convert_stat_struct(statbuf).ok_or(PosixError::new(
ErrorKind::InvalidArgument,
format!("{:?}: statbuf conversion failed {:?}", fd, statbuf),
))?)
}
pub fn setattr(path: &Path, attrs: SetAttrRequest) -> Result<FileAttribute, PosixError> {
let c_path = cstring_from_path(path)?;
if let Some(mode) = attrs.mode {
let result = unsafe { libc::chmod(c_path.as_ptr(), mode.try_into().unwrap()) };
if result == -1 {
return Err(PosixError::last_error(format!(
"{}: chmod failed in setattr",
path.display()
)));
}
}
if attrs.uid.is_some() || attrs.gid.is_some() {
let uid = attrs.uid.unwrap_or(0_u32.wrapping_sub(1));
let gid = attrs.gid.unwrap_or(0_u32.wrapping_sub(1));
let result = unsafe { libc::chown(c_path.as_ptr(), uid, gid) };
if result == -1 {
return Err(PosixError::last_error(format!(
"{}: chown failed in setattr",
path.display()
)));
}
}
if let Some(size) = attrs.size {
let result = {
let fd = unsafe { libc::open(c_path.as_ptr(), libc::O_WRONLY) };
if fd == -1 {
return Err(PosixError::last_error(format!(
"{}: open failed in setattr",
path.display()
)));
}
let res = unsafe {
libc::ftruncate(
fd,
i64::try_from(size).map_err(|_| {
PosixError::new(
ErrorKind::InvalidArgument,
format!(
"{}: ftruncate size ({}) out of bound in setattr",
path.display(),
size
),
)
})?,
)
};
unsafe { libc::close(fd) };
res
};
if result == -1 {
return Err(PosixError::last_error(format!(
"{}: ftruncate failed on setattr",
path.display()
)));
}
}
if let (Some(atime), Some(mtime)) = (attrs.atime, attrs.mtime) {
let times = match (atime, mtime) {
(TimeOrNow::Now, TimeOrNow::Now) => {
let now_spec = system_time_to_timespec(SystemTime::now())?;
[now_spec, now_spec]
}
(TimeOrNow::SpecificTime(at), TimeOrNow::SpecificTime(mt)) => {
let at_spec = system_time_to_timespec(at)?;
let mt_spec = system_time_to_timespec(mt)?;
[at_spec, mt_spec]
}
_ => {
return Err(PosixError::new(
ErrorKind::InvalidArgument,
"Could not convert timespec to TimeOrNow in setattr",
));
}
};
let result = unsafe {
libc::utimensat(
libc::AT_FDCWD,
c_path.as_ptr(),
×[0],
libc::AT_SYMLINK_NOFOLLOW,
)
};
if result == -1 {
return Err(PosixError::last_error(format!(
"{}: utimensat failed in setattr",
path.display()
)));
}
}
lookup(path)
}
pub fn readlink(path: &Path) -> Result<Vec<u8>, PosixError> {
let c_path = cstring_from_path(path)?;
let mut buf = vec![0u8; 1024]; let ret =
unsafe { libc::readlink(c_path.as_ptr(), buf.as_mut_ptr() as *mut c_char, buf.len()) };
if ret == -1 {
return Err(PosixError::last_error(format!(
"{}: readlink",
path.display()
)));
}
buf.truncate(ret as usize);
Ok(buf)
}
pub fn mknod(
path: &Path,
mode: u32,
umask: u32,
rdev: DeviceType,
) -> Result<FileAttribute, PosixError> {
let c_path = cstring_from_path(path)?;
let final_mode = mode & !umask;
let ret = unsafe {
libc::mknod(
c_path.as_ptr(),
final_mode.try_into().unwrap(),
rdev.to_rdev() as libc::dev_t,
)
};
if ret == -1 {
return Err(PosixError::last_error(format!(
"{}: mknod failed",
path.display()
)));
}
lookup(path)
}
pub fn mkdir(path: &Path, mode: u32, umask: u32) -> Result<FileAttribute, PosixError> {
let c_path = cstring_from_path(path)?;
let final_mode = mode & !umask;
let ret = unsafe { libc::mkdir(c_path.as_ptr(), final_mode.try_into().unwrap()) };
if ret == -1 {
return Err(PosixError::last_error(format!(
"{}: mkdir failed in setattr",
path.display()
)));
}
lookup(path)
}
pub fn unlink(path: &Path) -> Result<(), PosixError> {
let c_path = cstring_from_path(path)?;
let result = unsafe { libc::unlink(c_path.as_ptr()) };
if result == -1 {
return Err(PosixError::last_error(format!(
"{}: unlink failed",
path.display()
)));
}
Ok(())
}
pub fn rmdir(path: &Path) -> Result<(), PosixError> {
let c_path = cstring_from_path(path)?;
let result = unsafe { libc::rmdir(c_path.as_ptr()) };
if result == -1 {
return Err(PosixError::last_error(format!(
"{}: rmdir failed",
path.display()
)));
}
Ok(())
}
pub fn symlink(path: &Path, target: &Path) -> Result<FileAttribute, PosixError> {
let c_path = cstring_from_path(path)?;
let c_target = cstring_from_path(target)?;
let result = unsafe { libc::symlink(c_target.as_ptr(), c_path.as_ptr()) };
if result == -1 {
return Err(PosixError::last_error(format!(
"{}: symlink failed (target: {})",
path.display(),
target.display()
)));
}
lookup(path)
}
pub fn rename(oldpath: &Path, newpath: &Path, flags: RenameFlags) -> Result<(), PosixError> {
let old_cstr = cstring_from_path(oldpath)?;
let new_cstr = cstring_from_path(newpath)?;
let result = unsafe {
unix_impl::renameat2(
libc::AT_FDCWD, old_cstr.as_ptr(),
libc::AT_FDCWD, new_cstr.as_ptr(),
flags.bits(),
)
};
if result == 0 {
return Ok(());
}
Err(PosixError::last_error(format!(
"{}: rename failed into {}",
oldpath.display(),
newpath.display()
)))
}
pub fn open(path: &Path, flags: OpenFlags) -> Result<OwnedFd, PosixError> {
let c_path = cstring_from_path(path)?;
let fd = unsafe { libc::open(c_path.as_ptr(), flags.bits()) };
if fd == -1 {
return Err(PosixError::last_error(format!(
"{}: open failed",
path.display()
)));
}
Ok(unsafe { OwnedFd::from_raw_fd(fd.into()) })
}
pub fn read(fd: BorrowedFd, seek: SeekFrom, size: usize) -> Result<Vec<u8>, PosixError> {
let mut buffer = vec![0; size as usize];
let offset: libc::off_t = match seek {
SeekFrom::Start(offset) => offset.try_into().map_err(|_| {
PosixError::new(
ErrorKind::InvalidArgument,
"Offset too large for off_t".to_string(),
)
})?,
SeekFrom::Current(offset) => {
let current = lseek(fd, SeekFrom::Current(0))?;
current.checked_add(offset).ok_or_else(|| {
PosixError::new(
ErrorKind::InvalidArgument,
"Resulting offset too large for off_t".to_string(),
)
})?
}
SeekFrom::End(offset) => {
let end = lseek(fd, SeekFrom::End(0))?;
end.checked_add(offset).ok_or_else(|| {
PosixError::new(
ErrorKind::InvalidArgument,
"Resulting offset too large for off_t".to_string(),
)
})?
}
};
let bytes_read = unsafe {
libc::pread(
fd.as_raw_fd(),
buffer.as_mut_ptr() as *mut libc::c_void,
size,
offset,
)
};
if bytes_read == -1 {
return Err(PosixError::last_error(format!("{:?}: read failed", fd)));
}
buffer.truncate(bytes_read as usize);
Ok(buffer)
}
pub fn write(fd: BorrowedFd, seek: SeekFrom, data: &[u8]) -> Result<usize, PosixError> {
let bytes_to_write = data.len() as usize;
let offset: libc::off_t = match seek {
SeekFrom::Start(offset) => offset.try_into().map_err(|_| {
PosixError::new(
ErrorKind::InvalidArgument,
"Offset too large for off_t".to_string(),
)
})?,
SeekFrom::Current(offset) => {
let current = lseek(fd, SeekFrom::Current(0))?;
current.checked_add(offset).ok_or_else(|| {
PosixError::new(
ErrorKind::InvalidArgument,
"Resulting offset too large for off_t".to_string(),
)
})?
}
SeekFrom::End(offset) => {
let end = lseek(fd, SeekFrom::End(0))?;
end.checked_add(offset).ok_or_else(|| {
PosixError::new(
ErrorKind::InvalidArgument,
"Resulting offset too large for off_t".to_string(),
)
})?
}
};
let bytes_written = unsafe {
libc::pwrite(
fd.as_raw_fd(),
data.as_ptr() as *const libc::c_void,
bytes_to_write,
offset,
)
};
if bytes_written == -1 {
return Err(PosixError::last_error(format!("{:?}: write failed", fd)));
}
Ok(bytes_written as usize)
}
pub fn flush(fd: BorrowedFd) -> Result<(), PosixError> {
let result = unsafe { unix_impl::fdatasync(fd.as_raw_fd()) };
if result == -1 {
return Err(PosixError::last_error(format!("{:?}: flush failed", fd)));
}
Ok(())
}
pub fn fsync(fd: BorrowedFd, datasync: bool) -> Result<(), PosixError> {
let fd = fd.as_raw_fd();
let result = unsafe {
if datasync {
unix_impl::fdatasync(fd)
} else {
libc::fsync(fd)
}
};
if result == -1 {
return Err(PosixError::last_error(format!("{:?}: fsync failed", fd)));
}
Ok(())
}
pub fn readdir(path: &Path) -> Result<Vec<(OsString, FileKind)>, PosixError> {
let c_path = cstring_from_path(path)?;
let dir = unsafe { libc::opendir(c_path.as_ptr()) };
if dir.is_null() {
return Err(PosixError::last_error(format!(
"{}: opendir failed",
path.display()
)));
}
let mut result = Vec::new();
loop {
unix_impl::set_errno(0);
let entry = unsafe { libc::readdir(dir) };
if entry.is_null() {
if unix_impl::get_errno() != 0 {
unsafe { libc::closedir(dir) };
return Err(PosixError::last_error(format!(
"{}: readdir failed",
path.display()
)));
}
break;
}
let entry = unsafe { &*entry };
let name = unsafe { CStr::from_ptr(entry.d_name.as_ptr()) };
let name = OsStr::from_bytes(name.to_bytes()).to_owned();
if name == OsStr::new(".") || name == OsStr::new("..") {
continue;
}
let mut statbuf: libc::stat = unsafe { std::mem::zeroed() };
let full_path = path.join(&name);
let c_full_path = cstring_from_path(&full_path)?;
let stat_result = unsafe { libc::lstat(c_full_path.as_ptr(), &mut statbuf) };
if stat_result == -1 {
unsafe { libc::closedir(dir) };
return Err(PosixError::last_error(format!(
"{}: lstat failed",
full_path.display()
)));
}
if let Some(attr) = convert_stat_struct(statbuf) {
result.push((name, attr.kind));
}
}
unsafe { libc::closedir(dir) };
Ok(result)
}
pub fn release(fd: OwnedFd) -> Result<(), PosixError> {
let raw_fd = fd.into_raw_fd();
let result = unsafe { libc::close(raw_fd) };
if result == -1 {
return Err(PosixError::last_error(format!(
"{:?}: release failed",
raw_fd
)));
}
Ok(())
}
pub fn setxattr(
path: &Path,
name: &OsStr,
value: &[u8],
flags: FUSESetXAttrFlags,
position: u32,
) -> Result<(), PosixError> {
let c_path = cstring_from_path(path)?;
let c_name = CString::new(name.as_bytes()).map_err(|_| {
PosixError::new(
ErrorKind::InvalidArgument,
format!(
"{}: Cstring conversion failed in setxattr",
Path::display(name.as_ref())
),
)
})?;
let ret = unsafe {
unix_impl::setxattr(
c_path.as_ptr(),
c_name.as_ptr(),
value.as_ptr() as *const c_void,
value.len(),
position,
flags.bits(),
)
};
if ret == -1 {
return Err(PosixError::last_error(format!(
"{}: setxattr failed. Name: {}, value: {:?}, position: {}",
path.display(),
Path::display(name.as_ref()),
value,
position
)));
}
Ok(())
}
pub fn getxattr(path: &Path, name: &OsStr, size: u32) -> Result<Vec<u8>, PosixError> {
let c_path = cstring_from_path(path)?;
let c_name = CString::new(name.as_bytes()).map_err(|_| {
PosixError::new(
ErrorKind::InvalidArgument,
format!(
"{}: Cstring conversion failed in getxattr",
Path::display(name.as_ref())
),
)
})?;
let mut buf = vec![0u8; size as usize];
let ret = unsafe {
unix_impl::getxattr(
c_path.as_ptr(),
c_name.as_ptr(),
buf.as_mut_ptr() as *mut c_void,
buf.len(),
)
};
if ret == -1 {
return Err(PosixError::last_error(format!(
"{}: getxattr failed. Name: {}, Size: {}",
path.display(),
Path::display(name.as_ref()),
size
)));
}
buf.truncate(ret as usize);
Ok(buf)
}
pub fn listxattr(path: &Path, size: u32) -> Result<Vec<u8>, PosixError> {
let c_path = cstring_from_path(path)?;
let mut buf = vec![0u8; size as usize];
let ret =
unsafe { unix_impl::listxattr(c_path.as_ptr(), buf.as_mut_ptr() as *mut _, buf.len()) };
if ret == -1 {
return Err(PosixError::last_error(format!(
"{}: listxattr failed",
path.display()
)));
}
buf.truncate(ret as usize);
Ok(buf)
}
pub fn removexattr(path: &Path, name: &OsStr) -> Result<(), PosixError> {
let c_path = cstring_from_path(path)?;
let c_name = CString::new(name.as_bytes()).map_err(|_| {
PosixError::new(
ErrorKind::InvalidArgument,
format!(
"{}: CString conversion failed in removexattr",
Path::display(name.as_ref())
),
)
})?;
let ret = unsafe { unix_impl::removexattr(c_path.as_ptr(), c_name.as_ptr()) };
if ret == -1 {
return Err(PosixError::last_error(format!(
"{}: removexattr failed. Name: {}",
path.display(),
Path::display(name.as_ref())
)));
}
Ok(())
}
pub fn access(path: &Path, mask: AccessMask) -> Result<(), PosixError> {
let c_path = cstring_from_path(path)?;
let ret = unsafe { libc::access(c_path.as_ptr(), mask.bits()) };
if ret == -1 {
return Err(PosixError::last_error(format!(
"{}: access failed. Mask {:?}",
path.display(),
mask
)));
}
Ok(())
}
pub fn create(
path: &Path,
mode: u32,
umask: u32,
flags: OpenFlags,
) -> Result<(OwnedFd, FileAttribute), PosixError> {
let c_path = cstring_from_path(path)?;
let open_flags = flags.bits();
let final_mode = mode & !umask;
let open_flags = if open_flags & libc::O_ACCMODE == 0 {
open_flags | libc::O_WRONLY
} else {
open_flags
};
let fd = unsafe { libc::open(c_path.as_ptr(), open_flags | libc::O_CREAT, final_mode) };
if fd == -1 {
return Err(PosixError::last_error(format!(
"{}: create failed",
path.display()
)));
}
Ok((unsafe { OwnedFd::from_raw_fd(fd.into()) }, lookup(path)?))
}
pub fn fallocate(
fd: BorrowedFd,
offset: i64,
length: i64,
mode: FallocateFlags,
) -> Result<(), PosixError> {
let result = unsafe { unix_impl::fallocate(fd.as_raw_fd(), mode.bits(), offset, length) };
if result == -1 {
return Err(PosixError::last_error(format!(
"{:?}: fallocate failed",
fd
)));
}
Ok(())
}
pub fn lseek(fd: BorrowedFd, seek: SeekFrom) -> Result<i64, PosixError> {
let (whence, offset) = match seek {
SeekFrom::Start(offset) => (libc::SEEK_SET, offset as libc::off_t),
SeekFrom::Current(offset) => (libc::SEEK_CUR, offset as libc::off_t),
SeekFrom::End(offset) => (libc::SEEK_END, offset as libc::off_t),
};
let result = unsafe { libc::lseek(fd.as_raw_fd(), offset, whence) };
if result == -1 {
return Err(PosixError::last_error(format!(
"{:?}: lseek failed. Offset: {:?}, whence: {:?}",
fd, offset, whence
)));
}
Ok(result)
}
#[cfg(test)]
mod tests {
use tempfile::{NamedTempFile, TempDir};
use super::*;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
#[test]
fn test_convert_filetype() {
let tmpfile = NamedTempFile::new().unwrap();
let filetype = convert_filetype(fs::metadata(&tmpfile.path()).unwrap().file_type());
assert_eq!(filetype, FileKind::RegularFile);
drop(tmpfile);
}
#[test]
fn test_convert_fileattribute() {
let tmpfile = NamedTempFile::new().unwrap();
fs::write(&tmpfile.path(), "blah").unwrap();
let metadata = fs::metadata(&tmpfile.path()).unwrap();
let attr = convert_fileattribute(metadata);
assert!(attr.size > 0);
drop(tmpfile);
}
#[test]
fn test_system_time_to_timespec() {
let system_time = SystemTime::now();
let timespec = system_time_to_timespec(system_time).unwrap();
assert!(timespec.tv_sec > 0);
assert!(timespec.tv_nsec >= 0);
}
#[test]
fn test_cstring_from_path() {
let path = PathBuf::from("test_cstring");
let c_string = cstring_from_path(&path).unwrap();
assert_eq!(c_string.to_str().unwrap(), path.to_str().unwrap());
}
#[test]
fn test_get_attr() {
let tmpfile = NamedTempFile::new().unwrap();
fs::write(&tmpfile.path(), "blah").unwrap();
let attr1 = lookup(&tmpfile.path()).unwrap();
let fd = open(&tmpfile.path(), OpenFlags::READ_ONLY).unwrap();
let attr2 = getattr(fd.as_fd()).unwrap();
assert!(attr1.size > 0);
assert_eq!(attr1, attr2);
drop(tmpfile);
}
#[test]
fn test_readlink() {
let tmpdir = TempDir::new().unwrap();
let target_path = tmpdir.path().join("link_target");
let _ = File::create_new(&target_path).unwrap();
let symlink_path = tmpdir.path().join("symlink");
symlink(&symlink_path, &target_path).unwrap();
let link_target = readlink(&symlink_path).unwrap();
fs::remove_file(&target_path).unwrap();
fs::remove_file(&symlink_path).unwrap();
drop(tmpdir);
assert_eq!(Path::new(OsStr::from_bytes(&link_target)), target_path);
}
#[test]
fn test_mkdir_and_rmdir() {
let tmpdir = TempDir::new().unwrap();
let dir_path = tmpdir.path().join("dir");
mkdir(&dir_path, 0o755, 0).unwrap();
assert!(dir_path.exists());
rmdir(&dir_path).unwrap();
assert!(!dir_path.exists());
drop(tmpdir);
}
#[test]
fn test_symlink() {
let tmpdir = TempDir::new().unwrap();
let target_path = tmpdir.path().join("link_target");
let _ = File::create_new(&target_path).unwrap();
let symlink_path = tmpdir.path().join("symlink");
let attr = symlink(&symlink_path, &target_path).unwrap();
fs::remove_file(&target_path).unwrap();
fs::remove_file(&symlink_path).unwrap();
drop(tmpdir);
assert_eq!(attr.kind, FileKind::Symlink);
}
#[test]
fn test_unlink() {
let tmpdir = TempDir::new().unwrap();
let file_path = tmpdir.path().join("file");
File::create(&file_path).unwrap();
assert!(&file_path.exists());
unlink(&file_path).unwrap();
assert!(!file_path.exists());
drop(tmpdir);
}
#[test]
fn test_rename() {
let tmpdir = TempDir::new().unwrap();
let src_path = tmpdir.path().join("src");
File::create(&src_path).unwrap();
let dest_path = tmpdir.path().join("dest");
rename(&src_path, &dest_path, RenameFlags::empty()).unwrap();
assert!(!src_path.exists());
assert!(dest_path.exists());
fs::remove_file(&dest_path).unwrap();
}
#[test]
fn test_open() {
let tmpfile = NamedTempFile::new().unwrap();
let fd = open(&tmpfile.path(), OpenFlags::empty()).unwrap();
assert!(fd.as_raw_fd() > 0);
drop(tmpfile);
}
#[test]
fn test_read() {
let tmpfile = NamedTempFile::new().unwrap();
fs::write(&tmpfile.path(), b"Hello, world!").unwrap();
let fd = open(&tmpfile.path(), OpenFlags::READ_ONLY).unwrap();
let result = read(fd.as_fd(), SeekFrom::Current(0), 5).unwrap();
assert_eq!(result, b"Hello");
let result = read(fd.as_fd(), SeekFrom::Current(0), 5).unwrap();
assert_eq!(result, b"Hello");
let result = read(fd.as_fd(), SeekFrom::Current(50), 10);
assert!(!result.is_err());
assert_eq!(result.unwrap().len(), 0);
drop(tmpfile);
}
#[test]
fn test_write() {
let tmpfile = NamedTempFile::new().unwrap();
let fd = open(&tmpfile.path(), OpenFlags::READ_WRITE).unwrap();
let bytes_written = write(fd.as_fd(), SeekFrom::Current(0), b"Hello, world!").unwrap();
assert_eq!(bytes_written, 13);
let content = read(fd.as_fd(), SeekFrom::Start(0), 100).unwrap();
assert_eq!(&String::from_utf8(content).unwrap(), "Hello, world!");
let bytes_written = write(fd.as_fd(), SeekFrom::Current(7), b"Rustaceans!").unwrap();
assert_eq!(bytes_written, 11);
let content = read(fd.as_fd(), SeekFrom::Start(0), 100).unwrap();
assert_eq!(&String::from_utf8(content).unwrap(), "Hello, Rustaceans!");
drop(tmpfile);
}
#[test]
fn test_readdir() {
let tmpdir = TempDir::new().unwrap();
let file1 = tmpdir.path().join("file1");
File::create(&file1).unwrap();
let entries = readdir(&tmpdir.path()).unwrap();
assert!(entries.iter().any(|(name, _)| name == Path::new("file1")));
fs::remove_file(&file1).unwrap();
drop(tmpdir);
}
#[test]
fn test_statfs() {
let dir_path = Path::new("/tmp");
let stat = statfs(dir_path).unwrap();
assert!(stat.total_blocks > 0);
assert!(stat.block_size > 0);
}
#[test]
fn test_lseek() {
use std::io::Write;
let tmpfile = NamedTempFile::new().unwrap();
let path = tmpfile.path().to_path_buf();
{
let mut file = File::create(&path).unwrap();
file.write_all(b"Hello, World!").unwrap();
}
let fd = open(&path, OpenFlags::READ_WRITE).unwrap();
let borrowed_fd = fd.as_fd();
let new_pos = lseek(borrowed_fd, SeekFrom::Start(7)).unwrap();
assert_eq!(new_pos, 7);
let buffer = read(borrowed_fd, SeekFrom::Current(0), 6).unwrap();
assert_eq!(buffer, b"World!");
let buffer = read(borrowed_fd, SeekFrom::Current(0), 6).unwrap();
assert_eq!(buffer, b"World!");
let new_pos = lseek(borrowed_fd, SeekFrom::Current(-6)).unwrap();
assert_eq!(new_pos, 1);
let buffer = read(borrowed_fd, SeekFrom::Current(-1), 5).unwrap();
assert_eq!(buffer, b"Hello");
let new_pos = lseek(borrowed_fd, SeekFrom::End(-5)).unwrap();
assert_eq!(new_pos, 8);
let buffer = read(borrowed_fd, SeekFrom::Current(0), 5).unwrap();
assert_eq!(buffer, b"orld!");
let new_pos = lseek(borrowed_fd, SeekFrom::Start(20)).unwrap();
assert_eq!(new_pos, 20);
let result = read(borrowed_fd, SeekFrom::Current(0), 5).unwrap();
assert_eq!(result.len(), 0);
drop(tmpfile);
}
}