Commit c4262da8de

Andrew Kelley <superjoe30@gmail.com>
2017-10-09 20:21:35
implement os.path.real for windows and update allocator interface
1 parent a4310cf
std/os/windows/index.zig
@@ -1,5 +1,11 @@
 pub const ERROR = @import("error.zig");
 
+pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) -> BOOL;
+
+pub extern "kernel32" stdcallcc fn CreateFileA(lpFileName: LPCSTR, dwDesiredAccess: DWORD,
+    dwShareMode: DWORD, lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, dwCreationDisposition: DWORD,
+        dwFlagsAndAttributes: DWORD, hTemplateFile: ?HANDLE) -> HANDLE;
+
 pub extern "kernel32" stdcallcc fn CryptAcquireContext(phProv: &HCRYPTPROV, pszContainer: LPCTSTR,
     pszProvider: LPCTSTR, dwProvType: DWORD, dwFlags: DWORD) -> bool;
 
@@ -26,6 +32,9 @@ pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(in_hFile: HANDLE
     in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, out_lpFileInformation: &c_void,
     in_dwBufferSize: DWORD) -> bool;
 
+pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(hFile: HANDLE, lpszFilePath: LPSTR,
+  cchFilePath: DWORD, dwFlags: DWORD) -> DWORD;
+
 /// Retrieves a handle to the specified standard device (standard input, standard output, or standard error).
 pub extern "kernel32" stdcallcc fn GetStdHandle(in_nStdHandle: DWORD) -> ?HANDLE;
 
@@ -131,3 +140,53 @@ pub const FILE_NAME_INFO = extern struct {
     FileNameLength: DWORD,
     FileName: [1]WCHAR,
 };
+
+
+/// Return the normalized drive name. This is the default.
+pub const FILE_NAME_NORMALIZED = 0x0;
+/// Return the opened file name (not normalized).
+pub const FILE_NAME_OPENED = 0x8;
+
+/// Return the path with the drive letter. This is the default.
+pub const VOLUME_NAME_DOS = 0x0;
+/// Return the path with a volume GUID path instead of the drive name.
+pub const VOLUME_NAME_GUID = 0x1;
+/// Return the path with no drive information.
+pub const VOLUME_NAME_NONE = 0x4;
+/// Return the path with the volume device path.
+pub const VOLUME_NAME_NT = 0x2;
+
+
+pub const SECURITY_ATTRIBUTES = extern struct {
+    nLength: DWORD,
+    lpSecurityDescriptor: LPVOID,
+    bInheritHandle: BOOL,
+};
+pub const PSECURITY_ATTRIBUTES = &SECURITY_ATTRIBUTES;
+pub const LPSECURITY_ATTRIBUTES = &SECURITY_ATTRIBUTES;
+
+
+pub const GENERIC_READ = 0x80000000;
+pub const GENERIC_WRITE = 0x40000000;
+pub const GENERIC_EXECUTE = 0x20000000;
+pub const GENERIC_ALL = 0x10000000;
+
+pub const FILE_SHARE_DELETE = 0x00000004;
+pub const FILE_SHARE_READ = 0x00000001;
+pub const FILE_SHARE_WRITE = 0x00000002;
+
+pub const CREATE_ALWAYS = 2;
+pub const CREATE_NEW = 1;
+pub const OPEN_ALWAYS = 4;
+pub const OPEN_EXISTING = 3;
+pub const TRUNCATE_EXISTING = 5;
+
+
+pub const FILE_ATTRIBUTE_ARCHIVE = 0x20;
+pub const FILE_ATTRIBUTE_ENCRYPTED = 0x4000;
+pub const FILE_ATTRIBUTE_HIDDEN = 0x2;
+pub const FILE_ATTRIBUTE_NORMAL = 0x80;
+pub const FILE_ATTRIBUTE_OFFLINE = 0x1000;
+pub const FILE_ATTRIBUTE_READONLY = 0x1;
+pub const FILE_ATTRIBUTE_SYSTEM = 0x4;
+pub const FILE_ATTRIBUTE_TEMPORARY = 0x100;
std/os/index.zig
@@ -491,7 +491,7 @@ pub fn getCwd(allocator: &Allocator) -> %[]u8 {
                     continue;
                 }
 
