`zig fetch` failed if target project contains symlink

Zig version: 0.16.0 (On Windows)

Expectation: Zig should be able to handle any type of file in the repository.

Bug Report

If a project contains symbolic links (symlinks), it cannot be successfully fetched by the zig fetch command.

Here is a minimal example: a project that includes a symlink pointing to the README file. When you attempt to fetch it with zig fetch, the following error occurs:

error: unable to hash 'README': Unexpected

This issue may prove fatal to compatibility with the C/C++ ecosystem.

:thinking:

lib/std.Io.zig

pub const UnexpectedError = error{
    /// The Operating System returned an undocumented error code.
    ///
    /// This error is in theory not possible, but it would be better
    /// to handle this error than to invoke undefined behavior.
    ///
    /// When this error code is observed, it usually means the Zig Standard
    /// Library needs a small patch to add the error code to the error set for
    /// the respective function.
    Unexpected,
};
1 Like
error.Unexpected NTSTATUS=0x103 (PENDING)
E:/_dev/zig/build/stage3/lib/zig/std/Io/Threaded.zig:1459:40: 0x7ff60f29b409 in unexpectedNtstatus (zig_zcu.obj)
        return windows.unexpectedStatus(status);
                                       ^
E:/_dev/zig/build/stage3/lib/zig/std/Io/Threaded.zig:8202:59: 0x7ff60f2ff021 in dirReadLinkWindows (zig_zcu.obj)
        else => |status| return syscall.unexpectedNtstatus(status),
                                                          ^
E:/_dev/zig/build/stage3/lib/zig/std/Io/Threaded.zig:8079:46: 0x7ff60f2fc9a4 in dirReadLink (zig_zcu.obj)
        .windows => return dirReadLinkWindows(dir, sub_path, buffer),
                                             ^
E:/_dev/zig/build/stage3/lib/zig/std/Io/Dir.zig:1303:33: 0x7ff60f713e40 in readLink (zig_zcu.obj)
    return io.vtable.dirReadLink(io.userdata, dir, sub_path, buffer);
                                ^
E:\_dev\zig\src\Package\Fetch.zig:1922:54: 0x7ff60f729a60 in hashFileFallible (zig_zcu.obj)
            const link_name = buf[0..try dir.readLink(io, hashed_file.fs_path, &buf)];
                                                     ^
E:\_dev\zig\src\Package\Fetch.zig:1890:43: 0x7ff60f729699 in workerHashFile (zig_zcu.obj)
    hashed_file.failure = hashFileFallible(io, dir, hashed_file);
                                          ^
E:/_dev/zig/build/stage3/lib/zig/std/Io.zig:1265:17: 0x7ff60f72955b in start (zig_zcu.obj)
                _ = @as(Cancelable!void, @call(.auto, function, args_casted.*)) catch {};
                ^
E:/_dev/zig/build/stage3/lib/zig/std/Io/Threaded.zig:553:22: 0x7ff60f312427 in start (zig_zcu.obj)
            task.func(task.contextPointer());
                     ^
E:/_dev/zig/build/stage3/lib/zig/std/Io/Threaded.zig:1798:29: 0x7ff60f31167f in worker (zig_zcu.obj)
            runnable.startFn(runnable, &thread, t);
                            ^
E:/_dev/zig/build/stage3/lib/zig/std/Thread.zig:422:13: 0x7ff60f3111b1 in callFn__anon_48619 (zig_zcu.obj)
            @call(.auto, f, args);
            ^
E:/_dev/zig/build/stage3/lib/zig/std/Thread.zig:536:30: 0x7ff60f31108a in entryFn (zig_zcu.obj)
                return callFn(f, self.fn_args);
                             ^
???:?:?: 0x7ffaabd5e956 in ??? (KERNEL32.DLL)
???:?:?: 0x7ffaad267c1b in ??? (ntdll.dll)
error: unable to hash 'README': Unexpected

lib/zig/std/Io/Threaded.zig

// Line: 8179
    syscall = try .start();
    while (true) switch (windows.ntdll.NtFsControlFile(
        result_handle,
        null, // event
        null, // APC routine
        null, // APC context
        &io_status_block,
        .GET_REPARSE_POINT,
        null, // input buffer
        0, // input buffer length
        &reparse_buf,
        reparse_buf.len,
    )) {
        .SUCCESS => {
            syscall.finish();
            break;
        },
        .CANCELLED => {
            try syscall.checkCancel();
            continue;
        },
        .NOT_A_REPARSE_POINT => return syscall.fail(error.NotLink),
        else => |status| return syscall.unexpectedNtstatus(status),
    };
// Line: 8204

and NtFsControlFile function (ntifs.h) - Windows drivers | Microsoft Learn

This is a known issue and the current proposed fix is to introduce a .symlink_behavior = (.keep|.omit|.copy) option to build.zig.zon that would let you specify how to handle symlinks when unpacking dependencies.

Edit: Although the linked issues only concern fetching what is already declared in build.zig.zon, not the zig fetch command itself. But I would expect that the proposed fix also implies fixing the zig fetch command.

2 Likes

I think it should be indicated in the error message of command zig fetch.

But I see that #22350 is marked as urgent and has not been closed yet. Does it support it in the current Zig version?

No, it hasn’t been implemented yet. The “urgent” milestone means that the issue is in the backlog and should be worked on when given time, but that it’s not blocking a tagged release and less prioritized than issues tagged as 0.17, 0.18, etc.


However, I see now that the status code you got was PENDING which normally shouldn’t occur, so this might be a bug in the package fetching implementation on Windows. error.Unexpected should never be surfaced to users when using I/O APIs correctly, so you might want to consider filing a bug report stating that the expected behavior is to return an error that clearly communicates that symlinks are unsupported.

Yeah, in pre-0.16.0 versions it’d give you error.AccessDenied. Which isn’t actually much more descriptive on Windows…

It’s also failing during dirReadLink, so it’s not failing to create the symlink in this case.