Skip to content

Commit 8439ff6

Browse files
authored
Collect modification time in list_winreg_keys.
1 parent aaf93a1 commit 8439ff6

File tree

3 files changed

+171
-29
lines changed

3 files changed

+171
-29
lines changed

crates/rrg/src/action/list_winreg_keys.rs

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ struct Item {
2323
key: std::ffi::OsString,
2424
/// Listed subkey relative to `root` and `key`.
2525
subkey: std::ffi::OsString,
26+
/// Last modification time of the listed subkey.
27+
modified: Option<std::time::SystemTime>,
2628
}
2729

2830
/// Handles invocations of the `list_winreg_keys` action.
@@ -33,11 +35,15 @@ where
3335
{
3436
let key = args.root.open(&args.key)
3537
.map_err(crate::session::Error::action)?;
38+
let key_info = key.info()
39+
.map_err(crate::session::Error::action)?;
3640

3741
/// Single "stack frame" of the recursive key listing procedure.
3842
struct PendingKey {
3943
/// Handle to the key specified in the `key_rel_name`.
4044
key: winreg::OpenKey,
45+
/// Information about the key specified in the `key_rel_name`.
46+
key_info: winreg::KeyInfo,
4147
/// Name of the `key` relative to the key from which we started.
4248
key_rel_name: std::ffi::OsString,
4349
/// Current depth of the recursive walk.
@@ -50,31 +56,22 @@ where
5056
let mut pending_keys = Vec::new();
5157
pending_keys.push(PendingKey {
5258
key,
59+
key_info,
5360
key_rel_name: std::ffi::OsString::new(),
5461
depth: 0,
5562
});
5663

5764
loop {
5865
let PendingKey {
5966
key,
67+
key_info,
6068
key_rel_name,
6169
depth,
6270
} = match pending_keys.pop() {
6371
Some(pending_key) => pending_key,
6472
None => break,
6573
};
6674

67-
let key_info = match key.info() {
68-
Ok(key_info) => key_info,
69-
Err(error) => {
70-
log::error! {
71-
"failed to obtain information for key {:?}: {}",
72-
winreg::path::join(&args.key, &key_rel_name), error,
73-
};
74-
continue
75-
}
76-
};
77-
7875
for subkey_name in key_info.subkeys() {
7976
let subkey_name = match subkey_name {
8077
Ok(subkey_name) => subkey_name,
@@ -88,30 +85,54 @@ where
8885
};
8986
let subkey_rel_name = winreg::path::join(&key_rel_name, &subkey_name);
9087

91-
if depth + 1 < args.max_depth {
92-
match key.open(&subkey_name) {
93-
Ok(subkey) => {
94-
pending_keys.push(PendingKey {
95-
key: subkey,
96-
key_rel_name: subkey_rel_name.clone(),
97-
depth: depth + 1,
98-
});
99-
}
100-
Err(error) => {
101-
log::error! {
102-
"failed to open subkey '{:?}': {}",
103-
winreg::path::join(&key_rel_name, &subkey_name), error,
104-
};
105-
}
88+
let subkey = match key.open(&subkey_name) {
89+
Ok(subkey) => subkey,
90+
Err(error) => {
91+
log::error! {
92+
"failed to open subkey '{:?}': {error}",
93+
subkey_rel_name,
94+
};
95+
continue
10696
}
107-
}
97+
};
98+
let subkey_info = match subkey.info() {
99+
Ok(subkey_info) => subkey_info,
100+
Err(error) => {
101+
log::error! {
102+
"failed to obtain information for subkey {:?}: {error}",
103+
subkey_rel_name,
104+
};
105+
continue
106+
}
107+
};
108+
109+
let modified = match subkey_info.modified() {
110+
Ok(modified) => Some(modified),
111+
Err(error) => {
112+
log::error! {
113+
"failed to obtain modification time for subkey {:?}: {error}",
114+
subkey_rel_name,
115+
};
116+
None
117+
}
118+
};
108119

109120
session.reply(Item {
110121
root: args.root,
111122
// TODO(@panhania): Add support for case-correcting the key.
112123
key: args.key.clone(),
113-
subkey: subkey_rel_name,
124+
subkey: subkey_rel_name.clone(),
125+
modified,
114126
})?;
127+
128+
if depth + 1 < args.max_depth {
129+
pending_keys.push(PendingKey {
130+
key: subkey,
131+
key_info: subkey_info,
132+
key_rel_name: subkey_rel_name,
133+
depth: depth + 1,
134+
});
135+
}
115136
}
116137
}
117138

@@ -160,6 +181,10 @@ impl crate::response::Item for Item {
160181
proto.set_key(self.key.to_string_lossy().into_owned());
161182
proto.set_subkey(self.subkey.to_string_lossy().into_owned());
162183

184+
if let Some(modified) = self.modified {
185+
proto.set_modification_time(modified.into());
186+
}
187+
163188
proto
164189
}
165190
}

crates/winreg/src/windows.rs

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

158160
impl 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)]
747837
mod 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
}

proto/rrg/action/list_winreg_keys.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ syntax = "proto3";
77
package rrg.action.list_winreg_keys;
88

99
import "rrg/winreg.proto";
10+
import "google/protobuf/timestamp.proto";
1011

1112
message Args {
1213
// Root predefined key of the key to list subkeys of.
@@ -31,4 +32,7 @@ message Result {
3132

3233
// Listed subkey relative to `root` and `key`.
3334
string subkey = 3;
35+
36+
// Last modification time of the listed subkey.
37+
google.protobuf.Timestamp modification_time = 4;
3438
}

0 commit comments

Comments
 (0)