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 {
1919 var docgen_cmd = b.addCommand(null, b.env_map, [][]const u8{
2020 docgen_exe.getOutputPath(),
2121 rel_zig_exe,
22 "doc/langref.html.in",
22 "doc" ++ os.path.sep_str ++ "langref.html.in",
2323 os.path.join(b.allocator, b.cache_root, "langref.html") catch unreachable,
2424 });
2525 docgen_cmd.step.dependOn(&docgen_exe.step);
doc/docgen.zig+3-3
......@@ -34,10 +34,10 @@ pub fn main() !void {
3434 const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg"));
3535 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);
3838 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);
4141 defer out_file.close();
4242
4343 var file_in_stream = io.FileInStream.init(&in_file);
......@@ -738,7 +738,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
738738 try out.print("<pre><code class=\"zig\">{}</code></pre>", escaped_source);
739739 const name_plus_ext = try std.fmt.allocPrint(allocator, "{}.zig", code.name);
740740 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
743743 switch (code.id) {
744744 Code.Id.Exe => |expected_outcome| {
example/cat/main.zig+1-1
......@@ -20,7 +20,7 @@ pub fn main() !void {
2020 } else if (arg[0] == '-') {
2121 return usage(exe);
2222 } else {
23 var file = os.File.openRead(allocator, arg) catch |err| {
23 var file = os.File.openRead(arg) catch |err| {
2424 warn("Unable to open file: {}\n", @errorName(err));
2525 return err;
2626 };
src-self-hosted/compilation.zig+2-4
......@@ -257,8 +257,6 @@ pub const Compilation = struct {
257257 pub const BuildError = error{
258258 OutOfMemory,
259259 EndOfStream,
260 BadFd,
261 Io,
262260 IsDir,
263261 Unexpected,
264262 SystemResources,
......@@ -273,7 +271,6 @@ pub const Compilation = struct {
273271 NameTooLong,
274272 SystemFdQuotaExceeded,
275273 NoDevice,
276 PathNotFound,
277274 NoSpaceLeft,
278275 NotDir,
279276 FileSystem,
......@@ -302,6 +299,7 @@ pub const Compilation = struct {
302299 UnsupportedLinkArchitecture,
303300 UserResourceLimitReached,
304301 InvalidUtf8,
302 BadPathName,
305303 };
306304
307305 pub const Event = union(enum) {
......@@ -961,7 +959,7 @@ pub const Compilation = struct {
961959 if (self.root_src_path) |root_src_path| {
962960 const root_scope = blk: {
963961 // 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| {
965963 try self.addCompileErrorCli(root_src_path, "unable to open: {}", @errorName(err));
966964 return;
967965 };
src-self-hosted/errmsg.zig+1-1
......@@ -235,7 +235,7 @@ pub const Msg = struct {
235235 const allocator = msg.getAllocator();
236236 const tree = msg.getTree();
237237
238 const cwd = try os.getCwd(allocator);
238 const cwd = try os.getCwdAlloc(allocator);
239239 defer allocator.free(cwd);
240240
241241 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) ![
1414 const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig");
1515 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);
1818 file.close();
1919
2020 return test_zig_dir;
......@@ -22,7 +22,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![
2222
2323/// Caller must free result
2424pub fn findZigLibDir(allocator: *mem.Allocator) ![]u8 {
25 const self_exe_path = try os.selfExeDirPath(allocator);
25 const self_exe_path = try os.selfExeDirPathAlloc(allocator);
2626 defer allocator.free(self_exe_path);
2727
2828 var cur_path: []const u8 = self_exe_path;
src-self-hosted/libc_installation.zig+7-8
......@@ -233,7 +233,7 @@ pub const LibCInstallation = struct {
233233 const stdlib_path = try std.os.path.join(loop.allocator, search_path, "stdlib.h");
234234 defer loop.allocator.free(stdlib_path);
235235
236 if (try fileExists(loop.allocator, stdlib_path)) {
236 if (try fileExists(stdlib_path)) {
237237 self.include_dir = try std.mem.dupe(loop.allocator, u8, search_path);
238238 return;
239239 }
......@@ -257,7 +257,7 @@ pub const LibCInstallation = struct {
257257 const stdlib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "stdlib.h");
258258 defer loop.allocator.free(stdlib_path);
259259
260 if (try fileExists(loop.allocator, stdlib_path)) {
260 if (try fileExists(stdlib_path)) {
261261 self.include_dir = result_buf.toOwnedSlice();
262262 return;
263263 }
......@@ -285,7 +285,7 @@ pub const LibCInstallation = struct {
285285 }
286286 const ucrt_lib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "ucrt.lib");
287287 defer loop.allocator.free(ucrt_lib_path);
288 if (try fileExists(loop.allocator, ucrt_lib_path)) {
288 if (try fileExists(ucrt_lib_path)) {
289289 self.lib_dir = result_buf.toOwnedSlice();
290290 return;
291291 }
......@@ -360,7 +360,7 @@ pub const LibCInstallation = struct {
360360 }
361361 const kernel32_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "kernel32.lib");
362362 defer loop.allocator.free(kernel32_path);
363 if (try fileExists(loop.allocator, kernel32_path)) {
363 if (try fileExists(kernel32_path)) {
364364 self.kernel32_lib_dir = result_buf.toOwnedSlice();
365365 return;
366366 }
......@@ -449,12 +449,11 @@ fn fillSearch(search_buf: *[2]Search, sdk: *c.ZigWindowsSDK) []Search {
449449 return search_buf[0..search_end];
450450}
451451
452fn fileExists(allocator: *std.mem.Allocator, path: []const u8) !bool {
453 if (std.os.File.access(allocator, path)) |_| {
452fn fileExists(path: []const u8) !bool {
453 if (std.os.File.access(path)) |_| {
454454 return true;
455455 } else |err| switch (err) {
456 error.NotFound, error.PermissionDenied => return false,
457 error.OutOfMemory => return error.OutOfMemory,
456 error.FileNotFound, error.PermissionDenied => return false,
458457 else => return error.FileSystem,
459458 }
460459}
src-self-hosted/test.zig+2-2
......@@ -94,7 +94,7 @@ pub const TestContext = struct {
9494 }
9595
9696 // TODO async I/O
97 try std.io.writeFile(allocator, file1_path, source);
97 try std.io.writeFile(file1_path, source);
9898
9999 var comp = try Compilation.create(
100100 &self.zig_compiler,
......@@ -128,7 +128,7 @@ pub const TestContext = struct {
128128 }
129129
130130 // TODO async I/O
131 try std.io.writeFile(allocator, file1_path, source);
131 try std.io.writeFile(file1_path, source);
132132
133133 var comp = try Compilation.create(
134134 &self.zig_compiler,
std/build.zig+15-9
......@@ -267,7 +267,7 @@ pub const Builder = struct {
267267 if (self.verbose) {
268268 warn("rm {}\n", installed_file);
269269 }
270 _ = os.deleteFile(self.allocator, installed_file);
270 _ = os.deleteFile(installed_file);
271271 }
272272
273273 // TODO remove empty directories
......@@ -1182,7 +1182,7 @@ pub const LibExeObjStep = struct {
11821182
11831183 if (self.build_options_contents.len() > 0) {
11841184 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());
11861186 try zig_args.append("--pkg-begin");
11871187 try zig_args.append("build_options");
11881188 try zig_args.append(builder.pathFromRoot(build_options_file));
......@@ -1491,11 +1491,14 @@ pub const LibExeObjStep = struct {
14911491 }
14921492
14931493 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 ));
14951498 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");
14991502 }
15001503
15011504 for (self.full_path_libs.toSliceConst()) |full_path_lib| {
......@@ -1566,11 +1569,14 @@ pub const LibExeObjStep = struct {
15661569 cc_args.append("-o") catch unreachable;
15671570 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 ));
15701576 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
15751581 {
15761582 var it = self.link_libs.iterator();
......@@ -1917,7 +1923,7 @@ pub const WriteFileStep = struct {
19171923 warn("unable to make path {}: {}\n", full_path_dir, @errorName(err));
19181924 return err;
19191925 };
1920 io.writeFile(self.builder.allocator, full_path, self.data) catch |err| {
1926 io.writeFile(full_path, self.data) catch |err| {
19211927 warn("unable to write {}: {}\n", full_path, @errorName(err));
19221928 return err;
19231929 };
std/cstr.zig+6-5
......@@ -9,10 +9,9 @@ pub const line_sep = switch (builtin.os) {
99 else => "\n",
1010};
1111
12/// Deprecated, use mem.len
1213pub fn len(ptr: [*]const u8) usize {
13 var count: usize = 0;
14 while (ptr[count] != 0) : (count += 1) {}
15 return count;
14 return mem.len(u8, ptr);
1615}
1716
1817pub fn cmp(a: [*]const u8, b: [*]const u8) i8 {
......@@ -27,12 +26,14 @@ pub fn cmp(a: [*]const u8, b: [*]const u8) i8 {
2726 }
2827}
2928
29/// Deprecated, use mem.toSliceConst
3030pub fn toSliceConst(str: [*]const u8) []const u8 {
31 return str[0..len(str)];
31 return mem.toSliceConst(u8, str);
3232}
3333
34/// Deprecated, use mem.toSlice
3435pub fn toSlice(str: [*]u8) []u8 {
35 return str[0..len(str)];
36 return mem.toSlice(u8, str);
3637}
3738
3839test "cstr fns" {
std/debug/index.zig+3-3
......@@ -255,7 +255,7 @@ pub fn printSourceAtAddress(debug_info: *ElfStackTrace, out_stream: var, address
255255 address,
256256 compile_unit_name,
257257 );
258 if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) {
258 if (printLineFromFile(out_stream, line_info)) {
259259 if (line_info.column == 0) {
260260 try out_stream.write("\n");
261261 } else {
......@@ -340,8 +340,8 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) !*ElfStackTrace {
340340 }
341341}
342342
343fn printLineFromFile(allocator: *mem.Allocator, out_stream: var, line_info: *const LineInfo) !void {
344 var f = try os.File.openRead(allocator, line_info.file_name);
343fn printLineFromFile(out_stream: var, line_info: *const LineInfo) !void {
344 var f = try os.File.openRead(line_info.file_name);
345345 defer f.close();
346346 // 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
7878 builtin.Os.macosx,
7979 builtin.Os.linux,
8080 => return await (async pwritevPosix(loop, fd, data, offset) catch unreachable),
81 builtin.Os.windows,
82 => return await (async pwritevWindows(loop, fd, data, offset) catch unreachable),
81 builtin.Os.windows => return await (async pwritevWindows(loop, fd, data, offset) catch unreachable),
8382 else => @compileError("Unsupported OS"),
8483 }
8584}
......@@ -147,7 +146,6 @@ pub async fn pwriteWindows(loop: *Loop, fd: os.FileHandle, data: []const u8, off
147146 }
148147}
149148
150
151149/// data - just the inner references - must live until pwritev promise completes.
152150pub async fn pwritevPosix(loop: *Loop, fd: os.FileHandle, data: []const []const u8, offset: usize) !void {
153151 // 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:
203201 builtin.Os.macosx,
204202 builtin.Os.linux,
205203 => return await (async preadvPosix(loop, fd, data, offset) catch unreachable),
206 builtin.Os.windows,
207 => return await (async preadvWindows(loop, fd, data, offset) catch unreachable),
204 builtin.Os.windows => return await (async preadvWindows(loop, fd, data, offset) catch unreachable),
208205 else => @compileError("Unsupported OS"),
209206 }
210207}
......@@ -222,7 +219,7 @@ pub async fn preadvWindows(loop: *Loop, fd: os.FileHandle, data: []const []u8, o
222219 var inner_off: usize = 0;
223220 while (true) {
224221 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);
226223 off += amt_read;
227224 inner_off += amt_read;
228225 if (inner_off == v.len) {
......@@ -340,8 +337,7 @@ pub async fn openPosix(
340337 resume @handle();
341338 }
342339
343 const path_with_null = try std.cstr.addNullByte(loop.allocator, path);
344 defer loop.allocator.free(path_with_null);
340 const path_c = try std.os.toPosixPath(path);
345341
346342 var req_node = RequestNode{
347343 .prev = null,
......@@ -349,7 +345,7 @@ pub async fn openPosix(
349345 .data = Request{
350346 .msg = Request.Msg{
351347 .Open = Request.Msg.Open{
352 .path = path_with_null[0..path.len],
348 .path = path_c[0..path.len],
353349 .flags = flags,
354350 .mode = mode,
355351 .result = undefined,
......@@ -382,7 +378,6 @@ pub async fn openRead(loop: *Loop, path: []const u8) os.File.OpenError!os.FileHa
382378 },
383379
384380 builtin.Os.windows => return os.windowsOpen(
385 loop.allocator,
386381 path,
387382 windows.GENERIC_READ,
388383 windows.FILE_SHARE_READ,
......@@ -409,9 +404,7 @@ pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: os.File.Mode) os
409404 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
410405 return await (async openPosix(loop, path, flags, os.File.default_mode) catch unreachable);
411406 },
412 builtin.Os.windows,
413 => return os.windowsOpen(
414 loop.allocator,
407 builtin.Os.windows => return os.windowsOpen(
415408 path,
416409 windows.GENERIC_WRITE,
417410 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
......@@ -435,9 +428,8 @@ pub async fn openReadWrite(
435428 },
436429
437430 builtin.Os.windows => return os.windowsOpen(
438 loop.allocator,
439431 path,
440 windows.GENERIC_WRITE|windows.GENERIC_READ,
432 windows.GENERIC_WRITE | windows.GENERIC_READ,
441433 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
442434 windows.OPEN_ALWAYS,
443435 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
......@@ -513,8 +505,7 @@ pub const CloseOperation = struct {
513505 self.loop.allocator.destroy(self);
514506 }
515507 },
516 builtin.Os.windows,
517 => {
508 builtin.Os.windows => {
518509 if (self.os_data.handle) |handle| {
519510 os.close(handle);
520511 }
......@@ -532,8 +523,7 @@ pub const CloseOperation = struct {
532523 self.os_data.close_req_node.data.msg.Close.fd = handle;
533524 self.os_data.have_fd = true;
534525 },
535 builtin.Os.windows,
536 => {
526 builtin.Os.windows => {
537527 self.os_data.handle = handle;
538528 },
539529 else => @compileError("Unsupported OS"),
......@@ -548,8 +538,7 @@ pub const CloseOperation = struct {
548538 => {
549539 self.os_data.have_fd = false;
550540 },
551 builtin.Os.windows,
552 => {
541 builtin.Os.windows => {
553542 self.os_data.handle = null;
554543 },
555544 else => @compileError("Unsupported OS"),
......@@ -564,8 +553,7 @@ pub const CloseOperation = struct {
564553 assert(self.os_data.have_fd);
565554 return self.os_data.close_req_node.data.msg.Close.fd;
566555 },
567 builtin.Os.windows,
568 => {
556 builtin.Os.windows => {
569557 return self.os_data.handle.?;
570558 },
571559 else => @compileError("Unsupported OS"),
......@@ -585,15 +573,13 @@ pub async fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8,
585573 builtin.Os.linux,
586574 builtin.Os.macosx,
587575 => return await (async writeFileModeThread(loop, path, contents, mode) catch unreachable),
588 builtin.Os.windows,
589 => return await (async writeFileWindows(loop, path, contents) catch unreachable),
576 builtin.Os.windows => return await (async writeFileWindows(loop, path, contents) catch unreachable),
590577 else => @compileError("Unsupported OS"),
591578 }
592579}
593580
594581async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void {
595582 const handle = try os.windowsOpen(
596 loop.allocator,
597583 path,
598584 windows.GENERIC_WRITE,
599585 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
......@@ -1004,7 +990,7 @@ pub fn Watch(comptime V: type) type {
1004990 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.channel.loop.allocator, basename);
1005991 var basename_utf16le_null_consumed = false;
1006992 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
1009995 const dir_handle = windows.CreateFileW(
1010996 dirname_utf16le.ptr,
......@@ -1018,9 +1004,8 @@ pub fn Watch(comptime V: type) type {
10181004 if (dir_handle == windows.INVALID_HANDLE_VALUE) {
10191005 const err = windows.GetLastError();
10201006 switch (err) {
1021 windows.ERROR.FILE_NOT_FOUND,
1022 windows.ERROR.PATH_NOT_FOUND,
1023 => return error.PathNotFound,
1007 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
1008 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
10241009 else => return os.unexpectedErrorWindows(err),
10251010 }
10261011 }
......@@ -1106,7 +1091,10 @@ pub fn Watch(comptime V: type) type {
11061091
11071092 // TODO handle this error not in the channel but in the setup
11081093 _ = 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,
11101098 ) catch |err| {
11111099 await (async self.channel.put(err) catch unreachable);
11121100 return;
......@@ -1126,10 +1114,10 @@ pub fn Watch(comptime V: type) type {
11261114 &event_buf,
11271115 @intCast(windows.DWORD, event_buf.len),
11281116 windows.FALSE, // watch subtree
1129 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1130 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1131 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1132 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
1117 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1118 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1119 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1120 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
11331121 null, // number of bytes transferred (unused for async)
11341122 &overlapped,
11351123 null, // completion routine - unused because we use IOCP
......@@ -1156,7 +1144,7 @@ pub fn Watch(comptime V: type) type {
11561144 else => null,
11571145 };
11581146 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];
11601148 const user_value = blk: {
11611149 const held = await (async dir.table_lock.acquire() catch unreachable);
11621150 defer held.release();
std/io.zig+3-4
......@@ -254,9 +254,8 @@ pub fn OutStream(comptime WriteError: type) type {
254254 };
255255}
256256
257/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
258pub fn writeFile(allocator: *mem.Allocator, path: []const u8, data: []const u8) !void {
259 var file = try File.openWrite(allocator, path);
257pub fn writeFile(path: []const u8, data: []const u8) !void {
258 var file = try File.openWrite(path);
260259 defer file.close();
261260 try file.write(data);
262261}
......@@ -268,7 +267,7 @@ pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 {
268267
269268/// On success, caller owns returned buffer.
270269pub 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);
272271 defer file.close();
273272
274273 const size = try file.getEndPos();
std/io_test.zig+5-5
......@@ -16,7 +16,7 @@ test "write a file, read it, then delete it" {
1616 prng.random.bytes(data[0..]);
1717 const tmp_file_name = "temp_test_file.txt";
1818 {
19 var file = try os.File.openWrite(allocator, tmp_file_name);
19 var file = try os.File.openWrite(tmp_file_name);
2020 defer file.close();
2121
2222 var file_out_stream = io.FileOutStream.init(&file);
......@@ -28,7 +28,7 @@ test "write a file, read it, then delete it" {
2828 try buf_stream.flush();
2929 }
3030 {
31 var file = try os.File.openRead(allocator, tmp_file_name);
31 var file = try os.File.openRead(tmp_file_name);
3232 defer file.close();
3333
3434 const file_size = try file.getEndPos();
......@@ -45,7 +45,7 @@ test "write a file, read it, then delete it" {
4545 assert(mem.eql(u8, contents["begin".len .. contents.len - "end".len], data));
4646 assert(mem.eql(u8, contents[contents.len - "end".len ..], "end"));
4747 }
48 try os.deleteFile(allocator, tmp_file_name);
48 try os.deleteFile(tmp_file_name);
4949}
5050
5151test "BufferOutStream" {
......@@ -63,7 +63,7 @@ test "BufferOutStream" {
6363}
6464
6565test "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 };
6767 var ss = io.SliceInStream.init(bytes);
6868
6969 var dest: [4]u8 = undefined;
......@@ -81,7 +81,7 @@ test "SliceInStream" {
8181}
8282
8383test "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 };
8585 var ss = io.SliceInStream.init(bytes);
8686 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 {
179179 // NOTE: We do not use a volatile slice cast here since LLVM cannot
180180 // see that it can be replaced by a memset.
181181 const ptr = @ptrCast([*]volatile u8, s.ptr);
182 const len = s.len * @sizeOf(T);
183 @memset(ptr, 0, len);
182 const length = s.len * @sizeOf(T);
183 @memset(ptr, 0, length);
184184}
185185
186186test "mem.secureZero" {
......@@ -252,6 +252,20 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
252252 return true;
253253}
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
255269/// Returns true if all elements in a slice are equal to the scalar value provided
256270pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool {
257271 for (slice) |item| {
......@@ -809,3 +823,4 @@ pub fn endianSwap(comptime T: type, x: T) T {
809823test "std.mem.endianSwap" {
810824 assert(endianSwap(u32, 0xDEADBEEF) == 0xEFBEADDE);
811825}
826
std/os/child_process.zig+2-12
......@@ -349,14 +349,7 @@ pub const ChildProcess = struct {
349349 };
350350
351351 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: {
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 };
352 const dev_null_fd = if (any_ignore) try os.posixOpenC(c"/dev/null", posix.O_RDWR, 0) else undefined;
360353 defer {
361354 if (any_ignore) os.close(dev_null_fd);
362355 }
......@@ -453,10 +446,7 @@ pub const ChildProcess = struct {
453446 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
454447
455448 const nul_handle = if (any_ignore) blk: {
456 const nul_file_path = "NUL";
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);
449 break :blk try os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
460450 } else blk: {
461451 break :blk undefined;
462452 };
std/os/file.zig+88-53
......@@ -7,6 +7,7 @@ const assert = std.debug.assert;
77const posix = os.posix;
88const windows = os.windows;
99const Os = builtin.Os;
10const windows_util = @import("windows/util.zig");
1011
1112const is_posix = builtin.os != builtin.Os.windows;
1213const is_windows = builtin.os == builtin.Os.windows;
......@@ -27,16 +28,27 @@ pub const File = struct {
2728
2829 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 /// Call close to clean up.
32 pub fn openRead(allocator: *mem.Allocator, path: []const u8) OpenError!File {
31 /// `openRead` except with a null terminated path
32 pub fn openReadC(path: [*]const u8) OpenError!File {
3333 if (is_posix) {
3434 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);
3636 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) {
3851 const handle = try os.windowsOpen(
39 allocator,
4052 path,
4153 windows.GENERIC_READ,
4254 windows.FILE_SHARE_READ,
......@@ -44,28 +56,25 @@ pub const File = struct {
4456 windows.FILE_ATTRIBUTE_NORMAL,
4557 );
4658 return openHandle(handle);
47 } else {
48 @compileError("TODO implement openRead for this OS");
4959 }
60 @compileError("Unsupported OS");
5061 }
5162
5263 /// Calls `openWriteMode` with os.File.default_mode for the mode.
53 pub fn openWrite(allocator: *mem.Allocator, path: []const u8) OpenError!File {
54 return openWriteMode(allocator, path, os.File.default_mode);
64 pub fn openWrite(path: []const u8) OpenError!File {
65 return openWriteMode(path, os.File.default_mode);
5566 }
5667
5768 /// If the path does not exist it will be created.
5869 /// 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.
6070 /// 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 {
6272 if (is_posix) {
6373 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);
6575 return openHandle(fd);
6676 } else if (is_windows) {
6777 const handle = try os.windowsOpen(
68 allocator,
6978 path,
7079 windows.GENERIC_WRITE,
7180 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
......@@ -80,16 +89,14 @@ pub const File = struct {
8089
8190 /// If the path does not exist it will be created.
8291 /// 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.
8492 /// 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 {
8694 if (is_posix) {
8795 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);
8997 return openHandle(fd);
9098 } else if (is_windows) {
9199 const handle = try os.windowsOpen(
92 allocator,
93100 path,
94101 windows.GENERIC_WRITE,
95102 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
......@@ -108,23 +115,43 @@ pub const File = struct {
108115
109116 pub const AccessError = error{
110117 PermissionDenied,
111 NotFound,
118 FileNotFound,
112119 NameTooLong,
113 BadMode,
114 BadPathName,
115 Io,
120 InputOutput,
116121 SystemResources,
117 OutOfMemory,
122 BadPathName,
123
124 /// On Windows, file paths must be valid Unicode.
125 InvalidUtf8,
118126
119127 Unexpected,
120128 };
121129
122 pub fn access(allocator: *mem.Allocator, path: []const u8) AccessError!void {
123 const path_with_null = try std.cstr.addNullByte(allocator, path);
124 defer allocator.free(path_with_null);
130 /// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
131 /// Otherwise use `access` or `accessC`.
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 }
126153 if (is_posix) {
127 const result = posix.access(path_with_null.ptr, posix.F_OK);
154 const result = posix.access(path, posix.F_OK);
128155 const err = posix.getErrno(result);
129156 switch (err) {
130157 0 => return,
......@@ -132,32 +159,33 @@ pub const File = struct {
132159 posix.EROFS => return error.PermissionDenied,
133160 posix.ELOOP => return error.PermissionDenied,
134161 posix.ETXTBSY => return error.PermissionDenied,
135 posix.ENOTDIR => return error.NotFound,
136 posix.ENOENT => return error.NotFound,
162 posix.ENOTDIR => return error.FileNotFound,
163 posix.ENOENT => return error.FileNotFound,
137164
138165 posix.ENAMETOOLONG => return error.NameTooLong,
139166 posix.EINVAL => unreachable,
140 posix.EFAULT => return error.BadPathName,
141 posix.EIO => return error.Io,
167 posix.EFAULT => unreachable,
168 posix.EIO => return error.InputOutput,
142169 posix.ENOMEM => return error.SystemResources,
143170 else => return os.unexpectedErrorPosix(err),
144171 }
145 } else if (is_windows) {
146 if (os.windows.GetFileAttributesA(path_with_null.ptr) != os.windows.INVALID_FILE_ATTRIBUTES) {
147 return;
148 }
172 }
173 @compileError("Unsupported OS");
174 }
149175
150 const err = windows.GetLastError();
151 switch (err) {
152 windows.ERROR.FILE_NOT_FOUND,
153 windows.ERROR.PATH_NOT_FOUND,
154 => return error.NotFound,
155 windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
156 else => return os.unexpectedErrorWindows(err),
157 }
158 } else {
159 @compileError("TODO implement access for this OS");
176 pub fn access(path: []const u8) AccessError!void {
177 if (is_windows) {
178 const path_w = try windows_util.sliceToPrefixedFileW(path);
179 return accessW(&path_w);
180 }
181 if (is_posix) {
182 var path_with_null: [posix.PATH_MAX]u8 = undefined;
183 if (path.len >= posix.PATH_MAX) return error.NameTooLong;
184 mem.copy(u8, path_with_null[0..], path);
185 path_with_null[path.len] = 0;
186 return accessC(&path_with_null);
160187 }
188 @compileError("Unsupported OS");
161189 }
162190
163191 /// Upon success, the stream is in an uninitialized state. To continue using it,
......@@ -179,7 +207,9 @@ pub const File = struct {
179207 const err = posix.getErrno(result);
180208 if (err > 0) {
181209 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,
183213 posix.EINVAL => error.Unseekable,
184214 posix.EOVERFLOW => error.Unseekable,
185215 posix.ESPIPE => error.Unseekable,
......@@ -192,7 +222,7 @@ pub const File = struct {
192222 if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) {
193223 const err = windows.GetLastError();
194224 return switch (err) {
195 windows.ERROR.INVALID_PARAMETER => error.BadFd,
225 windows.ERROR.INVALID_PARAMETER => unreachable,
196226 else => os.unexpectedErrorWindows(err),
197227 };
198228 }
......@@ -209,7 +239,9 @@ pub const File = struct {
209239 const err = posix.getErrno(result);
210240 if (err > 0) {
211241 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,
213245 posix.EINVAL => error.Unseekable,
214246 posix.EOVERFLOW => error.Unseekable,
215247 posix.ESPIPE => error.Unseekable,
......@@ -223,7 +255,7 @@ pub const File = struct {
223255 if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) {
224256 const err = windows.GetLastError();
225257 return switch (err) {
226 windows.ERROR.INVALID_PARAMETER => error.BadFd,
258 windows.ERROR.INVALID_PARAMETER => unreachable,
227259 else => os.unexpectedErrorWindows(err),
228260 };
229261 }
......@@ -239,7 +271,9 @@ pub const File = struct {
239271 const err = posix.getErrno(result);
240272 if (err > 0) {
241273 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,
243277 posix.EINVAL => error.Unseekable,
244278 posix.EOVERFLOW => error.Unseekable,
245279 posix.ESPIPE => error.Unseekable,
......@@ -254,7 +288,7 @@ pub const File = struct {
254288 if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) {
255289 const err = windows.GetLastError();
256290 return switch (err) {
257 windows.ERROR.INVALID_PARAMETER => error.BadFd,
291 windows.ERROR.INVALID_PARAMETER => unreachable,
258292 else => os.unexpectedErrorWindows(err),
259293 };
260294 }
......@@ -287,7 +321,6 @@ pub const File = struct {
287321 }
288322
289323 pub const ModeError = error{
290 BadFd,
291324 SystemResources,
292325 Unexpected,
293326 };
......@@ -298,7 +331,9 @@ pub const File = struct {
298331 const err = posix.getErrno(posix.fstat(self.handle, &stat));
299332 if (err > 0) {
300333 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,
302337 posix.ENOMEM => error.SystemResources,
303338 else => os.unexpectedErrorPosix(err),
304339 };
std/os/get_app_data_dir.zig+2-1
......@@ -10,6 +10,7 @@ pub const GetAppDataDirError = error{
1010};
1111
1212/// Caller owns returned memory.
13/// TODO determine if we can remove the allocator requirement
1314pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 {
1415 switch (builtin.os) {
1516 builtin.Os.windows => {
......@@ -22,7 +23,7 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD
2223 )) {
2324 os.windows.S_OK => {
2425 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) {
2627 error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
2728 error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
2829 error.DanglingSurrogateHalf => return error.AppDataDirUnavailable,
std/os/index.zig+294-243
......@@ -39,6 +39,15 @@ pub const File = @import("file.zig").File;
3939pub const time = @import("time.zig");
4040
4141pub 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
4352pub const UserInfo = @import("get_user_id.zig").UserInfo;
4453pub const getUserInfo = @import("get_user_id.zig").getUserInfo;
......@@ -317,6 +326,8 @@ pub const PosixWriteError = error{
317326 NoSpaceLeft,
318327 AccessDenied,
319328 BrokenPipe,
329
330 /// See https://github.com/ziglang/zig/issues/1396
320331 Unexpected,
321332};
322333
......@@ -417,7 +428,6 @@ pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, off
417428}
418429
419430pub const PosixOpenError = error{
420 OutOfMemory,
421431 AccessDenied,
422432 FileTooBig,
423433 IsDir,
......@@ -426,22 +436,22 @@ pub const PosixOpenError = error{
426436 NameTooLong,
427437 SystemFdQuotaExceeded,
428438 NoDevice,
429 PathNotFound,
439 FileNotFound,
430440 SystemResources,
431441 NoSpaceLeft,
432442 NotDir,
433443 PathAlreadyExists,
444
445 /// See https://github.com/ziglang/zig/issues/1396
434446 Unexpected,
435447};
436448
437449/// ::file_path needs to be copied in memory to add a null terminating byte.
438450/// Calls POSIX open, keeps trying if it gets interrupted, and translates
439451/// the return value into zig errors.
440pub fn posixOpen(allocator: *Allocator, file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {
441 const path_with_null = try cstr.addNullByte(allocator, file_path);
442 defer allocator.free(path_with_null);
443
444 return posixOpenC(path_with_null.ptr, flags, perm);
452pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {
453 const file_path_c = try toPosixPath(file_path);
454 return posixOpenC(&file_path_c, flags, perm);
445455}
446456
447457// TODO https://github.com/ziglang/zig/issues/265
......@@ -463,7 +473,7 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {
463473 posix.ENAMETOOLONG => return PosixOpenError.NameTooLong,
464474 posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded,
465475 posix.ENODEV => return PosixOpenError.NoDevice,
466 posix.ENOENT => return PosixOpenError.PathNotFound,
476 posix.ENOENT => return PosixOpenError.FileNotFound,
467477 posix.ENOMEM => return PosixOpenError.SystemResources,
468478 posix.ENOSPC => return PosixOpenError.NoSpaceLeft,
469479 posix.ENOTDIR => return PosixOpenError.NotDir,
......@@ -476,6 +486,16 @@ pub fn posixOpenC(file_path: [*]const u8, flags: u32, perm: usize) !i32 {
476486 }
477487}
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
479499pub fn posixDup2(old_fd: i32, new_fd: i32) !void {
480500 while (true) {
481501 const err = posix.getErrno(posix.dup2(old_fd, new_fd));
......@@ -591,6 +611,8 @@ pub const PosixExecveError = error{
591611 FileNotFound,
592612 NotDir,
593613 FileBusy,
614
615 /// See https://github.com/ziglang/zig/issues/1396
594616 Unexpected,
595617};
596618
......@@ -719,43 +741,39 @@ pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwned
719741}
720742
721743/// Caller must free the returned memory.
722pub fn getCwd(allocator: *Allocator) ![]u8 {
723 switch (builtin.os) {
724 Os.windows => {
725 var buf = try allocator.alloc(u8, 256);
726 errdefer allocator.free(buf);
727
728 while (true) {
729 const result = windows.GetCurrentDirectoryA(@intCast(windows.WORD, buf.len), buf.ptr);
744pub fn getCwdAlloc(allocator: *Allocator) ![]u8 {
745 var buf: [MAX_PATH_BYTES]u8 = undefined;
746 return mem.dupe(allocator, u8, try getCwd(&buf));
747}
730748
731 if (result == 0) {
732 const err = windows.GetLastError();
733 return switch (err) {
734 else => unexpectedErrorWindows(err),
735 };
736 }
749pub const GetCwdError = error{Unexpected};
737750
738 if (result > buf.len) {
739 buf = try allocator.realloc(u8, buf, result);
740 continue;
751/// The result is a slice of out_buffer.
752pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) GetCwdError![]u8 {
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),
741763 }
742
743 return allocator.shrink(u8, buf, result);
744764 }
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];
745770 },
746771 else => {
747 var buf = try allocator.alloc(u8, 1024);
748 errdefer allocator.free(buf);
749 while (true) {
750 const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len));
751 if (err == posix.ERANGE) {
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));
772 const err = posix.getErrno(posix.getcwd(out_buffer, out_buffer.len));
773 switch (err) {
774 0 => return cstr.toSlice(out_buffer),
775 posix.ERANGE => unreachable,
776 else => return unexpectedErrorPosix(err),
759777 }
760778 },
761779 }
......@@ -763,7 +781,9 @@ pub fn getCwd(allocator: *Allocator) ![]u8 {
763781
764782test "os.getCwd" {
765783 // 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);
767787}
768788
769789pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError;
......@@ -778,6 +798,8 @@ pub fn symLink(allocator: *Allocator, existing_path: []const u8, new_path: []con
778798
779799pub const WindowsSymLinkError = error{
780800 OutOfMemory,
801
802 /// See https://github.com/ziglang/zig/issues/1396
781803 Unexpected,
782804};
783805
......@@ -808,6 +830,8 @@ pub const PosixSymLinkError = error{
808830 NoSpaceLeft,
809831 ReadOnlyFileSystem,
810832 NotDir,
833
834 /// See https://github.com/ziglang/zig/issues/1396
811835 Unexpected,
812836};
813837
......@@ -866,7 +890,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
866890 b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
867891
868892 if (symLink(allocator, existing_path, tmp_path)) {
869 return rename(allocator, tmp_path, new_path);
893 return rename(tmp_path, new_path);
870894 } else |err| switch (err) {
871895 error.PathAlreadyExists => continue,
872896 else => return err, // TODO zig should know this set does not include PathAlreadyExists
......@@ -885,70 +909,75 @@ pub const DeleteFileError = error{
885909 NotDir,
886910 SystemResources,
887911 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
890921 Unexpected,
891922};
892923
893pub fn deleteFile(allocator: *Allocator, file_path: []const u8) DeleteFileError!void {
924pub fn deleteFile(file_path: []const u8) DeleteFileError!void {
894925 if (builtin.os == Os.windows) {
895 return deleteFileWindows(allocator, file_path);
926 return deleteFileWindows(file_path);
896927 } else {
897 return deleteFilePosix(allocator, file_path);
928 return deleteFilePosix(file_path);
898929 }
899930}
900931
901pub fn deleteFileWindows(allocator: *Allocator, file_path: []const u8) !void {
902 const buf = try allocator.alloc(u8, file_path.len + 1);
903 defer allocator.free(buf);
932pub fn deleteFileWindows(file_path: []const u8) !void {
933 const file_path_w = try windows_util.sliceToPrefixedFileW(file_path);
904934
905 mem.copy(u8, buf, file_path);
906 buf[file_path.len] = 0;
907
908 if (windows.DeleteFileA(buf.ptr) == 0) {
935 if (windows.DeleteFileW(&file_path_w) == 0) {
909936 const err = windows.GetLastError();
910 return switch (err) {
911 windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
912 windows.ERROR.ACCESS_DENIED => error.AccessDenied,
913 windows.ERROR.FILENAME_EXCED_RANGE, windows.ERROR.INVALID_PARAMETER => error.NameTooLong,
914 else => unexpectedErrorWindows(err),
915 };
937 switch (err) {
938 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
939 windows.ERROR.ACCESS_DENIED => return error.AccessDenied,
940 windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
941 windows.ERROR.INVALID_PARAMETER => return error.NameTooLong,
942 else => return unexpectedErrorWindows(err),
943 }
916944 }
917945}
918946
919pub fn deleteFilePosix(allocator: *Allocator, file_path: []const u8) !void {
920 const buf = try allocator.alloc(u8, file_path.len + 1);
921 defer allocator.free(buf);
922
923 mem.copy(u8, buf, file_path);
924 buf[file_path.len] = 0;
925
926 const err = posix.getErrno(posix.unlink(buf.ptr));
927 if (err > 0) {
928 return switch (err) {
929 posix.EACCES, posix.EPERM => error.AccessDenied,
930 posix.EBUSY => error.FileBusy,
931 posix.EFAULT, posix.EINVAL => unreachable,
932 posix.EIO => error.FileSystem,
933 posix.EISDIR => error.IsDir,
934 posix.ELOOP => error.SymLinkLoop,
935 posix.ENAMETOOLONG => error.NameTooLong,
936 posix.ENOENT => error.FileNotFound,
937 posix.ENOTDIR => error.NotDir,
938 posix.ENOMEM => error.SystemResources,
939 posix.EROFS => error.ReadOnlyFileSystem,
940 else => unexpectedErrorPosix(err),
941 };
947pub fn deleteFilePosixC(file_path: [*]const u8) !void {
948 const err = posix.getErrno(posix.unlink(file_path));
949 switch (err) {
950 0 => return,
951 posix.EACCES => return error.AccessDenied,
952 posix.EPERM => return error.AccessDenied,
953 posix.EBUSY => return error.FileBusy,
954 posix.EFAULT => unreachable,
955 posix.EINVAL => unreachable,
956 posix.EIO => return error.FileSystem,
957 posix.EISDIR => return error.IsDir,
958 posix.ELOOP => return error.SymLinkLoop,
959 posix.ENAMETOOLONG => return error.NameTooLong,
960 posix.ENOENT => return error.FileNotFound,
961 posix.ENOTDIR => return error.NotDir,
962 posix.ENOMEM => return error.SystemResources,
963 posix.EROFS => return error.ReadOnlyFileSystem,
964 else => return unexpectedErrorPosix(err),
942965 }
943966}
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
945973/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
946974/// merged and readily available,
947975/// there is a possibility of power loss or application termination leaving temporary files present
948976/// in the same directory as dest_path.
949977/// Destination file will have the same mode as the source file.
978/// TODO investigate if this can work with no allocator
950979pub 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);
952981 defer in_file.close();
953982
954983 const mode = try in_file.mode();
......@@ -969,8 +998,9 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con
969998/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
970999/// merged and readily available,
9711000/// there is a possibility of power loss or application termination leaving temporary files present
1001/// TODO investigate if this can work with no allocator
9721002pub 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);
9741004 defer in_file.close();
9751005
9761006 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: [
9871017}
9881018
9891019pub const AtomicFile = struct {
1020 /// TODO investigate if we can make this work with no allocator
9901021 allocator: *Allocator,
9911022 file: os.File,
9921023 tmp_path: []u8,
......@@ -1014,7 +1045,7 @@ pub const AtomicFile = struct {
10141045 try getRandomBytes(rand_buf[0..]);
10151046 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) {
10181049 error.PathAlreadyExists => continue,
10191050 // TODO zig should figure out that this error set does not include PathAlreadyExists since
10201051 // it is handled in the above switch
......@@ -1035,7 +1066,7 @@ pub const AtomicFile = struct {
10351066 pub fn deinit(self: *AtomicFile) void {
10361067 if (!self.finished) {
10371068 self.file.close();
1038 deleteFile(self.allocator, self.tmp_path) catch {};
1069 deleteFile(self.tmp_path) catch {};
10391070 self.allocator.free(self.tmp_path);
10401071 self.finished = true;
10411072 }
......@@ -1044,70 +1075,72 @@ pub const AtomicFile = struct {
10441075 pub fn finish(self: *AtomicFile) !void {
10451076 assert(!self.finished);
10461077 self.file.close();
1047 try rename(self.allocator, self.tmp_path, self.dest_path);
1078 try rename(self.tmp_path, self.dest_path);
10481079 self.allocator.free(self.tmp_path);
10491080 self.finished = true;
10501081 }
10511082};
10521083
1053pub fn rename(allocator: *Allocator, old_path: []const u8, new_path: []const u8) !void {
1054 const full_buf = try allocator.alloc(u8, old_path.len + new_path.len + 2);
1055 defer allocator.free(full_buf);
1056
1057 const old_buf = full_buf;
1058 mem.copy(u8, old_buf, old_path);
1059 old_buf[old_path.len] = 0;
1060
1061 const new_buf = full_buf[old_path.len + 1 ..];
1062 mem.copy(u8, new_buf, new_path);
1063 new_buf[new_path.len] = 0;
1084pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void {
1085 if (is_windows) {
1086 @compileError("TODO implement for windows");
1087 } else {
1088 const err = posix.getErrno(posix.rename(old_path, new_path));
1089 switch (err) {
1090 0 => return,
1091 posix.EACCES => return error.AccessDenied,
1092 posix.EPERM => return error.AccessDenied,
1093 posix.EBUSY => return error.FileBusy,
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 {
10651115 if (is_windows) {
10661116 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) {
10681120 const err = windows.GetLastError();
1069 return switch (err) {
1070 else => unexpectedErrorWindows(err),
1071 };
1121 switch (err) {
1122 else => return unexpectedErrorWindows(err),
1123 }
10721124 }
10731125 } else {
1074 const err = posix.getErrno(posix.rename(old_buf.ptr, new_buf.ptr));
1075 if (err > 0) {
1076 return switch (err) {
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 }
1126 const old_path_c = try toPosixPath(old_path);
1127 const new_path_c = try toPosixPath(new_path);
1128 return renameC(&old_path_c, &new_path_c);
10951129 }
10961130}
10971131
1098pub fn makeDir(allocator: *Allocator, dir_path: []const u8) !void {
1132pub fn makeDir(dir_path: []const u8) !void {
10991133 if (is_windows) {
1100 return makeDirWindows(allocator, dir_path);
1134 return makeDirWindows(dir_path);
11011135 } else {
1102 return makeDirPosix(allocator, dir_path);
1136 return makeDirPosix(dir_path);
11031137 }
11041138}
11051139
1106pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {
1107 const path_buf = try cstr.addNullByte(allocator, dir_path);
1108 defer allocator.free(path_buf);
1140pub fn makeDirWindows(dir_path: []const u8) !void {
1141 const dir_path_w = try windows_util.sliceToPrefixedFileW(dir_path);
11091142
1110 if (windows.CreateDirectoryA(path_buf.ptr, null) == 0) {
1143 if (windows.CreateDirectoryW(&dir_path_w, null) == 0) {
11111144 const err = windows.GetLastError();
11121145 return switch (err) {
11131146 windows.ERROR.ALREADY_EXISTS => error.PathAlreadyExists,
......@@ -1117,39 +1150,42 @@ pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {
11171150 }
11181151}
11191152
1120pub fn makeDirPosix(allocator: *Allocator, dir_path: []const u8) !void {
1121 const path_buf = try cstr.addNullByte(allocator, dir_path);
1122 defer allocator.free(path_buf);
1123
1124 const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755));
1125 if (err > 0) {
1126 return switch (err) {
1127 posix.EACCES, posix.EPERM => error.AccessDenied,
1128 posix.EDQUOT => error.DiskQuota,
1129 posix.EEXIST => error.PathAlreadyExists,
1130 posix.EFAULT => unreachable,
1131 posix.ELOOP => error.SymLinkLoop,
1132 posix.EMLINK => error.LinkQuotaExceeded,
1133 posix.ENAMETOOLONG => error.NameTooLong,
1134 posix.ENOENT => error.FileNotFound,
1135 posix.ENOMEM => error.SystemResources,
1136 posix.ENOSPC => error.NoSpaceLeft,
1137 posix.ENOTDIR => error.NotDir,
1138 posix.EROFS => error.ReadOnlyFileSystem,
1139 else => unexpectedErrorPosix(err),
1140 };
1153pub fn makeDirPosixC(dir_path: [*]const u8) !void {
1154 const err = posix.getErrno(posix.mkdir(dir_path, 0o755));
1155 switch (err) {
1156 0 => return,
1157 posix.EACCES => return error.AccessDenied,
1158 posix.EPERM => return error.AccessDenied,
1159 posix.EDQUOT => return error.DiskQuota,
1160 posix.EEXIST => return error.PathAlreadyExists,
1161 posix.EFAULT => unreachable,
1162 posix.ELOOP => return error.SymLinkLoop,
1163 posix.EMLINK => return error.LinkQuotaExceeded,
1164 posix.ENAMETOOLONG => return error.NameTooLong,
1165 posix.ENOENT => return error.FileNotFound,
1166 posix.ENOMEM => return error.SystemResources,
1167 posix.ENOSPC => return error.NoSpaceLeft,
1168 posix.ENOTDIR => return error.NotDir,
1169 posix.EROFS => return error.ReadOnlyFileSystem,
1170 else => return unexpectedErrorPosix(err),
11411171 }
11421172}
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
11441179/// Calls makeDir recursively to make an entire path. Returns success if the path
11451180/// already exists and is a directory.
1181/// TODO determine if we can remove the allocator requirement from this function
11461182pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {
11471183 const resolved_path = try path.resolve(allocator, full_path);
11481184 defer allocator.free(resolved_path);
11491185
11501186 var end_index: usize = resolved_path.len;
11511187 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) {
11531189 error.PathAlreadyExists => {
11541190 // TODO stat the file and return an error if it's not a directory
11551191 // this is important because otherwise a dangling symlink
......@@ -1187,6 +1223,7 @@ pub const DeleteDirError = error{
11871223 ReadOnlyFileSystem,
11881224 OutOfMemory,
11891225
1226 /// See https://github.com/ziglang/zig/issues/1396
11901227 Unexpected,
11911228};
11921229
......@@ -1245,7 +1282,6 @@ const DeleteTreeError = error{
12451282 NameTooLong,
12461283 SystemFdQuotaExceeded,
12471284 NoDevice,
1248 PathNotFound,
12491285 SystemResources,
12501286 NoSpaceLeft,
12511287 PathAlreadyExists,
......@@ -1255,20 +1291,30 @@ const DeleteTreeError = error{
12551291 FileSystem,
12561292 FileBusy,
12571293 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
12581303 Unexpected,
12591304};
1305
1306/// TODO determine if we can remove the allocator requirement
12601307pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void {
12611308 start_over: while (true) {
12621309 var got_access_denied = false;
12631310 // 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)) {
12651312 return;
12661313 } else |err| switch (err) {
12671314 error.FileNotFound => return,
12681315 error.IsDir => {},
12691316 error.AccessDenied => got_access_denied = true,
12701317
1271 error.OutOfMemory,
12721318 error.SymLinkLoop,
12731319 error.NameTooLong,
12741320 error.SystemResources,
......@@ -1276,6 +1322,8 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
12761322 error.NotDir,
12771323 error.FileSystem,
12781324 error.FileBusy,
1325 error.InvalidUtf8,
1326 error.BadPathName,
12791327 error.Unexpected,
12801328 => return err,
12811329 }
......@@ -1297,7 +1345,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
12971345 error.NameTooLong,
12981346 error.SystemFdQuotaExceeded,
12991347 error.NoDevice,
1300 error.PathNotFound,
1348 error.FileNotFound,
13011349 error.SystemResources,
13021350 error.NoSpaceLeft,
13031351 error.PathAlreadyExists,
......@@ -1367,7 +1415,7 @@ pub const Dir = struct {
13671415 };
13681416
13691417 pub const OpenError = error{
1370 PathNotFound,
1418 FileNotFound,
13711419 NotDir,
13721420 AccessDenied,
13731421 FileTooBig,
......@@ -1382,9 +1430,11 @@ pub const Dir = struct {
13821430 PathAlreadyExists,
13831431 OutOfMemory,
13841432
1433 /// See https://github.com/ziglang/zig/issues/1396
13851434 Unexpected,
13861435 };
13871436
1437 /// TODO remove the allocator requirement from this API
13881438 pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {
13891439 return Dir{
13901440 .allocator = allocator,
......@@ -1400,7 +1450,6 @@ pub const Dir = struct {
14001450 },
14011451 Os.macosx, Os.ios => Handle{
14021452 .fd = try posixOpen(
1403 allocator,
14041453 dir_path,
14051454 posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,
14061455 0,
......@@ -1412,7 +1461,6 @@ pub const Dir = struct {
14121461 },
14131462 Os.linux => Handle{
14141463 .fd = try posixOpen(
1415 allocator,
14161464 dir_path,
14171465 posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC,
14181466 0,
......@@ -1609,39 +1657,32 @@ pub fn changeCurDir(allocator: *Allocator, dir_path: []const u8) !void {
16091657}
16101658
16111659/// Read value of a symbolic link.
1612pub fn readLink(allocator: *Allocator, pathname: []const u8) ![]u8 {
1613 const path_buf = try allocator.alloc(u8, pathname.len + 1);
1614 defer allocator.free(path_buf);
1615
1616 mem.copy(u8, path_buf, pathname);
1617 path_buf[pathname.len] = 0;
1618
1619 var result_buf = try allocator.alloc(u8, 1024);
1620 errdefer allocator.free(result_buf);
1621 while (true) {
1622 const ret_val = posix.readlink(path_buf.ptr, result_buf.ptr, result_buf.len);
1623 const err = posix.getErrno(ret_val);
1624 if (err > 0) {
1625 return switch (err) {
1626 posix.EACCES => error.AccessDenied,
1627 posix.EFAULT, posix.EINVAL => unreachable,
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);
1660/// The return value is a slice of out_buffer.
1661pub fn readLinkC(out_buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 {
1662 const rc = posix.readlink(pathname, out_buffer, out_buffer.len);
1663 const err = posix.getErrno(rc);
1664 switch (err) {
1665 0 => return out_buffer[0..rc],
1666 posix.EACCES => return error.AccessDenied,
1667 posix.EFAULT => unreachable,
1668 posix.EINVAL => unreachable,
1669 posix.EIO => return error.FileSystem,
1670 posix.ELOOP => return error.SymLinkLoop,
1671 posix.ENAMETOOLONG => unreachable, // out_buffer is at least PATH_MAX
1672 posix.ENOENT => return error.FileNotFound,
1673 posix.ENOMEM => return error.SystemResources,
1674 posix.ENOTDIR => return error.NotDir,
1675 else => return unexpectedErrorPosix(err),
16421676 }
16431677}
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
16451686pub fn posix_setuid(uid: u32) !void {
16461687 const err = posix.getErrno(posix.setuid(uid));
16471688 if (err == 0) return;
......@@ -1688,6 +1729,8 @@ pub fn posix_setregid(rgid: u32, egid: u32) !void {
16881729
16891730pub const WindowsGetStdHandleErrs = error{
16901731 NoStdHandles,
1732
1733 /// See https://github.com/ziglang/zig/issues/1396
16911734 Unexpected,
16921735};
16931736
......@@ -2015,7 +2058,7 @@ pub fn unexpectedErrorPosix(errno: usize) UnexpectedError {
20152058/// Call this when you made a windows DLL call or something that does SetLastError
20162059/// and you get an unexpected error.
20172060pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {
2018 if (unexpected_error_tracing) {
2061 if (true) {
20192062 debug.warn("unexpected GetLastError(): {}\n", err);
20202063 debug.dumpCurrentStackTrace(null);
20212064 }
......@@ -2024,17 +2067,12 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {
20242067
20252068pub fn openSelfExe() !os.File {
20262069 switch (builtin.os) {
2027 Os.linux => {
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 },
2070 Os.linux => return os.File.openReadC(c"/proc/self/exe"),
20332071 Os.macosx, Os.ios => {
2034 var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined;
2035 var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
2036 const self_exe_path = try selfExePath(&fixed_allocator.allocator);
2037 return os.File.openRead(&fixed_allocator.allocator, self_exe_path);
2072 var buf: [MAX_PATH_BYTES]u8 = undefined;
2073 const self_exe_path = try selfExePath(&buf);
2074 buf[self_exe_path.len] = 0;
2075 return os.File.openReadC(self_exe_path.ptr);
20382076 },
20392077 else => @compileError("Unsupported OS"),
20402078 }
......@@ -2043,7 +2081,7 @@ pub fn openSelfExe() !os.File {
20432081test "openSelfExe" {
20442082 switch (builtin.os) {
20452083 Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(),
2046 else => return, // Unsupported OS.
2084 else => return error.SkipZigTest, // Unsupported OS
20472085 }
20482086}
20492087
......@@ -2052,69 +2090,68 @@ test "openSelfExe" {
20522090/// If you only want an open file handle, use openSelfExe.
20532091/// This function may return an error if the current executable
20542092/// was deleted after spawning.
2055/// Caller owns returned memory.
2056pub fn selfExePath(allocator: *mem.Allocator) ![]u8 {
2093/// Returned value is a slice of out_buffer.
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 {
20572099 switch (builtin.os) {
2058 Os.linux => {
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 },
2100 Os.linux => return readLink(out_buffer, "/proc/self/exe"),
20632101 Os.windows => {
2064 var out_path = try Buffer.initSize(allocator, 0xff);
2065 errdefer out_path.deinit();
2066 while (true) {
2067 const dword_len = try math.cast(windows.DWORD, out_path.len());
2068 const copied_amt = windows.GetModuleFileNameA(null, out_path.ptr(), dword_len);
2069 if (copied_amt <= 0) {
2070 const err = windows.GetLastError();
2071 return switch (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();
2102 var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined;
2103 const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
2104 const rc = windows.GetModuleFileNameW(null, &utf16le_buf, casted_len);
2105 assert(rc <= utf16le_buf.len);
2106 if (rc == 0) {
2107 const err = windows.GetLastError();
2108 switch (err) {
2109 else => return unexpectedErrorWindows(err),
20782110 }
2079 const new_len = (out_path.len() << 1) | 0b1;
2080 try out_path.resize(new_len);
20812111 }
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];
20822116 },
20832117 Os.macosx, Os.ios => {
2084 var u32_len: u32 = 0;
2085 const ret1 = c._NSGetExecutablePath(undefined, &u32_len);
2086 assert(ret1 != 0);
2087 const bytes = try allocator.alloc(u8, u32_len);
2088 errdefer allocator.free(bytes);
2089 const ret2 = c._NSGetExecutablePath(bytes.ptr, &u32_len);
2090 assert(ret2 == 0);
2091 return bytes;
2118 var u32_len: u32 = @intCast(u32, out_buffer.len); // TODO shouldn't need this cast
2119 const rc = c._NSGetExecutablePath(out_buffer, &u32_len);
2120 if (rc != 0) return error.NameTooLong;
2121 return mem.toSlice(u8, out_buffer);
20922122 },
20932123 else => @compileError("Unsupported OS"),
20942124 }
20952125}
20962126
2097/// Get the directory path that contains the current executable.
2127/// `selfExeDirPath` except allocates the result on the heap.
20982128/// 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 {
21002137 switch (builtin.os) {
21012138 Os.linux => {
21022139 // If the currently executing binary has been deleted,
21032140 // the file path looks something like `/a/b/c/exe (deleted)`
21042141 // This path cannot be opened, but it's valid for determining the directory
21052142 // the executable was in when it was run.
2106 const full_exe_path = try readLink(allocator, "/proc/self/exe");
2107 errdefer allocator.free(full_exe_path);
2108 const dir = path.dirname(full_exe_path) orelse ".";
2109 return allocator.shrink(u8, full_exe_path, dir.len);
2143 const full_exe_path = try readLinkC(out_buffer, c"/proc/self/exe");
2144 // Assume that /proc/self/exe has an absolute path, and therefore dirname
2145 // will not return null.
2146 return path.dirname(full_exe_path).?;
21102147 },
21112148 Os.windows, Os.macosx, Os.ios => {
2112 const self_exe_path = try selfExePath(allocator);
2113 errdefer allocator.free(self_exe_path);
2114 const dirname = os.path.dirname(self_exe_path) orelse ".";
2115 return allocator.shrink(u8, self_exe_path, dirname.len);
2149 const self_exe_path = try selfExePath(out_buffer);
2150 // Assume that the OS APIs return absolute paths, and therefore dirname
2151 // will not return null.
2152 return path.dirname(self_exe_path).?;
21162153 },
2117 else => @compileError("unimplemented: std.os.selfExeDirPath for " ++ @tagName(builtin.os)),
2154 else => @compileError("Unsupported OS"),
21182155 }
21192156}
21202157
......@@ -2218,6 +2255,7 @@ pub const PosixBindError = error{
22182255 /// The socket inode would reside on a read-only filesystem.
22192256 ReadOnlyFileSystem,
22202257
2258 /// See https://github.com/ziglang/zig/issues/1396
22212259 Unexpected,
22222260};
22232261
......@@ -2261,6 +2299,7 @@ const PosixListenError = error{
22612299 /// The socket is not of a type that supports the listen() operation.
22622300 OperationNotSupported,
22632301
2302 /// See https://github.com/ziglang/zig/issues/1396
22642303 Unexpected,
22652304};
22662305
......@@ -2314,6 +2353,7 @@ pub const PosixAcceptError = error{
23142353 /// Firewall rules forbid connection.
23152354 BlockedByFirewall,
23162355
2356 /// See https://github.com/ziglang/zig/issues/1396
23172357 Unexpected,
23182358};
23192359
......@@ -2359,6 +2399,7 @@ pub const LinuxEpollCreateError = error{
23592399 /// There was insufficient memory to create the kernel object.
23602400 SystemResources,
23612401
2402 /// See https://github.com/ziglang/zig/issues/1396
23622403 Unexpected,
23632404};
23642405
......@@ -2413,6 +2454,7 @@ pub const LinuxEpollCtlError = error{
24132454 /// for example, a regular file or a directory.
24142455 FileDescriptorIncompatibleWithEpoll,
24152456
2457 /// See https://github.com/ziglang/zig/issues/1396
24162458 Unexpected,
24172459};
24182460
......@@ -2455,6 +2497,7 @@ pub const LinuxEventFdError = error{
24552497 ProcessFdQuotaExceeded,
24562498 SystemFdQuotaExceeded,
24572499
2500 /// See https://github.com/ziglang/zig/issues/1396
24582501 Unexpected,
24592502};
24602503
......@@ -2477,6 +2520,7 @@ pub const PosixGetSockNameError = error{
24772520 /// Insufficient resources were available in the system to perform the operation.
24782521 SystemResources,
24792522
2523 /// See https://github.com/ziglang/zig/issues/1396
24802524 Unexpected,
24812525};
24822526
......@@ -2530,6 +2574,7 @@ pub const PosixConnectError = error{
25302574 /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
25312575 ConnectionTimedOut,
25322576
2577 /// See https://github.com/ziglang/zig/issues/1396
25332578 Unexpected,
25342579};
25352580
......@@ -2751,6 +2796,7 @@ pub const SpawnThreadError = error{
27512796 /// Not enough userland memory to spawn the thread.
27522797 OutOfMemory,
27532798
2799 /// See https://github.com/ziglang/zig/issues/1396
27542800 Unexpected,
27552801};
27562802
......@@ -2926,7 +2972,9 @@ pub fn posixFStat(fd: i32) !posix.Stat {
29262972 const err = posix.getErrno(posix.fstat(fd, &stat));
29272973 if (err > 0) {
29282974 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,
29302978 posix.ENOMEM => error.SystemResources,
29312979 else => os.unexpectedErrorPosix(err),
29322980 };
......@@ -2938,6 +2986,8 @@ pub fn posixFStat(fd: i32) !posix.Stat {
29382986pub const CpuCountError = error{
29392987 OutOfMemory,
29402988 PermissionDenied,
2989
2990 /// See https://github.com/ziglang/zig/issues/1396
29412991 Unexpected,
29422992};
29432993
......@@ -3008,6 +3058,7 @@ pub const BsdKQueueError = error{
30083058 /// The system-wide limit on the total number of open files has been reached.
30093059 SystemFdQuotaExceeded,
30103060
3061 /// See https://github.com/ziglang/zig/issues/1396
30113062 Unexpected,
30123063};
30133064
std/os/path.zig+137-98
......@@ -11,11 +11,14 @@ const math = std.math;
1111const posix = os.posix;
1212const windows = os.windows;
1313const cstr = std.cstr;
14const windows_util = @import("windows/util.zig");
1415
1516pub const sep_windows = '\\';
1617pub const sep_posix = '/';
1718pub const sep = if (is_windows) sep_windows else sep_posix;
1819
20pub const sep_str = [1]u8{sep};
21
1922pub const delimiter_windows = ';';
2023pub const delimiter_posix = ':';
2124pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix;
......@@ -337,7 +340,7 @@ pub fn resolveSlice(allocator: *Allocator, paths: []const []const u8) ![]u8 {
337340pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
338341 if (paths.len == 0) {
339342 assert(is_windows); // resolveWindows called on non windows can't use getCwd
340 return os.getCwd(allocator);
343 return os.getCwdAlloc(allocator);
341344 }
342345
343346 // determine which disk designator we will result with, if any
......@@ -432,7 +435,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
432435 },
433436 WindowsPath.Kind.None => {
434437 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);
436439 defer allocator.free(cwd);
437440 const parsed_cwd = windowsParsePath(cwd);
438441 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 {
448451 } else {
449452 assert(is_windows); // resolveWindows called on non windows can't use getCwd
450453 // 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);
452455 defer allocator.free(cwd);
453456
454457 result = try allocator.alloc(u8, max_size + cwd.len + 1);
......@@ -516,7 +519,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
516519pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
517520 if (paths.len == 0) {
518521 assert(!is_windows); // resolvePosix called on windows can't use getCwd
519 return os.getCwd(allocator);
522 return os.getCwdAlloc(allocator);
520523 }
521524
522525 var first_index: usize = 0;
......@@ -538,7 +541,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
538541 result = try allocator.alloc(u8, max_size);
539542 } else {
540543 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);
542545 defer allocator.free(cwd);
543546 result = try allocator.alloc(u8, max_size + cwd.len + 1);
544547 mem.copy(u8, result, cwd);
......@@ -573,11 +576,11 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
573576 result_index += 1;
574577 }
575578
576 return result[0..result_index];
579 return allocator.shrink(u8, result, result_index);
577580}
578581
579582test "os.path.resolve" {
580 const cwd = try os.getCwd(debug.global_allocator);
583 const cwd = try os.getCwdAlloc(debug.global_allocator);
581584 if (is_windows) {
582585 if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {
583586 cwd[0] = asciiUpper(cwd[0]);
......@@ -591,7 +594,7 @@ test "os.path.resolve" {
591594
592595test "os.path.resolveWindows" {
593596 if (is_windows) {
594 const cwd = try os.getCwd(debug.global_allocator);
597 const cwd = try os.getCwdAlloc(debug.global_allocator);
595598 const parsed_cwd = windowsParsePath(cwd);
596599 {
597600 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
10731076 assert(mem.eql(u8, result, expected_output));
10741077}
10751078
1076/// Return the canonicalized absolute pathname.
1077/// Expands all symbolic links and resolves references to `.`, `..`, and
1078/// extra `/` characters in ::pathname.
1079/// Caller must deallocate result.
1080pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 {
1081 switch (builtin.os) {
1082 Os.windows => {
1083 const pathname_buf = try allocator.alloc(u8, pathname.len + 1);
1084 defer allocator.free(pathname_buf);
1085
1086 mem.copy(u8, pathname_buf, pathname);
1087 pathname_buf[pathname.len] = 0;
1088
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);
1090 if (h_file == windows.INVALID_HANDLE_VALUE) {
1091 const err = windows.GetLastError();
1092 return switch (err) {
1093 windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
1094 windows.ERROR.ACCESS_DENIED => error.AccessDenied,
1095 windows.ERROR.FILENAME_EXCED_RANGE => error.NameTooLong,
1096 else => os.unexpectedErrorWindows(err),
1097 };
1098 }
1099 defer os.close(h_file);
1100 var buf = try allocator.alloc(u8, 256);
1101 errdefer allocator.free(buf);
1102 while (true) {
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 }
1079pub const RealError = error{
1080 FileNotFound,
1081 AccessDenied,
1082 NameTooLong,
1083 NotSupported,
1084 NotDir,
1085 SymLinkLoop,
1086 InputOutput,
1087 FileTooBig,
1088 IsDir,
1089 ProcessFdQuotaExceeded,
1090 SystemFdQuotaExceeded,
1091 NoDevice,
1092 SystemResources,
1093 NoSpaceLeft,
1094 FileSystem,
1095 BadPathName,
1096
1097 /// On Windows, file paths must be valid Unicode.
1098 InvalidUtf8,
1099
1100 /// TODO remove this possibility
1101 PathAlreadyExists,
1102
1103 /// TODO remove this possibility
1104 Unexpected,
1105};
11151106
1116 if (result > buf.len) {
1117 buf = try allocator.realloc(u8, buf, result);
1118 continue;
1119 }
1107/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
1108/// Otherwise use `real` or `realC`.
1109pub fn realW(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u16) RealError![]u8 {
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 path
1122 // we strip it because nobody wants \\?\ prepended to their path
1123 const final_len = x: {
1124 if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) {
1125 var i: usize = 4;
1126 while (i < result) : (i += 1) {
1127 buf[i - 4] = buf[i];
1128 }
1129 break :x result - 4;
1130 } else {
1131 break :x result;
1132 }
1133 };
1134
1135 return allocator.shrink(u8, buf, final_len);
1136 }
1146 // windows returns \\?\ prepended to the path
1147 // we strip it because nobody wants \\?\ prepended to their path
1148 const prefix = []u16{ '\\', '\\', '?', '\\' };
1149 const start_index = if (mem.startsWith(u16, utf16le_slice, prefix)) prefix.len else 0;
1150
1151 // Trust that Windows gives us valid UTF-16LE.
1152 const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice[start_index..]) catch unreachable;
1153 return out_buffer[0..end_index];
1154}
1155
1156/// See `real`
1157/// Use this when you have a null terminated pointer path.
1158pub fn realC(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u8) RealError![]u8 {
1159 switch (builtin.os) {
1160 Os.windows => {
1161 const pathname_w = try windows_util.cStrToPrefixedFileW(pathname);
1162 return realW(out_buffer, pathname_w);
11371163 },
11381164 Os.macosx, Os.ios => {
1139 // TODO instead of calling the libc function here, port the implementation
1140 // to Zig, and then remove the NameTooLong error possibility.
1141 const pathname_buf = try allocator.alloc(u8, pathname.len + 1);
1142 defer allocator.free(pathname_buf);
1143
1144 const result_buf = try allocator.alloc(u8, posix.PATH_MAX);
1145 errdefer allocator.free(result_buf);
1146
1147 mem.copy(u8, pathname_buf, pathname);
1148 pathname_buf[pathname.len] = 0;
1149
1150 const err = posix.getErrno(posix.realpath(pathname_buf.ptr, result_buf.ptr));
1151 if (err > 0) {
1152 return switch (err) {
1153 posix.EINVAL => unreachable,
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 // TODO instead of calling the libc function here, port the implementation to Zig
1166 const err = posix.getErrno(posix.realpath(pathname, out_buffer));
1167 switch (err) {
1168 0 => return mem.toSlice(u8, out_buffer),
1169 posix.EINVAL => unreachable,
1170 posix.EBADF => unreachable,
1171 posix.EFAULT => unreachable,
1172 posix.EACCES => return error.AccessDenied,
1173 posix.ENOENT => return error.FileNotFound,
1174 posix.ENOTSUP => return error.NotSupported,
1175 posix.ENOTDIR => return error.NotDir,
1176 posix.ENAMETOOLONG => return error.NameTooLong,
1177 posix.ELOOP => return error.SymLinkLoop,
1178 posix.EIO => return error.InputOutput,
1179 else => return os.unexpectedErrorPosix(err),
11651180 }
1166 return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr));
11671181 },
11681182 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);
11701184 defer os.close(fd);
11711185
11721186 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);
11761190 },
11771191 else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)),
11781192 }
11791193}
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
11811219test "os.path.real" {
11821220 // 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);
11841223}
std/os/test.zig+8-8
......@@ -10,27 +10,27 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
1010const AtomicOrder = builtin.AtomicOrder;
1111
1212test "makePath, put some files in it, deleteTree" {
13 try os.makePath(a, "os_test_tmp/b/c");
14 try io.writeFile(a, "os_test_tmp/b/c/file.txt", "nonsense");
15 try io.writeFile(a, "os_test_tmp/b/file2.txt", "blah");
13 try os.makePath(a, "os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "c");
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("os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "file2.txt", "blah");
1616 try os.deleteTree(a, "os_test_tmp");
1717 if (os.Dir.open(a, "os_test_tmp")) |dir| {
1818 @panic("expected error");
1919 } else |err| {
20 assert(err == error.PathNotFound);
20 assert(err == error.FileNotFound);
2121 }
2222}
2323
2424test "access file" {
2525 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| {
2727 @panic("expected error");
2828 } else |err| {
29 assert(err == error.NotFound);
29 assert(err == error.FileNotFound);
3030 }
3131
32 try io.writeFile(a, "os_test_tmp/file.txt", "");
33 try os.File.access(a, "os_test_tmp/file.txt");
32 try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "file.txt", "");
33 try os.File.access("os_test_tmp" ++ os.path.sep_str ++ "file.txt");
3434 try os.deleteTree(a, "os_test_tmp");
3535}
3636
std/os/windows/kernel32.zig+25-13
......@@ -1,14 +1,11 @@
11use @import("index.zig");
22
3
43pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL;
54
65pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
76
8pub extern "kernel32" stdcallcc fn CreateDirectoryA(
9 lpPathName: LPCSTR,
10 lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
11) BOOL;
7pub extern "kernel32" stdcallcc fn CreateDirectoryA(lpPathName: [*]const u8, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
8pub extern "kernel32" stdcallcc fn CreateDirectoryW(lpPathName: [*]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
129
1310pub extern "kernel32" stdcallcc fn CreateFileA(
1411 lpFileName: [*]const u8, // TODO null terminated pointer type
......@@ -60,7 +57,8 @@ pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, Ex
6057
6158pub 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
6563pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
6664
......@@ -74,7 +72,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;
7472
7573pub 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
7978pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE;
8079pub extern "kernel32" stdcallcc fn GetCurrentThreadId() DWORD;
......@@ -87,9 +86,11 @@ pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCo
8786
8887pub 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
9495pub extern "kernel32" stdcallcc fn GetLastError() DWORD;
9596
......@@ -107,6 +108,12 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
107108 dwFlags: DWORD,
108109) DWORD;
109110
111pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleW(
112 hFile: HANDLE,
113 lpszFilePath: [*]u16,
114 cchFilePath: DWORD,
115 dwFlags: DWORD,
116) DWORD;
110117
111118pub 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
132139pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL;
133140
134141pub extern "kernel32" stdcallcc fn MoveFileExA(
135 lpExistingFileName: LPCSTR,
136 lpNewFileName: LPCSTR,
142 lpExistingFileName: [*]const u8,
143 lpNewFileName: [*]const u8,
144 dwFlags: DWORD,
145) BOOL;
146
147pub extern "kernel32" stdcallcc fn MoveFileExW(
148 lpExistingFileName: [*]const u16,
149 lpNewFileName: [*]const u16,
137150 dwFlags: DWORD,
138151) BOOL;
139152
......@@ -194,7 +207,6 @@ pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;
194207
195208pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;
196209
197
198210pub const FILE_NOTIFY_INFORMATION = extern struct {
199211 NextEntryOffset: DWORD,
200212 Action: DWORD,
......@@ -208,7 +220,7 @@ pub const FILE_ACTION_MODIFIED = 0x00000003;
208220pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;
209221pub 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
213225pub const FILE_LIST_DIRECTORY = 1;
214226
std/os/windows/util.zig+73-19
......@@ -7,9 +7,17 @@ const mem = std.mem;
77const BufMap = std.BufMap;
88const 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
1016pub const WaitError = error{
1117 WaitAbandoned,
1218 WaitTimeOut,
19
20 /// See https://github.com/ziglang/zig/issues/1396
1321 Unexpected,
1422};
1523
......@@ -37,6 +45,8 @@ pub const WriteError = error{
3745 SystemResources,
3846 OperationAborted,
3947 BrokenPipe,
48
49 /// See https://github.com/ziglang/zig/issues/1396
4050 Unexpected,
4151};
4252
......@@ -86,37 +96,51 @@ pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool {
8696pub const OpenError = error{
8797 SharingViolation,
8898 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.
89104 FileNotFound,
105
90106 AccessDenied,
91107 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
92118 Unexpected,
93 OutOfMemory,
94119};
95120
96/// `file_path` needs to be copied in memory to add a null terminating byte, hence the allocator.
97121pub fn windowsOpen(
98 allocator: *mem.Allocator,
99122 file_path: []const u8,
100123 desired_access: windows.DWORD,
101124 share_mode: windows.DWORD,
102125 creation_disposition: windows.DWORD,
103126 flags_and_attrs: windows.DWORD,
104127) OpenError!windows.HANDLE {
105 const path_with_null = try cstr.addNullByte(allocator, file_path);
106 defer allocator.free(path_with_null);
128 const file_path_w = try sliceToPrefixedFileW(file_path);
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
110132 if (result == windows.INVALID_HANDLE_VALUE) {
111133 const err = windows.GetLastError();
112 return switch (err) {
113 windows.ERROR.SHARING_VIOLATION => OpenError.SharingViolation,
114 windows.ERROR.ALREADY_EXISTS, windows.ERROR.FILE_EXISTS => OpenError.PathAlreadyExists,
115 windows.ERROR.FILE_NOT_FOUND => OpenError.FileNotFound,
116 windows.ERROR.ACCESS_DENIED => OpenError.AccessDenied,
117 windows.ERROR.PIPE_BUSY => OpenError.PipeBusy,
118 else => os.unexpectedErrorWindows(err),
119 };
134 switch (err) {
135 windows.ERROR.SHARING_VIOLATION => return OpenError.SharingViolation,
136 windows.ERROR.ALREADY_EXISTS => return OpenError.PathAlreadyExists,
137 windows.ERROR.FILE_EXISTS => return OpenError.PathAlreadyExists,
138 windows.ERROR.FILE_NOT_FOUND => return OpenError.FileNotFound,
139 windows.ERROR.PATH_NOT_FOUND => return OpenError.FileNotFound,
140 windows.ERROR.ACCESS_DENIED => return OpenError.AccessDenied,
141 windows.ERROR.PIPE_BUSY => return OpenError.PipeBusy,
142 else => return os.unexpectedErrorWindows(err),
143 }
120144 }
121145
122146 return result;
......@@ -192,9 +216,8 @@ pub fn windowsFindFirstFile(
192216 if (handle == windows.INVALID_HANDLE_VALUE) {
193217 const err = windows.GetLastError();
194218 switch (err) {
195 windows.ERROR.FILE_NOT_FOUND,
196 windows.ERROR.PATH_NOT_FOUND,
197 => return error.PathNotFound,
219 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
220 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
198221 else => return os.unexpectedErrorWindows(err),
199222 }
200223 }
......@@ -238,7 +261,7 @@ pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_
238261 }
239262}
240263
241pub const WindowsWaitResult = enum{
264pub const WindowsWaitResult = enum {
242265 Normal,
243266 Aborted,
244267 Cancelled,
......@@ -254,8 +277,39 @@ pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_t
254277 if (std.debug.runtime_safety) {
255278 std.debug.panic("unexpected error: {}\n", err);
256279 }
257 }
280 },
258281 }
259282 }
260283 return WindowsWaitResult.Normal;
261284}
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 {
218218 }
219219
220220 const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable;
221
222221 it.i += cp_len;
223222 return it.bytes[it.i - cp_len .. it.i];
224223 }
......@@ -236,6 +235,38 @@ const Utf8Iterator = struct {
236235 }
237236};
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
239270test "utf8 encode" {
240271 comptime testUtf8Encode() catch unreachable;
241272 try testUtf8Encode();
......@@ -446,42 +477,34 @@ fn testDecode(bytes: []const u8) !u32 {
446477 return utf8Decode(bytes);
447478}
448479
449// TODO: make this API on top of a non-allocating Utf16LeView
450pub fn utf16leToUtf8(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 {
480/// Caller must free returned memory.
481pub fn utf16leToUtf8Alloc(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 {
451482 var result = std.ArrayList(u8).init(allocator);
452483 // optimistically guess that it will all be ascii.
453484 try result.ensureCapacity(utf16le.len);
454
455 const utf16le_as_bytes = @sliceToBytes(utf16le);
456 var i: usize = 0;
457485 var out_index: usize = 0;
458 while (i < utf16le_as_bytes.len) : (i += 2) {
459 // decode
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
486 var it = Utf16LeIterator.init(utf16le);
487 while (try it.nextCodepoint()) |codepoint| {
476488 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
477489 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);
479491 out_index += utf8_len;
480492 }
481493
482494 return result.toOwnedSlice();
483495}
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
485508test "utf16leToUtf8" {
486509 var utf16le: [2]u16 = undefined;
487510 const utf16le_as_bytes = @sliceToBytes(utf16le[0..]);
......@@ -489,14 +512,14 @@ test "utf16leToUtf8" {
489512 {
490513 mem.writeInt(utf16le_as_bytes[0..], u16('A'), builtin.Endian.Little);
491514 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);
493516 assert(mem.eql(u8, utf8, "Aa"));
494517 }
495518
496519 {
497520 mem.writeInt(utf16le_as_bytes[0..], u16(0x80), builtin.Endian.Little);
498521 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);
500523 assert(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf"));
501524 }
502525
......@@ -504,7 +527,7 @@ test "utf16leToUtf8" {
504527 // the values just outside the surrogate half range
505528 mem.writeInt(utf16le_as_bytes[0..], u16(0xd7ff), builtin.Endian.Little);
506529 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);
508531 assert(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80"));
509532 }
510533
......@@ -512,7 +535,7 @@ test "utf16leToUtf8" {
512535 // smallest surrogate pair
513536 mem.writeInt(utf16le_as_bytes[0..], u16(0xd800), builtin.Endian.Little);
514537 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);
516539 assert(mem.eql(u8, utf8, "\xf0\x90\x80\x80"));
517540 }
518541
......@@ -520,14 +543,14 @@ test "utf16leToUtf8" {
520543 // largest surrogate pair
521544 mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);
522545 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);
524547 assert(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf"));
525548 }
526549
527550 {
528551 mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);
529552 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);
531554 assert(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80"));
532555 }
533556}
......@@ -548,3 +571,20 @@ pub fn utf8ToUtf16LeWithNull(allocator: *mem.Allocator, utf8: []const u8) ![]u16
548571 try result.append(0);
549572 return result.toOwnedSlice();
550573}
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 @@
11const A = error{
2 PathNotFound,
2 FileNotFound,
33 NotDir,
44};
55const B = error{OutOfMemory};
......@@ -15,7 +15,7 @@ test "merge error sets" {
1515 @panic("unexpected");
1616 } else |err| switch (err) {
1717 error.OutOfMemory => @panic("unexpected"),
18 error.PathNotFound => @panic("unexpected"),
18 error.FileNotFound => @panic("unexpected"),
1919 error.NotDir => {},
2020 }
2121}