use std::fs::{File, Metadata, OpenOptions};
use std::io;
use std::path::Path;
use crate::Error;
pub(crate) fn open_root(root: &Path) -> Result<File, Error> {
let file = root_options()
.open(root)
.map_err(|_defect| Error::RepositoryUnavailable)?;
let metadata = file
.metadata()
.map_err(|_defect| Error::RepositoryUnavailable)?;
if !metadata.is_dir() || !ordinary(&metadata) {
return Err(Error::RepositoryUnavailable);
}
Ok(file)
}
pub(crate) fn open_dir(parent: &File, name: &str) -> io::Result<File> {
let file = fs_at::OpenOptions::default()
.read(true)
.follow(false)
.open_dir_at(parent, name)?;
let metadata = file.metadata()?;
if !metadata.is_dir() || !ordinary(&metadata) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"not an ordinary directory",
));
}
Ok(file)
}
pub(crate) fn open_file(parent: &File, name: &str) -> io::Result<File> {
let file = fs_at::OpenOptions::default()
.read(true)
.follow(false)
.open_at(parent, name)?;
let metadata = file.metadata()?;
if !metadata.is_file() || !ordinary(&metadata) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"not an ordinary file",
));
}
Ok(file)
}
pub(crate) fn names(dir: &mut File) -> io::Result<Vec<String>> {
let mut out = Vec::new();
for entry in fs_at::read_dir(dir)? {
let entry = entry?;
if let Some(name) = entry.name().to_str()
&& name != "."
&& name != ".."
{
out.push(name.to_owned());
}
}
Ok(out)
}
#[cfg(windows)]
fn ordinary(metadata: &Metadata) -> bool {
use std::os::windows::fs::MetadataExt as _;
metadata.file_attributes()
& windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT
== 0
}
#[cfg(unix)]
const fn ordinary(_metadata: &Metadata) -> bool {
true
}
#[cfg(unix)]
fn root_options() -> OpenOptions {
use std::os::unix::fs::OpenOptionsExt as _;
let mut options = OpenOptions::new();
options
.read(true)
.custom_flags(libc::O_NOFOLLOW | libc::O_DIRECTORY | libc::O_CLOEXEC);
options
}
#[cfg(windows)]
fn root_options() -> OpenOptions {
use std::os::windows::fs::OpenOptionsExt as _;
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_LIST_DIRECTORY,
FILE_READ_ATTRIBUTES, FILE_TRAVERSE, SYNCHRONIZE,
};
let mut options = OpenOptions::new();
options
.access_mode(SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_LIST_DIRECTORY | FILE_TRAVERSE)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT);
options
}
#[cfg(unix)]
pub(crate) fn read_exact_at(file: &File, buf: &mut [u8], offset: u64) -> io::Result<()> {
use std::os::unix::fs::FileExt as _;
file.read_exact_at(buf, offset)
}
#[cfg(windows)]
pub(crate) fn read_exact_at(file: &File, buf: &mut [u8], offset: u64) -> io::Result<()> {
use std::os::windows::fs::FileExt as _;
let mut written = 0_usize;
while written < buf.len() {
let at = offset
.checked_add(u64::try_from(written).unwrap_or(u64::MAX))
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "offset overflow"))?;
let slice = buf
.get_mut(written..)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "buffer overflow"))?;
match file.seek_read(slice, at) {
Ok(0) => {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"short positioned read",
));
}
Ok(count) => written = written.saturating_add(count),
Err(defect) if defect.kind() == io::ErrorKind::Interrupted => {}
Err(defect) => return Err(defect),
}
}
Ok(())
}