-                return buf[0..result];
+                return allocator.shrink(u8, buf, result);
             }
         },
         else => {
@@ -506,12 +506,17 @@ pub fn getCwd(allocator: &Allocator) -> %[]u8 {
                     return error.Unexpected;
                 }
 
-                return cstr.toSlice(buf.ptr);
+                return allocator.shrink(u8, buf, cstr.len(buf.ptr));
             }
         },
     }
 }
 
+test "os.getCwd" {
+    // at least call it so it gets compiled
+    _ = getCwd(&debug.global_allocator);
+}
+
 pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) -> %void {
     const full_buf = %return allocator.alloc(u8, existing_path.len + new_path.len + 2);
     defer allocator.free(full_buf);
@@ -988,7 +993,7 @@ pub fn readLink(allocator: &Allocator, pathname: []const u8) -> %[]u8 {
             result_buf = %return allocator.realloc(u8, result_buf, result_buf.len * 2);
             continue;
         }
-        return result_buf[0..ret_val];
+        return allocator.shrink(u8, result_buf, ret_val);
     }
 }
 
std/os/path.zig
@@ -6,8 +6,9 @@ const mem = @import("../mem.zig");
 const fmt = @import("../fmt/index.zig");
 const Allocator = mem.Allocator;
 const os = @import("index.zig");
-const math = @import("../math.zig");
+const math = @import("../math/index.zig");
 const posix = os.posix;
+const windows = os.windows;
 const c = @import("../c/index.zig");
 const cstr = @import("../cstr.zig");
 
@@ -921,7 +922,62 @@ error Unexpected;
 /// Caller must deallocate result.
 pub fn real(allocator: &Allocator, pathname: []const u8) -> %[]u8 {
     switch (builtin.os) {
-        Os.windows => @compileError("TODO implement os.path.real for windows"),
+        Os.windows => {
+            const pathname_buf = %return 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 => error.Unexpected,
+                };
+            }
+            defer assert(windows.CloseHandle(h_file));
+            var buf = %return allocator.alloc(u8, 256);
+            %defer allocator.free(buf);
+            while (true) {
+                const buf_len = math.cast(windows.DWORD, buf.len) %% 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 => error.Unexpected,
+                    };
+                }
+
+                if (result > buf.len) {
+                    buf = %return allocator.realloc(u8, buf, result);
+                    continue;
+                }
+
+                // windows returns \\?\ prepended to the path
+                // we strip it because nobody wants \\?\ prepended to their path
+                const final_len = if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) {
+                    var i: usize = 4;
+                    while (i < result) : (i += 1) {
+                        buf[i - 4] = buf[i];
+                    }
+                    result - 4
+                } else {
+                    result
+                };
+
+                return allocator.shrink(u8, buf, final_len);
+            }
+        },
         Os.darwin, Os.macosx, Os.ios => {
             // TODO instead of calling the libc function here, port the implementation
             // to Zig, and then remove the NameTooLong error possibility.
@@ -950,7 +1006,7 @@ pub fn real(allocator: &Allocator, pathname: []const u8) -> %[]u8 {
                     else => error.Unexpected,
                 };
             }
-            return cstr.toSlice(result_buf.ptr);
+            return allocator.realloc(u8, result_buf, cstr.len(result_buf.ptr));
         },
         Os.linux => {
             const fd = %return os.posixOpen(pathname, posix.O_PATH|posix.O_NONBLOCK|posix.O_CLOEXEC, 0, allocator);
@@ -964,3 +1020,8 @@ pub fn real(allocator: &Allocator, pathname: []const u8) -> %[]u8 {
         else => @compileError("TODO implement os.path.real for " ++ @enumTagName(builtin.os)),
     }
 }
