Zig 0.17.0-dev (Split by item)

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 library itself.

Registry

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:

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.

WindowsSdk.Registry
const Registry = struct

File

lib/std/zig/WindowsSdk.zig:184

Code

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());
        }
    };
}