Commit ea1b21dbdb

Andrew Kelley <superjoe30@gmail.com>
2018-08-22 02:28:37
fix linux
* error.BadFd is not a valid error code. it would always be a bug to get this error code. * merge error.Io with existing error.InputOutput * merge error.PathNotFound with existing error.FileNotFound. Not all OS's support both. * add os.File.openReadC * add error.BadPathName for windows file operations with invalid characters * add os.toPosixPath to help stack allocate a null terminating byte * add some TODOs for other functions to investigate removing the allocator requirement * optimize some implementations to use the alternate functions when a null byte is already available * add a missing error.SkipZigTest * os.selfExePath uses a non-allocating API * os.selfExeDirPath uses a non-allocating API * os.path.real uses a non-allocating API * add os.path.realAlloc and os.path.realC * convert many windows syscalls to use the W versions (See #534)
1 parent 51852d2
doc/docgen.zig
@@ -34,10 +34,10 @@ pub fn main() !void {
     const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg"));
     defer allocator.free(out_file_name);
 
-    var in_file = try os.File.openRead(allocator, in_file_name);
+    var in_file = try os.File.openRead(in_file_name);
     defer in_file.close();
 
-    var out_file = try os.File.openWrite(allocator, out_file_name);
+    var out_file = try os.File.openWrite(out_file_name);
     defer out_file.close();
 
     var file_in_stream = io.FileInStream.init(&in_file);
@@ -738,7 +738,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
                 try out.print("<pre><code class=\"zig\">{}</code></pre>", escaped_source);
                 const name_plus_ext = try std.fmt.allocPrint(allocator, "{}.zig", code.name);
                 const tmp_source_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_ext);
