use crate::error::{FsError, Result};
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct ReadOptions {
pub require_single_link: bool,
pub share_read_only: bool,
}
impl ReadOptions {
pub const fn new() -> Self {
Self {
require_single_link: false,
share_read_only: false,
}
}
pub const fn require_single_link(mut self, yes: bool) -> Self {
self.require_single_link = yes;
self
}
pub const fn share_read_only(mut self, yes: bool) -> Self {
self.share_read_only = yes;
self
}
}
pub fn final_path(file: &File) -> io::Result<PathBuf> {
final_path_impl(file)
}
#[cfg(target_vendor = "apple")]
fn final_path_impl(file: &File) -> io::Result<PathBuf> {
use std::ffi::OsStr;
use std::os::fd::AsRawFd;
use std::os::unix::ffi::OsStrExt;
let mut buffer = [0u8; 1024];
let rc = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETPATH, buffer.as_mut_ptr()) };
if rc == -1 {
return Err(io::Error::last_os_error());
}
let len = buffer.iter().position(|b| *b == 0).unwrap_or(buffer.len());
Ok(PathBuf::from(OsStr::from_bytes(&buffer[..len])))
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn final_path_impl(file: &File) -> io::Result<PathBuf> {
use std::os::fd::AsRawFd;
std::fs::read_link(format!("/proc/self/fd/{}", file.as_raw_fd()))
}
#[cfg(all(
unix,
not(any(target_vendor = "apple", target_os = "linux", target_os = "android"))
))]
fn final_path_impl(_file: &File) -> io::Result<PathBuf> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"the final path of a handle is not available on this platform",
))
}
#[cfg(windows)]
fn final_path_impl(file: &File) -> io::Result<PathBuf> {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::os::windows::io::AsRawHandle;
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetFinalPathNameByHandleW(
handle: *mut std::ffi::c_void,
buffer: *mut u16,
len: u32,
flags: u32,
) -> u32;
}
let mut buf = vec![0u16; 512];
loop {
let n = unsafe {
GetFinalPathNameByHandleW(
file.as_raw_handle().cast(),
buf.as_mut_ptr(),
buf.len() as u32,
0, )
};
if n == 0 {
return Err(io::Error::last_os_error());
}
if (n as usize) < buf.len() {
buf.truncate(n as usize);
return Ok(crate::plain::plain_path(PathBuf::from(
OsString::from_wide(&buf),
)));
}
buf.resize(n as usize + 1, 0);
}
}
#[cfg(not(any(unix, windows)))]
fn final_path_impl(_file: &File) -> io::Result<PathBuf> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"the final path of a handle is not available on this platform",
))
}
pub fn link_count(file: &File) -> io::Result<u64> {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
Ok(file.metadata()?.nlink())
}
#[cfg(windows)]
{
crate::file_key::windows_info(file).map(|(_, links)| u64::from(links))
}
#[cfg(not(any(unix, windows)))]
{
let _ = file;
Err(io::Error::new(
io::ErrorKind::Unsupported,
"link counts are not available on this platform",
))
}
}
pub(crate) fn finish_read_open(file: File, shown: &Path, options: &ReadOptions) -> Result<File> {
let meta = file.metadata().map_err(|e| FsError::io(shown, e))?;
if !meta.is_file() {
return Err(FsError::NotAFile(shown.to_path_buf()));
}
#[cfg(unix)]
clear_nonblock(&file).map_err(|e| FsError::io(shown, e))?;
if options.require_single_link && link_count(&file).map_err(|e| FsError::io(shown, e))? != 1 {
return Err(FsError::HardLinked(shown.to_path_buf()));
}
Ok(file)
}
#[cfg(unix)]
pub(crate) fn clear_nonblock(file: &File) -> io::Result<()> {
use std::os::fd::AsRawFd;
unsafe {
let flags = libc::fcntl(file.as_raw_fd(), libc::F_GETFL);
if flags == -1 {
return Err(io::Error::last_os_error());
}
if flags & libc::O_NONBLOCK != 0
&& libc::fcntl(file.as_raw_fd(), libc::F_SETFL, flags & !libc::O_NONBLOCK) == -1
{
return Err(io::Error::last_os_error());
}
}
Ok(())
}
pub fn open_read_nofollow(path: &Path, options: &ReadOptions) -> Result<File> {
let file = open_leaf(path, options)?;
finish_read_open(file, path, options)
}
#[cfg(unix)]
fn open_leaf(path: &Path, _options: &ReadOptions) -> Result<File> {
use std::os::unix::fs::OpenOptionsExt;
match std::fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK)
.open(path)
{
Ok(file) => Ok(file),
Err(e) if e.raw_os_error() == Some(libc::ELOOP) => {
Err(FsError::Symlink(path.to_path_buf()))
}
Err(e) => Err(FsError::io(path, e)),
}
}
#[cfg(windows)]
fn open_leaf(path: &Path, options: &ReadOptions) -> Result<File> {
use std::os::windows::fs::OpenOptionsExt;
const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x0020_0000;
const FILE_SHARE_READ: u32 = 0x1;
const FILE_SHARE_READ_WRITE_DELETE: u32 = 0x7;
let file = std::fs::OpenOptions::new()
.read(true)
.share_mode(if options.share_read_only {
FILE_SHARE_READ
} else {
FILE_SHARE_READ_WRITE_DELETE
})
.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT)
.open(path)
.map_err(|e| FsError::io(path, e))?;
let meta = file.metadata().map_err(|e| FsError::io(path, e))?;
if meta.file_type().is_symlink() {
return Err(FsError::Symlink(path.to_path_buf()));
}
Ok(file)
}
#[cfg(not(any(unix, windows)))]
fn open_leaf(path: &Path, _options: &ReadOptions) -> Result<File> {
if let Ok(meta) = std::fs::symlink_metadata(path) {
if meta.file_type().is_symlink() {
return Err(FsError::Symlink(path.to_path_buf()));
}
}
File::open(path).map_err(|e| FsError::io(path, e))
}