Skip to content

Commit f96a56c

Browse files
committed
cp: extract WASI timestamp logic into set_timestamps function
1 parent b7cb225 commit f96a56c

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
@@ -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

Comments
 (0)