@@ -1757,6 +1757,55 @@ fn copy_extended_attrs(source: &Path, dest: &Path, skip_selinux: bool) -> CopyRe
17571757 Ok ( ( ) )
17581758}
17591759
1760+ /// Copy the access and modification timestamps from `source_metadata` onto `dest`.
1761+ /// If `dest` is a symlink, the symlink's own timestamps are set rather than the
1762+ /// target's.
1763+ ///
1764+ /// On WASI this calls `rustix::fs::utimensat` directly because `filetime`'s
1765+ /// WASI backend panics in `from_last_{access,modification}_time`. `SystemTime`
1766+ /// values are converted to `Timespec` against `UNIX_EPOCH`, matching WASI's
1767+ /// `path_filestat_set_times` contract (unsigned nanosecond count — pre-epoch
1768+ /// source times can't be represented).
1769+ fn set_timestamps ( source_metadata : & Metadata , dest : & Path ) -> CopyResult < ( ) > {
1770+ #[ cfg( target_os = "wasi" ) ]
1771+ {
1772+ use std:: time:: UNIX_EPOCH ;
1773+ let to_timespec = |t : std:: time:: SystemTime | -> io:: Result < rustix:: fs:: Timespec > {
1774+ let d = t
1775+ . duration_since ( UNIX_EPOCH )
1776+ . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: Unsupported , e) ) ?;
1777+ Ok ( rustix:: fs:: Timespec {
1778+ tv_sec : d. as_secs ( ) as i64 ,
1779+ tv_nsec : d. subsec_nanos ( ) as i32 ,
1780+ } )
1781+ } ;
1782+ let timestamps = rustix:: fs:: Timestamps {
1783+ last_access : to_timespec ( source_metadata. accessed ( ) ?) ?,
1784+ last_modification : to_timespec ( source_metadata. modified ( ) ?) ?,
1785+ } ;
1786+ let flags = if dest. is_symlink ( ) {
1787+ rustix:: fs:: AtFlags :: SYMLINK_NOFOLLOW
1788+ } else {
1789+ rustix:: fs:: AtFlags :: empty ( )
1790+ } ;
1791+ rustix:: fs:: utimensat ( rustix:: fs:: CWD , dest, & timestamps, flags)
1792+ . map_err ( io:: Error :: from) ?;
1793+ Ok ( ( ) )
1794+ }
1795+
1796+ #[ cfg( not( target_os = "wasi" ) ) ]
1797+ {
1798+ let atime = FileTime :: from_last_access_time ( source_metadata) ;
1799+ let mtime = FileTime :: from_last_modification_time ( source_metadata) ;
1800+ if dest. is_symlink ( ) {
1801+ filetime:: set_symlink_file_times ( dest, atime, mtime) ?;
1802+ } else {
1803+ filetime:: set_file_times ( dest, atime, mtime) ?;
1804+ }
1805+ Ok ( ( ) )
1806+ }
1807+ }
1808+
17601809/// Copy the specified attributes from one path to another.
17611810/// If `skip_selinux_xattr` is true, the security.selinux xattr will not be copied
17621811/// (used when -Z is specified to set the default context instead).
@@ -1870,51 +1919,8 @@ pub(crate) fn copy_attributes(
18701919 Ok ( ( ) )
18711920 } ) ?;
18721921
1873- handle_preserve ( attributes. timestamps , || -> CopyResult < ( ) > {
1874- #[ cfg( target_os = "wasi" ) ]
1875- {
1876- // `filetime`'s WASI backend panics in
1877- // `from_last_{access,modification}_time`. Reach `utimensat` directly
1878- // through `rustix`, converting `SystemTime` → `Timespec` via
1879- // `UNIX_EPOCH` (which matches the `path_filestat_set_times` contract).
1880- use std:: time:: UNIX_EPOCH ;
1881- let to_timespec = |t : std:: time:: SystemTime | -> io:: Result < rustix:: fs:: Timespec > {
1882- // Pre-epoch source times can't be represented by WASI's
1883- // `path_filestat_set_times` (unsigned nanosecond count).
1884- let d = t
1885- . duration_since ( UNIX_EPOCH )
1886- . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: Unsupported , e) ) ?;
1887- Ok ( rustix:: fs:: Timespec {
1888- tv_sec : d. as_secs ( ) as _ ,
1889- tv_nsec : d. subsec_nanos ( ) as _ ,
1890- } )
1891- } ;
1892- let timestamps = rustix:: fs:: Timestamps {
1893- last_access : to_timespec ( source_metadata. accessed ( ) ?) ?,
1894- last_modification : to_timespec ( source_metadata. modified ( ) ?) ?,
1895- } ;
1896- let flags = if dest. is_symlink ( ) {
1897- rustix:: fs:: AtFlags :: SYMLINK_NOFOLLOW
1898- } else {
1899- rustix:: fs:: AtFlags :: empty ( )
1900- } ;
1901- rustix:: fs:: utimensat ( rustix:: fs:: CWD , dest, & timestamps, flags)
1902- . map_err ( io:: Error :: from) ?;
1903- Ok ( ( ) )
1904- }
1905-
1906- #[ cfg( not( target_os = "wasi" ) ) ]
1907- {
1908- let atime = FileTime :: from_last_access_time ( & source_metadata) ;
1909- let mtime = FileTime :: from_last_modification_time ( & source_metadata) ;
1910- if dest. is_symlink ( ) {
1911- filetime:: set_symlink_file_times ( dest, atime, mtime) ?;
1912- } else {
1913- filetime:: set_file_times ( dest, atime, mtime) ?;
1914- }
1915-
1916- Ok ( ( ) )
1917- }
1922+ handle_preserve ( attributes. timestamps , || {
1923+ set_timestamps ( & source_metadata, dest)
19181924 } ) ?;
19191925
19201926 #[ cfg( all( feature = "selinux" , any( target_os = "linux" , target_os = "android" ) ) ) ]
0 commit comments