+
+test "os.path.real" {
+    // at least call it so it gets compiled
+    _ = real(&debug.global_allocator, "some_path");
+}
std/debug.zig
@@ -974,9 +974,13 @@ fn globalAlloc(self: &mem.Allocator, n: usize, alignment: usize) -> %[]u8 {
 }
 
 fn globalRealloc(self: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 {
-    const result = %return globalAlloc(self, new_size, alignment);
-    @memcpy(result.ptr, old_mem.ptr, old_mem.len);
-    return result;
+    if (new_size <= old_mem.len) {
+        return old_mem[0..new_size];
+    } else {
+        const result = %return globalAlloc(self, new_size, alignment);
+        @memcpy(result.ptr, old_mem.ptr, old_mem.len);
+        return result;
+    }
 }
 
-fn globalFree(self: &mem.Allocator, ptr: &u8) { }
+fn globalFree(self: &mem.Allocator, memory: []u8) { }
std/io.zig
@@ -65,7 +65,7 @@ error SystemFdQuotaExceeded;
 error NameTooLong;
 error NoDevice;
 error PathNotFound;
-error NoMem;
+error OutOfMemory;
 error Unseekable;
 error EndOfFile;
 error NoStdHandles;
@@ -394,7 +394,7 @@ pub const InStream = struct {
         if (err > 0) {
             return switch (err) {
                 system.EBADF => error.BadFd,
-                system.ENOMEM => error.NoMem,
+                system.ENOMEM => error.OutOfMemory,
                 else => error.Unexpected,
             }
         }
std/mem.zig
@@ -8,17 +8,22 @@ const Os = builtin.Os;
 
 pub const Cmp = math.Cmp;
 
-error NoMem;
+error OutOfMemory;
 
 pub const Allocator = struct {
     /// Allocate byte_count bytes and return them in a slice, with the
     /// slicer's pointer aligned at least to alignment bytes.
     allocFn: fn (self: &Allocator, byte_count: usize, alignment: usize) -> %[]u8,
 
-    /// Guaranteed: old_mem.len > 0 and alignment >= alignment of old_mem.ptr
+    /// Guaranteed: `old_mem.len` is the same as what was returned from allocFn or reallocFn.
+    /// Guaranteed: alignment >= alignment of old_mem.ptr
+    ///
+    /// If `new_byte_count` is less than or equal to `old_mem.len` this function must
+    /// return successfully.
     reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: usize) -> %[]u8,
 
-    freeFn: fn (self: &Allocator, ptr: &u8),
+    /// Guaranteed: `old_mem.len` is the same as what was returned from `allocFn` or `reallocFn`
+    freeFn: fn (self: &Allocator, old_mem: []u8),
 
     fn create(self: &Allocator, comptime T: type) -> %&T {
         const slice = %return self.alloc(T, 1);
@@ -41,24 +46,41 @@ pub const Allocator = struct {
         }
 
         // Assert that old_mem.ptr is properly aligned.
-        _ = @alignCast(@alignOf(T), old_mem.ptr);
+        const aligned_old_mem = @alignCast(@alignOf(T), old_mem);
 
         const byte_count = %return math.mul(usize, @sizeOf(T), n);
-        const byte_slice = %return self.reallocFn(self, ([]u8)(old_mem), byte_count, @alignOf(T));
-        ([]T)(@alignCast(@alignOf(T), byte_slice))
+        const byte_slice = %return self.reallocFn(self, ([]u8)(aligned_old_mem), byte_count, @alignOf(T));
+        return ([]T)(@alignCast(@alignOf(T), byte_slice));
+    }
+
+    /// Reallocate, but `n` must be less than or equal to `old_mem.len`.
+    /// Unlike `realloc`, this function cannot fail.
+    /// Shrinking to 0 is the same as calling `free`.
+    fn shrink(self: &Allocator, comptime T: type, old_mem: []T, n: usize) -> []T {
+        if (n == 0) {
+            self.free(old_mem);
+            return old_mem[0..0];
+        }
+
+        assert(n <= old_mem.len);
+
+        // Assert that old_mem.ptr is properly aligned.
+        const aligned_old_mem = @alignCast(@alignOf(T), old_mem);
+
+        // Here we skip the overflow checking on the multiplication because
+        // n <= old_mem.len and the multiplication didn't overflow for that operation.
+        const byte_count = @sizeOf(T) * n;
+
+        const byte_slice = %%self.reallocFn(self, ([]u8)(aligned_old_mem), byte_count, @alignOf(T));
+        return ([]T)(@alignCast(@alignOf(T), byte_slice));
     }
 
     fn free(self: &Allocator, memory: var) {
-        const ptr = if (@typeId(@typeOf(memory)) == builtin.TypeId.Pointer) {
-            memory
-        } else {
-            const const_slice = ([]const u8)(memory);
-            if (memory.len == 0)
-                return;
-            const_slice.ptr
-        };
-        const non_const_ptr = @intToPtr(&u8, @ptrToInt(ptr));
-        self.freeFn(self, non_const_ptr);
+        const bytes = ([]const u8)(memory);
+        if (bytes.len == 0)
+            return;
+        const non_const_ptr = @intToPtr(&u8, @ptrToInt(bytes.ptr));
+        self.freeFn(self, non_const_ptr[0..bytes.len]);
     }
 };
 
@@ -74,7 +96,7 @@ pub const IncrementingAllocator = struct {
                 const addr = p.mmap(null, capacity, p.PROT_READ|p.PROT_WRITE,
                     p.MAP_PRIVATE|p.MAP_ANONYMOUS|p.MAP_NORESERVE, -1, 0);
                 if (addr == p.MAP_FAILED) {
-                    return error.NoMem;
+                    return error.OutOfMemory;
                 }
                 return IncrementingAllocator {
                     .allocator = Allocator {
@@ -132,7 +154,7 @@ pub const IncrementingAllocator = struct {
         const adjusted_index = self.end_index + march_forward_bytes;
         const new_end_index = adjusted_index + n;
         if (new_end_index > self.bytes.len) {
-            return error.NoMem;
+            return error.OutOfMemory;
         }
         const result = self.bytes[adjusted_index .. new_end_index];
         self.end_index = new_end_index;
@@ -140,12 +162,16 @@ pub const IncrementingAllocator = struct {
     }
 
     fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 {
-        const result = %return alloc(allocator, new_size, alignment);
-        copy(u8, result, old_mem);
-        return result;
+        if (new_size <= old_mem.len) {
+            return old_mem[0..new_size];
+        } else {
+            const result = %return alloc(allocator, new_size, alignment);
+            copy(u8, result, old_mem);
+            return result;
+        }
     }
 
-    fn free(allocator: &Allocator, bytes: &u8) {
+    fn free(allocator: &Allocator, bytes: []u8) {
         // Do nothing. That's the point of an incrementing allocator.
     }
 };
std/net.zig
@@ -8,7 +8,7 @@ error Io;
 error TimedOut;
 error ConnectionReset;
 error ConnectionRefused;
-error NoMem;
+error OutOfMemory;
 error NotSocket;
 error BadFd;
 
@@ -38,7 +38,7 @@ const Connection = struct {
             linux.EFAULT => unreachable,
             linux.ENOTSOCK => return error.NotSocket,
             linux.EINTR => return error.SigInterrupt,
-            linux.ENOMEM => return error.NoMem,
+            linux.ENOMEM => return error.OutOfMemory,
             linux.ECONNREFUSED => return error.ConnectionRefused,
             linux.EBADF => return error.BadFd,
             // TODO more error values