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.

netLookupFallible

Threaded.netLookupFallible
fn netLookupFallible(
    t: *Threaded,
    host_name: HostName,
    resolved: *Io.Queue(HostName.LookupResult),
    options: HostName.LookupOptions,
) (net.HostName.LookupError || Io.QueueClosedError)!void

File

lib/std/Io/Threaded.zig:13675

Code

fn netLookupFallible(
    t: *Threaded,
    host_name: HostName,
    resolved: *Io.Queue(HostName.LookupResult),
    options: HostName.LookupOptions,
) (net.HostName.LookupError || Io.QueueClosedError)!void {
    if (!have_networking) return error.NetworkDown;

    const t_io = t.io();
    const name = host_name.bytes;
    assert(name.len <= HostName.max_len);

    // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics.
    // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it.
    const is_glibc = builtin.link_libc and builtin.target.isGnuLibC();

    if (is_glibc) {
        // TODO use getaddrinfo_a / gai_cancel
    }

    // On Linux, we have to go through glibc because of the Name Service Switch feature.
    const non_glibc_linux = native_os == .linux and !is_glibc;
    if (non_glibc_linux or is_windows) {
        if (IpAddress.parseIp6(name, options.port)) |addr| {
            if (options.family == .ip4) return error.UnknownHostName;
            if (copyCanon(options.canonical_name_buffer, name)) |canon| {
                try resolved.putAll(t_io, &.{
                    .{ .address = addr },
                    .{ .canonical_name = canon },
                });
            } else {
                try resolved.putOne(t_io, .{ .address = addr });
            }
            return;
        } else |_| {}

        if (IpAddress.parseIp4(name, options.port)) |addr| {
            if (options.family == .ip6) return error.UnknownHostName;
            if (copyCanon(options.canonical_name_buffer, name)) |canon| {
                try resolved.putAll(t_io, &.{
                    .{ .address = addr },
                    .{ .canonical_name = canon },
                });
            } else {
                try resolved.putOne(t_io, .{ .address = addr });
            }
            return;
        } else |_| {}

        if (t.lookupHosts(host_name, resolved, options)) return else |err| switch (err) {
            error.UnknownHostName => {},
            else => |e| return e,
        }

        // RFC 6761 Section 6.3.3
        // Name resolution APIs and libraries SHOULD recognize
        // localhost names as special and SHOULD always return the IP
        // loopback address for address queries and negative responses
        // for all other query types.

        // Check for equal to "localhost(.)" or ends in ".localhost(.)"
        const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
        if (std.mem.endsWith(u8, name, localhost) and
            (name.len == localhost.len or name[name.len - localhost.len - 1] == '.'))
        {
            var results_buffer: [3]HostName.LookupResult = undefined;
            var results_index: usize = 0;
            if (options.family != .ip4) {
                results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } };
                results_index += 1;
            }
            if (options.family != .ip6) {
                results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } };
                results_index += 1;
            }
            if (options.canonical_name_buffer) |buf| {
                const canon_name = "localhost";
                const canon_name_dest = buf[0..canon_name.len];
                canon_name_dest.* = canon_name.*;
                results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } };
                results_index += 1;
            }
            try resolved.putAll(t_io, results_buffer[0..results_index]);
            return;
        }

        if (native_os == .linux) return t.lookupDnsSearch(host_name, resolved, options);

        comptime assert(is_windows);
        var DnsQueryEx = t.dl.DnsQueryEx.load(.acquire);
        //var DnsCancelQuery = t.dl.DnsCancelQuery.load(.acquire);
        var DnsFree = t.dl.DnsFree.load(.acquire);
        if (DnsQueryEx == null or
            //DnsCancelQuery == null or
            DnsFree == null)
        {
            const dnsapi_dll = t.dl.dnsapi_dll.load(.acquire) orelse dnsapi_dll: {
                try Thread.checkCancel();
                var dnsapi_dll: *anyopaque = undefined;
                switch (windows.ntdll.LdrLoadDll(null, null, &.init(
                    &.{ 'd', 'n', 's', 'a', 'p', 'i', '.', 'd', 'l', 'l' },
                ), &dnsapi_dll)) {
                    .SUCCESS => {},
                    .DLL_NOT_FOUND => return error.Unexpected,
                    else => |status| return windows.unexpectedStatus(status),
                }
                const handle = t.dl.dnsapi_dll.cmpxchgStrong(null, dnsapi_dll, .release, .monotonic) orelse
                    break :dnsapi_dll dnsapi_dll;
                switch (windows.ntdll.LdrUnloadDll(dnsapi_dll)) {
                    .SUCCESS => break :dnsapi_dll handle.?,
                    else => |status| return windows.unexpectedStatus(status),
                }
            };
            switch (windows.ntdll.LdrGetProcedureAddress(dnsapi_dll, &.init(
                &.{ 'D', 'n', 's', 'Q', 'u', 'e', 'r', 'y', 'E', 'x' },
            ), 0, @ptrCast(&DnsQueryEx))) {
                .SUCCESS => t.dl.DnsQueryEx.store(DnsQueryEx, .release),
                else => |status| return windows.unexpectedStatus(status),
            }
            //switch (windows.ntdll.LdrGetProcedureAddress(dnsapi_dll, &.init(
            //    &.{ 'D', 'n', 's', 'C', 'a', 'n', 'c', 'e', 'l', 'Q', 'u', 'e', 'r', 'y' },
            //), 0, @ptrCast(&DnsCancelQuery))) {
            //    .SUCCESS => t.dl.DnsCancelQuery.store(DnsCancelQuery, .release),
            //    else => |status| return windows.unexpectedStatus(status),
            //}
            switch (windows.ntdll.LdrGetProcedureAddress(dnsapi_dll, &.init(
                &.{ 'D', 'n', 's', 'F', 'r', 'e', 'e' },
            ), 0, @ptrCast(&DnsFree))) {
                .SUCCESS => t.dl.DnsFree.store(DnsFree, .release),
                else => |status| return windows.unexpectedStatus(status),
            }
        }
        try Thread.checkCancel();
        const current_thread = Thread.current;
        var lookup_dns: LookupDnsWindows = .{
            .threaded = t,
            .thread = if (current_thread) |thread| thread.handle else undefined,
            .resolved = resolved,
            .options = options,
            .results = .{
                .Version = 1,
                .QueryStatus = undefined,
                .QueryOptions = undefined,
                .pQueryRecords = undefined,
                .Reserved = undefined,
            },
            .done = false,
        };
        var host_name_w: [HostName.max_len:0]windows.WCHAR = undefined;
        host_name_w[
            std.unicode.wtf8ToWtf16Le(&host_name_w, name) catch |err| switch (err) {
                error.InvalidWtf8 => return error.UnknownHostName,
            }
        ] = 0;
        //var cancel_token: windows.DNS.QUERY.CANCEL = undefined;
        // Workaround various bugs by attempting a synchronous non-wire query first
        switch (DnsQueryEx.?(&.{
            .Version = 1,
            .QueryName = &host_name_w,
            .QueryType = if (options.family == .ip4) .A else .AAAA,
            .QueryOptions = .{
                .NO_WIRE_QUERY = true,
                .NO_HOSTS_FILE = true, // handled above
                .ADDRCONFIG = true,
                .DUAL_ADDR = options.family == null,
            },
        }, &lookup_dns.results, null)) {
            .SUCCESS => try lookup_dns.completedFallible(),
            // We must wait for the APC routine.
            .DNS_REQUEST_PENDING => unreachable, // `pQueryCompletionCallback` was `null`
            .DNS_ERROR_RECORD_DOES_NOT_EXIST => switch (DnsQueryEx.?(&.{
                .Version = 1,
                .QueryName = &host_name_w,
                .QueryType = if (options.family == .ip4) .A else .AAAA,
                .QueryOptions = .{
                    .NO_HOSTS_FILE = true, // handled above
                    .ADDRCONFIG = true,
                    .DUAL_ADDR = options.family == null,
                    .MULTICAST_WAIT = true,
                },
                .pQueryCompletionCallback = if (current_thread) |_| &LookupDnsWindows.completed else null,
            }, &lookup_dns.results,
                //&cancel_token,
                null)) {
                .SUCCESS => try lookup_dns.completedFallible(),
                // We must wait for the APC routine.
                .DNS_REQUEST_PENDING => {
                    assert(current_thread != null); // `pQueryCompletionCallback` was `null`
                    while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) {
                        // Once we get here we must not return from the function until the
                        // operation completes, thereby releasing references to `host_name_w`,
                        // `lookup_dns.results`, and `cancel_token`.
                        const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
                            error.Canceled => |e| {
                                //_ = DnsCancelQuery.?(&cancel_token);
                                while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) waitForApcOrAlert();
                                return e;
                            },
                        };
                        waitForApcOrAlert();
                        alertable_syscall.finish();
                    }
                },
                else => |status| lookup_dns.results.QueryStatus = status,
            },
            else => |status| lookup_dns.results.QueryStatus = status,
        }
        switch (lookup_dns.results.QueryStatus) {
            .SUCCESS => return,
            .DNS_REQUEST_PENDING => unreachable, // already handled
            .INVALID_NAME,
            .DNS_ERROR_RCODE_NAME_ERROR,
            .DNS_INFO_NO_RECORDS,
            .DNS_ERROR_INVALID_NAME_CHAR,
            .DNS_ERROR_RECORD_DOES_NOT_EXIST,
            => return error.UnknownHostName,
            else => |err| return windows.unexpectedError(err),
        }
    }

    if (native_os == .openbsd) {
        // TODO use getaddrinfo_async / asr_abort
    }

    if (native_os == .freebsd) {
        // TODO use dnsres_getaddrinfo
    }

    if (is_darwin) {
        // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution
    }

    if (builtin.link_libc) {
        // This operating system lacks a way to resolve asynchronously. We are
        // stuck with getaddrinfo.
        var name_buffer: [HostName.max_len:0]u8 = undefined;
        @memcpy(name_buffer[0..name.len], name);
        name_buffer[name.len] = 0;
        const name_c = name_buffer[0..name.len :0];

        var port_buffer: [8]u8 = undefined;
        const port_c = std.fmt.bufPrintSentinel(&port_buffer, "{d}", .{options.port}, 0) catch unreachable;

        const hints: posix.addrinfo = .{
            .flags = .{ .CANONNAME = options.canonical_name_buffer != null, .NUMERICSERV = true },
            .family = posix.AF.UNSPEC,
            .socktype = posix.SOCK.STREAM,
            .protocol = posix.IPPROTO.TCP,
            .canonname = null,
            .addr = null,
            .addrlen = 0,
            .next = null,
        };
        var res: ?*posix.addrinfo = null;
        const syscall: Syscall = try .start();
        while (true) {
            switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) {
                @as(posix.system.EAI, @fromBackingInt(@intCast(0))) => {
                    syscall.finish();
                    break;
                },
                .SYSTEM => switch (posix.errno(-1)) {
                    .INTR => {
                        try syscall.checkCancel();
                        continue;
                    },
                    else => |e| {
                        syscall.finish();
                        return posix.unexpectedErrno(e);
                    },
                },
                else => |e| {
                    syscall.finish();
                    switch (e) {
                        .ADDRFAMILY => return error.AddressFamilyUnsupported,
                        .AGAIN => return error.NameServerFailure,
                        .FAIL => return error.NameServerFailure,
                        .FAMILY => return error.AddressFamilyUnsupported,
                        .MEMORY => return error.SystemResources,
                        .NODATA => return error.UnknownHostName,
                        .NONAME => return error.UnknownHostName,
                        else => return error.Unexpected,
                    }
                },
            }
        }
        defer if (res) |some| posix.system.freeaddrinfo(some);

        var it = res;
        var canon_name: ?[*:0]const u8 = null;
        while (it) |info| : (it = info.next) {
            const addr = info.addr orelse continue;
            try resolved.putOne(t_io, .{ .address = addressFromPosix(@alignCast(@fieldParentPtr("any", addr))) });

            if (info.canonname) |n| {
                if (canon_name == null) {
                    canon_name = n;
                }
            }
        }
        if (canon_name) |n| {
            if (copyCanon(options.canonical_name_buffer, std.mem.sliceTo(n, 0))) |canon| {
                try resolved.putOne(t_io, .{ .canonical_name = canon });
            }
        }
        return;
    }

    return error.OptionUnsupported;
}