Commit 68b87918df

Ryan Liptak <squeek502@hotmail.com>
2024-02-14 01:56:50
Fix handling of Windows (WTF-16) and WASI (UTF-8) paths
Windows paths now use WTF-16 <-> WTF-8 conversion everywhere, which is lossless. Previously, conversion of ill-formed UTF-16 paths would either fail or invoke illegal behavior. WASI paths must be valid UTF-8, and the relevant function calls have been updated to handle the possibility of failure due to paths not being encoded/encodable as valid UTF-8. Closes #18694 Closes #1774 Closes #2565
1 parent f6b6b8a
deps/aro/aro/Compilation.zig
@@ -69,7 +69,7 @@ pub const Environment = struct {
             const val: ?[]const u8 = std.process.getEnvVarOwned(allocator, env_var_name) catch |err| switch (err) {
                 error.OutOfMemory => |e| return e,
                 error.EnvironmentVariableNotFound => null,
-                error.InvalidUtf8 => null,
+                error.InvalidWtf8 => null,
             };
             @field(env, field.name) = val;
         }
deps/aro/aro/Driver.zig
@@ -523,7 +523,8 @@ pub fn errorDescription(e: anyerror) []const u8 {
         error.NotDir => "is not a directory",
         error.NotOpenForReading => "file is not open for reading",
         error.NotOpenForWriting => "file is not open for writing",
-        error.InvalidUtf8 => "input is not valid UTF-8",
+        error.InvalidUtf8 => "path is not valid UTF-8",
+        error.InvalidWtf8 => "path is not valid WTF-8",
         error.FileBusy => "file is busy",
         error.NameTooLong => "file name is too long",
         error.AccessDenied => "access denied",
lib/std/Build/Cache.zig
@@ -162,7 +162,7 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
 fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
     const relative = try std.fs.path.relative(allocator, prefix, path);
     errdefer allocator.free(relative);
-    var component_iterator = std.fs.path.NativeUtf8ComponentIterator.init(relative) catch {
+    var component_iterator = std.fs.path.NativeComponentIterator.init(relative) catch {
         return error.NotASubPath;
     };
     if (component_iterator.root() != null) {
lib/std/fs/Dir.zig
@@ -9,7 +9,14 @@ pub const Entry = struct {
     pub const Kind = File.Kind;
 };
 
-const IteratorError = error{ AccessDenied, SystemResources } || posix.UnexpectedError;
+const IteratorError = error{
+    AccessDenied,
+    SystemResources,
+    /// WASI-only. The path of an entry could not be encoded as valid UTF-8.
+    /// WASI is unable to handle paths that cannot be encoded as well-formed UTF-8.
+    /// https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
+    InvalidUtf8,
+} || posix.UnexpectedError;
 
 pub const Iterator = switch (builtin.os.tag) {
     .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {
@@ -445,13 +452,12 @@ pub const Iterator = switch (builtin.os.tag) {
                     self.index = self.buf.len;
                 }
 
-                const name_utf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
+                const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
 
-                if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' }))
+                if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' }))
                     continue;
-                // Trust that Windows gives us valid UTF-16LE
-                const name_utf8_len = std.unicode.utf16LeToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
-                const name_utf8 = self.name_data[0..name_utf8_len];
+                const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le);
+                const name_wtf8 = self.name_data[0..name_wtf8_len];
                 const kind: Entry.Kind = blk: {
                     const attrs = dir_info.FileAttributes;
                     if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
@@ -459,7 +465,7 @@ pub const Iterator = switch (builtin.os.tag) {
                     break :blk .file;
                 };
                 return Entry{
-                    .name = name_utf8,
+                    .name = name_wtf8,
                     .kind = kind,
                 };
             }
@@ -516,6 +522,7 @@ pub const Iterator = switch (builtin.os.tag) {
                         .INVAL => unreachable,
                         .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
                         .NOTCAPABLE => return error.AccessDenied,
+                        .ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
                         else => |err| return posix.unexpectedErrno(err),
                     }
                     if (bufused == 0) return null;
@@ -743,7 +750,11 @@ pub const OpenError = error{
     SystemFdQuotaExceeded,
     NoDevice,
     SystemResources,
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
     BadPathName,
     DeviceBusy,
     /// On Windows, `\\server` or `\\server\share` was not found.
@@ -759,6 +770,9 @@ pub fn close(self: *Dir) void {
 /// To create a new file, see `createFile`.
 /// Call `File.close` to release the resource.
 /// Asserts that the path parameter has no null bytes.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
     if (builtin.os.tag == .windows) {
         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
 /// Creates, opens, or overwrites a file with write access.
 /// Call `File.close` on the result when done.
 /// Asserts that the path parameter has no null bytes.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
     if (builtin.os.tag == .windows) {
         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)
 /// Creates a single directory with a relative or absolute path.
 /// To create multiple directories to make an entire path, see `makePath`.
 /// To operate on only absolute paths, see `makeDirAbsolute`.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 pub fn makeDir(self: Dir, sub_path: []const u8) !void {
     try posix.mkdirat(self.fd, sub_path, default_mode);
 }
 
-/// Creates a single directory with a relative or absolute null-terminated UTF-8-encoded path.
+/// Same as `makeDir`, but `sub_path` is null-terminated.
 /// To create multiple directories to make an entire path, see `makePath`.
 /// To operate on only absolute paths, see `makeDirAbsoluteZ`.
 pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
     try posix.mkdiratZ(self.fd, sub_path, default_mode);
 }
 
-/// Creates a single directory with a relative or absolute null-terminated WTF-16-encoded path.
+/// Creates a single directory with a relative or absolute null-terminated WTF-16 LE-encoded path.
 /// To create multiple directories to make an entire path, see `makePath`.
 /// To operate on only absolute paths, see `makeDirAbsoluteW`.
 pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
@@ -1083,6 +1103,9 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
 /// Returns success if the path already exists and is a directory.
 /// This function is not atomic, and if it returns an error, the file system may
 /// have been modified regardless.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 ///
 /// Paths containing `..` components are handled differently depending on the platform:
 /// - 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 {
     }
 }
 
-/// Calls makeOpenDirAccessMaskW iteratively to make an entire path
+/// Windows only. Calls makeOpenDirAccessMaskW iteratively to make an entire path
 /// (i.e. creating any parent directories that do not exist).
 /// Opens the dir if the path already exists and is a directory.
 /// This function is not atomic, and if it returns an error, the file system may
 /// have been modified regardless.
+/// `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
 fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
     const w = std.os.windows;
     var it = try fs.path.componentIterator(sub_path);
     // If there are no components in the path, then create a dummy component with the full path.
-    var component = it.last() orelse fs.path.NativeUtf8ComponentIterator.Component{
+    var component = it.last() orelse fs.path.NativeComponentIterator.Component{
         .name = "",
         .path = sub_path,
     };
@@ -1156,7 +1180,9 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no
 /// This function performs `makePath`, followed by `openDir`.
 /// If supported by the OS, this operation is atomic. It is not atomic on
 /// all operating systems.
-/// On Windows, this function performs `makeOpenPathAccessMaskW`.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
     return switch (builtin.os.tag) {
         .windows => {
@@ -1185,6 +1211,10 @@ pub const RealPathError = posix.RealPathError;
 /// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
 /// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
 /// argument.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 /// This function is not universally supported by all platforms.
 /// Currently supported hosts are: Linux, macOS, and Windows.
 /// 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
         error.FileLocksNotSupported => return error.Unexpected,
         error.FileBusy => return error.Unexpected,
         error.WouldBlock => return error.Unexpected,
+        error.InvalidUtf8 => unreachable, // WASI-only
         else => |e| return e,
     };
     defer posix.close(fd);
@@ -1246,7 +1277,8 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
     return result;
 }
 
-/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded.
+/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded.
+/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
 /// See also `Dir.realpath`, `realpathW`.
 pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {
     const w = std.os.windows;
@@ -1272,16 +1304,7 @@ pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathErr
     var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
     const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
     var big_out_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
-    const end_index = std.unicode.utf16leToUtf8(&big_out_buf, wide_slice) catch |e| switch (e) {
-        // TODO: Windows file paths can be arbitrary arrays of u16 values and
-        // must not fail with InvalidUtf8.
-        error.DanglingSurrogateHalf,
-        error.ExpectedSecondSurrogateHalf,
-        error.UnexpectedSecondSurrogateHalf,
-        error.CodepointTooLarge,
-        error.Utf8CannotEncodeSurrogateHalf,
-        => return error.InvalidUtf8,
-    };
+    const end_index = std.unicode.wtf16LeToWtf8(&big_out_buf, wide_slice);
     if (end_index > out_buffer.len)
         return error.NameTooLong;
     const result = out_buffer[0..end_index];
@@ -1344,6 +1367,9 @@ pub const OpenDirOptions = struct {
 /// open until `close` is called on the result.
 /// The directory cannot be iterated unless the `iterate` option is set to `true`.
 ///
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 /// Asserts that the path parameter has no null bytes.
 pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
     switch (builtin.os.tag) {
@@ -1428,7 +1454,7 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) Open
     }
 }
 
-/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
+/// Same as `openDir` except the path parameter is WTF-16 LE encoded, NT-prefixed.
 /// This function asserts the target OS is Windows.
 pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
     const w = std.os.windows;
@@ -1518,6 +1544,9 @@ fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u3
 pub const DeleteFileError = posix.UnlinkError;
 
 /// Delete a file name and possibly the file it refers to, based on an open directory handle.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 /// Asserts that the path parameter has no null bytes.
 pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
     if (builtin.os.tag == .windows) {
@@ -1553,7 +1582,7 @@ pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
     };
 }
 
-/// Same as `deleteFile` except the parameter is WTF-16 encoded.
+/// Same as `deleteFile` except the parameter is WTF-16 LE encoded.
 pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
     posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
         error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
@@ -1572,7 +1601,11 @@ pub const DeleteDirError = error{
     NotDir,
     SystemResources,
     ReadOnlyFileSystem,
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
     BadPathName,
     /// On Windows, `\\server` or `\\server\share` was not found.
     NetworkNotFound,
@@ -1581,6 +1614,9 @@ pub const DeleteDirError = error{
 
 /// Returns `error.DirNotEmpty` if the directory is not empty.
 /// To delete a directory recursively, see `deleteTree`.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 /// Asserts that the path parameter has no null bytes.
 pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
     if (builtin.os.tag == .windows) {
@@ -1605,7 +1641,7 @@ pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
     };
 }
 
-/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
+/// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed.
 /// This function is Windows-only.
 pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
     posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
@@ -1620,6 +1656,9 @@ pub const RenameError = posix.RenameError;
 /// If new_sub_path already exists, it will be replaced.
 /// Renaming a file over an existing directory or a directory
 /// over an existing file will fail with `error.IsDir` or `error.NotDir`
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
     return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
 }
@@ -1629,7 +1668,7 @@ pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]co
     return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
 }
 
-/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
+/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
 /// This function is Windows-only.
 pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
     return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
@@ -1647,6 +1686,9 @@ pub const SymLinkFlags = struct {
 /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
 /// one; the latter case is known as a dangling link.
 /// If `sym_link_path` exists, it will not be overwritten.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn symLink(
     self: Dir,
     target_path: []const u8,
@@ -1662,7 +1704,7 @@ pub fn symLink(
         // when converting to an NT namespaced path. CreateSymbolicLink in
         // symLinkW will handle the necessary conversion.
         var target_path_w: std.os.windows.PathSpace = undefined;
-        target_path_w.len = try std.unicode.utf8ToUtf16Le(&target_path_w.data, target_path);
+        target_path_w.len = try std.unicode.wtf8ToWtf16Le(&target_path_w.data, target_path);
         target_path_w.data[target_path_w.len] = 0;
         const sym_link_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sym_link_path);
         return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
@@ -1698,7 +1740,7 @@ pub fn symLinkZ(
 }
 
 /// Windows-only. Same as `symLink` except the pathname parameters
-/// are null-terminated, WTF16 encoded.
+/// are WTF16 LE encoded.
 pub fn symLinkW(
     self: Dir,
     /// WTF-16, does not need to be NT-prefixed. The NT-prefixing
@@ -1716,6 +1758,9 @@ pub const ReadLinkError = posix.ReadLinkError;
 /// Read value of a symbolic link.
 /// The return value is a slice of `buffer`, from index `0`.
 /// Asserts that the path parameter has no null bytes.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
     if (builtin.os.tag == .wasi and !builtin.link_libc) {
         return self.readLinkWasi(sub_path, buffer);
@@ -1733,7 +1778,7 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
     return posix.readlinkat(self.fd, sub_path, buffer);
 }
 
-/// Same as `readLink`, except the `pathname` parameter is null-terminated.
+/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.
 pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
     if (builtin.os.tag == .windows) {
         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 {
 }
 
 /// Windows-only. Same as `readLink` except the pathname parameter
-/// is null-terminated, WTF16 encoded.
+/// is WTF16 LE encoded.
 pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
     return std.os.windows.ReadLink(self.fd, sub_path_w, buffer);
 }
@@ -1753,6 +1798,9 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
 /// the situation is ambiguous. It could either mean that the entire file was read, and
 /// it exactly fits the buffer, or it could mean the buffer was not big enough for the
 /// entire file.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
 pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
     var file = try self.openFile(file_path, .{});
     defer file.close();
@@ -1763,6 +1811,9 @@ pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
 
 /// On success, caller owns returned buffer.
 /// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
 pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
     return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);
 }
@@ -1772,6 +1823,9 @@ pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8,
 /// If `size_hint` is specified the initial buffer size is calculated using
 /// that value, otherwise the effective file size is used instead.
 /// Allows specifying alignment and a sentinel value.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
 pub fn readFileAllocOptions(
     self: Dir,
     allocator: mem.Allocator,
@@ -1811,9 +1865,13 @@ pub const DeleteTreeError = error{
     /// This error is unreachable if `sub_path` does not contain a path separator.
     NotDir,
 
-    /// On Windows, file paths must be valid Unicode.
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
 
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
+
     /// On Windows, file paths cannot contain these characters:
     /// '/', '*', '?', '"', '<', '>', '|'
     BadPathName,
@@ -1826,6 +1884,9 @@ pub const DeleteTreeError = error{
 /// removes it. If it cannot be removed because it is a non-empty directory,
 /// this function recursively removes its entries and then tries again.
 /// This operation is not atomic on most file systems.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
     var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
 
@@ -1879,6 +1940,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
                             error.SystemResources,
                             error.Unexpected,
                             error.InvalidUtf8,
+                            error.InvalidWtf8,
                             error.BadPathName,
                             error.NetworkNotFound,
                             error.DeviceBusy,
@@ -1910,6 +1972,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
 
                         error.AccessDenied,
                         error.InvalidUtf8,
+                        error.InvalidWtf8,
                         error.SymLinkLoop,
                         error.NameTooLong,
                         error.SystemResources,
@@ -1973,6 +2036,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
                             error.SystemResources,
                             error.Unexpected,
                             error.InvalidUtf8,
+                            error.InvalidWtf8,
                             error.BadPathName,
                             error.NetworkNotFound,
                             error.DeviceBusy,
@@ -1994,6 +2058,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
 
                             error.AccessDenied,
                             error.InvalidUtf8,
+                            error.InvalidWtf8,
                             error.SymLinkLoop,
                             error.NameTooLong,
                             error.SystemResources,
@@ -2022,6 +2087,9 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
 
 /// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
 /// This is slower than `deleteTree` but uses less stack space.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
     return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
 }
@@ -2074,6 +2142,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
                             error.SystemResources,
                             error.Unexpected,
                             error.InvalidUtf8,
+                            error.InvalidWtf8,
                             error.BadPathName,
                             error.NetworkNotFound,
                             error.DeviceBusy,
@@ -2102,6 +2171,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
 
                             error.AccessDenied,
                             error.InvalidUtf8,
+                            error.InvalidWtf8,
                             error.SymLinkLoop,
                             error.NameTooLong,
                             error.SystemResources,
@@ -2171,6 +2241,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
                     error.SystemResources,
                     error.Unexpected,
                     error.InvalidUtf8,
+                    error.InvalidWtf8,
                     error.BadPathName,
                     error.DeviceBusy,
                     error.NetworkNotFound,
@@ -2189,6 +2260,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
 
                     error.AccessDenied,
                     error.InvalidUtf8,
+                    error.InvalidWtf8,
                     error.SymLinkLoop,
                     error.NameTooLong,
                     error.SystemResources,
@@ -2209,6 +2281,9 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
 pub const WriteFileError = File.WriteError || File.OpenError;
 
 /// Deprecated: use `writeFile2`.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void {
     return writeFile2(self, .{
         .sub_path = sub_path,
@@ -2218,6 +2293,9 @@ pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileErr
 }
 
 pub const WriteFileOptions = struct {
+    /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+    /// On WASI, `sub_path` should be encoded as valid UTF-8.
+    /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
     sub_path: []const u8,
     data: []const u8,
     flags: File.CreateFlags = .{},
@@ -2232,8 +2310,10 @@ pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void {
 
 pub const AccessError = posix.AccessError;
 
-/// Test accessing `path`.
-/// `path` is UTF-8-encoded.
+/// Test accessing `sub_path`.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 /// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
 /// For example, instead of testing if a file exists and then opening it, just
 /// 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
 }
 
 /// Same as `access` except asserts the target OS is Windows and the path parameter is
-/// * WTF-16 encoded
+/// * WTF-16 LE encoded
 /// * null-terminated
-/// * NtDll prefixed
+/// * relative or has the NT namespace prefix
 /// TODO currently this ignores `flags`.
 pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
     _ = flags;
@@ -2292,6 +2372,9 @@ pub const PrevStatus = enum {
 /// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
 /// Returns the previous status of the file before updating.
 /// If any of the directories do not exist for dest_path, they are created.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn updateFile(
     source_dir: Dir,
     source_path: []const u8,
@@ -2343,6 +2426,9 @@ pub const CopyFileError = File.OpenError || File.StatError ||
 /// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
 /// there is a possibility of power loss or application termination leaving temporary files present
 /// in the same directory as dest_path.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn copyFile(
     source_dir: Dir,
     source_path: []const u8,
@@ -2430,6 +2516,9 @@ pub const AtomicFileOptions = struct {
 /// Always call `AtomicFile.deinit` to clean up, regardless of whether
 /// `AtomicFile.finish` succeeded. `dest_path` must remain valid until
 /// `AtomicFile.deinit` is called.
+/// On Windows, `dest_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dest_path` should be encoded as valid UTF-8.
+/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
 pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
     if (fs.path.dirname(dest_path)) |dirname| {
         const dir = if (options.make_path)
@@ -2461,6 +2550,9 @@ pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError
 /// Symlinks are followed.
 ///
 /// `sub_path` may be absolute, in which case `self` is ignored.
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
 pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
     if (builtin.os.tag == .windows) {
         var file = try self.openFile(sub_path, .{});
lib/std/fs/File.zig
@@ -40,8 +40,11 @@ pub const OpenError = error{
     AccessDenied,
     PipeBusy,
     NameTooLong,
-    /// On Windows, file paths must be valid Unicode.
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
     /// On Windows, file paths cannot contain these characters:
     /// '/', '*', '?', '"', '<', '>', '|'
     BadPathName,
lib/std/fs/path.zig
@@ -1,3 +1,17 @@
+//! POSIX paths are arbitrary sequences of `u8` with no particular encoding.
+//!
+//! Windows paths are arbitrary sequences of `u16` (WTF-16).
+//! For cross-platform APIs that deal with sequences of `u8`, Windows
+//! paths are encoded by Zig as [WTF-8](https://simonsapin.github.io/wtf-8/).
+//! WTF-8 is a superset of UTF-8 that allows encoding surrogate codepoints,
+//! which enables lossless roundtripping when converting to/from WTF-16
+//! (as long as the WTF-8 encoded surrogate codepoints do not form a pair).
+//!
+//! WASI paths are sequences of valid Unicode scalar values,
+//! which means that WASI is unable to handle paths that cannot be
+//! encoded as well-formed UTF-8/UTF-16.
+//! https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
+
 const builtin = @import("builtin");
 const std = @import("../std.zig");
 const debug = std.debug;
@@ -438,7 +452,7 @@ fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool {
     var it1 = mem.tokenizeScalar(u8, ns1, sep1);
     var it2 = mem.tokenizeScalar(u8, ns2, sep2);
 
-    return windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?);
+    return windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?);
 }
 
 fn 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
             var it1 = mem.tokenizeScalar(u8, p1, sep1);
             var it2 = mem.tokenizeScalar(u8, p2, sep2);
 
-            return windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?) and windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?);
+            return windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?) and windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?);
         },
     }
 }
@@ -1099,7 +1113,7 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
         const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest());
         const to_rest = to_it.rest();
         if (to_it.next()) |to_component| {
-            if (windows.eqlIgnoreCaseUtf8(from_component, to_component))
+            if (windows.eqlIgnoreCaseWtf8(from_component, to_component))
                 continue;
         }
         var up_index_end = "..".len;
