use super::*;
use std::os::fd::{AsRawFd, FromRawFd};
use std::os::unix::fs::OpenOptionsExt;
pub(super) fn open_root(path: &Path) -> io::Result<std::fs::File> {
std::fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_DIRECTORY | libc::O_CLOEXEC)
.open(path)
}
fn name_bytes(name: ChildName<'_>) -> [u8; 256] {
let mut bytes = [0; 256];
bytes[..name.0.len()].copy_from_slice(name.0.as_bytes());
bytes
}
pub(super) fn create_directory(root: &std::fs::File, name: ChildName<'_>) -> io::Result<()> {
let name = name_bytes(name);
let result = unsafe { libc::mkdirat(root.as_raw_fd(), name.as_ptr().cast(), 0o700) };
if result == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub(super) fn create_control_candidate(
root: &std::fs::File,
name: ChildName<'_>,
) -> io::Result<std::fs::File> {
let name = name_bytes(name);
let fd = unsafe {
libc::openat(
root.as_raw_fd(),
name.as_ptr().cast(),
libc::O_RDWR | libc::O_CREAT | libc::O_EXCL | libc::O_CLOEXEC | libc::O_NOFOLLOW,
0o600,
)
};
if fd == -1 {
return Err(io::Error::last_os_error());
}
Ok(unsafe { std::fs::File::from_raw_fd(fd) })
}
pub(super) fn replace_control(
root: &std::fs::File,
_file: &std::fs::File,
source: ChildName<'_>,
target: ChildName<'_>,
) -> io::Result<()> {
let source = name_bytes(source);
let target = name_bytes(target);
let result = unsafe {
libc::renameat(
root.as_raw_fd(),
source.as_ptr().cast(),
root.as_raw_fd(),
target.as_ptr().cast(),
)
};
if result == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub(super) fn open_wal_append_child(
root: &std::fs::File,
name: ChildName<'_>,
) -> io::Result<std::fs::File> {
let name = name_bytes(name);
let fd = unsafe {
libc::openat(
root.as_raw_fd(),
name.as_ptr().cast(),
libc::O_RDWR | libc::O_APPEND | libc::O_CLOEXEC | libc::O_NOFOLLOW,
)
};
if fd == -1 {
return Err(io::Error::last_os_error());
}
Ok(unsafe { std::fs::File::from_raw_fd(fd) })
}
pub(super) fn open_mutable_child(
root: &std::fs::File,
name: ChildName<'_>,
) -> io::Result<std::fs::File> {
let name = name_bytes(name);
let fd = unsafe {
libc::openat(
root.as_raw_fd(),
name.as_ptr().cast(),
libc::O_RDWR | libc::O_CLOEXEC | libc::O_NOFOLLOW,
)
};
if fd == -1 {
return Err(io::Error::last_os_error());
}
Ok(unsafe { std::fs::File::from_raw_fd(fd) })
}
#[cfg(all(
any(test, feature = "test-support"),
any(target_os = "linux", target_vendor = "apple")
))]
pub(super) fn reopen_directory(root: &std::fs::File) -> io::Result<std::fs::File> {
open_directory(root, ChildName("."))
}
pub(super) fn sync_directory(root: &std::fs::File, _context: &FileOpenContext) -> io::Result<()> {
root.sync_all()
}
#[cfg(any(test, feature = "test-support"))]
pub(super) fn open_recorded_lock(
root: &std::fs::File,
name: ChildName<'_>,
create_new: bool,
) -> io::Result<std::fs::File> {
let bytes = name_bytes(name);
let create = if create_new {
libc::O_CREAT | libc::O_EXCL
} else {
0
};
let fd = unsafe {
libc::openat(
root.as_raw_fd(),
bytes.as_ptr().cast(),
libc::O_RDWR | create | libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_NONBLOCK,
0o600,
)
};
if fd == -1 {
return Err(io::Error::last_os_error());
}
Ok(unsafe { std::fs::File::from_raw_fd(fd) })
}
pub(super) fn open_child(root: &std::fs::File, name: ChildName<'_>) -> io::Result<std::fs::File> {
let bytes = name_bytes(name);
let fd = unsafe {
libc::openat(
root.as_raw_fd(),
bytes.as_ptr().cast(),
libc::O_RDONLY | libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_NONBLOCK,
)
};
if fd == -1 {
return Err(io::Error::last_os_error());
}
Ok(unsafe { std::fs::File::from_raw_fd(fd) })
}
pub(super) fn open_directory(
root: &std::fs::File,
name: ChildName<'_>,
) -> io::Result<std::fs::File> {
let bytes = name_bytes(name);
let fd = unsafe {
libc::openat(
root.as_raw_fd(),
bytes.as_ptr().cast(),
libc::O_RDONLY
| libc::O_DIRECTORY
| libc::O_CLOEXEC
| libc::O_NOFOLLOW
| libc::O_NONBLOCK,
)
};
if fd == -1 {
return Err(io::Error::last_os_error());
}
Ok(unsafe { std::fs::File::from_raw_fd(fd) })
}
pub(super) fn remove_child(
root: &std::fs::File,
name: ChildName<'_>,
_context: &FileOpenContext,
) -> io::Result<()> {
let bytes = name_bytes(name);
let status = unsafe { libc::unlinkat(root.as_raw_fd(), bytes.as_ptr().cast(), 0) };
if status == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub(super) fn remove_directory(
root: &std::fs::File,
name: ChildName<'_>,
_context: &FileOpenContext,
) -> io::Result<()> {
let name = name_bytes(name);
let result =
unsafe { libc::unlinkat(root.as_raw_fd(), name.as_ptr().cast(), libc::AT_REMOVEDIR) };
if result == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub(super) fn open_lock_file(
root: &std::fs::File,
name: ChildName<'_>,
mode: SourceDirectoryLockMode,
) -> io::Result<std::fs::File> {
let bytes = name_bytes(name);
let access = match mode {
SourceDirectoryLockMode::ReadExisting => libc::O_RDONLY,
SourceDirectoryLockMode::ReadWriteCreate => libc::O_RDWR | libc::O_CREAT,
};
let fd = unsafe {
libc::openat(
root.as_raw_fd(),
bytes.as_ptr().cast(),
access | libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_NONBLOCK,
0o600,
)
};
if fd == -1 {
return Err(io::Error::last_os_error());
}
Ok(unsafe { std::fs::File::from_raw_fd(fd) })
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
pub(super) fn visit_children(
root: &std::fs::File,
visitor: &mut dyn FnMut(&str) -> io::Result<()>,
) -> io::Result<()> {
visit_names(root, &mut |name| {
let name = name.to_str().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"source directory contains a non-UTF-8 name",
)
})?;
visitor(name)
})
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
pub(super) fn visit_names(
root: &std::fs::File,
visitor: &mut dyn FnMut(&std::ffi::OsStr) -> io::Result<()>,
) -> io::Result<()> {
use std::os::unix::ffi::OsStrExt;
let fd = unsafe {
libc::openat(
root.as_raw_fd(),
c".".as_ptr(),
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
)
};
if fd == -1 {
return Err(io::Error::last_os_error());
}
let directory = unsafe { libc::fdopendir(fd) };
if directory.is_null() {
let error = io::Error::last_os_error();
unsafe {
libc::close(fd);
}
return Err(error);
}
struct Cursor(*mut libc::DIR);
impl Drop for Cursor {
fn drop(&mut self) {
unsafe {
libc::closedir(self.0);
}
}
}
let cursor = Cursor(directory);
loop {
let entry = unsafe {
#[cfg(target_os = "linux")]
{
*libc::__errno_location() = 0;
}
#[cfg(target_vendor = "apple")]
{
*libc::__error() = 0;
}
libc::readdir(cursor.0)
};
if entry.is_null() {
let error = io::Error::last_os_error();
return if error.raw_os_error() == Some(0) {
Ok(())
} else {
Err(error)
};
}
let name = unsafe { std::ffi::CStr::from_ptr((*entry).d_name.as_ptr()) };
if matches!(name.to_bytes(), b"." | b"..") {
continue;
}
visitor(std::ffi::OsStr::from_bytes(name.to_bytes()))?;
}
}
#[cfg(all(
any(test, feature = "test-support"),
any(target_os = "linux", target_vendor = "apple")
))]
pub(super) fn open_recorded_child(
root: &std::fs::File,
name: &std::ffi::OsStr,
) -> io::Result<std::fs::File> {
use std::os::unix::ffi::OsStrExt;
let name = name.as_bytes();
if name.is_empty()
|| name.len() > 255
|| matches!(name, b"." | b"..")
|| name.iter().any(|byte| matches!(byte, 0 | b'/'))
{
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid native child name",
));
}
let mut encoded = [0_u8; 256];
encoded[..name.len()].copy_from_slice(name);
let mut metadata = std::mem::MaybeUninit::<libc::stat>::uninit();
let result = unsafe {
libc::fstatat(
root.as_raw_fd(),
encoded.as_ptr().cast(),
metadata.as_mut_ptr(),
libc::AT_SYMLINK_NOFOLLOW,
)
};
if result == -1 {
return Err(io::Error::last_os_error());
}
let kind = unsafe { metadata.assume_init() }.st_mode & libc::S_IFMT;
if kind != libc::S_IFREG && kind != libc::S_IFDIR {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"captured project contains a nonregular file",
));
}
let fd = unsafe {
libc::openat(
root.as_raw_fd(),
encoded.as_ptr().cast(),
libc::O_RDONLY | libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_NONBLOCK,
)
};
if fd == -1 {
return Err(io::Error::last_os_error());
}
Ok(unsafe { std::fs::File::from_raw_fd(fd) })
}
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))]
pub(super) fn visit_children(
_root: &std::fs::File,
_visitor: &mut dyn FnMut(&str) -> io::Result<()>,
) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"source directory enumeration requires Linux, Apple or Windows",
))
}
pub(super) fn matches_child_file(
root: &std::fs::File,
name: ChildName<'_>,
file: &std::fs::File,
_context: &FileOpenContext,
) -> io::Result<bool> {
use std::os::unix::fs::MetadataExt;
let name = name_bytes(name);
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
if unsafe {
libc::fstatat(
root.as_raw_fd(),
name.as_ptr().cast(),
stat.as_mut_ptr(),
libc::AT_SYMLINK_NOFOLLOW,
)
} == -1
{
return Err(io::Error::last_os_error());
}
let stat = unsafe { stat.assume_init() };
if stat.st_mode & libc::S_IFMT != libc::S_IFREG {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"source child is not a regular non-symlink file",
));
}
let held = file.metadata()?;
fn device_number(value: impl Into<i128>) -> u64 {
value.into() as u64
}
Ok(held.is_file() && held.dev() == device_number(stat.st_dev) && held.ino() == stat.st_ino)
}