Skip to content

Commit 260501c

Browse files
committed
cp: extract WASI timestamp logic into set_timestamps function
1 parent 5a9740f commit 260501c

1 file changed

Lines changed: 51 additions & 45 deletions

File tree

src/uu/cp/src/cp.rs

Lines changed: 51 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)