@@ -1564,14 +1578,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
     };
 }
 
-pub const NativeUtf8ComponentIterator = ComponentIterator(switch (native_os) {
+pub const NativeComponentIterator = ComponentIterator(switch (native_os) {
     .windows => .windows,
     .uefi => .uefi,
     else => .posix,
 }, u8);
 
-pub fn componentIterator(path: []const u8) !NativeUtf8ComponentIterator {
-    return NativeUtf8ComponentIterator.init(path);
+pub fn componentIterator(path: []const u8) !NativeComponentIterator {
+    return NativeComponentIterator.init(path);
 }
 
 test "ComponentIterator posix" {
@@ -1826,7 +1840,7 @@ test "ComponentIterator windows" {
     }
 }
 
-test "ComponentIterator windows UTF-16" {
+test "ComponentIterator windows WTF-16" {
     // TODO: Fix on big endian architectures
     if (builtin.cpu.arch.endian() != .little) {
         return error.SkipZigTest;
lib/std/fs/test.zig
@@ -26,39 +26,39 @@ const PathType = enum {
     }
 
     pub const TransformError = std.os.RealPathError || error{OutOfMemory};
-    pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8;
+    pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8;
 
     pub fn getTransformFn(comptime path_type: PathType) TransformFn {
         switch (path_type) {
             .relative => return struct {
-                fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
+                fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
                     _ = allocator;
                     _ = dir;
                     return relative_path;
                 }
             }.transform,
             .absolute => return struct {
-                fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
+                fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
                     // The final path may not actually exist which would cause realpath to fail.
                     // So instead, we get the path of the dir and join it with the relative path.
                     var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
                     const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
-                    return fs.path.join(allocator, &.{ dir_path, relative_path });
+                    return fs.path.joinZ(allocator, &.{ dir_path, relative_path });
                 }
             }.transform,
             .unc => return struct {
-                fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
+                fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
                     // Any drive absolute path (C:\foo) can be converted into a UNC path by
                     // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
                     var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
                     const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
                     const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path);
                     switch (windows_path_type) {
-                        .unc_absolute => return fs.path.join(allocator, &.{ dir_path, relative_path }),
+                        .unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }),
                         .drive_absolute => {
                             // `C:\<...>` -> `\\127.0.0.1\C$\<...>`
                             const prepended = "\\\\127.0.0.1\\";
-                            var path = try fs.path.join(allocator, &.{ prepended, dir_path, relative_path });
+                            var path = try fs.path.joinZ(allocator, &.{ prepended, dir_path, relative_path });
                             path[prepended.len + 1] = '$';
                             return path;
                         },
@@ -96,7 +96,7 @@ const TestContext = struct {
     /// Returns the `relative_path` transformed into the TestContext's `path_type`.
     /// The result is allocated by the TestContext's arena and will be free'd during
     /// `TestContext.deinit`.
-    pub fn transformPath(self: *TestContext, relative_path: []const u8) ![]const u8 {
+    pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 {
         return self.transform_fn(self.arena.allocator(), self.dir, relative_path);
     }
 };
@@ -1001,6 +1001,16 @@ test "openSelfExe" {
     self_exe_file.close();
 }
 
+test "selfExePath" {
+    if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+    var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+    const buf_self_exe_path = try std.fs.selfExePath(&buf);
+    const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator);
+    defer testing.allocator.free(alloc_self_exe_path);
+    try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path);
+}
+
 test "deleteTree does not follow symlinks" {
     var tmp = tmpDir(.{});
     defer tmp.cleanup();
@@ -1907,3 +1917,111 @@ test "delete a setAsCwd directory on Windows" {
     // Close the parent "tmp" so we don't leak the HANDLE.
     tmp.parent_dir.close();
 }
+
+test "invalid UTF-8/WTF-8 paths" {
+    const expected_err = switch (builtin.os.tag) {
+        .wasi => error.InvalidUtf8,
+        .windows => error.InvalidWtf8,
+        else => return error.SkipZigTest,
+    };
+
+    try testWithAllSupportedPathTypes(struct {
+        fn impl(ctx: *TestContext) !void {
+            // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte
+            const invalid_path = try ctx.transformPath("\xFF");
+
+            try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{}));
+            try testing.expectError(expected_err, ctx.dir.openFileZ(invalid_path, .{}));
+
+            try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{}));
+            try testing.expectError(expected_err, ctx.dir.createFileZ(invalid_path, .{}));
+
+            try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path));
+            try testing.expectError(expected_err, ctx.dir.makeDirZ(invalid_path));
+
+            try testing.expectError(expected_err, ctx.dir.makePath(invalid_path));
+            try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{}));
+
+            try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{}));
+            try testing.expectError(expected_err, ctx.dir.openDirZ(invalid_path, .{}));
+
+            try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path));
+            try testing.expectError(expected_err, ctx.dir.deleteFileZ(invalid_path));
+
+            try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path));
+            try testing.expectError(expected_err, ctx.dir.deleteDirZ(invalid_path));
+
+            try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path));
+            try testing.expectError(expected_err, ctx.dir.renameZ(invalid_path, invalid_path));
+
+            try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{}));
+            try testing.expectError(expected_err, ctx.dir.symLinkZ(invalid_path, invalid_path, .{}));
+            if (builtin.os.tag == .wasi) {
+                try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{}));
+            }
+
+            try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{}));
+            try testing.expectError(expected_err, ctx.dir.readLinkZ(invalid_path, &[_]u8{}));
+            if (builtin.os.tag == .wasi) {
+                try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{}));
+            }
+
+            try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{}));
+            try testing.expectError(expected_err, ctx.dir.readFileAlloc(testing.allocator, invalid_path, 0));
+
+            try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path));
+            try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path));
+
+            try testing.expectError(expected_err, ctx.dir.writeFile(invalid_path, ""));
+            try testing.expectError(expected_err, ctx.dir.writeFile2(.{
+                .sub_path = invalid_path,
+                .data = "",
+            }));
+
+            try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{}));
+            try testing.expectError(expected_err, ctx.dir.accessZ(invalid_path, .{}));
+
+            try testing.expectError(expected_err, ctx.dir.updateFile(invalid_path, ctx.dir, invalid_path, .{}));
+            try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{}));
+
+            try testing.expectError(expected_err, ctx.dir.statFile(invalid_path));
+
+            if (builtin.os.tag != .wasi) {
+                try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{}));
+                try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{}));
+                try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path));
+            }
+
+            try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path));
+            try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path));
+
+            if (builtin.os.tag != .wasi and ctx.path_type != .relative) {
+                try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{}));
+                try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));
+                try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));
+                try testing.expectError(expected_err, fs.makeDirAbsoluteZ(invalid_path));
+                try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path));
+                try testing.expectError(expected_err, fs.deleteDirAbsoluteZ(invalid_path));
+                try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path));
+                try testing.expectError(expected_err, fs.renameAbsoluteZ(invalid_path, invalid_path));
+                try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{}));
+                try testing.expectError(expected_err, fs.openDirAbsoluteZ(invalid_path, .{}));
+                try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{}));
+                try testing.expectError(expected_err, fs.openFileAbsoluteZ(invalid_path, .{}));
+                try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{}));
+                try testing.expectError(expected_err, fs.accessAbsoluteZ(invalid_path, .{}));
+                try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{}));
+                try testing.expectError(expected_err, fs.createFileAbsoluteZ(invalid_path, .{}));
+                try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path));
+                try testing.expectError(expected_err, fs.deleteFileAbsoluteZ(invalid_path));
+                try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path));
+                var readlink_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+                try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf));
+                try testing.expectError(expected_err, fs.readLinkAbsoluteZ(invalid_path, &readlink_buf));
+                try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{}));
+                try testing.expectError(expected_err, fs.symLinkAbsoluteZ(invalid_path, invalid_path, .{}));
+                try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path));
+            }
+        }
+    }.impl);
+}
lib/std/fs/watch.zig
@@ -1,719 +0,0 @@
-const std = @import("std");
-const builtin = @import("builtin");
-const event = std.event;
-const assert = std.debug.assert;
-const testing = std.testing;
-const os = std.os;
-const mem = std.mem;
-const windows = os.windows;
-const Loop = event.Loop;
-const fd_t = os.fd_t;
-const File = std.fs.File;
-const Allocator = mem.Allocator;
-
-const global_event_loop = Loop.instance orelse
-    @compileError("std.fs.Watch currently only works with event-based I/O");
-
-const WatchEventId = enum {
-    CloseWrite,
-    Delete,
-};
-
-const WatchEventError = error{
-    UserResourceLimitReached,
-    SystemResources,
-    AccessDenied,
-    Unexpected, // TODO remove this possibility
-};
-
-pub fn Watch(comptime V: type) type {
-    return struct {
-        channel: event.Channel(Event.Error!Event),
-        os_data: OsData,
-        allocator: Allocator,
-
-        const OsData = switch (builtin.os.tag) {
-            // TODO https://github.com/ziglang/zig/issues/3778
-            .macos, .freebsd, .netbsd, .dragonfly, .openbsd => KqOsData,
-            .linux => LinuxOsData,
-            .windows => WindowsOsData,
-
-            else => @compileError("Unsupported OS"),
-        };
-
-        const KqOsData = struct {
-            table_lock: event.Lock,
-            file_table: FileTable,
-
-            const FileTable = std.StringHashMapUnmanaged(*Put);
-            const Put = struct {
-                putter_frame: @Frame(kqPutEvents),
-                cancelled: bool = false,
-                value: V,
-            };
-        };
-
-        const WindowsOsData = struct {
-            table_lock: event.Lock,
-            dir_table: DirTable,
-            cancelled: bool = false,
-
-            const DirTable = std.StringHashMapUnmanaged(*Dir);
-            const FileTable = std.StringHashMapUnmanaged(V);
-
-            const Dir = struct {
-                putter_frame: @Frame(windowsDirReader),
-                file_table: FileTable,
-                dir_handle: os.windows.HANDLE,
-            };
-        };
-
-        const LinuxOsData = struct {
-            putter_frame: @Frame(linuxEventPutter),
-            inotify_fd: i32,
-            wd_table: WdTable,
-            table_lock: event.Lock,
-            cancelled: bool = false,
-
-            const WdTable = std.AutoHashMapUnmanaged(i32, Dir);
-            const FileTable = std.StringHashMapUnmanaged(V);
-
-            const Dir = struct {
-                dirname: []const u8,
-                file_table: FileTable,
-            };
-        };
-
-        const Self = @This();
-
-        pub const Event = struct {
-            id: Id,
-            data: V,
-            dirname: []const u8,
-            basename: []const u8,
-
-            pub const Id = WatchEventId;
-            pub const Error = WatchEventError;
-        };
-
-        pub fn init(allocator: Allocator, event_buf_count: usize) !*Self {
-            const self = try allocator.create(Self);
-            errdefer allocator.destroy(self);
-
-            switch (builtin.os.tag) {
-                .linux => {
-                    const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
-                    errdefer os.close(inotify_fd);
-
-                    self.* = Self{
-                        .allocator = allocator,
-                        .channel = undefined,
-                        .os_data = OsData{
-                            .putter_frame = undefined,
-                            .inotify_fd = inotify_fd,
-                            .wd_table = OsData.WdTable.init(allocator),
-                            .table_lock = event.Lock{},
-                        },
-                    };
-
-                    const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
-                    self.channel.init(buf);
-                    self.os_data.putter_frame = async self.linuxEventPutter();
-                    return self;
-                },
-
-                .windows => {
-                    self.* = Self{
-                        .allocator = allocator,
-                        .channel = undefined,
-                        .os_data = OsData{
-                            .table_lock = event.Lock{},
-                            .dir_table = OsData.DirTable.init(allocator),
-                        },
-                    };
-
-                    const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
-                    self.channel.init(buf);
-                    return self;
-                },
-
-                .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
-                    self.* = Self{
-                        .allocator = allocator,
-                        .channel = undefined,
-                        .os_data = OsData{
-                            .table_lock = event.Lock{},
-                            .file_table = OsData.FileTable.init(allocator),
-                        },
-                    };
-
-                    const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
-                    self.channel.init(buf);
-                    return self;
-                },
-                else => @compileError("Unsupported OS"),
-            }
-        }
-
-        pub fn deinit(self: *Self) void {
-            switch (builtin.os.tag) {
-                .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
-                    var it = self.os_data.file_table.iterator();
-                    while (it.next()) |entry| {
-                        const key = entry.key_ptr.*;
-                        const value = entry.value_ptr.*;
-                        value.cancelled = true;
-                        // @TODO Close the fd here?
-                        await value.putter_frame;
-                        self.allocator.free(key);
-                        self.allocator.destroy(value);
-                    }
-                },
-                .linux => {
-                    self.os_data.cancelled = true;
-                    {
-                        // Remove all directory watches linuxEventPutter will take care of
-                        // cleaning up the memory and closing the inotify fd.
-                        var dir_it = self.os_data.wd_table.keyIterator();
-                        while (dir_it.next()) |wd_key| {
-                            const rc = os.linux.inotify_rm_watch(self.os_data.inotify_fd, wd_key.*);
-                            // Errno can only be EBADF, EINVAL if either the inotify fs or the wd are invalid
-                            std.debug.assert(rc == 0);
-                        }
-                    }
-                    await self.os_data.putter_frame;
-                },
-                .windows => {
-                    self.os_data.cancelled = true;
-                    var dir_it = self.os_data.dir_table.iterator();
-                    while (dir_it.next()) |dir_entry| {
-                        if (windows.kernel32.CancelIoEx(dir_entry.value.dir_handle, null) != 0) {
-                            // We canceled the pending ReadDirectoryChangesW operation, but our
-                            // frame is still suspending, now waiting indefinitely.
-                            // Thus, it is safe to resume it ourslves
-                            resume dir_entry.value.putter_frame;
-                        } else {
-                            std.debug.assert(windows.kernel32.GetLastError() == .NOT_FOUND);
-                            // We are at another suspend point, we can await safely for the
-                            // function to exit the loop
-                            await dir_entry.value.putter_frame;
-                        }
-
-                        self.allocator.free(dir_entry.key_ptr.*);
-                        var file_it = dir_entry.value.file_table.keyIterator();
-                        while (file_it.next()) |file_entry| {
-                            self.allocator.free(file_entry.*);
-                        }
-                        dir_entry.value.file_table.deinit(self.allocator);
-                        self.allocator.destroy(dir_entry.value_ptr.*);
-                    }
-                    self.os_data.dir_table.deinit(self.allocator);
-                },
-                else => @compileError("Unsupported OS"),
-            }
-            self.allocator.free(self.channel.buffer_nodes);
-            self.channel.deinit();
-            self.allocator.destroy(self);
-        }
-
-        pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
-            switch (builtin.os.tag) {
-                .macos, .freebsd, .netbsd, .dragonfly, .openbsd => return addFileKEvent(self, file_path, value),
-                .linux => return addFileLinux(self, file_path, value),
-                .windows => return addFileWindows(self, file_path, value),
-                else => @compileError("Unsupported OS"),
-            }
-        }
-
-        fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
-            var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
-            const realpath = try os.realpath(file_path, &realpath_buf);
-
-            const held = self.os_data.table_lock.acquire();
-            defer held.release();
-
-            const gop = try self.os_data.file_table.getOrPut(self.allocator, realpath);
-            errdefer assert(self.os_data.file_table.remove(realpath));
-            if (gop.found_existing) {
-                const prev_value = gop.value_ptr.value;
-                gop.value_ptr.value = value;
-                return prev_value;
-            }
-
-            gop.key_ptr.* = try self.allocator.dupe(u8, realpath);
-            errdefer self.allocator.free(gop.key_ptr.*);
-            gop.value_ptr.* = try self.allocator.create(OsData.Put);
-            errdefer self.allocator.destroy(gop.value_ptr.*);
-            gop.value_ptr.* = .{
-                .putter_frame = undefined,
-                .value = value,
-            };
-
-            // @TODO Can I close this fd and get an error from bsdWaitKev?
-            const flags = if (comptime builtin.target.isDarwin()) os.O.SYMLINK | os.O.EVTONLY else 0;
-            const fd = try os.open(realpath, flags, 0);
-            gop.value_ptr.putter_frame = async self.kqPutEvents(fd, gop.key_ptr.*, gop.value_ptr.*);
-            return null;
-        }
-
-        fn kqPutEvents(self: *Self, fd: os.fd_t, file_path: []const u8, put: *OsData.Put) void {
-            global_event_loop.beginOneEvent();
-            defer {
-                global_event_loop.finishOneEvent();
-                // @TODO: Remove this if we force close otherwise
-                os.close(fd);
-            }
-
-            // We need to manually do a bsdWaitKev to access the fflags.
-            var resume_node = event.Loop.ResumeNode.Basic{
-                .base = .{
-                    .id = .Basic,
-                    .handle = @frame(),
-                    .overlapped = event.Loop.ResumeNode.overlapped_init,
-                },
-                .kev = undefined,
-            };
-
-            var kevs = [1]os.Kevent{undefined};
-            const kev = &kevs[0];
-
-            while (!put.cancelled) {
-                kev.* = os.Kevent{
-                    .ident = @as(usize, @intCast(fd)),
-                    .filter = os.EVFILT_VNODE,
-                    .flags = os.EV_ADD | os.EV_ENABLE | os.EV_CLEAR | os.EV_ONESHOT |
-                        os.NOTE_WRITE | os.NOTE_DELETE | os.NOTE_REVOKE,
-                    .fflags = 0,
-                    .data = 0,
-                    .udata = @intFromPtr(&resume_node.base),
-                };
-                suspend {
-                    global_event_loop.beginOneEvent();
-                    errdefer global_event_loop.finishOneEvent();
-
-                    const empty_kevs = &[0]os.Kevent{};
-                    _ = os.kevent(global_event_loop.os_data.kqfd, &kevs, empty_kevs, null) catch |err| switch (err) {
-                        error.EventNotFound,
-                        error.ProcessNotFound,
-                        error.Overflow,
-                        => unreachable,
-                        error.AccessDenied, error.SystemResources => |e| {
-                            self.channel.put(e);
-                            continue;
-                        },
-                    };
-                }
-
-                if (kev.flags & os.EV_ERROR != 0) {
-                    self.channel.put(os.unexpectedErrno(os.errno(kev.data)));
-                    continue;
-                }
-
-                if (kev.fflags & os.NOTE_DELETE != 0 or kev.fflags & os.NOTE_REVOKE != 0) {
-                    self.channel.put(Self.Event{
-                        .id = .Delete,
-                        .data = put.value,
-                        .dirname = std.fs.path.dirname(file_path) orelse "/",
-                        .basename = std.fs.path.basename(file_path),
-                    });
-                } else if (kev.fflags & os.NOTE_WRITE != 0) {
-                    self.channel.put(Self.Event{
-                        .id = .CloseWrite,
-                        .data = put.value,
-                        .dirname = std.fs.path.dirname(file_path) orelse "/",
-                        .basename = std.fs.path.basename(file_path),
-                    });
-                }
-            }
-        }
-
-        fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
-            const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
-            const basename = std.fs.path.basename(file_path);
-
-            const wd = try os.inotify_add_watch(
-                self.os_data.inotify_fd,
-                dirname,
-                os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_DELETE | os.linux.IN_EXCL_UNLINK,
-            );
-            // wd is either a newly created watch or an existing one.
-
-            const held = self.os_data.table_lock.acquire();
-            defer held.release();
-
-            const gop = try self.os_data.wd_table.getOrPut(self.allocator, wd);
-            errdefer assert(self.os_data.wd_table.remove(wd));
-            if (!gop.found_existing) {
-                gop.value_ptr.* = OsData.Dir{
-                    .dirname = try self.allocator.dupe(u8, dirname),
-                    .file_table = OsData.FileTable.init(self.allocator),
-                };
-            }
-
-            const dir = gop.value_ptr;
-            const file_table_gop = try dir.file_table.getOrPut(self.allocator, basename);
-            errdefer assert(dir.file_table.remove(basename));
-            if (file_table_gop.found_existing) {
-                const prev_value = file_table_gop.value_ptr.*;
-                file_table_gop.value_ptr.* = value;
-                return prev_value;
-            } else {
-                file_table_gop.key_ptr.* = try self.allocator.dupe(u8, basename);
-                file_table_gop.value_ptr.* = value;
-                return null;
-            }
-        }
-
-        fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
-            // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
-            const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
-            var dirname_path_space: windows.PathSpace = undefined;
-            dirname_path_space.len = try std.unicode.utf8ToUtf16Le(&dirname_path_space.data, dirname);
-            dirname_path_space.data[dirname_path_space.len] = 0;
-
-            const basename = std.fs.path.basename(file_path);
-            var basename_path_space: windows.PathSpace = undefined;
-            basename_path_space.len = try std.unicode.utf8ToUtf16Le(&basename_path_space.data, basename);
-            basename_path_space.data[basename_path_space.len] = 0;
-
-            const held = self.os_data.table_lock.acquire();
-            defer held.release();
-
-            const gop = try self.os_data.dir_table.getOrPut(self.allocator, dirname);
-            errdefer assert(self.os_data.dir_table.remove(dirname));
-            if (gop.found_existing) {
-                const dir = gop.value_ptr.*;
-
-                const file_gop = try dir.file_table.getOrPut(self.allocator, basename);
-                errdefer assert(dir.file_table.remove(basename));
-                if (file_gop.found_existing) {
-                    const prev_value = file_gop.value_ptr.*;
-                    file_gop.value_ptr.* = value;
-                    return prev_value;
-                } else {
-                    file_gop.value_ptr.* = value;
-                    file_gop.key_ptr.* = try self.allocator.dupe(u8, basename);
-                    return null;
-                }
-            } else {
-                const dir_handle = try windows.OpenFile(dirname_path_space.span(), .{
-                    .dir = std.fs.cwd().fd,
-                    .access_mask = windows.FILE_LIST_DIRECTORY,
-                    .creation = windows.FILE_OPEN,
-                    .io_mode = .evented,
-                    .filter = .dir_only,
-                });
-                errdefer windows.CloseHandle(dir_handle);
-
-                const dir = try self.allocator.create(OsData.Dir);
-                errdefer self.allocator.destroy(dir);
-
-                gop.key_ptr.* = try self.allocator.dupe(u8, dirname);
-                errdefer self.allocator.free(gop.key_ptr.*);
-
-                dir.* = OsData.Dir{
-                    .file_table = OsData.FileTable.init(self.allocator),
-                    .putter_frame = undefined,
-                    .dir_handle = dir_handle,
-                };
-                gop.value_ptr.* = dir;
-                try dir.file_table.put(self.allocator, try self.allocator.dupe(u8, basename), value);
-                dir.putter_frame = async self.windowsDirReader(dir, gop.key_ptr.*);
-                return null;
-            }
-        }
-
-        fn windowsDirReader(self: *Self, dir: *OsData.Dir, dirname: []const u8) void {
-            defer os.close(dir.dir_handle);
-            var resume_node = Loop.ResumeNode.Basic{
-                .base = Loop.ResumeNode{
-                    .id = .Basic,
-                    .handle = @frame(),
-                    .overlapped = windows.OVERLAPPED{
-                        .Internal = 0,
-                        .InternalHigh = 0,
-                        .DUMMYUNIONNAME = .{
-                            .DUMMYSTRUCTNAME = .{
-                                .Offset = 0,
-                                .OffsetHigh = 0,
-                            },
-                        },
-                        .hEvent = null,
-                    },
-                },
-            };
-
-            var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
-
-            global_event_loop.beginOneEvent();
-            defer global_event_loop.finishOneEvent();
-
-            while (!self.os_data.cancelled) main_loop: {
-                suspend {
-                    _ = windows.kernel32.ReadDirectoryChangesW(
-                        dir.dir_handle,
-                        &event_buf,
-                        event_buf.len,
-                        windows.FALSE, // watch subtree
-                        windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
-                            windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
-                            windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
-                            windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
-                        null, // number of bytes transferred (unused for async)
-                        &resume_node.base.overlapped,
-                        null, // completion routine - unused because we use IOCP
-                    );
-                }
-
-                var bytes_transferred: windows.DWORD = undefined;
-                if (windows.kernel32.GetOverlappedResult(
-                    dir.dir_handle,
-                    &resume_node.base.overlapped,
-                    &bytes_transferred,
-                    windows.FALSE,
-                ) == 0) {
-                    const potential_error = windows.kernel32.GetLastError();
-                    const err = switch (potential_error) {
-                        .OPERATION_ABORTED, .IO_INCOMPLETE => err_blk: {
-                            if (self.os_data.cancelled)
-                                break :main_loop
-                            else
-                                break :err_blk windows.unexpectedError(potential_error);
-                        },
-                        else => |err| windows.unexpectedError(err),
-                    };
-                    self.channel.put(err);
-                } else {
-                    var ptr: [*]u8 = &event_buf;
-                    const end_ptr = ptr + bytes_transferred;
-                    while (@intFromPtr(ptr) < @intFromPtr(end_ptr)) {
-                        const ev = @as(*const windows.FILE_NOTIFY_INFORMATION, @ptrCast(ptr));
-                        const emit = switch (ev.Action) {
-                            windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
-                            windows.FILE_ACTION_MODIFIED => .CloseWrite,
-                            else => null,
-                        };
-                        if (emit) |id| {
-                            const basename_ptr = @as([*]u16, @ptrCast(ptr + @sizeOf(windows.FILE_NOTIFY_INFORMATION)));
-                            const basename_utf16le = basename_ptr[0 .. ev.FileNameLength / 2];
-                            var basename_data: [std.fs.MAX_PATH_BYTES]u8 = undefined;
-                            const basename = basename_data[0 .. std.unicode.utf16LeToUtf8(&basename_data, basename_utf16le) catch unreachable];
-
-                            if (dir.file_table.getEntry(basename)) |entry| {
-                                self.channel.put(Event{
-                                    .id = id,
-                                    .data = entry.value_ptr.*,
-                                    .dirname = dirname,
-                                    .basename = entry.key_ptr.*,
-                                });
-                            }
-                        }
-
-                        if (ev.NextEntryOffset == 0) break;
-                        ptr = @alignCast(ptr + ev.NextEntryOffset);
-                    }
-                }
-            }
-        }
-
-        pub fn removeFile(self: *Self, file_path: []const u8) !?V {
-            switch (builtin.os.tag) {
-                .linux => {
-                    const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
-                    const basename = std.fs.path.basename(file_path);
-
-                    const held = self.os_data.table_lock.acquire();
-                    defer held.release();
-
-                    const dir = self.os_data.wd_table.get(dirname) orelse return null;
-                    if (dir.file_table.fetchRemove(basename)) |file_entry| {
-                        self.allocator.free(file_entry.key);
-                        return file_entry.value;
-                    }
-                    return null;
-                },
-                .windows => {
-                    const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
-                    const basename = std.fs.path.basename(file_path);
-
-                    const held = self.os_data.table_lock.acquire();
-                    defer held.release();
-
-                    const dir = self.os_data.dir_table.get(dirname) orelse return null;
-                    if (dir.file_table.fetchRemove(basename)) |file_entry| {
-                        self.allocator.free(file_entry.key);
-                        return file_entry.value;
-                    }
-                    return null;
-                },
-                .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
-                    var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
-                    const realpath = try os.realpath(file_path, &realpath_buf);
-
-                    const held = self.os_data.table_lock.acquire();
-                    defer held.release();
-
-                    const entry = self.os_data.file_table.getEntry(realpath) orelse return null;
-                    entry.value_ptr.cancelled = true;
-                    // @TODO Close the fd here?
-                    await entry.value_ptr.putter_frame;
-                    self.allocator.free(entry.key_ptr.*);
-                    self.allocator.destroy(entry.value_ptr.*);
-
-                    assert(self.os_data.file_table.remove(realpath));
-                },
-                else => @compileError("Unsupported OS"),
-            }
-        }
-
-        fn linuxEventPutter(self: *Self) void {
-            global_event_loop.beginOneEvent();
-
-            defer {
-                std.debug.assert(self.os_data.wd_table.count() == 0);
-                self.os_data.wd_table.deinit(self.allocator);
-                os.close(self.os_data.inotify_fd);
-                self.allocator.free(self.channel.buffer_nodes);
-                self.channel.deinit();
-                global_event_loop.finishOneEvent();
-            }
-
-            var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
-
-            while (!self.os_data.cancelled) {
-                const bytes_read = global_event_loop.read(self.os_data.inotify_fd, &event_buf, false) catch unreachable;
-
-                var ptr: [*]u8 = &event_buf;
-                const end_ptr = ptr + bytes_read;
-                while (@intFromPtr(ptr) < @intFromPtr(end_ptr)) {
-                    const ev = @as(*const os.linux.inotify_event, @ptrCast(ptr));
-                    if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
-                        const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
-                        const basename = std.mem.span(@as([*:0]u8, @ptrCast(basename_ptr)));
-
-                        const dir = &self.os_data.wd_table.get(ev.wd).?;
-                        if (dir.file_table.getEntry(basename)) |file_value| {
-                            self.channel.put(Event{
-                                .id = .CloseWrite,
-                                .data = file_value.value_ptr.*,
-                                .dirname = dir.dirname,
-                                .basename = file_value.key_ptr.*,
-                            });
-                        }
-                    } else if (ev.mask & os.linux.IN_IGNORED == os.linux.IN_IGNORED) {
-                        // Directory watch was removed
-                        const held = self.os_data.table_lock.acquire();
-                        defer held.release();
-                        if (self.os_data.wd_table.fetchRemove(ev.wd)) |wd_entry| {
-                            var file_it = wd_entry.value.file_table.keyIterator();
-                            while (file_it.next()) |file_entry| {
-                                self.allocator.free(file_entry.*);
-                            }
-                            self.allocator.free(wd_entry.value.dirname);
-                            wd_entry.value.file_table.deinit(self.allocator);
-                        }
-                    } else if (ev.mask & os.linux.IN_DELETE == os.linux.IN_DELETE) {
-                        // File or directory was removed or deleted
-                        const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
-                        const basename = std.mem.span(@as([*:0]u8, @ptrCast(basename_ptr)));
-
-                        const dir = &self.os_data.wd_table.get(ev.wd).?;
-                        if (dir.file_table.getEntry(basename)) |file_value| {
-                            self.channel.put(Event{
-                                .id = .Delete,
-                                .data = file_value.value_ptr.*,
-                                .dirname = dir.dirname,
-                                .basename = file_value.key_ptr.*,
-                            });
-                        }
-                    }
-
-                    ptr = @alignCast(ptr + @sizeOf(os.linux.inotify_event) + ev.len);
-                }
-            }
-        }
-    };
-}
-
-const test_tmp_dir = "std_event_fs_test";
-
-test "write a file, watch it, write it again, delete it" {
-    if (!std.io.is_async) return error.SkipZigTest;
-    // TODO https://github.com/ziglang/zig/issues/1908
-    if (builtin.single_threaded) return error.SkipZigTest;
-
-    try std.fs.cwd().makePath(test_tmp_dir);
-    defer std.fs.cwd().deleteTree(test_tmp_dir) catch {};
-
-    return testWriteWatchWriteDelete(std.testing.allocator);
-}
-
-fn testWriteWatchWriteDelete(allocator: Allocator) !void {
-    const file_path = try std.fs.path.join(allocator, &[_][]const u8{ test_tmp_dir, "file.txt" });
-    defer allocator.free(file_path);
-
-    const contents =
-        \\line 1
-        \\line 2
-    ;
-    const line2_offset = 7;
-
-    // first just write then read the file
-    try std.fs.cwd().writeFile(file_path, contents);
-
-    const read_contents = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
-    defer allocator.free(read_contents);
-    try testing.expectEqualSlices(u8, contents, read_contents);
-
-    // now watch the file
-    var watch = try Watch(void).init(allocator, 0);
-    defer watch.deinit();
-
-    try testing.expect((try watch.addFile(file_path, {})) == null);
-
-    var ev = async watch.channel.get();
-    var ev_consumed = false;
-    defer if (!ev_consumed) {
-        _ = await ev;
-    };
-
-    // overwrite line 2
-    const file = try std.fs.cwd().openFile(file_path, .{ .mode = .read_write });
-    {
-        defer file.close();
-        const write_contents = "lorem ipsum";
-        var iovec = [_]os.iovec_const{.{
-            .iov_base = write_contents,
-            .iov_len = write_contents.len,
-        }};
-        _ = try file.pwritevAll(&iovec, line2_offset);
-    }
-
-    switch ((try await ev).id) {
-        .CloseWrite => {
-            ev_consumed = true;
-        },
-        .Delete => @panic("wrong event"),
-    }
-
-    const contents_updated = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
-    defer allocator.free(contents_updated);
-
-    try testing.expectEqualSlices(u8,
-        \\line 1
-        \\lorem ipsum
-    , contents_updated);
-
-    ev = async watch.channel.get();
-    ev_consumed = false;
-
-    try std.fs.cwd().deleteFile(file_path);
-    switch ((try await ev).id) {
-        .Delete => {
-            ev_consumed = true;
-        },
-        .CloseWrite => @panic("wrong event"),
-    }
-}
-
-// TODO Test: Add another file watch, remove the old file watch, get an event in the new
lib/std/os/windows.zig
@@ -1,8 +1,8 @@
 //! This file contains thin wrappers around Windows-specific APIs, with these
 //! specific goals in mind:
 //! * Convert "errno"-style error codes into Zig errors.
