@@ -31,7 +31,7 @@ use windows_sys::{
3131 ERROR_FILENAME_EXCED_RANGE , ERROR_INSUFFICIENT_BUFFER , ERROR_INVALID_FUNCTION ,
3232 ERROR_INVALID_HANDLE , ERROR_INVALID_NAME , ERROR_INVALID_PARAMETER , ERROR_MORE_DATA ,
3333 ERROR_NOT_READY , ERROR_NOT_SUPPORTED , ERROR_PATH_NOT_FOUND , ERROR_SHARING_VIOLATION ,
34- GENERIC_READ , GENERIC_WRITE , GetHandleInformation , GetLastError , HANDLE ,
34+ FILETIME , GENERIC_READ , GENERIC_WRITE , GetHandleInformation , GetLastError , HANDLE ,
3535 HANDLE_FLAG_INHERIT , INVALID_HANDLE_VALUE , MAX_PATH , SetHandleInformation ,
3636 } ,
3737 Globalization :: { CP_UTF8 , MultiByteToWideChar , WideCharToMultiByte } ,
@@ -48,7 +48,7 @@ use windows_sys::{
4848 GetVolumePathNameW , GetVolumePathNamesForVolumeNameW , INVALID_FILE_ATTRIBUTES ,
4949 OPEN_EXISTING , RemoveDirectoryW , SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE ,
5050 SYMBOLIC_LINK_FLAG_DIRECTORY , SetFileAttributesW , SetFileInformationByHandle ,
51- WIN32_FILE_ATTRIBUTE_DATA , WIN32_FIND_DATAW ,
51+ SetFileTime , WIN32_FILE_ATTRIBUTE_DATA , WIN32_FIND_DATAW ,
5252 } ,
5353 System :: {
5454 Console ,
@@ -288,33 +288,58 @@ pub fn supports_virtual_terminal() -> bool {
288288}
289289
290290pub fn symlink (
291- src : & Path ,
292- dst : & Path ,
291+ _src : & Path ,
292+ _dst : & Path ,
293293 src_wide : & widestring:: WideCStr ,
294294 dst_wide : & widestring:: WideCStr ,
295295 target_is_directory : bool ,
296296) -> io:: Result < ( ) > {
297297 static HAS_UNPRIVILEGED_FLAG : AtomicBool = AtomicBool :: new ( true ) ;
298298
299- fn check_dir ( src : & Path , dst : & Path ) -> bool {
299+ fn check_dir ( src : & widestring :: WideCStr , dst : & widestring :: WideCStr ) -> bool {
300300 use windows_sys:: Win32 :: Storage :: FileSystem :: GetFileExInfoStandard ;
301301
302- let Some ( dst_parent) = dst. parent ( ) else {
302+ // `_check_dirW`: two WCHAR[MAX_PATH] buffers; a join that does not
303+ // fit is "not a directory".
304+ let src = src. as_slice ( ) ;
305+ let dst = dst. as_slice ( ) ;
306+ let max_path = MAX_PATH_USIZE ;
307+ if dst. len ( ) >= max_path {
303308 return false ;
304- } ;
305- let resolved = if src. is_absolute ( ) {
306- src. to_path_buf ( )
309+ }
310+ let parent_len = dst
311+ . iter ( )
312+ . rposition ( |& unit| unit == b'\\' as u16 || unit == b'/' as u16 )
313+ . unwrap_or ( 0 ) ;
314+ let parent = & dst[ ..parent_len] ;
315+ let absolute = src
316+ . first ( )
317+ . is_some_and ( |& unit| unit == b'\\' as u16 || unit == b'/' as u16 )
318+ || ( src. first ( ) . is_some_and ( |& unit| unit != 0 ) && src. get ( 1 ) == Some ( & ( b':' as u16 ) ) ) ;
319+
320+ let mut resolved = Vec :: with_capacity ( max_path) ;
321+ if absolute {
322+ if src. len ( ) >= max_path {
323+ return false ;
324+ }
325+ resolved. extend_from_slice ( src) ;
307326 } else {
308- dst_parent. join ( src)
309- } ;
310- let wide = match widestring:: WideCString :: from_os_str ( & resolved) {
311- Ok ( wide) => wide,
312- Err ( _) => return false ,
313- } ;
327+ let separator_len = usize:: from ( !parent. is_empty ( ) ) ;
328+ if parent. len ( ) + separator_len + src. len ( ) >= max_path {
329+ return false ;
330+ }
331+ resolved. extend_from_slice ( parent) ;
332+ if !parent. is_empty ( ) {
333+ resolved. push ( b'\\' as u16 ) ;
334+ }
335+ resolved. extend_from_slice ( src) ;
336+ }
337+ resolved. push ( 0 ) ;
338+
314339 let mut info: WIN32_FILE_ATTRIBUTE_DATA = unsafe { core:: mem:: zeroed ( ) } ;
315340 let ok = unsafe {
316341 GetFileAttributesExW (
317- wide . as_ptr ( ) ,
342+ resolved . as_ptr ( ) ,
318343 GetFileExInfoStandard ,
319344 ( & mut info as * mut WIN32_FILE_ATTRIBUTE_DATA ) . cast ( ) ,
320345 )
@@ -326,7 +351,7 @@ pub fn symlink(
326351 if HAS_UNPRIVILEGED_FLAG . load ( Ordering :: Relaxed ) {
327352 flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE ;
328353 }
329- if target_is_directory || check_dir ( src , dst ) {
354+ if target_is_directory || check_dir ( src_wide , dst_wide ) {
330355 flags |= SYMBOLIC_LINK_FLAG_DIRECTORY ;
331356 }
332357
@@ -349,6 +374,53 @@ pub fn symlink(
349374 }
350375}
351376
377+ /// `time_t_to_FILE_TIME` plus `SetFileTime`. A FILETIME counts 100ns ticks
378+ /// from 1601-01-01, so the epoch shift makes a second before 1970 an ordinary
379+ /// positive tick count. The arithmetic wraps: a second this filesystem cannot
380+ /// hold writes the bits the multiplication leaves.
381+ pub fn set_file_times (
382+ path : & widestring:: WideCStr ,
383+ atime_sec : i64 ,
384+ atime_nsec : i64 ,
385+ mtime_sec : i64 ,
386+ mtime_nsec : i64 ,
387+ ) -> io:: Result < ( ) > {
388+ const EPOCH_DIFF : i64 = 11_644_473_600 ;
389+ let to_filetime = |sec : i64 , nsec : i64 | -> FILETIME {
390+ let ticks = sec
391+ . wrapping_add ( EPOCH_DIFF )
392+ . wrapping_mul ( 10_000_000 )
393+ . wrapping_add ( nsec / 100 ) as u64 ;
394+ FILETIME {
395+ dwLowDateTime : ticks as u32 ,
396+ dwHighDateTime : ( ticks >> 32 ) as u32 ,
397+ }
398+ } ;
399+ let atime = to_filetime ( atime_sec, atime_nsec) ;
400+ let mtime = to_filetime ( mtime_sec, mtime_nsec) ;
401+ let handle = unsafe {
402+ CreateFileW (
403+ path. as_ptr ( ) ,
404+ FILE_WRITE_ATTRIBUTES ,
405+ 0 ,
406+ core:: ptr:: null ( ) ,
407+ OPEN_EXISTING ,
408+ FILE_FLAG_BACKUP_SEMANTICS ,
409+ core:: ptr:: null_mut ( ) ,
410+ )
411+ } ;
412+ if handle == INVALID_HANDLE_VALUE {
413+ return Err ( io:: Error :: last_os_error ( ) ) ;
414+ }
415+ let wrote = unsafe { SetFileTime ( handle, core:: ptr:: null ( ) , & atime, & mtime) } ;
416+ let error = ( wrote == 0 ) . then ( io:: Error :: last_os_error) ;
417+ unsafe { CloseHandle ( handle) } ;
418+ match error {
419+ Some ( error) => Err ( error) ,
420+ None => Ok ( ( ) ) ,
421+ }
422+ }
423+
352424#[ allow( clippy:: not_unsafe_ptr_arg_deref) ]
353425pub fn win32_hchmod ( handle : HANDLE , mode : u32 , write_bit : u32 ) -> io:: Result < ( ) > {
354426 let mut info: FILE_BASIC_INFO = unsafe { core:: mem:: zeroed ( ) } ;
0 commit comments