@@ -153,6 +153,8 @@ pub struct KeyInfo {
153153 max_value_name_len : u32 ,
154154 // Maximum length of the value data (in bytes).
155155 max_value_data_len : u32 ,
156+ // Last modification time of this key.
157+ modified : Result < std:: time:: SystemTime , UnsupportedFiletimeError > ,
156158}
157159
158160impl KeyInfo {
@@ -189,6 +191,11 @@ impl KeyInfo {
189191 data_buf : Vec :: with_capacity ( self . max_value_data_len as usize ) ,
190192 }
191193 }
194+
195+ /// Returns the last modification time of this key.
196+ pub fn modified ( & self ) -> std:: io:: Result < std:: time:: SystemTime > {
197+ self . modified . map_err ( |error| std:: io:: Error :: from ( error) )
198+ }
192199}
193200
194201/// Iterator over registry key subkey names.
@@ -612,6 +619,8 @@ unsafe fn query_raw_key_info(
612619 let mut max_value_name_len = std:: mem:: MaybeUninit :: uninit ( ) ;
613620 let mut max_value_data_len = std:: mem:: MaybeUninit :: uninit ( ) ;
614621
622+ let mut last_write_time = std:: mem:: MaybeUninit :: uninit ( ) ;
623+
615624 // SAFETY: This is just an FFI call as described in the docs [1].
616625 //
617626 // [1]: https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryinfokeyw
@@ -628,7 +637,7 @@ unsafe fn query_raw_key_info(
628637 max_value_name_len. as_mut_ptr ( ) ,
629638 max_value_data_len. as_mut_ptr ( ) ,
630639 std:: ptr:: null_mut ( ) ,
631- std :: ptr :: null_mut ( ) ,
640+ last_write_time . as_mut_ptr ( ) ,
632641 )
633642 } ;
634643
@@ -653,6 +662,11 @@ unsafe fn query_raw_key_info(
653662 max_value_data_len : unsafe {
654663 max_value_data_len. assume_init ( )
655664 } ,
665+ // SAFETY: We verified that the call above succeeded, the value is now
666+ // initialized.
667+ modified : filetime_to_system_time ( unsafe {
668+ last_write_time. assume_init ( )
669+ } ) ,
656670 } )
657671}
658672
@@ -743,6 +757,82 @@ unsafe fn query_raw_key_value_data(
743757 . map_err ( std:: io:: Error :: from)
744758}
745759
760+ /// Converts the given Windows `FILETIME` object to Rust's [`SystemTime`].
761+ ///
762+ /// [`SystemTime`]: std::time::SystemTime
763+ fn filetime_to_system_time (
764+ filetime : windows_sys:: Win32 :: Foundation :: FILETIME ,
765+ ) -> Result < std:: time:: SystemTime , UnsupportedFiletimeError > {
766+ let epoch_win_nanos100 = {
767+ let hi = u64:: from ( filetime. dwHighDateTime ) ;
768+ let lo = u64:: from ( filetime. dwLowDateTime ) ;
769+ ( hi << u32:: BITS ) | lo
770+ } ;
771+ // So, we have the last write time in 100-nanosecond intervals since Windows
772+ // epoch, i.e. January 1, 1601 [1]. A difference between that and the UNIX
773+ // epoch is 11,644,473,600 seconds [2, 3].
774+ //
775+ // [1]: https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
776+ // [2]: https://learn.microsoft.com/en-us/windows/win32/sysinfo/converting-a-time-t-value-to-a-file-time
777+ // [3]: https://devblogs.microsoft.com/oldnewthing/20220602-00/?p=106706
778+ let epoch_win_secs = {
779+ epoch_win_nanos100 / ( 1_000_000_000 / 100 )
780+ } ;
781+ let epoch_win_nanos = {
782+ epoch_win_nanos100 % ( 1_000_000_000 / 100 ) * 100
783+ } ;
784+ let epoch_win_since = {
785+ std:: time:: Duration :: from_secs ( epoch_win_secs) +
786+ std:: time:: Duration :: from_nanos ( epoch_win_nanos)
787+ } ;
788+ let epoch_unix_since = epoch_win_since
789+ // Windows epoch is before the UNIX one, so it is possible to underflow
790+ // here.
791+ . checked_sub ( std:: time:: Duration :: from_secs ( 11_644_473_600 ) )
792+ . ok_or ( UnsupportedFiletimeError :: Underflow ( epoch_win_nanos100) ) ?;
793+
794+ std:: time:: SystemTime :: UNIX_EPOCH
795+ // Generally this should not overflow as we started with a valid 64-bit
796+ // value and then did a bunch of conversions to reach `SystemTime` that
797+ // internally uses what we started with. But in practice if we pass max
798+ // `FILETIME` object this does overflow so we need to back ourselves up.
799+ . checked_add ( epoch_unix_since)
800+ . ok_or ( UnsupportedFiletimeError :: Overflow ( epoch_win_nanos100) )
801+ }
802+
803+ #[ derive( Clone , Copy , Debug ) ]
804+ enum UnsupportedFiletimeError {
805+ Underflow ( u64 ) ,
806+ Overflow ( u64 ) ,
807+ }
808+
809+ impl std:: fmt:: Display for UnsupportedFiletimeError {
810+
811+ fn fmt ( & self , fmt : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
812+ write ! ( fmt, "filetime value " ) ?;
813+ match * self {
814+ UnsupportedFiletimeError :: Underflow ( value) => {
815+ write ! ( fmt, "underflow: {value}" ) ?
816+ }
817+ UnsupportedFiletimeError :: Overflow ( value) => {
818+ write ! ( fmt, "overflow: {value}" ) ?
819+ }
820+ }
821+
822+ Ok ( ( ) )
823+ }
824+ }
825+
826+ impl std:: error:: Error for UnsupportedFiletimeError {
827+ }
828+
829+ impl From < UnsupportedFiletimeError > for std:: io:: Error {
830+
831+ fn from ( error : UnsupportedFiletimeError ) -> std:: io:: Error {
832+ std:: io:: Error :: new ( std:: io:: ErrorKind :: Unsupported , error)
833+ }
834+ }
835+
746836#[ cfg( test) ]
747837mod tests {
748838
@@ -903,4 +993,27 @@ mod tests {
903993
904994 assert ! ( matches!( current, ValueData :: U32 ( _) ) ) ;
905995 }
996+
997+ #[ test]
998+ fn filetime_to_system_time_epoch_win ( ) {
999+ let epoch_win = windows_sys:: Win32 :: Foundation :: FILETIME {
1000+ dwLowDateTime : 0 ,
1001+ dwHighDateTime : 0 ,
1002+ } ;
1003+
1004+ // Windows epoch is before the UNIX one.
1005+ let error = filetime_to_system_time ( epoch_win) . unwrap_err ( ) ;
1006+ assert ! ( matches!( error, UnsupportedFiletimeError :: Underflow ( _) ) ) ;
1007+ }
1008+
1009+ #[ test]
1010+ fn filetime_to_system_max ( ) {
1011+ let max = windows_sys:: Win32 :: Foundation :: FILETIME {
1012+ dwLowDateTime : u32:: MAX ,
1013+ dwHighDateTime : u32:: MAX ,
1014+ } ;
1015+
1016+ // We just want to verify that this does not panic.
1017+ let _ = filetime_to_system_time ( max) ;
1018+ }
9061019}
0 commit comments