-//! * When null-terminated or UTF16LE byte buffers are required, provide APIs which accept
-//!   slices as well as APIs which accept null-terminated UTF16LE byte buffers.
+//! * When null-terminated or WTF16LE byte buffers are required, provide APIs which accept
+//!   slices as well as APIs which accept null-terminated WTF16LE byte buffers.
 
 const builtin = @import("builtin");
 const std = @import("../std.zig");
@@ -548,7 +548,6 @@ pub fn WriteFile(
 
 pub const SetCurrentDirectoryError = error{
     NameTooLong,
-    InvalidUtf8,
     FileNotFound,
     NotDir,
     AccessDenied,
@@ -587,24 +586,24 @@ pub const GetCurrentDirectoryError = error{
 };
 
 /// The result is a slice of `buffer`, indexed from 0.
+/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
 pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
-    var utf16le_buf: [PATH_MAX_WIDE]u16 = undefined;
-    const result = kernel32.GetCurrentDirectoryW(utf16le_buf.len, &utf16le_buf);
+    var wtf16le_buf: [PATH_MAX_WIDE]u16 = undefined;
+    const result = kernel32.GetCurrentDirectoryW(wtf16le_buf.len, &wtf16le_buf);
     if (result == 0) {
         switch (kernel32.GetLastError()) {
             else => |err| return unexpectedError(err),
         }
     }
-    assert(result <= utf16le_buf.len);
-    const utf16le_slice = utf16le_buf[0..result];
-    // Trust that Windows gives us valid UTF-16LE.
+    assert(result <= wtf16le_buf.len);
+    const wtf16le_slice = wtf16le_buf[0..result];
     var end_index: usize = 0;
-    var it = std.unicode.Utf16LeIterator.init(utf16le_slice);
-    while (it.nextCodepoint() catch unreachable) |codepoint| {
+    var it = std.unicode.Wtf16LeIterator.init(wtf16le_slice);
+    while (it.nextCodepoint()) |codepoint| {
         const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable;
         if (end_index + seq_len >= buffer.len)
             return error.NameTooLong;
-        end_index += std.unicode.utf8Encode(codepoint, buffer[end_index..]) catch unreachable;
+        end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable;
     }
     return buffer[0..end_index];
 }
@@ -812,6 +811,8 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
     }
 }
 
