@@ -2226,3 +2226,222 @@ fn test_open_options_invalid_combinations() {
22262226 assert_eq ! ( err. kind( ) , ErrorKind :: InvalidInput ) ;
22272227 assert_eq ! ( err. to_string( ) , "must specify at least one of read, write, or append access" ) ;
22282228}
2229+
2230+ #[ test]
2231+ fn test_fs_set_times ( ) {
2232+ #[ cfg( target_vendor = "apple" ) ]
2233+ use crate :: os:: darwin:: fs:: FileTimesExt ;
2234+ #[ cfg( windows) ]
2235+ use crate :: os:: windows:: fs:: FileTimesExt ;
2236+
2237+ let tmp = tmpdir ( ) ;
2238+ let path = tmp. join ( "foo" ) ;
2239+ File :: create ( & path) . unwrap ( ) ;
2240+
2241+ let mut times = FileTimes :: new ( ) ;
2242+ let accessed = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 12345 ) ;
2243+ let modified = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 54321 ) ;
2244+ times = times. set_accessed ( accessed) . set_modified ( modified) ;
2245+
2246+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2247+ let created = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 32123 ) ;
2248+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2249+ {
2250+ times = times. set_created ( created) ;
2251+ }
2252+
2253+ match fs:: set_times ( & path, times) {
2254+ // Allow unsupported errors on platforms which don't support setting times.
2255+ #[ cfg( not( any(
2256+ windows,
2257+ all(
2258+ unix,
2259+ not( any(
2260+ target_os = "android" ,
2261+ target_os = "redox" ,
2262+ target_os = "espidf" ,
2263+ target_os = "horizon"
2264+ ) )
2265+ )
2266+ ) ) ) ]
2267+ Err ( e) if e. kind ( ) == ErrorKind :: Unsupported => return ,
2268+ Err ( e) => panic ! ( "error setting file times: {e:?}" ) ,
2269+ Ok ( _) => { }
2270+ }
2271+
2272+ let metadata = fs:: metadata ( & path) . unwrap ( ) ;
2273+ assert_eq ! ( metadata. accessed( ) . unwrap( ) , accessed) ;
2274+ assert_eq ! ( metadata. modified( ) . unwrap( ) , modified) ;
2275+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2276+ {
2277+ assert_eq ! ( metadata. created( ) . unwrap( ) , created) ;
2278+ }
2279+ }
2280+
2281+ #[ test]
2282+ fn test_fs_set_times_follows_symlink ( ) {
2283+ #[ cfg( target_vendor = "apple" ) ]
2284+ use crate :: os:: darwin:: fs:: FileTimesExt ;
2285+ #[ cfg( windows) ]
2286+ use crate :: os:: windows:: fs:: FileTimesExt ;
2287+
2288+ let tmp = tmpdir ( ) ;
2289+
2290+ // Create a target file
2291+ let target = tmp. join ( "target" ) ;
2292+ File :: create ( & target) . unwrap ( ) ;
2293+
2294+ // Create a symlink to the target
2295+ #[ cfg( unix) ]
2296+ let link = tmp. join ( "link" ) ;
2297+ #[ cfg( unix) ]
2298+ crate :: os:: unix:: fs:: symlink ( & target, & link) . unwrap ( ) ;
2299+
2300+ #[ cfg( windows) ]
2301+ let link = tmp. join ( "link.txt" ) ;
2302+ #[ cfg( windows) ]
2303+ crate :: os:: windows:: fs:: symlink_file ( & target, & link) . unwrap ( ) ;
2304+
2305+ // Get the symlink's own modified time BEFORE calling set_times (to compare later)
2306+ // We don't check accessed time because reading metadata may update atime on some platforms.
2307+ let link_metadata_before = fs:: symlink_metadata ( & link) . unwrap ( ) ;
2308+ let link_modified_before = link_metadata_before. modified ( ) . unwrap ( ) ;
2309+
2310+ let mut times = FileTimes :: new ( ) ;
2311+ let accessed = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 12345 ) ;
2312+ let modified = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 54321 ) ;
2313+ times = times. set_accessed ( accessed) . set_modified ( modified) ;
2314+
2315+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2316+ let created = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 32123 ) ;
2317+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2318+ {
2319+ times = times. set_created ( created) ;
2320+ }
2321+
2322+ // Call fs::set_times on the symlink - it should follow the link and modify the target
2323+ match fs:: set_times ( & link, times) {
2324+ // Allow unsupported errors on platforms which don't support setting times.
2325+ #[ cfg( not( any(
2326+ windows,
2327+ all(
2328+ unix,
2329+ not( any(
2330+ target_os = "android" ,
2331+ target_os = "redox" ,
2332+ target_os = "espidf" ,
2333+ target_os = "horizon"
2334+ ) )
2335+ )
2336+ ) ) ) ]
2337+ Err ( e) if e. kind ( ) == ErrorKind :: Unsupported => return ,
2338+ Err ( e) => panic ! ( "error setting file times through symlink: {e:?}" ) ,
2339+ Ok ( _) => { }
2340+ }
2341+
2342+ // Verify that the TARGET file's times were changed (following the symlink)
2343+ let target_metadata = fs:: metadata ( & target) . unwrap ( ) ;
2344+ assert_eq ! (
2345+ target_metadata. accessed( ) . unwrap( ) ,
2346+ accessed,
2347+ "target file accessed time should match"
2348+ ) ;
2349+ assert_eq ! (
2350+ target_metadata. modified( ) . unwrap( ) ,
2351+ modified,
2352+ "target file modified time should match"
2353+ ) ;
2354+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2355+ {
2356+ assert_eq ! (
2357+ target_metadata. created( ) . unwrap( ) ,
2358+ created,
2359+ "target file created time should match"
2360+ ) ;
2361+ }
2362+
2363+ // Also verify through the symlink (fs::metadata follows symlinks)
2364+ let link_followed_metadata = fs:: metadata ( & link) . unwrap ( ) ;
2365+ assert_eq ! ( link_followed_metadata. accessed( ) . unwrap( ) , accessed) ;
2366+ assert_eq ! ( link_followed_metadata. modified( ) . unwrap( ) , modified) ;
2367+
2368+ // Verify that the SYMLINK ITSELF was NOT modified
2369+ // Note: We only check modified time, not accessed time, because reading the symlink
2370+ // metadata may update its atime on some platforms (e.g., Linux).
2371+ let link_metadata_after = fs:: symlink_metadata ( & link) . unwrap ( ) ;
2372+ assert_eq ! (
2373+ link_metadata_after. modified( ) . unwrap( ) ,
2374+ link_modified_before,
2375+ "symlink's own modified time should not change"
2376+ ) ;
2377+ }
2378+
2379+ #[ test]
2380+ fn test_fs_set_times_nofollow ( ) {
2381+ #[ cfg( target_vendor = "apple" ) ]
2382+ use crate :: os:: darwin:: fs:: FileTimesExt ;
2383+ #[ cfg( windows) ]
2384+ use crate :: os:: windows:: fs:: FileTimesExt ;
2385+
2386+ let tmp = tmpdir ( ) ;
2387+
2388+ // Create a target file and a symlink to it
2389+ let target = tmp. join ( "target" ) ;
2390+ File :: create ( & target) . unwrap ( ) ;
2391+
2392+ #[ cfg( unix) ]
2393+ let link = tmp. join ( "link" ) ;
2394+ #[ cfg( unix) ]
2395+ crate :: os:: unix:: fs:: symlink ( & target, & link) . unwrap ( ) ;
2396+
2397+ #[ cfg( windows) ]
2398+ let link = tmp. join ( "link.txt" ) ;
2399+ #[ cfg( windows) ]
2400+ crate :: os:: windows:: fs:: symlink_file ( & target, & link) . unwrap ( ) ;
2401+
2402+ let mut times = FileTimes :: new ( ) ;
2403+ let accessed = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 11111 ) ;
2404+ let modified = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 22222 ) ;
2405+ times = times. set_accessed ( accessed) . set_modified ( modified) ;
2406+
2407+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2408+ let created = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 33333 ) ;
2409+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2410+ {
2411+ times = times. set_created ( created) ;
2412+ }
2413+
2414+ // Set times on the symlink itself (not following it)
2415+ match fs:: set_times_nofollow ( & link, times) {
2416+ // Allow unsupported errors on platforms which don't support setting times.
2417+ #[ cfg( not( any(
2418+ windows,
2419+ all(
2420+ unix,
2421+ not( any(
2422+ target_os = "android" ,
2423+ target_os = "redox" ,
2424+ target_os = "espidf" ,
2425+ target_os = "horizon"
2426+ ) )
2427+ )
2428+ ) ) ) ]
2429+ Err ( e) if e. kind ( ) == ErrorKind :: Unsupported => return ,
2430+ Err ( e) => panic ! ( "error setting symlink times: {e:?}" ) ,
2431+ Ok ( _) => { }
2432+ }
2433+
2434+ // Read symlink metadata (without following)
2435+ let metadata = fs:: symlink_metadata ( & link) . unwrap ( ) ;
2436+ assert_eq ! ( metadata. accessed( ) . unwrap( ) , accessed) ;
2437+ assert_eq ! ( metadata. modified( ) . unwrap( ) , modified) ;
2438+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2439+ {
2440+ assert_eq ! ( metadata. created( ) . unwrap( ) , created) ;
2441+ }
2442+
2443+ // Verify that the target file's times were NOT changed
2444+ let target_metadata = fs:: metadata ( & target) . unwrap ( ) ;
2445+ assert_ne ! ( target_metadata. accessed( ) . unwrap( ) , accessed) ;
2446+ assert_ne ! ( target_metadata. modified( ) . unwrap( ) , modified) ;
2447+ }
0 commit comments