authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2024-02-13 16:56:50-08:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2024-02-24 14:05:24-08:00
log68b87918df9ad82cf3161f323c55f2e238319922
treec802aea2b636236f767e6c1cdd864f01c2b47d15
parentf6b6b8a4ae4780f21c45824d34d8e14b3c3b5037

Fix handling of Windows (WTF-16) and WASI (UTF-8) paths

Windows paths now use WTF-16 <-> WTF-8 conversion everywhere, which is lossless. Previously, conversion of ill-formed UTF-16 paths would either fail or invoke illegal behavior. WASI paths must be valid UTF-8, and the relevant function calls have been updated to handle the possibility of failure due to paths not being encoded/encodable as valid UTF-8. Closes #18694 Closes #1774 Closes #2565

20 files changed, 1000 insertions(+), 1109 deletions(-)

deps/aro/aro/Compilation.zig+1-1
...@@ -69,7 +69,7 @@ pub const Environment = struct {...@@ -69,7 +69,7 @@ pub const Environment = struct {
69 const val: ?[]const u8 = std.process.getEnvVarOwned(allocator, env_var_name) catch |err| switch (err) {69 const val: ?[]const u8 = std.process.getEnvVarOwned(allocator, env_var_name) catch |err| switch (err) {
70 error.OutOfMemory => |e| return e,70 error.OutOfMemory => |e| return e,
71 error.EnvironmentVariableNotFound => null,71 error.EnvironmentVariableNotFound => null,
72 error.InvalidUtf8 => null,72 error.InvalidWtf8 => null,
73 };73 };
74 @field(env, field.name) = val;74 @field(env, field.name) = val;
75 }75 }
deps/aro/aro/Driver.zig+2-1
...@@ -523,7 +523,8 @@ pub fn errorDescription(e: anyerror) []const u8 {...@@ -523,7 +523,8 @@ pub fn errorDescription(e: anyerror) []const u8 {
523 error.NotDir => "is not a directory",523 error.NotDir => "is not a directory",
524 error.NotOpenForReading => "file is not open for reading",524 error.NotOpenForReading => "file is not open for reading",
525 error.NotOpenForWriting => "file is not open for writing",525 error.NotOpenForWriting => "file is not open for writing",
526 error.InvalidUtf8 => "input is not valid UTF-8",526 error.InvalidUtf8 => "path is not valid UTF-8",
527 error.InvalidWtf8 => "path is not valid WTF-8",
527 error.FileBusy => "file is busy",528 error.FileBusy => "file is busy",
528 error.NameTooLong => "file name is too long",529 error.NameTooLong => "file name is too long",
529 error.AccessDenied => "access denied",530 error.AccessDenied => "access denied",
lib/std/Build/Cache.zig+1-1
...@@ -162,7 +162,7 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {...@@ -162,7 +162,7 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
162fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {162fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
163 const relative = try std.fs.path.relative(allocator, prefix, path);163 const relative = try std.fs.path.relative(allocator, prefix, path);
164 errdefer allocator.free(relative);164 errdefer allocator.free(relative);
165 var component_iterator = std.fs.path.NativeUtf8ComponentIterator.init(relative) catch {165 var component_iterator = std.fs.path.NativeComponentIterator.init(relative) catch {
166 return error.NotASubPath;166 return error.NotASubPath;
167 };167 };
168 if (component_iterator.root() != null) {168 if (component_iterator.root() != null) {
lib/std/Thread.zig+4-9
...@@ -91,7 +91,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {...@@ -91,7 +91,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
91 },91 },
92 .windows => {92 .windows => {
93 var buf: [max_name_len]u16 = undefined;93 var buf: [max_name_len]u16 = undefined;
94 const len = try std.unicode.utf8ToUtf16Le(&buf, name);94 const len = try std.unicode.wtf8ToWtf16Le(&buf, name);
95 const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong;95 const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong;
9696
97 // Note: NT allocates its own copy, no use-after-free here.97 // Note: NT allocates its own copy, no use-after-free here.
...@@ -157,17 +157,12 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {...@@ -157,17 +157,12 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
157}157}
158158
159pub const GetNameError = error{159pub const GetNameError = error{
160 // For Windows, the name is converted from UTF16 to UTF8
161 CodepointTooLarge,
162 Utf8CannotEncodeSurrogateHalf,
163 DanglingSurrogateHalf,
164 ExpectedSecondSurrogateHalf,
165 UnexpectedSecondSurrogateHalf,
166
167 Unsupported,160 Unsupported,
168 Unexpected,161 Unexpected,
169} || os.PrctlError || os.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError;162} || os.PrctlError || os.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError;
170163
164/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
165/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
171pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 {166pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 {
172 buffer_ptr[max_name_len] = 0;167 buffer_ptr[max_name_len] = 0;
173 var buffer: [:0]u8 = buffer_ptr;168 var buffer: [:0]u8 = buffer_ptr;
...@@ -213,7 +208,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co...@@ -213,7 +208,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
213 )) {208 )) {
214 .SUCCESS => {209 .SUCCESS => {
215 const string = @as(*const os.windows.UNICODE_STRING, @ptrCast(&buf));210 const string = @as(*const os.windows.UNICODE_STRING, @ptrCast(&buf));
216 const len = try std.unicode.utf16LeToUtf8(buffer, string.Buffer[0 .. string.Length / 2]);211 const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer[0 .. string.Length / 2]);
217 return if (len > 0) buffer[0..len] else null;212 return if (len > 0) buffer[0..len] else null;
218 },213 },
219 .NOT_IMPLEMENTED => return error.Unsupported,214 .NOT_IMPLEMENTED => return error.Unsupported,
lib/std/child_process.zig+21-22
...@@ -129,10 +129,9 @@ pub const ChildProcess = struct {...@@ -129,10 +129,9 @@ pub const ChildProcess = struct {
129 /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.129 /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.
130 NoDevice,130 NoDevice,
131131
132 /// Windows-only. One of:132 /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8.
133 /// * `cwd` was provided and it could not be re-encoded into UTF16LE, or133 /// https://simonsapin.github.io/wtf-8/
134 /// * The `PATH` or `PATHEXT` environment variable contained invalid UTF-8.134 InvalidWtf8,
135 InvalidUtf8,
136135
137 /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.136 /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
138 CurrentWorkingDirectoryUnlinked,137 CurrentWorkingDirectoryUnlinked,
...@@ -767,7 +766,7 @@ pub const ChildProcess = struct {...@@ -767,7 +766,7 @@ pub const ChildProcess = struct {
767 };766 };
768 var piProcInfo: windows.PROCESS_INFORMATION = undefined;767 var piProcInfo: windows.PROCESS_INFORMATION = undefined;
769768
770 const cwd_w = if (self.cwd) |cwd| try unicode.utf8ToUtf16LeAllocZ(self.allocator, cwd) else null;769 const cwd_w = if (self.cwd) |cwd| try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd) else null;
771 defer if (cwd_w) |cwd| self.allocator.free(cwd);770 defer if (cwd_w) |cwd| self.allocator.free(cwd);
772 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;771 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
773772
...@@ -775,8 +774,8 @@ pub const ChildProcess = struct {...@@ -775,8 +774,8 @@ pub const ChildProcess = struct {
775 defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);774 defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
776 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;775 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
777776
778 const app_name_utf8 = self.argv[0];777 const app_name_wtf8 = self.argv[0];
779 const app_name_is_absolute = fs.path.isAbsolute(app_name_utf8);778 const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8);
780779
781 // the cwd set in ChildProcess is in effect when choosing the executable path780 // the cwd set in ChildProcess is in effect when choosing the executable path
782 // to match posix semantics781 // to match posix semantics
...@@ -785,11 +784,11 @@ pub const ChildProcess = struct {...@@ -785,11 +784,11 @@ pub const ChildProcess = struct {
785 // If the app name is absolute, then we need to use its dirname as the cwd784 // If the app name is absolute, then we need to use its dirname as the cwd
786 if (app_name_is_absolute) {785 if (app_name_is_absolute) {
787 cwd_path_w_needs_free = true;786 cwd_path_w_needs_free = true;
788 const dir = fs.path.dirname(app_name_utf8).?;787 const dir = fs.path.dirname(app_name_wtf8).?;
789 break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, dir);788 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, dir);
790 } else if (self.cwd) |cwd| {789 } else if (self.cwd) |cwd| {
791 cwd_path_w_needs_free = true;790 cwd_path_w_needs_free = true;
792 break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, cwd);791 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd);
793 } else {792 } else {
794 break :x &[_:0]u16{}; // empty for cwd793 break :x &[_:0]u16{}; // empty for cwd
795 }794 }
...@@ -800,19 +799,19 @@ pub const ChildProcess = struct {...@@ -800,19 +799,19 @@ pub const ChildProcess = struct {
800 // into the basename and dirname and use the dirname as an addition to the cwd799 // into the basename and dirname and use the dirname as an addition to the cwd
801 // path. This is because NtQueryDirectoryFile cannot accept FileName params with800 // path. This is because NtQueryDirectoryFile cannot accept FileName params with
802 // path separators.801 // path separators.
803 const app_basename_utf8 = fs.path.basename(app_name_utf8);802 const app_basename_wtf8 = fs.path.basename(app_name_wtf8);
804 // If the app name is absolute, then the cwd will already have the app's dirname in it,803 // If the app name is absolute, then the cwd will already have the app's dirname in it,
805 // so only populate app_dirname if app name is a relative path with > 0 path separators.804 // so only populate app_dirname if app name is a relative path with > 0 path separators.
806 const maybe_app_dirname_utf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_utf8) else null;805 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_wtf8) else null;
807 const app_dirname_w: ?[:0]u16 = x: {806 const app_dirname_w: ?[:0]u16 = x: {
808 if (maybe_app_dirname_utf8) |app_dirname_utf8| {807 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
809 break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, app_dirname_utf8);808 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_dirname_wtf8);
810 }809 }
811 break :x null;810 break :x null;
812 };811 };
813 defer if (app_dirname_w != null) self.allocator.free(app_dirname_w.?);812 defer if (app_dirname_w != null) self.allocator.free(app_dirname_w.?);
814813
815 const app_name_w = try unicode.utf8ToUtf16LeAllocZ(self.allocator, app_basename_utf8);814 const app_name_w = try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_basename_wtf8);
816 defer self.allocator.free(app_name_w);815 defer self.allocator.free(app_name_w);
817816
818 const cmd_line_w = argvToCommandLineWindows(self.allocator, self.argv) catch |err| switch (err) {817 const cmd_line_w = argvToCommandLineWindows(self.allocator, self.argv) catch |err| switch (err) {
...@@ -1173,7 +1172,7 @@ const CreateProcessSupportedExtension = enum {...@@ -1173,7 +1172,7 @@ const CreateProcessSupportedExtension = enum {
1173 exe,1172 exe,
1174};1173};
11751174
1176/// Case-insensitive UTF-16 lookup1175/// Case-insensitive WTF-16 lookup
1177fn windowsCreateProcessSupportsExtension(ext: []const u16) ?CreateProcessSupportedExtension {1176fn windowsCreateProcessSupportsExtension(ext: []const u16) ?CreateProcessSupportedExtension {
1178 if (ext.len != 4) return null;1177 if (ext.len != 4) return null;
1179 const State = enum {1178 const State = enum {
...@@ -1237,7 +1236,7 @@ test "windowsCreateProcessSupportsExtension" {...@@ -1237,7 +1236,7 @@ test "windowsCreateProcessSupportsExtension" {
1237 try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null);1236 try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null);
1238}1237}
12391238
1240pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidUtf8, InvalidArg0 };1239pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
12411240
1242/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and1241/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and
1243/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice.1242/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice.
...@@ -1320,7 +1319,7 @@ pub fn argvToCommandLineWindows(...@@ -1320,7 +1319,7 @@ pub fn argvToCommandLineWindows(
1320 }1319 }
1321 }1320 }
13221321
1323 return try unicode.utf8ToUtf16LeAllocZ(allocator, buf.items);1322 return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
1324}1323}
13251324
1326test "argvToCommandLineWindows" {1325test "argvToCommandLineWindows" {
...@@ -1386,7 +1385,7 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c...@@ -1386,7 +1385,7 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c
1386 const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv);1385 const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv);
1387 defer std.testing.allocator.free(cmd_line_w);1386 defer std.testing.allocator.free(cmd_line_w);
13881387
1389 const cmd_line = try unicode.utf16LeToUtf8Alloc(std.testing.allocator, cmd_line_w);1388 const cmd_line = try unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w);
1390 defer std.testing.allocator.free(cmd_line);1389 defer std.testing.allocator.free(cmd_line);
13911390
1392 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);1391 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
...@@ -1424,7 +1423,7 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons...@@ -1424,7 +1423,7 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons
1424 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",1423 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
1425 .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .Monotonic) },1424 .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .Monotonic) },
1426 ) catch unreachable;1425 ) catch unreachable;
1427 const len = std.unicode.utf8ToUtf16Le(&tmp_bufw, pipe_path) catch unreachable;1426 const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
1428 tmp_bufw[len] = 0;1427 tmp_bufw[len] = 0;
1429 break :blk tmp_bufw[0..len :0];1428 break :blk tmp_bufw[0..len :0];
1430 };1429 };
...@@ -1521,10 +1520,10 @@ pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) !...@@ -1521,10 +1520,10 @@ pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) !
1521 var it = env_map.iterator();1520 var it = env_map.iterator();
1522 var i: usize = 0;1521 var i: usize = 0;
1523 while (it.next()) |pair| {1522 while (it.next()) |pair| {
1524 i += try unicode.utf8ToUtf16Le(result[i..], pair.key_ptr.*);1523 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
1525 result[i] = '=';1524 result[i] = '=';
1526 i += 1;1525 i += 1;
1527 i += try unicode.utf8ToUtf16Le(result[i..], pair.value_ptr.*);1526 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
1528 result[i] = 0;1527 result[i] = 0;
1529 i += 1;1528 i += 1;
1530 }1529 }
lib/std/fs.zig+139-38
...@@ -31,18 +31,21 @@ pub const realpathW = os.realpathW;...@@ -31,18 +31,21 @@ pub const realpathW = os.realpathW;
31pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;31pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
32pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;32pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
3333
34/// This represents the maximum size of a UTF-8 encoded file path that the34/// This represents the maximum size of a `[]u8` file path that the
35/// operating system will accept. Paths, including those returned from file35/// operating system will accept. Paths, including those returned from file
36/// system operations, may be longer than this length, but such paths cannot36/// system operations, may be longer than this length, but such paths cannot
37/// be successfully passed back in other file system operations. However,37/// be successfully passed back in other file system operations. However,
38/// all path components returned by file system operations are assumed to38/// all path components returned by file system operations are assumed to
39/// fit into a UTF-8 encoded array of this length.39/// fit into a `u8` array of this length.
40/// The byte count includes room for a null sentinel byte.40/// The byte count includes room for a null sentinel byte.
41/// On Windows, `[]u8` file paths are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
42/// On WASI, `[]u8` file paths are encoded as valid UTF-8.
43/// On other platforms, `[]u8` file paths are opaque sequences of bytes with no particular encoding.
41pub const MAX_PATH_BYTES = switch (builtin.os.tag) {44pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
42 .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten => os.PATH_MAX,45 .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten => os.PATH_MAX,
43 // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.46 // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes.
44 // If it would require 4 UTF-8 bytes, then there would be a surrogate47 // If it would require 4 WTF-8 bytes, then there would be a surrogate
45 // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.48 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
46 // +1 for the null byte at the end, which can be encoded in 1 byte.49 // +1 for the null byte at the end, which can be encoded in 1 byte.
47 .windows => os.windows.PATH_MAX_WIDE * 3 + 1,50 .windows => os.windows.PATH_MAX_WIDE * 3 + 1,
48 // TODO work out what a reasonable value we should use here51 // TODO work out what a reasonable value we should use here
...@@ -53,18 +56,21 @@ pub const MAX_PATH_BYTES = switch (builtin.os.tag) {...@@ -53,18 +56,21 @@ pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
53 @compileError("PATH_MAX not implemented for " ++ @tagName(builtin.os.tag)),56 @compileError("PATH_MAX not implemented for " ++ @tagName(builtin.os.tag)),
54};57};
5558
56/// This represents the maximum size of a UTF-8 encoded file name component that59/// This represents the maximum size of a `[]u8` file name component that
57/// the platform's common file systems support. File name components returned by file system60/// the platform's common file systems support. File name components returned by file system
58/// operations are likely to fit into a UTF-8 encoded array of this length, but61/// operations are likely to fit into a `u8` array of this length, but
59/// (depending on the platform) this assumption may not hold for every configuration.62/// (depending on the platform) this assumption may not hold for every configuration.
60/// The byte count does not include a null sentinel byte.63/// The byte count does not include a null sentinel byte.
64/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
65/// On WASI, file name components are encoded as valid UTF-8.
66/// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding.
61pub const MAX_NAME_BYTES = switch (builtin.os.tag) {67pub const MAX_NAME_BYTES = switch (builtin.os.tag) {
62 .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos => os.NAME_MAX,68 .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos => os.NAME_MAX,
63 // Haiku's NAME_MAX includes the null terminator, so subtract one.69 // Haiku's NAME_MAX includes the null terminator, so subtract one.
64 .haiku => os.NAME_MAX - 1,70 .haiku => os.NAME_MAX - 1,
65 // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.71 // Each WTF-16LE character may be expanded to 3 WTF-8 bytes.
66 // If it would require 4 UTF-8 bytes, then there would be a surrogate72 // If it would require 4 WTF-8 bytes, then there would be a surrogate
67 // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.73 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
68 .windows => os.windows.NAME_MAX * 3,74 .windows => os.windows.NAME_MAX * 3,
69 // For WASI, the MAX_NAME will depend on the host OS, so it needs to be75 // For WASI, the MAX_NAME will depend on the host OS, so it needs to be
70 // as large as the largest MAX_NAME_BYTES (Windows) in order to work on any host OS.76 // as large as the largest MAX_NAME_BYTES (Windows) in order to work on any host OS.
...@@ -86,6 +92,9 @@ pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);...@@ -86,6 +92,9 @@ pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);
8692
87/// TODO remove the allocator requirement from this API93/// TODO remove the allocator requirement from this API
88/// TODO move to Dir94/// TODO move to Dir
95/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
96/// On WASI, both paths should be encoded as valid UTF-8.
97/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
89pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {98pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
90 if (cwd().symLink(existing_path, new_path, .{})) {99 if (cwd().symLink(existing_path, new_path, .{})) {
91 return;100 return;
...@@ -117,6 +126,9 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:...@@ -117,6 +126,9 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:
117/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`126/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`
118/// are absolute. See `Dir.updateFile` for a function that operates on both127/// are absolute. See `Dir.updateFile` for a function that operates on both
119/// absolute and relative paths.128/// absolute and relative paths.
129/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
130/// On WASI, both paths should be encoded as valid UTF-8.
131/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
120pub fn updateFileAbsolute(132pub fn updateFileAbsolute(
121 source_path: []const u8,133 source_path: []const u8,
122 dest_path: []const u8,134 dest_path: []const u8,
...@@ -131,6 +143,9 @@ pub fn updateFileAbsolute(...@@ -131,6 +143,9 @@ pub fn updateFileAbsolute(
131/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`143/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
132/// are absolute. See `Dir.copyFile` for a function that operates on both144/// are absolute. See `Dir.copyFile` for a function that operates on both
133/// absolute and relative paths.145/// absolute and relative paths.
146/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
147/// On WASI, both paths should be encoded as valid UTF-8.
148/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
134pub fn copyFileAbsolute(149pub fn copyFileAbsolute(
135 source_path: []const u8,150 source_path: []const u8,
136 dest_path: []const u8,151 dest_path: []const u8,
...@@ -145,24 +160,30 @@ pub fn copyFileAbsolute(...@@ -145,24 +160,30 @@ pub fn copyFileAbsolute(
145/// Create a new directory, based on an absolute path.160/// Create a new directory, based on an absolute path.
146/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates161/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
147/// on both absolute and relative paths.162/// on both absolute and relative paths.
163/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
164/// On WASI, `absolute_path` should be encoded as valid UTF-8.
165/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
148pub fn makeDirAbsolute(absolute_path: []const u8) !void {166pub fn makeDirAbsolute(absolute_path: []const u8) !void {
149 assert(path.isAbsolute(absolute_path));167 assert(path.isAbsolute(absolute_path));
150 return os.mkdir(absolute_path, Dir.default_mode);168 return os.mkdir(absolute_path, Dir.default_mode);
151}169}
152170
153/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF-8-encoded string.171/// Same as `makeDirAbsolute` except the parameter is null-terminated.
154pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {172pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
155 assert(path.isAbsoluteZ(absolute_path_z));173 assert(path.isAbsoluteZ(absolute_path_z));
156 return os.mkdirZ(absolute_path_z, Dir.default_mode);174 return os.mkdirZ(absolute_path_z, Dir.default_mode);
157}175}
158176
159/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16-encoded string.177/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.
160pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {178pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
161 assert(path.isAbsoluteWindowsW(absolute_path_w));179 assert(path.isAbsoluteWindowsW(absolute_path_w));
162 return os.mkdirW(absolute_path_w, Dir.default_mode);180 return os.mkdirW(absolute_path_w, Dir.default_mode);
163}181}
164182
165/// Same as `Dir.deleteDir` except the path is absolute.183/// Same as `Dir.deleteDir` except the path is absolute.
184/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
185/// On WASI, `dir_path` should be encoded as valid UTF-8.
186/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
166pub fn deleteDirAbsolute(dir_path: []const u8) !void {187pub fn deleteDirAbsolute(dir_path: []const u8) !void {
167 assert(path.isAbsolute(dir_path));188 assert(path.isAbsolute(dir_path));
168 return os.rmdir(dir_path);189 return os.rmdir(dir_path);
...@@ -181,6 +202,9 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {...@@ -181,6 +202,9 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {
181}202}
182203
183/// Same as `Dir.rename` except the paths are absolute.204/// Same as `Dir.rename` except the paths are absolute.
205/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
206/// On WASI, both paths should be encoded as valid UTF-8.
207/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
184pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {208pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
185 assert(path.isAbsolute(old_path));209 assert(path.isAbsolute(old_path));
186 assert(path.isAbsolute(new_path));210 assert(path.isAbsolute(new_path));
...@@ -211,7 +235,7 @@ pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_su...@@ -211,7 +235,7 @@ pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_su
211 return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);235 return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
212}236}
213237
214/// Same as `rename` except the parameters are UTF16LE, NT prefixed.238/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
215/// This function is Windows-only.239/// This function is Windows-only.
216pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {240pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
217 return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);241 return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
...@@ -240,6 +264,9 @@ pub fn defaultWasiCwd() std.os.wasi.fd_t {...@@ -240,6 +264,9 @@ pub fn defaultWasiCwd() std.os.wasi.fd_t {
240/// See `openDirAbsoluteZ` for a function that accepts a null-terminated path.264/// See `openDirAbsoluteZ` for a function that accepts a null-terminated path.
241///265///
242/// Asserts that the path parameter has no null bytes.266/// Asserts that the path parameter has no null bytes.
267/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
268/// On WASI, `absolute_path` should be encoded as valid UTF-8.
269/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
243pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {270pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
244 assert(path.isAbsolute(absolute_path));271 assert(path.isAbsolute(absolute_path));
245 return cwd().openDir(absolute_path, flags);272 return cwd().openDir(absolute_path, flags);
...@@ -262,6 +289,9 @@ pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptio...@@ -262,6 +289,9 @@ pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptio
262/// operates on both absolute and relative paths.289/// operates on both absolute and relative paths.
263/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function290/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function
264/// that accepts a null-terminated path.291/// that accepts a null-terminated path.
292/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
293/// On WASI, `absolute_path` should be encoded as valid UTF-8.
294/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
265pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {295pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
266 assert(path.isAbsolute(absolute_path));296 assert(path.isAbsolute(absolute_path));
267 return cwd().openFile(absolute_path, flags);297 return cwd().openFile(absolute_path, flags);
...@@ -280,11 +310,13 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi...@@ -280,11 +310,13 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
280}310}
281311
282/// Test accessing `path`.312/// Test accessing `path`.
283/// `path` is UTF-8-encoded.
284/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.313/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
285/// For example, instead of testing if a file exists and then opening it, just314/// For example, instead of testing if a file exists and then opening it, just
286/// open it and handle the error for file not found.315/// open it and handle the error for file not found.
287/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.316/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
317/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
318/// On WASI, `absolute_path` should be encoded as valid UTF-8.
319/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
288pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {320pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
289 assert(path.isAbsolute(absolute_path));321 assert(path.isAbsolute(absolute_path));
290 try cwd().access(absolute_path, flags);322 try cwd().access(absolute_path, flags);
...@@ -306,6 +338,9 @@ pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir...@@ -306,6 +338,9 @@ pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir
306/// operates on both absolute and relative paths.338/// operates on both absolute and relative paths.
307/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function339/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function
308/// that accepts a null-terminated path.340/// that accepts a null-terminated path.
341/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
342/// On WASI, `absolute_path` should be encoded as valid UTF-8.
343/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
309pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {344pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
310 assert(path.isAbsolute(absolute_path));345 assert(path.isAbsolute(absolute_path));
311 return cwd().createFile(absolute_path, flags);346 return cwd().createFile(absolute_path, flags);
...@@ -327,6 +362,9 @@ pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFl...@@ -327,6 +362,9 @@ pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFl
327/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that362/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that
328/// operates on both absolute and relative paths.363/// operates on both absolute and relative paths.
329/// Asserts that the path parameter has no null bytes.364/// Asserts that the path parameter has no null bytes.
365/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
366/// On WASI, `absolute_path` should be encoded as valid UTF-8.
367/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
330pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {368pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
331 assert(path.isAbsolute(absolute_path));369 assert(path.isAbsolute(absolute_path));
332 return cwd().deleteFile(absolute_path);370 return cwd().deleteFile(absolute_path);
...@@ -349,6 +387,9 @@ pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!...@@ -349,6 +387,9 @@ pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!
349/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that387/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that
350/// operates on both absolute and relative paths.388/// operates on both absolute and relative paths.
351/// Asserts that the path parameter has no null bytes.389/// Asserts that the path parameter has no null bytes.
390/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
391/// On WASI, `absolute_path` should be encoded as valid UTF-8.
392/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
352pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {393pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
353 assert(path.isAbsolute(absolute_path));394 assert(path.isAbsolute(absolute_path));
354 const dirname = path.dirname(absolute_path) orelse return error{395 const dirname = path.dirname(absolute_path) orelse return error{
...@@ -364,6 +405,9 @@ pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {...@@ -364,6 +405,9 @@ pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
364}405}
365406
366/// Same as `Dir.readLink`, except it asserts the path is absolute.407/// Same as `Dir.readLink`, except it asserts the path is absolute.
408/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
409/// On WASI, `pathname` should be encoded as valid UTF-8.
410/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
367pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {411pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
368 assert(path.isAbsolute(pathname));412 assert(path.isAbsolute(pathname));
369 return os.readlink(pathname, buffer);413 return os.readlink(pathname, buffer);
...@@ -387,6 +431,9 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8)...@@ -387,6 +431,9 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8)
387/// one; the latter case is known as a dangling link.431/// one; the latter case is known as a dangling link.
388/// If `sym_link_path` exists, it will not be overwritten.432/// If `sym_link_path` exists, it will not be overwritten.
389/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.433/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
434/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
435/// On WASI, both paths should be encoded as valid UTF-8.
436/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
390pub fn symLinkAbsolute(437pub fn symLinkAbsolute(
391 target_path: []const u8,438 target_path: []const u8,
392 sym_link_path: []const u8,439 sym_link_path: []const u8,
...@@ -402,7 +449,7 @@ pub fn symLinkAbsolute(...@@ -402,7 +449,7 @@ pub fn symLinkAbsolute(
402 return os.symlink(target_path, sym_link_path);449 return os.symlink(target_path, sym_link_path);
403}450}
404451
405/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 encoded.452/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 LE encoded.
406/// Note that this function will by default try creating a symbolic link to a file. If you would453/// Note that this function will by default try creating a symbolic link to a file. If you would
407/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.454/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
408/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.455/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
...@@ -426,27 +473,14 @@ pub fn symLinkAbsoluteZ(...@@ -426,27 +473,14 @@ pub fn symLinkAbsoluteZ(
426 assert(path.isAbsoluteZ(target_path_c));473 assert(path.isAbsoluteZ(target_path_c));
427 assert(path.isAbsoluteZ(sym_link_path_c));474 assert(path.isAbsoluteZ(sym_link_path_c));
428 if (builtin.os.tag == .windows) {475 if (builtin.os.tag == .windows) {
429 const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);476 const target_path_w = try os.windows.cStrToPrefixedFileW(null, target_path_c);
430 const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);477 const sym_link_path_w = try os.windows.cStrToPrefixedFileW(null, sym_link_path_c);
431 return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);478 return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
432 }479 }
433 return os.symlinkZ(target_path_c, sym_link_path_c);480 return os.symlinkZ(target_path_c, sym_link_path_c);
434}481}
435482
436pub const OpenSelfExeError = error{483pub const OpenSelfExeError = os.OpenError || SelfExePathError || os.FlockError;
437 SharingViolation,
438 PathAlreadyExists,
439 FileNotFound,
440 AccessDenied,
441 PipeBusy,
442 NameTooLong,
443 /// On Windows, file paths must be valid Unicode.
444 InvalidUtf8,
445 /// On Windows, file paths cannot contain these characters:
446 /// '/', '*', '?', '"', '<', '>', '|'
447 BadPathName,
448 Unexpected,
449} || os.OpenError || SelfExePathError || os.FlockError;
450484
451pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {485pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
452 if (builtin.os.tag == .linux) {486 if (builtin.os.tag == .linux) {
...@@ -469,7 +503,45 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {...@@ -469,7 +503,45 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
469 return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags);503 return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags);
470}504}
471505
472pub const SelfExePathError = os.ReadLinkError || os.SysCtlError || os.RealPathError;506// This is os.ReadLinkError || os.RealPathError with impossible errors excluded
507pub const SelfExePathError = error{
508 FileNotFound,
509 AccessDenied,
510 NameTooLong,
511 NotSupported,
512 NotDir,
513 SymLinkLoop,
514 InputOutput,
515 FileTooBig,
516 IsDir,
517 ProcessFdQuotaExceeded,
518 SystemFdQuotaExceeded,
519 NoDevice,
520 SystemResources,
521 NoSpaceLeft,
522 FileSystem,
523 BadPathName,
524 DeviceBusy,
525 SharingViolation,
526 PipeBusy,
527 NotLink,
528 PathAlreadyExists,
529 InvalidHandle,
530
531 /// On Windows, `\\server` or `\\server\share` was not found.
532 NetworkNotFound,
533
534 /// On Windows, antivirus software is enabled by default. It can be
535 /// disabled, but Windows Update sometimes ignores the user's preference
536 /// and re-enables it. When enabled, antivirus software on Windows
537 /// intercepts file system operations and makes them significantly slower
538 /// in addition to possibly failing with this error code.
539 AntivirusInterference,
540
541 /// On Windows, the volume does not contain a recognized file system. File
542 /// system drivers might not be loaded, or the volume may be corrupt.
543 UnrecognizedVolume,
544} || os.SysCtlError;
473545
474/// `selfExePath` except allocates the result on the heap.546/// `selfExePath` except allocates the result on the heap.
475/// Caller owns returned memory.547/// Caller owns returned memory.
...@@ -491,6 +563,8 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {...@@ -491,6 +563,8 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {
491/// This function may return an error if the current executable563/// This function may return an error if the current executable
492/// was deleted after spawning.564/// was deleted after spawning.
493/// Returned value is a slice of out_buffer.565/// Returned value is a slice of out_buffer.
566/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
567/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
494///568///
495/// On Linux, depends on procfs being mounted. If the currently executing binary has569/// On Linux, depends on procfs being mounted. If the currently executing binary has
496/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.570/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
...@@ -505,15 +579,31 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {...@@ -505,15 +579,31 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
505 if (rc != 0) return error.NameTooLong;579 if (rc != 0) return error.NameTooLong;
506580
507 var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;581 var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
508 const real_path = try std.os.realpathZ(&symlink_path_buf, &real_path_buf);582 const real_path = std.os.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) {
583 error.InvalidWtf8 => unreachable, // Windows-only
584 error.NetworkNotFound => unreachable, // Windows-only
585 else => |e| return e,
586 };
509 if (real_path.len > out_buffer.len) return error.NameTooLong;587 if (real_path.len > out_buffer.len) return error.NameTooLong;
510 const result = out_buffer[0..real_path.len];588 const result = out_buffer[0..real_path.len];
511 @memcpy(result, real_path);589 @memcpy(result, real_path);
512 return result;590 return result;
513 }591 }
514 switch (builtin.os.tag) {592 switch (builtin.os.tag) {
515 .linux => return os.readlinkZ("/proc/self/exe", out_buffer),593 .linux => return os.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) {
516 .solaris, .illumos => return os.readlinkZ("/proc/self/path/a.out", out_buffer),594 error.InvalidUtf8 => unreachable, // WASI-only
595 error.InvalidWtf8 => unreachable, // Windows-only
596 error.UnsupportedReparsePointType => unreachable, // Windows-only
597 error.NetworkNotFound => unreachable, // Windows-only
598 else => |e| return e,
599 },
600 .solaris, .illumos => return os.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) {
601 error.InvalidUtf8 => unreachable, // WASI-only
602 error.InvalidWtf8 => unreachable, // Windows-only
603 error.UnsupportedReparsePointType => unreachable, // Windows-only
604 error.NetworkNotFound => unreachable, // Windows-only
605 else => |e| return e,
606 },
517 .freebsd, .dragonfly => {607 .freebsd, .dragonfly => {
518 var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC, os.KERN.PROC_PATHNAME, -1 };608 var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC, os.KERN.PROC_PATHNAME, -1 };
519 var out_len: usize = out_buffer.len;609 var out_len: usize = out_buffer.len;
...@@ -537,7 +627,11 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {...@@ -537,7 +627,11 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
537 if (mem.indexOf(u8, argv0, "/") != null) {627 if (mem.indexOf(u8, argv0, "/") != null) {
538 // argv[0] is a path (relative or absolute): use realpath(3) directly628 // argv[0] is a path (relative or absolute): use realpath(3) directly
539 var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;629 var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
540 const real_path = try os.realpathZ(os.argv[0], &real_path_buf);630 const real_path = os.realpathZ(os.argv[0], &real_path_buf) catch |err| switch (err) {
631 error.InvalidWtf8 => unreachable, // Windows-only
632 error.NetworkNotFound => unreachable, // Windows-only
633 else => |e| return e,
634 };
541 if (real_path.len > out_buffer.len)635 if (real_path.len > out_buffer.len)
542 return error.NameTooLong;636 return error.NameTooLong;
543 const result = out_buffer[0..real_path.len];637 const result = out_buffer[0..real_path.len];
...@@ -575,7 +669,10 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {...@@ -575,7 +669,10 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
575 // symlink, not the path that the symlink points to. We want the path669 // symlink, not the path that the symlink points to. We want the path
576 // that the symlink points to, though, so we need to get the realpath.670 // that the symlink points to, though, so we need to get the realpath.
577 const pathname_w = try os.windows.wToPrefixedFileW(null, image_path_name);671 const pathname_w = try os.windows.wToPrefixedFileW(null, image_path_name);
578 return std.fs.cwd().realpathW(pathname_w.span(), out_buffer);672 return std.fs.cwd().realpathW(pathname_w.span(), out_buffer) catch |err| switch (err) {
673 error.InvalidWtf8 => unreachable,
674 else => |e| return e,
675 };
579 },676 },
580 else => @compileError("std.fs.selfExePath not supported for this target"),677 else => @compileError("std.fs.selfExePath not supported for this target"),
581 }678 }
...@@ -599,6 +696,8 @@ pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 {...@@ -599,6 +696,8 @@ pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 {
599696
600/// Get the directory path that contains the current executable.697/// Get the directory path that contains the current executable.
601/// Returned value is a slice of out_buffer.698/// Returned value is a slice of out_buffer.
699/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
700/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
602pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {701pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
603 const self_exe_path = try selfExePath(out_buffer);702 const self_exe_path = try selfExePath(out_buffer);
604 // Assume that the OS APIs return absolute paths, and therefore dirname703 // Assume that the OS APIs return absolute paths, and therefore dirname
...@@ -607,6 +706,8 @@ pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {...@@ -607,6 +706,8 @@ pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
607}706}
608707
609/// `realpath`, except caller must free the returned memory.708/// `realpath`, except caller must free the returned memory.
709/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
710/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
610/// See also `Dir.realpath`.711/// See also `Dir.realpath`.
611pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {712pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
612 // Use of MAX_PATH_BYTES here is valid as the realpath function does not713 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
lib/std/fs/Dir.zig+128-36
...@@ -9,7 +9,14 @@ pub const Entry = struct {...@@ -9,7 +9,14 @@ pub const Entry = struct {
9 pub const Kind = File.Kind;9 pub const Kind = File.Kind;
10};10};
1111
12const IteratorError = error{ AccessDenied, SystemResources } || posix.UnexpectedError;12const IteratorError = error{
13 AccessDenied,
14 SystemResources,
15 /// WASI-only. The path of an entry could not be encoded as valid UTF-8.
16 /// WASI is unable to handle paths that cannot be encoded as well-formed UTF-8.
17 /// https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
18 InvalidUtf8,
19} || posix.UnexpectedError;
1320
14pub const Iterator = switch (builtin.os.tag) {21pub const Iterator = switch (builtin.os.tag) {
15 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {22 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {
...@@ -445,13 +452,12 @@ pub const Iterator = switch (builtin.os.tag) {...@@ -445,13 +452,12 @@ pub const Iterator = switch (builtin.os.tag) {
445 self.index = self.buf.len;452 self.index = self.buf.len;
446 }453 }
447454
448 const name_utf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];455 const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
449456
450 if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' }))457 if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' }))
451 continue;458 continue;
452 // Trust that Windows gives us valid UTF-16LE459 const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le);
453 const name_utf8_len = std.unicode.utf16LeToUtf8(self.name_data[0..], name_utf16le) catch unreachable;460 const name_wtf8 = self.name_data[0..name_wtf8_len];
454 const name_utf8 = self.name_data[0..name_utf8_len];
455 const kind: Entry.Kind = blk: {461 const kind: Entry.Kind = blk: {
456 const attrs = dir_info.FileAttributes;462 const attrs = dir_info.FileAttributes;
457 if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;463 if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
...@@ -459,7 +465,7 @@ pub const Iterator = switch (builtin.os.tag) {...@@ -459,7 +465,7 @@ pub const Iterator = switch (builtin.os.tag) {
459 break :blk .file;465 break :blk .file;
460 };466 };
461 return Entry{467 return Entry{
462 .name = name_utf8,468 .name = name_wtf8,
463 .kind = kind,469 .kind = kind,
464 };470 };
465 }471 }
...@@ -516,6 +522,7 @@ pub const Iterator = switch (builtin.os.tag) {...@@ -516,6 +522,7 @@ pub const Iterator = switch (builtin.os.tag) {
516 .INVAL => unreachable,522 .INVAL => unreachable,
517 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.523 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
518 .NOTCAPABLE => return error.AccessDenied,524 .NOTCAPABLE => return error.AccessDenied,
525 .ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
519 else => |err| return posix.unexpectedErrno(err),526 else => |err| return posix.unexpectedErrno(err),
520 }527 }
521 if (bufused == 0) return null;528 if (bufused == 0) return null;
...@@ -743,7 +750,11 @@ pub const OpenError = error{...@@ -743,7 +750,11 @@ pub const OpenError = error{
743 SystemFdQuotaExceeded,750 SystemFdQuotaExceeded,
744 NoDevice,751 NoDevice,
745 SystemResources,752 SystemResources,
753 /// WASI-only; file paths must be valid UTF-8.
746 InvalidUtf8,754 InvalidUtf8,
755 /// Windows-only; file paths provided by the user must be valid WTF-8.
756 /// https://simonsapin.github.io/wtf-8/
757 InvalidWtf8,
747 BadPathName,758 BadPathName,
748 DeviceBusy,759 DeviceBusy,
749 /// On Windows, `\\server` or `\\server\share` was not found.760 /// On Windows, `\\server` or `\\server\share` was not found.
...@@ -759,6 +770,9 @@ pub fn close(self: *Dir) void {...@@ -759,6 +770,9 @@ pub fn close(self: *Dir) void {
759/// To create a new file, see `createFile`.770/// To create a new file, see `createFile`.
760/// Call `File.close` to release the resource.771/// Call `File.close` to release the resource.
761/// Asserts that the path parameter has no null bytes.772/// Asserts that the path parameter has no null bytes.
773/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
774/// On WASI, `sub_path` should be encoded as valid UTF-8.
775/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
762pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {776pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
763 if (builtin.os.tag == .windows) {777 if (builtin.os.tag == .windows) {
764 const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);778 const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
...@@ -911,6 +925,9 @@ pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File...@@ -911,6 +925,9 @@ pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File
911/// Creates, opens, or overwrites a file with write access.925/// Creates, opens, or overwrites a file with write access.
912/// Call `File.close` on the result when done.926/// Call `File.close` on the result when done.
913/// Asserts that the path parameter has no null bytes.927/// Asserts that the path parameter has no null bytes.
928/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
929/// On WASI, `sub_path` should be encoded as valid UTF-8.
930/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
914pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {931pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
915 if (builtin.os.tag == .windows) {932 if (builtin.os.tag == .windows) {
916 const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);933 const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
...@@ -1060,18 +1077,21 @@ pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags)...@@ -1060,18 +1077,21 @@ pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags)
1060/// Creates a single directory with a relative or absolute path.1077/// Creates a single directory with a relative or absolute path.
1061/// To create multiple directories to make an entire path, see `makePath`.1078/// To create multiple directories to make an entire path, see `makePath`.
1062/// To operate on only absolute paths, see `makeDirAbsolute`.1079/// To operate on only absolute paths, see `makeDirAbsolute`.
1080/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1081/// On WASI, `sub_path` should be encoded as valid UTF-8.
1082/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1063pub fn makeDir(self: Dir, sub_path: []const u8) !void {1083pub fn makeDir(self: Dir, sub_path: []const u8) !void {
1064 try posix.mkdirat(self.fd, sub_path, default_mode);1084 try posix.mkdirat(self.fd, sub_path, default_mode);
1065}1085}
10661086
1067/// Creates a single directory with a relative or absolute null-terminated UTF-8-encoded path.1087/// Same as `makeDir`, but `sub_path` is null-terminated.
1068/// To create multiple directories to make an entire path, see `makePath`.1088/// To create multiple directories to make an entire path, see `makePath`.
1069/// To operate on only absolute paths, see `makeDirAbsoluteZ`.1089/// To operate on only absolute paths, see `makeDirAbsoluteZ`.
1070pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {1090pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
1071 try posix.mkdiratZ(self.fd, sub_path, default_mode);1091 try posix.mkdiratZ(self.fd, sub_path, default_mode);
1072}1092}
10731093
1074/// Creates a single directory with a relative or absolute null-terminated WTF-16-encoded path.1094/// Creates a single directory with a relative or absolute null-terminated WTF-16 LE-encoded path.
1075/// To create multiple directories to make an entire path, see `makePath`.1095/// To create multiple directories to make an entire path, see `makePath`.
1076/// To operate on only absolute paths, see `makeDirAbsoluteW`.1096/// To operate on only absolute paths, see `makeDirAbsoluteW`.
1077pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {1097pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
...@@ -1083,6 +1103,9 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {...@@ -1083,6 +1103,9 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
1083/// Returns success if the path already exists and is a directory.1103/// Returns success if the path already exists and is a directory.
1084/// This function is not atomic, and if it returns an error, the file system may1104/// This function is not atomic, and if it returns an error, the file system may
1085/// have been modified regardless.1105/// have been modified regardless.
1106/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1107/// On WASI, `sub_path` should be encoded as valid UTF-8.
1108/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1086///1109///
1087/// Paths containing `..` components are handled differently depending on the platform:1110/// Paths containing `..` components are handled differently depending on the platform:
1088/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning1111/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
...@@ -1119,16 +1142,17 @@ pub fn makePath(self: Dir, sub_path: []const u8) !void {...@@ -1119,16 +1142,17 @@ pub fn makePath(self: Dir, sub_path: []const u8) !void {
1119 }1142 }
1120}1143}
11211144
1122/// Calls makeOpenDirAccessMaskW iteratively to make an entire path1145/// Windows only. Calls makeOpenDirAccessMaskW iteratively to make an entire path
1123/// (i.e. creating any parent directories that do not exist).1146/// (i.e. creating any parent directories that do not exist).
1124/// Opens the dir if the path already exists and is a directory.1147/// Opens the dir if the path already exists and is a directory.
1125/// This function is not atomic, and if it returns an error, the file system may1148/// This function is not atomic, and if it returns an error, the file system may
1126/// have been modified regardless.1149/// have been modified regardless.
1150/// `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1127fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {1151fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
1128 const w = std.os.windows;1152 const w = std.os.windows;
1129 var it = try fs.path.componentIterator(sub_path);1153 var it = try fs.path.componentIterator(sub_path);
1130 // If there are no components in the path, then create a dummy component with the full path.1154 // If there are no components in the path, then create a dummy component with the full path.
1131 var component = it.last() orelse fs.path.NativeUtf8ComponentIterator.Component{1155 var component = it.last() orelse fs.path.NativeComponentIterator.Component{
1132 .name = "",1156 .name = "",
1133 .path = sub_path,1157 .path = sub_path,
1134 };1158 };
...@@ -1156,7 +1180,9 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no...@@ -1156,7 +1180,9 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no
1156/// This function performs `makePath`, followed by `openDir`.1180/// This function performs `makePath`, followed by `openDir`.
1157/// If supported by the OS, this operation is atomic. It is not atomic on1181/// If supported by the OS, this operation is atomic. It is not atomic on
1158/// all operating systems.1182/// all operating systems.
1159/// On Windows, this function performs `makeOpenPathAccessMaskW`.1183/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1184/// On WASI, `sub_path` should be encoded as valid UTF-8.
1185/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1160pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {1186pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
1161 return switch (builtin.os.tag) {1187 return switch (builtin.os.tag) {
1162 .windows => {1188 .windows => {
...@@ -1185,6 +1211,10 @@ pub const RealPathError = posix.RealPathError;...@@ -1185,6 +1211,10 @@ pub const RealPathError = posix.RealPathError;
1185/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this1211/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
1186/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`1212/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
1187/// argument.1213/// argument.
1214/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1215/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1216/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1217/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1188/// This function is not universally supported by all platforms.1218/// This function is not universally supported by all platforms.
1189/// Currently supported hosts are: Linux, macOS, and Windows.1219/// Currently supported hosts are: Linux, macOS, and Windows.
1190/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.1220/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
...@@ -1224,6 +1254,7 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE...@@ -1224,6 +1254,7 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
1224 error.FileLocksNotSupported => return error.Unexpected,1254 error.FileLocksNotSupported => return error.Unexpected,
1225 error.FileBusy => return error.Unexpected,1255 error.FileBusy => return error.Unexpected,
1226 error.WouldBlock => return error.Unexpected,1256 error.WouldBlock => return error.Unexpected,
1257 error.InvalidUtf8 => unreachable, // WASI-only
1227 else => |e| return e,1258 else => |e| return e,
1228 };1259 };
1229 defer posix.close(fd);1260 defer posix.close(fd);
...@@ -1246,7 +1277,8 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE...@@ -1246,7 +1277,8 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
1246 return result;1277 return result;
1247}1278}
12481279
1249/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded.1280/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded.
1281/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1250/// See also `Dir.realpath`, `realpathW`.1282/// See also `Dir.realpath`, `realpathW`.
1251pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {1283pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {
1252 const w = std.os.windows;1284 const w = std.os.windows;
...@@ -1272,16 +1304,7 @@ pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathErr...@@ -1272,16 +1304,7 @@ pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathErr
1272 var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;1304 var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
1273 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);1305 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
1274 var big_out_buf: [fs.MAX_PATH_BYTES]u8 = undefined;1306 var big_out_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
1275 const end_index = std.unicode.utf16leToUtf8(&big_out_buf, wide_slice) catch |e| switch (e) {1307 const end_index = std.unicode.wtf16LeToWtf8(&big_out_buf, wide_slice);
1276 // TODO: Windows file paths can be arbitrary arrays of u16 values and
1277 // must not fail with InvalidUtf8.
1278 error.DanglingSurrogateHalf,
1279 error.ExpectedSecondSurrogateHalf,
1280 error.UnexpectedSecondSurrogateHalf,
1281 error.CodepointTooLarge,
1282 error.Utf8CannotEncodeSurrogateHalf,
1283 => return error.InvalidUtf8,
1284 };
1285 if (end_index > out_buffer.len)1308 if (end_index > out_buffer.len)
1286 return error.NameTooLong;1309 return error.NameTooLong;
1287 const result = out_buffer[0..end_index];1310 const result = out_buffer[0..end_index];
...@@ -1344,6 +1367,9 @@ pub const OpenDirOptions = struct {...@@ -1344,6 +1367,9 @@ pub const OpenDirOptions = struct {
1344/// open until `close` is called on the result.1367/// open until `close` is called on the result.
1345/// The directory cannot be iterated unless the `iterate` option is set to `true`.1368/// The directory cannot be iterated unless the `iterate` option is set to `true`.
1346///1369///
1370/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1371/// On WASI, `sub_path` should be encoded as valid UTF-8.
1372/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1347/// Asserts that the path parameter has no null bytes.1373/// Asserts that the path parameter has no null bytes.
1348pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {1374pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
1349 switch (builtin.os.tag) {1375 switch (builtin.os.tag) {
...@@ -1428,7 +1454,7 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) Open...@@ -1428,7 +1454,7 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) Open
1428 }1454 }
1429}1455}
14301456
1431/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.1457/// Same as `openDir` except the path parameter is WTF-16 LE encoded, NT-prefixed.
1432/// This function asserts the target OS is Windows.1458/// This function asserts the target OS is Windows.
1433pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {1459pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
1434 const w = std.os.windows;1460 const w = std.os.windows;
...@@ -1518,6 +1544,9 @@ fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u3...@@ -1518,6 +1544,9 @@ fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u3
1518pub const DeleteFileError = posix.UnlinkError;1544pub const DeleteFileError = posix.UnlinkError;
15191545
1520/// Delete a file name and possibly the file it refers to, based on an open directory handle.1546/// Delete a file name and possibly the file it refers to, based on an open directory handle.
1547/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1548/// On WASI, `sub_path` should be encoded as valid UTF-8.
1549/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1521/// Asserts that the path parameter has no null bytes.1550/// Asserts that the path parameter has no null bytes.
1522pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {1551pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
1523 if (builtin.os.tag == .windows) {1552 if (builtin.os.tag == .windows) {
...@@ -1553,7 +1582,7 @@ pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {...@@ -1553,7 +1582,7 @@ pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
1553 };1582 };
1554}1583}
15551584
1556/// Same as `deleteFile` except the parameter is WTF-16 encoded.1585/// Same as `deleteFile` except the parameter is WTF-16 LE encoded.
1557pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {1586pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
1558 posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {1587 posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
1559 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR1588 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
...@@ -1572,7 +1601,11 @@ pub const DeleteDirError = error{...@@ -1572,7 +1601,11 @@ pub const DeleteDirError = error{
1572 NotDir,1601 NotDir,
1573 SystemResources,1602 SystemResources,
1574 ReadOnlyFileSystem,1603 ReadOnlyFileSystem,
1604 /// WASI-only; file paths must be valid UTF-8.
1575 InvalidUtf8,1605 InvalidUtf8,
1606 /// Windows-only; file paths provided by the user must be valid WTF-8.
1607 /// https://simonsapin.github.io/wtf-8/
1608 InvalidWtf8,
1576 BadPathName,1609 BadPathName,
1577 /// On Windows, `\\server` or `\\server\share` was not found.1610 /// On Windows, `\\server` or `\\server\share` was not found.
1578 NetworkNotFound,1611 NetworkNotFound,
...@@ -1581,6 +1614,9 @@ pub const DeleteDirError = error{...@@ -1581,6 +1614,9 @@ pub const DeleteDirError = error{
15811614
1582/// Returns `error.DirNotEmpty` if the directory is not empty.1615/// Returns `error.DirNotEmpty` if the directory is not empty.
1583/// To delete a directory recursively, see `deleteTree`.1616/// To delete a directory recursively, see `deleteTree`.
1617/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1618/// On WASI, `sub_path` should be encoded as valid UTF-8.
1619/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1584/// Asserts that the path parameter has no null bytes.1620/// Asserts that the path parameter has no null bytes.
1585pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {1621pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
1586 if (builtin.os.tag == .windows) {1622 if (builtin.os.tag == .windows) {
...@@ -1605,7 +1641,7 @@ pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {...@@ -1605,7 +1641,7 @@ pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
1605 };1641 };
1606}1642}
16071643
1608/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.1644/// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed.
1609/// This function is Windows-only.1645/// This function is Windows-only.
1610pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {1646pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
1611 posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {1647 posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
...@@ -1620,6 +1656,9 @@ pub const RenameError = posix.RenameError;...@@ -1620,6 +1656,9 @@ pub const RenameError = posix.RenameError;
1620/// If new_sub_path already exists, it will be replaced.1656/// If new_sub_path already exists, it will be replaced.
1621/// Renaming a file over an existing directory or a directory1657/// Renaming a file over an existing directory or a directory
1622/// over an existing file will fail with `error.IsDir` or `error.NotDir`1658/// over an existing file will fail with `error.IsDir` or `error.NotDir`
1659/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1660/// On WASI, both paths should be encoded as valid UTF-8.
1661/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1623pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {1662pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
1624 return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);1663 return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
1625}1664}
...@@ -1629,7 +1668,7 @@ pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]co...@@ -1629,7 +1668,7 @@ pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]co
1629 return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);1668 return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
1630}1669}
16311670
1632/// Same as `rename` except the parameters are UTF16LE, NT prefixed.1671/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
1633/// This function is Windows-only.1672/// This function is Windows-only.
1634pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {1673pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
1635 return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);1674 return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
...@@ -1647,6 +1686,9 @@ pub const SymLinkFlags = struct {...@@ -1647,6 +1686,9 @@ pub const SymLinkFlags = struct {
1647/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent1686/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1648/// one; the latter case is known as a dangling link.1687/// one; the latter case is known as a dangling link.
1649/// If `sym_link_path` exists, it will not be overwritten.1688/// If `sym_link_path` exists, it will not be overwritten.
1689/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1690/// On WASI, both paths should be encoded as valid UTF-8.
1691/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1650pub fn symLink(1692pub fn symLink(
1651 self: Dir,1693 self: Dir,
1652 target_path: []const u8,1694 target_path: []const u8,
...@@ -1662,7 +1704,7 @@ pub fn symLink(...@@ -1662,7 +1704,7 @@ pub fn symLink(
1662 // when converting to an NT namespaced path. CreateSymbolicLink in1704 // when converting to an NT namespaced path. CreateSymbolicLink in
1663 // symLinkW will handle the necessary conversion.1705 // symLinkW will handle the necessary conversion.
1664 var target_path_w: std.os.windows.PathSpace = undefined;1706 var target_path_w: std.os.windows.PathSpace = undefined;
1665 target_path_w.len = try std.unicode.utf8ToUtf16Le(&target_path_w.data, target_path);1707 target_path_w.len = try std.unicode.wtf8ToWtf16Le(&target_path_w.data, target_path);
1666 target_path_w.data[target_path_w.len] = 0;1708 target_path_w.data[target_path_w.len] = 0;
1667 const sym_link_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sym_link_path);1709 const sym_link_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sym_link_path);
1668 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);1710 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
...@@ -1698,7 +1740,7 @@ pub fn symLinkZ(...@@ -1698,7 +1740,7 @@ pub fn symLinkZ(
1698}1740}
16991741
1700/// Windows-only. Same as `symLink` except the pathname parameters1742/// Windows-only. Same as `symLink` except the pathname parameters
1701/// are null-terminated, WTF16 encoded.1743/// are WTF16 LE encoded.
1702pub fn symLinkW(1744pub fn symLinkW(
1703 self: Dir,1745 self: Dir,
1704 /// WTF-16, does not need to be NT-prefixed. The NT-prefixing1746 /// WTF-16, does not need to be NT-prefixed. The NT-prefixing
...@@ -1716,6 +1758,9 @@ pub const ReadLinkError = posix.ReadLinkError;...@@ -1716,6 +1758,9 @@ pub const ReadLinkError = posix.ReadLinkError;
1716/// Read value of a symbolic link.1758/// Read value of a symbolic link.
1717/// The return value is a slice of `buffer`, from index `0`.1759/// The return value is a slice of `buffer`, from index `0`.
1718/// Asserts that the path parameter has no null bytes.1760/// Asserts that the path parameter has no null bytes.
1761/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1762/// On WASI, `sub_path` should be encoded as valid UTF-8.
1763/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1719pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {1764pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
1720 if (builtin.os.tag == .wasi and !builtin.link_libc) {1765 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1721 return self.readLinkWasi(sub_path, buffer);1766 return self.readLinkWasi(sub_path, buffer);
...@@ -1733,7 +1778,7 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {...@@ -1733,7 +1778,7 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1733 return posix.readlinkat(self.fd, sub_path, buffer);1778 return posix.readlinkat(self.fd, sub_path, buffer);
1734}1779}
17351780
1736/// Same as `readLink`, except the `pathname` parameter is null-terminated.1781/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.
1737pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {1782pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
1738 if (builtin.os.tag == .windows) {1783 if (builtin.os.tag == .windows) {
1739 const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);1784 const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
...@@ -1743,7 +1788,7 @@ pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {...@@ -1743,7 +1788,7 @@ pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
1743}1788}
17441789
1745/// Windows-only. Same as `readLink` except the pathname parameter1790/// Windows-only. Same as `readLink` except the pathname parameter
1746/// is null-terminated, WTF16 encoded.1791/// is WTF16 LE encoded.
1747pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {1792pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
1748 return std.os.windows.ReadLink(self.fd, sub_path_w, buffer);1793 return std.os.windows.ReadLink(self.fd, sub_path_w, buffer);
1749}1794}
...@@ -1753,6 +1798,9 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {...@@ -1753,6 +1798,9 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
1753/// the situation is ambiguous. It could either mean that the entire file was read, and1798/// the situation is ambiguous. It could either mean that the entire file was read, and
1754/// it exactly fits the buffer, or it could mean the buffer was not big enough for the1799/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
1755/// entire file.1800/// entire file.
1801/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1802/// On WASI, `file_path` should be encoded as valid UTF-8.
1803/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1756pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {1804pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
1757 var file = try self.openFile(file_path, .{});1805 var file = try self.openFile(file_path, .{});
1758 defer file.close();1806 defer file.close();
...@@ -1763,6 +1811,9 @@ pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {...@@ -1763,6 +1811,9 @@ pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
17631811
1764/// On success, caller owns returned buffer.1812/// On success, caller owns returned buffer.
1765/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.1813/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1814/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1815/// On WASI, `file_path` should be encoded as valid UTF-8.
1816/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1766pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {1817pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
1767 return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);1818 return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);
1768}1819}
...@@ -1772,6 +1823,9 @@ pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8,...@@ -1772,6 +1823,9 @@ pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8,
1772/// If `size_hint` is specified the initial buffer size is calculated using1823/// If `size_hint` is specified the initial buffer size is calculated using
1773/// that value, otherwise the effective file size is used instead.1824/// that value, otherwise the effective file size is used instead.
1774/// Allows specifying alignment and a sentinel value.1825/// Allows specifying alignment and a sentinel value.
1826/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1827/// On WASI, `file_path` should be encoded as valid UTF-8.
1828/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1775pub fn readFileAllocOptions(1829pub fn readFileAllocOptions(
1776 self: Dir,1830 self: Dir,
1777 allocator: mem.Allocator,1831 allocator: mem.Allocator,
...@@ -1811,9 +1865,13 @@ pub const DeleteTreeError = error{...@@ -1811,9 +1865,13 @@ pub const DeleteTreeError = error{
1811 /// This error is unreachable if `sub_path` does not contain a path separator.1865 /// This error is unreachable if `sub_path` does not contain a path separator.
1812 NotDir,1866 NotDir,
18131867
1814 /// On Windows, file paths must be valid Unicode.1868 /// WASI-only; file paths must be valid UTF-8.
1815 InvalidUtf8,1869 InvalidUtf8,
18161870
1871 /// Windows-only; file paths provided by the user must be valid WTF-8.
1872 /// https://simonsapin.github.io/wtf-8/
1873 InvalidWtf8,
1874
1817 /// On Windows, file paths cannot contain these characters:1875 /// On Windows, file paths cannot contain these characters:
1818 /// '/', '*', '?', '"', '<', '>', '|'1876 /// '/', '*', '?', '"', '<', '>', '|'
1819 BadPathName,1877 BadPathName,
...@@ -1826,6 +1884,9 @@ pub const DeleteTreeError = error{...@@ -1826,6 +1884,9 @@ pub const DeleteTreeError = error{
1826/// removes it. If it cannot be removed because it is a non-empty directory,1884/// removes it. If it cannot be removed because it is a non-empty directory,
1827/// this function recursively removes its entries and then tries again.1885/// this function recursively removes its entries and then tries again.
1828/// This operation is not atomic on most file systems.1886/// This operation is not atomic on most file systems.
1887/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1888/// On WASI, `sub_path` should be encoded as valid UTF-8.
1889/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1829pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {1890pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1830 var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;1891 var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
18311892
...@@ -1879,6 +1940,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {...@@ -1879,6 +1940,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1879 error.SystemResources,1940 error.SystemResources,
1880 error.Unexpected,1941 error.Unexpected,
1881 error.InvalidUtf8,1942 error.InvalidUtf8,
1943 error.InvalidWtf8,
1882 error.BadPathName,1944 error.BadPathName,
1883 error.NetworkNotFound,1945 error.NetworkNotFound,
1884 error.DeviceBusy,1946 error.DeviceBusy,
...@@ -1910,6 +1972,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {...@@ -1910,6 +1972,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
19101972
1911 error.AccessDenied,1973 error.AccessDenied,
1912 error.InvalidUtf8,1974 error.InvalidUtf8,
1975 error.InvalidWtf8,
1913 error.SymLinkLoop,1976 error.SymLinkLoop,
1914 error.NameTooLong,1977 error.NameTooLong,
1915 error.SystemResources,1978 error.SystemResources,
...@@ -1973,6 +2036,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {...@@ -1973,6 +2036,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1973 error.SystemResources,2036 error.SystemResources,
1974 error.Unexpected,2037 error.Unexpected,
1975 error.InvalidUtf8,2038 error.InvalidUtf8,
2039 error.InvalidWtf8,
1976 error.BadPathName,2040 error.BadPathName,
1977 error.NetworkNotFound,2041 error.NetworkNotFound,
1978 error.DeviceBusy,2042 error.DeviceBusy,
...@@ -1994,6 +2058,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {...@@ -1994,6 +2058,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
19942058
1995 error.AccessDenied,2059 error.AccessDenied,
1996 error.InvalidUtf8,2060 error.InvalidUtf8,
2061 error.InvalidWtf8,
1997 error.SymLinkLoop,2062 error.SymLinkLoop,
1998 error.NameTooLong,2063 error.NameTooLong,
1999 error.SystemResources,2064 error.SystemResources,
...@@ -2022,6 +2087,9 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {...@@ -2022,6 +2087,9 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
20222087
2023/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.2088/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
2024/// This is slower than `deleteTree` but uses less stack space.2089/// This is slower than `deleteTree` but uses less stack space.
2090/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2091/// On WASI, `sub_path` should be encoded as valid UTF-8.
2092/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
2025pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {2093pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2026 return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);2094 return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
2027}2095}
...@@ -2074,6 +2142,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint...@@ -2074,6 +2142,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
2074 error.SystemResources,2142 error.SystemResources,
2075 error.Unexpected,2143 error.Unexpected,
2076 error.InvalidUtf8,2144 error.InvalidUtf8,
2145 error.InvalidWtf8,
2077 error.BadPathName,2146 error.BadPathName,
2078 error.NetworkNotFound,2147 error.NetworkNotFound,
2079 error.DeviceBusy,2148 error.DeviceBusy,
...@@ -2102,6 +2171,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint...@@ -2102,6 +2171,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
21022171
2103 error.AccessDenied,2172 error.AccessDenied,
2104 error.InvalidUtf8,2173 error.InvalidUtf8,
2174 error.InvalidWtf8,
2105 error.SymLinkLoop,2175 error.SymLinkLoop,
2106 error.NameTooLong,2176 error.NameTooLong,
2107 error.SystemResources,2177 error.SystemResources,
...@@ -2171,6 +2241,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File...@@ -2171,6 +2241,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
2171 error.SystemResources,2241 error.SystemResources,
2172 error.Unexpected,2242 error.Unexpected,
2173 error.InvalidUtf8,2243 error.InvalidUtf8,
2244 error.InvalidWtf8,
2174 error.BadPathName,2245 error.BadPathName,
2175 error.DeviceBusy,2246 error.DeviceBusy,
2176 error.NetworkNotFound,2247 error.NetworkNotFound,
...@@ -2189,6 +2260,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File...@@ -2189,6 +2260,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
21892260
2190 error.AccessDenied,2261 error.AccessDenied,
2191 error.InvalidUtf8,2262 error.InvalidUtf8,
2263 error.InvalidWtf8,
2192 error.SymLinkLoop,2264 error.SymLinkLoop,
2193 error.NameTooLong,2265 error.NameTooLong,
2194 error.SystemResources,2266 error.SystemResources,
...@@ -2209,6 +2281,9 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File...@@ -2209,6 +2281,9 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
2209pub const WriteFileError = File.WriteError || File.OpenError;2281pub const WriteFileError = File.WriteError || File.OpenError;
22102282
2211/// Deprecated: use `writeFile2`.2283/// Deprecated: use `writeFile2`.
2284/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2285/// On WASI, `sub_path` should be encoded as valid UTF-8.
2286/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
2212pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void {2287pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void {
2213 return writeFile2(self, .{2288 return writeFile2(self, .{
2214 .sub_path = sub_path,2289 .sub_path = sub_path,
...@@ -2218,6 +2293,9 @@ pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileErr...@@ -2218,6 +2293,9 @@ pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileErr
2218}2293}
22192294
2220pub const WriteFileOptions = struct {2295pub const WriteFileOptions = struct {
2296 /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2297 /// On WASI, `sub_path` should be encoded as valid UTF-8.
2298 /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
2221 sub_path: []const u8,2299 sub_path: []const u8,
2222 data: []const u8,2300 data: []const u8,
2223 flags: File.CreateFlags = .{},2301 flags: File.CreateFlags = .{},
...@@ -2232,8 +2310,10 @@ pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void {...@@ -2232,8 +2310,10 @@ pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void {
22322310
2233pub const AccessError = posix.AccessError;2311pub const AccessError = posix.AccessError;
22342312
2235/// Test accessing `path`.2313/// Test accessing `sub_path`.
2236/// `path` is UTF-8-encoded.2314/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2315/// On WASI, `sub_path` should be encoded as valid UTF-8.
2316/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
2237/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.2317/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
2238/// For example, instead of testing if a file exists and then opening it, just2318/// For example, instead of testing if a file exists and then opening it, just
2239/// open it and handle the error for file not found.2319/// open it and handle the error for file not found.
...@@ -2268,9 +2348,9 @@ pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) Access...@@ -2268,9 +2348,9 @@ pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) Access
2268}2348}
22692349
2270/// Same as `access` except asserts the target OS is Windows and the path parameter is2350/// Same as `access` except asserts the target OS is Windows and the path parameter is
2271/// * WTF-16 encoded2351/// * WTF-16 LE encoded
2272/// * null-terminated2352/// * null-terminated
2273/// * NtDll prefixed2353/// * relative or has the NT namespace prefix
2274/// TODO currently this ignores `flags`.2354/// TODO currently this ignores `flags`.
2275pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {2355pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
2276 _ = flags;2356 _ = flags;
...@@ -2292,6 +2372,9 @@ pub const PrevStatus = enum {...@@ -2292,6 +2372,9 @@ pub const PrevStatus = enum {
2292/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.2372/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
2293/// Returns the previous status of the file before updating.2373/// Returns the previous status of the file before updating.
2294/// If any of the directories do not exist for dest_path, they are created.2374/// If any of the directories do not exist for dest_path, they are created.
2375/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2376/// On WASI, both paths should be encoded as valid UTF-8.
2377/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2295pub fn updateFile(2378pub fn updateFile(
2296 source_dir: Dir,2379 source_dir: Dir,
2297 source_path: []const u8,2380 source_path: []const u8,
...@@ -2343,6 +2426,9 @@ pub const CopyFileError = File.OpenError || File.StatError ||...@@ -2343,6 +2426,9 @@ pub const CopyFileError = File.OpenError || File.StatError ||
2343/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,2426/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
2344/// there is a possibility of power loss or application termination leaving temporary files present2427/// there is a possibility of power loss or application termination leaving temporary files present
2345/// in the same directory as dest_path.2428/// in the same directory as dest_path.
2429/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2430/// On WASI, both paths should be encoded as valid UTF-8.
2431/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2346pub fn copyFile(2432pub fn copyFile(
2347 source_dir: Dir,2433 source_dir: Dir,
2348 source_path: []const u8,2434 source_path: []const u8,
...@@ -2430,6 +2516,9 @@ pub const AtomicFileOptions = struct {...@@ -2430,6 +2516,9 @@ pub const AtomicFileOptions = struct {
2430/// Always call `AtomicFile.deinit` to clean up, regardless of whether2516/// Always call `AtomicFile.deinit` to clean up, regardless of whether
2431/// `AtomicFile.finish` succeeded. `dest_path` must remain valid until2517/// `AtomicFile.finish` succeeded. `dest_path` must remain valid until
2432/// `AtomicFile.deinit` is called.2518/// `AtomicFile.deinit` is called.
2519/// On Windows, `dest_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2520/// On WASI, `dest_path` should be encoded as valid UTF-8.
2521/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
2433pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {2522pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
2434 if (fs.path.dirname(dest_path)) |dirname| {2523 if (fs.path.dirname(dest_path)) |dirname| {
2435 const dir = if (options.make_path)2524 const dir = if (options.make_path)
...@@ -2461,6 +2550,9 @@ pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError...@@ -2461,6 +2550,9 @@ pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError
2461/// Symlinks are followed.2550/// Symlinks are followed.
2462///2551///
2463/// `sub_path` may be absolute, in which case `self` is ignored.2552/// `sub_path` may be absolute, in which case `self` is ignored.
2553/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2554/// On WASI, `sub_path` should be encoded as valid UTF-8.
2555/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
2464pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {2556pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
2465 if (builtin.os.tag == .windows) {2557 if (builtin.os.tag == .windows) {
2466 var file = try self.openFile(sub_path, .{});2558 var file = try self.openFile(sub_path, .{});
lib/std/fs/File.zig+4-1
...@@ -40,8 +40,11 @@ pub const OpenError = error{...@@ -40,8 +40,11 @@ pub const OpenError = error{
40 AccessDenied,40 AccessDenied,
41 PipeBusy,41 PipeBusy,
42 NameTooLong,42 NameTooLong,
43 /// On Windows, file paths must be valid Unicode.43 /// WASI-only; file paths must be valid UTF-8.
44 InvalidUtf8,44 InvalidUtf8,
45 /// Windows-only; file paths provided by the user must be valid WTF-8.
46 /// https://simonsapin.github.io/wtf-8/
47 InvalidWtf8,
45 /// On Windows, file paths cannot contain these characters:48 /// On Windows, file paths cannot contain these characters:
46 /// '/', '*', '?', '"', '<', '>', '|'49 /// '/', '*', '?', '"', '<', '>', '|'
47 BadPathName,50 BadPathName,
lib/std/fs/path.zig+21-7
...@@ -1,3 +1,17 @@...@@ -1,3 +1,17 @@
1//! POSIX paths are arbitrary sequences of `u8` with no particular encoding.
2//!
3//! Windows paths are arbitrary sequences of `u16` (WTF-16).
4//! For cross-platform APIs that deal with sequences of `u8`, Windows
5//! paths are encoded by Zig as [WTF-8](https://simonsapin.github.io/wtf-8/).
6//! WTF-8 is a superset of UTF-8 that allows encoding surrogate codepoints,
7//! which enables lossless roundtripping when converting to/from WTF-16
8//! (as long as the WTF-8 encoded surrogate codepoints do not form a pair).
9//!
10//! WASI paths are sequences of valid Unicode scalar values,
11//! which means that WASI is unable to handle paths that cannot be
12//! encoded as well-formed UTF-8/UTF-16.
13//! https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
14
1const builtin = @import("builtin");15const builtin = @import("builtin");
2const std = @import("../std.zig");16const std = @import("../std.zig");
3const debug = std.debug;17const debug = std.debug;
...@@ -438,7 +452,7 @@ fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool {...@@ -438,7 +452,7 @@ fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool {
438 var it1 = mem.tokenizeScalar(u8, ns1, sep1);452 var it1 = mem.tokenizeScalar(u8, ns1, sep1);
439 var it2 = mem.tokenizeScalar(u8, ns2, sep2);453 var it2 = mem.tokenizeScalar(u8, ns2, sep2);
440454
441 return windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?);455 return windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?);
442}456}
443457
444fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8) bool {458fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8) bool {
...@@ -458,7 +472,7 @@ fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8...@@ -458,7 +472,7 @@ fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8
458 var it1 = mem.tokenizeScalar(u8, p1, sep1);472 var it1 = mem.tokenizeScalar(u8, p1, sep1);
459 var it2 = mem.tokenizeScalar(u8, p2, sep2);473 var it2 = mem.tokenizeScalar(u8, p2, sep2);
460474
461 return windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?) and windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?);475 return windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?) and windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?);
462 },476 },
463 }477 }
464}478}
...@@ -1099,7 +1113,7 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !...@@ -1099,7 +1113,7 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
1099 const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest());1113 const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest());
1100 const to_rest = to_it.rest();1114 const to_rest = to_it.rest();
1101 if (to_it.next()) |to_component| {1115 if (to_it.next()) |to_component| {
1102 if (windows.eqlIgnoreCaseUtf8(from_component, to_component))1116 if (windows.eqlIgnoreCaseWtf8(from_component, to_component))
1103 continue;1117 continue;
1104 }1118 }
1105 var up_index_end = "..".len;1119 var up_index_end = "..".len;
...@@ -1564,14 +1578,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {...@@ -1564,14 +1578,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
1564 };1578 };
1565}1579}
15661580
1567pub const NativeUtf8ComponentIterator = ComponentIterator(switch (native_os) {1581pub const NativeComponentIterator = ComponentIterator(switch (native_os) {
1568 .windows => .windows,1582 .windows => .windows,
1569 .uefi => .uefi,1583 .uefi => .uefi,
1570 else => .posix,1584 else => .posix,
1571}, u8);1585}, u8);
15721586
1573pub fn componentIterator(path: []const u8) !NativeUtf8ComponentIterator {1587pub fn componentIterator(path: []const u8) !NativeComponentIterator {
1574 return NativeUtf8ComponentIterator.init(path);1588 return NativeComponentIterator.init(path);
1575}1589}
15761590
1577test "ComponentIterator posix" {1591test "ComponentIterator posix" {
...@@ -1826,7 +1840,7 @@ test "ComponentIterator windows" {...@@ -1826,7 +1840,7 @@ test "ComponentIterator windows" {
1826 }1840 }
1827}1841}
18281842
1829test "ComponentIterator windows UTF-16" {1843test "ComponentIterator windows WTF-16" {
1830 // TODO: Fix on big endian architectures1844 // TODO: Fix on big endian architectures
1831 if (builtin.cpu.arch.endian() != .little) {1845 if (builtin.cpu.arch.endian() != .little) {
1832 return error.SkipZigTest;1846 return error.SkipZigTest;
lib/std/fs/test.zig+126-8
...@@ -26,39 +26,39 @@ const PathType = enum {...@@ -26,39 +26,39 @@ const PathType = enum {
26 }26 }
2727
28 pub const TransformError = std.os.RealPathError || error{OutOfMemory};28 pub const TransformError = std.os.RealPathError || error{OutOfMemory};
29 pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8;29 pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8;
3030
31 pub fn getTransformFn(comptime path_type: PathType) TransformFn {31 pub fn getTransformFn(comptime path_type: PathType) TransformFn {
32 switch (path_type) {32 switch (path_type) {
33 .relative => return struct {33 .relative => return struct {
34 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {34 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
35 _ = allocator;35 _ = allocator;
36 _ = dir;36 _ = dir;
37 return relative_path;37 return relative_path;
38 }38 }
39 }.transform,39 }.transform,
40 .absolute => return struct {40 .absolute => return struct {
41 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {41 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
42 // The final path may not actually exist which would cause realpath to fail.42 // The final path may not actually exist which would cause realpath to fail.
43 // So instead, we get the path of the dir and join it with the relative path.43 // So instead, we get the path of the dir and join it with the relative path.
44 var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;44 var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
45 const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);45 const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
46 return fs.path.join(allocator, &.{ dir_path, relative_path });46 return fs.path.joinZ(allocator, &.{ dir_path, relative_path });
47 }47 }
48 }.transform,48 }.transform,
49 .unc => return struct {49 .unc => return struct {
50 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {50 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
51 // Any drive absolute path (C:\foo) can be converted into a UNC path by51 // Any drive absolute path (C:\foo) can be converted into a UNC path by
52 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.52 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
53 var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;53 var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
54 const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);54 const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
55 const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path);55 const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path);
56 switch (windows_path_type) {56 switch (windows_path_type) {
57 .unc_absolute => return fs.path.join(allocator, &.{ dir_path, relative_path }),57 .unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }),
58 .drive_absolute => {58 .drive_absolute => {
59 // `C:\<...>` -> `\\127.0.0.1\C$\<...>`59 // `C:\<...>` -> `\\127.0.0.1\C$\<...>`
60 const prepended = "\\\\127.0.0.1\\";60 const prepended = "\\\\127.0.0.1\\";
61 var path = try fs.path.join(allocator, &.{ prepended, dir_path, relative_path });61 var path = try fs.path.joinZ(allocator, &.{ prepended, dir_path, relative_path });
62 path[prepended.len + 1] = '$';62 path[prepended.len + 1] = '$';
63 return path;63 return path;
64 },64 },
...@@ -96,7 +96,7 @@ const TestContext = struct {...@@ -96,7 +96,7 @@ const TestContext = struct {
96 /// Returns the `relative_path` transformed into the TestContext's `path_type`.96 /// Returns the `relative_path` transformed into the TestContext's `path_type`.
97 /// The result is allocated by the TestContext's arena and will be free'd during97 /// The result is allocated by the TestContext's arena and will be free'd during
98 /// `TestContext.deinit`.98 /// `TestContext.deinit`.
99 pub fn transformPath(self: *TestContext, relative_path: []const u8) ![]const u8 {99 pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 {
100 return self.transform_fn(self.arena.allocator(), self.dir, relative_path);100 return self.transform_fn(self.arena.allocator(), self.dir, relative_path);
101 }101 }
102};102};
...@@ -1001,6 +1001,16 @@ test "openSelfExe" {...@@ -1001,6 +1001,16 @@ test "openSelfExe" {
1001 self_exe_file.close();1001 self_exe_file.close();
1002}1002}
10031003
1004test "selfExePath" {
1005 if (builtin.os.tag == .wasi) return error.SkipZigTest;
1006
1007 var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
1008 const buf_self_exe_path = try std.fs.selfExePath(&buf);
1009 const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator);
1010 defer testing.allocator.free(alloc_self_exe_path);
1011 try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path);
1012}
1013
1004test "deleteTree does not follow symlinks" {1014test "deleteTree does not follow symlinks" {
1005 var tmp = tmpDir(.{});1015 var tmp = tmpDir(.{});
1006 defer tmp.cleanup();1016 defer tmp.cleanup();
...@@ -1907,3 +1917,111 @@ test "delete a setAsCwd directory on Windows" {...@@ -1907,3 +1917,111 @@ test "delete a setAsCwd directory on Windows" {
1907 // Close the parent "tmp" so we don't leak the HANDLE.1917 // Close the parent "tmp" so we don't leak the HANDLE.
1908 tmp.parent_dir.close();1918 tmp.parent_dir.close();
1909}1919}
1920
1921test "invalid UTF-8/WTF-8 paths" {
1922 const expected_err = switch (builtin.os.tag) {
1923 .wasi => error.InvalidUtf8,
1924 .windows => error.InvalidWtf8,
1925 else => return error.SkipZigTest,
1926 };
1927
1928 try testWithAllSupportedPathTypes(struct {
1929 fn impl(ctx: *TestContext) !void {
1930 // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte
1931 const invalid_path = try ctx.transformPath("\xFF");
1932
1933 try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{}));
1934 try testing.expectError(expected_err, ctx.dir.openFileZ(invalid_path, .{}));
1935
1936 try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{}));
1937 try testing.expectError(expected_err, ctx.dir.createFileZ(invalid_path, .{}));
1938
1939 try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path));
1940 try testing.expectError(expected_err, ctx.dir.makeDirZ(invalid_path));
1941
1942 try testing.expectError(expected_err, ctx.dir.makePath(invalid_path));
1943 try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{}));
1944
1945 try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{}));
1946 try testing.expectError(expected_err, ctx.dir.openDirZ(invalid_path, .{}));
1947
1948 try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path));
1949 try testing.expectError(expected_err, ctx.dir.deleteFileZ(invalid_path));
1950
1951 try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path));
1952 try testing.expectError(expected_err, ctx.dir.deleteDirZ(invalid_path));
1953
1954 try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path));
1955 try testing.expectError(expected_err, ctx.dir.renameZ(invalid_path, invalid_path));
1956
1957 try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{}));
1958 try testing.expectError(expected_err, ctx.dir.symLinkZ(invalid_path, invalid_path, .{}));
1959 if (builtin.os.tag == .wasi) {
1960 try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{}));
1961 }
1962
1963 try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{}));
1964 try testing.expectError(expected_err, ctx.dir.readLinkZ(invalid_path, &[_]u8{}));
1965 if (builtin.os.tag == .wasi) {
1966 try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{}));
1967 }
1968
1969 try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{}));
1970 try testing.expectError(expected_err, ctx.dir.readFileAlloc(testing.allocator, invalid_path, 0));
1971
1972 try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path));
1973 try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path));
1974
1975 try testing.expectError(expected_err, ctx.dir.writeFile(invalid_path, ""));
1976 try testing.expectError(expected_err, ctx.dir.writeFile2(.{
1977 .sub_path = invalid_path,
1978 .data = "",
1979 }));
1980
1981 try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{}));
1982 try testing.expectError(expected_err, ctx.dir.accessZ(invalid_path, .{}));
1983
1984 try testing.expectError(expected_err, ctx.dir.updateFile(invalid_path, ctx.dir, invalid_path, .{}));
1985 try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{}));
1986
1987 try testing.expectError(expected_err, ctx.dir.statFile(invalid_path));
1988
1989 if (builtin.os.tag != .wasi) {
1990 try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{}));
1991 try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{}));
1992 try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path));
1993 }
1994
1995 try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path));
1996 try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path));
1997
1998 if (builtin.os.tag != .wasi and ctx.path_type != .relative) {
1999 try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{}));
2000 try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));
2001 try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));
2002 try testing.expectError(expected_err, fs.makeDirAbsoluteZ(invalid_path));
2003 try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path));
2004 try testing.expectError(expected_err, fs.deleteDirAbsoluteZ(invalid_path));
2005 try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path));
2006 try testing.expectError(expected_err, fs.renameAbsoluteZ(invalid_path, invalid_path));
2007 try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{}));
2008 try testing.expectError(expected_err, fs.openDirAbsoluteZ(invalid_path, .{}));
2009 try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{}));
2010 try testing.expectError(expected_err, fs.openFileAbsoluteZ(invalid_path, .{}));
2011 try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{}));
2012 try testing.expectError(expected_err, fs.accessAbsoluteZ(invalid_path, .{}));
2013 try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{}));
2014 try testing.expectError(expected_err, fs.createFileAbsoluteZ(invalid_path, .{}));
2015 try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path));
2016 try testing.expectError(expected_err, fs.deleteFileAbsoluteZ(invalid_path));
2017 try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path));
2018 var readlink_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
2019 try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf));
2020 try testing.expectError(expected_err, fs.readLinkAbsoluteZ(invalid_path, &readlink_buf));
2021 try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{}));
2022 try testing.expectError(expected_err, fs.symLinkAbsoluteZ(invalid_path, invalid_path, .{}));
2023 try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path));
2024 }
2025 }
2026 }.impl);
2027}
lib/std/fs/watch.zig deleted-719
...@@ -1,719 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14const global_event_loop = Loop.instance orelse
15 @compileError("std.fs.Watch currently only works with event-based I/O");
16
17const WatchEventId = enum {
18 CloseWrite,
19 Delete,
20};
21
22const WatchEventError = error{
23 UserResourceLimitReached,
24 SystemResources,
25 AccessDenied,
26 Unexpected, // TODO remove this possibility
27};
28
29pub fn Watch(comptime V: type) type {
30 return struct {
31 channel: event.Channel(Event.Error!Event),
32 os_data: OsData,
33 allocator: Allocator,
34
35 const OsData = switch (builtin.os.tag) {
36 // TODO https://github.com/ziglang/zig/issues/3778
37 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => KqOsData,
38 .linux => LinuxOsData,
39 .windows => WindowsOsData,
40
41 else => @compileError("Unsupported OS"),
42 };
43
44 const KqOsData = struct {
45 table_lock: event.Lock,
46 file_table: FileTable,
47
48 const FileTable = std.StringHashMapUnmanaged(*Put);
49 const Put = struct {
50 putter_frame: @Frame(kqPutEvents),
51 cancelled: bool = false,
52 value: V,
53 };
54 };
55
56 const WindowsOsData = struct {
57 table_lock: event.Lock,
58 dir_table: DirTable,
59 cancelled: bool = false,
60
61 const DirTable = std.StringHashMapUnmanaged(*Dir);
62 const FileTable = std.StringHashMapUnmanaged(V);
63
64 const Dir = struct {
65 putter_frame: @Frame(windowsDirReader),
66 file_table: FileTable,
67 dir_handle: os.windows.HANDLE,
68 };
69 };
70
71 const LinuxOsData = struct {
72 putter_frame: @Frame(linuxEventPutter),
73 inotify_fd: i32,
74 wd_table: WdTable,
75 table_lock: event.Lock,
76 cancelled: bool = false,
77
78 const WdTable = std.AutoHashMapUnmanaged(i32, Dir);
79 const FileTable = std.StringHashMapUnmanaged(V);
80
81 const Dir = struct {
82 dirname: []const u8,
83 file_table: FileTable,
84 };
85 };
86
87 const Self = @This();
88
89 pub const Event = struct {
90 id: Id,
91 data: V,
92 dirname: []const u8,
93 basename: []const u8,
94
95 pub const Id = WatchEventId;
96 pub const Error = WatchEventError;
97 };
98
99 pub fn init(allocator: Allocator, event_buf_count: usize) !*Self {
100 const self = try allocator.create(Self);
101 errdefer allocator.destroy(self);
102
103 switch (builtin.os.tag) {
104 .linux => {
105 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
106 errdefer os.close(inotify_fd);
107
108 self.* = Self{
109 .allocator = allocator,
110 .channel = undefined,
111 .os_data = OsData{
112 .putter_frame = undefined,
113 .inotify_fd = inotify_fd,
114 .wd_table = OsData.WdTable.init(allocator),
115 .table_lock = event.Lock{},
116 },
117 };
118
119 const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
120 self.channel.init(buf);
121 self.os_data.putter_frame = async self.linuxEventPutter();
122 return self;
123 },
124
125 .windows => {
126 self.* = Self{
127 .allocator = allocator,
128 .channel = undefined,
129 .os_data = OsData{
130 .table_lock = event.Lock{},
131 .dir_table = OsData.DirTable.init(allocator),
132 },
133 };
134
135 const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
136 self.channel.init(buf);
137 return self;
138 },
139
140 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
141 self.* = Self{
142 .allocator = allocator,
143 .channel = undefined,
144 .os_data = OsData{
145 .table_lock = event.Lock{},
146 .file_table = OsData.FileTable.init(allocator),
147 },
148 };
149
150 const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
151 self.channel.init(buf);
152 return self;
153 },
154 else => @compileError("Unsupported OS"),
155 }
156 }
157
158 pub fn deinit(self: *Self) void {
159 switch (builtin.os.tag) {
160 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
161 var it = self.os_data.file_table.iterator();
162 while (it.next()) |entry| {
163 const key = entry.key_ptr.*;
164 const value = entry.value_ptr.*;
165 value.cancelled = true;
166 // @TODO Close the fd here?
167 await value.putter_frame;
168 self.allocator.free(key);
169 self.allocator.destroy(value);
170 }
171 },
172 .linux => {
173 self.os_data.cancelled = true;
174 {
175 // Remove all directory watches linuxEventPutter will take care of
176 // cleaning up the memory and closing the inotify fd.
177 var dir_it = self.os_data.wd_table.keyIterator();
178 while (dir_it.next()) |wd_key| {
179 const rc = os.linux.inotify_rm_watch(self.os_data.inotify_fd, wd_key.*);
180 // Errno can only be EBADF, EINVAL if either the inotify fs or the wd are invalid
181 std.debug.assert(rc == 0);
182 }
183 }
184 await self.os_data.putter_frame;
185 },
186 .windows => {
187 self.os_data.cancelled = true;
188 var dir_it = self.os_data.dir_table.iterator();
189 while (dir_it.next()) |dir_entry| {
190 if (windows.kernel32.CancelIoEx(dir_entry.value.dir_handle, null) != 0) {
191 // We canceled the pending ReadDirectoryChangesW operation, but our
192 // frame is still suspending, now waiting indefinitely.
193 // Thus, it is safe to resume it ourslves
194 resume dir_entry.value.putter_frame;
195 } else {
196 std.debug.assert(windows.kernel32.GetLastError() == .NOT_FOUND);
197 // We are at another suspend point, we can await safely for the
198 // function to exit the loop
199 await dir_entry.value.putter_frame;
200 }
201
202 self.allocator.free(dir_entry.key_ptr.*);
203 var file_it = dir_entry.value.file_table.keyIterator();
204 while (file_it.next()) |file_entry| {
205 self.allocator.free(file_entry.*);
206 }
207 dir_entry.value.file_table.deinit(self.allocator);
208 self.allocator.destroy(dir_entry.value_ptr.*);
209 }
210 self.os_data.dir_table.deinit(self.allocator);
211 },
212 else => @compileError("Unsupported OS"),
213 }
214 self.allocator.free(self.channel.buffer_nodes);
215 self.channel.deinit();
216 self.allocator.destroy(self);
217 }
218
219 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
220 switch (builtin.os.tag) {
221 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => return addFileKEvent(self, file_path, value),
222 .linux => return addFileLinux(self, file_path, value),
223 .windows => return addFileWindows(self, file_path, value),
224 else => @compileError("Unsupported OS"),
225 }
226 }
227
228 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
229 var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
230 const realpath = try os.realpath(file_path, &realpath_buf);
231
232 const held = self.os_data.table_lock.acquire();
233 defer held.release();
234
235 const gop = try self.os_data.file_table.getOrPut(self.allocator, realpath);
236 errdefer assert(self.os_data.file_table.remove(realpath));
237 if (gop.found_existing) {
238 const prev_value = gop.value_ptr.value;
239 gop.value_ptr.value = value;
240 return prev_value;
241 }
242
243 gop.key_ptr.* = try self.allocator.dupe(u8, realpath);
244 errdefer self.allocator.free(gop.key_ptr.*);
245 gop.value_ptr.* = try self.allocator.create(OsData.Put);
246 errdefer self.allocator.destroy(gop.value_ptr.*);
247 gop.value_ptr.* = .{
248 .putter_frame = undefined,
249 .value = value,
250 };
251
252 // @TODO Can I close this fd and get an error from bsdWaitKev?
253 const flags = if (comptime builtin.target.isDarwin()) os.O.SYMLINK | os.O.EVTONLY else 0;
254 const fd = try os.open(realpath, flags, 0);
255 gop.value_ptr.putter_frame = async self.kqPutEvents(fd, gop.key_ptr.*, gop.value_ptr.*);
256 return null;
257 }
258
259 fn kqPutEvents(self: *Self, fd: os.fd_t, file_path: []const u8, put: *OsData.Put) void {
260 global_event_loop.beginOneEvent();
261 defer {
262 global_event_loop.finishOneEvent();
263 // @TODO: Remove this if we force close otherwise
264 os.close(fd);
265 }
266
267 // We need to manually do a bsdWaitKev to access the fflags.
268 var resume_node = event.Loop.ResumeNode.Basic{
269 .base = .{
270 .id = .Basic,
271 .handle = @frame(),
272 .overlapped = event.Loop.ResumeNode.overlapped_init,
273 },
274 .kev = undefined,
275 };
276
277 var kevs = [1]os.Kevent{undefined};
278 const kev = &kevs[0];
279
280 while (!put.cancelled) {
281 kev.* = os.Kevent{
282 .ident = @as(usize, @intCast(fd)),
283 .filter = os.EVFILT_VNODE,
284 .flags = os.EV_ADD | os.EV_ENABLE | os.EV_CLEAR | os.EV_ONESHOT |
285 os.NOTE_WRITE | os.NOTE_DELETE | os.NOTE_REVOKE,
286 .fflags = 0,
287 .data = 0,
288 .udata = @intFromPtr(&resume_node.base),
289 };
290 suspend {
291 global_event_loop.beginOneEvent();
292 errdefer global_event_loop.finishOneEvent();
293
294 const empty_kevs = &[0]os.Kevent{};
295 _ = os.kevent(global_event_loop.os_data.kqfd, &kevs, empty_kevs, null) catch |err| switch (err) {
296 error.EventNotFound,
297 error.ProcessNotFound,
298 error.Overflow,
299 => unreachable,
300 error.AccessDenied, error.SystemResources => |e| {
301 self.channel.put(e);
302 continue;
303 },
304 };
305 }
306
307 if (kev.flags & os.EV_ERROR != 0) {
308 self.channel.put(os.unexpectedErrno(os.errno(kev.data)));
309 continue;
310 }
311
312 if (kev.fflags & os.NOTE_DELETE != 0 or kev.fflags & os.NOTE_REVOKE != 0) {
313 self.channel.put(Self.Event{
314 .id = .Delete,
315 .data = put.value,
316 .dirname = std.fs.path.dirname(file_path) orelse "/",
317 .basename = std.fs.path.basename(file_path),
318 });
319 } else if (kev.fflags & os.NOTE_WRITE != 0) {
320 self.channel.put(Self.Event{
321 .id = .CloseWrite,
322 .data = put.value,
323 .dirname = std.fs.path.dirname(file_path) orelse "/",
324 .basename = std.fs.path.basename(file_path),
325 });
326 }
327 }
328 }
329
330 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
331 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
332 const basename = std.fs.path.basename(file_path);
333
334 const wd = try os.inotify_add_watch(
335 self.os_data.inotify_fd,
336 dirname,
337 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_DELETE | os.linux.IN_EXCL_UNLINK,
338 );
339 // wd is either a newly created watch or an existing one.
340
341 const held = self.os_data.table_lock.acquire();
342 defer held.release();
343
344 const gop = try self.os_data.wd_table.getOrPut(self.allocator, wd);
345 errdefer assert(self.os_data.wd_table.remove(wd));
346 if (!gop.found_existing) {
347 gop.value_ptr.* = OsData.Dir{
348 .dirname = try self.allocator.dupe(u8, dirname),
349 .file_table = OsData.FileTable.init(self.allocator),
350 };
351 }
352
353 const dir = gop.value_ptr;
354 const file_table_gop = try dir.file_table.getOrPut(self.allocator, basename);
355 errdefer assert(dir.file_table.remove(basename));
356 if (file_table_gop.found_existing) {
357 const prev_value = file_table_gop.value_ptr.*;
358 file_table_gop.value_ptr.* = value;
359 return prev_value;
360 } else {
361 file_table_gop.key_ptr.* = try self.allocator.dupe(u8, basename);
362 file_table_gop.value_ptr.* = value;
363 return null;
364 }
365 }
366
367 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
368 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
369 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
370 var dirname_path_space: windows.PathSpace = undefined;
371 dirname_path_space.len = try std.unicode.utf8ToUtf16Le(&dirname_path_space.data, dirname);
372 dirname_path_space.data[dirname_path_space.len] = 0;
373
374 const basename = std.fs.path.basename(file_path);
375 var basename_path_space: windows.PathSpace = undefined;
376 basename_path_space.len = try std.unicode.utf8ToUtf16Le(&basename_path_space.data, basename);
377 basename_path_space.data[basename_path_space.len] = 0;
378
379 const held = self.os_data.table_lock.acquire();
380 defer held.release();
381
382 const gop = try self.os_data.dir_table.getOrPut(self.allocator, dirname);
383 errdefer assert(self.os_data.dir_table.remove(dirname));
384 if (gop.found_existing) {
385 const dir = gop.value_ptr.*;
386
387 const file_gop = try dir.file_table.getOrPut(self.allocator, basename);
388 errdefer assert(dir.file_table.remove(basename));
389 if (file_gop.found_existing) {
390 const prev_value = file_gop.value_ptr.*;
391 file_gop.value_ptr.* = value;
392 return prev_value;
393 } else {
394 file_gop.value_ptr.* = value;
395 file_gop.key_ptr.* = try self.allocator.dupe(u8, basename);
396 return null;
397 }
398 } else {
399 const dir_handle = try windows.OpenFile(dirname_path_space.span(), .{
400 .dir = std.fs.cwd().fd,
401 .access_mask = windows.FILE_LIST_DIRECTORY,
402 .creation = windows.FILE_OPEN,
403 .io_mode = .evented,
404 .filter = .dir_only,
405 });
406 errdefer windows.CloseHandle(dir_handle);
407
408 const dir = try self.allocator.create(OsData.Dir);
409 errdefer self.allocator.destroy(dir);
410
411 gop.key_ptr.* = try self.allocator.dupe(u8, dirname);
412 errdefer self.allocator.free(gop.key_ptr.*);
413
414 dir.* = OsData.Dir{
415 .file_table = OsData.FileTable.init(self.allocator),
416 .putter_frame = undefined,
417 .dir_handle = dir_handle,
418 };
419 gop.value_ptr.* = dir;
420 try dir.file_table.put(self.allocator, try self.allocator.dupe(u8, basename), value);
421 dir.putter_frame = async self.windowsDirReader(dir, gop.key_ptr.*);
422 return null;
423 }
424 }
425
426 fn windowsDirReader(self: *Self, dir: *OsData.Dir, dirname: []const u8) void {
427 defer os.close(dir.dir_handle);
428 var resume_node = Loop.ResumeNode.Basic{
429 .base = Loop.ResumeNode{
430 .id = .Basic,
431 .handle = @frame(),
432 .overlapped = windows.OVERLAPPED{
433 .Internal = 0,
434 .InternalHigh = 0,
435 .DUMMYUNIONNAME = .{
436 .DUMMYSTRUCTNAME = .{
437 .Offset = 0,
438 .OffsetHigh = 0,
439 },
440 },
441 .hEvent = null,
442 },
443 },
444 };
445
446 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
447
448 global_event_loop.beginOneEvent();
449 defer global_event_loop.finishOneEvent();
450
451 while (!self.os_data.cancelled) main_loop: {
452 suspend {
453 _ = windows.kernel32.ReadDirectoryChangesW(
454 dir.dir_handle,
455 &event_buf,
456 event_buf.len,
457 windows.FALSE, // watch subtree
458 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
459 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
460 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
461 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
462 null, // number of bytes transferred (unused for async)
463 &resume_node.base.overlapped,
464 null, // completion routine - unused because we use IOCP
465 );
466 }
467
468 var bytes_transferred: windows.DWORD = undefined;
469 if (windows.kernel32.GetOverlappedResult(
470 dir.dir_handle,
471 &resume_node.base.overlapped,
472 &bytes_transferred,
473 windows.FALSE,
474 ) == 0) {
475 const potential_error = windows.kernel32.GetLastError();
476 const err = switch (potential_error) {
477 .OPERATION_ABORTED, .IO_INCOMPLETE => err_blk: {
478 if (self.os_data.cancelled)
479 break :main_loop
480 else
481 break :err_blk windows.unexpectedError(potential_error);
482 },
483 else => |err| windows.unexpectedError(err),
484 };
485 self.channel.put(err);
486 } else {
487 var ptr: [*]u8 = &event_buf;
488 const end_ptr = ptr + bytes_transferred;
489 while (@intFromPtr(ptr) < @intFromPtr(end_ptr)) {
490 const ev = @as(*const windows.FILE_NOTIFY_INFORMATION, @ptrCast(ptr));
491 const emit = switch (ev.Action) {
492 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
493 windows.FILE_ACTION_MODIFIED => .CloseWrite,
494 else => null,
495 };
496 if (emit) |id| {
497 const basename_ptr = @as([*]u16, @ptrCast(ptr + @sizeOf(windows.FILE_NOTIFY_INFORMATION)));
498 const basename_utf16le = basename_ptr[0 .. ev.FileNameLength / 2];
499 var basename_data: [std.fs.MAX_PATH_BYTES]u8 = undefined;
500 const basename = basename_data[0 .. std.unicode.utf16LeToUtf8(&basename_data, basename_utf16le) catch unreachable];
501
502 if (dir.file_table.getEntry(basename)) |entry| {
503 self.channel.put(Event{
504 .id = id,
505 .data = entry.value_ptr.*,
506 .dirname = dirname,
507 .basename = entry.key_ptr.*,
508 });
509 }
510 }
511
512 if (ev.NextEntryOffset == 0) break;
513 ptr = @alignCast(ptr + ev.NextEntryOffset);
514 }
515 }
516 }
517 }
518
519 pub fn removeFile(self: *Self, file_path: []const u8) !?V {
520 switch (builtin.os.tag) {
521 .linux => {
522 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
523 const basename = std.fs.path.basename(file_path);
524
525 const held = self.os_data.table_lock.acquire();
526 defer held.release();
527
528 const dir = self.os_data.wd_table.get(dirname) orelse return null;
529 if (dir.file_table.fetchRemove(basename)) |file_entry| {
530 self.allocator.free(file_entry.key);
531 return file_entry.value;
532 }
533 return null;
534 },
535 .windows => {
536 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
537 const basename = std.fs.path.basename(file_path);
538
539 const held = self.os_data.table_lock.acquire();
540 defer held.release();
541
542 const dir = self.os_data.dir_table.get(dirname) orelse return null;
543 if (dir.file_table.fetchRemove(basename)) |file_entry| {
544 self.allocator.free(file_entry.key);
545 return file_entry.value;
546 }
547 return null;
548 },
549 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
550 var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
551 const realpath = try os.realpath(file_path, &realpath_buf);
552
553 const held = self.os_data.table_lock.acquire();
554 defer held.release();
555
556 const entry = self.os_data.file_table.getEntry(realpath) orelse return null;
557 entry.value_ptr.cancelled = true;
558 // @TODO Close the fd here?
559 await entry.value_ptr.putter_frame;
560 self.allocator.free(entry.key_ptr.*);
561 self.allocator.destroy(entry.value_ptr.*);
562
563 assert(self.os_data.file_table.remove(realpath));
564 },
565 else => @compileError("Unsupported OS"),
566 }
567 }
568
569 fn linuxEventPutter(self: *Self) void {
570 global_event_loop.beginOneEvent();
571
572 defer {
573 std.debug.assert(self.os_data.wd_table.count() == 0);
574 self.os_data.wd_table.deinit(self.allocator);
575 os.close(self.os_data.inotify_fd);
576 self.allocator.free(self.channel.buffer_nodes);
577 self.channel.deinit();
578 global_event_loop.finishOneEvent();
579 }
580
581 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
582
583 while (!self.os_data.cancelled) {
584 const bytes_read = global_event_loop.read(self.os_data.inotify_fd, &event_buf, false) catch unreachable;
585
586 var ptr: [*]u8 = &event_buf;
587 const end_ptr = ptr + bytes_read;
588 while (@intFromPtr(ptr) < @intFromPtr(end_ptr)) {
589 const ev = @as(*const os.linux.inotify_event, @ptrCast(ptr));
590 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
591 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
592 const basename = std.mem.span(@as([*:0]u8, @ptrCast(basename_ptr)));
593
594 const dir = &self.os_data.wd_table.get(ev.wd).?;
595 if (dir.file_table.getEntry(basename)) |file_value| {
596 self.channel.put(Event{
597 .id = .CloseWrite,
598 .data = file_value.value_ptr.*,
599 .dirname = dir.dirname,
600 .basename = file_value.key_ptr.*,
601 });
602 }
603 } else if (ev.mask & os.linux.IN_IGNORED == os.linux.IN_IGNORED) {
604 // Directory watch was removed
605 const held = self.os_data.table_lock.acquire();
606 defer held.release();
607 if (self.os_data.wd_table.fetchRemove(ev.wd)) |wd_entry| {
608 var file_it = wd_entry.value.file_table.keyIterator();
609 while (file_it.next()) |file_entry| {
610 self.allocator.free(file_entry.*);
611 }
612 self.allocator.free(wd_entry.value.dirname);
613 wd_entry.value.file_table.deinit(self.allocator);
614 }
615 } else if (ev.mask & os.linux.IN_DELETE == os.linux.IN_DELETE) {
616 // File or directory was removed or deleted
617 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
618 const basename = std.mem.span(@as([*:0]u8, @ptrCast(basename_ptr)));
619
620 const dir = &self.os_data.wd_table.get(ev.wd).?;
621 if (dir.file_table.getEntry(basename)) |file_value| {
622 self.channel.put(Event{
623 .id = .Delete,
624 .data = file_value.value_ptr.*,
625 .dirname = dir.dirname,
626 .basename = file_value.key_ptr.*,
627 });
628 }
629 }
630
631 ptr = @alignCast(ptr + @sizeOf(os.linux.inotify_event) + ev.len);
632 }
633 }
634 }
635 };
636}
637
638const test_tmp_dir = "std_event_fs_test";
639
640test "write a file, watch it, write it again, delete it" {
641 if (!std.io.is_async) return error.SkipZigTest;
642 // TODO https://github.com/ziglang/zig/issues/1908
643 if (builtin.single_threaded) return error.SkipZigTest;
644
645 try std.fs.cwd().makePath(test_tmp_dir);
646 defer std.fs.cwd().deleteTree(test_tmp_dir) catch {};
647
648 return testWriteWatchWriteDelete(std.testing.allocator);
649}
650
651fn testWriteWatchWriteDelete(allocator: Allocator) !void {
652 const file_path = try std.fs.path.join(allocator, &[_][]const u8{ test_tmp_dir, "file.txt" });
653 defer allocator.free(file_path);
654
655 const contents =
656 \\line 1
657 \\line 2
658 ;
659 const line2_offset = 7;
660
661 // first just write then read the file
662 try std.fs.cwd().writeFile(file_path, contents);
663
664 const read_contents = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
665 defer allocator.free(read_contents);
666 try testing.expectEqualSlices(u8, contents, read_contents);
667
668 // now watch the file
669 var watch = try Watch(void).init(allocator, 0);
670 defer watch.deinit();
671
672 try testing.expect((try watch.addFile(file_path, {})) == null);
673
674 var ev = async watch.channel.get();
675 var ev_consumed = false;
676 defer if (!ev_consumed) {
677 _ = await ev;
678 };
679
680 // overwrite line 2
681 const file = try std.fs.cwd().openFile(file_path, .{ .mode = .read_write });
682 {
683 defer file.close();
684 const write_contents = "lorem ipsum";
685 var iovec = [_]os.iovec_const{.{
686 .iov_base = write_contents,
687 .iov_len = write_contents.len,
688 }};
689 _ = try file.pwritevAll(&iovec, line2_offset);
690 }
691
692 switch ((try await ev).id) {
693 .CloseWrite => {
694 ev_consumed = true;
695 },
696 .Delete => @panic("wrong event"),
697 }
698
699 const contents_updated = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
700 defer allocator.free(contents_updated);
701
702 try testing.expectEqualSlices(u8,
703 \\line 1
704 \\lorem ipsum
705 , contents_updated);
706
707 ev = async watch.channel.get();
708 ev_consumed = false;
709
710 try std.fs.cwd().deleteFile(file_path);
711 switch ((try await ev).id) {
712 .Delete => {
713 ev_consumed = true;
714 },
715 .CloseWrite => @panic("wrong event"),
716 }
717}
718
719// TODO Test: Add another file watch, remove the old file watch, get an event in the new
lib/std/os.zig+257-40
...@@ -3,7 +3,7 @@...@@ -3,7 +3,7 @@
3//! * Convert "errno"-style error codes into Zig errors.3//! * Convert "errno"-style error codes into Zig errors.
4//! * When null-terminated byte buffers are required, provide APIs which accept4//! * When null-terminated byte buffers are required, provide APIs which accept
5//! slices as well as APIs which accept null-terminated byte buffers. Same goes5//! slices as well as APIs which accept null-terminated byte buffers. Same goes
6//! for UTF-16LE encoding.6//! for WTF-16LE encoding.
7//! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide7//! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
8//! cross platform abstracting.8//! cross platform abstracting.
9//! * When there exists a corresponding libc function and linking libc, the libc9//! * When there exists a corresponding libc function and linking libc, the libc
...@@ -498,6 +498,7 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr...@@ -498,6 +498,7 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr
498 const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {498 const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {
499 error.NameTooLong => unreachable,499 error.NameTooLong => unreachable,
500 error.FileNotFound => unreachable,500 error.FileNotFound => unreachable,
501 error.InvalidUtf8 => unreachable,
501 else => |e| return e,502 else => |e| return e,
502 };503 };
503 if ((stat.mode & S.IFMT) == S.IFLNK)504 if ((stat.mode & S.IFMT) == S.IFLNK)
...@@ -1614,9 +1615,16 @@ pub const OpenError = error{...@@ -1614,9 +1615,16 @@ pub const OpenError = error{
1614 /// The underlying filesystem does not support file locks1615 /// The underlying filesystem does not support file locks
1615 FileLocksNotSupported,1616 FileLocksNotSupported,
16161617
1618 /// Path contains characters that are disallowed by the underlying filesystem.
1617 BadPathName,1619 BadPathName,
1620
1621 /// WASI-only; file paths must be valid UTF-8.
1618 InvalidUtf8,1622 InvalidUtf8,
16191623
1624 /// Windows-only; file paths provided by the user must be valid WTF-8.
1625 /// https://simonsapin.github.io/wtf-8/
1626 InvalidWtf8,
1627
1620 /// On Windows, `\\server` or `\\server\share` was not found.1628 /// On Windows, `\\server` or `\\server\share` was not found.
1621 NetworkNotFound,1629 NetworkNotFound,
16221630
...@@ -1634,6 +1642,9 @@ pub const OpenError = error{...@@ -1634,6 +1642,9 @@ pub const OpenError = error{
1634} || UnexpectedError;1642} || UnexpectedError;
16351643
1636/// Open and possibly create a file. Keeps trying if it gets interrupted.1644/// Open and possibly create a file. Keeps trying if it gets interrupted.
1645/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1646/// On WASI, `file_path` should be encoded as valid UTF-8.
1647/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1637/// See also `openZ`.1648/// See also `openZ`.
1638pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {1649pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
1639 if (builtin.os.tag == .windows) {1650 if (builtin.os.tag == .windows) {
...@@ -1646,6 +1657,9 @@ pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {...@@ -1646,6 +1657,9 @@ pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
1646}1657}
16471658
1648/// Open and possibly create a file. Keeps trying if it gets interrupted.1659/// Open and possibly create a file. Keeps trying if it gets interrupted.
1660/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1661/// On WASI, `file_path` should be encoded as valid UTF-8.
1662/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1649/// See also `open`.1663/// See also `open`.
1650pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {1664pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
1651 if (builtin.os.tag == .windows) {1665 if (builtin.os.tag == .windows) {
...@@ -1687,6 +1701,9 @@ pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {...@@ -1687,6 +1701,9 @@ pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
16871701
1688/// Open and possibly create a file. Keeps trying if it gets interrupted.1702/// Open and possibly create a file. Keeps trying if it gets interrupted.
1689/// `file_path` is relative to the open directory handle `dir_fd`.1703/// `file_path` is relative to the open directory handle `dir_fd`.
1704/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1705/// On WASI, `file_path` should be encoded as valid UTF-8.
1706/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1690/// See also `openatZ`.1707/// See also `openatZ`.
1691pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t {1708pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t {
1692 if (builtin.os.tag == .windows) {1709 if (builtin.os.tag == .windows) {
...@@ -1829,6 +1846,7 @@ pub fn openatWasi(...@@ -1829,6 +1846,7 @@ pub fn openatWasi(
1829 .EXIST => return error.PathAlreadyExists,1846 .EXIST => return error.PathAlreadyExists,
1830 .BUSY => return error.DeviceBusy,1847 .BUSY => return error.DeviceBusy,
1831 .NOTCAPABLE => return error.AccessDenied,1848 .NOTCAPABLE => return error.AccessDenied,
1849 .ILSEQ => return error.InvalidUtf8,
1832 else => |err| return unexpectedErrno(err),1850 else => |err| return unexpectedErrno(err),
1833 }1851 }
1834 }1852 }
...@@ -1836,6 +1854,9 @@ pub fn openatWasi(...@@ -1836,6 +1854,9 @@ pub fn openatWasi(
18361854
1837/// Open and possibly create a file. Keeps trying if it gets interrupted.1855/// Open and possibly create a file. Keeps trying if it gets interrupted.
1838/// `file_path` is relative to the open directory handle `dir_fd`.1856/// `file_path` is relative to the open directory handle `dir_fd`.
1857/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1858/// On WASI, `file_path` should be encoded as valid UTF-8.
1859/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1839/// See also `openat`.1860/// See also `openat`.
1840pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t {1861pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t {
1841 if (builtin.os.tag == .windows) {1862 if (builtin.os.tag == .windows) {
...@@ -2156,13 +2177,23 @@ pub const SymLinkError = error{...@@ -2156,13 +2177,23 @@ pub const SymLinkError = error{
2156 ReadOnlyFileSystem,2177 ReadOnlyFileSystem,
2157 NotDir,2178 NotDir,
2158 NameTooLong,2179 NameTooLong,
2180
2181 /// WASI-only; file paths must be valid UTF-8.
2159 InvalidUtf8,2182 InvalidUtf8,
2183
2184 /// Windows-only; file paths provided by the user must be valid WTF-8.
2185 /// https://simonsapin.github.io/wtf-8/
2186 InvalidWtf8,
2187
2160 BadPathName,2188 BadPathName,
2161} || UnexpectedError;2189} || UnexpectedError;
21622190
2163/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.2191/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
2164/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent2192/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
2165/// one; the latter case is known as a dangling link.2193/// one; the latter case is known as a dangling link.
2194/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2195/// On WASI, both paths should be encoded as valid UTF-8.
2196/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2166/// If `sym_link_path` exists, it will not be overwritten.2197/// If `sym_link_path` exists, it will not be overwritten.
2167/// See also `symlinkZ.2198/// See also `symlinkZ.
2168pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {2199pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
...@@ -2200,6 +2231,10 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin...@@ -2200,6 +2231,10 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
2200 .NOMEM => return error.SystemResources,2231 .NOMEM => return error.SystemResources,
2201 .NOSPC => return error.NoSpaceLeft,2232 .NOSPC => return error.NoSpaceLeft,
2202 .ROFS => return error.ReadOnlyFileSystem,2233 .ROFS => return error.ReadOnlyFileSystem,
2234 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2235 return error.InvalidUtf8
2236 else
2237 return unexpectedErrno(err),
2203 else => |err| return unexpectedErrno(err),2238 else => |err| return unexpectedErrno(err),
2204 }2239 }
2205}2240}
...@@ -2208,6 +2243,9 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin...@@ -2208,6 +2243,9 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
2208/// `target_path` **relative** to `newdirfd` directory handle.2243/// `target_path` **relative** to `newdirfd` directory handle.
2209/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent2244/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
2210/// one; the latter case is known as a dangling link.2245/// one; the latter case is known as a dangling link.
2246/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2247/// On WASI, both paths should be encoded as valid UTF-8.
2248/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2211/// If `sym_link_path` exists, it will not be overwritten.2249/// If `sym_link_path` exists, it will not be overwritten.
2212/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.2250/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
2213pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {2251pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
...@@ -2242,6 +2280,7 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c...@@ -2242,6 +2280,7 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c
2242 .NOSPC => return error.NoSpaceLeft,2280 .NOSPC => return error.NoSpaceLeft,
2243 .ROFS => return error.ReadOnlyFileSystem,2281 .ROFS => return error.ReadOnlyFileSystem,
2244 .NOTCAPABLE => return error.AccessDenied,2282 .NOTCAPABLE => return error.AccessDenied,
2283 .ILSEQ => return error.InvalidUtf8,
2245 else => |err| return unexpectedErrno(err),2284 else => |err| return unexpectedErrno(err),
2246 }2285 }
2247}2286}
...@@ -2270,6 +2309,10 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:...@@ -2270,6 +2309,10 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:
2270 .NOMEM => return error.SystemResources,2309 .NOMEM => return error.SystemResources,
2271 .NOSPC => return error.NoSpaceLeft,2310 .NOSPC => return error.NoSpaceLeft,
2272 .ROFS => return error.ReadOnlyFileSystem,2311 .ROFS => return error.ReadOnlyFileSystem,
2312 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2313 return error.InvalidUtf8
2314 else
2315 return unexpectedErrno(err),
2273 else => |err| return unexpectedErrno(err),2316 else => |err| return unexpectedErrno(err),
2274 }2317 }
2275}2318}
...@@ -2287,8 +2330,13 @@ pub const LinkError = UnexpectedError || error{...@@ -2287,8 +2330,13 @@ pub const LinkError = UnexpectedError || error{
2287 NoSpaceLeft,2330 NoSpaceLeft,
2288 ReadOnlyFileSystem,2331 ReadOnlyFileSystem,
2289 NotSameFileSystem,2332 NotSameFileSystem,
2333
2334 /// WASI-only; file paths must be valid UTF-8.
2335 InvalidUtf8,
2290};2336};
22912337
2338/// On WASI, both paths should be encoded as valid UTF-8.
2339/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2292pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {2340pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {
2293 if (builtin.os.tag == .wasi and !builtin.link_libc) {2341 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2294 return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags);2342 return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags);
...@@ -2310,10 +2358,16 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr...@@ -2310,10 +2358,16 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr
2310 .ROFS => return error.ReadOnlyFileSystem,2358 .ROFS => return error.ReadOnlyFileSystem,
2311 .XDEV => return error.NotSameFileSystem,2359 .XDEV => return error.NotSameFileSystem,
2312 .INVAL => unreachable,2360 .INVAL => unreachable,
2361 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2362 return error.InvalidUtf8
2363 else
2364 return unexpectedErrno(err),
2313 else => |err| return unexpectedErrno(err),2365 else => |err| return unexpectedErrno(err),
2314 }2366 }
2315}2367}
23162368
2369/// On WASI, both paths should be encoded as valid UTF-8.
2370/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2317pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {2371pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {
2318 if (builtin.os.tag == .wasi and !builtin.link_libc) {2372 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2319 return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) {2373 return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) {
...@@ -2328,6 +2382,8 @@ pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void...@@ -2328,6 +2382,8 @@ pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void
23282382
2329pub const LinkatError = LinkError || error{NotDir};2383pub const LinkatError = LinkError || error{NotDir};
23302384
2385/// On WASI, both paths should be encoded as valid UTF-8.
2386/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2331pub fn linkatZ(2387pub fn linkatZ(
2332 olddir: fd_t,2388 olddir: fd_t,
2333 oldpath: [*:0]const u8,2389 oldpath: [*:0]const u8,
...@@ -2356,10 +2412,16 @@ pub fn linkatZ(...@@ -2356,10 +2412,16 @@ pub fn linkatZ(
2356 .ROFS => return error.ReadOnlyFileSystem,2412 .ROFS => return error.ReadOnlyFileSystem,
2357 .XDEV => return error.NotSameFileSystem,2413 .XDEV => return error.NotSameFileSystem,
2358 .INVAL => unreachable,2414 .INVAL => unreachable,
2415 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2416 return error.InvalidUtf8
2417 else
2418 return unexpectedErrno(err),
2359 else => |err| return unexpectedErrno(err),2419 else => |err| return unexpectedErrno(err),
2360 }2420 }
2361}2421}
23622422
2423/// On WASI, both paths should be encoded as valid UTF-8.
2424/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2363pub fn linkat(2425pub fn linkat(
2364 olddir: fd_t,2426 olddir: fd_t,
2365 oldpath: []const u8,2427 oldpath: []const u8,
...@@ -2399,6 +2461,7 @@ pub fn linkat(...@@ -2399,6 +2461,7 @@ pub fn linkat(
2399 .ROFS => return error.ReadOnlyFileSystem,2461 .ROFS => return error.ReadOnlyFileSystem,
2400 .XDEV => return error.NotSameFileSystem,2462 .XDEV => return error.NotSameFileSystem,
2401 .INVAL => unreachable,2463 .INVAL => unreachable,
2464 .ILSEQ => return error.InvalidUtf8,
2402 else => |err| return unexpectedErrno(err),2465 else => |err| return unexpectedErrno(err),
2403 }2466 }
2404 }2467 }
...@@ -2422,9 +2485,13 @@ pub const UnlinkError = error{...@@ -2422,9 +2485,13 @@ pub const UnlinkError = error{
2422 SystemResources,2485 SystemResources,
2423 ReadOnlyFileSystem,2486 ReadOnlyFileSystem,
24242487
2425 /// On Windows, file paths must be valid Unicode.2488 /// WASI-only; file paths must be valid UTF-8.
2426 InvalidUtf8,2489 InvalidUtf8,
24272490
2491 /// Windows-only; file paths provided by the user must be valid WTF-8.
2492 /// https://simonsapin.github.io/wtf-8/
2493 InvalidWtf8,
2494
2428 /// On Windows, file paths cannot contain these characters:2495 /// On Windows, file paths cannot contain these characters:
2429 /// '/', '*', '?', '"', '<', '>', '|'2496 /// '/', '*', '?', '"', '<', '>', '|'
2430 BadPathName,2497 BadPathName,
...@@ -2434,6 +2501,9 @@ pub const UnlinkError = error{...@@ -2434,6 +2501,9 @@ pub const UnlinkError = error{
2434} || UnexpectedError;2501} || UnexpectedError;
24352502
2436/// Delete a name and possibly the file it refers to.2503/// Delete a name and possibly the file it refers to.
2504/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2505/// On WASI, `file_path` should be encoded as valid UTF-8.
2506/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
2437/// See also `unlinkZ`.2507/// See also `unlinkZ`.
2438pub fn unlink(file_path: []const u8) UnlinkError!void {2508pub fn unlink(file_path: []const u8) UnlinkError!void {
2439 if (builtin.os.tag == .wasi and !builtin.link_libc) {2509 if (builtin.os.tag == .wasi and !builtin.link_libc) {
...@@ -2450,7 +2520,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {...@@ -2450,7 +2520,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
2450 }2520 }
2451}2521}
24522522
2453/// Same as `unlink` except the parameter is a null terminated UTF8-encoded string.2523/// Same as `unlink` except the parameter is null terminated.
2454pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {2524pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
2455 if (builtin.os.tag == .windows) {2525 if (builtin.os.tag == .windows) {
2456 const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);2526 const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
...@@ -2473,11 +2543,15 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {...@@ -2473,11 +2543,15 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
2473 .NOTDIR => return error.NotDir,2543 .NOTDIR => return error.NotDir,
2474 .NOMEM => return error.SystemResources,2544 .NOMEM => return error.SystemResources,
2475 .ROFS => return error.ReadOnlyFileSystem,2545 .ROFS => return error.ReadOnlyFileSystem,
2546 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2547 return error.InvalidUtf8
2548 else
2549 return unexpectedErrno(err),
2476 else => |err| return unexpectedErrno(err),2550 else => |err| return unexpectedErrno(err),
2477 }2551 }
2478}2552}
24792553
2480/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded.2554/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 LE encoded.
2481pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {2555pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
2482 windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd }) catch |err| switch (err) {2556 windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd }) catch |err| switch (err) {
2483 error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true2557 error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true
...@@ -2491,6 +2565,9 @@ pub const UnlinkatError = UnlinkError || error{...@@ -2491,6 +2565,9 @@ pub const UnlinkatError = UnlinkError || error{
2491};2565};
24922566
2493/// Delete a file name and possibly the file it refers to, based on an open directory handle.2567/// Delete a file name and possibly the file it refers to, based on an open directory handle.
2568/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2569/// On WASI, `file_path` should be encoded as valid UTF-8.
2570/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
2494/// Asserts that the path parameter has no null bytes.2571/// Asserts that the path parameter has no null bytes.
2495pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {2572pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
2496 if (builtin.os.tag == .windows) {2573 if (builtin.os.tag == .windows) {
...@@ -2528,6 +2605,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro...@@ -2528,6 +2605,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
2528 .ROFS => return error.ReadOnlyFileSystem,2605 .ROFS => return error.ReadOnlyFileSystem,
2529 .NOTEMPTY => return error.DirNotEmpty,2606 .NOTEMPTY => return error.DirNotEmpty,
2530 .NOTCAPABLE => return error.AccessDenied,2607 .NOTCAPABLE => return error.AccessDenied,
2608 .ILSEQ => return error.InvalidUtf8,
25312609
2532 .INVAL => unreachable, // invalid flags, or pathname has . as last component2610 .INVAL => unreachable, // invalid flags, or pathname has . as last component
2533 .BADF => unreachable, // always a race condition2611 .BADF => unreachable, // always a race condition
...@@ -2560,6 +2638,10 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr...@@ -2560,6 +2638,10 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
2560 .ROFS => return error.ReadOnlyFileSystem,2638 .ROFS => return error.ReadOnlyFileSystem,
2561 .EXIST => return error.DirNotEmpty,2639 .EXIST => return error.DirNotEmpty,
2562 .NOTEMPTY => return error.DirNotEmpty,2640 .NOTEMPTY => return error.DirNotEmpty,
2641 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2642 return error.InvalidUtf8
2643 else
2644 return unexpectedErrno(err),
25632645
2564 .INVAL => unreachable, // invalid flags, or pathname has . as last component2646 .INVAL => unreachable, // invalid flags, or pathname has . as last component
2565 .BADF => unreachable, // always a race condition2647 .BADF => unreachable, // always a race condition
...@@ -2568,7 +2650,7 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr...@@ -2568,7 +2650,7 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
2568 }2650 }
2569}2651}
25702652
2571/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.2653/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only.
2572pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {2654pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
2573 const remove_dir = (flags & AT.REMOVEDIR) != 0;2655 const remove_dir = (flags & AT.REMOVEDIR) != 0;
2574 return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });2656 return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
...@@ -2594,7 +2676,11 @@ pub const RenameError = error{...@@ -2594,7 +2676,11 @@ pub const RenameError = error{
2594 PathAlreadyExists,2676 PathAlreadyExists,
2595 ReadOnlyFileSystem,2677 ReadOnlyFileSystem,
2596 RenameAcrossMountPoints,2678 RenameAcrossMountPoints,
2679 /// WASI-only; file paths must be valid UTF-8.
2597 InvalidUtf8,2680 InvalidUtf8,
2681 /// Windows-only; file paths provided by the user must be valid WTF-8.
2682 /// https://simonsapin.github.io/wtf-8/
2683 InvalidWtf8,
2598 BadPathName,2684 BadPathName,
2599 NoDevice,2685 NoDevice,
2600 SharingViolation,2686 SharingViolation,
...@@ -2610,6 +2696,9 @@ pub const RenameError = error{...@@ -2610,6 +2696,9 @@ pub const RenameError = error{
2610} || UnexpectedError;2696} || UnexpectedError;
26112697
2612/// Change the name or location of a file.2698/// Change the name or location of a file.
2699/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2700/// On WASI, both paths should be encoded as valid UTF-8.
2701/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2613pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {2702pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
2614 if (builtin.os.tag == .wasi and !builtin.link_libc) {2703 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2615 return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path);2704 return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path);
...@@ -2624,7 +2713,7 @@ pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {...@@ -2624,7 +2713,7 @@ pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
2624 }2713 }
2625}2714}
26262715
2627/// Same as `rename` except the parameters are null-terminated byte arrays.2716/// Same as `rename` except the parameters are null-terminated.
2628pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {2717pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {
2629 if (builtin.os.tag == .windows) {2718 if (builtin.os.tag == .windows) {
2630 const old_path_w = try windows.cStrToPrefixedFileW(null, old_path);2719 const old_path_w = try windows.cStrToPrefixedFileW(null, old_path);
...@@ -2653,11 +2742,15 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi...@@ -2653,11 +2742,15 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi
2653 .NOTEMPTY => return error.PathAlreadyExists,2742 .NOTEMPTY => return error.PathAlreadyExists,
2654 .ROFS => return error.ReadOnlyFileSystem,2743 .ROFS => return error.ReadOnlyFileSystem,
2655 .XDEV => return error.RenameAcrossMountPoints,2744 .XDEV => return error.RenameAcrossMountPoints,
2745 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2746 return error.InvalidUtf8
2747 else
2748 return unexpectedErrno(err),
2656 else => |err| return unexpectedErrno(err),2749 else => |err| return unexpectedErrno(err),
2657 }2750 }
2658}2751}
26592752
2660/// Same as `rename` except the parameters are null-terminated UTF16LE encoded byte arrays.2753/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded.
2661/// Assumes target is Windows.2754/// Assumes target is Windows.
2662pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {2755pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {
2663 const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;2756 const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
...@@ -2665,6 +2758,9 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v...@@ -2665,6 +2758,9 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v
2665}2758}
26662759
2667/// Change the name or location of a file based on an open directory handle.2760/// Change the name or location of a file based on an open directory handle.
2761/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2762/// On WASI, both paths should be encoded as valid UTF-8.
2763/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2668pub fn renameat(2764pub fn renameat(
2669 old_dir_fd: fd_t,2765 old_dir_fd: fd_t,
2670 old_path: []const u8,2766 old_path: []const u8,
...@@ -2710,11 +2806,12 @@ pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!vo...@@ -2710,11 +2806,12 @@ pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!vo
2710 .ROFS => return error.ReadOnlyFileSystem,2806 .ROFS => return error.ReadOnlyFileSystem,
2711 .XDEV => return error.RenameAcrossMountPoints,2807 .XDEV => return error.RenameAcrossMountPoints,
2712 .NOTCAPABLE => return error.AccessDenied,2808 .NOTCAPABLE => return error.AccessDenied,
2809 .ILSEQ => return error.InvalidUtf8,
2713 else => |err| return unexpectedErrno(err),2810 else => |err| return unexpectedErrno(err),
2714 }2811 }
2715}2812}
27162813
2717/// Same as `renameat` except the parameters are null-terminated byte arrays.2814/// Same as `renameat` except the parameters are null-terminated.
2718pub fn renameatZ(2815pub fn renameatZ(
2719 old_dir_fd: fd_t,2816 old_dir_fd: fd_t,
2720 old_path: [*:0]const u8,2817 old_path: [*:0]const u8,
...@@ -2749,6 +2846,10 @@ pub fn renameatZ(...@@ -2749,6 +2846,10 @@ pub fn renameatZ(
2749 .NOTEMPTY => return error.PathAlreadyExists,2846 .NOTEMPTY => return error.PathAlreadyExists,
2750 .ROFS => return error.ReadOnlyFileSystem,2847 .ROFS => return error.ReadOnlyFileSystem,
2751 .XDEV => return error.RenameAcrossMountPoints,2848 .XDEV => return error.RenameAcrossMountPoints,
2849 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2850 return error.InvalidUtf8
2851 else
2852 return unexpectedErrno(err),
2752 else => |err| return unexpectedErrno(err),2853 else => |err| return unexpectedErrno(err),
2753 }2854 }
2754}2855}
...@@ -2860,6 +2961,9 @@ pub fn renameatW(...@@ -2860,6 +2961,9 @@ pub fn renameatW(
2860 }2961 }
2861}2962}
28622963
2964/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2965/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.
2966/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.
2863pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {2967pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
2864 if (builtin.os.tag == .windows) {2968 if (builtin.os.tag == .windows) {
2865 const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path);2969 const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path);
...@@ -2891,14 +2995,16 @@ pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirErr...@@ -2891,14 +2995,16 @@ pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirErr
2891 .NOTDIR => return error.NotDir,2995 .NOTDIR => return error.NotDir,
2892 .ROFS => return error.ReadOnlyFileSystem,2996 .ROFS => return error.ReadOnlyFileSystem,
2893 .NOTCAPABLE => return error.AccessDenied,2997 .NOTCAPABLE => return error.AccessDenied,
2998 .ILSEQ => return error.InvalidUtf8,
2894 else => |err| return unexpectedErrno(err),2999 else => |err| return unexpectedErrno(err),
2895 }3000 }
2896}3001}
28973002
3003/// Same as `mkdirat` except the parameters are null-terminated.
2898pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {3004pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
2899 if (builtin.os.tag == .windows) {3005 if (builtin.os.tag == .windows) {
2900 const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path);3006 const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path);
2901 return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);3007 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
2902 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {3008 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
2903 return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);3009 return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);
2904 }3010 }
...@@ -2920,10 +3026,15 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr...@@ -2920,10 +3026,15 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
2920 .ROFS => return error.ReadOnlyFileSystem,3026 .ROFS => return error.ReadOnlyFileSystem,
2921 // dragonfly: when dir_fd is unlinked from filesystem3027 // dragonfly: when dir_fd is unlinked from filesystem
2922 .NOTCONN => return error.FileNotFound,3028 .NOTCONN => return error.FileNotFound,
3029 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3030 return error.InvalidUtf8
3031 else
3032 return unexpectedErrno(err),
2923 else => |err| return unexpectedErrno(err),3033 else => |err| return unexpectedErrno(err),
2924 }3034 }
2925}3035}
29263036
3037/// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded.
2927pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {3038pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
2928 _ = mode;3039 _ = mode;
2929 const sub_dir_handle = windows.OpenFile(sub_path_w, .{3040 const sub_dir_handle = windows.OpenFile(sub_path_w, .{
...@@ -2955,7 +3066,11 @@ pub const MakeDirError = error{...@@ -2955,7 +3066,11 @@ pub const MakeDirError = error{
2955 NoSpaceLeft,3066 NoSpaceLeft,
2956 NotDir,3067 NotDir,
2957 ReadOnlyFileSystem,3068 ReadOnlyFileSystem,
3069 /// WASI-only; file paths must be valid UTF-8.
2958 InvalidUtf8,3070 InvalidUtf8,
3071 /// Windows-only; file paths provided by the user must be valid WTF-8.
3072 /// https://simonsapin.github.io/wtf-8/
3073 InvalidWtf8,
2959 BadPathName,3074 BadPathName,
2960 NoDevice,3075 NoDevice,
2961 /// On Windows, `\\server` or `\\server\share` was not found.3076 /// On Windows, `\\server` or `\\server\share` was not found.
...@@ -2964,6 +3079,9 @@ pub const MakeDirError = error{...@@ -2964,6 +3079,9 @@ pub const MakeDirError = error{
29643079
2965/// Create a directory.3080/// Create a directory.
2966/// `mode` is ignored on Windows and WASI.3081/// `mode` is ignored on Windows and WASI.
3082/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3083/// On WASI, `dir_path` should be encoded as valid UTF-8.
3084/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
2967pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {3085pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
2968 if (builtin.os.tag == .wasi and !builtin.link_libc) {3086 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2969 return mkdirat(wasi.AT.FDCWD, dir_path, mode);3087 return mkdirat(wasi.AT.FDCWD, dir_path, mode);
...@@ -2976,7 +3094,10 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {...@@ -2976,7 +3094,10 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
2976 }3094 }
2977}3095}
29783096
2979/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.3097/// Same as `mkdir` but the parameter is null-terminated.
3098/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3099/// On WASI, `dir_path` should be encoded as valid UTF-8.
3100/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
2980pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {3101pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
2981 if (builtin.os.tag == .windows) {3102 if (builtin.os.tag == .windows) {
2982 const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);3103 const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
...@@ -2999,11 +3120,15 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {...@@ -2999,11 +3120,15 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
2999 .NOSPC => return error.NoSpaceLeft,3120 .NOSPC => return error.NoSpaceLeft,
3000 .NOTDIR => return error.NotDir,3121 .NOTDIR => return error.NotDir,
3001 .ROFS => return error.ReadOnlyFileSystem,3122 .ROFS => return error.ReadOnlyFileSystem,
3123 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3124 return error.InvalidUtf8
3125 else
3126 return unexpectedErrno(err),
3002 else => |err| return unexpectedErrno(err),3127 else => |err| return unexpectedErrno(err),
3003 }3128 }
3004}3129}
30053130
3006/// Windows-only. Same as `mkdir` but the parameters is WTF16 encoded.3131/// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded.
3007pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {3132pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
3008 _ = mode;3133 _ = mode;
3009 const sub_dir_handle = windows.OpenFile(dir_path_w, .{3134 const sub_dir_handle = windows.OpenFile(dir_path_w, .{
...@@ -3031,13 +3156,20 @@ pub const DeleteDirError = error{...@@ -3031,13 +3156,20 @@ pub const DeleteDirError = error{
3031 NotDir,3156 NotDir,
3032 DirNotEmpty,3157 DirNotEmpty,
3033 ReadOnlyFileSystem,3158 ReadOnlyFileSystem,
3159 /// WASI-only; file paths must be valid UTF-8.
3034 InvalidUtf8,3160 InvalidUtf8,
3161 /// Windows-only; file paths provided by the user must be valid WTF-8.
3162 /// https://simonsapin.github.io/wtf-8/
3163 InvalidWtf8,
3035 BadPathName,3164 BadPathName,
3036 /// On Windows, `\\server` or `\\server\share` was not found.3165 /// On Windows, `\\server` or `\\server\share` was not found.
3037 NetworkNotFound,3166 NetworkNotFound,
3038} || UnexpectedError;3167} || UnexpectedError;
30393168
3040/// Deletes an empty directory.3169/// Deletes an empty directory.
3170/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3171/// On WASI, `dir_path` should be encoded as valid UTF-8.
3172/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
3041pub fn rmdir(dir_path: []const u8) DeleteDirError!void {3173pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
3042 if (builtin.os.tag == .wasi and !builtin.link_libc) {3174 if (builtin.os.tag == .wasi and !builtin.link_libc) {
3043 return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {3175 return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {
...@@ -3055,6 +3187,9 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {...@@ -3055,6 +3187,9 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
3055}3187}
30563188
3057/// Same as `rmdir` except the parameter is null-terminated.3189/// Same as `rmdir` except the parameter is null-terminated.
3190/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3191/// On WASI, `dir_path` should be encoded as valid UTF-8.
3192/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
3058pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {3193pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
3059 if (builtin.os.tag == .windows) {3194 if (builtin.os.tag == .windows) {
3060 const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);3195 const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
...@@ -3077,11 +3212,15 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {...@@ -3077,11 +3212,15 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
3077 .EXIST => return error.DirNotEmpty,3212 .EXIST => return error.DirNotEmpty,
3078 .NOTEMPTY => return error.DirNotEmpty,3213 .NOTEMPTY => return error.DirNotEmpty,
3079 .ROFS => return error.ReadOnlyFileSystem,3214 .ROFS => return error.ReadOnlyFileSystem,
3215 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3216 return error.InvalidUtf8
3217 else
3218 return unexpectedErrno(err),
3080 else => |err| return unexpectedErrno(err),3219 else => |err| return unexpectedErrno(err),
3081 }3220 }
3082}3221}
30833222
3084/// Windows-only. Same as `rmdir` except the parameter is WTF16 encoded.3223/// Windows-only. Same as `rmdir` except the parameter is WTF-16 LE encoded.
3085pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {3224pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
3086 return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {3225 return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
3087 error.IsDir => unreachable,3226 error.IsDir => unreachable,
...@@ -3098,21 +3237,25 @@ pub const ChangeCurDirError = error{...@@ -3098,21 +3237,25 @@ pub const ChangeCurDirError = error{
3098 SystemResources,3237 SystemResources,
3099 NotDir,3238 NotDir,
3100 BadPathName,3239 BadPathName,
31013240 /// WASI-only; file paths must be valid UTF-8.
3102 /// On Windows, file paths must be valid Unicode.
3103 InvalidUtf8,3241 InvalidUtf8,
3242 /// Windows-only; file paths provided by the user must be valid WTF-8.
3243 /// https://simonsapin.github.io/wtf-8/
3244 InvalidWtf8,
3104} || UnexpectedError;3245} || UnexpectedError;
31053246
3106/// Changes the current working directory of the calling process.3247/// Changes the current working directory of the calling process.
3107/// `dir_path` is recommended to be a UTF-8 encoded string.3248/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3249/// On WASI, `dir_path` should be encoded as valid UTF-8.
3250/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
3108pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {3251pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
3109 if (builtin.os.tag == .wasi and !builtin.link_libc) {3252 if (builtin.os.tag == .wasi and !builtin.link_libc) {
3110 @compileError("WASI does not support os.chdir");3253 @compileError("WASI does not support os.chdir");
3111 } else if (builtin.os.tag == .windows) {3254 } else if (builtin.os.tag == .windows) {
3112 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;3255 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3113 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);3256 const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], dir_path);
3114 if (len > utf16_dir_path.len) return error.NameTooLong;3257 if (len > wtf16_dir_path.len) return error.NameTooLong;
3115 return chdirW(utf16_dir_path[0..len]);3258 return chdirW(wtf16_dir_path[0..len]);
3116 } else {3259 } else {
3117 const dir_path_c = try toPosixPath(dir_path);3260 const dir_path_c = try toPosixPath(dir_path);
3118 return chdirZ(&dir_path_c);3261 return chdirZ(&dir_path_c);
...@@ -3120,12 +3263,15 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {...@@ -3120,12 +3263,15 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
3120}3263}
31213264
3122/// Same as `chdir` except the parameter is null-terminated.3265/// Same as `chdir` except the parameter is null-terminated.
3266/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3267/// On WASI, `dir_path` should be encoded as valid UTF-8.
3268/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
3123pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {3269pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
3124 if (builtin.os.tag == .windows) {3270 if (builtin.os.tag == .windows) {
3125 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;3271 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3126 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], mem.span(dir_path));3272 const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], mem.span(dir_path));
3127 if (len > utf16_dir_path.len) return error.NameTooLong;3273 if (len > wtf16_dir_path.len) return error.NameTooLong;
3128 return chdirW(utf16_dir_path[0..len]);3274 return chdirW(wtf16_dir_path[0..len]);
3129 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {3275 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
3130 return chdir(mem.span(dir_path));3276 return chdir(mem.span(dir_path));
3131 }3277 }
...@@ -3139,11 +3285,15 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {...@@ -3139,11 +3285,15 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
3139 .NOENT => return error.FileNotFound,3285 .NOENT => return error.FileNotFound,
3140 .NOMEM => return error.SystemResources,3286 .NOMEM => return error.SystemResources,
3141 .NOTDIR => return error.NotDir,3287 .NOTDIR => return error.NotDir,
3288 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3289 return error.InvalidUtf8
3290 else
3291 return unexpectedErrno(err),
3142 else => |err| return unexpectedErrno(err),3292 else => |err| return unexpectedErrno(err),
3143 }3293 }
3144}3294}
31453295
3146/// Windows-only. Same as `chdir` except the parameter is WTF16 encoded.3296/// Windows-only. Same as `chdir` except the parameter is WTF16 LE encoded.
3147pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void {3297pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void {
3148 windows.SetCurrentDirectory(dir_path) catch |err| switch (err) {3298 windows.SetCurrentDirectory(dir_path) catch |err| switch (err) {
3149 error.NoDevice => return error.FileSystem,3299 error.NoDevice => return error.FileSystem,
...@@ -3183,7 +3333,11 @@ pub const ReadLinkError = error{...@@ -3183,7 +3333,11 @@ pub const ReadLinkError = error{
3183 SystemResources,3333 SystemResources,
3184 NotLink,3334 NotLink,
3185 NotDir,3335 NotDir,
3336 /// WASI-only; file paths must be valid UTF-8.
3186 InvalidUtf8,3337 InvalidUtf8,
3338 /// Windows-only; file paths provided by the user must be valid WTF-8.
3339 /// https://simonsapin.github.io/wtf-8/
3340 InvalidWtf8,
3187 BadPathName,3341 BadPathName,
3188 /// Windows-only. This error may occur if the opened reparse point is3342 /// Windows-only. This error may occur if the opened reparse point is
3189 /// of unsupported type.3343 /// of unsupported type.
...@@ -3193,7 +3347,13 @@ pub const ReadLinkError = error{...@@ -3193,7 +3347,13 @@ pub const ReadLinkError = error{
3193} || UnexpectedError;3347} || UnexpectedError;
31943348
3195/// Read value of a symbolic link.3349/// Read value of a symbolic link.
3350/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3351/// On WASI, `file_path` should be encoded as valid UTF-8.
3352/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
3196/// The return value is a slice of `out_buffer` from index 0.3353/// The return value is a slice of `out_buffer` from index 0.
3354/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3355/// On WASI, the result is encoded as UTF-8.
3356/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
3197pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {3357pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
3198 if (builtin.os.tag == .wasi and !builtin.link_libc) {3358 if (builtin.os.tag == .wasi and !builtin.link_libc) {
3199 return readlinkat(wasi.AT.FDCWD, file_path, out_buffer);3359 return readlinkat(wasi.AT.FDCWD, file_path, out_buffer);
...@@ -3206,7 +3366,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {...@@ -3206,7 +3366,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
3206 }3366 }
3207}3367}
32083368
3209/// Windows-only. Same as `readlink` except `file_path` is WTF16 encoded.3369/// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded.
3370/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3210/// See also `readlinkZ`.3371/// See also `readlinkZ`.
3211pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {3372pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
3212 return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);3373 return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
...@@ -3215,7 +3376,7 @@ pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {...@@ -3215,7 +3376,7 @@ pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
3215/// Same as `readlink` except `file_path` is null-terminated.3376/// Same as `readlink` except `file_path` is null-terminated.
3216pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {3377pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
3217 if (builtin.os.tag == .windows) {3378 if (builtin.os.tag == .windows) {
3218 const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);3379 const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
3219 return readlinkW(file_path_w.span(), out_buffer);3380 return readlinkW(file_path_w.span(), out_buffer);
3220 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {3381 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
3221 return readlink(mem.sliceTo(file_path, 0), out_buffer);3382 return readlink(mem.sliceTo(file_path, 0), out_buffer);
...@@ -3232,12 +3393,22 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8...@@ -3232,12 +3393,22 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
3232 .NOENT => return error.FileNotFound,3393 .NOENT => return error.FileNotFound,
3233 .NOMEM => return error.SystemResources,3394 .NOMEM => return error.SystemResources,
3234 .NOTDIR => return error.NotDir,3395 .NOTDIR => return error.NotDir,
3396 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3397 return error.InvalidUtf8
3398 else
3399 return unexpectedErrno(err),
3235 else => |err| return unexpectedErrno(err),3400 else => |err| return unexpectedErrno(err),
3236 }3401 }
3237}3402}
32383403
3239/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle.3404/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle.
3405/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3406/// On WASI, `file_path` should be encoded as valid UTF-8.
3407/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
3240/// The return value is a slice of `out_buffer` from index 0.3408/// The return value is a slice of `out_buffer` from index 0.
3409/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3410/// On WASI, the result is encoded as UTF-8.
3411/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
3241/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.3412/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
3242pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {3413pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
3243 if (builtin.os.tag == .wasi and !builtin.link_libc) {3414 if (builtin.os.tag == .wasi and !builtin.link_libc) {
...@@ -3267,11 +3438,13 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read...@@ -3267,11 +3438,13 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
3267 .NOMEM => return error.SystemResources,3438 .NOMEM => return error.SystemResources,
3268 .NOTDIR => return error.NotDir,3439 .NOTDIR => return error.NotDir,
3269 .NOTCAPABLE => return error.AccessDenied,3440 .NOTCAPABLE => return error.AccessDenied,
3441 .ILSEQ => return error.InvalidUtf8,
3270 else => |err| return unexpectedErrno(err),3442 else => |err| return unexpectedErrno(err),
3271 }3443 }
3272}3444}
32733445
3274/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.3446/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded.
3447/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3275/// See also `readlinkat`.3448/// See also `readlinkat`.
3276pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {3449pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
3277 return windows.ReadLink(dirfd, file_path, out_buffer);3450 return windows.ReadLink(dirfd, file_path, out_buffer);
...@@ -3298,6 +3471,10 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read...@@ -3298,6 +3471,10 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
3298 .NOENT => return error.FileNotFound,3471 .NOENT => return error.FileNotFound,
3299 .NOMEM => return error.SystemResources,3472 .NOMEM => return error.SystemResources,
3300 .NOTDIR => return error.NotDir,3473 .NOTDIR => return error.NotDir,
3474 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3475 return error.InvalidUtf8
3476 else
3477 return unexpectedErrno(err),
3301 else => |err| return unexpectedErrno(err),3478 else => |err| return unexpectedErrno(err),
3302 }3479 }
3303}3480}
...@@ -4274,10 +4451,18 @@ pub fn fstat_wasi(fd: fd_t) FStatError!wasi.filestat_t {...@@ -4274,10 +4451,18 @@ pub fn fstat_wasi(fd: fd_t) FStatError!wasi.filestat_t {
4274 }4451 }
4275}4452}
42764453
4277pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLinkLoop };4454pub const FStatAtError = FStatError || error{
4455 NameTooLong,
4456 FileNotFound,
4457 SymLinkLoop,
4458 /// WASI-only; file paths must be valid UTF-8.
4459 InvalidUtf8,
4460};
42784461
4279/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`4462/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`
4280/// which is relative to `dirfd` handle.4463/// which is relative to `dirfd` handle.
4464/// On WASI, `pathname` should be encoded as valid UTF-8.
4465/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
4281/// See also `fstatatZ` and `fstatat_wasi`.4466/// See also `fstatatZ` and `fstatat_wasi`.
4282pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {4467pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
4283 if (builtin.os.tag == .wasi and !builtin.link_libc) {4468 if (builtin.os.tag == .wasi and !builtin.link_libc) {
...@@ -4294,6 +4479,7 @@ pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat...@@ -4294,6 +4479,7 @@ pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat
4294}4479}
42954480
4296/// WASI-only. Same as `fstatat` but targeting WASI.4481/// WASI-only. Same as `fstatat` but targeting WASI.
4482/// `pathname` should be encoded as valid UTF-8.
4297/// See also `fstatat`.4483/// See also `fstatat`.
4298pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t) FStatAtError!wasi.filestat_t {4484pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t) FStatAtError!wasi.filestat_t {
4299 var stat: wasi.filestat_t = undefined;4485 var stat: wasi.filestat_t = undefined;
...@@ -4308,6 +4494,7 @@ pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t...@@ -4308,6 +4494,7 @@ pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t
4308 .NOENT => return error.FileNotFound,4494 .NOENT => return error.FileNotFound,
4309 .NOTDIR => return error.FileNotFound,4495 .NOTDIR => return error.FileNotFound,
4310 .NOTCAPABLE => return error.AccessDenied,4496 .NOTCAPABLE => return error.AccessDenied,
4497 .ILSEQ => return error.InvalidUtf8,
4311 else => |err| return unexpectedErrno(err),4498 else => |err| return unexpectedErrno(err),
4312 }4499 }
4313}4500}
...@@ -4337,6 +4524,10 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S...@@ -4337,6 +4524,10 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S
4337 .LOOP => return error.SymLinkLoop,4524 .LOOP => return error.SymLinkLoop,
4338 .NOENT => return error.FileNotFound,4525 .NOENT => return error.FileNotFound,
4339 .NOTDIR => return error.FileNotFound,4526 .NOTDIR => return error.FileNotFound,
4527 .ILSEQ => |err| if (builtin.os.tag == .wasi)
4528 return error.InvalidUtf8
4529 else
4530 return unexpectedErrno(err),
4340 else => |err| return unexpectedErrno(err),4531 else => |err| return unexpectedErrno(err),
4341 }4532 }
4342}4533}
...@@ -4693,12 +4884,17 @@ pub const AccessError = error{...@@ -4693,12 +4884,17 @@ pub const AccessError = error{
4693 FileBusy,4884 FileBusy,
4694 SymLinkLoop,4885 SymLinkLoop,
4695 ReadOnlyFileSystem,4886 ReadOnlyFileSystem,
46964887 /// WASI-only; file paths must be valid UTF-8.
4697 /// On Windows, file paths must be valid Unicode.
4698 InvalidUtf8,4888 InvalidUtf8,
4889 /// Windows-only; file paths provided by the user must be valid WTF-8.
4890 /// https://simonsapin.github.io/wtf-8/
4891 InvalidWtf8,
4699} || UnexpectedError;4892} || UnexpectedError;
47004893
4701/// check user's permissions for a file4894/// check user's permissions for a file
4895/// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
4896/// On WASI, `path` should be encoded as valid UTF-8.
4897/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
4702/// TODO currently this assumes `mode` is `F.OK` on Windows.4898/// TODO currently this assumes `mode` is `F.OK` on Windows.
4703pub fn access(path: []const u8, mode: u32) AccessError!void {4899pub fn access(path: []const u8, mode: u32) AccessError!void {
4704 if (builtin.os.tag == .windows) {4900 if (builtin.os.tag == .windows) {
...@@ -4740,12 +4936,16 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {...@@ -4740,12 +4936,16 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
4740 .FAULT => unreachable,4936 .FAULT => unreachable,
4741 .IO => return error.InputOutput,4937 .IO => return error.InputOutput,
4742 .NOMEM => return error.SystemResources,4938 .NOMEM => return error.SystemResources,
4939 .ILSEQ => |err| if (builtin.os.tag == .wasi)
4940 return error.InvalidUtf8
4941 else
4942 return unexpectedErrno(err),
4743 else => |err| return unexpectedErrno(err),4943 else => |err| return unexpectedErrno(err),
4744 }4944 }
4745}4945}
47464946
4747/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.4947/// Call from Windows-specific code if you already have a WTF-16LE encoded, null terminated string.
4748/// Otherwise use `access` or `accessC`.4948/// Otherwise use `access` or `accessZ`.
4749/// TODO currently this ignores `mode`.4949/// TODO currently this ignores `mode`.
4750pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!void {4950pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!void {
4751 _ = mode;4951 _ = mode;
...@@ -4762,6 +4962,9 @@ pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!v...@@ -4762,6 +4962,9 @@ pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!v
4762}4962}
47634963
4764/// Check user's permissions for a file, based on an open directory handle.4964/// Check user's permissions for a file, based on an open directory handle.
4965/// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
4966/// On WASI, `path` should be encoded as valid UTF-8.
4967/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
4765/// TODO currently this ignores `mode` and `flags` on Windows.4968/// TODO currently this ignores `mode` and `flags` on Windows.
4766pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {4969pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
4767 if (builtin.os.tag == .windows) {4970 if (builtin.os.tag == .windows) {
...@@ -4832,6 +5035,10 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces...@@ -4832,6 +5035,10 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces
4832 .FAULT => unreachable,5035 .FAULT => unreachable,
4833 .IO => return error.InputOutput,5036 .IO => return error.InputOutput,
4834 .NOMEM => return error.SystemResources,5037 .NOMEM => return error.SystemResources,
5038 .ILSEQ => |err| if (builtin.os.tag == .wasi)
5039 return error.InvalidUtf8
5040 else
5041 return unexpectedErrno(err),
4835 else => |err| return unexpectedErrno(err),5042 else => |err| return unexpectedErrno(err),
4836 }5043 }
4837}5044}
...@@ -5339,8 +5546,9 @@ pub const RealPathError = error{...@@ -5339,8 +5546,9 @@ pub const RealPathError = error{
5339 /// On WASI, the current CWD may not be associated with an absolute path.5546 /// On WASI, the current CWD may not be associated with an absolute path.
5340 InvalidHandle,5547 InvalidHandle,
53415548
5342 /// On Windows, file paths must be valid Unicode.5549 /// Windows-only; file paths provided by the user must be valid WTF-8.
5343 InvalidUtf8,5550 /// https://simonsapin.github.io/wtf-8/
5551 InvalidWtf8,
53445552
5345 /// On Windows, `\\server` or `\\server\share` was not found.5553 /// On Windows, `\\server` or `\\server\share` was not found.
5346 NetworkNotFound,5554 NetworkNotFound,
...@@ -5362,8 +5570,12 @@ pub const RealPathError = error{...@@ -5362,8 +5570,12 @@ pub const RealPathError = error{
5362/// Return the canonicalized absolute pathname.5570/// Return the canonicalized absolute pathname.
5363/// Expands all symbolic links and resolves references to `.`, `..`, and5571/// Expands all symbolic links and resolves references to `.`, `..`, and
5364/// extra `/` characters in `pathname`.5572/// extra `/` characters in `pathname`.
5573/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
5574/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
5365/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.5575/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
5366/// See also `realpathZ` and `realpathW`.5576/// See also `realpathZ` and `realpathW`.
5577/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
5578/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
5367/// Calling this function is usually a bug.5579/// Calling this function is usually a bug.
5368pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {5580pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
5369 if (builtin.os.tag == .windows) {5581 if (builtin.os.tag == .windows) {
...@@ -5402,6 +5614,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP...@@ -5402,6 +5614,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
5402 error.WouldBlock => unreachable,5614 error.WouldBlock => unreachable,
5403 error.FileBusy => unreachable, // not asking for write permissions5615 error.FileBusy => unreachable, // not asking for write permissions
5404 error.InvalidHandle => unreachable, // WASI-only5616 error.InvalidHandle => unreachable, // WASI-only
5617 error.InvalidUtf8 => unreachable, // WASI-only
5405 else => |e| return e,5618 else => |e| return e,
5406 };5619 };
5407 defer close(fd);5620 defer close(fd);
...@@ -5425,7 +5638,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP...@@ -5425,7 +5638,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
5425 return mem.sliceTo(result_path, 0);5638 return mem.sliceTo(result_path, 0);
5426}5639}
54275640
5428/// Same as `realpath` except `pathname` is UTF16LE-encoded.5641/// Same as `realpath` except `pathname` is WTF16LE-encoded.
5642/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
5429/// Calling this function is usually a bug.5643/// Calling this function is usually a bug.
5430pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {5644pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
5431 const w = windows;5645 const w = windows;
...@@ -5475,6 +5689,8 @@ pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {...@@ -5475,6 +5689,8 @@ pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
5475/// This function is very host-specific and is not universally supported by all hosts.5689/// This function is very host-specific and is not universally supported by all hosts.
5476/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is5690/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
5477/// unsupported on WASI.5691/// unsupported on WASI.
5692/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
5693/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
5478/// Calling this function is usually a bug.5694/// Calling this function is usually a bug.
5479pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {5695pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
5480 if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {5696 if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
...@@ -5485,10 +5701,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {...@@ -5485,10 +5701,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
5485 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;5701 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
5486 const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);5702 const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
54875703
5488 // TODO: Windows file paths can be arbitrary arrays of u16 values5704 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
5489 // and must not fail with InvalidUtf8.
5490 const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch
5491 return error.InvalidUtf8;
5492 return out_buffer[0..end_index];5705 return out_buffer[0..end_index];
5493 },5706 },
5494 .macos, .ios, .watchos, .tvos => {5707 .macos, .ios, .watchos, .tvos => {
...@@ -5512,8 +5725,12 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {...@@ -5512,8 +5725,12 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
55125725
5513 const target = readlinkZ(proc_path, out_buffer) catch |err| {5726 const target = readlinkZ(proc_path, out_buffer) catch |err| {
5514 switch (err) {5727 switch (err) {
5515 error.UnsupportedReparsePointType => unreachable, // Windows only,
5516 error.NotLink => unreachable,5728 error.NotLink => unreachable,
5729 error.BadPathName => unreachable,
5730 error.InvalidUtf8 => unreachable, // WASI-only
5731 error.InvalidWtf8 => unreachable, // Windows-only
5732 error.UnsupportedReparsePointType => unreachable, // Windows-only
5733 error.NetworkNotFound => unreachable, // Windows-only
5517 else => |e| return e,5734 else => |e| return e,
5518 }5735 }
5519 };5736 };
lib/std/os/windows.zig+51-37
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1//! This file contains thin wrappers around Windows-specific APIs, with these1//! This file contains thin wrappers around Windows-specific APIs, with these
2//! specific goals in mind:2//! specific goals in mind:
3//! * Convert "errno"-style error codes into Zig errors.3//! * Convert "errno"-style error codes into Zig errors.
4//! * When null-terminated or UTF16LE byte buffers are required, provide APIs which accept4//! * When null-terminated or WTF16LE byte buffers are required, provide APIs which accept
5//! slices as well as APIs which accept null-terminated UTF16LE byte buffers.5//! slices as well as APIs which accept null-terminated WTF16LE byte buffers.
66
7const builtin = @import("builtin");7const builtin = @import("builtin");
8const std = @import("../std.zig");8const std = @import("../std.zig");
...@@ -548,7 +548,6 @@ pub fn WriteFile(...@@ -548,7 +548,6 @@ pub fn WriteFile(
548548
549pub const SetCurrentDirectoryError = error{549pub const SetCurrentDirectoryError = error{
550 NameTooLong,550 NameTooLong,
551 InvalidUtf8,
552 FileNotFound,551 FileNotFound,
553 NotDir,552 NotDir,
554 AccessDenied,553 AccessDenied,
...@@ -587,24 +586,24 @@ pub const GetCurrentDirectoryError = error{...@@ -587,24 +586,24 @@ pub const GetCurrentDirectoryError = error{
587};586};
588587
589/// The result is a slice of `buffer`, indexed from 0.588/// The result is a slice of `buffer`, indexed from 0.
589/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
590pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {590pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
591 var utf16le_buf: [PATH_MAX_WIDE]u16 = undefined;591 var wtf16le_buf: [PATH_MAX_WIDE]u16 = undefined;
592 const result = kernel32.GetCurrentDirectoryW(utf16le_buf.len, &utf16le_buf);592 const result = kernel32.GetCurrentDirectoryW(wtf16le_buf.len, &wtf16le_buf);
593 if (result == 0) {593 if (result == 0) {
594 switch (kernel32.GetLastError()) {594 switch (kernel32.GetLastError()) {
595 else => |err| return unexpectedError(err),595 else => |err| return unexpectedError(err),
596 }596 }
597 }597 }
598 assert(result <= utf16le_buf.len);598 assert(result <= wtf16le_buf.len);
599 const utf16le_slice = utf16le_buf[0..result];599 const wtf16le_slice = wtf16le_buf[0..result];
600 // Trust that Windows gives us valid UTF-16LE.
601 var end_index: usize = 0;600 var end_index: usize = 0;
602 var it = std.unicode.Utf16LeIterator.init(utf16le_slice);601 var it = std.unicode.Wtf16LeIterator.init(wtf16le_slice);
603 while (it.nextCodepoint() catch unreachable) |codepoint| {602 while (it.nextCodepoint()) |codepoint| {
604 const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable;603 const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable;
605 if (end_index + seq_len >= buffer.len)604 if (end_index + seq_len >= buffer.len)
606 return error.NameTooLong;605 return error.NameTooLong;
607 end_index += std.unicode.utf8Encode(codepoint, buffer[end_index..]) catch unreachable;606 end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable;
608 }607 }
609 return buffer[0..end_index];608 return buffer[0..end_index];
610}609}
...@@ -812,6 +811,8 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin...@@ -812,6 +811,8 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
812 }811 }
813}812}
814813
814/// Asserts that there is enough space is `out_buffer`.
815/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
815fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {816fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {
816 const win32_namespace_path = path: {817 const win32_namespace_path = path: {
817 if (is_relative) break :path path;818 if (is_relative) break :path path;
...@@ -821,7 +822,7 @@ fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u...@@ -821,7 +822,7 @@ fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u
821 };822 };
822 break :path win32_path.span();823 break :path win32_path.span();
823 };824 };
824 const out_len = std.unicode.utf16LeToUtf8(out_buffer, win32_namespace_path) catch unreachable;825 const out_len = std.unicode.wtf16LeToWtf8(out_buffer, win32_namespace_path);
825 return out_buffer[0..out_len];826 return out_buffer[0..out_len];
826}827}
827828
...@@ -1942,13 +1943,13 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {...@@ -1942,13 +1943,13 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {
1942 if (@inComptime() or builtin.os.tag != .windows) {1943 if (@inComptime() or builtin.os.tag != .windows) {
1943 // This function compares the strings code unit by code unit (aka u16-to-u16),1944 // This function compares the strings code unit by code unit (aka u16-to-u16),
1944 // so any length difference implies inequality. In other words, there's no possible1945 // so any length difference implies inequality. In other words, there's no possible
1945 // conversion that changes the number of UTF-16 code units needed for the uppercase/lowercase1946 // conversion that changes the number of WTF-16 code units needed for the uppercase/lowercase
1946 // version in the conversion table since only codepoints <= max(u16) are eligible1947 // version in the conversion table since only codepoints <= max(u16) are eligible
1947 // for conversion at all.1948 // for conversion at all.
1948 if (a.len != b.len) return false;1949 if (a.len != b.len) return false;
19491950
1950 for (a, b) |a_c, b_c| {1951 for (a, b) |a_c, b_c| {
1951 // The slices are always UTF-16 LE, so need to convert the elements to native1952 // The slices are always WTF-16 LE, so need to convert the elements to native
1952 // endianness for the uppercasing1953 // endianness for the uppercasing
1953 const a_c_native = std.mem.littleToNative(u16, a_c);1954 const a_c_native = std.mem.littleToNative(u16, a_c);
1954 const b_c_native = std.mem.littleToNative(u16, b_c);1955 const b_c_native = std.mem.littleToNative(u16, b_c);
...@@ -1975,18 +1976,18 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {...@@ -1975,18 +1976,18 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {
1975 return ntdll.RtlEqualUnicodeString(&a_string, &b_string, TRUE) == TRUE;1976 return ntdll.RtlEqualUnicodeString(&a_string, &b_string, TRUE) == TRUE;
1976}1977}
19771978
1978/// Compares two UTF-8 strings using the equivalent functionality of1979/// Compares two WTF-8 strings using the equivalent functionality of
1979/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).1980/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
1980/// This function can be called on any target.1981/// This function can be called on any target.
1981/// Assumes `a` and `b` are valid UTF-8.1982/// Assumes `a` and `b` are valid WTF-8.
1982pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {1983pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
1983 // A length equality check is not possible here because there are1984 // A length equality check is not possible here because there are
1984 // some codepoints that have a different length uppercase UTF-8 representations1985 // some codepoints that have a different length uppercase UTF-8 representations
1985 // than their lowercase counterparts, e.g. U+0250 (2 bytes) <-> U+2C6F (3 bytes).1986 // than their lowercase counterparts, e.g. U+0250 (2 bytes) <-> U+2C6F (3 bytes).
1986 // There are 7 such codepoints in the uppercase data used by Windows.1987 // There are 7 such codepoints in the uppercase data used by Windows.
19871988
1988 var a_utf8_it = std.unicode.Utf8View.initUnchecked(a).iterator();1989 var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();
1989 var b_utf8_it = std.unicode.Utf8View.initUnchecked(b).iterator();1990 var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator();
19901991
1991 // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a1992 // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
1992 // redundant copy of the uppercase data.1993 // redundant copy of the uppercase data.
...@@ -1996,8 +1997,8 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {...@@ -1996,8 +1997,8 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
1996 };1997 };
19971998
1998 while (true) {1999 while (true) {
1999 const a_cp = a_utf8_it.nextCodepoint() orelse break;2000 const a_cp = a_wtf8_it.nextCodepoint() orelse break;
2000 const b_cp = b_utf8_it.nextCodepoint() orelse return false;2001 const b_cp = b_wtf8_it.nextCodepoint() orelse return false;
20012002
2002 if (a_cp <= std.math.maxInt(u16) and b_cp <= std.math.maxInt(u16)) {2003 if (a_cp <= std.math.maxInt(u16) and b_cp <= std.math.maxInt(u16)) {
2003 if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) {2004 if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) {
...@@ -2008,26 +2009,26 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {...@@ -2008,26 +2009,26 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
2008 }2009 }
2009 }2010 }
2010 // Make sure there are no leftover codepoints in b2011 // Make sure there are no leftover codepoints in b
2011 if (b_utf8_it.nextCodepoint() != null) return false;2012 if (b_wtf8_it.nextCodepoint() != null) return false;
20122013
2013 return true;2014 return true;
2014}2015}
20152016
2016fn testEqlIgnoreCase(comptime expect_eql: bool, comptime a: []const u8, comptime b: []const u8) !void {2017fn testEqlIgnoreCase(comptime expect_eql: bool, comptime a: []const u8, comptime b: []const u8) !void {
2017 try std.testing.expectEqual(expect_eql, eqlIgnoreCaseUtf8(a, b));2018 try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWtf8(a, b));
2018 try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWTF16(2019 try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWTF16(
2019 std.unicode.utf8ToUtf16LeStringLiteral(a),2020 std.unicode.utf8ToUtf16LeStringLiteral(a),
2020 std.unicode.utf8ToUtf16LeStringLiteral(b),2021 std.unicode.utf8ToUtf16LeStringLiteral(b),
2021 ));2022 ));
20222023
2023 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseUtf8(a, b));2024 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWtf8(a, b));
2024 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWTF16(2025 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWTF16(
2025 std.unicode.utf8ToUtf16LeStringLiteral(a),2026 std.unicode.utf8ToUtf16LeStringLiteral(a),
2026 std.unicode.utf8ToUtf16LeStringLiteral(b),2027 std.unicode.utf8ToUtf16LeStringLiteral(b),
2027 ));2028 ));
2028}2029}
20292030
2030test "eqlIgnoreCaseWTF16/Utf8" {2031test "eqlIgnoreCaseWTF16/Wtf8" {
2031 try testEqlIgnoreCase(true, "\x01 a B Λ ɐ", "\x01 A b λ Ɐ");2032 try testEqlIgnoreCase(true, "\x01 a B Λ ɐ", "\x01 A b λ Ɐ");
2032 // does not do case-insensitive comparison for codepoints >= U+100002033 // does not do case-insensitive comparison for codepoints >= U+10000
2033 try testEqlIgnoreCase(false, "𐓏", "𐓷");2034 try testEqlIgnoreCase(false, "𐓏", "𐓷");
...@@ -2117,20 +2118,32 @@ pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize {...@@ -2117,20 +2118,32 @@ pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize {
2117 return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]);2118 return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]);
2118}2119}
21192120
2121pub const Wtf8ToPrefixedFileWError = error{InvalidWtf8} || Wtf16ToPrefixedFileWError;
2122
2120/// Same as `sliceToPrefixedFileW` but accepts a pointer2123/// Same as `sliceToPrefixedFileW` but accepts a pointer
2121/// to a null-terminated path.2124/// to a null-terminated WTF-8 encoded path.
2122pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) !PathSpace {2125/// https://simonsapin.github.io/wtf-8/
2126pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWError!PathSpace {
2123 return sliceToPrefixedFileW(dir, mem.sliceTo(s, 0));2127 return sliceToPrefixedFileW(dir, mem.sliceTo(s, 0));
2124}2128}
21252129
2126/// Same as `wToPrefixedFileW` but accepts a UTF-8 encoded path.2130/// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path.
2127pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace {2131/// https://simonsapin.github.io/wtf-8/
2132pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace {
2128 var temp_path: PathSpace = undefined;2133 var temp_path: PathSpace = undefined;
2129 temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, path);2134 temp_path.len = try std.unicode.wtf8ToWtf16Le(&temp_path.data, path);
2130 temp_path.data[temp_path.len] = 0;2135 temp_path.data[temp_path.len] = 0;
2131 return wToPrefixedFileW(dir, temp_path.span());2136 return wToPrefixedFileW(dir, temp_path.span());
2132}2137}
21332138
2139pub const Wtf16ToPrefixedFileWError = error{
2140 AccessDenied,
2141 BadPathName,
2142 FileNotFound,
2143 NameTooLong,
2144 Unexpected,
2145};
2146
2134/// Converts the `path` to WTF16, null-terminated. If the path contains any2147/// Converts the `path` to WTF16, null-terminated. If the path contains any
2135/// namespace prefix, or is anything but a relative path (rooted, drive relative,2148/// namespace prefix, or is anything but a relative path (rooted, drive relative,
2136/// etc) the result will have the NT-style prefix `\??\`.2149/// etc) the result will have the NT-style prefix `\??\`.
...@@ -2142,7 +2155,7 @@ pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace {...@@ -2142,7 +2155,7 @@ pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace {
2142/// is non-null, or the CWD if it is null.2155/// is non-null, or the CWD if it is null.
2143/// - Special case device names like COM1, NUL, etc are not handled specially (TODO)2156/// - Special case device names like COM1, NUL, etc are not handled specially (TODO)
2144/// - . and space are not stripped from the end of relative paths (potential TODO)2157/// - . and space are not stripped from the end of relative paths (potential TODO)
2145pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) !PathSpace {2158pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!PathSpace {
2146 const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };2159 const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };
2147 switch (getNamespacePrefix(u16, path)) {2160 switch (getNamespacePrefix(u16, path)) {
2148 // TODO: Figure out a way to design an API that can avoid the copy for .nt,2161 // TODO: Figure out a way to design an API that can avoid the copy for .nt,
...@@ -2312,7 +2325,7 @@ pub const NamespacePrefix = enum {...@@ -2312,7 +2325,7 @@ pub const NamespacePrefix = enum {
2312 nt,2325 nt,
2313};2326};
23142327
2315/// If `T` is `u16`, then `path` should be encoded as UTF-16LE.2328/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
2316pub fn getNamespacePrefix(comptime T: type, path: []const T) NamespacePrefix {2329pub fn getNamespacePrefix(comptime T: type, path: []const T) NamespacePrefix {
2317 if (path.len < 4) return .none;2330 if (path.len < 4) return .none;
2318 var all_backslash = switch (mem.littleToNative(T, path[0])) {2331 var all_backslash = switch (mem.littleToNative(T, path[0])) {
...@@ -2366,7 +2379,7 @@ pub const UnprefixedPathType = enum {...@@ -2366,7 +2379,7 @@ pub const UnprefixedPathType = enum {
23662379
2367/// Get the path type of a path that is known to not have any namespace prefixes2380/// Get the path type of a path that is known to not have any namespace prefixes
2368/// (`\\?\`, `\\.\`, `\??\`).2381/// (`\\?\`, `\\.\`, `\??\`).
2369/// If `T` is `u16`, then `path` should be encoded as UTF-16LE.2382/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
2370pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathType {2383pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathType {
2371 if (path.len < 1) return .relative;2384 if (path.len < 1) return .relative;
23722385
...@@ -2420,7 +2433,7 @@ test getUnprefixedPathType {...@@ -2420,7 +2433,7 @@ test getUnprefixedPathType {
2420/// Functionality is based on the ReactOS test cases found here:2433/// Functionality is based on the ReactOS test cases found here:
2421/// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c2434/// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c
2422///2435///
2423/// `path` should be encoded as UTF-16LE.2436/// `path` should be encoded as WTF-16LE.
2424pub fn ntToWin32Namespace(path: []const u16) !PathSpace {2437pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
2425 if (path.len > PATH_MAX_WIDE) return error.NameTooLong;2438 if (path.len > PATH_MAX_WIDE) return error.NameTooLong;
24262439
...@@ -2530,7 +2543,6 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {...@@ -2530,7 +2543,6 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {
2530 if (std.os.unexpected_error_tracing) {2543 if (std.os.unexpected_error_tracing) {
2531 // 614 is the length of the longest windows error description2544 // 614 is the length of the longest windows error description
2532 var buf_wstr: [614]WCHAR = undefined;2545 var buf_wstr: [614]WCHAR = undefined;
2533 var buf_utf8: [614]u8 = undefined;
2534 const len = kernel32.FormatMessageW(2546 const len = kernel32.FormatMessageW(
2535 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,2547 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
2536 null,2548 null,
...@@ -2540,8 +2552,10 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {...@@ -2540,8 +2552,10 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {
2540 buf_wstr.len,2552 buf_wstr.len,
2541 null,2553 null,
2542 );2554 );
2543 _ = std.unicode.utf16LeToUtf8(&buf_utf8, buf_wstr[0..len]) catch unreachable;2555 std.debug.print("error.Unexpected: GetLastError({}): {}\n", .{
2544 std.debug.print("error.Unexpected: GetLastError({}): {s}\n", .{ @intFromEnum(err), buf_utf8[0..len] });2556 @intFromEnum(err),
2557 std.unicode.fmtUtf16Le(buf_wstr[0..len]),
2558 });
2545 std.debug.dumpCurrentStackTrace(@returnAddress());2559 std.debug.dumpCurrentStackTrace(@returnAddress());
2546 }2560 }
2547 return error.Unexpected;2561 return error.Unexpected;
lib/std/process.zig+89-63
...@@ -16,11 +16,15 @@ pub const changeCurDir = os.chdir;...@@ -16,11 +16,15 @@ pub const changeCurDir = os.chdir;
16pub const changeCurDirC = os.chdirC;16pub const changeCurDirC = os.chdirC;
1717
18/// The result is a slice of `out_buffer`, from index `0`.18/// The result is a slice of `out_buffer`, from index `0`.
19/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
20/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
19pub fn getCwd(out_buffer: []u8) ![]u8 {21pub fn getCwd(out_buffer: []u8) ![]u8 {
20 return os.getcwd(out_buffer);22 return os.getcwd(out_buffer);
21}23}
2224
23/// Caller must free the returned memory.25/// Caller must free the returned memory.
26/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
27/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
24pub fn getCwdAlloc(allocator: Allocator) ![]u8 {28pub fn getCwdAlloc(allocator: Allocator) ![]u8 {
25 // The use of MAX_PATH_BYTES here is just a heuristic: most paths will fit29 // The use of MAX_PATH_BYTES here is just a heuristic: most paths will fit
26 // in stack_buf, avoiding an extra allocation in the common case.30 // in stack_buf, avoiding an extra allocation in the common case.
...@@ -76,7 +80,7 @@ pub const EnvMap = struct {...@@ -76,7 +80,7 @@ pub const EnvMap = struct {
76 _ = self;80 _ = self;
77 if (builtin.os.tag == .windows) {81 if (builtin.os.tag == .windows) {
78 var h = std.hash.Wyhash.init(0);82 var h = std.hash.Wyhash.init(0);
79 var it = std.unicode.Utf8View.initUnchecked(s).iterator();83 var it = std.unicode.Wtf8View.initUnchecked(s).iterator();
80 while (it.nextCodepoint()) |cp| {84 while (it.nextCodepoint()) |cp| {
81 const cp_upper = upcase(cp);85 const cp_upper = upcase(cp);
82 h.update(&[_]u8{86 h.update(&[_]u8{
...@@ -93,8 +97,8 @@ pub const EnvMap = struct {...@@ -93,8 +97,8 @@ pub const EnvMap = struct {
93 pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {97 pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
94 _ = self;98 _ = self;
95 if (builtin.os.tag == .windows) {99 if (builtin.os.tag == .windows) {
96 var it_a = std.unicode.Utf8View.initUnchecked(a).iterator();100 var it_a = std.unicode.Wtf8View.initUnchecked(a).iterator();
97 var it_b = std.unicode.Utf8View.initUnchecked(b).iterator();101 var it_b = std.unicode.Wtf8View.initUnchecked(b).iterator();
98 while (true) {102 while (true) {
99 const c_a = it_a.nextCodepoint() orelse break;103 const c_a = it_a.nextCodepoint() orelse break;
100 const c_b = it_b.nextCodepoint() orelse return false;104 const c_b = it_b.nextCodepoint() orelse return false;
...@@ -129,8 +133,9 @@ pub const EnvMap = struct {...@@ -129,8 +133,9 @@ pub const EnvMap = struct {
129 /// Same as `put` but the key and value become owned by the EnvMap rather133 /// Same as `put` but the key and value become owned by the EnvMap rather
130 /// than being copied.134 /// than being copied.
131 /// If `putMove` fails, the ownership of key and value does not transfer.135 /// If `putMove` fails, the ownership of key and value does not transfer.
132 /// On Windows `key` must be a valid UTF-8 string.136 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
133 pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {137 pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {
138 assert(std.unicode.wtf8ValidateSlice(key));
134 const get_or_put = try self.hash_map.getOrPut(key);139 const get_or_put = try self.hash_map.getOrPut(key);
135 if (get_or_put.found_existing) {140 if (get_or_put.found_existing) {
136 self.free(get_or_put.key_ptr.*);141 self.free(get_or_put.key_ptr.*);
...@@ -141,8 +146,9 @@ pub const EnvMap = struct {...@@ -141,8 +146,9 @@ pub const EnvMap = struct {
141 }146 }
142147
143 /// `key` and `value` are copied into the EnvMap.148 /// `key` and `value` are copied into the EnvMap.
144 /// On Windows `key` must be a valid UTF-8 string.149 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
145 pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {150 pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {
151 assert(std.unicode.wtf8ValidateSlice(key));
146 const value_copy = try self.copy(value);152 const value_copy = try self.copy(value);
147 errdefer self.free(value_copy);153 errdefer self.free(value_copy);
148 const get_or_put = try self.hash_map.getOrPut(key);154 const get_or_put = try self.hash_map.getOrPut(key);
...@@ -159,23 +165,26 @@ pub const EnvMap = struct {...@@ -159,23 +165,26 @@ pub const EnvMap = struct {
159165
160 /// Find the address of the value associated with a key.166 /// Find the address of the value associated with a key.
161 /// The returned pointer is invalidated if the map resizes.167 /// The returned pointer is invalidated if the map resizes.
162 /// On Windows `key` must be a valid UTF-8 string.168 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
163 pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {169 pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {
170 assert(std.unicode.wtf8ValidateSlice(key));
164 return self.hash_map.getPtr(key);171 return self.hash_map.getPtr(key);
165 }172 }
166173
167 /// Return the map's copy of the value associated with174 /// Return the map's copy of the value associated with
168 /// a key. The returned string is invalidated if this175 /// a key. The returned string is invalidated if this
169 /// key is removed from the map.176 /// key is removed from the map.
170 /// On Windows `key` must be a valid UTF-8 string.177 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
171 pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {178 pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {
179 assert(std.unicode.wtf8ValidateSlice(key));
172 return self.hash_map.get(key);180 return self.hash_map.get(key);
173 }181 }
174182
175 /// Removes the item from the map and frees its value.183 /// Removes the item from the map and frees its value.
176 /// This invalidates the value returned by get() for this key.184 /// This invalidates the value returned by get() for this key.
177 /// On Windows `key` must be a valid UTF-8 string.185 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
178 pub fn remove(self: *EnvMap, key: []const u8) void {186 pub fn remove(self: *EnvMap, key: []const u8) void {
187 assert(std.unicode.wtf8ValidateSlice(key));
179 const kv = self.hash_map.fetchRemove(key) orelse return;188 const kv = self.hash_map.fetchRemove(key) orelse return;
180 self.free(kv.key);189 self.free(kv.key);
181 self.free(kv.value);190 self.free(kv.value);
...@@ -239,18 +248,34 @@ test "EnvMap" {...@@ -239,18 +248,34 @@ test "EnvMap" {
239248
240 try testing.expectEqual(@as(EnvMap.Size, 1), env.count());249 try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
241250
242 // test Unicode case-insensitivity on Windows
243 if (builtin.os.tag == .windows) {251 if (builtin.os.tag == .windows) {
252 // test Unicode case-insensitivity on Windows
244 try env.put("КИРиллИЦА", "something else");253 try env.put("КИРиллИЦА", "something else");
245 try testing.expectEqualStrings("something else", env.get("кириллица").?);254 try testing.expectEqualStrings("something else", env.get("кириллица").?);
255
256 // and WTF-8 that's not valid UTF-8
257 const wtf8_with_surrogate_pair = try std.unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
258 std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
259 });
260 defer testing.allocator.free(wtf8_with_surrogate_pair);
261
262 try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
263 try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
246 }264 }
247}265}
248266
267pub const GetEnvMapError = error{
268 OutOfMemory,
269 /// WASI-only. `environ_sizes_get` or `environ_get`
270 /// failed for an unexpected reason.
271 Unexpected,
272};
273
249/// Returns a snapshot of the environment variables of the current process.274/// Returns a snapshot of the environment variables of the current process.
250/// Any modifications to the resulting EnvMap will not be reflected in the environment, and275/// Any modifications to the resulting EnvMap will not be reflected in the environment, and
251/// likewise, any future modifications to the environment will not be reflected in the EnvMap.276/// likewise, any future modifications to the environment will not be reflected in the EnvMap.
252/// Caller owns resulting `EnvMap` and should call its `deinit` fn when done.277/// Caller owns resulting `EnvMap` and should call its `deinit` fn when done.
253pub fn getEnvMap(allocator: Allocator) !EnvMap {278pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap {
254 var result = EnvMap.init(allocator);279 var result = EnvMap.init(allocator);
255 errdefer result.deinit();280 errdefer result.deinit();
256281
...@@ -269,7 +294,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {...@@ -269,7 +294,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {
269294
270 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}295 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
271 const key_w = ptr[key_start..i];296 const key_w = ptr[key_start..i];
272 const key = try std.unicode.utf16LeToUtf8Alloc(allocator, key_w);297 const key = try std.unicode.wtf16LeToWtf8Alloc(allocator, key_w);
273 errdefer allocator.free(key);298 errdefer allocator.free(key);
274299
275 if (ptr[i] == '=') i += 1;300 if (ptr[i] == '=') i += 1;
...@@ -277,7 +302,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {...@@ -277,7 +302,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {
277 const value_start = i;302 const value_start = i;
278 while (ptr[i] != 0) : (i += 1) {}303 while (ptr[i] != 0) : (i += 1) {}
279 const value_w = ptr[value_start..i];304 const value_w = ptr[value_start..i];
280 const value = try std.unicode.utf16LeToUtf8Alloc(allocator, value_w);305 const value = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_w);
281 errdefer allocator.free(value);306 errdefer allocator.free(value);
282307
283 i += 1; // skip over null byte308 i += 1; // skip over null byte
...@@ -355,25 +380,26 @@ pub const GetEnvVarOwnedError = error{...@@ -355,25 +380,26 @@ pub const GetEnvVarOwnedError = error{
355 OutOfMemory,380 OutOfMemory,
356 EnvironmentVariableNotFound,381 EnvironmentVariableNotFound,
357382
358 /// See https://github.com/ziglang/zig/issues/1774383 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
359 InvalidUtf8,384 /// https://simonsapin.github.io/wtf-8/
385 InvalidWtf8,
360};386};
361387
362/// Caller must free returned memory.388/// Caller must free returned memory.
389/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/),
390/// then `error.InvalidWtf8` is returned.
391/// On Windows, the value is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
392/// On other platforms, the value is an opaque sequence of bytes with no particular encoding.
363pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {393pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
364 if (builtin.os.tag == .windows) {394 if (builtin.os.tag == .windows) {
365 const result_w = blk: {395 const result_w = blk: {
366 const key_w = try std.unicode.utf8ToUtf16LeAllocZ(allocator, key);396 const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(allocator, key);
367 defer allocator.free(key_w);397 defer allocator.free(key_w);
368398
369 break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound;399 break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
370 };400 };
371 return std.unicode.utf16LeToUtf8Alloc(allocator, result_w) catch |err| switch (err) {401 // wtf16LeToWtf8Alloc can only fail with OutOfMemory
372 error.DanglingSurrogateHalf => return error.InvalidUtf8,402 return std.unicode.wtf16LeToWtf8Alloc(allocator, result_w);
373 error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,
374 error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,
375 else => |e| return e,
376 };
377 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {403 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
378 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;404 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
379 defer envmap.deinit();405 defer envmap.deinit();
...@@ -385,6 +411,7 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError...@@ -385,6 +411,7 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
385 }411 }
386}412}
387413
414/// On Windows, `key` must be valid UTF-8.
388pub fn hasEnvVarConstant(comptime key: []const u8) bool {415pub fn hasEnvVarConstant(comptime key: []const u8) bool {
389 if (builtin.os.tag == .windows) {416 if (builtin.os.tag == .windows) {
390 const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);417 const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);
...@@ -396,11 +423,22 @@ pub fn hasEnvVarConstant(comptime key: []const u8) bool {...@@ -396,11 +423,22 @@ pub fn hasEnvVarConstant(comptime key: []const u8) bool {
396 }423 }
397}424}
398425
399pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool {426pub const HasEnvVarError = error{
427 OutOfMemory,
428
429 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
430 /// https://simonsapin.github.io/wtf-8/
431 InvalidWtf8,
432};
433
434/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/),
435/// then `error.InvalidWtf8` is returned.
436pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
400 if (builtin.os.tag == .windows) {437 if (builtin.os.tag == .windows) {
401 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);438 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
402 const key_w = try std.unicode.utf8ToUtf16LeAllocZ(stack_alloc.get(), key);439 const stack_allocator = stack_alloc.get();
403 defer stack_alloc.allocator.free(key_w);440 const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
441 defer stack_allocator.free(key_w);
404 return std.os.getenvW(key_w) != null;442 return std.os.getenvW(key_w) != null;
405 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {443 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
406 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;444 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
...@@ -411,9 +449,22 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool...@@ -411,9 +449,22 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool
411 }449 }
412}450}
413451
414test "os.getEnvVarOwned" {452test getEnvVarOwned {
415 const ga = std.testing.allocator;453 try testing.expectError(
416 try testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV"));454 error.EnvironmentVariableNotFound,
455 getEnvVarOwned(std.testing.allocator, "BADENV"),
456 );
457}
458
459test hasEnvVarConstant {
460 if (builtin.os.tag == .wasi and !builtin.link_libc) return error.SkipZigTest;
461
462 try testing.expect(!hasEnvVarConstant("BADENV"));
463}
464
465test hasEnvVar {
466 const has_env = try hasEnvVar(std.testing.allocator, "BADENV");
467 try testing.expect(!has_env);
417}468}
418469
419pub const ArgIteratorPosix = struct {470pub const ArgIteratorPosix = struct {
...@@ -531,6 +582,7 @@ pub const ArgIteratorWasi = struct {...@@ -531,6 +582,7 @@ pub const ArgIteratorWasi = struct {
531pub const ArgIteratorWindows = struct {582pub const ArgIteratorWindows = struct {
532 allocator: Allocator,583 allocator: Allocator,
533 /// Owned by the iterator.584 /// Owned by the iterator.
585 /// Encoded as WTF-8.
534 cmd_line: []const u8,586 cmd_line: []const u8,
535 index: usize = 0,587 index: usize = 0,
536 /// Owned by the iterator. Long enough to hold the entire `cmd_line` plus a null terminator.588 /// Owned by the iterator. Long enough to hold the entire `cmd_line` plus a null terminator.
...@@ -538,20 +590,14 @@ pub const ArgIteratorWindows = struct {...@@ -538,20 +590,14 @@ pub const ArgIteratorWindows = struct {
538 start: usize = 0,590 start: usize = 0,
539 end: usize = 0,591 end: usize = 0,
540592
541 pub const InitError = error{ OutOfMemory, InvalidCmdLine };593 pub const InitError = error{OutOfMemory};
542594
543 /// `cmd_line_w` *must* be an UTF16-LE-encoded string.595 /// `cmd_line_w` *must* be a WTF16-LE-encoded string.
544 ///596 ///
545 /// The iterator makes a copy of `cmd_line_w` converted UTF-8 and keeps it; it does *not* take597 /// The iterator makes a copy of `cmd_line_w` converted WTF-8 and keeps it; it does *not* take
546 /// ownership of `cmd_line_w`.598 /// ownership of `cmd_line_w`.
547 pub fn init(allocator: Allocator, cmd_line_w: [*:0]const u16) InitError!ArgIteratorWindows {599 pub fn init(allocator: Allocator, cmd_line_w: [*:0]const u16) InitError!ArgIteratorWindows {
548 const cmd_line = std.unicode.utf16LeToUtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0)) catch |err| switch (err) {600 const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0));
549 error.DanglingSurrogateHalf,
550 error.ExpectedSecondSurrogateHalf,
551 error.UnexpectedSecondSurrogateHalf,
552 => return error.InvalidCmdLine,
553 error.OutOfMemory => return error.OutOfMemory,
554 };
555 errdefer allocator.free(cmd_line);601 errdefer allocator.free(cmd_line);
556602
557 const buffer = try allocator.alloc(u8, cmd_line.len + 1);603 const buffer = try allocator.alloc(u8, cmd_line.len + 1);
...@@ -566,6 +612,7 @@ pub const ArgIteratorWindows = struct {...@@ -566,6 +612,7 @@ pub const ArgIteratorWindows = struct {
566612
567 /// Returns the next argument and advances the iterator. Returns `null` if at the end of the613 /// Returns the next argument and advances the iterator. Returns `null` if at the end of the
568 /// command-line string. The iterator owns the returned slice.614 /// command-line string. The iterator owns the returned slice.
615 /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
569 pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 {616 pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 {
570 return self.nextWithStrategy(next_strategy);617 return self.nextWithStrategy(next_strategy);
571 }618 }
...@@ -777,7 +824,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {...@@ -777,7 +824,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
777 pub const Self = @This();824 pub const Self = @This();
778825
779 pub const InitError = error{OutOfMemory};826 pub const InitError = error{OutOfMemory};
780 pub const InitUtf16leError = error{ OutOfMemory, InvalidCmdLine };
781827
782 /// cmd_line_utf8 MUST remain valid and constant while using this instance828 /// cmd_line_utf8 MUST remain valid and constant while using this instance
783 pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {829 pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
...@@ -805,30 +851,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {...@@ -805,30 +851,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
805 };851 };
806 }852 }
807853
808 /// cmd_line_utf16le MUST be encoded UTF16-LE, and is converted to UTF-8 in an internal buffer
809 pub fn initUtf16le(allocator: Allocator, cmd_line_utf16le: [*:0]const u16) InitUtf16leError!Self {
810 const utf16le_slice = mem.sliceTo(cmd_line_utf16le, 0);
811 const cmd_line = std.unicode.utf16LeToUtf8Alloc(allocator, utf16le_slice) catch |err| switch (err) {
812 error.ExpectedSecondSurrogateHalf,
813 error.DanglingSurrogateHalf,
814 error.UnexpectedSecondSurrogateHalf,
815 => return error.InvalidCmdLine,
816
817 error.OutOfMemory => return error.OutOfMemory,
818 };
819 errdefer allocator.free(cmd_line);
820
821 const buffer = try allocator.alloc(u8, cmd_line.len + 1);
822 errdefer allocator.free(buffer);
823
824 return Self{
825 .allocator = allocator,
826 .cmd_line = cmd_line,
827 .free_cmd_line_on_deinit = true,
828 .buffer = buffer,
829 };
830 }
831
832 // Skips over whitespace in the cmd_line.854 // Skips over whitespace in the cmd_line.
833 // Returns false if the terminating sentinel is reached, true otherwise.855 // Returns false if the terminating sentinel is reached, true otherwise.
834 // Also skips over comments (if supported).856 // Also skips over comments (if supported).
...@@ -1021,6 +1043,8 @@ pub const ArgIterator = struct {...@@ -1021,6 +1043,8 @@ pub const ArgIterator = struct {
10211043
1022 /// Get the next argument. Returns 'null' if we are at the end.1044 /// Get the next argument. Returns 'null' if we are at the end.
1023 /// Returned slice is pointing to the iterator's internal buffer.1045 /// Returned slice is pointing to the iterator's internal buffer.
1046 /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1047 /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1024 pub fn next(self: *ArgIterator) ?([:0]const u8) {1048 pub fn next(self: *ArgIterator) ?([:0]const u8) {
1025 return self.inner.next();1049 return self.inner.next();
1026 }1050 }
...@@ -1057,6 +1081,8 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator...@@ -1057,6 +1081,8 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator
1057}1081}
10581082
1059/// Caller must call argsFree on result.1083/// Caller must call argsFree on result.
1084/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1085/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1060pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {1086pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
1061 // TODO refactor to only make 1 allocation.1087 // TODO refactor to only make 1 allocation.
1062 var it = try argsWithAllocator(allocator);1088 var it = try argsWithAllocator(allocator);
...@@ -1201,7 +1227,7 @@ test "ArgIteratorWindows" {...@@ -1201,7 +1227,7 @@ test "ArgIteratorWindows" {
1201}1227}
12021228
1203fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {1229fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {
1204 const cmd_line_w = try std.unicode.utf8ToUtf16LeAllocZ(testing.allocator, cmd_line);1230 const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);
1205 defer testing.allocator.free(cmd_line_w);1231 defer testing.allocator.free(cmd_line_w);
12061232
1207 // next1233 // next
lib/std/unicode.zig+50-28
...@@ -488,7 +488,9 @@ pub const Utf16LeIterator = struct {...@@ -488,7 +488,9 @@ pub const Utf16LeIterator = struct {
488 };488 };
489 }489 }
490490
491 pub fn nextCodepoint(it: *Utf16LeIterator) !?u21 {491 pub const NextCodepointError = error{ DanglingSurrogateHalf, ExpectedSecondSurrogateHalf, UnexpectedSecondSurrogateHalf };
492
493 pub fn nextCodepoint(it: *Utf16LeIterator) NextCodepointError!?u21 {
492 assert(it.i <= it.bytes.len);494 assert(it.i <= it.bytes.len);
493 if (it.i == it.bytes.len) return null;495 if (it.i == it.bytes.len) return null;
494 var code_units: [2]u16 = undefined;496 var code_units: [2]u16 = undefined;
...@@ -923,7 +925,14 @@ test "fmtUtf8" {...@@ -923,7 +925,14 @@ test "fmtUtf8" {
923 try expectFmt("����A", "{}", .{fmtUtf8("\xE1\x80\xE2\xF0\x91\x92\xF1\xBFA")});925 try expectFmt("����A", "{}", .{fmtUtf8("\xE1\x80\xE2\xF0\x91\x92\xF1\xBFA")});
924}926}
925927
926fn utf16LeToUtf8ArrayListImpl(array_list: *std.ArrayList(u8), utf16le: []const u16, comptime surrogates: Surrogates) !void {928fn utf16LeToUtf8ArrayListImpl(
929 array_list: *std.ArrayList(u8),
930 utf16le: []const u16,
931 comptime surrogates: Surrogates,
932) (switch (surrogates) {
933 .cannot_encode_surrogate_half => Utf16LeToUtf8AllocError,
934 .can_encode_surrogate_half => mem.Allocator.Error,
935})!void {
927 // optimistically guess that it will all be ascii.936 // optimistically guess that it will all be ascii.
928 try array_list.ensureTotalCapacityPrecise(utf16le.len);937 try array_list.ensureTotalCapacityPrecise(utf16le.len);
929938
...@@ -975,7 +984,9 @@ fn utf16LeToUtf8ArrayListImpl(array_list: *std.ArrayList(u8), utf16le: []const u...@@ -975,7 +984,9 @@ fn utf16LeToUtf8ArrayListImpl(array_list: *std.ArrayList(u8), utf16le: []const u
975 }984 }
976}985}
977986
978pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) !void {987pub const Utf16LeToUtf8AllocError = mem.Allocator.Error || Utf16LeToUtf8Error;
988
989pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) Utf16LeToUtf8AllocError!void {
979 return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .cannot_encode_surrogate_half);990 return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .cannot_encode_surrogate_half);
980}991}
981992
...@@ -983,7 +994,7 @@ pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u...@@ -983,7 +994,7 @@ pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u
983pub const utf16leToUtf8Alloc = utf16LeToUtf8Alloc;994pub const utf16leToUtf8Alloc = utf16LeToUtf8Alloc;
984995
985/// Caller must free returned memory.996/// Caller must free returned memory.
986pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8 {997pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![]u8 {
987 // optimistically guess that it will all be ascii.998 // optimistically guess that it will all be ascii.
988 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len);999 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len);
989 errdefer result.deinit();1000 errdefer result.deinit();
...@@ -997,7 +1008,7 @@ pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8...@@ -997,7 +1008,7 @@ pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8
997pub const utf16leToUtf8AllocZ = utf16LeToUtf8AllocZ;1008pub const utf16leToUtf8AllocZ = utf16LeToUtf8AllocZ;
9981009
999/// Caller must free returned memory.1010/// Caller must free returned memory.
1000pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) ![:0]u8 {1011pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![:0]u8 {
1001 // optimistically guess that it will all be ascii (and allocate space for the null terminator)1012 // optimistically guess that it will all be ascii (and allocate space for the null terminator)
1002 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len + 1);1013 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len + 1);
1003 errdefer result.deinit();1014 errdefer result.deinit();
...@@ -1007,9 +1018,14 @@ pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) ![:0]...@@ -1007,9 +1018,14 @@ pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) ![:0]
1007 return result.toOwnedSliceSentinel(0);1018 return result.toOwnedSliceSentinel(0);
1008}1019}
10091020
1021pub const Utf16LeToUtf8Error = Utf16LeIterator.NextCodepointError;
1022
1010/// Asserts that the output buffer is big enough.1023/// Asserts that the output buffer is big enough.
1011/// Returns end byte index into utf8.1024/// Returns end byte index into utf8.
1012fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surrogates) !usize {1025fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surrogates) (switch (surrogates) {
1026 .cannot_encode_surrogate_half => Utf16LeToUtf8Error,
1027 .can_encode_surrogate_half => error{},
1028})!usize {
1013 var end_index: usize = 0;1029 var end_index: usize = 0;
10141030
1015 var remaining = utf16le;1031 var remaining = utf16le;
...@@ -1043,7 +1059,9 @@ fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surr...@@ -1043,7 +1059,9 @@ fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surr
1043 // The maximum possible codepoint encoded by UTF-16 is U+10FFFF,1059 // The maximum possible codepoint encoded by UTF-16 is U+10FFFF,
1044 // which is within the valid codepoint range.1060 // which is within the valid codepoint range.
1045 error.CodepointTooLarge => unreachable,1061 error.CodepointTooLarge => unreachable,
1046 else => |e| return e,1062 // We know the codepoint was valid in UTF-16, meaning it is not
1063 // an unpaired surrogate codepoint.
1064 error.Utf8CannotEncodeSurrogateHalf => unreachable,
1047 };1065 };
1048 }1066 }
1049 },1067 },
...@@ -1064,7 +1082,7 @@ fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surr...@@ -1064,7 +1082,7 @@ fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surr
1064/// Deprecated; renamed to utf16LeToUtf81082/// Deprecated; renamed to utf16LeToUtf8
1065pub const utf16leToUtf8 = utf16LeToUtf8;1083pub const utf16leToUtf8 = utf16LeToUtf8;
10661084
1067pub fn utf16LeToUtf8(utf8: []u8, utf16le: []const u16) !usize {1085pub fn utf16LeToUtf8(utf8: []u8, utf16le: []const u16) Utf16LeToUtf8Error!usize {
1068 return utf16LeToUtf8Impl(utf8, utf16le, .cannot_encode_surrogate_half);1086 return utf16LeToUtf8Impl(utf8, utf16le, .cannot_encode_surrogate_half);
1069}1087}
10701088
...@@ -1176,11 +1194,11 @@ fn utf8ToUtf16LeArrayListImpl(array_list: *std.ArrayList(u16), utf8: []const u8,...@@ -1176,11 +1194,11 @@ fn utf8ToUtf16LeArrayListImpl(array_list: *std.ArrayList(u16), utf8: []const u8,
1176 }1194 }
1177}1195}
11781196
1179pub fn utf8ToUtf16LeArrayList(array_list: *std.ArrayList(u16), utf8: []const u8) !void {1197pub fn utf8ToUtf16LeArrayList(array_list: *std.ArrayList(u16), utf8: []const u8) error{ InvalidUtf8, OutOfMemory }!void {
1180 return utf8ToUtf16LeArrayListImpl(array_list, utf8, .cannot_encode_surrogate_half);1198 return utf8ToUtf16LeArrayListImpl(array_list, utf8, .cannot_encode_surrogate_half);
1181}1199}
11821200
1183pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) ![]u16 {1201pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![]u16 {
1184 // optimistically guess that it will not require surrogate pairs1202 // optimistically guess that it will not require surrogate pairs
1185 var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len);1203 var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len);
1186 errdefer result.deinit();1204 errdefer result.deinit();
...@@ -1193,7 +1211,7 @@ pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) ![]u16 {...@@ -1193,7 +1211,7 @@ pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) ![]u16 {
1193/// Deprecated; renamed to utf8ToUtf16LeAllocZ1211/// Deprecated; renamed to utf8ToUtf16LeAllocZ
1194pub const utf8ToUtf16LeWithNull = utf8ToUtf16LeAllocZ;1212pub const utf8ToUtf16LeWithNull = utf8ToUtf16LeAllocZ;
11951213
1196pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) ![:0]u16 {1214pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![:0]u16 {
1197 // optimistically guess that it will not require surrogate pairs1215 // optimistically guess that it will not require surrogate pairs
1198 var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len + 1);1216 var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len + 1);
1199 errdefer result.deinit();1217 errdefer result.deinit();
...@@ -1205,7 +1223,7 @@ pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) ![:0]u16...@@ -1205,7 +1223,7 @@ pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) ![:0]u16
12051223
1206/// Returns index of next character. If exact fit, returned index equals output slice length.1224/// Returns index of next character. If exact fit, returned index equals output slice length.
1207/// Assumes there is enough space for the output.1225/// Assumes there is enough space for the output.
1208pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize {1226pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) error{InvalidUtf8}!usize {
1209 return utf8ToUtf16LeImpl(utf16le, utf8, .cannot_encode_surrogate_half);1227 return utf8ToUtf16LeImpl(utf16le, utf8, .cannot_encode_surrogate_half);
1210}1228}
12111229
...@@ -1236,11 +1254,14 @@ pub fn utf8ToUtf16LeImpl(utf16le: []u16, utf8: []const u8, comptime surrogates:...@@ -1236,11 +1254,14 @@ pub fn utf8ToUtf16LeImpl(utf16le: []u16, utf8: []const u8, comptime surrogates:
12361254
1237 var src_i: usize = 0;1255 var src_i: usize = 0;
1238 while (src_i < remaining.len) {1256 while (src_i < remaining.len) {
1239 const n = utf8ByteSequenceLength(remaining[src_i]) catch return error.InvalidUtf8;1257 const n = utf8ByteSequenceLength(remaining[src_i]) catch return switch (surrogates) {
1258 .cannot_encode_surrogate_half => error.InvalidUtf8,
1259 .can_encode_surrogate_half => error.InvalidWtf8,
1260 };
1240 const next_src_i = src_i + n;1261 const next_src_i = src_i + n;
1241 const codepoint = switch (surrogates) {1262 const codepoint = switch (surrogates) {
1242 .cannot_encode_surrogate_half => utf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8,1263 .cannot_encode_surrogate_half => utf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8,
1243 .can_encode_surrogate_half => wtf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8,1264 .can_encode_surrogate_half => wtf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidWtf8,
1244 };1265 };
1245 if (codepoint < 0x10000) {1266 if (codepoint < 0x10000) {
1246 const short = @as(u16, @intCast(codepoint));1267 const short = @as(u16, @intCast(codepoint));
...@@ -1600,9 +1621,9 @@ fn testValidateWtf8Slice() !void {...@@ -1600,9 +1621,9 @@ fn testValidateWtf8Slice() !void {
1600pub const Wtf8View = struct {1621pub const Wtf8View = struct {
1601 bytes: []const u8,1622 bytes: []const u8,
16021623
1603 pub fn init(s: []const u8) !Wtf8View {1624 pub fn init(s: []const u8) error{InvalidWtf8}!Wtf8View {
1604 if (!wtf8ValidateSlice(s)) {1625 if (!wtf8ValidateSlice(s)) {
1605 return error.InvalidUtf8;1626 return error.InvalidWtf8;
1606 }1627 }
16071628
1608 return initUnchecked(s);1629 return initUnchecked(s);
...@@ -1614,8 +1635,8 @@ pub const Wtf8View = struct {...@@ -1614,8 +1635,8 @@ pub const Wtf8View = struct {
16141635
1615 pub inline fn initComptime(comptime s: []const u8) Wtf8View {1636 pub inline fn initComptime(comptime s: []const u8) Wtf8View {
1616 return comptime if (init(s)) |r| r else |err| switch (err) {1637 return comptime if (init(s)) |r| r else |err| switch (err) {
1617 error.InvalidUtf8 => {1638 error.InvalidWtf8 => {
1618 @compileError("invalid utf8 detected in wtf8 string");1639 @compileError("invalid wtf8");
1619 },1640 },
1620 };1641 };
1621 }1642 }
...@@ -1665,12 +1686,12 @@ pub const Wtf8Iterator = struct {...@@ -1665,12 +1686,12 @@ pub const Wtf8Iterator = struct {
1665 }1686 }
1666};1687};
16671688
1668pub fn wtf16LeToWtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) !void {1689pub fn wtf16LeToWtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) mem.Allocator.Error!void {
1669 return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .can_encode_surrogate_half);1690 return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .can_encode_surrogate_half);
1670}1691}
16711692
1672/// Caller must free returned memory.1693/// Caller must free returned memory.
1673pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) ![]u8 {1694pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![]u8 {
1674 // optimistically guess that it will all be ascii.1695 // optimistically guess that it will all be ascii.
1675 var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len);1696 var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len);
1676 errdefer result.deinit();1697 errdefer result.deinit();
...@@ -1681,7 +1702,7 @@ pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) ![]u8...@@ -1681,7 +1702,7 @@ pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) ![]u8
1681}1702}
16821703
1683/// Caller must free returned memory.1704/// Caller must free returned memory.
1684pub fn wtf16LeToWtf8AllocZ(allocator: mem.Allocator, wtf16le: []const u16) ![:0]u8 {1705pub fn wtf16LeToWtf8AllocZ(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![:0]u8 {
1685 // optimistically guess that it will all be ascii (and allocate space for the null terminator)1706 // optimistically guess that it will all be ascii (and allocate space for the null terminator)
1686 var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len + 1);1707 var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len + 1);
1687 errdefer result.deinit();1708 errdefer result.deinit();
...@@ -1695,11 +1716,11 @@ pub fn wtf16LeToWtf8(wtf8: []u8, wtf16le: []const u16) usize {...@@ -1695,11 +1716,11 @@ pub fn wtf16LeToWtf8(wtf8: []u8, wtf16le: []const u16) usize {
1695 return utf16LeToUtf8Impl(wtf8, wtf16le, .can_encode_surrogate_half) catch |err| switch (err) {};1716 return utf16LeToUtf8Impl(wtf8, wtf16le, .can_encode_surrogate_half) catch |err| switch (err) {};
1696}1717}
16971718
1698pub fn wtf8ToWtf16LeArrayList(array_list: *std.ArrayList(u16), wtf8: []const u8) !void {1719pub fn wtf8ToWtf16LeArrayList(array_list: *std.ArrayList(u16), wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }!void {
1699 return utf8ToUtf16LeArrayListImpl(array_list, wtf8, .can_encode_surrogate_half);1720 return utf8ToUtf16LeArrayListImpl(array_list, wtf8, .can_encode_surrogate_half);
1700}1721}
17011722
1702pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u16 {1723pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u16 {
1703 // optimistically guess that it will not require surrogate pairs1724 // optimistically guess that it will not require surrogate pairs
1704 var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len);1725 var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len);
1705 errdefer result.deinit();1726 errdefer result.deinit();
...@@ -1709,7 +1730,7 @@ pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u16 {...@@ -1709,7 +1730,7 @@ pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u16 {
1709 return result.toOwnedSlice();1730 return result.toOwnedSlice();
1710}1731}
17111732
1712pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u16 {1733pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u16 {
1713 // optimistically guess that it will not require surrogate pairs1734 // optimistically guess that it will not require surrogate pairs
1714 var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len + 1);1735 var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len + 1);
1715 errdefer result.deinit();1736 errdefer result.deinit();
...@@ -1721,7 +1742,7 @@ pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u16...@@ -1721,7 +1742,7 @@ pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u16
17211742
1722/// Returns index of next character. If exact fit, returned index equals output slice length.1743/// Returns index of next character. If exact fit, returned index equals output slice length.
1723/// Assumes there is enough space for the output.1744/// Assumes there is enough space for the output.
1724pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) !usize {1745pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{InvalidWtf8}!usize {
1725 return utf8ToUtf16LeImpl(wtf16le, wtf8, .can_encode_surrogate_half);1746 return utf8ToUtf16LeImpl(wtf16le, wtf8, .can_encode_surrogate_half);
1726}1747}
17271748
...@@ -1732,7 +1753,8 @@ pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) !usize {...@@ -1732,7 +1753,8 @@ pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) !usize {
1732/// In-place conversion is supported when `utf8` and `wtf8` refer to the same slice.1753/// In-place conversion is supported when `utf8` and `wtf8` refer to the same slice.
1733/// Note: If `wtf8` is entirely composed of well-formed UTF-8, then no conversion is necessary.1754/// Note: If `wtf8` is entirely composed of well-formed UTF-8, then no conversion is necessary.
1734/// `utf8ValidateSlice` can be used to check if lossy conversion is worthwhile.1755/// `utf8ValidateSlice` can be used to check if lossy conversion is worthwhile.
1735pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) !void {1756/// If `wtf8` is not valid WTF-8, then `error.InvalidWtf8` is returned.
1757pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) error{InvalidWtf8}!void {
1736 assert(utf8.len >= wtf8.len);1758 assert(utf8.len >= wtf8.len);
17371759
1738 const in_place = utf8.ptr == wtf8.ptr;1760 const in_place = utf8.ptr == wtf8.ptr;
...@@ -1762,7 +1784,7 @@ pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) !void {...@@ -1762,7 +1784,7 @@ pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) !void {
1762 }1784 }
1763}1785}
17641786
1765pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u8 {1787pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u8 {
1766 const utf8 = try allocator.alloc(u8, wtf8.len);1788 const utf8 = try allocator.alloc(u8, wtf8.len);
1767 errdefer allocator.free(utf8);1789 errdefer allocator.free(utf8);
17681790
...@@ -1771,7 +1793,7 @@ pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u8 {...@@ -1771,7 +1793,7 @@ pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u8 {
1771 return utf8;1793 return utf8;
1772}1794}
17731795
1774pub fn wtf8ToUtf8LossyAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u8 {1796pub fn wtf8ToUtf8LossyAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u8 {
1775 const utf8 = try allocator.allocSentinel(u8, wtf8.len, 0);1797 const utf8 = try allocator.allocSentinel(u8, wtf8.len, 0);
1776 errdefer allocator.free(utf8);1798 errdefer allocator.free(utf8);
17771799
lib/std/zig/system.zig+8-4
...@@ -639,7 +639,8 @@ pub fn abiAndDynamicLinkerFromFile(...@@ -639,7 +639,8 @@ pub fn abiAndDynamicLinkerFromFile(
639 var link_buf: [std.os.PATH_MAX]u8 = undefined;639 var link_buf: [std.os.PATH_MAX]u8 = undefined;
640 const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {640 const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {
641 error.NameTooLong => unreachable,641 error.NameTooLong => unreachable,
642 error.InvalidUtf8 => unreachable, // Windows only642 error.InvalidUtf8 => unreachable, // WASI only
643 error.InvalidWtf8 => unreachable, // Windows only
643 error.BadPathName => unreachable, // Windows only644 error.BadPathName => unreachable, // Windows only
644 error.UnsupportedReparsePointType => unreachable, // Windows only645 error.UnsupportedReparsePointType => unreachable, // Windows only
645 error.NetworkNotFound => unreachable, // Windows only646 error.NetworkNotFound => unreachable, // Windows only
...@@ -730,7 +731,8 @@ test glibcVerFromLinkName {...@@ -730,7 +731,8 @@ test glibcVerFromLinkName {
730fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {731fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
731 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {732 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
732 error.NameTooLong => unreachable,733 error.NameTooLong => unreachable,
733 error.InvalidUtf8 => unreachable,734 error.InvalidUtf8 => unreachable, // WASI only
735 error.InvalidWtf8 => unreachable, // Windows-only
734 error.BadPathName => unreachable,736 error.BadPathName => unreachable,
735 error.DeviceBusy => unreachable,737 error.DeviceBusy => unreachable,
736 error.NetworkNotFound => unreachable, // Windows-only738 error.NetworkNotFound => unreachable, // Windows-only
...@@ -761,7 +763,8 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {...@@ -761,7 +763,8 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
761 const glibc_so_basename = "libc.so.6";763 const glibc_so_basename = "libc.so.6";
762 var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {764 var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
763 error.NameTooLong => unreachable,765 error.NameTooLong => unreachable,
764 error.InvalidUtf8 => unreachable, // Windows only766 error.InvalidUtf8 => unreachable, // WASI only
767 error.InvalidWtf8 => unreachable, // Windows only
765 error.BadPathName => unreachable, // Windows only768 error.BadPathName => unreachable, // Windows only
766 error.PipeBusy => unreachable, // Windows-only769 error.PipeBusy => unreachable, // Windows-only
767 error.SharingViolation => unreachable, // Windows-only770 error.SharingViolation => unreachable, // Windows-only
...@@ -998,7 +1001,8 @@ fn detectAbiAndDynamicLinker(...@@ -998,7 +1001,8 @@ fn detectAbiAndDynamicLinker(
998 error.NameTooLong => unreachable,1001 error.NameTooLong => unreachable,
999 error.PathAlreadyExists => unreachable,1002 error.PathAlreadyExists => unreachable,
1000 error.SharingViolation => unreachable,1003 error.SharingViolation => unreachable,
1001 error.InvalidUtf8 => unreachable,1004 error.InvalidUtf8 => unreachable, // WASI only
1005 error.InvalidWtf8 => unreachable, // Windows only
1002 error.BadPathName => unreachable,1006 error.BadPathName => unreachable,
1003 error.PipeBusy => unreachable,1007 error.PipeBusy => unreachable,
1004 error.FileLocksNotSupported => unreachable,1008 error.FileLocksNotSupported => unreachable,
lib/std/zig/system/NativePaths.zig+2-2
...@@ -41,7 +41,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {...@@ -41,7 +41,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
41 }41 }
42 }42 }
43 } else |err| switch (err) {43 } else |err| switch (err) {
44 error.InvalidUtf8 => {},44 error.InvalidWtf8 => unreachable,
45 error.EnvironmentVariableNotFound => {},45 error.EnvironmentVariableNotFound => {},
46 error.OutOfMemory => |e| return e,46 error.OutOfMemory => |e| return e,
47 }47 }
...@@ -73,7 +73,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {...@@ -73,7 +73,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
73 }73 }
74 }74 }
75 } else |err| switch (err) {75 } else |err| switch (err) {
76 error.InvalidUtf8 => {},76 error.InvalidWtf8 => unreachable,
77 error.EnvironmentVariableNotFound => {},77 error.EnvironmentVariableNotFound => {},
78 error.OutOfMemory => |e| return e,78 error.OutOfMemory => |e| return e,
79 }79 }
src/Module.zig+1
...@@ -2662,6 +2662,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {...@@ -2662,6 +2662,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
2662 }) catch |err| switch (err) {2662 }) catch |err| switch (err) {
2663 error.NotDir => unreachable, // no dir components2663 error.NotDir => unreachable, // no dir components
2664 error.InvalidUtf8 => unreachable, // it's a hex encoded name2664 error.InvalidUtf8 => unreachable, // it's a hex encoded name
2665 error.InvalidWtf8 => unreachable, // it's a hex encoded name
2665 error.BadPathName => unreachable, // it's a hex encoded name2666 error.BadPathName => unreachable, // it's a hex encoded name
2666 error.NameTooLong => unreachable, // it's a fixed size name2667 error.NameTooLong => unreachable, // it's a fixed size name
2667 error.PipeBusy => unreachable, // it's not a pipe2668 error.PipeBusy => unreachable, // it's not a pipe
src/libc_installation.zig+8-2
...@@ -246,7 +246,10 @@ pub const LibCInstallation = struct {...@@ -246,7 +246,10 @@ pub const LibCInstallation = struct {
246 const allocator = args.allocator;246 const allocator = args.allocator;
247247
248 // Detect infinite loops.248 // Detect infinite loops.
249 var env_map = try std.process.getEnvMap(allocator);249 var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
250 error.Unexpected => unreachable, // WASI-only
251 else => |e| return e,
252 };
250 defer env_map.deinit();253 defer env_map.deinit();
251 const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {254 const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
252 if (std.mem.eql(u8, phase, "1")) {255 if (std.mem.eql(u8, phase, "1")) {
...@@ -572,7 +575,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {...@@ -572,7 +575,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
572 const allocator = args.allocator;575 const allocator = args.allocator;
573576
574 // Detect infinite loops.577 // Detect infinite loops.
575 var env_map = try std.process.getEnvMap(allocator);578 var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
579 error.Unexpected => unreachable, // WASI-only
580 else => |e| return e,
581 };
576 defer env_map.deinit();582 defer env_map.deinit();
577 const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {583 const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
578 if (std.mem.eql(u8, phase, "1")) {584 if (std.mem.eql(u8, phase, "1")) {
src/windows_sdk.zig+87-90
...@@ -84,26 +84,26 @@ fn iterateAndFilterBySemVer(...@@ -84,26 +84,26 @@ fn iterateAndFilterBySemVer(
84 return dirs_filtered_slice;84 return dirs_filtered_slice;
85}85}
8686
87const RegistryUtf8 = struct {87const RegistryWtf8 = struct {
88 key: windows.HKEY,88 key: windows.HKEY,
8989
90 /// Assert that `key` is valid UTF-8 string90 /// Assert that `key` is valid WTF-8 string
91 pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryUtf8 {91 pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryWtf8 {
92 const key_utf16le: [:0]const u16 = key_utf16le: {92 const key_wtf16le: [:0]const u16 = key_wtf16le: {
93 var key_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;93 var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
94 const key_utf16le_len: usize = std.unicode.utf8ToUtf16Le(key_utf16le_buf[0..], key) catch |err| switch (err) {94 const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) {
95 error.InvalidUtf8 => unreachable,95 error.InvalidWtf8 => unreachable,
96 };96 };
97 key_utf16le_buf[key_utf16le_len] = 0;97 key_wtf16le_buf[key_wtf16le_len] = 0;
98 break :key_utf16le key_utf16le_buf[0..key_utf16le_len :0];98 break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0];
99 };99 };
100100
101 const registry_utf16le = try RegistryUtf16Le.openKey(hkey, key_utf16le);101 const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le);
102 return RegistryUtf8{ .key = registry_utf16le.key };102 return RegistryWtf8{ .key = registry_wtf16le.key };
103 }103 }
104104
105 /// Closes key, after that usage is invalid105 /// Closes key, after that usage is invalid
106 pub fn closeKey(self: *const RegistryUtf8) void {106 pub fn closeKey(self: *const RegistryWtf8) void {
107 const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);107 const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
108 const return_code: windows.Win32Error = @enumFromInt(return_code_int);108 const return_code: windows.Win32Error = @enumFromInt(return_code_int);
109 switch (return_code) {109 switch (return_code) {
...@@ -114,71 +114,68 @@ const RegistryUtf8 = struct {...@@ -114,71 +114,68 @@ const RegistryUtf8 = struct {
114114
115 /// Get string from registry.115 /// Get string from registry.
116 /// Caller owns result.116 /// Caller owns result.
117 pub fn getString(self: *const RegistryUtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {117 pub fn getString(self: *const RegistryWtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
118 const subkey_utf16le: [:0]const u16 = subkey_utf16le: {118 const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
119 var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;119 var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
120 const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;120 const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
121 subkey_utf16le_buf[subkey_utf16le_len] = 0;121 subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
122 break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];122 break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
123 };123 };
124124
125 const value_name_utf16le: [:0]const u16 = value_name_utf16le: {125 const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
126 var value_name_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;126 var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
127 const value_name_utf16le_len: usize = std.unicode.utf8ToUtf16Le(value_name_utf16le_buf[0..], value_name) catch unreachable;127 const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
128 value_name_utf16le_buf[value_name_utf16le_len] = 0;128 value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
129 break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];129 break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
130 };130 };
131131
132 const registry_utf16le = RegistryUtf16Le{ .key = self.key };132 const registry_wtf16le = RegistryWtf16Le{ .key = self.key };
133 const value_utf16le = try registry_utf16le.getString(allocator, subkey_utf16le, value_name_utf16le);133 const value_wtf16le = try registry_wtf16le.getString(allocator, subkey_wtf16le, value_name_wtf16le);
134 defer allocator.free(value_utf16le);134 defer allocator.free(value_wtf16le);
135135
136 const value_utf8: []u8 = std.unicode.utf16LeToUtf8Alloc(allocator, value_utf16le) catch |err| switch (err) {136 const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_wtf16le);
137 error.OutOfMemory => return error.OutOfMemory,137 errdefer allocator.free(value_wtf8);
138 else => return error.StringNotFound,
139 };
140 errdefer allocator.free(value_utf8);
141138
142 return value_utf8;139 return value_wtf8;
143 }140 }
144141
145 /// Get DWORD (u32) from registry.142 /// Get DWORD (u32) from registry.
146 pub fn getDword(self: *const RegistryUtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {143 pub fn getDword(self: *const RegistryWtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
147 const subkey_utf16le: [:0]const u16 = subkey_utf16le: {144 const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
148 var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;145 var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
149 const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;146 const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
150 subkey_utf16le_buf[subkey_utf16le_len] = 0;147 subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
151 break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];148 break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
152 };149 };
153150
154 const value_name_utf16le: [:0]const u16 = value_name_utf16le: {151 const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
155 var value_name_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;152 var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
156 const value_name_utf16le_len: usize = std.unicode.utf8ToUtf16Le(value_name_utf16le_buf[0..], value_name) catch unreachable;153 const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
157 value_name_utf16le_buf[value_name_utf16le_len] = 0;154 value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
158 break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];155 break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
159 };156 };
160157
161 const registry_utf16le = RegistryUtf16Le{ .key = self.key };158 const registry_wtf16le = RegistryWtf16Le{ .key = self.key };
162 return try registry_utf16le.getDword(subkey_utf16le, value_name_utf16le);159 return try registry_wtf16le.getDword(subkey_wtf16le, value_name_wtf16le);
163 }160 }
164161
165 /// Under private space with flags:162 /// Under private space with flags:
166 /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.163 /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
167 /// After finishing work, call `closeKey`.164 /// After finishing work, call `closeKey`.
168 pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryUtf8 {165 pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryWtf8 {
169 const absolute_path_utf16le: [:0]const u16 = absolute_path_utf16le: {166 const absolute_path_wtf16le: [:0]const u16 = absolute_path_wtf16le: {
170 var absolute_path_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;167 var absolute_path_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
171 const absolute_path_utf16le_len: usize = std.unicode.utf8ToUtf16Le(absolute_path_utf16le_buf[0..], absolute_path) catch unreachable;168 const absolute_path_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(absolute_path_wtf16le_buf[0..], absolute_path) catch unreachable;
172 absolute_path_utf16le_buf[absolute_path_utf16le_len] = 0;169 absolute_path_wtf16le_buf[absolute_path_wtf16le_len] = 0;
173 break :absolute_path_utf16le absolute_path_utf16le_buf[0..absolute_path_utf16le_len :0];170 break :absolute_path_wtf16le absolute_path_wtf16le_buf[0..absolute_path_wtf16le_len :0];
174 };171 };
175172
176 const registry_utf16le = try RegistryUtf16Le.loadFromPath(absolute_path_utf16le);173 const registry_wtf16le = try RegistryWtf16Le.loadFromPath(absolute_path_wtf16le);
177 return RegistryUtf8{ .key = registry_utf16le.key };174 return RegistryWtf8{ .key = registry_wtf16le.key };
178 }175 }
179};176};
180177
181const RegistryUtf16Le = struct {178const RegistryWtf16Le = struct {
182 key: windows.HKEY,179 key: windows.HKEY,
183180
184 /// Includes root key (f.e. HKEY_LOCAL_MACHINE).181 /// Includes root key (f.e. HKEY_LOCAL_MACHINE).
...@@ -191,11 +188,11 @@ const RegistryUtf16Le = struct {...@@ -191,11 +188,11 @@ const RegistryUtf16Le = struct {
191 /// Under HKEY_LOCAL_MACHINE with flags:188 /// Under HKEY_LOCAL_MACHINE with flags:
192 /// KEY_QUERY_VALUE, KEY_WOW64_32KEY, and KEY_ENUMERATE_SUB_KEYS.189 /// KEY_QUERY_VALUE, KEY_WOW64_32KEY, and KEY_ENUMERATE_SUB_KEYS.
193 /// After finishing work, call `closeKey`.190 /// After finishing work, call `closeKey`.
194 fn openKey(hkey: windows.HKEY, key_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le {191 fn openKey(hkey: windows.HKEY, key_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le {
195 var key: windows.HKEY = undefined;192 var key: windows.HKEY = undefined;
196 const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW(193 const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW(
197 hkey,194 hkey,
198 key_utf16le,195 key_wtf16le,
199 0,196 0,
200 windows.KEY_QUERY_VALUE | windows.KEY_WOW64_32KEY | windows.KEY_ENUMERATE_SUB_KEYS,197 windows.KEY_QUERY_VALUE | windows.KEY_WOW64_32KEY | windows.KEY_ENUMERATE_SUB_KEYS,
201 &key,198 &key,
...@@ -207,11 +204,11 @@ const RegistryUtf16Le = struct {...@@ -207,11 +204,11 @@ const RegistryUtf16Le = struct {
207204
208 else => return error.KeyNotFound,205 else => return error.KeyNotFound,
209 }206 }
210 return RegistryUtf16Le{ .key = key };207 return RegistryWtf16Le{ .key = key };
211 }208 }
212209
213 /// Closes key, after that usage is invalid210 /// Closes key, after that usage is invalid
214 fn closeKey(self: *const RegistryUtf16Le) void {211 fn closeKey(self: *const RegistryWtf16Le) void {
215 const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);212 const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
216 const return_code: windows.Win32Error = @enumFromInt(return_code_int);213 const return_code: windows.Win32Error = @enumFromInt(return_code_int);
217 switch (return_code) {214 switch (return_code) {
...@@ -221,25 +218,25 @@ const RegistryUtf16Le = struct {...@@ -221,25 +218,25 @@ const RegistryUtf16Le = struct {
221 }218 }
222219
223 /// Get string ([:0]const u16) from registry.220 /// Get string ([:0]const u16) from registry.
224 fn getString(self: *const RegistryUtf16Le, allocator: std.mem.Allocator, subkey_utf16le: [:0]const u16, value_name_utf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 {221 fn getString(self: *const RegistryWtf16Le, allocator: std.mem.Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 {
225 var actual_type: windows.ULONG = undefined;222 var actual_type: windows.ULONG = undefined;
226223
227 // Calculating length to allocate224 // Calculating length to allocate
228 var value_utf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters.225 var value_wtf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters.
229 var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(226 var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
230 self.key,227 self.key,
231 subkey_utf16le,228 subkey_wtf16le,
232 value_name_utf16le,229 value_name_wtf16le,
233 RRF.RT_REG_SZ,230 RRF.RT_REG_SZ,
234 &actual_type,231 &actual_type,
235 null,232 null,
236 &value_utf16le_buf_size,233 &value_wtf16le_buf_size,
237 );234 );
238235
239 // Check returned code and type236 // Check returned code and type
240 var return_code: windows.Win32Error = @enumFromInt(return_code_int);237 var return_code: windows.Win32Error = @enumFromInt(return_code_int);
241 switch (return_code) {238 switch (return_code) {
242 .SUCCESS => std.debug.assert(value_utf16le_buf_size != 0),239 .SUCCESS => std.debug.assert(value_wtf16le_buf_size != 0),
243 .MORE_DATA => unreachable, // We are only reading length240 .MORE_DATA => unreachable, // We are only reading length
244 .FILE_NOT_FOUND => return error.ValueNameNotFound,241 .FILE_NOT_FOUND => return error.ValueNameNotFound,
245 .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY242 .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
...@@ -250,17 +247,17 @@ const RegistryUtf16Le = struct {...@@ -250,17 +247,17 @@ const RegistryUtf16Le = struct {
250 else => return error.NotAString,247 else => return error.NotAString,
251 }248 }
252249
253 const value_utf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_utf16le_buf_size, 2) catch unreachable);250 const value_wtf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable);
254 errdefer allocator.free(value_utf16le_buf);251 errdefer allocator.free(value_wtf16le_buf);
255252
256 return_code_int = windows.advapi32.RegGetValueW(253 return_code_int = windows.advapi32.RegGetValueW(
257 self.key,254 self.key,
258 subkey_utf16le,255 subkey_wtf16le,
259 value_name_utf16le,256 value_name_wtf16le,
260 RRF.RT_REG_SZ,257 RRF.RT_REG_SZ,
261 &actual_type,258 &actual_type,
262 value_utf16le_buf.ptr,259 value_wtf16le_buf.ptr,
263 &value_utf16le_buf_size,260 &value_wtf16le_buf_size,
264 );261 );
265262
266 // Check returned code and (just in case) type again.263 // Check returned code and (just in case) type again.
...@@ -277,28 +274,28 @@ const RegistryUtf16Le = struct {...@@ -277,28 +274,28 @@ const RegistryUtf16Le = struct {
277 else => return error.NotAString,274 else => return error.NotAString,
278 }275 }
279276
280 const value_utf16le: []const u16 = value_utf16le: {277 const value_wtf16le: []const u16 = value_wtf16le: {
281 // note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space278 // note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space
282 // we will just search for zero termination and forget length279 // we will just search for zero termination and forget length
283 // Windows sure is strange280 // Windows sure is strange
284 const value_utf16le_overestimated: [*:0]const u16 = @ptrCast(value_utf16le_buf.ptr);281 const value_wtf16le_overestimated: [*:0]const u16 = @ptrCast(value_wtf16le_buf.ptr);
285 break :value_utf16le std.mem.span(value_utf16le_overestimated);282 break :value_wtf16le std.mem.span(value_wtf16le_overestimated);
286 };283 };
287284
288 _ = allocator.resize(value_utf16le_buf, value_utf16le.len);285 _ = allocator.resize(value_wtf16le_buf, value_wtf16le.len);
289 return value_utf16le;286 return value_wtf16le;
290 }287 }
291288
292 /// Get DWORD (u32) from registry.289 /// Get DWORD (u32) from registry.
293 fn getDword(self: *const RegistryUtf16Le, subkey_utf16le: [:0]const u16, value_name_utf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {290 fn getDword(self: *const RegistryWtf16Le, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
294 var actual_type: windows.ULONG = undefined;291 var actual_type: windows.ULONG = undefined;
295 var reg_size: u32 = @sizeOf(u32);292 var reg_size: u32 = @sizeOf(u32);
296 var reg_value: u32 = 0;293 var reg_value: u32 = 0;
297294
298 const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(295 const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
299 self.key,296 self.key,
300 subkey_utf16le,297 subkey_wtf16le,
301 value_name_utf16le,298 value_name_wtf16le,
302 RRF.RT_REG_DWORD,299 RRF.RT_REG_DWORD,
303 &actual_type,300 &actual_type,
304 &reg_value,301 &reg_value,
...@@ -324,11 +321,11 @@ const RegistryUtf16Le = struct {...@@ -324,11 +321,11 @@ const RegistryUtf16Le = struct {
324 /// Under private space with flags:321 /// Under private space with flags:
325 /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.322 /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
326 /// After finishing work, call `closeKey`.323 /// After finishing work, call `closeKey`.
327 fn loadFromPath(absolute_path_as_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le {324 fn loadFromPath(absolute_path_as_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le {
328 var key: windows.HKEY = undefined;325 var key: windows.HKEY = undefined;
329326
330 const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW(327 const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW(
331 absolute_path_as_utf16le,328 absolute_path_as_wtf16le,
332 &key,329 &key,
333 windows.KEY_QUERY_VALUE | windows.KEY_ENUMERATE_SUB_KEYS,330 windows.KEY_QUERY_VALUE | windows.KEY_ENUMERATE_SUB_KEYS,
334 0,331 0,
...@@ -340,7 +337,7 @@ const RegistryUtf16Le = struct {...@@ -340,7 +337,7 @@ const RegistryUtf16Le = struct {
340 else => return error.KeyNotFound,337 else => return error.KeyNotFound,
341 }338 }
342339
343 return RegistryUtf16Le{ .key = key };340 return RegistryWtf16Le{ .key = key };
344 }341 }
345};342};
346343
...@@ -352,7 +349,7 @@ pub const Windows10Sdk = struct {...@@ -352,7 +349,7 @@ pub const Windows10Sdk = struct {
352 /// Caller owns the result's fields.349 /// Caller owns the result's fields.
353 /// After finishing work, call `free(allocator)`.350 /// After finishing work, call `free(allocator)`.
354 fn find(allocator: std.mem.Allocator) error{ OutOfMemory, Windows10SdkNotFound, PathTooLong, VersionTooLong }!Windows10Sdk {351 fn find(allocator: std.mem.Allocator) error{ OutOfMemory, Windows10SdkNotFound, PathTooLong, VersionTooLong }!Windows10Sdk {
355 const v10_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) {352 const v10_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) {
356 error.KeyNotFound => return error.Windows10SdkNotFound,353 error.KeyNotFound => return error.Windows10SdkNotFound,
357 };354 };
358 defer v10_key.closeKey();355 defer v10_key.closeKey();
...@@ -413,11 +410,11 @@ pub const Windows10Sdk = struct {...@@ -413,11 +410,11 @@ pub const Windows10Sdk = struct {
413 /// Check whether this version is enumerated in registry.410 /// Check whether this version is enumerated in registry.
414 fn isValidVersion(windows10sdk: *const Windows10Sdk) bool {411 fn isValidVersion(windows10sdk: *const Windows10Sdk) bool {
415 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;412 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
416 const reg_query_as_utf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) {413 const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) {
417 error.NoSpaceLeft => return false,414 error.NoSpaceLeft => return false,
418 };415 };
419416
420 const options_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_utf8) catch |err| switch (err) {417 const options_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_wtf8) catch |err| switch (err) {
421 error.KeyNotFound => return false,418 error.KeyNotFound => return false,
422 };419 };
423 defer options_key.closeKey();420 defer options_key.closeKey();
...@@ -447,7 +444,7 @@ pub const Windows81Sdk = struct {...@@ -447,7 +444,7 @@ pub const Windows81Sdk = struct {
447 /// Find path and version of Windows 8.1 SDK.444 /// Find path and version of Windows 8.1 SDK.
448 /// Caller owns the result's fields.445 /// Caller owns the result's fields.
449 /// After finishing work, call `free(allocator)`.446 /// After finishing work, call `free(allocator)`.
450 fn find(allocator: std.mem.Allocator, roots_key: *const RegistryUtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk {447 fn find(allocator: std.mem.Allocator, roots_key: *const RegistryWtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk {
451 const path: []const u8 = path81: {448 const path: []const u8 = path81: {
452 const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", "KitsRoot81") catch |err| switch (err) {449 const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", "KitsRoot81") catch |err| switch (err) {
453 error.NotAString => return error.Windows81SdkNotFound,450 error.NotAString => return error.Windows81SdkNotFound,
...@@ -523,7 +520,7 @@ pub const ZigWindowsSDK = struct {...@@ -523,7 +520,7 @@ pub const ZigWindowsSDK = struct {
523 if (builtin.os.tag != .windows) return error.NotFound;520 if (builtin.os.tag != .windows) return error.NotFound;
524521
525 //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed522 //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed
526 const roots_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) {523 const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) {
527 error.KeyNotFound => return error.NotFound,524 error.KeyNotFound => return error.NotFound,
528 };525 };
529 defer roots_key.closeKey();526 defer roots_key.closeKey();
...@@ -583,7 +580,7 @@ pub const ZigWindowsSDK = struct {...@@ -583,7 +580,7 @@ pub const ZigWindowsSDK = struct {
583const MsvcLibDir = struct {580const MsvcLibDir = struct {
584 fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir {581 fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir {
585 const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}";582 const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}";
586 const setup_config_key = RegistryUtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) {583 const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) {
587 error.KeyNotFound => return error.PathNotFound,584 error.KeyNotFound => return error.PathNotFound,
588 };585 };
589 defer setup_config_key.closeKey();586 defer setup_config_key.closeKey();
...@@ -805,13 +802,13 @@ const MsvcLibDir = struct {...@@ -805,13 +802,13 @@ const MsvcLibDir = struct {
805 for (vs_versions) |vs_version| allocator.free(vs_version);802 for (vs_versions) |vs_version| allocator.free(vs_version);
806 allocator.free(vs_versions);803 allocator.free(vs_versions);
807 }804 }
808 var config_subkey_buf: [RegistryUtf16Le.key_name_max_len * 2]u8 = undefined;805 var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined;
809 const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {806 const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {
810 const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;807 const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;
811 defer allocator.free(privateregistry_absolute_path);808 defer allocator.free(privateregistry_absolute_path);
812 if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue;809 if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue;
813810
814 const visualstudio_registry = RegistryUtf8.loadFromPath(privateregistry_absolute_path) catch continue;811 const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue;
815 defer visualstudio_registry.closeKey();812 defer visualstudio_registry.closeKey();
816813
817 const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable;814 const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable;
...@@ -894,7 +891,7 @@ const MsvcLibDir = struct {...@@ -894,7 +891,7 @@ const MsvcLibDir = struct {
894 }891 }
895 }892 }
896893
897 const vs7_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound;894 const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound;
898 defer vs7_key.closeKey();895 defer vs7_key.closeKey();
899 try_vs7_key: {896 try_vs7_key: {
900 const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) {897 const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) {