-                try io.writeFile(allocator, tmp_source_file_name, trimmed_raw_source);
+                try io.writeFile(tmp_source_file_name, trimmed_raw_source);
 
                 switch (code.id) {
                     Code.Id.Exe => |expected_outcome| {
example/cat/main.zig
@@ -20,7 +20,7 @@ pub fn main() !void {
         } else if (arg[0] == '-') {
             return usage(exe);
         } else {
-            var file = os.File.openRead(allocator, arg) catch |err| {
+            var file = os.File.openRead(arg) catch |err| {
                 warn("Unable to open file: {}\n", @errorName(err));
                 return err;
             };
src-self-hosted/compilation.zig
@@ -257,8 +257,6 @@ pub const Compilation = struct {
     pub const BuildError = error{
         OutOfMemory,
         EndOfStream,
-        BadFd,
-        Io,
         IsDir,
         Unexpected,
         SystemResources,
@@ -273,7 +271,6 @@ pub const Compilation = struct {
         NameTooLong,
         SystemFdQuotaExceeded,
         NoDevice,
-        PathNotFound,
         NoSpaceLeft,
         NotDir,
         FileSystem,
@@ -962,7 +959,7 @@ pub const Compilation = struct {
         if (self.root_src_path) |root_src_path| {
             const root_scope = blk: {
                 // TODO async/await os.path.real
-                const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| {
+                const root_src_real_path = os.path.realAlloc(self.gpa(), root_src_path) catch |err| {
                     try self.addCompileErrorCli(root_src_path, "unable to open: {}", @errorName(err));
                     return;
                 };
src-self-hosted/introspect.zig
@@ -22,7 +22,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![
 
 /// Caller must free result
 pub fn findZigLibDir(allocator: *mem.Allocator) ![]u8 {
-    const self_exe_path = try os.selfExeDirPath(allocator);
+    const self_exe_path = try os.selfExeDirPathAlloc(allocator);
     defer allocator.free(self_exe_path);
 
     var cur_path: []const u8 = self_exe_path;
src-self-hosted/libc_installation.zig
@@ -453,7 +453,7 @@ fn fileExists(path: []const u8) !bool {
     if (std.os.File.access(path)) |_| {
         return true;
     } else |err| switch (err) {
-        error.FileNotFound, error.PathNotFound, error.PermissionDenied => return false,
+        error.FileNotFound, error.PermissionDenied => return false,
         else => return error.FileSystem,
     }
 }
std/debug/index.zig
@@ -255,7 +255,7 @@ pub fn printSourceAtAddress(debug_info: *ElfStackTrace, out_stream: var, address
                         address,
                         compile_unit_name,
                     );
-                    if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) {
+                    if (printLineFromFile(out_stream, line_info)) {
                         if (line_info.column == 0) {
                             try out_stream.write("\n");
                         } else {
std/event/fs.zig
@@ -78,8 +78,7 @@ pub async fn pwritev(loop: *Loop, fd: os.FileHandle, data: []const []const u8, o
         builtin.Os.macosx,
         builtin.Os.linux,
         => return await (async pwritevPosix(loop, fd, data, offset) catch unreachable),
-        builtin.Os.windows,
-        => return await (async pwritevWindows(loop, fd, data, offset) catch unreachable),
+        builtin.Os.windows => return await (async pwritevWindows(loop, fd, data, offset) catch unreachable),
         else => @compileError("Unsupported OS"),
     }
 }
@@ -147,7 +146,6 @@ pub async fn pwriteWindows(loop: *Loop, fd: os.FileHandle, data: []const u8, off
     }
 }
 
-
 /// data - just the inner references - must live until pwritev promise completes.
 pub async fn pwritevPosix(loop: *Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void {
     // workaround for https://github.com/ziglang/zig/issues/1194
@@ -203,8 +201,7 @@ pub async fn preadv(loop: *Loop, fd: os.FileHandle, data: []const []u8, offset:
         builtin.Os.macosx,
         builtin.Os.linux,
         => return await (async preadvPosix(loop, fd, data, offset) catch unreachable),
-        builtin.Os.windows,
-        => return await (async preadvWindows(loop, fd, data, offset) catch unreachable),
+        builtin.Os.windows => return await (async preadvWindows(loop, fd, data, offset) catch unreachable),
         else => @compileError("Unsupported OS"),
     }
 }
@@ -222,7 +219,7 @@ pub async fn preadvWindows(loop: *Loop, fd: os.FileHandle, data: []const []u8, o
     var inner_off: usize = 0;
     while (true) {
         const v = data_copy[iov_i];
-        const amt_read = try await (async preadWindows(loop, fd, v[inner_off .. v.len-inner_off], offset + off) catch unreachable);
+        const amt_read = try await (async preadWindows(loop, fd, v[inner_off .. v.len - inner_off], offset + off) catch unreachable);
         off += amt_read;
         inner_off += amt_read;
         if (inner_off == v.len) {
@@ -340,8 +337,7 @@ pub async fn openPosix(
         resume @handle();
     }
 
-    const path_with_null = try std.cstr.addNullByte(loop.allocator, path);
-    defer loop.allocator.free(path_with_null);
+    const path_c = try std.os.toPosixPath(path);
 
     var req_node = RequestNode{
         .prev = null,
@@ -349,7 +345,7 @@ pub async fn openPosix(
         .data = Request{
             .msg = Request.Msg{
                 .Open = Request.Msg.Open{
-                    .path = path_with_null[0..path.len],
+                    .path = path_c[0..path.len],
                     .flags = flags,
                     .mode = mode,
                     .result = undefined,
@@ -408,8 +404,7 @@ pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: os.File.Mode) os
             const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
             return await (async openPosix(loop, path, flags, os.File.default_mode) catch unreachable);
         },
-        builtin.Os.windows,
-        => return os.windowsOpen(
+        builtin.Os.windows => return os.windowsOpen(
             path,
             windows.GENERIC_WRITE,
             windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
@@ -434,7 +429,7 @@ pub async fn openReadWrite(
 
         builtin.Os.windows => return os.windowsOpen(
             path,
-            windows.GENERIC_WRITE|windows.GENERIC_READ,
+            windows.GENERIC_WRITE | windows.GENERIC_READ,
             windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
             windows.OPEN_ALWAYS,
             windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
@@ -510,8 +505,7 @@ pub const CloseOperation = struct {
                     self.loop.allocator.destroy(self);
                 }
             },
-            builtin.Os.windows,
-            => {
+            builtin.Os.windows => {
                 if (self.os_data.handle) |handle| {
                     os.close(handle);
                 }
@@ -529,8 +523,7 @@ pub const CloseOperation = struct {
                 self.os_data.close_req_node.data.msg.Close.fd = handle;
                 self.os_data.have_fd = true;
             },
-            builtin.Os.windows,
-            => {
+            builtin.Os.windows => {
                 self.os_data.handle = handle;
             },
             else => @compileError("Unsupported OS"),
@@ -545,8 +538,7 @@ pub const CloseOperation = struct {
             => {
                 self.os_data.have_fd = false;
             },
-            builtin.Os.windows,
-            => {
+            builtin.Os.windows => {
                 self.os_data.handle = null;
             },
             else => @compileError("Unsupported OS"),
@@ -561,8 +553,7 @@ pub const CloseOperation = struct {
                 assert(self.os_data.have_fd);
                 return self.os_data.close_req_node.data.msg.Close.fd;
             },
-            builtin.Os.windows,
-            => {
+            builtin.Os.windows => {
                 return self.os_data.handle.?;
             },
             else => @compileError("Unsupported OS"),
@@ -582,8 +573,7 @@ pub async fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8,
         builtin.Os.linux,
         builtin.Os.macosx,
         => return await (async writeFileModeThread(loop, path, contents, mode) catch unreachable),
-        builtin.Os.windows,
-        => return await (async writeFileWindows(loop, path, contents) catch unreachable),
+        builtin.Os.windows => return await (async writeFileWindows(loop, path, contents) catch unreachable),
         else => @compileError("Unsupported OS"),
     }
 }
@@ -1000,7 +990,7 @@ pub fn Watch(comptime V: type) type {
             const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename);
             var basename_utf16le_null_consumed = false;
             defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null);
-            const basename_utf16le_no_null = basename_utf16le_null[0..basename_utf16le_null.len-1];
+            const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
 
             const dir_handle = windows.CreateFileW(
                 dirname_utf16le.ptr,
@@ -1014,9 +1004,8 @@ pub fn Watch(comptime V: type) type {
             if (dir_handle == windows.INVALID_HANDLE_VALUE) {
                 const err = windows.GetLastError();
                 switch (err) {
-                    windows.ERROR.FILE_NOT_FOUND,
-                    windows.ERROR.PATH_NOT_FOUND,
-                    => return error.PathNotFound,
+                    windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
+                    windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
                     else => return os.unexpectedErrorWindows(err),
                 }
             }
@@ -1102,7 +1091,10 @@ pub fn Watch(comptime V: type) type {
 
             // TODO handle this error not in the channel but in the setup
             _ = os.windowsCreateIoCompletionPort(
-                dir_handle, self.channel.loop.os_data.io_port, completion_key, undefined,
+                dir_handle,
+                self.channel.loop.os_data.io_port,
+                completion_key,
+                undefined,
             ) catch |err| {
                 await (async self.channel.put(err) catch unreachable);
                 return;
@@ -1122,10 +1114,10 @@ pub fn Watch(comptime V: type) type {
                             &event_buf,
                             @intCast(windows.DWORD, event_buf.len),
                             windows.FALSE, // watch subtree
-                            windows.FILE_NOTIFY_CHANGE_FILE_NAME        | windows.FILE_NOTIFY_CHANGE_DIR_NAME     |
-                                windows.FILE_NOTIFY_CHANGE_ATTRIBUTES   | windows.FILE_NOTIFY_CHANGE_SIZE         |
-                                windows.FILE_NOTIFY_CHANGE_LAST_WRITE   | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS  |
-                                windows.FILE_NOTIFY_CHANGE_CREATION     | windows.FILE_NOTIFY_CHANGE_SECURITY,
+                            windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
+                                windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
+                                windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
+                                windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
                             null, // number of bytes transferred (unused for async)
                             &overlapped,
                             null, // completion routine - unused because we use IOCP
@@ -1152,7 +1144,7 @@ pub fn Watch(comptime V: type) type {
                             else => null,
                         };
                         if (emit) |id| {
-                            const basename_utf16le = ([*]u16)(&ev.FileName)[0..ev.FileNameLength/2];
+                            const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
                             const user_value = blk: {
                                 const held = await (async dir.table_lock.acquire() catch unreachable);
                                 defer held.release();
std/os/windows/kernel32.zig
@@ -4,8 +4,8 @@ pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVE
 
 pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
 
-pub extern "kernel32" stdcallcc fn CreateDirectoryA( lpPathName: [*]const u8, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
-pub extern "kernel32" stdcallcc fn CreateDirectoryW( lpPathName: [*]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
+pub extern "kernel32" stdcallcc fn CreateDirectoryA(lpPathName: [*]const u8, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
+pub extern "kernel32" stdcallcc fn CreateDirectoryW(lpPathName: [*]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
 
 pub extern "kernel32" stdcallcc fn CreateFileA(
     lpFileName: [*]const u8, // TODO null terminated pointer type
@@ -89,7 +89,8 @@ pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LAR
 pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: [*]const CHAR) DWORD;
 pub extern "kernel32" stdcallcc fn GetFileAttributesW(lpFileName: [*]const WCHAR) DWORD;
 
-pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: LPSTR, nSize: DWORD) DWORD;
+pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: [*]u8, nSize: DWORD) DWORD;
+pub extern "kernel32" stdcallcc fn GetModuleFileNameW(hModule: ?HMODULE, lpFilename: [*]u16, nSize: DWORD) DWORD;
 
 pub extern "kernel32" stdcallcc fn GetLastError() DWORD;
 
@@ -107,6 +108,13 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
     dwFlags: DWORD,
 ) DWORD;
 
+pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleW(
+    hFile: HANDLE,
+    lpszFilePath: [*]u16,
+    cchFilePath: DWORD,
+    dwFlags: DWORD,
+) DWORD;
+
 pub extern "kernel32" stdcallcc fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) BOOL;
 
 pub extern "kernel32" stdcallcc fn GetProcessHeap() ?HANDLE;
std/os/windows/util.zig
@@ -97,12 +97,12 @@ pub const OpenError = error{
     SharingViolation,
     PathAlreadyExists,
 
-    /// When all the path components are found but the file component is not.
+    /// When any of the path components can not be found or the file component can not
+    /// be found. Some operating systems distinguish between path components not found and
+    /// file components not found, but they are collapsed into FileNotFound to gain
+    /// consistency across operating systems.
     FileNotFound,
 
-    /// When one or more path components are not found.
-    PathNotFound,
-
     AccessDenied,
     PipeBusy,
     NameTooLong,
@@ -136,7 +136,7 @@ pub fn windowsOpen(
             windows.ERROR.ALREADY_EXISTS => return OpenError.PathAlreadyExists,
             windows.ERROR.FILE_EXISTS => return OpenError.PathAlreadyExists,
             windows.ERROR.FILE_NOT_FOUND => return OpenError.FileNotFound,
-            windows.ERROR.PATH_NOT_FOUND => return OpenError.PathNotFound,
+            windows.ERROR.PATH_NOT_FOUND => return OpenError.FileNotFound,
             windows.ERROR.ACCESS_DENIED => return OpenError.AccessDenied,
             windows.ERROR.PIPE_BUSY => return OpenError.PipeBusy,
             else => return os.unexpectedErrorWindows(err),
@@ -216,9 +216,8 @@ pub fn windowsFindFirstFile(
     if (handle == windows.INVALID_HANDLE_VALUE) {
         const err = windows.GetLastError();
         switch (err) {
-            windows.ERROR.FILE_NOT_FOUND,
-            windows.ERROR.PATH_NOT_FOUND,
-            => return error.PathNotFound,
+            windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
+            windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
             else => return os.unexpectedErrorWindows(err),
         }
     }
@@ -284,13 +283,13 @@ pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_t
     return WindowsWaitResult.Normal;
 }
 
-pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE+1]u16 {
+pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 {
     return sliceToPrefixedFileW(mem.toSliceConst(u8, s));
 }
 
-pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE+1]u16 {
+pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 {
     // TODO well defined copy elision
-    var result: [PATH_MAX_WIDE+1]u16 = undefined;
+    var result: [PATH_MAX_WIDE + 1]u16 = undefined;
 
     // > File I/O functions in the Windows API convert "/" to "\" as part of
     // > converting the name to an NT-style name, except when using the "\\?\"
@@ -298,12 +297,13 @@ pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE+1]u16 {
     // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
     // Because we want the larger maximum path length for absolute paths, we
     // disallow forward slashes in zig std lib file functions on Windows.
-    for (s) |byte| switch (byte) {
+    for (s) |byte|
+        switch (byte) {
         '/', '*', '?', '"', '<', '>', '|' => return error.BadPathName,
         else => {},
     };
     const start_index = if (mem.startsWith(u8, s, "\\\\") or !os.path.isAbsolute(s)) 0 else blk: {
-        const prefix = []u16{'\\', '\\', '?', '\\'};
+        const prefix = []u16{ '\\', '\\', '?', '\\' };
         mem.copy(u16, result[0..], prefix);
         break :blk prefix.len;
     };
std/os/child_process.zig
@@ -349,14 +349,7 @@ pub const ChildProcess = struct {
         };
 
         const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
-        const dev_null_fd = if (any_ignore) blk: {
-            const dev_null_path = "/dev/null";
-            var fixed_buffer_mem: [dev_null_path.len + 1]u8 = undefined;
-            var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
-            break :blk try os.posixOpen(&fixed_allocator.allocator, "/dev/null", posix.O_RDWR, 0);
-        } else blk: {
-            break :blk undefined;
-        };
+        const dev_null_fd = if (any_ignore) try os.posixOpenC(c"/dev/null", posix.O_RDWR, 0) else undefined;
         defer {
             if (any_ignore) os.close(dev_null_fd);
         }
std/os/file.zig
@@ -28,13 +28,26 @@ pub const File = struct {
 
     pub const OpenError = os.WindowsOpenError || os.PosixOpenError;
 
-    /// Call close to clean up.
-    pub fn openRead(path: []const u8) OpenError!File {
+    /// `openRead` except with a null terminated path
+    pub fn openReadC(path: [*]const u8) OpenError!File {
         if (is_posix) {
             const flags = posix.O_LARGEFILE | posix.O_RDONLY;
-            const fd = try os.posixOpen(path, flags, 0);
+            const fd = try os.posixOpenC(path, flags, 0);
             return openHandle(fd);
-        } else if (is_windows) {
+        }
+        if (is_windows) {
+            return openRead(mem.toSliceConst(u8, path));
+        }
+        @compileError("Unsupported OS");
+    }
+
+    /// Call close to clean up.
+    pub fn openRead(path: []const u8) OpenError!File {
+        if (is_posix) {
+            const path_c = try os.toPosixPath(path);
+            return openReadC(&path_c);
+        }
+        if (is_windows) {
             const handle = try os.windowsOpen(
                 path,
                 windows.GENERIC_READ,
@@ -43,9 +56,8 @@ pub const File = struct {
                 windows.FILE_ATTRIBUTE_NORMAL,
             );
             return openHandle(handle);
-        } else {
-            @compileError("TODO implement openRead for this OS");
         }
+        @compileError("Unsupported OS");
     }
 
     /// Calls `openWriteMode` with os.File.default_mode for the mode.
@@ -103,13 +115,11 @@ pub const File = struct {
 
     pub const AccessError = error{
         PermissionDenied,
-        PathNotFound,
         FileNotFound,
         NameTooLong,
-        BadMode,
-        BadPathName,
-        Io,
+        InputOutput,
         SystemResources,
+        BadPathName,
 
         /// On Windows, file paths must be valid Unicode.
         InvalidUtf8,
@@ -127,7 +137,7 @@ pub const File = struct {
         const err = windows.GetLastError();
         switch (err) {
             windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
-            windows.ERROR.PATH_NOT_FOUND => return error.PathNotFound,
+            windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
             windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
             else => return os.unexpectedErrorWindows(err),
         }
@@ -149,13 +159,13 @@ pub const File = struct {
                 posix.EROFS => return error.PermissionDenied,
                 posix.ELOOP => return error.PermissionDenied,
                 posix.ETXTBSY => return error.PermissionDenied,
-                posix.ENOTDIR => return error.NotFound,
-                posix.ENOENT => return error.NotFound,
+                posix.ENOTDIR => return error.FileNotFound,
+                posix.ENOENT => return error.FileNotFound,
 
                 posix.ENAMETOOLONG => return error.NameTooLong,
                 posix.EINVAL => unreachable,
-                posix.EFAULT => return error.BadPathName,
-                posix.EIO => return error.Io,
+                posix.EFAULT => unreachable,
+                posix.EIO => return error.InputOutput,
                 posix.ENOMEM => return error.SystemResources,
                 else => return os.unexpectedErrorPosix(err),
             }
@@ -197,7 +207,9 @@ pub const File = struct {
                 const err = posix.getErrno(result);
                 if (err > 0) {
                     return switch (err) {
-                        posix.EBADF => error.BadFd,
+                        // We do not make this an error code because if you get EBADF it's always a bug,
+                        // since the fd could have been reused.
+                        posix.EBADF => unreachable,
                         posix.EINVAL => error.Unseekable,
                         posix.EOVERFLOW => error.Unseekable,
                         posix.ESPIPE => error.Unseekable,
@@ -210,7 +222,7 @@ pub const File = struct {
                 if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) {
                     const err = windows.GetLastError();
                     return switch (err) {
-                        windows.ERROR.INVALID_PARAMETER => error.BadFd,
+                        windows.ERROR.INVALID_PARAMETER => unreachable,
                         else => os.unexpectedErrorWindows(err),
                     };
                 }
@@ -227,7 +239,9 @@ pub const File = struct {
                 const err = posix.getErrno(result);
                 if (err > 0) {
                     return switch (err) {
-                        posix.EBADF => error.BadFd,
+                        // We do not make this an error code because if you get EBADF it's always a bug,
+                        // since the fd could have been reused.
+                        posix.EBADF => unreachable,
                         posix.EINVAL => error.Unseekable,
                         posix.EOVERFLOW => error.Unseekable,
                         posix.ESPIPE => error.Unseekable,
@@ -241,7 +255,7 @@ pub const File = struct {
                 if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) {
                     const err = windows.GetLastError();
                     return switch (err) {
-                        windows.ERROR.INVALID_PARAMETER => error.BadFd,
+                        windows.ERROR.INVALID_PARAMETER => unreachable,
                         else => os.unexpectedErrorWindows(err),
                     };
                 }
@@ -257,7 +271,9 @@ pub const File = struct {
                 const err = posix.getErrno(result);
                 if (err > 0) {
                     return switch (err) {
-                        posix.EBADF => error.BadFd,
+                        // We do not make this an error code because if you get EBADF it's always a bug,
+                        // since the fd could have been reused.
+                        posix.EBADF => unreachable,
                         posix.EINVAL => error.Unseekable,
                         posix.EOVERFLOW => error.Unseekable,
                         posix.ESPIPE => error.Unseekable,
@@ -272,7 +288,7 @@ pub const File = struct {
                 if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) {
                     const err = windows.GetLastError();
                     return switch (err) {
-                        windows.ERROR.INVALID_PARAMETER => error.BadFd,
+                        windows.ERROR.INVALID_PARAMETER => unreachable,
                         else => os.unexpectedErrorWindows(err),
                     };
                 }
@@ -305,7 +321,6 @@ pub const File = struct {
     }
 
     pub const ModeError = error{
-        BadFd,
         SystemResources,
         Unexpected,
     };
@@ -316,7 +331,9 @@ pub const File = struct {
             const err = posix.getErrno(posix.fstat(self.handle, &stat));
             if (err > 0) {
                 return switch (err) {
-                    posix.EBADF => error.BadFd,
+                    // We do not make this an error code because if you get EBADF it's always a bug,
+                    // since the fd could have been reused.
+                    posix.EBADF => unreachable,
                     posix.ENOMEM => error.SystemResources,
                     else => os.unexpectedErrorPosix(err),
                 };
std/os/index.zig
@@ -436,7 +436,7 @@ pub const PosixOpenError = error{
     NameTooLong,
     SystemFdQuotaExceeded,
     NoDevice,
-    PathNotFound,
+    FileNotFound,
     SystemResources,
     NoSpaceLeft,
     NotDir,
@@ -450,11 +450,8 @@ pub const PosixOpenError = error{
 /// Calls POSIX open, keeps trying if it gets interrupted, and translates
 /// the return value into zig errors.
 pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {
-    var path_with_null: [posix.PATH_MAX]u8 = undefined;
-    if (file_path.len >= posix.PATH_MAX) return error.NameTooLong;
-    mem.copy(u8, path_with_null[0..], file_path);
-    path_with_null[file_path.len] = 0;
-    return posixOpenC(&path_with_null, flags, perm);
+    const file_path_c = try toPosixPath(file_path);
+    return posixOpenC(&file_path_c, flags, perm);
 }
 
 // TODO https://github.com/ziglang/zig/issues/265
@@ -476,7 +473,7 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {
                 posix.ENAMETOOLONG => return PosixOpenError.NameTooLong,
                 posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded,
                 posix.ENODEV => return PosixOpenError.NoDevice,
-                posix.ENOENT => return PosixOpenError.PathNotFound,
+                posix.ENOENT => return PosixOpenError.FileNotFound,
                 posix.ENOMEM => return PosixOpenError.SystemResources,
                 posix.ENOSPC => return PosixOpenError.NoSpaceLeft,
                 posix.ENOTDIR => return PosixOpenError.NotDir,
@@ -489,6 +486,16 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {
     }
 }
 
+/// Used to convert a slice to a null terminated slice on the stack.
+/// TODO well defined copy elision
+pub fn toPosixPath(file_path: []const u8) ![posix.PATH_MAX]u8 {
+    var path_with_null: [posix.PATH_MAX]u8 = undefined;
+    if (file_path.len >= posix.PATH_MAX) return error.NameTooLong;
+    mem.copy(u8, path_with_null[0..], file_path);
+    path_with_null[file_path.len] = 0;
+    return path_with_null;
+}
+
 pub fn posixDup2(old_fd: i32, new_fd: i32) !void {
     while (true) {
         const err = posix.getErrno(posix.dup2(old_fd, new_fd));
@@ -742,7 +749,6 @@ pub fn getCwdAlloc(allocator: *Allocator) ![]u8 {
 pub const GetCwdError = error{Unexpected};
 
 /// The result is a slice of out_buffer.
-/// TODO with well defined copy elision we could make the API of this function better.
 pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) GetCwdError![]u8 {
     switch (builtin.os) {
         Os.windows => {
@@ -960,11 +966,8 @@ pub fn deleteFilePosixC(file_path: [*]const u8) !void {
 }
 
 pub fn deleteFilePosix(file_path: []const u8) !void {
-    var path_with_null: [posix.PATH_MAX]u8 = undefined;
-    if (file_path.len >= posix.PATH_MAX) return error.NameTooLong;
-    mem.copy(u8, path_with_null[0..], file_path);
-    path_with_null[file_path.len] = 0;
-    return deleteFilePosixC(&path_with_null);
+    const file_path_c = try toPosixPath(file_path);
+    return deleteFilePosixC(&file_path_c);
 }
 
 /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
@@ -1120,17 +1123,9 @@ pub fn rename(old_path: []const u8, new_path: []const u8) !void {
             }
         }
     } else {
-        var old_path_with_null: [posix.PATH_MAX]u8 = undefined;
-        if (old_path.len >= posix.PATH_MAX) return error.NameTooLong;
-        mem.copy(u8, old_path_with_null[0..], old_path);
-        old_path_with_null[old_path.len] = 0;
-
-        var new_path_with_null: [posix.PATH_MAX]u8 = undefined;
-        if (new_path.len >= posix.PATH_MAX) return error.NameTooLong;
-        mem.copy(u8, new_path_with_null[0..], new_path);
-        new_path_with_null[new_path.len] = 0;
-
-        return renameC(&old_path_with_null, &new_path_with_null);
+        const old_path_c = try toPosixPath(old_path);
+        const new_path_c = try toPosixPath(new_path);
+        return renameC(&old_path_c, &new_path_c);
     }
 }
 
@@ -1156,7 +1151,7 @@ pub fn makeDirWindows(dir_path: []const u8) !void {
 }
 
 pub fn makeDirPosixC(dir_path: [*]const u8) !void {
-    const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755));
+    const err = posix.getErrno(posix.mkdir(dir_path, 0o755));
     switch (err) {
         0 => return,
         posix.EACCES => return error.AccessDenied,
@@ -1177,11 +1172,8 @@ pub fn makeDirPosixC(dir_path: [*]const u8) !void {
 }
 
 pub fn makeDirPosix(dir_path: []const u8) !void {
-    var path_with_null: [posix.PATH_MAX]u8 = undefined;
-    if (dir_path.len >= posix.PATH_MAX) return error.NameTooLong;
-    mem.copy(u8, path_with_null[0..], dir_path);
-    path_with_null[dir_path.len] = 0;
-    return makeDirPosixC(&path_with_null);
+    const dir_path_c = try toPosixPath(dir_path);
+    return makeDirPosixC(&dir_path_c);
 }
 
 /// Calls makeDir recursively to make an entire path. Returns success if the path
@@ -1290,7 +1282,6 @@ const DeleteTreeError = error{
     NameTooLong,
     SystemFdQuotaExceeded,
     NoDevice,
-    PathNotFound,
     SystemResources,
     NoSpaceLeft,
     PathAlreadyExists,
@@ -1354,7 +1345,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
                 error.NameTooLong,
                 error.SystemFdQuotaExceeded,
                 error.NoDevice,
-                error.PathNotFound,
+                error.FileNotFound,
                 error.SystemResources,
                 error.NoSpaceLeft,
                 error.PathAlreadyExists,
@@ -1424,7 +1415,7 @@ pub const Dir = struct {
     };
 
     pub const OpenError = error{
-        PathNotFound,
+        FileNotFound,
         NotDir,
         AccessDenied,
         FileTooBig,
@@ -1443,6 +1434,7 @@ pub const Dir = struct {
         Unexpected,
     };
 
+    /// TODO remove the allocator requirement from this API
     pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {
         return Dir{
             .allocator = allocator,
@@ -1458,7 +1450,6 @@ pub const Dir = struct {
                 },
                 Os.macosx, Os.ios => Handle{
                     .fd = try posixOpen(
-                        allocator,
                         dir_path,
                         posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,
                         0,
@@ -1470,7 +1461,6 @@ pub const Dir = struct {
                 },
                 Os.linux => Handle{
                     .fd = try posixOpen(
-                        allocator,
                         dir_path,
                         posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC,
                         0,
@@ -1668,12 +1658,12 @@ pub fn changeCurDir(allocator: *Allocator, dir_path: []const u8) !void {
 
 /// Read value of a symbolic link.
 /// The return value is a slice of out_buffer.
-pub fn readLinkC(pathname: [*]const u8, out_buffer: *[posix.PATH_MAX]u8) ![]u8 {
+pub fn readLinkC(out_buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 {
     const rc = posix.readlink(pathname, out_buffer, out_buffer.len);
     const err = posix.getErrno(rc);
     switch (err) {
         0 => return out_buffer[0..rc],
-        posix.EACCES => error.AccessDenied,
+        posix.EACCES => return error.AccessDenied,
         posix.EFAULT => unreachable,
         posix.EINVAL => unreachable,
         posix.EIO => return error.FileSystem,
@@ -1688,12 +1678,9 @@ pub fn readLinkC(pathname: [*]const u8, out_buffer: *[posix.PATH_MAX]u8) ![]u8 {
 
 /// Read value of a symbolic link.
 /// The return value is a slice of out_buffer.
-pub fn readLink(file_path: []const u8, out_buffer: *[posix.PATH_MAX]u8) ![]u8 {
-    var path_with_null: [posix.PATH_MAX]u8 = undefined;
-    if (file_path.len >= posix.PATH_MAX) return error.NameTooLong;
-    mem.copy(u8, path_with_null[0..], file_path);
-    path_with_null[file_path.len] = 0;
-    return readLinkC(&path_with_null, out_buffer);
+pub fn readLink(out_buffer: *[posix.PATH_MAX]u8, file_path: []const u8) ![]u8 {
+    const file_path_c = try toPosixPath(file_path);
+    return readLinkC(out_buffer, &file_path_c);
 }
 
 pub fn posix_setuid(uid: u32) !void {
@@ -2080,17 +2067,12 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {
 
 pub fn openSelfExe() !os.File {
     switch (builtin.os) {
-        Os.linux => {
-            const proc_file_path = "/proc/self/exe";
-            var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined;
-            var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
-            return os.File.openRead(&fixed_allocator.allocator, proc_file_path);
-        },
+        Os.linux => return os.File.openReadC(c"/proc/self/exe"),
         Os.macosx, Os.ios => {
-            var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined;
-            var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
-            const self_exe_path = try selfExePath(&fixed_allocator.allocator);
-            return os.File.openRead(&fixed_allocator.allocator, self_exe_path);
+            var buf: [MAX_PATH_BYTES]u8 = undefined;
+            const self_exe_path = try selfExePath(&buf);
+            buf[self_exe_path.len] = 0;
+            return os.File.openReadC(self_exe_path.ptr);
         },
         else => @compileError("Unsupported OS"),
     }
@@ -2099,7 +2081,7 @@ pub fn openSelfExe() !os.File {
 test "openSelfExe" {
     switch (builtin.os) {
         Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(),
-        else => return, // Unsupported OS.
+        else => return error.SkipZigTest, // Unsupported OS
     }
 }
 
@@ -2108,69 +2090,67 @@ test "openSelfExe" {
 /// If you only want an open file handle, use openSelfExe.
 /// This function may return an error if the current executable
 /// was deleted after spawning.
-/// Caller owns returned memory.
-pub fn selfExePath(allocator: *mem.Allocator) ![]u8 {
+/// Returned value is a slice of out_buffer.
+///
+/// On Linux, depends on procfs being mounted. If the currently executing binary has
+/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
+pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
     switch (builtin.os) {
-        Os.linux => {
-            // If the currently executing binary has been deleted,
-            // the file path looks something like `/a/b/c/exe (deleted)`
-            return readLink(allocator, "/proc/self/exe");
-        },
+        Os.linux => return readLink(out_buffer, "/proc/self/exe"),
         Os.windows => {
-            var out_path = try Buffer.initSize(allocator, 0xff);
-            errdefer out_path.deinit();
-            while (true) {
-                const dword_len = try math.cast(windows.DWORD, out_path.len());
-                const copied_amt = windows.GetModuleFileNameA(null, out_path.ptr(), dword_len);
-                if (copied_amt <= 0) {
-                    const err = windows.GetLastError();
-                    return switch (err) {
-                        else => unexpectedErrorWindows(err),
-                    };
-                }
-                if (copied_amt < out_path.len()) {
-                    out_path.shrink(copied_amt);
-                    return out_path.toOwnedSlice();
+            var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined;
+            const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
+            const rc = windows.GetModuleFileNameW(null, &utf16le_buf, casted_len);
+            assert(rc <= utf16le_buf.len);
+            if (rc == 0) {
+                const err = windows.GetLastError();
+                switch (err) {
+                    else => return unexpectedErrorWindows(err),
                 }
-                const new_len = (out_path.len() << 1) | 0b1;
-                try out_path.resize(new_len);
             }
+            const utf16le_slice = utf16le_buf[0..rc];
+            // Trust that Windows gives us valid UTF-16LE.
+            const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable;
+            return out_buffer[0..end_index];
         },
         Os.macosx, Os.ios => {
-            var u32_len: u32 = 0;
-            const ret1 = c._NSGetExecutablePath(undefined, &u32_len);
-            assert(ret1 != 0);
-            const bytes = try allocator.alloc(u8, u32_len);
-            errdefer allocator.free(bytes);
-            const ret2 = c._NSGetExecutablePath(bytes.ptr, &u32_len);
-            assert(ret2 == 0);
-            return bytes;
+            var u32_len: u32 = @intCast(u32, out_buffer.len); // TODO shouldn't need this cast
+            const rc = c._NSGetExecutablePath(out_buffer, &u32_len);
+            if (rc != 0) return error.NameTooLong;
+            return out_buffer[0..u32_len];
         },
         else => @compileError("Unsupported OS"),
     }
 }
 
-/// Get the directory path that contains the current executable.
+/// `selfExeDirPath` except allocates the result on the heap.
 /// Caller owns returned memory.
-pub fn selfExeDirPath(allocator: *mem.Allocator) ![]u8 {
+pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 {
+    var buf: [MAX_PATH_BYTES]u8 = undefined;
+    return mem.dupe(allocator, u8, try selfExeDirPath(&buf));
+}
+
+/// Get the directory path that contains the current executable.
+/// Returned value is a slice of out_buffer.
+pub fn selfExeDirPath(out_buffer: *[MAX_PATH_BYTES]u8) ![]const u8 {
     switch (builtin.os) {
         Os.linux => {
             // If the currently executing binary has been deleted,
             // the file path looks something like `/a/b/c/exe (deleted)`
             // This path cannot be opened, but it's valid for determining the directory
             // the executable was in when it was run.
-            const full_exe_path = try readLink(allocator, "/proc/self/exe");
-            errdefer allocator.free(full_exe_path);
-            const dir = path.dirname(full_exe_path) orelse ".";
-            return allocator.shrink(u8, full_exe_path, dir.len);
+            const full_exe_path = try readLinkC(out_buffer, c"/proc/self/exe");
+            // Assume that /proc/self/exe has an absolute path, and therefore dirname
+            // will not return null.
+            return path.dirname(full_exe_path).?;
         },
         Os.windows, Os.macosx, Os.ios => {
-            const self_exe_path = try selfExePath(allocator);
-            errdefer allocator.free(self_exe_path);
-            const dirname = os.path.dirname(self_exe_path) orelse ".";
-            return allocator.shrink(u8, self_exe_path, dirname.len);
+            const self_exe_path = try selfExePath(out_buffer);
+            // Assume that the OS APIs return absolute paths, and therefore dirname
+            // will not return null.
+            return path.dirname(self_exe_path).?;
         },
-        else => @compileError("unimplemented: std.os.selfExeDirPath for " ++ @tagName(builtin.os)),
+        else => @compileError("Unsupported OS"),
     }
 }
 
@@ -2991,7 +2971,9 @@ pub fn posixFStat(fd: i32) !posix.Stat {
     const err = posix.getErrno(posix.fstat(fd, &stat));
     if (err > 0) {
         return switch (err) {
-            posix.EBADF => error.BadFd,
+            // We do not make this an error code because if you get EBADF it's always a bug,
+            // since the fd could have been reused.
+            posix.EBADF => unreachable,
             posix.ENOMEM => error.SystemResources,
             else => os.unexpectedErrorPosix(err),
         };
std/os/path.zig
@@ -11,6 +11,7 @@ const math = std.math;
 const posix = os.posix;
 const windows = os.windows;
 const cstr = std.cstr;
+const windows_util = @import("windows/util.zig");
 
 pub const sep_windows = '\\';
 pub const sep_posix = '/';
@@ -1075,113 +1076,148 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons
     assert(mem.eql(u8, result, expected_output));
 }
 
-/// Return the canonicalized absolute pathname.
-/// Expands all symbolic links and resolves references to `.`, `..`, and
-/// extra `/` characters in ::pathname.
-/// Caller must deallocate result.
-/// TODO rename this to realAlloc and provide real with no allocator. See #1392
-pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 {
-    switch (builtin.os) {
-        Os.windows => {
-            const pathname_buf = try allocator.alloc(u8, pathname.len + 1);
-            defer allocator.free(pathname_buf);
-
-            mem.copy(u8, pathname_buf, pathname);
-            pathname_buf[pathname.len] = 0;
-
-            const h_file = windows.CreateFileA(pathname_buf.ptr, windows.GENERIC_READ, windows.FILE_SHARE_READ, null, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null);
-            if (h_file == windows.INVALID_HANDLE_VALUE) {
-                const err = windows.GetLastError();
-                return switch (err) {
-                    windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
-                    windows.ERROR.ACCESS_DENIED => error.AccessDenied,
-                    windows.ERROR.FILENAME_EXCED_RANGE => error.NameTooLong,
-                    else => os.unexpectedErrorWindows(err),
-                };
-            }
-            defer os.close(h_file);
-            var buf = try allocator.alloc(u8, 256);
-            errdefer allocator.free(buf);
-            while (true) {
-                const buf_len = math.cast(windows.DWORD, buf.len) catch return error.NameTooLong;
-                const result = windows.GetFinalPathNameByHandleA(h_file, buf.ptr, buf_len, windows.VOLUME_NAME_DOS);
-
-                if (result == 0) {
-                    const err = windows.GetLastError();
-                    return switch (err) {
-                        windows.ERROR.PATH_NOT_FOUND => error.FileNotFound,
-                        windows.ERROR.NOT_ENOUGH_MEMORY => error.OutOfMemory,
-                        windows.ERROR.INVALID_PARAMETER => unreachable,
-                        else => os.unexpectedErrorWindows(err),
-                    };
-                }
+pub const RealError = error{
+    FileNotFound,
+    AccessDenied,
+    NameTooLong,
+    NotSupported,
+    NotDir,
+    SymLinkLoop,
+    InputOutput,
+    FileTooBig,
+    IsDir,
+    ProcessFdQuotaExceeded,
+    SystemFdQuotaExceeded,
+    NoDevice,
+    SystemResources,
+    NoSpaceLeft,
+    FileSystem,
+    BadPathName,
+
+    /// On Windows, file paths must be valid Unicode.
+    InvalidUtf8,
+
+    /// TODO remove this possibility
+    PathAlreadyExists,
+
+    /// TODO remove this possibility
+    Unexpected,
+};
 
-                if (result > buf.len) {
-                    buf = try allocator.realloc(u8, buf, result);
-                    continue;
-                }
+/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
+/// Otherwise use `real` or `realC`.
+pub fn realW(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u16) RealError![]u8 {
+    const h_file = windows.CreateFileW(
+        pathname,
+        windows.GENERIC_READ,
+        windows.FILE_SHARE_READ,
+        null,
+        windows.OPEN_EXISTING,
+        windows.FILE_ATTRIBUTE_NORMAL,
+        null,
+    );
+    if (h_file == windows.INVALID_HANDLE_VALUE) {
+        const err = windows.GetLastError();
+        switch (err) {
+            windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
+            windows.ERROR.ACCESS_DENIED => return error.AccessDenied,
+            windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
+            else => return os.unexpectedErrorWindows(err),
+        }
+    }
+    defer os.close(h_file);
+    var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined;
+    const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
+    const result = windows.GetFinalPathNameByHandleW(h_file, &utf16le_buf, casted_len, windows.VOLUME_NAME_DOS);
+    assert(result <= utf16le_buf.len);
+    if (result == 0) {
+        const err = windows.GetLastError();
+        switch (err) {
+            windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
+            windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
+            windows.ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources,
+            windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
+            windows.ERROR.INVALID_PARAMETER => unreachable,
+            else => return os.unexpectedErrorWindows(err),
+        }
+    }
+    const utf16le_slice = utf16le_buf[0..result];
 
-                // windows returns \\?\ prepended to the path
-                // we strip it because nobody wants \\?\ prepended to their path
-                const final_len = x: {
-                    if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) {
-                        var i: usize = 4;
-                        while (i < result) : (i += 1) {
-                            buf[i - 4] = buf[i];
-                        }
-                        break :x result - 4;
-                    } else {
-                        break :x result;
-                    }
-                };
-
-                return allocator.shrink(u8, buf, final_len);
-            }
+    // windows returns \\?\ prepended to the path
+    // we strip it because nobody wants \\?\ prepended to their path
+    const prefix = []u16{ '\\', '\\', '?', '\\' };
+    const start_index = if (mem.startsWith(u16, utf16le_slice, prefix)) prefix.len else 0;
+
+    // Trust that Windows gives us valid UTF-16LE.
+    const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice[start_index..]) catch unreachable;
+    return out_buffer[0..end_index];
+}
+
+/// See `real`
+/// Use this when you have a null terminated pointer path.
+pub fn realC(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u8) RealError![]u8 {
+    switch (builtin.os) {
+        Os.windows => {
+            const pathname_w = try windows_util.cStrToPrefixedFileW(pathname);
+            return realW(out_buffer, pathname_w);
         },
         Os.macosx, Os.ios => {
-            // TODO instead of calling the libc function here, port the implementation
-            // to Zig, and then remove the NameTooLong error possibility.
-            const pathname_buf = try allocator.alloc(u8, pathname.len + 1);
-            defer allocator.free(pathname_buf);
-
-            const result_buf = try allocator.alloc(u8, posix.PATH_MAX);
-            errdefer allocator.free(result_buf);
-
-            mem.copy(u8, pathname_buf, pathname);
-            pathname_buf[pathname.len] = 0;
-
-            const err = posix.getErrno(posix.realpath(pathname_buf.ptr, result_buf.ptr));
-            if (err > 0) {
-                return switch (err) {
-                    posix.EINVAL => unreachable,
-                    posix.EBADF => unreachable,
-                    posix.EFAULT => unreachable,
-                    posix.EACCES => error.AccessDenied,
-                    posix.ENOENT => error.FileNotFound,
-                    posix.ENOTSUP => error.NotSupported,
-                    posix.ENOTDIR => error.NotDir,
-                    posix.ENAMETOOLONG => error.NameTooLong,
-                    posix.ELOOP => error.SymLinkLoop,
-                    posix.EIO => error.InputOutput,
-                    else => os.unexpectedErrorPosix(err),
-                };
+            // TODO instead of calling the libc function here, port the implementation to Zig
+            const err = posix.getErrno(posix.realpath(pathname, out_buffer));
+            switch (err) {
+                0 => return mem.toSlice(u8, out_buffer),
+                posix.EINVAL => unreachable,
+                posix.EBADF => unreachable,
+                posix.EFAULT => unreachable,
+                posix.EACCES => return error.AccessDenied,
+                posix.ENOENT => return error.FileNotFound,
+                posix.ENOTSUP => return error.NotSupported,
+                posix.ENOTDIR => return error.NotDir,
+                posix.ENAMETOOLONG => return error.NameTooLong,
+                posix.ELOOP => return error.SymLinkLoop,
+                posix.EIO => return error.InputOutput,
+                else => return os.unexpectedErrorPosix(err),
             }
-            return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr));
         },
         Os.linux => {
-            const fd = try os.posixOpen(allocator, pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);
+            const fd = try os.posixOpenC(pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);
             defer os.close(fd);
 
             var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;
-            const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd) catch unreachable;
+            const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}\x00", fd) catch unreachable;
 
-            return os.readLink(allocator, proc_path);
+            return os.readLinkC(out_buffer, proc_path.ptr);
         },
         else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)),
     }
 }
 
+/// Return the canonicalized absolute pathname.
+/// Expands all symbolic links and resolves references to `.`, `..`, and
+/// extra `/` characters in ::pathname.
+/// The return value is a slice of out_buffer, and not necessarily from the beginning.
+pub fn real(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: []const u8) RealError![]u8 {
+    switch (builtin.os) {
+        Os.windows => {
+            const pathname_w = try windows_util.sliceToPrefixedFileW(pathname);
+            return realW(out_buffer, &pathname_w);
+        },
+        Os.macosx, Os.ios, Os.linux => {
+            const pathname_c = try os.toPosixPath(pathname);
+            return realC(out_buffer, &pathname_c);
+        },
+        else => @compileError("Unsupported OS"),
+    }
+}
+
+/// `real`, except caller must free the returned memory.
+pub fn realAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 {
+    var buf: [os.MAX_PATH_BYTES]u8 = undefined;
+    return mem.dupe(allocator, u8, try real(&buf, pathname));
+}
+
 test "os.path.real" {
     // at least call it so it gets compiled
-    _ = real(debug.global_allocator, "some_path");
+    var buf: [os.MAX_PATH_BYTES]u8 = undefined;
+    std.debug.assertError(real(&buf, "definitely_bogus_does_not_exist1234"), error.FileNotFound);
 }
std/os/test.zig
@@ -17,7 +17,7 @@ test "makePath, put some files in it, deleteTree" {
     if (os.Dir.open(a, "os_test_tmp")) |dir| {
         @panic("expected error");
     } else |err| {
-        assert(err == error.PathNotFound);
+        assert(err == error.FileNotFound);
     }
 }
 
std/build.zig
@@ -1491,11 +1491,14 @@ pub const LibExeObjStep = struct {
                     }
 
                     if (!is_darwin) {
-                        const rpath_arg = builder.fmt("-Wl,-rpath,{}", os.path.real(builder.allocator, builder.pathFromRoot(builder.cache_root)) catch unreachable);
+                        const rpath_arg = builder.fmt("-Wl,-rpath,{}", try os.path.realAlloc(
+                            builder.allocator,
+                            builder.pathFromRoot(builder.cache_root),
+                        ));
                         defer builder.allocator.free(rpath_arg);
-                        cc_args.append(rpath_arg) catch unreachable;
+                        try cc_args.append(rpath_arg);
 
-                        cc_args.append("-rdynamic") catch unreachable;
+                        try cc_args.append("-rdynamic");
                     }
 
                     for (self.full_path_libs.toSliceConst()) |full_path_lib| {
@@ -1566,11 +1569,14 @@ pub const LibExeObjStep = struct {
                 cc_args.append("-o") catch unreachable;
                 cc_args.append(output_path) catch unreachable;
 
-                const rpath_arg = builder.fmt("-Wl,-rpath,{}", os.path.real(builder.allocator, builder.pathFromRoot(builder.cache_root)) catch unreachable);
+                const rpath_arg = builder.fmt("-Wl,-rpath,{}", try os.path.realAlloc(
+                    builder.allocator,
+                    builder.pathFromRoot(builder.cache_root),
+                ));
                 defer builder.allocator.free(rpath_arg);
-                cc_args.append(rpath_arg) catch unreachable;
+                try cc_args.append(rpath_arg);
 
-                cc_args.append("-rdynamic") catch unreachable;
+                try cc_args.append("-rdynamic");
 
                 {
                     var it = self.link_libs.iterator();
std/unicode.zig
@@ -495,7 +495,7 @@ pub fn utf16leToUtf8Alloc(allocator: *mem.Allocator, utf16le: []const u16) ![]u8
 }
 
 /// Asserts that the output buffer is big enough.
-/// Returns end index.
+/// Returns end byte index into utf8.
 pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !usize {
     var end_index: usize = 0;
     var it = Utf16LeIterator.init(utf16le);
test/cases/merge_error_sets.zig
@@ -1,5 +1,5 @@
 const A = error{
-    PathNotFound,
+    FileNotFound,
     NotDir,
 };
 const B = error{OutOfMemory};
@@ -15,7 +15,7 @@ test "merge error sets" {
         @panic("unexpected");
     } else |err| switch (err) {
         error.OutOfMemory => @panic("unexpected"),
-        error.PathNotFound => @panic("unexpected"),
+        error.FileNotFound => @panic("unexpected"),
         error.NotDir => {},
     }
 }