From bda5539e9d8b5f15b8165393e4118c8601188276 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 21 Aug 2018 00:46:42 -0400 Subject: [PATCH] *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 --- std/debug/index.zig | 2 +- std/io_test.zig | 6 +- std/os/file.zig | 42 +++-- std/os/index.zig | 346 +++++++++++++++++------------------- std/os/path.zig | 5 +- std/os/windows/kernel32.zig | 8 +- std/os/windows/util.zig | 27 +-- std/unicode.zig | 69 ++++--- 8 files changed, 254 insertions(+), 251 deletions(-) diff --git a/std/debug/index.zig b/std/debug/index.zig index 748aa2b969..98a665914e 100644 --- a/std/debug/index.zig +++ b/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 diff --git a/std/io_test.zig b/std/io_test.zig index fb0f850095..1f96c7857d 100644 --- a/std/io_test.zig +++ b/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); diff --git a/std/os/file.zig b/std/os/file.zig index df79ab4adc..4b2383fd26 100644 --- a/std/os/file.zig +++ b/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 { diff --git a/std/os/index.zig b/std/os/index.zig index da18e8e47b..6132ac2ae4 100644 --- a/std/os/index.zig +++ b/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 { +pub fn getCwdAlloc(allocator: *Allocator) ![]u8 { + var buf: [MAX_PATH_BYTES]u8 = undefined; + return mem.dupe(allocator, u8, try getCwd(&buf)); +} + +pub const GetCwdError = error{Unexpected}; + +/// 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 buf = try allocator.alloc(u8, 256); - errdefer allocator.free(buf); - - while (true) { - const result = windows.GetCurrentDirectoryA(@intCast(windows.WORD, buf.len), buf.ptr); - - if (result == 0) { - const err = windows.GetLastError(); - return switch (err) { - else => unexpectedErrorWindows(err), - }; + 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), } - - if (result > buf.len) { - buf = try allocator.realloc(u8, buf, result); - continue; - } - - 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); +pub fn deleteFileWindows(file_path: []const u8) !void { + @compileError("TODO rewrite with DeleteFileW and no allocator"); +} - mem.copy(u8, buf, file_path); - buf[file_path.len] = 0; - - 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) { - return makeDirWindows(allocator, dir_path); + @compileError("TODO rewrite with MoveFileExW and no allocator"); } else { - return makeDirPosix(allocator, dir_path); + 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(dir_path); + } else { + 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"), } diff --git a/std/os/path.zig b/std/os/path.zig index 8c11126d9b..c4c686366d 100644 --- a/std/os/path.zig +++ b/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; diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig index 0cdf27754a..130ca8502f 100644 --- a/std/os/windows/kernel32.zig +++ b/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; diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig index 2f9f4f2c72..b837bf164d 100644 --- a/std/os/windows/util.zig +++ b/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; diff --git a/std/unicode.zig b/std/unicode.zig index 0e7b4cdc3e..99e0144800 100644 --- a/std/unicode.zig +++ b/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..]);