+/// Asserts that there is enough space is `out_buffer`.
+/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
 fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {
     const win32_namespace_path = path: {
         if (is_relative) break :path path;
@@ -821,7 +822,7 @@ fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u
         };
         break :path win32_path.span();
     };
-    const out_len = std.unicode.utf16LeToUtf8(out_buffer, win32_namespace_path) catch unreachable;
+    const out_len = std.unicode.wtf16LeToWtf8(out_buffer, win32_namespace_path);
     return out_buffer[0..out_len];
 }
 
@@ -1942,13 +1943,13 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {
     if (@inComptime() or builtin.os.tag != .windows) {
         // This function compares the strings code unit by code unit (aka u16-to-u16),
         // so any length difference implies inequality. In other words, there's no possible
-        // conversion that changes the number of UTF-16 code units needed for the uppercase/lowercase
+        // conversion that changes the number of WTF-16 code units needed for the uppercase/lowercase
         // version in the conversion table since only codepoints <= max(u16) are eligible
         // for conversion at all.
         if (a.len != b.len) return false;
 
         for (a, b) |a_c, b_c| {
-            // The slices are always UTF-16 LE, so need to convert the elements to native
+            // The slices are always WTF-16 LE, so need to convert the elements to native
             // endianness for the uppercasing
             const a_c_native = std.mem.littleToNative(u16, a_c);
             const b_c_native = std.mem.littleToNative(u16, b_c);
@@ -1975,18 +1976,18 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {
     return ntdll.RtlEqualUnicodeString(&a_string, &b_string, TRUE) == TRUE;
 }
 
-/// Compares two UTF-8 strings using the equivalent functionality of
+/// Compares two WTF-8 strings using the equivalent functionality of
 /// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
 /// This function can be called on any target.
-/// Assumes `a` and `b` are valid UTF-8.
-pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
+/// Assumes `a` and `b` are valid WTF-8.
+pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
     // A length equality check is not possible here because there are
     // some codepoints that have a different length uppercase UTF-8 representations
     // than their lowercase counterparts, e.g. U+0250 (2 bytes) <-> U+2C6F (3 bytes).
     // There are 7 such codepoints in the uppercase data used by Windows.
 
-    var a_utf8_it = std.unicode.Utf8View.initUnchecked(a).iterator();
-    var b_utf8_it = std.unicode.Utf8View.initUnchecked(b).iterator();
+    var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();
+    var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator();
 
     // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
     // redundant copy of the uppercase data.
@@ -1996,8 +1997,8 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
     };
 
     while (true) {
-        const a_cp = a_utf8_it.nextCodepoint() orelse break;
-        const b_cp = b_utf8_it.nextCodepoint() orelse return false;
+        const a_cp = a_wtf8_it.nextCodepoint() orelse break;
+        const b_cp = b_wtf8_it.nextCodepoint() orelse return false;
 
         if (a_cp <= std.math.maxInt(u16) and b_cp <= std.math.maxInt(u16)) {
             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 {
         }
     }
     // Make sure there are no leftover codepoints in b
-    if (b_utf8_it.nextCodepoint() != null) return false;
+    if (b_wtf8_it.nextCodepoint() != null) return false;
 
     return true;
 }
 
 fn testEqlIgnoreCase(comptime expect_eql: bool, comptime a: []const u8, comptime b: []const u8) !void {
-    try std.testing.expectEqual(expect_eql, eqlIgnoreCaseUtf8(a, b));
+    try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWtf8(a, b));
     try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWTF16(
         std.unicode.utf8ToUtf16LeStringLiteral(a),
         std.unicode.utf8ToUtf16LeStringLiteral(b),
     ));
 
-    try comptime std.testing.expect(expect_eql == eqlIgnoreCaseUtf8(a, b));
+    try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWtf8(a, b));
     try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWTF16(
         std.unicode.utf8ToUtf16LeStringLiteral(a),
         std.unicode.utf8ToUtf16LeStringLiteral(b),
     ));
 }
 
-test "eqlIgnoreCaseWTF16/Utf8" {
+test "eqlIgnoreCaseWTF16/Wtf8" {
     try testEqlIgnoreCase(true, "\x01 a B ฮ› ษ", "\x01 A b ฮป โฑฏ");
     // does not do case-insensitive comparison for codepoints >= U+10000
     try testEqlIgnoreCase(false, "๐“", "๐“ท");
@@ -2117,20 +2118,32 @@ pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize {
     return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]);
 }
 
+pub const Wtf8ToPrefixedFileWError = error{InvalidWtf8} || Wtf16ToPrefixedFileWError;
+
 /// Same as `sliceToPrefixedFileW` but accepts a pointer
-/// to a null-terminated path.
-pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) !PathSpace {
+/// to a null-terminated WTF-8 encoded path.
+/// https://simonsapin.github.io/wtf-8/
+pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWError!PathSpace {
     return sliceToPrefixedFileW(dir, mem.sliceTo(s, 0));
 }
 
-/// Same as `wToPrefixedFileW` but accepts a UTF-8 encoded path.
-pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace {
+/// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path.
+/// https://simonsapin.github.io/wtf-8/
+pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace {
     var temp_path: PathSpace = undefined;
-    temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, path);
+    temp_path.len = try std.unicode.wtf8ToWtf16Le(&temp_path.data, path);
     temp_path.data[temp_path.len] = 0;
     return wToPrefixedFileW(dir, temp_path.span());
 }
 
+pub const Wtf16ToPrefixedFileWError = error{
+    AccessDenied,
+    BadPathName,
+    FileNotFound,
+    NameTooLong,
+    Unexpected,
+};
+
 /// Converts the `path` to WTF16, null-terminated. If the path contains any
 /// namespace prefix, or is anything but a relative path (rooted, drive relative,
 /// etc) the result will have the NT-style prefix `\??\`.
@@ -2142,7 +2155,7 @@ pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace {
 ///   is non-null, or the CWD if it is null.
 /// - Special case device names like COM1, NUL, etc are not handled specially (TODO)
 /// - . and space are not stripped from the end of relative paths (potential TODO)
-pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) !PathSpace {
+pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!PathSpace {
     const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };
     switch (getNamespacePrefix(u16, path)) {
         // TODO: Figure out a way to design an API that can avoid the copy for .nt,
@@ -2312,7 +2325,7 @@ pub const NamespacePrefix = enum {
     nt,
 };
 
-/// If `T` is `u16`, then `path` should be encoded as UTF-16LE.
+/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
 pub fn getNamespacePrefix(comptime T: type, path: []const T) NamespacePrefix {
     if (path.len < 4) return .none;
     var all_backslash = switch (mem.littleToNative(T, path[0])) {
@@ -2366,7 +2379,7 @@ pub const UnprefixedPathType = enum {
 
 /// Get the path type of a path that is known to not have any namespace prefixes
 /// (`\\?\`, `\\.\`, `\??\`).
-/// If `T` is `u16`, then `path` should be encoded as UTF-16LE.
+/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
 pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathType {
     if (path.len < 1) return .relative;
 
@@ -2420,7 +2433,7 @@ test getUnprefixedPathType {
 /// Functionality is based on the ReactOS test cases found here:
 /// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c
 ///
-/// `path` should be encoded as UTF-16LE.
+/// `path` should be encoded as WTF-16LE.
 pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
     if (path.len > PATH_MAX_WIDE) return error.NameTooLong;
 
@@ -2530,7 +2543,6 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {
     if (std.os.unexpected_error_tracing) {
         // 614 is the length of the longest windows error description
         var buf_wstr: [614]WCHAR = undefined;
-        var buf_utf8: [614]u8 = undefined;
         const len = kernel32.FormatMessageW(
             FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
             null,
@@ -2540,8 +2552,10 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {
             buf_wstr.len,
             null,
         );
-        _ = std.unicode.utf16LeToUtf8(&buf_utf8, buf_wstr[0..len]) catch unreachable;
-        std.debug.print("error.Unexpected: GetLastError({}): {s}\n", .{ @intFromEnum(err), buf_utf8[0..len] });
+        std.debug.print("error.Unexpected: GetLastError({}): {}\n", .{
+            @intFromEnum(err),
+            std.unicode.fmtUtf16Le(buf_wstr[0..len]),
+        });
         std.debug.dumpCurrentStackTrace(@returnAddress());
     }
     return error.Unexpected;
lib/std/zig/system/NativePaths.zig
@@ -41,7 +41,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
             }
         }
     } else |err| switch (err) {
-        error.InvalidUtf8 => {},
+        error.InvalidWtf8 => unreachable,
         error.EnvironmentVariableNotFound => {},
         error.OutOfMemory => |e| return e,
     }
@@ -73,7 +73,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
             }
         }
     } else |err| switch (err) {
-        error.InvalidUtf8 => {},
+        error.InvalidWtf8 => unreachable,
         error.EnvironmentVariableNotFound => {},
         error.OutOfMemory => |e| return e,
     }
lib/std/zig/system.zig
@@ -639,7 +639,8 @@ pub fn abiAndDynamicLinkerFromFile(
             var link_buf: [std.os.PATH_MAX]u8 = undefined;
             const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {
                 error.NameTooLong => unreachable,
-                error.InvalidUtf8 => unreachable, // Windows only
+                error.InvalidUtf8 => unreachable, // WASI only
+                error.InvalidWtf8 => unreachable, // Windows only
                 error.BadPathName => unreachable, // Windows only
                 error.UnsupportedReparsePointType => unreachable, // Windows only
                 error.NetworkNotFound => unreachable, // Windows only
@@ -730,7 +731,8 @@ test glibcVerFromLinkName {
 fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
     var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
         error.NameTooLong => unreachable,
-        error.InvalidUtf8 => unreachable,
+        error.InvalidUtf8 => unreachable, // WASI only
+        error.InvalidWtf8 => unreachable, // Windows-only
         error.BadPathName => unreachable,
         error.DeviceBusy => unreachable,
         error.NetworkNotFound => unreachable, // Windows-only
@@ -761,7 +763,8 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
     const glibc_so_basename = "libc.so.6";
     var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
         error.NameTooLong => unreachable,
-        error.InvalidUtf8 => unreachable, // Windows only
+        error.InvalidUtf8 => unreachable, // WASI only
+        error.InvalidWtf8 => unreachable, // Windows only
         error.BadPathName => unreachable, // Windows only
         error.PipeBusy => unreachable, // Windows-only
         error.SharingViolation => unreachable, // Windows-only
@@ -998,7 +1001,8 @@ fn detectAbiAndDynamicLinker(
                 error.NameTooLong => unreachable,
                 error.PathAlreadyExists => unreachable,
                 error.SharingViolation => unreachable,
-                error.InvalidUtf8 => unreachable,
+                error.InvalidUtf8 => unreachable, // WASI only
+                error.InvalidWtf8 => unreachable, // Windows only
                 error.BadPathName => unreachable,
                 error.PipeBusy => unreachable,
                 error.FileLocksNotSupported => unreachable,
lib/std/child_process.zig
@@ -129,10 +129,9 @@ pub const ChildProcess = struct {
         /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.
         NoDevice,
 
-        /// Windows-only. One of:
-        /// * `cwd` was provided and it could not be re-encoded into UTF16LE, or
-        /// * The `PATH` or `PATHEXT` environment variable contained invalid UTF-8.
-        InvalidUtf8,
+        /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8.
+        /// https://simonsapin.github.io/wtf-8/
+        InvalidWtf8,
 
         /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
         CurrentWorkingDirectoryUnlinked,
@@ -767,7 +766,7 @@ pub const ChildProcess = struct {
         };
         var piProcInfo: windows.PROCESS_INFORMATION = undefined;
 
-        const cwd_w = if (self.cwd) |cwd| try unicode.utf8ToUtf16LeAllocZ(self.allocator, cwd) else null;
+        const cwd_w = if (self.cwd) |cwd| try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd) else null;
         defer if (cwd_w) |cwd| self.allocator.free(cwd);
         const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
 
@@ -775,8 +774,8 @@ pub const ChildProcess = struct {
         defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
         const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
 
-        const app_name_utf8 = self.argv[0];
-        const app_name_is_absolute = fs.path.isAbsolute(app_name_utf8);
+        const app_name_wtf8 = self.argv[0];
+        const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8);
 
         // the cwd set in ChildProcess is in effect when choosing the executable path
         // to match posix semantics
@@ -785,11 +784,11 @@ pub const ChildProcess = struct {
             // If the app name is absolute, then we need to use its dirname as the cwd
             if (app_name_is_absolute) {
                 cwd_path_w_needs_free = true;
-                const dir = fs.path.dirname(app_name_utf8).?;
-                break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, dir);
+                const dir = fs.path.dirname(app_name_wtf8).?;
+                break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, dir);
             } else if (self.cwd) |cwd| {
                 cwd_path_w_needs_free = true;
-                break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, cwd);
+                break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd);
             } else {
                 break :x &[_:0]u16{}; // empty for cwd
             }
@@ -800,19 +799,19 @@ pub const ChildProcess = struct {
         // into the basename and dirname and use the dirname as an addition to the cwd
         // path. This is because NtQueryDirectoryFile cannot accept FileName params with
         // path separators.
-        const app_basename_utf8 = fs.path.basename(app_name_utf8);
+        const app_basename_wtf8 = fs.path.basename(app_name_wtf8);
         // If the app name is absolute, then the cwd will already have the app's dirname in it,
         // so only populate app_dirname if app name is a relative path with > 0 path separators.
-        const maybe_app_dirname_utf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_utf8) else null;
+        const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_wtf8) else null;
         const app_dirname_w: ?[:0]u16 = x: {
-            if (maybe_app_dirname_utf8) |app_dirname_utf8| {
-                break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, app_dirname_utf8);
+            if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
+                break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_dirname_wtf8);
             }
             break :x null;
         };
         defer if (app_dirname_w != null) self.allocator.free(app_dirname_w.?);
 
-        const app_name_w = try unicode.utf8ToUtf16LeAllocZ(self.allocator, app_basename_utf8);
+        const app_name_w = try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_basename_wtf8);
         defer self.allocator.free(app_name_w);
 
         const cmd_line_w = argvToCommandLineWindows(self.allocator, self.argv) catch |err| switch (err) {
@@ -1173,7 +1172,7 @@ const CreateProcessSupportedExtension = enum {
     exe,
 };
 
-/// Case-insensitive UTF-16 lookup
+/// Case-insensitive WTF-16 lookup
 fn windowsCreateProcessSupportsExtension(ext: []const u16) ?CreateProcessSupportedExtension {
     if (ext.len != 4) return null;
     const State = enum {
@@ -1237,7 +1236,7 @@ test "windowsCreateProcessSupportsExtension" {
     try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null);
 }
 
-pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidUtf8, InvalidArg0 };
+pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
 
 /// Serializes `argv` to a Windows command-line string suitable for passing to a child process and
 /// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice.
@@ -1320,7 +1319,7 @@ pub fn argvToCommandLineWindows(
         }
     }
 
-    return try unicode.utf8ToUtf16LeAllocZ(allocator, buf.items);
+    return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
 }
 
 test "argvToCommandLineWindows" {
@@ -1386,7 +1385,7 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c
     const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv);
     defer std.testing.allocator.free(cmd_line_w);
 
-    const cmd_line = try unicode.utf16LeToUtf8Alloc(std.testing.allocator, cmd_line_w);
+    const cmd_line = try unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w);
     defer std.testing.allocator.free(cmd_line);
 
     try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
@@ -1424,7 +1423,7 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons
             "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
             .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .Monotonic) },
         ) catch unreachable;
-        const len = std.unicode.utf8ToUtf16Le(&tmp_bufw, pipe_path) catch unreachable;
+        const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
         tmp_bufw[len] = 0;
         break :blk tmp_bufw[0..len :0];
     };
@@ -1521,10 +1520,10 @@ pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) !
     var it = env_map.iterator();
     var i: usize = 0;
     while (it.next()) |pair| {
-        i += try unicode.utf8ToUtf16Le(result[i..], pair.key_ptr.*);
+        i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
         result[i] = '=';
         i += 1;
-        i += try unicode.utf8ToUtf16Le(result[i..], pair.value_ptr.*);
+        i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
         result[i] = 0;
         i += 1;
     }
lib/std/fs.zig
@@ -31,18 +31,21 @@ pub const realpathW = os.realpathW;
 pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
 pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
 
-/// This represents the maximum size of a UTF-8 encoded file path that the
+/// This represents the maximum size of a `[]u8` file path that the
 /// operating system will accept. Paths, including those returned from file
 /// system operations, may be longer than this length, but such paths cannot
 /// be successfully passed back in other file system operations. However,
 /// all path components returned by file system operations are assumed to
-/// fit into a UTF-8 encoded array of this length.
+/// fit into a `u8` array of this length.
 /// The byte count includes room for a null sentinel byte.
+/// On Windows, `[]u8` file paths are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `[]u8` file paths are encoded as valid UTF-8.
+/// On other platforms, `[]u8` file paths are opaque sequences of bytes with no particular encoding.
 pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
     .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten => os.PATH_MAX,
-    // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
-    // If it would require 4 UTF-8 bytes, then there would be a surrogate
-    // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
+    // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes.
+    // If it would require 4 WTF-8 bytes, then there would be a surrogate
+    // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
     // +1 for the null byte at the end, which can be encoded in 1 byte.
     .windows => os.windows.PATH_MAX_WIDE * 3 + 1,
     // TODO work out what a reasonable value we should use here
@@ -53,18 +56,21 @@ pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
         @compileError("PATH_MAX not implemented for " ++ @tagName(builtin.os.tag)),
 };
 
-/// This represents the maximum size of a UTF-8 encoded file name component that
+/// This represents the maximum size of a `[]u8` file name component that
 /// the platform's common file systems support. File name components returned by file system
-/// operations are likely to fit into a UTF-8 encoded array of this length, but
+/// operations are likely to fit into a `u8` array of this length, but
 /// (depending on the platform) this assumption may not hold for every configuration.
 /// The byte count does not include a null sentinel byte.
