authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-08-21 21:02:01-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-08-21 21:02:01-04:00
log3d780cf2ef8391b6b48124f599858ee99ddc4cdc
tree5e073a9784a6fa4699e0eca9a3eb0148756e6722
parentb2917e6be09138adcf7cfdab51a1909a30eec320
parent3dd1026c8bcb438228c336add7cc4014552aa05c

Merge branch 'shawnl-path_max'

This does a proof of concept of changing most file system APIs to not require an allocator and remove the possibility of failure via OutOfMemory. This also does most of the work of #534.

25 files changed, 794 insertions(+), 566 deletions(-)

build.zig+1-1
...@@ -19,7 +19,7 @@ pub fn build(b: *Builder) !void {...@@ -19,7 +19,7 @@ pub fn build(b: *Builder) !void {
19 var docgen_cmd = b.addCommand(null, b.env_map, [][]const u8{19 var docgen_cmd = b.addCommand(null, b.env_map, [][]const u8{
20 docgen_exe.getOutputPath(),20 docgen_exe.getOutputPath(),
21 rel_zig_exe,21 rel_zig_exe,
22 "doc/langref.html.in",22 "doc" ++ os.path.sep_str ++ "langref.html.in",
23 os.path.join(b.allocator, b.cache_root, "langref.html") catch unreachable,23 os.path.join(b.allocator, b.cache_root, "langref.html") catch unreachable,
24 });24 });
25 docgen_cmd.step.dependOn(&docgen_exe.step);25 docgen_cmd.step.dependOn(&docgen_exe.step);
doc/docgen.zig+3-3
...@@ -34,10 +34,10 @@ pub fn main() !void {...@@ -34,10 +34,10 @@ pub fn main() !void {
34 const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg"));34 const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg"));
35 defer allocator.free(out_file_name);35 defer allocator.free(out_file_name);
3636
37 var in_file = try os.File.openRead(allocator, in_file_name);37 var in_file = try os.File.openRead(in_file_name);
38 defer in_file.close();38 defer in_file.close();
3939
40 var out_file = try os.File.openWrite(allocator, out_file_name);40 var out_file = try os.File.openWrite(out_file_name);
41 defer out_file.close();41 defer out_file.close();
4242
43 var file_in_stream = io.FileInStream.init(&in_file);43 var file_in_stream = io.FileInStream.init(&in_file);
...@@ -738,7 +738,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var...@@ -738,7 +738,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
738 try out.print("<pre><code class=\"zig\">{}</code></pre>", escaped_source);738 try out.print("<pre><code class=\"zig\">{}</code></pre>", escaped_source);
739 const name_plus_ext = try std.fmt.allocPrint(allocator, "{}.zig", code.name);739 const name_plus_ext = try std.fmt.allocPrint(allocator, "{}.zig", code.name);
740 const tmp_source_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_ext);740 const tmp_source_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_ext);
741 try io.writeFile(allocator, tmp_source_file_name, trimmed_raw_source);741 try io.writeFile(tmp_source_file_name, trimmed_raw_source);
742742
743 switch (code.id) {743 switch (code.id) {
744 Code.Id.Exe => |expected_outcome| {744 Code.Id.Exe => |expected_outcome| {
example/cat/main.zig+1-1
...@@ -20,7 +20,7 @@ pub fn main() !void {...@@ -20,7 +20,7 @@ pub fn main() !void {
20 } else if (arg[0] == '-') {20 } else if (arg[0] == '-') {
21 return usage(exe);21 return usage(exe);
22 } else {22 } else {
23 var file = os.File.openRead(allocator, arg) catch |err| {23 var file = os.File.openRead(arg) catch |err| {
24 warn("Unable to open file: {}\n", @errorName(err));24 warn("Unable to open file: {}\n", @errorName(err));
25 return err;25 return err;
26 };26 };
src-self-hosted/compilation.zig+2-4
...@@ -257,8 +257,6 @@ pub const Compilation = struct {...@@ -257,8 +257,6 @@ pub const Compilation = struct {
257 pub const BuildError = error{257 pub const BuildError = error{
258 OutOfMemory,258 OutOfMemory,
259 EndOfStream,259 EndOfStream,
260 BadFd,
261 Io,
262 IsDir,260 IsDir,
263 Unexpected,261 Unexpected,
264 SystemResources,262 SystemResources,
...@@ -273,7 +271,6 @@ pub const Compilation = struct {...@@ -273,7 +271,6 @@ pub const Compilation = struct {
273 NameTooLong,271 NameTooLong,
274 SystemFdQuotaExceeded,272 SystemFdQuotaExceeded,
275 NoDevice,273 NoDevice,
276 PathNotFound,
277 NoSpaceLeft,274 NoSpaceLeft,
278 NotDir,275 NotDir,
279 FileSystem,276 FileSystem,
...@@ -302,6 +299,7 @@ pub const Compilation = struct {...@@ -302,6 +299,7 @@ pub const Compilation = struct {
302 UnsupportedLinkArchitecture,299 UnsupportedLinkArchitecture,
303 UserResourceLimitReached,300 UserResourceLimitReached,
304 InvalidUtf8,301 InvalidUtf8,
302 BadPathName,
305 };303 };
306304
307 pub const Event = union(enum) {305 pub const Event = union(enum) {
...@@ -961,7 +959,7 @@ pub const Compilation = struct {...@@ -961,7 +959,7 @@ pub const Compilation = struct {
961 if (self.root_src_path) |root_src_path| {959 if (self.root_src_path) |root_src_path| {
962 const root_scope = blk: {960 const root_scope = blk: {
963 // TODO async/await os.path.real961 // TODO async/await os.path.real
964 const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| {962 const root_src_real_path = os.path.realAlloc(self.gpa(), root_src_path) catch |err| {
965 try self.addCompileErrorCli(root_src_path, "unable to open: {}", @errorName(err));963 try self.addCompileErrorCli(root_src_path, "unable to open: {}", @errorName(err));
966 return;964 return;
967 };965 };
src-self-hosted/errmsg.zig+1-1
...@@ -235,7 +235,7 @@ pub const Msg = struct {...@@ -235,7 +235,7 @@ pub const Msg = struct {
235 const allocator = msg.getAllocator();235 const allocator = msg.getAllocator();
236 const tree = msg.getTree();236 const tree = msg.getTree();
237237
238 const cwd = try os.getCwd(allocator);238 const cwd = try os.getCwdAlloc(allocator);
239 defer allocator.free(cwd);239 defer allocator.free(cwd);
240240
241 const relpath = try os.path.relative(allocator, cwd, msg.realpath);241 const relpath = try os.path.relative(allocator, cwd, msg.realpath);
src-self-hosted/introspect.zig+2-2
...@@ -14,7 +14,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![...@@ -14,7 +14,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![
14 const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig");14 const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig");
15 defer allocator.free(test_index_file);15 defer allocator.free(test_index_file);
1616
17 var file = try os.File.openRead(allocator, test_index_file);17 var file = try os.File.openRead(test_index_file);
18 file.close();18 file.close();
1919
20 return test_zig_dir;20 return test_zig_dir;
...@@ -22,7 +22,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![...@@ -22,7 +22,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![
2222
23/// Caller must free result23/// Caller must free result
24pub fn findZigLibDir(allocator: *mem.Allocator) ![]u8 {24pub fn findZigLibDir(allocator: *mem.Allocator) ![]u8 {
25 const self_exe_path = try os.selfExeDirPath(allocator);25 const self_exe_path = try os.selfExeDirPathAlloc(allocator);
26 defer allocator.free(self_exe_path);26 defer allocator.free(self_exe_path);
2727
28 var cur_path: []const u8 = self_exe_path;28 var cur_path: []const u8 = self_exe_path;
src-self-hosted/libc_installation.zig+7-8
...@@ -233,7 +233,7 @@ pub const LibCInstallation = struct {...@@ -233,7 +233,7 @@ pub const LibCInstallation = struct {
233 const stdlib_path = try std.os.path.join(loop.allocator, search_path, "stdlib.h");233 const stdlib_path = try std.os.path.join(loop.allocator, search_path, "stdlib.h");
234 defer loop.allocator.free(stdlib_path);234 defer loop.allocator.free(stdlib_path);
235235
236 if (try fileExists(loop.allocator, stdlib_path)) {236 if (try fileExists(stdlib_path)) {
237 self.include_dir = try std.mem.dupe(loop.allocator, u8, search_path);237 self.include_dir = try std.mem.dupe(loop.allocator, u8, search_path);
238 return;238 return;
239 }239 }
...@@ -257,7 +257,7 @@ pub const LibCInstallation = struct {...@@ -257,7 +257,7 @@ pub const LibCInstallation = struct {
257 const stdlib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "stdlib.h");257 const stdlib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "stdlib.h");
258 defer loop.allocator.free(stdlib_path);258 defer loop.allocator.free(stdlib_path);
259259
260 if (try fileExists(loop.allocator, stdlib_path)) {260 if (try fileExists(stdlib_path)) {
261 self.include_dir = result_buf.toOwnedSlice();261 self.include_dir = result_buf.toOwnedSlice();
262 return;262 return;
263 }263 }
...@@ -285,7 +285,7 @@ pub const LibCInstallation = struct {...@@ -285,7 +285,7 @@ pub const LibCInstallation = struct {
285 }285 }
286 const ucrt_lib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "ucrt.lib");286 const ucrt_lib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "ucrt.lib");
287 defer loop.allocator.free(ucrt_lib_path);287 defer loop.allocator.free(ucrt_lib_path);
288 if (try fileExists(loop.allocator, ucrt_lib_path)) {288 if (try fileExists(ucrt_lib_path)) {
289 self.lib_dir = result_buf.toOwnedSlice();289 self.lib_dir = result_buf.toOwnedSlice();
290 return;290 return;
291 }291 }
...@@ -360,7 +360,7 @@ pub const LibCInstallation = struct {...@@ -360,7 +360,7 @@ pub const LibCInstallation = struct {
360 }360 }
361 const kernel32_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "kernel32.lib");361 const kernel32_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "kernel32.lib");
362 defer loop.allocator.free(kernel32_path);362 defer loop.allocator.free(kernel32_path);
363 if (try fileExists(loop.allocator, kernel32_path)) {363 if (try fileExists(kernel32_path)) {
364 self.kernel32_lib_dir = result_buf.toOwnedSlice();364 self.kernel32_lib_dir = result_buf.toOwnedSlice();
365 return;365 return;
366 }366 }
...@@ -449,12 +449,11 @@ fn fillSearch(search_buf: *[2]Search, sdk: *c.ZigWindowsSDK) []Search {...@@ -449,12 +449,11 @@ fn fillSearch(search_buf: *[2]Search, sdk: *c.ZigWindowsSDK) []Search {
449 return search_buf[0..search_end];449 return search_buf[0..search_end];
450}450}
451451
452fn fileExists(allocator: *std.mem.Allocator, path: []const u8) !bool {452fn fileExists(path: []const u8) !bool {
453 if (std.os.File.access(allocator, path)) |_| {453 if (std.os.File.access(path)) |_| {
454 return true;454 return true;
455 } else |err| switch (err) {455 } else |err| switch (err) {
456 error.NotFound, error.PermissionDenied => return false,456 error.FileNotFound, error.PermissionDenied => return false,
457 error.OutOfMemory => return error.OutOfMemory,
458 else => return error.FileSystem,457 else => return error.FileSystem,
459 }458 }
460}459}
src-self-hosted/test.zig+2-2
...@@ -94,7 +94,7 @@ pub const TestContext = struct {...@@ -94,7 +94,7 @@ pub const TestContext = struct {
94 }94 }
9595
96 // TODO async I/O96 // TODO async I/O
97 try std.io.writeFile(allocator, file1_path, source);97 try std.io.writeFile(file1_path, source);
9898
99 var comp = try Compilation.create(99 var comp = try Compilation.create(
100 &self.zig_compiler,100 &self.zig_compiler,
...@@ -128,7 +128,7 @@ pub const TestContext = struct {...@@ -128,7 +128,7 @@ pub const TestContext = struct {
128 }128 }
129129
130 // TODO async I/O130 // TODO async I/O
131 try std.io.writeFile(allocator, file1_path, source);131 try std.io.writeFile(file1_path, source);
132132
133 var comp = try Compilation.create(133 var comp = try Compilation.create(
134 &self.zig_compiler,134 &self.zig_compiler,
std/build.zig+15-9
...@@ -267,7 +267,7 @@ pub const Builder = struct {...@@ -267,7 +267,7 @@ pub const Builder = struct {
267 if (self.verbose) {267 if (self.verbose) {
268 warn("rm {}\n", installed_file);268 warn("rm {}\n", installed_file);
269 }269 }
270 _ = os.deleteFile(self.allocator, installed_file);270 _ = os.deleteFile(installed_file);
271 }271 }
272272
273 // TODO remove empty directories273 // TODO remove empty directories
...@@ -1182,7 +1182,7 @@ pub const LibExeObjStep = struct {...@@ -1182,7 +1182,7 @@ pub const LibExeObjStep = struct {
11821182
1183 if (self.build_options_contents.len() > 0) {1183 if (self.build_options_contents.len() > 0) {
1184 const build_options_file = try os.path.join(builder.allocator, builder.cache_root, builder.fmt("{}_build_options.zig", self.name));1184 const build_options_file = try os.path.join(builder.allocator, builder.cache_root, builder.fmt("{}_build_options.zig", self.name));
1185 try std.io.writeFile(builder.allocator, build_options_file, self.build_options_contents.toSliceConst());1185 try std.io.writeFile(build_options_file, self.build_options_contents.toSliceConst());
1186 try zig_args.append("--pkg-begin");1186 try zig_args.append("--pkg-begin");
1187 try zig_args.append("build_options");1187 try zig_args.append("build_options");
1188 try zig_args.append(builder.pathFromRoot(build_options_file));1188 try zig_args.append(builder.pathFromRoot(build_options_file));
...@@ -1491,11 +1491,14 @@ pub const LibExeObjStep = struct {...@@ -1491,11 +1491,14 @@ pub const LibExeObjStep = struct {
1491 }1491 }
14921492
1493 if (!is_darwin) {1493 if (!is_darwin) {
1494 const rpath_arg = builder.fmt("-Wl,-rpath,{}", os.path.real(builder.allocator, builder.pathFromRoot(builder.cache_root)) catch unreachable);1494 const rpath_arg = builder.fmt("-Wl,-rpath,{}", try os.path.realAlloc(
1495 builder.allocator,
1496 builder.pathFromRoot(builder.cache_root),
1497 ));
1495 defer builder.allocator.free(rpath_arg);1498 defer builder.allocator.free(rpath_arg);
1496 cc_args.append(rpath_arg) catch unreachable;1499 try cc_args.append(rpath_arg);
14971500
1498 cc_args.append("-rdynamic") catch unreachable;1501 try cc_args.append("-rdynamic");
1499 }1502 }
15001503
1501 for (self.full_path_libs.toSliceConst()) |full_path_lib| {1504 for (self.full_path_libs.toSliceConst()) |full_path_lib| {
...@@ -1566,11 +1569,14 @@ pub const LibExeObjStep = struct {...@@ -1566,11 +1569,14 @@ pub const LibExeObjStep = struct {
1566 cc_args.append("-o") catch unreachable;1569 cc_args.append("-o") catch unreachable;
1567 cc_args.append(output_path) catch unreachable;1570 cc_args.append(output_path) catch unreachable;
15681571
1569 const rpath_arg = builder.fmt("-Wl,-rpath,{}", os.path.real(builder.allocator, builder.pathFromRoot(builder.cache_root)) catch unreachable);1572 const rpath_arg = builder.fmt("-Wl,-rpath,{}", try os.path.realAlloc(
1573 builder.allocator,
1574 builder.pathFromRoot(builder.cache_root),
1575 ));
1570 defer builder.allocator.free(rpath_arg);1576 defer builder.allocator.free(rpath_arg);
1571 cc_args.append(rpath_arg) catch unreachable;1577 try cc_args.append(rpath_arg);
15721578
1573 cc_args.append("-rdynamic") catch unreachable;1579 try cc_args.append("-rdynamic");
15741580
1575 {1581 {
1576 var it = self.link_libs.iterator();1582 var it = self.link_libs.iterator();
...@@ -1917,7 +1923,7 @@ pub const WriteFileStep = struct {...@@ -1917,7 +1923,7 @@ pub const WriteFileStep = struct {
1917 warn("unable to make path {}: {}\n", full_path_dir, @errorName(err));1923 warn("unable to make path {}: {}\n", full_path_dir, @errorName(err));
1918 return err;1924 return err;
1919 };1925 };
1920 io.writeFile(self.builder.allocator, full_path, self.data) catch |err| {1926 io.writeFile(full_path, self.data) catch |err| {
1921 warn("unable to write {}: {}\n", full_path, @errorName(err));1927 warn("unable to write {}: {}\n", full_path, @errorName(err));
1922 return err;1928 return err;
1923 };1929 };
std/cstr.zig+6-5
...@@ -9,10 +9,9 @@ pub const line_sep = switch (builtin.os) {...@@ -9,10 +9,9 @@ pub const line_sep = switch (builtin.os) {
9 else => "\n",9 else => "\n",
10};10};
1111
12/// Deprecated, use mem.len
12pub fn len(ptr: [*]const u8) usize {13pub fn len(ptr: [*]const u8) usize {
13 var count: usize = 0;14 return mem.len(u8, ptr);
14 while (ptr[count] != 0) : (count += 1) {}
15 return count;
16}15}
1716
18pub fn cmp(a: [*]const u8, b: [*]const u8) i8 {17pub fn cmp(a: [*]const u8, b: [*]const u8) i8 {
...@@ -27,12 +26,14 @@ pub fn cmp(a: [*]const u8, b: [*]const u8) i8 {...@@ -27,12 +26,14 @@ pub fn cmp(a: [*]const u8, b: [*]const u8) i8 {
27 }26 }
28}27}
2928
29/// Deprecated, use mem.toSliceConst
30pub fn toSliceConst(str: [*]const u8) []const u8 {30pub fn toSliceConst(str: [*]const u8) []const u8 {
31 return str[0..len(str)];31 return mem.toSliceConst(u8, str);
32}32}
3333
34/// Deprecated, use mem.toSlice
34pub fn toSlice(str: [*]u8) []u8 {35pub fn toSlice(str: [*]u8) []u8 {
35 return str[0..len(str)];36 return mem.toSlice(u8, str);
36}37}
3738
38test "cstr fns" {39test "cstr fns" {
std/debug/index.zig+3-3
...@@ -255,7 +255,7 @@ pub fn printSourceAtAddress(debug_info: *ElfStackTrace, out_stream: var, address...@@ -255,7 +255,7 @@ pub fn printSourceAtAddress(debug_info: *ElfStackTrace, out_stream: var, address
255 address,255 address,
256 compile_unit_name,256 compile_unit_name,
257 );257 );
258 if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) {258 if (printLineFromFile(out_stream, line_info)) {
259 if (line_info.column == 0) {259 if (line_info.column == 0) {
260 try out_stream.write("\n");260 try out_stream.write("\n");
261 } else {261 } else {
...@@ -340,8 +340,8 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) !*ElfStackTrace {...@@ -340,8 +340,8 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) !*ElfStackTrace {
340 }340 }
341}341}
342342
343fn printLineFromFile(allocator: *mem.Allocator, out_stream: var, line_info: *const LineInfo) !void {343fn printLineFromFile(out_stream: var, line_info: *const LineInfo) !void {
344 var f = try os.File.openRead(allocator, line_info.file_name);344 var f = try os.File.openRead(line_info.file_name);
345 defer f.close();345 defer f.close();
346 // TODO fstat and make sure that the file has the correct size346 // TODO fstat and make sure that the file has the correct size
347347
std/event/fs.zig+24-36
...@@ -78,8 +78,7 @@ pub async fn pwritev(loop: *Loop, fd: os.FileHandle, data: []const []const u8, o...@@ -78,8 +78,7 @@ pub async fn pwritev(loop: *Loop, fd: os.FileHandle, data: []const []const u8, o
78 builtin.Os.macosx,78 builtin.Os.macosx,
79 builtin.Os.linux,79 builtin.Os.linux,
80 => return await (async pwritevPosix(loop, fd, data, offset) catch unreachable),80 => return await (async pwritevPosix(loop, fd, data, offset) catch unreachable),
81 builtin.Os.windows,81 builtin.Os.windows => return await (async pwritevWindows(loop, fd, data, offset) catch unreachable),
82 => return await (async pwritevWindows(loop, fd, data, offset) catch unreachable),
83 else => @compileError("Unsupported OS"),82 else => @compileError("Unsupported OS"),
84 }83 }
85}84}
...@@ -147,7 +146,6 @@ pub async fn pwriteWindows(loop: *Loop, fd: os.FileHandle, data: []const u8, off...@@ -147,7 +146,6 @@ pub async fn pwriteWindows(loop: *Loop, fd: os.FileHandle, data: []const u8, off
147 }146 }
148}147}
149148
150
151/// data - just the inner references - must live until pwritev promise completes.149/// data - just the inner references - must live until pwritev promise completes.
152pub async fn pwritevPosix(loop: *Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void {150pub async fn pwritevPosix(loop: *Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void {
153 // workaround for https://github.com/ziglang/zig/issues/1194151 // 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:...@@ -203,8 +201,7 @@ pub async fn preadv(loop: *Loop, fd: os.FileHandle, data: []const []u8, offset:
203 builtin.Os.macosx,201 builtin.Os.macosx,
204 builtin.Os.linux,202 builtin.Os.linux,
205 => return await (async preadvPosix(loop, fd, data, offset) catch unreachable),203 => return await (async preadvPosix(loop, fd, data, offset) catch unreachable),
206 builtin.Os.windows,204 builtin.Os.windows => return await (async preadvWindows(loop, fd, data, offset) catch unreachable),
207 => return await (async preadvWindows(loop, fd, data, offset) catch unreachable),
208 else => @compileError("Unsupported OS"),205 else => @compileError("Unsupported OS"),
209 }206 }
210}207}
...@@ -222,7 +219,7 @@ pub async fn preadvWindows(loop: *Loop, fd: os.FileHandle, data: []const []u8, o...@@ -222,7 +219,7 @@ pub async fn preadvWindows(loop: *Loop, fd: os.FileHandle, data: []const []u8, o
222 var inner_off: usize = 0;219 var inner_off: usize = 0;
223 while (true) {220 while (true) {
224 const v = data_copy[iov_i];221 const v = data_copy[iov_i];
225 const amt_read = try await (async preadWindows(loop, fd, v[inner_off .. v.len-inner_off], offset + off) catch unreachable);222 const amt_read = try await (async preadWindows(loop, fd, v[inner_off .. v.len - inner_off], offset + off) catch unreachable);
226 off += amt_read;223 off += amt_read;
227 inner_off += amt_read;224 inner_off += amt_read;
228 if (inner_off == v.len) {225 if (inner_off == v.len) {
...@@ -340,8 +337,7 @@ pub async fn openPosix(...@@ -340,8 +337,7 @@ pub async fn openPosix(
340 resume @handle();337 resume @handle();
341 }338 }
342339
343 const path_with_null = try std.cstr.addNullByte(loop.allocator, path);340 const path_c = try std.os.toPosixPath(path);
344 defer loop.allocator.free(path_with_null);
345341
346 var req_node = RequestNode{342 var req_node = RequestNode{
347 .prev = null,343 .prev = null,
...@@ -349,7 +345,7 @@ pub async fn openPosix(...@@ -349,7 +345,7 @@ pub async fn openPosix(
349 .data = Request{345 .data = Request{
350 .msg = Request.Msg{346 .msg = Request.Msg{
351 .Open = Request.Msg.Open{347 .Open = Request.Msg.Open{
352 .path = path_with_null[0..path.len],348 .path = path_c[0..path.len],
353 .flags = flags,349 .flags = flags,
354 .mode = mode,350 .mode = mode,
355 .result = undefined,351 .result = undefined,
...@@ -382,7 +378,6 @@ pub async fn openRead(loop: *Loop, path: []const u8) os.File.OpenError!os.FileHa...@@ -382,7 +378,6 @@ pub async fn openRead(loop: *Loop, path: []const u8) os.File.OpenError!os.FileHa
382 },378 },
383379
384 builtin.Os.windows => return os.windowsOpen(380 builtin.Os.windows => return os.windowsOpen(
385 loop.allocator,
386 path,381 path,
387 windows.GENERIC_READ,382 windows.GENERIC_READ,
388 windows.FILE_SHARE_READ,383 windows.FILE_SHARE_READ,
...@@ -409,9 +404,7 @@ pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: os.File.Mode) os...@@ -409,9 +404,7 @@ pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: os.File.Mode) os
409 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;404 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
410 return await (async openPosix(loop, path, flags, os.File.default_mode) catch unreachable);405 return await (async openPosix(loop, path, flags, os.File.default_mode) catch unreachable);
411 },406 },
412 builtin.Os.windows,407 builtin.Os.windows => return os.windowsOpen(
413 => return os.windowsOpen(
414 loop.allocator,
415 path,408 path,
416 windows.GENERIC_WRITE,409 windows.GENERIC_WRITE,
417 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,410 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
...@@ -435,9 +428,8 @@ pub async fn openReadWrite(...@@ -435,9 +428,8 @@ pub async fn openReadWrite(
435 },428 },
436429
437 builtin.Os.windows => return os.windowsOpen(430 builtin.Os.windows => return os.windowsOpen(
438 loop.allocator,
439 path,431 path,
440 windows.GENERIC_WRITE|windows.GENERIC_READ,432 windows.GENERIC_WRITE | windows.GENERIC_READ,
441 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,433 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
442 windows.OPEN_ALWAYS,434 windows.OPEN_ALWAYS,
443 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,435 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
...@@ -513,8 +505,7 @@ pub const CloseOperation = struct {...@@ -513,8 +505,7 @@ pub const CloseOperation = struct {
513 self.loop.allocator.destroy(self);505 self.loop.allocator.destroy(self);
514 }506 }
515 },507 },
516 builtin.Os.windows,508 builtin.Os.windows => {
517 => {
518 if (self.os_data.handle) |handle| {509 if (self.os_data.handle) |handle| {
519 os.close(handle);510 os.close(handle);
520 }511 }
...@@ -532,8 +523,7 @@ pub const CloseOperation = struct {...@@ -532,8 +523,7 @@ pub const CloseOperation = struct {
532 self.os_data.close_req_node.data.msg.Close.fd = handle;523 self.os_data.close_req_node.data.msg.Close.fd = handle;
533 self.os_data.have_fd = true;524 self.os_data.have_fd = true;
534 },525 },
535 builtin.Os.windows,526 builtin.Os.windows => {
536 => {
537 self.os_data.handle = handle;527 self.os_data.handle = handle;
538 },528 },
539 else => @compileError("Unsupported OS"),529 else => @compileError("Unsupported OS"),
...@@ -548,8 +538,7 @@ pub const CloseOperation = struct {...@@ -548,8 +538,7 @@ pub const CloseOperation = struct {
548 => {538 => {
549 self.os_data.have_fd = false;539 self.os_data.have_fd = false;
550 },540 },
551 builtin.Os.windows,541 builtin.Os.windows => {
552 => {
553 self.os_data.handle = null;542 self.os_data.handle = null;
554 },543 },
555 else => @compileError("Unsupported OS"),544 else => @compileError("Unsupported OS"),
...@@ -564,8 +553,7 @@ pub const CloseOperation = struct {...@@ -564,8 +553,7 @@ pub const CloseOperation = struct {
564 assert(self.os_data.have_fd);553 assert(self.os_data.have_fd);
565 return self.os_data.close_req_node.data.msg.Close.fd;554 return self.os_data.close_req_node.data.msg.Close.fd;
566 },555 },
567 builtin.Os.windows,556 builtin.Os.windows => {
568 => {
569 return self.os_data.handle.?;557 return self.os_data.handle.?;
570 },558 },
571 else => @compileError("Unsupported OS"),559 else => @compileError("Unsupported OS"),
...@@ -585,15 +573,13 @@ pub async fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8,...@@ -585,15 +573,13 @@ pub async fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8,
585 builtin.Os.linux,573 builtin.Os.linux,
586 builtin.Os.macosx,574 builtin.Os.macosx,
587 => return await (async writeFileModeThread(loop, path, contents, mode) catch unreachable),575 => return await (async writeFileModeThread(loop, path, contents, mode) catch unreachable),
588 builtin.Os.windows,576 builtin.Os.windows => return await (async writeFileWindows(loop, path, contents) catch unreachable),
589 => return await (async writeFileWindows(loop, path, contents) catch unreachable),
590 else => @compileError("Unsupported OS"),577 else => @compileError("Unsupported OS"),
591 }578 }
592}579}
593580
594async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void {581async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void {
595 const handle = try os.windowsOpen(582 const handle = try os.windowsOpen(
596 loop.allocator,
597 path,583 path,
598 windows.GENERIC_WRITE,584 windows.GENERIC_WRITE,
599 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,585 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
...@@ -1004,7 +990,7 @@ pub fn Watch(comptime V: type) type {...@@ -1004,7 +990,7 @@ pub fn Watch(comptime V: type) type {
1004 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename);990 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename);
1005 var basename_utf16le_null_consumed = false;991 var basename_utf16le_null_consumed = false;
1006 defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null);992 defer if (!basename_utf16le_null_consumed) self.channel.loop.allocator.free(basename_utf16le_null);
1007 const basename_utf16le_no_null = basename_utf16le_null[0..basename_utf16le_null.len-1];993 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
1008994
1009 const dir_handle = windows.CreateFileW(995 const dir_handle = windows.CreateFileW(
1010 dirname_utf16le.ptr,996 dirname_utf16le.ptr,
...@@ -1018,9 +1004,8 @@ pub fn Watch(comptime V: type) type {...@@ -1018,9 +1004,8 @@ pub fn Watch(comptime V: type) type {
1018 if (dir_handle == windows.INVALID_HANDLE_VALUE) {1004 if (dir_handle == windows.INVALID_HANDLE_VALUE) {
1019 const err = windows.GetLastError();1005 const err = windows.GetLastError();
1020 switch (err) {1006 switch (err) {
1021 windows.ERROR.FILE_NOT_FOUND,1007 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
1022 windows.ERROR.PATH_NOT_FOUND,1008 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
1023 => return error.PathNotFound,
1024 else => return os.unexpectedErrorWindows(err),1009 else => return os.unexpectedErrorWindows(err),
1025 }1010 }
1026 }1011 }
...@@ -1106,7 +1091,10 @@ pub fn Watch(comptime V: type) type {...@@ -1106,7 +1091,10 @@ pub fn Watch(comptime V: type) type {
11061091
1107 // TODO handle this error not in the channel but in the setup1092 // TODO handle this error not in the channel but in the setup
1108 _ = os.windowsCreateIoCompletionPort(1093 _ = os.windowsCreateIoCompletionPort(
1109 dir_handle, self.channel.loop.os_data.io_port, completion_key, undefined,1094 dir_handle,
1095 self.channel.loop.os_data.io_port,
1096 completion_key,
1097 undefined,
1110 ) catch |err| {1098 ) catch |err| {
1111 await (async self.channel.put(err) catch unreachable);1099 await (async self.channel.put(err) catch unreachable);
1112 return;1100 return;
...@@ -1126,10 +1114,10 @@ pub fn Watch(comptime V: type) type {...@@ -1126,10 +1114,10 @@ pub fn Watch(comptime V: type) type {
1126 &event_buf,1114 &event_buf,
1127 @intCast(windows.DWORD, event_buf.len),1115 @intCast(windows.DWORD, event_buf.len),
1128 windows.FALSE, // watch subtree1116 windows.FALSE, // watch subtree
1129 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |1117 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1130 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |1118 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1131 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |1119 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1132 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,1120 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
1133 null, // number of bytes transferred (unused for async)1121 null, // number of bytes transferred (unused for async)
1134 &overlapped,1122 &overlapped,
1135 null, // completion routine - unused because we use IOCP1123 null, // completion routine - unused because we use IOCP
...@@ -1156,7 +1144,7 @@ pub fn Watch(comptime V: type) type {...@@ -1156,7 +1144,7 @@ pub fn Watch(comptime V: type) type {
1156 else => null,1144 else => null,
1157 };1145 };
1158 if (emit) |id| {1146 if (emit) |id| {
1159 const basename_utf16le = ([*]u16)(&ev.FileName)[0..ev.FileNameLength/2];1147 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
1160 const user_value = blk: {1148 const user_value = blk: {
1161 const held = await (async dir.table_lock.acquire() catch unreachable);1149 const held = await (async dir.table_lock.acquire() catch unreachable);
1162 defer held.release();1150 defer held.release();
std/io.zig+3-4
...@@ -254,9 +254,8 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -254,9 +254,8 @@ pub fn OutStream(comptime WriteError: type) type {
254 };254 };
255}255}
256256
257/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.257pub fn writeFile(path: []const u8, data: []const u8) !void {
258pub fn writeFile(allocator: *mem.Allocator, path: []const u8, data: []const u8) !void {258 var file = try File.openWrite(path);
259 var file = try File.openWrite(allocator, path);
260 defer file.close();259 defer file.close();
261 try file.write(data);260 try file.write(data);
262}261}
...@@ -268,7 +267,7 @@ pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 {...@@ -268,7 +267,7 @@ pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 {
268267
269/// On success, caller owns returned buffer.268/// On success, caller owns returned buffer.
270pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptime A: u29) ![]align(A) u8 {269pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptime A: u29) ![]align(A) u8 {
271 var file = try File.openRead(allocator, path);270 var file = try File.openRead(path);
272 defer file.close();271 defer file.close();
273272
274 const size = try file.getEndPos();273 const size = try file.getEndPos();
std/io_test.zig+5-5
...@@ -16,7 +16,7 @@ test "write a file, read it, then delete it" {...@@ -16,7 +16,7 @@ test "write a file, read it, then delete it" {
16 prng.random.bytes(data[0..]);16 prng.random.bytes(data[0..]);
17 const tmp_file_name = "temp_test_file.txt";17 const tmp_file_name = "temp_test_file.txt";
18 {18 {
19 var file = try os.File.openWrite(allocator, tmp_file_name);19 var file = try os.File.openWrite(tmp_file_name);
20 defer file.close();20 defer file.close();
2121
22 var file_out_stream = io.FileOutStream.init(&file);22 var file_out_stream = io.FileOutStream.init(&file);
...@@ -28,7 +28,7 @@ test "write a file, read it, then delete it" {...@@ -28,7 +28,7 @@ test "write a file, read it, then delete it" {
28 try buf_stream.flush();28 try buf_stream.flush();
29 }29 }
30 {30 {
31 var file = try os.File.openRead(allocator, tmp_file_name);31 var file = try os.File.openRead(tmp_file_name);
32 defer file.close();32 defer file.close();
3333
34 const file_size = try file.getEndPos();34 const file_size = try file.getEndPos();
...@@ -45,7 +45,7 @@ test "write a file, read it, then delete it" {...@@ -45,7 +45,7 @@ test "write a file, read it, then delete it" {
45 assert(mem.eql(u8, contents["begin".len .. contents.len - "end".len], data));45 assert(mem.eql(u8, contents["begin".len .. contents.len - "end".len], data));
46 assert(mem.eql(u8, contents[contents.len - "end".len ..], "end"));46 assert(mem.eql(u8, contents[contents.len - "end".len ..], "end"));
47 }47 }
48 try os.deleteFile(allocator, tmp_file_name);48 try os.deleteFile(tmp_file_name);
49}49}
5050
51test "BufferOutStream" {51test "BufferOutStream" {
...@@ -63,7 +63,7 @@ test "BufferOutStream" {...@@ -63,7 +63,7 @@ test "BufferOutStream" {
63}63}
6464
65test "SliceInStream" {65test "SliceInStream" {
66 const bytes = []const u8 { 1, 2, 3, 4, 5, 6, 7 };66 const bytes = []const u8{ 1, 2, 3, 4, 5, 6, 7 };
67 var ss = io.SliceInStream.init(bytes);67 var ss = io.SliceInStream.init(bytes);
6868
69 var dest: [4]u8 = undefined;69 var dest: [4]u8 = undefined;
...@@ -81,7 +81,7 @@ test "SliceInStream" {...@@ -81,7 +81,7 @@ test "SliceInStream" {
81}81}
8282
83test "PeekStream" {83test "PeekStream" {
84 const bytes = []const u8 { 1, 2, 3, 4, 5, 6, 7, 8 };84 const bytes = []const u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
85 var ss = io.SliceInStream.init(bytes);85 var ss = io.SliceInStream.init(bytes);
86 var ps = io.PeekStream(2, io.SliceInStream.Error).init(&ss.stream);86 var ps = io.PeekStream(2, io.SliceInStream.Error).init(&ss.stream);
8787
std/mem.zig+17-2
...@@ -179,8 +179,8 @@ pub fn secureZero(comptime T: type, s: []T) void {...@@ -179,8 +179,8 @@ pub fn secureZero(comptime T: type, s: []T) void {
179 // NOTE: We do not use a volatile slice cast here since LLVM cannot179 // NOTE: We do not use a volatile slice cast here since LLVM cannot
180 // see that it can be replaced by a memset.180 // see that it can be replaced by a memset.
181 const ptr = @ptrCast([*]volatile u8, s.ptr);181 const ptr = @ptrCast([*]volatile u8, s.ptr);
182 const len = s.len * @sizeOf(T);182 const length = s.len * @sizeOf(T);
183 @memset(ptr, 0, len);183 @memset(ptr, 0, length);
184}184}
185185
186test "mem.secureZero" {186test "mem.secureZero" {
...@@ -252,6 +252,20 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) bool {...@@ -252,6 +252,20 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
252 return true;252 return true;
253}253}
254254
255pub fn len(comptime T: type, ptr: [*]const T) usize {
256 var count: usize = 0;
257 while (ptr[count] != 0) : (count += 1) {}
258 return count;
259}
260
261pub fn toSliceConst(comptime T: type, ptr: [*]const T) []const T {
262 return ptr[0..len(T, ptr)];
263}
264
265pub fn toSlice(comptime T: type, ptr: [*]T) []T {
266 return ptr[0..len(T, ptr)];
267}
268
255/// Returns true if all elements in a slice are equal to the scalar value provided269/// Returns true if all elements in a slice are equal to the scalar value provided
256pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool {270pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool {
257 for (slice) |item| {271 for (slice) |item| {
...@@ -809,3 +823,4 @@ pub fn endianSwap(comptime T: type, x: T) T {...@@ -809,3 +823,4 @@ pub fn endianSwap(comptime T: type, x: T) T {
809test "std.mem.endianSwap" {823test "std.mem.endianSwap" {
810 assert(endianSwap(u32, 0xDEADBEEF) == 0xEFBEADDE);824 assert(endianSwap(u32, 0xDEADBEEF) == 0xEFBEADDE);
811}825}
826
std/os/child_process.zig+2-12
...@@ -349,14 +349,7 @@ pub const ChildProcess = struct {...@@ -349,14 +349,7 @@ pub const ChildProcess = struct {
349 };349 };
350350
351 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);351 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
352 const dev_null_fd = if (any_ignore) blk: {352 const dev_null_fd = if (any_ignore) try os.posixOpenC(c"/dev/null", posix.O_RDWR, 0) else undefined;
353 const dev_null_path = "/dev/null";
354 var fixed_buffer_mem: [dev_null_path.len + 1]u8 = undefined;
355 var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
356 break :blk try os.posixOpen(&fixed_allocator.allocator, "/dev/null", posix.O_RDWR, 0);
357 } else blk: {
358 break :blk undefined;
359 };
360 defer {353 defer {
361 if (any_ignore) os.close(dev_null_fd);354 if (any_ignore) os.close(dev_null_fd);
362 }355 }
...@@ -453,10 +446,7 @@ pub const ChildProcess = struct {...@@ -453,10 +446,7 @@ pub const ChildProcess = struct {
453 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);446 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
454447
455 const nul_handle = if (any_ignore) blk: {448 const nul_handle = if (any_ignore) blk: {
456 const nul_file_path = "NUL";449 break :blk try os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
457 var fixed_buffer_mem: [nul_file_path.len + 1]u8 = undefined;
458 var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
459 break :blk try os.windowsOpen(&fixed_allocator.allocator, "NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
460 } else blk: {450 } else blk: {
461 break :blk undefined;451 break :blk undefined;
462 };452 };
std/os/file.zig+88-53
...@@ -7,6 +7,7 @@ const assert = std.debug.assert;...@@ -7,6 +7,7 @@ const assert = std.debug.assert;
7const posix = os.posix;7const posix = os.posix;
8const windows = os.windows;8const windows = os.windows;
9const Os = builtin.Os;9const Os = builtin.Os;
10const windows_util = @import("windows/util.zig");
1011
11const is_posix = builtin.os != builtin.Os.windows;12const is_posix = builtin.os != builtin.Os.windows;
12const is_windows = builtin.os == builtin.Os.windows;13const is_windows = builtin.os == builtin.Os.windows;
...@@ -27,16 +28,27 @@ pub const File = struct {...@@ -27,16 +28,27 @@ pub const File = struct {
2728
28 pub const OpenError = os.WindowsOpenError || os.PosixOpenError;29 pub const OpenError = os.WindowsOpenError || os.PosixOpenError;
2930
30 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.31 /// `openRead` except with a null terminated path
31 /// Call close to clean up.32 pub fn openReadC(path: [*]const u8) OpenError!File {
32 pub fn openRead(allocator: *mem.Allocator, path: []const u8) OpenError!File {
33 if (is_posix) {33 if (is_posix) {
34 const flags = posix.O_LARGEFILE | posix.O_RDONLY;34 const flags = posix.O_LARGEFILE | posix.O_RDONLY;
35 const fd = try os.posixOpen(allocator, path, flags, 0);35 const fd = try os.posixOpenC(path, flags, 0);
36 return openHandle(fd);36 return openHandle(fd);
37 } else if (is_windows) {37 }
38 if (is_windows) {
39 return openRead(mem.toSliceConst(u8, path));
40 }
41 @compileError("Unsupported OS");
42 }
43
44 /// Call close to clean up.
45 pub fn openRead(path: []const u8) OpenError!File {
46 if (is_posix) {
47 const path_c = try os.toPosixPath(path);
48 return openReadC(&path_c);
49 }
50 if (is_windows) {
38 const handle = try os.windowsOpen(51 const handle = try os.windowsOpen(
39 allocator,
40 path,52 path,
41 windows.GENERIC_READ,53 windows.GENERIC_READ,
42 windows.FILE_SHARE_READ,54 windows.FILE_SHARE_READ,
...@@ -44,28 +56,25 @@ pub const File = struct {...@@ -44,28 +56,25 @@ pub const File = struct {
44 windows.FILE_ATTRIBUTE_NORMAL,56 windows.FILE_ATTRIBUTE_NORMAL,
45 );57 );
46 return openHandle(handle);58 return openHandle(handle);
47 } else {
48 @compileError("TODO implement openRead for this OS");
49 }59 }
60 @compileError("Unsupported OS");
50 }61 }
5162
52 /// Calls `openWriteMode` with os.File.default_mode for the mode.63 /// Calls `openWriteMode` with os.File.default_mode for the mode.
53 pub fn openWrite(allocator: *mem.Allocator, path: []const u8) OpenError!File {64 pub fn openWrite(path: []const u8) OpenError!File {
54 return openWriteMode(allocator, path, os.File.default_mode);65 return openWriteMode(path, os.File.default_mode);
55 }66 }
5667
57 /// If the path does not exist it will be created.68 /// If the path does not exist it will be created.
58 /// If a file already exists in the destination it will be truncated.69 /// If a file already exists in the destination it will be truncated.
59 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
60 /// Call close to clean up.70 /// Call close to clean up.
61 pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {71 pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File {
62 if (is_posix) {72 if (is_posix) {
63 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;73 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
64 const fd = try os.posixOpen(allocator, path, flags, file_mode);74 const fd = try os.posixOpen(path, flags, file_mode);
65 return openHandle(fd);75 return openHandle(fd);
66 } else if (is_windows) {76 } else if (is_windows) {
67 const handle = try os.windowsOpen(77 const handle = try os.windowsOpen(
68 allocator,
69 path,78 path,
70 windows.GENERIC_WRITE,79 windows.GENERIC_WRITE,
71 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,80 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
...@@ -80,16 +89,14 @@ pub const File = struct {...@@ -80,16 +89,14 @@ pub const File = struct {
8089
81 /// If the path does not exist it will be created.90 /// If the path does not exist it will be created.
82 /// If a file already exists in the destination this returns OpenError.PathAlreadyExists91 /// If a file already exists in the destination this returns OpenError.PathAlreadyExists
83 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
84 /// Call close to clean up.92 /// Call close to clean up.
85 pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {93 pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
86 if (is_posix) {94 if (is_posix) {
87 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;95 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;
88 const fd = try os.posixOpen(allocator, path, flags, file_mode);96 const fd = try os.posixOpen(path, flags, file_mode);
89 return openHandle(fd);97 return openHandle(fd);
90 } else if (is_windows) {98 } else if (is_windows) {
91 const handle = try os.windowsOpen(99 const handle = try os.windowsOpen(
92 allocator,
93 path,100 path,
94 windows.GENERIC_WRITE,101 windows.GENERIC_WRITE,
95 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,102 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
...@@ -108,23 +115,43 @@ pub const File = struct {...@@ -108,23 +115,43 @@ pub const File = struct {
108115
109 pub const AccessError = error{116 pub const AccessError = error{
110 PermissionDenied,117 PermissionDenied,
111 NotFound,118 FileNotFound,
112 NameTooLong,119 NameTooLong,
113 BadMode,120 InputOutput,
114 BadPathName,
115 Io,
116 SystemResources,121 SystemResources,
117 OutOfMemory,122 BadPathName,
123
124 /// On Windows, file paths must be valid Unicode.
125 InvalidUtf8,
118126
119 Unexpected,127 Unexpected,
120 };128 };
121129
122 pub fn access(allocator: *mem.Allocator, path: []const u8) AccessError!void {130 /// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
123 const path_with_null = try std.cstr.addNullByte(allocator, path);131 /// Otherwise use `access` or `accessC`.
124 defer allocator.free(path_with_null);132 pub fn accessW(path: [*]const u16) AccessError!void {
133 if (os.windows.GetFileAttributesW(path) != os.windows.INVALID_FILE_ATTRIBUTES) {
134 return;
135 }
136
137 const err = windows.GetLastError();
138 switch (err) {
139 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
140 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
141 windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
142 else => return os.unexpectedErrorWindows(err),
143 }
144 }
125145
146 /// Call if you have a UTF-8 encoded, null-terminated string.
147 /// Otherwise use `access` or `accessW`.
148 pub fn accessC(path: [*]const u8) AccessError!void {
149 if (is_windows) {
150 const path_w = try windows_util.cStrToPrefixedFileW(path);
151 return accessW(&path_w);
152 }
126 if (is_posix) {153 if (is_posix) {
127 const result = posix.access(path_with_null.ptr, posix.F_OK);154 const result = posix.access(path, posix.F_OK);
128 const err = posix.getErrno(result);155 const err = posix.getErrno(result);
129 switch (err) {156 switch (err) {
130 0 => return,157 0 => return,
...@@ -132,32 +159,33 @@ pub const File = struct {...@@ -132,32 +159,33 @@ pub const File = struct {
132 posix.EROFS => return error.PermissionDenied,159 posix.EROFS => return error.PermissionDenied,
133 posix.ELOOP => return error.PermissionDenied,160 posix.ELOOP => return error.PermissionDenied,
134 posix.ETXTBSY => return error.PermissionDenied,161 posix.ETXTBSY => return error.PermissionDenied,
135 posix.ENOTDIR => return error.NotFound,162 posix.ENOTDIR => return error.FileNotFound,
136 posix.ENOENT => return error.NotFound,163 posix.ENOENT => return error.FileNotFound,
137164
138 posix.ENAMETOOLONG => return error.NameTooLong,165 posix.ENAMETOOLONG => return error.NameTooLong,
139 posix.EINVAL => unreachable,166 posix.EINVAL => unreachable,
140 posix.EFAULT => return error.BadPathName,167 posix.EFAULT => unreachable,
141 posix.EIO => return error.Io,168 posix.EIO => return error.InputOutput,
142 posix.ENOMEM => return error.SystemResources,169 posix.ENOMEM => return error.SystemResources,
143 else => return os.unexpectedErrorPosix(err),170 else => return os.unexpectedErrorPosix(err),
144 }171 }
145 } else if (is_windows) {172 }
146 if (os.windows.GetFileAttributesA(path_with_null.ptr) != os.windows.INVALID_FILE_ATTRIBUTES) {173 @compileError("Unsupported OS");
147 return;174 }
148 }
149175
150 const err = windows.GetLastError();176 pub fn access(path: []const u8) AccessError!void {
151 switch (err) {177 if (is_windows) {
152 windows.ERROR.FILE_NOT_FOUND,178 const path_w = try windows_util.sliceToPrefixedFileW(path);
153 windows.ERROR.PATH_NOT_FOUND,179 return accessW(&path_w);
154 => return error.NotFound,180 }
155 windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,181 if (is_posix) {
156 else => return os.unexpectedErrorWindows(err),182 var path_with_null: [posix.PATH_MAX]u8 = undefined;
157 }183 if (path.len >= posix.PATH_MAX) return error.NameTooLong;
158 } else {184 mem.copy(u8, path_with_null[0..], path);
159 @compileError("TODO implement access for this OS");185 path_with_null[path.len] = 0;
186 return accessC(&path_with_null);
160 }187 }
188 @compileError("Unsupported OS");
161 }189 }
162190
163 /// Upon success, the stream is in an uninitialized state. To continue using it,191 /// Upon success, the stream is in an uninitialized state. To continue using it,
...@@ -179,7 +207,9 @@ pub const File = struct {...@@ -179,7 +207,9 @@ pub const File = struct {
179 const err = posix.getErrno(result);207 const err = posix.getErrno(result);
180 if (err > 0) {208 if (err > 0) {
181 return switch (err) {209 return switch (err) {
182 posix.EBADF => error.BadFd,210 // We do not make this an error code because if you get EBADF it's always a bug,
211 // since the fd could have been reused.
212 posix.EBADF => unreachable,
183 posix.EINVAL => error.Unseekable,213 posix.EINVAL => error.Unseekable,
184 posix.EOVERFLOW => error.Unseekable,214 posix.EOVERFLOW => error.Unseekable,
185 posix.ESPIPE => error.Unseekable,215 posix.ESPIPE => error.Unseekable,
...@@ -192,7 +222,7 @@ pub const File = struct {...@@ -192,7 +222,7 @@ pub const File = struct {
192 if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) {222 if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) {
193 const err = windows.GetLastError();223 const err = windows.GetLastError();
194 return switch (err) {224 return switch (err) {
195 windows.ERROR.INVALID_PARAMETER => error.BadFd,225 windows.ERROR.INVALID_PARAMETER => unreachable,
196 else => os.unexpectedErrorWindows(err),226 else => os.unexpectedErrorWindows(err),
197 };227 };
198 }228 }
...@@ -209,7 +239,9 @@ pub const File = struct {...@@ -209,7 +239,9 @@ pub const File = struct {
209 const err = posix.getErrno(result);239 const err = posix.getErrno(result);
210 if (err > 0) {240 if (err > 0) {
211 return switch (err) {241 return switch (err) {
212 posix.EBADF => error.BadFd,242 // We do not make this an error code because if you get EBADF it's always a bug,
243 // since the fd could have been reused.
244 posix.EBADF => unreachable,
213 posix.EINVAL => error.Unseekable,245 posix.EINVAL => error.Unseekable,
214 posix.EOVERFLOW => error.Unseekable,246 posix.EOVERFLOW => error.Unseekable,
215 posix.ESPIPE => error.Unseekable,247 posix.ESPIPE => error.Unseekable,
...@@ -223,7 +255,7 @@ pub const File = struct {...@@ -223,7 +255,7 @@ pub const File = struct {
223 if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) {255 if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) {
224 const err = windows.GetLastError();256 const err = windows.GetLastError();
225 return switch (err) {257 return switch (err) {
226 windows.ERROR.INVALID_PARAMETER => error.BadFd,258 windows.ERROR.INVALID_PARAMETER => unreachable,
227 else => os.unexpectedErrorWindows(err),259 else => os.unexpectedErrorWindows(err),
228 };260 };
229 }261 }
...@@ -239,7 +271,9 @@ pub const File = struct {...@@ -239,7 +271,9 @@ pub const File = struct {
239 const err = posix.getErrno(result);271 const err = posix.getErrno(result);
240 if (err > 0) {272 if (err > 0) {
241 return switch (err) {273 return switch (err) {
242 posix.EBADF => error.BadFd,274 // We do not make this an error code because if you get EBADF it's always a bug,
275 // since the fd could have been reused.
276 posix.EBADF => unreachable,
243 posix.EINVAL => error.Unseekable,277 posix.EINVAL => error.Unseekable,
244 posix.EOVERFLOW => error.Unseekable,278 posix.EOVERFLOW => error.Unseekable,
245 posix.ESPIPE => error.Unseekable,279 posix.ESPIPE => error.Unseekable,
...@@ -254,7 +288,7 @@ pub const File = struct {...@@ -254,7 +288,7 @@ pub const File = struct {
254 if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) {288 if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) {
255 const err = windows.GetLastError();289 const err = windows.GetLastError();
256 return switch (err) {290 return switch (err) {
257 windows.ERROR.INVALID_PARAMETER => error.BadFd,291 windows.ERROR.INVALID_PARAMETER => unreachable,
258 else => os.unexpectedErrorWindows(err),292 else => os.unexpectedErrorWindows(err),
259 };293 };
260 }294 }
...@@ -287,7 +321,6 @@ pub const File = struct {...@@ -287,7 +321,6 @@ pub const File = struct {
287 }321 }
288322
289 pub const ModeError = error{323 pub const ModeError = error{
290 BadFd,
291 SystemResources,324 SystemResources,
292 Unexpected,325 Unexpected,
293 };326 };
...@@ -298,7 +331,9 @@ pub const File = struct {...@@ -298,7 +331,9 @@ pub const File = struct {
298 const err = posix.getErrno(posix.fstat(self.handle, &stat));331 const err = posix.getErrno(posix.fstat(self.handle, &stat));
299 if (err > 0) {332 if (err > 0) {
300 return switch (err) {333 return switch (err) {
301 posix.EBADF => error.BadFd,334 // We do not make this an error code because if you get EBADF it's always a bug,
335 // since the fd could have been reused.
336 posix.EBADF => unreachable,
302 posix.ENOMEM => error.SystemResources,337 posix.ENOMEM => error.SystemResources,
303 else => os.unexpectedErrorPosix(err),338 else => os.unexpectedErrorPosix(err),
304 };339 };
std/os/get_app_data_dir.zig+2-1
...@@ -10,6 +10,7 @@ pub const GetAppDataDirError = error{...@@ -10,6 +10,7 @@ pub const GetAppDataDirError = error{
10};10};
1111
12/// Caller owns returned memory.12/// Caller owns returned memory.
13/// TODO determine if we can remove the allocator requirement
13pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 {14pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 {
14 switch (builtin.os) {15 switch (builtin.os) {
15 builtin.Os.windows => {16 builtin.Os.windows => {
...@@ -22,7 +23,7 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD...@@ -22,7 +23,7 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD
22 )) {23 )) {
23 os.windows.S_OK => {24 os.windows.S_OK => {
24 defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr));25 defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr));
25 const global_dir = unicode.utf16leToUtf8(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) {26 const global_dir = unicode.utf16leToUtf8Alloc(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) {
26 error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,27 error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
27 error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,28 error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
28 error.DanglingSurrogateHalf => return error.AppDataDirUnavailable,29 error.DanglingSurrogateHalf => return error.AppDataDirUnavailable,
std/os/index.zig+294-243
...@@ -39,6 +39,15 @@ pub const File = @import("file.zig").File;...@@ -39,6 +39,15 @@ pub const File = @import("file.zig").File;
39pub const time = @import("time.zig");39pub const time = @import("time.zig");
4040
41pub const page_size = 4 * 1024;41pub const page_size = 4 * 1024;
42pub const MAX_PATH_BYTES = switch (builtin.os) {
43 Os.linux, Os.macosx, Os.ios => posix.PATH_MAX,
44 // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
45 // If it would require 4 UTF-8 bytes, then there would be a surrogate
46 // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
47 // +1 for the null byte at the end, which can be encoded in 1 byte.
48 Os.windows => windows_util.PATH_MAX_WIDE * 3 + 1,
49 else => @compileError("Unsupported OS"),
50};
4251
43pub const UserInfo = @import("get_user_id.zig").UserInfo;52pub const UserInfo = @import("get_user_id.zig").UserInfo;
44pub const getUserInfo = @import("get_user_id.zig").getUserInfo;53pub const getUserInfo = @import("get_user_id.zig").getUserInfo;
...@@ -317,6 +326,8 @@ pub const PosixWriteError = error{...@@ -317,6 +326,8 @@ pub const PosixWriteError = error{
317 NoSpaceLeft,326 NoSpaceLeft,
318 AccessDenied,327 AccessDenied,
319 BrokenPipe,328 BrokenPipe,
329
330 /// See https://github.com/ziglang/zig/issues/1396
320 Unexpected,331 Unexpected,
321};332};
322333
...@@ -417,7 +428,6 @@ pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, off...@@ -417,7 +428,6 @@ pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, off
417}428}
418429
419pub const PosixOpenError = error{430pub const PosixOpenError = error{
420 OutOfMemory,
421 AccessDenied,431 AccessDenied,
422 FileTooBig,432 FileTooBig,
423 IsDir,433 IsDir,
...@@ -426,22 +436,22 @@ pub const PosixOpenError = error{...@@ -426,22 +436,22 @@ pub const PosixOpenError = error{
426 NameTooLong,436 NameTooLong,
427 SystemFdQuotaExceeded,437 SystemFdQuotaExceeded,
428 NoDevice,438 NoDevice,
429 PathNotFound,439 FileNotFound,
430 SystemResources,440 SystemResources,
431 NoSpaceLeft,441 NoSpaceLeft,
432 NotDir,442 NotDir,
433 PathAlreadyExists,443 PathAlreadyExists,
444
445 /// See https://github.com/ziglang/zig/issues/1396
434 Unexpected,446 Unexpected,
435};447};
436448
437/// ::file_path needs to be copied in memory to add a null terminating byte.449/// ::file_path needs to be copied in memory to add a null terminating byte.
438/// Calls POSIX open, keeps trying if it gets interrupted, and translates450/// Calls POSIX open, keeps trying if it gets interrupted, and translates
439/// the return value into zig errors.451/// the return value into zig errors.
440pub fn posixOpen(allocator: *Allocator, file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {452pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {
441 const path_with_null = try cstr.addNullByte(allocator, file_path);453 const file_path_c = try toPosixPath(file_path);
442 defer allocator.free(path_with_null);454 return posixOpenC(&file_path_c, flags, perm);
443
444 return posixOpenC(path_with_null.ptr, flags, perm);
445}455}
446456
447// TODO https://github.com/ziglang/zig/issues/265457// TODO https://github.com/ziglang/zig/issues/265
...@@ -463,7 +473,7 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {...@@ -463,7 +473,7 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {
463 posix.ENAMETOOLONG => return PosixOpenError.NameTooLong,473 posix.ENAMETOOLONG => return PosixOpenError.NameTooLong,
464 posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded,474 posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded,
465 posix.ENODEV => return PosixOpenError.NoDevice,475 posix.ENODEV => return PosixOpenError.NoDevice,
466 posix.ENOENT => return PosixOpenError.PathNotFound,476 posix.ENOENT => return PosixOpenError.FileNotFound,
467 posix.ENOMEM => return PosixOpenError.SystemResources,477 posix.ENOMEM => return PosixOpenError.SystemResources,
468 posix.ENOSPC => return PosixOpenError.NoSpaceLeft,478 posix.ENOSPC => return PosixOpenError.NoSpaceLeft,
469 posix.ENOTDIR => return PosixOpenError.NotDir,479 posix.ENOTDIR => return PosixOpenError.NotDir,
...@@ -476,6 +486,16 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {...@@ -476,6 +486,16 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {
476 }486 }
477}487}
478488
489/// Used to convert a slice to a null terminated slice on the stack.
490/// TODO well defined copy elision
491pub fn toPosixPath(file_path: []const u8) ![posix.PATH_MAX]u8 {
492 var path_with_null: [posix.PATH_MAX]u8 = undefined;
493 if (file_path.len >= posix.PATH_MAX) return error.NameTooLong;
494 mem.copy(u8, path_with_null[0..], file_path);
495 path_with_null[file_path.len] = 0;
496 return path_with_null;
497}
498
479pub fn posixDup2(old_fd: i32, new_fd: i32) !void {499pub fn posixDup2(old_fd: i32, new_fd: i32) !void {
480 while (true) {500 while (true) {
481 const err = posix.getErrno(posix.dup2(old_fd, new_fd));501 const err = posix.getErrno(posix.dup2(old_fd, new_fd));
...@@ -591,6 +611,8 @@ pub const PosixExecveError = error{...@@ -591,6 +611,8 @@ pub const PosixExecveError = error{
591 FileNotFound,611 FileNotFound,
592 NotDir,612 NotDir,
593 FileBusy,613 FileBusy,
614
615 /// See https://github.com/ziglang/zig/issues/1396
594 Unexpected,616 Unexpected,
595};617};
596618
...@@ -719,43 +741,39 @@ pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwned...@@ -719,43 +741,39 @@ pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwned
719}741}
720742
721/// Caller must free the returned memory.743/// Caller must free the returned memory.
722pub fn getCwd(allocator: *Allocator) ![]u8 {744pub fn getCwdAlloc(allocator: *Allocator) ![]u8 {
723 switch (builtin.os) {745 var buf: [MAX_PATH_BYTES]u8 = undefined;
724 Os.windows => {746 return mem.dupe(allocator, u8, try getCwd(&buf));
725 var buf = try allocator.alloc(u8, 256);747}
726 errdefer allocator.free(buf);
727
728 while (true) {
729 const result = windows.GetCurrentDirectoryA(@intCast(windows.WORD, buf.len), buf.ptr);
730748
731 if (result == 0) {749pub const GetCwdError = error{Unexpected};
732 const err = windows.GetLastError();
733 return switch (err) {
734 else => unexpectedErrorWindows(err),
735 };
736 }
737750
738 if (result > buf.len) {751/// The result is a slice of out_buffer.
739 buf = try allocator.realloc(u8, buf, result);752pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) GetCwdError![]u8 {
740 continue;753 switch (builtin.os) {
754 Os.windows => {
755 var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined;
756 const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
757 const casted_ptr = ([*]u16)(&utf16le_buf); // TODO shouldn't need this cast
758 const result = windows.GetCurrentDirectoryW(casted_len, casted_ptr);
759 if (result == 0) {
760 const err = windows.GetLastError();
761 switch (err) {
762 else => return unexpectedErrorWindows(err),
741 }763 }
742
743 return allocator.shrink(u8, buf, result);
744 }764 }
765 assert(result <= utf16le_buf.len);
766 const utf16le_slice = utf16le_buf[0..result];
767 // Trust that Windows gives us valid UTF-16LE.
768 const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable;
769 return out_buffer[0..end_index];
745 },770 },
746 else => {771 else => {
747 var buf = try allocator.alloc(u8, 1024);772 const err = posix.getErrno(posix.getcwd(out_buffer, out_buffer.len));
748 errdefer allocator.free(buf);773 switch (err) {
749 while (true) {774 0 => return cstr.toSlice(out_buffer),
750 const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len));775 posix.ERANGE => unreachable,
751 if (err == posix.ERANGE) {776 else => return unexpectedErrorPosix(err),
752 buf = try allocator.realloc(u8, buf, buf.len * 2);
753 continue;
754 } else if (err > 0) {
755 return unexpectedErrorPosix(err);
756 }
757
758 return allocator.shrink(u8, buf, cstr.len(buf.ptr));
759 }777 }
760 },778 },
761 }779 }
...@@ -763,7 +781,9 @@ pub fn getCwd(allocator: *Allocator) ![]u8 {...@@ -763,7 +781,9 @@ pub fn getCwd(allocator: *Allocator) ![]u8 {
763781
764test "os.getCwd" {782test "os.getCwd" {
765 // at least call it so it gets compiled783 // at least call it so it gets compiled
766 _ = getCwd(debug.global_allocator);784 _ = getCwdAlloc(debug.global_allocator);
785 var buf: [MAX_PATH_BYTES]u8 = undefined;
786 _ = getCwd(&buf);
767}787}
768788
769pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError;789pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError;
...@@ -778,6 +798,8 @@ pub fn symLink(allocator: *Allocator, existing_path: []const u8, new_path: []con...@@ -778,6 +798,8 @@ pub fn symLink(allocator: *Allocator, existing_path: []const u8, new_path: []con
778798
779pub const WindowsSymLinkError = error{799pub const WindowsSymLinkError = error{
780 OutOfMemory,800 OutOfMemory,
801
802 /// See https://github.com/ziglang/zig/issues/1396
781 Unexpected,803 Unexpected,
782};804};
783805
...@@ -808,6 +830,8 @@ pub const PosixSymLinkError = error{...@@ -808,6 +830,8 @@ pub const PosixSymLinkError = error{
808 NoSpaceLeft,830 NoSpaceLeft,
809 ReadOnlyFileSystem,831 ReadOnlyFileSystem,
810 NotDir,832 NotDir,
833
834 /// See https://github.com/ziglang/zig/issues/1396
811 Unexpected,835 Unexpected,
812};836};
813837
...@@ -866,7 +890,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:...@@ -866,7 +890,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
866 b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);890 b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
867891
868 if (symLink(allocator, existing_path, tmp_path)) {892 if (symLink(allocator, existing_path, tmp_path)) {
869 return rename(allocator, tmp_path, new_path);893 return rename(tmp_path, new_path);
870 } else |err| switch (err) {894 } else |err| switch (err) {
871 error.PathAlreadyExists => continue,895 error.PathAlreadyExists => continue,
872 else => return err, // TODO zig should know this set does not include PathAlreadyExists896 else => return err, // TODO zig should know this set does not include PathAlreadyExists
...@@ -885,70 +909,75 @@ pub const DeleteFileError = error{...@@ -885,70 +909,75 @@ pub const DeleteFileError = error{
885 NotDir,909 NotDir,
886 SystemResources,910 SystemResources,
887 ReadOnlyFileSystem,911 ReadOnlyFileSystem,
888 OutOfMemory,
889912
913 /// On Windows, file paths must be valid Unicode.
914 InvalidUtf8,
915
916 /// On Windows, file paths cannot contain these characters:
917 /// '/', '*', '?', '"', '<', '>', '|'
918 BadPathName,
919
920 /// See https://github.com/ziglang/zig/issues/1396
890 Unexpected,921 Unexpected,
891};922};
892923
893pub fn deleteFile(allocator: *Allocator, file_path: []const u8) DeleteFileError!void {924pub fn deleteFile(file_path: []const u8) DeleteFileError!void {
894 if (builtin.os == Os.windows) {925 if (builtin.os == Os.windows) {
895 return deleteFileWindows(allocator, file_path);926 return deleteFileWindows(file_path);
896 } else {927 } else {
897 return deleteFilePosix(allocator, file_path);928 return deleteFilePosix(file_path);
898 }929 }
899}930}
900931
901pub fn deleteFileWindows(allocator: *Allocator, file_path: []const u8) !void {932pub fn deleteFileWindows(file_path: []const u8) !void {
902 const buf = try allocator.alloc(u8, file_path.len + 1);933 const file_path_w = try windows_util.sliceToPrefixedFileW(file_path);
903 defer allocator.free(buf);
904934
905 mem.copy(u8, buf, file_path);935 if (windows.DeleteFileW(&file_path_w) == 0) {
906 buf[file_path.len] = 0;
907
908 if (windows.DeleteFileA(buf.ptr) == 0) {
909 const err = windows.GetLastError();936 const err = windows.GetLastError();
910 return switch (err) {937 switch (err) {
911 windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,938 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
912 windows.ERROR.ACCESS_DENIED => error.AccessDenied,939 windows.ERROR.ACCESS_DENIED => return error.AccessDenied,
913 windows.ERROR.FILENAME_EXCED_RANGE, windows.ERROR.INVALID_PARAMETER => error.NameTooLong,940 windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
914 else => unexpectedErrorWindows(err),941 windows.ERROR.INVALID_PARAMETER => return error.NameTooLong,
915 };942 else => return unexpectedErrorWindows(err),
943 }
916 }944 }
917}945}
918946
919pub fn deleteFilePosix(allocator: *Allocator, file_path: []const u8) !void {947pub fn deleteFilePosixC(file_path: [*]const u8) !void {
920 const buf = try allocator.alloc(u8, file_path.len + 1);948 const err = posix.getErrno(posix.unlink(file_path));
921 defer allocator.free(buf);949 switch (err) {
922950 0 => return,
923 mem.copy(u8, buf, file_path);951 posix.EACCES => return error.AccessDenied,
924 buf[file_path.len] = 0;952 posix.EPERM => return error.AccessDenied,
925953 posix.EBUSY => return error.FileBusy,
926 const err = posix.getErrno(posix.unlink(buf.ptr));954 posix.EFAULT => unreachable,
927 if (err > 0) {955 posix.EINVAL => unreachable,
928 return switch (err) {956 posix.EIO => return error.FileSystem,
929 posix.EACCES, posix.EPERM => error.AccessDenied,957 posix.EISDIR => return error.IsDir,
930 posix.EBUSY => error.FileBusy,958 posix.ELOOP => return error.SymLinkLoop,
931 posix.EFAULT, posix.EINVAL => unreachable,959 posix.ENAMETOOLONG => return error.NameTooLong,
932 posix.EIO => error.FileSystem,960 posix.ENOENT => return error.FileNotFound,
933 posix.EISDIR => error.IsDir,961 posix.ENOTDIR => return error.NotDir,
934 posix.ELOOP => error.SymLinkLoop,962 posix.ENOMEM => return error.SystemResources,
935 posix.ENAMETOOLONG => error.NameTooLong,963 posix.EROFS => return error.ReadOnlyFileSystem,
936 posix.ENOENT => error.FileNotFound,964 else => return unexpectedErrorPosix(err),
937 posix.ENOTDIR => error.NotDir,
938 posix.ENOMEM => error.SystemResources,
939 posix.EROFS => error.ReadOnlyFileSystem,
940 else => unexpectedErrorPosix(err),
941 };
942 }965 }
943}966}
944967
968pub fn deleteFilePosix(file_path: []const u8) !void {
969 const file_path_c = try toPosixPath(file_path);
970 return deleteFilePosixC(&file_path_c);
971}
972
945/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is973/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
946/// merged and readily available,974/// merged and readily available,
947/// there is a possibility of power loss or application termination leaving temporary files present975/// there is a possibility of power loss or application termination leaving temporary files present
948/// in the same directory as dest_path.976/// in the same directory as dest_path.
949/// Destination file will have the same mode as the source file.977/// Destination file will have the same mode as the source file.
978/// TODO investigate if this can work with no allocator
950pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []const u8) !void {979pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []const u8) !void {
951 var in_file = try os.File.openRead(allocator, source_path);980 var in_file = try os.File.openRead(source_path);
952 defer in_file.close();981 defer in_file.close();
953982
954 const mode = try in_file.mode();983 const mode = try in_file.mode();
...@@ -969,8 +998,9 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con...@@ -969,8 +998,9 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con
969/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is998/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
970/// merged and readily available,999/// merged and readily available,
971/// there is a possibility of power loss or application termination leaving temporary files present1000/// there is a possibility of power loss or application termination leaving temporary files present
1001/// TODO investigate if this can work with no allocator
972pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {1002pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {
973 var in_file = try os.File.openRead(allocator, source_path);1003 var in_file = try os.File.openRead(source_path);
974 defer in_file.close();1004 defer in_file.close();
9751005
976 var atomic_file = try AtomicFile.init(allocator, dest_path, mode);1006 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: [...@@ -987,6 +1017,7 @@ pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: [
987}1017}
9881018
989pub const AtomicFile = struct {1019pub const AtomicFile = struct {
1020 /// TODO investigate if we can make this work with no allocator
990 allocator: *Allocator,1021 allocator: *Allocator,
991 file: os.File,1022 file: os.File,
992 tmp_path: []u8,1023 tmp_path: []u8,
...@@ -1014,7 +1045,7 @@ pub const AtomicFile = struct {...@@ -1014,7 +1045,7 @@ pub const AtomicFile = struct {
1014 try getRandomBytes(rand_buf[0..]);1045 try getRandomBytes(rand_buf[0..]);
1015 b64_fs_encoder.encode(tmp_path[dirname_component_len..], rand_buf);1046 b64_fs_encoder.encode(tmp_path[dirname_component_len..], rand_buf);
10161047
1017 const file = os.File.openWriteNoClobber(allocator, tmp_path, mode) catch |err| switch (err) {1048 const file = os.File.openWriteNoClobber(tmp_path, mode) catch |err| switch (err) {
1018 error.PathAlreadyExists => continue,1049 error.PathAlreadyExists => continue,
1019 // TODO zig should figure out that this error set does not include PathAlreadyExists since1050 // TODO zig should figure out that this error set does not include PathAlreadyExists since
1020 // it is handled in the above switch1051 // it is handled in the above switch
...@@ -1035,7 +1066,7 @@ pub const AtomicFile = struct {...@@ -1035,7 +1066,7 @@ pub const AtomicFile = struct {
1035 pub fn deinit(self: *AtomicFile) void {1066 pub fn deinit(self: *AtomicFile) void {
1036 if (!self.finished) {1067 if (!self.finished) {
1037 self.file.close();1068 self.file.close();
1038 deleteFile(self.allocator, self.tmp_path) catch {};1069 deleteFile(self.tmp_path) catch {};
1039 self.allocator.free(self.tmp_path);1070 self.allocator.free(self.tmp_path);
1040 self.finished = true;1071 self.finished = true;
1041 }1072 }
...@@ -1044,70 +1075,72 @@ pub const AtomicFile = struct {...@@ -1044,70 +1075,72 @@ pub const AtomicFile = struct {
1044 pub fn finish(self: *AtomicFile) !void {1075 pub fn finish(self: *AtomicFile) !void {
1045 assert(!self.finished);1076 assert(!self.finished);
1046 self.file.close();1077 self.file.close();
1047 try rename(self.allocator, self.tmp_path, self.dest_path);1078 try rename(self.tmp_path, self.dest_path);
1048 self.allocator.free(self.tmp_path);1079 self.allocator.free(self.tmp_path);
1049 self.finished = true;1080 self.finished = true;
1050 }1081 }
1051};1082};
10521083
1053pub fn rename(allocator: *Allocator, old_path: []const u8, new_path: []const u8) !void {1084pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void {
1054 const full_buf = try allocator.alloc(u8, old_path.len + new_path.len + 2);1085 if (is_windows) {
1055 defer allocator.free(full_buf);1086 @compileError("TODO implement for windows");
10561087 } else {
1057 const old_buf = full_buf;1088 const err = posix.getErrno(posix.rename(old_path, new_path));
1058 mem.copy(u8, old_buf, old_path);1089 switch (err) {
1059 old_buf[old_path.len] = 0;1090 0 => return,
10601091 posix.EACCES => return error.AccessDenied,
1061 const new_buf = full_buf[old_path.len + 1 ..];1092 posix.EPERM => return error.AccessDenied,
1062 mem.copy(u8, new_buf, new_path);1093 posix.EBUSY => return error.FileBusy,
1063 new_buf[new_path.len] = 0;1094 posix.EDQUOT => return error.DiskQuota,
1095 posix.EFAULT => unreachable,
1096 posix.EINVAL => unreachable,
1097 posix.EISDIR => return error.IsDir,
1098 posix.ELOOP => return error.SymLinkLoop,
1099 posix.EMLINK => return error.LinkQuotaExceeded,
1100 posix.ENAMETOOLONG => return error.NameTooLong,
1101 posix.ENOENT => return error.FileNotFound,
1102 posix.ENOTDIR => return error.NotDir,
1103 posix.ENOMEM => return error.SystemResources,
1104 posix.ENOSPC => return error.NoSpaceLeft,
1105 posix.EEXIST => return error.PathAlreadyExists,
1106 posix.ENOTEMPTY => return error.PathAlreadyExists,
1107 posix.EROFS => return error.ReadOnlyFileSystem,
1108 posix.EXDEV => return error.RenameAcrossMountPoints,
1109 else => return unexpectedErrorPosix(err),
1110 }
1111 }
1112}
10641113
1114pub fn rename(old_path: []const u8, new_path: []const u8) !void {
1065 if (is_windows) {1115 if (is_windows) {
1066 const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;1116 const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
1067 if (windows.MoveFileExA(old_buf.ptr, new_buf.ptr, flags) == 0) {1117 const old_path_w = try windows_util.sliceToPrefixedFileW(old_path);
1118 const new_path_w = try windows_util.sliceToPrefixedFileW(new_path);
1119 if (windows.MoveFileExW(&old_path_w, &new_path_w, flags) == 0) {
1068 const err = windows.GetLastError();1120 const err = windows.GetLastError();
1069 return switch (err) {1121 switch (err) {
1070 else => unexpectedErrorWindows(err),1122 else => return unexpectedErrorWindows(err),
1071 };1123 }
1072 }1124 }
1073 } else {1125 } else {
1074 const err = posix.getErrno(posix.rename(old_buf.ptr, new_buf.ptr));1126 const old_path_c = try toPosixPath(old_path);
1075 if (err > 0) {1127 const new_path_c = try toPosixPath(new_path);
1076 return switch (err) {1128 return renameC(&old_path_c, &new_path_c);
1077 posix.EACCES, posix.EPERM => error.AccessDenied,
1078 posix.EBUSY => error.FileBusy,
1079 posix.EDQUOT => error.DiskQuota,
1080 posix.EFAULT, posix.EINVAL => unreachable,
1081 posix.EISDIR => error.IsDir,
1082 posix.ELOOP => error.SymLinkLoop,
1083 posix.EMLINK => error.LinkQuotaExceeded,
1084 posix.ENAMETOOLONG => error.NameTooLong,
1085 posix.ENOENT => error.FileNotFound,
1086 posix.ENOTDIR => error.NotDir,
1087 posix.ENOMEM => error.SystemResources,
1088 posix.ENOSPC => error.NoSpaceLeft,
1089 posix.EEXIST, posix.ENOTEMPTY => error.PathAlreadyExists,
1090 posix.EROFS => error.ReadOnlyFileSystem,
1091 posix.EXDEV => error.RenameAcrossMountPoints,
1092 else => unexpectedErrorPosix(err),
1093 };
1094 }
1095 }1129 }
1096}1130}
10971131
1098pub fn makeDir(allocator: *Allocator, dir_path: []const u8) !void {1132pub fn makeDir(dir_path: []const u8) !void {
1099 if (is_windows) {1133 if (is_windows) {
1100 return makeDirWindows(allocator, dir_path);1134 return makeDirWindows(dir_path);
1101 } else {1135 } else {
1102 return makeDirPosix(allocator, dir_path);1136 return makeDirPosix(dir_path);
1103 }1137 }
1104}1138}
11051139
1106pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {1140pub fn makeDirWindows(dir_path: []const u8) !void {
1107 const path_buf = try cstr.addNullByte(allocator, dir_path);1141 const dir_path_w = try windows_util.sliceToPrefixedFileW(dir_path);
1108 defer allocator.free(path_buf);
11091142
1110 if (windows.CreateDirectoryA(path_buf.ptr, null) == 0) {1143 if (windows.CreateDirectoryW(&dir_path_w, null) == 0) {
1111 const err = windows.GetLastError();1144 const err = windows.GetLastError();
1112 return switch (err) {1145 return switch (err) {
1113 windows.ERROR.ALREADY_EXISTS => error.PathAlreadyExists,1146 windows.ERROR.ALREADY_EXISTS => error.PathAlreadyExists,
...@@ -1117,39 +1150,42 @@ pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {...@@ -1117,39 +1150,42 @@ pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {
1117 }1150 }
1118}1151}
11191152
1120pub fn makeDirPosix(allocator: *Allocator, dir_path: []const u8) !void {1153pub fn makeDirPosixC(dir_path: [*]const u8) !void {
1121 const path_buf = try cstr.addNullByte(allocator, dir_path);1154 const err = posix.getErrno(posix.mkdir(dir_path, 0o755));
1122 defer allocator.free(path_buf);1155 switch (err) {
11231156 0 => return,
1124 const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755));1157 posix.EACCES => return error.AccessDenied,
1125 if (err > 0) {1158 posix.EPERM => return error.AccessDenied,
1126 return switch (err) {1159 posix.EDQUOT => return error.DiskQuota,
1127 posix.EACCES, posix.EPERM => error.AccessDenied,1160 posix.EEXIST => return error.PathAlreadyExists,
1128 posix.EDQUOT => error.DiskQuota,1161 posix.EFAULT => unreachable,
1129 posix.EEXIST => error.PathAlreadyExists,1162 posix.ELOOP => return error.SymLinkLoop,
1130 posix.EFAULT => unreachable,1163 posix.EMLINK => return error.LinkQuotaExceeded,
1131 posix.ELOOP => error.SymLinkLoop,1164 posix.ENAMETOOLONG => return error.NameTooLong,
1132 posix.EMLINK => error.LinkQuotaExceeded,1165 posix.ENOENT => return error.FileNotFound,
1133 posix.ENAMETOOLONG => error.NameTooLong,1166 posix.ENOMEM => return error.SystemResources,
1134 posix.ENOENT => error.FileNotFound,1167 posix.ENOSPC => return error.NoSpaceLeft,
1135 posix.ENOMEM => error.SystemResources,1168 posix.ENOTDIR => return error.NotDir,
1136 posix.ENOSPC => error.NoSpaceLeft,1169 posix.EROFS => return error.ReadOnlyFileSystem,
1137 posix.ENOTDIR => error.NotDir,1170 else => return unexpectedErrorPosix(err),
1138 posix.EROFS => error.ReadOnlyFileSystem,
1139 else => unexpectedErrorPosix(err),
1140 };
1141 }1171 }
1142}1172}
11431173
1174pub fn makeDirPosix(dir_path: []const u8) !void {
1175 const dir_path_c = try toPosixPath(dir_path);
1176 return makeDirPosixC(&dir_path_c);
1177}
1178
1144/// Calls makeDir recursively to make an entire path. Returns success if the path1179/// Calls makeDir recursively to make an entire path. Returns success if the path
1145/// already exists and is a directory.1180/// already exists and is a directory.
1181/// TODO determine if we can remove the allocator requirement from this function
1146pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {1182pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {
1147 const resolved_path = try path.resolve(allocator, full_path);1183 const resolved_path = try path.resolve(allocator, full_path);
1148 defer allocator.free(resolved_path);1184 defer allocator.free(resolved_path);
11491185
1150 var end_index: usize = resolved_path.len;1186 var end_index: usize = resolved_path.len;
1151 while (true) {1187 while (true) {
1152 makeDir(allocator, resolved_path[0..end_index]) catch |err| switch (err) {1188 makeDir(resolved_path[0..end_index]) catch |err| switch (err) {
1153 error.PathAlreadyExists => {1189 error.PathAlreadyExists => {
1154 // TODO stat the file and return an error if it's not a directory1190 // TODO stat the file and return an error if it's not a directory
1155 // this is important because otherwise a dangling symlink1191 // this is important because otherwise a dangling symlink
...@@ -1187,6 +1223,7 @@ pub const DeleteDirError = error{...@@ -1187,6 +1223,7 @@ pub const DeleteDirError = error{
1187 ReadOnlyFileSystem,1223 ReadOnlyFileSystem,
1188 OutOfMemory,1224 OutOfMemory,
11891225
1226 /// See https://github.com/ziglang/zig/issues/1396
1190 Unexpected,1227 Unexpected,
1191};1228};
11921229
...@@ -1245,7 +1282,6 @@ const DeleteTreeError = error{...@@ -1245,7 +1282,6 @@ const DeleteTreeError = error{
1245 NameTooLong,1282 NameTooLong,
1246 SystemFdQuotaExceeded,1283 SystemFdQuotaExceeded,
1247 NoDevice,1284 NoDevice,
1248 PathNotFound,
1249 SystemResources,1285 SystemResources,
1250 NoSpaceLeft,1286 NoSpaceLeft,
1251 PathAlreadyExists,1287 PathAlreadyExists,
...@@ -1255,20 +1291,30 @@ const DeleteTreeError = error{...@@ -1255,20 +1291,30 @@ const DeleteTreeError = error{
1255 FileSystem,1291 FileSystem,
1256 FileBusy,1292 FileBusy,
1257 DirNotEmpty,1293 DirNotEmpty,
1294
1295 /// On Windows, file paths must be valid Unicode.
1296 InvalidUtf8,
1297
1298 /// On Windows, file paths cannot contain these characters:
1299 /// '/', '*', '?', '"', '<', '>', '|'
1300 BadPathName,
1301
1302 /// See https://github.com/ziglang/zig/issues/1396
1258 Unexpected,1303 Unexpected,
1259};1304};
1305
1306/// TODO determine if we can remove the allocator requirement
1260pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void {1307pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void {
1261 start_over: while (true) {1308 start_over: while (true) {
1262 var got_access_denied = false;1309 var got_access_denied = false;
1263 // First, try deleting the item as a file. This way we don't follow sym links.1310 // First, try deleting the item as a file. This way we don't follow sym links.
1264 if (deleteFile(allocator, full_path)) {1311 if (deleteFile(full_path)) {
1265 return;1312 return;
1266 } else |err| switch (err) {1313 } else |err| switch (err) {
1267 error.FileNotFound => return,1314 error.FileNotFound => return,
1268 error.IsDir => {},1315 error.IsDir => {},
1269 error.AccessDenied => got_access_denied = true,1316 error.AccessDenied => got_access_denied = true,
12701317
1271 error.OutOfMemory,
1272 error.SymLinkLoop,1318 error.SymLinkLoop,
1273 error.NameTooLong,1319 error.NameTooLong,
1274 error.SystemResources,1320 error.SystemResources,
...@@ -1276,6 +1322,8 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!...@@ -1276,6 +1322,8 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
1276 error.NotDir,1322 error.NotDir,
1277 error.FileSystem,1323 error.FileSystem,
1278 error.FileBusy,1324 error.FileBusy,
1325 error.InvalidUtf8,
1326 error.BadPathName,
1279 error.Unexpected,1327 error.Unexpected,
1280 => return err,1328 => return err,
1281 }1329 }
...@@ -1297,7 +1345,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!...@@ -1297,7 +1345,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
1297 error.NameTooLong,1345 error.NameTooLong,
1298 error.SystemFdQuotaExceeded,1346 error.SystemFdQuotaExceeded,
1299 error.NoDevice,1347 error.NoDevice,
1300 error.PathNotFound,1348 error.FileNotFound,
1301 error.SystemResources,1349 error.SystemResources,
1302 error.NoSpaceLeft,1350 error.NoSpaceLeft,
1303 error.PathAlreadyExists,1351 error.PathAlreadyExists,
...@@ -1367,7 +1415,7 @@ pub const Dir = struct {...@@ -1367,7 +1415,7 @@ pub const Dir = struct {
1367 };1415 };
13681416
1369 pub const OpenError = error{1417 pub const OpenError = error{
1370 PathNotFound,1418 FileNotFound,
1371 NotDir,1419 NotDir,
1372 AccessDenied,1420 AccessDenied,
1373 FileTooBig,1421 FileTooBig,
...@@ -1382,9 +1430,11 @@ pub const Dir = struct {...@@ -1382,9 +1430,11 @@ pub const Dir = struct {
1382 PathAlreadyExists,1430 PathAlreadyExists,
1383 OutOfMemory,1431 OutOfMemory,
13841432
1433 /// See https://github.com/ziglang/zig/issues/1396
1385 Unexpected,1434 Unexpected,
1386 };1435 };
13871436
1437 /// TODO remove the allocator requirement from this API
1388 pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {1438 pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {
1389 return Dir{1439 return Dir{
1390 .allocator = allocator,1440 .allocator = allocator,
...@@ -1400,7 +1450,6 @@ pub const Dir = struct {...@@ -1400,7 +1450,6 @@ pub const Dir = struct {
1400 },1450 },
1401 Os.macosx, Os.ios => Handle{1451 Os.macosx, Os.ios => Handle{
1402 .fd = try posixOpen(1452 .fd = try posixOpen(
1403 allocator,
1404 dir_path,1453 dir_path,
1405 posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,1454 posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,
1406 0,1455 0,
...@@ -1412,7 +1461,6 @@ pub const Dir = struct {...@@ -1412,7 +1461,6 @@ pub const Dir = struct {
1412 },1461 },
1413 Os.linux => Handle{1462 Os.linux => Handle{
1414 .fd = try posixOpen(1463 .fd = try posixOpen(
1415 allocator,
1416 dir_path,1464 dir_path,
1417 posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC,1465 posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC,
1418 0,1466 0,
...@@ -1609,39 +1657,32 @@ pub fn changeCurDir(allocator: *Allocator, dir_path: []const u8) !void {...@@ -1609,39 +1657,32 @@ pub fn changeCurDir(allocator: *Allocator, dir_path: []const u8) !void {
1609}1657}
16101658
1611/// Read value of a symbolic link.1659/// Read value of a symbolic link.
1612pub fn readLink(allocator: *Allocator, pathname: []const u8) ![]u8 {1660/// The return value is a slice of out_buffer.
1613 const path_buf = try allocator.alloc(u8, pathname.len + 1);1661pub fn readLinkC(out_buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 {
1614 defer allocator.free(path_buf);1662 const rc = posix.readlink(pathname, out_buffer, out_buffer.len);
16151663 const err = posix.getErrno(rc);
1616 mem.copy(u8, path_buf, pathname);1664 switch (err) {
1617 path_buf[pathname.len] = 0;1665 0 => return out_buffer[0..rc],
16181666 posix.EACCES => return error.AccessDenied,
1619 var result_buf = try allocator.alloc(u8, 1024);1667 posix.EFAULT => unreachable,
1620 errdefer allocator.free(result_buf);1668 posix.EINVAL => unreachable,
1621 while (true) {1669 posix.EIO => return error.FileSystem,
1622 const ret_val = posix.readlink(path_buf.ptr, result_buf.ptr, result_buf.len);1670 posix.ELOOP => return error.SymLinkLoop,
1623 const err = posix.getErrno(ret_val);1671 posix.ENAMETOOLONG => unreachable, // out_buffer is at least PATH_MAX
1624 if (err > 0) {1672 posix.ENOENT => return error.FileNotFound,
1625 return switch (err) {1673 posix.ENOMEM => return error.SystemResources,
1626 posix.EACCES => error.AccessDenied,1674 posix.ENOTDIR => return error.NotDir,
1627 posix.EFAULT, posix.EINVAL => unreachable,1675 else => return unexpectedErrorPosix(err),
1628 posix.EIO => error.FileSystem,
1629 posix.ELOOP => error.SymLinkLoop,
1630 posix.ENAMETOOLONG => error.NameTooLong,
1631 posix.ENOENT => error.FileNotFound,
1632 posix.ENOMEM => error.SystemResources,
1633 posix.ENOTDIR => error.NotDir,
1634 else => unexpectedErrorPosix(err),
1635 };
1636 }
1637 if (ret_val == result_buf.len) {
1638 result_buf = try allocator.realloc(u8, result_buf, result_buf.len * 2);
1639 continue;
1640 }
1641 return allocator.shrink(u8, result_buf, ret_val);
1642 }1676 }
1643}1677}
16441678
1679/// Read value of a symbolic link.
1680/// The return value is a slice of out_buffer.
1681pub fn readLink(out_buffer: *[posix.PATH_MAX]u8, file_path: []const u8) ![]u8 {
1682 const file_path_c = try toPosixPath(file_path);
1683 return readLinkC(out_buffer, &file_path_c);
1684}
1685
1645pub fn posix_setuid(uid: u32) !void {1686pub fn posix_setuid(uid: u32) !void {
1646 const err = posix.getErrno(posix.setuid(uid));1687 const err = posix.getErrno(posix.setuid(uid));
1647 if (err == 0) return;1688 if (err == 0) return;
...@@ -1688,6 +1729,8 @@ pub fn posix_setregid(rgid: u32, egid: u32) !void {...@@ -1688,6 +1729,8 @@ pub fn posix_setregid(rgid: u32, egid: u32) !void {
16881729
1689pub const WindowsGetStdHandleErrs = error{1730pub const WindowsGetStdHandleErrs = error{
1690 NoStdHandles,1731 NoStdHandles,
1732
1733 /// See https://github.com/ziglang/zig/issues/1396
1691 Unexpected,1734 Unexpected,
1692};1735};
16931736
...@@ -2015,7 +2058,7 @@ pub fn unexpectedErrorPosix(errno: usize) UnexpectedError {...@@ -2015,7 +2058,7 @@ pub fn unexpectedErrorPosix(errno: usize) UnexpectedError {
2015/// Call this when you made a windows DLL call or something that does SetLastError2058/// Call this when you made a windows DLL call or something that does SetLastError
2016/// and you get an unexpected error.2059/// and you get an unexpected error.
2017pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {2060pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {
2018 if (unexpected_error_tracing) {2061 if (true) {
2019 debug.warn("unexpected GetLastError(): {}\n", err);2062 debug.warn("unexpected GetLastError(): {}\n", err);
2020 debug.dumpCurrentStackTrace(null);2063 debug.dumpCurrentStackTrace(null);
2021 }2064 }
...@@ -2024,17 +2067,12 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {...@@ -2024,17 +2067,12 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {
20242067
2025pub fn openSelfExe() !os.File {2068pub fn openSelfExe() !os.File {
2026 switch (builtin.os) {2069 switch (builtin.os) {
2027 Os.linux => {2070 Os.linux => return os.File.openReadC(c"/proc/self/exe"),
2028 const proc_file_path = "/proc/self/exe";
2029 var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined;
2030 var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
2031 return os.File.openRead(&fixed_allocator.allocator, proc_file_path);
2032 },
2033 Os.macosx, Os.ios => {2071 Os.macosx, Os.ios => {
2034 var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined;2072 var buf: [MAX_PATH_BYTES]u8 = undefined;
2035 var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);2073 const self_exe_path = try selfExePath(&buf);
2036 const self_exe_path = try selfExePath(&fixed_allocator.allocator);2074 buf[self_exe_path.len] = 0;
2037 return os.File.openRead(&fixed_allocator.allocator, self_exe_path);2075 return os.File.openReadC(self_exe_path.ptr);
2038 },2076 },
2039 else => @compileError("Unsupported OS"),2077 else => @compileError("Unsupported OS"),
2040 }2078 }
...@@ -2043,7 +2081,7 @@ pub fn openSelfExe() !os.File {...@@ -2043,7 +2081,7 @@ pub fn openSelfExe() !os.File {
2043test "openSelfExe" {2081test "openSelfExe" {
2044 switch (builtin.os) {2082 switch (builtin.os) {
2045 Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(),2083 Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(),
2046 else => return, // Unsupported OS.2084 else => return error.SkipZigTest, // Unsupported OS
2047 }2085 }
2048}2086}
20492087
...@@ -2052,69 +2090,68 @@ test "openSelfExe" {...@@ -2052,69 +2090,68 @@ test "openSelfExe" {
2052/// If you only want an open file handle, use openSelfExe.2090/// If you only want an open file handle, use openSelfExe.
2053/// This function may return an error if the current executable2091/// This function may return an error if the current executable
2054/// was deleted after spawning.2092/// was deleted after spawning.
2055/// Caller owns returned memory.2093/// Returned value is a slice of out_buffer.
2056pub fn selfExePath(allocator: *mem.Allocator) ![]u8 {2094///
2095/// On Linux, depends on procfs being mounted. If the currently executing binary has
2096/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
2097/// TODO make the return type of this a null terminated pointer
2098pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
2057 switch (builtin.os) {2099 switch (builtin.os) {
2058 Os.linux => {2100 Os.linux => return readLink(out_buffer, "/proc/self/exe"),
2059 // If the currently executing binary has been deleted,
2060 // the file path looks something like `/a/b/c/exe (deleted)`
2061 return readLink(allocator, "/proc/self/exe");
2062 },
2063 Os.windows => {2101 Os.windows => {
2064 var out_path = try Buffer.initSize(allocator, 0xff);2102 var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined;
2065 errdefer out_path.deinit();2103 const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
2066 while (true) {2104 const rc = windows.GetModuleFileNameW(null, &utf16le_buf, casted_len);
2067 const dword_len = try math.cast(windows.DWORD, out_path.len());2105 assert(rc <= utf16le_buf.len);
2068 const copied_amt = windows.GetModuleFileNameA(null, out_path.ptr(), dword_len);2106 if (rc == 0) {
2069 if (copied_amt <= 0) {2107 const err = windows.GetLastError();
2070 const err = windows.GetLastError();2108 switch (err) {
2071 return switch (err) {2109 else => return unexpectedErrorWindows(err),
2072 else => unexpectedErrorWindows(err),
2073 };
2074 }
2075 if (copied_amt < out_path.len()) {
2076 out_path.shrink(copied_amt);
2077 return out_path.toOwnedSlice();
2078 }2110 }
2079 const new_len = (out_path.len() << 1) | 0b1;
2080 try out_path.resize(new_len);
2081 }2111 }
2112 const utf16le_slice = utf16le_buf[0..rc];
2113 // Trust that Windows gives us valid UTF-16LE.
2114 const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable;
2115 return out_buffer[0..end_index];
2082 },2116 },
2083 Os.macosx, Os.ios => {2117 Os.macosx, Os.ios => {
2084 var u32_len: u32 = 0;2118 var u32_len: u32 = @intCast(u32, out_buffer.len); // TODO shouldn't need this cast
2085 const ret1 = c._NSGetExecutablePath(undefined, &u32_len);2119 const rc = c._NSGetExecutablePath(out_buffer, &u32_len);
2086 assert(ret1 != 0);2120 if (rc != 0) return error.NameTooLong;
2087 const bytes = try allocator.alloc(u8, u32_len);2121 return mem.toSlice(u8, out_buffer);
2088 errdefer allocator.free(bytes);
2089 const ret2 = c._NSGetExecutablePath(bytes.ptr, &u32_len);
2090 assert(ret2 == 0);
2091 return bytes;
2092 },2122 },
2093 else => @compileError("Unsupported OS"),2123 else => @compileError("Unsupported OS"),
2094 }2124 }
2095}2125}
20962126
2097/// Get the directory path that contains the current executable.2127/// `selfExeDirPath` except allocates the result on the heap.
2098/// Caller owns returned memory.2128/// Caller owns returned memory.
2099pub fn selfExeDirPath(allocator: *mem.Allocator) ![]u8 {2129pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 {
2130 var buf: [MAX_PATH_BYTES]u8 = undefined;
2131 return mem.dupe(allocator, u8, try selfExeDirPath(&buf));
2132}
2133
2134/// Get the directory path that contains the current executable.
2135/// Returned value is a slice of out_buffer.
2136pub fn selfExeDirPath(out_buffer: *[MAX_PATH_BYTES]u8) ![]const u8 {
2100 switch (builtin.os) {2137 switch (builtin.os) {
2101 Os.linux => {2138 Os.linux => {
2102 // If the currently executing binary has been deleted,2139 // If the currently executing binary has been deleted,
2103 // the file path looks something like `/a/b/c/exe (deleted)`2140 // the file path looks something like `/a/b/c/exe (deleted)`
2104 // This path cannot be opened, but it's valid for determining the directory2141 // This path cannot be opened, but it's valid for determining the directory
2105 // the executable was in when it was run.2142 // the executable was in when it was run.
2106 const full_exe_path = try readLink(allocator, "/proc/self/exe");2143 const full_exe_path = try readLinkC(out_buffer, c"/proc/self/exe");
2107 errdefer allocator.free(full_exe_path);2144 // Assume that /proc/self/exe has an absolute path, and therefore dirname
2108 const dir = path.dirname(full_exe_path) orelse ".";2145 // will not return null.
2109 return allocator.shrink(u8, full_exe_path, dir.len);2146 return path.dirname(full_exe_path).?;
2110 },2147 },
2111 Os.windows, Os.macosx, Os.ios => {2148 Os.windows, Os.macosx, Os.ios => {
2112 const self_exe_path = try selfExePath(allocator);2149 const self_exe_path = try selfExePath(out_buffer);
2113 errdefer allocator.free(self_exe_path);2150 // Assume that the OS APIs return absolute paths, and therefore dirname
2114 const dirname = os.path.dirname(self_exe_path) orelse ".";2151 // will not return null.
2115 return allocator.shrink(u8, self_exe_path, dirname.len);2152 return path.dirname(self_exe_path).?;
2116 },2153 },
2117 else => @compileError("unimplemented: std.os.selfExeDirPath for " ++ @tagName(builtin.os)),2154 else => @compileError("Unsupported OS"),
2118 }2155 }
2119}2156}
21202157
...@@ -2218,6 +2255,7 @@ pub const PosixBindError = error{...@@ -2218,6 +2255,7 @@ pub const PosixBindError = error{
2218 /// The socket inode would reside on a read-only filesystem.2255 /// The socket inode would reside on a read-only filesystem.
2219 ReadOnlyFileSystem,2256 ReadOnlyFileSystem,
22202257
2258 /// See https://github.com/ziglang/zig/issues/1396
2221 Unexpected,2259 Unexpected,
2222};2260};
22232261
...@@ -2261,6 +2299,7 @@ const PosixListenError = error{...@@ -2261,6 +2299,7 @@ const PosixListenError = error{
2261 /// The socket is not of a type that supports the listen() operation.2299 /// The socket is not of a type that supports the listen() operation.
2262 OperationNotSupported,2300 OperationNotSupported,
22632301
2302 /// See https://github.com/ziglang/zig/issues/1396
2264 Unexpected,2303 Unexpected,
2265};2304};
22662305
...@@ -2314,6 +2353,7 @@ pub const PosixAcceptError = error{...@@ -2314,6 +2353,7 @@ pub const PosixAcceptError = error{
2314 /// Firewall rules forbid connection.2353 /// Firewall rules forbid connection.
2315 BlockedByFirewall,2354 BlockedByFirewall,
23162355
2356 /// See https://github.com/ziglang/zig/issues/1396
2317 Unexpected,2357 Unexpected,
2318};2358};
23192359
...@@ -2359,6 +2399,7 @@ pub const LinuxEpollCreateError = error{...@@ -2359,6 +2399,7 @@ pub const LinuxEpollCreateError = error{
2359 /// There was insufficient memory to create the kernel object.2399 /// There was insufficient memory to create the kernel object.
2360 SystemResources,2400 SystemResources,
23612401
2402 /// See https://github.com/ziglang/zig/issues/1396
2362 Unexpected,2403 Unexpected,
2363};2404};
23642405
...@@ -2413,6 +2454,7 @@ pub const LinuxEpollCtlError = error{...@@ -2413,6 +2454,7 @@ pub const LinuxEpollCtlError = error{
2413 /// for example, a regular file or a directory.2454 /// for example, a regular file or a directory.
2414 FileDescriptorIncompatibleWithEpoll,2455 FileDescriptorIncompatibleWithEpoll,
24152456
2457 /// See https://github.com/ziglang/zig/issues/1396
2416 Unexpected,2458 Unexpected,
2417};2459};
24182460
...@@ -2455,6 +2497,7 @@ pub const LinuxEventFdError = error{...@@ -2455,6 +2497,7 @@ pub const LinuxEventFdError = error{
2455 ProcessFdQuotaExceeded,2497 ProcessFdQuotaExceeded,
2456 SystemFdQuotaExceeded,2498 SystemFdQuotaExceeded,
24572499
2500 /// See https://github.com/ziglang/zig/issues/1396
2458 Unexpected,2501 Unexpected,
2459};2502};
24602503
...@@ -2477,6 +2520,7 @@ pub const PosixGetSockNameError = error{...@@ -2477,6 +2520,7 @@ pub const PosixGetSockNameError = error{
2477 /// Insufficient resources were available in the system to perform the operation.2520 /// Insufficient resources were available in the system to perform the operation.
2478 SystemResources,2521 SystemResources,
24792522
2523 /// See https://github.com/ziglang/zig/issues/1396
2480 Unexpected,2524 Unexpected,
2481};2525};
24822526
...@@ -2530,6 +2574,7 @@ pub const PosixConnectError = error{...@@ -2530,6 +2574,7 @@ pub const PosixConnectError = error{
2530 /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.2574 /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
2531 ConnectionTimedOut,2575 ConnectionTimedOut,
25322576
2577 /// See https://github.com/ziglang/zig/issues/1396
2533 Unexpected,2578 Unexpected,
2534};2579};
25352580
...@@ -2751,6 +2796,7 @@ pub const SpawnThreadError = error{...@@ -2751,6 +2796,7 @@ pub const SpawnThreadError = error{
2751 /// Not enough userland memory to spawn the thread.2796 /// Not enough userland memory to spawn the thread.
2752 OutOfMemory,2797 OutOfMemory,
27532798
2799 /// See https://github.com/ziglang/zig/issues/1396
2754 Unexpected,2800 Unexpected,
2755};2801};
27562802
...@@ -2926,7 +2972,9 @@ pub fn posixFStat(fd: i32) !posix.Stat {...@@ -2926,7 +2972,9 @@ pub fn posixFStat(fd: i32) !posix.Stat {
2926 const err = posix.getErrno(posix.fstat(fd, &stat));2972 const err = posix.getErrno(posix.fstat(fd, &stat));
2927 if (err > 0) {2973 if (err > 0) {
2928 return switch (err) {2974 return switch (err) {
2929 posix.EBADF => error.BadFd,2975 // We do not make this an error code because if you get EBADF it's always a bug,
2976 // since the fd could have been reused.
2977 posix.EBADF => unreachable,
2930 posix.ENOMEM => error.SystemResources,2978 posix.ENOMEM => error.SystemResources,
2931 else => os.unexpectedErrorPosix(err),2979 else => os.unexpectedErrorPosix(err),
2932 };2980 };
...@@ -2938,6 +2986,8 @@ pub fn posixFStat(fd: i32) !posix.Stat {...@@ -2938,6 +2986,8 @@ pub fn posixFStat(fd: i32) !posix.Stat {
2938pub const CpuCountError = error{2986pub const CpuCountError = error{
2939 OutOfMemory,2987 OutOfMemory,
2940 PermissionDenied,2988 PermissionDenied,
2989
2990 /// See https://github.com/ziglang/zig/issues/1396
2941 Unexpected,2991 Unexpected,
2942};2992};
29432993
...@@ -3008,6 +3058,7 @@ pub const BsdKQueueError = error{...@@ -3008,6 +3058,7 @@ pub const BsdKQueueError = error{
3008 /// The system-wide limit on the total number of open files has been reached.3058 /// The system-wide limit on the total number of open files has been reached.
3009 SystemFdQuotaExceeded,3059 SystemFdQuotaExceeded,
30103060
3061 /// See https://github.com/ziglang/zig/issues/1396
3011 Unexpected,3062 Unexpected,
3012};3063};
30133064
std/os/path.zig+137-98
...@@ -11,11 +11,14 @@ const math = std.math;...@@ -11,11 +11,14 @@ const math = std.math;
11const posix = os.posix;11const posix = os.posix;
12const windows = os.windows;12const windows = os.windows;
13const cstr = std.cstr;13const cstr = std.cstr;
14const windows_util = @import("windows/util.zig");
1415
15pub const sep_windows = '\\';16pub const sep_windows = '\\';
16pub const sep_posix = '/';17pub const sep_posix = '/';
17pub const sep = if (is_windows) sep_windows else sep_posix;18pub const sep = if (is_windows) sep_windows else sep_posix;
1819
20pub const sep_str = [1]u8{sep};
21
19pub const delimiter_windows = ';';22pub const delimiter_windows = ';';
20pub const delimiter_posix = ':';23pub const delimiter_posix = ':';
21pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix;24pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix;
...@@ -337,7 +340,7 @@ pub fn resolveSlice(allocator: *Allocator, paths: []const []const u8) ![]u8 {...@@ -337,7 +340,7 @@ pub fn resolveSlice(allocator: *Allocator, paths: []const []const u8) ![]u8 {
337pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {340pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
338 if (paths.len == 0) {341 if (paths.len == 0) {
339 assert(is_windows); // resolveWindows called on non windows can't use getCwd342 assert(is_windows); // resolveWindows called on non windows can't use getCwd
340 return os.getCwd(allocator);343 return os.getCwdAlloc(allocator);
341 }344 }
342345
343 // determine which disk designator we will result with, if any346 // determine which disk designator we will result with, if any
...@@ -432,7 +435,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {...@@ -432,7 +435,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
432 },435 },
433 WindowsPath.Kind.None => {436 WindowsPath.Kind.None => {
434 assert(is_windows); // resolveWindows called on non windows can't use getCwd437 assert(is_windows); // resolveWindows called on non windows can't use getCwd
435 const cwd = try os.getCwd(allocator);438 const cwd = try os.getCwdAlloc(allocator);
436 defer allocator.free(cwd);439 defer allocator.free(cwd);
437 const parsed_cwd = windowsParsePath(cwd);440 const parsed_cwd = windowsParsePath(cwd);
438 result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1);441 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 {...@@ -448,7 +451,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
448 } else {451 } else {
449 assert(is_windows); // resolveWindows called on non windows can't use getCwd452 assert(is_windows); // resolveWindows called on non windows can't use getCwd
450 // TODO call get cwd for the result_disk_designator instead of the global one453 // TODO call get cwd for the result_disk_designator instead of the global one
451 const cwd = try os.getCwd(allocator);454 const cwd = try os.getCwdAlloc(allocator);
452 defer allocator.free(cwd);455 defer allocator.free(cwd);
453456
454 result = try allocator.alloc(u8, max_size + cwd.len + 1);457 result = try allocator.alloc(u8, max_size + cwd.len + 1);
...@@ -516,7 +519,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {...@@ -516,7 +519,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
516pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {519pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
517 if (paths.len == 0) {520 if (paths.len == 0) {
518 assert(!is_windows); // resolvePosix called on windows can't use getCwd521 assert(!is_windows); // resolvePosix called on windows can't use getCwd
519 return os.getCwd(allocator);522 return os.getCwdAlloc(allocator);
520 }523 }
521524
522 var first_index: usize = 0;525 var first_index: usize = 0;
...@@ -538,7 +541,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {...@@ -538,7 +541,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
538 result = try allocator.alloc(u8, max_size);541 result = try allocator.alloc(u8, max_size);
539 } else {542 } else {
540 assert(!is_windows); // resolvePosix called on windows can't use getCwd543 assert(!is_windows); // resolvePosix called on windows can't use getCwd
541 const cwd = try os.getCwd(allocator);544 const cwd = try os.getCwdAlloc(allocator);
542 defer allocator.free(cwd);545 defer allocator.free(cwd);
543 result = try allocator.alloc(u8, max_size + cwd.len + 1);546 result = try allocator.alloc(u8, max_size + cwd.len + 1);
544 mem.copy(u8, result, cwd);547 mem.copy(u8, result, cwd);
...@@ -573,11 +576,11 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {...@@ -573,11 +576,11 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
573 result_index += 1;576 result_index += 1;
574 }577 }
575578
576 return result[0..result_index];579 return allocator.shrink(u8, result, result_index);
577}580}
578581
579test "os.path.resolve" {582test "os.path.resolve" {
580 const cwd = try os.getCwd(debug.global_allocator);583 const cwd = try os.getCwdAlloc(debug.global_allocator);
581 if (is_windows) {584 if (is_windows) {
582 if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {585 if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {
583 cwd[0] = asciiUpper(cwd[0]);586 cwd[0] = asciiUpper(cwd[0]);
...@@ -591,7 +594,7 @@ test "os.path.resolve" {...@@ -591,7 +594,7 @@ test "os.path.resolve" {
591594
592test "os.path.resolveWindows" {595test "os.path.resolveWindows" {
593 if (is_windows) {596 if (is_windows) {
594 const cwd = try os.getCwd(debug.global_allocator);597 const cwd = try os.getCwdAlloc(debug.global_allocator);
595 const parsed_cwd = windowsParsePath(cwd);598 const parsed_cwd = windowsParsePath(cwd);
596 {599 {
597 const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" });600 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...@@ -1073,112 +1076,148 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons
1073 assert(mem.eql(u8, result, expected_output));1076 assert(mem.eql(u8, result, expected_output));
1074}1077}
10751078
1076/// Return the canonicalized absolute pathname.1079pub const RealError = error{
1077/// Expands all symbolic links and resolves references to `.`, `..`, and1080 FileNotFound,
1078/// extra `/` characters in ::pathname.1081 AccessDenied,
1079/// Caller must deallocate result.1082 NameTooLong,
1080pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 {1083 NotSupported,
1081 switch (builtin.os) {1084 NotDir,
1082 Os.windows => {1085 SymLinkLoop,
1083 const pathname_buf = try allocator.alloc(u8, pathname.len + 1);1086 InputOutput,
1084 defer allocator.free(pathname_buf);1087 FileTooBig,
10851088 IsDir,
1086 mem.copy(u8, pathname_buf, pathname);1089 ProcessFdQuotaExceeded,
1087 pathname_buf[pathname.len] = 0;1090 SystemFdQuotaExceeded,
10881091 NoDevice,
1089 const h_file = windows.CreateFileA(pathname_buf.ptr, windows.GENERIC_READ, windows.FILE_SHARE_READ, null, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null);1092 SystemResources,
1090 if (h_file == windows.INVALID_HANDLE_VALUE) {1093 NoSpaceLeft,
1091 const err = windows.GetLastError();1094 FileSystem,
1092 return switch (err) {1095 BadPathName,
1093 windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,1096
1094 windows.ERROR.ACCESS_DENIED => error.AccessDenied,1097 /// On Windows, file paths must be valid Unicode.
1095 windows.ERROR.FILENAME_EXCED_RANGE => error.NameTooLong,1098 InvalidUtf8,
1096 else => os.unexpectedErrorWindows(err),1099
1097 };1100 /// TODO remove this possibility
1098 }1101 PathAlreadyExists,
1099 defer os.close(h_file);1102
1100 var buf = try allocator.alloc(u8, 256);1103 /// TODO remove this possibility
1101 errdefer allocator.free(buf);1104 Unexpected,
1102 while (true) {1105};
1103 const buf_len = math.cast(windows.DWORD, buf.len) catch return error.NameTooLong;
1104 const result = windows.GetFinalPathNameByHandleA(h_file, buf.ptr, buf_len, windows.VOLUME_NAME_DOS);
1105
1106 if (result == 0) {
1107 const err = windows.GetLastError();
1108 return switch (err) {
1109 windows.ERROR.PATH_NOT_FOUND => error.FileNotFound,
1110 windows.ERROR.NOT_ENOUGH_MEMORY => error.OutOfMemory,
1111 windows.ERROR.INVALID_PARAMETER => unreachable,
1112 else => os.unexpectedErrorWindows(err),
1113 };
1114 }
11151106
1116 if (result > buf.len) {1107/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
1117 buf = try allocator.realloc(u8, buf, result);1108/// Otherwise use `real` or `realC`.
1118 continue;1109pub fn realW(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u16) RealError![]u8 {
1119 }1110 const h_file = windows.CreateFileW(
1111 pathname,
1112 windows.GENERIC_READ,
1113 windows.FILE_SHARE_READ,
1114 null,
1115 windows.OPEN_EXISTING,
1116 windows.FILE_ATTRIBUTE_NORMAL,
1117 null,
1118 );
1119 if (h_file == windows.INVALID_HANDLE_VALUE) {
1120 const err = windows.GetLastError();
1121 switch (err) {
1122 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
1123 windows.ERROR.ACCESS_DENIED => return error.AccessDenied,
1124 windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
1125 else => return os.unexpectedErrorWindows(err),
1126 }
1127 }
1128 defer os.close(h_file);
1129 var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined;
1130 const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
1131 const result = windows.GetFinalPathNameByHandleW(h_file, &utf16le_buf, casted_len, windows.VOLUME_NAME_DOS);
1132 assert(result <= utf16le_buf.len);
1133 if (result == 0) {
1134 const err = windows.GetLastError();
1135 switch (err) {
1136 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
1137 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
1138 windows.ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources,
1139 windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
1140 windows.ERROR.INVALID_PARAMETER => unreachable,
1141 else => return os.unexpectedErrorWindows(err),
1142 }
1143 }
1144 const utf16le_slice = utf16le_buf[0..result];
11201145
1121 // windows returns \\?\ prepended to the path1146 // windows returns \\?\ prepended to the path
1122 // we strip it because nobody wants \\?\ prepended to their path1147 // we strip it because nobody wants \\?\ prepended to their path
1123 const final_len = x: {1148 const prefix = []u16{ '\\', '\\', '?', '\\' };
1124 if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) {1149 const start_index = if (mem.startsWith(u16, utf16le_slice, prefix)) prefix.len else 0;
1125 var i: usize = 4;1150
1126 while (i < result) : (i += 1) {1151 // Trust that Windows gives us valid UTF-16LE.
1127 buf[i - 4] = buf[i];1152 const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice[start_index..]) catch unreachable;
1128 }1153 return out_buffer[0..end_index];
1129 break :x result - 4;1154}
1130 } else {1155
1131 break :x result;1156/// See `real`
1132 }1157/// Use this when you have a null terminated pointer path.
1133 };1158pub fn realC(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u8) RealError![]u8 {
11341159 switch (builtin.os) {
1135 return allocator.shrink(u8, buf, final_len);1160 Os.windows => {
1136 }1161 const pathname_w = try windows_util.cStrToPrefixedFileW(pathname);
1162 return realW(out_buffer, pathname_w);
1137 },1163 },
1138 Os.macosx, Os.ios => {1164 Os.macosx, Os.ios => {
1139 // TODO instead of calling the libc function here, port the implementation1165 // TODO instead of calling the libc function here, port the implementation to Zig
1140 // to Zig, and then remove the NameTooLong error possibility.1166 const err = posix.getErrno(posix.realpath(pathname, out_buffer));
1141 const pathname_buf = try allocator.alloc(u8, pathname.len + 1);1167 switch (err) {
1142 defer allocator.free(pathname_buf);1168 0 => return mem.toSlice(u8, out_buffer),
11431169 posix.EINVAL => unreachable,
1144 const result_buf = try allocator.alloc(u8, posix.PATH_MAX);1170 posix.EBADF => unreachable,
1145 errdefer allocator.free(result_buf);1171 posix.EFAULT => unreachable,
11461172 posix.EACCES => return error.AccessDenied,
1147 mem.copy(u8, pathname_buf, pathname);1173 posix.ENOENT => return error.FileNotFound,
1148 pathname_buf[pathname.len] = 0;1174 posix.ENOTSUP => return error.NotSupported,
11491175 posix.ENOTDIR => return error.NotDir,
1150 const err = posix.getErrno(posix.realpath(pathname_buf.ptr, result_buf.ptr));1176 posix.ENAMETOOLONG => return error.NameTooLong,
1151 if (err > 0) {1177 posix.ELOOP => return error.SymLinkLoop,
1152 return switch (err) {1178 posix.EIO => return error.InputOutput,
1153 posix.EINVAL => unreachable,1179 else => return os.unexpectedErrorPosix(err),
1154 posix.EBADF => unreachable,
1155 posix.EFAULT => unreachable,
1156 posix.EACCES => error.AccessDenied,
1157 posix.ENOENT => error.FileNotFound,
1158 posix.ENOTSUP => error.NotSupported,
1159 posix.ENOTDIR => error.NotDir,
1160 posix.ENAMETOOLONG => error.NameTooLong,
1161 posix.ELOOP => error.SymLinkLoop,
1162 posix.EIO => error.InputOutput,
1163 else => os.unexpectedErrorPosix(err),
1164 };
1165 }1180 }
1166 return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr));
1167 },1181 },
1168 Os.linux => {1182 Os.linux => {
1169 const fd = try os.posixOpen(allocator, pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);1183 const fd = try os.posixOpenC(pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);
1170 defer os.close(fd);1184 defer os.close(fd);
11711185
1172 var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;1186 var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;
1173 const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd) catch unreachable;1187 const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}\x00", fd) catch unreachable;
11741188
1175 return os.readLink(allocator, proc_path);1189 return os.readLinkC(out_buffer, proc_path.ptr);
1176 },1190 },
1177 else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)),1191 else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)),
1178 }1192 }
1179}1193}
11801194
1195/// Return the canonicalized absolute pathname.
1196/// Expands all symbolic links and resolves references to `.`, `..`, and
1197/// extra `/` characters in ::pathname.
1198/// The return value is a slice of out_buffer, and not necessarily from the beginning.
1199pub fn real(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: []const u8) RealError![]u8 {
1200 switch (builtin.os) {
1201 Os.windows => {
1202 const pathname_w = try windows_util.sliceToPrefixedFileW(pathname);
1203 return realW(out_buffer, &pathname_w);
1204 },
1205 Os.macosx, Os.ios, Os.linux => {
1206 const pathname_c = try os.toPosixPath(pathname);
1207 return realC(out_buffer, &pathname_c);
1208 },
1209 else => @compileError("Unsupported OS"),
1210 }
1211}
1212
1213/// `real`, except caller must free the returned memory.
1214pub fn realAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 {
1215 var buf: [os.MAX_PATH_BYTES]u8 = undefined;
1216 return mem.dupe(allocator, u8, try real(&buf, pathname));
1217}
1218
1181test "os.path.real" {1219test "os.path.real" {
1182 // at least call it so it gets compiled1220 // at least call it so it gets compiled
1183 _ = real(debug.global_allocator, "some_path");1221 var buf: [os.MAX_PATH_BYTES]u8 = undefined;
1222 std.debug.assertError(real(&buf, "definitely_bogus_does_not_exist1234"), error.FileNotFound);
1184}1223}
std/os/test.zig+8-8
...@@ -10,27 +10,27 @@ const AtomicRmwOp = builtin.AtomicRmwOp;...@@ -10,27 +10,27 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
10const AtomicOrder = builtin.AtomicOrder;10const AtomicOrder = builtin.AtomicOrder;
1111
12test "makePath, put some files in it, deleteTree" {12test "makePath, put some files in it, deleteTree" {
13 try os.makePath(a, "os_test_tmp/b/c");13 try os.makePath(a, "os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "c");
14 try io.writeFile(a, "os_test_tmp/b/c/file.txt", "nonsense");14 try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "c" ++ os.path.sep_str ++ "file.txt", "nonsense");
15 try io.writeFile(a, "os_test_tmp/b/file2.txt", "blah");15 try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "file2.txt", "blah");
16 try os.deleteTree(a, "os_test_tmp");16 try os.deleteTree(a, "os_test_tmp");
17 if (os.Dir.open(a, "os_test_tmp")) |dir| {17 if (os.Dir.open(a, "os_test_tmp")) |dir| {
18 @panic("expected error");18 @panic("expected error");
19 } else |err| {19 } else |err| {
20 assert(err == error.PathNotFound);20 assert(err == error.FileNotFound);
21 }21 }
22}22}
2323
24test "access file" {24test "access file" {
25 try os.makePath(a, "os_test_tmp");25 try os.makePath(a, "os_test_tmp");
26 if (os.File.access(a, "os_test_tmp/file.txt")) |ok| {26 if (os.File.access("os_test_tmp" ++ os.path.sep_str ++ "file.txt")) |ok| {
27 @panic("expected error");27 @panic("expected error");
28 } else |err| {28 } else |err| {
29 assert(err == error.NotFound);29 assert(err == error.FileNotFound);
30 }30 }
3131
32 try io.writeFile(a, "os_test_tmp/file.txt", "");32 try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "file.txt", "");
33 try os.File.access(a, "os_test_tmp/file.txt");33 try os.File.access("os_test_tmp" ++ os.path.sep_str ++ "file.txt");
34 try os.deleteTree(a, "os_test_tmp");34 try os.deleteTree(a, "os_test_tmp");
35}35}
3636
std/os/windows/kernel32.zig+25-13
...@@ -1,14 +1,11 @@...@@ -1,14 +1,11 @@
1use @import("index.zig");1use @import("index.zig");
22
3
4pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL;3pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL;
54
6pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;5pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
76
8pub extern "kernel32" stdcallcc fn CreateDirectoryA(7pub extern "kernel32" stdcallcc fn CreateDirectoryA(lpPathName: [*]const u8, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
9 lpPathName: LPCSTR,8pub extern "kernel32" stdcallcc fn CreateDirectoryW(lpPathName: [*]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
10 lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
11) BOOL;
129
13pub extern "kernel32" stdcallcc fn CreateFileA(10pub extern "kernel32" stdcallcc fn CreateFileA(
14 lpFileName: [*]const u8, // TODO null terminated pointer type11 lpFileName: [*]const u8, // TODO null terminated pointer type
...@@ -60,7 +57,8 @@ pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, Ex...@@ -60,7 +57,8 @@ pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, Ex
6057
61pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE;58pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE;
6259
63pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL;60pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: [*]const u8) BOOL;
61pub extern "kernel32" stdcallcc fn DeleteFileW(lpFileName: [*]const u16) BOOL;
6462
65pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;63pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
6664
...@@ -74,7 +72,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;...@@ -74,7 +72,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;
7472
75pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL;73pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL;
7674
77pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) DWORD;75pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: DWORD, lpBuffer: ?[*]CHAR) DWORD;
76pub extern "kernel32" stdcallcc fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: ?[*]WCHAR) DWORD;
7877
79pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE;78pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE;
80pub extern "kernel32" stdcallcc fn GetCurrentThreadId() DWORD;79pub extern "kernel32" stdcallcc fn GetCurrentThreadId() DWORD;
...@@ -87,9 +86,11 @@ pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCo...@@ -87,9 +86,11 @@ pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCo
8786
88pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL;87pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL;
8988
90pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: LPCSTR) DWORD;89pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: [*]const CHAR) DWORD;
90pub extern "kernel32" stdcallcc fn GetFileAttributesW(lpFileName: [*]const WCHAR) DWORD;
9191
92pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: LPSTR, nSize: DWORD) DWORD;92pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: [*]u8, nSize: DWORD) DWORD;
93pub extern "kernel32" stdcallcc fn GetModuleFileNameW(hModule: ?HMODULE, lpFilename: [*]u16, nSize: DWORD) DWORD;
9394
94pub extern "kernel32" stdcallcc fn GetLastError() DWORD;95pub extern "kernel32" stdcallcc fn GetLastError() DWORD;
9596
...@@ -107,6 +108,12 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(...@@ -107,6 +108,12 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
107 dwFlags: DWORD,108 dwFlags: DWORD,
108) DWORD;109) DWORD;
109110
111pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleW(
112 hFile: HANDLE,
113 lpszFilePath: [*]u16,
114 cchFilePath: DWORD,
115 dwFlags: DWORD,
116) DWORD;
110117
111pub extern "kernel32" stdcallcc fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) BOOL;118pub extern "kernel32" stdcallcc fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) BOOL;
112119
...@@ -132,8 +139,14 @@ pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem...@@ -132,8 +139,14 @@ pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem
132pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL;139pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL;
133140
134pub extern "kernel32" stdcallcc fn MoveFileExA(141pub extern "kernel32" stdcallcc fn MoveFileExA(
135 lpExistingFileName: LPCSTR,142 lpExistingFileName: [*]const u8,
136 lpNewFileName: LPCSTR,143 lpNewFileName: [*]const u8,
144 dwFlags: DWORD,
145) BOOL;
146
147pub extern "kernel32" stdcallcc fn MoveFileExW(
148 lpExistingFileName: [*]const u16,
149 lpNewFileName: [*]const u16,
137 dwFlags: DWORD,150 dwFlags: DWORD,
138) BOOL;151) BOOL;
139152
...@@ -194,7 +207,6 @@ pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;...@@ -194,7 +207,6 @@ pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;
194207
195pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;208pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;
196209
197
198pub const FILE_NOTIFY_INFORMATION = extern struct {210pub const FILE_NOTIFY_INFORMATION = extern struct {
199 NextEntryOffset: DWORD,211 NextEntryOffset: DWORD,
200 Action: DWORD,212 Action: DWORD,
...@@ -208,7 +220,7 @@ pub const FILE_ACTION_MODIFIED = 0x00000003;...@@ -208,7 +220,7 @@ pub const FILE_ACTION_MODIFIED = 0x00000003;
208pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;220pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;
209pub const FILE_ACTION_RENAMED_NEW_NAME = 0x00000005;221pub const FILE_ACTION_RENAMED_NEW_NAME = 0x00000005;
210222
211pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?extern fn(DWORD, DWORD, *OVERLAPPED) void;223pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?extern fn (DWORD, DWORD, *OVERLAPPED) void;
212224
213pub const FILE_LIST_DIRECTORY = 1;225pub const FILE_LIST_DIRECTORY = 1;
214226
std/os/windows/util.zig+73-19
...@@ -7,9 +7,17 @@ const mem = std.mem;...@@ -7,9 +7,17 @@ const mem = std.mem;
7const BufMap = std.BufMap;7const BufMap = std.BufMap;
8const cstr = std.cstr;8const cstr = std.cstr;
99
10// > The maximum path of 32,767 characters is approximate, because the "\\?\"
11// > prefix may be expanded to a longer string by the system at run time, and
12// > this expansion applies to the total length.
13// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
14pub const PATH_MAX_WIDE = 32767;
15
10pub const WaitError = error{16pub const WaitError = error{
11 WaitAbandoned,17 WaitAbandoned,
12 WaitTimeOut,18 WaitTimeOut,
19
20 /// See https://github.com/ziglang/zig/issues/1396
13 Unexpected,21 Unexpected,
14};22};
1523
...@@ -37,6 +45,8 @@ pub const WriteError = error{...@@ -37,6 +45,8 @@ pub const WriteError = error{
37 SystemResources,45 SystemResources,
38 OperationAborted,46 OperationAborted,
39 BrokenPipe,47 BrokenPipe,
48
49 /// See https://github.com/ziglang/zig/issues/1396
40 Unexpected,50 Unexpected,
41};51};
4252
...@@ -86,37 +96,51 @@ pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool {...@@ -86,37 +96,51 @@ pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool {
86pub const OpenError = error{96pub const OpenError = error{
87 SharingViolation,97 SharingViolation,
88 PathAlreadyExists,98 PathAlreadyExists,
99
100 /// When any of the path components can not be found or the file component can not
101 /// be found. Some operating systems distinguish between path components not found and
102 /// file components not found, but they are collapsed into FileNotFound to gain
103 /// consistency across operating systems.
89 FileNotFound,104 FileNotFound,
105
90 AccessDenied,106 AccessDenied,
91 PipeBusy,107 PipeBusy,
108 NameTooLong,
109
110 /// On Windows, file paths must be valid Unicode.
111 InvalidUtf8,
112
113 /// On Windows, file paths cannot contain these characters:
114 /// '/', '*', '?', '"', '<', '>', '|'
115 BadPathName,
116
117 /// See https://github.com/ziglang/zig/issues/1396
92 Unexpected,118 Unexpected,
93 OutOfMemory,
94};119};
95120
96/// `file_path` needs to be copied in memory to add a null terminating byte, hence the allocator.
97pub fn windowsOpen(121pub fn windowsOpen(
98 allocator: *mem.Allocator,
99 file_path: []const u8,122 file_path: []const u8,
100 desired_access: windows.DWORD,123 desired_access: windows.DWORD,
101 share_mode: windows.DWORD,124 share_mode: windows.DWORD,
102 creation_disposition: windows.DWORD,125 creation_disposition: windows.DWORD,
103 flags_and_attrs: windows.DWORD,126 flags_and_attrs: windows.DWORD,
104) OpenError!windows.HANDLE {127) OpenError!windows.HANDLE {
105 const path_with_null = try cstr.addNullByte(allocator, file_path);128 const file_path_w = try sliceToPrefixedFileW(file_path);
106 defer allocator.free(path_with_null);
107129
108 const result = windows.CreateFileA(path_with_null.ptr, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null);130 const result = windows.CreateFileW(&file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null);
109131
110 if (result == windows.INVALID_HANDLE_VALUE) {132 if (result == windows.INVALID_HANDLE_VALUE) {
111 const err = windows.GetLastError();133 const err = windows.GetLastError();
112 return switch (err) {134 switch (err) {
113 windows.ERROR.SHARING_VIOLATION => OpenError.SharingViolation,135 windows.ERROR.SHARING_VIOLATION => return OpenError.SharingViolation,
114 windows.ERROR.ALREADY_EXISTS, windows.ERROR.FILE_EXISTS => OpenError.PathAlreadyExists,136 windows.ERROR.ALREADY_EXISTS => return OpenError.PathAlreadyExists,
115 windows.ERROR.FILE_NOT_FOUND => OpenError.FileNotFound,137 windows.ERROR.FILE_EXISTS => return OpenError.PathAlreadyExists,
116 windows.ERROR.ACCESS_DENIED => OpenError.AccessDenied,138 windows.ERROR.FILE_NOT_FOUND => return OpenError.FileNotFound,
117 windows.ERROR.PIPE_BUSY => OpenError.PipeBusy,139 windows.ERROR.PATH_NOT_FOUND => return OpenError.FileNotFound,
118 else => os.unexpectedErrorWindows(err),140 windows.ERROR.ACCESS_DENIED => return OpenError.AccessDenied,
119 };141 windows.ERROR.PIPE_BUSY => return OpenError.PipeBusy,
142 else => return os.unexpectedErrorWindows(err),
143 }
120 }144 }
121145
122 return result;146 return result;
...@@ -192,9 +216,8 @@ pub fn windowsFindFirstFile(...@@ -192,9 +216,8 @@ pub fn windowsFindFirstFile(
192 if (handle == windows.INVALID_HANDLE_VALUE) {216 if (handle == windows.INVALID_HANDLE_VALUE) {
193 const err = windows.GetLastError();217 const err = windows.GetLastError();
194 switch (err) {218 switch (err) {
195 windows.ERROR.FILE_NOT_FOUND,219 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
196 windows.ERROR.PATH_NOT_FOUND,220 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
197 => return error.PathNotFound,
198 else => return os.unexpectedErrorWindows(err),221 else => return os.unexpectedErrorWindows(err),
199 }222 }
200 }223 }
...@@ -238,7 +261,7 @@ pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_...@@ -238,7 +261,7 @@ pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_
238 }261 }
239}262}
240263
241pub const WindowsWaitResult = enum{264pub const WindowsWaitResult = enum {
242 Normal,265 Normal,
243 Aborted,266 Aborted,
244 Cancelled,267 Cancelled,
...@@ -254,8 +277,39 @@ pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_t...@@ -254,8 +277,39 @@ pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_t
254 if (std.debug.runtime_safety) {277 if (std.debug.runtime_safety) {
255 std.debug.panic("unexpected error: {}\n", err);278 std.debug.panic("unexpected error: {}\n", err);
256 }279 }
257 }280 },
258 }281 }
259 }282 }
260 return WindowsWaitResult.Normal;283 return WindowsWaitResult.Normal;
261}284}
285
286pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 {
287 return sliceToPrefixedFileW(mem.toSliceConst(u8, s));
288}
289
290pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 {
291 // TODO well defined copy elision
292 var result: [PATH_MAX_WIDE + 1]u16 = undefined;
293
294 // > File I/O functions in the Windows API convert "/" to "\" as part of
295 // > converting the name to an NT-style name, except when using the "\\?\"
296 // > prefix as detailed in the following sections.
297 // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
298 // Because we want the larger maximum path length for absolute paths, we
299 // disallow forward slashes in zig std lib file functions on Windows.
300 for (s) |byte|
301 switch (byte) {
302 '/', '*', '?', '"', '<', '>', '|' => return error.BadPathName,
303 else => {},
304 };
305 const start_index = if (mem.startsWith(u8, s, "\\\\") or !os.path.isAbsolute(s)) 0 else blk: {
306 const prefix = []u16{ '\\', '\\', '?', '\\' };
307 mem.copy(u16, result[0..], prefix);
308 break :blk prefix.len;
309 };
310 const end_index = start_index + try std.unicode.utf8ToUtf16Le(result[start_index..], s);
311 assert(end_index <= result.len);
312 if (end_index == result.len) return error.NameTooLong;
313 result[end_index] = 0;
314 return result;
315}
std/unicode.zig+71-31
...@@ -218,7 +218,6 @@ const Utf8Iterator = struct {...@@ -218,7 +218,6 @@ const Utf8Iterator = struct {
218 }218 }
219219
220 const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable;220 const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable;
221
222 it.i += cp_len;221 it.i += cp_len;
223 return it.bytes[it.i - cp_len .. it.i];222 return it.bytes[it.i - cp_len .. it.i];
224 }223 }
...@@ -236,6 +235,38 @@ const Utf8Iterator = struct {...@@ -236,6 +235,38 @@ const Utf8Iterator = struct {
236 }235 }
237};236};
238237
238pub const Utf16LeIterator = struct {
239 bytes: []const u8,
240 i: usize,
241
242 pub fn init(s: []const u16) Utf16LeIterator {
243 return Utf16LeIterator{
244 .bytes = @sliceToBytes(s),
245 .i = 0,
246 };
247 }
248
249 pub fn nextCodepoint(it: *Utf16LeIterator) !?u32 {
250 assert(it.i <= it.bytes.len);
251 if (it.i == it.bytes.len) return null;
252 const c0: u32 = mem.readIntLE(u16, it.bytes[it.i .. it.i + 2]);
253 if (c0 & ~u32(0x03ff) == 0xd800) {
254 // surrogate pair
255 it.i += 2;
256 if (it.i >= it.bytes.len) return error.DanglingSurrogateHalf;
257 const c1: u32 = mem.readIntLE(u16, it.bytes[it.i .. it.i + 2]);
258 if (c1 & ~u32(0x03ff) != 0xdc00) return error.ExpectedSecondSurrogateHalf;
259 it.i += 2;
260 return 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff));
261 } else if (c0 & ~u32(0x03ff) == 0xdc00) {
262 return error.UnexpectedSecondSurrogateHalf;
263 } else {
264 it.i += 2;
265 return c0;
266 }
267 }
268};
269
239test "utf8 encode" {270test "utf8 encode" {
240 comptime testUtf8Encode() catch unreachable;271 comptime testUtf8Encode() catch unreachable;
241 try testUtf8Encode();272 try testUtf8Encode();
...@@ -446,42 +477,34 @@ fn testDecode(bytes: []const u8) !u32 {...@@ -446,42 +477,34 @@ fn testDecode(bytes: []const u8) !u32 {
446 return utf8Decode(bytes);477 return utf8Decode(bytes);
447}478}
448479
449// TODO: make this API on top of a non-allocating Utf16LeView480/// Caller must free returned memory.
450pub fn utf16leToUtf8(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 {481pub fn utf16leToUtf8Alloc(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 {
451 var result = std.ArrayList(u8).init(allocator);482 var result = std.ArrayList(u8).init(allocator);
452 // optimistically guess that it will all be ascii.483 // optimistically guess that it will all be ascii.
453 try result.ensureCapacity(utf16le.len);484 try result.ensureCapacity(utf16le.len);
454
455 const utf16le_as_bytes = @sliceToBytes(utf16le);
456 var i: usize = 0;
457 var out_index: usize = 0;485 var out_index: usize = 0;
458 while (i < utf16le_as_bytes.len) : (i += 2) {486 var it = Utf16LeIterator.init(utf16le);
459 // decode487 while (try it.nextCodepoint()) |codepoint| {
460 const c0: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]);
461 var codepoint: u32 = undefined;
462 if (c0 & ~u32(0x03ff) == 0xd800) {
463 // surrogate pair
464 i += 2;
465 if (i >= utf16le_as_bytes.len) return error.DanglingSurrogateHalf;
466 const c1: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]);
467 if (c1 & ~u32(0x03ff) != 0xdc00) return error.ExpectedSecondSurrogateHalf;
468 codepoint = 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff));
469 } else if (c0 & ~u32(0x03ff) == 0xdc00) {
470 return error.UnexpectedSecondSurrogateHalf;
471 } else {
472 codepoint = c0;
473 }
474
475 // encode
476 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;488 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
477 try result.resize(result.len + utf8_len);489 try result.resize(result.len + utf8_len);
478 _ = utf8Encode(codepoint, result.items[out_index..]) catch unreachable;490 assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len);
479 out_index += utf8_len;491 out_index += utf8_len;
480 }492 }
481493
482 return result.toOwnedSlice();494 return result.toOwnedSlice();
483}495}
484496
497/// Asserts that the output buffer is big enough.
498/// Returns end byte index into utf8.
499pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !usize {
500 var end_index: usize = 0;
501 var it = Utf16LeIterator.init(utf16le);
502 while (try it.nextCodepoint()) |codepoint| {
503 end_index += try utf8Encode(codepoint, utf8[end_index..]);
504 }
505 return end_index;
506}
507
485test "utf16leToUtf8" {508test "utf16leToUtf8" {
486 var utf16le: [2]u16 = undefined;509 var utf16le: [2]u16 = undefined;
487 const utf16le_as_bytes = @sliceToBytes(utf16le[0..]);510 const utf16le_as_bytes = @sliceToBytes(utf16le[0..]);
...@@ -489,14 +512,14 @@ test "utf16leToUtf8" {...@@ -489,14 +512,14 @@ test "utf16leToUtf8" {
489 {512 {
490 mem.writeInt(utf16le_as_bytes[0..], u16('A'), builtin.Endian.Little);513 mem.writeInt(utf16le_as_bytes[0..], u16('A'), builtin.Endian.Little);
491 mem.writeInt(utf16le_as_bytes[2..], u16('a'), builtin.Endian.Little);514 mem.writeInt(utf16le_as_bytes[2..], u16('a'), builtin.Endian.Little);
492 const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);515 const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
493 assert(mem.eql(u8, utf8, "Aa"));516 assert(mem.eql(u8, utf8, "Aa"));
494 }517 }
495518
496 {519 {
497 mem.writeInt(utf16le_as_bytes[0..], u16(0x80), builtin.Endian.Little);520 mem.writeInt(utf16le_as_bytes[0..], u16(0x80), builtin.Endian.Little);
498 mem.writeInt(utf16le_as_bytes[2..], u16(0xffff), builtin.Endian.Little);521 mem.writeInt(utf16le_as_bytes[2..], u16(0xffff), builtin.Endian.Little);
499 const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);522 const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
500 assert(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf"));523 assert(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf"));
501 }524 }
502525
...@@ -504,7 +527,7 @@ test "utf16leToUtf8" {...@@ -504,7 +527,7 @@ test "utf16leToUtf8" {
504 // the values just outside the surrogate half range527 // the values just outside the surrogate half range
505 mem.writeInt(utf16le_as_bytes[0..], u16(0xd7ff), builtin.Endian.Little);528 mem.writeInt(utf16le_as_bytes[0..], u16(0xd7ff), builtin.Endian.Little);
506 mem.writeInt(utf16le_as_bytes[2..], u16(0xe000), builtin.Endian.Little);529 mem.writeInt(utf16le_as_bytes[2..], u16(0xe000), builtin.Endian.Little);
507 const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);530 const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
508 assert(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80"));531 assert(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80"));
509 }532 }
510533
...@@ -512,7 +535,7 @@ test "utf16leToUtf8" {...@@ -512,7 +535,7 @@ test "utf16leToUtf8" {
512 // smallest surrogate pair535 // smallest surrogate pair
513 mem.writeInt(utf16le_as_bytes[0..], u16(0xd800), builtin.Endian.Little);536 mem.writeInt(utf16le_as_bytes[0..], u16(0xd800), builtin.Endian.Little);
514 mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little);537 mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little);
515 const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);538 const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
516 assert(mem.eql(u8, utf8, "\xf0\x90\x80\x80"));539 assert(mem.eql(u8, utf8, "\xf0\x90\x80\x80"));
517 }540 }
518541
...@@ -520,14 +543,14 @@ test "utf16leToUtf8" {...@@ -520,14 +543,14 @@ test "utf16leToUtf8" {
520 // largest surrogate pair543 // largest surrogate pair
521 mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);544 mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);
522 mem.writeInt(utf16le_as_bytes[2..], u16(0xdfff), builtin.Endian.Little);545 mem.writeInt(utf16le_as_bytes[2..], u16(0xdfff), builtin.Endian.Little);
523 const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);546 const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
524 assert(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf"));547 assert(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf"));
525 }548 }
526549
527 {550 {
528 mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);551 mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);
529 mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little);552 mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little);
530 const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);553 const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
531 assert(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80"));554 assert(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80"));
532 }555 }
533}556}
...@@ -548,3 +571,20 @@ pub fn utf8ToUtf16LeWithNull(allocator: *mem.Allocator, utf8: []const u8) ![]u16...@@ -548,3 +571,20 @@ pub fn utf8ToUtf16LeWithNull(allocator: *mem.Allocator, utf8: []const u8) ![]u16
548 try result.append(0);571 try result.append(0);
549 return result.toOwnedSlice();572 return result.toOwnedSlice();
550}573}
574
575/// Returns index of next character. If exact fit, returned index equals output slice length.
576/// If ran out of room, returned index equals output slice length + 1.
577/// TODO support codepoints bigger than 16 bits
578pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize {
579 const utf16le_as_bytes = @sliceToBytes(utf16le[0..]);
580 var end_index: usize = 0;
581
582 var it = (try Utf8View.init(utf8)).iterator();
583 while (it.nextCodepoint()) |codepoint| {
584 if (end_index == utf16le_as_bytes.len) return (end_index / 2) + 1;
585 // TODO surrogate pairs
586 mem.writeInt(utf16le_as_bytes[end_index..], @intCast(u16, codepoint), builtin.Endian.Little);
587 end_index += 2;
588 }
589 return end_index / 2;
590}
test/cases/merge_error_sets.zig+2-2
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1const A = error{1const A = error{
2 PathNotFound,2 FileNotFound,
3 NotDir,3 NotDir,
4};4};
5const B = error{OutOfMemory};5const B = error{OutOfMemory};
...@@ -15,7 +15,7 @@ test "merge error sets" {...@@ -15,7 +15,7 @@ test "merge error sets" {
15 @panic("unexpected");15 @panic("unexpected");
16 } else |err| switch (err) {16 } else |err| switch (err) {
17 error.OutOfMemory => @panic("unexpected"),17 error.OutOfMemory => @panic("unexpected"),
18 error.PathNotFound => @panic("unexpected"),18 error.FileNotFound => @panic("unexpected"),
19 error.NotDir => {},19 error.NotDir => {},
20 }20 }
21}21}