@@ -206,9 +206,9 @@ pub(super) mod _os {
206206 #[ cfg( any( unix, windows) ) ]
207207 use crate :: utils:: ToCString ;
208208 use crate :: {
209- AsObject , Py , PyObjectRef , PyPayload , PyRef , PyResult , TryFromObject ,
209+ AsObject , Py , PyObject , PyObjectRef , PyPayload , PyRef , PyResult , TryFromObject ,
210210 builtins:: {
211- PyBytesRef , PyGenericAlias , PyIntRef , PyStrRef , PyTuple , PyTupleRef , PyTypeRef ,
211+ PyBytesRef , PyFloat , PyGenericAlias , PyIntRef , PyStrRef , PyTuple , PyTupleRef , PyTypeRef ,
212212 } ,
213213 class:: PyClassDef ,
214214 common:: lock:: { OnceCell , PyRwLock } ,
@@ -225,11 +225,12 @@ pub(super) mod _os {
225225 } ,
226226 vm:: VirtualMachine ,
227227 } ;
228+ use core:: hint:: cold_path;
228229 #[ cfg( not( windows) ) ]
229230 use core:: marker:: PhantomData ;
230- use core:: { hint:: cold_path, time:: Duration } ;
231231 use crossbeam_utils:: atomic:: AtomicCell ;
232232 use rustpython_common:: wtf8:: Wtf8Buf ;
233+ use rustpython_host_env:: os:: { FileTime , FileTimeSeconds } ;
233234 #[ cfg( windows) ]
234235 use rustpython_host_env:: { nt as host_nt, windows:: ToWideString } ;
235236
@@ -1838,6 +1839,56 @@ pub(super) mod _os {
18381839 follow_symlinks : FollowSymlinks ,
18391840 }
18401841
1842+ fn file_time_seconds ( obj : & PyObject , vm : & VirtualMachine ) -> PyResult < FileTimeSeconds > {
1843+ obj. try_index ( vm)
1844+ . and_then ( |value| value. try_to_primitive ( vm) )
1845+ . map_err ( |err| {
1846+ if err. fast_isinstance ( vm. ctx . exceptions . overflow_error ) {
1847+ vm. new_overflow_error ( "timestamp out of range for platform time_t" )
1848+ } else {
1849+ err
1850+ }
1851+ } )
1852+ }
1853+
1854+ fn file_time ( obj : & PyObject , vm : & VirtualMachine ) -> PyResult < FileTime > {
1855+ if let Some ( float) = obj. downcast_ref :: < PyFloat > ( ) {
1856+ let value = float. to_f64 ( ) ;
1857+ if value. is_nan ( ) {
1858+ return Err ( vm. new_value_error ( "Invalid value NaN (not a number)" ) ) ;
1859+ }
1860+ let mut seconds = value. trunc ( ) ;
1861+ let mut nanoseconds = ( value. fract ( ) * 1_000_000_000.0 ) . floor ( ) ;
1862+ if nanoseconds < 0.0 {
1863+ nanoseconds += 1_000_000_000.0 ;
1864+ seconds -= 1.0 ;
1865+ }
1866+ let minimum = FileTimeSeconds :: MIN as f64 ;
1867+ // The signed maximum rounds up when represented by f64.
1868+ if !( minimum..-minimum) . contains ( & seconds) {
1869+ return Err ( vm. new_overflow_error ( "timestamp out of range for platform time_t" ) ) ;
1870+ }
1871+ Ok ( FileTime {
1872+ seconds : seconds as FileTimeSeconds ,
1873+ nanoseconds : nanoseconds as libc:: c_long ,
1874+ } )
1875+ } else {
1876+ let seconds = match file_time_seconds ( obj, vm) {
1877+ Err ( err) if err. fast_isinstance ( vm. ctx . exceptions . type_error ) => {
1878+ return Err ( vm. new_type_error ( format ! (
1879+ "argument must be int or float, not {}" ,
1880+ obj. class( ) . fully_qualified_name( vm) ?
1881+ ) ) ) ;
1882+ }
1883+ result => result?,
1884+ } ;
1885+ Ok ( FileTime {
1886+ seconds,
1887+ nanoseconds : 0 ,
1888+ } )
1889+ }
1890+ }
1891+
18411892 #[ pyfunction]
18421893 fn utime ( args : UtimeArgs < ' _ > , vm : & VirtualMachine ) -> PyResult < ( ) > {
18431894 let parse_tup = |tup : & Py < PyTuple > | -> Option < ( PyObjectRef , PyObjectRef ) > {
@@ -1852,13 +1903,13 @@ pub(super) mod _os {
18521903 let ( a, m) = parse_tup ( & t) . ok_or_else ( || {
18531904 vm. new_type_error ( "utime: 'times' must be either a tuple of two ints or None" )
18541905 } ) ?;
1855- ( a . try_into_value ( vm) ?, m . try_into_value ( vm) ?)
1906+ ( file_time ( & a , vm) ?, file_time ( & m , vm) ?)
18561907 }
18571908 ( None , Some ( ns) ) => {
18581909 let ( a, m) = parse_tup ( & ns)
18591910 . ok_or_else ( || vm. new_type_error ( "utime: 'ns' must be a tuple of two ints" ) ) ?;
18601911 let ns_in_sec: PyObjectRef = vm. ctx . new_int ( 1_000_000_000 ) . into ( ) ;
1861- let ns_to_dur = |obj : PyObjectRef | {
1912+ let ns_to_time = |obj : PyObjectRef | {
18621913 let divmod = vm. _divmod ( & obj, & ns_in_sec) ?;
18631914 let ( div, rem) = divmod
18641915 . downcast_ref :: < PyTuple > ( )
@@ -1870,16 +1921,31 @@ pub(super) mod _os {
18701921 divmod. class( ) . name( )
18711922 ) )
18721923 } ) ?;
1873- let secs = div. try_index ( vm) ?. try_to_primitive ( vm) ?;
1874- let ns = rem. try_index ( vm) ?. try_to_primitive ( vm) ?;
1875- Ok ( Duration :: new ( secs, ns) )
1924+ let seconds = file_time_seconds ( & div, vm) ?;
1925+ let rem = rem. try_index ( vm) ?;
1926+ let nanoseconds = rem. try_to_primitive :: < libc:: c_long > ( vm) . map_err ( |err| {
1927+ if err. fast_isinstance ( vm. ctx . exceptions . overflow_error ) {
1928+ vm. new_overflow_error ( "Python int too large to convert to C long" )
1929+ } else {
1930+ err
1931+ }
1932+ } ) ?;
1933+ Ok ( FileTime {
1934+ seconds,
1935+ nanoseconds,
1936+ } )
18761937 } ;
1877- // TODO: do validation to make sure this doesn't.. underflow?
1878- ( ns_to_dur ( a) ?, ns_to_dur ( m) ?)
1938+ ( ns_to_time ( a) ?, ns_to_time ( m) ?)
18791939 }
18801940 ( None , None ) => {
18811941 let now = SystemTime :: now ( ) ;
18821942 let now = now. duration_since ( SystemTime :: UNIX_EPOCH ) . unwrap ( ) ;
1943+ let now = FileTime {
1944+ seconds : now. as_secs ( ) . try_into ( ) . map_err ( |_| {
1945+ vm. new_overflow_error ( "timestamp out of range for platform time_t" )
1946+ } ) ?,
1947+ nanoseconds : now. subsec_nanos ( ) as libc:: c_long ,
1948+ } ;
18831949 ( now, now)
18841950 }
18851951 ( Some ( _) , Some ( _) ) => {
@@ -1893,8 +1959,8 @@ pub(super) mod _os {
18931959
18941960 fn utime_impl (
18951961 path : OsPath ,
1896- acc : Duration ,
1897- modif : Duration ,
1962+ acc : FileTime ,
1963+ modif : FileTime ,
18981964 dir_fd : DirFd < ' _ , { UTIME_DIR_FD as usize } > ,
18991965 _follow_symlinks : FollowSymlinks ,
19001966 vm : & VirtualMachine ,
@@ -1934,8 +2000,15 @@ pub(super) mod _os {
19342000 ) ) ;
19352001 }
19362002
1937- crate :: host_env:: os:: set_file_times ( & path, acc, modif)
1938- . map_err ( |err| OSErrorBuilder :: with_filename ( & err, path, vm) )
2003+ let wide_path = path. to_wide_cstring ( vm) ?;
2004+ host_nt:: set_file_times (
2005+ & wide_path,
2006+ acc. seconds ,
2007+ acc. nanoseconds . into ( ) ,
2008+ modif. seconds ,
2009+ modif. nanoseconds . into ( ) ,
2010+ )
2011+ . map_err ( |err| OSErrorBuilder :: with_filename ( & err, path, vm) )
19392012 }
19402013 }
19412014
0 commit comments