+/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, file name components are encoded as valid UTF-8.
+/// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding.
 pub const MAX_NAME_BYTES = switch (builtin.os.tag) {
     .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos => os.NAME_MAX,
     // Haiku's NAME_MAX includes the null terminator, so subtract one.
     .haiku => os.NAME_MAX - 1,
-    // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
-    // If it would require 4 UTF-8 bytes, then there would be a surrogate
-    // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
+    // Each WTF-16LE character may be expanded to 3 WTF-8 bytes.
+    // If it would require 4 WTF-8 bytes, then there would be a surrogate
+    // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
     .windows => os.windows.NAME_MAX * 3,
     // For WASI, the MAX_NAME will depend on the host OS, so it needs to be
     // 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);
 
 /// TODO remove the allocator requirement from this API
 /// TODO move to Dir
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
     if (cwd().symLink(existing_path, new_path, .{})) {
         return;
@@ -117,6 +126,9 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:
 /// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`
 /// are absolute. See `Dir.updateFile` for a function that operates on both
 /// absolute and relative paths.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn updateFileAbsolute(
     source_path: []const u8,
     dest_path: []const u8,
@@ -131,6 +143,9 @@ pub fn updateFileAbsolute(
 /// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
 /// are absolute. See `Dir.copyFile` for a function that operates on both
 /// absolute and relative paths.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn copyFileAbsolute(
     source_path: []const u8,
     dest_path: []const u8,
@@ -145,24 +160,30 @@ pub fn copyFileAbsolute(
 /// Create a new directory, based on an absolute path.
 /// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
 /// on both absolute and relative paths.
+/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `absolute_path` should be encoded as valid UTF-8.
+/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
 pub fn makeDirAbsolute(absolute_path: []const u8) !void {
     assert(path.isAbsolute(absolute_path));
     return os.mkdir(absolute_path, Dir.default_mode);
 }
 
-/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF-8-encoded string.
+/// Same as `makeDirAbsolute` except the parameter is null-terminated.
 pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
     assert(path.isAbsoluteZ(absolute_path_z));
     return os.mkdirZ(absolute_path_z, Dir.default_mode);
 }
 
-/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16-encoded string.
+/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.
 pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
     assert(path.isAbsoluteWindowsW(absolute_path_w));
     return os.mkdirW(absolute_path_w, Dir.default_mode);
 }
 
 /// Same as `Dir.deleteDir` except the path is absolute.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
 pub fn deleteDirAbsolute(dir_path: []const u8) !void {
     assert(path.isAbsolute(dir_path));
     return os.rmdir(dir_path);
@@ -181,6 +202,9 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {
 }
 
 /// Same as `Dir.rename` except the paths are absolute.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
     assert(path.isAbsolute(old_path));
     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
     return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
 }
 
-/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
+/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
 /// This function is Windows-only.
 pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
     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 {
 /// See `openDirAbsoluteZ` for a function that accepts a null-terminated path.
 ///
 /// Asserts that the path parameter has no null bytes.
+/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `absolute_path` should be encoded as valid UTF-8.
+/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
 pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
     assert(path.isAbsolute(absolute_path));
     return cwd().openDir(absolute_path, flags);
@@ -262,6 +289,9 @@ pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptio
 /// operates on both absolute and relative paths.
 /// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function
 /// that accepts a null-terminated path.
+/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `absolute_path` should be encoded as valid UTF-8.
+/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
 pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
     assert(path.isAbsolute(absolute_path));
     return cwd().openFile(absolute_path, flags);
@@ -280,11 +310,13 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
 }
 
 /// Test accessing `path`.
-/// `path` is UTF-8-encoded.
 /// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
 /// For example, instead of testing if a file exists and then opening it, just
 /// open it and handle the error for file not found.
 /// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
+/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `absolute_path` should be encoded as valid UTF-8.
+/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
 pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
     assert(path.isAbsolute(absolute_path));
     try cwd().access(absolute_path, flags);
@@ -306,6 +338,9 @@ pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir
 /// operates on both absolute and relative paths.
 /// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function
 /// that accepts a null-terminated path.
+/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `absolute_path` should be encoded as valid UTF-8.
+/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
 pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
     assert(path.isAbsolute(absolute_path));
     return cwd().createFile(absolute_path, flags);
@@ -327,6 +362,9 @@ pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFl
 /// Asserts that the path is absolute. See `Dir.deleteFile` for a function that
 /// operates on both absolute and relative paths.
 /// Asserts that the path parameter has no null bytes.
+/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `absolute_path` should be encoded as valid UTF-8.
+/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
 pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
     assert(path.isAbsolute(absolute_path));
     return cwd().deleteFile(absolute_path);
@@ -349,6 +387,9 @@ pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!
 /// Asserts that the path is absolute. See `Dir.deleteTree` for a function that
 /// operates on both absolute and relative paths.
 /// Asserts that the path parameter has no null bytes.
+/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `absolute_path` should be encoded as valid UTF-8.
+/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
 pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
     assert(path.isAbsolute(absolute_path));
     const dirname = path.dirname(absolute_path) orelse return error{
@@ -364,6 +405,9 @@ pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
 }
 
 /// Same as `Dir.readLink`, except it asserts the path is absolute.
+/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `pathname` should be encoded as valid UTF-8.
+/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
 pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
     assert(path.isAbsolute(pathname));
     return os.readlink(pathname, buffer);
@@ -387,6 +431,9 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8)
 /// one; the latter case is known as a dangling link.
 /// If `sym_link_path` exists, it will not be overwritten.
 /// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn symLinkAbsolute(
     target_path: []const u8,
     sym_link_path: []const u8,
@@ -402,7 +449,7 @@ pub fn symLinkAbsolute(
     return os.symlink(target_path, sym_link_path);
 }
 
-/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 encoded.
+/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 LE encoded.
 /// Note that this function will by default try creating a symbolic link to a file. If you would
 /// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
 /// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
@@ -426,27 +473,14 @@ pub fn symLinkAbsoluteZ(
     assert(path.isAbsoluteZ(target_path_c));
     assert(path.isAbsoluteZ(sym_link_path_c));
     if (builtin.os.tag == .windows) {
-        const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
-        const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
-        return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
+        const target_path_w = try os.windows.cStrToPrefixedFileW(null, target_path_c);
+        const sym_link_path_w = try os.windows.cStrToPrefixedFileW(null, sym_link_path_c);
+        return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
     }
     return os.symlinkZ(target_path_c, sym_link_path_c);
 }
 
-pub const OpenSelfExeError = error{
-    SharingViolation,
-    PathAlreadyExists,
-    FileNotFound,
-    AccessDenied,
-    PipeBusy,
-    NameTooLong,
-    /// On Windows, file paths must be valid Unicode.
-    InvalidUtf8,
-    /// On Windows, file paths cannot contain these characters:
-    /// '/', '*', '?', '"', '<', '>', '|'
-    BadPathName,
-    Unexpected,
-} || os.OpenError || SelfExePathError || os.FlockError;
+pub const OpenSelfExeError = os.OpenError || SelfExePathError || os.FlockError;
 
 pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
     if (builtin.os.tag == .linux) {
@@ -469,7 +503,45 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
     return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags);
 }
 
-pub const SelfExePathError = os.ReadLinkError || os.SysCtlError || os.RealPathError;
+// This is os.ReadLinkError || os.RealPathError with impossible errors excluded
+pub const SelfExePathError = error{
+    FileNotFound,
+    AccessDenied,
+    NameTooLong,
+    NotSupported,
+    NotDir,
+    SymLinkLoop,
+    InputOutput,
+    FileTooBig,
+    IsDir,
+    ProcessFdQuotaExceeded,
+    SystemFdQuotaExceeded,
+    NoDevice,
+    SystemResources,
+    NoSpaceLeft,
+    FileSystem,
+    BadPathName,
+    DeviceBusy,
+    SharingViolation,
+    PipeBusy,
+    NotLink,
+    PathAlreadyExists,
+    InvalidHandle,
+
+    /// On Windows, `\\server` or `\\server\share` was not found.
+    NetworkNotFound,
+
+    /// On Windows, antivirus software is enabled by default. It can be
+    /// disabled, but Windows Update sometimes ignores the user's preference
+    /// and re-enables it. When enabled, antivirus software on Windows
+    /// intercepts file system operations and makes them significantly slower
+    /// in addition to possibly failing with this error code.
+    AntivirusInterference,
+
+    /// On Windows, the volume does not contain a recognized file system. File
+    /// system drivers might not be loaded, or the volume may be corrupt.
+    UnrecognizedVolume,
+} || os.SysCtlError;
 
 /// `selfExePath` except allocates the result on the heap.
 /// Caller owns returned memory.
@@ -491,6 +563,8 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {
 /// This function may return an error if the current executable
 /// was deleted after spawning.
 /// Returned value is a slice of out_buffer.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 ///
 /// On Linux, depends on procfs being mounted. If the currently executing binary has
 /// 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 {
         if (rc != 0) return error.NameTooLong;
 
         var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
-        const real_path = try std.os.realpathZ(&symlink_path_buf, &real_path_buf);
+        const real_path = std.os.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) {
+            error.InvalidWtf8 => unreachable, // Windows-only
+            error.NetworkNotFound => unreachable, // Windows-only
+            else => |e| return e,
+        };
         if (real_path.len > out_buffer.len) return error.NameTooLong;
         const result = out_buffer[0..real_path.len];
         @memcpy(result, real_path);
         return result;
     }
     switch (builtin.os.tag) {
-        .linux => return os.readlinkZ("/proc/self/exe", out_buffer),
-        .solaris, .illumos => return os.readlinkZ("/proc/self/path/a.out", out_buffer),
+        .linux => return os.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) {
+            error.InvalidUtf8 => unreachable, // WASI-only
+            error.InvalidWtf8 => unreachable, // Windows-only
+            error.UnsupportedReparsePointType => unreachable, // Windows-only
+            error.NetworkNotFound => unreachable, // Windows-only
+            else => |e| return e,
+        },
+        .solaris, .illumos => return os.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) {
+            error.InvalidUtf8 => unreachable, // WASI-only
+            error.InvalidWtf8 => unreachable, // Windows-only
+            error.UnsupportedReparsePointType => unreachable, // Windows-only
+            error.NetworkNotFound => unreachable, // Windows-only
+            else => |e| return e,
+        },
         .freebsd, .dragonfly => {
             var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC, os.KERN.PROC_PATHNAME, -1 };
             var out_len: usize = out_buffer.len;
@@ -537,7 +627,11 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
             if (mem.indexOf(u8, argv0, "/") != null) {
                 // argv[0] is a path (relative or absolute): use realpath(3) directly
                 var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
-                const real_path = try os.realpathZ(os.argv[0], &real_path_buf);
+                const real_path = os.realpathZ(os.argv[0], &real_path_buf) catch |err| switch (err) {
+                    error.InvalidWtf8 => unreachable, // Windows-only
+                    error.NetworkNotFound => unreachable, // Windows-only
+                    else => |e| return e,
+                };
                 if (real_path.len > out_buffer.len)
                     return error.NameTooLong;
                 const result = out_buffer[0..real_path.len];
@@ -575,7 +669,10 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
             // symlink, not the path that the symlink points to. We want the path
             // that the symlink points to, though, so we need to get the realpath.
             const pathname_w = try os.windows.wToPrefixedFileW(null, image_path_name);
-            return std.fs.cwd().realpathW(pathname_w.span(), out_buffer);
+            return std.fs.cwd().realpathW(pathname_w.span(), out_buffer) catch |err| switch (err) {
+                error.InvalidWtf8 => unreachable,
+                else => |e| return e,
+            };
         },
         else => @compileError("std.fs.selfExePath not supported for this target"),
     }
@@ -599,6 +696,8 @@ pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 {
 
 /// Get the directory path that contains the current executable.
 /// Returned value is a slice of out_buffer.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
     const self_exe_path = try selfExePath(out_buffer);
     // Assume that the OS APIs return absolute paths, and therefore dirname
@@ -607,6 +706,8 @@ pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
 }
 
 /// `realpath`, except caller must free the returned memory.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 /// See also `Dir.realpath`.
 pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
     // Use of MAX_PATH_BYTES here is valid as the realpath function does not
lib/std/os.zig
@@ -3,7 +3,7 @@
 //! * Convert "errno"-style error codes into Zig errors.
 //! * When null-terminated byte buffers are required, provide APIs which accept
 //!   slices as well as APIs which accept null-terminated byte buffers. Same goes
-//!   for UTF-16LE encoding.
+//!   for WTF-16LE encoding.
 //! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
 //!   cross platform abstracting.
 //! * 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
     const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {
         error.NameTooLong => unreachable,
         error.FileNotFound => unreachable,
+        error.InvalidUtf8 => unreachable,
         else => |e| return e,
     };
     if ((stat.mode & S.IFMT) == S.IFLNK)
