| author | |
| committer | |
| log | 76aa1fffb7a06f0be0d803cb3379f3102c0b2590 |
| tree | abcf7206b7fe0c4619dc98ff3c87273e9adcc754 |
| parent | 1ab008d89dbc18cd79abed88f306ad9bd0397622 |
| parent | 28df1d09dc8c5e79db3ddc4b3da701d151f09e0c |
| signature |
x86_64: hotfix for crash during in-memory coercion of large type6 files changed, 455 insertions(+), 97 deletions(-)
lib/std/child_process.zig+3-6| ... | ... | @@ -957,15 +957,12 @@ fn windowsCreateProcessPathExt( |
| 957 | 957 | // NtQueryDirectoryFile calls. |
| 958 | 958 | |
| 959 | 959 | var dir = dir: { |
| 960 | if (fs.path.isAbsoluteWindowsWTF16(dir_buf.items[0..dir_path_len])) { | |
| 961 | const prefixed_path = try windows.wToPrefixedFileW(dir_buf.items[0..dir_path_len]); | |
| 962 | break :dir fs.cwd().openDirW(prefixed_path.span().ptr, .{}, true) catch return error.FileNotFound; | |
| 963 | } | |
| 964 | 960 | // needs to be null-terminated |
| 965 | 961 | try dir_buf.append(allocator, 0); |
| 966 | defer dir_buf.shrinkRetainingCapacity(dir_buf.items[0..dir_path_len].len); | |
| 962 | defer dir_buf.shrinkRetainingCapacity(dir_path_len); | |
| 967 | 963 | const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; |
| 968 | break :dir std.fs.cwd().openDirW(dir_path_z.ptr, .{}, true) catch return error.FileNotFound; | |
| 964 | const prefixed_path = try windows.wToPrefixedFileW(dir_path_z); | |
| 965 | break :dir fs.cwd().openDirW(prefixed_path.span().ptr, .{}, true) catch return error.FileNotFound; | |
| 969 | 966 | }; |
| 970 | 967 | defer dir.close(); |
| 971 | 968 |
lib/std/os/windows.zig+242-65| ... | ... | @@ -1157,9 +1157,9 @@ pub fn GetFinalPathNameByHandle( |
| 1157 | 1157 | |
| 1158 | 1158 | // This surprising path is a filesystem path to the mount manager on Windows. |
| 1159 | 1159 | // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points |
| 1160 | const mgmt_path = "\\MountPointManager"; | |
| 1161 | const mgmt_path_u16 = sliceToPrefixedFileW(mgmt_path) catch unreachable; | |
| 1162 | const mgmt_handle = OpenFile(mgmt_path_u16.span(), .{ | |
| 1160 | // This is the NT namespaced version of \\.\MountPointManager | |
| 1161 | const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager"); | |
| 1162 | const mgmt_handle = OpenFile(mgmt_path_u16, .{ | |
| 1163 | 1163 | .access_mask = SYNCHRONIZE, |
| 1164 | 1164 | .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE, |
| 1165 | 1165 | .creation = FILE_OPEN, |
| ... | ... | @@ -1997,43 +1997,248 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { |
| 1997 | 1997 | return sliceToPrefixedFileW(mem.sliceTo(s, 0)); |
| 1998 | 1998 | } |
| 1999 | 1999 | |
| 2000 | /// Converts the path `s` to WTF16, null-terminated. If the path is absolute, | |
| 2001 | /// it will get NT-style prefix `\??\` prepended automatically. | |
| 2002 | pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { | |
| 2003 | // TODO https://github.com/ziglang/zig/issues/2765 | |
| 2004 | var path_space: PathSpace = undefined; | |
| 2005 | const prefix = "\\??\\"; | |
| 2006 | const prefix_index: usize = if (mem.startsWith(u8, s, prefix)) prefix.len else 0; | |
| 2007 | for (s[prefix_index..]) |byte| { | |
| 2008 | switch (byte) { | |
| 2009 | '*', '?', '"', '<', '>', '|' => return error.BadPathName, | |
| 2010 | else => {}, | |
| 2011 | } | |
| 2012 | } | |
| 2013 | const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' }; | |
| 2014 | const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: { | |
| 2015 | path_space.data[0..prefix_u16.len].* = prefix_u16; | |
| 2016 | break :blk prefix_u16.len; | |
| 2017 | }; | |
| 2018 | path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s); | |
| 2019 | if (path_space.len > path_space.data.len) return error.NameTooLong; | |
| 2020 | path_space.len = start_index + (normalizePath(u16, path_space.data[start_index..path_space.len]) catch |err| switch (err) { | |
| 2021 | error.TooManyParentDirs => { | |
| 2022 | if (!std.fs.path.isAbsolute(s)) { | |
| 2023 | var temp_path: PathSpace = undefined; | |
| 2024 | temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, s); | |
| 2025 | std.debug.assert(temp_path.len == path_space.len); | |
| 2026 | temp_path.data[path_space.len] = 0; | |
| 2027 | path_space.len = prefix_u16.len + try getFullPathNameW(&temp_path.data, path_space.data[prefix_u16.len..]); | |
| 2028 | path_space.data[0..prefix_u16.len].* = prefix_u16; | |
| 2029 | std.debug.assert(path_space.data[path_space.len] == 0); | |
| 2000 | /// Same as `wToPrefixedFileW` but accepts a UTF-8 encoded path. | |
| 2001 | pub fn sliceToPrefixedFileW(path: []const u8) !PathSpace { | |
| 2002 | var temp_path: PathSpace = undefined; | |
| 2003 | temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, path); | |
| 2004 | temp_path.data[temp_path.len] = 0; | |
| 2005 | return wToPrefixedFileW(temp_path.span()); | |
| 2006 | } | |
| 2007 | ||
| 2008 | /// Converts the `path` to WTF16, null-terminated. If the path contains any | |
| 2009 | /// namespace prefix, or is anything but a relative path (rooted, drive relative, | |
| 2010 | /// etc) the result will have the NT-style prefix `\??\`. | |
| 2011 | /// | |
| 2012 | /// Similar to RtlDosPathNameToNtPathName_U with a few differences: | |
| 2013 | /// - Does not allocate on the heap. | |
| 2014 | /// - Relative paths are kept as relative unless they contain too many .. | |
| 2015 | /// components, in which case they are treated as drive-relative and resolved | |
| 2016 | /// against the CWD. | |
| 2017 | /// - Special case device names like COM1, NUL, etc are not handled specially (TODO) | |
| 2018 | /// - . and space are not stripped from the end of relative paths (potential TODO) | |
| 2019 | pub fn wToPrefixedFileW(path: [:0]const u16) !PathSpace { | |
| 2020 | const nt_prefix = [_]u16{ '\\', '?', '?', '\\' }; | |
| 2021 | switch (getNamespacePrefix(u16, path)) { | |
| 2022 | // TODO: Figure out a way to design an API that can avoid the copy for .nt, | |
| 2023 | // since it is always returned fully unmodified. | |
| 2024 | .nt, .verbatim => { | |
| 2025 | var path_space: PathSpace = undefined; | |
| 2026 | path_space.data[0..nt_prefix.len].* = nt_prefix; | |
| 2027 | const len_after_prefix = path.len - nt_prefix.len; | |
| 2028 | @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]); | |
| 2029 | path_space.len = path.len; | |
| 2030 | path_space.data[path_space.len] = 0; | |
| 2031 | return path_space; | |
| 2032 | }, | |
| 2033 | .local_device, .fake_verbatim => { | |
| 2034 | var path_space: PathSpace = undefined; | |
| 2035 | const path_byte_len = ntdll.RtlGetFullPathName_U( | |
| 2036 | path.ptr, | |
| 2037 | path_space.data.len * 2, | |
| 2038 | &path_space.data, | |
| 2039 | null, | |
| 2040 | ); | |
| 2041 | if (path_byte_len == 0) { | |
| 2042 | // TODO: This may not be the right error | |
| 2043 | return error.BadPathName; | |
| 2044 | } else if (path_byte_len / 2 > path_space.data.len) { | |
| 2045 | return error.NameTooLong; | |
| 2046 | } | |
| 2047 | path_space.len = path_byte_len / 2; | |
| 2048 | // Both prefixes will be normalized but retained, so all | |
| 2049 | // we need to do now is replace them with the NT prefix | |
| 2050 | path_space.data[0..nt_prefix.len].* = nt_prefix; | |
| 2051 | return path_space; | |
| 2052 | }, | |
| 2053 | .none => { | |
| 2054 | const path_type = getUnprefixedPathType(u16, path); | |
| 2055 | var path_space: PathSpace = undefined; | |
| 2056 | relative: { | |
| 2057 | if (path_type == .relative) { | |
| 2058 | // TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc. | |
| 2059 | // See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html | |
| 2060 | ||
| 2061 | // TODO: Potentially strip all trailing . and space characters from the | |
| 2062 | // end of the path. This is something that both RtlDosPathNameToNtPathName_U | |
| 2063 | // and RtlGetFullPathName_U do. Technically, trailing . and spaces | |
| 2064 | // are allowed, but such paths may not interact well with Windows (i.e. | |
| 2065 | // files with these paths can't be deleted from explorer.exe, etc). | |
| 2066 | // This could be something that normalizePath may want to do. | |
| 2067 | ||
| 2068 | @memcpy(path_space.data[0..path.len], path); | |
| 2069 | // Try to normalize, but if we get too many parent directories, | |
| 2070 | // then this is effectively a 'drive relative' path, so we need to | |
| 2071 | // start over and use RtlGetFullPathName_U instead. | |
| 2072 | path_space.len = normalizePath(u16, path_space.data[0..path.len]) catch |err| switch (err) { | |
| 2073 | error.TooManyParentDirs => break :relative, | |
| 2074 | }; | |
| 2075 | path_space.data[path_space.len] = 0; | |
| 2076 | return path_space; | |
| 2077 | } | |
| 2078 | } | |
| 2079 | // We now know we are going to return an absolute NT path, so | |
| 2080 | // we can unconditionally prefix it with the NT prefix. | |
| 2081 | path_space.data[0..nt_prefix.len].* = nt_prefix; | |
| 2082 | if (path_type == .root_local_device) { | |
| 2083 | // `\\.` and `\\?` always get converted to `\??\` exactly, so | |
| 2084 | // we can just stop here | |
| 2085 | path_space.len = nt_prefix.len; | |
| 2086 | path_space.data[path_space.len] = 0; | |
| 2030 | 2087 | return path_space; |
| 2031 | 2088 | } |
| 2032 | return error.BadPathName; | |
| 2089 | const path_buf_offset = switch (path_type) { | |
| 2090 | // UNC paths will always start with `\\`. However, we want to | |
| 2091 | // end up with something like `\??\UNC\server\share`, so to get | |
| 2092 | // RtlGetFullPathName to write into the spot we want the `server` | |
| 2093 | // part to end up, we need to provide an offset such that | |
| 2094 | // the `\\` part gets written where the `C\` of `UNC\` will be | |
| 2095 | // in the final NT path. | |
| 2096 | .unc_absolute => nt_prefix.len + 2, | |
| 2097 | else => nt_prefix.len, | |
| 2098 | }; | |
| 2099 | const buf_len = @intCast(u32, path_space.data.len - path_buf_offset); | |
| 2100 | const path_byte_len = ntdll.RtlGetFullPathName_U( | |
| 2101 | path.ptr, | |
| 2102 | buf_len * 2, | |
| 2103 | path_space.data[path_buf_offset..].ptr, | |
| 2104 | null, | |
| 2105 | ); | |
| 2106 | if (path_byte_len == 0) { | |
| 2107 | // TODO: This may not be the right error | |
| 2108 | return error.BadPathName; | |
| 2109 | } else if (path_byte_len / 2 > buf_len) { | |
| 2110 | return error.NameTooLong; | |
| 2111 | } | |
| 2112 | path_space.len = path_buf_offset + (path_byte_len / 2); | |
| 2113 | if (path_type == .unc_absolute) { | |
| 2114 | // Now add in the UNC, the `C` should overwrite the first `\` of the | |
| 2115 | // FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>` | |
| 2116 | std.debug.assert(path_space.data[path_buf_offset] == '\\'); | |
| 2117 | std.debug.assert(path_space.data[path_buf_offset + 1] == '\\'); | |
| 2118 | const unc = [_]u16{ 'U', 'N', 'C' }; | |
| 2119 | path_space.data[nt_prefix.len..][0..unc.len].* = unc; | |
| 2120 | } | |
| 2121 | return path_space; | |
| 2033 | 2122 | }, |
| 2034 | }); | |
| 2035 | path_space.data[path_space.len] = 0; | |
| 2036 | return path_space; | |
| 2123 | } | |
| 2124 | } | |
| 2125 | ||
| 2126 | pub const NamespacePrefix = enum { | |
| 2127 | none, | |
| 2128 | /// `\\.\` (path separators can be `\` or `/`) | |
| 2129 | local_device, | |
| 2130 | /// `\\?\` | |
| 2131 | /// When converted to an NT path, everything past the prefix is left | |
| 2132 | /// untouched and `\\?\` is replaced by `\??\`. | |
| 2133 | verbatim, | |
| 2134 | /// `\\?\` without all path separators being `\`. | |
| 2135 | /// This seems to be recognized as a prefix, but the 'verbatim' aspect | |
| 2136 | /// is not respected (i.e. if `//?/C:/foo` is converted to an NT path, | |
| 2137 | /// it will become `\??\C:\foo` [it will be canonicalized and the //?/ won't | |
| 2138 | /// be treated as part of the final path]) | |
| 2139 | fake_verbatim, | |
| 2140 | /// `\??\` | |
| 2141 | nt, | |
| 2142 | }; | |
| 2143 | ||
| 2144 | pub fn getNamespacePrefix(comptime T: type, path: []const T) NamespacePrefix { | |
| 2145 | if (path.len < 4) return .none; | |
| 2146 | var all_backslash = switch (path[0]) { | |
| 2147 | '\\' => true, | |
| 2148 | '/' => false, | |
| 2149 | else => return .none, | |
| 2150 | }; | |
| 2151 | all_backslash = all_backslash and switch (path[3]) { | |
| 2152 | '\\' => true, | |
| 2153 | '/' => false, | |
| 2154 | else => return .none, | |
| 2155 | }; | |
| 2156 | switch (path[1]) { | |
| 2157 | '?' => if (path[2] == '?' and all_backslash) return .nt else return .none, | |
| 2158 | '\\' => {}, | |
| 2159 | '/' => all_backslash = false, | |
| 2160 | else => return .none, | |
| 2161 | } | |
| 2162 | return switch (path[2]) { | |
| 2163 | '?' => if (all_backslash) .verbatim else .fake_verbatim, | |
| 2164 | '.' => .local_device, | |
| 2165 | else => .none, | |
| 2166 | }; | |
| 2167 | } | |
| 2168 | ||
| 2169 | test getNamespacePrefix { | |
| 2170 | try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "")); | |
| 2171 | try std.testing.expectEqual(NamespacePrefix.nt, getNamespacePrefix(u8, "\\??\\")); | |
| 2172 | try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "/??/")); | |
| 2173 | try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "/??\\")); | |
| 2174 | try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "\\?\\\\")); | |
| 2175 | try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "\\\\.\\")); | |
| 2176 | try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "\\\\./")); | |
| 2177 | try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "/\\./")); | |
| 2178 | try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "//./")); | |
| 2179 | try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "/.//")); | |
| 2180 | try std.testing.expectEqual(NamespacePrefix.verbatim, getNamespacePrefix(u8, "\\\\?\\")); | |
| 2181 | try std.testing.expectEqual(NamespacePrefix.fake_verbatim, getNamespacePrefix(u8, "\\/?\\")); | |
| 2182 | try std.testing.expectEqual(NamespacePrefix.fake_verbatim, getNamespacePrefix(u8, "\\/?/")); | |
| 2183 | try std.testing.expectEqual(NamespacePrefix.fake_verbatim, getNamespacePrefix(u8, "//?/")); | |
| 2184 | } | |
| 2185 | ||
| 2186 | pub const UnprefixedPathType = enum { | |
| 2187 | unc_absolute, | |
| 2188 | drive_absolute, | |
| 2189 | drive_relative, | |
| 2190 | rooted, | |
| 2191 | relative, | |
| 2192 | root_local_device, | |
| 2193 | }; | |
| 2194 | ||
| 2195 | inline fn isSepW(c: u16) bool { | |
| 2196 | return c == '/' or c == '\\'; | |
| 2197 | } | |
| 2198 | ||
| 2199 | /// Get the path type of a path that is known to not have any namespace prefixes | |
| 2200 | /// (`\\?\`, `\\.\`, `\??\`). | |
| 2201 | pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathType { | |
| 2202 | if (path.len < 1) return .relative; | |
| 2203 | ||
| 2204 | if (std.debug.runtime_safety) { | |
| 2205 | std.debug.assert(getNamespacePrefix(T, path) == .none); | |
| 2206 | } | |
| 2207 | ||
| 2208 | if (isSepW(path[0])) { | |
| 2209 | // \x | |
| 2210 | if (path.len < 2 or !isSepW(path[1])) return .rooted; | |
| 2211 | // exactly \\. or \\? with nothing trailing | |
| 2212 | if (path.len == 3 and (path[2] == '.' or path[2] == '?')) return .root_local_device; | |
| 2213 | // \\x | |
| 2214 | return .unc_absolute; | |
| 2215 | } else { | |
| 2216 | // x | |
| 2217 | if (path.len < 2 or path[1] != ':') return .relative; | |
| 2218 | // x:\ | |
| 2219 | if (path.len > 2 and isSepW(path[2])) return .drive_absolute; | |
| 2220 | // x: | |
| 2221 | return .drive_relative; | |
| 2222 | } | |
| 2223 | } | |
| 2224 | ||
| 2225 | test getUnprefixedPathType { | |
| 2226 | try std.testing.expectEqual(UnprefixedPathType.relative, getUnprefixedPathType(u8, "")); | |
| 2227 | try std.testing.expectEqual(UnprefixedPathType.relative, getUnprefixedPathType(u8, "x")); | |
| 2228 | try std.testing.expectEqual(UnprefixedPathType.relative, getUnprefixedPathType(u8, "x\\")); | |
| 2229 | try std.testing.expectEqual(UnprefixedPathType.root_local_device, getUnprefixedPathType(u8, "//.")); | |
| 2230 | try std.testing.expectEqual(UnprefixedPathType.root_local_device, getUnprefixedPathType(u8, "/\\?")); | |
| 2231 | try std.testing.expectEqual(UnprefixedPathType.root_local_device, getUnprefixedPathType(u8, "\\\\?")); | |
| 2232 | try std.testing.expectEqual(UnprefixedPathType.unc_absolute, getUnprefixedPathType(u8, "\\\\x")); | |
| 2233 | try std.testing.expectEqual(UnprefixedPathType.unc_absolute, getUnprefixedPathType(u8, "//x")); | |
| 2234 | try std.testing.expectEqual(UnprefixedPathType.rooted, getUnprefixedPathType(u8, "\\x")); | |
| 2235 | try std.testing.expectEqual(UnprefixedPathType.rooted, getUnprefixedPathType(u8, "/")); | |
| 2236 | try std.testing.expectEqual(UnprefixedPathType.drive_relative, getUnprefixedPathType(u8, "x:")); | |
| 2237 | try std.testing.expectEqual(UnprefixedPathType.drive_relative, getUnprefixedPathType(u8, "x:abc")); | |
| 2238 | try std.testing.expectEqual(UnprefixedPathType.drive_relative, getUnprefixedPathType(u8, "x:a/b/c")); | |
| 2239 | try std.testing.expectEqual(UnprefixedPathType.drive_absolute, getUnprefixedPathType(u8, "x:\\")); | |
| 2240 | try std.testing.expectEqual(UnprefixedPathType.drive_absolute, getUnprefixedPathType(u8, "x:\\abc")); | |
| 2241 | try std.testing.expectEqual(UnprefixedPathType.drive_absolute, getUnprefixedPathType(u8, "x:/a/b/c")); | |
| 2037 | 2242 | } |
| 2038 | 2243 | |
| 2039 | 2244 | fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize { |
| ... | ... | @@ -2046,34 +2251,6 @@ fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize { |
| 2046 | 2251 | return result; |
| 2047 | 2252 | } |
| 2048 | 2253 | |
| 2049 | /// Assumes an absolute path. | |
| 2050 | pub fn wToPrefixedFileW(s: []const u16) !PathSpace { | |
| 2051 | // TODO https://github.com/ziglang/zig/issues/2765 | |
| 2052 | var path_space: PathSpace = undefined; | |
| 2053 | ||
| 2054 | const start_index = if (mem.startsWith(u16, s, &[_]u16{ '\\', '?' })) 0 else blk: { | |
| 2055 | const prefix = [_]u16{ '\\', '?', '?', '\\' }; | |
| 2056 | path_space.data[0..prefix.len].* = prefix; | |
| 2057 | break :blk prefix.len; | |
| 2058 | }; | |
| 2059 | path_space.len = start_index + s.len; | |
| 2060 | if (path_space.len > path_space.data.len) return error.NameTooLong; | |
| 2061 | @memcpy(path_space.data[start_index..][0..s.len], s); | |
| 2062 | // > File I/O functions in the Windows API convert "/" to "\" as part of | |
| 2063 | // > converting the name to an NT-style name, except when using the "\\?\" | |
| 2064 | // > prefix as detailed in the following sections. | |
| 2065 | // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation | |
| 2066 | // Because we want the larger maximum path length for absolute paths, we | |
| 2067 | // convert forward slashes to backward slashes here. | |
| 2068 | for (path_space.data[0..path_space.len]) |*elem| { | |
| 2069 | if (elem.* == '/') { | |
| 2070 | elem.* = '\\'; | |
| 2071 | } | |
| 2072 | } | |
| 2073 | path_space.data[path_space.len] = 0; | |
| 2074 | return path_space; | |
| 2075 | } | |
| 2076 | ||
| 2077 | 2254 | inline fn MAKELANGID(p: c_ushort, s: c_ushort) LANGID { |
| 2078 | 2255 | return (s << 10) | p; |
| 2079 | 2256 | } |
lib/std/os/windows/ntdll.zig+10| ... | ... | @@ -158,6 +158,16 @@ pub extern "ntdll" fn RtlDosPathNameToNtPathName_U( |
| 158 | 158 | ) callconv(WINAPI) BOOL; |
| 159 | 159 | pub extern "ntdll" fn RtlFreeUnicodeString(UnicodeString: *UNICODE_STRING) callconv(WINAPI) void; |
| 160 | 160 | |
| 161 | /// Returns the number of bytes written to `Buffer`. | |
| 162 | /// If the returned count is larger than `BufferByteLength`, the buffer was too small. | |
| 163 | /// If the returned count is zero, an error occurred. | |
| 164 | pub extern "ntdll" fn RtlGetFullPathName_U( | |
| 165 | FileName: [*:0]const u16, | |
| 166 | BufferByteLength: ULONG, | |
| 167 | Buffer: [*]u16, | |
| 168 | ShortName: ?*[*:0]const u16, | |
| 169 | ) callconv(windows.WINAPI) windows.ULONG; | |
| 170 | ||
| 161 | 171 | pub extern "ntdll" fn NtQueryDirectoryFile( |
| 162 | 172 | FileHandle: HANDLE, |
| 163 | 173 | Event: ?HANDLE, |
lib/std/os/windows/test.zig+175-1| ... | ... | @@ -3,7 +3,181 @@ const builtin = @import("builtin"); |
| 3 | 3 | const windows = std.os.windows; |
| 4 | 4 | const mem = std.mem; |
| 5 | 5 | const testing = std.testing; |
| 6 | const expect = testing.expect; | |
| 6 | ||
| 7 | /// Wrapper around RtlDosPathNameToNtPathName_U for use in comparing | |
| 8 | /// the behavior of RtlDosPathNameToNtPathName_U with wToPrefixedFileW | |
| 9 | /// Note: RtlDosPathNameToNtPathName_U is not used in the Zig implementation | |
| 10 | // because it allocates. | |
| 11 | fn RtlDosPathNameToNtPathName_U(path: [:0]const u16) !windows.PathSpace { | |
| 12 | var out: windows.UNICODE_STRING = undefined; | |
| 13 | const rc = windows.ntdll.RtlDosPathNameToNtPathName_U(path, &out, null, null); | |
| 14 | if (rc != windows.TRUE) return error.BadPathName; | |
| 15 | defer windows.ntdll.RtlFreeUnicodeString(&out); | |
| 16 | ||
| 17 | var path_space: windows.PathSpace = undefined; | |
| 18 | const out_path = out.Buffer[0 .. out.Length / 2]; | |
| 19 | std.mem.copy(u16, path_space.data[0..], out_path); | |
| 20 | path_space.len = out.Length / 2; | |
| 21 | path_space.data[path_space.len] = 0; | |
| 22 | ||
| 23 | return path_space; | |
| 24 | } | |
| 25 | ||
| 26 | /// Test that the Zig conversion matches the expected_path (for instances where | |
| 27 | /// the Zig implementation intentionally diverges from what RtlDosPathNameToNtPathName_U does). | |
| 28 | fn testToPrefixedFileNoOracle(comptime path: []const u8, comptime expected_path: []const u8) !void { | |
| 29 | const path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(path); | |
| 30 | const expected_path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(expected_path); | |
| 31 | const actual_path = try windows.wToPrefixedFileW(path_utf16); | |
| 32 | std.testing.expectEqualSlices(u16, expected_path_utf16, actual_path.span()) catch |e| { | |
| 33 | std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16le(actual_path.span()), std.unicode.fmtUtf16le(expected_path_utf16) }); | |
| 34 | return e; | |
| 35 | }; | |
| 36 | } | |
| 37 | ||
| 38 | /// Test that the Zig conversion matches the expected_path and that the | |
| 39 | /// expected_path matches the conversion that RtlDosPathNameToNtPathName_U does. | |
| 40 | fn testToPrefixedFileWithOracle(comptime path: []const u8, comptime expected_path: []const u8) !void { | |
| 41 | try testToPrefixedFileNoOracle(path, expected_path); | |
| 42 | try testToPrefixedFileOnlyOracle(path); | |
| 43 | } | |
| 44 | ||
| 45 | /// Test that the Zig conversion matches the conversion that RtlDosPathNameToNtPathName_U does. | |
| 46 | fn testToPrefixedFileOnlyOracle(comptime path: []const u8) !void { | |
| 47 | const path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(path); | |
| 48 | const zig_result = try windows.wToPrefixedFileW(path_utf16); | |
| 49 | const win32_api_result = try RtlDosPathNameToNtPathName_U(path_utf16); | |
| 50 | std.testing.expectEqualSlices(u16, win32_api_result.span(), zig_result.span()) catch |e| { | |
| 51 | std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16le(zig_result.span()), std.unicode.fmtUtf16le(win32_api_result.span()) }); | |
| 52 | return e; | |
| 53 | }; | |
| 54 | } | |
| 55 | ||
| 56 | test "toPrefixedFileW" { | |
| 57 | if (builtin.os.tag != .windows) | |
| 58 | return; | |
| 59 | ||
| 60 | // Most test cases come from https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html | |
| 61 | // Note that these tests do not actually touch the filesystem or care about whether or not | |
| 62 | // any of the paths actually exist or are otherwise valid. | |
| 63 | ||
| 64 | // Drive Absolute | |
| 65 | try testToPrefixedFileWithOracle("X:\\ABC\\DEF", "\\??\\X:\\ABC\\DEF"); | |
| 66 | try testToPrefixedFileWithOracle("X:\\", "\\??\\X:\\"); | |
| 67 | try testToPrefixedFileWithOracle("X:\\ABC\\", "\\??\\X:\\ABC\\"); | |
| 68 | // Trailing . and space characters are stripped | |
| 69 | try testToPrefixedFileWithOracle("X:\\ABC\\DEF. .", "\\??\\X:\\ABC\\DEF"); | |
| 70 | try testToPrefixedFileWithOracle("X:/ABC/DEF", "\\??\\X:\\ABC\\DEF"); | |
| 71 | try testToPrefixedFileWithOracle("X:\\ABC\\..\\XYZ", "\\??\\X:\\XYZ"); | |
| 72 | try testToPrefixedFileWithOracle("X:\\ABC\\..\\..\\..", "\\??\\X:\\"); | |
| 73 | // Drive letter casing is unchanged | |
| 74 | try testToPrefixedFileWithOracle("x:\\", "\\??\\x:\\"); | |
| 75 | ||
| 76 | // Drive Relative | |
| 77 | // These tests depend on the CWD of the specified drive letter which can vary, | |
| 78 | // so instead we just test that the Zig implementation matches the result of | |
| 79 | // RtlDosPathNameToNtPathName_U. | |
| 80 | // TODO: Setting the =X: environment variable didn't seem to affect | |
| 81 | // RtlDosPathNameToNtPathName_U, not sure why that is but getting that | |
| 82 | // to work could be an avenue to making these cases environment-independent. | |
| 83 | // All -> are examples of the result if the X drive's cwd was X:\ABC | |
| 84 | try testToPrefixedFileOnlyOracle("X:DEF\\GHI"); // -> \??\X:\ABC\DEF\GHI | |
| 85 | try testToPrefixedFileOnlyOracle("X:"); // -> \??\X:\ABC | |
| 86 | try testToPrefixedFileOnlyOracle("X:DEF. ."); // -> \??\X:\ABC\DEF | |
| 87 | try testToPrefixedFileOnlyOracle("X:ABC\\..\\XYZ"); // -> \??\X:\ABC\XYZ | |
| 88 | try testToPrefixedFileOnlyOracle("X:ABC\\..\\..\\.."); // -> \??\X:\ | |
| 89 | try testToPrefixedFileOnlyOracle("x:"); // -> \??\X:\ABC | |
| 90 | ||
| 91 | // Rooted | |
| 92 | // These tests depend on the drive letter of the CWD which can vary, so | |
| 93 | // instead we just test that the Zig implementation matches the result of | |
| 94 | // RtlDosPathNameToNtPathName_U. | |
| 95 | // TODO: Getting the CWD path, getting the drive letter from it, and using it to | |
| 96 | // construct the expected NT paths could be an avenue to making these cases | |
| 97 | // environment-independent and therefore able to use testToPrefixedFileWithOracle. | |
| 98 | // All -> are examples of the result if the CWD's drive letter was X | |
| 99 | try testToPrefixedFileOnlyOracle("\\ABC\\DEF"); // -> \??\X:\ABC\DEF | |
| 100 | try testToPrefixedFileOnlyOracle("\\"); // -> \??\X:\ | |
| 101 | try testToPrefixedFileOnlyOracle("\\ABC\\DEF. ."); // -> \??\X:\ABC\DEF | |
| 102 | try testToPrefixedFileOnlyOracle("/ABC/DEF"); // -> \??\X:\ABC\DEF | |
| 103 | try testToPrefixedFileOnlyOracle("\\ABC\\..\\XYZ"); // -> \??\X:\XYZ | |
| 104 | try testToPrefixedFileOnlyOracle("\\ABC\\..\\..\\.."); // -> \??\X:\ | |
| 105 | ||
| 106 | // Relative | |
| 107 | // These cases differ in functionality to RtlDosPathNameToNtPathName_U. | |
| 108 | // Relative paths remain relative if they don't have enough .. components | |
| 109 | // to error with TooManyParentDirs | |
| 110 | try testToPrefixedFileNoOracle("ABC\\DEF", "ABC\\DEF"); | |
| 111 | // TODO: enable this if trailing . and spaces are stripped from relative paths | |
| 112 | //try testToPrefixedFileNoOracle("ABC\\DEF. .", "ABC\\DEF"); | |
| 113 | try testToPrefixedFileNoOracle("ABC/DEF", "ABC\\DEF"); | |
| 114 | try testToPrefixedFileNoOracle("./ABC/.././DEF", "DEF"); | |
| 115 | // TooManyParentDirs, so resolved relative to the CWD | |
| 116 | // All -> are examples of the result if the CWD was X:\ABC\DEF | |
| 117 | try testToPrefixedFileOnlyOracle("..\\GHI"); // -> \??\X:\ABC\GHI | |
| 118 | try testToPrefixedFileOnlyOracle("GHI\\..\\..\\.."); // -> \??\X:\ | |
| 119 | ||
| 120 | // UNC Absolute | |
| 121 | try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\DEF", "\\??\\UNC\\server\\share\\ABC\\DEF"); | |
| 122 | try testToPrefixedFileWithOracle("\\\\server", "\\??\\UNC\\server"); | |
| 123 | try testToPrefixedFileWithOracle("\\\\server\\share", "\\??\\UNC\\server\\share"); | |
| 124 | try testToPrefixedFileWithOracle("\\\\server\\share\\ABC. .", "\\??\\UNC\\server\\share\\ABC"); | |
| 125 | try testToPrefixedFileWithOracle("//server/share/ABC/DEF", "\\??\\UNC\\server\\share\\ABC\\DEF"); | |
| 126 | try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\..\\XYZ", "\\??\\UNC\\server\\share\\XYZ"); | |
| 127 | try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\..\\..\\..", "\\??\\UNC\\server\\share"); | |
| 128 | ||
| 129 | // Local Device | |
| 130 | try testToPrefixedFileWithOracle("\\\\.\\COM20", "\\??\\COM20"); | |
| 131 | try testToPrefixedFileWithOracle("\\\\.\\pipe\\mypipe", "\\??\\pipe\\mypipe"); | |
| 132 | try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\DEF. .", "\\??\\X:\\ABC\\DEF"); | |
| 133 | try testToPrefixedFileWithOracle("\\\\.\\X:/ABC/DEF", "\\??\\X:\\ABC\\DEF"); | |
| 134 | try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\..\\XYZ", "\\??\\X:\\XYZ"); | |
| 135 | // Can replace the first component of the path (contrary to drive absolute and UNC absolute paths) | |
| 136 | try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\..\\..\\C:\\", "\\??\\C:\\"); | |
| 137 | try testToPrefixedFileWithOracle("\\\\.\\pipe\\mypipe\\..\\notmine", "\\??\\pipe\\notmine"); | |
| 138 | ||
| 139 | // Special-case device names | |
| 140 | // TODO: Enable once these are supported | |
| 141 | // more cases to test here: https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html | |
| 142 | //try testToPrefixedFileWithOracle("COM1", "\\??\\COM1"); | |
| 143 | // Sometimes the special-cased device names are not respected | |
| 144 | try testToPrefixedFileWithOracle("\\\\.\\X:\\COM1", "\\??\\X:\\COM1"); | |
| 145 | try testToPrefixedFileWithOracle("\\\\abc\\xyz\\COM1", "\\??\\UNC\\abc\\xyz\\COM1"); | |
| 146 | ||
| 147 | // Verbatim | |
| 148 | // Left untouched except \\?\ is replaced by \??\ | |
| 149 | try testToPrefixedFileWithOracle("\\\\?\\X:", "\\??\\X:"); | |
| 150 | try testToPrefixedFileWithOracle("\\\\?\\X:\\COM1", "\\??\\X:\\COM1"); | |
| 151 | try testToPrefixedFileWithOracle("\\\\?\\X:/ABC/DEF. .", "\\??\\X:/ABC/DEF. ."); | |
| 152 | try testToPrefixedFileWithOracle("\\\\?\\X:\\ABC\\..\\..\\..", "\\??\\X:\\ABC\\..\\..\\.."); | |
| 153 | // NT Namespace | |
| 154 | // Fully unmodified | |
| 155 | try testToPrefixedFileWithOracle("\\??\\X:", "\\??\\X:"); | |
| 156 | try testToPrefixedFileWithOracle("\\??\\X:\\COM1", "\\??\\X:\\COM1"); | |
| 157 | try testToPrefixedFileWithOracle("\\??\\X:/ABC/DEF. .", "\\??\\X:/ABC/DEF. ."); | |
| 158 | try testToPrefixedFileWithOracle("\\??\\X:\\ABC\\..\\..\\..", "\\??\\X:\\ABC\\..\\..\\.."); | |
| 159 | ||
| 160 | // 'Fake' Verbatim | |
| 161 | // If the prefix looks like the verbatim prefix but not all path separators in the | |
| 162 | // prefix are backslashes, then it gets canonicalized and the prefix is dropped in favor | |
| 163 | // of the NT prefix. | |
| 164 | try testToPrefixedFileWithOracle("//?/C:/ABC", "\\??\\C:\\ABC"); | |
| 165 | // 'Fake' NT | |
| 166 | // If the prefix looks like the NT prefix but not all path separators in the prefix | |
| 167 | // are backslashes, then it gets canonicalized and the /??/ is not dropped but | |
| 168 | // rather treated as part of the path. In other words, the path is treated | |
| 169 | // as a rooted path, so the final path is resolved relative to the CWD's | |
| 170 | // drive letter. | |
| 171 | // The -> shows an example of the result if the CWD's drive letter was X | |
| 172 | try testToPrefixedFileOnlyOracle("/??/C:/ABC"); // -> \??\X:\??\C:\ABC | |
| 173 | ||
| 174 | // Root Local Device | |
| 175 | // \\. and \\? always get converted to \??\ | |
| 176 | try testToPrefixedFileWithOracle("\\\\.", "\\??\\"); | |
| 177 | try testToPrefixedFileWithOracle("\\\\?", "\\??\\"); | |
| 178 | try testToPrefixedFileWithOracle("//?", "\\??\\"); | |
| 179 | try testToPrefixedFileWithOracle("//.", "\\??\\"); | |
| 180 | } | |
| 7 | 181 | |
| 8 | 182 | fn testRemoveDotDirs(str: []const u8, expected: []const u8) !void { |
| 9 | 183 | const mutable = try testing.allocator.dupe(u8, str); |
src/arch/x86_64/CodeGen.zig+25-24| ... | ... | @@ -10175,37 +10175,38 @@ fn airBitCast(self: *Self, inst: Air.Inst.Index) !void { |
| 10175 | 10175 | if (dst_ty.isAbiInt()) dst_ty.intInfo(self.target.*).signedness else .unsigned; |
| 10176 | 10176 | const src_signedness = |
| 10177 | 10177 | if (src_ty.isAbiInt()) src_ty.intInfo(self.target.*).signedness else .unsigned; |
| 10178 | if (dst_signedness == src_signedness) break :result dst_mcv; | |
| 10179 | ||
| 10178 | 10180 | const abi_size = @intCast(u16, dst_ty.abiSize(self.target.*)); |
| 10179 | 10181 | const bit_size = @intCast(u16, dst_ty.bitSize(self.target.*)); |
| 10180 | const dst_limbs_len = math.divCeil(u16, bit_size, 64) catch unreachable; | |
| 10181 | if (dst_signedness != src_signedness and abi_size * 8 > bit_size) { | |
| 10182 | const high_reg = if (dst_mcv.isRegister()) | |
| 10183 | dst_mcv.getReg().? | |
| 10184 | else | |
| 10185 | try self.copyToTmpRegister( | |
| 10186 | Type.usize, | |
| 10187 | dst_mcv.address().offset((dst_limbs_len - 1) * 8).deref(), | |
| 10188 | ); | |
| 10189 | const high_lock = self.register_manager.lockReg(high_reg); | |
| 10190 | defer if (high_lock) |lock| self.register_manager.unlockReg(lock); | |
| 10191 | ||
| 10192 | var high_pl = Type.Payload.Bits{ | |
| 10193 | .base = .{ .tag = switch (dst_signedness) { | |
| 10194 | .signed => .int_signed, | |
| 10195 | .unsigned => .int_unsigned, | |
| 10196 | } }, | |
| 10197 | .data = bit_size % 64, | |
| 10198 | }; | |
| 10199 | const high_ty = Type.initPayload(&high_pl.base); | |
| 10182 | if (abi_size * 8 <= bit_size) break :result dst_mcv; | |
| 10200 | 10183 | |
| 10201 | try self.truncateRegister(high_ty, high_reg); | |
| 10202 | if (!dst_mcv.isRegister()) try self.genCopy( | |
| 10184 | const dst_limbs_len = math.divCeil(i32, bit_size, 64) catch unreachable; | |
| 10185 | const high_reg = if (dst_mcv.isRegister()) | |
| 10186 | dst_mcv.getReg().? | |
| 10187 | else | |
| 10188 | try self.copyToTmpRegister( | |
| 10203 | 10189 | Type.usize, |
| 10204 | 10190 | dst_mcv.address().offset((dst_limbs_len - 1) * 8).deref(), |
| 10205 | .{ .register = high_reg }, | |
| 10206 | 10191 | ); |
| 10207 | } | |
| 10192 | const high_lock = self.register_manager.lockReg(high_reg); | |
| 10193 | defer if (high_lock) |lock| self.register_manager.unlockReg(lock); | |
| 10194 | ||
| 10195 | var high_pl = Type.Payload.Bits{ | |
| 10196 | .base = .{ .tag = switch (dst_signedness) { | |
| 10197 | .signed => .int_signed, | |
| 10198 | .unsigned => .int_unsigned, | |
| 10199 | } }, | |
| 10200 | .data = bit_size % 64, | |
| 10201 | }; | |
| 10202 | const high_ty = Type.initPayload(&high_pl.base); | |
| 10208 | 10203 | |
| 10204 | try self.truncateRegister(high_ty, high_reg); | |
| 10205 | if (!dst_mcv.isRegister()) try self.genCopy( | |
| 10206 | Type.usize, | |
| 10207 | dst_mcv.address().offset((dst_limbs_len - 1) * 8).deref(), | |
| 10208 | .{ .register = high_reg }, | |
| 10209 | ); | |
| 10209 | 10210 | break :result dst_mcv; |
| 10210 | 10211 | }; |
| 10211 | 10212 | return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); |
test/behavior/maximum_minimum.zig-1| ... | ... | @@ -197,7 +197,6 @@ test "@min/@max notices vector bounds" { |
| 197 | 197 | |
| 198 | 198 | test "@min/@max on comptime_int" { |
| 199 | 199 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO |
| 200 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO | |
| 201 | 200 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO |
| 202 | 201 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 203 | 202 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |