From e49cdfe220276fc860d004b9f8c540ac713d266b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Thu, 19 Feb 2026 13:22:14 +0800 Subject: [PATCH] fix: fallback in Windows casefold for MS-DOS devices (#3700) --- yazi-fs/src/provider/local/casefold.rs | 46 ++++++++++++++++++-------- yazi-shared/src/sync_cell.rs | 4 +-- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/yazi-fs/src/provider/local/casefold.rs b/yazi-fs/src/provider/local/casefold.rs index cae9877f..df188010 100644 --- a/yazi-fs/src/provider/local/casefold.rs +++ b/yazi-fs/src/provider/local/casefold.rs @@ -180,17 +180,12 @@ fn final_path(path: &Path) -> io::Result { #[cfg(target_os = "windows")] fn final_path(path: &Path) -> io::Result { - use std::{ffi::OsString, fs::File, os::windows::{ffi::OsStringExt, fs::OpenOptionsExt, io::AsRawHandle}}; + use std::{ffi::OsString, fs::File, mem, os::windows::{ffi::{OsStrExt, OsStringExt}, fs::OpenOptionsExt, io::AsRawHandle}}; use either::Either; - use windows_sys::Win32::{Foundation::HANDLE, Storage::FileSystem::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, GetFinalPathNameByHandleW, VOLUME_NAME_DOS}}; + use windows_sys::Win32::{Foundation::{HANDLE, INVALID_HANDLE_VALUE}, Storage::FileSystem::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FindClose, FindFirstFileW, GetFinalPathNameByHandleW, VOLUME_NAME_DOS, WIN32_FIND_DATAW}}; - let file = std::fs::OpenOptions::new() - .access_mode(0) - .custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT) - .open(path)?; - - fn inner(file: &File, buf: &mut [u16]) -> io::Result> { + fn by_handle(file: &File, buf: &mut [u16]) -> io::Result> { let len = unsafe { GetFinalPathNameByHandleW( file.as_raw_handle() as HANDLE, @@ -211,11 +206,36 @@ fn final_path(path: &Path) -> io::Result { }) } - match inner(&file, &mut [0u16; 512])? { - Either::Left(path) => Ok(path), - Either::Right(len) => inner(&file, &mut vec![0u16; len as usize])? - .left() - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "path too long")), + fn by_find(path: &Path) -> io::Result { + let Some(parent) = path.parent() else { + return Ok(path.to_path_buf()); + }; + + let wide: Vec = path.as_os_str().encode_wide().chain([0]).collect(); + let mut data = unsafe { mem::zeroed::() }; + match unsafe { FindFirstFileW(wide.as_ptr(), &mut data) } { + INVALID_HANDLE_VALUE => return Err(io::Error::last_os_error()), + handle => _ = unsafe { FindClose(handle) }, + } + + let name = data.cFileName.split(|&c| c == 0).next().unwrap_or(&data.cFileName); + Ok(parent.join(OsString::from_wide(name))) + } + + let file = std::fs::OpenOptions::new() + .access_mode(0) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT) + .open(path)?; + + match by_handle(&file, &mut [0u16; 512]) { + Ok(Either::Left(path)) => Ok(path), + Ok(Either::Right(len)) => match by_handle(&file, &mut vec![0u16; len as usize])? { + Either::Left(path) => Ok(path), + Either::Right(_) => Err(io::Error::new(io::ErrorKind::InvalidData, "path too long")), + }, + // Fallback for paths that GetFinalPathNameByHandleW cannot handle, + // such as those on DefineDosDeviceW-created devices (error 1005). + Err(_) => by_find(path), } } diff --git a/yazi-shared/src/sync_cell.rs b/yazi-shared/src/sync_cell.rs index 6f5d4780..cc434150 100644 --- a/yazi-shared/src/sync_cell.rs +++ b/yazi-shared/src/sync_cell.rs @@ -2,9 +2,9 @@ use std::{cell::Cell, fmt::{Debug, Display, Formatter}, ops::Deref}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -/// [`SyncCell`], but [`Sync`]. +/// [`Cell`], but [`Sync`]. /// -/// This is just an `Cell`, except it implements `Sync` +/// This is just a `Cell`, except it implements `Sync` /// if `T` implements `Sync`. pub struct SyncCell(Cell);