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 {
6969 const val: ?[]const u8 = std.process.getEnvVarOwned(allocator, env_var_name) catch |err| switch (err) {
7070 error.OutOfMemory => |e| return e,
7171 error.EnvironmentVariableNotFound => null,
72 error.InvalidUtf8 => null,
72 error.InvalidWtf8 => null,
7373 };
7474 @field(env, field.name) = val;
7575 }
deps/aro/aro/Driver.zig+2-1
......@@ -523,7 +523,8 @@ pub fn errorDescription(e: anyerror) []const u8 {
523523 error.NotDir => "is not a directory",
524524 error.NotOpenForReading => "file is not open for reading",
525525 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",
527528 error.FileBusy => "file is busy",
528529 error.NameTooLong => "file name is too long",
529530 error.AccessDenied => "access denied",
lib/std/Build/Cache.zig+1-1
......@@ -162,7 +162,7 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
162162fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
163163 const relative = try std.fs.path.relative(allocator, prefix, path);
164164 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 {
166166 return error.NotASubPath;
167167 };
168168 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 {
9191 },
9292 .windows => {
9393 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);
9595 const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong;
9696
9797 // 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 {
157157}
158158
159159pub 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
167160 Unsupported,
168161 Unexpected,
169162} || 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.
171166pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 {
172167 buffer_ptr[max_name_len] = 0;
173168 var buffer: [:0]u8 = buffer_ptr;
......@@ -213,7 +208,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
213208 )) {
214209 .SUCCESS => {
215210 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]);
217212 return if (len > 0) buffer[0..len] else null;
218213 },
219214 .NOT_IMPLEMENTED => return error.Unsupported,
lib/std/child_process.zig+21-22
......@@ -129,10 +129,9 @@ pub const ChildProcess = struct {
129129 /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.
130130 NoDevice,
131131
132 /// Windows-only. One of:
133 /// * `cwd` was provided and it could not be re-encoded into UTF16LE, or
134 /// * The `PATH` or `PATHEXT` environment variable contained invalid UTF-8.
135 InvalidUtf8,
132 /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8.
133 /// https://simonsapin.github.io/wtf-8/
134 InvalidWtf8,
136135
137136 /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
138137 CurrentWorkingDirectoryUnlinked,
......@@ -767,7 +766,7 @@ pub const ChildProcess = struct {
767766 };
768767 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;
771770 defer if (cwd_w) |cwd| self.allocator.free(cwd);
772771 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
773772
......@@ -775,8 +774,8 @@ pub const ChildProcess = struct {
775774 defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
776775 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
777776
778 const app_name_utf8 = self.argv[0];
779 const app_name_is_absolute = fs.path.isAbsolute(app_name_utf8);
777 const app_name_wtf8 = self.argv[0];
778 const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8);
780779
781780 // the cwd set in ChildProcess is in effect when choosing the executable path
782781 // to match posix semantics
......@@ -785,11 +784,11 @@ pub const ChildProcess = struct {
785784 // If the app name is absolute, then we need to use its dirname as the cwd
786785 if (app_name_is_absolute) {
787786 cwd_path_w_needs_free = true;
788 const dir = fs.path.dirname(app_name_utf8).?;
789 break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, dir);
787 const dir = fs.path.dirname(app_name_wtf8).?;
788 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, dir);
790789 } else if (self.cwd) |cwd| {
791790 cwd_path_w_needs_free = true;
792 break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, cwd);
791 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd);
793792 } else {
794793 break :x &[_:0]u16{}; // empty for cwd
795794 }
......@@ -800,19 +799,19 @@ pub const ChildProcess = struct {
800799 // into the basename and dirname and use the dirname as an addition to the cwd
801800 // path. This is because NtQueryDirectoryFile cannot accept FileName params with
802801 // path separators.
803 const app_basename_utf8 = fs.path.basename(app_name_utf8);
802 const app_basename_wtf8 = fs.path.basename(app_name_wtf8);
804803 // If the app name is absolute, then the cwd will already have the app's dirname in it,
805804 // 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;
807806 const app_dirname_w: ?[:0]u16 = x: {
808 if (maybe_app_dirname_utf8) |app_dirname_utf8| {
809 break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, app_dirname_utf8);
807 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
808 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_dirname_wtf8);
810809 }
811810 break :x null;
812811 };
813812 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);
816815 defer self.allocator.free(app_name_w);
817816
818817 const cmd_line_w = argvToCommandLineWindows(self.allocator, self.argv) catch |err| switch (err) {
......@@ -1173,7 +1172,7 @@ const CreateProcessSupportedExtension = enum {
11731172 exe,
11741173};
11751174
1176/// Case-insensitive UTF-16 lookup
1175/// Case-insensitive WTF-16 lookup
11771176fn windowsCreateProcessSupportsExtension(ext: []const u16) ?CreateProcessSupportedExtension {
11781177 if (ext.len != 4) return null;
11791178 const State = enum {
......@@ -1237,7 +1236,7 @@ test "windowsCreateProcessSupportsExtension" {
12371236 try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null);
12381237}
12391238
1240pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidUtf8, InvalidArg0 };
1239pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
12411240
12421241/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and
12431242/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice.
......@@ -1320,7 +1319,7 @@ pub fn argvToCommandLineWindows(
13201319 }
13211320 }
13221321
1323 return try unicode.utf8ToUtf16LeWithNull(allocator, buf.items);
1322 return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
13241323}
13251324
13261325test "argvToCommandLineWindows" {
......@@ -1386,7 +1385,7 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c
13861385 const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv);
13871386 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);
13901389 defer std.testing.allocator.free(cmd_line);
13911390
13921391 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
......@@ -1424,7 +1423,7 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons
14241423 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
14251424 .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .Monotonic) },
14261425 ) 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;
14281427 tmp_bufw[len] = 0;
14291428 break :blk tmp_bufw[0..len :0];
14301429 };
......@@ -1521,10 +1520,10 @@ pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) !
15211520 var it = env_map.iterator();
15221521 var i: usize = 0;
15231522 while (it.next()) |pair| {
1524 i += try unicode.utf8ToUtf16Le(result[i..], pair.key_ptr.*);
1523 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
15251524 result[i] = '=';
15261525 i += 1;
1527 i += try unicode.utf8ToUtf16Le(result[i..], pair.value_ptr.*);
1526 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
15281527 result[i] = 0;
15291528 i += 1;
15301529 }
lib/std/fs.zig+139-38
......@@ -31,18 +31,21 @@ pub const realpathW = os.realpathW;
3131pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
3232pub 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 the
34/// This represents the maximum size of a `[]u8` file path that the
3535/// operating system will accept. Paths, including those returned from file
3636/// system operations, may be longer than this length, but such paths cannot
3737/// be successfully passed back in other file system operations. However,
3838/// 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.
4040/// 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.
4144pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
4245 .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.
44 // If it would require 4 UTF-8 bytes, then there would be a surrogate
45 // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
46 // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes.
47 // If it would require 4 WTF-8 bytes, then there would be a surrogate
48 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
4649 // +1 for the null byte at the end, which can be encoded in 1 byte.
4750 .windows => os.windows.PATH_MAX_WIDE * 3 + 1,
4851 // TODO work out what a reasonable value we should use here
......@@ -53,18 +56,21 @@ pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
5356 @compileError("PATH_MAX not implemented for " ++ @tagName(builtin.os.tag)),
5457};
5558
56/// This represents the maximum size of a UTF-8 encoded file name component that
59/// This represents the maximum size of a `[]u8` file name component that
5760/// 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, but
61/// operations are likely to fit into a `u8` array of this length, but
5962/// (depending on the platform) this assumption may not hold for every configuration.
6063/// 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.
6167pub const MAX_NAME_BYTES = switch (builtin.os.tag) {
6268 .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos => os.NAME_MAX,
6369 // Haiku's NAME_MAX includes the null terminator, so subtract one.
6470 .haiku => os.NAME_MAX - 1,
65 // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
66 // If it would require 4 UTF-8 bytes, then there would be a surrogate
67 // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
71 // Each WTF-16LE character may be expanded to 3 WTF-8 bytes.
72 // If it would require 4 WTF-8 bytes, then there would be a surrogate
73 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
6874 .windows => os.windows.NAME_MAX * 3,
6975 // For WASI, the MAX_NAME will depend on the host OS, so it needs to be
7076 // 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);
8692
8793/// TODO remove the allocator requirement from this API
8894/// 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.
8998pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
9099 if (cwd().symLink(existing_path, new_path, .{})) {
91100 return;
......@@ -117,6 +126,9 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:
117126/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`
118127/// are absolute. See `Dir.updateFile` for a function that operates on both
119128/// 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.
120132pub fn updateFileAbsolute(
121133 source_path: []const u8,
122134 dest_path: []const u8,
......@@ -131,6 +143,9 @@ pub fn updateFileAbsolute(
131143/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
132144/// are absolute. See `Dir.copyFile` for a function that operates on both
133145/// 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.
134149pub fn copyFileAbsolute(
135150 source_path: []const u8,
136151 dest_path: []const u8,
......@@ -145,24 +160,30 @@ pub fn copyFileAbsolute(
145160/// Create a new directory, based on an absolute path.
146161/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
147162/// 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.
148166pub fn makeDirAbsolute(absolute_path: []const u8) !void {
149167 assert(path.isAbsolute(absolute_path));
150168 return os.mkdir(absolute_path, Dir.default_mode);
151169}
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.
154172pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
155173 assert(path.isAbsoluteZ(absolute_path_z));
156174 return os.mkdirZ(absolute_path_z, Dir.default_mode);
157175}
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.
160178pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
161179 assert(path.isAbsoluteWindowsW(absolute_path_w));
162180 return os.mkdirW(absolute_path_w, Dir.default_mode);
163181}
164182
165183/// 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.
166187pub fn deleteDirAbsolute(dir_path: []const u8) !void {
167188 assert(path.isAbsolute(dir_path));
168189 return os.rmdir(dir_path);
......@@ -181,6 +202,9 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {
181202}
182203
183204/// 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.
184208pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
185209 assert(path.isAbsolute(old_path));
186210 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
211235 return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
212236}
213237
214/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
238/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
215239/// This function is Windows-only.
216240pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
217241 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 {
240264/// See `openDirAbsoluteZ` for a function that accepts a null-terminated path.
241265///
242266/// 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.
243270pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
244271 assert(path.isAbsolute(absolute_path));
245272 return cwd().openDir(absolute_path, flags);
......@@ -262,6 +289,9 @@ pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptio
262289/// operates on both absolute and relative paths.
263290/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function
264291/// 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.
265295pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
266296 assert(path.isAbsolute(absolute_path));
267297 return cwd().openFile(absolute_path, flags);
......@@ -280,11 +310,13 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
280310}
281311
282312/// Test accessing `path`.
283/// `path` is UTF-8-encoded.
284313/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
285314/// For example, instead of testing if a file exists and then opening it, just
286315/// open it and handle the error for file not found.
287316/// 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.
288320pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
289321 assert(path.isAbsolute(absolute_path));
290322 try cwd().access(absolute_path, flags);
......@@ -306,6 +338,9 @@ pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir
306338/// operates on both absolute and relative paths.
307339/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function
308340/// 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.
309344pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
310345 assert(path.isAbsolute(absolute_path));
311346 return cwd().createFile(absolute_path, flags);
......@@ -327,6 +362,9 @@ pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFl
327362/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that
328363/// operates on both absolute and relative paths.
329364/// 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.
330368pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
331369 assert(path.isAbsolute(absolute_path));
332370 return cwd().deleteFile(absolute_path);
......@@ -349,6 +387,9 @@ pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!
349387/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that
350388/// operates on both absolute and relative paths.
351389/// 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.
352393pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
353394 assert(path.isAbsolute(absolute_path));
354395 const dirname = path.dirname(absolute_path) orelse return error{
......@@ -364,6 +405,9 @@ pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
364405}
365406
366407/// 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.
367411pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
368412 assert(path.isAbsolute(pathname));
369413 return os.readlink(pathname, buffer);
......@@ -387,6 +431,9 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8)
387431/// one; the latter case is known as a dangling link.
388432/// If `sym_link_path` exists, it will not be overwritten.
389433/// 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.
390437pub fn symLinkAbsolute(
391438 target_path: []const u8,
392439 sym_link_path: []const u8,
......@@ -402,7 +449,7 @@ pub fn symLinkAbsolute(
402449 return os.symlink(target_path, sym_link_path);
403450}
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.
406453/// Note that this function will by default try creating a symbolic link to a file. If you would
407454/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
408455/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
......@@ -426,27 +473,14 @@ pub fn symLinkAbsoluteZ(
426473 assert(path.isAbsoluteZ(target_path_c));
427474 assert(path.isAbsoluteZ(sym_link_path_c));
428475 if (builtin.os.tag == .windows) {
429 const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
430 const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
431 return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
476 const target_path_w = try os.windows.cStrToPrefixedFileW(null, target_path_c);
477 const sym_link_path_w = try os.windows.cStrToPrefixedFileW(null, sym_link_path_c);
478 return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
432479 }
433480 return os.symlinkZ(target_path_c, sym_link_path_c);
434481}
435482
436pub const OpenSelfExeError = error{
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;
483pub const OpenSelfExeError = os.OpenError || SelfExePathError || os.FlockError;
450484
451485pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
452486 if (builtin.os.tag == .linux) {
......@@ -469,7 +503,45 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
469503 return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags);
470504}
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
474546/// `selfExePath` except allocates the result on the heap.
475547/// Caller owns returned memory.
......@@ -491,6 +563,8 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {
491563/// This function may return an error if the current executable
492564/// was deleted after spawning.
493565/// 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.
494568///
495569/// On Linux, depends on procfs being mounted. If the currently executing binary has
496570/// 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 {
505579 if (rc != 0) return error.NameTooLong;
506580
507581 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 };
509587 if (real_path.len > out_buffer.len) return error.NameTooLong;
510588 const result = out_buffer[0..real_path.len];
511589 @memcpy(result, real_path);
512590 return result;
513591 }
514592 switch (builtin.os.tag) {
515 .linux => return os.readlinkZ("/proc/self/exe", out_buffer),
516 .solaris, .illumos => return os.readlinkZ("/proc/self/path/a.out", out_buffer),
593 .linux => return os.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) {
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 },
517607 .freebsd, .dragonfly => {
518608 var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC, os.KERN.PROC_PATHNAME, -1 };
519609 var out_len: usize = out_buffer.len;
......@@ -537,7 +627,11 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
537627 if (mem.indexOf(u8, argv0, "/") != null) {
538628 // argv[0] is a path (relative or absolute): use realpath(3) directly
539629 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 };
541635 if (real_path.len > out_buffer.len)
542636 return error.NameTooLong;
543637 const result = out_buffer[0..real_path.len];
......@@ -575,7 +669,10 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
575669 // symlink, not the path that the symlink points to. We want the path
576670 // that the symlink points to, though, so we need to get the realpath.
577671 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 };
579676 },
580677 else => @compileError("std.fs.selfExePath not supported for this target"),
581678 }
......@@ -599,6 +696,8 @@ pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 {
599696
600697/// Get the directory path that contains the current executable.
601698/// 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.
602701pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
603702 const self_exe_path = try selfExePath(out_buffer);
604703 // Assume that the OS APIs return absolute paths, and therefore dirname
......@@ -607,6 +706,8 @@ pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
607706}
608707
609708/// `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.
610711/// See also `Dir.realpath`.
611712pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
612713 // 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 {
99 pub const Kind = File.Kind;
1010};
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
1421pub const Iterator = switch (builtin.os.tag) {
1522 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {
......@@ -445,13 +452,12 @@ pub const Iterator = switch (builtin.os.tag) {
445452 self.index = self.buf.len;
446453 }
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{ '.', '.' }))
451458 continue;
452 // Trust that Windows gives us valid UTF-16LE
453 const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
454 const name_utf8 = self.name_data[0..name_utf8_len];
459 const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le);
460 const name_wtf8 = self.name_data[0..name_wtf8_len];
455461 const kind: Entry.Kind = blk: {
456462 const attrs = dir_info.FileAttributes;
457463 if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
......@@ -459,7 +465,7 @@ pub const Iterator = switch (builtin.os.tag) {
459465 break :blk .file;
460466 };
461467 return Entry{
462 .name = name_utf8,
468 .name = name_wtf8,
463469 .kind = kind,
464470 };
465471 }
......@@ -516,6 +522,7 @@ pub const Iterator = switch (builtin.os.tag) {
516522 .INVAL => unreachable,
517523 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
518524 .NOTCAPABLE => return error.AccessDenied,
525 .ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
519526 else => |err| return posix.unexpectedErrno(err),
520527 }
521528 if (bufused == 0) return null;
......@@ -743,7 +750,11 @@ pub const OpenError = error{
743750 SystemFdQuotaExceeded,
744751 NoDevice,
745752 SystemResources,
753 /// WASI-only; file paths must be valid UTF-8.
746754 InvalidUtf8,
755 /// Windows-only; file paths provided by the user must be valid WTF-8.
756 /// https://simonsapin.github.io/wtf-8/
757 InvalidWtf8,
747758 BadPathName,
748759 DeviceBusy,
749760 /// On Windows, `\\server` or `\\server\share` was not found.
......@@ -759,6 +770,9 @@ pub fn close(self: *Dir) void {
759770/// To create a new file, see `createFile`.
760771/// Call `File.close` to release the resource.
761772/// 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.
762776pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
763777 if (builtin.os.tag == .windows) {
764778 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
911925/// Creates, opens, or overwrites a file with write access.
912926/// Call `File.close` on the result when done.
913927/// 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.
914931pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
915932 if (builtin.os.tag == .windows) {
916933 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)
10601077/// Creates a single directory with a relative or absolute path.
10611078/// To create multiple directories to make an entire path, see `makePath`.
10621079/// 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.
10631083pub fn makeDir(self: Dir, sub_path: []const u8) !void {
10641084 try posix.mkdirat(self.fd, sub_path, default_mode);
10651085}
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.
10681088/// To create multiple directories to make an entire path, see `makePath`.
10691089/// To operate on only absolute paths, see `makeDirAbsoluteZ`.
10701090pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
10711091 try posix.mkdiratZ(self.fd, sub_path, default_mode);
10721092}
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.
10751095/// To create multiple directories to make an entire path, see `makePath`.
10761096/// To operate on only absolute paths, see `makeDirAbsoluteW`.
10771097pub 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 {
10831103/// Returns success if the path already exists and is a directory.
10841104/// This function is not atomic, and if it returns an error, the file system may
10851105/// 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.
10861109///
10871110/// Paths containing `..` components are handled differently depending on the platform:
10881111/// - 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 {
11191142 }
11201143}
11211144
1122/// Calls makeOpenDirAccessMaskW iteratively to make an entire path
1145/// Windows only. Calls makeOpenDirAccessMaskW iteratively to make an entire path
11231146/// (i.e. creating any parent directories that do not exist).
11241147/// Opens the dir if the path already exists and is a directory.
11251148/// This function is not atomic, and if it returns an error, the file system may
11261149/// have been modified regardless.
1150/// `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
11271151fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
11281152 const w = std.os.windows;
11291153 var it = try fs.path.componentIterator(sub_path);
11301154 // 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{
11321156 .name = "",
11331157 .path = sub_path,
11341158 };
......@@ -1156,7 +1180,9 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no
11561180/// This function performs `makePath`, followed by `openDir`.
11571181/// If supported by the OS, this operation is atomic. It is not atomic on
11581182/// 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.
11601186pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
11611187 return switch (builtin.os.tag) {
11621188 .windows => {
......@@ -1185,6 +1211,10 @@ pub const RealPathError = posix.RealPathError;
11851211/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
11861212/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
11871213/// 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.
11881218/// This function is not universally supported by all platforms.
11891219/// Currently supported hosts are: Linux, macOS, and Windows.
11901220/// 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
12241254 error.FileLocksNotSupported => return error.Unexpected,
12251255 error.FileBusy => return error.Unexpected,
12261256 error.WouldBlock => return error.Unexpected,
1257 error.InvalidUtf8 => unreachable, // WASI-only
12271258 else => |e| return e,
12281259 };
12291260 defer posix.close(fd);
......@@ -1246,7 +1277,8 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
12461277 return result;
12471278}
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/).
12501282/// See also `Dir.realpath`, `realpathW`.
12511283pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {
12521284 const w = std.os.windows;
......@@ -1272,16 +1304,7 @@ pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathErr
12721304 var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
12731305 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
12741306 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) {
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 };
1307 const end_index = std.unicode.wtf16LeToWtf8(&big_out_buf, wide_slice);
12851308 if (end_index > out_buffer.len)
12861309 return error.NameTooLong;
12871310 const result = out_buffer[0..end_index];
......@@ -1344,6 +1367,9 @@ pub const OpenDirOptions = struct {
13441367/// open until `close` is called on the result.
13451368/// The directory cannot be iterated unless the `iterate` option is set to `true`.
13461369///
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.
13471373/// Asserts that the path parameter has no null bytes.
13481374pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
13491375 switch (builtin.os.tag) {
......@@ -1428,7 +1454,7 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) Open
14281454 }
14291455}
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.
14321458/// This function asserts the target OS is Windows.
14331459pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
14341460 const w = std.os.windows;
......@@ -1518,6 +1544,9 @@ fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u3
15181544pub const DeleteFileError = posix.UnlinkError;
15191545
15201546/// 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.
15211550/// Asserts that the path parameter has no null bytes.
15221551pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
15231552 if (builtin.os.tag == .windows) {
......@@ -1553,7 +1582,7 @@ pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
15531582 };
15541583}
15551584
1556/// Same as `deleteFile` except the parameter is WTF-16 encoded.
1585/// Same as `deleteFile` except the parameter is WTF-16 LE encoded.
15571586pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
15581587 posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
15591588 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
......@@ -1572,7 +1601,11 @@ pub const DeleteDirError = error{
15721601 NotDir,
15731602 SystemResources,
15741603 ReadOnlyFileSystem,
1604 /// WASI-only; file paths must be valid UTF-8.
15751605 InvalidUtf8,
1606 /// Windows-only; file paths provided by the user must be valid WTF-8.
1607 /// https://simonsapin.github.io/wtf-8/
1608 InvalidWtf8,
15761609 BadPathName,
15771610 /// On Windows, `\\server` or `\\server\share` was not found.
15781611 NetworkNotFound,
......@@ -1581,6 +1614,9 @@ pub const DeleteDirError = error{
15811614
15821615/// Returns `error.DirNotEmpty` if the directory is not empty.
15831616/// 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.
15841620/// Asserts that the path parameter has no null bytes.
15851621pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
15861622 if (builtin.os.tag == .windows) {
......@@ -1605,7 +1641,7 @@ pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
16051641 };
16061642}
16071643
1608/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
1644/// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed.
16091645/// This function is Windows-only.
16101646pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
16111647 posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
......@@ -1620,6 +1656,9 @@ pub const RenameError = posix.RenameError;
16201656/// If new_sub_path already exists, it will be replaced.
16211657/// Renaming a file over an existing directory or a directory
16221658/// 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.
16231662pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
16241663 return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
16251664}
......@@ -1629,7 +1668,7 @@ pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]co
16291668 return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
16301669}
16311670
1632/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
1671/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
16331672/// This function is Windows-only.
16341673pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
16351674 return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
......@@ -1647,6 +1686,9 @@ pub const SymLinkFlags = struct {
16471686/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
16481687/// one; the latter case is known as a dangling link.
16491688/// 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.
16501692pub fn symLink(
16511693 self: Dir,
16521694 target_path: []const u8,
......@@ -1662,7 +1704,7 @@ pub fn symLink(
16621704 // when converting to an NT namespaced path. CreateSymbolicLink in
16631705 // symLinkW will handle the necessary conversion.
16641706 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);
16661708 target_path_w.data[target_path_w.len] = 0;
16671709 const sym_link_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sym_link_path);
16681710 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
......@@ -1698,7 +1740,7 @@ pub fn symLinkZ(
16981740}
16991741
17001742/// Windows-only. Same as `symLink` except the pathname parameters
1701/// are null-terminated, WTF16 encoded.
1743/// are WTF16 LE encoded.
17021744pub fn symLinkW(
17031745 self: Dir,
17041746 /// WTF-16, does not need to be NT-prefixed. The NT-prefixing
......@@ -1716,6 +1758,9 @@ pub const ReadLinkError = posix.ReadLinkError;
17161758/// Read value of a symbolic link.
17171759/// The return value is a slice of `buffer`, from index `0`.
17181760/// 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.
17191764pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
17201765 if (builtin.os.tag == .wasi and !builtin.link_libc) {
17211766 return self.readLinkWasi(sub_path, buffer);
......@@ -1733,7 +1778,7 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
17331778 return posix.readlinkat(self.fd, sub_path, buffer);
17341779}
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.
17371782pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
17381783 if (builtin.os.tag == .windows) {
17391784 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 {
17431788}
17441789
17451790/// Windows-only. Same as `readLink` except the pathname parameter
1746/// is null-terminated, WTF16 encoded.
1791/// is WTF16 LE encoded.
17471792pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
17481793 return std.os.windows.ReadLink(self.fd, sub_path_w, buffer);
17491794}
......@@ -1753,6 +1798,9 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
17531798/// the situation is ambiguous. It could either mean that the entire file was read, and
17541799/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
17551800/// 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.
17561804pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
17571805 var file = try self.openFile(file_path, .{});
17581806 defer file.close();
......@@ -1763,6 +1811,9 @@ pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
17631811
17641812/// On success, caller owns returned buffer.
17651813/// 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.
17661817pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
17671818 return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);
17681819}
......@@ -1772,6 +1823,9 @@ pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8,
17721823/// If `size_hint` is specified the initial buffer size is calculated using
17731824/// that value, otherwise the effective file size is used instead.
17741825/// 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.
17751829pub fn readFileAllocOptions(
17761830 self: Dir,
17771831 allocator: mem.Allocator,
......@@ -1811,9 +1865,13 @@ pub const DeleteTreeError = error{
18111865 /// This error is unreachable if `sub_path` does not contain a path separator.
18121866 NotDir,
18131867
1814 /// On Windows, file paths must be valid Unicode.
1868 /// WASI-only; file paths must be valid UTF-8.
18151869 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
18171875 /// On Windows, file paths cannot contain these characters:
18181876 /// '/', '*', '?', '"', '<', '>', '|'
18191877 BadPathName,
......@@ -1826,6 +1884,9 @@ pub const DeleteTreeError = error{
18261884/// removes it. If it cannot be removed because it is a non-empty directory,
18271885/// this function recursively removes its entries and then tries again.
18281886/// 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.
18291890pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
18301891 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 {
18791940 error.SystemResources,
18801941 error.Unexpected,
18811942 error.InvalidUtf8,
1943 error.InvalidWtf8,
18821944 error.BadPathName,
18831945 error.NetworkNotFound,
18841946 error.DeviceBusy,
......@@ -1910,6 +1972,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
19101972
19111973 error.AccessDenied,
19121974 error.InvalidUtf8,
1975 error.InvalidWtf8,
19131976 error.SymLinkLoop,
19141977 error.NameTooLong,
19151978 error.SystemResources,
......@@ -1973,6 +2036,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
19732036 error.SystemResources,
19742037 error.Unexpected,
19752038 error.InvalidUtf8,
2039 error.InvalidWtf8,
19762040 error.BadPathName,
19772041 error.NetworkNotFound,
19782042 error.DeviceBusy,
......@@ -1994,6 +2058,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
19942058
19952059 error.AccessDenied,
19962060 error.InvalidUtf8,
2061 error.InvalidWtf8,
19972062 error.SymLinkLoop,
19982063 error.NameTooLong,
19992064 error.SystemResources,
......@@ -2022,6 +2087,9 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
20222087
20232088/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
20242089/// 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.
20252093pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
20262094 return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
20272095}
......@@ -2074,6 +2142,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
20742142 error.SystemResources,
20752143 error.Unexpected,
20762144 error.InvalidUtf8,
2145 error.InvalidWtf8,
20772146 error.BadPathName,
20782147 error.NetworkNotFound,
20792148 error.DeviceBusy,
......@@ -2102,6 +2171,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
21022171
21032172 error.AccessDenied,
21042173 error.InvalidUtf8,
2174 error.InvalidWtf8,
21052175 error.SymLinkLoop,
21062176 error.NameTooLong,
21072177 error.SystemResources,
......@@ -2171,6 +2241,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
21712241 error.SystemResources,
21722242 error.Unexpected,
21732243 error.InvalidUtf8,
2244 error.InvalidWtf8,
21742245 error.BadPathName,
21752246 error.DeviceBusy,
21762247 error.NetworkNotFound,
......@@ -2189,6 +2260,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
21892260
21902261 error.AccessDenied,
21912262 error.InvalidUtf8,
2263 error.InvalidWtf8,
21922264 error.SymLinkLoop,
21932265 error.NameTooLong,
21942266 error.SystemResources,
......@@ -2209,6 +2281,9 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
22092281pub const WriteFileError = File.WriteError || File.OpenError;
22102282
22112283/// 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.
22122287pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void {
22132288 return writeFile2(self, .{
22142289 .sub_path = sub_path,
......@@ -2218,6 +2293,9 @@ pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileErr
22182293}
22192294
22202295pub 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.
22212299 sub_path: []const u8,
22222300 data: []const u8,
22232301 flags: File.CreateFlags = .{},
......@@ -2232,8 +2310,10 @@ pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void {
22322310
22332311pub const AccessError = posix.AccessError;
22342312
2235/// Test accessing `path`.
2236/// `path` is UTF-8-encoded.
2313/// Test accessing `sub_path`.
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.
22372317/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
22382318/// For example, instead of testing if a file exists and then opening it, just
22392319/// 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
22682348}
22692349
22702350/// Same as `access` except asserts the target OS is Windows and the path parameter is
2271/// * WTF-16 encoded
2351/// * WTF-16 LE encoded
22722352/// * null-terminated
2273/// * NtDll prefixed
2353/// * relative or has the NT namespace prefix
22742354/// TODO currently this ignores `flags`.
22752355pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
22762356 _ = flags;
......@@ -2292,6 +2372,9 @@ pub const PrevStatus = enum {
22922372/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
22932373/// Returns the previous status of the file before updating.
22942374/// 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.
22952378pub fn updateFile(
22962379 source_dir: Dir,
22972380 source_path: []const u8,
......@@ -2343,6 +2426,9 @@ pub const CopyFileError = File.OpenError || File.StatError ||
23432426/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
23442427/// there is a possibility of power loss or application termination leaving temporary files present
23452428/// 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.
23462432pub fn copyFile(
23472433 source_dir: Dir,
23482434 source_path: []const u8,
......@@ -2430,6 +2516,9 @@ pub const AtomicFileOptions = struct {
24302516/// Always call `AtomicFile.deinit` to clean up, regardless of whether
24312517/// `AtomicFile.finish` succeeded. `dest_path` must remain valid until
24322518/// `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.
24332522pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
24342523 if (fs.path.dirname(dest_path)) |dirname| {
24352524 const dir = if (options.make_path)
......@@ -2461,6 +2550,9 @@ pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError
24612550/// Symlinks are followed.
24622551///
24632552/// `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.
24642556pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
24652557 if (builtin.os.tag == .windows) {
24662558 var file = try self.openFile(sub_path, .{});
lib/std/fs/File.zig+4-1
......@@ -40,8 +40,11 @@ pub const OpenError = error{
4040 AccessDenied,
4141 PipeBusy,
4242 NameTooLong,
43 /// On Windows, file paths must be valid Unicode.
43 /// WASI-only; file paths must be valid UTF-8.
4444 InvalidUtf8,
45 /// Windows-only; file paths provided by the user must be valid WTF-8.
46 /// https://simonsapin.github.io/wtf-8/
47 InvalidWtf8,
4548 /// On Windows, file paths cannot contain these characters:
4649 /// '/', '*', '?', '"', '<', '>', '|'
4750 BadPathName,
lib/std/fs/path.zig+36-7
......@@ -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
115const builtin = @import("builtin");
216const std = @import("../std.zig");
317const debug = std.debug;
......@@ -438,7 +452,7 @@ fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool {
438452 var it1 = mem.tokenizeScalar(u8, ns1, sep1);
439453 var it2 = mem.tokenizeScalar(u8, ns2, sep2);
440454
441 return windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?);
455 return windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?);
442456}
443457
444458fn 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
458472 var it1 = mem.tokenizeScalar(u8, p1, sep1);
459473 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().?);
462476 },
463477 }
464478}
......@@ -1099,7 +1113,7 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
10991113 const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest());
11001114 const to_rest = to_it.rest();
11011115 if (to_it.next()) |to_component| {
1102 if (windows.eqlIgnoreCaseUtf8(from_component, to_component))
1116 if (windows.eqlIgnoreCaseWtf8(from_component, to_component))
11031117 continue;
11041118 }
11051119 var up_index_end = "..".len;
......@@ -1564,14 +1578,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
15641578 };
15651579}
15661580
1567pub const NativeUtf8ComponentIterator = ComponentIterator(switch (native_os) {
1581pub const NativeComponentIterator = ComponentIterator(switch (native_os) {
15681582 .windows => .windows,
15691583 .uefi => .uefi,
15701584 else => .posix,
15711585}, u8);
15721586
1573pub fn componentIterator(path: []const u8) !NativeUtf8ComponentIterator {
1574 return NativeUtf8ComponentIterator.init(path);
1587pub fn componentIterator(path: []const u8) !NativeComponentIterator {
1588 return NativeComponentIterator.init(path);
15751589}
15761590
15771591test "ComponentIterator posix" {
......@@ -1826,7 +1840,7 @@ test "ComponentIterator windows" {
18261840 }
18271841}
18281842
1829test "ComponentIterator windows UTF-16" {
1843test "ComponentIterator windows WTF-16" {
18301844 // TODO: Fix on big endian architectures
18311845 if (builtin.cpu.arch.endian() != .little) {
18321846 return error.SkipZigTest;
......@@ -1925,3 +1939,18 @@ test "ComponentIterator roots" {
19251939 try std.testing.expectEqualStrings("//a/b//", it.root().?);
19261940 }
19271941}
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 {
2626 }
2727
2828 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
3131 pub fn getTransformFn(comptime path_type: PathType) TransformFn {
3232 switch (path_type) {
3333 .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 {
3535 _ = allocator;
3636 _ = dir;
3737 return relative_path;
3838 }
3939 }.transform,
4040 .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 {
4242 // The final path may not actually exist which would cause realpath to fail.
4343 // So instead, we get the path of the dir and join it with the relative path.
4444 var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
4545 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 });
4747 }
4848 }.transform,
4949 .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 {
5151 // Any drive absolute path (C:\foo) can be converted into a UNC path by
5252 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
5353 var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
5454 const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
5555 const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path);
5656 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 }),
5858 .drive_absolute => {
5959 // `C:\<...>` -> `\\127.0.0.1\C$\<...>`
6060 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 });
6262 path[prepended.len + 1] = '$';
6363 return path;
6464 },
......@@ -96,7 +96,7 @@ const TestContext = struct {
9696 /// Returns the `relative_path` transformed into the TestContext's `path_type`.
9797 /// The result is allocated by the TestContext's arena and will be free'd during
9898 /// `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 {
100100 return self.transform_fn(self.arena.allocator(), self.dir, relative_path);
101101 }
102102};
......@@ -1001,6 +1001,16 @@ test "openSelfExe" {
10011001 self_exe_file.close();
10021002}
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
10041014test "deleteTree does not follow symlinks" {
10051015 var tmp = tmpDir(.{});
10061016 defer tmp.cleanup();
......@@ -1907,3 +1917,111 @@ test "delete a setAsCwd directory on Windows" {
19071917 // Close the parent "tmp" so we don't leak the HANDLE.
19081918 tmp.parent_dir.close();
19091919}
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 @@
33//! * Convert "errno"-style error codes into Zig errors.
44//! * When null-terminated byte buffers are required, provide APIs which accept
55//! slices as well as APIs which accept null-terminated byte buffers. Same goes
6//! for UTF-16LE encoding.
6//! for WTF-16LE encoding.
77//! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
88//! cross platform abstracting.
99//! * 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
498498 const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {
499499 error.NameTooLong => unreachable,
500500 error.FileNotFound => unreachable,
501 error.InvalidUtf8 => unreachable,
501502 else => |e| return e,
502503 };
503504 if ((stat.mode & S.IFMT) == S.IFLNK)
......@@ -1614,9 +1615,16 @@ pub const OpenError = error{
16141615 /// The underlying filesystem does not support file locks
16151616 FileLocksNotSupported,
16161617
1618 /// Path contains characters that are disallowed by the underlying filesystem.
16171619 BadPathName,
1620
1621 /// WASI-only; file paths must be valid UTF-8.
16181622 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
16201628 /// On Windows, `\\server` or `\\server\share` was not found.
16211629 NetworkNotFound,
16221630
......@@ -1634,6 +1642,9 @@ pub const OpenError = error{
16341642} || UnexpectedError;
16351643
16361644/// 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.
16371648/// See also `openZ`.
16381649pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
16391650 if (builtin.os.tag == .windows) {
......@@ -1646,6 +1657,9 @@ pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
16461657}
16471658
16481659/// 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.
16491663/// See also `open`.
16501664pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
16511665 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 {
16871701
16881702/// Open and possibly create a file. Keeps trying if it gets interrupted.
16891703/// `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.
16901707/// See also `openatZ`.
16911708pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t {
16921709 if (builtin.os.tag == .windows) {
......@@ -1829,6 +1846,7 @@ pub fn openatWasi(
18291846 .EXIST => return error.PathAlreadyExists,
18301847 .BUSY => return error.DeviceBusy,
18311848 .NOTCAPABLE => return error.AccessDenied,
1849 .ILSEQ => return error.InvalidUtf8,
18321850 else => |err| return unexpectedErrno(err),
18331851 }
18341852 }
......@@ -1836,6 +1854,9 @@ pub fn openatWasi(
18361854
18371855/// Open and possibly create a file. Keeps trying if it gets interrupted.
18381856/// `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.
18391860/// See also `openat`.
18401861pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t {
18411862 if (builtin.os.tag == .windows) {
......@@ -2156,13 +2177,23 @@ pub const SymLinkError = error{
21562177 ReadOnlyFileSystem,
21572178 NotDir,
21582179 NameTooLong,
2180
2181 /// WASI-only; file paths must be valid UTF-8.
21592182 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
21602188 BadPathName,
21612189} || UnexpectedError;
21622190
21632191/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
21642192/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
21652193/// 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.
21662197/// If `sym_link_path` exists, it will not be overwritten.
21672198/// See also `symlinkZ.
21682199pub 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
22002231 .NOMEM => return error.SystemResources,
22012232 .NOSPC => return error.NoSpaceLeft,
22022233 .ROFS => return error.ReadOnlyFileSystem,
2234 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2235 return error.InvalidUtf8
2236 else
2237 return unexpectedErrno(err),
22032238 else => |err| return unexpectedErrno(err),
22042239 }
22052240}
......@@ -2208,6 +2243,9 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
22082243/// `target_path` **relative** to `newdirfd` directory handle.
22092244/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
22102245/// 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.
22112249/// If `sym_link_path` exists, it will not be overwritten.
22122250/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
22132251pub 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
22422280 .NOSPC => return error.NoSpaceLeft,
22432281 .ROFS => return error.ReadOnlyFileSystem,
22442282 .NOTCAPABLE => return error.AccessDenied,
2283 .ILSEQ => return error.InvalidUtf8,
22452284 else => |err| return unexpectedErrno(err),
22462285 }
22472286}
......@@ -2270,6 +2309,10 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:
22702309 .NOMEM => return error.SystemResources,
22712310 .NOSPC => return error.NoSpaceLeft,
22722311 .ROFS => return error.ReadOnlyFileSystem,
2312 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2313 return error.InvalidUtf8
2314 else
2315 return unexpectedErrno(err),
22732316 else => |err| return unexpectedErrno(err),
22742317 }
22752318}
......@@ -2287,8 +2330,13 @@ pub const LinkError = UnexpectedError || error{
22872330 NoSpaceLeft,
22882331 ReadOnlyFileSystem,
22892332 NotSameFileSystem,
2333
2334 /// WASI-only; file paths must be valid UTF-8.
2335 InvalidUtf8,
22902336};
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.
22922340pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {
22932341 if (builtin.os.tag == .wasi and !builtin.link_libc) {
22942342 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
23102358 .ROFS => return error.ReadOnlyFileSystem,
23112359 .XDEV => return error.NotSameFileSystem,
23122360 .INVAL => unreachable,
2361 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2362 return error.InvalidUtf8
2363 else
2364 return unexpectedErrno(err),
23132365 else => |err| return unexpectedErrno(err),
23142366 }
23152367}
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.
23172371pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {
23182372 if (builtin.os.tag == .wasi and !builtin.link_libc) {
23192373 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
23282382
23292383pub 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.
23312387pub fn linkatZ(
23322388 olddir: fd_t,
23332389 oldpath: [*:0]const u8,
......@@ -2356,10 +2412,16 @@ pub fn linkatZ(
23562412 .ROFS => return error.ReadOnlyFileSystem,
23572413 .XDEV => return error.NotSameFileSystem,
23582414 .INVAL => unreachable,
2415 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2416 return error.InvalidUtf8
2417 else
2418 return unexpectedErrno(err),
23592419 else => |err| return unexpectedErrno(err),
23602420 }
23612421}
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.
23632425pub fn linkat(
23642426 olddir: fd_t,
23652427 oldpath: []const u8,
......@@ -2399,6 +2461,7 @@ pub fn linkat(
23992461 .ROFS => return error.ReadOnlyFileSystem,
24002462 .XDEV => return error.NotSameFileSystem,
24012463 .INVAL => unreachable,
2464 .ILSEQ => return error.InvalidUtf8,
24022465 else => |err| return unexpectedErrno(err),
24032466 }
24042467 }
......@@ -2422,9 +2485,13 @@ pub const UnlinkError = error{
24222485 SystemResources,
24232486 ReadOnlyFileSystem,
24242487
2425 /// On Windows, file paths must be valid Unicode.
2488 /// WASI-only; file paths must be valid UTF-8.
24262489 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
24282495 /// On Windows, file paths cannot contain these characters:
24292496 /// '/', '*', '?', '"', '<', '>', '|'
24302497 BadPathName,
......@@ -2434,6 +2501,9 @@ pub const UnlinkError = error{
24342501} || UnexpectedError;
24352502
24362503/// 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.
24372507/// See also `unlinkZ`.
24382508pub fn unlink(file_path: []const u8) UnlinkError!void {
24392509 if (builtin.os.tag == .wasi and !builtin.link_libc) {
......@@ -2450,7 +2520,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
24502520 }
24512521}
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.
24542524pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
24552525 if (builtin.os.tag == .windows) {
24562526 const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
......@@ -2473,11 +2543,15 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
24732543 .NOTDIR => return error.NotDir,
24742544 .NOMEM => return error.SystemResources,
24752545 .ROFS => return error.ReadOnlyFileSystem,
2546 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2547 return error.InvalidUtf8
2548 else
2549 return unexpectedErrno(err),
24762550 else => |err| return unexpectedErrno(err),
24772551 }
24782552}
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.
24812555pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
24822556 windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd }) catch |err| switch (err) {
24832557 error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true
......@@ -2491,6 +2565,9 @@ pub const UnlinkatError = UnlinkError || error{
24912565};
24922566
24932567/// 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.
24942571/// Asserts that the path parameter has no null bytes.
24952572pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
24962573 if (builtin.os.tag == .windows) {
......@@ -2528,6 +2605,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
25282605 .ROFS => return error.ReadOnlyFileSystem,
25292606 .NOTEMPTY => return error.DirNotEmpty,
25302607 .NOTCAPABLE => return error.AccessDenied,
2608 .ILSEQ => return error.InvalidUtf8,
25312609
25322610 .INVAL => unreachable, // invalid flags, or pathname has . as last component
25332611 .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
25602638 .ROFS => return error.ReadOnlyFileSystem,
25612639 .EXIST => return error.DirNotEmpty,
25622640 .NOTEMPTY => return error.DirNotEmpty,
2641 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2642 return error.InvalidUtf8
2643 else
2644 return unexpectedErrno(err),
25632645
25642646 .INVAL => unreachable, // invalid flags, or pathname has . as last component
25652647 .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
25682650 }
25692651}
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.
25722654pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
25732655 const remove_dir = (flags & AT.REMOVEDIR) != 0;
25742656 return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
......@@ -2594,7 +2676,11 @@ pub const RenameError = error{
25942676 PathAlreadyExists,
25952677 ReadOnlyFileSystem,
25962678 RenameAcrossMountPoints,
2679 /// WASI-only; file paths must be valid UTF-8.
25972680 InvalidUtf8,
2681 /// Windows-only; file paths provided by the user must be valid WTF-8.
2682 /// https://simonsapin.github.io/wtf-8/
2683 InvalidWtf8,
25982684 BadPathName,
25992685 NoDevice,
26002686 SharingViolation,
......@@ -2610,6 +2696,9 @@ pub const RenameError = error{
26102696} || UnexpectedError;
26112697
26122698/// 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.
26132702pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
26142703 if (builtin.os.tag == .wasi and !builtin.link_libc) {
26152704 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 {
26242713 }
26252714}
26262715
2627/// Same as `rename` except the parameters are null-terminated byte arrays.
2716/// Same as `rename` except the parameters are null-terminated.
26282717pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {
26292718 if (builtin.os.tag == .windows) {
26302719 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
26532742 .NOTEMPTY => return error.PathAlreadyExists,
26542743 .ROFS => return error.ReadOnlyFileSystem,
26552744 .XDEV => return error.RenameAcrossMountPoints,
2745 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2746 return error.InvalidUtf8
2747 else
2748 return unexpectedErrno(err),
26562749 else => |err| return unexpectedErrno(err),
26572750 }
26582751}
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.
26612754/// Assumes target is Windows.
26622755pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {
26632756 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
26652758}
26662759
26672760/// 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.
26682764pub fn renameat(
26692765 old_dir_fd: fd_t,
26702766 old_path: []const u8,
......@@ -2710,11 +2806,12 @@ pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!vo
27102806 .ROFS => return error.ReadOnlyFileSystem,
27112807 .XDEV => return error.RenameAcrossMountPoints,
27122808 .NOTCAPABLE => return error.AccessDenied,
2809 .ILSEQ => return error.InvalidUtf8,
27132810 else => |err| return unexpectedErrno(err),
27142811 }
27152812}
27162813
2717/// Same as `renameat` except the parameters are null-terminated byte arrays.
2814/// Same as `renameat` except the parameters are null-terminated.
27182815pub fn renameatZ(
27192816 old_dir_fd: fd_t,
27202817 old_path: [*:0]const u8,
......@@ -2749,6 +2846,10 @@ pub fn renameatZ(
27492846 .NOTEMPTY => return error.PathAlreadyExists,
27502847 .ROFS => return error.ReadOnlyFileSystem,
27512848 .XDEV => return error.RenameAcrossMountPoints,
2849 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2850 return error.InvalidUtf8
2851 else
2852 return unexpectedErrno(err),
27522853 else => |err| return unexpectedErrno(err),
27532854 }
27542855}
......@@ -2860,6 +2961,9 @@ pub fn renameatW(
28602961 }
28612962}
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.
28632967pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
28642968 if (builtin.os.tag == .windows) {
28652969 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
28912995 .NOTDIR => return error.NotDir,
28922996 .ROFS => return error.ReadOnlyFileSystem,
28932997 .NOTCAPABLE => return error.AccessDenied,
2998 .ILSEQ => return error.InvalidUtf8,
28942999 else => |err| return unexpectedErrno(err),
28953000 }
28963001}
28973002
3003/// Same as `mkdirat` except the parameters are null-terminated.
28983004pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
28993005 if (builtin.os.tag == .windows) {
29003006 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);
29023008 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
29033009 return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);
29043010 }
......@@ -2920,10 +3026,15 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
29203026 .ROFS => return error.ReadOnlyFileSystem,
29213027 // dragonfly: when dir_fd is unlinked from filesystem
29223028 .NOTCONN => return error.FileNotFound,
3029 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3030 return error.InvalidUtf8
3031 else
3032 return unexpectedErrno(err),
29233033 else => |err| return unexpectedErrno(err),
29243034 }
29253035}
29263036
3037/// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded.
29273038pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
29283039 _ = mode;
29293040 const sub_dir_handle = windows.OpenFile(sub_path_w, .{
......@@ -2955,7 +3066,11 @@ pub const MakeDirError = error{
29553066 NoSpaceLeft,
29563067 NotDir,
29573068 ReadOnlyFileSystem,
3069 /// WASI-only; file paths must be valid UTF-8.
29583070 InvalidUtf8,
3071 /// Windows-only; file paths provided by the user must be valid WTF-8.
3072 /// https://simonsapin.github.io/wtf-8/
3073 InvalidWtf8,
29593074 BadPathName,
29603075 NoDevice,
29613076 /// On Windows, `\\server` or `\\server\share` was not found.
......@@ -2964,6 +3079,9 @@ pub const MakeDirError = error{
29643079
29653080/// Create a directory.
29663081/// `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.
29673085pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
29683086 if (builtin.os.tag == .wasi and !builtin.link_libc) {
29693087 return mkdirat(wasi.AT.FDCWD, dir_path, mode);
......@@ -2976,7 +3094,10 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
29763094 }
29773095}
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.
29803101pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
29813102 if (builtin.os.tag == .windows) {
29823103 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 {
29993120 .NOSPC => return error.NoSpaceLeft,
30003121 .NOTDIR => return error.NotDir,
30013122 .ROFS => return error.ReadOnlyFileSystem,
3123 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3124 return error.InvalidUtf8
3125 else
3126 return unexpectedErrno(err),
30023127 else => |err| return unexpectedErrno(err),
30033128 }
30043129}
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.
30073132pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
30083133 _ = mode;
30093134 const sub_dir_handle = windows.OpenFile(dir_path_w, .{
......@@ -3031,13 +3156,20 @@ pub const DeleteDirError = error{
30313156 NotDir,
30323157 DirNotEmpty,
30333158 ReadOnlyFileSystem,
3159 /// WASI-only; file paths must be valid UTF-8.
30343160 InvalidUtf8,
3161 /// Windows-only; file paths provided by the user must be valid WTF-8.
3162 /// https://simonsapin.github.io/wtf-8/
3163 InvalidWtf8,
30353164 BadPathName,
30363165 /// On Windows, `\\server` or `\\server\share` was not found.
30373166 NetworkNotFound,
30383167} || UnexpectedError;
30393168
30403169/// 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.
30413173pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
30423174 if (builtin.os.tag == .wasi and !builtin.link_libc) {
30433175 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 {
30553187}
30563188
30573189/// 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.
30583193pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
30593194 if (builtin.os.tag == .windows) {
30603195 const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
......@@ -3077,11 +3212,15 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
30773212 .EXIST => return error.DirNotEmpty,
30783213 .NOTEMPTY => return error.DirNotEmpty,
30793214 .ROFS => return error.ReadOnlyFileSystem,
3215 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3216 return error.InvalidUtf8
3217 else
3218 return unexpectedErrno(err),
30803219 else => |err| return unexpectedErrno(err),
30813220 }
30823221}
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.
30853224pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
30863225 return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
30873226 error.IsDir => unreachable,
......@@ -3098,21 +3237,25 @@ pub const ChangeCurDirError = error{
30983237 SystemResources,
30993238 NotDir,
31003239 BadPathName,
3101
3102 /// On Windows, file paths must be valid Unicode.
3240 /// WASI-only; file paths must be valid UTF-8.
31033241 InvalidUtf8,
3242 /// Windows-only; file paths provided by the user must be valid WTF-8.
3243 /// https://simonsapin.github.io/wtf-8/
3244 InvalidWtf8,
31043245} || UnexpectedError;
31053246
31063247/// 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.
31083251pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
31093252 if (builtin.os.tag == .wasi and !builtin.link_libc) {
31103253 @compileError("WASI does not support os.chdir");
31113254 } else if (builtin.os.tag == .windows) {
3112 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3113 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
3114 if (len > utf16_dir_path.len) return error.NameTooLong;
3115 return chdirW(utf16_dir_path[0..len]);
3255 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3256 const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], dir_path);
3257 if (len > wtf16_dir_path.len) return error.NameTooLong;
3258 return chdirW(wtf16_dir_path[0..len]);
31163259 } else {
31173260 const dir_path_c = try toPosixPath(dir_path);
31183261 return chdirZ(&dir_path_c);
......@@ -3120,12 +3263,15 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
31203263}
31213264
31223265/// 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.
31233269pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
31243270 if (builtin.os.tag == .windows) {
3125 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3126 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], mem.span(dir_path));
3127 if (len > utf16_dir_path.len) return error.NameTooLong;
3128 return chdirW(utf16_dir_path[0..len]);
3271 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3272 const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], mem.span(dir_path));
3273 if (len > wtf16_dir_path.len) return error.NameTooLong;
3274 return chdirW(wtf16_dir_path[0..len]);
31293275 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
31303276 return chdir(mem.span(dir_path));
31313277 }
......@@ -3139,11 +3285,15 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
31393285 .NOENT => return error.FileNotFound,
31403286 .NOMEM => return error.SystemResources,
31413287 .NOTDIR => return error.NotDir,
3288 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3289 return error.InvalidUtf8
3290 else
3291 return unexpectedErrno(err),
31423292 else => |err| return unexpectedErrno(err),
31433293 }
31443294}
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.
31473297pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void {
31483298 windows.SetCurrentDirectory(dir_path) catch |err| switch (err) {
31493299 error.NoDevice => return error.FileSystem,
......@@ -3183,7 +3333,11 @@ pub const ReadLinkError = error{
31833333 SystemResources,
31843334 NotLink,
31853335 NotDir,
3336 /// WASI-only; file paths must be valid UTF-8.
31863337 InvalidUtf8,
3338 /// Windows-only; file paths provided by the user must be valid WTF-8.
3339 /// https://simonsapin.github.io/wtf-8/
3340 InvalidWtf8,
31873341 BadPathName,
31883342 /// Windows-only. This error may occur if the opened reparse point is
31893343 /// of unsupported type.
......@@ -3193,7 +3347,13 @@ pub const ReadLinkError = error{
31933347} || UnexpectedError;
31943348
31953349/// 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.
31963353/// 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.
31973357pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
31983358 if (builtin.os.tag == .wasi and !builtin.link_libc) {
31993359 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 {
32063366 }
32073367}
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/).
32103371/// See also `readlinkZ`.
32113372pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
32123373 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 {
32153376/// Same as `readlink` except `file_path` is null-terminated.
32163377pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
32173378 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);
32193380 return readlinkW(file_path_w.span(), out_buffer);
32203381 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
32213382 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
32323393 .NOENT => return error.FileNotFound,
32333394 .NOMEM => return error.SystemResources,
32343395 .NOTDIR => return error.NotDir,
3396 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3397 return error.InvalidUtf8
3398 else
3399 return unexpectedErrno(err),
32353400 else => |err| return unexpectedErrno(err),
32363401 }
32373402}
32383403
32393404/// 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.
32403408/// 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.
32413412/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
32423413pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
32433414 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
32673438 .NOMEM => return error.SystemResources,
32683439 .NOTDIR => return error.NotDir,
32693440 .NOTCAPABLE => return error.AccessDenied,
3441 .ILSEQ => return error.InvalidUtf8,
32703442 else => |err| return unexpectedErrno(err),
32713443 }
32723444}
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/).
32753448/// See also `readlinkat`.
32763449pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
32773450 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
32983471 .NOENT => return error.FileNotFound,
32993472 .NOMEM => return error.SystemResources,
33003473 .NOTDIR => return error.NotDir,
3474 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3475 return error.InvalidUtf8
3476 else
3477 return unexpectedErrno(err),
33013478 else => |err| return unexpectedErrno(err),
33023479 }
33033480}
......@@ -4274,10 +4451,18 @@ pub fn fstat_wasi(fd: fd_t) FStatError!wasi.filestat_t {
42744451 }
42754452}
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
42794462/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`
42804463/// 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.
42814466/// See also `fstatatZ` and `fstatat_wasi`.
42824467pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
42834468 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
42944479}
42954480
42964481/// WASI-only. Same as `fstatat` but targeting WASI.
4482/// `pathname` should be encoded as valid UTF-8.
42974483/// See also `fstatat`.
42984484pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t) FStatAtError!wasi.filestat_t {
42994485 var stat: wasi.filestat_t = undefined;
......@@ -4308,6 +4494,7 @@ pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t
43084494 .NOENT => return error.FileNotFound,
43094495 .NOTDIR => return error.FileNotFound,
43104496 .NOTCAPABLE => return error.AccessDenied,
4497 .ILSEQ => return error.InvalidUtf8,
43114498 else => |err| return unexpectedErrno(err),
43124499 }
43134500}
......@@ -4337,6 +4524,10 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S
43374524 .LOOP => return error.SymLinkLoop,
43384525 .NOENT => return error.FileNotFound,
43394526 .NOTDIR => return error.FileNotFound,
4527 .ILSEQ => |err| if (builtin.os.tag == .wasi)
4528 return error.InvalidUtf8
4529 else
4530 return unexpectedErrno(err),
43404531 else => |err| return unexpectedErrno(err),
43414532 }
43424533}
......@@ -4693,12 +4884,17 @@ pub const AccessError = error{
46934884 FileBusy,
46944885 SymLinkLoop,
46954886 ReadOnlyFileSystem,
4696
4697 /// On Windows, file paths must be valid Unicode.
4887 /// WASI-only; file paths must be valid UTF-8.
46984888 InvalidUtf8,
4889 /// Windows-only; file paths provided by the user must be valid WTF-8.
4890 /// https://simonsapin.github.io/wtf-8/
4891 InvalidWtf8,
46994892} || UnexpectedError;
47004893
47014894/// 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.
47024898/// TODO currently this assumes `mode` is `F.OK` on Windows.
47034899pub fn access(path: []const u8, mode: u32) AccessError!void {
47044900 if (builtin.os.tag == .windows) {
......@@ -4740,12 +4936,16 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
47404936 .FAULT => unreachable,
47414937 .IO => return error.InputOutput,
47424938 .NOMEM => return error.SystemResources,
4939 .ILSEQ => |err| if (builtin.os.tag == .wasi)
4940 return error.InvalidUtf8
4941 else
4942 return unexpectedErrno(err),
47434943 else => |err| return unexpectedErrno(err),
47444944 }
47454945}
47464946
4747/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
4748/// Otherwise use `access` or `accessC`.
4947/// Call from Windows-specific code if you already have a WTF-16LE encoded, null terminated string.
4948/// Otherwise use `access` or `accessZ`.
47494949/// TODO currently this ignores `mode`.
47504950pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!void {
47514951 _ = mode;
......@@ -4762,6 +4962,9 @@ pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!v
47624962}
47634963
47644964/// 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.
47654968/// TODO currently this ignores `mode` and `flags` on Windows.
47664969pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
47674970 if (builtin.os.tag == .windows) {
......@@ -4832,6 +5035,10 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces
48325035 .FAULT => unreachable,
48335036 .IO => return error.InputOutput,
48345037 .NOMEM => return error.SystemResources,
5038 .ILSEQ => |err| if (builtin.os.tag == .wasi)
5039 return error.InvalidUtf8
5040 else
5041 return unexpectedErrno(err),
48355042 else => |err| return unexpectedErrno(err),
48365043 }
48375044}
......@@ -5339,8 +5546,9 @@ pub const RealPathError = error{
53395546 /// On WASI, the current CWD may not be associated with an absolute path.
53405547 InvalidHandle,
53415548
5342 /// On Windows, file paths must be valid Unicode.
5343 InvalidUtf8,
5549 /// Windows-only; file paths provided by the user must be valid WTF-8.
5550 /// https://simonsapin.github.io/wtf-8/
5551 InvalidWtf8,
53445552
53455553 /// On Windows, `\\server` or `\\server\share` was not found.
53465554 NetworkNotFound,
......@@ -5362,8 +5570,12 @@ pub const RealPathError = error{
53625570/// Return the canonicalized absolute pathname.
53635571/// Expands all symbolic links and resolves references to `.`, `..`, and
53645572/// 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.
53655575/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
53665576/// 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.
53675579/// Calling this function is usually a bug.
53685580pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
53695581 if (builtin.os.tag == .windows) {
......@@ -5402,6 +5614,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
54025614 error.WouldBlock => unreachable,
54035615 error.FileBusy => unreachable, // not asking for write permissions
54045616 error.InvalidHandle => unreachable, // WASI-only
5617 error.InvalidUtf8 => unreachable, // WASI-only
54055618 else => |e| return e,
54065619 };
54075620 defer close(fd);
......@@ -5425,7 +5638,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
54255638 return mem.sliceTo(result_path, 0);
54265639}
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/).
54295643/// Calling this function is usually a bug.
54305644pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
54315645 const w = windows;
......@@ -5475,6 +5689,8 @@ pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
54755689/// This function is very host-specific and is not universally supported by all hosts.
54765690/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
54775691/// 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.
54785694/// Calling this function is usually a bug.
54795695pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
54805696 if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
......@@ -5485,10 +5701,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
54855701 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
54865702 const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
54875703
5488 // TODO: Windows file paths can be arbitrary arrays of u16 values
5489 // and must not fail with InvalidUtf8.
5490 const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch
5491 return error.InvalidUtf8;
5704 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
54925705 return out_buffer[0..end_index];
54935706 },
54945707 .macos, .ios, .watchos, .tvos => {
......@@ -5512,8 +5725,12 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
55125725
55135726 const target = readlinkZ(proc_path, out_buffer) catch |err| {
55145727 switch (err) {
5515 error.UnsupportedReparsePointType => unreachable, // Windows only,
55165728 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
55175734 else => |e| return e,
55185735 }
55195736 };
lib/std/os/windows.zig+51-37
......@@ -1,8 +1,8 @@
11//! This file contains thin wrappers around Windows-specific APIs, with these
22//! specific goals in mind:
33//! * Convert "errno"-style error codes into Zig errors.
4//! * When null-terminated or UTF16LE byte buffers are required, provide APIs which accept
5//! slices as well as APIs which accept null-terminated UTF16LE byte buffers.
4//! * When null-terminated or WTF16LE byte buffers are required, provide APIs which accept
5//! slices as well as APIs which accept null-terminated WTF16LE byte buffers.
66
77const builtin = @import("builtin");
88const std = @import("../std.zig");
......@@ -548,7 +548,6 @@ pub fn WriteFile(
548548
549549pub const SetCurrentDirectoryError = error{
550550 NameTooLong,
551 InvalidUtf8,
552551 FileNotFound,
553552 NotDir,
554553 AccessDenied,
......@@ -587,24 +586,24 @@ pub const GetCurrentDirectoryError = error{
587586};
588587
589588/// The result is a slice of `buffer`, indexed from 0.
589/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
590590pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
591 var utf16le_buf: [PATH_MAX_WIDE]u16 = undefined;
592 const result = kernel32.GetCurrentDirectoryW(utf16le_buf.len, &utf16le_buf);
591 var wtf16le_buf: [PATH_MAX_WIDE]u16 = undefined;
592 const result = kernel32.GetCurrentDirectoryW(wtf16le_buf.len, &wtf16le_buf);
593593 if (result == 0) {
594594 switch (kernel32.GetLastError()) {
595595 else => |err| return unexpectedError(err),
596596 }
597597 }
598 assert(result <= utf16le_buf.len);
599 const utf16le_slice = utf16le_buf[0..result];
600 // Trust that Windows gives us valid UTF-16LE.
598 assert(result <= wtf16le_buf.len);
599 const wtf16le_slice = wtf16le_buf[0..result];
601600 var end_index: usize = 0;
602 var it = std.unicode.Utf16LeIterator.init(utf16le_slice);
603 while (it.nextCodepoint() catch unreachable) |codepoint| {
601 var it = std.unicode.Wtf16LeIterator.init(wtf16le_slice);
602 while (it.nextCodepoint()) |codepoint| {
604603 const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable;
605604 if (end_index + seq_len >= buffer.len)
606605 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;
608607 }
609608 return buffer[0..end_index];
610609}
......@@ -812,6 +811,8 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
812811 }
813812}
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/).
815816fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {
816817 const win32_namespace_path = path: {
817818 if (is_relative) break :path path;
......@@ -821,7 +822,7 @@ fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u
821822 };
822823 break :path win32_path.span();
823824 };
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);
825826 return out_buffer[0..out_len];
826827}
827828
......@@ -1942,13 +1943,13 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {
19421943 if (@inComptime() or builtin.os.tag != .windows) {
19431944 // This function compares the strings code unit by code unit (aka u16-to-u16),
19441945 // 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/lowercase
1946 // conversion that changes the number of WTF-16 code units needed for the uppercase/lowercase
19461947 // version in the conversion table since only codepoints <= max(u16) are eligible
19471948 // for conversion at all.
19481949 if (a.len != b.len) return false;
19491950
19501951 for (a, b) |a_c, b_c| {
1951 // The slices are always UTF-16 LE, so need to convert the elements to native
1952 // The slices are always WTF-16 LE, so need to convert the elements to native
19521953 // endianness for the uppercasing
19531954 const a_c_native = std.mem.littleToNative(u16, a_c);
19541955 const b_c_native = std.mem.littleToNative(u16, b_c);
......@@ -1975,18 +1976,18 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {
19751976 return ntdll.RtlEqualUnicodeString(&a_string, &b_string, TRUE) == TRUE;
19761977}
19771978
1978/// Compares two UTF-8 strings using the equivalent functionality of
1979/// Compares two WTF-8 strings using the equivalent functionality of
19791980/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
19801981/// This function can be called on any target.
1981/// Assumes `a` and `b` are valid UTF-8.
1982pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
1982/// Assumes `a` and `b` are valid WTF-8.
1983pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
19831984 // A length equality check is not possible here because there are
19841985 // some codepoints that have a different length uppercase UTF-8 representations
19851986 // than their lowercase counterparts, e.g. U+0250 (2 bytes) <-> U+2C6F (3 bytes).
19861987 // 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 b_utf8_it = std.unicode.Utf8View.initUnchecked(b).iterator();
1989 var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();
1990 var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator();
19901991
19911992 // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
19921993 // redundant copy of the uppercase data.
......@@ -1996,8 +1997,8 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
19961997 };
19971998
19981999 while (true) {
1999 const a_cp = a_utf8_it.nextCodepoint() orelse break;
2000 const b_cp = b_utf8_it.nextCodepoint() orelse return false;
2000 const a_cp = a_wtf8_it.nextCodepoint() orelse break;
2001 const b_cp = b_wtf8_it.nextCodepoint() orelse return false;
20012002
20022003 if (a_cp <= std.math.maxInt(u16) and b_cp <= std.math.maxInt(u16)) {
20032004 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 {
20082009 }
20092010 }
20102011 // 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
20132014 return true;
20142015}
20152016
20162017fn 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));
20182019 try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWTF16(
20192020 std.unicode.utf8ToUtf16LeStringLiteral(a),
20202021 std.unicode.utf8ToUtf16LeStringLiteral(b),
20212022 ));
20222023
2023 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseUtf8(a, b));
2024 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWtf8(a, b));
20242025 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWTF16(
20252026 std.unicode.utf8ToUtf16LeStringLiteral(a),
20262027 std.unicode.utf8ToUtf16LeStringLiteral(b),
20272028 ));
20282029}
20292030
2030test "eqlIgnoreCaseWTF16/Utf8" {
2031test "eqlIgnoreCaseWTF16/Wtf8" {
20312032 try testEqlIgnoreCase(true, "\x01 a B Λ ɐ", "\x01 A b λ Ɐ");
20322033 // does not do case-insensitive comparison for codepoints >= U+10000
20332034 try testEqlIgnoreCase(false, "𐓏", "𐓷");
......@@ -2117,20 +2118,32 @@ pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize {
21172118 return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]);
21182119}
21192120
2121pub const Wtf8ToPrefixedFileWError = error{InvalidWtf8} || Wtf16ToPrefixedFileWError;
2122
21202123/// Same as `sliceToPrefixedFileW` but accepts a pointer
2121/// to a null-terminated path.
2122pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) !PathSpace {
2124/// to a null-terminated WTF-8 encoded path.
2125/// https://simonsapin.github.io/wtf-8/
2126pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWError!PathSpace {
21232127 return sliceToPrefixedFileW(dir, mem.sliceTo(s, 0));
21242128}
21252129
2126/// Same as `wToPrefixedFileW` but accepts a UTF-8 encoded path.
2127pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace {
2130/// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path.
2131/// https://simonsapin.github.io/wtf-8/
2132pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace {
21282133 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);
21302135 temp_path.data[temp_path.len] = 0;
21312136 return wToPrefixedFileW(dir, temp_path.span());
21322137}
21332138
2139pub const Wtf16ToPrefixedFileWError = error{
2140 AccessDenied,
2141 BadPathName,
2142 FileNotFound,
2143 NameTooLong,
2144 Unexpected,
2145};
2146
21342147/// Converts the `path` to WTF16, null-terminated. If the path contains any
21352148/// namespace prefix, or is anything but a relative path (rooted, drive relative,
21362149/// etc) the result will have the NT-style prefix `\??\`.
......@@ -2142,7 +2155,7 @@ pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace {
21422155/// is non-null, or the CWD if it is null.
21432156/// - Special case device names like COM1, NUL, etc are not handled specially (TODO)
21442157/// - . 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 {
21462159 const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };
21472160 switch (getNamespacePrefix(u16, path)) {
21482161 // TODO: Figure out a way to design an API that can avoid the copy for .nt,
......@@ -2312,7 +2325,7 @@ pub const NamespacePrefix = enum {
23122325 nt,
23132326};
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.
23162329pub fn getNamespacePrefix(comptime T: type, path: []const T) NamespacePrefix {
23172330 if (path.len < 4) return .none;
23182331 var all_backslash = switch (mem.littleToNative(T, path[0])) {
......@@ -2366,7 +2379,7 @@ pub const UnprefixedPathType = enum {
23662379
23672380/// Get the path type of a path that is known to not have any namespace prefixes
23682381/// (`\\?\`, `\\.\`, `\??\`).
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.
23702383pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathType {
23712384 if (path.len < 1) return .relative;
23722385
......@@ -2420,7 +2433,7 @@ test getUnprefixedPathType {
24202433/// Functionality is based on the ReactOS test cases found here:
24212434/// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c
24222435///
2423/// `path` should be encoded as UTF-16LE.
2436/// `path` should be encoded as WTF-16LE.
24242437pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
24252438 if (path.len > PATH_MAX_WIDE) return error.NameTooLong;
24262439
......@@ -2530,7 +2543,6 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {
25302543 if (std.os.unexpected_error_tracing) {
25312544 // 614 is the length of the longest windows error description
25322545 var buf_wstr: [614]WCHAR = undefined;
2533 var buf_utf8: [614]u8 = undefined;
25342546 const len = kernel32.FormatMessageW(
25352547 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
25362548 null,
......@@ -2540,8 +2552,10 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {
25402552 buf_wstr.len,
25412553 null,
25422554 );
2543 _ = std.unicode.utf16leToUtf8(&buf_utf8, buf_wstr[0..len]) catch unreachable;
2544 std.debug.print("error.Unexpected: GetLastError({}): {s}\n", .{ @intFromEnum(err), buf_utf8[0..len] });
2555 std.debug.print("error.Unexpected: GetLastError({}): {}\n", .{
2556 @intFromEnum(err),
2557 std.unicode.fmtUtf16Le(buf_wstr[0..len]),
2558 });
25452559 std.debug.dumpCurrentStackTrace(@returnAddress());
25462560 }
25472561 return error.Unexpected;
lib/std/os/windows/test.zig+2-2
......@@ -30,7 +30,7 @@ fn testToPrefixedFileNoOracle(comptime path: []const u8, comptime expected_path:
3030 const expected_path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(expected_path);
3131 const actual_path = try windows.wToPrefixedFileW(null, path_utf16);
3232 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) });
3434 return e;
3535 };
3636}
......@@ -48,7 +48,7 @@ fn testToPrefixedFileOnlyOracle(comptime path: []const u8) !void {
4848 const zig_result = try windows.wToPrefixedFileW(null, path_utf16);
4949 const win32_api_result = try RtlDosPathNameToNtPathName_U(path_utf16);
5050 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()) });
5252 return e;
5353 };
5454}
lib/std/process.zig+92-64
......@@ -16,11 +16,15 @@ pub const changeCurDir = os.chdir;
1616pub const changeCurDirC = os.chdirC;
1717
1818/// 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.
1921pub fn getCwd(out_buffer: []u8) ![]u8 {
2022 return os.getcwd(out_buffer);
2123}
2224
2325/// 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.
2428pub fn getCwdAlloc(allocator: Allocator) ![]u8 {
2529 // The use of MAX_PATH_BYTES here is just a heuristic: most paths will fit
2630 // in stack_buf, avoiding an extra allocation in the common case.
......@@ -76,7 +80,7 @@ pub const EnvMap = struct {
7680 _ = self;
7781 if (builtin.os.tag == .windows) {
7882 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();
8084 while (it.nextCodepoint()) |cp| {
8185 const cp_upper = upcase(cp);
8286 h.update(&[_]u8{
......@@ -93,8 +97,8 @@ pub const EnvMap = struct {
9397 pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
9498 _ = self;
9599 if (builtin.os.tag == .windows) {
96 var it_a = std.unicode.Utf8View.initUnchecked(a).iterator();
97 var it_b = std.unicode.Utf8View.initUnchecked(b).iterator();
100 var it_a = std.unicode.Wtf8View.initUnchecked(a).iterator();
101 var it_b = std.unicode.Wtf8View.initUnchecked(b).iterator();
98102 while (true) {
99103 const c_a = it_a.nextCodepoint() orelse break;
100104 const c_b = it_b.nextCodepoint() orelse return false;
......@@ -129,8 +133,9 @@ pub const EnvMap = struct {
129133 /// Same as `put` but the key and value become owned by the EnvMap rather
130134 /// than being copied.
131135 /// 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.
133137 pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {
138 assert(std.unicode.wtf8ValidateSlice(key));
134139 const get_or_put = try self.hash_map.getOrPut(key);
135140 if (get_or_put.found_existing) {
136141 self.free(get_or_put.key_ptr.*);
......@@ -141,8 +146,9 @@ pub const EnvMap = struct {
141146 }
142147
143148 /// `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.
145150 pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {
151 assert(std.unicode.wtf8ValidateSlice(key));
146152 const value_copy = try self.copy(value);
147153 errdefer self.free(value_copy);
148154 const get_or_put = try self.hash_map.getOrPut(key);
......@@ -159,23 +165,26 @@ pub const EnvMap = struct {
159165
160166 /// Find the address of the value associated with a key.
161167 /// 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.
163169 pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {
170 assert(std.unicode.wtf8ValidateSlice(key));
164171 return self.hash_map.getPtr(key);
165172 }
166173
167174 /// Return the map's copy of the value associated with
168175 /// a key. The returned string is invalidated if this
169176 /// 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.
171178 pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {
179 assert(std.unicode.wtf8ValidateSlice(key));
172180 return self.hash_map.get(key);
173181 }
174182
175183 /// Removes the item from the map and frees its value.
176184 /// 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.
178186 pub fn remove(self: *EnvMap, key: []const u8) void {
187 assert(std.unicode.wtf8ValidateSlice(key));
179188 const kv = self.hash_map.fetchRemove(key) orelse return;
180189 self.free(kv.key);
181190 self.free(kv.value);
......@@ -239,18 +248,34 @@ test "EnvMap" {
239248
240249 try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
241250
242 // test Unicode case-insensitivity on Windows
243251 if (builtin.os.tag == .windows) {
252 // test Unicode case-insensitivity on Windows
244253 try env.put("КИРиллИЦА", "something else");
245254 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).?);
246264 }
247265}
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
249274/// Returns a snapshot of the environment variables of the current process.
250275/// Any modifications to the resulting EnvMap will not be reflected in the environment, and
251276/// likewise, any future modifications to the environment will not be reflected in the EnvMap.
252277/// 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 {
254279 var result = EnvMap.init(allocator);
255280 errdefer result.deinit();
256281
......@@ -269,7 +294,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {
269294
270295 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
271296 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);
273298 errdefer allocator.free(key);
274299
275300 if (ptr[i] == '=') i += 1;
......@@ -277,7 +302,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {
277302 const value_start = i;
278303 while (ptr[i] != 0) : (i += 1) {}
279304 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);
281306 errdefer allocator.free(value);
282307
283308 i += 1; // skip over null byte
......@@ -355,25 +380,28 @@ pub const GetEnvVarOwnedError = error{
355380 OutOfMemory,
356381 EnvironmentVariableNotFound,
357382
358 /// See https://github.com/ziglang/zig/issues/1774
359 InvalidUtf8,
383 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
384 /// https://simonsapin.github.io/wtf-8/
385 InvalidWtf8,
360386};
361387
362388/// 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.
363393pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
364394 if (builtin.os.tag == .windows) {
365395 const result_w = blk: {
366 const key_w = try std.unicode.utf8ToUtf16LeWithNull(allocator, key);
367 defer allocator.free(key_w);
396 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
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
369401 break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
370402 };
371 return std.unicode.utf16leToUtf8Alloc(allocator, result_w) catch |err| switch (err) {
372 error.DanglingSurrogateHalf => return error.InvalidUtf8,
373 error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,
374 error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,
375 else => |e| return e,
376 };
403 // wtf16LeToWtf8Alloc can only fail with OutOfMemory
404 return std.unicode.wtf16LeToWtf8Alloc(allocator, result_w);
377405 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
378406 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
379407 defer envmap.deinit();
......@@ -385,6 +413,7 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
385413 }
386414}
387415
416/// On Windows, `key` must be valid UTF-8.
388417pub fn hasEnvVarConstant(comptime key: []const u8) bool {
389418 if (builtin.os.tag == .windows) {
390419 const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);
......@@ -396,11 +425,22 @@ pub fn hasEnvVarConstant(comptime key: []const u8) bool {
396425 }
397426}
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 {
400439 if (builtin.os.tag == .windows) {
401440 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
402 const key_w = try std.unicode.utf8ToUtf16LeWithNull(stack_alloc.get(), key);
403 defer stack_alloc.allocator.free(key_w);
441 const stack_allocator = stack_alloc.get();
442 const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
443 defer stack_allocator.free(key_w);
404444 return std.os.getenvW(key_w) != null;
405445 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
406446 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
......@@ -411,9 +451,22 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool
411451 }
412452}
413453
414test "os.getEnvVarOwned" {
415 const ga = std.testing.allocator;
416 try testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV"));
454test getEnvVarOwned {
455 try testing.expectError(
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);
417470}
418471
419472pub const ArgIteratorPosix = struct {
......@@ -531,6 +584,7 @@ pub const ArgIteratorWasi = struct {
531584pub const ArgIteratorWindows = struct {
532585 allocator: Allocator,
533586 /// Owned by the iterator.
587 /// Encoded as WTF-8.
534588 cmd_line: []const u8,
535589 index: usize = 0,
536590 /// Owned by the iterator. Long enough to hold the entire `cmd_line` plus a null terminator.
......@@ -538,20 +592,14 @@ pub const ArgIteratorWindows = struct {
538592 start: usize = 0,
539593 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.
544598 ///
545 /// The iterator makes a copy of `cmd_line_w` converted UTF-8 and keeps it; it does *not* take
599 /// The iterator makes a copy of `cmd_line_w` converted WTF-8 and keeps it; it does *not* take
546600 /// ownership of `cmd_line_w`.
547601 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) {
549 error.DanglingSurrogateHalf,
550 error.ExpectedSecondSurrogateHalf,
551 error.UnexpectedSecondSurrogateHalf,
552 => return error.InvalidCmdLine,
553 error.OutOfMemory => return error.OutOfMemory,
554 };
602 const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0));
555603 errdefer allocator.free(cmd_line);
556604
557605 const buffer = try allocator.alloc(u8, cmd_line.len + 1);
......@@ -566,6 +614,7 @@ pub const ArgIteratorWindows = struct {
566614
567615 /// Returns the next argument and advances the iterator. Returns `null` if at the end of the
568616 /// command-line string. The iterator owns the returned slice.
617 /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
569618 pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 {
570619 return self.nextWithStrategy(next_strategy);
571620 }
......@@ -777,7 +826,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
777826 pub const Self = @This();
778827
779828 pub const InitError = error{OutOfMemory};
780 pub const InitUtf16leError = error{ OutOfMemory, InvalidCmdLine };
781829
782830 /// cmd_line_utf8 MUST remain valid and constant while using this instance
783831 pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
......@@ -805,30 +853,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
805853 };
806854 }
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
832856 // Skips over whitespace in the cmd_line.
833857 // Returns false if the terminating sentinel is reached, true otherwise.
834858 // Also skips over comments (if supported).
......@@ -1021,6 +1045,8 @@ pub const ArgIterator = struct {
10211045
10221046 /// Get the next argument. Returns 'null' if we are at the end.
10231047 /// 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.
10241050 pub fn next(self: *ArgIterator) ?([:0]const u8) {
10251051 return self.inner.next();
10261052 }
......@@ -1057,6 +1083,8 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator
10571083}
10581084
10591085/// 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.
10601088pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
10611089 // TODO refactor to only make 1 allocation.
10621090 var it = try argsWithAllocator(allocator);
......@@ -1201,7 +1229,7 @@ test "ArgIteratorWindows" {
12011229}
12021230
12031231fn 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);
12051233 defer testing.allocator.free(cmd_line_w);
12061234
12071235 // next
lib/std/unicode.zig+914-104
......@@ -39,7 +39,16 @@ pub fn utf8ByteSequenceLength(first_byte: u8) !u3 {
3939/// out: the out buffer to write to. Must have a len >= utf8CodepointSequenceLength(c).
4040/// Errors: if c cannot be encoded in UTF-8.
4141/// 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 {
4352 const length = try utf8CodepointSequenceLength(c);
4453 assert(out.len >= length);
4554 switch (length) {
......@@ -53,7 +62,9 @@ pub fn utf8Encode(c: u21, out: []u8) !u3 {
5362 out[1] = @as(u8, @intCast(0b10000000 | (c & 0b111111)));
5463 },
5564 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 }
5768 out[0] = @as(u8, @intCast(0b11100000 | (c >> 12)));
5869 out[1] = @as(u8, @intCast(0b10000000 | ((c >> 6) & 0b111111)));
5970 out[2] = @as(u8, @intCast(0b10000000 | (c & 0b111111)));
......@@ -116,12 +127,22 @@ pub fn utf8Decode2(bytes: []const u8) Utf8Decode2Error!u21 {
116127 return value;
117128}
118129
119const Utf8Decode3Error = error{
120 Utf8ExpectedContinuation,
121 Utf8OverlongEncoding,
130const Utf8Decode3Error = Utf8Decode3AllowSurrogateHalfError || error{
122131 Utf8EncodesSurrogateHalf,
123132};
124133pub 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 {
125146 assert(bytes.len == 3);
126147 assert(bytes[0] & 0b11110000 == 0b11100000);
127148 var value: u21 = bytes[0] & 0b00001111;
......@@ -135,7 +156,6 @@ pub fn utf8Decode3(bytes: []const u8) Utf8Decode3Error!u21 {
135156 value |= bytes[2] & 0b00111111;
136157
137158 if (value < 0x800) return error.Utf8OverlongEncoding;
138 if (0xd800 <= value and value <= 0xdfff) return error.Utf8EncodesSurrogateHalf;
139159
140160 return value;
141161}
......@@ -213,6 +233,10 @@ pub fn utf8CountCodepoints(s: []const u8) !usize {
213233
214234/// Returns true if the input consists entirely of UTF-8 codepoints
215235pub 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 {
216240 var remaining = input;
217241
218242 const chunk_len = std.simd.suggestVectorLength(u8) orelse 1;
......@@ -240,9 +264,15 @@ pub fn utf8ValidateSlice(input: []const u8) bool {
240264 const xx = 0xF1; // invalid: size 1
241265 const as = 0xF0; // ASCII: size 1
242266 const s1 = 0x02; // accept 0, size 2
243 const s2 = 0x13; // accept 1, size 3
267 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 };
244271 const s3 = 0x03; // accept 0, size 3
245 const s4 = 0x23; // accept 2, size 3
272 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 };
246276 const s5 = 0x34; // accept 3, size 4
247277 const s6 = 0x04; // accept 0, size 4
248278 const s7 = 0x44; // accept 4, size 4
......@@ -458,7 +488,9 @@ pub const Utf16LeIterator = struct {
458488 };
459489 }
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 {
462494 assert(it.i <= it.bytes.len);
463495 if (it.i == it.bytes.len) return null;
464496 var code_units: [2]u16 = undefined;
......@@ -770,11 +802,139 @@ fn testDecode(bytes: []const u8) !u21 {
770802 return utf8Decode(bytes);
771803}
772804
773/// Caller must free returned memory.
774pub fn utf16leToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8 {
805/// Print the given `utf8` string, encoded as UTF-8 bytes.
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 {
775936 // optimistically guess that it will all be ascii.
776 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len);
777 errdefer result.deinit();
937 try array_list.ensureTotalCapacityPrecise(utf16le.len);
778938
779939 var remaining = utf16le;
780940 if (builtin.zig_backend != .stage2_x86_64) {
......@@ -796,68 +956,76 @@ pub fn utf16leToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8
796956 // We allocated enough space to encode every UTF-16 code unit
797957 // as ASCII, so if the entire string is ASCII then we are
798958 // guaranteed to have enough space allocated
799 result.appendSliceAssumeCapacity(&ascii_bytes);
959 array_list.appendSliceAssumeCapacity(&ascii_bytes);
800960 remaining = remaining[chunk_len..];
801961 }
802962 }
803963
804 var out_index: usize = result.items.len;
805 var it = Utf16LeIterator.init(remaining);
806 while (try it.nextCodepoint()) |codepoint| {
807 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
808 try result.resize(result.items.len + utf8_len);
809 assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len);
810 out_index += utf8_len;
964 var out_index: usize = array_list.items.len;
965 switch (surrogates) {
966 .cannot_encode_surrogate_half => {
967 var it = Utf16LeIterator.init(remaining);
968 while (try it.nextCodepoint()) |codepoint| {
969 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
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 },
811984 }
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
8131004 return result.toOwnedSlice();
8141005}
8151006
1007/// Deprecated; renamed to utf16LeToUtf8AllocZ
1008pub const utf16leToUtf8AllocZ = utf16LeToUtf8AllocZ;
1009
8161010/// 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 {
8181012 // optimistically guess that it will all be ascii (and allocate space for the null terminator)
8191013 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len + 1);
8201014 errdefer result.deinit();
8211015
822 var remaining = 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 }
1016 try utf16LeToUtf8ArrayList(&result, utf16le);
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 }
8551018 return result.toOwnedSliceSentinel(0);
8561019}
8571020
1021pub const Utf16LeToUtf8Error = Utf16LeIterator.NextCodepointError;
1022
8581023/// Asserts that the output buffer is big enough.
8591024/// 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 {
8611029 var end_index: usize = 0;
8621030
8631031 var remaining = utf16le;
......@@ -883,30 +1051,58 @@ pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !usize {
8831051 }
8841052 }
8851053
886 var it = Utf16LeIterator.init(remaining);
887 while (try it.nextCodepoint()) |codepoint| {
888 end_index += try utf8Encode(codepoint, utf8[end_index..]);
1054 switch (surrogates) {
1055 .cannot_encode_surrogate_half => {
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 },
8891078 }
8901079 return end_index;
8911080}
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 {
8941090 var utf16le: [2]u16 = undefined;
8951091 const utf16le_as_bytes = mem.sliceAsBytes(utf16le[0..]);
8961092
8971093 {
8981094 mem.writeInt(u16, utf16le_as_bytes[0..2], 'A', .little);
8991095 mem.writeInt(u16, utf16le_as_bytes[2..4], 'a', .little);
900 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);
901 defer std.testing.allocator.free(utf8);
1096 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
1097 defer testing.allocator.free(utf8);
9021098 try testing.expect(mem.eql(u8, utf8, "Aa"));
9031099 }
9041100
9051101 {
9061102 mem.writeInt(u16, utf16le_as_bytes[0..2], 0x80, .little);
9071103 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xffff, .little);
908 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);
909 defer std.testing.allocator.free(utf8);
1104 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
1105 defer testing.allocator.free(utf8);
9101106 try testing.expect(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf"));
9111107 }
9121108
......@@ -914,8 +1110,8 @@ test "utf16leToUtf8" {
9141110 // the values just outside the surrogate half range
9151111 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xd7ff, .little);
9161112 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xe000, .little);
917 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);
918 defer std.testing.allocator.free(utf8);
1113 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
1114 defer testing.allocator.free(utf8);
9191115 try testing.expect(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80"));
9201116 }
9211117
......@@ -923,8 +1119,8 @@ test "utf16leToUtf8" {
9231119 // smallest surrogate pair
9241120 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xd800, .little);
9251121 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdc00, .little);
926 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);
927 defer std.testing.allocator.free(utf8);
1122 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
1123 defer testing.allocator.free(utf8);
9281124 try testing.expect(mem.eql(u8, utf8, "\xf0\x90\x80\x80"));
9291125 }
9301126
......@@ -932,31 +1128,30 @@ test "utf16leToUtf8" {
9321128 // largest surrogate pair
9331129 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdbff, .little);
9341130 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdfff, .little);
935 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);
936 defer std.testing.allocator.free(utf8);
1131 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
1132 defer testing.allocator.free(utf8);
9371133 try testing.expect(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf"));
9381134 }
9391135
9401136 {
9411137 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdbff, .little);
9421138 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdc00, .little);
943 const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le);
944 defer std.testing.allocator.free(utf8);
1139 const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le);
1140 defer testing.allocator.free(utf8);
9451141 try testing.expect(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80"));
9461142 }
9471143
9481144 {
9491145 mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdcdc, .little);
9501146 mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdcdc, .little);
951 const result = utf16leToUtf8Alloc(std.testing.allocator, &utf16le);
952 try std.testing.expectError(error.UnexpectedSecondSurrogateHalf, result);
1147 const result = utf16LeToUtf8Alloc(testing.allocator, &utf16le);
1148 try testing.expectError(error.UnexpectedSecondSurrogateHalf, result);
9531149 }
9541150}
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 {
9571153 // optimistically guess that it will not require surrogate pairs
958 var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len + 1);
959 errdefer result.deinit();
1154 try array_list.ensureTotalCapacityPrecise(utf8.len);
9601155
9611156 var remaining = utf8;
9621157 // Need support for std.simd.interlace
......@@ -974,33 +1169,65 @@ pub fn utf8ToUtf16LeWithNull(allocator: mem.Allocator, utf8: []const u8) ![:0]u1
9741169 }
9751170 const zeroes: Chunk = @splat(0);
9761171 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));
9781173 remaining = remaining[chunk_len..];
9791174 }
9801175 }
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 };
9831181 var it = view.iterator();
9841182 while (it.nextCodepoint()) |codepoint| {
9851183 if (codepoint < 0x10000) {
9861184 const short = @as(u16, @intCast(codepoint));
987 try result.append(mem.nativeToLittle(u16, short));
1185 try array_list.append(mem.nativeToLittle(u16, short));
9881186 } else {
9891187 const high = @as(u16, @intCast((codepoint - 0x10000) >> 10)) + 0xD800;
9901188 const low = @as(u16, @intCast(codepoint & 0x3FF)) + 0xDC00;
9911189 var out: [2]u16 = undefined;
9921190 out[0] = mem.nativeToLittle(u16, high);
9931191 out[1] = mem.nativeToLittle(u16, low);
994 try result.appendSlice(out[0..]);
1192 try array_list.appendSlice(out[0..]);
9951193 }
9961194 }
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
9981221 return result.toOwnedSliceSentinel(0);
9991222}
10001223
10011224/// Returns index of next character. If exact fit, returned index equals output slice length.
10021225/// 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 {
10041231 var dest_i: usize = 0;
10051232
10061233 var remaining = utf8;
......@@ -1027,9 +1254,15 @@ pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize {
10271254
10281255 var src_i: usize = 0;
10291256 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 };
10311261 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 };
10331266 if (codepoint < 0x10000) {
10341267 const short = @as(u16, @intCast(codepoint));
10351268 utf16le[dest_i] = mem.nativeToLittle(u16, short);
......@@ -1064,21 +1297,59 @@ test "utf8ToUtf16Le" {
10641297 }
10651298}
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 {
10681339 {
1069 const utf16 = try utf8ToUtf16LeWithNull(testing.allocator, "𐐷");
1340 const utf16 = try utf8ToUtf16LeAllocZ(testing.allocator, "𐐷");
10701341 defer testing.allocator.free(utf16);
10711342 try testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc", mem.sliceAsBytes(utf16[0..]));
10721343 try testing.expect(utf16[2] == 0);
10731344 }
10741345 {
1075 const utf16 = try utf8ToUtf16LeWithNull(testing.allocator, "\u{10FFFF}");
1346 const utf16 = try utf8ToUtf16LeAllocZ(testing.allocator, "\u{10FFFF}");
10761347 defer testing.allocator.free(utf16);
10771348 try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", mem.sliceAsBytes(utf16[0..]));
10781349 try testing.expect(utf16[2] == 0);
10791350 }
10801351 {
1081 const result = utf8ToUtf16LeWithNull(testing.allocator, "\xf4\x90\x80\x80");
1352 const result = utf8ToUtf16LeAllocZ(testing.allocator, "\xf4\x90\x80\x80");
10821353 try testing.expectError(error.InvalidUtf8, result);
10831354 }
10841355}
......@@ -1127,8 +1398,9 @@ test "calculate utf16 string length of given utf8 string in u16" {
11271398 try comptime testCalcUtf16LeLen();
11281399}
11291400
1130/// Print the given `utf16le` string
1131fn formatUtf16le(
1401/// Print the given `utf16le` string, encoded as UTF-8 bytes.
1402/// Unpaired surrogates are replaced by the replacement character (U+FFFD).
1403fn formatUtf16Le(
11321404 utf16le: []const u16,
11331405 comptime fmt: []const u8,
11341406 options: std.fmt.FormatOptions,
......@@ -1136,13 +1408,14 @@ fn formatUtf16le(
11361408) !void {
11371409 _ = fmt;
11381410 _ = options;
1139 var buf: [300]u8 = undefined; // just a random size I chose
1411 var buf: [300]u8 = undefined; // just an arbitrary size
11401412 var it = Utf16LeIterator.init(utf16le);
11411413 var u8len: usize = 0;
11421414 while (it.nextCodepoint() catch replacement_character) |codepoint| {
11431415 u8len += utf8Encode(codepoint, buf[u8len..]) catch
11441416 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) {
11461419 try writer.writeAll(buf[0..u8len]);
11471420 u8len = 0;
11481421 }
......@@ -1150,22 +1423,27 @@ fn formatUtf16le(
11501423 try writer.writeAll(buf[0..u8len]);
11511424}
11521425
1153/// Return a Formatter for a Utf16le string
1154pub fn fmtUtf16le(utf16le: []const u16) std.fmt.Formatter(formatUtf16le) {
1426/// Deprecated; renamed to fmtUtf16Le
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) {
11551433 return .{ .data = utf16le };
11561434}
11571435
1158test "fmtUtf16le" {
1159 const expectFmt = std.testing.expectFmt;
1160 try expectFmt("", "{}", .{fmtUtf16le(utf8ToUtf16LeStringLiteral(""))});
1161 try expectFmt("foo", "{}", .{fmtUtf16le(utf8ToUtf16LeStringLiteral("foo"))});
1162 try expectFmt("𐐷", "{}", .{fmtUtf16le(utf8ToUtf16LeStringLiteral("𐐷"))});
1163 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)})});
1165 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)})});
1167 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)})});
1436test "fmtUtf16Le" {
1437 const expectFmt = testing.expectFmt;
1438 try expectFmt("", "{}", .{fmtUtf16Le(utf8ToUtf16LeStringLiteral(""))});
1439 try expectFmt("foo", "{}", .{fmtUtf16Le(utf8ToUtf16LeStringLiteral("foo"))});
1440 try expectFmt("𐐷", "{}", .{fmtUtf16Le(utf8ToUtf16LeStringLiteral("𐐷"))});
1441 try expectFmt("퟿", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\xff\xd7", native_endian)})});
1442 try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\x00\xd8", native_endian)})});
1443 try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\xff\xdb", native_endian)})});
1444 try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\x00\xdc", native_endian)})});
1445 try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\xff\xdf", native_endian)})});
1446 try expectFmt("", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\x00\xe0", native_endian)})});
11691447}
11701448
11711449test "utf8ToUtf16LeStringLiteral" {
......@@ -1248,3 +1526,535 @@ test "utf8 valid codepoint" {
12481526 try testUtf8ValidCodepoint();
12491527 try comptime testUtf8ValidCodepoint();
12501528}
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(
639639 var link_buf: [std.os.PATH_MAX]u8 = undefined;
640640 const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {
641641 error.NameTooLong => unreachable,
642 error.InvalidUtf8 => unreachable, // Windows only
642 error.InvalidUtf8 => unreachable, // WASI only
643 error.InvalidWtf8 => unreachable, // Windows only
643644 error.BadPathName => unreachable, // Windows only
644645 error.UnsupportedReparsePointType => unreachable, // Windows only
645646 error.NetworkNotFound => unreachable, // Windows only
......@@ -730,7 +731,8 @@ test glibcVerFromLinkName {
730731fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
731732 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
732733 error.NameTooLong => unreachable,
733 error.InvalidUtf8 => unreachable,
734 error.InvalidUtf8 => unreachable, // WASI only
735 error.InvalidWtf8 => unreachable, // Windows-only
734736 error.BadPathName => unreachable,
735737 error.DeviceBusy => unreachable,
736738 error.NetworkNotFound => unreachable, // Windows-only
......@@ -761,7 +763,8 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
761763 const glibc_so_basename = "libc.so.6";
762764 var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
763765 error.NameTooLong => unreachable,
764 error.InvalidUtf8 => unreachable, // Windows only
766 error.InvalidUtf8 => unreachable, // WASI only
767 error.InvalidWtf8 => unreachable, // Windows only
765768 error.BadPathName => unreachable, // Windows only
766769 error.PipeBusy => unreachable, // Windows-only
767770 error.SharingViolation => unreachable, // Windows-only
......@@ -998,7 +1001,8 @@ fn detectAbiAndDynamicLinker(
9981001 error.NameTooLong => unreachable,
9991002 error.PathAlreadyExists => unreachable,
10001003 error.SharingViolation => unreachable,
1001 error.InvalidUtf8 => unreachable,
1004 error.InvalidUtf8 => unreachable, // WASI only
1005 error.InvalidWtf8 => unreachable, // Windows only
10021006 error.BadPathName => unreachable,
10031007 error.PipeBusy => unreachable,
10041008 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 {
4141 }
4242 }
4343 } else |err| switch (err) {
44 error.InvalidUtf8 => {},
44 error.InvalidWtf8 => unreachable,
4545 error.EnvironmentVariableNotFound => {},
4646 error.OutOfMemory => |e| return e,
4747 }
......@@ -73,7 +73,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
7373 }
7474 }
7575 } else |err| switch (err) {
76 error.InvalidUtf8 => {},
76 error.InvalidWtf8 => unreachable,
7777 error.EnvironmentVariableNotFound => {},
7878 error.OutOfMemory => |e| return e,
7979 }
lib/std/zig/system/windows.zig+1-1
......@@ -160,7 +160,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
160160 => {
161161 var buf = @field(args, field.name).value_buf;
162162 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]);
164164 buf[len] = 0;
165165 },
166166
src/Module.zig+1
......@@ -2662,6 +2662,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
26622662 }) catch |err| switch (err) {
26632663 error.NotDir => unreachable, // no dir components
26642664 error.InvalidUtf8 => unreachable, // it's a hex encoded name
2665 error.InvalidWtf8 => unreachable, // it's a hex encoded name
26652666 error.BadPathName => unreachable, // it's a hex encoded name
26662667 error.NameTooLong => unreachable, // it's a fixed size name
26672668 error.PipeBusy => unreachable, // it's not a pipe
src/libc_installation.zig+8-2
......@@ -246,7 +246,10 @@ pub const LibCInstallation = struct {
246246 const allocator = args.allocator;
247247
248248 // 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 };
250253 defer env_map.deinit();
251254 const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
252255 if (std.mem.eql(u8, phase, "1")) {
......@@ -572,7 +575,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
572575 const allocator = args.allocator;
573576
574577 // 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 };
576582 defer env_map.deinit();
577583 const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
578584 if (std.mem.eql(u8, phase, "1")) {
src/main.zig+1-1
......@@ -5756,7 +5756,7 @@ fn readSourceFileToEndAlloc(
57565756 // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8
57575757 if (mem.startsWith(u8, source_code, "\xff\xfe")) {
57585758 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) {
57605760 error.DanglingSurrogateHalf => error.UnsupportedEncoding,
57615761 error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,
57625762 error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,
src/windows_sdk.zig+87-90
......@@ -84,26 +84,26 @@ fn iterateAndFilterBySemVer(
8484 return dirs_filtered_slice;
8585}
8686
87const RegistryUtf8 = struct {
87const RegistryWtf8 = struct {
8888 key: windows.HKEY,
8989
90 /// Assert that `key` is valid UTF-8 string
91 pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryUtf8 {
92 const key_utf16le: [:0]const u16 = key_utf16le: {
93 var key_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
94 const key_utf16le_len: usize = std.unicode.utf8ToUtf16Le(key_utf16le_buf[0..], key) catch |err| switch (err) {
95 error.InvalidUtf8 => unreachable,
90 /// Assert that `key` is valid WTF-8 string
91 pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryWtf8 {
92 const key_wtf16le: [:0]const u16 = key_wtf16le: {
93 var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
94 const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) {
95 error.InvalidWtf8 => unreachable,
9696 };
97 key_utf16le_buf[key_utf16le_len] = 0;
98 break :key_utf16le key_utf16le_buf[0..key_utf16le_len :0];
97 key_wtf16le_buf[key_wtf16le_len] = 0;
98 break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0];
9999 };
100100
101 const registry_utf16le = try RegistryUtf16Le.openKey(hkey, key_utf16le);
102 return RegistryUtf8{ .key = registry_utf16le.key };
101 const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le);
102 return RegistryWtf8{ .key = registry_wtf16le.key };
103103 }
104104
105105 /// Closes key, after that usage is invalid
106 pub fn closeKey(self: *const RegistryUtf8) void {
106 pub fn closeKey(self: *const RegistryWtf8) void {
107107 const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
108108 const return_code: windows.Win32Error = @enumFromInt(return_code_int);
109109 switch (return_code) {
......@@ -114,71 +114,68 @@ const RegistryUtf8 = struct {
114114
115115 /// Get string from registry.
116116 /// 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 {
118 const subkey_utf16le: [:0]const u16 = subkey_utf16le: {
119 var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
120 const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;
121 subkey_utf16le_buf[subkey_utf16le_len] = 0;
122 break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];
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_wtf16le: [:0]const u16 = subkey_wtf16le: {
119 var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
120 const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
121 subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
122 break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
123123 };
124124
125 const value_name_utf16le: [:0]const u16 = value_name_utf16le: {
126 var value_name_utf16le_buf: [RegistryUtf16Le.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;
128 value_name_utf16le_buf[value_name_utf16le_len] = 0;
129 break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];
125 const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
126 var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
127 const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
128 value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
129 break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
130130 };
131131
132 const registry_utf16le = RegistryUtf16Le{ .key = self.key };
133 const value_utf16le = try registry_utf16le.getString(allocator, subkey_utf16le, value_name_utf16le);
134 defer allocator.free(value_utf16le);
132 const registry_wtf16le = RegistryWtf16Le{ .key = self.key };
133 const value_wtf16le = try registry_wtf16le.getString(allocator, subkey_wtf16le, value_name_wtf16le);
134 defer allocator.free(value_wtf16le);
135135
136 const value_utf8: []u8 = std.unicode.utf16leToUtf8Alloc(allocator, value_utf16le) catch |err| switch (err) {
137 error.OutOfMemory => return error.OutOfMemory,
138 else => return error.StringNotFound,
139 };
140 errdefer allocator.free(value_utf8);
136 const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_wtf16le);
137 errdefer allocator.free(value_wtf8);
141138
142 return value_utf8;
139 return value_wtf8;
143140 }
144141
145142 /// 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 {
147 const subkey_utf16le: [:0]const u16 = subkey_utf16le: {
148 var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
149 const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;
150 subkey_utf16le_buf[subkey_utf16le_len] = 0;
151 break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];
143 pub fn getDword(self: *const RegistryWtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
144 const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
145 var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
146 const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
147 subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
148 break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
152149 };
153150
154 const value_name_utf16le: [:0]const u16 = value_name_utf16le: {
155 var value_name_utf16le_buf: [RegistryUtf16Le.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;
157 value_name_utf16le_buf[value_name_utf16le_len] = 0;
158 break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];
151 const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
152 var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
153 const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
154 value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
155 break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
159156 };
160157
161 const registry_utf16le = RegistryUtf16Le{ .key = self.key };
162 return try registry_utf16le.getDword(subkey_utf16le, value_name_utf16le);
158 const registry_wtf16le = RegistryWtf16Le{ .key = self.key };
159 return try registry_wtf16le.getDword(subkey_wtf16le, value_name_wtf16le);
163160 }
164161
165162 /// Under private space with flags:
166163 /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
167164 /// After finishing work, call `closeKey`.
168 pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryUtf8 {
169 const absolute_path_utf16le: [:0]const u16 = absolute_path_utf16le: {
170 var absolute_path_utf16le_buf: [RegistryUtf16Le.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;
172 absolute_path_utf16le_buf[absolute_path_utf16le_len] = 0;
173 break :absolute_path_utf16le absolute_path_utf16le_buf[0..absolute_path_utf16le_len :0];
165 pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryWtf8 {
166 const absolute_path_wtf16le: [:0]const u16 = absolute_path_wtf16le: {
167 var absolute_path_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
168 const absolute_path_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(absolute_path_wtf16le_buf[0..], absolute_path) catch unreachable;
169 absolute_path_wtf16le_buf[absolute_path_wtf16le_len] = 0;
170 break :absolute_path_wtf16le absolute_path_wtf16le_buf[0..absolute_path_wtf16le_len :0];
174171 };
175172
176 const registry_utf16le = try RegistryUtf16Le.loadFromPath(absolute_path_utf16le);
177 return RegistryUtf8{ .key = registry_utf16le.key };
173 const registry_wtf16le = try RegistryWtf16Le.loadFromPath(absolute_path_wtf16le);
174 return RegistryWtf8{ .key = registry_wtf16le.key };
178175 }
179176};
180177
181const RegistryUtf16Le = struct {
178const RegistryWtf16Le = struct {
182179 key: windows.HKEY,
183180
184181 /// Includes root key (f.e. HKEY_LOCAL_MACHINE).
......@@ -191,11 +188,11 @@ const RegistryUtf16Le = struct {
191188 /// Under HKEY_LOCAL_MACHINE with flags:
192189 /// KEY_QUERY_VALUE, KEY_WOW64_32KEY, and KEY_ENUMERATE_SUB_KEYS.
193190 /// 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 {
195192 var key: windows.HKEY = undefined;
196193 const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW(
197194 hkey,
198 key_utf16le,
195 key_wtf16le,
199196 0,
200197 windows.KEY_QUERY_VALUE | windows.KEY_WOW64_32KEY | windows.KEY_ENUMERATE_SUB_KEYS,
201198 &key,
......@@ -207,11 +204,11 @@ const RegistryUtf16Le = struct {
207204
208205 else => return error.KeyNotFound,
209206 }
210 return RegistryUtf16Le{ .key = key };
207 return RegistryWtf16Le{ .key = key };
211208 }
212209
213210 /// Closes key, after that usage is invalid
214 fn closeKey(self: *const RegistryUtf16Le) void {
211 fn closeKey(self: *const RegistryWtf16Le) void {
215212 const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
216213 const return_code: windows.Win32Error = @enumFromInt(return_code_int);
217214 switch (return_code) {
......@@ -221,25 +218,25 @@ const RegistryUtf16Le = struct {
221218 }
222219
223220 /// 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 {
225222 var actual_type: windows.ULONG = undefined;
226223
227224 // 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.
229226 var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
230227 self.key,
231 subkey_utf16le,
232 value_name_utf16le,
228 subkey_wtf16le,
229 value_name_wtf16le,
233230 RRF.RT_REG_SZ,
234231 &actual_type,
235232 null,
236 &value_utf16le_buf_size,
233 &value_wtf16le_buf_size,
237234 );
238235
239236 // Check returned code and type
240237 var return_code: windows.Win32Error = @enumFromInt(return_code_int);
241238 switch (return_code) {
242 .SUCCESS => std.debug.assert(value_utf16le_buf_size != 0),
239 .SUCCESS => std.debug.assert(value_wtf16le_buf_size != 0),
243240 .MORE_DATA => unreachable, // We are only reading length
244241 .FILE_NOT_FOUND => return error.ValueNameNotFound,
245242 .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
......@@ -250,17 +247,17 @@ const RegistryUtf16Le = struct {
250247 else => return error.NotAString,
251248 }
252249
253 const value_utf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_utf16le_buf_size, 2) catch unreachable);
254 errdefer allocator.free(value_utf16le_buf);
250 const value_wtf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable);
251 errdefer allocator.free(value_wtf16le_buf);
255252
256253 return_code_int = windows.advapi32.RegGetValueW(
257254 self.key,
258 subkey_utf16le,
259 value_name_utf16le,
255 subkey_wtf16le,
256 value_name_wtf16le,
260257 RRF.RT_REG_SZ,
261258 &actual_type,
262 value_utf16le_buf.ptr,
263 &value_utf16le_buf_size,
259 value_wtf16le_buf.ptr,
260 &value_wtf16le_buf_size,
264261 );
265262
266263 // Check returned code and (just in case) type again.
......@@ -277,28 +274,28 @@ const RegistryUtf16Le = struct {
277274 else => return error.NotAString,
278275 }
279276
280 const value_utf16le: []const u16 = value_utf16le: {
277 const value_wtf16le: []const u16 = value_wtf16le: {
281278 // note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space
282279 // we will just search for zero termination and forget length
283280 // Windows sure is strange
284 const value_utf16le_overestimated: [*:0]const u16 = @ptrCast(value_utf16le_buf.ptr);
285 break :value_utf16le std.mem.span(value_utf16le_overestimated);
281 const value_wtf16le_overestimated: [*:0]const u16 = @ptrCast(value_wtf16le_buf.ptr);
282 break :value_wtf16le std.mem.span(value_wtf16le_overestimated);
286283 };
287284
288 _ = allocator.resize(value_utf16le_buf, value_utf16le.len);
289 return value_utf16le;
285 _ = allocator.resize(value_wtf16le_buf, value_wtf16le.len);
286 return value_wtf16le;
290287 }
291288
292289 /// 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 {
294291 var actual_type: windows.ULONG = undefined;
295292 var reg_size: u32 = @sizeOf(u32);
296293 var reg_value: u32 = 0;
297294
298295 const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
299296 self.key,
300 subkey_utf16le,
301 value_name_utf16le,
297 subkey_wtf16le,
298 value_name_wtf16le,
302299 RRF.RT_REG_DWORD,
303300 &actual_type,
304301 &reg_value,
......@@ -324,11 +321,11 @@ const RegistryUtf16Le = struct {
324321 /// Under private space with flags:
325322 /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
326323 /// 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 {
328325 var key: windows.HKEY = undefined;
329326
330327 const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW(
331 absolute_path_as_utf16le,
328 absolute_path_as_wtf16le,
332329 &key,
333330 windows.KEY_QUERY_VALUE | windows.KEY_ENUMERATE_SUB_KEYS,
334331 0,
......@@ -340,7 +337,7 @@ const RegistryUtf16Le = struct {
340337 else => return error.KeyNotFound,
341338 }
342339
343 return RegistryUtf16Le{ .key = key };
340 return RegistryWtf16Le{ .key = key };
344341 }
345342};
346343
......@@ -352,7 +349,7 @@ pub const Windows10Sdk = struct {
352349 /// Caller owns the result's fields.
353350 /// After finishing work, call `free(allocator)`.
354351 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) {
356353 error.KeyNotFound => return error.Windows10SdkNotFound,
357354 };
358355 defer v10_key.closeKey();
......@@ -413,11 +410,11 @@ pub const Windows10Sdk = struct {
413410 /// Check whether this version is enumerated in registry.
414411 fn isValidVersion(windows10sdk: *const Windows10Sdk) bool {
415412 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) {
417414 error.NoSpaceLeft => return false,
418415 };
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) {
421418 error.KeyNotFound => return false,
422419 };
423420 defer options_key.closeKey();
......@@ -447,7 +444,7 @@ pub const Windows81Sdk = struct {
447444 /// Find path and version of Windows 8.1 SDK.
448445 /// Caller owns the result's fields.
449446 /// 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 {
451448 const path: []const u8 = path81: {
452449 const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", "KitsRoot81") catch |err| switch (err) {
453450 error.NotAString => return error.Windows81SdkNotFound,
......@@ -523,7 +520,7 @@ pub const ZigWindowsSDK = struct {
523520 if (builtin.os.tag != .windows) return error.NotFound;
524521
525522 //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) {
527524 error.KeyNotFound => return error.NotFound,
528525 };
529526 defer roots_key.closeKey();
......@@ -583,7 +580,7 @@ pub const ZigWindowsSDK = struct {
583580const MsvcLibDir = struct {
584581 fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir {
585582 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) {
587584 error.KeyNotFound => return error.PathNotFound,
588585 };
589586 defer setup_config_key.closeKey();
......@@ -805,13 +802,13 @@ const MsvcLibDir = struct {
805802 for (vs_versions) |vs_version| allocator.free(vs_version);
806803 allocator.free(vs_versions);
807804 }
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;
809806 const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {
810807 const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;
811808 defer allocator.free(privateregistry_absolute_path);
812809 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;
815812 defer visualstudio_registry.closeKey();
816813
817814 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 {
894891 }
895892 }
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;
898895 defer vs7_key.closeKey();
899896 try_vs7_key: {
900897 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 {
1717
1818 const tmp_absolute_path = try tmp.dir.realpathAlloc(allocator, ".");
1919 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);
2121 defer allocator.free(tmp_absolute_path_w);
2222 const cwd_absolute_path = try std.fs.cwd().realpathAlloc(allocator, ".");
2323 defer allocator.free(cwd_absolute_path);