Commit bda5539e9d

Andrew Kelley <superjoe30@gmail.com>
2018-08-21 06:46:42
*WIP* std.os assumes comptime-known max path size
this allows us to remove the requirement of allocators for a lot of functions See #1392
1 parent 3029363
std/debug/index.zig
@@ -340,7 +340,7 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) !*ElfStackTrace {
     }
 }
 
-fn printLineFromFile(allocator: *mem.Allocator, out_stream: var, line_info: *const LineInfo) !void {
+fn printLineFromFile(out_stream: var, line_info: *const LineInfo) !void {
     var f = try os.File.openRead(line_info.file_name);
     defer f.close();
     // TODO fstat and make sure that the file has the correct size
std/os/windows/kernel32.zig
@@ -1,6 +1,5 @@
 use @import("index.zig");
 
-
 pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL;
 
 pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
@@ -74,7 +73,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;
 
 pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL;
 
-pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) DWORD;
+pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?[*]CHAR) DWORD;
+pub extern "kernel32" stdcallcc fn GetCurrentDirectoryW(nBufferLength: WORD, lpBuffer: ?[*]WCHAR) DWORD;
 
 pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE;
 pub extern "kernel32" stdcallcc fn GetCurrentThreadId() DWORD;
@@ -107,7 +107,6 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
     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;
@@ -194,7 +193,6 @@ pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;
 
 pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;
 
-
 pub const FILE_NOTIFY_INFORMATION = extern struct {
     NextEntryOffset: DWORD,
     Action: DWORD,
@@ -208,7 +206,7 @@ pub const FILE_ACTION_MODIFIED = 0x00000003;
 pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;
 pub const FILE_ACTION_RENAMED_NEW_NAME = 0x00000005;
 
-pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?extern fn(DWORD, DWORD, *OVERLAPPED) void;
+pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?extern fn (DWORD, DWORD, *OVERLAPPED) void;
 
 pub const FILE_LIST_DIRECTORY = 1;
 
std/os/windows/util.zig
@@ -7,6 +7,8 @@ const mem = std.mem;
 const BufMap = std.BufMap;
 const cstr = std.cstr;
 
+pub const PATH_MAX_UTF16 = 32767;
+
 pub const WaitError = error{
     WaitAbandoned,
     WaitTimeOut,
@@ -90,36 +92,17 @@ pub const OpenError = error{
     AccessDenied,
     PipeBusy,
     Unexpected,
-    OutOfMemory,
 };
 
 /// `file_path` needs to be copied in memory to add a null terminating byte, hence the allocator.
 pub fn windowsOpen(
-    allocator: *mem.Allocator,
     file_path: []const u8,
     desired_access: windows.DWORD,
     share_mode: windows.DWORD,
     creation_disposition: windows.DWORD,
     flags_and_attrs: windows.DWORD,
 ) OpenError!windows.HANDLE {
-    const path_with_null = try cstr.addNullByte(allocator, file_path);
-    defer allocator.free(path_with_null);
-
-    const result = windows.CreateFileA(path_with_null.ptr, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null);
-
-    if (result == windows.INVALID_HANDLE_VALUE) {
-        const err = windows.GetLastError();
-        return switch (err) {
-            windows.ERROR.SHARING_VIOLATION => OpenError.SharingViolation,
-            windows.ERROR.ALREADY_EXISTS, windows.ERROR.FILE_EXISTS => OpenError.PathAlreadyExists,
-            windows.ERROR.FILE_NOT_FOUND => OpenError.FileNotFound,
-            windows.ERROR.ACCESS_DENIED => OpenError.AccessDenied,
-            windows.ERROR.PIPE_BUSY => OpenError.PipeBusy,
-            else => os.unexpectedErrorWindows(err),
-        };
-    }
-
-    return result;
+    @compileError("TODO rewrite with CreateFileW and no allocator");
 }
 
 /// Caller must free result.
@@ -238,7 +221,7 @@ pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_
     }
 }
 
