Zig 0.17.0-dev (Split by item)

Shortcut keys
?
Show these tips
S
Focus search field
Esc
Clear focus & close tips
U
Jump to source code
↑
Move up in search results
↓
Move down in search results
↵
Go to active search result
This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig standard library. Private Struct

Registry

Private Struct
std.zig.WindowsSdk.Registry
const Registry = struct

Not a general purpose implementation of an ntdll-based Registry API. Only intended to support the particular calls necessary for the purposes of finding the SDK/MSVC installation paths.

The advapi32 APIs internally open and cache \Registry\Machine and the current user key when HKEY_LOCAL_MACHINE (HKLM) and HKEY_CURRENT_USER (HKCU) are passed, and also rewrite key path values to go through WOW6432Node when appropriate.

For example, when opening Software\Foo relative to HKEY_LOCAL_MACHINE with the WOW64_32KEY option set, that will end up as a call to NtLoadKeyEx with the path rewritten to Software\WOW6432Node\Foo relative to a cached \REGISTRY\Machine key.

For our purposes, we really only care about 4 potential variations of the Software key:

  • Relative to HKLM, no redirection through WOW6432Node
  • Relative to HKLM, redirected through WOW6432Node
  • Relative to HKCU, no redirection through WOW6432Node
  • Relative to HKCU, redirected through WOW6432Node (e.g. all the values we care about are within one of those Software keys)

So, we cache those variants of the Software keys instead of HKLM/HKCU and treat them as the "root" keys that the user can specify, which in turn (1) allows all provided key paths to be agnostic to WOW6432Node, (2) avoids the need for internal path rewriting, and (3) works correctly on 32-bit targets without any special support.

For example, instead of an advapi32 call with Software\Foo relative to HKEY_LOCAL_MACHINE which may get rewritten to Software\WOW6432Node\Foo, the equivalent is now a call to open Foo relative to some Software key variant.

Fields

cache: Cache = .{}

Structs

Types

Functions

pub fn deinit(self: Registry) void
fn tryOpenSoftwareKeyWithPrecedence(registry: *Registry, variants: []const Software, sub_path: []const u16) error
fn openSoftwareKey(registry: *Registry, software: Software, sub_path: []const u16) error

File

