authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-25 01:00:25-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-02-25 01:00:25-08:00
log6c2eb0f131588be111652a755a4492ff72d16440
tree0d317950da0694df32c4eb088278662f159e8736
parent63ea3e172e2788856cfb69b2f6085930a1c69d5b
parent9fec608b3bbe3c00528e01bd09aa29f9b9f97415
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #19005 from squeek502/wtf

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

23 files changed, 1887 insertions(+), 472 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.utf8ToUtf16LeWithNull(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.utf8ToUtf16LeWithNull(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.utf8ToUtf16LeWithNull(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.utf8ToUtf16LeWithNull(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.utf8ToUtf16LeWithNull(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.utf8ToUtf16LeWithNull(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+36-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;
...@@ -1925,3 +1939,18 @@ test "ComponentIterator roots" {...@@ -1925,3 +1939,18 @@ test "ComponentIterator roots" {
1925 try std.testing.expectEqualStrings("//a/b//", it.root().?);1939 try std.testing.expectEqualStrings("//a/b//", it.root().?);
1926 }1940 }
1927}1941}
1942
1943/// Format a path encoded as bytes for display as UTF-8.
1944/// Returns a Formatter for the given path. The path will be converted to valid UTF-8
1945/// during formatting. This is a lossy conversion if the path contains any ill-formed UTF-8.
1946/// Ill-formed UTF-8 byte sequences are replaced by the replacement character (U+FFFD)
1947/// according to "U+FFFD Substitution of Maximal Subparts" from Chapter 3 of
1948/// the Unicode standard, and as specified by https://encoding.spec.whatwg.org/#utf-8-decoder
1949pub const fmtAsUtf8Lossy = std.unicode.fmtUtf8;
1950
1951/// Format a path encoded as WTF-16 LE for display as UTF-8.
1952/// Return a Formatter for a (potentially ill-formed) UTF-16 LE path.
1953/// The path will be converted to valid UTF-8 during formatting. This is
1954/// a lossy conversion if the path contains any unpaired surrogates.
1955/// Unpaired surrogates are replaced by the replacement character (U+FFFD).
1956pub const fmtWtf16LeAsUtf8Lossy = std.unicode.fmtUtf16Le;
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/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/os/windows/test.zig+2-2
...@@ -30,7 +30,7 @@ fn testToPrefixedFileNoOracle(comptime path: []const u8, comptime expected_path:...@@ -30,7 +30,7 @@ fn testToPrefixedFileNoOracle(comptime path: []const u8, comptime expected_path:
30 const expected_path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(expected_path);30 const expected_path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(expected_path);
31 const actual_path = try windows.wToPrefixedFileW(null, path_utf16);31 const actual_path = try windows.wToPrefixedFileW(null, path_utf16);
32 std.testing.expectEqualSlices(u16, expected_path_utf16, actual_path.span()) catch |e| {32 std.testing.expectEqualSlices(u16, expected_path_utf16, actual_path.span()) catch |e| {
33 std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16le(actual_path.span()), std.unicode.fmtUtf16le(expected_path_utf16) });33 std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16Le(actual_path.span()), std.unicode.fmtUtf16le(expected_path_utf16) });
34 return e;34 return e;
35 };35 };
36}36}
...@@ -48,7 +48,7 @@ fn testToPrefixedFileOnlyOracle(comptime path: []const u8) !void {...@@ -48,7 +48,7 @@ fn testToPrefixedFileOnlyOracle(comptime path: []const u8) !void {
48 const zig_result = try windows.wToPrefixedFileW(null, path_utf16);48 const zig_result = try windows.wToPrefixedFileW(null, path_utf16);
49 const win32_api_result = try RtlDosPathNameToNtPathName_U(path_utf16);49 const win32_api_result = try RtlDosPathNameToNtPathName_U(path_utf16);
50 std.testing.expectEqualSlices(u16, win32_api_result.span(), zig_result.span()) catch |e| {50 std.testing.expectEqualSlices(u16, win32_api_result.span(), zig_result.span()) catch |e| {
51 std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16le(zig_result.span()), std.unicode.fmtUtf16le(win32_api_result.span()) });51 std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16Le(zig_result.span()), std.unicode.fmtUtf16le(win32_api_result.span()) });
52 return e;52 return e;
53 };53 };
54}54}
lib/std/process.zig+92-64
...@@ -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,28 @@ pub const GetEnvVarOwnedError = error{...@@ -355,25 +380,28 @@ 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.utf8ToUtf16LeWithNull(allocator, key);396 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
367 defer allocator.free(key_w);397 const stack_allocator = stack_alloc.get();
398 const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
399 defer stack_allocator.free(key_w);
368400
369 break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound;401 break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
370 };402 };
371 return std.unicode.utf16leToUtf8Alloc(allocator, result_w) catch |err| switch (err) {403 // wtf16LeToWtf8Alloc can only fail with OutOfMemory
372 error.DanglingSurrogateHalf => return error.InvalidUtf8,404 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) {405 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
378 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;406 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
379 defer envmap.deinit();407 defer envmap.deinit();
...@@ -385,6 +413,7 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError...@@ -385,6 +413,7 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
385 }413 }
386}414}
387415
416/// On Windows, `key` must be valid UTF-8.
388pub fn hasEnvVarConstant(comptime key: []const u8) bool {417pub fn hasEnvVarConstant(comptime key: []const u8) bool {
389 if (builtin.os.tag == .windows) {418 if (builtin.os.tag == .windows) {
390 const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);419 const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);
...@@ -396,11 +425,22 @@ pub fn hasEnvVarConstant(comptime key: []const u8) bool {...@@ -396,11 +425,22 @@ pub fn hasEnvVarConstant(comptime key: []const u8) bool {
396 }425 }
397}426}
398427
399pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool {428pub const HasEnvVarError = error{
429 OutOfMemory,
430
431 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
432 /// https://simonsapin.github.io/wtf-8/
433 InvalidWtf8,
434};
435
436/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/),
437/// then `error.InvalidWtf8` is returned.
438pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
400 if (builtin.os.tag == .windows) {439 if (builtin.os.tag == .windows) {
401 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);440 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
402 const key_w = try std.unicode.utf8ToUtf16LeWithNull(stack_alloc.get(), key);441 const stack_allocator = stack_alloc.get();
403 defer stack_alloc.allocator.free(key_w);442 const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
443 defer stack_allocator.free(key_w);
404 return std.os.getenvW(key_w) != null;444 return std.os.getenvW(key_w) != null;
405 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {445 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
406 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;446 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
...@@ -411,9 +451,22 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool...@@ -411,9 +451,22 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool
411 }451 }
412}452}
413453
414test "os.getEnvVarOwned" {454test getEnvVarOwned {
415 const ga = std.testing.allocator;455 try testing.expectError(
416 try testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV"));456 error.EnvironmentVariableNotFound,
457 getEnvVarOwned(std.testing.allocator, "BADENV"),
458 );
459}
460
461test hasEnvVarConstant {
462 if (builtin.os.tag == .wasi and !builtin.link_libc) return error.SkipZigTest;
463
464 try testing.expect(!hasEnvVarConstant("BADENV"));
465}
466
467test hasEnvVar {
468 const has_env = try hasEnvVar(std.testing.allocator, "BADENV");
469 try testing.expect(!has_env);
417}470}
418471
419pub const ArgIteratorPosix = struct {472pub const ArgIteratorPosix = struct {
...@@ -531,6 +584,7 @@ pub const ArgIteratorWasi = struct {...@@ -531,6 +584,7 @@ pub const ArgIteratorWasi = struct {
531pub const ArgIteratorWindows = struct {584pub const ArgIteratorWindows = struct {
532 allocator: Allocator,585 allocator: Allocator,
533 /// Owned by the iterator.586 /// Owned by the iterator.
587 /// Encoded as WTF-8.
534 cmd_line: []const u8,588 cmd_line: []const u8,
535 index: usize = 0,589 index: usize = 0,
536 /// Owned by the iterator. Long enough to hold the entire `cmd_line` plus a null terminator.590 /// Owned by the iterator. Long enough to hold the entire `cmd_line` plus a null terminator.
...@@ -538,20 +592,14 @@ pub const ArgIteratorWindows = struct {...@@ -538,20 +592,14 @@ pub const ArgIteratorWindows = struct {
538 start: usize = 0,592 start: usize = 0,
539 end: usize = 0,593 end: usize = 0,
540594
541 pub const InitError = error{ OutOfMemory, InvalidCmdLine };595 pub const InitError = error{OutOfMemory};
542596
543 /// `cmd_line_w` *must* be an UTF16-LE-encoded string.597 /// `cmd_line_w` *must* be a WTF16-LE-encoded string.
544 ///598 ///
545 /// The iterator makes a copy of `cmd_line_w` converted UTF-8 and keeps it; it does *not* take599 /// 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`.600 /// ownership of `cmd_line_w`.
547 pub fn init(allocator: Allocator, cmd_line_w: [*:0]const u16) InitError!ArgIteratorWindows {601 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) {602 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);603 errdefer allocator.free(cmd_line);
556604
557 const buffer = try allocator.alloc(u8, cmd_line.len + 1);605 const buffer = try allocator.alloc(u8, cmd_line.len + 1);
...@@ -566,6 +614,7 @@ pub const ArgIteratorWindows = struct {...@@ -566,6 +614,7 @@ pub const ArgIteratorWindows = struct {
566614
567 /// Returns the next argument and advances the iterator. Returns `null` if at the end of the615 /// 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.616 /// command-line string. The iterator owns the returned slice.
617 /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
569 pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 {618 pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 {
570 return self.nextWithStrategy(next_strategy);619 return self.nextWithStrategy(next_strategy);
571 }620 }
...@@ -777,7 +826,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {...@@ -777,7 +826,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
777 pub const Self = @This();826 pub const Self = @This();
778827
779 pub const InitError = error{OutOfMemory};828 pub const InitError = error{OutOfMemory};
780 pub const InitUtf16leError = error{ OutOfMemory, InvalidCmdLine };
781829
782 /// cmd_line_utf8 MUST remain valid and constant while using this instance830 /// 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 {831 pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
...@@ -805,30 +853,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {...@@ -805,30 +853,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
805 };853 };
806 }854 }
807855
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.856 // Skips over whitespace in the cmd_line.
833 // Returns false if the terminating sentinel is reached, true otherwise.857 // Returns false if the terminating sentinel is reached, true otherwise.
834 // Also skips over comments (if supported).858 // Also skips over comments (if supported).
...@@ -1021,6 +1045,8 @@ pub const ArgIterator = struct {...@@ -1021,6 +1045,8 @@ pub const ArgIterator = struct {
10211045
1022 /// Get the next argument. Returns 'null' if we are at the end.1046 /// Get the next argument. Returns 'null' if we are at the end.
1023 /// Returned slice is pointing to the iterator's internal buffer.1047 /// Returned slice is pointing to the iterator's internal buffer.
1048 /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1049 /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1024 pub fn next(self: *ArgIterator) ?([:0]const u8) {1050 pub fn next(self: *ArgIterator) ?([:0]const u8) {
1025 return self.inner.next();1051 return self.inner.next();
1026 }1052 }
...@@ -1057,6 +1083,8 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator...@@ -1057,6 +1083,8 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator
1057}1083}
10581084
1059/// Caller must call argsFree on result.1085/// Caller must call argsFree on result.
1086/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1087/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1060pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {1088pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
1061 // TODO refactor to only make 1 allocation.1089 // TODO refactor to only make 1 allocation.
1062 var it = try argsWithAllocator(allocator);1090 var it = try argsWithAllocator(allocator);
...@@ -1201,7 +1229,7 @@ test "ArgIteratorWindows" {...@@ -1201,7 +1229,7 @@ test "ArgIteratorWindows" {
1201}1229}
12021230
1203fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {1231fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {
1204 const cmd_line_w = try std.unicode.utf8ToUtf16LeWithNull(testing.allocator, cmd_line);1232 const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);
1205 defer testing.allocator.free(cmd_line_w);1233 defer testing.allocator.free(cmd_line_w);
12061234
1207 // next1235 // next
lib/std/unicode.zig+914-104
...@@ -39,7 +39,16 @@ pub fn utf8ByteSequenceLength(first_byte: u8) !u3 {...@@ -39,7 +39,16 @@ pub fn utf8ByteSequenceLength(first_byte: u8) !u3 {
39/// out: the out buffer to write to. Must have a len >= utf8CodepointSequenceLength(c).39/// out: the out buffer to write to. Must have a len >= utf8CodepointSequenceLength(c).
40/// Errors: if c cannot be encoded in UTF-8.40/// Errors: if c cannot be encoded in UTF-8.
41/// Returns: the number of bytes written to out.41/// Returns: the number of bytes written to out.
42pub fn utf8Encode(c: u21, out: []u8) !u3 {42pub fn utf8Encode(c: u21, out: []u8) error{ Utf8CannotEncodeSurrogateHalf, CodepointTooLarge }!u3 {
43 return utf8EncodeImpl(c, out, .cannot_encode_surrogate_half);
44}
45
46const Surrogates = enum {
47 cannot_encode_surrogate_half,
48 can_encode_surrogate_half,
49};
50
51fn utf8EncodeImpl(c: u21, out: []u8, comptime surrogates: Surrogates) !u3 {
43 const length = try utf8CodepointSequenceLength(c);52 const length = try utf8CodepointSequenceLength(c);
44 assert(out.len >= length);53 assert(out.len >= length);
45 switch (length) {54 switch (length) {
...@@ -53,7 +62,9 @@ pub fn utf8Encode(c: u21, out: []u8) !u3 {...@@ -53,7 +62,9 @@ pub fn utf8Encode(c: u21, out: []u8) !u3 {
53 out[1] = @as(u8, @intCast(0b10000000 | (c & 0b111111)));62 out[1] = @as(u8, @intCast(0b10000000 | (c & 0b111111)));
54 },63 },
55 3 => {64 3 => {
56 if (0xd800 <= c and c <= 0xdfff) return error.Utf8CannotEncodeSurrogateHalf;65 if (surrogates == .cannot_encode_surrogate_half and isSurrogateCodepoint(c)) {
66 return error.Utf8CannotEncodeSurrogateHalf;
67 }
57 out[0] = @as(u8, @intCast(0b11100000 | (c >> 12)));68 out[0] = @as(u8, @intCast(0b11100000 | (c >> 12)));
58 out[1] = @as(u8, @intCast(0b10000000 | ((c >> 6) & 0b111111)));69 out[1] = @as(u8, @intCast(0b10000000 | ((c >> 6) & 0b111111)));
59 out[2] = @as(u8, @intCast(0b10000000 | (c & 0b111111)));70 out[2] = @as(u8, @intCast(0b10000000 | (c & 0b111111)));
...@@ -116,12 +127,22 @@ pub fn utf8Decode2(bytes: []const u8) Utf8Decode2Error!u21 {...@@ -116,12 +127,22 @@ pub fn utf8Decode2(bytes: []const u8) Utf8Decode2Error!u21 {
116 return value;127 return value;
117}128}
118129
119const Utf8Decode3Error = error{130const Utf8Decode3Error = Utf8Decode3AllowSurrogateHalfError || error{
120 Utf8ExpectedContinuation,
121 Utf8OverlongEncoding,
122 Utf8EncodesSurrogateHalf,131 Utf8EncodesSurrogateHalf,
123};132};
124pub fn utf8Decode3(bytes: []const u8) Utf8Decode3Error!u21 {133pub fn utf8Decode3(bytes: []const u8) Utf8Decode3Error!u21 {
134 const value = try utf8Decode3AllowSurrogateHalf(bytes);
135
136 if (0xd800 <= value and value <= 0xdfff) return error.Utf8EncodesSurrogateHalf;
137
138 return value;
139}
140
141const Utf8Decode3AllowSurrogateHalfError = error{
142 Utf8ExpectedContinuation,
143 Utf8OverlongEncoding,
144};
145pub fn utf8Decode3AllowSurrogateHalf(bytes: []const u8) Utf8Decode3AllowSurrogateHalfError!u21 {
125 assert(bytes.len == 3);146 assert(bytes.len == 3);
126 assert(bytes[0] & 0b11110000 == 0b11100000);147 assert(bytes[0] & 0b11110000 == 0b11100000);
127 var value: u21 = bytes[0] & 0b00001111;148 var value: u21 = bytes[0] & 0b00001111;
...@@ -135,7 +156,6 @@ pub fn utf8Decode3(bytes: []const u8) Utf8Decode3Error!u21 {...@@ -135,7 +156,6 @@ pub fn utf8Decode3(bytes: []const u8) Utf8Decode3Error!u21 {
135 value |= bytes[2] & 0b00111111;156 value |= bytes[2] & 0b00111111;
136157
137 if (value < 0x800) return error.Utf8OverlongEncoding;158 if (value < 0x800) return error.Utf8OverlongEncoding;
138 if (0xd800 <= value and value <= 0xdfff) return error.Utf8EncodesSurrogateHalf;
139159
140 return value;160 return value;
141}161}
...@@ -213,6 +233,10 @@ pub fn utf8CountCodepoints(s: []const u8) !usize {...@@ -213,6 +233,10 @@ pub fn utf8CountCodepoints(s: []const u8) !usize {
213233
214/// Returns true if the input consists entirely of UTF-8 codepoints234/// Returns true if the input consists entirely of UTF-8 codepoints
215pub fn utf8ValidateSlice(input: []const u8) bool {235pub fn utf8ValidateSlice(input: []const u8) bool {
236 return utf8ValidateSliceImpl(input, .cannot_encode_surrogate_half);
237}
238
239fn utf8ValidateSliceImpl(input: []const u8, comptime surrogates: Surrogates) bool {
216 var remaining = input;240 var remaining = input;
217241
218 const chunk_len = std.simd.suggestVectorLength(u8) orelse 1;242 const chunk_len = std.simd.suggestVectorLength(u8) orelse 1;
...@@ -240,9 +264,15 @@ pub fn utf8ValidateSlice(input: []const u8) bool {...@@ -240,9 +264,15 @@ pub fn utf8ValidateSlice(input: []const u8) bool {
240 const xx = 0xF1; // invalid: size 1264 const xx = 0xF1; // invalid: size 1
241 const as = 0xF0; // ASCII: size 1265 const as = 0xF0; // ASCII: size 1
242 const s1 = 0x02; // accept 0, size 2266 const s1 = 0x02; // accept 0, size 2
243 const s2 = 0x13; // accept 1, size 3267 const s2 = switch (surrogates) {
268 .cannot_encode_surrogate_half => 0x13, // accept 1, size 3
269 .can_encode_surrogate_half => 0x03, // accept 0, size 3
270 };
244 const s3 = 0x03; // accept 0, size 3271 const s3 = 0x03; // accept 0, size 3
245 const s4 = 0x23; // accept 2, size 3272 const s4 = switch (surrogates) {
273 .cannot_encode_surrogate_half => 0x23, // accept 2, size 3
274 .can_encode_surrogate_half => 0x03, // accept 0, size 3
275 };
246 const s5 = 0x34; // accept 3, size 4276 const s5 = 0x34; // accept 3, size 4
247 const s6 = 0x04; // accept 0, size 4277 const s6 = 0x04; // accept 0, size 4
248 const s7 = 0x44; // accept 4, size 4278 const s7 = 0x44; // accept 4, size 4
...@@ -458,7 +488,9 @@ pub const Utf16LeIterator = struct {...@@ -458,7 +488,9 @@ pub const Utf16LeIterator = struct {
458 };488 };
459 }489 }
460490
461 pub fn nextCodepoint(it: *Utf16LeIterator) !?u21 {491 pub const NextCodepointError = error{ DanglingSurrogateHalf, ExpectedSecondSurrogateHalf, UnexpectedSecondSurrogateHalf };
492
493 pub fn nextCodepoint(it: *Utf16LeIterator) NextCodepointError!?u21 {
462 assert(it.i <= it.bytes.len);494 assert(it.i <= it.bytes.len);
463 if (it.i == it.bytes.len) return null;495 if (it.i == it.bytes.len) return null;
464 var code_units: [2]u16 = undefined;496 var code_units: [2]u16 = undefined;
...@@ -770,11 +802,139 @@ fn testDecode(bytes: []const u8) !u21 {...@@ -770,11 +802,139 @@ fn testDecode(bytes: []const u8) !u21 {
770 return utf8Decode(bytes);802 return utf8Decode(bytes);
771}803}
772804
773/// Caller must free returned memory.805/// Print the given `utf8` string, encoded as UTF-8 bytes.
774pub fn utf16leToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8 {806/// Ill-formed UTF-8 byte sequences are replaced by the replacement character (U+FFFD)
807/// according to "U+FFFD Substitution of Maximal Subparts" from Chapter 3 of
808/// the Unicode standard, and as specified by https://encoding.spec.whatwg.org/#utf-8-decoder
809fn formatUtf8(
810 utf8: []const u8,
811 comptime fmt: []const u8,
812 options: std.fmt.FormatOptions,
813 writer: anytype,
814) !void {
815 _ = fmt;
816 _ = options;
817 var buf: [300]u8 = undefined; // just an arbitrary size
818 var u8len: usize = 0;
819
820 // This implementation is based on this specification:
821 // https://encoding.spec.whatwg.org/#utf-8-decoder
822 var codepoint: u21 = 0;
823 var cont_bytes_seen: u3 = 0;
824 var cont_bytes_needed: u3 = 0;
825 var lower_boundary: u8 = 0x80;
826 var upper_boundary: u8 = 0xBF;
827
828 var i: usize = 0;
829 while (i < utf8.len) {
830 const byte = utf8[i];
831 if (cont_bytes_needed == 0) {
832 switch (byte) {
833 0x00...0x7F => {
834 buf[u8len] = byte;
835 u8len += 1;
836 },
837 0xC2...0xDF => {
838 cont_bytes_needed = 1;
839 codepoint = byte & 0b00011111;
840 },
841 0xE0...0xEF => {
842 if (byte == 0xE0) lower_boundary = 0xA0;
843 if (byte == 0xED) upper_boundary = 0x9F;
844 cont_bytes_needed = 2;
845 codepoint = byte & 0b00001111;
846 },
847 0xF0...0xF4 => {
848 if (byte == 0xF0) lower_boundary = 0x90;
849 if (byte == 0xF4) upper_boundary = 0x8F;
850 cont_bytes_needed = 3;
851 codepoint = byte & 0b00000111;
852 },
853 else => {
854 u8len += utf8Encode(replacement_character, buf[u8len..]) catch unreachable;
855 },
856 }
857 // consume the byte
858 i += 1;
859 } else if (byte < lower_boundary or byte > upper_boundary) {
860 codepoint = 0;
861 cont_bytes_needed = 0;
862 cont_bytes_seen = 0;
863 lower_boundary = 0x80;
864 upper_boundary = 0xBF;
865 u8len += utf8Encode(replacement_character, buf[u8len..]) catch unreachable;
866 // do not consume the current byte, it should now be treated as a possible start byte
867 } else {
868 lower_boundary = 0x80;
869 upper_boundary = 0xBF;
870 codepoint <<= 6;
871 codepoint |= byte & 0b00111111;
872 cont_bytes_seen += 1;
873 // consume the byte
874 i += 1;
875
876 if (cont_bytes_seen == cont_bytes_needed) {
877 const codepoint_len = cont_bytes_seen + 1;
878 const codepoint_start_i = i - codepoint_len;
879 @memcpy(buf[u8len..][0..codepoint_len], utf8[codepoint_start_i..][0..codepoint_len]);
880 u8len += codepoint_len;
881
882 codepoint = 0;
883 cont_bytes_needed = 0;
884 cont_bytes_seen = 0;
885 }
886 }
887 // make sure there's always enough room for another maximum length UTF-8 codepoint
888 if (u8len + 4 > buf.len) {
889 try writer.writeAll(buf[0..u8len]);
890 u8len = 0;
891 }
892 }
893 if (cont_bytes_needed != 0) {
894 // we know there's enough room because we always flush
895 // if there's less than 4 bytes remaining in the buffer.
896 u8len += utf8Encode(replacement_character, buf[u8len..]) catch unreachable;
897 }
898 try writer.writeAll(buf[0..u8len]);
899}
900
901/// Return a Formatter for a (potentially ill-formed) UTF-8 string.
902/// Ill-formed UTF-8 byte sequences are replaced by the replacement character (U+FFFD)
903/// according to "U+FFFD Substitution of Maximal Subparts" from Chapter 3 of
904/// the Unicode standard, and as specified by https://encoding.spec.whatwg.org/#utf-8-decoder
905pub fn fmtUtf8(utf8: []const u8) std.fmt.Formatter(formatUtf8) {
906 return .{ .data = utf8 };
907}
908
909test "fmtUtf8" {
910 const expectFmt = testing.expectFmt;
911 try expectFmt("", "{}", .{fmtUtf8("")});
912 try expectFmt("foo", "{}", .{fmtUtf8("foo")});
913 try expectFmt("𐐷", "{}", .{fmtUtf8("𐐷")});
914
915 // Table 3-8. U+FFFD for Non-Shortest Form Sequences
916 try expectFmt("��������A", "{}", .{fmtUtf8("\xC0\xAF\xE0\x80\xBF\xF0\x81\x82A")});
917
918 // Table 3-9. U+FFFD for Ill-Formed Sequences for Surrogates
919 try expectFmt("��������A", "{}", .{fmtUtf8("\xED\xA0\x80\xED\xBF\xBF\xED\xAFA")});
920
921 // Table 3-10. U+FFFD for Other Ill-Formed Sequences
922 try expectFmt("�����A��B", "{}", .{fmtUtf8("\xF4\x91\x92\x93\xFFA\x80\xBFB")});
923
924 // Table 3-11. U+FFFD for Truncated Sequences
925 try expectFmt("����A", "{}", .{fmtUtf8("\xE1\x80\xE2\xF0\x91\x92\xF1\xBFA")});
926}
927
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 {
775 // optimistically guess that it will all be ascii.936 // optimistically guess that it will all be ascii.
776 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len);937 try array_list.ensureTotalCapacityPrecise(utf16le.len);
777 errdefer result.deinit();
778938
779 var remaining = utf16le;939 var remaining = utf16le;
780 if (builtin.zig_backend != .stage2_x86_64) {940 if (builtin.zig_backend != .stage2_x86_64) {
...@@ -796,68 +956,76 @@ pub fn utf16leToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8...@@ -796,68 +956,76 @@ pub fn utf16leToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8
796 // We allocated enough space to encode every UTF-16 code unit956 // We allocated enough space to encode every UTF-16 code unit
797 // as ASCII, so if the entire string is ASCII then we are957 // as ASCII, so if the entire string is ASCII then we are
798 // guaranteed to have enough space allocated958 // guaranteed to have enough space allocated
799 result.appendSliceAssumeCapacity(&ascii_bytes);959 array_list.appendSliceAssumeCapacity(&ascii_bytes);
800 remaining = remaining[chunk_len..];960 remaining = remaining[chunk_len..];
801 }961 }
802 }962 }
803963
804 var out_index: usize = result.items.len;964 var out_index: usize = array_list.items.len;
805 var it = Utf16LeIterator.init(remaining);965 switch (surrogates) {
806 while (try it.nextCodepoint()) |codepoint| {966 .cannot_encode_surrogate_half => {
807 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;967 var it = Utf16LeIterator.init(remaining);
808 try result.resize(result.items.len + utf8_len);968 while (try it.nextCodepoint()) |codepoint| {
809 assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len);969 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
810 out_index += utf8_len;970 try array_list.resize(array_list.items.len + utf8_len);
971 assert((utf8Encode(codepoint, array_list.items[out_index..]) catch unreachable) == utf8_len);
972 out_index += utf8_len;
973 }
974 },
975 .can_encode_surrogate_half => {
976 var it = Wtf16LeIterator.init(remaining);
977 while (it.nextCodepoint()) |codepoint| {
978 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
979 try array_list.resize(array_list.items.len + utf8_len);
980 assert((wtf8Encode(codepoint, array_list.items[out_index..]) catch unreachable) == utf8_len);
981 out_index += utf8_len;
982 }
983 },
811 }984 }
985}
986
987pub const Utf16LeToUtf8AllocError = mem.Allocator.Error || Utf16LeToUtf8Error;
988
989pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) Utf16LeToUtf8AllocError!void {
990 return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .cannot_encode_surrogate_half);
991}
992
993/// Deprecated; renamed to utf16LeToUtf8Alloc
994pub const utf16leToUtf8Alloc = utf16LeToUtf8Alloc;
995
996/// Caller must free returned memory.
997pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![]u8 {
998 // optimistically guess that it will all be ascii.
999 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len);
1000 errdefer result.deinit();
1001
1002 try utf16LeToUtf8ArrayList(&result, utf16le);
8121003
813 return result.toOwnedSlice();1004 return result.toOwnedSlice();
814}1005}
8151006
1007/// Deprecated; renamed to utf16LeToUtf8AllocZ
1008pub const utf16leToUtf8AllocZ = utf16LeToUtf8AllocZ;
1009
816/// Caller must free returned memory.1010/// Caller must free returned memory.
817pub fn utf16leToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) ![:0]u8 {1011pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![:0]u8 {
818 // 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)
819 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len + 1);1013 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len + 1);
820 errdefer result.deinit();1014 errdefer result.deinit();
8211015
822 var remaining = utf16le;1016 try utf16LeToUtf8ArrayList(&result, utf16le);
823 if (builtin.zig_backend != .stage2_x86_64) {
824 const chunk_len = std.simd.suggestVectorLength(u16) orelse 1;
825 const Chunk = @Vector(chunk_len, u16);
826
827 // Fast path. Check for and encode ASCII characters at the start of the input.
828 while (remaining.len >= chunk_len) {
829 const chunk: Chunk = remaining[0..chunk_len].*;
830 const mask: Chunk = @splat(std.mem.nativeToLittle(u16, 0x7F));
831 if (@reduce(.Or, chunk | mask != mask)) {
832 // found a non ASCII code unit
833 break;
834 }
835 const chunk_byte_len = chunk_len * 2;
836 const chunk_bytes: @Vector(chunk_byte_len, u8) = (std.mem.sliceAsBytes(remaining)[0..chunk_byte_len]).*;
837 const deinterlaced_bytes = std.simd.deinterlace(2, chunk_bytes);
838 const ascii_bytes: [chunk_len]u8 = deinterlaced_bytes[0];
839 // We allocated enough space to encode every UTF-16 code unit
840 // as ASCII, so if the entire string is ASCII then we are
841 // guaranteed to have enough space allocated
842 result.appendSliceAssumeCapacity(&ascii_bytes);
843 remaining = remaining[chunk_len..];
844 }
845 }
8461017
847 var out_index = result.items.len;
848 var it = Utf16LeIterator.init(remaining);
849 while (try it.nextCodepoint()) |codepoint| {
850 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
851 try result.resize(result.items.len + utf8_len);
852 assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len);
853 out_index += utf8_len;
854 }
855 return result.toOwnedSliceSentinel(0);1018 return result.toOwnedSliceSentinel(0);
856}1019}
8571020
1021pub const Utf16LeToUtf8Error = Utf16LeIterator.NextCodepointError;
1022
858/// Asserts that the output buffer is big enough.1023/// Asserts that the output buffer is big enough.
859/// Returns end byte index into utf8.1024/// Returns end byte index into utf8.
860pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !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 {
861 var end_index: usize = 0;1029 var end_index: usize = 0;
8621030
863 var remaining = utf16le;1031 var remaining = utf16le;
...@@ -883,30 +1051,58 @@ pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !usize {...@@ -883,30 +1051,58 @@ pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !usize {
883 }1051 }
884 }1052 }
8851053
886 var it = Utf16LeIterator.init(remaining);1054 switch (surrogates) {
887 while (try it.nextCodepoint()) |codepoint| {1055 .cannot_encode_surrogate_half => {
888 end_index += try utf8Encode(codepoint, utf8[end_index..]);1056 var it = Utf16LeIterator.init(remaining);
1057 while (try it.nextCodepoint()) |codepoint| {
1058 end_index += utf8Encode(codepoint, utf8[end_index..]) catch |err| switch (err) {
1059 // The maximum possible codepoint encoded by UTF-16 is U+10FFFF,
1060 // which is within the valid codepoint range.
1061 error.CodepointTooLarge => unreachable,
1062 // We know the codepoint was valid in UTF-16, meaning it is not
1063 // an unpaired surrogate codepoint.
1064 error.Utf8CannotEncodeSurrogateHalf => unreachable,
1065 };
1066 }
1067 },
1068 .can_encode_surrogate_half => {
1069 var it = Wtf16LeIterator.init(remaining);
1070 while (it.nextCodepoint()) |codepoint| {
1071 end_index += wtf8Encode(codepoint, utf8[end_index..]) catch |err| switch (err) {
1072 // The maximum possible codepoint encoded by UTF-16 is U+10FFFF,
1073 // which is within the valid codepoint range.
1074 error.CodepointTooLarge => unreachable,
1075 };
1076 }
1077 },
889 }1078 }
890 return end_index;1079 return end_index;
891}1080}
8921081
893test "utf16leToUtf8" {1082/// Deprecated; renamed to utf16LeToUtf8
1083pub const utf16leToUtf8 = utf16LeToUtf8;
1084
1085pub fn utf16LeToUtf8(utf8: []u8, utf16le: []const u16) Utf16LeToUtf8Error!usize {
1086 return utf16LeToUtf8Impl(utf8, utf16le, .cannot_encode_surrogate_half);
1087}
1088
1089test utf16LeToUtf8 {
894 var utf16le: [2]u16 = undefined;1090 var utf16le: [2]u16 = undefined;
895 const utf16le_as_bytes = mem.sliceAsBytes(utf16le[0..]);1091 const utf16le_as_bytes = mem.sliceAsBytes(utf16le[0..]);
8961092
897 {1093 {
898 mem.writeInt(u16, utf16le_as_bytes[0..2], 'A', .little);1094 mem.writeInt(u16, utf16le_as_bytes[0..2], 'A', .little);
899 mem.writeInt(u16, utf16le_as_bytes[2..4], 'a', .little);1095 mem.writeInt(u16, utf16le_as_bytes[2..4], 'a', .little);
900 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);1096 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
901 defer std.testing.allocator.free(utf8);1097 defer testing.allocator.free(utf8);
902 try testing.expect(mem.eql(u8, utf8, "Aa"));1098 try testing.expect(mem.eql(u8, utf8, "Aa"));
903 }1099 }
9041100
905 {1101 {
906 mem.writeInt(u16, utf16le_as_bytes[0..2], 0x80, .little);1102 mem.writeInt(u16, utf16le_as_bytes[0..2], 0x80, .little);
907 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xffff, .little);1103 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xffff, .little);
908 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);1104 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
909 defer std.testing.allocator.free(utf8);1105 defer testing.allocator.free(utf8);
910 try testing.expect(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf"));1106 try testing.expect(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf"));
911 }1107 }
9121108
...@@ -914,8 +1110,8 @@ test "utf16leToUtf8" {...@@ -914,8 +1110,8 @@ test "utf16leToUtf8" {
914 // the values just outside the surrogate half range1110 // the values just outside the surrogate half range
915 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xd7ff, .little);1111 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xd7ff, .little);
916 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xe000, .little);1112 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xe000, .little);
917 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);1113 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
918 defer std.testing.allocator.free(utf8);1114 defer testing.allocator.free(utf8);
919 try testing.expect(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80"));1115 try testing.expect(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80"));
920 }1116 }
9211117
...@@ -923,8 +1119,8 @@ test "utf16leToUtf8" {...@@ -923,8 +1119,8 @@ test "utf16leToUtf8" {
923 // smallest surrogate pair1119 // smallest surrogate pair
924 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xd800, .little);1120 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xd800, .little);
925 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdc00, .little);1121 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdc00, .little);
926 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);1122 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
927 defer std.testing.allocator.free(utf8);1123 defer testing.allocator.free(utf8);
928 try testing.expect(mem.eql(u8, utf8, "\xf0\x90\x80\x80"));1124 try testing.expect(mem.eql(u8, utf8, "\xf0\x90\x80\x80"));
929 }1125 }
9301126
...@@ -932,31 +1128,30 @@ test "utf16leToUtf8" {...@@ -932,31 +1128,30 @@ test "utf16leToUtf8" {
932 // largest surrogate pair1128 // largest surrogate pair
933 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdbff, .little);1129 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdbff, .little);
934 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdfff, .little);1130 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdfff, .little);
935 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);1131 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
936 defer std.testing.allocator.free(utf8);1132 defer testing.allocator.free(utf8);
937 try testing.expect(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf"));1133 try testing.expect(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf"));
938 }1134 }
9391135
940 {1136 {
941 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdbff, .little);1137 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdbff, .little);
942 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdc00, .little);1138 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdc00, .little);
943 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);1139 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
944 defer std.testing.allocator.free(utf8);1140 defer testing.allocator.free(utf8);
945 try testing.expect(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80"));1141 try testing.expect(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80"));
946 }1142 }
9471143
948 {1144 {
949 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdcdc, .little);1145 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdcdc, .little);
950 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdcdc, .little);1146 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdcdc, .little);
951 const result = utf16leToUtf8Alloc(std.testing.allocator, &utf16le);1147 const result = utf16LeToUtf8Alloc(testing.allocator, &utf16le);
952 try std.testing.expectError(error.UnexpectedSecondSurrogateHalf, result);1148 try testing.expectError(error.UnexpectedSecondSurrogateHalf, result);
953 }1149 }
954}1150}
9551151
956pub fn utf8ToUtf16LeWithNull(allocator: mem.Allocator, utf8: []const u8) ![:0]u16 {1152fn utf8ToUtf16LeArrayListImpl(array_list: *std.ArrayList(u16), utf8: []const u8, comptime surrogates: Surrogates) !void {
957 // optimistically guess that it will not require surrogate pairs1153 // optimistically guess that it will not require surrogate pairs
958 var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len + 1);1154 try array_list.ensureTotalCapacityPrecise(utf8.len);
959 errdefer result.deinit();
9601155
961 var remaining = utf8;1156 var remaining = utf8;
962 // Need support for std.simd.interlace1157 // Need support for std.simd.interlace
...@@ -974,33 +1169,65 @@ pub fn utf8ToUtf16LeWithNull(allocator: mem.Allocator, utf8: []const u8) ![:0]u1...@@ -974,33 +1169,65 @@ pub fn utf8ToUtf16LeWithNull(allocator: mem.Allocator, utf8: []const u8) ![:0]u1
974 }1169 }
975 const zeroes: Chunk = @splat(0);1170 const zeroes: Chunk = @splat(0);
976 const utf16_chunk: [chunk_len * 2]u8 align(@alignOf(u16)) = std.simd.interlace(.{ chunk, zeroes });1171 const utf16_chunk: [chunk_len * 2]u8 align(@alignOf(u16)) = std.simd.interlace(.{ chunk, zeroes });
977 result.appendSliceAssumeCapacity(std.mem.bytesAsSlice(u16, &utf16_chunk));1172 array_list.appendSliceAssumeCapacity(std.mem.bytesAsSlice(u16, &utf16_chunk));
978 remaining = remaining[chunk_len..];1173 remaining = remaining[chunk_len..];
979 }1174 }
980 }1175 }
9811176
982 const view = try Utf8View.init(remaining);1177 const view = switch (surrogates) {
1178 .cannot_encode_surrogate_half => try Utf8View.init(remaining),
1179 .can_encode_surrogate_half => try Wtf8View.init(remaining),
1180 };
983 var it = view.iterator();1181 var it = view.iterator();
984 while (it.nextCodepoint()) |codepoint| {1182 while (it.nextCodepoint()) |codepoint| {
985 if (codepoint < 0x10000) {1183 if (codepoint < 0x10000) {
986 const short = @as(u16, @intCast(codepoint));1184 const short = @as(u16, @intCast(codepoint));
987 try result.append(mem.nativeToLittle(u16, short));1185 try array_list.append(mem.nativeToLittle(u16, short));
988 } else {1186 } else {
989 const high = @as(u16, @intCast((codepoint - 0x10000) >> 10)) + 0xD800;1187 const high = @as(u16, @intCast((codepoint - 0x10000) >> 10)) + 0xD800;
990 const low = @as(u16, @intCast(codepoint & 0x3FF)) + 0xDC00;1188 const low = @as(u16, @intCast(codepoint & 0x3FF)) + 0xDC00;
991 var out: [2]u16 = undefined;1189 var out: [2]u16 = undefined;
992 out[0] = mem.nativeToLittle(u16, high);1190 out[0] = mem.nativeToLittle(u16, high);
993 out[1] = mem.nativeToLittle(u16, low);1191 out[1] = mem.nativeToLittle(u16, low);
994 try result.appendSlice(out[0..]);1192 try array_list.appendSlice(out[0..]);
995 }1193 }
996 }1194 }
1195}
1196
1197pub fn utf8ToUtf16LeArrayList(array_list: *std.ArrayList(u16), utf8: []const u8) error{ InvalidUtf8, OutOfMemory }!void {
1198 return utf8ToUtf16LeArrayListImpl(array_list, utf8, .cannot_encode_surrogate_half);
1199}
1200
1201pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![]u16 {
1202 // optimistically guess that it will not require surrogate pairs
1203 var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len);
1204 errdefer result.deinit();
1205
1206 try utf8ToUtf16LeArrayListImpl(&result, utf8, .cannot_encode_surrogate_half);
1207
1208 return result.toOwnedSlice();
1209}
1210
1211/// Deprecated; renamed to utf8ToUtf16LeAllocZ
1212pub const utf8ToUtf16LeWithNull = utf8ToUtf16LeAllocZ;
1213
1214pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![:0]u16 {
1215 // optimistically guess that it will not require surrogate pairs
1216 var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len + 1);
1217 errdefer result.deinit();
1218
1219 try utf8ToUtf16LeArrayListImpl(&result, utf8, .cannot_encode_surrogate_half);
9971220
998 return result.toOwnedSliceSentinel(0);1221 return result.toOwnedSliceSentinel(0);
999}1222}
10001223
1001/// 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.
1002/// Assumes there is enough space for the output.1225/// Assumes there is enough space for the output.
1003pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize {1226pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) error{InvalidUtf8}!usize {
1227 return utf8ToUtf16LeImpl(utf16le, utf8, .cannot_encode_surrogate_half);
1228}
1229
1230pub fn utf8ToUtf16LeImpl(utf16le: []u16, utf8: []const u8, comptime surrogates: Surrogates) !usize {
1004 var dest_i: usize = 0;1231 var dest_i: usize = 0;
10051232
1006 var remaining = utf8;1233 var remaining = utf8;
...@@ -1027,9 +1254,15 @@ pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize {...@@ -1027,9 +1254,15 @@ pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize {
10271254
1028 var src_i: usize = 0;1255 var src_i: usize = 0;
1029 while (src_i < remaining.len) {1256 while (src_i < remaining.len) {
1030 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 };
1031 const next_src_i = src_i + n;1261 const next_src_i = src_i + n;
1032 const codepoint = utf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8;1262 const codepoint = switch (surrogates) {
1263 .cannot_encode_surrogate_half => utf8Decode(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,
1265 };
1033 if (codepoint < 0x10000) {1266 if (codepoint < 0x10000) {
1034 const short = @as(u16, @intCast(codepoint));1267 const short = @as(u16, @intCast(codepoint));
1035 utf16le[dest_i] = mem.nativeToLittle(u16, short);1268 utf16le[dest_i] = mem.nativeToLittle(u16, short);
...@@ -1064,21 +1297,59 @@ test "utf8ToUtf16Le" {...@@ -1064,21 +1297,59 @@ test "utf8ToUtf16Le" {
1064 }1297 }
1065}1298}
10661299
1067test "utf8ToUtf16LeWithNull" {1300test utf8ToUtf16LeArrayList {
1301 {
1302 var list = std.ArrayList(u16).init(testing.allocator);
1303 defer list.deinit();
1304 try utf8ToUtf16LeArrayList(&list, "𐐷");
1305 try testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc", mem.sliceAsBytes(list.items));
1306 }
1307 {
1308 var list = std.ArrayList(u16).init(testing.allocator);
1309 defer list.deinit();
1310 try utf8ToUtf16LeArrayList(&list, "\u{10FFFF}");
1311 try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", mem.sliceAsBytes(list.items));
1312 }
1313 {
1314 var list = std.ArrayList(u16).init(testing.allocator);
1315 defer list.deinit();
1316 const result = utf8ToUtf16LeArrayList(&list, "\xf4\x90\x80\x80");
1317 try testing.expectError(error.InvalidUtf8, result);
1318 }
1319}
1320
1321test utf8ToUtf16LeAlloc {
1322 {
1323 const utf16 = try utf8ToUtf16LeAlloc(testing.allocator, "𐐷");
1324 defer testing.allocator.free(utf16);
1325 try testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc", mem.sliceAsBytes(utf16[0..]));
1326 }
1327 {
1328 const utf16 = try utf8ToUtf16LeAlloc(testing.allocator, "\u{10FFFF}");
1329 defer testing.allocator.free(utf16);
1330 try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", mem.sliceAsBytes(utf16[0..]));
1331 }
1332 {
1333 const result = utf8ToUtf16LeAlloc(testing.allocator, "\xf4\x90\x80\x80");
1334 try testing.expectError(error.InvalidUtf8, result);
1335 }
1336}
1337
1338test utf8ToUtf16LeAllocZ {
1068 {1339 {
1069 const utf16 = try utf8ToUtf16LeWithNull(testing.allocator, "𐐷");1340 const utf16 = try utf8ToUtf16LeAllocZ(testing.allocator, "𐐷");
1070 defer testing.allocator.free(utf16);1341 defer testing.allocator.free(utf16);
1071 try testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc", mem.sliceAsBytes(utf16[0..]));1342 try testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc", mem.sliceAsBytes(utf16[0..]));
1072 try testing.expect(utf16[2] == 0);1343 try testing.expect(utf16[2] == 0);
1073 }1344 }
1074 {1345 {
1075 const utf16 = try utf8ToUtf16LeWithNull(testing.allocator, "\u{10FFFF}");1346 const utf16 = try utf8ToUtf16LeAllocZ(testing.allocator, "\u{10FFFF}");
1076 defer testing.allocator.free(utf16);1347 defer testing.allocator.free(utf16);
1077 try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", mem.sliceAsBytes(utf16[0..]));1348 try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", mem.sliceAsBytes(utf16[0..]));
1078 try testing.expect(utf16[2] == 0);1349 try testing.expect(utf16[2] == 0);
1079 }1350 }
1080 {1351 {
1081 const result = utf8ToUtf16LeWithNull(testing.allocator, "\xf4\x90\x80\x80");1352 const result = utf8ToUtf16LeAllocZ(testing.allocator, "\xf4\x90\x80\x80");
1082 try testing.expectError(error.InvalidUtf8, result);1353 try testing.expectError(error.InvalidUtf8, result);
1083 }1354 }
1084}1355}
...@@ -1127,8 +1398,9 @@ test "calculate utf16 string length of given utf8 string in u16" {...@@ -1127,8 +1398,9 @@ test "calculate utf16 string length of given utf8 string in u16" {
1127 try comptime testCalcUtf16LeLen();1398 try comptime testCalcUtf16LeLen();
1128}1399}
11291400
1130/// Print the given `utf16le` string1401/// Print the given `utf16le` string, encoded as UTF-8 bytes.
1131fn formatUtf16le(1402/// Unpaired surrogates are replaced by the replacement character (U+FFFD).
1403fn formatUtf16Le(
1132 utf16le: []const u16,1404 utf16le: []const u16,
1133 comptime fmt: []const u8,1405 comptime fmt: []const u8,
1134 options: std.fmt.FormatOptions,1406 options: std.fmt.FormatOptions,
...@@ -1136,13 +1408,14 @@ fn formatUtf16le(...@@ -1136,13 +1408,14 @@ fn formatUtf16le(
1136) !void {1408) !void {
1137 _ = fmt;1409 _ = fmt;
1138 _ = options;1410 _ = options;
1139 var buf: [300]u8 = undefined; // just a random size I chose1411 var buf: [300]u8 = undefined; // just an arbitrary size
1140 var it = Utf16LeIterator.init(utf16le);1412 var it = Utf16LeIterator.init(utf16le);
1141 var u8len: usize = 0;1413 var u8len: usize = 0;
1142 while (it.nextCodepoint() catch replacement_character) |codepoint| {1414 while (it.nextCodepoint() catch replacement_character) |codepoint| {
1143 u8len += utf8Encode(codepoint, buf[u8len..]) catch1415 u8len += utf8Encode(codepoint, buf[u8len..]) catch
1144 utf8Encode(replacement_character, buf[u8len..]) catch unreachable;1416 utf8Encode(replacement_character, buf[u8len..]) catch unreachable;
1145 if (u8len + 3 >= buf.len) {1417 // make sure there's always enough room for another maximum length UTF-8 codepoint
1418 if (u8len + 4 > buf.len) {
1146 try writer.writeAll(buf[0..u8len]);1419 try writer.writeAll(buf[0..u8len]);
1147 u8len = 0;1420 u8len = 0;
1148 }1421 }
...@@ -1150,22 +1423,27 @@ fn formatUtf16le(...@@ -1150,22 +1423,27 @@ fn formatUtf16le(
1150 try writer.writeAll(buf[0..u8len]);1423 try writer.writeAll(buf[0..u8len]);
1151}1424}
11521425
1153/// Return a Formatter for a Utf16le string1426/// Deprecated; renamed to fmtUtf16Le
1154pub fn fmtUtf16le(utf16le: []const u16) std.fmt.Formatter(formatUtf16le) {1427pub const fmtUtf16le = fmtUtf16Le;
1428
1429/// Return a Formatter for a (potentially ill-formed) UTF-16 LE string,
1430/// which will be converted to UTF-8 during formatting.
1431/// Unpaired surrogates are replaced by the replacement character (U+FFFD).
1432pub fn fmtUtf16Le(utf16le: []const u16) std.fmt.Formatter(formatUtf16Le) {
1155 return .{ .data = utf16le };1433 return .{ .data = utf16le };
1156}1434}
11571435
1158test "fmtUtf16le" {1436test "fmtUtf16Le" {
1159 const expectFmt = std.testing.expectFmt;1437 const expectFmt = testing.expectFmt;
1160 try expectFmt("", "{}", .{fmtUtf16le(utf8ToUtf16LeStringLiteral(""))});1438 try expectFmt("", "{}", .{fmtUtf16Le(utf8ToUtf16LeStringLiteral(""))});
1161 try expectFmt("foo", "{}", .{fmtUtf16le(utf8ToUtf16LeStringLiteral("foo"))});1439 try expectFmt("foo", "{}", .{fmtUtf16Le(utf8ToUtf16LeStringLiteral("foo"))});
1162 try expectFmt("𐐷", "{}", .{fmtUtf16le(utf8ToUtf16LeStringLiteral("𐐷"))});1440 try expectFmt("𐐷", "{}", .{fmtUtf16Le(utf8ToUtf16LeStringLiteral("𐐷"))});
1163 try expectFmt("퟿", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\xff\xd7", native_endian)})});1441 try expectFmt("퟿", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\xff\xd7", native_endian)})});
1164 try expectFmt("�", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\x00\xd8", native_endian)})});1442 try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\x00\xd8", native_endian)})});
1165 try expectFmt("�", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\xff\xdb", native_endian)})});1443 try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\xff\xdb", native_endian)})});
1166 try expectFmt("�", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\x00\xdc", native_endian)})});1444 try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\x00\xdc", native_endian)})});
1167 try expectFmt("�", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\xff\xdf", native_endian)})});1445 try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\xff\xdf", native_endian)})});
1168 try expectFmt("", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\x00\xe0", native_endian)})});1446 try expectFmt("", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\x00\xe0", native_endian)})});
1169}1447}
11701448
1171test "utf8ToUtf16LeStringLiteral" {1449test "utf8ToUtf16LeStringLiteral" {
...@@ -1248,3 +1526,535 @@ test "utf8 valid codepoint" {...@@ -1248,3 +1526,535 @@ test "utf8 valid codepoint" {
1248 try testUtf8ValidCodepoint();1526 try testUtf8ValidCodepoint();
1249 try comptime testUtf8ValidCodepoint();1527 try comptime testUtf8ValidCodepoint();
1250}1528}
1529
1530/// Returns true if the codepoint is a surrogate (U+DC00 to U+DFFF)
1531pub fn isSurrogateCodepoint(c: u21) bool {
1532 return switch (c) {
1533 0xD800...0xDFFF => true,
1534 else => false,
1535 };
1536}
1537
1538/// Encodes the given codepoint into a WTF-8 byte sequence.
1539/// c: the codepoint.
1540/// out: the out buffer to write to. Must have a len >= utf8CodepointSequenceLength(c).
1541/// Errors: if c cannot be encoded in WTF-8.
1542/// Returns: the number of bytes written to out.
1543pub fn wtf8Encode(c: u21, out: []u8) error{CodepointTooLarge}!u3 {
1544 return utf8EncodeImpl(c, out, .can_encode_surrogate_half);
1545}
1546
1547const Wtf8DecodeError = Utf8Decode2Error || Utf8Decode3AllowSurrogateHalfError || Utf8Decode4Error;
1548
1549pub fn wtf8Decode(bytes: []const u8) Wtf8DecodeError!u21 {
1550 return switch (bytes.len) {
1551 1 => @as(u21, bytes[0]),
1552 2 => utf8Decode2(bytes),
1553 3 => utf8Decode3AllowSurrogateHalf(bytes),
1554 4 => utf8Decode4(bytes),
1555 else => unreachable,
1556 };
1557}
1558
1559/// Returns true if the input consists entirely of WTF-8 codepoints
1560/// (all the same restrictions as UTF-8, but allows surrogate codepoints
1561/// U+D800 to U+DFFF).
1562/// Does not check for well-formed WTF-8, meaning that this function
1563/// does not check that all surrogate halves are unpaired.
1564pub fn wtf8ValidateSlice(input: []const u8) bool {
1565 return utf8ValidateSliceImpl(input, .can_encode_surrogate_half);
1566}
1567
1568test "validate WTF-8 slice" {
1569 try testValidateWtf8Slice();
1570 try comptime testValidateWtf8Slice();
1571
1572 // We skip a variable (based on recommended vector size) chunks of
1573 // ASCII characters. Let's make sure we're chunking correctly.
1574 const str = [_]u8{'a'} ** 550 ++ "\xc0";
1575 for (0..str.len - 3) |i| {
1576 try testing.expect(!wtf8ValidateSlice(str[i..]));
1577 }
1578}
1579fn testValidateWtf8Slice() !void {
1580 // These are valid/invalid under both UTF-8 and WTF-8 rules.
1581 try testing.expect(wtf8ValidateSlice("abc"));
1582 try testing.expect(wtf8ValidateSlice("abc\xdf\xbf"));
1583 try testing.expect(wtf8ValidateSlice(""));
1584 try testing.expect(wtf8ValidateSlice("a"));
1585 try testing.expect(wtf8ValidateSlice("abc"));
1586 try testing.expect(wtf8ValidateSlice("Ж"));
1587 try testing.expect(wtf8ValidateSlice("ЖЖ"));
1588 try testing.expect(wtf8ValidateSlice("брэд-ЛГТМ"));
1589 try testing.expect(wtf8ValidateSlice("☺☻☹"));
1590 try testing.expect(wtf8ValidateSlice("a\u{fffdb}"));
1591 try testing.expect(wtf8ValidateSlice("\xf4\x8f\xbf\xbf"));
1592 try testing.expect(wtf8ValidateSlice("abc\xdf\xbf"));
1593
1594 try testing.expect(!wtf8ValidateSlice("abc\xc0"));
1595 try testing.expect(!wtf8ValidateSlice("abc\xc0abc"));
1596 try testing.expect(!wtf8ValidateSlice("aa\xe2"));
1597 try testing.expect(!wtf8ValidateSlice("\x42\xfa"));
1598 try testing.expect(!wtf8ValidateSlice("\x42\xfa\x43"));
1599 try testing.expect(!wtf8ValidateSlice("abc\xc0"));
1600 try testing.expect(!wtf8ValidateSlice("abc\xc0abc"));
1601 try testing.expect(!wtf8ValidateSlice("\xf4\x90\x80\x80"));
1602 try testing.expect(!wtf8ValidateSlice("\xf7\xbf\xbf\xbf"));
1603 try testing.expect(!wtf8ValidateSlice("\xfb\xbf\xbf\xbf\xbf"));
1604 try testing.expect(!wtf8ValidateSlice("\xc0\x80"));
1605
1606 // But surrogate codepoints are only valid in WTF-8.
1607 try testing.expect(wtf8ValidateSlice("\xed\xa0\x80"));
1608 try testing.expect(wtf8ValidateSlice("\xed\xbf\xbf"));
1609}
1610
1611/// Wtf8View iterates the code points of a WTF-8 encoded string,
1612/// including surrogate halves.
1613///
1614/// ```
1615/// var wtf8 = (try std.unicode.Wtf8View.init("hi there")).iterator();
1616/// while (wtf8.nextCodepointSlice()) |codepoint| {
1617/// // note: codepoint could be a surrogate half which is invalid
1618/// // UTF-8, avoid printing or otherwise sending/emitting this directly
1619/// }
1620/// ```
1621pub const Wtf8View = struct {
1622 bytes: []const u8,
1623
1624 pub fn init(s: []const u8) error{InvalidWtf8}!Wtf8View {
1625 if (!wtf8ValidateSlice(s)) {
1626 return error.InvalidWtf8;
1627 }
1628
1629 return initUnchecked(s);
1630 }
1631
1632 pub fn initUnchecked(s: []const u8) Wtf8View {
1633 return Wtf8View{ .bytes = s };
1634 }
1635
1636 pub inline fn initComptime(comptime s: []const u8) Wtf8View {
1637 return comptime if (init(s)) |r| r else |err| switch (err) {
1638 error.InvalidWtf8 => {
1639 @compileError("invalid wtf8");
1640 },
1641 };
1642 }
1643
1644 pub fn iterator(s: Wtf8View) Wtf8Iterator {
1645 return Wtf8Iterator{
1646 .bytes = s.bytes,
1647 .i = 0,
1648 };
1649 }
1650};
1651
1652/// Asserts that `bytes` is valid WTF-8
1653pub const Wtf8Iterator = struct {
1654 bytes: []const u8,
1655 i: usize,
1656
1657 pub fn nextCodepointSlice(it: *Wtf8Iterator) ?[]const u8 {
1658 if (it.i >= it.bytes.len) {
1659 return null;
1660 }
1661
1662 const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable;
1663 it.i += cp_len;
1664 return it.bytes[it.i - cp_len .. it.i];
1665 }
1666
1667 pub fn nextCodepoint(it: *Wtf8Iterator) ?u21 {
1668 const slice = it.nextCodepointSlice() orelse return null;
1669 return wtf8Decode(slice) catch unreachable;
1670 }
1671
1672 /// Look ahead at the next n codepoints without advancing the iterator.
1673 /// If fewer than n codepoints are available, then return the remainder of the string.
1674 pub fn peek(it: *Wtf8Iterator, n: usize) []const u8 {
1675 const original_i = it.i;
1676 defer it.i = original_i;
1677
1678 var end_ix = original_i;
1679 var found: usize = 0;
1680 while (found < n) : (found += 1) {
1681 const next_codepoint = it.nextCodepointSlice() orelse return it.bytes[original_i..];
1682 end_ix += next_codepoint.len;
1683 }
1684
1685 return it.bytes[original_i..end_ix];
1686 }
1687};
1688
1689pub fn wtf16LeToWtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) mem.Allocator.Error!void {
1690 return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .can_encode_surrogate_half);
1691}
1692
1693/// Caller must free returned memory.
1694pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![]u8 {
1695 // optimistically guess that it will all be ascii.
1696 var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len);
1697 errdefer result.deinit();
1698
1699 try wtf16LeToWtf8ArrayList(&result, wtf16le);
1700
1701 return result.toOwnedSlice();
1702}
1703
1704/// Caller must free returned memory.
1705pub fn wtf16LeToWtf8AllocZ(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![:0]u8 {
1706 // optimistically guess that it will all be ascii (and allocate space for the null terminator)
1707 var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len + 1);
1708 errdefer result.deinit();
1709
1710 try wtf16LeToWtf8ArrayList(&result, wtf16le);
1711
1712 return result.toOwnedSliceSentinel(0);
1713}
1714
1715pub fn wtf16LeToWtf8(wtf8: []u8, wtf16le: []const u16) usize {
1716 return utf16LeToUtf8Impl(wtf8, wtf16le, .can_encode_surrogate_half) catch |err| switch (err) {};
1717}
1718
1719pub fn wtf8ToWtf16LeArrayList(array_list: *std.ArrayList(u16), wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }!void {
1720 return utf8ToUtf16LeArrayListImpl(array_list, wtf8, .can_encode_surrogate_half);
1721}
1722
1723pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u16 {
1724 // optimistically guess that it will not require surrogate pairs
1725 var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len);
1726 errdefer result.deinit();
1727
1728 try utf8ToUtf16LeArrayListImpl(&result, wtf8, .can_encode_surrogate_half);
1729
1730 return result.toOwnedSlice();
1731}
1732
1733pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u16 {
1734 // optimistically guess that it will not require surrogate pairs
1735 var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len + 1);
1736 errdefer result.deinit();
1737
1738 try utf8ToUtf16LeArrayListImpl(&result, wtf8, .can_encode_surrogate_half);
1739
1740 return result.toOwnedSliceSentinel(0);
1741}
1742
1743/// Returns index of next character. If exact fit, returned index equals output slice length.
1744/// Assumes there is enough space for the output.
1745pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{InvalidWtf8}!usize {
1746 return utf8ToUtf16LeImpl(wtf16le, wtf8, .can_encode_surrogate_half);
1747}
1748
1749/// Surrogate codepoints (U+D800 to U+DFFF) are replaced by the Unicode replacement
1750/// character (U+FFFD).
1751/// All surrogate codepoints and the replacement character are encoded as three
1752/// bytes, meaning the input and output slices will always be the same length.
1753/// In-place conversion is supported when `utf8` and `wtf8` refer to the same slice.
1754/// Note: If `wtf8` is entirely composed of well-formed UTF-8, then no conversion is necessary.
1755/// `utf8ValidateSlice` can be used to check if lossy conversion is worthwhile.
1756/// If `wtf8` is not valid WTF-8, then `error.InvalidWtf8` is returned.
1757pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) error{InvalidWtf8}!void {
1758 assert(utf8.len >= wtf8.len);
1759
1760 const in_place = utf8.ptr == wtf8.ptr;
1761 const replacement_char_bytes = comptime blk: {
1762 var buf: [3]u8 = undefined;
1763 assert((utf8Encode(replacement_character, &buf) catch unreachable) == 3);
1764 break :blk buf;
1765 };
1766
1767 var dest_i: usize = 0;
1768 const view = try Wtf8View.init(wtf8);
1769 var it = view.iterator();
1770 while (it.nextCodepointSlice()) |codepoint_slice| {
1771 // All surrogate codepoints are encoded as 3 bytes
1772 if (codepoint_slice.len == 3) {
1773 const codepoint = wtf8Decode(codepoint_slice) catch unreachable;
1774 if (isSurrogateCodepoint(codepoint)) {
1775 @memcpy(utf8[dest_i..][0..replacement_char_bytes.len], &replacement_char_bytes);
1776 dest_i += replacement_char_bytes.len;
1777 continue;
1778 }
1779 }
1780 if (!in_place) {
1781 @memcpy(utf8[dest_i..][0..codepoint_slice.len], codepoint_slice);
1782 }
1783 dest_i += codepoint_slice.len;
1784 }
1785}
1786
1787pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u8 {
1788 const utf8 = try allocator.alloc(u8, wtf8.len);
1789 errdefer allocator.free(utf8);
1790
1791 try wtf8ToUtf8Lossy(utf8, wtf8);
1792
1793 return utf8;
1794}
1795
1796pub fn wtf8ToUtf8LossyAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u8 {
1797 const utf8 = try allocator.allocSentinel(u8, wtf8.len, 0);
1798 errdefer allocator.free(utf8);
1799
1800 try wtf8ToUtf8Lossy(utf8, wtf8);
1801
1802 return utf8;
1803}
1804
1805test wtf8ToUtf8Lossy {
1806 var buf: [32]u8 = undefined;
1807
1808 const invalid_utf8 = "\xff";
1809 try testing.expectError(error.InvalidWtf8, wtf8ToUtf8Lossy(&buf, invalid_utf8));
1810
1811 const ascii = "abcd";
1812 try wtf8ToUtf8Lossy(&buf, ascii);
1813 try testing.expectEqualStrings("abcd", buf[0..ascii.len]);
1814
1815 const high_surrogate_half = "ab\xed\xa0\xbdcd";
1816 try wtf8ToUtf8Lossy(&buf, high_surrogate_half);
1817 try testing.expectEqualStrings("ab\u{FFFD}cd", buf[0..high_surrogate_half.len]);
1818
1819 const low_surrogate_half = "ab\xed\xb2\xa9cd";
1820 try wtf8ToUtf8Lossy(&buf, low_surrogate_half);
1821 try testing.expectEqualStrings("ab\u{FFFD}cd", buf[0..low_surrogate_half.len]);
1822
1823 // If the WTF-8 is not well-formed, each surrogate half is converted into a separate
1824 // replacement character instead of being interpreted as a surrogate pair.
1825 const encoded_surrogate_pair = "ab\xed\xa0\xbd\xed\xb2\xa9cd";
1826 try wtf8ToUtf8Lossy(&buf, encoded_surrogate_pair);
1827 try testing.expectEqualStrings("ab\u{FFFD}\u{FFFD}cd", buf[0..encoded_surrogate_pair.len]);
1828
1829 // in place
1830 @memcpy(buf[0..low_surrogate_half.len], low_surrogate_half);
1831 const slice = buf[0..low_surrogate_half.len];
1832 try wtf8ToUtf8Lossy(slice, slice);
1833 try testing.expectEqualStrings("ab\u{FFFD}cd", slice);
1834}
1835
1836test wtf8ToUtf8LossyAlloc {
1837 const invalid_utf8 = "\xff";
1838 try testing.expectError(error.InvalidWtf8, wtf8ToUtf8LossyAlloc(testing.allocator, invalid_utf8));
1839
1840 {
1841 const ascii = "abcd";
1842 const utf8 = try wtf8ToUtf8LossyAlloc(testing.allocator, ascii);
1843 defer testing.allocator.free(utf8);
1844 try testing.expectEqualStrings("abcd", utf8);
1845 }
1846
1847 {
1848 const surrogate_half = "ab\xed\xa0\xbdcd";
1849 const utf8 = try wtf8ToUtf8LossyAlloc(testing.allocator, surrogate_half);
1850 defer testing.allocator.free(utf8);
1851 try testing.expectEqualStrings("ab\u{FFFD}cd", utf8);
1852 }
1853
1854 {
1855 // If the WTF-8 is not well-formed, each surrogate half is converted into a separate
1856 // replacement character instead of being interpreted as a surrogate pair.
1857 const encoded_surrogate_pair = "ab\xed\xa0\xbd\xed\xb2\xa9cd";
1858 const utf8 = try wtf8ToUtf8LossyAlloc(testing.allocator, encoded_surrogate_pair);
1859 defer testing.allocator.free(utf8);
1860 try testing.expectEqualStrings("ab\u{FFFD}\u{FFFD}cd", utf8);
1861 }
1862}
1863
1864test wtf8ToUtf8LossyAllocZ {
1865 const invalid_utf8 = "\xff";
1866 try testing.expectError(error.InvalidWtf8, wtf8ToUtf8LossyAllocZ(testing.allocator, invalid_utf8));
1867
1868 {
1869 const ascii = "abcd";
1870 const utf8 = try wtf8ToUtf8LossyAllocZ(testing.allocator, ascii);
1871 defer testing.allocator.free(utf8);
1872 try testing.expectEqualStrings("abcd", utf8);
1873 }
1874
1875 {
1876 const surrogate_half = "ab\xed\xa0\xbdcd";
1877 const utf8 = try wtf8ToUtf8LossyAllocZ(testing.allocator, surrogate_half);
1878 defer testing.allocator.free(utf8);
1879 try testing.expectEqualStrings("ab\u{FFFD}cd", utf8);
1880 }
1881
1882 {
1883 // If the WTF-8 is not well-formed, each surrogate half is converted into a separate
1884 // replacement character instead of being interpreted as a surrogate pair.
1885 const encoded_surrogate_pair = "ab\xed\xa0\xbd\xed\xb2\xa9cd";
1886 const utf8 = try wtf8ToUtf8LossyAllocZ(testing.allocator, encoded_surrogate_pair);
1887 defer testing.allocator.free(utf8);
1888 try testing.expectEqualStrings("ab\u{FFFD}\u{FFFD}cd", utf8);
1889 }
1890}
1891
1892pub const Wtf16LeIterator = struct {
1893 bytes: []const u8,
1894 i: usize,
1895
1896 pub fn init(s: []const u16) Wtf16LeIterator {
1897 return Wtf16LeIterator{
1898 .bytes = std.mem.sliceAsBytes(s),
1899 .i = 0,
1900 };
1901 }
1902
1903 /// If the next codepoint is encoded by a surrogate pair, returns the
1904 /// codepoint that the surrogate pair represents.
1905 /// If the next codepoint is an unpaired surrogate, returns the codepoint
1906 /// of the unpaired surrogate.
1907 pub fn nextCodepoint(it: *Wtf16LeIterator) ?u21 {
1908 assert(it.i <= it.bytes.len);
1909 if (it.i == it.bytes.len) return null;
1910 var code_units: [2]u16 = undefined;
1911 code_units[0] = std.mem.readInt(u16, it.bytes[it.i..][0..2], .little);
1912 it.i += 2;
1913 surrogate_pair: {
1914 if (utf16IsHighSurrogate(code_units[0])) {
1915 if (it.i >= it.bytes.len) break :surrogate_pair;
1916 code_units[1] = std.mem.readInt(u16, it.bytes[it.i..][0..2], .little);
1917 const codepoint = utf16DecodeSurrogatePair(&code_units) catch break :surrogate_pair;
1918 it.i += 2;
1919 return codepoint;
1920 }
1921 }
1922 return code_units[0];
1923 }
1924};
1925
1926test "non-well-formed WTF-8 does not roundtrip" {
1927 // This encodes the surrogate pair U+D83D U+DCA9.
1928 // The well-formed version of this would be U+1F4A9 which is \xF0\x9F\x92\xA9.
1929 const non_well_formed_wtf8 = "\xed\xa0\xbd\xed\xb2\xa9";
1930
1931 var wtf16_buf: [2]u16 = undefined;
1932 const wtf16_len = try wtf8ToWtf16Le(&wtf16_buf, non_well_formed_wtf8);
1933 const wtf16 = wtf16_buf[0..wtf16_len];
1934
1935 try testing.expectEqualSlices(u16, &[_]u16{
1936 mem.nativeToLittle(u16, 0xD83D), // high surrogate
1937 mem.nativeToLittle(u16, 0xDCA9), // low surrogate
1938 }, wtf16);
1939
1940 var wtf8_buf: [4]u8 = undefined;
1941 const wtf8_len = wtf16LeToWtf8(&wtf8_buf, wtf16);
1942 const wtf8 = wtf8_buf[0..wtf8_len];
1943
1944 // Converting to WTF-16 and back results in well-formed WTF-8,
1945 // but it does not match the input WTF-8
1946 try testing.expectEqualSlices(u8, "\xf0\x9f\x92\xa9", wtf8);
1947}
1948
1949fn testRoundtripWtf8(wtf8: []const u8) !void {
1950 // Buffer
1951 {
1952 var wtf16_buf: [32]u16 = undefined;
1953 const wtf16_len = try wtf8ToWtf16Le(&wtf16_buf, wtf8);
1954 const wtf16 = wtf16_buf[0..wtf16_len];
1955
1956 var roundtripped_buf: [32]u8 = undefined;
1957 const roundtripped_len = wtf16LeToWtf8(&roundtripped_buf, wtf16);
1958 const roundtripped = roundtripped_buf[0..roundtripped_len];
1959
1960 try testing.expectEqualSlices(u8, wtf8, roundtripped);
1961 }
1962 // Alloc
1963 {
1964 const wtf16 = try wtf8ToWtf16LeAlloc(testing.allocator, wtf8);
1965 defer testing.allocator.free(wtf16);
1966
1967 const roundtripped = try wtf16LeToWtf8Alloc(testing.allocator, wtf16);
1968 defer testing.allocator.free(roundtripped);
1969
1970 try testing.expectEqualSlices(u8, wtf8, roundtripped);
1971 }
1972 // AllocZ
1973 {
1974 const wtf16 = try wtf8ToWtf16LeAllocZ(testing.allocator, wtf8);
1975 defer testing.allocator.free(wtf16);
1976
1977 const roundtripped = try wtf16LeToWtf8AllocZ(testing.allocator, wtf16);
1978 defer testing.allocator.free(roundtripped);
1979
1980 try testing.expectEqualSlices(u8, wtf8, roundtripped);
1981 }
1982}
1983
1984test "well-formed WTF-8 roundtrips" {
1985 try testRoundtripWtf8("\xed\x9f\xbf"); // not a surrogate half
1986 try testRoundtripWtf8("\xed\xa0\xbd"); // high surrogate
1987 try testRoundtripWtf8("\xed\xb2\xa9"); // low surrogate
1988 try testRoundtripWtf8("\xed\xa0\xbd \xed\xb2\xa9"); // <high surrogate><space><low surrogate>
1989 try testRoundtripWtf8("\xed\xa0\x80\xed\xaf\xbf"); // <high surrogate><high surrogate>
1990 try testRoundtripWtf8("\xed\xa0\x80\xee\x80\x80"); // <high surrogate><not surrogate>
1991 try testRoundtripWtf8("\xed\x9f\xbf\xed\xb0\x80"); // <not surrogate><low surrogate>
1992 try testRoundtripWtf8("a\xed\xb0\x80"); // <not surrogate><low surrogate>
1993 try testRoundtripWtf8("\xf0\x9f\x92\xa9"); // U+1F4A9, encoded as a surrogate pair in WTF-16
1994}
1995
1996fn testRoundtripWtf16(wtf16le: []const u16) !void {
1997 // Buffer
1998 {
1999 var wtf8_buf: [32]u8 = undefined;
2000 const wtf8_len = wtf16LeToWtf8(&wtf8_buf, wtf16le);
2001 const wtf8 = wtf8_buf[0..wtf8_len];
2002
2003 var roundtripped_buf: [32]u16 = undefined;
2004 const roundtripped_len = try wtf8ToWtf16Le(&roundtripped_buf, wtf8);
2005 const roundtripped = roundtripped_buf[0..roundtripped_len];
2006
2007 try testing.expectEqualSlices(u16, wtf16le, roundtripped);
2008 }
2009 // Alloc
2010 {
2011 const wtf8 = try wtf16LeToWtf8Alloc(testing.allocator, wtf16le);
2012 defer testing.allocator.free(wtf8);
2013
2014 const roundtripped = try wtf8ToWtf16LeAlloc(testing.allocator, wtf8);
2015 defer testing.allocator.free(roundtripped);
2016
2017 try testing.expectEqualSlices(u16, wtf16le, roundtripped);
2018 }
2019 // AllocZ
2020 {
2021 const wtf8 = try wtf16LeToWtf8AllocZ(testing.allocator, wtf16le);
2022 defer testing.allocator.free(wtf8);
2023
2024 const roundtripped = try wtf8ToWtf16LeAllocZ(testing.allocator, wtf8);
2025 defer testing.allocator.free(roundtripped);
2026
2027 try testing.expectEqualSlices(u16, wtf16le, roundtripped);
2028 }
2029}
2030
2031test "well-formed WTF-16 roundtrips" {
2032 try testRoundtripWtf16(&[_]u16{
2033 std.mem.nativeToLittle(u16, 0xD83D), // high surrogate
2034 std.mem.nativeToLittle(u16, 0xDCA9), // low surrogate
2035 });
2036 try testRoundtripWtf16(&[_]u16{
2037 std.mem.nativeToLittle(u16, 0xD83D), // high surrogate
2038 std.mem.nativeToLittle(u16, ' '), // not surrogate
2039 std.mem.nativeToLittle(u16, 0xDCA9), // low surrogate
2040 });
2041 try testRoundtripWtf16(&[_]u16{
2042 std.mem.nativeToLittle(u16, 0xD800), // high surrogate
2043 std.mem.nativeToLittle(u16, 0xDBFF), // high surrogate
2044 });
2045 try testRoundtripWtf16(&[_]u16{
2046 std.mem.nativeToLittle(u16, 0xD800), // high surrogate
2047 std.mem.nativeToLittle(u16, 0xE000), // not surrogate
2048 });
2049 try testRoundtripWtf16(&[_]u16{
2050 std.mem.nativeToLittle(u16, 0xD7FF), // not surrogate
2051 std.mem.nativeToLittle(u16, 0xDC00), // low surrogate
2052 });
2053 try testRoundtripWtf16(&[_]u16{
2054 std.mem.nativeToLittle(u16, 0x61), // not surrogate
2055 std.mem.nativeToLittle(u16, 0xDC00), // low surrogate
2056 });
2057 try testRoundtripWtf16(&[_]u16{
2058 std.mem.nativeToLittle(u16, 0xDC00), // low surrogate
2059 });
2060}
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 }
lib/std/zig/system/windows.zig+1-1
...@@ -160,7 +160,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {...@@ -160,7 +160,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
160 => {160 => {
161 var buf = @field(args, field.name).value_buf;161 var buf = @field(args, field.name).value_buf;
162 const entry = @as(*align(1) const std.os.windows.UNICODE_STRING, @ptrCast(table[i + 1].EntryContext));162 const entry = @as(*align(1) const std.os.windows.UNICODE_STRING, @ptrCast(table[i + 1].EntryContext));
163 const len = try std.unicode.utf16leToUtf8(buf, entry.Buffer[0 .. entry.Length / 2]);163 const len = try std.unicode.utf16LeToUtf8(buf, entry.Buffer[0 .. entry.Length / 2]);
164 buf[len] = 0;164 buf[len] = 0;
165 },165 },
166166
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/main.zig+1-1
...@@ -5756,7 +5756,7 @@ fn readSourceFileToEndAlloc(...@@ -5756,7 +5756,7 @@ fn readSourceFileToEndAlloc(
5756 // If the file starts with a UTF-16 little endian BOM, translate it to UTF-85756 // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8
5757 if (mem.startsWith(u8, source_code, "\xff\xfe")) {5757 if (mem.startsWith(u8, source_code, "\xff\xfe")) {
5758 const source_code_utf16_le = mem.bytesAsSlice(u16, source_code);5758 const source_code_utf16_le = mem.bytesAsSlice(u16, source_code);
5759 const source_code_utf8 = std.unicode.utf16leToUtf8AllocZ(allocator, source_code_utf16_le) catch |err| switch (err) {5759 const source_code_utf8 = std.unicode.utf16LeToUtf8AllocZ(allocator, source_code_utf16_le) catch |err| switch (err) {
5760 error.DanglingSurrogateHalf => error.UnsupportedEncoding,5760 error.DanglingSurrogateHalf => error.UnsupportedEncoding,
5761 error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,5761 error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,
5762 error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,5762 error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,
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) {
test/standalone/windows_spawn/main.zig+1-1
...@@ -17,7 +17,7 @@ pub fn main() anyerror!void {...@@ -17,7 +17,7 @@ pub fn main() anyerror!void {
1717
18 const tmp_absolute_path = try tmp.dir.realpathAlloc(allocator, ".");18 const tmp_absolute_path = try tmp.dir.realpathAlloc(allocator, ".");
19 defer allocator.free(tmp_absolute_path);19 defer allocator.free(tmp_absolute_path);
20 const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeWithNull(allocator, tmp_absolute_path);20 const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeAllocZ(allocator, tmp_absolute_path);
21 defer allocator.free(tmp_absolute_path_w);21 defer allocator.free(tmp_absolute_path_w);
22 const cwd_absolute_path = try std.fs.cwd().realpathAlloc(allocator, ".");22 const cwd_absolute_path = try std.fs.cwd().realpathAlloc(allocator, ".");
23 defer allocator.free(cwd_absolute_path);23 defer allocator.free(cwd_absolute_path);