Repository: sxyazi/yazi
File: yazi-fs/src/provider/local/casefold.rs (Rust)

Code before:
use std::{io, path::{Path, PathBuf}};

pub async fn valid_name_case(path: impl AsRef<Path>) -> io::Result<bool> {
	let path = path.as_ref().to_owned();
	tokio::task::spawn_blocking(move || valid_name_case_impl(path)).await?
}

#[cfg(any(
	target_os = "macos",
	target_os = "netbsd",
	target_os = "openbsd",
	target_os = "freebsd"
))]
fn valid_name_case_impl(path: PathBuf) -> io::Result<bool> {
	use std::{ffi::{CStr, CString, OsStr}, os::{fd::{AsRawFd, FromRawFd, OwnedFd}, unix::ffi::OsStrExt}};

	use libc::{F_GETPATH, O_RDONLY, O_SYMLINK, PATH_MAX};

	let cstr = CString::new(path.into_os_string().into_encoded_bytes())?;
	let Some(name) = Path::new(OsStr::from_bytes(cstr.as_bytes())).file_name() else {
		return Ok(true);
	};

	let fd = match unsafe { libc::open(cstr.as_ptr(), O_RDONLY | O_SYMLINK) } {
		ret if ret < 0 => return Err(io::Error::last_os_error()),
		ret => unsafe { OwnedFd::from_raw_fd(ret) },
	};

	let mut buf = [0u8; PATH_MAX as usize];
	if unsafe { libc::fcntl(fd.as_raw_fd(), F_GETPATH, buf.as_mut_ptr()) } < 0 {
		return Err(io::Error::last_os_error());
	}

	Ok(
		unsafe { CStr::from_ptr(buf.as_ptr() as *const i8) }
			.to_bytes()
			.ends_with(name.as_encoded_bytes()),
	)
}

#[cfg(any(target_os = "linux", target_os = "android"))]
fn valid_name_case_impl(path: PathBuf) -> io::Result<bool> {
	use std::{ffi::{CString, OsStr}, fs::File, os::{fd::{AsRawFd, FromRawFd}, unix::ffi::OsStrExt}};

	use libc::{O_NOFOLLOW, O_PATH};

	let cstr = CString::new(path.into_os_string().into_encoded_bytes())?;
	let path = Path::new(OsStr::from_bytes(cstr.as_bytes()));
	let Some(name) = path.file_name() else { return Ok(true) };

	let file = match unsafe { libc::open(cstr.as_ptr(), O_PATH | O_NOFOLLOW) } {

Review comment (performance):
The `into_vec()` conversion on line 26 and the subsequent vector operations could be inefficient for large paths. Consider using byte slice operations where possible to avoid unnecessary allocations.

Code after:
use std::{io, path::{Path, PathBuf}};

#[inline]
pub async fn casefold(path: impl AsRef<Path>) -> io::Result<PathBuf> {
	let path = path.as_ref().to_owned();
	tokio::task::spawn_blocking(move || casefold_impl(path)).await?
}

#[inline]
pub async fn must_case_match(path: impl AsRef<Path>) -> bool {
	let path = path.as_ref();
	casefold(path).await.is_ok_and(|p| p == path)
}

#[cfg(any(
	target_os = "macos",
	target_os = "netbsd",
	target_os = "openbsd",
	target_os = "freebsd"
))]
fn casefold_impl(path: PathBuf) -> io::Result<PathBuf> {
	use std::{ffi::{CStr, CString, OsString}, os::{fd::{AsRawFd, FromRawFd, OwnedFd}, unix::ffi::OsStringExt}};

	use libc::{F_GETPATH, O_RDONLY, O_SYMLINK, PATH_MAX};

	let cstr = CString::new(path.into_os_string().into_vec())?;
	let fd = match unsafe { libc::open(cstr.as_ptr(), O_RDONLY | O_SYMLINK) } {
		ret if ret < 0 => return Err(io::Error::last_os_error()),
		ret => unsafe { OwnedFd::from_raw_fd(ret) },
	};

	let mut buf = [0u8; PATH_MAX as usize];
	if unsafe { libc::fcntl(fd.as_raw_fd(), F_GETPATH, buf.as_mut_ptr()) } < 0 {
		return Err(io::Error::last_os_error());
	}

	let cstr = unsafe { CStr::from_ptr(buf.as_ptr() as *const i8) };
	Ok(OsString::from_vec(cstr.to_bytes().to_vec()).into())
}

#[cfg(any(target_os = "linux", target_os = "android"))]
fn casefold_impl(path: PathBuf) -> io::Result<PathBuf> {
	use std::{ffi::{CString, OsStr, OsString}, fs::File, os::{fd::{AsRawFd, FromRawFd}, unix::{ffi::{OsStrExt, OsStringExt}, fs::MetadataExt}}};

	use libc::{O_NOFOLLOW, O_PATH};

	let cstr = CString::new(path.into_os_string().into_vec())?;
	let path = Path::new(OsStr::from_bytes(cstr.as_bytes()));
	let Some(parent) = path.parent() else {
		return Ok(PathBuf::from(OsString::from_vec(cstr.into_bytes())));
	};