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

Code before:
))]
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())));
	};

	let file = match unsafe { libc::open(cstr.as_ptr(), O_PATH | O_NOFOLLOW) } {
		ret if ret < 0 => return Err(io::Error::last_os_error()),
		ret => unsafe { File::from_raw_fd(ret) },
	};

	// Fast path: if the `/proc/self/fd/N` matches
	let oss = path.as_os_str();
	if let Ok(p) = std::fs::read_link(format!("/proc/self/fd/{}", file.as_raw_fd()))
		&& let Some(b) = p.as_os_str().as_bytes().get(..oss.len())
		&& b.eq_ignore_ascii_case(oss.as_bytes())
	{
		let mut b = p.into_os_string().into_vec();
		b.truncate(oss.len());
		return Ok(PathBuf::from(OsString::from_vec(b)));
	}

	// Fallback: scan the directory for matching inodes
	let meta = file.metadata()?;

Review comment (bug):
The error message "Cannot get filename" is not descriptive enough. It should specify which path component failed and provide more context about when this condition occurs.
```suggestion
			return Err(io::Error::other(format!(
				"Cannot get filename for path component: '{}' while reconstructing casefolded path",
				p.display()
			)));
```

Code after:
	target_os = "windows"
))]
fn casefold_impl(path: PathBuf) -> io::Result<PathBuf> {
	let mut it = path.components();
	let mut parts = vec![];
	loop {
		let p = it.as_path();
		let q = final_path(p)?;
		if p != q {
			parts.push(q);
		} else if parts.is_empty() {
			return Ok(q);
		} else {
			break;
		}
		if it.next_back().is_none() {
			break;
		}
	}

	let mut buf = it.as_path().to_path_buf();
	for p in parts.into_iter().rev() {
		if let Some(name) = p.file_name() {
			buf.push(name);
		} else {
			return Err(io::Error::other("Cannot get filename"));
		}
	}
	Ok(buf)
}

#[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())));
	};

	let file = match unsafe { libc::open(cstr.as_ptr(), O_PATH | O_NOFOLLOW) } {
		ret if ret < 0 => return Err(io::Error::last_os_error()),
		ret => unsafe { File::from_raw_fd(ret) },
	};

	// Fast path: if the `/proc/self/fd/N` matches
	let oss = path.as_os_str();
	if let Ok(p) = std::fs::read_link(format!("/proc/self/fd/{}", file.as_raw_fd()))