@@ -1614,9 +1615,16 @@ pub const OpenError = error{
     /// The underlying filesystem does not support file locks
     FileLocksNotSupported,
 
+    /// Path contains characters that are disallowed by the underlying filesystem.
     BadPathName,
+
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
 
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
+
     /// On Windows, `\\server` or `\\server\share` was not found.
     NetworkNotFound,
 
@@ -1634,6 +1642,9 @@ pub const OpenError = error{
 } || UnexpectedError;
 
 /// Open and possibly create a file. Keeps trying if it gets interrupted.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
 /// See also `openZ`.
 pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
     if (builtin.os.tag == .windows) {
@@ -1646,6 +1657,9 @@ pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
 }
 
 /// Open and possibly create a file. Keeps trying if it gets interrupted.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
 /// See also `open`.
 pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
     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 {
 
 /// Open and possibly create a file. Keeps trying if it gets interrupted.
 /// `file_path` is relative to the open directory handle `dir_fd`.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
 /// See also `openatZ`.
 pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t {
     if (builtin.os.tag == .windows) {
@@ -1829,6 +1846,7 @@ pub fn openatWasi(
             .EXIST => return error.PathAlreadyExists,
             .BUSY => return error.DeviceBusy,
             .NOTCAPABLE => return error.AccessDenied,
+            .ILSEQ => return error.InvalidUtf8,
             else => |err| return unexpectedErrno(err),
         }
     }
@@ -1836,6 +1854,9 @@ pub fn openatWasi(
 
 /// Open and possibly create a file. Keeps trying if it gets interrupted.
 /// `file_path` is relative to the open directory handle `dir_fd`.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
 /// See also `openat`.
 pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t {
     if (builtin.os.tag == .windows) {
@@ -2156,13 +2177,23 @@ pub const SymLinkError = error{
     ReadOnlyFileSystem,
     NotDir,
     NameTooLong,
+
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
+
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
+
     BadPathName,
 } || UnexpectedError;
 
 /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
 /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
 /// one; the latter case is known as a dangling link.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 /// If `sym_link_path` exists, it will not be overwritten.
 /// See also `symlinkZ.
 pub 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
         .NOMEM => return error.SystemResources,
         .NOSPC => return error.NoSpaceLeft,
         .ROFS => return error.ReadOnlyFileSystem,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
@@ -2208,6 +2243,9 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
 /// `target_path` **relative** to `newdirfd` directory handle.
 /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
 /// one; the latter case is known as a dangling link.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 /// If `sym_link_path` exists, it will not be overwritten.
 /// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
 pub 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
         .NOSPC => return error.NoSpaceLeft,
         .ROFS => return error.ReadOnlyFileSystem,
         .NOTCAPABLE => return error.AccessDenied,
+        .ILSEQ => return error.InvalidUtf8,
         else => |err| return unexpectedErrno(err),
     }
 }
@@ -2270,6 +2309,10 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:
         .NOMEM => return error.SystemResources,
         .NOSPC => return error.NoSpaceLeft,
         .ROFS => return error.ReadOnlyFileSystem,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
@@ -2287,8 +2330,13 @@ pub const LinkError = UnexpectedError || error{
     NoSpaceLeft,
     ReadOnlyFileSystem,
     NotSameFileSystem,
+
+    /// WASI-only; file paths must be valid UTF-8.
+    InvalidUtf8,
 };
 
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {
     if (builtin.os.tag == .wasi and !builtin.link_libc) {
         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
         .ROFS => return error.ReadOnlyFileSystem,
         .XDEV => return error.NotSameFileSystem,
         .INVAL => unreachable,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
 
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {
     if (builtin.os.tag == .wasi and !builtin.link_libc) {
         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
 
 pub const LinkatError = LinkError || error{NotDir};
 
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn linkatZ(
     olddir: fd_t,
     oldpath: [*:0]const u8,
@@ -2356,10 +2412,16 @@ pub fn linkatZ(
         .ROFS => return error.ReadOnlyFileSystem,
         .XDEV => return error.NotSameFileSystem,
         .INVAL => unreachable,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
 
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn linkat(
     olddir: fd_t,
     oldpath: []const u8,
@@ -2399,6 +2461,7 @@ pub fn linkat(
             .ROFS => return error.ReadOnlyFileSystem,
             .XDEV => return error.NotSameFileSystem,
             .INVAL => unreachable,
+            .ILSEQ => return error.InvalidUtf8,
             else => |err| return unexpectedErrno(err),
         }
     }
@@ -2422,9 +2485,13 @@ pub const UnlinkError = error{
     SystemResources,
     ReadOnlyFileSystem,
 
-    /// On Windows, file paths must be valid Unicode.
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
 
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
+
     /// On Windows, file paths cannot contain these characters:
     /// '/', '*', '?', '"', '<', '>', '|'
     BadPathName,
@@ -2434,6 +2501,9 @@ pub const UnlinkError = error{
 } || UnexpectedError;
 
 /// Delete a name and possibly the file it refers to.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
 /// See also `unlinkZ`.
 pub fn unlink(file_path: []const u8) UnlinkError!void {
     if (builtin.os.tag == .wasi and !builtin.link_libc) {
@@ -2450,7 +2520,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
     }
 }
 
-/// Same as `unlink` except the parameter is a null terminated UTF8-encoded string.
+/// Same as `unlink` except the parameter is null terminated.
 pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
     if (builtin.os.tag == .windows) {
         const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
@@ -2473,11 +2543,15 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
         .NOTDIR => return error.NotDir,
         .NOMEM => return error.SystemResources,
         .ROFS => return error.ReadOnlyFileSystem,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
 
-/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded.
+/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 LE encoded.
 pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
     windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd }) catch |err| switch (err) {
         error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true
@@ -2491,6 +2565,9 @@ pub const UnlinkatError = UnlinkError || error{
 };
 
 /// Delete a file name and possibly the file it refers to, based on an open directory handle.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
 /// Asserts that the path parameter has no null bytes.
 pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
     if (builtin.os.tag == .windows) {
@@ -2528,6 +2605,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
         .ROFS => return error.ReadOnlyFileSystem,
         .NOTEMPTY => return error.DirNotEmpty,
         .NOTCAPABLE => return error.AccessDenied,
+        .ILSEQ => return error.InvalidUtf8,
 
         .INVAL => unreachable, // invalid flags, or pathname has . as last component
         .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
         .ROFS => return error.ReadOnlyFileSystem,
         .EXIST => return error.DirNotEmpty,
         .NOTEMPTY => return error.DirNotEmpty,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
 
         .INVAL => unreachable, // invalid flags, or pathname has . as last component
         .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
     }
 }
 
-/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
+/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only.
 pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
     const remove_dir = (flags & AT.REMOVEDIR) != 0;
     return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
@@ -2594,7 +2676,11 @@ pub const RenameError = error{
     PathAlreadyExists,
     ReadOnlyFileSystem,
     RenameAcrossMountPoints,
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
     BadPathName,
     NoDevice,
     SharingViolation,
@@ -2610,6 +2696,9 @@ pub const RenameError = error{
 } || UnexpectedError;
 
 /// Change the name or location of a file.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
     if (builtin.os.tag == .wasi and !builtin.link_libc) {
         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 {
     }
 }
 
-/// Same as `rename` except the parameters are null-terminated byte arrays.
+/// Same as `rename` except the parameters are null-terminated.
 pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {
     if (builtin.os.tag == .windows) {
         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
         .NOTEMPTY => return error.PathAlreadyExists,
         .ROFS => return error.ReadOnlyFileSystem,
         .XDEV => return error.RenameAcrossMountPoints,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
 
-/// Same as `rename` except the parameters are null-terminated UTF16LE encoded byte arrays.
+/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded.
 /// Assumes target is Windows.
 pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {
     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
 }
 
 /// Change the name or location of a file based on an open directory handle.
+/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, both paths should be encoded as valid UTF-8.
+/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
 pub fn renameat(
     old_dir_fd: fd_t,
     old_path: []const u8,
@@ -2710,11 +2806,12 @@ pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!vo
         .ROFS => return error.ReadOnlyFileSystem,
         .XDEV => return error.RenameAcrossMountPoints,
         .NOTCAPABLE => return error.AccessDenied,
+        .ILSEQ => return error.InvalidUtf8,
         else => |err| return unexpectedErrno(err),
     }
 }
 
-/// Same as `renameat` except the parameters are null-terminated byte arrays.
+/// Same as `renameat` except the parameters are null-terminated.
 pub fn renameatZ(
     old_dir_fd: fd_t,
     old_path: [*:0]const u8,
@@ -2749,6 +2846,10 @@ pub fn renameatZ(
         .NOTEMPTY => return error.PathAlreadyExists,
         .ROFS => return error.ReadOnlyFileSystem,
         .XDEV => return error.RenameAcrossMountPoints,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
@@ -2860,6 +2961,9 @@ pub fn renameatW(
     }
 }
 
+/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.
 pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
     if (builtin.os.tag == .windows) {
         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
         .NOTDIR => return error.NotDir,
         .ROFS => return error.ReadOnlyFileSystem,
         .NOTCAPABLE => return error.AccessDenied,
+        .ILSEQ => return error.InvalidUtf8,
         else => |err| return unexpectedErrno(err),
     }
 }
 
+/// Same as `mkdirat` except the parameters are null-terminated.
 pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
     if (builtin.os.tag == .windows) {
         const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path);
-        return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
+        return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
     } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
         return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);
     }
@@ -2920,10 +3026,15 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
         .ROFS => return error.ReadOnlyFileSystem,
         // dragonfly: when dir_fd is unlinked from filesystem
         .NOTCONN => return error.FileNotFound,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
 
+/// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded.
 pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
     _ = mode;
     const sub_dir_handle = windows.OpenFile(sub_path_w, .{
@@ -2955,7 +3066,11 @@ pub const MakeDirError = error{
     NoSpaceLeft,
     NotDir,
     ReadOnlyFileSystem,
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
     BadPathName,
     NoDevice,
     /// On Windows, `\\server` or `\\server\share` was not found.
@@ -2964,6 +3079,9 @@ pub const MakeDirError = error{
 
 /// Create a directory.
 /// `mode` is ignored on Windows and WASI.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
 pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
     if (builtin.os.tag == .wasi and !builtin.link_libc) {
         return mkdirat(wasi.AT.FDCWD, dir_path, mode);
@@ -2976,7 +3094,10 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
     }
 }
 
-/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
+/// Same as `mkdir` but the parameter is null-terminated.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
 pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
     if (builtin.os.tag == .windows) {
         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 {
         .NOSPC => return error.NoSpaceLeft,
         .NOTDIR => return error.NotDir,
         .ROFS => return error.ReadOnlyFileSystem,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
 
-/// Windows-only. Same as `mkdir` but the parameters is  WTF16 encoded.
+/// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded.
 pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
     _ = mode;
     const sub_dir_handle = windows.OpenFile(dir_path_w, .{
@@ -3031,13 +3156,20 @@ pub const DeleteDirError = error{
     NotDir,
     DirNotEmpty,
     ReadOnlyFileSystem,
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
     BadPathName,
     /// On Windows, `\\server` or `\\server\share` was not found.
     NetworkNotFound,
 } || UnexpectedError;
 
 /// Deletes an empty directory.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
 pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
     if (builtin.os.tag == .wasi and !builtin.link_libc) {
         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 {
 }
 
 /// Same as `rmdir` except the parameter is null-terminated.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
 pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
     if (builtin.os.tag == .windows) {
         const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
@@ -3077,11 +3212,15 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
         .EXIST => return error.DirNotEmpty,
         .NOTEMPTY => return error.DirNotEmpty,
         .ROFS => return error.ReadOnlyFileSystem,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
 
-/// Windows-only. Same as `rmdir` except the parameter is WTF16 encoded.
+/// Windows-only. Same as `rmdir` except the parameter is WTF-16 LE encoded.
 pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
     return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
         error.IsDir => unreachable,
@@ -3098,21 +3237,25 @@ pub const ChangeCurDirError = error{
     SystemResources,
     NotDir,
     BadPathName,
-
-    /// On Windows, file paths must be valid Unicode.
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
 } || UnexpectedError;
 
 /// Changes the current working directory of the calling process.
-/// `dir_path` is recommended to be a UTF-8 encoded string.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
 pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
     if (builtin.os.tag == .wasi and !builtin.link_libc) {
         @compileError("WASI does not support os.chdir");
     } else if (builtin.os.tag == .windows) {
-        var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
-        const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
-        if (len > utf16_dir_path.len) return error.NameTooLong;
-        return chdirW(utf16_dir_path[0..len]);
+        var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
+        const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], dir_path);
+        if (len > wtf16_dir_path.len) return error.NameTooLong;
+        return chdirW(wtf16_dir_path[0..len]);
     } else {
         const dir_path_c = try toPosixPath(dir_path);
         return chdirZ(&dir_path_c);
@@ -3120,12 +3263,15 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
 }
 
 /// Same as `chdir` except the parameter is null-terminated.
+/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `dir_path` should be encoded as valid UTF-8.
+/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
 pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
     if (builtin.os.tag == .windows) {
-        var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
-        const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], mem.span(dir_path));
-        if (len > utf16_dir_path.len) return error.NameTooLong;
-        return chdirW(utf16_dir_path[0..len]);
+        var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
+        const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], mem.span(dir_path));
+        if (len > wtf16_dir_path.len) return error.NameTooLong;
+        return chdirW(wtf16_dir_path[0..len]);
     } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
         return chdir(mem.span(dir_path));
     }
@@ -3139,11 +3285,15 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
         .NOENT => return error.FileNotFound,
         .NOMEM => return error.SystemResources,
         .NOTDIR => return error.NotDir,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
 
-/// Windows-only. Same as `chdir` except the parameter is WTF16 encoded.
+/// Windows-only. Same as `chdir` except the parameter is WTF16 LE encoded.
 pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void {
     windows.SetCurrentDirectory(dir_path) catch |err| switch (err) {
         error.NoDevice => return error.FileSystem,
@@ -3183,7 +3333,11 @@ pub const ReadLinkError = error{
     SystemResources,
     NotLink,
     NotDir,
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
     BadPathName,
     /// Windows-only. This error may occur if the opened reparse point is
     /// of unsupported type.
@@ -3193,7 +3347,13 @@ pub const ReadLinkError = error{
 } || UnexpectedError;
 
 /// Read value of a symbolic link.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
 /// The return value is a slice of `out_buffer` from index 0.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, the result is encoded as UTF-8.
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
     if (builtin.os.tag == .wasi and !builtin.link_libc) {
         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 {
     }
 }
 
-/// Windows-only. Same as `readlink` except `file_path` is WTF16 encoded.
+/// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded.
+/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
 /// See also `readlinkZ`.
 pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
     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 {
 /// Same as `readlink` except `file_path` is null-terminated.
 pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
     if (builtin.os.tag == .windows) {
-        const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
+        const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
         return readlinkW(file_path_w.span(), out_buffer);
     } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
         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
         .NOENT => return error.FileNotFound,
         .NOMEM => return error.SystemResources,
         .NOTDIR => return error.NotDir,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
 
 /// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle.
+/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `file_path` should be encoded as valid UTF-8.
+/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
 /// The return value is a slice of `out_buffer` from index 0.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, the result is encoded as UTF-8.
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 /// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
 pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
     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
         .NOMEM => return error.SystemResources,
         .NOTDIR => return error.NotDir,
         .NOTCAPABLE => return error.AccessDenied,
+        .ILSEQ => return error.InvalidUtf8,
         else => |err| return unexpectedErrno(err),
     }
 }
 
-/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.
+/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded.
+/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
 /// See also `readlinkat`.
 pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
     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
         .NOENT => return error.FileNotFound,
         .NOMEM => return error.SystemResources,
         .NOTDIR => return error.NotDir,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
@@ -4274,10 +4451,18 @@ pub fn fstat_wasi(fd: fd_t) FStatError!wasi.filestat_t {
     }
 }
 
-pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLinkLoop };
+pub const FStatAtError = FStatError || error{
+    NameTooLong,
+    FileNotFound,
+    SymLinkLoop,
+    /// WASI-only; file paths must be valid UTF-8.
+    InvalidUtf8,
+};
 
 /// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`
 /// which is relative to `dirfd` handle.
+/// On WASI, `pathname` should be encoded as valid UTF-8.
+/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
 /// See also `fstatatZ` and `fstatat_wasi`.
 pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
     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
 }
 
 /// WASI-only. Same as `fstatat` but targeting WASI.
+/// `pathname` should be encoded as valid UTF-8.
 /// See also `fstatat`.
 pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t) FStatAtError!wasi.filestat_t {
     var stat: wasi.filestat_t = undefined;
@@ -4308,6 +4494,7 @@ pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t
         .NOENT => return error.FileNotFound,
         .NOTDIR => return error.FileNotFound,
         .NOTCAPABLE => return error.AccessDenied,
+        .ILSEQ => return error.InvalidUtf8,
         else => |err| return unexpectedErrno(err),
     }
 }
@@ -4337,6 +4524,10 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S
         .LOOP => return error.SymLinkLoop,
         .NOENT => return error.FileNotFound,
         .NOTDIR => return error.FileNotFound,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
@@ -4693,12 +4884,17 @@ pub const AccessError = error{
     FileBusy,
     SymLinkLoop,
     ReadOnlyFileSystem,
-
-    /// On Windows, file paths must be valid Unicode.
+    /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
 } || UnexpectedError;
 
 /// check user's permissions for a file
+/// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `path` should be encoded as valid UTF-8.
+/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
 /// TODO currently this assumes `mode` is `F.OK` on Windows.
 pub fn access(path: []const u8, mode: u32) AccessError!void {
     if (builtin.os.tag == .windows) {
@@ -4740,12 +4936,16 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
         .FAULT => unreachable,
         .IO => return error.InputOutput,
         .NOMEM => return error.SystemResources,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
 
-/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
-/// Otherwise use `access` or `accessC`.
+/// Call from Windows-specific code if you already have a WTF-16LE encoded, null terminated string.
+/// Otherwise use `access` or `accessZ`.
 /// TODO currently this ignores `mode`.
 pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!void {
     _ = mode;
@@ -4762,6 +4962,9 @@ pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!v
 }
 
 /// Check user's permissions for a file, based on an open directory handle.
+/// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `path` should be encoded as valid UTF-8.
+/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
 /// TODO currently this ignores `mode` and `flags` on Windows.
 pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
     if (builtin.os.tag == .windows) {
@@ -4832,6 +5035,10 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces
         .FAULT => unreachable,
         .IO => return error.InputOutput,
         .NOMEM => return error.SystemResources,
+        .ILSEQ => |err| if (builtin.os.tag == .wasi)
+            return error.InvalidUtf8
+        else
+            return unexpectedErrno(err),
         else => |err| return unexpectedErrno(err),
     }
 }
@@ -5339,8 +5546,9 @@ pub const RealPathError = error{
     /// On WASI, the current CWD may not be associated with an absolute path.
     InvalidHandle,
 
-    /// On Windows, file paths must be valid Unicode.
-    InvalidUtf8,
+    /// Windows-only; file paths provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
 
     /// On Windows, `\\server` or `\\server\share` was not found.
     NetworkNotFound,
@@ -5362,8 +5570,12 @@ pub const RealPathError = error{
 /// Return the canonicalized absolute pathname.
 /// Expands all symbolic links and resolves references to `.`, `..`, and
 /// extra `/` characters in `pathname`.
+/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
 /// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
 /// See also `realpathZ` and `realpathW`.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 /// Calling this function is usually a bug.
 pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
     if (builtin.os.tag == .windows) {
@@ -5402,6 +5614,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
             error.WouldBlock => unreachable,
             error.FileBusy => unreachable, // not asking for write permissions
             error.InvalidHandle => unreachable, // WASI-only
+            error.InvalidUtf8 => unreachable, // WASI-only
             else => |e| return e,
         };
         defer close(fd);
@@ -5425,7 +5638,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
     return mem.sliceTo(result_path, 0);
 }
 
-/// Same as `realpath` except `pathname` is UTF16LE-encoded.
+/// Same as `realpath` except `pathname` is WTF16LE-encoded.
+/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
 /// Calling this function is usually a bug.
 pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
     const w = windows;
@@ -5475,6 +5689,8 @@ pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
 /// This function is very host-specific and is not universally supported by all hosts.
 /// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
 /// unsupported on WASI.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 /// Calling this function is usually a bug.
 pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
     if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
@@ -5485,10 +5701,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
             var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
             const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
 
-            // TODO: Windows file paths can be arbitrary arrays of u16 values
-            // and must not fail with InvalidUtf8.
-            const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch
-                return error.InvalidUtf8;
+            const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
             return out_buffer[0..end_index];
         },
         .macos, .ios, .watchos, .tvos => {
@@ -5512,8 +5725,12 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
 
             const target = readlinkZ(proc_path, out_buffer) catch |err| {
                 switch (err) {
-                    error.UnsupportedReparsePointType => unreachable, // Windows only,
                     error.NotLink => unreachable,
+                    error.BadPathName => unreachable,
+                    error.InvalidUtf8 => unreachable, // WASI-only
+                    error.InvalidWtf8 => unreachable, // Windows-only
+                    error.UnsupportedReparsePointType => unreachable, // Windows-only
+                    error.NetworkNotFound => unreachable, // Windows-only
                     else => |e| return e,
                 }
             };
lib/std/process.zig
@@ -16,11 +16,15 @@ pub const changeCurDir = os.chdir;
 pub const changeCurDirC = os.chdirC;
 
 /// The result is a slice of `out_buffer`, from index `0`.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 pub fn getCwd(out_buffer: []u8) ![]u8 {
     return os.getcwd(out_buffer);
 }
 
 /// Caller must free the returned memory.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 pub fn getCwdAlloc(allocator: Allocator) ![]u8 {
     // The use of MAX_PATH_BYTES here is just a heuristic: most paths will fit
     // in stack_buf, avoiding an extra allocation in the common case.
@@ -76,7 +80,7 @@ pub const EnvMap = struct {
             _ = self;
             if (builtin.os.tag == .windows) {
                 var h = std.hash.Wyhash.init(0);
-                var it = std.unicode.Utf8View.initUnchecked(s).iterator();
+                var it = std.unicode.Wtf8View.initUnchecked(s).iterator();
                 while (it.nextCodepoint()) |cp| {
                     const cp_upper = upcase(cp);
                     h.update(&[_]u8{
@@ -93,8 +97,8 @@ pub const EnvMap = struct {
         pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
             _ = self;
             if (builtin.os.tag == .windows) {
-                var it_a = std.unicode.Utf8View.initUnchecked(a).iterator();
-                var it_b = std.unicode.Utf8View.initUnchecked(b).iterator();
+                var it_a = std.unicode.Wtf8View.initUnchecked(a).iterator();
+                var it_b = std.unicode.Wtf8View.initUnchecked(b).iterator();
                 while (true) {
                     const c_a = it_a.nextCodepoint() orelse break;
                     const c_b = it_b.nextCodepoint() orelse return false;
@@ -129,8 +133,9 @@ pub const EnvMap = struct {
     /// Same as `put` but the key and value become owned by the EnvMap rather
     /// than being copied.
     /// If `putMove` fails, the ownership of key and value does not transfer.
-    /// On Windows `key` must be a valid UTF-8 string.
+    /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
     pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {
+        assert(std.unicode.wtf8ValidateSlice(key));
         const get_or_put = try self.hash_map.getOrPut(key);
         if (get_or_put.found_existing) {
             self.free(get_or_put.key_ptr.*);
@@ -141,8 +146,9 @@ pub const EnvMap = struct {
     }
 
     /// `key` and `value` are copied into the EnvMap.
-    /// On Windows `key` must be a valid UTF-8 string.
+    /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
     pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {
+        assert(std.unicode.wtf8ValidateSlice(key));
         const value_copy = try self.copy(value);
         errdefer self.free(value_copy);
         const get_or_put = try self.hash_map.getOrPut(key);
@@ -159,23 +165,26 @@ pub const EnvMap = struct {
 
     /// Find the address of the value associated with a key.
     /// The returned pointer is invalidated if the map resizes.
-    /// On Windows `key` must be a valid UTF-8 string.
+    /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
     pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {
+        assert(std.unicode.wtf8ValidateSlice(key));
         return self.hash_map.getPtr(key);
     }
 
     /// Return the map's copy of the value associated with
     /// a key.  The returned string is invalidated if this
     /// key is removed from the map.
-    /// On Windows `key` must be a valid UTF-8 string.
+    /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
     pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {
+        assert(std.unicode.wtf8ValidateSlice(key));
         return self.hash_map.get(key);
     }
 
     /// Removes the item from the map and frees its value.
     /// This invalidates the value returned by get() for this key.
-    /// On Windows `key` must be a valid UTF-8 string.
+    /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
     pub fn remove(self: *EnvMap, key: []const u8) void {
+        assert(std.unicode.wtf8ValidateSlice(key));
         const kv = self.hash_map.fetchRemove(key) orelse return;
         self.free(kv.key);
         self.free(kv.value);
@@ -239,18 +248,34 @@ test "EnvMap" {
 
     try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
 
-    // test Unicode case-insensitivity on Windows
     if (builtin.os.tag == .windows) {
+        // test Unicode case-insensitivity on Windows
         try env.put("ะšะ˜ะ ะธะปะปะ˜ะฆะ", "something else");
         try testing.expectEqualStrings("something else", env.get("ะบะธั€ะธะปะปะธั†ะฐ").?);
+
+        // and WTF-8 that's not valid UTF-8
+        const wtf8_with_surrogate_pair = try std.unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
+            std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
+        });
+        defer testing.allocator.free(wtf8_with_surrogate_pair);
+
+        try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
+        try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
     }
 }
 
+pub const GetEnvMapError = error{
+    OutOfMemory,
+    /// WASI-only. `environ_sizes_get` or `environ_get`
+    /// failed for an unexpected reason.
+    Unexpected,
+};
+
 /// Returns a snapshot of the environment variables of the current process.
 /// Any modifications to the resulting EnvMap will not be reflected in the environment, and
 /// likewise, any future modifications to the environment will not be reflected in the EnvMap.
 /// Caller owns resulting `EnvMap` and should call its `deinit` fn when done.
-pub fn getEnvMap(allocator: Allocator) !EnvMap {
+pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap {
     var result = EnvMap.init(allocator);
     errdefer result.deinit();
 
@@ -269,7 +294,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {
 
             while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
             const key_w = ptr[key_start..i];
-            const key = try std.unicode.utf16LeToUtf8Alloc(allocator, key_w);
+            const key = try std.unicode.wtf16LeToWtf8Alloc(allocator, key_w);
             errdefer allocator.free(key);
 
             if (ptr[i] == '=') i += 1;
@@ -277,7 +302,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {
             const value_start = i;
             while (ptr[i] != 0) : (i += 1) {}
             const value_w = ptr[value_start..i];
-            const value = try std.unicode.utf16LeToUtf8Alloc(allocator, value_w);
+            const value = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_w);
             errdefer allocator.free(value);
 
             i += 1; // skip over null byte
@@ -355,25 +380,26 @@ pub const GetEnvVarOwnedError = error{
     OutOfMemory,
     EnvironmentVariableNotFound,
 
-    /// See https://github.com/ziglang/zig/issues/1774
-    InvalidUtf8,
+    /// On Windows, environment variable keys provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
 };
 
 /// Caller must free returned memory.
+/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/),
+/// then `error.InvalidWtf8` is returned.
+/// On Windows, the value is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, the value is an opaque sequence of bytes with no particular encoding.
 pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
     if (builtin.os.tag == .windows) {
         const result_w = blk: {
-            const key_w = try std.unicode.utf8ToUtf16LeAllocZ(allocator, key);
+            const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(allocator, key);
             defer allocator.free(key_w);
 
             break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
         };
-        return std.unicode.utf16LeToUtf8Alloc(allocator, result_w) catch |err| switch (err) {
-            error.DanglingSurrogateHalf => return error.InvalidUtf8,
-            error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,
-            error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,
-            else => |e| return e,
-        };
+        // wtf16LeToWtf8Alloc can only fail with OutOfMemory
+        return std.unicode.wtf16LeToWtf8Alloc(allocator, result_w);
     } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
         var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
         defer envmap.deinit();
@@ -385,6 +411,7 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
     }
 }
 
+/// On Windows, `key` must be valid UTF-8.
 pub fn hasEnvVarConstant(comptime key: []const u8) bool {
     if (builtin.os.tag == .windows) {
         const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);
@@ -396,11 +423,22 @@ pub fn hasEnvVarConstant(comptime key: []const u8) bool {
     }
 }
 
-pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool {
+pub const HasEnvVarError = error{
+    OutOfMemory,
+
+    /// On Windows, environment variable keys provided by the user must be valid WTF-8.
+    /// https://simonsapin.github.io/wtf-8/
+    InvalidWtf8,
+};
+
+/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/),
+/// then `error.InvalidWtf8` is returned.
+pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
     if (builtin.os.tag == .windows) {
         var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
-        const key_w = try std.unicode.utf8ToUtf16LeAllocZ(stack_alloc.get(), key);
-        defer stack_alloc.allocator.free(key_w);
+        const stack_allocator = stack_alloc.get();
+        const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
+        defer stack_allocator.free(key_w);
         return std.os.getenvW(key_w) != null;
     } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
         var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
@@ -411,9 +449,22 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool
     }
 }
 
-test "os.getEnvVarOwned" {
-    const ga = std.testing.allocator;
-    try testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV"));
+test getEnvVarOwned {
+    try testing.expectError(
+        error.EnvironmentVariableNotFound,
+        getEnvVarOwned(std.testing.allocator, "BADENV"),
+    );
+}
+
+test hasEnvVarConstant {
+    if (builtin.os.tag == .wasi and !builtin.link_libc) return error.SkipZigTest;
+
+    try testing.expect(!hasEnvVarConstant("BADENV"));
+}
+
+test hasEnvVar {
+    const has_env = try hasEnvVar(std.testing.allocator, "BADENV");
+    try testing.expect(!has_env);
 }
 
 pub const ArgIteratorPosix = struct {
@@ -531,6 +582,7 @@ pub const ArgIteratorWasi = struct {
 pub const ArgIteratorWindows = struct {
     allocator: Allocator,
     /// Owned by the iterator.
+    /// Encoded as WTF-8.
     cmd_line: []const u8,
     index: usize = 0,
     /// Owned by the iterator. Long enough to hold the entire `cmd_line` plus a null terminator.
@@ -538,20 +590,14 @@ pub const ArgIteratorWindows = struct {
     start: usize = 0,
     end: usize = 0,
 
-    pub const InitError = error{ OutOfMemory, InvalidCmdLine };
+    pub const InitError = error{OutOfMemory};
 
-    /// `cmd_line_w` *must* be an UTF16-LE-encoded string.
+    /// `cmd_line_w` *must* be a WTF16-LE-encoded string.
     ///
-    /// The iterator makes a copy of `cmd_line_w` converted UTF-8 and keeps it; it does *not* take
+    /// The iterator makes a copy of `cmd_line_w` converted WTF-8 and keeps it; it does *not* take
     /// ownership of `cmd_line_w`.
     pub fn init(allocator: Allocator, cmd_line_w: [*:0]const u16) InitError!ArgIteratorWindows {
-        const cmd_line = std.unicode.utf16LeToUtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0)) catch |err| switch (err) {
-            error.DanglingSurrogateHalf,
-            error.ExpectedSecondSurrogateHalf,
-            error.UnexpectedSecondSurrogateHalf,
-            => return error.InvalidCmdLine,
-            error.OutOfMemory => return error.OutOfMemory,
-        };
+        const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0));
         errdefer allocator.free(cmd_line);
 
         const buffer = try allocator.alloc(u8, cmd_line.len + 1);
@@ -566,6 +612,7 @@ pub const ArgIteratorWindows = struct {
 
     /// Returns the next argument and advances the iterator. Returns `null` if at the end of the
     /// command-line string. The iterator owns the returned slice.
+    /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
     pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 {
         return self.nextWithStrategy(next_strategy);
     }
@@ -777,7 +824,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
         pub const Self = @This();
 
         pub const InitError = error{OutOfMemory};
-        pub const InitUtf16leError = error{ OutOfMemory, InvalidCmdLine };
 
         /// cmd_line_utf8 MUST remain valid and constant while using this instance
         pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
@@ -805,30 +851,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
             };
         }
 
-        /// cmd_line_utf16le MUST be encoded UTF16-LE, and is converted to UTF-8 in an internal buffer
-        pub fn initUtf16le(allocator: Allocator, cmd_line_utf16le: [*:0]const u16) InitUtf16leError!Self {
-            const utf16le_slice = mem.sliceTo(cmd_line_utf16le, 0);
-            const cmd_line = std.unicode.utf16LeToUtf8Alloc(allocator, utf16le_slice) catch |err| switch (err) {
-                error.ExpectedSecondSurrogateHalf,
-                error.DanglingSurrogateHalf,
-                error.UnexpectedSecondSurrogateHalf,
-                => return error.InvalidCmdLine,
-
-                error.OutOfMemory => return error.OutOfMemory,
-            };
-            errdefer allocator.free(cmd_line);
-
-            const buffer = try allocator.alloc(u8, cmd_line.len + 1);
-            errdefer allocator.free(buffer);
-
-            return Self{
-                .allocator = allocator,
-                .cmd_line = cmd_line,
-                .free_cmd_line_on_deinit = true,
-                .buffer = buffer,
-            };
-        }
-
         // Skips over whitespace in the cmd_line.
         // Returns false if the terminating sentinel is reached, true otherwise.
         // Also skips over comments (if supported).
@@ -1021,6 +1043,8 @@ pub const ArgIterator = struct {
 
     /// Get the next argument. Returns 'null' if we are at the end.
     /// Returned slice is pointing to the iterator's internal buffer.
+    /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+    /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
     pub fn next(self: *ArgIterator) ?([:0]const u8) {
         return self.inner.next();
     }
@@ -1057,6 +1081,8 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator
 }
 
 /// Caller must call argsFree on result.
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
     // TODO refactor to only make 1 allocation.
     var it = try argsWithAllocator(allocator);
@@ -1201,7 +1227,7 @@ test "ArgIteratorWindows" {
 }
 
 fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {
-    const cmd_line_w = try std.unicode.utf8ToUtf16LeAllocZ(testing.allocator, cmd_line);
+    const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);
     defer testing.allocator.free(cmd_line_w);
 
     // next
lib/std/Thread.zig
@@ -91,7 +91,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
         },
         .windows => {
             var buf: [max_name_len]u16 = undefined;
-            const len = try std.unicode.utf8ToUtf16Le(&buf, name);
+            const len = try std.unicode.wtf8ToWtf16Le(&buf, name);
             const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong;
 
             // 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 {
 }
 
 pub const GetNameError = error{
-    // For Windows, the name is converted from UTF16 to UTF8
-    CodepointTooLarge,
-    Utf8CannotEncodeSurrogateHalf,
-    DanglingSurrogateHalf,
-    ExpectedSecondSurrogateHalf,
-    UnexpectedSecondSurrogateHalf,
-
     Unsupported,
     Unexpected,
 } || os.PrctlError || os.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError;
 
+/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
 pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 {
     buffer_ptr[max_name_len] = 0;
     var buffer: [:0]u8 = buffer_ptr;
@@ -213,7 +208,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
             )) {
                 .SUCCESS => {
                     const string = @as(*const os.windows.UNICODE_STRING, @ptrCast(&buf));
-                    const len = try std.unicode.utf16LeToUtf8(buffer, string.Buffer[0 .. string.Length / 2]);
+                    const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer[0 .. string.Length / 2]);
                     return if (len > 0) buffer[0..len] else null;
                 },
                 .NOT_IMPLEMENTED => return error.Unsupported,
lib/std/unicode.zig
@@ -488,7 +488,9 @@ pub const Utf16LeIterator = struct {
         };
     }
 
-    pub fn nextCodepoint(it: *Utf16LeIterator) !?u21 {
+    pub const NextCodepointError = error{ DanglingSurrogateHalf, ExpectedSecondSurrogateHalf, UnexpectedSecondSurrogateHalf };
+
+    pub fn nextCodepoint(it: *Utf16LeIterator) NextCodepointError!?u21 {
         assert(it.i <= it.bytes.len);
         if (it.i == it.bytes.len) return null;
         var code_units: [2]u16 = undefined;
@@ -923,7 +925,14 @@ test "fmtUtf8" {
     try expectFmt("๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝA", "{}", .{fmtUtf8("\xE1\x80\xE2\xF0\x91\x92\xF1\xBFA")});
 }
 
-fn utf16LeToUtf8ArrayListImpl(array_list: *std.ArrayList(u8), utf16le: []const u16, comptime surrogates: Surrogates) !void {
+fn utf16LeToUtf8ArrayListImpl(
+    array_list: *std.ArrayList(u8),
+    utf16le: []const u16,
+    comptime surrogates: Surrogates,
+) (switch (surrogates) {
+    .cannot_encode_surrogate_half => Utf16LeToUtf8AllocError,
+    .can_encode_surrogate_half => mem.Allocator.Error,
+})!void {
     // optimistically guess that it will all be ascii.
     try array_list.ensureTotalCapacityPrecise(utf16le.len);
 
@@ -975,7 +984,9 @@ fn utf16LeToUtf8ArrayListImpl(array_list: *std.ArrayList(u8), utf16le: []const u
     }
 }
 
-pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) !void {
+pub const Utf16LeToUtf8AllocError = mem.Allocator.Error || Utf16LeToUtf8Error;
+
+pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) Utf16LeToUtf8AllocError!void {
     return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .cannot_encode_surrogate_half);
 }
 
@@ -983,7 +994,7 @@ pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u
 pub const utf16leToUtf8Alloc = utf16LeToUtf8Alloc;
 
 /// Caller must free returned memory.
-pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8 {
+pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![]u8 {
     // optimistically guess that it will all be ascii.
     var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len);
     errdefer result.deinit();
@@ -997,7 +1008,7 @@ pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8
 pub const utf16leToUtf8AllocZ = utf16LeToUtf8AllocZ;
 
 /// Caller must free returned memory.
-pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) ![:0]u8 {
+pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![:0]u8 {
     // optimistically guess that it will all be ascii (and allocate space for the null terminator)
     var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len + 1);
     errdefer result.deinit();
@@ -1007,9 +1018,14 @@ pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) ![:0]
     return result.toOwnedSliceSentinel(0);
 }
 
+pub const Utf16LeToUtf8Error = Utf16LeIterator.NextCodepointError;
+
 /// Asserts that the output buffer is big enough.
 /// Returns end byte index into utf8.
-fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surrogates) !usize {
+fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surrogates) (switch (surrogates) {
+    .cannot_encode_surrogate_half => Utf16LeToUtf8Error,
+    .can_encode_surrogate_half => error{},
+})!usize {
     var end_index: usize = 0;
 
     var remaining = utf16le;
@@ -1043,7 +1059,9 @@ fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surr
                     // The maximum possible codepoint encoded by UTF-16 is U+10FFFF,
                     // which is within the valid codepoint range.
                     error.CodepointTooLarge => unreachable,
-                    else => |e| return e,
+                    // We know the codepoint was valid in UTF-16, meaning it is not
+                    // an unpaired surrogate codepoint.
+                    error.Utf8CannotEncodeSurrogateHalf => unreachable,
                 };
             }
         },
@@ -1064,7 +1082,7 @@ fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surr
 /// Deprecated; renamed to utf16LeToUtf8
 pub const utf16leToUtf8 = utf16LeToUtf8;
 
-pub fn utf16LeToUtf8(utf8: []u8, utf16le: []const u16) !usize {
+pub fn utf16LeToUtf8(utf8: []u8, utf16le: []const u16) Utf16LeToUtf8Error!usize {
     return utf16LeToUtf8Impl(utf8, utf16le, .cannot_encode_surrogate_half);
 }
 
@@ -1176,11 +1194,11 @@ fn utf8ToUtf16LeArrayListImpl(array_list: *std.ArrayList(u16), utf8: []const u8,
     }
 }
 
-pub fn utf8ToUtf16LeArrayList(array_list: *std.ArrayList(u16), utf8: []const u8) !void {
+pub fn utf8ToUtf16LeArrayList(array_list: *std.ArrayList(u16), utf8: []const u8) error{ InvalidUtf8, OutOfMemory }!void {
     return utf8ToUtf16LeArrayListImpl(array_list, utf8, .cannot_encode_surrogate_half);
 }
 
-pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) ![]u16 {
+pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![]u16 {
     // optimistically guess that it will not require surrogate pairs
     var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len);
     errdefer result.deinit();
@@ -1193,7 +1211,7 @@ pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) ![]u16 {
 /// Deprecated; renamed to utf8ToUtf16LeAllocZ
 pub const utf8ToUtf16LeWithNull = utf8ToUtf16LeAllocZ;
 
-pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) ![:0]u16 {
+pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![:0]u16 {
     // optimistically guess that it will not require surrogate pairs
     var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len + 1);
     errdefer result.deinit();
@@ -1205,7 +1223,7 @@ pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) ![:0]u16
 
 /// Returns index of next character. If exact fit, returned index equals output slice length.
 /// Assumes there is enough space for the output.
-pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize {
+pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) error{InvalidUtf8}!usize {
     return utf8ToUtf16LeImpl(utf16le, utf8, .cannot_encode_surrogate_half);
 }
 
@@ -1236,11 +1254,14 @@ pub fn utf8ToUtf16LeImpl(utf16le: []u16, utf8: []const u8, comptime surrogates:
 
     var src_i: usize = 0;
     while (src_i < remaining.len) {
-        const n = utf8ByteSequenceLength(remaining[src_i]) catch return error.InvalidUtf8;
+        const n = utf8ByteSequenceLength(remaining[src_i]) catch return switch (surrogates) {
+            .cannot_encode_surrogate_half => error.InvalidUtf8,
+            .can_encode_surrogate_half => error.InvalidWtf8,
+        };
         const next_src_i = src_i + n;
         const codepoint = switch (surrogates) {
             .cannot_encode_surrogate_half => utf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8,
-            .can_encode_surrogate_half => wtf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8,
+            .can_encode_surrogate_half => wtf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidWtf8,
         };
         if (codepoint < 0x10000) {
             const short = @as(u16, @intCast(codepoint));
@@ -1600,9 +1621,9 @@ fn testValidateWtf8Slice() !void {
 pub const Wtf8View = struct {
     bytes: []const u8,
 
-    pub fn init(s: []const u8) !Wtf8View {
+    pub fn init(s: []const u8) error{InvalidWtf8}!Wtf8View {
         if (!wtf8ValidateSlice(s)) {
-            return error.InvalidUtf8;
+            return error.InvalidWtf8;
         }
 
         return initUnchecked(s);
@@ -1614,8 +1635,8 @@ pub const Wtf8View = struct {
 
     pub inline fn initComptime(comptime s: []const u8) Wtf8View {
         return comptime if (init(s)) |r| r else |err| switch (err) {
-            error.InvalidUtf8 => {
-                @compileError("invalid utf8 detected in wtf8 string");
+            error.InvalidWtf8 => {
+                @compileError("invalid wtf8");
             },
         };
     }
@@ -1665,12 +1686,12 @@ pub const Wtf8Iterator = struct {
     }
 };
 
-pub fn wtf16LeToWtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) !void {
+pub fn wtf16LeToWtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) mem.Allocator.Error!void {
     return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .can_encode_surrogate_half);
 }
 
 /// Caller must free returned memory.
-pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) ![]u8 {
+pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![]u8 {
     // optimistically guess that it will all be ascii.
     var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len);
     errdefer result.deinit();
@@ -1681,7 +1702,7 @@ pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) ![]u8
 }
 
 /// Caller must free returned memory.
-pub fn wtf16LeToWtf8AllocZ(allocator: mem.Allocator, wtf16le: []const u16) ![:0]u8 {
+pub fn wtf16LeToWtf8AllocZ(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![:0]u8 {
     // optimistically guess that it will all be ascii (and allocate space for the null terminator)
     var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len + 1);
     errdefer result.deinit();
@@ -1695,11 +1716,11 @@ pub fn wtf16LeToWtf8(wtf8: []u8, wtf16le: []const u16) usize {
     return utf16LeToUtf8Impl(wtf8, wtf16le, .can_encode_surrogate_half) catch |err| switch (err) {};
 }
 
-pub fn wtf8ToWtf16LeArrayList(array_list: *std.ArrayList(u16), wtf8: []const u8) !void {
+pub fn wtf8ToWtf16LeArrayList(array_list: *std.ArrayList(u16), wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }!void {
     return utf8ToUtf16LeArrayListImpl(array_list, wtf8, .can_encode_surrogate_half);
 }
 
-pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u16 {
+pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u16 {
     // optimistically guess that it will not require surrogate pairs
     var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len);
     errdefer result.deinit();
@@ -1709,7 +1730,7 @@ pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u16 {
     return result.toOwnedSlice();
 }
 
-pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u16 {
+pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u16 {
     // optimistically guess that it will not require surrogate pairs
     var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len + 1);
     errdefer result.deinit();
@@ -1721,7 +1742,7 @@ pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u16
 
 /// Returns index of next character. If exact fit, returned index equals output slice length.
 /// Assumes there is enough space for the output.
-pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) !usize {
+pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{InvalidWtf8}!usize {
     return utf8ToUtf16LeImpl(wtf16le, wtf8, .can_encode_surrogate_half);
 }
 
@@ -1732,7 +1753,8 @@ pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) !usize {
 /// In-place conversion is supported when `utf8` and `wtf8` refer to the same slice.
 /// Note: If `wtf8` is entirely composed of well-formed UTF-8, then no conversion is necessary.
 ///       `utf8ValidateSlice` can be used to check if lossy conversion is worthwhile.
-pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) !void {
+/// If `wtf8` is not valid WTF-8, then `error.InvalidWtf8` is returned.
+pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) error{InvalidWtf8}!void {
     assert(utf8.len >= wtf8.len);
 
     const in_place = utf8.ptr == wtf8.ptr;
@@ -1762,7 +1784,7 @@ pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) !void {
     }
 }
 
-pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u8 {
+pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u8 {
     const utf8 = try allocator.alloc(u8, wtf8.len);
     errdefer allocator.free(utf8);
 
@@ -1771,7 +1793,7 @@ pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u8 {
     return utf8;
 }
 
-pub fn wtf8ToUtf8LossyAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u8 {
+pub fn wtf8ToUtf8LossyAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u8 {
     const utf8 = try allocator.allocSentinel(u8, wtf8.len, 0);
     errdefer allocator.free(utf8);
 
src/libc_installation.zig
@@ -246,7 +246,10 @@ pub const LibCInstallation = struct {
         const allocator = args.allocator;
 
         // Detect infinite loops.
-        var env_map = try std.process.getEnvMap(allocator);
+        var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
+            error.Unexpected => unreachable, // WASI-only
+            else => |e| return e,
+        };
         defer env_map.deinit();
         const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
             if (std.mem.eql(u8, phase, "1")) {
@@ -572,7 +575,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
     const allocator = args.allocator;
 
     // Detect infinite loops.
-    var env_map = try std.process.getEnvMap(allocator);
+    var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
+        error.Unexpected => unreachable, // WASI-only
+        else => |e| return e,
+    };
     defer env_map.deinit();
     const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
         if (std.mem.eql(u8, phase, "1")) {
src/Module.zig
@@ -2662,6 +2662,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
         }) catch |err| switch (err) {
             error.NotDir => unreachable, // no dir components
             error.InvalidUtf8 => unreachable, // it's a hex encoded name
+            error.InvalidWtf8 => unreachable, // it's a hex encoded name
             error.BadPathName => unreachable, // it's a hex encoded name
             error.NameTooLong => unreachable, // it's a fixed size name
             error.PipeBusy => unreachable, // it's not a pipe
src/windows_sdk.zig
@@ -84,26 +84,26 @@ fn iterateAndFilterBySemVer(
     return dirs_filtered_slice;
 }
 
-const RegistryUtf8 = struct {
+const RegistryWtf8 = struct {
     key: windows.HKEY,
 
-    /// Assert that `key` is valid UTF-8 string
-    pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryUtf8 {
-        const key_utf16le: [:0]const u16 = key_utf16le: {
-            var key_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
-            const key_utf16le_len: usize = std.unicode.utf8ToUtf16Le(key_utf16le_buf[0..], key) catch |err| switch (err) {
-                error.InvalidUtf8 => unreachable,
+    /// Assert that `key` is valid WTF-8 string
+    pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryWtf8 {
+        const key_wtf16le: [:0]const u16 = key_wtf16le: {
+            var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
+            const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) {
+                error.InvalidWtf8 => unreachable,
             };
-            key_utf16le_buf[key_utf16le_len] = 0;
-            break :key_utf16le key_utf16le_buf[0..key_utf16le_len :0];
+            key_wtf16le_buf[key_wtf16le_len] = 0;
+            break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0];
         };
 
-        const registry_utf16le = try RegistryUtf16Le.openKey(hkey, key_utf16le);
-        return RegistryUtf8{ .key = registry_utf16le.key };
+        const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le);
+        return RegistryWtf8{ .key = registry_wtf16le.key };
     }
 
     /// Closes key, after that usage is invalid
-    pub fn closeKey(self: *const RegistryUtf8) void {
+    pub fn closeKey(self: *const RegistryWtf8) void {
         const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
         const return_code: windows.Win32Error = @enumFromInt(return_code_int);
         switch (return_code) {
@@ -114,71 +114,68 @@ const RegistryUtf8 = struct {
 
     /// Get string from registry.
     /// Caller owns result.
-    pub fn getString(self: *const RegistryUtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
-        const subkey_utf16le: [:0]const u16 = subkey_utf16le: {
-            var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
-            const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;
-            subkey_utf16le_buf[subkey_utf16le_len] = 0;
-            break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];
+    pub fn getString(self: *const RegistryWtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
+        const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
+            var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
+            const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
+            subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
+            break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
         };
 
-        const value_name_utf16le: [:0]const u16 = value_name_utf16le: {
-            var value_name_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
-            const value_name_utf16le_len: usize = std.unicode.utf8ToUtf16Le(value_name_utf16le_buf[0..], value_name) catch unreachable;
-            value_name_utf16le_buf[value_name_utf16le_len] = 0;
-            break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];
+        const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
+            var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
+            const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
+            value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
+            break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
         };
 
-        const registry_utf16le = RegistryUtf16Le{ .key = self.key };
-        const value_utf16le = try registry_utf16le.getString(allocator, subkey_utf16le, value_name_utf16le);
-        defer allocator.free(value_utf16le);
+        const registry_wtf16le = RegistryWtf16Le{ .key = self.key };
+        const value_wtf16le = try registry_wtf16le.getString(allocator, subkey_wtf16le, value_name_wtf16le);
+        defer allocator.free(value_wtf16le);
 
-        const value_utf8: []u8 = std.unicode.utf16LeToUtf8Alloc(allocator, value_utf16le) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            else => return error.StringNotFound,
-        };
-        errdefer allocator.free(value_utf8);
+        const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_wtf16le);
+        errdefer allocator.free(value_wtf8);
 
-        return value_utf8;
+        return value_wtf8;
     }
 
     /// Get DWORD (u32) from registry.
-    pub fn getDword(self: *const RegistryUtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
-        const subkey_utf16le: [:0]const u16 = subkey_utf16le: {
-            var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
-            const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;
-            subkey_utf16le_buf[subkey_utf16le_len] = 0;
-            break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];
+    pub fn getDword(self: *const RegistryWtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
+        const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
+            var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
+            const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
+            subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
+            break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
         };
 
-        const value_name_utf16le: [:0]const u16 = value_name_utf16le: {
-            var value_name_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
-            const value_name_utf16le_len: usize = std.unicode.utf8ToUtf16Le(value_name_utf16le_buf[0..], value_name) catch unreachable;
-            value_name_utf16le_buf[value_name_utf16le_len] = 0;
-            break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];
+        const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
+            var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
+            const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
+            value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
+            break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
         };
 
-        const registry_utf16le = RegistryUtf16Le{ .key = self.key };
-        return try registry_utf16le.getDword(subkey_utf16le, value_name_utf16le);
+        const registry_wtf16le = RegistryWtf16Le{ .key = self.key };
+        return try registry_wtf16le.getDword(subkey_wtf16le, value_name_wtf16le);
     }
 
     /// Under private space with flags:
     /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
     /// After finishing work, call `closeKey`.
-    pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryUtf8 {
-        const absolute_path_utf16le: [:0]const u16 = absolute_path_utf16le: {
-            var absolute_path_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
-            const absolute_path_utf16le_len: usize = std.unicode.utf8ToUtf16Le(absolute_path_utf16le_buf[0..], absolute_path) catch unreachable;
-            absolute_path_utf16le_buf[absolute_path_utf16le_len] = 0;
-            break :absolute_path_utf16le absolute_path_utf16le_buf[0..absolute_path_utf16le_len :0];
+    pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryWtf8 {
+        const absolute_path_wtf16le: [:0]const u16 = absolute_path_wtf16le: {
+            var absolute_path_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
+            const absolute_path_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(absolute_path_wtf16le_buf[0..], absolute_path) catch unreachable;
+            absolute_path_wtf16le_buf[absolute_path_wtf16le_len] = 0;
+            break :absolute_path_wtf16le absolute_path_wtf16le_buf[0..absolute_path_wtf16le_len :0];
         };
 
-        const registry_utf16le = try RegistryUtf16Le.loadFromPath(absolute_path_utf16le);
-        return RegistryUtf8{ .key = registry_utf16le.key };
+        const registry_wtf16le = try RegistryWtf16Le.loadFromPath(absolute_path_wtf16le);
+        return RegistryWtf8{ .key = registry_wtf16le.key };
     }
 };
 
-const RegistryUtf16Le = struct {
+const RegistryWtf16Le = struct {
     key: windows.HKEY,
 
     /// Includes root key (f.e. HKEY_LOCAL_MACHINE).
@@ -191,11 +188,11 @@ const RegistryUtf16Le = struct {
     /// Under HKEY_LOCAL_MACHINE with flags:
     /// KEY_QUERY_VALUE, KEY_WOW64_32KEY, and KEY_ENUMERATE_SUB_KEYS.
     /// After finishing work, call `closeKey`.
-    fn openKey(hkey: windows.HKEY, key_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le {
+    fn openKey(hkey: windows.HKEY, key_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le {
         var key: windows.HKEY = undefined;
         const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW(
             hkey,
-            key_utf16le,
+            key_wtf16le,
             0,
             windows.KEY_QUERY_VALUE | windows.KEY_WOW64_32KEY | windows.KEY_ENUMERATE_SUB_KEYS,
             &key,
@@ -207,11 +204,11 @@ const RegistryUtf16Le = struct {
 
             else => return error.KeyNotFound,
         }
-        return RegistryUtf16Le{ .key = key };
+        return RegistryWtf16Le{ .key = key };
     }
 
     /// Closes key, after that usage is invalid
-    fn closeKey(self: *const RegistryUtf16Le) void {
+    fn closeKey(self: *const RegistryWtf16Le) void {
         const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
         const return_code: windows.Win32Error = @enumFromInt(return_code_int);
         switch (return_code) {
@@ -221,25 +218,25 @@ const RegistryUtf16Le = struct {
     }
 
     /// Get string ([:0]const u16) from registry.
-    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 {
+    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 {
         var actual_type: windows.ULONG = undefined;
 
         // Calculating length to allocate
-        var value_utf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters.
+        var value_wtf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters.
         var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
             self.key,
-            subkey_utf16le,
-            value_name_utf16le,
+            subkey_wtf16le,
+            value_name_wtf16le,
             RRF.RT_REG_SZ,
             &actual_type,
             null,
-            &value_utf16le_buf_size,
+            &value_wtf16le_buf_size,
         );
 
         // Check returned code and type
         var return_code: windows.Win32Error = @enumFromInt(return_code_int);
         switch (return_code) {
-            .SUCCESS => std.debug.assert(value_utf16le_buf_size != 0),
+            .SUCCESS => std.debug.assert(value_wtf16le_buf_size != 0),
             .MORE_DATA => unreachable, // We are only reading length
             .FILE_NOT_FOUND => return error.ValueNameNotFound,
             .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
@@ -250,17 +247,17 @@ const RegistryUtf16Le = struct {
             else => return error.NotAString,
         }
 
-        const value_utf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_utf16le_buf_size, 2) catch unreachable);
-        errdefer allocator.free(value_utf16le_buf);
+        const value_wtf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable);
+        errdefer allocator.free(value_wtf16le_buf);
 
         return_code_int = windows.advapi32.RegGetValueW(
             self.key,
-            subkey_utf16le,
-            value_name_utf16le,
+            subkey_wtf16le,
+            value_name_wtf16le,
             RRF.RT_REG_SZ,
             &actual_type,
-            value_utf16le_buf.ptr,
-            &value_utf16le_buf_size,
+            value_wtf16le_buf.ptr,
+            &value_wtf16le_buf_size,
         );
 
         // Check returned code and (just in case) type again.
@@ -277,28 +274,28 @@ const RegistryUtf16Le = struct {
             else => return error.NotAString,
         }
 
-        const value_utf16le: []const u16 = value_utf16le: {
+        const value_wtf16le: []const u16 = value_wtf16le: {
             // note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space
             // we will just search for zero termination and forget length
             // Windows sure is strange
-            const value_utf16le_overestimated: [*:0]const u16 = @ptrCast(value_utf16le_buf.ptr);
-            break :value_utf16le std.mem.span(value_utf16le_overestimated);
+            const value_wtf16le_overestimated: [*:0]const u16 = @ptrCast(value_wtf16le_buf.ptr);
+            break :value_wtf16le std.mem.span(value_wtf16le_overestimated);
         };
 
-        _ = allocator.resize(value_utf16le_buf, value_utf16le.len);
-        return value_utf16le;
+        _ = allocator.resize(value_wtf16le_buf, value_wtf16le.len);
+        return value_wtf16le;
     }
 
     /// Get DWORD (u32) from registry.
-    fn getDword(self: *const RegistryUtf16Le, subkey_utf16le: [:0]const u16, value_name_utf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
+    fn getDword(self: *const RegistryWtf16Le, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
         var actual_type: windows.ULONG = undefined;
         var reg_size: u32 = @sizeOf(u32);
         var reg_value: u32 = 0;
 
         const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
             self.key,
-            subkey_utf16le,
-            value_name_utf16le,
+            subkey_wtf16le,
+            value_name_wtf16le,
             RRF.RT_REG_DWORD,
             &actual_type,
             &reg_value,
@@ -324,11 +321,11 @@ const RegistryUtf16Le = struct {
     /// Under private space with flags:
     /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
     /// After finishing work, call `closeKey`.
-    fn loadFromPath(absolute_path_as_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le {
+    fn loadFromPath(absolute_path_as_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le {
         var key: windows.HKEY = undefined;
 
         const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW(
-            absolute_path_as_utf16le,
+            absolute_path_as_wtf16le,
             &key,
             windows.KEY_QUERY_VALUE | windows.KEY_ENUMERATE_SUB_KEYS,
             0,
@@ -340,7 +337,7 @@ const RegistryUtf16Le = struct {
             else => return error.KeyNotFound,
         }
 
-        return RegistryUtf16Le{ .key = key };
+        return RegistryWtf16Le{ .key = key };
     }
 };
 
@@ -352,7 +349,7 @@ pub const Windows10Sdk = struct {
     /// Caller owns the result's fields.
     /// After finishing work, call `free(allocator)`.
     fn find(allocator: std.mem.Allocator) error{ OutOfMemory, Windows10SdkNotFound, PathTooLong, VersionTooLong }!Windows10Sdk {
-        const v10_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) {
+        const v10_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) {
             error.KeyNotFound => return error.Windows10SdkNotFound,
         };
         defer v10_key.closeKey();
@@ -413,11 +410,11 @@ pub const Windows10Sdk = struct {
     /// Check whether this version is enumerated in registry.
     fn isValidVersion(windows10sdk: *const Windows10Sdk) bool {
         var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
-        const reg_query_as_utf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) {
+        const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) {
             error.NoSpaceLeft => return false,
         };
 
-        const options_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_utf8) catch |err| switch (err) {
+        const options_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_wtf8) catch |err| switch (err) {
             error.KeyNotFound => return false,
         };
         defer options_key.closeKey();
@@ -447,7 +444,7 @@ pub const Windows81Sdk = struct {
     /// Find path and version of Windows 8.1 SDK.
     /// Caller owns the result's fields.
     /// After finishing work, call `free(allocator)`.
-    fn find(allocator: std.mem.Allocator, roots_key: *const RegistryUtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk {
+    fn find(allocator: std.mem.Allocator, roots_key: *const RegistryWtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk {
         const path: []const u8 = path81: {
             const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", "KitsRoot81") catch |err| switch (err) {
                 error.NotAString => return error.Windows81SdkNotFound,
@@ -523,7 +520,7 @@ pub const ZigWindowsSDK = struct {
         if (builtin.os.tag != .windows) return error.NotFound;
 
         //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed
-        const roots_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) {
+        const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) {
             error.KeyNotFound => return error.NotFound,
         };
         defer roots_key.closeKey();
@@ -583,7 +580,7 @@ pub const ZigWindowsSDK = struct {
 const MsvcLibDir = struct {
     fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir {
         const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}";
-        const setup_config_key = RegistryUtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) {
+        const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) {
             error.KeyNotFound => return error.PathNotFound,
         };
         defer setup_config_key.closeKey();
@@ -805,13 +802,13 @@ const MsvcLibDir = struct {
             for (vs_versions) |vs_version| allocator.free(vs_version);
             allocator.free(vs_versions);
         }
-        var config_subkey_buf: [RegistryUtf16Le.key_name_max_len * 2]u8 = undefined;
+        var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined;
         const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {
             const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;
             defer allocator.free(privateregistry_absolute_path);
             if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue;
 
-            const visualstudio_registry = RegistryUtf8.loadFromPath(privateregistry_absolute_path) catch continue;
+            const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue;
             defer visualstudio_registry.closeKey();
 
             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 {
                 }
             }
 
-            const vs7_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound;
+            const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound;
             defer vs7_key.closeKey();
             try_vs7_key: {
                 const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) {