-pub const WindowsWaitResult = enum{
+pub const WindowsWaitResult = enum {
     Normal,
     Aborted,
     Cancelled,
@@ -254,7 +237,7 @@ pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_t
                 if (std.debug.runtime_safety) {
                     std.debug.panic("unexpected error: {}\n", err);
                 }
-            }
+            },
         }
     }
     return WindowsWaitResult.Normal;
std/os/file.zig
@@ -27,7 +27,6 @@ pub const File = struct {
 
     pub const OpenError = os.WindowsOpenError || os.PosixOpenError;
 
-    /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
     /// Call close to clean up.
     pub fn openRead(path: []const u8) OpenError!File {
         if (is_posix) {
@@ -49,15 +48,14 @@ pub const File = struct {
     }
 
     /// Calls `openWriteMode` with os.File.default_mode for the mode.
-    pub fn openWrite(allocator: *mem.Allocator, path: []const u8) OpenError!File {
-        return openWriteMode(allocator, path, os.File.default_mode);
+    pub fn openWrite(path: []const u8) OpenError!File {
+        return openWriteMode(path, os.File.default_mode);
     }
 
     /// If the path does not exist it will be created.
     /// If a file already exists in the destination it will be truncated.
-    /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
     /// Call close to clean up.
-    pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {
+    pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File {
         if (is_posix) {
             const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
             const fd = try os.posixOpen(path, flags, file_mode);
@@ -78,16 +76,14 @@ pub const File = struct {
 
     /// If the path does not exist it will be created.
     /// If a file already exists in the destination this returns OpenError.PathAlreadyExists
-    /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
     /// Call close to clean up.
-    pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {
+    pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
         if (is_posix) {
             const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;
-            const fd = try os.posixOpen(allocator, path, flags, file_mode);
+            const fd = try os.posixOpen(path, flags, file_mode);
             return openHandle(fd);
         } else if (is_windows) {
             const handle = try os.windowsOpen(
-                allocator,
                 path,
                 windows.GENERIC_WRITE,
                 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
@@ -117,12 +113,13 @@ pub const File = struct {
         Unexpected,
     };
 
-    pub fn access(allocator: *mem.Allocator, path: []const u8) AccessError!void {
-        const path_with_null = try std.cstr.addNullByte(allocator, path);
-        defer allocator.free(path_with_null);
-
+    pub fn accessC(path: [*]const u8) AccessError!void {
+        if (is_windows) {
+            // this needs to convert to UTF-16LE and call accessW
+            @compileError("TODO support windows");
+        }
         if (is_posix) {
-            const result = posix.access(path_with_null.ptr, posix.F_OK);
+            const result = posix.access(path, posix.F_OK);
             const err = posix.getErrno(result);
             switch (err) {
                 0 => return,
@@ -141,7 +138,7 @@ pub const File = struct {
                 else => return os.unexpectedErrorPosix(err),
             }
         } else if (is_windows) {
-            if (os.windows.GetFileAttributesA(path_with_null.ptr) != os.windows.INVALID_FILE_ATTRIBUTES) {
+            if (os.windows.GetFileAttributesA(path) != os.windows.INVALID_FILE_ATTRIBUTES) {
                 return;
             }
 
@@ -158,6 +155,21 @@ pub const File = struct {
         }
     }
 
+    pub fn access(path: []const u8) AccessError!void {
+        if (is_windows) {
+            // this needs to convert to UTF-16LE and call accessW
+            @compileError("TODO support windows");
+        }
+        if (is_posix) {
+            var path_with_null: [posix.PATH_MAX]u8 = undefined;
+            if (path.len >= posix.PATH_MAX) return error.NameTooLong;
+            mem.copy(u8, path_with_null[0..], path);
+            path_with_null[path.len] = 0;
+            return accessC(&path_with_null);
+        }
+        @compileError("TODO implement access for this OS");
+    }
+
     /// Upon success, the stream is in an uninitialized state. To continue using it,
     /// you must use the open() function.
     pub fn close(self: *File) void {
std/os/index.zig
@@ -39,11 +39,14 @@ pub const File = @import("file.zig").File;
 pub const time = @import("time.zig");
 
 pub const page_size = 4 * 1024;
-pub const PATH_MAX = switch (builtin.os) {
-    Os.linux => linux.PATH_MAX,
-    Os.macosx, Os.ios => darwin.PATH_MAX,
+pub const MAX_PATH_BYTES = switch (builtin.os) {
+    Os.linux, Os.macosx, Os.ios => posix.PATH_MAX,
+    // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
+    // If it would require 4 UTF-8 bytes, then there would be a surrogate
+    // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
+    // +1 for the null byte at the end, which can be encoded in 1 byte.
+    Os.windows => 32767 * 3 + 1,
     else => @compileError("Unsupported OS"),
-    // https://msdn.microsoft.com/en-us/library/930f87yf.aspx
 };
 
 pub const UserInfo = @import("get_user_id.zig").UserInfo;
@@ -423,7 +426,6 @@ pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, off
 }
 
 pub const PosixOpenError = error{
-    OutOfMemory,
     AccessDenied,
     FileTooBig,
     IsDir,
@@ -444,12 +446,10 @@ 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: [PATH_MAX]u8 = undefined;
-    if (file_path.len > PATH_MAX - 1)
-        return error.NameTooLong;
-    mem.copy(u8, path_with_null[0..PATH_MAX - 1], file_path);
-    path_with_null[file_path.len] = '\x00';
-
+    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);
 }
 
@@ -728,43 +728,35 @@ pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwned
 }
 
 /// Caller must free the returned memory.
-pub fn getCwd(allocator: *Allocator) ![]u8 {
-    switch (builtin.os) {
-        Os.windows => {
-            var buf = try allocator.alloc(u8, 256);
-            errdefer allocator.free(buf);
-
-            while (true) {
-                const result = windows.GetCurrentDirectoryA(@intCast(windows.WORD, buf.len), buf.ptr);
+pub fn getCwdAlloc(allocator: *Allocator) ![]u8 {
+    var buf: [MAX_PATH_BYTES]u8 = undefined;
+    return mem.dupe(allocator, u8, try getCwd(&buf));
+}
 
-                if (result == 0) {
-                    const err = windows.GetLastError();
-                    return switch (err) {
-                        else => unexpectedErrorWindows(err),
-                    };
-                }
+pub const GetCwdError = error{Unexpected};
 
-                if (result > buf.len) {
-                    buf = try allocator.realloc(u8, buf, result);
-                    continue;
+/// The result is a slice of out_buffer.
+pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) GetCwdError![]u8 {
+    switch (builtin.os) {
+        Os.windows => {
+            var utf16le_buf: [windows_util.PATH_MAX_UTF16]u16 = undefined;
+            const result = windows.GetCurrentDirectoryW(utf16le_buf.len, &utf16le_buf);
+            if (result == 0) {
+                const err = windows.GetLastError();
+                switch (err) {
+                    else => return unexpectedErrorWindows(err),
                 }
-
-                return allocator.shrink(u8, buf, result);
             }
+            assert(result <= buf.len);
+            const utf16le_slice = utf16le_buf[0..result];
+            return std.unicode.utf16leToUtf8(out_buffer, utf16le_buf);
         },
         else => {
-            var buf = try allocator.alloc(u8, 1024);
-            errdefer allocator.free(buf);
-            while (true) {
-                const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len));
-                if (err == posix.ERANGE) {
-                    buf = try allocator.realloc(u8, buf, buf.len * 2);
-                    continue;
-                } else if (err > 0) {
-                    return unexpectedErrorPosix(err);
-                }
-
-                return allocator.shrink(u8, buf, cstr.len(buf.ptr));
+            const err = posix.getErrno(posix.getcwd(out_buffer, out_buffer.len));
+            switch (err) {
+                0 => return cstr.toSlice(out_buffer),
+                posix.ERANGE => unreachable,
+                else => return unexpectedErrorPosix(err),
             }
         },
     }
@@ -899,56 +891,45 @@ pub const DeleteFileError = error{
     Unexpected,
 };
 
-pub fn deleteFile(allocator: *Allocator, file_path: []const u8) DeleteFileError!void {
+pub fn deleteFile(file_path: []const u8) DeleteFileError!void {
     if (builtin.os == Os.windows) {
-        return deleteFileWindows(allocator, file_path);
+        return deleteFileWindows(file_path);
     } else {
-        return deleteFilePosix(allocator, file_path);
+        return deleteFilePosix(file_path);
     }
 }
 
-pub fn deleteFileWindows(allocator: *Allocator, file_path: []const u8) !void {
-    const buf = try allocator.alloc(u8, file_path.len + 1);
-    defer allocator.free(buf);
-
-    mem.copy(u8, buf, file_path);
-    buf[file_path.len] = 0;
+pub fn deleteFileWindows(file_path: []const u8) !void {
+    @compileError("TODO rewrite with DeleteFileW and no allocator");
+}
 
-    if (windows.DeleteFileA(buf.ptr) == 0) {
-        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, windows.ERROR.INVALID_PARAMETER => error.NameTooLong,
-            else => unexpectedErrorWindows(err),
-        };
+pub fn deleteFilePosixC(file_path: [*]const u8) !void {
+    const err = posix.getErrno(posix.unlink(file_path));
+    switch (err) {
+        0 => return,
+        posix.EACCES => return error.AccessDenied,
+        posix.EPERM => return error.AccessDenied,
+        posix.EBUSY => return error.FileBusy,
+        posix.EFAULT => unreachable,
+        posix.EINVAL => unreachable,
+        posix.EIO => return error.FileSystem,
+        posix.EISDIR => return error.IsDir,
+        posix.ELOOP => return error.SymLinkLoop,
+        posix.ENAMETOOLONG => return error.NameTooLong,
+        posix.ENOENT => return error.FileNotFound,
+        posix.ENOTDIR => return error.NotDir,
+        posix.ENOMEM => return error.SystemResources,
+        posix.EROFS => return error.ReadOnlyFileSystem,
+        else => return unexpectedErrorPosix(err),
     }
 }
 
-pub fn deleteFilePosix(allocator: *Allocator, file_path: []const u8) !void {
-    const buf = try allocator.alloc(u8, file_path.len + 1);
-    defer allocator.free(buf);
-
-    mem.copy(u8, buf, file_path);
-    buf[file_path.len] = 0;
-
-    const err = posix.getErrno(posix.unlink(buf.ptr));
-    if (err > 0) {
-        return switch (err) {
-            posix.EACCES, posix.EPERM => error.AccessDenied,
-            posix.EBUSY => error.FileBusy,
-            posix.EFAULT, posix.EINVAL => unreachable,
-            posix.EIO => error.FileSystem,
-            posix.EISDIR => error.IsDir,
-            posix.ELOOP => error.SymLinkLoop,
-            posix.ENAMETOOLONG => error.NameTooLong,
-            posix.ENOENT => error.FileNotFound,
-            posix.ENOTDIR => error.NotDir,
-            posix.ENOMEM => error.SystemResources,
-            posix.EROFS => error.ReadOnlyFileSystem,
-            else => unexpectedErrorPosix(err),
-        };
-    }
+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);
 }
 
 /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
@@ -956,6 +937,7 @@ pub fn deleteFilePosix(allocator: *Allocator, file_path: []const u8) !void {
 /// there is a possibility of power loss or application termination leaving temporary files present
 /// in the same directory as dest_path.
 /// Destination file will have the same mode as the source file.
+/// TODO investigate if this can work with no allocator
 pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []const u8) !void {
     var in_file = try os.File.openRead(source_path);
     defer in_file.close();
@@ -978,6 +960,7 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con
 /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
 /// merged and readily available,
 /// there is a possibility of power loss or application termination leaving temporary files present
+/// TODO investigate if this can work with no allocator
 pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {
     var in_file = try os.File.openRead(source_path);
     defer in_file.close();
@@ -996,6 +979,7 @@ pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: [
 }
 
 pub const AtomicFile = struct {
+    /// TODO investigate if we can make this work with no allocator
     allocator: *Allocator,
     file: os.File,
     tmp_path: []u8,
@@ -1023,7 +1007,7 @@ pub const AtomicFile = struct {
             try getRandomBytes(rand_buf[0..]);
             b64_fs_encoder.encode(tmp_path[dirname_component_len..], rand_buf);
 
-            const file = os.File.openWriteNoClobber(allocator, tmp_path, mode) catch |err| switch (err) {
+            const file = os.File.openWriteNoClobber(tmp_path, mode) catch |err| switch (err) {
                 error.PathAlreadyExists => continue,
                 // TODO zig should figure out that this error set does not include PathAlreadyExists since
                 // it is handled in the above switch
@@ -1059,56 +1043,59 @@ pub const AtomicFile = struct {
     }
 };
 
-pub fn rename(allocator: *Allocator, old_path: []const u8, new_path: []const u8) !void {
-    const full_buf = try allocator.alloc(u8, old_path.len + new_path.len + 2);
-    defer allocator.free(full_buf);
-
-    const old_buf = full_buf;
-    mem.copy(u8, old_buf, old_path);
-    old_buf[old_path.len] = 0;
-
-    const new_buf = full_buf[old_path.len + 1 ..];
-    mem.copy(u8, new_buf, new_path);
-    new_buf[new_path.len] = 0;
-
+pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void {
     if (is_windows) {
-        const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
-        if (windows.MoveFileExA(old_buf.ptr, new_buf.ptr, flags) == 0) {
-            const err = windows.GetLastError();
-            return switch (err) {
-                else => unexpectedErrorWindows(err),
-            };
-        }
+        @compileError("TODO implement for windows");
     } else {
-        const err = posix.getErrno(posix.rename(old_buf.ptr, new_buf.ptr));
-        if (err > 0) {
-            return switch (err) {
-                posix.EACCES, posix.EPERM => error.AccessDenied,
-                posix.EBUSY => error.FileBusy,
-                posix.EDQUOT => error.DiskQuota,
-                posix.EFAULT, posix.EINVAL => unreachable,
-                posix.EISDIR => error.IsDir,
-                posix.ELOOP => error.SymLinkLoop,
-                posix.EMLINK => error.LinkQuotaExceeded,
-                posix.ENAMETOOLONG => error.NameTooLong,
-                posix.ENOENT => error.FileNotFound,
-                posix.ENOTDIR => error.NotDir,
-                posix.ENOMEM => error.SystemResources,
-                posix.ENOSPC => error.NoSpaceLeft,
-                posix.EEXIST, posix.ENOTEMPTY => error.PathAlreadyExists,
-                posix.EROFS => error.ReadOnlyFileSystem,
-                posix.EXDEV => error.RenameAcrossMountPoints,
-                else => unexpectedErrorPosix(err),
-            };
+        const err = posix.getErrno(posix.rename(old_path, new_path));
+        switch (err) {
+            0 => return,
+            posix.EACCES => return error.AccessDenied,
+            posix.EPERM => return error.AccessDenied,
+            posix.EBUSY => return error.FileBusy,
+            posix.EDQUOT => return error.DiskQuota,
+            posix.EFAULT => unreachable,
+            posix.EINVAL => unreachable,
+            posix.EISDIR => return error.IsDir,
+            posix.ELOOP => return error.SymLinkLoop,
+            posix.EMLINK => return error.LinkQuotaExceeded,
+            posix.ENAMETOOLONG => return error.NameTooLong,
+            posix.ENOENT => return error.FileNotFound,
+            posix.ENOTDIR => return error.NotDir,
+            posix.ENOMEM => return error.SystemResources,
+            posix.ENOSPC => return error.NoSpaceLeft,
+            posix.EEXIST => return error.PathAlreadyExists,
+            posix.ENOTEMPTY => return error.PathAlreadyExists,
+            posix.EROFS => return error.ReadOnlyFileSystem,
+            posix.EXDEV => return error.RenameAcrossMountPoints,
+            else => return unexpectedErrorPosix(err),
         }
     }
 }
 
-pub fn makeDir(allocator: *Allocator, dir_path: []const u8) !void {
+pub fn rename(old_path: []const u8, new_path: []const u8) !void {
+    if (is_windows) {
+        @compileError("TODO rewrite with MoveFileExW and no allocator");
+    } 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);
+    }
+}
+
+pub fn makeDir(dir_path: []const u8) !void {
     if (is_windows) {
-        return makeDirWindows(allocator, dir_path);
+        return makeDirWindows(dir_path);
     } else {
-        return makeDirPosix(allocator, dir_path);
+        return makeDirPosix(dir_path);
     }
 }
 
@@ -1126,30 +1113,35 @@ pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {
     }
 }
 
-pub fn makeDirPosix(allocator: *Allocator, dir_path: []const u8) !void {
-    const path_buf = try cstr.addNullByte(allocator, dir_path);
-    defer allocator.free(path_buf);
-
+pub fn makeDirPosixC(dir_path: [*]const u8) !void {
     const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755));
-    if (err > 0) {
-        return switch (err) {
-            posix.EACCES, posix.EPERM => error.AccessDenied,
-            posix.EDQUOT => error.DiskQuota,
-            posix.EEXIST => error.PathAlreadyExists,
-            posix.EFAULT => unreachable,
-            posix.ELOOP => error.SymLinkLoop,
-            posix.EMLINK => error.LinkQuotaExceeded,
-            posix.ENAMETOOLONG => error.NameTooLong,
-            posix.ENOENT => error.FileNotFound,
-            posix.ENOMEM => error.SystemResources,
-            posix.ENOSPC => error.NoSpaceLeft,
-            posix.ENOTDIR => error.NotDir,
-            posix.EROFS => error.ReadOnlyFileSystem,
-            else => unexpectedErrorPosix(err),
-        };
+    switch (err) {
+        0 => return,
+        posix.EACCES => return error.AccessDenied,
+        posix.EPERM => return error.AccessDenied,
+        posix.EDQUOT => return error.DiskQuota,
+        posix.EEXIST => return error.PathAlreadyExists,
+        posix.EFAULT => unreachable,
+        posix.ELOOP => return error.SymLinkLoop,
+        posix.EMLINK => return error.LinkQuotaExceeded,
+        posix.ENAMETOOLONG => return error.NameTooLong,
+        posix.ENOENT => return error.FileNotFound,
+        posix.ENOMEM => return error.SystemResources,
+        posix.ENOSPC => return error.NoSpaceLeft,
+        posix.ENOTDIR => return error.NotDir,
+        posix.EROFS => return error.ReadOnlyFileSystem,
+        else => return unexpectedErrorPosix(err),
     }
 }
 
+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);
+}
+
 /// Calls makeDir recursively to make an entire path. Returns success if the path
 /// already exists and is a directory.
 pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {
@@ -1409,6 +1401,7 @@ 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,
@@ -1420,6 +1413,7 @@ pub const Dir = struct {
                 },
                 Os.linux => Handle{
                     .fd = try posixOpen(
+                        allocator,
                         dir_path,
                         posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC,
                         0,
@@ -1616,39 +1610,35 @@ pub fn changeCurDir(allocator: *Allocator, dir_path: []const u8) !void {
 }
 
 /// Read value of a symbolic link.
-pub fn readLink(allocator: *Allocator, file_path: []const u8) ![]u8 {
-    var path_with_null: [PATH_MAX]u8 = undefined;
-    if (file_path.len > PATH_MAX - 1)
-        return error.NameTooLong;
-    mem.copy(u8, path_with_null[0..PATH_MAX - 1], file_path);
-    path_with_null[file_path.len] = '\x00';
-
-    var result_buf = try allocator.alloc(u8, 1024);
-    errdefer allocator.free(result_buf);
-    while (true) {
-        const ret_val = posix.readlink(&path_with_null, result_buf.ptr, result_buf.len);
-        const err = posix.getErrno(ret_val);
-        if (err > 0) {
-            return switch (err) {
-                posix.EACCES => error.AccessDenied,
-                posix.EFAULT, posix.EINVAL => unreachable,
-                posix.EIO => error.FileSystem,
-                posix.ELOOP => error.SymLinkLoop,
-                posix.ENAMETOOLONG => error.NameTooLong,
-                posix.ENOENT => error.FileNotFound,
-                posix.ENOMEM => error.SystemResources,
-                posix.ENOTDIR => error.NotDir,
-                else => unexpectedErrorPosix(err),
-            };
-        }
-        if (ret_val == result_buf.len) {
-            result_buf = try allocator.realloc(u8, result_buf, result_buf.len * 2);
-            continue;
-        }
-        return allocator.shrink(u8, result_buf, ret_val);
+/// The return value is a slice of out_buffer.
+pub fn readLinkC(pathname: [*]const u8, out_buffer: *[posix.PATH_MAX]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.EFAULT => unreachable,
+        posix.EINVAL => unreachable,
+        posix.EIO => return error.FileSystem,
+        posix.ELOOP => return error.SymLinkLoop,
+        posix.ENAMETOOLONG => unreachable, // out_buffer is at least PATH_MAX
+        posix.ENOENT => return error.FileNotFound,
+        posix.ENOMEM => return error.SystemResources,
+        posix.ENOTDIR => return error.NotDir,
+        else => return unexpectedErrorPosix(err),
     }
 }
 
+/// 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 posix_setuid(uid: u32) !void {
     const err = posix.getErrno(posix.setuid(uid));
     if (err == 0) return;
@@ -2035,13 +2025,13 @@ pub fn openSelfExe() !os.File {
             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(proc_file_path);
+            return os.File.openRead(&fixed_allocator.allocator, proc_file_path);
         },
         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(self_exe_path);
+            return os.File.openRead(&fixed_allocator.allocator, self_exe_path);
         },
         else => @compileError("Unsupported OS"),
     }
std/os/path.zig
@@ -573,7 +573,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
         result_index += 1;
     }
 
-    return result[0..result_index];
+    return allocator.shrink(u8, result, result_index);
 }
 
 test "os.path.resolve" {
@@ -1077,6 +1077,7 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons
 /// 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 => {
@@ -1166,7 +1167,7 @@ pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 {
             return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr));
         },
         Os.linux => {
-            const fd = try os.posixOpen(pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);
+            const fd = try os.posixOpen(allocator, pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);
             defer os.close(fd);
 
             var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;
std/io_test.zig
@@ -16,7 +16,7 @@ test "write a file, read it, then delete it" {
     prng.random.bytes(data[0..]);
     const tmp_file_name = "temp_test_file.txt";
     {
-        var file = try os.File.openWrite(allocator, tmp_file_name);
+        var file = try os.File.openWrite(tmp_file_name);
         defer file.close();
 
         var file_out_stream = io.FileOutStream.init(&file);
@@ -63,7 +63,7 @@ test "BufferOutStream" {
 }
 
 test "SliceInStream" {
-    const bytes = []const u8 { 1, 2, 3, 4, 5, 6, 7 };
+    const bytes = []const u8{ 1, 2, 3, 4, 5, 6, 7 };
     var ss = io.SliceInStream.init(bytes);
 
     var dest: [4]u8 = undefined;
@@ -81,7 +81,7 @@ test "SliceInStream" {
 }
 
 test "PeekStream" {
-    const bytes = []const u8 { 1, 2, 3, 4, 5, 6, 7, 8 };
+    const bytes = []const u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
     var ss = io.SliceInStream.init(bytes);
     var ps = io.PeekStream(2, io.SliceInStream.Error).init(&ss.stream);
 
std/unicode.zig
@@ -218,7 +218,6 @@ const Utf8Iterator = struct {
         }
 
         const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable;
-
         it.i += cp_len;
         return it.bytes[it.i - cp_len .. it.i];
     }
@@ -236,6 +235,34 @@ const Utf8Iterator = struct {
     }
 };
 
+pub const Utf16LeIterator = struct {
+    bytes: []const u8,
+    i: usize,
+
+    pub fn init(s: []const u16) Utf16LeIterator {
+        return Utf16LeIterator{
+            .bytes = @sliceToBytes(s),
+            .i = 0,
+        };
+    }
+
+    pub fn nextCodepoint(it: *Utf16LeIterator) !?u32 {
+        const c0: u32 = mem.readIntLE(u16, it.bytes[it.i .. it.i + 2]);
+        if (c0 & ~u32(0x03ff) == 0xd800) {
+            // surrogate pair
+            it.i += 2;
+            if (it.i >= it.bytes.len) return error.DanglingSurrogateHalf;
+            const c1: u32 = mem.readIntLE(u16, it.bytes[it.i .. it.i + 2]);
+            if (c1 & ~u32(0x03ff) != 0xdc00) return error.ExpectedSecondSurrogateHalf;
+            return 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff));
+        } else if (c0 & ~u32(0x03ff) == 0xdc00) {
+            return error.UnexpectedSecondSurrogateHalf;
+        } else {
+            return c0;
+        }
+    }
+};
+
 test "utf8 encode" {
     comptime testUtf8Encode() catch unreachable;
     try testUtf8Encode();
@@ -446,42 +473,34 @@ fn testDecode(bytes: []const u8) !u32 {
     return utf8Decode(bytes);
 }
 
-// TODO: make this API on top of a non-allocating Utf16LeView
-pub fn utf16leToUtf8(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 {
+/// Caller must free returned memory.
+pub fn utf16leToUtf8Alloc(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 {
     var result = std.ArrayList(u8).init(allocator);
     // optimistically guess that it will all be ascii.
     try result.ensureCapacity(utf16le.len);
-
-    const utf16le_as_bytes = @sliceToBytes(utf16le);
-    var i: usize = 0;
     var out_index: usize = 0;
-    while (i < utf16le_as_bytes.len) : (i += 2) {
-        // decode
-        const c0: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]);
-        var codepoint: u32 = undefined;
-        if (c0 & ~u32(0x03ff) == 0xd800) {
-            // surrogate pair
-            i += 2;
-            if (i >= utf16le_as_bytes.len) return error.DanglingSurrogateHalf;
-            const c1: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]);
-            if (c1 & ~u32(0x03ff) != 0xdc00) return error.ExpectedSecondSurrogateHalf;
-            codepoint = 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff));
-        } else if (c0 & ~u32(0x03ff) == 0xdc00) {
-            return error.UnexpectedSecondSurrogateHalf;
-        } else {
-            codepoint = c0;
-        }
-
-        // encode
+    var it = Utf16LeIterator.init(utf16le);
+    while (try it.nextCodepoint()) |codepoint| {
         const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
         try result.resize(result.len + utf8_len);
-        _ = utf8Encode(codepoint, result.items[out_index..]) catch unreachable;
+        assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len);
         out_index += utf8_len;
     }
 
     return result.toOwnedSlice();
 }
 
+pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !void {
+    var out_index: usize = 0;
+    var it = Utf16LeIterator.init(utf16le);
+    while (try it.nextCodepoint()) |codepoint| {
+        const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
+        try result.resize(result.len + utf8_len);
+        assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len);
+        out_index += utf8_len;
+    }
+}
+
 test "utf16leToUtf8" {
     var utf16le: [2]u16 = undefined;
     const utf16le_as_bytes = @sliceToBytes(utf16le[0..]);