@@ -2226,3 +2226,225 @@ 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 times BEFORE calling set_times (to compare later)
2306+ let link_metadata_before = fs:: symlink_metadata ( & link) . unwrap ( ) ;
2307+ let link_accessed_before = link_metadata_before. accessed ( ) . 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+ let link_metadata_after = fs:: symlink_metadata ( & link) . unwrap ( ) ;
2370+ assert_eq ! (
2371+ link_metadata_after. accessed( ) . unwrap( ) ,
2372+ link_accessed_before,
2373+ "symlink's own accessed time should not change"
2374+ ) ;
2375+ assert_eq ! (
2376+ link_metadata_after. modified( ) . unwrap( ) ,
2377+ link_modified_before,
2378+ "symlink's own modified time should not change"
2379+ ) ;
2380+ }
2381+
2382+ #[ test]
2383+ fn test_fs_set_times_nofollow ( ) {
2384+ #[ cfg( target_vendor = "apple" ) ]
2385+ use crate :: os:: darwin:: fs:: FileTimesExt ;
2386+ #[ cfg( windows) ]
2387+ use crate :: os:: windows:: fs:: FileTimesExt ;
2388+
2389+ let tmp = tmpdir ( ) ;
2390+
2391+ // Create a target file and a symlink to it
2392+ let target = tmp. join ( "target" ) ;
2393+ File :: create ( & target) . unwrap ( ) ;
2394+
2395+ #[ cfg( unix) ]
2396+ let link = tmp. join ( "link" ) ;
2397+ #[ cfg( unix) ]
2398+ crate :: os:: unix:: fs:: symlink ( & target, & link) . unwrap ( ) ;
2399+
2400+ #[ cfg( windows) ]
2401+ let link = tmp. join ( "link.txt" ) ;
2402+ #[ cfg( windows) ]
2403+ crate :: os:: windows:: fs:: symlink_file ( & target, & link) . unwrap ( ) ;
2404+
2405+ let mut times = FileTimes :: new ( ) ;
2406+ let accessed = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 11111 ) ;
2407+ let modified = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 22222 ) ;
2408+ times = times. set_accessed ( accessed) . set_modified ( modified) ;
2409+
2410+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2411+ let created = SystemTime :: UNIX_EPOCH + Duration :: from_secs ( 33333 ) ;
2412+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2413+ {
2414+ times = times. set_created ( created) ;
2415+ }
2416+
2417+ // Set times on the symlink itself (not following it)
2418+ match fs:: set_times_nofollow ( & link, times) {
2419+ // Allow unsupported errors on platforms which don't support setting times.
2420+ #[ cfg( not( any(
2421+ windows,
2422+ all(
2423+ unix,
2424+ not( any(
2425+ target_os = "android" ,
2426+ target_os = "redox" ,
2427+ target_os = "espidf" ,
2428+ target_os = "horizon"
2429+ ) )
2430+ )
2431+ ) ) ) ]
2432+ Err ( e) if e. kind ( ) == ErrorKind :: Unsupported => return ,
2433+ Err ( e) => panic ! ( "error setting symlink times: {e:?}" ) ,
2434+ Ok ( _) => { }
2435+ }
2436+
2437+ // Read symlink metadata (without following)
2438+ let metadata = fs:: symlink_metadata ( & link) . unwrap ( ) ;
2439+ assert_eq ! ( metadata. accessed( ) . unwrap( ) , accessed) ;
2440+ assert_eq ! ( metadata. modified( ) . unwrap( ) , modified) ;
2441+ #[ cfg( any( windows, target_vendor = "apple" ) ) ]
2442+ {
2443+ assert_eq ! ( metadata. created( ) . unwrap( ) , created) ;
2444+ }
2445+
2446+ // Verify that the target file's times were NOT changed
2447+ let target_metadata = fs:: metadata ( & target) . unwrap ( ) ;
2448+ assert_ne ! ( target_metadata. accessed( ) . unwrap( ) , accessed) ;
2449+ assert_ne ! ( target_metadata. modified( ) . unwrap( ) , modified) ;
2450+ }
0 commit comments