#[cfg(unix)]
use super::secure_fs::{open_beneath_from_file, verified_from_open_file};
use super::{
secure_fs::{identity_drift, inspect_absolute, ContentHash, SecureDirectory, VerifiedPath},
FrozenPathKind, WorkflowResult,
};
use std::{ffi::OsString, fs::File, io, path::Path};
impl SecureDirectory {
pub(crate) fn directory_names(&self, relative: &Path) -> WorkflowResult<Vec<OsString>> {
let directory = self.open_directory(relative)?;
Ok(names(&directory)?)
}
}
pub(super) fn opened_names(file: &File) -> io::Result<Vec<OsString>> {
names(file)
}
pub(crate) fn open_verified_directory(path: &Path) -> WorkflowResult<VerifiedPath> {
inspect_absolute(path, FrozenPathKind::Directory, ContentHash::Skip)
}
pub(crate) fn opened_directory_names(directory: &VerifiedPath) -> WorkflowResult<Vec<OsString>> {
if directory.identity.kind != FrozenPathKind::Directory {
return Err(identity_drift(
Path::new(&directory.identity.canonical_path),
"expected an opened directory",
));
}
Ok(opened_names(&directory.file)?)
}
#[cfg(unix)]
pub(crate) fn open_verified_file_in_directory(
directory: &VerifiedPath,
relative: &Path,
content_hash: ContentHash,
) -> WorkflowResult<VerifiedPath> {
if directory.identity.kind != FrozenPathKind::Directory {
return Err(identity_drift(
Path::new(&directory.identity.canonical_path),
"expected an opened directory",
));
}
let file = open_beneath_from_file(
directory.file.try_clone()?,
relative,
FrozenPathKind::File,
false,
)?;
verified_from_open_file(
file,
Path::new(&directory.identity.canonical_path).join(relative),
FrozenPathKind::File,
content_hash,
)
}
#[cfg(not(unix))]
pub(crate) fn open_verified_file_in_directory(
directory: &VerifiedPath,
relative: &Path,
_content_hash: ContentHash,
) -> WorkflowResult<VerifiedPath> {
Err(identity_drift(
&Path::new(&directory.identity.canonical_path).join(relative),
"handle-relative catalog reads are unsupported on this platform",
))
}
#[cfg(unix)]
fn names(file: &File) -> io::Result<Vec<OsString>> {
use std::{ffi::CStr, os::fd::IntoRawFd as _, os::unix::ffi::OsStrExt as _};
let fd = file.try_clone()?.into_raw_fd();
let directory = unsafe { libc::fdopendir(fd) };
if directory.is_null() {
unsafe { libc::close(fd) };
return Err(io::Error::last_os_error());
}
let mut names = Vec::new();
loop {
unsafe { *readdir_errno_location() = 0 };
let entry = unsafe { libc::readdir(directory) };
if entry.is_null() {
let error = io::Error::last_os_error();
if error.raw_os_error() == Some(0) {
break;
}
unsafe { libc::closedir(directory) };
return Err(error);
}
let bytes = unsafe { CStr::from_ptr((*entry).d_name.as_ptr()) }.to_bytes();
if bytes != b"." && bytes != b".." {
names.push(std::ffi::OsStr::from_bytes(bytes).to_owned());
}
}
if unsafe { libc::closedir(directory) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(names)
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "fuchsia",
target_os = "hurd",
target_os = "redox"
))]
unsafe fn readdir_errno_location() -> *mut libc::c_int {
unsafe { libc::__errno_location() }
}
#[cfg(any(
target_os = "android",
target_os = "netbsd",
target_os = "openbsd",
target_os = "nuttx"
))]
unsafe fn readdir_errno_location() -> *mut libc::c_int {
unsafe { libc::__errno() }
}
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
unsafe fn readdir_errno_location() -> *mut libc::c_int {
unsafe { libc::__error() }
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
unsafe fn readdir_errno_location() -> *mut libc::c_int {
unsafe { libc::___errno() }
}
#[cfg(target_os = "haiku")]
unsafe fn readdir_errno_location() -> *mut libc::c_int {
unsafe { libc::_errnop() }
}
#[cfg(target_os = "aix")]
unsafe fn readdir_errno_location() -> *mut libc::c_int {
unsafe { libc::_Errno() }
}
#[cfg(target_os = "nto")]
unsafe fn readdir_errno_location() -> *mut libc::c_int {
unsafe { libc::__get_errno_ptr() }
}
#[cfg(windows)]
fn names(file: &File) -> io::Result<Vec<OsString>> {
use std::{os::windows::ffi::OsStringExt as _, os::windows::io::AsRawHandle as _};
use windows_sys::Win32::{
Foundation::{ERROR_HANDLE_EOF, ERROR_NO_MORE_FILES},
Storage::FileSystem::{
FileIdBothDirectoryInfo, GetFileInformationByHandleEx, FILE_ID_BOTH_DIR_INFO,
},
};
let mut buffer = [0u64; 1024];
let mut names = Vec::new();
loop {
let succeeded = unsafe {
GetFileInformationByHandleEx(
file.as_raw_handle(),
FileIdBothDirectoryInfo,
buffer.as_mut_ptr().cast(),
std::mem::size_of_val(&buffer) as u32,
)
};
if succeeded == 0 {
let error = io::Error::last_os_error();
if error.raw_os_error() == Some(ERROR_NO_MORE_FILES as i32)
|| error.raw_os_error() == Some(ERROR_HANDLE_EOF as i32)
{
break;
}
return Err(error);
}
let mut offset = 0usize;
loop {
let entry = unsafe {
&*buffer
.as_ptr()
.cast::<u8>()
.add(offset)
.cast::<FILE_ID_BOTH_DIR_INFO>()
};
let name = unsafe {
std::slice::from_raw_parts(
entry.FileName.as_ptr(),
entry.FileNameLength as usize / std::mem::size_of::<u16>(),
)
};
if name != [b'.' as u16] && name != [b'.' as u16, b'.' as u16] {
names.push(OsString::from_wide(name));
}
if entry.NextEntryOffset == 0 {
break;
}
offset += entry.NextEntryOffset as usize;
}
}
Ok(names)
}
#[cfg(not(any(unix, windows)))]
fn names(_file: &File) -> io::Result<Vec<OsString>> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"handle-based directory enumeration is unavailable",
))
}