diff --git a/std/debug.zig b/std/debug.zig index 08cc783b95..aef67c315f 100644 --- a/std/debug.zig +++ b/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) { } diff --git a/std/io.zig b/std/io.zig index 7ca89d587f..e1f4a818fb 100644 --- a/std/io.zig +++ b/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, } } diff --git a/std/mem.zig b/std/mem.zig index 9aca27ed69..7ce497762d 100644 --- a/std/mem.zig +++ b/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. } }; diff --git a/std/net.zig b/std/net.zig index 53ed9d2606..1a0bfb6f03 100644 --- a/std/net.zig +++ b/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 diff --git a/std/os/index.zig b/std/os/index.zig index 40f2ba2a91..0a1b39d66d 100644 --- a/std/os/index.zig +++ b/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); } } diff --git a/std/os/path.zig b/std/os/path.zig index f83744785a..faf5a5843e 100644 --- a/std/os/path.zig +++ b/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"); +} diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig index 498170a9b8..bf230a2a0d 100644 --- a/std/os/windows/index.zig +++ b/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;