Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OpenOptions to windows-registry #3461

Merged
merged 5 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions crates/libs/registry/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
```
24 changes: 24 additions & 0 deletions crates/libs/registry/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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;
Expand Down
42 changes: 11 additions & 31 deletions crates/libs/registry/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: AsRef<str>>(&self, path: T) -> Result<Self> {
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<T: AsRef<str>>(&self, path: T) -> Result<Self> {
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.
Expand Down Expand Up @@ -284,8 +266,6 @@ impl Key {

impl Drop for Key {
fn drop(&mut self) {
unsafe {
RegCloseKey(self.0);
}
unsafe { RegCloseKey(self.0) };
}
}
6 changes: 6 additions & 0 deletions crates/libs/registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
107 changes: 107 additions & 0 deletions crates/libs/registry/src/open_options.rs
Original file line number Diff line number Diff line change
@@ -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>,
riverar marked this conversation as resolved.
Show resolved Hide resolved
}

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<T: AsRef<str>>(&self, path: T) -> Result<Key> {
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))
}
}
53 changes: 53 additions & 0 deletions crates/libs/registry/src/transaction.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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(())
}
}
riverar marked this conversation as resolved.
Show resolved Hide resolved

/// 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 key handle.
kennykerr marked this conversation as resolved.
Show resolved Hide resolved
pub fn as_raw(&self) -> *mut core::ffi::c_void {
self.0
}
}

impl Drop for Transaction {
fn drop(&mut self) {
unsafe { CloseHandle(self.0) };
}
}
riverar marked this conversation as resolved.
Show resolved Hide resolved
40 changes: 40 additions & 0 deletions crates/tests/misc/registry/tests/transaction.rs
Original file line number Diff line number Diff line change
@@ -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)
);
}
6 changes: 6 additions & 0 deletions crates/tools/bindings/src/registry.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
--flat --sys --no-core --no-comment

--filter
CloseHandle
CommitTransaction
CreateTransaction
ERROR_INVALID_DATA
ERROR_NO_MORE_ITEMS
GetProcessHeap
Expand All @@ -13,6 +16,7 @@
HKEY_CURRENT_USER
HKEY_LOCAL_MACHINE
HKEY_USERS
INVALID_HANDLE_VALUE
KEY_READ
KEY_WRITE
REG_BINARY
Expand All @@ -24,11 +28,13 @@
REG_SZ
RegCloseKey
RegCreateKeyExW
RegCreateKeyTransactedW
RegDeleteTreeW
RegDeleteValueW
RegEnumKeyExW
RegEnumValueW
RegOpenKeyExW
RegOpenKeyTransactedW
RegQueryInfoKeyW
RegQueryValueExW
RegSetValueExW