zig/WindowsSdk.zig:184
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
const Registry = struct {
    cache: Cache = .{},

    pub fn deinit(self: Registry) void {
        if (!is_32_bit) {
            if (self.cache.hklm_software_foreign) |key| windows.CloseHandle(key);
            if (self.cache.hkcu_software_foreign) |key| windows.CloseHandle(key);
        }
        if (self.cache.hklm_software_native) |key| windows.CloseHandle(key);
        if (self.cache.hkcu_software_native) |key| windows.CloseHandle(key);
        if (self.cache.hkcu) |key| windows.CloseHandle(key);
    }

    const Cache = struct {
        hklm_software_foreign: if (is_32_bit) void else ?windows.HANDLE = if (is_32_bit) {} else null,
        hkcu_software_foreign: if (is_32_bit) void else ?windows.HANDLE = if (is_32_bit) {} else null,
        hklm_software_native: ?windows.HANDLE = null,
        hkcu_software_native: ?windows.HANDLE = null,
        hkcu: ?windows.HANDLE = null,

        fn getSoftware(cache: *const Cache, variant: Software) ?windows.HANDLE {
            if (!is_32_bit and variant.wow64 == .wow64_32) {
                return switch (variant.root) {
                    .local_machine => cache.hklm_software_foreign,
                    .current_user => cache.hkcu_software_foreign,
                };
            }
            return switch (variant.root) {
                .local_machine => cache.hklm_software_native,
                .current_user => cache.hkcu_software_native,
            };
        }
    };

    // This does not correspond to HKEY_LOCAL_MACHINE/HKEY_CURRENT_USER
    // since WOW64 redirection is applicable to e.g. `HKLM\Software` instead of
    // HKLM/HKCU directly. Since we are only ever interested in the
    // `Software` key, it makes more sense to treat `Software` as the "root"
    // since that allows us to work entirely with relative paths that are agnostic
    // to WOW6432Node redirection.
    const Software = struct {
        root: Root,
        wow64: Wow64 = .native,

        const Root = enum {
            local_machine,
            current_user,
        };

        fn getOrOpenKey(self: Software, registry: *Registry) !Key {
            if (registry.cache.getSoftware(self)) |handle| {
                return .{ .handle = handle };
            }

            const is_foreign = !is_32_bit and self.wow64 == .wow64_32;
            switch (self.root) {
                .local_machine => {
                    const path = if (is_foreign) L("\\Registry\\Machine\\Software\\WOW6432Node") else L("\\Registry\\Machine\\Software");
                    var key: Key = undefined;
                    const attr: windows.OBJECT.ATTRIBUTES = .{
                        .RootDirectory = null,
                        .Attributes = .{},
                        .ObjectName = @constCast(&windows.UNICODE_STRING.init(path)),
                        .SecurityDescriptor = null,
                        .SecurityQualityOfService = null,
                    };
                    const status = windows.ntdll.NtOpenKeyEx(
                        &key.handle,
                        .{ .MAXIMUM_ALLOWED = true },
                        &attr,
                        .{},
                    );
                    switch (status) {
                        .SUCCESS => {},
                        else => return error.KeyNotFound,
                    }
                    if (is_foreign) {
                        registry.cache.hklm_software_foreign = key.handle;
                    } else {
                        registry.cache.hklm_software_native = key.handle;
                    }
                    return key;
                },
                .current_user => {
                    const cu_handle: windows.HANDLE = registry.cache.hkcu orelse hkcu: {
                        var cu_handle: windows.HANDLE = undefined;
                        const status = windows.ntdll.RtlOpenCurrentUser(
                            .{ .MAXIMUM_ALLOWED = true },
                            &cu_handle,
                        );
                        switch (status) {
                            .SUCCESS => {},
                            else => return error.KeyNotFound,
                        }
                        registry.cache.hkcu = cu_handle;
                        break :hkcu cu_handle;
                    };
                    const cu_key: Registry.Key = .{ .handle = cu_handle };
                    const path = if (is_foreign) L("Software\\WOW6432Node") else L("Software");
                    const key = try cu_key.open(path);
                    if (is_foreign) {
                        registry.cache.hkcu_software_foreign = key.handle;
                    } else {
                        registry.cache.hkcu_software_native = key.handle;
                    }
                    return key;
                },
            }
        }
    };

    /// For 32-bit programs on a 64-bit operating system, the WOW64
    /// version of ntdll.dll handles the WOW6432Node redirection before
    /// calling into ntdll.dll proper, so no special handling is needed
    /// and this setting is irrelevant in that case.
    const Wow64 = enum {
        /// Use 64-bit registry on 64-bit targets and 32-bit registry on
        /// 32-bit targets.
        native,
        /// Go through WOW6432Node on both 32-bit and 64-bit targets,
        /// if relevant (ignored for 32-bit programs executed on a 32-bit
        /// OS).
        wow64_32,
    };

    fn tryOpenSoftwareKeyWithPrecedence(registry: *Registry, variants: []const Software, sub_path: []const u16) error{KeyNotFound}!Key {
        for (variants) |variant| {
            return registry.openSoftwareKey(variant, sub_path) catch continue;
        }
        return error.KeyNotFound;
    }

    fn openSoftwareKey(registry: *Registry, software: Software, sub_path: []const u16) error{KeyNotFound}!Key {
        const software_key = try software.getOrOpenKey(registry);
        return software_key.open(sub_path);
    }

    const Key = struct {
        handle: windows.HANDLE,

        fn close(self: Key) void {
            windows.CloseHandle(self.handle);
        }

        fn open(self: Key, sub_path: []const u16) error{KeyNotFound}!Key {
            var key: Key = undefined;
            const attr: windows.OBJECT.ATTRIBUTES = .{
                .RootDirectory = self.handle,
                .Attributes = .{},
                .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path)),
                .SecurityDescriptor = null,
                .SecurityQualityOfService = null,
            };
            const status = windows.ntdll.NtOpenKeyEx(
                &key.handle,
                .{ .SPECIFIC = .{
                    .KEY = .{
                        .QUERY_VALUE = true,
                        .ENUMERATE_SUB_KEYS = true,
                    },
                } },
                &attr,
                .{},
            );
            switch (status) {
                .SUCCESS => return key,
                else => return error.KeyNotFound,
            }
        }

        const ValueEntry = union(enum) {
            default: void,
            name: []const u16,
        };

        fn getString(
            key: Key,
            gpa: Allocator,
            entry: ValueEntry,
            comptime result_encoding: enum { wtf16, wtf8 },
        ) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }!(switch (result_encoding) {
            .wtf8 => []u8,
            .wtf16 => []u16,
        }) {
            const num_data_bytes = windows.MAX_PATH * 2;
            const stack_buf_len = @sizeOf(windows.KEY.VALUE.PARTIAL_INFORMATION) + num_data_bytes;
            var stack_info_buf: [stack_buf_len]u8 align(@alignOf(windows.KEY.VALUE.PARTIAL_INFORMATION)) = undefined;
            var result_len: windows.ULONG = undefined;
            const rc = windows.ntdll.NtQueryValueKey(
                key.handle,
                switch (entry) {
                    .name => |name| @constCast(&windows.UNICODE_STRING.init(name)),
                    .default => @constCast(&windows.UNICODE_STRING.empty),
                },
                .Partial,
                &stack_info_buf,
                stack_buf_len,
                &result_len,
            );
            var heap_info_buf: ?[]align(@alignOf(windows.KEY.VALUE.PARTIAL_INFORMATION)) u8 = null;
            defer if (heap_info_buf) |buf| gpa.free(buf);

            const info: *const windows.KEY.VALUE.PARTIAL_INFORMATION = switch (rc) {
                .SUCCESS => @ptrCast(&stack_info_buf),
                .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => heap_info: {
                    heap_info_buf = try gpa.alignedAlloc(u8, .of(windows.KEY.VALUE.PARTIAL_INFORMATION), result_len);
                    const heap_rc = windows.ntdll.NtQueryValueKey(
                        key.handle,
                        switch (entry) {
                            .name => |name| @constCast(&windows.UNICODE_STRING.init(name)),
                            .default => @constCast(&windows.UNICODE_STRING.empty),
                        },
                        .Partial,
                        heap_info_buf.?.ptr,
                        @intCast(heap_info_buf.?.len),
                        &result_len,
                    );
                    switch (heap_rc) {
                        .SUCCESS => break :heap_info @ptrCast(heap_info_buf.?.ptr),
                        .OBJECT_NAME_NOT_FOUND => return error.ValueNameNotFound,
                        else => return error.StringNotFound,
                    }
                },
                .OBJECT_NAME_NOT_FOUND => return error.ValueNameNotFound,
                else => return error.StringNotFound,
            };

            switch (info.Type) {
                .SZ => {},
                else => return error.NotAString,
            }

            const data_wtf16_with_nul = @as([*]const u16, @ptrCast(@alignCast(info.data())))[0..@divExact(info.DataLength, 2)];
            const data_wtf16 = std.mem.trimEnd(u16, data_wtf16_with_nul, L("\x00"));
            switch (result_encoding) {
                .wtf16 => return gpa.dupe(u16, data_wtf16),
                .wtf8 => return std.unicode.wtf16LeToWtf8Alloc(gpa, data_wtf16),
            }
        }

        fn getDword(key: Key, entry: ValueEntry) error{ ValueNameNotFound, NotADword, DwordNotFound }!windows.DWORD {
            const num_data_bytes = @sizeOf(windows.DWORD);
            const buf_len = @sizeOf(windows.KEY.VALUE.PARTIAL_INFORMATION) + num_data_bytes;
            var info_buf: [buf_len]u8 align(@alignOf(windows.KEY.VALUE.PARTIAL_INFORMATION)) = undefined;
            var result_len: windows.ULONG = undefined;
            const rc = windows.ntdll.NtQueryValueKey(
                key.handle,
                switch (entry) {
                    .name => |name| @constCast(&windows.UNICODE_STRING.init(name)),
                    .default => @constCast(&windows.UNICODE_STRING.empty),
                },
                .Partial,
                &info_buf,
                buf_len,
                &result_len,
            );
            switch (rc) {
                .SUCCESS => {},
                .OBJECT_NAME_NOT_FOUND => return error.ValueNameNotFound,
                else => return error.DwordNotFound,
            }

            const info: *const windows.KEY.VALUE.PARTIAL_INFORMATION = @ptrCast(&info_buf);

            switch (info.Type) {
                .DWORD => {},
                else => return error.NotADword,
            }

            return std.mem.bytesToValue(windows.DWORD, info.data());
        }
    };
}