diff options
Diffstat (limited to 'std')
| -rw-r--r-- | std/build.zig | 24 | ||||
| -rw-r--r-- | std/cstr.zig | 11 | ||||
| -rw-r--r-- | std/debug/index.zig | 6 | ||||
| -rw-r--r-- | std/event/fs.zig | 60 | ||||
| -rw-r--r-- | std/io.zig | 7 | ||||
| -rw-r--r-- | std/io_test.zig | 10 | ||||
| -rw-r--r-- | std/mem.zig | 19 | ||||
| -rw-r--r-- | std/os/child_process.zig | 14 | ||||
| -rw-r--r-- | std/os/file.zig | 141 | ||||
| -rw-r--r-- | std/os/get_app_data_dir.zig | 3 | ||||
| -rw-r--r-- | std/os/index.zig | 537 | ||||
| -rw-r--r-- | std/os/path.zig | 235 | ||||
| -rw-r--r-- | std/os/test.zig | 16 | ||||
| -rw-r--r-- | std/os/windows/kernel32.zig | 38 | ||||
| -rw-r--r-- | std/os/windows/util.zig | 92 | ||||
| -rw-r--r-- | std/unicode.zig | 102 |
16 files changed, 773 insertions, 542 deletions
diff --git a/std/build.zig b/std/build.zig index bc14c29dee..08bb5635d9 100644 --- a/std/build.zig +++ b/std/build.zig @@ -267,7 +267,7 @@ pub const Builder = struct { if (self.verbose) { warn("rm {}\n", installed_file); } - _ = os.deleteFile(self.allocator, installed_file); + _ = os.deleteFile(installed_file); } // TODO remove empty directories @@ -1182,7 +1182,7 @@ pub const LibExeObjStep = struct { if (self.build_options_contents.len() > 0) { const build_options_file = try os.path.join(builder.allocator, builder.cache_root, builder.fmt("{}_build_options.zig", self.name)); - try std.io.writeFile(builder.allocator, build_options_file, self.build_options_contents.toSliceConst()); + try std.io.writeFile(build_options_file, self.build_options_contents.toSliceConst()); try zig_args.append("--pkg-begin"); try zig_args.append("build_options"); try zig_args.append(builder.pathFromRoot(build_options_file)); @@ -1491,11 +1491,14 @@ pub const LibExeObjStep = struct { } if (!is_darwin) { - const rpath_arg = builder.fmt("-Wl,-rpath,{}", os.path.real(builder.allocator, builder.pathFromRoot(builder.cache_root)) catch unreachable); + const rpath_arg = builder.fmt("-Wl,-rpath,{}", try os.path.realAlloc( + builder.allocator, + builder.pathFromRoot(builder.cache_root), + )); defer builder.allocator.free(rpath_arg); - cc_args.append(rpath_arg) catch unreachable; + try cc_args.append(rpath_arg); - cc_args.append("-rdynamic") catch unreachable; + try cc_args.append("-rdynamic"); } for (self.full_path_libs.toSliceConst()) |full_path_lib| { @@ -1566,11 +1569,14 @@ pub const LibExeObjStep = struct { cc_args.append("-o") catch unreachable; cc_args.append(output_path) catch unreachable; - const rpath_arg = builder.fmt("-Wl,-rpath,{}", os.path.real(builder.allocator, builder.pathFromRoot(builder.cache_root)) catch unreachable); + const rpath_arg = builder.fmt("-Wl,-rpath,{}", try os.path.realAlloc( + builder.allocator, + builder.pathFromRoot(builder.cache_root), + )); defer builder.allocator.free(rpath_arg); - cc_args.append(rpath_arg) catch unreachable; + try cc_args.append(rpath_arg); - cc_args.append("-rdynamic") catch unreachable; + try cc_args.append("-rdynamic"); { var it = self.link_libs.iterator(); @@ -1917,7 +1923,7 @@ pub const WriteFileStep = struct { warn("unable to make path {}: {}\n", full_path_dir, @errorName(err)); return err; }; - io.writeFile(self.builder.allocator, full_path, self.data) catch |err| { + io.writeFile(full_path, self.data) catch |err| { warn("unable to write {}: {}\n", full_path, @errorName(err)); return err; }; diff --git a/std/cstr.zig b/std/cstr.zig index e83d5a39e9..a8aaf21279 100644 --- a/std/cstr.zig +++ b/std/cstr.zig @@ -9,10 +9,9 @@ pub const line_sep = switch (builtin.os) { else => "\n", }; +/// Deprecated, use mem.len pub fn len(ptr: [*]const u8) usize { - var count: usize = 0; - while (ptr[count] != 0) : (count += 1) {} - return count; + return mem.len(u8, ptr); } pub fn cmp(a: [*]const u8, b: [*]const u8) i8 { @@ -27,12 +26,14 @@ pub fn cmp(a: [*]const u8, b: [*]const u8) i8 { } } +/// Deprecated, use mem.toSliceConst pub fn toSliceConst(str: [*]const u8) []const u8 { - return str[0..len(str)]; + return mem.toSliceConst(u8, str); } +/// Deprecated, use mem.toSlice pub fn toSlice(str: [*]u8) []u8 { - return str[0..len(str)]; + return mem.toSlice(u8, str); } test "cstr fns" { diff --git a/std/debug/index.zig b/std/debug/index.zig index f06da85f54..4dbd8bf08d 100644 --- a/std/debug/index.zig +++ b/std/debug/index.zig @@ -255,7 +255,7 @@ pub fn printSourceAtAddress(debug_info: *ElfStackTrace, out_stream: var, address address, compile_unit_name, ); - if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) { + if (printLineFromFile(out_stream, line_info)) { if (line_info.column == 0) { try out_stream.write("\n"); } else { @@ -340,8 +340,8 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) !*ElfStackTrace { } } -fn printLineFromFile(allocator: *mem.Allocator, out_stream: var, line_info: *const LineInfo) !void { - var f = try os.File.openRead(allocator, line_info.file_name); +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/event/fs.zig b/std/event/fs.zig index 00f45f2af5..ac99f13c9b 100644 --- a/std/event/fs.zig +++ b/std/event/fs.zig @@ -78,8 +78,7 @@ pub async fn pwritev(loop: *Loop, fd: os.FileHandle, data: []const []const u8, o builtin.Os.macosx, builtin.Os.linux, => return await (async pwritevPosix(loop, fd, data, offset) catch unreachable), - builtin.Os.windows, - => return await (async pwritevWindows(loop, fd, data, offset) catch unreachable), + builtin.Os.windows => return await (async pwritevWindows(loop, fd, data, offset) catch unreachable), else => @compileError("Unsupported OS"), } } @@ -147,7 +146,6 @@ pub async fn pwriteWindows(loop: *Loop, fd: os.FileHandle, data: []const u8, off } } - /// data - just the inner references - must live until pwritev promise completes. pub async fn pwritevPosix(loop: *Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void { // workaround for https://github.com/ziglang/zig/issues/1194 @@ -203,8 +201,7 @@ pub async fn preadv(loop: *Loop, fd: os.FileHandle, data: []const []u8, offset: builtin.Os.macosx, builtin.Os.linux, => return await (async preadvPosix(loop, fd, data, offset) catch unreachable), - builtin.Os.windows, - => return await (async preadvWindows(loop, fd, data, offset) catch unreachable), + builtin.Os.windows => return await (async preadvWindows(loop, fd, data, offset) catch unreachable), else => @compileError("Unsupported OS"), } } @@ -222,7 +219,7 @@ pub async fn preadvWindows(loop: *Loop, fd: os.FileHandle, data: []const []u8, o var inner_off: usize = 0; while (true) { const v = data_copy[iov_i]; - const amt_read = try await (async preadWindows(loop, fd, v[inner_off .. v.len-inner_off], offset + off) catch unreachable); + const amt_read = try await (async preadWindows(loop, fd, v[inner_off .. v.len - inner_off], offset + off) catch unreachable); off += amt_read; inner_off += amt_read; if (inner_off == v.len) { @@ -340,8 +337,7 @@ pub async fn openPosix( resume @handle(); } - const path_with_null = try std.cstr.addNullByte(loop.allocator, path); - defer loop.allocator.free(path_with_null); + const path_c = try std.os.toPosixPath(path); var req_node = RequestNode{ .prev = null, @@ -349,7 +345,7 @@ pub async fn openPosix( .data = Request{ .msg = Request.Msg{ .Open = Request.Msg.Open{ - .path = path_with_null[0..path.len], + .path = path_c[0..path.len], .flags = flags, .mode = mode, .result = undefined, @@ -382,7 +378,6 @@ pub async fn openRead(loop: *Loop, path: []const u8) os.File.OpenError!os.FileHa }, builtin.Os.windows => return os.windowsOpen( - loop.allocator, path, windows.GENERIC_READ, windows.FILE_SHARE_READ, @@ -409,9 +404,7 @@ pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: os.File.Mode) os const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC; return await (async openPosix(loop, path, flags, os.File.default_mode) catch unreachable); }, - builtin.Os.windows, - => return os.windowsOpen( - loop.allocator, + builtin.Os.windows => return os.windowsOpen( path, windows.GENERIC_WRITE, windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, @@ -435,9 +428,8 @@ pub async fn openReadWrite( }, builtin.Os.windows => return os.windowsOpen( - loop.allocator, path, - windows.GENERIC_WRITE|windows.GENERIC_READ, + windows.GENERIC_WRITE | windows.GENERIC_READ, windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, windows.OPEN_ALWAYS, windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED, @@ -513,8 +505,7 @@ pub const CloseOperation = struct { self.loop.allocator.destroy(self); } }, - builtin.Os.windows, - => { + builtin.Os.windows => { if (self.os_data.handle) |handle| { os.close(handle); } @@ -532,8 +523,7 @@ pub const CloseOperation = struct { self.os_data.close_req_node.data.msg.Close.fd = handle; self.os_data.have_fd = true; }, - builtin.Os.windows, - => { + builtin.Os.windows => { self.os_data.handle = handle; }, else => @compileError("Unsupported OS"), @@ -548,8 +538,7 @@ pub const CloseOperation = struct { => { self.os_data.have_fd = false; }, - builtin.Os.windows, - => { + builtin.Os.windows => { self.os_data.handle = null; }, else => @compileError("Unsupported OS"), @@ -564,8 +553,7 @@ pub const CloseOperation = struct { assert(self.os_data.have_fd); return self.os_data.close_req_node.data.msg.Close.fd; }, - builtin.Os.windows, - => { + builtin.Os.windows => { return self.os_data.handle.?; }, else => @compileError("Unsupported OS"), @@ -585,15 +573,13 @@ pub async fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8, builtin.Os.linux, builtin.Os.macosx, => return await (async writeFileModeThread(loop, path, contents, mode) catch unreachable), - builtin.Os.windows, - => return await (async writeFileWindows(loop, path, contents) catch unreachable), + builtin.Os.windows => return await (async writeFileWindows(loop, path, contents) catch unreachable), else => @compileError("Unsupported OS"), } } async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void { const handle = try os.windowsOpen( - loop.allocator, path, windows.GENERIC_WRITE, windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, @@ -1004,7 +990,7 @@ pub fn Watch(comptime V: type) type { const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename); var basename_utf16le_null_consumed = false; defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null); - const basename_utf16le_no_null = basename_utf16le_null[0..basename_utf16le_null.len-1]; + const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; const dir_handle = windows.CreateFileW( dirname_utf16le.ptr, @@ -1018,9 +1004,8 @@ pub fn Watch(comptime V: type) type { if (dir_handle == windows.INVALID_HANDLE_VALUE) { const err = windows.GetLastError(); switch (err) { - windows.ERROR.FILE_NOT_FOUND, - windows.ERROR.PATH_NOT_FOUND, - => return error.PathNotFound, + windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, + windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, else => return os.unexpectedErrorWindows(err), } } @@ -1106,7 +1091,10 @@ pub fn Watch(comptime V: type) type { // TODO handle this error not in the channel but in the setup _ = os.windowsCreateIoCompletionPort( - dir_handle, self.channel.loop.os_data.io_port, completion_key, undefined, + dir_handle, + self.channel.loop.os_data.io_port, + completion_key, + undefined, ) catch |err| { await (async self.channel.put(err) catch unreachable); return; @@ -1126,10 +1114,10 @@ pub fn Watch(comptime V: type) type { &event_buf, @intCast(windows.DWORD, event_buf.len), windows.FALSE, // watch subtree - windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME | - windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE | - windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS | - windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY, + windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME | + windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE | + windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS | + windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY, null, // number of bytes transferred (unused for async) &overlapped, null, // completion routine - unused because we use IOCP @@ -1156,7 +1144,7 @@ pub fn Watch(comptime V: type) type { else => null, }; if (emit) |id| { - const basename_utf16le = ([*]u16)(&ev.FileName)[0..ev.FileNameLength/2]; + const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2]; const user_value = blk: { const held = await (async dir.table_lock.acquire() catch unreachable); defer held.release(); diff --git a/std/io.zig b/std/io.zig index 49e03a64b2..c7154065cb 100644 --- a/std/io.zig +++ b/std/io.zig @@ -254,9 +254,8 @@ pub fn OutStream(comptime WriteError: type) type { }; } -/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. -pub fn writeFile(allocator: *mem.Allocator, path: []const u8, data: []const u8) !void { - var file = try File.openWrite(allocator, path); +pub fn writeFile(path: []const u8, data: []const u8) !void { + var file = try File.openWrite(path); defer file.close(); try file.write(data); } @@ -268,7 +267,7 @@ pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 { /// On success, caller owns returned buffer. pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptime A: u29) ![]align(A) u8 { - var file = try File.openRead(allocator, path); + var file = try File.openRead(path); defer file.close(); const size = try file.getEndPos(); diff --git a/std/io_test.zig b/std/io_test.zig index 56f8a9a6ad..7a44032673 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); @@ -28,7 +28,7 @@ test "write a file, read it, then delete it" { try buf_stream.flush(); } { - var file = try os.File.openRead(allocator, tmp_file_name); + var file = try os.File.openRead(tmp_file_name); defer file.close(); const file_size = try file.getEndPos(); @@ -45,7 +45,7 @@ test "write a file, read it, then delete it" { assert(mem.eql(u8, contents["begin".len .. contents.len - "end".len], data)); assert(mem.eql(u8, contents[contents.len - "end".len ..], "end")); } - try os.deleteFile(allocator, tmp_file_name); + try os.deleteFile(tmp_file_name); } test "BufferOutStream" { @@ -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/mem.zig b/std/mem.zig index f05b43ee56..1ba5b3b73e 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -179,8 +179,8 @@ pub fn secureZero(comptime T: type, s: []T) void { // NOTE: We do not use a volatile slice cast here since LLVM cannot // see that it can be replaced by a memset. const ptr = @ptrCast([*]volatile u8, s.ptr); - const len = s.len * @sizeOf(T); - @memset(ptr, 0, len); + const length = s.len * @sizeOf(T); + @memset(ptr, 0, length); } test "mem.secureZero" { @@ -252,6 +252,20 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) bool { return true; } +pub fn len(comptime T: type, ptr: [*]const T) usize { + var count: usize = 0; + while (ptr[count] != 0) : (count += 1) {} + return count; +} + +pub fn toSliceConst(comptime T: type, ptr: [*]const T) []const T { + return ptr[0..len(T, ptr)]; +} + +pub fn toSlice(comptime T: type, ptr: [*]T) []T { + return ptr[0..len(T, ptr)]; +} + /// Returns true if all elements in a slice are equal to the scalar value provided pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool { for (slice) |item| { @@ -809,3 +823,4 @@ pub fn endianSwap(comptime T: type, x: T) T { test "std.mem.endianSwap" { assert(endianSwap(u32, 0xDEADBEEF) == 0xEFBEADDE); } + diff --git a/std/os/child_process.zig b/std/os/child_process.zig index 693129eea8..b79a8de16f 100644 --- a/std/os/child_process.zig +++ b/std/os/child_process.zig @@ -349,14 +349,7 @@ pub const ChildProcess = struct { }; const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); - const dev_null_fd = if (any_ignore) blk: { - const dev_null_path = "/dev/null"; - var fixed_buffer_mem: [dev_null_path.len + 1]u8 = undefined; - var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); - break :blk try os.posixOpen(&fixed_allocator.allocator, "/dev/null", posix.O_RDWR, 0); - } else blk: { - break :blk undefined; - }; + const dev_null_fd = if (any_ignore) try os.posixOpenC(c"/dev/null", posix.O_RDWR, 0) else undefined; defer { if (any_ignore) os.close(dev_null_fd); } @@ -453,10 +446,7 @@ pub const ChildProcess = struct { const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); const nul_handle = if (any_ignore) blk: { - const nul_file_path = "NUL"; - var fixed_buffer_mem: [nul_file_path.len + 1]u8 = undefined; - var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); - break :blk try os.windowsOpen(&fixed_allocator.allocator, "NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL); + break :blk try os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL); } else blk: { break :blk undefined; }; diff --git a/std/os/file.zig b/std/os/file.zig index 074547193c..1f5ce7cf9d 100644 --- a/std/os/file.zig +++ b/std/os/file.zig @@ -7,6 +7,7 @@ const assert = std.debug.assert; const posix = os.posix; const windows = os.windows; const Os = builtin.Os; +const windows_util = @import("windows/util.zig"); const is_posix = builtin.os != builtin.Os.windows; const is_windows = builtin.os == builtin.Os.windows; @@ -27,16 +28,27 @@ 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(allocator: *mem.Allocator, path: []const u8) OpenError!File { + /// `openRead` except with a null terminated path + pub fn openReadC(path: [*]const u8) OpenError!File { if (is_posix) { const flags = posix.O_LARGEFILE | posix.O_RDONLY; - const fd = try os.posixOpen(allocator, path, flags, 0); + const fd = try os.posixOpenC(path, flags, 0); return openHandle(fd); - } else if (is_windows) { + } + if (is_windows) { + return openRead(mem.toSliceConst(u8, path)); + } + @compileError("Unsupported OS"); + } + + /// Call close to clean up. + pub fn openRead(path: []const u8) OpenError!File { + if (is_posix) { + const path_c = try os.toPosixPath(path); + return openReadC(&path_c); + } + if (is_windows) { const handle = try os.windowsOpen( - allocator, path, windows.GENERIC_READ, windows.FILE_SHARE_READ, @@ -44,28 +56,25 @@ pub const File = struct { windows.FILE_ATTRIBUTE_NORMAL, ); return openHandle(handle); - } else { - @compileError("TODO implement openRead for this OS"); } + @compileError("Unsupported OS"); } /// Calls `openWriteMode` with os.File.default_mode for the mode. - 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(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, @@ -80,16 +89,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, @@ -108,23 +115,43 @@ pub const File = struct { pub const AccessError = error{ PermissionDenied, - NotFound, + FileNotFound, NameTooLong, - BadMode, - BadPathName, - Io, + InputOutput, SystemResources, - OutOfMemory, + BadPathName, + + /// On Windows, file paths must be valid Unicode. + InvalidUtf8, 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); + /// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string. + /// Otherwise use `access` or `accessC`. + pub fn accessW(path: [*]const u16) AccessError!void { + if (os.windows.GetFileAttributesW(path) != os.windows.INVALID_FILE_ATTRIBUTES) { + return; + } + + const err = windows.GetLastError(); + switch (err) { + windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, + windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, + windows.ERROR.ACCESS_DENIED => return error.PermissionDenied, + else => return os.unexpectedErrorWindows(err), + } + } + /// Call if you have a UTF-8 encoded, null-terminated string. + /// Otherwise use `access` or `accessW`. + pub fn accessC(path: [*]const u8) AccessError!void { + if (is_windows) { + const path_w = try windows_util.cStrToPrefixedFileW(path); + return accessW(&path_w); + } 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, @@ -132,32 +159,33 @@ pub const File = struct { posix.EROFS => return error.PermissionDenied, posix.ELOOP => return error.PermissionDenied, posix.ETXTBSY => return error.PermissionDenied, - posix.ENOTDIR => return error.NotFound, - posix.ENOENT => return error.NotFound, + posix.ENOTDIR => return error.FileNotFound, + posix.ENOENT => return error.FileNotFound, posix.ENAMETOOLONG => return error.NameTooLong, posix.EINVAL => unreachable, - posix.EFAULT => return error.BadPathName, - posix.EIO => return error.Io, + posix.EFAULT => unreachable, + posix.EIO => return error.InputOutput, posix.ENOMEM => return error.SystemResources, else => return os.unexpectedErrorPosix(err), } - } else if (is_windows) { - if (os.windows.GetFileAttributesA(path_with_null.ptr) != os.windows.INVALID_FILE_ATTRIBUTES) { - return; - } + } + @compileError("Unsupported OS"); + } - const err = windows.GetLastError(); - switch (err) { - windows.ERROR.FILE_NOT_FOUND, - windows.ERROR.PATH_NOT_FOUND, - => return error.NotFound, - windows.ERROR.ACCESS_DENIED => return error.PermissionDenied, - else => return os.unexpectedErrorWindows(err), - } - } else { - @compileError("TODO implement access for this OS"); + pub fn access(path: []const u8) AccessError!void { + if (is_windows) { + const path_w = try windows_util.sliceToPrefixedFileW(path); + return accessW(&path_w); + } + 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("Unsupported OS"); } /// Upon success, the stream is in an uninitialized state. To continue using it, @@ -179,7 +207,9 @@ pub const File = struct { const err = posix.getErrno(result); if (err > 0) { return switch (err) { - posix.EBADF => error.BadFd, + // We do not make this an error code because if you get EBADF it's always a bug, + // since the fd could have been reused. + posix.EBADF => unreachable, posix.EINVAL => error.Unseekable, posix.EOVERFLOW => error.Unseekable, posix.ESPIPE => error.Unseekable, @@ -192,7 +222,7 @@ pub const File = struct { if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) { const err = windows.GetLastError(); return switch (err) { - windows.ERROR.INVALID_PARAMETER => error.BadFd, + windows.ERROR.INVALID_PARAMETER => unreachable, else => os.unexpectedErrorWindows(err), }; } @@ -209,7 +239,9 @@ pub const File = struct { const err = posix.getErrno(result); if (err > 0) { return switch (err) { - posix.EBADF => error.BadFd, + // We do not make this an error code because if you get EBADF it's always a bug, + // since the fd could have been reused. + posix.EBADF => unreachable, posix.EINVAL => error.Unseekable, posix.EOVERFLOW => error.Unseekable, posix.ESPIPE => error.Unseekable, @@ -223,7 +255,7 @@ pub const File = struct { if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) { const err = windows.GetLastError(); return switch (err) { - windows.ERROR.INVALID_PARAMETER => error.BadFd, + windows.ERROR.INVALID_PARAMETER => unreachable, else => os.unexpectedErrorWindows(err), }; } @@ -239,7 +271,9 @@ pub const File = struct { const err = posix.getErrno(result); if (err > 0) { return switch (err) { - posix.EBADF => error.BadFd, + // We do not make this an error code because if you get EBADF it's always a bug, + // since the fd could have been reused. + posix.EBADF => unreachable, posix.EINVAL => error.Unseekable, posix.EOVERFLOW => error.Unseekable, posix.ESPIPE => error.Unseekable, @@ -254,7 +288,7 @@ pub const File = struct { if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) { const err = windows.GetLastError(); return switch (err) { - windows.ERROR.INVALID_PARAMETER => error.BadFd, + windows.ERROR.INVALID_PARAMETER => unreachable, else => os.unexpectedErrorWindows(err), }; } @@ -287,7 +321,6 @@ pub const File = struct { } pub const ModeError = error{ - BadFd, SystemResources, Unexpected, }; @@ -298,7 +331,9 @@ pub const File = struct { const err = posix.getErrno(posix.fstat(self.handle, &stat)); if (err > 0) { return switch (err) { - posix.EBADF => error.BadFd, + // We do not make this an error code because if you get EBADF it's always a bug, + // since the fd could have been reused. + posix.EBADF => unreachable, posix.ENOMEM => error.SystemResources, else => os.unexpectedErrorPosix(err), }; diff --git a/std/os/get_app_data_dir.zig b/std/os/get_app_data_dir.zig index e8ae5dd490..da9c6c3cb4 100644 --- a/std/os/get_app_data_dir.zig +++ b/std/os/get_app_data_dir.zig @@ -10,6 +10,7 @@ pub const GetAppDataDirError = error{ }; /// Caller owns returned memory. +/// TODO determine if we can remove the allocator requirement pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 { switch (builtin.os) { builtin.Os.windows => { @@ -22,7 +23,7 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD )) { os.windows.S_OK => { defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr)); - const global_dir = unicode.utf16leToUtf8(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) { + const global_dir = unicode.utf16leToUtf8Alloc(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) { error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable, error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable, error.DanglingSurrogateHalf => return error.AppDataDirUnavailable, diff --git a/std/os/index.zig b/std/os/index.zig index caef6d1e37..a0b6e6bf45 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -39,6 +39,15 @@ pub const File = @import("file.zig").File; pub const time = @import("time.zig"); pub const page_size = 4 * 1024; +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 => windows_util.PATH_MAX_WIDE * 3 + 1, + else => @compileError("Unsupported OS"), +}; pub const UserInfo = @import("get_user_id.zig").UserInfo; pub const getUserInfo = @import("get_user_id.zig").getUserInfo; @@ -317,6 +326,8 @@ pub const PosixWriteError = error{ NoSpaceLeft, AccessDenied, BrokenPipe, + + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -417,7 +428,6 @@ pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, off } pub const PosixOpenError = error{ - OutOfMemory, AccessDenied, FileTooBig, IsDir, @@ -426,22 +436,22 @@ pub const PosixOpenError = error{ NameTooLong, SystemFdQuotaExceeded, NoDevice, - PathNotFound, + FileNotFound, SystemResources, NoSpaceLeft, NotDir, PathAlreadyExists, + + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; /// ::file_path needs to be copied in memory to add a null terminating byte. /// Calls POSIX open, keeps trying if it gets interrupted, and translates /// the return value into zig errors. -pub fn posixOpen(allocator: *Allocator, file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 { - const path_with_null = try cstr.addNullByte(allocator, file_path); - defer allocator.free(path_with_null); - - return posixOpenC(path_with_null.ptr, flags, perm); +pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 { + const file_path_c = try toPosixPath(file_path); + return posixOpenC(&file_path_c, flags, perm); } // TODO https://github.com/ziglang/zig/issues/265 @@ -463,7 +473,7 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 { posix.ENAMETOOLONG => return PosixOpenError.NameTooLong, posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded, posix.ENODEV => return PosixOpenError.NoDevice, - posix.ENOENT => return PosixOpenError.PathNotFound, + posix.ENOENT => return PosixOpenError.FileNotFound, posix.ENOMEM => return PosixOpenError.SystemResources, posix.ENOSPC => return PosixOpenError.NoSpaceLeft, posix.ENOTDIR => return PosixOpenError.NotDir, @@ -476,6 +486,16 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 { } } +/// Used to convert a slice to a null terminated slice on the stack. +/// TODO well defined copy elision +pub fn toPosixPath(file_path: []const u8) ![posix.PATH_MAX]u8 { + var path_with_null: [posix.PATH_MAX]u8 = undefined; + if (file_path.len >= posix.PATH_MAX) return error.NameTooLong; + mem.copy(u8, path_with_null[0..], file_path); + path_with_null[file_path.len] = 0; + return path_with_null; +} + pub fn posixDup2(old_fd: i32, new_fd: i32) !void { while (true) { const err = posix.getErrno(posix.dup2(old_fd, new_fd)); @@ -591,6 +611,8 @@ pub const PosixExecveError = error{ FileNotFound, NotDir, FileBusy, + + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -719,43 +741,39 @@ 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_WIDE]u16 = undefined; + const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast + const casted_ptr = ([*]u16)(&utf16le_buf); // TODO shouldn't need this cast + const result = windows.GetCurrentDirectoryW(casted_len, casted_ptr); + if (result == 0) { + const err = windows.GetLastError(); + switch (err) { + else => return unexpectedErrorWindows(err), } - - return allocator.shrink(u8, buf, result); } + assert(result <= utf16le_buf.len); + const utf16le_slice = utf16le_buf[0..result]; + // Trust that Windows gives us valid UTF-16LE. + const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable; + return out_buffer[0..end_index]; }, 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), } }, } @@ -763,7 +781,9 @@ pub fn getCwd(allocator: *Allocator) ![]u8 { test "os.getCwd" { // at least call it so it gets compiled - _ = getCwd(debug.global_allocator); + _ = getCwdAlloc(debug.global_allocator); + var buf: [MAX_PATH_BYTES]u8 = undefined; + _ = getCwd(&buf); } pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError; @@ -778,6 +798,8 @@ pub fn symLink(allocator: *Allocator, existing_path: []const u8, new_path: []con pub const WindowsSymLinkError = error{ OutOfMemory, + + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -808,6 +830,8 @@ pub const PosixSymLinkError = error{ NoSpaceLeft, ReadOnlyFileSystem, NotDir, + + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -866,7 +890,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); if (symLink(allocator, existing_path, tmp_path)) { - return rename(allocator, tmp_path, new_path); + return rename(tmp_path, new_path); } else |err| switch (err) { error.PathAlreadyExists => continue, else => return err, // TODO zig should know this set does not include PathAlreadyExists @@ -885,70 +909,75 @@ pub const DeleteFileError = error{ NotDir, SystemResources, ReadOnlyFileSystem, - OutOfMemory, + /// On Windows, file paths must be valid Unicode. + InvalidUtf8, + + /// On Windows, file paths cannot contain these characters: + /// '/', '*', '?', '"', '<', '>', '|' + BadPathName, + + /// See https://github.com/ziglang/zig/issues/1396 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 { + const file_path_w = try windows_util.sliceToPrefixedFileW(file_path); - mem.copy(u8, buf, file_path); - buf[file_path.len] = 0; - - if (windows.DeleteFileA(buf.ptr) == 0) { + if (windows.DeleteFileW(&file_path_w) == 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), - }; + switch (err) { + windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, + windows.ERROR.ACCESS_DENIED => return error.AccessDenied, + windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, + windows.ERROR.INVALID_PARAMETER => return error.NameTooLong, + else => return unexpectedErrorWindows(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 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(file_path: []const u8) !void { + const file_path_c = try toPosixPath(file_path); + return deleteFilePosixC(&file_path_c); +} + /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is /// merged and readily available, /// 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(allocator, source_path); + var in_file = try os.File.openRead(source_path); defer in_file.close(); const mode = try in_file.mode(); @@ -969,8 +998,9 @@ 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(allocator, source_path); + var in_file = try os.File.openRead(source_path); defer in_file.close(); var atomic_file = try AtomicFile.init(allocator, dest_path, mode); @@ -987,6 +1017,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, @@ -1014,7 +1045,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 @@ -1035,7 +1066,7 @@ pub const AtomicFile = struct { pub fn deinit(self: *AtomicFile) void { if (!self.finished) { self.file.close(); - deleteFile(self.allocator, self.tmp_path) catch {}; + deleteFile(self.tmp_path) catch {}; self.allocator.free(self.tmp_path); self.finished = true; } @@ -1044,70 +1075,72 @@ pub const AtomicFile = struct { pub fn finish(self: *AtomicFile) !void { assert(!self.finished); self.file.close(); - try rename(self.allocator, self.tmp_path, self.dest_path); + try rename(self.tmp_path, self.dest_path); self.allocator.free(self.tmp_path); self.finished = true; } }; -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) { + @compileError("TODO implement for windows"); + } else { + 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 rename(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 old_path_w = try windows_util.sliceToPrefixedFileW(old_path); + const new_path_w = try windows_util.sliceToPrefixedFileW(new_path); + if (windows.MoveFileExW(&old_path_w, &new_path_w, flags) == 0) { const err = windows.GetLastError(); - return switch (err) { - else => unexpectedErrorWindows(err), - }; + switch (err) { + else => return unexpectedErrorWindows(err), + } } } 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 old_path_c = try toPosixPath(old_path); + const new_path_c = try toPosixPath(new_path); + return renameC(&old_path_c, &new_path_c); } } -pub fn makeDir(allocator: *Allocator, dir_path: []const u8) !void { +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); } } -pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void { - const path_buf = try cstr.addNullByte(allocator, dir_path); - defer allocator.free(path_buf); +pub fn makeDirWindows(dir_path: []const u8) !void { + const dir_path_w = try windows_util.sliceToPrefixedFileW(dir_path); - if (windows.CreateDirectoryA(path_buf.ptr, null) == 0) { + if (windows.CreateDirectoryW(&dir_path_w, null) == 0) { const err = windows.GetLastError(); return switch (err) { windows.ERROR.ALREADY_EXISTS => error.PathAlreadyExists, @@ -1117,39 +1150,42 @@ 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); - - 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), - }; +pub fn makeDirPosixC(dir_path: [*]const u8) !void { + const err = posix.getErrno(posix.mkdir(dir_path, 0o755)); + 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 { + const dir_path_c = try toPosixPath(dir_path); + return makeDirPosixC(&dir_path_c); +} + /// Calls makeDir recursively to make an entire path. Returns success if the path /// already exists and is a directory. +/// TODO determine if we can remove the allocator requirement from this function pub fn makePath(allocator: *Allocator, full_path: []const u8) !void { const resolved_path = try path.resolve(allocator, full_path); defer allocator.free(resolved_path); var end_index: usize = resolved_path.len; while (true) { - makeDir(allocator, resolved_path[0..end_index]) catch |err| switch (err) { + makeDir(resolved_path[0..end_index]) catch |err| switch (err) { error.PathAlreadyExists => { // TODO stat the file and return an error if it's not a directory // this is important because otherwise a dangling symlink @@ -1187,6 +1223,7 @@ pub const DeleteDirError = error{ ReadOnlyFileSystem, OutOfMemory, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -1245,7 +1282,6 @@ const DeleteTreeError = error{ NameTooLong, SystemFdQuotaExceeded, NoDevice, - PathNotFound, SystemResources, NoSpaceLeft, PathAlreadyExists, @@ -1255,20 +1291,30 @@ const DeleteTreeError = error{ FileSystem, FileBusy, DirNotEmpty, + + /// On Windows, file paths must be valid Unicode. + InvalidUtf8, + + /// On Windows, file paths cannot contain these characters: + /// '/', '*', '?', '"', '<', '>', '|' + BadPathName, + + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; + +/// TODO determine if we can remove the allocator requirement pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void { start_over: while (true) { var got_access_denied = false; // First, try deleting the item as a file. This way we don't follow sym links. - if (deleteFile(allocator, full_path)) { + if (deleteFile(full_path)) { return; } else |err| switch (err) { error.FileNotFound => return, error.IsDir => {}, error.AccessDenied => got_access_denied = true, - error.OutOfMemory, error.SymLinkLoop, error.NameTooLong, error.SystemResources, @@ -1276,6 +1322,8 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError! error.NotDir, error.FileSystem, error.FileBusy, + error.InvalidUtf8, + error.BadPathName, error.Unexpected, => return err, } @@ -1297,7 +1345,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError! error.NameTooLong, error.SystemFdQuotaExceeded, error.NoDevice, - error.PathNotFound, + error.FileNotFound, error.SystemResources, error.NoSpaceLeft, error.PathAlreadyExists, @@ -1367,7 +1415,7 @@ pub const Dir = struct { }; pub const OpenError = error{ - PathNotFound, + FileNotFound, NotDir, AccessDenied, FileTooBig, @@ -1382,9 +1430,11 @@ pub const Dir = struct { PathAlreadyExists, OutOfMemory, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; + /// TODO remove the allocator requirement from this API pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir { return Dir{ .allocator = allocator, @@ -1400,7 +1450,6 @@ pub const Dir = struct { }, Os.macosx, Os.ios => Handle{ .fd = try posixOpen( - allocator, dir_path, posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC, 0, @@ -1412,7 +1461,6 @@ pub const Dir = struct { }, Os.linux => Handle{ .fd = try posixOpen( - allocator, dir_path, posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, 0, @@ -1609,39 +1657,32 @@ pub fn changeCurDir(allocator: *Allocator, dir_path: []const u8) !void { } /// Read value of a symbolic link. -pub fn readLink(allocator: *Allocator, pathname: []const u8) ![]u8 { - const path_buf = try allocator.alloc(u8, pathname.len + 1); - defer allocator.free(path_buf); - - mem.copy(u8, path_buf, pathname); - path_buf[pathname.len] = 0; - - var result_buf = try allocator.alloc(u8, 1024); - errdefer allocator.free(result_buf); - while (true) { - const ret_val = posix.readlink(path_buf.ptr, 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(out_buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 { + const rc = posix.readlink(pathname, out_buffer, out_buffer.len); + const err = posix.getErrno(rc); + switch (err) { + 0 => return out_buffer[0..rc], + posix.EACCES => return 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(out_buffer: *[posix.PATH_MAX]u8, file_path: []const u8) ![]u8 { + const file_path_c = try toPosixPath(file_path); + return readLinkC(out_buffer, &file_path_c); +} + pub fn posix_setuid(uid: u32) !void { const err = posix.getErrno(posix.setuid(uid)); if (err == 0) return; @@ -1688,6 +1729,8 @@ pub fn posix_setregid(rgid: u32, egid: u32) !void { pub const WindowsGetStdHandleErrs = error{ NoStdHandles, + + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -2015,7 +2058,7 @@ pub fn unexpectedErrorPosix(errno: usize) UnexpectedError { /// Call this when you made a windows DLL call or something that does SetLastError /// and you get an unexpected error. pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError { - if (unexpected_error_tracing) { + if (true) { debug.warn("unexpected GetLastError(): {}\n", err); debug.dumpCurrentStackTrace(null); } @@ -2024,17 +2067,12 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError { pub fn openSelfExe() !os.File { switch (builtin.os) { - Os.linux => { - const proc_file_path = "/proc/self/exe"; - var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined; - var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); - return os.File.openRead(&fixed_allocator.allocator, proc_file_path); - }, + Os.linux => return os.File.openReadC(c"/proc/self/exe"), Os.macosx, Os.ios => { - var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined; - var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); - const self_exe_path = try selfExePath(&fixed_allocator.allocator); - return os.File.openRead(&fixed_allocator.allocator, self_exe_path); + var buf: [MAX_PATH_BYTES]u8 = undefined; + const self_exe_path = try selfExePath(&buf); + buf[self_exe_path.len] = 0; + return os.File.openReadC(self_exe_path.ptr); }, else => @compileError("Unsupported OS"), } @@ -2043,7 +2081,7 @@ pub fn openSelfExe() !os.File { test "openSelfExe" { switch (builtin.os) { Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(), - else => return, // Unsupported OS. + else => return error.SkipZigTest, // Unsupported OS } } @@ -2052,69 +2090,68 @@ test "openSelfExe" { /// If you only want an open file handle, use openSelfExe. /// This function may return an error if the current executable /// was deleted after spawning. -/// Caller owns returned memory. -pub fn selfExePath(allocator: *mem.Allocator) ![]u8 { +/// Returned value is a slice of out_buffer. +/// +/// On Linux, depends on procfs being mounted. If the currently executing binary has +/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`. +/// TODO make the return type of this a null terminated pointer +pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { switch (builtin.os) { - Os.linux => { - // If the currently executing binary has been deleted, - // the file path looks something like `/a/b/c/exe (deleted)` - return readLink(allocator, "/proc/self/exe"); - }, + Os.linux => return readLink(out_buffer, "/proc/self/exe"), Os.windows => { - var out_path = try Buffer.initSize(allocator, 0xff); - errdefer out_path.deinit(); - while (true) { - const dword_len = try math.cast(windows.DWORD, out_path.len()); - const copied_amt = windows.GetModuleFileNameA(null, out_path.ptr(), dword_len); - if (copied_amt <= 0) { - const err = windows.GetLastError(); - return switch (err) { - else => unexpectedErrorWindows(err), - }; - } - if (copied_amt < out_path.len()) { - out_path.shrink(copied_amt); - return out_path.toOwnedSlice(); + var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; + const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast + const rc = windows.GetModuleFileNameW(null, &utf16le_buf, casted_len); + assert(rc <= utf16le_buf.len); + if (rc == 0) { + const err = windows.GetLastError(); + switch (err) { + else => return unexpectedErrorWindows(err), } - const new_len = (out_path.len() << 1) | 0b1; - try out_path.resize(new_len); } + const utf16le_slice = utf16le_buf[0..rc]; + // Trust that Windows gives us valid UTF-16LE. + const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable; + return out_buffer[0..end_index]; }, Os.macosx, Os.ios => { - var u32_len: u32 = 0; - const ret1 = c._NSGetExecutablePath(undefined, &u32_len); - assert(ret1 != 0); - const bytes = try allocator.alloc(u8, u32_len); - errdefer allocator.free(bytes); - const ret2 = c._NSGetExecutablePath(bytes.ptr, &u32_len); - assert(ret2 == 0); - return bytes; + var u32_len: u32 = @intCast(u32, out_buffer.len); // TODO shouldn't need this cast + const rc = c._NSGetExecutablePath(out_buffer, &u32_len); + if (rc != 0) return error.NameTooLong; + return mem.toSlice(u8, out_buffer); }, else => @compileError("Unsupported OS"), } } -/// Get the directory path that contains the current executable. +/// `selfExeDirPath` except allocates the result on the heap. /// Caller owns returned memory. -pub fn selfExeDirPath(allocator: *mem.Allocator) ![]u8 { +pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 { + var buf: [MAX_PATH_BYTES]u8 = undefined; + return mem.dupe(allocator, u8, try selfExeDirPath(&buf)); +} + +/// Get the directory path that contains the current executable. +/// Returned value is a slice of out_buffer. +pub fn selfExeDirPath(out_buffer: *[MAX_PATH_BYTES]u8) ![]const u8 { switch (builtin.os) { Os.linux => { // If the currently executing binary has been deleted, // the file path looks something like `/a/b/c/exe (deleted)` // This path cannot be opened, but it's valid for determining the directory // the executable was in when it was run. - const full_exe_path = try readLink(allocator, "/proc/self/exe"); - errdefer allocator.free(full_exe_path); - const dir = path.dirname(full_exe_path) orelse "."; - return allocator.shrink(u8, full_exe_path, dir.len); + const full_exe_path = try readLinkC(out_buffer, c"/proc/self/exe"); + // Assume that /proc/self/exe has an absolute path, and therefore dirname + // will not return null. + return path.dirname(full_exe_path).?; }, Os.windows, Os.macosx, Os.ios => { - const self_exe_path = try selfExePath(allocator); - errdefer allocator.free(self_exe_path); - const dirname = os.path.dirname(self_exe_path) orelse "."; - return allocator.shrink(u8, self_exe_path, dirname.len); + const self_exe_path = try selfExePath(out_buffer); + // Assume that the OS APIs return absolute paths, and therefore dirname + // will not return null. + return path.dirname(self_exe_path).?; }, - else => @compileError("unimplemented: std.os.selfExeDirPath for " ++ @tagName(builtin.os)), + else => @compileError("Unsupported OS"), } } @@ -2218,6 +2255,7 @@ pub const PosixBindError = error{ /// The socket inode would reside on a read-only filesystem. ReadOnlyFileSystem, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -2261,6 +2299,7 @@ const PosixListenError = error{ /// The socket is not of a type that supports the listen() operation. OperationNotSupported, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -2314,6 +2353,7 @@ pub const PosixAcceptError = error{ /// Firewall rules forbid connection. BlockedByFirewall, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -2359,6 +2399,7 @@ pub const LinuxEpollCreateError = error{ /// There was insufficient memory to create the kernel object. SystemResources, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -2413,6 +2454,7 @@ pub const LinuxEpollCtlError = error{ /// for example, a regular file or a directory. FileDescriptorIncompatibleWithEpoll, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -2455,6 +2497,7 @@ pub const LinuxEventFdError = error{ ProcessFdQuotaExceeded, SystemFdQuotaExceeded, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -2477,6 +2520,7 @@ pub const PosixGetSockNameError = error{ /// Insufficient resources were available in the system to perform the operation. SystemResources, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -2530,6 +2574,7 @@ pub const PosixConnectError = error{ /// that for IP sockets the timeout may be very long when syncookies are enabled on the server. ConnectionTimedOut, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -2751,6 +2796,7 @@ pub const SpawnThreadError = error{ /// Not enough userland memory to spawn the thread. OutOfMemory, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -2926,7 +2972,9 @@ pub fn posixFStat(fd: i32) !posix.Stat { const err = posix.getErrno(posix.fstat(fd, &stat)); if (err > 0) { return switch (err) { - posix.EBADF => error.BadFd, + // We do not make this an error code because if you get EBADF it's always a bug, + // since the fd could have been reused. + posix.EBADF => unreachable, posix.ENOMEM => error.SystemResources, else => os.unexpectedErrorPosix(err), }; @@ -2938,6 +2986,8 @@ pub fn posixFStat(fd: i32) !posix.Stat { pub const CpuCountError = error{ OutOfMemory, PermissionDenied, + + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -3008,6 +3058,7 @@ pub const BsdKQueueError = error{ /// The system-wide limit on the total number of open files has been reached. SystemFdQuotaExceeded, + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; diff --git a/std/os/path.zig b/std/os/path.zig index 23c217b295..b3cfec1a3a 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -11,11 +11,14 @@ const math = std.math; const posix = os.posix; const windows = os.windows; const cstr = std.cstr; +const windows_util = @import("windows/util.zig"); pub const sep_windows = '\\'; pub const sep_posix = '/'; pub const sep = if (is_windows) sep_windows else sep_posix; +pub const sep_str = [1]u8{sep}; + pub const delimiter_windows = ';'; pub const delimiter_posix = ':'; pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix; @@ -337,7 +340,7 @@ pub fn resolveSlice(allocator: *Allocator, paths: []const []const u8) ![]u8 { pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { if (paths.len == 0) { assert(is_windows); // resolveWindows called on non windows can't use getCwd - return os.getCwd(allocator); + return os.getCwdAlloc(allocator); } // determine which disk designator we will result with, if any @@ -432,7 +435,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { }, WindowsPath.Kind.None => { assert(is_windows); // resolveWindows called on non windows can't use getCwd - const cwd = try os.getCwd(allocator); + const cwd = try os.getCwdAlloc(allocator); defer allocator.free(cwd); const parsed_cwd = windowsParsePath(cwd); result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1); @@ -448,7 +451,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { } else { assert(is_windows); // resolveWindows called on non windows can't use getCwd // TODO call get cwd for the result_disk_designator instead of the global one - const cwd = try os.getCwd(allocator); + const cwd = try os.getCwdAlloc(allocator); defer allocator.free(cwd); result = try allocator.alloc(u8, max_size + cwd.len + 1); @@ -516,7 +519,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { if (paths.len == 0) { assert(!is_windows); // resolvePosix called on windows can't use getCwd - return os.getCwd(allocator); + return os.getCwdAlloc(allocator); } var first_index: usize = 0; @@ -538,7 +541,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { result = try allocator.alloc(u8, max_size); } else { assert(!is_windows); // resolvePosix called on windows can't use getCwd - const cwd = try os.getCwd(allocator); + const cwd = try os.getCwdAlloc(allocator); defer allocator.free(cwd); result = try allocator.alloc(u8, max_size + cwd.len + 1); mem.copy(u8, result, cwd); @@ -573,11 +576,11 @@ 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" { - const cwd = try os.getCwd(debug.global_allocator); + const cwd = try os.getCwdAlloc(debug.global_allocator); if (is_windows) { if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) { cwd[0] = asciiUpper(cwd[0]); @@ -591,7 +594,7 @@ test "os.path.resolve" { test "os.path.resolveWindows" { if (is_windows) { - const cwd = try os.getCwd(debug.global_allocator); + const cwd = try os.getCwdAlloc(debug.global_allocator); const parsed_cwd = windowsParsePath(cwd); { const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" }); @@ -1073,112 +1076,148 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons assert(mem.eql(u8, result, expected_output)); } -/// Return the canonicalized absolute pathname. -/// Expands all symbolic links and resolves references to `.`, `..`, and -/// extra `/` characters in ::pathname. -/// Caller must deallocate result. -pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 { - switch (builtin.os) { - Os.windows => { - const pathname_buf = try allocator.alloc(u8, pathname.len + 1); - defer allocator.free(pathname_buf); - - mem.copy(u8, pathname_buf, pathname); - pathname_buf[pathname.len] = 0; - - const h_file = windows.CreateFileA(pathname_buf.ptr, windows.GENERIC_READ, windows.FILE_SHARE_READ, null, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null); - if (h_file == windows.INVALID_HANDLE_VALUE) { - const err = windows.GetLastError(); - return switch (err) { - windows.ERROR.FILE_NOT_FOUND => error.FileNotFound, - windows.ERROR.ACCESS_DENIED => error.AccessDenied, - windows.ERROR.FILENAME_EXCED_RANGE => error.NameTooLong, - else => os.unexpectedErrorWindows(err), - }; - } - defer os.close(h_file); - var buf = try allocator.alloc(u8, 256); - errdefer allocator.free(buf); - while (true) { - const buf_len = math.cast(windows.DWORD, buf.len) catch return error.NameTooLong; - const result = windows.GetFinalPathNameByHandleA(h_file, buf.ptr, buf_len, windows.VOLUME_NAME_DOS); - - if (result == 0) { - const err = windows.GetLastError(); - return switch (err) { - windows.ERROR.PATH_NOT_FOUND => error.FileNotFound, - windows.ERROR.NOT_ENOUGH_MEMORY => error.OutOfMemory, - windows.ERROR.INVALID_PARAMETER => unreachable, - else => os.unexpectedErrorWindows(err), - }; - } +pub const RealError = error{ + FileNotFound, + AccessDenied, + NameTooLong, + NotSupported, + NotDir, + SymLinkLoop, + InputOutput, + FileTooBig, + IsDir, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + NoSpaceLeft, + FileSystem, + BadPathName, + + /// On Windows, file paths must be valid Unicode. + InvalidUtf8, + + /// TODO remove this possibility + PathAlreadyExists, + + /// TODO remove this possibility + Unexpected, +}; - if (result > buf.len) { - buf = try allocator.realloc(u8, buf, result); - continue; - } +/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string. +/// Otherwise use `real` or `realC`. +pub fn realW(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u16) RealError![]u8 { + const h_file = windows.CreateFileW( + pathname, + windows.GENERIC_READ, + windows.FILE_SHARE_READ, + null, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL, + null, + ); + if (h_file == windows.INVALID_HANDLE_VALUE) { + const err = windows.GetLastError(); + switch (err) { + windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, + windows.ERROR.ACCESS_DENIED => return error.AccessDenied, + windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, + else => return os.unexpectedErrorWindows(err), + } + } + defer os.close(h_file); + var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; + const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast + const result = windows.GetFinalPathNameByHandleW(h_file, &utf16le_buf, casted_len, windows.VOLUME_NAME_DOS); + assert(result <= utf16le_buf.len); + if (result == 0) { + const err = windows.GetLastError(); + switch (err) { + windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, + windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, + windows.ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources, + windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, + windows.ERROR.INVALID_PARAMETER => unreachable, + else => return os.unexpectedErrorWindows(err), + } + } + const utf16le_slice = utf16le_buf[0..result]; - // windows returns \\?\ prepended to the path - // we strip it because nobody wants \\?\ prepended to their path - const final_len = x: { - if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) { - var i: usize = 4; - while (i < result) : (i += 1) { - buf[i - 4] = buf[i]; - } - break :x result - 4; - } else { - break :x result; - } - }; - - return allocator.shrink(u8, buf, final_len); - } + // windows returns \\?\ prepended to the path + // we strip it because nobody wants \\?\ prepended to their path + const prefix = []u16{ '\\', '\\', '?', '\\' }; + const start_index = if (mem.startsWith(u16, utf16le_slice, prefix)) prefix.len else 0; + + // Trust that Windows gives us valid UTF-16LE. + const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice[start_index..]) catch unreachable; + return out_buffer[0..end_index]; +} + +/// See `real` +/// Use this when you have a null terminated pointer path. +pub fn realC(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u8) RealError![]u8 { + switch (builtin.os) { + Os.windows => { + const pathname_w = try windows_util.cStrToPrefixedFileW(pathname); + return realW(out_buffer, pathname_w); }, Os.macosx, Os.ios => { - // TODO instead of calling the libc function here, port the implementation - // to Zig, and then remove the NameTooLong error possibility. - const pathname_buf = try allocator.alloc(u8, pathname.len + 1); - defer allocator.free(pathname_buf); - - const result_buf = try allocator.alloc(u8, posix.PATH_MAX); - errdefer allocator.free(result_buf); - - mem.copy(u8, pathname_buf, pathname); - pathname_buf[pathname.len] = 0; - - const err = posix.getErrno(posix.realpath(pathname_buf.ptr, result_buf.ptr)); - if (err > 0) { - return switch (err) { - posix.EINVAL => unreachable, - posix.EBADF => unreachable, - posix.EFAULT => unreachable, - posix.EACCES => error.AccessDenied, - posix.ENOENT => error.FileNotFound, - posix.ENOTSUP => error.NotSupported, - posix.ENOTDIR => error.NotDir, - posix.ENAMETOOLONG => error.NameTooLong, - posix.ELOOP => error.SymLinkLoop, - posix.EIO => error.InputOutput, - else => os.unexpectedErrorPosix(err), - }; + // TODO instead of calling the libc function here, port the implementation to Zig + const err = posix.getErrno(posix.realpath(pathname, out_buffer)); + switch (err) { + 0 => return mem.toSlice(u8, out_buffer), + posix.EINVAL => unreachable, + posix.EBADF => unreachable, + posix.EFAULT => unreachable, + posix.EACCES => return error.AccessDenied, + posix.ENOENT => return error.FileNotFound, + posix.ENOTSUP => return error.NotSupported, + posix.ENOTDIR => return error.NotDir, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ELOOP => return error.SymLinkLoop, + posix.EIO => return error.InputOutput, + else => return os.unexpectedErrorPosix(err), } - return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr)); }, Os.linux => { - const fd = try os.posixOpen(allocator, pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0); + const fd = try os.posixOpenC(pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0); defer os.close(fd); var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined; - const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd) catch unreachable; + const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}\x00", fd) catch unreachable; - return os.readLink(allocator, proc_path); + return os.readLinkC(out_buffer, proc_path.ptr); }, else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)), } } +/// Return the canonicalized absolute pathname. +/// Expands all symbolic links and resolves references to `.`, `..`, and +/// extra `/` characters in ::pathname. +/// The return value is a slice of out_buffer, and not necessarily from the beginning. +pub fn real(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: []const u8) RealError![]u8 { + switch (builtin.os) { + Os.windows => { + const pathname_w = try windows_util.sliceToPrefixedFileW(pathname); + return realW(out_buffer, &pathname_w); + }, + Os.macosx, Os.ios, Os.linux => { + const pathname_c = try os.toPosixPath(pathname); + return realC(out_buffer, &pathname_c); + }, + else => @compileError("Unsupported OS"), + } +} + +/// `real`, except caller must free the returned memory. +pub fn realAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 { + var buf: [os.MAX_PATH_BYTES]u8 = undefined; + return mem.dupe(allocator, u8, try real(&buf, pathname)); +} + test "os.path.real" { // at least call it so it gets compiled - _ = real(debug.global_allocator, "some_path"); + var buf: [os.MAX_PATH_BYTES]u8 = undefined; + std.debug.assertError(real(&buf, "definitely_bogus_does_not_exist1234"), error.FileNotFound); } diff --git a/std/os/test.zig b/std/os/test.zig index 82054d3f32..653ab13fd8 100644 --- a/std/os/test.zig +++ b/std/os/test.zig @@ -10,27 +10,27 @@ const AtomicRmwOp = builtin.AtomicRmwOp; const AtomicOrder = builtin.AtomicOrder; test "makePath, put some files in it, deleteTree" { - try os.makePath(a, "os_test_tmp/b/c"); - try io.writeFile(a, "os_test_tmp/b/c/file.txt", "nonsense"); - try io.writeFile(a, "os_test_tmp/b/file2.txt", "blah"); + try os.makePath(a, "os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "c"); + try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "c" ++ os.path.sep_str ++ "file.txt", "nonsense"); + try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "file2.txt", "blah"); try os.deleteTree(a, "os_test_tmp"); if (os.Dir.open(a, "os_test_tmp")) |dir| { @panic("expected error"); } else |err| { - assert(err == error.PathNotFound); + assert(err == error.FileNotFound); } } test "access file" { try os.makePath(a, "os_test_tmp"); - if (os.File.access(a, "os_test_tmp/file.txt")) |ok| { + if (os.File.access("os_test_tmp" ++ os.path.sep_str ++ "file.txt")) |ok| { @panic("expected error"); } else |err| { - assert(err == error.NotFound); + assert(err == error.FileNotFound); } - try io.writeFile(a, "os_test_tmp/file.txt", ""); - try os.File.access(a, "os_test_tmp/file.txt"); + try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "file.txt", ""); + try os.File.access("os_test_tmp" ++ os.path.sep_str ++ "file.txt"); try os.deleteTree(a, "os_test_tmp"); } diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig index 0cdf27754a..66b5291189 100644 --- a/std/os/windows/kernel32.zig +++ b/std/os/windows/kernel32.zig @@ -1,14 +1,11 @@ use @import("index.zig"); - pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL; pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL; -pub extern "kernel32" stdcallcc fn CreateDirectoryA( - lpPathName: LPCSTR, - lpSecurityAttributes: ?*SECURITY_ATTRIBUTES, -) BOOL; +pub extern "kernel32" stdcallcc fn CreateDirectoryA(lpPathName: [*]const u8, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL; +pub extern "kernel32" stdcallcc fn CreateDirectoryW(lpPathName: [*]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL; pub extern "kernel32" stdcallcc fn CreateFileA( lpFileName: [*]const u8, // TODO null terminated pointer type @@ -60,7 +57,8 @@ pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, Ex pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE; -pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL; +pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: [*]const u8) BOOL; +pub extern "kernel32" stdcallcc fn DeleteFileW(lpFileName: [*]const u16) BOOL; pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn; @@ -74,7 +72,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: DWORD, lpBuffer: ?[*]CHAR) DWORD; +pub extern "kernel32" stdcallcc fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: ?[*]WCHAR) DWORD; pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE; pub extern "kernel32" stdcallcc fn GetCurrentThreadId() DWORD; @@ -87,9 +86,11 @@ pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCo pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL; -pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: LPCSTR) DWORD; +pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: [*]const CHAR) DWORD; +pub extern "kernel32" stdcallcc fn GetFileAttributesW(lpFileName: [*]const WCHAR) DWORD; -pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: LPSTR, nSize: DWORD) DWORD; +pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: [*]u8, nSize: DWORD) DWORD; +pub extern "kernel32" stdcallcc fn GetModuleFileNameW(hModule: ?HMODULE, lpFilename: [*]u16, nSize: DWORD) DWORD; pub extern "kernel32" stdcallcc fn GetLastError() DWORD; @@ -107,6 +108,12 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA( dwFlags: DWORD, ) DWORD; +pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleW( + hFile: HANDLE, + lpszFilePath: [*]u16, + cchFilePath: DWORD, + dwFlags: DWORD, +) DWORD; pub extern "kernel32" stdcallcc fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) BOOL; @@ -132,8 +139,14 @@ pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL; pub extern "kernel32" stdcallcc fn MoveFileExA( - lpExistingFileName: LPCSTR, - lpNewFileName: LPCSTR, + lpExistingFileName: [*]const u8, + lpNewFileName: [*]const u8, + dwFlags: DWORD, +) BOOL; + +pub extern "kernel32" stdcallcc fn MoveFileExW( + lpExistingFileName: [*]const u16, + lpNewFileName: [*]const u16, dwFlags: DWORD, ) BOOL; @@ -194,7 +207,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 +220,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..72de896996 100644 --- a/std/os/windows/util.zig +++ b/std/os/windows/util.zig @@ -7,9 +7,17 @@ const mem = std.mem; const BufMap = std.BufMap; const cstr = std.cstr; +// > The maximum path of 32,767 characters is approximate, because the "\\?\" +// > prefix may be expanded to a longer string by the system at run time, and +// > this expansion applies to the total length. +// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation +pub const PATH_MAX_WIDE = 32767; + pub const WaitError = error{ WaitAbandoned, WaitTimeOut, + + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -37,6 +45,8 @@ pub const WriteError = error{ SystemResources, OperationAborted, BrokenPipe, + + /// See https://github.com/ziglang/zig/issues/1396 Unexpected, }; @@ -86,37 +96,51 @@ pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool { pub const OpenError = error{ SharingViolation, PathAlreadyExists, + + /// When any of the path components can not be found or the file component can not + /// be found. Some operating systems distinguish between path components not found and + /// file components not found, but they are collapsed into FileNotFound to gain + /// consistency across operating systems. FileNotFound, + AccessDenied, PipeBusy, + NameTooLong, + + /// On Windows, file paths must be valid Unicode. + InvalidUtf8, + + /// On Windows, file paths cannot contain these characters: + /// '/', '*', '?', '"', '<', '>', '|' + BadPathName, + + /// See https://github.com/ziglang/zig/issues/1396 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 file_path_w = try sliceToPrefixedFileW(file_path); - const result = windows.CreateFileA(path_with_null.ptr, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null); + const result = windows.CreateFileW(&file_path_w, 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), - }; + switch (err) { + windows.ERROR.SHARING_VIOLATION => return OpenError.SharingViolation, + windows.ERROR.ALREADY_EXISTS => return OpenError.PathAlreadyExists, + windows.ERROR.FILE_EXISTS => return OpenError.PathAlreadyExists, + windows.ERROR.FILE_NOT_FOUND => return OpenError.FileNotFound, + windows.ERROR.PATH_NOT_FOUND => return OpenError.FileNotFound, + windows.ERROR.ACCESS_DENIED => return OpenError.AccessDenied, + windows.ERROR.PIPE_BUSY => return OpenError.PipeBusy, + else => return os.unexpectedErrorWindows(err), + } } return result; @@ -192,9 +216,8 @@ pub fn windowsFindFirstFile( if (handle == windows.INVALID_HANDLE_VALUE) { const err = windows.GetLastError(); switch (err) { - windows.ERROR.FILE_NOT_FOUND, - windows.ERROR.PATH_NOT_FOUND, - => return error.PathNotFound, + windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, + windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, else => return os.unexpectedErrorWindows(err), } } @@ -238,7 +261,7 @@ pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_ } } -pub const WindowsWaitResult = enum{ +pub const WindowsWaitResult = enum { Normal, Aborted, Cancelled, @@ -254,8 +277,39 @@ pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_t if (std.debug.runtime_safety) { std.debug.panic("unexpected error: {}\n", err); } - } + }, } } return WindowsWaitResult.Normal; } + +pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 { + return sliceToPrefixedFileW(mem.toSliceConst(u8, s)); +} + +pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 { + // TODO well defined copy elision + var result: [PATH_MAX_WIDE + 1]u16 = undefined; + + // > File I/O functions in the Windows API convert "/" to "\" as part of + // > converting the name to an NT-style name, except when using the "\\?\" + // > prefix as detailed in the following sections. + // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation + // Because we want the larger maximum path length for absolute paths, we + // disallow forward slashes in zig std lib file functions on Windows. + for (s) |byte| + switch (byte) { + '/', '*', '?', '"', '<', '>', '|' => return error.BadPathName, + else => {}, + }; + const start_index = if (mem.startsWith(u8, s, "\\\\") or !os.path.isAbsolute(s)) 0 else blk: { + const prefix = []u16{ '\\', '\\', '?', '\\' }; + mem.copy(u16, result[0..], prefix); + break :blk prefix.len; + }; + const end_index = start_index + try std.unicode.utf8ToUtf16Le(result[start_index..], s); + assert(end_index <= result.len); + if (end_index == result.len) return error.NameTooLong; + result[end_index] = 0; + return result; +} diff --git a/std/unicode.zig b/std/unicode.zig index 0e7b4cdc3e..105c38627f 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,38 @@ 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 { + assert(it.i <= it.bytes.len); + if (it.i == it.bytes.len) return null; + 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; + it.i += 2; + return 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff)); + } else if (c0 & ~u32(0x03ff) == 0xdc00) { + return error.UnexpectedSecondSurrogateHalf; + } else { + it.i += 2; + return c0; + } + } +}; + test "utf8 encode" { comptime testUtf8Encode() catch unreachable; try testUtf8Encode(); @@ -446,42 +477,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(); } +/// Asserts that the output buffer is big enough. +/// Returns end byte index into utf8. +pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !usize { + var end_index: usize = 0; + var it = Utf16LeIterator.init(utf16le); + while (try it.nextCodepoint()) |codepoint| { + end_index += try utf8Encode(codepoint, utf8[end_index..]); + } + return end_index; +} + test "utf16leToUtf8" { var utf16le: [2]u16 = undefined; const utf16le_as_bytes = @sliceToBytes(utf16le[0..]); @@ -489,14 +512,14 @@ test "utf16leToUtf8" { { mem.writeInt(utf16le_as_bytes[0..], u16('A'), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16('a'), builtin.Endian.Little); - const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le); assert(mem.eql(u8, utf8, "Aa")); } { mem.writeInt(utf16le_as_bytes[0..], u16(0x80), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16(0xffff), builtin.Endian.Little); - const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le); assert(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf")); } @@ -504,7 +527,7 @@ test "utf16leToUtf8" { // the values just outside the surrogate half range mem.writeInt(utf16le_as_bytes[0..], u16(0xd7ff), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16(0xe000), builtin.Endian.Little); - const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le); assert(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80")); } @@ -512,7 +535,7 @@ test "utf16leToUtf8" { // smallest surrogate pair mem.writeInt(utf16le_as_bytes[0..], u16(0xd800), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little); - const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le); assert(mem.eql(u8, utf8, "\xf0\x90\x80\x80")); } @@ -520,14 +543,14 @@ test "utf16leToUtf8" { // largest surrogate pair mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16(0xdfff), builtin.Endian.Little); - const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le); assert(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf")); } { mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little); mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little); - const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le); + const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le); assert(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80")); } } @@ -548,3 +571,20 @@ pub fn utf8ToUtf16LeWithNull(allocator: *mem.Allocator, utf8: []const u8) ![]u16 try result.append(0); return result.toOwnedSlice(); } + +/// Returns index of next character. If exact fit, returned index equals output slice length. +/// If ran out of room, returned index equals output slice length + 1. +/// TODO support codepoints bigger than 16 bits +pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize { + const utf16le_as_bytes = @sliceToBytes(utf16le[0..]); + var end_index: usize = 0; + + var it = (try Utf8View.init(utf8)).iterator(); + while (it.nextCodepoint()) |codepoint| { + if (end_index == utf16le_as_bytes.len) return (end_index / 2) + 1; + // TODO surrogate pairs + mem.writeInt(utf16le_as_bytes[end_index..], @intCast(u16, codepoint), builtin.Endian.Little); + end_index += 2; + } + return end_index / 2; +} |
