@@ -1755,6 +1755,55 @@ fn copy_extended_attrs(source: &Path, dest: &Path, skip_selinux: bool) -> CopyRe
17551755 Ok ( ( ) )
17561756}
17571757
1758+ /// Copy the access and modification timestamps from `source_metadata` onto `dest`.
1759+ /// If `dest` is a symlink, the symlink's own timestamps are set rather than the
1760+ /// target's.
1761+ ///
1762+ /// On WASI this calls `rustix::fs::utimensat` directly because `filetime`'s
1763+ /// WASI backend panics in `from_last_{access,modification}_time`. `SystemTime`
1764+ /// values are converted to `Timespec` against `UNIX_EPOCH`, matching WASI's
1765+ /// `path_filestat_set_times` contract (unsigned nanosecond count — pre-epoch
1766+ /// source times can't be represented).
1767+ fn set_timestamps ( source_metadata : & Metadata , dest : & Path ) -> CopyResult < ( ) > {
1768+ #[ cfg( target_os = "wasi" ) ]
1769+ {
1770+ use std:: time:: UNIX_EPOCH ;
1771+ let to_timespec = |t : std:: time:: SystemTime | -> io:: Result < rustix:: fs:: Timespec > {
1772+ let d = t
1773+ . duration_since ( UNIX_EPOCH )
1774+ . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: Unsupported , e) ) ?;
1775+ Ok ( rustix:: fs:: Timespec {
1776+ tv_sec : d. as_secs ( ) as i64 ,
1777+ tv_nsec : d. subsec_nanos ( ) as i32 ,
1778+ } )
1779+ } ;
1780+ let timestamps = rustix:: fs:: Timestamps {
1781+ last_access : to_timespec ( source_metadata. accessed ( ) ?) ?,
1782+ last_modification : to_timespec ( source_metadata. modified ( ) ?) ?,
1783+ } ;
1784+ let flags = if dest. is_symlink ( ) {
1785+ rustix:: fs:: AtFlags :: SYMLINK_NOFOLLOW
1786+ } else {
1787+ rustix:: fs:: AtFlags :: empty ( )
1788+ } ;
1789+ rustix:: fs:: utimensat ( rustix:: fs:: CWD , dest, & timestamps, flags)
1790+ . map_err ( io:: Error :: from) ?;
1791+ Ok ( ( ) )
1792+ }
1793+
1794+ #[ cfg( not( target_os = "wasi" ) ) ]
1795+ {
1796+ let atime = FileTime :: from_last_access_time ( source_metadata) ;
1797+ let mtime = FileTime :: from_last_modification_time ( source_metadata) ;
1798+ if dest. is_symlink ( ) {
1799+ filetime:: set_symlink_file_times ( dest, atime, mtime) ?;
1800+ } else {
1801+ filetime:: set_file_times ( dest, atime, mtime) ?;
1802+ }
1803+ Ok ( ( ) )
1804+ }
1805+ }
1806+
17581807/// Copy the specified attributes from one path to another.
17591808/// If `skip_selinux_xattr` is true, the security.selinux xattr will not be copied
17601809/// (used when -Z is specified to set the default context instead).
@@ -1835,51 +1884,8 @@ pub(crate) fn copy_attributes(
18351884 Ok ( ( ) )
18361885 } ) ?;
18371886
1838- handle_preserve ( attributes. timestamps , || -> CopyResult < ( ) > {
1839- #[ cfg( target_os = "wasi" ) ]
1840- {
1841- // `filetime`'s WASI backend panics in
1842- // `from_last_{access,modification}_time`. Reach `utimensat` directly
1843- // through `rustix`, converting `SystemTime` → `Timespec` via
1844- // `UNIX_EPOCH` (which matches the `path_filestat_set_times` contract).
1845- use std:: time:: UNIX_EPOCH ;
1846- let to_timespec = |t : std:: time:: SystemTime | -> io:: Result < rustix:: fs:: Timespec > {
1847- // Pre-epoch source times can't be represented by WASI's
1848- // `path_filestat_set_times` (unsigned nanosecond count).
1849- let d = t
1850- . duration_since ( UNIX_EPOCH )
1851- . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: Unsupported , e) ) ?;
1852- Ok ( rustix:: fs:: Timespec {
1853- tv_sec : d. as_secs ( ) as _ ,
1854- tv_nsec : d. subsec_nanos ( ) as _ ,
1855- } )
1856- } ;
1857- let timestamps = rustix:: fs:: Timestamps {
1858- last_access : to_timespec ( source_metadata. accessed ( ) ?) ?,
1859- last_modification : to_timespec ( source_metadata. modified ( ) ?) ?,
1860- } ;
1861- let flags = if dest. is_symlink ( ) {
1862- rustix:: fs:: AtFlags :: SYMLINK_NOFOLLOW
1863- } else {
1864- rustix:: fs:: AtFlags :: empty ( )
1865- } ;
1866- rustix:: fs:: utimensat ( rustix:: fs:: CWD , dest, & timestamps, flags)
1867- . map_err ( io:: Error :: from) ?;
1868- Ok ( ( ) )
1869- }
1870-
1871- #[ cfg( not( target_os = "wasi" ) ) ]
1872- {
1873- let atime = FileTime :: from_last_access_time ( & source_metadata) ;
1874- let mtime = FileTime :: from_last_modification_time ( & source_metadata) ;
1875- if dest. is_symlink ( ) {
1876- filetime:: set_symlink_file_times ( dest, atime, mtime) ?;
1877- } else {
1878- filetime:: set_file_times ( dest, atime, mtime) ?;
1879- }
1880-
1881- Ok ( ( ) )
1882- }
1887+ handle_preserve ( attributes. timestamps , || {
1888+ set_timestamps ( & source_metadata, dest)
18831889 } ) ?;
18841890
18851891 #[ cfg( all( feature = "selinux" , any( target_os = "linux" , target_os = "android" ) ) ) ]
0 commit comments