Skip to content

Conversation

@Techatrix
Copy link
Contributor

The std.fs.path.resolveWindows function does not appear to properly handle UNC paths that have mixed path separators.

Example:

const std = @import("std");
const print = std.debug.print;
const ally = std.heap.page_allocator;
const resolve = std.fs.path.resolveWindows;
pub fn main() !void {
    // these are valid UNC paths when I tested them in my shell
    print("{s}\n", .{try resolve(ally, &.{ "//server/share", "..", "relative" })});
    print("{s}\n", .{try resolve(ally, &.{ "\\\\server\\share", "..", "relative" })});
    print("{s}\n", .{try resolve(ally, &.{ "//server\\share", "..", "relative" })});
    print("{s}\n", .{try resolve(ally, &.{ "\\\\server/share", "..", "relative" })});

    // these aren't valid UNC paths when I tested them in my shell
    print("{s}\n", .{try resolve(ally, &.{ "/\\server\\share", "..", "relative" })});
    print("{s}\n", .{try resolve(ally, &.{ "\\/server/share", "..", "relative" })});
}

This would previously output the following:

\\server\share\relative
\\server\share\relative
server\relative
server\relative

server\relative
server\relative

With this patch it will output the following:

\\server\share\relative
\\server\share\relative
\\server\share\relative
\\server\share\relative

server\relative
server\relative

Copy link
Member

@squeek502 squeek502 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix!

With regards to the \/ and /\ starting patterns, I think it'd be better to treat them as UNC paths, too. It does seem that not everything accepts them as UNC paths (e.g. putting them into the explorer location bar opened a web browser window for me), but for both /\server\share/../relative and \/server/share/../relative:

  • RtlDetermineDosPathNameType_U returns RtlPathTypeUncAbsolute
  • RtlGetFullPathName_U returns \\server\share\relative
  • RtlDosPathNameToNtPathName_U returns \??\UNC\server\share\relative
test code

Added to std/os/windows/test.zig

fn RtlGetFullPathName_U(path: [:0]const u16) !windows.PathSpace {
    var path_space: windows.PathSpace = undefined;
    const path_byte_len = windows.ntdll.RtlGetFullPathName_U(
        path.ptr,
        path_space.data.len * 2,
        &path_space.data,
        null,
    );
    if (path_byte_len == 0) {
        // TODO: This may not be the right error
        return error.BadPathName;
    } else if (path_byte_len / 2 > path_space.data.len) {
        return error.NameTooLong;
    }
    path_space.len = path_byte_len / 2;

    return path_space;
}

const RTL_PATH_TYPE = enum(c_int) {
    Unknown,
    UncAbsolute,
    DriveAbsolute,
    DriveRelative,
    Rooted,
    Relative,
    LocalDevice,
    RootLocalDevice,
};

pub extern "ntdll" fn RtlDetermineDosPathNameType_U(
    Path: [*:0]const u16,
) callconv(.winapi) RTL_PATH_TYPE;

test "curiosity" {
    const path = std.unicode.wtf8ToWtf16LeStringLiteral("\\/server\\share/../relative");
    const full_path = try RtlGetFullPathName_U(path);
    const path_type = RtlDetermineDosPathNameType_U(path);
    const nt_path = try RtlDosPathNameToNtPathName_U(path);
    std.debug.print("full path: {f}\n", .{std.unicode.fmtUtf16Le(full_path.span())});
    std.debug.print("path type: {}\n", .{path_type});
    std.debug.print("  nt path: {f}\n", .{std.unicode.fmtUtf16Le(nt_path.span())});
}

outputs:

full path: \\server\share\relative
path type: .UncAbsolute
  nt path: \??\UNC\server\share\relative

Additionally, at least the tree command recognizes the \/ variant (the /\ variant is mistaken for a command line flag so it's ineligible):

>tree "\/server\share\temp/stage4"
Folder PATH listing for volume share
Volume serial number is XXXX-XXXX
\\SERVER\SHARE\TEMP\STAGE4
└───bin

(note that this is a real command acting on a real path that I then redacted)

This will also bring path.resolveWindows in line with std.os.windows.getUnprefixedPathType and its callsites which accepts \/ and /\ as valid UNC starts.

Copy link
Member

@squeek502 squeek502 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, I'm happy to merge this as-is and address os.windows.getUnprefixedPathType/fs.path.windowsParsePath discrepancies in a follow-up PR.

EDIT: Made an issue: #25702

@alexrp alexrp merged commit bd1e960 into ziglang:master Oct 26, 2025
7 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants