Module: Winreg
- Defined in:
- lib/winreg.rb,
lib/winreg/version.rb,
ext/winreg/winreg.c
Overview
winreg — typed Windows registry access for Ruby: exact wire formats, least-privilege opens, WOW64 views as a first-class option, and change notification that cooperates with a fiber scheduler.
Winreg.open('HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion') do |k|
k.string("ProductName") # => "Windows 10 ..."
k.read("CurrentMajorVersionNumber") # => [:dword, 10]
end
Winreg.create('HKCU\Software\Vendor\App') do |k|
k.write_multi_string("Plugins", %w[alpha beta]) # correct double-NUL wire format
k.watch(filter: :values) { |event| break if event == :deleted }
end
Defined Under Namespace
Classes: AccessDenied, Closed, Error, Key, KeyDeleted, MalformedValue, NotFound, OSError, TypeMismatch, Watch
Constant Summary collapse
- TYPES =
Registry type tags (verified, winnt.h) — Symbol <-> tag map.
{ none: 0, sz: 1, expand_sz: 2, binary: 3, dword: 4, dword_be: 5, link: 6, multi_sz: 7, qword: 11 }.freeze
- KEY_QUERY_VALUE =
---- Win32 flag values (verified, winnt.h) ------------------------------
0x0001- KEY_NOTIFY =
0x0010- KEY_READ =
STANDARD_RIGHTS_READ | QUERY_VALUE | ENUMERATE_SUB_KEYS | NOTIFY
0x20019- KEY_WRITE =
STANDARD_RIGHTS_WRITE | SET_VALUE | CREATE_SUB_KEY
0x20006- KEY_WOW64_64KEY =
0x0100- KEY_WOW64_32KEY =
0x0200- REG_NOTIFY_CHANGE_NAME =
0x1- REG_NOTIFY_CHANGE_ATTRIBUTES =
0x2- REG_NOTIFY_CHANGE_LAST_SET =
0x4- REG_NOTIFY_CHANGE_SECURITY =
0x8- VERSION =
"0.1.0"- HKEY_CLASSES_ROOT =
---- predefined root handle values (verified, winreg.h) ---- Public-but-undocumented plumbing for the Ruby layer's path parser. HKEY_PERFORMANCE_DATA (0x80000004) is deliberately not defined.
ULONG2NUM(0x80000000UL)
- HKEY_CURRENT_USER =
ULONG2NUM(0x80000001UL)
- HKEY_LOCAL_MACHINE =
ULONG2NUM(0x80000002UL)
- HKEY_USERS =
ULONG2NUM(0x80000003UL)
- HKEY_CURRENT_CONFIG =
ULONG2NUM(0x80000005UL)
Class Method Summary collapse
-
._expand(str) ⇒ Object
Winreg._expand — ExpandEnvironmentStringsW =====================================================================.
-
.create(path, access: :read_write, view: :default) ⇒ Object
Open-or-create a key (creates all missing intermediate keys).
-
.expand_string(str) ⇒ Object
Expand %VAR% references using ExpandEnvironmentStringsW (NOT a Ruby gsub over ENV — semantics differ: unknown vars stay literal, exactly as the OS defines).
-
.ms_for(timeout) ⇒ Object
Seconds (nil = infinite) -> milliseconds for the C layer (-1 = INFINITE).
-
.open(path, access: :read, view: :default) ⇒ Object
Open an existing key.
-
.run_blocking ⇒ Object
Run a blocking native call cooperatively.
Class Method Details
._expand(str) ⇒ Object
=====================================================================
Winreg._expand — ExpandEnvironmentStringsW
825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 |
# File 'ext/winreg/winreg.c', line 825
static VALUE
wr_expand(VALUE mod, VALUE str)
{
VALUE wsrc = to_wide_val(str); /* Ruby-owned: raise-safe across allocs */
VALUE wout, out;
DWORD n, r;
/* size probe: returned size is in WCHARs and includes the NUL */
n = ExpandEnvironmentStringsW((const WCHAR *)RSTRING_PTR(wsrc), NULL, 0);
if (n == 0) raise_gle("ExpandEnvironmentStrings", GetLastError());
wout = rb_str_new(NULL, (long)n * (long)sizeof(WCHAR));
for (;;) {
/* re-take both pointers after any Ruby allocation */
r = ExpandEnvironmentStringsW((const WCHAR *)RSTRING_PTR(wsrc),
(WCHAR *)RSTRING_PTR(wout), n);
if (r == 0) raise_gle("ExpandEnvironmentStrings", GetLastError());
if (r <= n) { n = r; break; }
n = r; /* grew between calls (env mutated): resize and retry */
rb_str_resize(wout, (long)n * (long)sizeof(WCHAR));
}
/* n includes the terminating NUL; exclude it from the result */
out = wide_to_utf8((const WCHAR *)RSTRING_PTR(wout), (int)(n - 1));
RB_GC_GUARD(wsrc);
RB_GC_GUARD(wout);
return out;
}
|
.create(path, access: :read_write, view: :default) ⇒ Object
Open-or-create a key (creates all missing intermediate keys). Default access is :read_write — you create keys to write to them. The security descriptor is inherited from the parent (NULL SECURITY_ATTRIBUTES).
136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/winreg.rb', line 136 def create(path, access: :read_write, view: :default) root, sub = parse_path(path) sam = access_mask(access) | view_mask(view) k = sub.nil? ? Key.send(:_open, root, nil, sam) : Key.send(:_create, root, sub, sam) k.send(:_init_meta, root, sub, view) return k unless block_given? begin yield k ensure k.close end end |
.expand_string(str) ⇒ Object
Expand %VAR% references using ExpandEnvironmentStringsW (NOT a Ruby gsub over ENV — semantics differ: unknown vars stay literal, exactly as the OS defines). Input/output UTF-8 String.
153 154 155 |
# File 'lib/winreg.rb', line 153 def (str) (str) end |
.ms_for(timeout) ⇒ Object
Seconds (nil = infinite) -> milliseconds for the C layer (-1 = INFINITE). Never collapse a tiny-but-positive wait into a non-blocking poll.
105 106 107 108 109 110 111 112 113 |
# File 'lib/winreg.rb', line 105 def ms_for(timeout) return -1 if timeout.nil? t = Float(timeout) raise ArgumentError, "timeout must be non-negative, got #{timeout.inspect}" if t.negative? ms = (t * 1000).round ms.zero? && t.positive? ? 1 : ms end |
.open(path, access: :read, view: :default) ⇒ Object
Open an existing key. access: :read (default) | :read_write | Integer raw samDesired (must not contain the WOW64 view bits — the view comes from view:). view: :default | :v64 | :v32. Yields the Key (ensure-closed) if a block is given, returning the block's value.
119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/winreg.rb', line 119 def open(path, access: :read, view: :default) root, sub = parse_path(path) sam = access_mask(access) | view_mask(view) k = Key.send(:_open, root, sub, sam) k.send(:_init_meta, root, sub, view) return k unless block_given? begin yield k ensure k.close end end |
.run_blocking ⇒ Object
Run a blocking native call cooperatively. Under a Fiber scheduler (e.g. winloop) the call is offloaded to a worker Thread so the calling fiber parks (Thread#value routes through the scheduler) and the event loop keeps serving other fibers; with no scheduler it runs inline (the C call already releases the GVL). On fiber unwind the worker is killed+joined so it can't leak. Registry watch registrations are REG_NOTIFY_THREAD_AGNOSTIC, so the ephemeral workers are sound (a registration does not die with its thread).
Caveat: a fiber unwound (e.g. Timeout) after the worker observed :changed but before value delivery loses that one delivery — harmless here, because :changed is stateless ("go look") and the registration stays armed.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/winreg.rb', line 85 def run_blocking sched = Fiber.scheduler return yield unless sched worker = Thread.new do Thread.current.report_on_exception = false yield end begin worker.value ensure if worker.alive? worker.kill worker.join end end end |