diff --git a/crates/libs/registry/readme.md b/crates/libs/registry/readme.md index bf114653ff..7f4ffd00d0 100644 --- a/crates/libs/registry/readme.md +++ b/crates/libs/registry/readme.md @@ -30,3 +30,27 @@ fn main() -> Result<()> { Ok(()) } ``` + +Use the `options()` method for even more control: + +```rust,no_run +use windows_registry::*; + +fn main() -> Result<()> { + let tx = Transaction::new()?; + + let key = CURRENT_USER + .options() + .read(true) + .write(true) + .create(true) + .transaction(&tx) + .open("software\\windows-rs")?; + + key.set_u32("name", 123)?; + + tx.commit()?; + + Ok(()) +} +``` \ No newline at end of file diff --git a/crates/libs/registry/src/bindings.rs b/crates/libs/registry/src/bindings.rs index 187030da59..c6a4cae367 100644 --- a/crates/libs/registry/src/bindings.rs +++ b/crates/libs/registry/src/bindings.rs @@ -6,16 +6,21 @@ clippy::all )] +windows_link::link!("kernel32.dll" "system" fn CloseHandle(hobject : HANDLE) -> BOOL); +windows_link::link!("ktmw32.dll" "system" fn CommitTransaction(transactionhandle : HANDLE) -> BOOL); +windows_link::link!("ktmw32.dll" "system" fn CreateTransaction(lptransactionattributes : *mut SECURITY_ATTRIBUTES, uow : *mut GUID, createoptions : u32, isolationlevel : u32, isolationflags : u32, timeout : u32, description : PCWSTR) -> HANDLE); windows_link::link!("kernel32.dll" "system" fn GetProcessHeap() -> HANDLE); windows_link::link!("kernel32.dll" "system" fn HeapAlloc(hheap : HANDLE, dwflags : HEAP_FLAGS, dwbytes : usize) -> *mut core::ffi::c_void); windows_link::link!("kernel32.dll" "system" fn HeapFree(hheap : HANDLE, dwflags : HEAP_FLAGS, lpmem : *const core::ffi::c_void) -> BOOL); windows_link::link!("advapi32.dll" "system" fn RegCloseKey(hkey : HKEY) -> WIN32_ERROR); windows_link::link!("advapi32.dll" "system" fn RegCreateKeyExW(hkey : HKEY, lpsubkey : PCWSTR, reserved : u32, lpclass : PCWSTR, dwoptions : REG_OPEN_CREATE_OPTIONS, samdesired : REG_SAM_FLAGS, lpsecurityattributes : *const SECURITY_ATTRIBUTES, phkresult : *mut HKEY, lpdwdisposition : *mut REG_CREATE_KEY_DISPOSITION) -> WIN32_ERROR); +windows_link::link!("advapi32.dll" "system" fn RegCreateKeyTransactedW(hkey : HKEY, lpsubkey : PCWSTR, reserved : u32, lpclass : PCWSTR, dwoptions : REG_OPEN_CREATE_OPTIONS, samdesired : REG_SAM_FLAGS, lpsecurityattributes : *const SECURITY_ATTRIBUTES, phkresult : *mut HKEY, lpdwdisposition : *mut REG_CREATE_KEY_DISPOSITION, htransaction : HANDLE, pextendedparemeter : *const core::ffi::c_void) -> WIN32_ERROR); windows_link::link!("advapi32.dll" "system" fn RegDeleteTreeW(hkey : HKEY, lpsubkey : PCWSTR) -> WIN32_ERROR); windows_link::link!("advapi32.dll" "system" fn RegDeleteValueW(hkey : HKEY, lpvaluename : PCWSTR) -> WIN32_ERROR); windows_link::link!("advapi32.dll" "system" fn RegEnumKeyExW(hkey : HKEY, dwindex : u32, lpname : PWSTR, lpcchname : *mut u32, lpreserved : *const u32, lpclass : PWSTR, lpcchclass : *mut u32, lpftlastwritetime : *mut FILETIME) -> WIN32_ERROR); windows_link::link!("advapi32.dll" "system" fn RegEnumValueW(hkey : HKEY, dwindex : u32, lpvaluename : PWSTR, lpcchvaluename : *mut u32, lpreserved : *const u32, lptype : *mut u32, lpdata : *mut u8, lpcbdata : *mut u32) -> WIN32_ERROR); windows_link::link!("advapi32.dll" "system" fn RegOpenKeyExW(hkey : HKEY, lpsubkey : PCWSTR, uloptions : u32, samdesired : REG_SAM_FLAGS, phkresult : *mut HKEY) -> WIN32_ERROR); +windows_link::link!("advapi32.dll" "system" fn RegOpenKeyTransactedW(hkey : HKEY, lpsubkey : PCWSTR, uloptions : u32, samdesired : REG_SAM_FLAGS, phkresult : *mut HKEY, htransaction : HANDLE, pextendedparemeter : *const core::ffi::c_void) -> WIN32_ERROR); windows_link::link!("advapi32.dll" "system" fn RegQueryInfoKeyW(hkey : HKEY, lpclass : PWSTR, lpcchclass : *mut u32, lpreserved : *const u32, lpcsubkeys : *mut u32, lpcbmaxsubkeylen : *mut u32, lpcbmaxclasslen : *mut u32, lpcvalues : *mut u32, lpcbmaxvaluenamelen : *mut u32, lpcbmaxvaluelen : *mut u32, lpcbsecuritydescriptor : *mut u32, lpftlastwritetime : *mut FILETIME) -> WIN32_ERROR); windows_link::link!("advapi32.dll" "system" fn RegQueryValueExW(hkey : HKEY, lpvaluename : PCWSTR, lpreserved : *const u32, lptype : *mut REG_VALUE_TYPE, lpdata : *mut u8, lpcbdata : *mut u32) -> WIN32_ERROR); windows_link::link!("advapi32.dll" "system" fn RegSetValueExW(hkey : HKEY, lpvaluename : PCWSTR, reserved : u32, dwtype : REG_VALUE_TYPE, lpdata : *const u8, cbdata : u32) -> WIN32_ERROR); @@ -28,6 +33,24 @@ pub struct FILETIME { pub dwLowDateTime: u32, pub dwHighDateTime: u32, } +#[repr(C)] +#[derive(Clone, Copy)] +pub struct GUID { + pub data1: u32, + pub data2: u16, + pub data3: u16, + pub data4: [u8; 8], +} +impl GUID { + pub const fn from_u128(uuid: u128) -> Self { + Self { + data1: (uuid >> 96) as u32, + data2: (uuid >> 80 & 0xffff) as u16, + data3: (uuid >> 64 & 0xffff) as u16, + data4: (uuid as u64).to_be_bytes(), + } + } +} pub type HANDLE = *mut core::ffi::c_void; pub type HEAP_FLAGS = u32; pub type HKEY = *mut core::ffi::c_void; @@ -36,6 +59,7 @@ pub const HKEY_CURRENT_CONFIG: HKEY = -2147483643i32 as _; pub const HKEY_CURRENT_USER: HKEY = -2147483647i32 as _; pub const HKEY_LOCAL_MACHINE: HKEY = -2147483646i32 as _; pub const HKEY_USERS: HKEY = -2147483645i32 as _; +pub const INVALID_HANDLE_VALUE: HANDLE = -1i32 as _; pub const KEY_READ: REG_SAM_FLAGS = 131097u32; pub const KEY_WRITE: REG_SAM_FLAGS = 131078u32; pub type PCWSTR = *const u16; diff --git a/crates/libs/registry/src/key.rs b/crates/libs/registry/src/key.rs index 9af1282a84..0b0e4d11d9 100644 --- a/crates/libs/registry/src/key.rs +++ b/crates/libs/registry/src/key.rs @@ -5,42 +5,24 @@ use super::*; #[derive(Debug)] pub struct Key(pub(crate) HKEY); -impl Default for Key { - fn default() -> Self { - Self(null_mut()) - } -} - impl Key { /// Creates a registry key. If the key already exists, the function opens it. pub fn create>(&self, path: T) -> Result { - let mut handle = null_mut(); - - let result = unsafe { - RegCreateKeyExW( - self.0, - pcwstr(path).as_ptr(), - 0, - null(), - REG_OPTION_NON_VOLATILE, - KEY_READ | KEY_WRITE, - null(), - &mut handle, - null_mut(), - ) - }; - - win32_error(result).map(|_| Self(handle)) + self.options() + .read(true) + .write(true) + .create(true) + .open(path) } /// Opens a registry key. pub fn open>(&self, path: T) -> Result { - let mut handle = null_mut(); - - let result = - unsafe { RegOpenKeyExW(self.0, pcwstr(path).as_ptr(), 0, KEY_READ, &mut handle) }; + self.options().read(true).open(path) + } - win32_error(result).map(|_| Self(handle)) + /// Creates an `OpenOptions` object for the registry key. + pub fn options(&self) -> OpenOptions<'_> { + OpenOptions::new(self) } /// Constructs a registry key from an existing handle. @@ -284,8 +266,6 @@ impl Key { impl Drop for Key { fn drop(&mut self) { - unsafe { - RegCloseKey(self.0); - } + unsafe { RegCloseKey(self.0) }; } } diff --git a/crates/libs/registry/src/lib.rs b/crates/libs/registry/src/lib.rs index cf35f9d628..9b01aa4a8b 100644 --- a/crates/libs/registry/src/lib.rs +++ b/crates/libs/registry/src/lib.rs @@ -12,9 +12,15 @@ use core::ptr::{null, null_mut}; mod bindings; use bindings::*; +mod open_options; +pub use open_options::OpenOptions; + mod key; pub use key::Key; +mod transaction; +pub use transaction::Transaction; + mod value; pub use value::Value; diff --git a/crates/libs/registry/src/open_options.rs b/crates/libs/registry/src/open_options.rs new file mode 100644 index 0000000000..0b40550ef7 --- /dev/null +++ b/crates/libs/registry/src/open_options.rs @@ -0,0 +1,107 @@ +use super::*; + +/// Options and flags used to configure how a registry key is opened. +pub struct OpenOptions<'a> { + parent: &'a Key, + read: bool, + write: bool, + create: bool, + transaction: Option<&'a Transaction>, +} + +impl<'a> OpenOptions<'a> { + pub(crate) fn new(parent: &'a Key) -> Self { + Self { + parent, + read: false, + write: false, + create: false, + transaction: None, + } + } + + /// Sets the option for read access. + pub fn read(&mut self, read: bool) -> &mut Self { + self.read = read; + self + } + + /// Sets the option for write access. + pub fn write(&mut self, write: bool) -> &mut Self { + self.write = write; + self + } + + /// Sets the option to create a new registry key, or open it if it already exists. + pub fn create(&mut self, create: bool) -> &mut Self { + self.create = create; + self + } + + /// Associate the registry key with a transaction. + pub fn transaction(&mut self, transaction: &'a Transaction) -> &mut Self { + self.transaction = Some(transaction); + self + } + + /// Opens a registry key with the options provided by `self`. + pub fn open>(&self, path: T) -> Result { + let mut flags = 0; + + if self.read { + flags |= KEY_READ; + } + + if self.write { + flags |= KEY_WRITE; + } + + let mut handle = null_mut(); + + let result = unsafe { + if let Some(transaction) = self.transaction { + if self.create { + RegCreateKeyTransactedW( + self.parent.0, + pcwstr(path).as_ptr(), + 0, + null(), + REG_OPTION_NON_VOLATILE, + flags, + null(), + &mut handle, + null_mut(), + transaction.0, + null(), + ) + } else { + RegOpenKeyTransactedW( + self.parent.0, + pcwstr(path).as_ptr(), + 0, + flags, + &mut handle, + transaction.0, + null(), + ) + } + } else if self.create { + RegCreateKeyExW( + self.parent.0, + pcwstr(path).as_ptr(), + 0, + null(), + REG_OPTION_NON_VOLATILE, + flags, + null(), + &mut handle, + null_mut(), + ) + } else { + RegOpenKeyExW(self.parent.0, pcwstr(path).as_ptr(), 0, flags, &mut handle) + } + }; + + win32_error(result).map(|_| Key(handle)) + } +} diff --git a/crates/libs/registry/src/transaction.rs b/crates/libs/registry/src/transaction.rs new file mode 100644 index 0000000000..4f999c143c --- /dev/null +++ b/crates/libs/registry/src/transaction.rs @@ -0,0 +1,53 @@ +use super::*; + +/// A transaction object. +#[repr(transparent)] +#[derive(Debug)] +pub struct Transaction(pub(crate) HANDLE); + +impl Transaction { + /// Creates a new transaction. + pub fn new() -> Result { + let handle = unsafe { CreateTransaction(null_mut(), null_mut(), 0, 0, 0, 0, null()) }; + + if handle == INVALID_HANDLE_VALUE { + Err(Error::from_win32()) + } else { + Ok(Self(handle)) + } + } + + /// Commits the transaction. + /// + /// The transaction rolls back if it is dropped before `commit` is called. + pub fn commit(self) -> Result<()> { + let result = unsafe { CommitTransaction(self.0) }; + + if result == 0 { + Err(Error::from_win32()) + } else { + Ok(()) + } + } + + /// Constructs a transaction object from an existing handle. + /// + /// # Safety + /// + /// This function takes ownership of the handle. + /// The handle must be owned by the caller and safe to free with `CloseHandle`. + pub unsafe fn from_raw(handle: *mut core::ffi::c_void) -> Self { + Self(handle) + } + + /// Returns the underlying transaction handle. + pub fn as_raw(&self) -> *mut core::ffi::c_void { + self.0 + } +} + +impl Drop for Transaction { + fn drop(&mut self) { + unsafe { CloseHandle(self.0) }; + } +} diff --git a/crates/tests/misc/registry/tests/transaction.rs b/crates/tests/misc/registry/tests/transaction.rs new file mode 100644 index 0000000000..c8510d6776 --- /dev/null +++ b/crates/tests/misc/registry/tests/transaction.rs @@ -0,0 +1,40 @@ +use windows_registry::*; +use windows_result::*; +use windows_sys::Win32::Foundation::*; + +#[test] +fn create_with_transaction() { + let test_key = "software\\windows-rs\\tests\\transaction"; + _ = CURRENT_USER.remove_tree(test_key); + let key = CURRENT_USER.create(test_key).unwrap(); + + let tx = Transaction::new().unwrap(); + + let tx_key = CURRENT_USER + .options() + .transaction(&tx) + .read(true) + .write(true) + .open(test_key) + .unwrap(); + + tx_key.set_u64("u64", 123u64).unwrap(); + assert_eq!(tx_key.get_u64("u64").unwrap(), 123u64); + + // The transaction is not yet committed so this non-transaction read will fail. + assert_eq!( + key.get_u64("u64").unwrap_err().code(), + HRESULT::from_win32(ERROR_FILE_NOT_FOUND) + ); + + tx.commit().unwrap(); + + // Now that the transaction is committed the non-transaction read will succeed. + assert_eq!(key.get_u64("u64").unwrap(), 123u64); + + // The transaction is no longer active so this key cannot be used. + assert_eq!( + tx_key.get_u64("u64").unwrap_err().code(), + HRESULT::from_win32(ERROR_TRANSACTION_NOT_ACTIVE) + ); +} diff --git a/crates/tools/bindings/src/registry.txt b/crates/tools/bindings/src/registry.txt index 9668bef7ae..64c90f4602 100644 --- a/crates/tools/bindings/src/registry.txt +++ b/crates/tools/bindings/src/registry.txt @@ -3,6 +3,9 @@ --flat --sys --no-core --no-comment --filter + CloseHandle + CommitTransaction + CreateTransaction ERROR_INVALID_DATA ERROR_NO_MORE_ITEMS GetProcessHeap @@ -13,6 +16,7 @@ HKEY_CURRENT_USER HKEY_LOCAL_MACHINE HKEY_USERS + INVALID_HANDLE_VALUE KEY_READ KEY_WRITE REG_BINARY @@ -24,11 +28,13 @@ REG_SZ RegCloseKey RegCreateKeyExW + RegCreateKeyTransactedW RegDeleteTreeW RegDeleteValueW RegEnumKeyExW RegEnumValueW RegOpenKeyExW + RegOpenKeyTransactedW RegQueryInfoKeyW RegQueryValueExW RegSetValueExW