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

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

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

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

deps/aro/aro/Compilation.zig+1-1
......@@ -69,7 +69,7 @@ pub const Environment = struct {
6969 const val: ?[]const u8 = std.process.getEnvVarOwned(allocator, env_var_name) catch |err| switch (err) {
7070 error.OutOfMemory => |e| return e,
7171 error.EnvironmentVariableNotFound => null,
72 error.InvalidUtf8 => null,
72 error.InvalidWtf8 => null,
7373 };
7474 @field(env, field.name) = val;
7575 }
deps/aro/aro/Driver.zig+2-1
......@@ -523,7 +523,8 @@ pub fn errorDescription(e: anyerror) []const u8 {
523523 error.NotDir => "is not a directory",
524524 error.NotOpenForReading => "file is not open for reading",
525525 error.NotOpenForWriting => "file is not open for writing",
526 error.InvalidUtf8 => "input is not valid UTF-8",
526 error.InvalidUtf8 => "path is not valid UTF-8",
527 error.InvalidWtf8 => "path is not valid WTF-8",
527528 error.FileBusy => "file is busy",
528529 error.NameTooLong => "file name is too long",
529530 error.AccessDenied => "access denied",
lib/std/Build/Cache.zig+1-1
......@@ -162,7 +162,7 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
162162fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
163163 const relative = try std.fs.path.relative(allocator, prefix, path);
164164 errdefer allocator.free(relative);
165 var component_iterator = std.fs.path.NativeUtf8ComponentIterator.init(relative) catch {
165 var component_iterator = std.fs.path.NativeComponentIterator.init(relative) catch {
166166 return error.NotASubPath;
167167 };
168168 if (component_iterator.root() != null) {
lib/std/Thread.zig+4-9
......@@ -91,7 +91,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
9191 },
9292 .windows => {
9393 var buf: [max_name_len]u16 = undefined;
94 const len = try std.unicode.utf8ToUtf16Le(&buf, name);
94 const len = try std.unicode.wtf8ToWtf16Le(&buf, name);
9595 const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong;
9696
9797 // Note: NT allocates its own copy, no use-after-free here.
......@@ -157,17 +157,12 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
157157}
158158
159159pub const GetNameError = error{
160 // For Windows, the name is converted from UTF16 to UTF8
161 CodepointTooLarge,
162 Utf8CannotEncodeSurrogateHalf,
163 DanglingSurrogateHalf,
164 ExpectedSecondSurrogateHalf,
165 UnexpectedSecondSurrogateHalf,
166
167160 Unsupported,
168161 Unexpected,
169162} || os.PrctlError || os.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError;
170163
164/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
165/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
171166pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 {
172167 buffer_ptr[max_name_len] = 0;
173168 var buffer: [:0]u8 = buffer_ptr;
......@@ -213,7 +208,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
213208 )) {
214209 .SUCCESS => {
215210 const string = @as(*const os.windows.UNICODE_STRING, @ptrCast(&buf));
216 const len = try std.unicode.utf16LeToUtf8(buffer, string.Buffer[0 .. string.Length / 2]);
211 const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer[0 .. string.Length / 2]);
217212 return if (len > 0) buffer[0..len] else null;
218213 },
219214 .NOT_IMPLEMENTED => return error.Unsupported,
lib/std/child_process.zig+21-22
......@@ -129,10 +129,9 @@ pub const ChildProcess = struct {
129129 /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.
130130 NoDevice,
131131
132 /// Windows-only. One of:
133 /// * `cwd` was provided and it could not be re-encoded into UTF16LE, or
134 /// * The `PATH` or `PATHEXT` environment variable contained invalid UTF-8.
135 InvalidUtf8,
132 /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8.
133 /// https://simonsapin.github.io/wtf-8/
134 InvalidWtf8,
136135
137136 /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
138137 CurrentWorkingDirectoryUnlinked,
......@@ -767,7 +766,7 @@ pub const ChildProcess = struct {
767766 };
768767 var piProcInfo: windows.PROCESS_INFORMATION = undefined;
769768
770 const cwd_w = if (self.cwd) |cwd| try unicode.utf8ToUtf16LeAllocZ(self.allocator, cwd) else null;
769 const cwd_w = if (self.cwd) |cwd| try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd) else null;
771770 defer if (cwd_w) |cwd| self.allocator.free(cwd);
772771 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
773772
......@@ -775,8 +774,8 @@ pub const ChildProcess = struct {
775774 defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
776775 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
777776
778 const app_name_utf8 = self.argv[0];
779 const app_name_is_absolute = fs.path.isAbsolute(app_name_utf8);
777 const app_name_wtf8 = self.argv[0];
778 const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8);
780779
781780 // the cwd set in ChildProcess is in effect when choosing the executable path
782781 // to match posix semantics
......@@ -785,11 +784,11 @@ pub const ChildProcess = struct {
785784 // If the app name is absolute, then we need to use its dirname as the cwd
786785 if (app_name_is_absolute) {
787786 cwd_path_w_needs_free = true;
788 const dir = fs.path.dirname(app_name_utf8).?;
789 break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, dir);
787 const dir = fs.path.dirname(app_name_wtf8).?;
788 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, dir);
790789 } else if (self.cwd) |cwd| {
791790 cwd_path_w_needs_free = true;
792 break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, cwd);
791 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd);
793792 } else {
794793 break :x &[_:0]u16{}; // empty for cwd
795794 }
......@@ -800,19 +799,19 @@ pub const ChildProcess = struct {
800799 // into the basename and dirname and use the dirname as an addition to the cwd
801800 // path. This is because NtQueryDirectoryFile cannot accept FileName params with
802801 // path separators.
803 const app_basename_utf8 = fs.path.basename(app_name_utf8);
802 const app_basename_wtf8 = fs.path.basename(app_name_wtf8);
804803 // If the app name is absolute, then the cwd will already have the app's dirname in it,
805804 // so only populate app_dirname if app name is a relative path with > 0 path separators.
806 const maybe_app_dirname_utf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_utf8) else null;
805 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_wtf8) else null;
807806 const app_dirname_w: ?[:0]u16 = x: {
808 if (maybe_app_dirname_utf8) |app_dirname_utf8| {
809 break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, app_dirname_utf8);
807 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
808 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_dirname_wtf8);
810809 }
811810 break :x null;
812811 };
813812 defer if (app_dirname_w != null) self.allocator.free(app_dirname_w.?);
814813
815 const app_name_w = try unicode.utf8ToUtf16LeAllocZ(self.allocator, app_basename_utf8);
814 const app_name_w = try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_basename_wtf8);
816815 defer self.allocator.free(app_name_w);
817816
818817 const cmd_line_w = argvToCommandLineWindows(self.allocator, self.argv) catch |err| switch (err) {
......@@ -1173,7 +1172,7 @@ const CreateProcessSupportedExtension = enum {
11731172 exe,
11741173};
11751174
1176/// Case-insensitive UTF-16 lookup
1175/// Case-insensitive WTF-16 lookup
11771176fn windowsCreateProcessSupportsExtension(ext: []const u16) ?CreateProcessSupportedExtension {
11781177 if (ext.len != 4) return null;
11791178 const State = enum {
......@@ -1237,7 +1236,7 @@ test "windowsCreateProcessSupportsExtension" {
12371236 try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null);
12381237}
12391238
1240pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidUtf8, InvalidArg0 };
1239pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
12411240
12421241/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and
12431242/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice.
......@@ -1320,7 +1319,7 @@ pub fn argvToCommandLineWindows(
13201319 }
13211320 }
13221321
1323 return try unicode.utf8ToUtf16LeAllocZ(allocator, buf.items);
1322 return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
13241323}
13251324
13261325test "argvToCommandLineWindows" {
......@@ -1386,7 +1385,7 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c
13861385 const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv);
13871386 defer std.testing.allocator.free(cmd_line_w);
13881387
1389 const cmd_line = try unicode.utf16LeToUtf8Alloc(std.testing.allocator, cmd_line_w);
1388 const cmd_line = try unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w);
13901389 defer std.testing.allocator.free(cmd_line);
13911390
13921391 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
......@@ -1424,7 +1423,7 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons
14241423 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
14251424 .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .Monotonic) },
14261425 ) catch unreachable;
1427 const len = std.unicode.utf8ToUtf16Le(&tmp_bufw, pipe_path) catch unreachable;
1426 const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
14281427 tmp_bufw[len] = 0;
14291428 break :blk tmp_bufw[0..len :0];
14301429 };
......@@ -1521,10 +1520,10 @@ pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) !
15211520 var it = env_map.iterator();
15221521 var i: usize = 0;
15231522 while (it.next()) |pair| {
1524 i += try unicode.utf8ToUtf16Le(result[i..], pair.key_ptr.*);
1523 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
15251524 result[i] = '=';
15261525 i += 1;
1527 i += try unicode.utf8ToUtf16Le(result[i..], pair.value_ptr.*);
1526 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
15281527 result[i] = 0;
15291528 i += 1;
15301529 }
lib/std/fs.zig+139-38
......@@ -31,18 +31,21 @@ pub const realpathW = os.realpathW;
3131pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
3232pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
3333
34/// This represents the maximum size of a UTF-8 encoded file path that the
34/// This represents the maximum size of a `[]u8` file path that the
3535/// operating system will accept. Paths, including those returned from file
3636/// system operations, may be longer than this length, but such paths cannot
3737/// be successfully passed back in other file system operations. However,
3838/// all path components returned by file system operations are assumed to
39/// fit into a UTF-8 encoded array of this length.
39/// fit into a `u8` array of this length.
4040/// The byte count includes room for a null sentinel byte.
41/// On Windows, `[]u8` file paths are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
42/// On WASI, `[]u8` file paths are encoded as valid UTF-8.
43/// On other platforms, `[]u8` file paths are opaque sequences of bytes with no particular encoding.
4144pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
4245 .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten => os.PATH_MAX,
43 // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
44 // If it would require 4 UTF-8 bytes, then there would be a surrogate
45 // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
46 // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes.
47 // If it would require 4 WTF-8 bytes, then there would be a surrogate
48 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
4649 // +1 for the null byte at the end, which can be encoded in 1 byte.
4750 .windows => os.windows.PATH_MAX_WIDE * 3 + 1,
4851 // TODO work out what a reasonable value we should use here
......@@ -53,18 +56,21 @@ pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
5356 @compileError("PATH_MAX not implemented for " ++ @tagName(builtin.os.tag)),
5457};
5558
56/// This represents the maximum size of a UTF-8 encoded file name component that
59/// This represents the maximum size of a `[]u8` file name component that
5760/// the platform's common file systems support. File name components returned by file system
58/// operations are likely to fit into a UTF-8 encoded array of this length, but
61/// operations are likely to fit into a `u8` array of this length, but
5962/// (depending on the platform) this assumption may not hold for every configuration.
6063/// The byte count does not include a null sentinel byte.
64/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
65/// On WASI, file name components are encoded as valid UTF-8.
66/// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding.
6167pub const MAX_NAME_BYTES = switch (builtin.os.tag) {
6268 .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos => os.NAME_MAX,
6369 // Haiku's NAME_MAX includes the null terminator, so subtract one.
6470 .haiku => os.NAME_MAX - 1,
65 // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
66 // If it would require 4 UTF-8 bytes, then there would be a surrogate
67 // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
71 // Each WTF-16LE character may be expanded to 3 WTF-8 bytes.
72 // If it would require 4 WTF-8 bytes, then there would be a surrogate
73 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
6874 .windows => os.windows.NAME_MAX * 3,
6975 // For WASI, the MAX_NAME will depend on the host OS, so it needs to be
7076 // as large as the largest MAX_NAME_BYTES (Windows) in order to work on any host OS.
......@@ -86,6 +92,9 @@ pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);
8692
8793/// TODO remove the allocator requirement from this API
8894/// TODO move to Dir
95/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
96/// On WASI, both paths should be encoded as valid UTF-8.
97/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
8998pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
9099 if (cwd().symLink(existing_path, new_path, .{})) {
91100 return;
......@@ -117,6 +126,9 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:
117126/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`
118127/// are absolute. See `Dir.updateFile` for a function that operates on both
119128/// absolute and relative paths.
129/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
130/// On WASI, both paths should be encoded as valid UTF-8.
131/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
120132pub fn updateFileAbsolute(
121133 source_path: []const u8,
122134 dest_path: []const u8,
......@@ -131,6 +143,9 @@ pub fn updateFileAbsolute(
131143/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
132144/// are absolute. See `Dir.copyFile` for a function that operates on both
133145/// absolute and relative paths.
146/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
147/// On WASI, both paths should be encoded as valid UTF-8.
148/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
134149pub fn copyFileAbsolute(
135150 source_path: []const u8,
136151 dest_path: []const u8,
......@@ -145,24 +160,30 @@ pub fn copyFileAbsolute(
145160/// Create a new directory, based on an absolute path.
146161/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
147162/// on both absolute and relative paths.
163/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
164/// On WASI, `absolute_path` should be encoded as valid UTF-8.
165/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
148166pub fn makeDirAbsolute(absolute_path: []const u8) !void {
149167 assert(path.isAbsolute(absolute_path));
150168 return os.mkdir(absolute_path, Dir.default_mode);
151169}
152170
153/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF-8-encoded string.
171/// Same as `makeDirAbsolute` except the parameter is null-terminated.
154172pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
155173 assert(path.isAbsoluteZ(absolute_path_z));
156174 return os.mkdirZ(absolute_path_z, Dir.default_mode);
157175}
158176
159/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16-encoded string.
177/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.
160178pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
161179 assert(path.isAbsoluteWindowsW(absolute_path_w));
162180 return os.mkdirW(absolute_path_w, Dir.default_mode);
163181}
164182
165183/// Same as `Dir.deleteDir` except the path is absolute.
184/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
185/// On WASI, `dir_path` should be encoded as valid UTF-8.
186/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
166187pub fn deleteDirAbsolute(dir_path: []const u8) !void {
167188 assert(path.isAbsolute(dir_path));
168189 return os.rmdir(dir_path);
......@@ -181,6 +202,9 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {
181202}
182203
183204/// Same as `Dir.rename` except the paths are absolute.
205/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
206/// On WASI, both paths should be encoded as valid UTF-8.
207/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
184208pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
185209 assert(path.isAbsolute(old_path));
186210 assert(path.isAbsolute(new_path));
......@@ -211,7 +235,7 @@ pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_su
211235 return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
212236}
213237
214/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
238/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
215239/// This function is Windows-only.
216240pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
217241 return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
......@@ -240,6 +264,9 @@ pub fn defaultWasiCwd() std.os.wasi.fd_t {
240264/// See `openDirAbsoluteZ` for a function that accepts a null-terminated path.
241265///
242266/// Asserts that the path parameter has no null bytes.
267/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
268/// On WASI, `absolute_path` should be encoded as valid UTF-8.
269/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
243270pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
244271 assert(path.isAbsolute(absolute_path));
245272 return cwd().openDir(absolute_path, flags);
......@@ -262,6 +289,9 @@ pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptio
262289/// operates on both absolute and relative paths.
263290/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function
264291/// that accepts a null-terminated path.
292/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
293/// On WASI, `absolute_path` should be encoded as valid UTF-8.
294/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
265295pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
266296 assert(path.isAbsolute(absolute_path));
267297 return cwd().openFile(absolute_path, flags);
......@@ -280,11 +310,13 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
280310}
281311
282312/// Test accessing `path`.
283/// `path` is UTF-8-encoded.
284313/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
285314/// For example, instead of testing if a file exists and then opening it, just
286315/// open it and handle the error for file not found.
287316/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
317/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
318/// On WASI, `absolute_path` should be encoded as valid UTF-8.
319/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
288320pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
289321 assert(path.isAbsolute(absolute_path));
290322 try cwd().access(absolute_path, flags);
......@@ -306,6 +338,9 @@ pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir
306338/// operates on both absolute and relative paths.
307339/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function
308340/// that accepts a null-terminated path.
341/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
342/// On WASI, `absolute_path` should be encoded as valid UTF-8.
343/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
309344pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
310345 assert(path.isAbsolute(absolute_path));
311346 return cwd().createFile(absolute_path, flags);
......@@ -327,6 +362,9 @@ pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFl
327362/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that
328363/// operates on both absolute and relative paths.
329364/// Asserts that the path parameter has no null bytes.
365/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
366/// On WASI, `absolute_path` should be encoded as valid UTF-8.
367/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
330368pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
331369 assert(path.isAbsolute(absolute_path));
332370 return cwd().deleteFile(absolute_path);
......@@ -349,6 +387,9 @@ pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!
349387/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that
350388/// operates on both absolute and relative paths.
351389/// Asserts that the path parameter has no null bytes.
390/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
391/// On WASI, `absolute_path` should be encoded as valid UTF-8.
392/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
352393pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
353394 assert(path.isAbsolute(absolute_path));
354395 const dirname = path.dirname(absolute_path) orelse return error{
......@@ -364,6 +405,9 @@ pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
364405}
365406
366407/// Same as `Dir.readLink`, except it asserts the path is absolute.
408/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
409/// On WASI, `pathname` should be encoded as valid UTF-8.
410/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
367411pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
368412 assert(path.isAbsolute(pathname));
369413 return os.readlink(pathname, buffer);
......@@ -387,6 +431,9 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8)
387431/// one; the latter case is known as a dangling link.
388432/// If `sym_link_path` exists, it will not be overwritten.
389433/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
434/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
435/// On WASI, both paths should be encoded as valid UTF-8.
436/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
390437pub fn symLinkAbsolute(
391438 target_path: []const u8,
392439 sym_link_path: []const u8,
......@@ -402,7 +449,7 @@ pub fn symLinkAbsolute(
402449 return os.symlink(target_path, sym_link_path);
403450}
404451
405/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 encoded.
452/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 LE encoded.
406453/// Note that this function will by default try creating a symbolic link to a file. If you would
407454/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
408455/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
......@@ -426,27 +473,14 @@ pub fn symLinkAbsoluteZ(
426473 assert(path.isAbsoluteZ(target_path_c));
427474 assert(path.isAbsoluteZ(sym_link_path_c));
428475 if (builtin.os.tag == .windows) {
429 const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
430 const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
431 return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
476 const target_path_w = try os.windows.cStrToPrefixedFileW(null, target_path_c);
477 const sym_link_path_w = try os.windows.cStrToPrefixedFileW(null, sym_link_path_c);
478 return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
432479 }
433480 return os.symlinkZ(target_path_c, sym_link_path_c);
434481}
435482
436pub const OpenSelfExeError = error{
437 SharingViolation,
438 PathAlreadyExists,
439 FileNotFound,
440 AccessDenied,
441 PipeBusy,
442 NameTooLong,
443 /// On Windows, file paths must be valid Unicode.
444 InvalidUtf8,
445 /// On Windows, file paths cannot contain these characters:
446 /// '/', '*', '?', '"', '<', '>', '|'
447 BadPathName,
448 Unexpected,
449} || os.OpenError || SelfExePathError || os.FlockError;
483pub const OpenSelfExeError = os.OpenError || SelfExePathError || os.FlockError;
450484
451485pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
452486 if (builtin.os.tag == .linux) {
......@@ -469,7 +503,45 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
469503 return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags);
470504}
471505
472pub const SelfExePathError = os.ReadLinkError || os.SysCtlError || os.RealPathError;
506// This is os.ReadLinkError || os.RealPathError with impossible errors excluded
507pub const SelfExePathError = error{
508 FileNotFound,
509 AccessDenied,
510 NameTooLong,
511 NotSupported,
512 NotDir,
513 SymLinkLoop,
514 InputOutput,
515 FileTooBig,
516 IsDir,
517 ProcessFdQuotaExceeded,
518 SystemFdQuotaExceeded,
519 NoDevice,
520 SystemResources,
521 NoSpaceLeft,
522 FileSystem,
523 BadPathName,
524 DeviceBusy,
525 SharingViolation,
526 PipeBusy,
527 NotLink,
528 PathAlreadyExists,
529 InvalidHandle,
530
531 /// On Windows, `\\server` or `\\server\share` was not found.
532 NetworkNotFound,
533
534 /// On Windows, antivirus software is enabled by default. It can be
535 /// disabled, but Windows Update sometimes ignores the user's preference
536 /// and re-enables it. When enabled, antivirus software on Windows
537 /// intercepts file system operations and makes them significantly slower
538 /// in addition to possibly failing with this error code.
539 AntivirusInterference,
540
541 /// On Windows, the volume does not contain a recognized file system. File
542 /// system drivers might not be loaded, or the volume may be corrupt.
543 UnrecognizedVolume,
544} || os.SysCtlError;
473545
474546/// `selfExePath` except allocates the result on the heap.
475547/// Caller owns returned memory.
......@@ -491,6 +563,8 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {
491563/// This function may return an error if the current executable
492564/// was deleted after spawning.
493565/// Returned value is a slice of out_buffer.
566/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
567/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
494568///
495569/// On Linux, depends on procfs being mounted. If the currently executing binary has
496570/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
......@@ -505,15 +579,31 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
505579 if (rc != 0) return error.NameTooLong;
506580
507581 var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
508 const real_path = try std.os.realpathZ(&symlink_path_buf, &real_path_buf);
582 const real_path = std.os.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) {
583 error.InvalidWtf8 => unreachable, // Windows-only
584 error.NetworkNotFound => unreachable, // Windows-only
585 else => |e| return e,
586 };
509587 if (real_path.len > out_buffer.len) return error.NameTooLong;
510588 const result = out_buffer[0..real_path.len];
511589 @memcpy(result, real_path);
512590 return result;
513591 }
514592 switch (builtin.os.tag) {
515 .linux => return os.readlinkZ("/proc/self/exe", out_buffer),
516 .solaris, .illumos => return os.readlinkZ("/proc/self/path/a.out", out_buffer),
593 .linux => return os.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) {
594 error.InvalidUtf8 => unreachable, // WASI-only
595 error.InvalidWtf8 => unreachable, // Windows-only
596 error.UnsupportedReparsePointType => unreachable, // Windows-only
597 error.NetworkNotFound => unreachable, // Windows-only
598 else => |e| return e,
599 },
600 .solaris, .illumos => return os.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) {
601 error.InvalidUtf8 => unreachable, // WASI-only
602 error.InvalidWtf8 => unreachable, // Windows-only
603 error.UnsupportedReparsePointType => unreachable, // Windows-only
604 error.NetworkNotFound => unreachable, // Windows-only
605 else => |e| return e,
606 },
517607 .freebsd, .dragonfly => {
518608 var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC, os.KERN.PROC_PATHNAME, -1 };
519609 var out_len: usize = out_buffer.len;
......@@ -537,7 +627,11 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
537627 if (mem.indexOf(u8, argv0, "/") != null) {
538628 // argv[0] is a path (relative or absolute): use realpath(3) directly
539629 var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
540 const real_path = try os.realpathZ(os.argv[0], &real_path_buf);
630 const real_path = os.realpathZ(os.argv[0], &real_path_buf) catch |err| switch (err) {
631 error.InvalidWtf8 => unreachable, // Windows-only
632 error.NetworkNotFound => unreachable, // Windows-only
633 else => |e| return e,
634 };
541635 if (real_path.len > out_buffer.len)
542636 return error.NameTooLong;
543637 const result = out_buffer[0..real_path.len];
......@@ -575,7 +669,10 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
575669 // symlink, not the path that the symlink points to. We want the path
576670 // that the symlink points to, though, so we need to get the realpath.
577671 const pathname_w = try os.windows.wToPrefixedFileW(null, image_path_name);
578 return std.fs.cwd().realpathW(pathname_w.span(), out_buffer);
672 return std.fs.cwd().realpathW(pathname_w.span(), out_buffer) catch |err| switch (err) {
673 error.InvalidWtf8 => unreachable,
674 else => |e| return e,
675 };
579676 },
580677 else => @compileError("std.fs.selfExePath not supported for this target"),
581678 }
......@@ -599,6 +696,8 @@ pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 {
599696
600697/// Get the directory path that contains the current executable.
601698/// Returned value is a slice of out_buffer.
699/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
700/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
602701pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
603702 const self_exe_path = try selfExePath(out_buffer);
604703 // Assume that the OS APIs return absolute paths, and therefore dirname
......@@ -607,6 +706,8 @@ pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
607706}
608707
609708/// `realpath`, except caller must free the returned memory.
709/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
710/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
610711/// See also `Dir.realpath`.
611712pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
612713 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
lib/std/fs/Dir.zig+128-36
......@@ -9,7 +9,14 @@ pub const Entry = struct {
99 pub const Kind = File.Kind;
1010};
1111
12const IteratorError = error{ AccessDenied, SystemResources } || posix.UnexpectedError;
12const IteratorError = error{
13 AccessDenied,
14 SystemResources,
15 /// WASI-only. The path of an entry could not be encoded as valid UTF-8.
16 /// WASI is unable to handle paths that cannot be encoded as well-formed UTF-8.
17 /// https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
18 InvalidUtf8,
19} || posix.UnexpectedError;
1320
1421pub const Iterator = switch (builtin.os.tag) {
1522 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {
......@@ -445,13 +452,12 @@ pub const Iterator = switch (builtin.os.tag) {
445452 self.index = self.buf.len;
446453 }
447454
448 const name_utf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
455 const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
449456
450 if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' }))
457 if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' }))
451458 continue;
452 // Trust that Windows gives us valid UTF-16LE
453 const name_utf8_len = std.unicode.utf16LeToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
454 const name_utf8 = self.name_data[0..name_utf8_len];
459 const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le);
460 const name_wtf8 = self.name_data[0..name_wtf8_len];
455461 const kind: Entry.Kind = blk: {
456462 const attrs = dir_info.FileAttributes;
457463 if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
......@@ -459,7 +465,7 @@ pub const Iterator = switch (builtin.os.tag) {
459465 break :blk .file;
460466 };
461467 return Entry{
462 .name = name_utf8,
468 .name = name_wtf8,
463469 .kind = kind,
464470 };
465471 }
......@@ -516,6 +522,7 @@ pub const Iterator = switch (builtin.os.tag) {
516522 .INVAL => unreachable,
517523 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
518524 .NOTCAPABLE => return error.AccessDenied,
525 .ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
519526 else => |err| return posix.unexpectedErrno(err),
520527 }
521528 if (bufused == 0) return null;
......@@ -743,7 +750,11 @@ pub const OpenError = error{
743750 SystemFdQuotaExceeded,
744751 NoDevice,
745752 SystemResources,
753 /// WASI-only; file paths must be valid UTF-8.
746754 InvalidUtf8,
755 /// Windows-only; file paths provided by the user must be valid WTF-8.
756 /// https://simonsapin.github.io/wtf-8/
757 InvalidWtf8,
747758 BadPathName,
748759 DeviceBusy,
749760 /// On Windows, `\\server` or `\\server\share` was not found.
......@@ -759,6 +770,9 @@ pub fn close(self: *Dir) void {
759770/// To create a new file, see `createFile`.
760771/// Call `File.close` to release the resource.
761772/// Asserts that the path parameter has no null bytes.
773/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
774/// On WASI, `sub_path` should be encoded as valid UTF-8.
775/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
762776pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
763777 if (builtin.os.tag == .windows) {
764778 const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
......@@ -911,6 +925,9 @@ pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File
911925/// Creates, opens, or overwrites a file with write access.
912926/// Call `File.close` on the result when done.
913927/// Asserts that the path parameter has no null bytes.
928/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
929/// On WASI, `sub_path` should be encoded as valid UTF-8.
930/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
914931pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
915932 if (builtin.os.tag == .windows) {
916933 const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
......@@ -1060,18 +1077,21 @@ pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags)
10601077/// Creates a single directory with a relative or absolute path.
10611078/// To create multiple directories to make an entire path, see `makePath`.
10621079/// To operate on only absolute paths, see `makeDirAbsolute`.
1080/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1081/// On WASI, `sub_path` should be encoded as valid UTF-8.
1082/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
10631083pub fn makeDir(self: Dir, sub_path: []const u8) !void {
10641084 try posix.mkdirat(self.fd, sub_path, default_mode);
10651085}
10661086
1067/// Creates a single directory with a relative or absolute null-terminated UTF-8-encoded path.
1087/// Same as `makeDir`, but `sub_path` is null-terminated.
10681088/// To create multiple directories to make an entire path, see `makePath`.
10691089/// To operate on only absolute paths, see `makeDirAbsoluteZ`.
10701090pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
10711091 try posix.mkdiratZ(self.fd, sub_path, default_mode);
10721092}
10731093
1074/// Creates a single directory with a relative or absolute null-terminated WTF-16-encoded path.
1094/// Creates a single directory with a relative or absolute null-terminated WTF-16 LE-encoded path.
10751095/// To create multiple directories to make an entire path, see `makePath`.
10761096/// To operate on only absolute paths, see `makeDirAbsoluteW`.
10771097pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
......@@ -1083,6 +1103,9 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
10831103/// Returns success if the path already exists and is a directory.
10841104/// This function is not atomic, and if it returns an error, the file system may
10851105/// have been modified regardless.
1106/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1107/// On WASI, `sub_path` should be encoded as valid UTF-8.
1108/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
10861109///
10871110/// Paths containing `..` components are handled differently depending on the platform:
10881111/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
......@@ -1119,16 +1142,17 @@ pub fn makePath(self: Dir, sub_path: []const u8) !void {
11191142 }
11201143}
11211144
1122/// Calls makeOpenDirAccessMaskW iteratively to make an entire path
1145/// Windows only. Calls makeOpenDirAccessMaskW iteratively to make an entire path
11231146/// (i.e. creating any parent directories that do not exist).
11241147/// Opens the dir if the path already exists and is a directory.
11251148/// This function is not atomic, and if it returns an error, the file system may
11261149/// have been modified regardless.
1150/// `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
11271151fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
11281152 const w = std.os.windows;
11291153 var it = try fs.path.componentIterator(sub_path);
11301154 // If there are no components in the path, then create a dummy component with the full path.
1131 var component = it.last() orelse fs.path.NativeUtf8ComponentIterator.Component{
1155 var component = it.last() orelse fs.path.NativeComponentIterator.Component{
11321156 .name = "",
11331157 .path = sub_path,
11341158 };
......@@ -1156,7 +1180,9 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no
11561180/// This function performs `makePath`, followed by `openDir`.
11571181/// If supported by the OS, this operation is atomic. It is not atomic on
11581182/// all operating systems.
1159/// On Windows, this function performs `makeOpenPathAccessMaskW`.
1183/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1184/// On WASI, `sub_path` should be encoded as valid UTF-8.
1185/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
11601186pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
11611187 return switch (builtin.os.tag) {
11621188 .windows => {
......@@ -1185,6 +1211,10 @@ pub const RealPathError = posix.RealPathError;
11851211/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
11861212/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
11871213/// argument.
1214/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1215/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1216/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1217/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
11881218/// This function is not universally supported by all platforms.
11891219/// Currently supported hosts are: Linux, macOS, and Windows.
11901220/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
......@@ -1224,6 +1254,7 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
12241254 error.FileLocksNotSupported => return error.Unexpected,
12251255 error.FileBusy => return error.Unexpected,
12261256 error.WouldBlock => return error.Unexpected,
1257 error.InvalidUtf8 => unreachable, // WASI-only
12271258 else => |e| return e,
12281259 };
12291260 defer posix.close(fd);
......@@ -1246,7 +1277,8 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
12461277 return result;
12471278}
12481279
1249/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded.
1280/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded.
1281/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
12501282/// See also `Dir.realpath`, `realpathW`.
12511283pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {
12521284 const w = std.os.windows;
......@@ -1272,16 +1304,7 @@ pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathErr
12721304 var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
12731305 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
12741306 var big_out_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
1275 const end_index = std.unicode.utf16leToUtf8(&big_out_buf, wide_slice) catch |e| switch (e) {
1276 // TODO: Windows file paths can be arbitrary arrays of u16 values and
1277 // must not fail with InvalidUtf8.
1278 error.DanglingSurrogateHalf,
1279 error.ExpectedSecondSurrogateHalf,
1280 error.UnexpectedSecondSurrogateHalf,
1281 error.CodepointTooLarge,
1282 error.Utf8CannotEncodeSurrogateHalf,
1283 => return error.InvalidUtf8,
1284 };
1307 const end_index = std.unicode.wtf16LeToWtf8(&big_out_buf, wide_slice);
12851308 if (end_index > out_buffer.len)
12861309 return error.NameTooLong;
12871310 const result = out_buffer[0..end_index];
......@@ -1344,6 +1367,9 @@ pub const OpenDirOptions = struct {
13441367/// open until `close` is called on the result.
13451368/// The directory cannot be iterated unless the `iterate` option is set to `true`.
13461369///
1370/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1371/// On WASI, `sub_path` should be encoded as valid UTF-8.
1372/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
13471373/// Asserts that the path parameter has no null bytes.
13481374pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
13491375 switch (builtin.os.tag) {
......@@ -1428,7 +1454,7 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) Open
14281454 }
14291455}
14301456
1431/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
1457/// Same as `openDir` except the path parameter is WTF-16 LE encoded, NT-prefixed.
14321458/// This function asserts the target OS is Windows.
14331459pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
14341460 const w = std.os.windows;
......@@ -1518,6 +1544,9 @@ fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u3
15181544pub const DeleteFileError = posix.UnlinkError;
15191545
15201546/// Delete a file name and possibly the file it refers to, based on an open directory handle.
1547/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1548/// On WASI, `sub_path` should be encoded as valid UTF-8.
1549/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
15211550/// Asserts that the path parameter has no null bytes.
15221551pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
15231552 if (builtin.os.tag == .windows) {
......@@ -1553,7 +1582,7 @@ pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
15531582 };
15541583}
15551584
1556/// Same as `deleteFile` except the parameter is WTF-16 encoded.
1585/// Same as `deleteFile` except the parameter is WTF-16 LE encoded.
15571586pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
15581587 posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
15591588 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
......@@ -1572,7 +1601,11 @@ pub const DeleteDirError = error{
15721601 NotDir,
15731602 SystemResources,
15741603 ReadOnlyFileSystem,
1604 /// WASI-only; file paths must be valid UTF-8.
15751605 InvalidUtf8,
1606 /// Windows-only; file paths provided by the user must be valid WTF-8.
1607 /// https://simonsapin.github.io/wtf-8/
1608 InvalidWtf8,
15761609 BadPathName,
15771610 /// On Windows, `\\server` or `\\server\share` was not found.
15781611 NetworkNotFound,
......@@ -1581,6 +1614,9 @@ pub const DeleteDirError = error{
15811614
15821615/// Returns `error.DirNotEmpty` if the directory is not empty.
15831616/// To delete a directory recursively, see `deleteTree`.
1617/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1618/// On WASI, `sub_path` should be encoded as valid UTF-8.
1619/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
15841620/// Asserts that the path parameter has no null bytes.
15851621pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
15861622 if (builtin.os.tag == .windows) {
......@@ -1605,7 +1641,7 @@ pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
16051641 };
16061642}
16071643
1608/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
1644/// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed.
16091645/// This function is Windows-only.
16101646pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
16111647 posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
......@@ -1620,6 +1656,9 @@ pub const RenameError = posix.RenameError;
16201656/// If new_sub_path already exists, it will be replaced.
16211657/// Renaming a file over an existing directory or a directory
16221658/// over an existing file will fail with `error.IsDir` or `error.NotDir`
1659/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1660/// On WASI, both paths should be encoded as valid UTF-8.
1661/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
16231662pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
16241663 return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
16251664}
......@@ -1629,7 +1668,7 @@ pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]co
16291668 return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
16301669}
16311670
1632/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
1671/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
16331672/// This function is Windows-only.
16341673pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
16351674 return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
......@@ -1647,6 +1686,9 @@ pub const SymLinkFlags = struct {
16471686/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
16481687/// one; the latter case is known as a dangling link.
16491688/// If `sym_link_path` exists, it will not be overwritten.
1689/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1690/// On WASI, both paths should be encoded as valid UTF-8.
1691/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
16501692pub fn symLink(
16511693 self: Dir,
16521694 target_path: []const u8,
......@@ -1662,7 +1704,7 @@ pub fn symLink(
16621704 // when converting to an NT namespaced path. CreateSymbolicLink in
16631705 // symLinkW will handle the necessary conversion.
16641706 var target_path_w: std.os.windows.PathSpace = undefined;
1665 target_path_w.len = try std.unicode.utf8ToUtf16Le(&target_path_w.data, target_path);
1707 target_path_w.len = try std.unicode.wtf8ToWtf16Le(&target_path_w.data, target_path);
16661708 target_path_w.data[target_path_w.len] = 0;
16671709 const sym_link_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sym_link_path);
16681710 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
......@@ -1698,7 +1740,7 @@ pub fn symLinkZ(
16981740}
16991741
17001742/// Windows-only. Same as `symLink` except the pathname parameters
1701/// are null-terminated, WTF16 encoded.
1743/// are WTF16 LE encoded.
17021744pub fn symLinkW(
17031745 self: Dir,
17041746 /// WTF-16, does not need to be NT-prefixed. The NT-prefixing
......@@ -1716,6 +1758,9 @@ pub const ReadLinkError = posix.ReadLinkError;
17161758/// Read value of a symbolic link.
17171759/// The return value is a slice of `buffer`, from index `0`.
17181760/// Asserts that the path parameter has no null bytes.
1761/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1762/// On WASI, `sub_path` should be encoded as valid UTF-8.
1763/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
17191764pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
17201765 if (builtin.os.tag == .wasi and !builtin.link_libc) {
17211766 return self.readLinkWasi(sub_path, buffer);
......@@ -1733,7 +1778,7 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
17331778 return posix.readlinkat(self.fd, sub_path, buffer);
17341779}
17351780
1736/// Same as `readLink`, except the `pathname` parameter is null-terminated.
1781/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.
17371782pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
17381783 if (builtin.os.tag == .windows) {
17391784 const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
......@@ -1743,7 +1788,7 @@ pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
17431788}
17441789
17451790/// Windows-only. Same as `readLink` except the pathname parameter
1746/// is null-terminated, WTF16 encoded.
1791/// is WTF16 LE encoded.
17471792pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
17481793 return std.os.windows.ReadLink(self.fd, sub_path_w, buffer);
17491794}
......@@ -1753,6 +1798,9 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
17531798/// the situation is ambiguous. It could either mean that the entire file was read, and
17541799/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
17551800/// entire file.
1801/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1802/// On WASI, `file_path` should be encoded as valid UTF-8.
1803/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
17561804pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
17571805 var file = try self.openFile(file_path, .{});
17581806 defer file.close();
......@@ -1763,6 +1811,9 @@ pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
17631811
17641812/// On success, caller owns returned buffer.
17651813/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1814/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1815/// On WASI, `file_path` should be encoded as valid UTF-8.
1816/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
17661817pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
17671818 return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);
17681819}
......@@ -1772,6 +1823,9 @@ pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8,
17721823/// If `size_hint` is specified the initial buffer size is calculated using
17731824/// that value, otherwise the effective file size is used instead.
17741825/// Allows specifying alignment and a sentinel value.
1826/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1827/// On WASI, `file_path` should be encoded as valid UTF-8.
1828/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
17751829pub fn readFileAllocOptions(
17761830 self: Dir,
17771831 allocator: mem.Allocator,
......@@ -1811,9 +1865,13 @@ pub const DeleteTreeError = error{
18111865 /// This error is unreachable if `sub_path` does not contain a path separator.
18121866 NotDir,
18131867
1814 /// On Windows, file paths must be valid Unicode.
1868 /// WASI-only; file paths must be valid UTF-8.
18151869 InvalidUtf8,
18161870
1871 /// Windows-only; file paths provided by the user must be valid WTF-8.
1872 /// https://simonsapin.github.io/wtf-8/
1873 InvalidWtf8,
1874
18171875 /// On Windows, file paths cannot contain these characters:
18181876 /// '/', '*', '?', '"', '<', '>', '|'
18191877 BadPathName,
......@@ -1826,6 +1884,9 @@ pub const DeleteTreeError = error{
18261884/// removes it. If it cannot be removed because it is a non-empty directory,
18271885/// this function recursively removes its entries and then tries again.
18281886/// This operation is not atomic on most file systems.
1887/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1888/// On WASI, `sub_path` should be encoded as valid UTF-8.
1889/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
18291890pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
18301891 var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
18311892
......@@ -1879,6 +1940,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
18791940 error.SystemResources,
18801941 error.Unexpected,
18811942 error.InvalidUtf8,
1943 error.InvalidWtf8,
18821944 error.BadPathName,
18831945 error.NetworkNotFound,
18841946 error.DeviceBusy,
......@@ -1910,6 +1972,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
19101972
19111973 error.AccessDenied,
19121974 error.InvalidUtf8,
1975 error.InvalidWtf8,
19131976 error.SymLinkLoop,
19141977 error.NameTooLong,
19151978 error.SystemResources,
......@@ -1973,6 +2036,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
19732036 error.SystemResources,
19742037 error.Unexpected,
19752038 error.InvalidUtf8,
2039 error.InvalidWtf8,
19762040 error.BadPathName,
19772041 error.NetworkNotFound,
19782042 error.DeviceBusy,
......@@ -1994,6 +2058,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
19942058
19952059 error.AccessDenied,
19962060 error.InvalidUtf8,
2061 error.InvalidWtf8,
19972062 error.SymLinkLoop,
19982063 error.NameTooLong,
19992064 error.SystemResources,
......@@ -2022,6 +2087,9 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
20222087
20232088/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
20242089/// This is slower than `deleteTree` but uses less stack space.
2090/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2091/// On WASI, `sub_path` should be encoded as valid UTF-8.
2092/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
20252093pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
20262094 return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
20272095}
......@@ -2074,6 +2142,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
20742142 error.SystemResources,
20752143 error.Unexpected,
20762144 error.InvalidUtf8,
2145 error.InvalidWtf8,
20772146 error.BadPathName,
20782147 error.NetworkNotFound,
20792148 error.DeviceBusy,
......@@ -2102,6 +2171,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
21022171
21032172 error.AccessDenied,
21042173 error.InvalidUtf8,
2174 error.InvalidWtf8,
21052175 error.SymLinkLoop,
21062176 error.NameTooLong,
21072177 error.SystemResources,
......@@ -2171,6 +2241,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
21712241 error.SystemResources,
21722242 error.Unexpected,
21732243 error.InvalidUtf8,
2244 error.InvalidWtf8,
21742245 error.BadPathName,
21752246 error.DeviceBusy,
21762247 error.NetworkNotFound,
......@@ -2189,6 +2260,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
21892260
21902261 error.AccessDenied,
21912262 error.InvalidUtf8,
2263 error.InvalidWtf8,
21922264 error.SymLinkLoop,
21932265 error.NameTooLong,
21942266 error.SystemResources,
......@@ -2209,6 +2281,9 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
22092281pub const WriteFileError = File.WriteError || File.OpenError;
22102282
22112283/// Deprecated: use `writeFile2`.
2284/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2285/// On WASI, `sub_path` should be encoded as valid UTF-8.
2286/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
22122287pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void {
22132288 return writeFile2(self, .{
22142289 .sub_path = sub_path,
......@@ -2218,6 +2293,9 @@ pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileErr
22182293}
22192294
22202295pub const WriteFileOptions = struct {
2296 /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2297 /// On WASI, `sub_path` should be encoded as valid UTF-8.
2298 /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
22212299 sub_path: []const u8,
22222300 data: []const u8,
22232301 flags: File.CreateFlags = .{},
......@@ -2232,8 +2310,10 @@ pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void {
22322310
22332311pub const AccessError = posix.AccessError;
22342312
2235/// Test accessing `path`.
2236/// `path` is UTF-8-encoded.
2313/// Test accessing `sub_path`.
2314/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2315/// On WASI, `sub_path` should be encoded as valid UTF-8.
2316/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
22372317/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
22382318/// For example, instead of testing if a file exists and then opening it, just
22392319/// open it and handle the error for file not found.
......@@ -2268,9 +2348,9 @@ pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) Access
22682348}
22692349
22702350/// Same as `access` except asserts the target OS is Windows and the path parameter is
2271/// * WTF-16 encoded
2351/// * WTF-16 LE encoded
22722352/// * null-terminated
2273/// * NtDll prefixed
2353/// * relative or has the NT namespace prefix
22742354/// TODO currently this ignores `flags`.
22752355pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
22762356 _ = flags;
......@@ -2292,6 +2372,9 @@ pub const PrevStatus = enum {
22922372/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
22932373/// Returns the previous status of the file before updating.
22942374/// If any of the directories do not exist for dest_path, they are created.
2375/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2376/// On WASI, both paths should be encoded as valid UTF-8.
2377/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
22952378pub fn updateFile(
22962379 source_dir: Dir,
22972380 source_path: []const u8,
......@@ -2343,6 +2426,9 @@ pub const CopyFileError = File.OpenError || File.StatError ||
23432426/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
23442427/// there is a possibility of power loss or application termination leaving temporary files present
23452428/// in the same directory as dest_path.
2429/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2430/// On WASI, both paths should be encoded as valid UTF-8.
2431/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
23462432pub fn copyFile(
23472433 source_dir: Dir,
23482434 source_path: []const u8,
......@@ -2430,6 +2516,9 @@ pub const AtomicFileOptions = struct {
24302516/// Always call `AtomicFile.deinit` to clean up, regardless of whether
24312517/// `AtomicFile.finish` succeeded. `dest_path` must remain valid until
24322518/// `AtomicFile.deinit` is called.
2519/// On Windows, `dest_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2520/// On WASI, `dest_path` should be encoded as valid UTF-8.
2521/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
24332522pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
24342523 if (fs.path.dirname(dest_path)) |dirname| {
24352524 const dir = if (options.make_path)
......@@ -2461,6 +2550,9 @@ pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError
24612550/// Symlinks are followed.
24622551///
24632552/// `sub_path` may be absolute, in which case `self` is ignored.
2553/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2554/// On WASI, `sub_path` should be encoded as valid UTF-8.
2555/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
24642556pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
24652557 if (builtin.os.tag == .windows) {
24662558 var file = try self.openFile(sub_path, .{});
lib/std/fs/File.zig+4-1
......@@ -40,8 +40,11 @@ pub const OpenError = error{
4040 AccessDenied,
4141 PipeBusy,
4242 NameTooLong,
43 /// On Windows, file paths must be valid Unicode.
43 /// WASI-only; file paths must be valid UTF-8.
4444 InvalidUtf8,
45 /// Windows-only; file paths provided by the user must be valid WTF-8.
46 /// https://simonsapin.github.io/wtf-8/
47 InvalidWtf8,
4548 /// On Windows, file paths cannot contain these characters:
4649 /// '/', '*', '?', '"', '<', '>', '|'
4750 BadPathName,
lib/std/fs/path.zig+21-7
......@@ -1,3 +1,17 @@
1//! POSIX paths are arbitrary sequences of `u8` with no particular encoding.
2//!
3//! Windows paths are arbitrary sequences of `u16` (WTF-16).
4//! For cross-platform APIs that deal with sequences of `u8`, Windows
5//! paths are encoded by Zig as [WTF-8](https://simonsapin.github.io/wtf-8/).
6//! WTF-8 is a superset of UTF-8 that allows encoding surrogate codepoints,
7//! which enables lossless roundtripping when converting to/from WTF-16
8//! (as long as the WTF-8 encoded surrogate codepoints do not form a pair).
9//!
10//! WASI paths are sequences of valid Unicode scalar values,
11//! which means that WASI is unable to handle paths that cannot be
12//! encoded as well-formed UTF-8/UTF-16.
13//! https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
14
115const builtin = @import("builtin");
216const std = @import("../std.zig");
317const debug = std.debug;
......@@ -438,7 +452,7 @@ fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool {
438452 var it1 = mem.tokenizeScalar(u8, ns1, sep1);
439453 var it2 = mem.tokenizeScalar(u8, ns2, sep2);
440454
441 return windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?);
455 return windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?);
442456}
443457
444458fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8) bool {
......@@ -458,7 +472,7 @@ fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8
458472 var it1 = mem.tokenizeScalar(u8, p1, sep1);
459473 var it2 = mem.tokenizeScalar(u8, p2, sep2);
460474
461 return windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?) and windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?);
475 return windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?) and windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?);
462476 },
463477 }
464478}
......@@ -1099,7 +1113,7 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
10991113 const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest());
11001114 const to_rest = to_it.rest();
11011115 if (to_it.next()) |to_component| {
1102 if (windows.eqlIgnoreCaseUtf8(from_component, to_component))
1116 if (windows.eqlIgnoreCaseWtf8(from_component, to_component))
11031117 continue;
11041118 }
11051119 var up_index_end = "..".len;
......@@ -1564,14 +1578,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
15641578 };
15651579}
15661580
1567pub const NativeUtf8ComponentIterator = ComponentIterator(switch (native_os) {
1581pub const NativeComponentIterator = ComponentIterator(switch (native_os) {
15681582 .windows => .windows,
15691583 .uefi => .uefi,
15701584 else => .posix,
15711585}, u8);
15721586
1573pub fn componentIterator(path: []const u8) !NativeUtf8ComponentIterator {
1574 return NativeUtf8ComponentIterator.init(path);
1587pub fn componentIterator(path: []const u8) !NativeComponentIterator {
1588 return NativeComponentIterator.init(path);
15751589}
15761590
15771591test "ComponentIterator posix" {
......@@ -1826,7 +1840,7 @@ test "ComponentIterator windows" {
18261840 }
18271841}
18281842
1829test "ComponentIterator windows UTF-16" {
1843test "ComponentIterator windows WTF-16" {
18301844 // TODO: Fix on big endian architectures
18311845 if (builtin.cpu.arch.endian() != .little) {
18321846 return error.SkipZigTest;
lib/std/fs/test.zig+126-8
......@@ -26,39 +26,39 @@ const PathType = enum {
2626 }
2727
2828 pub const TransformError = std.os.RealPathError || error{OutOfMemory};
29 pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8;
29 pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8;
3030
3131 pub fn getTransformFn(comptime path_type: PathType) TransformFn {
3232 switch (path_type) {
3333 .relative => return struct {
34 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
34 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
3535 _ = allocator;
3636 _ = dir;
3737 return relative_path;
3838 }
3939 }.transform,
4040 .absolute => return struct {
41 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
41 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
4242 // The final path may not actually exist which would cause realpath to fail.
4343 // So instead, we get the path of the dir and join it with the relative path.
4444 var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
4545 const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
46 return fs.path.join(allocator, &.{ dir_path, relative_path });
46 return fs.path.joinZ(allocator, &.{ dir_path, relative_path });
4747 }
4848 }.transform,
4949 .unc => return struct {
50 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
50 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
5151 // Any drive absolute path (C:\foo) can be converted into a UNC path by
5252 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
5353 var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
5454 const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
5555 const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path);
5656 switch (windows_path_type) {
57 .unc_absolute => return fs.path.join(allocator, &.{ dir_path, relative_path }),
57 .unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }),
5858 .drive_absolute => {
5959 // `C:\<...>` -> `\\127.0.0.1\C$\<...>`
6060 const prepended = "\\\\127.0.0.1\\";
61 var path = try fs.path.join(allocator, &.{ prepended, dir_path, relative_path });
61 var path = try fs.path.joinZ(allocator, &.{ prepended, dir_path, relative_path });
6262 path[prepended.len + 1] = '$';
6363 return path;
6464 },
......@@ -96,7 +96,7 @@ const TestContext = struct {
9696 /// Returns the `relative_path` transformed into the TestContext's `path_type`.
9797 /// The result is allocated by the TestContext's arena and will be free'd during
9898 /// `TestContext.deinit`.
99 pub fn transformPath(self: *TestContext, relative_path: []const u8) ![]const u8 {
99 pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 {
100100 return self.transform_fn(self.arena.allocator(), self.dir, relative_path);
101101 }
102102};
......@@ -1001,6 +1001,16 @@ test "openSelfExe" {
10011001 self_exe_file.close();
10021002}
10031003
1004test "selfExePath" {
1005 if (builtin.os.tag == .wasi) return error.SkipZigTest;
1006
1007 var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
1008 const buf_self_exe_path = try std.fs.selfExePath(&buf);
1009 const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator);
1010 defer testing.allocator.free(alloc_self_exe_path);
1011 try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path);
1012}
1013
10041014test "deleteTree does not follow symlinks" {
10051015 var tmp = tmpDir(.{});
10061016 defer tmp.cleanup();
......@@ -1907,3 +1917,111 @@ test "delete a setAsCwd directory on Windows" {
19071917 // Close the parent "tmp" so we don't leak the HANDLE.
19081918 tmp.parent_dir.close();
19091919}
1920
1921test "invalid UTF-8/WTF-8 paths" {
1922 const expected_err = switch (builtin.os.tag) {
1923 .wasi => error.InvalidUtf8,
1924 .windows => error.InvalidWtf8,
1925 else => return error.SkipZigTest,
1926 };
1927
1928 try testWithAllSupportedPathTypes(struct {
1929 fn impl(ctx: *TestContext) !void {
1930 // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte
1931 const invalid_path = try ctx.transformPath("\xFF");
1932
1933 try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{}));
1934 try testing.expectError(expected_err, ctx.dir.openFileZ(invalid_path, .{}));
1935
1936 try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{}));
1937 try testing.expectError(expected_err, ctx.dir.createFileZ(invalid_path, .{}));
1938
1939 try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path));
1940 try testing.expectError(expected_err, ctx.dir.makeDirZ(invalid_path));
1941
1942 try testing.expectError(expected_err, ctx.dir.makePath(invalid_path));
1943 try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{}));
1944
1945 try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{}));
1946 try testing.expectError(expected_err, ctx.dir.openDirZ(invalid_path, .{}));
1947
1948 try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path));
1949 try testing.expectError(expected_err, ctx.dir.deleteFileZ(invalid_path));
1950
1951 try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path));
1952 try testing.expectError(expected_err, ctx.dir.deleteDirZ(invalid_path));
1953
1954 try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path));
1955 try testing.expectError(expected_err, ctx.dir.renameZ(invalid_path, invalid_path));
1956
1957 try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{}));
1958 try testing.expectError(expected_err, ctx.dir.symLinkZ(invalid_path, invalid_path, .{}));
1959 if (builtin.os.tag == .wasi) {
1960 try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{}));
1961 }
1962
1963 try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{}));
1964 try testing.expectError(expected_err, ctx.dir.readLinkZ(invalid_path, &[_]u8{}));
1965 if (builtin.os.tag == .wasi) {
1966 try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{}));
1967 }
1968
1969 try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{}));
1970 try testing.expectError(expected_err, ctx.dir.readFileAlloc(testing.allocator, invalid_path, 0));
1971
1972 try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path));
1973 try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path));
1974
1975 try testing.expectError(expected_err, ctx.dir.writeFile(invalid_path, ""));
1976 try testing.expectError(expected_err, ctx.dir.writeFile2(.{
1977 .sub_path = invalid_path,
1978 .data = "",
1979 }));
1980
1981 try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{}));
1982 try testing.expectError(expected_err, ctx.dir.accessZ(invalid_path, .{}));
1983
1984 try testing.expectError(expected_err, ctx.dir.updateFile(invalid_path, ctx.dir, invalid_path, .{}));
1985 try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{}));
1986
1987 try testing.expectError(expected_err, ctx.dir.statFile(invalid_path));
1988
1989 if (builtin.os.tag != .wasi) {
1990 try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{}));
1991 try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{}));
1992 try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path));
1993 }
1994
1995 try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path));
1996 try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path));
1997
1998 if (builtin.os.tag != .wasi and ctx.path_type != .relative) {
1999 try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{}));
2000 try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));
2001 try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));
2002 try testing.expectError(expected_err, fs.makeDirAbsoluteZ(invalid_path));
2003 try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path));
2004 try testing.expectError(expected_err, fs.deleteDirAbsoluteZ(invalid_path));
2005 try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path));
2006 try testing.expectError(expected_err, fs.renameAbsoluteZ(invalid_path, invalid_path));
2007 try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{}));
2008 try testing.expectError(expected_err, fs.openDirAbsoluteZ(invalid_path, .{}));
2009 try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{}));
2010 try testing.expectError(expected_err, fs.openFileAbsoluteZ(invalid_path, .{}));
2011 try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{}));
2012 try testing.expectError(expected_err, fs.accessAbsoluteZ(invalid_path, .{}));
2013 try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{}));
2014 try testing.expectError(expected_err, fs.createFileAbsoluteZ(invalid_path, .{}));
2015 try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path));
2016 try testing.expectError(expected_err, fs.deleteFileAbsoluteZ(invalid_path));
2017 try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path));
2018 var readlink_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
2019 try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf));
2020 try testing.expectError(expected_err, fs.readLinkAbsoluteZ(invalid_path, &readlink_buf));
2021 try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{}));
2022 try testing.expectError(expected_err, fs.symLinkAbsoluteZ(invalid_path, invalid_path, .{}));
2023 try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path));
2024 }
2025 }
2026 }.impl);
2027}
lib/std/fs/watch.zig deleted-719
......@@ -1,719 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14const global_event_loop = Loop.instance orelse
15 @compileError("std.fs.Watch currently only works with event-based I/O");
16
17const WatchEventId = enum {
18 CloseWrite,
19 Delete,
20};
21
22const WatchEventError = error{
23 UserResourceLimitReached,
24 SystemResources,
25 AccessDenied,
26 Unexpected, // TODO remove this possibility
27};
28
29pub fn Watch(comptime V: type) type {
30 return struct {
31 channel: event.Channel(Event.Error!Event),
32 os_data: OsData,
33 allocator: Allocator,
34
35 const OsData = switch (builtin.os.tag) {
36 // TODO https://github.com/ziglang/zig/issues/3778
37 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => KqOsData,
38 .linux => LinuxOsData,
39 .windows => WindowsOsData,
40
41 else => @compileError("Unsupported OS"),
42 };
43
44 const KqOsData = struct {
45 table_lock: event.Lock,
46 file_table: FileTable,
47
48 const FileTable = std.StringHashMapUnmanaged(*Put);
49 const Put = struct {
50 putter_frame: @Frame(kqPutEvents),
51 cancelled: bool = false,
52 value: V,
53 };
54 };
55
56 const WindowsOsData = struct {
57 table_lock: event.Lock,
58 dir_table: DirTable,
59 cancelled: bool = false,
60
61 const DirTable = std.StringHashMapUnmanaged(*Dir);
62 const FileTable = std.StringHashMapUnmanaged(V);
63
64 const Dir = struct {
65 putter_frame: @Frame(windowsDirReader),
66 file_table: FileTable,
67 dir_handle: os.windows.HANDLE,
68 };
69 };
70
71 const LinuxOsData = struct {
72 putter_frame: @Frame(linuxEventPutter),
73 inotify_fd: i32,
74 wd_table: WdTable,
75 table_lock: event.Lock,
76 cancelled: bool = false,
77
78 const WdTable = std.AutoHashMapUnmanaged(i32, Dir);
79 const FileTable = std.StringHashMapUnmanaged(V);
80
81 const Dir = struct {
82 dirname: []const u8,
83 file_table: FileTable,
84 };
85 };
86
87 const Self = @This();
88
89 pub const Event = struct {
90 id: Id,
91 data: V,
92 dirname: []const u8,
93 basename: []const u8,
94
95 pub const Id = WatchEventId;
96 pub const Error = WatchEventError;
97 };
98
99 pub fn init(allocator: Allocator, event_buf_count: usize) !*Self {
100 const self = try allocator.create(Self);
101 errdefer allocator.destroy(self);
102
103 switch (builtin.os.tag) {
104 .linux => {
105 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
106 errdefer os.close(inotify_fd);
107
108 self.* = Self{
109 .allocator = allocator,
110 .channel = undefined,
111 .os_data = OsData{
112 .putter_frame = undefined,
113 .inotify_fd = inotify_fd,
114 .wd_table = OsData.WdTable.init(allocator),
115 .table_lock = event.Lock{},
116 },
117 };
118
119 const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
120 self.channel.init(buf);
121 self.os_data.putter_frame = async self.linuxEventPutter();
122 return self;
123 },
124
125 .windows => {
126 self.* = Self{
127 .allocator = allocator,
128 .channel = undefined,
129 .os_data = OsData{
130 .table_lock = event.Lock{},
131 .dir_table = OsData.DirTable.init(allocator),
132 },
133 };
134
135 const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
136 self.channel.init(buf);
137 return self;
138 },
139
140 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
141 self.* = Self{
142 .allocator = allocator,
143 .channel = undefined,
144 .os_data = OsData{
145 .table_lock = event.Lock{},
146 .file_table = OsData.FileTable.init(allocator),
147 },
148 };
149
150 const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
151 self.channel.init(buf);
152 return self;
153 },
154 else => @compileError("Unsupported OS"),
155 }
156 }
157
158 pub fn deinit(self: *Self) void {
159 switch (builtin.os.tag) {
160 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
161 var it = self.os_data.file_table.iterator();
162 while (it.next()) |entry| {
163 const key = entry.key_ptr.*;
164 const value = entry.value_ptr.*;
165 value.cancelled = true;
166 // @TODO Close the fd here?
167 await value.putter_frame;
168 self.allocator.free(key);
169 self.allocator.destroy(value);
170 }
171 },
172 .linux => {
173 self.os_data.cancelled = true;
174 {
175 // Remove all directory watches linuxEventPutter will take care of
176 // cleaning up the memory and closing the inotify fd.
177 var dir_it = self.os_data.wd_table.keyIterator();
178 while (dir_it.next()) |wd_key| {
179 const rc = os.linux.inotify_rm_watch(self.os_data.inotify_fd, wd_key.*);
180 // Errno can only be EBADF, EINVAL if either the inotify fs or the wd are invalid
181 std.debug.assert(rc == 0);
182 }
183 }
184 await self.os_data.putter_frame;
185 },
186 .windows => {
187 self.os_data.cancelled = true;
188 var dir_it = self.os_data.dir_table.iterator();
189 while (dir_it.next()) |dir_entry| {
190 if (windows.kernel32.CancelIoEx(dir_entry.value.dir_handle, null) != 0) {
191 // We canceled the pending ReadDirectoryChangesW operation, but our
192 // frame is still suspending, now waiting indefinitely.
193 // Thus, it is safe to resume it ourslves
194 resume dir_entry.value.putter_frame;
195 } else {
196 std.debug.assert(windows.kernel32.GetLastError() == .NOT_FOUND);
197 // We are at another suspend point, we can await safely for the
198 // function to exit the loop
199 await dir_entry.value.putter_frame;
200 }
201
202 self.allocator.free(dir_entry.key_ptr.*);
203 var file_it = dir_entry.value.file_table.keyIterator();
204 while (file_it.next()) |file_entry| {
205 self.allocator.free(file_entry.*);
206 }
207 dir_entry.value.file_table.deinit(self.allocator);
208 self.allocator.destroy(dir_entry.value_ptr.*);
209 }
210 self.os_data.dir_table.deinit(self.allocator);
211 },
212 else => @compileError("Unsupported OS"),
213 }
214 self.allocator.free(self.channel.buffer_nodes);
215 self.channel.deinit();
216 self.allocator.destroy(self);
217 }
218
219 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
220 switch (builtin.os.tag) {
221 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => return addFileKEvent(self, file_path, value),
222 .linux => return addFileLinux(self, file_path, value),
223 .windows => return addFileWindows(self, file_path, value),
224 else => @compileError("Unsupported OS"),
225 }
226 }
227
228 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
229 var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
230 const realpath = try os.realpath(file_path, &realpath_buf);
231
232 const held = self.os_data.table_lock.acquire();
233 defer held.release();
234
235 const gop = try self.os_data.file_table.getOrPut(self.allocator, realpath);
236 errdefer assert(self.os_data.file_table.remove(realpath));
237 if (gop.found_existing) {
238 const prev_value = gop.value_ptr.value;
239 gop.value_ptr.value = value;
240 return prev_value;
241 }
242
243 gop.key_ptr.* = try self.allocator.dupe(u8, realpath);
244 errdefer self.allocator.free(gop.key_ptr.*);
245 gop.value_ptr.* = try self.allocator.create(OsData.Put);
246 errdefer self.allocator.destroy(gop.value_ptr.*);
247 gop.value_ptr.* = .{
248 .putter_frame = undefined,
249 .value = value,
250 };
251
252 // @TODO Can I close this fd and get an error from bsdWaitKev?
253 const flags = if (comptime builtin.target.isDarwin()) os.O.SYMLINK | os.O.EVTONLY else 0;
254 const fd = try os.open(realpath, flags, 0);
255 gop.value_ptr.putter_frame = async self.kqPutEvents(fd, gop.key_ptr.*, gop.value_ptr.*);
256 return null;
257 }
258
259 fn kqPutEvents(self: *Self, fd: os.fd_t, file_path: []const u8, put: *OsData.Put) void {
260 global_event_loop.beginOneEvent();
261 defer {
262 global_event_loop.finishOneEvent();
263 // @TODO: Remove this if we force close otherwise
264 os.close(fd);
265 }
266
267 // We need to manually do a bsdWaitKev to access the fflags.
268 var resume_node = event.Loop.ResumeNode.Basic{
269 .base = .{
270 .id = .Basic,
271 .handle = @frame(),
272 .overlapped = event.Loop.ResumeNode.overlapped_init,
273 },
274 .kev = undefined,
275 };
276
277 var kevs = [1]os.Kevent{undefined};
278 const kev = &kevs[0];
279
280 while (!put.cancelled) {
281 kev.* = os.Kevent{
282 .ident = @as(usize, @intCast(fd)),
283 .filter = os.EVFILT_VNODE,
284 .flags = os.EV_ADD | os.EV_ENABLE | os.EV_CLEAR | os.EV_ONESHOT |
285 os.NOTE_WRITE | os.NOTE_DELETE | os.NOTE_REVOKE,
286 .fflags = 0,
287 .data = 0,
288 .udata = @intFromPtr(&resume_node.base),
289 };
290 suspend {
291 global_event_loop.beginOneEvent();
292 errdefer global_event_loop.finishOneEvent();
293
294 const empty_kevs = &[0]os.Kevent{};
295 _ = os.kevent(global_event_loop.os_data.kqfd, &kevs, empty_kevs, null) catch |err| switch (err) {
296 error.EventNotFound,
297 error.ProcessNotFound,
298 error.Overflow,
299 => unreachable,
300 error.AccessDenied, error.SystemResources => |e| {
301 self.channel.put(e);
302 continue;
303 },
304 };
305 }
306
307 if (kev.flags & os.EV_ERROR != 0) {
308 self.channel.put(os.unexpectedErrno(os.errno(kev.data)));
309 continue;
310 }
311
312 if (kev.fflags & os.NOTE_DELETE != 0 or kev.fflags & os.NOTE_REVOKE != 0) {
313 self.channel.put(Self.Event{
314 .id = .Delete,
315 .data = put.value,
316 .dirname = std.fs.path.dirname(file_path) orelse "/",
317 .basename = std.fs.path.basename(file_path),
318 });
319 } else if (kev.fflags & os.NOTE_WRITE != 0) {
320 self.channel.put(Self.Event{
321 .id = .CloseWrite,
322 .data = put.value,
323 .dirname = std.fs.path.dirname(file_path) orelse "/",
324 .basename = std.fs.path.basename(file_path),
325 });
326 }
327 }
328 }
329
330 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
331 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
332 const basename = std.fs.path.basename(file_path);
333
334 const wd = try os.inotify_add_watch(
335 self.os_data.inotify_fd,
336 dirname,
337 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_DELETE | os.linux.IN_EXCL_UNLINK,
338 );
339 // wd is either a newly created watch or an existing one.
340
341 const held = self.os_data.table_lock.acquire();
342 defer held.release();
343
344 const gop = try self.os_data.wd_table.getOrPut(self.allocator, wd);
345 errdefer assert(self.os_data.wd_table.remove(wd));
346 if (!gop.found_existing) {
347 gop.value_ptr.* = OsData.Dir{
348 .dirname = try self.allocator.dupe(u8, dirname),
349 .file_table = OsData.FileTable.init(self.allocator),
350 };
351 }
352
353 const dir = gop.value_ptr;
354 const file_table_gop = try dir.file_table.getOrPut(self.allocator, basename);
355 errdefer assert(dir.file_table.remove(basename));
356 if (file_table_gop.found_existing) {
357 const prev_value = file_table_gop.value_ptr.*;
358 file_table_gop.value_ptr.* = value;
359 return prev_value;
360 } else {
361 file_table_gop.key_ptr.* = try self.allocator.dupe(u8, basename);
362 file_table_gop.value_ptr.* = value;
363 return null;
364 }
365 }
366
367 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
368 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
369 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
370 var dirname_path_space: windows.PathSpace = undefined;
371 dirname_path_space.len = try std.unicode.utf8ToUtf16Le(&dirname_path_space.data, dirname);
372 dirname_path_space.data[dirname_path_space.len] = 0;
373
374 const basename = std.fs.path.basename(file_path);
375 var basename_path_space: windows.PathSpace = undefined;
376 basename_path_space.len = try std.unicode.utf8ToUtf16Le(&basename_path_space.data, basename);
377 basename_path_space.data[basename_path_space.len] = 0;
378
379 const held = self.os_data.table_lock.acquire();
380 defer held.release();
381
382 const gop = try self.os_data.dir_table.getOrPut(self.allocator, dirname);
383 errdefer assert(self.os_data.dir_table.remove(dirname));
384 if (gop.found_existing) {
385 const dir = gop.value_ptr.*;
386
387 const file_gop = try dir.file_table.getOrPut(self.allocator, basename);
388 errdefer assert(dir.file_table.remove(basename));
389 if (file_gop.found_existing) {
390 const prev_value = file_gop.value_ptr.*;
391 file_gop.value_ptr.* = value;
392 return prev_value;
393 } else {
394 file_gop.value_ptr.* = value;
395 file_gop.key_ptr.* = try self.allocator.dupe(u8, basename);
396 return null;
397 }
398 } else {
399 const dir_handle = try windows.OpenFile(dirname_path_space.span(), .{
400 .dir = std.fs.cwd().fd,
401 .access_mask = windows.FILE_LIST_DIRECTORY,
402 .creation = windows.FILE_OPEN,
403 .io_mode = .evented,
404 .filter = .dir_only,
405 });
406 errdefer windows.CloseHandle(dir_handle);
407
408 const dir = try self.allocator.create(OsData.Dir);
409 errdefer self.allocator.destroy(dir);
410
411 gop.key_ptr.* = try self.allocator.dupe(u8, dirname);
412 errdefer self.allocator.free(gop.key_ptr.*);
413
414 dir.* = OsData.Dir{
415 .file_table = OsData.FileTable.init(self.allocator),
416 .putter_frame = undefined,
417 .dir_handle = dir_handle,
418 };
419 gop.value_ptr.* = dir;
420 try dir.file_table.put(self.allocator, try self.allocator.dupe(u8, basename), value);
421 dir.putter_frame = async self.windowsDirReader(dir, gop.key_ptr.*);
422 return null;
423 }
424 }
425
426 fn windowsDirReader(self: *Self, dir: *OsData.Dir, dirname: []const u8) void {
427 defer os.close(dir.dir_handle);
428 var resume_node = Loop.ResumeNode.Basic{
429 .base = Loop.ResumeNode{
430 .id = .Basic,
431 .handle = @frame(),
432 .overlapped = windows.OVERLAPPED{
433 .Internal = 0,
434 .InternalHigh = 0,
435 .DUMMYUNIONNAME = .{
436 .DUMMYSTRUCTNAME = .{
437 .Offset = 0,
438 .OffsetHigh = 0,
439 },
440 },
441 .hEvent = null,
442 },
443 },
444 };
445
446 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
447
448 global_event_loop.beginOneEvent();
449 defer global_event_loop.finishOneEvent();
450
451 while (!self.os_data.cancelled) main_loop: {
452 suspend {
453 _ = windows.kernel32.ReadDirectoryChangesW(
454 dir.dir_handle,
455 &event_buf,
456 event_buf.len,
457 windows.FALSE, // watch subtree
458 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
459 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
460 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
461 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
462 null, // number of bytes transferred (unused for async)
463 &resume_node.base.overlapped,
464 null, // completion routine - unused because we use IOCP
465 );
466 }
467
468 var bytes_transferred: windows.DWORD = undefined;
469 if (windows.kernel32.GetOverlappedResult(
470 dir.dir_handle,
471 &resume_node.base.overlapped,
472 &bytes_transferred,
473 windows.FALSE,
474 ) == 0) {
475 const potential_error = windows.kernel32.GetLastError();
476 const err = switch (potential_error) {
477 .OPERATION_ABORTED, .IO_INCOMPLETE => err_blk: {
478 if (self.os_data.cancelled)
479 break :main_loop
480 else
481 break :err_blk windows.unexpectedError(potential_error);
482 },
483 else => |err| windows.unexpectedError(err),
484 };
485 self.channel.put(err);
486 } else {
487 var ptr: [*]u8 = &event_buf;
488 const end_ptr = ptr + bytes_transferred;
489 while (@intFromPtr(ptr) < @intFromPtr(end_ptr)) {
490 const ev = @as(*const windows.FILE_NOTIFY_INFORMATION, @ptrCast(ptr));
491 const emit = switch (ev.Action) {
492 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
493 windows.FILE_ACTION_MODIFIED => .CloseWrite,
494 else => null,
495 };
496 if (emit) |id| {
497 const basename_ptr = @as([*]u16, @ptrCast(ptr + @sizeOf(windows.FILE_NOTIFY_INFORMATION)));
498 const basename_utf16le = basename_ptr[0 .. ev.FileNameLength / 2];
499 var basename_data: [std.fs.MAX_PATH_BYTES]u8 = undefined;
500 const basename = basename_data[0 .. std.unicode.utf16LeToUtf8(&basename_data, basename_utf16le) catch unreachable];
501
502 if (dir.file_table.getEntry(basename)) |entry| {
503 self.channel.put(Event{
504 .id = id,
505 .data = entry.value_ptr.*,
506 .dirname = dirname,
507 .basename = entry.key_ptr.*,
508 });
509 }
510 }
511
512 if (ev.NextEntryOffset == 0) break;
513 ptr = @alignCast(ptr + ev.NextEntryOffset);
514 }
515 }
516 }
517 }
518
519 pub fn removeFile(self: *Self, file_path: []const u8) !?V {
520 switch (builtin.os.tag) {
521 .linux => {
522 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
523 const basename = std.fs.path.basename(file_path);
524
525 const held = self.os_data.table_lock.acquire();
526 defer held.release();
527
528 const dir = self.os_data.wd_table.get(dirname) orelse return null;
529 if (dir.file_table.fetchRemove(basename)) |file_entry| {
530 self.allocator.free(file_entry.key);
531 return file_entry.value;
532 }
533 return null;
534 },
535 .windows => {
536 const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
537 const basename = std.fs.path.basename(file_path);
538
539 const held = self.os_data.table_lock.acquire();
540 defer held.release();
541
542 const dir = self.os_data.dir_table.get(dirname) orelse return null;
543 if (dir.file_table.fetchRemove(basename)) |file_entry| {
544 self.allocator.free(file_entry.key);
545 return file_entry.value;
546 }
547 return null;
548 },
549 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
550 var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
551 const realpath = try os.realpath(file_path, &realpath_buf);
552
553 const held = self.os_data.table_lock.acquire();
554 defer held.release();
555
556 const entry = self.os_data.file_table.getEntry(realpath) orelse return null;
557 entry.value_ptr.cancelled = true;
558 // @TODO Close the fd here?
559 await entry.value_ptr.putter_frame;
560 self.allocator.free(entry.key_ptr.*);
561 self.allocator.destroy(entry.value_ptr.*);
562
563 assert(self.os_data.file_table.remove(realpath));
564 },
565 else => @compileError("Unsupported OS"),
566 }
567 }
568
569 fn linuxEventPutter(self: *Self) void {
570 global_event_loop.beginOneEvent();
571
572 defer {
573 std.debug.assert(self.os_data.wd_table.count() == 0);
574 self.os_data.wd_table.deinit(self.allocator);
575 os.close(self.os_data.inotify_fd);
576 self.allocator.free(self.channel.buffer_nodes);
577 self.channel.deinit();
578 global_event_loop.finishOneEvent();
579 }
580
581 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
582
583 while (!self.os_data.cancelled) {
584 const bytes_read = global_event_loop.read(self.os_data.inotify_fd, &event_buf, false) catch unreachable;
585
586 var ptr: [*]u8 = &event_buf;
587 const end_ptr = ptr + bytes_read;
588 while (@intFromPtr(ptr) < @intFromPtr(end_ptr)) {
589 const ev = @as(*const os.linux.inotify_event, @ptrCast(ptr));
590 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
591 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
592 const basename = std.mem.span(@as([*:0]u8, @ptrCast(basename_ptr)));
593
594 const dir = &self.os_data.wd_table.get(ev.wd).?;
595 if (dir.file_table.getEntry(basename)) |file_value| {
596 self.channel.put(Event{
597 .id = .CloseWrite,
598 .data = file_value.value_ptr.*,
599 .dirname = dir.dirname,
600 .basename = file_value.key_ptr.*,
601 });
602 }
603 } else if (ev.mask & os.linux.IN_IGNORED == os.linux.IN_IGNORED) {
604 // Directory watch was removed
605 const held = self.os_data.table_lock.acquire();
606 defer held.release();
607 if (self.os_data.wd_table.fetchRemove(ev.wd)) |wd_entry| {
608 var file_it = wd_entry.value.file_table.keyIterator();
609 while (file_it.next()) |file_entry| {
610 self.allocator.free(file_entry.*);
611 }
612 self.allocator.free(wd_entry.value.dirname);
613 wd_entry.value.file_table.deinit(self.allocator);
614 }
615 } else if (ev.mask & os.linux.IN_DELETE == os.linux.IN_DELETE) {
616 // File or directory was removed or deleted
617 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
618 const basename = std.mem.span(@as([*:0]u8, @ptrCast(basename_ptr)));
619
620 const dir = &self.os_data.wd_table.get(ev.wd).?;
621 if (dir.file_table.getEntry(basename)) |file_value| {
622 self.channel.put(Event{
623 .id = .Delete,
624 .data = file_value.value_ptr.*,
625 .dirname = dir.dirname,
626 .basename = file_value.key_ptr.*,
627 });
628 }
629 }
630
631 ptr = @alignCast(ptr + @sizeOf(os.linux.inotify_event) + ev.len);
632 }
633 }
634 }
635 };
636}
637
638const test_tmp_dir = "std_event_fs_test";
639
640test "write a file, watch it, write it again, delete it" {
641 if (!std.io.is_async) return error.SkipZigTest;
642 // TODO https://github.com/ziglang/zig/issues/1908
643 if (builtin.single_threaded) return error.SkipZigTest;
644
645 try std.fs.cwd().makePath(test_tmp_dir);
646 defer std.fs.cwd().deleteTree(test_tmp_dir) catch {};
647
648 return testWriteWatchWriteDelete(std.testing.allocator);
649}
650
651fn testWriteWatchWriteDelete(allocator: Allocator) !void {
652 const file_path = try std.fs.path.join(allocator, &[_][]const u8{ test_tmp_dir, "file.txt" });
653 defer allocator.free(file_path);
654
655 const contents =
656 \\line 1
657 \\line 2
658 ;
659 const line2_offset = 7;
660
661 // first just write then read the file
662 try std.fs.cwd().writeFile(file_path, contents);
663
664 const read_contents = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
665 defer allocator.free(read_contents);
666 try testing.expectEqualSlices(u8, contents, read_contents);
667
668 // now watch the file
669 var watch = try Watch(void).init(allocator, 0);
670 defer watch.deinit();
671
672 try testing.expect((try watch.addFile(file_path, {})) == null);
673
674 var ev = async watch.channel.get();
675 var ev_consumed = false;
676 defer if (!ev_consumed) {
677 _ = await ev;
678 };
679
680 // overwrite line 2
681 const file = try std.fs.cwd().openFile(file_path, .{ .mode = .read_write });
682 {
683 defer file.close();
684 const write_contents = "lorem ipsum";
685 var iovec = [_]os.iovec_const{.{
686 .iov_base = write_contents,
687 .iov_len = write_contents.len,
688 }};
689 _ = try file.pwritevAll(&iovec, line2_offset);
690 }
691
692 switch ((try await ev).id) {
693 .CloseWrite => {
694 ev_consumed = true;
695 },
696 .Delete => @panic("wrong event"),
697 }
698
699 const contents_updated = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
700 defer allocator.free(contents_updated);
701
702 try testing.expectEqualSlices(u8,
703 \\line 1
704 \\lorem ipsum
705 , contents_updated);
706
707 ev = async watch.channel.get();
708 ev_consumed = false;
709
710 try std.fs.cwd().deleteFile(file_path);
711 switch ((try await ev).id) {
712 .Delete => {
713 ev_consumed = true;
714 },
715 .CloseWrite => @panic("wrong event"),
716 }
717}
718
719// TODO Test: Add another file watch, remove the old file watch, get an event in the new
lib/std/os.zig+257-40
......@@ -3,7 +3,7 @@
33//! * Convert "errno"-style error codes into Zig errors.
44//! * When null-terminated byte buffers are required, provide APIs which accept
55//! slices as well as APIs which accept null-terminated byte buffers. Same goes
6//! for UTF-16LE encoding.
6//! for WTF-16LE encoding.
77//! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
88//! cross platform abstracting.
99//! * When there exists a corresponding libc function and linking libc, the libc
......@@ -498,6 +498,7 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr
498498 const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {
499499 error.NameTooLong => unreachable,
500500 error.FileNotFound => unreachable,
501 error.InvalidUtf8 => unreachable,
501502 else => |e| return e,
502503 };
503504 if ((stat.mode & S.IFMT) == S.IFLNK)
......@@ -1614,9 +1615,16 @@ pub const OpenError = error{
16141615 /// The underlying filesystem does not support file locks
16151616 FileLocksNotSupported,
16161617
1618 /// Path contains characters that are disallowed by the underlying filesystem.
16171619 BadPathName,
1620
1621 /// WASI-only; file paths must be valid UTF-8.
16181622 InvalidUtf8,
16191623
1624 /// Windows-only; file paths provided by the user must be valid WTF-8.
1625 /// https://simonsapin.github.io/wtf-8/
1626 InvalidWtf8,
1627
16201628 /// On Windows, `\\server` or `\\server\share` was not found.
16211629 NetworkNotFound,
16221630
......@@ -1634,6 +1642,9 @@ pub const OpenError = error{
16341642} || UnexpectedError;
16351643
16361644/// Open and possibly create a file. Keeps trying if it gets interrupted.
1645/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1646/// On WASI, `file_path` should be encoded as valid UTF-8.
1647/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
16371648/// See also `openZ`.
16381649pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
16391650 if (builtin.os.tag == .windows) {
......@@ -1646,6 +1657,9 @@ pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
16461657}
16471658
16481659/// Open and possibly create a file. Keeps trying if it gets interrupted.
1660/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1661/// On WASI, `file_path` should be encoded as valid UTF-8.
1662/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
16491663/// See also `open`.
16501664pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
16511665 if (builtin.os.tag == .windows) {
......@@ -1687,6 +1701,9 @@ pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
16871701
16881702/// Open and possibly create a file. Keeps trying if it gets interrupted.
16891703/// `file_path` is relative to the open directory handle `dir_fd`.
1704/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1705/// On WASI, `file_path` should be encoded as valid UTF-8.
1706/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
16901707/// See also `openatZ`.
16911708pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t {
16921709 if (builtin.os.tag == .windows) {
......@@ -1829,6 +1846,7 @@ pub fn openatWasi(
18291846 .EXIST => return error.PathAlreadyExists,
18301847 .BUSY => return error.DeviceBusy,
18311848 .NOTCAPABLE => return error.AccessDenied,
1849 .ILSEQ => return error.InvalidUtf8,
18321850 else => |err| return unexpectedErrno(err),
18331851 }
18341852 }
......@@ -1836,6 +1854,9 @@ pub fn openatWasi(
18361854
18371855/// Open and possibly create a file. Keeps trying if it gets interrupted.
18381856/// `file_path` is relative to the open directory handle `dir_fd`.
1857/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1858/// On WASI, `file_path` should be encoded as valid UTF-8.
1859/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
18391860/// See also `openat`.
18401861pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t {
18411862 if (builtin.os.tag == .windows) {
......@@ -2156,13 +2177,23 @@ pub const SymLinkError = error{
21562177 ReadOnlyFileSystem,
21572178 NotDir,
21582179 NameTooLong,
2180
2181 /// WASI-only; file paths must be valid UTF-8.
21592182 InvalidUtf8,
2183
2184 /// Windows-only; file paths provided by the user must be valid WTF-8.
2185 /// https://simonsapin.github.io/wtf-8/
2186 InvalidWtf8,
2187
21602188 BadPathName,
21612189} || UnexpectedError;
21622190
21632191/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
21642192/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
21652193/// one; the latter case is known as a dangling link.
2194/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2195/// On WASI, both paths should be encoded as valid UTF-8.
2196/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
21662197/// If `sym_link_path` exists, it will not be overwritten.
21672198/// See also `symlinkZ.
21682199pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
......@@ -2200,6 +2231,10 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
22002231 .NOMEM => return error.SystemResources,
22012232 .NOSPC => return error.NoSpaceLeft,
22022233 .ROFS => return error.ReadOnlyFileSystem,
2234 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2235 return error.InvalidUtf8
2236 else
2237 return unexpectedErrno(err),
22032238 else => |err| return unexpectedErrno(err),
22042239 }
22052240}
......@@ -2208,6 +2243,9 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
22082243/// `target_path` **relative** to `newdirfd` directory handle.
22092244/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
22102245/// one; the latter case is known as a dangling link.
2246/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2247/// On WASI, both paths should be encoded as valid UTF-8.
2248/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
22112249/// If `sym_link_path` exists, it will not be overwritten.
22122250/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
22132251pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
......@@ -2242,6 +2280,7 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c
22422280 .NOSPC => return error.NoSpaceLeft,
22432281 .ROFS => return error.ReadOnlyFileSystem,
22442282 .NOTCAPABLE => return error.AccessDenied,
2283 .ILSEQ => return error.InvalidUtf8,
22452284 else => |err| return unexpectedErrno(err),
22462285 }
22472286}
......@@ -2270,6 +2309,10 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:
22702309 .NOMEM => return error.SystemResources,
22712310 .NOSPC => return error.NoSpaceLeft,
22722311 .ROFS => return error.ReadOnlyFileSystem,
2312 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2313 return error.InvalidUtf8
2314 else
2315 return unexpectedErrno(err),
22732316 else => |err| return unexpectedErrno(err),
22742317 }
22752318}
......@@ -2287,8 +2330,13 @@ pub const LinkError = UnexpectedError || error{
22872330 NoSpaceLeft,
22882331 ReadOnlyFileSystem,
22892332 NotSameFileSystem,
2333
2334 /// WASI-only; file paths must be valid UTF-8.
2335 InvalidUtf8,
22902336};
22912337
2338/// On WASI, both paths should be encoded as valid UTF-8.
2339/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
22922340pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {
22932341 if (builtin.os.tag == .wasi and !builtin.link_libc) {
22942342 return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags);
......@@ -2310,10 +2358,16 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr
23102358 .ROFS => return error.ReadOnlyFileSystem,
23112359 .XDEV => return error.NotSameFileSystem,
23122360 .INVAL => unreachable,
2361 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2362 return error.InvalidUtf8
2363 else
2364 return unexpectedErrno(err),
23132365 else => |err| return unexpectedErrno(err),
23142366 }
23152367}
23162368
2369/// On WASI, both paths should be encoded as valid UTF-8.
2370/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
23172371pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {
23182372 if (builtin.os.tag == .wasi and !builtin.link_libc) {
23192373 return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) {
......@@ -2328,6 +2382,8 @@ pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void
23282382
23292383pub const LinkatError = LinkError || error{NotDir};
23302384
2385/// On WASI, both paths should be encoded as valid UTF-8.
2386/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
23312387pub fn linkatZ(
23322388 olddir: fd_t,
23332389 oldpath: [*:0]const u8,
......@@ -2356,10 +2412,16 @@ pub fn linkatZ(
23562412 .ROFS => return error.ReadOnlyFileSystem,
23572413 .XDEV => return error.NotSameFileSystem,
23582414 .INVAL => unreachable,
2415 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2416 return error.InvalidUtf8
2417 else
2418 return unexpectedErrno(err),
23592419 else => |err| return unexpectedErrno(err),
23602420 }
23612421}
23622422
2423/// On WASI, both paths should be encoded as valid UTF-8.
2424/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
23632425pub fn linkat(
23642426 olddir: fd_t,
23652427 oldpath: []const u8,
......@@ -2399,6 +2461,7 @@ pub fn linkat(
23992461 .ROFS => return error.ReadOnlyFileSystem,
24002462 .XDEV => return error.NotSameFileSystem,
24012463 .INVAL => unreachable,
2464 .ILSEQ => return error.InvalidUtf8,
24022465 else => |err| return unexpectedErrno(err),
24032466 }
24042467 }
......@@ -2422,9 +2485,13 @@ pub const UnlinkError = error{
24222485 SystemResources,
24232486 ReadOnlyFileSystem,
24242487
2425 /// On Windows, file paths must be valid Unicode.
2488 /// WASI-only; file paths must be valid UTF-8.
24262489 InvalidUtf8,
24272490
2491 /// Windows-only; file paths provided by the user must be valid WTF-8.
2492 /// https://simonsapin.github.io/wtf-8/
2493 InvalidWtf8,
2494
24282495 /// On Windows, file paths cannot contain these characters:
24292496 /// '/', '*', '?', '"', '<', '>', '|'
24302497 BadPathName,
......@@ -2434,6 +2501,9 @@ pub const UnlinkError = error{
24342501} || UnexpectedError;
24352502
24362503/// Delete a name and possibly the file it refers to.
2504/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2505/// On WASI, `file_path` should be encoded as valid UTF-8.
2506/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
24372507/// See also `unlinkZ`.
24382508pub fn unlink(file_path: []const u8) UnlinkError!void {
24392509 if (builtin.os.tag == .wasi and !builtin.link_libc) {
......@@ -2450,7 +2520,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
24502520 }
24512521}
24522522
2453/// Same as `unlink` except the parameter is a null terminated UTF8-encoded string.
2523/// Same as `unlink` except the parameter is null terminated.
24542524pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
24552525 if (builtin.os.tag == .windows) {
24562526 const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
......@@ -2473,11 +2543,15 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
24732543 .NOTDIR => return error.NotDir,
24742544 .NOMEM => return error.SystemResources,
24752545 .ROFS => return error.ReadOnlyFileSystem,
2546 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2547 return error.InvalidUtf8
2548 else
2549 return unexpectedErrno(err),
24762550 else => |err| return unexpectedErrno(err),
24772551 }
24782552}
24792553
2480/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded.
2554/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 LE encoded.
24812555pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
24822556 windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd }) catch |err| switch (err) {
24832557 error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true
......@@ -2491,6 +2565,9 @@ pub const UnlinkatError = UnlinkError || error{
24912565};
24922566
24932567/// Delete a file name and possibly the file it refers to, based on an open directory handle.
2568/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2569/// On WASI, `file_path` should be encoded as valid UTF-8.
2570/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
24942571/// Asserts that the path parameter has no null bytes.
24952572pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
24962573 if (builtin.os.tag == .windows) {
......@@ -2528,6 +2605,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
25282605 .ROFS => return error.ReadOnlyFileSystem,
25292606 .NOTEMPTY => return error.DirNotEmpty,
25302607 .NOTCAPABLE => return error.AccessDenied,
2608 .ILSEQ => return error.InvalidUtf8,
25312609
25322610 .INVAL => unreachable, // invalid flags, or pathname has . as last component
25332611 .BADF => unreachable, // always a race condition
......@@ -2560,6 +2638,10 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
25602638 .ROFS => return error.ReadOnlyFileSystem,
25612639 .EXIST => return error.DirNotEmpty,
25622640 .NOTEMPTY => return error.DirNotEmpty,
2641 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2642 return error.InvalidUtf8
2643 else
2644 return unexpectedErrno(err),
25632645
25642646 .INVAL => unreachable, // invalid flags, or pathname has . as last component
25652647 .BADF => unreachable, // always a race condition
......@@ -2568,7 +2650,7 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
25682650 }
25692651}
25702652
2571/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
2653/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only.
25722654pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
25732655 const remove_dir = (flags & AT.REMOVEDIR) != 0;
25742656 return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
......@@ -2594,7 +2676,11 @@ pub const RenameError = error{
25942676 PathAlreadyExists,
25952677 ReadOnlyFileSystem,
25962678 RenameAcrossMountPoints,
2679 /// WASI-only; file paths must be valid UTF-8.
25972680 InvalidUtf8,
2681 /// Windows-only; file paths provided by the user must be valid WTF-8.
2682 /// https://simonsapin.github.io/wtf-8/
2683 InvalidWtf8,
25982684 BadPathName,
25992685 NoDevice,
26002686 SharingViolation,
......@@ -2610,6 +2696,9 @@ pub const RenameError = error{
26102696} || UnexpectedError;
26112697
26122698/// Change the name or location of a file.
2699/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2700/// On WASI, both paths should be encoded as valid UTF-8.
2701/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
26132702pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
26142703 if (builtin.os.tag == .wasi and !builtin.link_libc) {
26152704 return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path);
......@@ -2624,7 +2713,7 @@ pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
26242713 }
26252714}
26262715
2627/// Same as `rename` except the parameters are null-terminated byte arrays.
2716/// Same as `rename` except the parameters are null-terminated.
26282717pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {
26292718 if (builtin.os.tag == .windows) {
26302719 const old_path_w = try windows.cStrToPrefixedFileW(null, old_path);
......@@ -2653,11 +2742,15 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi
26532742 .NOTEMPTY => return error.PathAlreadyExists,
26542743 .ROFS => return error.ReadOnlyFileSystem,
26552744 .XDEV => return error.RenameAcrossMountPoints,
2745 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2746 return error.InvalidUtf8
2747 else
2748 return unexpectedErrno(err),
26562749 else => |err| return unexpectedErrno(err),
26572750 }
26582751}
26592752
2660/// Same as `rename` except the parameters are null-terminated UTF16LE encoded byte arrays.
2753/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded.
26612754/// Assumes target is Windows.
26622755pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {
26632756 const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
......@@ -2665,6 +2758,9 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v
26652758}
26662759
26672760/// Change the name or location of a file based on an open directory handle.
2761/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2762/// On WASI, both paths should be encoded as valid UTF-8.
2763/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
26682764pub fn renameat(
26692765 old_dir_fd: fd_t,
26702766 old_path: []const u8,
......@@ -2710,11 +2806,12 @@ pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!vo
27102806 .ROFS => return error.ReadOnlyFileSystem,
27112807 .XDEV => return error.RenameAcrossMountPoints,
27122808 .NOTCAPABLE => return error.AccessDenied,
2809 .ILSEQ => return error.InvalidUtf8,
27132810 else => |err| return unexpectedErrno(err),
27142811 }
27152812}
27162813
2717/// Same as `renameat` except the parameters are null-terminated byte arrays.
2814/// Same as `renameat` except the parameters are null-terminated.
27182815pub fn renameatZ(
27192816 old_dir_fd: fd_t,
27202817 old_path: [*:0]const u8,
......@@ -2749,6 +2846,10 @@ pub fn renameatZ(
27492846 .NOTEMPTY => return error.PathAlreadyExists,
27502847 .ROFS => return error.ReadOnlyFileSystem,
27512848 .XDEV => return error.RenameAcrossMountPoints,
2849 .ILSEQ => |err| if (builtin.os.tag == .wasi)
2850 return error.InvalidUtf8
2851 else
2852 return unexpectedErrno(err),
27522853 else => |err| return unexpectedErrno(err),
27532854 }
27542855}
......@@ -2860,6 +2961,9 @@ pub fn renameatW(
28602961 }
28612962}
28622963
2964/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2965/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.
2966/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.
28632967pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
28642968 if (builtin.os.tag == .windows) {
28652969 const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path);
......@@ -2891,14 +2995,16 @@ pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirErr
28912995 .NOTDIR => return error.NotDir,
28922996 .ROFS => return error.ReadOnlyFileSystem,
28932997 .NOTCAPABLE => return error.AccessDenied,
2998 .ILSEQ => return error.InvalidUtf8,
28942999 else => |err| return unexpectedErrno(err),
28953000 }
28963001}
28973002
3003/// Same as `mkdirat` except the parameters are null-terminated.
28983004pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
28993005 if (builtin.os.tag == .windows) {
29003006 const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path);
2901 return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
3007 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
29023008 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
29033009 return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);
29043010 }
......@@ -2920,10 +3026,15 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
29203026 .ROFS => return error.ReadOnlyFileSystem,
29213027 // dragonfly: when dir_fd is unlinked from filesystem
29223028 .NOTCONN => return error.FileNotFound,
3029 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3030 return error.InvalidUtf8
3031 else
3032 return unexpectedErrno(err),
29233033 else => |err| return unexpectedErrno(err),
29243034 }
29253035}
29263036
3037/// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded.
29273038pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
29283039 _ = mode;
29293040 const sub_dir_handle = windows.OpenFile(sub_path_w, .{
......@@ -2955,7 +3066,11 @@ pub const MakeDirError = error{
29553066 NoSpaceLeft,
29563067 NotDir,
29573068 ReadOnlyFileSystem,
3069 /// WASI-only; file paths must be valid UTF-8.
29583070 InvalidUtf8,
3071 /// Windows-only; file paths provided by the user must be valid WTF-8.
3072 /// https://simonsapin.github.io/wtf-8/
3073 InvalidWtf8,
29593074 BadPathName,
29603075 NoDevice,
29613076 /// On Windows, `\\server` or `\\server\share` was not found.
......@@ -2964,6 +3079,9 @@ pub const MakeDirError = error{
29643079
29653080/// Create a directory.
29663081/// `mode` is ignored on Windows and WASI.
3082/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3083/// On WASI, `dir_path` should be encoded as valid UTF-8.
3084/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
29673085pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
29683086 if (builtin.os.tag == .wasi and !builtin.link_libc) {
29693087 return mkdirat(wasi.AT.FDCWD, dir_path, mode);
......@@ -2976,7 +3094,10 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
29763094 }
29773095}
29783096
2979/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
3097/// Same as `mkdir` but the parameter is null-terminated.
3098/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3099/// On WASI, `dir_path` should be encoded as valid UTF-8.
3100/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
29803101pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
29813102 if (builtin.os.tag == .windows) {
29823103 const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
......@@ -2999,11 +3120,15 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
29993120 .NOSPC => return error.NoSpaceLeft,
30003121 .NOTDIR => return error.NotDir,
30013122 .ROFS => return error.ReadOnlyFileSystem,
3123 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3124 return error.InvalidUtf8
3125 else
3126 return unexpectedErrno(err),
30023127 else => |err| return unexpectedErrno(err),
30033128 }
30043129}
30053130
3006/// Windows-only. Same as `mkdir` but the parameters is WTF16 encoded.
3131/// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded.
30073132pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
30083133 _ = mode;
30093134 const sub_dir_handle = windows.OpenFile(dir_path_w, .{
......@@ -3031,13 +3156,20 @@ pub const DeleteDirError = error{
30313156 NotDir,
30323157 DirNotEmpty,
30333158 ReadOnlyFileSystem,
3159 /// WASI-only; file paths must be valid UTF-8.
30343160 InvalidUtf8,
3161 /// Windows-only; file paths provided by the user must be valid WTF-8.
3162 /// https://simonsapin.github.io/wtf-8/
3163 InvalidWtf8,
30353164 BadPathName,
30363165 /// On Windows, `\\server` or `\\server\share` was not found.
30373166 NetworkNotFound,
30383167} || UnexpectedError;
30393168
30403169/// Deletes an empty directory.
3170/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3171/// On WASI, `dir_path` should be encoded as valid UTF-8.
3172/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
30413173pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
30423174 if (builtin.os.tag == .wasi and !builtin.link_libc) {
30433175 return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {
......@@ -3055,6 +3187,9 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
30553187}
30563188
30573189/// Same as `rmdir` except the parameter is null-terminated.
3190/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3191/// On WASI, `dir_path` should be encoded as valid UTF-8.
3192/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
30583193pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
30593194 if (builtin.os.tag == .windows) {
30603195 const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
......@@ -3077,11 +3212,15 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
30773212 .EXIST => return error.DirNotEmpty,
30783213 .NOTEMPTY => return error.DirNotEmpty,
30793214 .ROFS => return error.ReadOnlyFileSystem,
3215 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3216 return error.InvalidUtf8
3217 else
3218 return unexpectedErrno(err),
30803219 else => |err| return unexpectedErrno(err),
30813220 }
30823221}
30833222
3084/// Windows-only. Same as `rmdir` except the parameter is WTF16 encoded.
3223/// Windows-only. Same as `rmdir` except the parameter is WTF-16 LE encoded.
30853224pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
30863225 return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
30873226 error.IsDir => unreachable,
......@@ -3098,21 +3237,25 @@ pub const ChangeCurDirError = error{
30983237 SystemResources,
30993238 NotDir,
31003239 BadPathName,
3101
3102 /// On Windows, file paths must be valid Unicode.
3240 /// WASI-only; file paths must be valid UTF-8.
31033241 InvalidUtf8,
3242 /// Windows-only; file paths provided by the user must be valid WTF-8.
3243 /// https://simonsapin.github.io/wtf-8/
3244 InvalidWtf8,
31043245} || UnexpectedError;
31053246
31063247/// Changes the current working directory of the calling process.
3107/// `dir_path` is recommended to be a UTF-8 encoded string.
3248/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3249/// On WASI, `dir_path` should be encoded as valid UTF-8.
3250/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
31083251pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
31093252 if (builtin.os.tag == .wasi and !builtin.link_libc) {
31103253 @compileError("WASI does not support os.chdir");
31113254 } else if (builtin.os.tag == .windows) {
3112 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3113 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
3114 if (len > utf16_dir_path.len) return error.NameTooLong;
3115 return chdirW(utf16_dir_path[0..len]);
3255 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3256 const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], dir_path);
3257 if (len > wtf16_dir_path.len) return error.NameTooLong;
3258 return chdirW(wtf16_dir_path[0..len]);
31163259 } else {
31173260 const dir_path_c = try toPosixPath(dir_path);
31183261 return chdirZ(&dir_path_c);
......@@ -3120,12 +3263,15 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
31203263}
31213264
31223265/// Same as `chdir` except the parameter is null-terminated.
3266/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3267/// On WASI, `dir_path` should be encoded as valid UTF-8.
3268/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
31233269pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
31243270 if (builtin.os.tag == .windows) {
3125 var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3126 const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], mem.span(dir_path));
3127 if (len > utf16_dir_path.len) return error.NameTooLong;
3128 return chdirW(utf16_dir_path[0..len]);
3271 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3272 const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], mem.span(dir_path));
3273 if (len > wtf16_dir_path.len) return error.NameTooLong;
3274 return chdirW(wtf16_dir_path[0..len]);
31293275 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
31303276 return chdir(mem.span(dir_path));
31313277 }
......@@ -3139,11 +3285,15 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
31393285 .NOENT => return error.FileNotFound,
31403286 .NOMEM => return error.SystemResources,
31413287 .NOTDIR => return error.NotDir,
3288 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3289 return error.InvalidUtf8
3290 else
3291 return unexpectedErrno(err),
31423292 else => |err| return unexpectedErrno(err),
31433293 }
31443294}
31453295
3146/// Windows-only. Same as `chdir` except the parameter is WTF16 encoded.
3296/// Windows-only. Same as `chdir` except the parameter is WTF16 LE encoded.
31473297pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void {
31483298 windows.SetCurrentDirectory(dir_path) catch |err| switch (err) {
31493299 error.NoDevice => return error.FileSystem,
......@@ -3183,7 +3333,11 @@ pub const ReadLinkError = error{
31833333 SystemResources,
31843334 NotLink,
31853335 NotDir,
3336 /// WASI-only; file paths must be valid UTF-8.
31863337 InvalidUtf8,
3338 /// Windows-only; file paths provided by the user must be valid WTF-8.
3339 /// https://simonsapin.github.io/wtf-8/
3340 InvalidWtf8,
31873341 BadPathName,
31883342 /// Windows-only. This error may occur if the opened reparse point is
31893343 /// of unsupported type.
......@@ -3193,7 +3347,13 @@ pub const ReadLinkError = error{
31933347} || UnexpectedError;
31943348
31953349/// Read value of a symbolic link.
3350/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3351/// On WASI, `file_path` should be encoded as valid UTF-8.
3352/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
31963353/// The return value is a slice of `out_buffer` from index 0.
3354/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3355/// On WASI, the result is encoded as UTF-8.
3356/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
31973357pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
31983358 if (builtin.os.tag == .wasi and !builtin.link_libc) {
31993359 return readlinkat(wasi.AT.FDCWD, file_path, out_buffer);
......@@ -3206,7 +3366,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
32063366 }
32073367}
32083368
3209/// Windows-only. Same as `readlink` except `file_path` is WTF16 encoded.
3369/// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded.
3370/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
32103371/// See also `readlinkZ`.
32113372pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
32123373 return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
......@@ -3215,7 +3376,7 @@ pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
32153376/// Same as `readlink` except `file_path` is null-terminated.
32163377pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
32173378 if (builtin.os.tag == .windows) {
3218 const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
3379 const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
32193380 return readlinkW(file_path_w.span(), out_buffer);
32203381 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
32213382 return readlink(mem.sliceTo(file_path, 0), out_buffer);
......@@ -3232,12 +3393,22 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
32323393 .NOENT => return error.FileNotFound,
32333394 .NOMEM => return error.SystemResources,
32343395 .NOTDIR => return error.NotDir,
3396 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3397 return error.InvalidUtf8
3398 else
3399 return unexpectedErrno(err),
32353400 else => |err| return unexpectedErrno(err),
32363401 }
32373402}
32383403
32393404/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle.
3405/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3406/// On WASI, `file_path` should be encoded as valid UTF-8.
3407/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
32403408/// The return value is a slice of `out_buffer` from index 0.
3409/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
3410/// On WASI, the result is encoded as UTF-8.
3411/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
32413412/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
32423413pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
32433414 if (builtin.os.tag == .wasi and !builtin.link_libc) {
......@@ -3267,11 +3438,13 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
32673438 .NOMEM => return error.SystemResources,
32683439 .NOTDIR => return error.NotDir,
32693440 .NOTCAPABLE => return error.AccessDenied,
3441 .ILSEQ => return error.InvalidUtf8,
32703442 else => |err| return unexpectedErrno(err),
32713443 }
32723444}
32733445
3274/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.
3446/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded.
3447/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
32753448/// See also `readlinkat`.
32763449pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
32773450 return windows.ReadLink(dirfd, file_path, out_buffer);
......@@ -3298,6 +3471,10 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
32983471 .NOENT => return error.FileNotFound,
32993472 .NOMEM => return error.SystemResources,
33003473 .NOTDIR => return error.NotDir,
3474 .ILSEQ => |err| if (builtin.os.tag == .wasi)
3475 return error.InvalidUtf8
3476 else
3477 return unexpectedErrno(err),
33013478 else => |err| return unexpectedErrno(err),
33023479 }
33033480}
......@@ -4274,10 +4451,18 @@ pub fn fstat_wasi(fd: fd_t) FStatError!wasi.filestat_t {
42744451 }
42754452}
42764453
4277pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLinkLoop };
4454pub const FStatAtError = FStatError || error{
4455 NameTooLong,
4456 FileNotFound,
4457 SymLinkLoop,
4458 /// WASI-only; file paths must be valid UTF-8.
4459 InvalidUtf8,
4460};
42784461
42794462/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`
42804463/// which is relative to `dirfd` handle.
4464/// On WASI, `pathname` should be encoded as valid UTF-8.
4465/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
42814466/// See also `fstatatZ` and `fstatat_wasi`.
42824467pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
42834468 if (builtin.os.tag == .wasi and !builtin.link_libc) {
......@@ -4294,6 +4479,7 @@ pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat
42944479}
42954480
42964481/// WASI-only. Same as `fstatat` but targeting WASI.
4482/// `pathname` should be encoded as valid UTF-8.
42974483/// See also `fstatat`.
42984484pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t) FStatAtError!wasi.filestat_t {
42994485 var stat: wasi.filestat_t = undefined;
......@@ -4308,6 +4494,7 @@ pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t
43084494 .NOENT => return error.FileNotFound,
43094495 .NOTDIR => return error.FileNotFound,
43104496 .NOTCAPABLE => return error.AccessDenied,
4497 .ILSEQ => return error.InvalidUtf8,
43114498 else => |err| return unexpectedErrno(err),
43124499 }
43134500}
......@@ -4337,6 +4524,10 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S
43374524 .LOOP => return error.SymLinkLoop,
43384525 .NOENT => return error.FileNotFound,
43394526 .NOTDIR => return error.FileNotFound,
4527 .ILSEQ => |err| if (builtin.os.tag == .wasi)
4528 return error.InvalidUtf8
4529 else
4530 return unexpectedErrno(err),
43404531 else => |err| return unexpectedErrno(err),
43414532 }
43424533}
......@@ -4693,12 +4884,17 @@ pub const AccessError = error{
46934884 FileBusy,
46944885 SymLinkLoop,
46954886 ReadOnlyFileSystem,
4696
4697 /// On Windows, file paths must be valid Unicode.
4887 /// WASI-only; file paths must be valid UTF-8.
46984888 InvalidUtf8,
4889 /// Windows-only; file paths provided by the user must be valid WTF-8.
4890 /// https://simonsapin.github.io/wtf-8/
4891 InvalidWtf8,
46994892} || UnexpectedError;
47004893
47014894/// check user's permissions for a file
4895/// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
4896/// On WASI, `path` should be encoded as valid UTF-8.
4897/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
47024898/// TODO currently this assumes `mode` is `F.OK` on Windows.
47034899pub fn access(path: []const u8, mode: u32) AccessError!void {
47044900 if (builtin.os.tag == .windows) {
......@@ -4740,12 +4936,16 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
47404936 .FAULT => unreachable,
47414937 .IO => return error.InputOutput,
47424938 .NOMEM => return error.SystemResources,
4939 .ILSEQ => |err| if (builtin.os.tag == .wasi)
4940 return error.InvalidUtf8
4941 else
4942 return unexpectedErrno(err),
47434943 else => |err| return unexpectedErrno(err),
47444944 }
47454945}
47464946
4747/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
4748/// Otherwise use `access` or `accessC`.
4947/// Call from Windows-specific code if you already have a WTF-16LE encoded, null terminated string.
4948/// Otherwise use `access` or `accessZ`.
47494949/// TODO currently this ignores `mode`.
47504950pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!void {
47514951 _ = mode;
......@@ -4762,6 +4962,9 @@ pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!v
47624962}
47634963
47644964/// Check user's permissions for a file, based on an open directory handle.
4965/// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
4966/// On WASI, `path` should be encoded as valid UTF-8.
4967/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
47654968/// TODO currently this ignores `mode` and `flags` on Windows.
47664969pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
47674970 if (builtin.os.tag == .windows) {
......@@ -4832,6 +5035,10 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces
48325035 .FAULT => unreachable,
48335036 .IO => return error.InputOutput,
48345037 .NOMEM => return error.SystemResources,
5038 .ILSEQ => |err| if (builtin.os.tag == .wasi)
5039 return error.InvalidUtf8
5040 else
5041 return unexpectedErrno(err),
48355042 else => |err| return unexpectedErrno(err),
48365043 }
48375044}
......@@ -5339,8 +5546,9 @@ pub const RealPathError = error{
53395546 /// On WASI, the current CWD may not be associated with an absolute path.
53405547 InvalidHandle,
53415548
5342 /// On Windows, file paths must be valid Unicode.
5343 InvalidUtf8,
5549 /// Windows-only; file paths provided by the user must be valid WTF-8.
5550 /// https://simonsapin.github.io/wtf-8/
5551 InvalidWtf8,
53445552
53455553 /// On Windows, `\\server` or `\\server\share` was not found.
53465554 NetworkNotFound,
......@@ -5362,8 +5570,12 @@ pub const RealPathError = error{
53625570/// Return the canonicalized absolute pathname.
53635571/// Expands all symbolic links and resolves references to `.`, `..`, and
53645572/// extra `/` characters in `pathname`.
5573/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
5574/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
53655575/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
53665576/// See also `realpathZ` and `realpathW`.
5577/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
5578/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
53675579/// Calling this function is usually a bug.
53685580pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
53695581 if (builtin.os.tag == .windows) {
......@@ -5402,6 +5614,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
54025614 error.WouldBlock => unreachable,
54035615 error.FileBusy => unreachable, // not asking for write permissions
54045616 error.InvalidHandle => unreachable, // WASI-only
5617 error.InvalidUtf8 => unreachable, // WASI-only
54055618 else => |e| return e,
54065619 };
54075620 defer close(fd);
......@@ -5425,7 +5638,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
54255638 return mem.sliceTo(result_path, 0);
54265639}
54275640
5428/// Same as `realpath` except `pathname` is UTF16LE-encoded.
5641/// Same as `realpath` except `pathname` is WTF16LE-encoded.
5642/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
54295643/// Calling this function is usually a bug.
54305644pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
54315645 const w = windows;
......@@ -5475,6 +5689,8 @@ pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
54755689/// This function is very host-specific and is not universally supported by all hosts.
54765690/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
54775691/// unsupported on WASI.
5692/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
5693/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
54785694/// Calling this function is usually a bug.
54795695pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
54805696 if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
......@@ -5485,10 +5701,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
54855701 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
54865702 const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
54875703
5488 // TODO: Windows file paths can be arbitrary arrays of u16 values
5489 // and must not fail with InvalidUtf8.
5490 const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch
5491 return error.InvalidUtf8;
5704 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
54925705 return out_buffer[0..end_index];
54935706 },
54945707 .macos, .ios, .watchos, .tvos => {
......@@ -5512,8 +5725,12 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
55125725
55135726 const target = readlinkZ(proc_path, out_buffer) catch |err| {
55145727 switch (err) {
5515 error.UnsupportedReparsePointType => unreachable, // Windows only,
55165728 error.NotLink => unreachable,
5729 error.BadPathName => unreachable,
5730 error.InvalidUtf8 => unreachable, // WASI-only
5731 error.InvalidWtf8 => unreachable, // Windows-only
5732 error.UnsupportedReparsePointType => unreachable, // Windows-only
5733 error.NetworkNotFound => unreachable, // Windows-only
55175734 else => |e| return e,
55185735 }
55195736 };
lib/std/os/windows.zig+51-37
......@@ -1,8 +1,8 @@
11//! This file contains thin wrappers around Windows-specific APIs, with these
22//! specific goals in mind:
33//! * Convert "errno"-style error codes into Zig errors.
4//! * When null-terminated or UTF16LE byte buffers are required, provide APIs which accept
5//! slices as well as APIs which accept null-terminated UTF16LE byte buffers.
4//! * When null-terminated or WTF16LE byte buffers are required, provide APIs which accept
5//! slices as well as APIs which accept null-terminated WTF16LE byte buffers.
66
77const builtin = @import("builtin");
88const std = @import("../std.zig");
......@@ -548,7 +548,6 @@ pub fn WriteFile(
548548
549549pub const SetCurrentDirectoryError = error{
550550 NameTooLong,
551 InvalidUtf8,
552551 FileNotFound,
553552 NotDir,
554553 AccessDenied,
......@@ -587,24 +586,24 @@ pub const GetCurrentDirectoryError = error{
587586};
588587
589588/// The result is a slice of `buffer`, indexed from 0.
589/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
590590pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
591 var utf16le_buf: [PATH_MAX_WIDE]u16 = undefined;
592 const result = kernel32.GetCurrentDirectoryW(utf16le_buf.len, &utf16le_buf);
591 var wtf16le_buf: [PATH_MAX_WIDE]u16 = undefined;
592 const result = kernel32.GetCurrentDirectoryW(wtf16le_buf.len, &wtf16le_buf);
593593 if (result == 0) {
594594 switch (kernel32.GetLastError()) {
595595 else => |err| return unexpectedError(err),
596596 }
597597 }
598 assert(result <= utf16le_buf.len);
599 const utf16le_slice = utf16le_buf[0..result];
600 // Trust that Windows gives us valid UTF-16LE.
598 assert(result <= wtf16le_buf.len);
599 const wtf16le_slice = wtf16le_buf[0..result];
601600 var end_index: usize = 0;
602 var it = std.unicode.Utf16LeIterator.init(utf16le_slice);
603 while (it.nextCodepoint() catch unreachable) |codepoint| {
601 var it = std.unicode.Wtf16LeIterator.init(wtf16le_slice);
602 while (it.nextCodepoint()) |codepoint| {
604603 const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable;
605604 if (end_index + seq_len >= buffer.len)
606605 return error.NameTooLong;
607 end_index += std.unicode.utf8Encode(codepoint, buffer[end_index..]) catch unreachable;
606 end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable;
608607 }
609608 return buffer[0..end_index];
610609}
......@@ -812,6 +811,8 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
812811 }
813812}
814813
814/// Asserts that there is enough space is `out_buffer`.
815/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
815816fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {
816817 const win32_namespace_path = path: {
817818 if (is_relative) break :path path;
......@@ -821,7 +822,7 @@ fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u
821822 };
822823 break :path win32_path.span();
823824 };
824 const out_len = std.unicode.utf16LeToUtf8(out_buffer, win32_namespace_path) catch unreachable;
825 const out_len = std.unicode.wtf16LeToWtf8(out_buffer, win32_namespace_path);
825826 return out_buffer[0..out_len];
826827}
827828
......@@ -1942,13 +1943,13 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {
19421943 if (@inComptime() or builtin.os.tag != .windows) {
19431944 // This function compares the strings code unit by code unit (aka u16-to-u16),
19441945 // so any length difference implies inequality. In other words, there's no possible
1945 // conversion that changes the number of UTF-16 code units needed for the uppercase/lowercase
1946 // conversion that changes the number of WTF-16 code units needed for the uppercase/lowercase
19461947 // version in the conversion table since only codepoints <= max(u16) are eligible
19471948 // for conversion at all.
19481949 if (a.len != b.len) return false;
19491950
19501951 for (a, b) |a_c, b_c| {
1951 // The slices are always UTF-16 LE, so need to convert the elements to native
1952 // The slices are always WTF-16 LE, so need to convert the elements to native
19521953 // endianness for the uppercasing
19531954 const a_c_native = std.mem.littleToNative(u16, a_c);
19541955 const b_c_native = std.mem.littleToNative(u16, b_c);
......@@ -1975,18 +1976,18 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {
19751976 return ntdll.RtlEqualUnicodeString(&a_string, &b_string, TRUE) == TRUE;
19761977}
19771978
1978/// Compares two UTF-8 strings using the equivalent functionality of
1979/// Compares two WTF-8 strings using the equivalent functionality of
19791980/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
19801981/// This function can be called on any target.
1981/// Assumes `a` and `b` are valid UTF-8.
1982pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
1982/// Assumes `a` and `b` are valid WTF-8.
1983pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
19831984 // A length equality check is not possible here because there are
19841985 // some codepoints that have a different length uppercase UTF-8 representations
19851986 // than their lowercase counterparts, e.g. U+0250 (2 bytes) <-> U+2C6F (3 bytes).
19861987 // There are 7 such codepoints in the uppercase data used by Windows.
19871988
1988 var a_utf8_it = std.unicode.Utf8View.initUnchecked(a).iterator();
1989 var b_utf8_it = std.unicode.Utf8View.initUnchecked(b).iterator();
1989 var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();
1990 var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator();
19901991
19911992 // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
19921993 // redundant copy of the uppercase data.
......@@ -1996,8 +1997,8 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
19961997 };
19971998
19981999 while (true) {
1999 const a_cp = a_utf8_it.nextCodepoint() orelse break;
2000 const b_cp = b_utf8_it.nextCodepoint() orelse return false;
2000 const a_cp = a_wtf8_it.nextCodepoint() orelse break;
2001 const b_cp = b_wtf8_it.nextCodepoint() orelse return false;
20012002
20022003 if (a_cp <= std.math.maxInt(u16) and b_cp <= std.math.maxInt(u16)) {
20032004 if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) {
......@@ -2008,26 +2009,26 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
20082009 }
20092010 }
20102011 // Make sure there are no leftover codepoints in b
2011 if (b_utf8_it.nextCodepoint() != null) return false;
2012 if (b_wtf8_it.nextCodepoint() != null) return false;
20122013
20132014 return true;
20142015}
20152016
20162017fn testEqlIgnoreCase(comptime expect_eql: bool, comptime a: []const u8, comptime b: []const u8) !void {
2017 try std.testing.expectEqual(expect_eql, eqlIgnoreCaseUtf8(a, b));
2018 try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWtf8(a, b));
20182019 try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWTF16(
20192020 std.unicode.utf8ToUtf16LeStringLiteral(a),
20202021 std.unicode.utf8ToUtf16LeStringLiteral(b),
20212022 ));
20222023
2023 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseUtf8(a, b));
2024 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWtf8(a, b));
20242025 try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWTF16(
20252026 std.unicode.utf8ToUtf16LeStringLiteral(a),
20262027 std.unicode.utf8ToUtf16LeStringLiteral(b),
20272028 ));
20282029}
20292030
2030test "eqlIgnoreCaseWTF16/Utf8" {
2031test "eqlIgnoreCaseWTF16/Wtf8" {
20312032 try testEqlIgnoreCase(true, "\x01 a B Λ ɐ", "\x01 A b λ Ɐ");
20322033 // does not do case-insensitive comparison for codepoints >= U+10000
20332034 try testEqlIgnoreCase(false, "𐓏", "𐓷");
......@@ -2117,20 +2118,32 @@ pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize {
21172118 return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]);
21182119}
21192120
2121pub const Wtf8ToPrefixedFileWError = error{InvalidWtf8} || Wtf16ToPrefixedFileWError;
2122
21202123/// Same as `sliceToPrefixedFileW` but accepts a pointer
2121/// to a null-terminated path.
2122pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) !PathSpace {
2124/// to a null-terminated WTF-8 encoded path.
2125/// https://simonsapin.github.io/wtf-8/
2126pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWError!PathSpace {
21232127 return sliceToPrefixedFileW(dir, mem.sliceTo(s, 0));
21242128}
21252129
2126/// Same as `wToPrefixedFileW` but accepts a UTF-8 encoded path.
2127pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace {
2130/// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path.
2131/// https://simonsapin.github.io/wtf-8/
2132pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace {
21282133 var temp_path: PathSpace = undefined;
2129 temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, path);
2134 temp_path.len = try std.unicode.wtf8ToWtf16Le(&temp_path.data, path);
21302135 temp_path.data[temp_path.len] = 0;
21312136 return wToPrefixedFileW(dir, temp_path.span());
21322137}
21332138
2139pub const Wtf16ToPrefixedFileWError = error{
2140 AccessDenied,
2141 BadPathName,
2142 FileNotFound,
2143 NameTooLong,
2144 Unexpected,
2145};
2146
21342147/// Converts the `path` to WTF16, null-terminated. If the path contains any
21352148/// namespace prefix, or is anything but a relative path (rooted, drive relative,
21362149/// etc) the result will have the NT-style prefix `\??\`.
......@@ -2142,7 +2155,7 @@ pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace {
21422155/// is non-null, or the CWD if it is null.
21432156/// - Special case device names like COM1, NUL, etc are not handled specially (TODO)
21442157/// - . and space are not stripped from the end of relative paths (potential TODO)
2145pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) !PathSpace {
2158pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!PathSpace {
21462159 const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };
21472160 switch (getNamespacePrefix(u16, path)) {
21482161 // TODO: Figure out a way to design an API that can avoid the copy for .nt,
......@@ -2312,7 +2325,7 @@ pub const NamespacePrefix = enum {
23122325 nt,
23132326};
23142327
2315/// If `T` is `u16`, then `path` should be encoded as UTF-16LE.
2328/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
23162329pub fn getNamespacePrefix(comptime T: type, path: []const T) NamespacePrefix {
23172330 if (path.len < 4) return .none;
23182331 var all_backslash = switch (mem.littleToNative(T, path[0])) {
......@@ -2366,7 +2379,7 @@ pub const UnprefixedPathType = enum {
23662379
23672380/// Get the path type of a path that is known to not have any namespace prefixes
23682381/// (`\\?\`, `\\.\`, `\??\`).
2369/// If `T` is `u16`, then `path` should be encoded as UTF-16LE.
2382/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
23702383pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathType {
23712384 if (path.len < 1) return .relative;
23722385
......@@ -2420,7 +2433,7 @@ test getUnprefixedPathType {
24202433/// Functionality is based on the ReactOS test cases found here:
24212434/// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c
24222435///
2423/// `path` should be encoded as UTF-16LE.
2436/// `path` should be encoded as WTF-16LE.
24242437pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
24252438 if (path.len > PATH_MAX_WIDE) return error.NameTooLong;
24262439
......@@ -2530,7 +2543,6 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {
25302543 if (std.os.unexpected_error_tracing) {
25312544 // 614 is the length of the longest windows error description
25322545 var buf_wstr: [614]WCHAR = undefined;
2533 var buf_utf8: [614]u8 = undefined;
25342546 const len = kernel32.FormatMessageW(
25352547 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
25362548 null,
......@@ -2540,8 +2552,10 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {
25402552 buf_wstr.len,
25412553 null,
25422554 );
2543 _ = std.unicode.utf16LeToUtf8(&buf_utf8, buf_wstr[0..len]) catch unreachable;
2544 std.debug.print("error.Unexpected: GetLastError({}): {s}\n", .{ @intFromEnum(err), buf_utf8[0..len] });
2555 std.debug.print("error.Unexpected: GetLastError({}): {}\n", .{
2556 @intFromEnum(err),
2557 std.unicode.fmtUtf16Le(buf_wstr[0..len]),
2558 });
25452559 std.debug.dumpCurrentStackTrace(@returnAddress());
25462560 }
25472561 return error.Unexpected;
lib/std/process.zig+89-63
......@@ -16,11 +16,15 @@ pub const changeCurDir = os.chdir;
1616pub const changeCurDirC = os.chdirC;
1717
1818/// The result is a slice of `out_buffer`, from index `0`.
19/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
20/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1921pub fn getCwd(out_buffer: []u8) ![]u8 {
2022 return os.getcwd(out_buffer);
2123}
2224
2325/// Caller must free the returned memory.
26/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
27/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
2428pub fn getCwdAlloc(allocator: Allocator) ![]u8 {
2529 // The use of MAX_PATH_BYTES here is just a heuristic: most paths will fit
2630 // in stack_buf, avoiding an extra allocation in the common case.
......@@ -76,7 +80,7 @@ pub const EnvMap = struct {
7680 _ = self;
7781 if (builtin.os.tag == .windows) {
7882 var h = std.hash.Wyhash.init(0);
79 var it = std.unicode.Utf8View.initUnchecked(s).iterator();
83 var it = std.unicode.Wtf8View.initUnchecked(s).iterator();
8084 while (it.nextCodepoint()) |cp| {
8185 const cp_upper = upcase(cp);
8286 h.update(&[_]u8{
......@@ -93,8 +97,8 @@ pub const EnvMap = struct {
9397 pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
9498 _ = self;
9599 if (builtin.os.tag == .windows) {
96 var it_a = std.unicode.Utf8View.initUnchecked(a).iterator();
97 var it_b = std.unicode.Utf8View.initUnchecked(b).iterator();
100 var it_a = std.unicode.Wtf8View.initUnchecked(a).iterator();
101 var it_b = std.unicode.Wtf8View.initUnchecked(b).iterator();
98102 while (true) {
99103 const c_a = it_a.nextCodepoint() orelse break;
100104 const c_b = it_b.nextCodepoint() orelse return false;
......@@ -129,8 +133,9 @@ pub const EnvMap = struct {
129133 /// Same as `put` but the key and value become owned by the EnvMap rather
130134 /// than being copied.
131135 /// If `putMove` fails, the ownership of key and value does not transfer.
132 /// On Windows `key` must be a valid UTF-8 string.
136 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
133137 pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {
138 assert(std.unicode.wtf8ValidateSlice(key));
134139 const get_or_put = try self.hash_map.getOrPut(key);
135140 if (get_or_put.found_existing) {
136141 self.free(get_or_put.key_ptr.*);
......@@ -141,8 +146,9 @@ pub const EnvMap = struct {
141146 }
142147
143148 /// `key` and `value` are copied into the EnvMap.
144 /// On Windows `key` must be a valid UTF-8 string.
149 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
145150 pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {
151 assert(std.unicode.wtf8ValidateSlice(key));
146152 const value_copy = try self.copy(value);
147153 errdefer self.free(value_copy);
148154 const get_or_put = try self.hash_map.getOrPut(key);
......@@ -159,23 +165,26 @@ pub const EnvMap = struct {
159165
160166 /// Find the address of the value associated with a key.
161167 /// The returned pointer is invalidated if the map resizes.
162 /// On Windows `key` must be a valid UTF-8 string.
168 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
163169 pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {
170 assert(std.unicode.wtf8ValidateSlice(key));
164171 return self.hash_map.getPtr(key);
165172 }
166173
167174 /// Return the map's copy of the value associated with
168175 /// a key. The returned string is invalidated if this
169176 /// key is removed from the map.
170 /// On Windows `key` must be a valid UTF-8 string.
177 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
171178 pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {
179 assert(std.unicode.wtf8ValidateSlice(key));
172180 return self.hash_map.get(key);
173181 }
174182
175183 /// Removes the item from the map and frees its value.
176184 /// This invalidates the value returned by get() for this key.
177 /// On Windows `key` must be a valid UTF-8 string.
185 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
178186 pub fn remove(self: *EnvMap, key: []const u8) void {
187 assert(std.unicode.wtf8ValidateSlice(key));
179188 const kv = self.hash_map.fetchRemove(key) orelse return;
180189 self.free(kv.key);
181190 self.free(kv.value);
......@@ -239,18 +248,34 @@ test "EnvMap" {
239248
240249 try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
241250
242 // test Unicode case-insensitivity on Windows
243251 if (builtin.os.tag == .windows) {
252 // test Unicode case-insensitivity on Windows
244253 try env.put("КИРиллИЦА", "something else");
245254 try testing.expectEqualStrings("something else", env.get("кириллица").?);
255
256 // and WTF-8 that's not valid UTF-8
257 const wtf8_with_surrogate_pair = try std.unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
258 std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
259 });
260 defer testing.allocator.free(wtf8_with_surrogate_pair);
261
262 try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
263 try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
246264 }
247265}
248266
267pub const GetEnvMapError = error{
268 OutOfMemory,
269 /// WASI-only. `environ_sizes_get` or `environ_get`
270 /// failed for an unexpected reason.
271 Unexpected,
272};
273
249274/// Returns a snapshot of the environment variables of the current process.
250275/// Any modifications to the resulting EnvMap will not be reflected in the environment, and
251276/// likewise, any future modifications to the environment will not be reflected in the EnvMap.
252277/// Caller owns resulting `EnvMap` and should call its `deinit` fn when done.
253pub fn getEnvMap(allocator: Allocator) !EnvMap {
278pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap {
254279 var result = EnvMap.init(allocator);
255280 errdefer result.deinit();
256281
......@@ -269,7 +294,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {
269294
270295 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
271296 const key_w = ptr[key_start..i];
272 const key = try std.unicode.utf16LeToUtf8Alloc(allocator, key_w);
297 const key = try std.unicode.wtf16LeToWtf8Alloc(allocator, key_w);
273298 errdefer allocator.free(key);
274299
275300 if (ptr[i] == '=') i += 1;
......@@ -277,7 +302,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {
277302 const value_start = i;
278303 while (ptr[i] != 0) : (i += 1) {}
279304 const value_w = ptr[value_start..i];
280 const value = try std.unicode.utf16LeToUtf8Alloc(allocator, value_w);
305 const value = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_w);
281306 errdefer allocator.free(value);
282307
283308 i += 1; // skip over null byte
......@@ -355,25 +380,26 @@ pub const GetEnvVarOwnedError = error{
355380 OutOfMemory,
356381 EnvironmentVariableNotFound,
357382
358 /// See https://github.com/ziglang/zig/issues/1774
359 InvalidUtf8,
383 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
384 /// https://simonsapin.github.io/wtf-8/
385 InvalidWtf8,
360386};
361387
362388/// Caller must free returned memory.
389/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/),
390/// then `error.InvalidWtf8` is returned.
391/// On Windows, the value is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
392/// On other platforms, the value is an opaque sequence of bytes with no particular encoding.
363393pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
364394 if (builtin.os.tag == .windows) {
365395 const result_w = blk: {
366 const key_w = try std.unicode.utf8ToUtf16LeAllocZ(allocator, key);
396 const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(allocator, key);
367397 defer allocator.free(key_w);
368398
369399 break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
370400 };
371 return std.unicode.utf16LeToUtf8Alloc(allocator, result_w) catch |err| switch (err) {
372 error.DanglingSurrogateHalf => return error.InvalidUtf8,
373 error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,
374 error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,
375 else => |e| return e,
376 };
401 // wtf16LeToWtf8Alloc can only fail with OutOfMemory
402 return std.unicode.wtf16LeToWtf8Alloc(allocator, result_w);
377403 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
378404 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
379405 defer envmap.deinit();
......@@ -385,6 +411,7 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
385411 }
386412}
387413
414/// On Windows, `key` must be valid UTF-8.
388415pub fn hasEnvVarConstant(comptime key: []const u8) bool {
389416 if (builtin.os.tag == .windows) {
390417 const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);
......@@ -396,11 +423,22 @@ pub fn hasEnvVarConstant(comptime key: []const u8) bool {
396423 }
397424}
398425
399pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool {
426pub const HasEnvVarError = error{
427 OutOfMemory,
428
429 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
430 /// https://simonsapin.github.io/wtf-8/
431 InvalidWtf8,
432};
433
434/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/),
435/// then `error.InvalidWtf8` is returned.
436pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
400437 if (builtin.os.tag == .windows) {
401438 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
402 const key_w = try std.unicode.utf8ToUtf16LeAllocZ(stack_alloc.get(), key);
403 defer stack_alloc.allocator.free(key_w);
439 const stack_allocator = stack_alloc.get();
440 const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
441 defer stack_allocator.free(key_w);
404442 return std.os.getenvW(key_w) != null;
405443 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
406444 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
......@@ -411,9 +449,22 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool
411449 }
412450}
413451
414test "os.getEnvVarOwned" {
415 const ga = std.testing.allocator;
416 try testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV"));
452test getEnvVarOwned {
453 try testing.expectError(
454 error.EnvironmentVariableNotFound,
455 getEnvVarOwned(std.testing.allocator, "BADENV"),
456 );
457}
458
459test hasEnvVarConstant {
460 if (builtin.os.tag == .wasi and !builtin.link_libc) return error.SkipZigTest;
461
462 try testing.expect(!hasEnvVarConstant("BADENV"));
463}
464
465test hasEnvVar {
466 const has_env = try hasEnvVar(std.testing.allocator, "BADENV");
467 try testing.expect(!has_env);
417468}
418469
419470pub const ArgIteratorPosix = struct {
......@@ -531,6 +582,7 @@ pub const ArgIteratorWasi = struct {
531582pub const ArgIteratorWindows = struct {
532583 allocator: Allocator,
533584 /// Owned by the iterator.
585 /// Encoded as WTF-8.
534586 cmd_line: []const u8,
535587 index: usize = 0,
536588 /// Owned by the iterator. Long enough to hold the entire `cmd_line` plus a null terminator.
......@@ -538,20 +590,14 @@ pub const ArgIteratorWindows = struct {
538590 start: usize = 0,
539591 end: usize = 0,
540592
541 pub const InitError = error{ OutOfMemory, InvalidCmdLine };
593 pub const InitError = error{OutOfMemory};
542594
543 /// `cmd_line_w` *must* be an UTF16-LE-encoded string.
595 /// `cmd_line_w` *must* be a WTF16-LE-encoded string.
544596 ///
545 /// The iterator makes a copy of `cmd_line_w` converted UTF-8 and keeps it; it does *not* take
597 /// The iterator makes a copy of `cmd_line_w` converted WTF-8 and keeps it; it does *not* take
546598 /// ownership of `cmd_line_w`.
547599 pub fn init(allocator: Allocator, cmd_line_w: [*:0]const u16) InitError!ArgIteratorWindows {
548 const cmd_line = std.unicode.utf16LeToUtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0)) catch |err| switch (err) {
549 error.DanglingSurrogateHalf,
550 error.ExpectedSecondSurrogateHalf,
551 error.UnexpectedSecondSurrogateHalf,
552 => return error.InvalidCmdLine,
553 error.OutOfMemory => return error.OutOfMemory,
554 };
600 const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0));
555601 errdefer allocator.free(cmd_line);
556602
557603 const buffer = try allocator.alloc(u8, cmd_line.len + 1);
......@@ -566,6 +612,7 @@ pub const ArgIteratorWindows = struct {
566612
567613 /// Returns the next argument and advances the iterator. Returns `null` if at the end of the
568614 /// command-line string. The iterator owns the returned slice.
615 /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
569616 pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 {
570617 return self.nextWithStrategy(next_strategy);
571618 }
......@@ -777,7 +824,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
777824 pub const Self = @This();
778825
779826 pub const InitError = error{OutOfMemory};
780 pub const InitUtf16leError = error{ OutOfMemory, InvalidCmdLine };
781827
782828 /// cmd_line_utf8 MUST remain valid and constant while using this instance
783829 pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
......@@ -805,30 +851,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
805851 };
806852 }
807853
808 /// cmd_line_utf16le MUST be encoded UTF16-LE, and is converted to UTF-8 in an internal buffer
809 pub fn initUtf16le(allocator: Allocator, cmd_line_utf16le: [*:0]const u16) InitUtf16leError!Self {
810 const utf16le_slice = mem.sliceTo(cmd_line_utf16le, 0);
811 const cmd_line = std.unicode.utf16LeToUtf8Alloc(allocator, utf16le_slice) catch |err| switch (err) {
812 error.ExpectedSecondSurrogateHalf,
813 error.DanglingSurrogateHalf,
814 error.UnexpectedSecondSurrogateHalf,
815 => return error.InvalidCmdLine,
816
817 error.OutOfMemory => return error.OutOfMemory,
818 };
819 errdefer allocator.free(cmd_line);
820
821 const buffer = try allocator.alloc(u8, cmd_line.len + 1);
822 errdefer allocator.free(buffer);
823
824 return Self{
825 .allocator = allocator,
826 .cmd_line = cmd_line,
827 .free_cmd_line_on_deinit = true,
828 .buffer = buffer,
829 };
830 }
831
832854 // Skips over whitespace in the cmd_line.
833855 // Returns false if the terminating sentinel is reached, true otherwise.
834856 // Also skips over comments (if supported).
......@@ -1021,6 +1043,8 @@ pub const ArgIterator = struct {
10211043
10221044 /// Get the next argument. Returns 'null' if we are at the end.
10231045 /// Returned slice is pointing to the iterator's internal buffer.
1046 /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1047 /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
10241048 pub fn next(self: *ArgIterator) ?([:0]const u8) {
10251049 return self.inner.next();
10261050 }
......@@ -1057,6 +1081,8 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator
10571081}
10581082
10591083/// Caller must call argsFree on result.
1084/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
1085/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
10601086pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
10611087 // TODO refactor to only make 1 allocation.
10621088 var it = try argsWithAllocator(allocator);
......@@ -1201,7 +1227,7 @@ test "ArgIteratorWindows" {
12011227}
12021228
12031229fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {
1204 const cmd_line_w = try std.unicode.utf8ToUtf16LeAllocZ(testing.allocator, cmd_line);
1230 const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);
12051231 defer testing.allocator.free(cmd_line_w);
12061232
12071233 // next
lib/std/unicode.zig+50-28
......@@ -488,7 +488,9 @@ pub const Utf16LeIterator = struct {
488488 };
489489 }
490490
491 pub fn nextCodepoint(it: *Utf16LeIterator) !?u21 {
491 pub const NextCodepointError = error{ DanglingSurrogateHalf, ExpectedSecondSurrogateHalf, UnexpectedSecondSurrogateHalf };
492
493 pub fn nextCodepoint(it: *Utf16LeIterator) NextCodepointError!?u21 {
492494 assert(it.i <= it.bytes.len);
493495 if (it.i == it.bytes.len) return null;
494496 var code_units: [2]u16 = undefined;
......@@ -923,7 +925,14 @@ test "fmtUtf8" {
923925 try expectFmt("����A", "{}", .{fmtUtf8("\xE1\x80\xE2\xF0\x91\x92\xF1\xBFA")});
924926}
925927
926fn utf16LeToUtf8ArrayListImpl(array_list: *std.ArrayList(u8), utf16le: []const u16, comptime surrogates: Surrogates) !void {
928fn utf16LeToUtf8ArrayListImpl(
929 array_list: *std.ArrayList(u8),
930 utf16le: []const u16,
931 comptime surrogates: Surrogates,
932) (switch (surrogates) {
933 .cannot_encode_surrogate_half => Utf16LeToUtf8AllocError,
934 .can_encode_surrogate_half => mem.Allocator.Error,
935})!void {
927936 // optimistically guess that it will all be ascii.
928937 try array_list.ensureTotalCapacityPrecise(utf16le.len);
929938
......@@ -975,7 +984,9 @@ fn utf16LeToUtf8ArrayListImpl(array_list: *std.ArrayList(u8), utf16le: []const u
975984 }
976985}
977986
978pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) !void {
987pub const Utf16LeToUtf8AllocError = mem.Allocator.Error || Utf16LeToUtf8Error;
988
989pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) Utf16LeToUtf8AllocError!void {
979990 return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .cannot_encode_surrogate_half);
980991}
981992
......@@ -983,7 +994,7 @@ pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u
983994pub const utf16leToUtf8Alloc = utf16LeToUtf8Alloc;
984995
985996/// Caller must free returned memory.
986pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8 {
997pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![]u8 {
987998 // optimistically guess that it will all be ascii.
988999 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len);
9891000 errdefer result.deinit();
......@@ -997,7 +1008,7 @@ pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8
9971008pub const utf16leToUtf8AllocZ = utf16LeToUtf8AllocZ;
9981009
9991010/// Caller must free returned memory.
1000pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) ![:0]u8 {
1011pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![:0]u8 {
10011012 // optimistically guess that it will all be ascii (and allocate space for the null terminator)
10021013 var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len + 1);
10031014 errdefer result.deinit();
......@@ -1007,9 +1018,14 @@ pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) ![:0]
10071018 return result.toOwnedSliceSentinel(0);
10081019}
10091020
1021pub const Utf16LeToUtf8Error = Utf16LeIterator.NextCodepointError;
1022
10101023/// Asserts that the output buffer is big enough.
10111024/// Returns end byte index into utf8.
1012fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surrogates) !usize {
1025fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surrogates) (switch (surrogates) {
1026 .cannot_encode_surrogate_half => Utf16LeToUtf8Error,
1027 .can_encode_surrogate_half => error{},
1028})!usize {
10131029 var end_index: usize = 0;
10141030
10151031 var remaining = utf16le;
......@@ -1043,7 +1059,9 @@ fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surr
10431059 // The maximum possible codepoint encoded by UTF-16 is U+10FFFF,
10441060 // which is within the valid codepoint range.
10451061 error.CodepointTooLarge => unreachable,
1046 else => |e| return e,
1062 // We know the codepoint was valid in UTF-16, meaning it is not
1063 // an unpaired surrogate codepoint.
1064 error.Utf8CannotEncodeSurrogateHalf => unreachable,
10471065 };
10481066 }
10491067 },
......@@ -1064,7 +1082,7 @@ fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surr
10641082/// Deprecated; renamed to utf16LeToUtf8
10651083pub const utf16leToUtf8 = utf16LeToUtf8;
10661084
1067pub fn utf16LeToUtf8(utf8: []u8, utf16le: []const u16) !usize {
1085pub fn utf16LeToUtf8(utf8: []u8, utf16le: []const u16) Utf16LeToUtf8Error!usize {
10681086 return utf16LeToUtf8Impl(utf8, utf16le, .cannot_encode_surrogate_half);
10691087}
10701088
......@@ -1176,11 +1194,11 @@ fn utf8ToUtf16LeArrayListImpl(array_list: *std.ArrayList(u16), utf8: []const u8,
11761194 }
11771195}
11781196
1179pub fn utf8ToUtf16LeArrayList(array_list: *std.ArrayList(u16), utf8: []const u8) !void {
1197pub fn utf8ToUtf16LeArrayList(array_list: *std.ArrayList(u16), utf8: []const u8) error{ InvalidUtf8, OutOfMemory }!void {
11801198 return utf8ToUtf16LeArrayListImpl(array_list, utf8, .cannot_encode_surrogate_half);
11811199}
11821200
1183pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) ![]u16 {
1201pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![]u16 {
11841202 // optimistically guess that it will not require surrogate pairs
11851203 var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len);
11861204 errdefer result.deinit();
......@@ -1193,7 +1211,7 @@ pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) ![]u16 {
11931211/// Deprecated; renamed to utf8ToUtf16LeAllocZ
11941212pub const utf8ToUtf16LeWithNull = utf8ToUtf16LeAllocZ;
11951213
1196pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) ![:0]u16 {
1214pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![:0]u16 {
11971215 // optimistically guess that it will not require surrogate pairs
11981216 var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len + 1);
11991217 errdefer result.deinit();
......@@ -1205,7 +1223,7 @@ pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) ![:0]u16
12051223
12061224/// Returns index of next character. If exact fit, returned index equals output slice length.
12071225/// Assumes there is enough space for the output.
1208pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize {
1226pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) error{InvalidUtf8}!usize {
12091227 return utf8ToUtf16LeImpl(utf16le, utf8, .cannot_encode_surrogate_half);
12101228}
12111229
......@@ -1236,11 +1254,14 @@ pub fn utf8ToUtf16LeImpl(utf16le: []u16, utf8: []const u8, comptime surrogates:
12361254
12371255 var src_i: usize = 0;
12381256 while (src_i < remaining.len) {
1239 const n = utf8ByteSequenceLength(remaining[src_i]) catch return error.InvalidUtf8;
1257 const n = utf8ByteSequenceLength(remaining[src_i]) catch return switch (surrogates) {
1258 .cannot_encode_surrogate_half => error.InvalidUtf8,
1259 .can_encode_surrogate_half => error.InvalidWtf8,
1260 };
12401261 const next_src_i = src_i + n;
12411262 const codepoint = switch (surrogates) {
12421263 .cannot_encode_surrogate_half => utf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8,
1243 .can_encode_surrogate_half => wtf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8,
1264 .can_encode_surrogate_half => wtf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidWtf8,
12441265 };
12451266 if (codepoint < 0x10000) {
12461267 const short = @as(u16, @intCast(codepoint));
......@@ -1600,9 +1621,9 @@ fn testValidateWtf8Slice() !void {
16001621pub const Wtf8View = struct {
16011622 bytes: []const u8,
16021623
1603 pub fn init(s: []const u8) !Wtf8View {
1624 pub fn init(s: []const u8) error{InvalidWtf8}!Wtf8View {
16041625 if (!wtf8ValidateSlice(s)) {
1605 return error.InvalidUtf8;
1626 return error.InvalidWtf8;
16061627 }
16071628
16081629 return initUnchecked(s);
......@@ -1614,8 +1635,8 @@ pub const Wtf8View = struct {
16141635
16151636 pub inline fn initComptime(comptime s: []const u8) Wtf8View {
16161637 return comptime if (init(s)) |r| r else |err| switch (err) {
1617 error.InvalidUtf8 => {
1618 @compileError("invalid utf8 detected in wtf8 string");
1638 error.InvalidWtf8 => {
1639 @compileError("invalid wtf8");
16191640 },
16201641 };
16211642 }
......@@ -1665,12 +1686,12 @@ pub const Wtf8Iterator = struct {
16651686 }
16661687};
16671688
1668pub fn wtf16LeToWtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) !void {
1689pub fn wtf16LeToWtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) mem.Allocator.Error!void {
16691690 return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .can_encode_surrogate_half);
16701691}
16711692
16721693/// Caller must free returned memory.
1673pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) ![]u8 {
1694pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![]u8 {
16741695 // optimistically guess that it will all be ascii.
16751696 var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len);
16761697 errdefer result.deinit();
......@@ -1681,7 +1702,7 @@ pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) ![]u8
16811702}
16821703
16831704/// Caller must free returned memory.
1684pub fn wtf16LeToWtf8AllocZ(allocator: mem.Allocator, wtf16le: []const u16) ![:0]u8 {
1705pub fn wtf16LeToWtf8AllocZ(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![:0]u8 {
16851706 // optimistically guess that it will all be ascii (and allocate space for the null terminator)
16861707 var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len + 1);
16871708 errdefer result.deinit();
......@@ -1695,11 +1716,11 @@ pub fn wtf16LeToWtf8(wtf8: []u8, wtf16le: []const u16) usize {
16951716 return utf16LeToUtf8Impl(wtf8, wtf16le, .can_encode_surrogate_half) catch |err| switch (err) {};
16961717}
16971718
1698pub fn wtf8ToWtf16LeArrayList(array_list: *std.ArrayList(u16), wtf8: []const u8) !void {
1719pub fn wtf8ToWtf16LeArrayList(array_list: *std.ArrayList(u16), wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }!void {
16991720 return utf8ToUtf16LeArrayListImpl(array_list, wtf8, .can_encode_surrogate_half);
17001721}
17011722
1702pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u16 {
1723pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u16 {
17031724 // optimistically guess that it will not require surrogate pairs
17041725 var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len);
17051726 errdefer result.deinit();
......@@ -1709,7 +1730,7 @@ pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u16 {
17091730 return result.toOwnedSlice();
17101731}
17111732
1712pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u16 {
1733pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u16 {
17131734 // optimistically guess that it will not require surrogate pairs
17141735 var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len + 1);
17151736 errdefer result.deinit();
......@@ -1721,7 +1742,7 @@ pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u16
17211742
17221743/// Returns index of next character. If exact fit, returned index equals output slice length.
17231744/// Assumes there is enough space for the output.
1724pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) !usize {
1745pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{InvalidWtf8}!usize {
17251746 return utf8ToUtf16LeImpl(wtf16le, wtf8, .can_encode_surrogate_half);
17261747}
17271748
......@@ -1732,7 +1753,8 @@ pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) !usize {
17321753/// In-place conversion is supported when `utf8` and `wtf8` refer to the same slice.
17331754/// Note: If `wtf8` is entirely composed of well-formed UTF-8, then no conversion is necessary.
17341755/// `utf8ValidateSlice` can be used to check if lossy conversion is worthwhile.
1735pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) !void {
1756/// If `wtf8` is not valid WTF-8, then `error.InvalidWtf8` is returned.
1757pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) error{InvalidWtf8}!void {
17361758 assert(utf8.len >= wtf8.len);
17371759
17381760 const in_place = utf8.ptr == wtf8.ptr;
......@@ -1762,7 +1784,7 @@ pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) !void {
17621784 }
17631785}
17641786
1765pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u8 {
1787pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u8 {
17661788 const utf8 = try allocator.alloc(u8, wtf8.len);
17671789 errdefer allocator.free(utf8);
17681790
......@@ -1771,7 +1793,7 @@ pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u8 {
17711793 return utf8;
17721794}
17731795
1774pub fn wtf8ToUtf8LossyAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u8 {
1796pub fn wtf8ToUtf8LossyAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u8 {
17751797 const utf8 = try allocator.allocSentinel(u8, wtf8.len, 0);
17761798 errdefer allocator.free(utf8);
17771799
lib/std/zig/system.zig+8-4
......@@ -639,7 +639,8 @@ pub fn abiAndDynamicLinkerFromFile(
639639 var link_buf: [std.os.PATH_MAX]u8 = undefined;
640640 const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {
641641 error.NameTooLong => unreachable,
642 error.InvalidUtf8 => unreachable, // Windows only
642 error.InvalidUtf8 => unreachable, // WASI only
643 error.InvalidWtf8 => unreachable, // Windows only
643644 error.BadPathName => unreachable, // Windows only
644645 error.UnsupportedReparsePointType => unreachable, // Windows only
645646 error.NetworkNotFound => unreachable, // Windows only
......@@ -730,7 +731,8 @@ test glibcVerFromLinkName {
730731fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
731732 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
732733 error.NameTooLong => unreachable,
733 error.InvalidUtf8 => unreachable,
734 error.InvalidUtf8 => unreachable, // WASI only
735 error.InvalidWtf8 => unreachable, // Windows-only
734736 error.BadPathName => unreachable,
735737 error.DeviceBusy => unreachable,
736738 error.NetworkNotFound => unreachable, // Windows-only
......@@ -761,7 +763,8 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
761763 const glibc_so_basename = "libc.so.6";
762764 var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
763765 error.NameTooLong => unreachable,
764 error.InvalidUtf8 => unreachable, // Windows only
766 error.InvalidUtf8 => unreachable, // WASI only
767 error.InvalidWtf8 => unreachable, // Windows only
765768 error.BadPathName => unreachable, // Windows only
766769 error.PipeBusy => unreachable, // Windows-only
767770 error.SharingViolation => unreachable, // Windows-only
......@@ -998,7 +1001,8 @@ fn detectAbiAndDynamicLinker(
9981001 error.NameTooLong => unreachable,
9991002 error.PathAlreadyExists => unreachable,
10001003 error.SharingViolation => unreachable,
1001 error.InvalidUtf8 => unreachable,
1004 error.InvalidUtf8 => unreachable, // WASI only
1005 error.InvalidWtf8 => unreachable, // Windows only
10021006 error.BadPathName => unreachable,
10031007 error.PipeBusy => unreachable,
10041008 error.FileLocksNotSupported => unreachable,
lib/std/zig/system/NativePaths.zig+2-2
......@@ -41,7 +41,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
4141 }
4242 }
4343 } else |err| switch (err) {
44 error.InvalidUtf8 => {},
44 error.InvalidWtf8 => unreachable,
4545 error.EnvironmentVariableNotFound => {},
4646 error.OutOfMemory => |e| return e,
4747 }
......@@ -73,7 +73,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
7373 }
7474 }
7575 } else |err| switch (err) {
76 error.InvalidUtf8 => {},
76 error.InvalidWtf8 => unreachable,
7777 error.EnvironmentVariableNotFound => {},
7878 error.OutOfMemory => |e| return e,
7979 }
src/Module.zig+1
......@@ -2662,6 +2662,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
26622662 }) catch |err| switch (err) {
26632663 error.NotDir => unreachable, // no dir components
26642664 error.InvalidUtf8 => unreachable, // it's a hex encoded name
2665 error.InvalidWtf8 => unreachable, // it's a hex encoded name
26652666 error.BadPathName => unreachable, // it's a hex encoded name
26662667 error.NameTooLong => unreachable, // it's a fixed size name
26672668 error.PipeBusy => unreachable, // it's not a pipe
src/libc_installation.zig+8-2
......@@ -246,7 +246,10 @@ pub const LibCInstallation = struct {
246246 const allocator = args.allocator;
247247
248248 // Detect infinite loops.
249 var env_map = try std.process.getEnvMap(allocator);
249 var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
250 error.Unexpected => unreachable, // WASI-only
251 else => |e| return e,
252 };
250253 defer env_map.deinit();
251254 const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
252255 if (std.mem.eql(u8, phase, "1")) {
......@@ -572,7 +575,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
572575 const allocator = args.allocator;
573576
574577 // Detect infinite loops.
575 var env_map = try std.process.getEnvMap(allocator);
578 var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
579 error.Unexpected => unreachable, // WASI-only
580 else => |e| return e,
581 };
576582 defer env_map.deinit();
577583 const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
578584 if (std.mem.eql(u8, phase, "1")) {
src/windows_sdk.zig+87-90
......@@ -84,26 +84,26 @@ fn iterateAndFilterBySemVer(
8484 return dirs_filtered_slice;
8585}
8686
87const RegistryUtf8 = struct {
87const RegistryWtf8 = struct {
8888 key: windows.HKEY,
8989
90 /// Assert that `key` is valid UTF-8 string
91 pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryUtf8 {
92 const key_utf16le: [:0]const u16 = key_utf16le: {
93 var key_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
94 const key_utf16le_len: usize = std.unicode.utf8ToUtf16Le(key_utf16le_buf[0..], key) catch |err| switch (err) {
95 error.InvalidUtf8 => unreachable,
90 /// Assert that `key` is valid WTF-8 string
91 pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryWtf8 {
92 const key_wtf16le: [:0]const u16 = key_wtf16le: {
93 var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
94 const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) {
95 error.InvalidWtf8 => unreachable,
9696 };
97 key_utf16le_buf[key_utf16le_len] = 0;
98 break :key_utf16le key_utf16le_buf[0..key_utf16le_len :0];
97 key_wtf16le_buf[key_wtf16le_len] = 0;
98 break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0];
9999 };
100100
101 const registry_utf16le = try RegistryUtf16Le.openKey(hkey, key_utf16le);
102 return RegistryUtf8{ .key = registry_utf16le.key };
101 const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le);
102 return RegistryWtf8{ .key = registry_wtf16le.key };
103103 }
104104
105105 /// Closes key, after that usage is invalid
106 pub fn closeKey(self: *const RegistryUtf8) void {
106 pub fn closeKey(self: *const RegistryWtf8) void {
107107 const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
108108 const return_code: windows.Win32Error = @enumFromInt(return_code_int);
109109 switch (return_code) {
......@@ -114,71 +114,68 @@ const RegistryUtf8 = struct {
114114
115115 /// Get string from registry.
116116 /// Caller owns result.
117 pub fn getString(self: *const RegistryUtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
118 const subkey_utf16le: [:0]const u16 = subkey_utf16le: {
119 var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
120 const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;
121 subkey_utf16le_buf[subkey_utf16le_len] = 0;
122 break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];
117 pub fn getString(self: *const RegistryWtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
118 const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
119 var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
120 const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
121 subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
122 break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
123123 };
124124
125 const value_name_utf16le: [:0]const u16 = value_name_utf16le: {
126 var value_name_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
127 const value_name_utf16le_len: usize = std.unicode.utf8ToUtf16Le(value_name_utf16le_buf[0..], value_name) catch unreachable;
128 value_name_utf16le_buf[value_name_utf16le_len] = 0;
129 break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];
125 const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
126 var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
127 const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
128 value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
129 break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
130130 };
131131
132 const registry_utf16le = RegistryUtf16Le{ .key = self.key };
133 const value_utf16le = try registry_utf16le.getString(allocator, subkey_utf16le, value_name_utf16le);
134 defer allocator.free(value_utf16le);
132 const registry_wtf16le = RegistryWtf16Le{ .key = self.key };
133 const value_wtf16le = try registry_wtf16le.getString(allocator, subkey_wtf16le, value_name_wtf16le);
134 defer allocator.free(value_wtf16le);
135135
136 const value_utf8: []u8 = std.unicode.utf16LeToUtf8Alloc(allocator, value_utf16le) catch |err| switch (err) {
137 error.OutOfMemory => return error.OutOfMemory,
138 else => return error.StringNotFound,
139 };
140 errdefer allocator.free(value_utf8);
136 const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_wtf16le);
137 errdefer allocator.free(value_wtf8);
141138
142 return value_utf8;
139 return value_wtf8;
143140 }
144141
145142 /// Get DWORD (u32) from registry.
146 pub fn getDword(self: *const RegistryUtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
147 const subkey_utf16le: [:0]const u16 = subkey_utf16le: {
148 var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
149 const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;
150 subkey_utf16le_buf[subkey_utf16le_len] = 0;
151 break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];
143 pub fn getDword(self: *const RegistryWtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
144 const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
145 var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
146 const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
147 subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
148 break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
152149 };
153150
154 const value_name_utf16le: [:0]const u16 = value_name_utf16le: {
155 var value_name_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
156 const value_name_utf16le_len: usize = std.unicode.utf8ToUtf16Le(value_name_utf16le_buf[0..], value_name) catch unreachable;
157 value_name_utf16le_buf[value_name_utf16le_len] = 0;
158 break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];
151 const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
152 var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
153 const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
154 value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
155 break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
159156 };
160157
161 const registry_utf16le = RegistryUtf16Le{ .key = self.key };
162 return try registry_utf16le.getDword(subkey_utf16le, value_name_utf16le);
158 const registry_wtf16le = RegistryWtf16Le{ .key = self.key };
159 return try registry_wtf16le.getDword(subkey_wtf16le, value_name_wtf16le);
163160 }
164161
165162 /// Under private space with flags:
166163 /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
167164 /// After finishing work, call `closeKey`.
168 pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryUtf8 {
169 const absolute_path_utf16le: [:0]const u16 = absolute_path_utf16le: {
170 var absolute_path_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
171 const absolute_path_utf16le_len: usize = std.unicode.utf8ToUtf16Le(absolute_path_utf16le_buf[0..], absolute_path) catch unreachable;
172 absolute_path_utf16le_buf[absolute_path_utf16le_len] = 0;
173 break :absolute_path_utf16le absolute_path_utf16le_buf[0..absolute_path_utf16le_len :0];
165 pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryWtf8 {
166 const absolute_path_wtf16le: [:0]const u16 = absolute_path_wtf16le: {
167 var absolute_path_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
168 const absolute_path_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(absolute_path_wtf16le_buf[0..], absolute_path) catch unreachable;
169 absolute_path_wtf16le_buf[absolute_path_wtf16le_len] = 0;
170 break :absolute_path_wtf16le absolute_path_wtf16le_buf[0..absolute_path_wtf16le_len :0];
174171 };
175172
176 const registry_utf16le = try RegistryUtf16Le.loadFromPath(absolute_path_utf16le);
177 return RegistryUtf8{ .key = registry_utf16le.key };
173 const registry_wtf16le = try RegistryWtf16Le.loadFromPath(absolute_path_wtf16le);
174 return RegistryWtf8{ .key = registry_wtf16le.key };
178175 }
179176};
180177
181const RegistryUtf16Le = struct {
178const RegistryWtf16Le = struct {
182179 key: windows.HKEY,
183180
184181 /// Includes root key (f.e. HKEY_LOCAL_MACHINE).
......@@ -191,11 +188,11 @@ const RegistryUtf16Le = struct {
191188 /// Under HKEY_LOCAL_MACHINE with flags:
192189 /// KEY_QUERY_VALUE, KEY_WOW64_32KEY, and KEY_ENUMERATE_SUB_KEYS.
193190 /// After finishing work, call `closeKey`.
194 fn openKey(hkey: windows.HKEY, key_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le {
191 fn openKey(hkey: windows.HKEY, key_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le {
195192 var key: windows.HKEY = undefined;
196193 const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW(
197194 hkey,
198 key_utf16le,
195 key_wtf16le,
199196 0,
200197 windows.KEY_QUERY_VALUE | windows.KEY_WOW64_32KEY | windows.KEY_ENUMERATE_SUB_KEYS,
201198 &key,
......@@ -207,11 +204,11 @@ const RegistryUtf16Le = struct {
207204
208205 else => return error.KeyNotFound,
209206 }
210 return RegistryUtf16Le{ .key = key };
207 return RegistryWtf16Le{ .key = key };
211208 }
212209
213210 /// Closes key, after that usage is invalid
214 fn closeKey(self: *const RegistryUtf16Le) void {
211 fn closeKey(self: *const RegistryWtf16Le) void {
215212 const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
216213 const return_code: windows.Win32Error = @enumFromInt(return_code_int);
217214 switch (return_code) {
......@@ -221,25 +218,25 @@ const RegistryUtf16Le = struct {
221218 }
222219
223220 /// Get string ([:0]const u16) from registry.
224 fn getString(self: *const RegistryUtf16Le, allocator: std.mem.Allocator, subkey_utf16le: [:0]const u16, value_name_utf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 {
221 fn getString(self: *const RegistryWtf16Le, allocator: std.mem.Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 {
225222 var actual_type: windows.ULONG = undefined;
226223
227224 // Calculating length to allocate
228 var value_utf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters.
225 var value_wtf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters.
229226 var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
230227 self.key,
231 subkey_utf16le,
232 value_name_utf16le,
228 subkey_wtf16le,
229 value_name_wtf16le,
233230 RRF.RT_REG_SZ,
234231 &actual_type,
235232 null,
236 &value_utf16le_buf_size,
233 &value_wtf16le_buf_size,
237234 );
238235
239236 // Check returned code and type
240237 var return_code: windows.Win32Error = @enumFromInt(return_code_int);
241238 switch (return_code) {
242 .SUCCESS => std.debug.assert(value_utf16le_buf_size != 0),
239 .SUCCESS => std.debug.assert(value_wtf16le_buf_size != 0),
243240 .MORE_DATA => unreachable, // We are only reading length
244241 .FILE_NOT_FOUND => return error.ValueNameNotFound,
245242 .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
......@@ -250,17 +247,17 @@ const RegistryUtf16Le = struct {
250247 else => return error.NotAString,
251248 }
252249
253 const value_utf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_utf16le_buf_size, 2) catch unreachable);
254 errdefer allocator.free(value_utf16le_buf);
250 const value_wtf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable);
251 errdefer allocator.free(value_wtf16le_buf);
255252
256253 return_code_int = windows.advapi32.RegGetValueW(
257254 self.key,
258 subkey_utf16le,
259 value_name_utf16le,
255 subkey_wtf16le,
256 value_name_wtf16le,
260257 RRF.RT_REG_SZ,
261258 &actual_type,
262 value_utf16le_buf.ptr,
263 &value_utf16le_buf_size,
259 value_wtf16le_buf.ptr,
260 &value_wtf16le_buf_size,
264261 );
265262
266263 // Check returned code and (just in case) type again.
......@@ -277,28 +274,28 @@ const RegistryUtf16Le = struct {
277274 else => return error.NotAString,
278275 }
279276
280 const value_utf16le: []const u16 = value_utf16le: {
277 const value_wtf16le: []const u16 = value_wtf16le: {
281278 // note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space
282279 // we will just search for zero termination and forget length
283280 // Windows sure is strange
284 const value_utf16le_overestimated: [*:0]const u16 = @ptrCast(value_utf16le_buf.ptr);
285 break :value_utf16le std.mem.span(value_utf16le_overestimated);
281 const value_wtf16le_overestimated: [*:0]const u16 = @ptrCast(value_wtf16le_buf.ptr);
282 break :value_wtf16le std.mem.span(value_wtf16le_overestimated);
286283 };
287284
288 _ = allocator.resize(value_utf16le_buf, value_utf16le.len);
289 return value_utf16le;
285 _ = allocator.resize(value_wtf16le_buf, value_wtf16le.len);
286 return value_wtf16le;
290287 }
291288
292289 /// Get DWORD (u32) from registry.
293 fn getDword(self: *const RegistryUtf16Le, subkey_utf16le: [:0]const u16, value_name_utf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
290 fn getDword(self: *const RegistryWtf16Le, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
294291 var actual_type: windows.ULONG = undefined;
295292 var reg_size: u32 = @sizeOf(u32);
296293 var reg_value: u32 = 0;
297294
298295 const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
299296 self.key,
300 subkey_utf16le,
301 value_name_utf16le,
297 subkey_wtf16le,
298 value_name_wtf16le,
302299 RRF.RT_REG_DWORD,
303300 &actual_type,
304301 &reg_value,
......@@ -324,11 +321,11 @@ const RegistryUtf16Le = struct {
324321 /// Under private space with flags:
325322 /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
326323 /// After finishing work, call `closeKey`.
327 fn loadFromPath(absolute_path_as_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le {
324 fn loadFromPath(absolute_path_as_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le {
328325 var key: windows.HKEY = undefined;
329326
330327 const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW(
331 absolute_path_as_utf16le,
328 absolute_path_as_wtf16le,
332329 &key,
333330 windows.KEY_QUERY_VALUE | windows.KEY_ENUMERATE_SUB_KEYS,
334331 0,
......@@ -340,7 +337,7 @@ const RegistryUtf16Le = struct {
340337 else => return error.KeyNotFound,
341338 }
342339
343 return RegistryUtf16Le{ .key = key };
340 return RegistryWtf16Le{ .key = key };
344341 }
345342};
346343
......@@ -352,7 +349,7 @@ pub const Windows10Sdk = struct {
352349 /// Caller owns the result's fields.
353350 /// After finishing work, call `free(allocator)`.
354351 fn find(allocator: std.mem.Allocator) error{ OutOfMemory, Windows10SdkNotFound, PathTooLong, VersionTooLong }!Windows10Sdk {
355 const v10_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) {
352 const v10_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) {
356353 error.KeyNotFound => return error.Windows10SdkNotFound,
357354 };
358355 defer v10_key.closeKey();
......@@ -413,11 +410,11 @@ pub const Windows10Sdk = struct {
413410 /// Check whether this version is enumerated in registry.
414411 fn isValidVersion(windows10sdk: *const Windows10Sdk) bool {
415412 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
416 const reg_query_as_utf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) {
413 const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) {
417414 error.NoSpaceLeft => return false,
418415 };
419416
420 const options_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_utf8) catch |err| switch (err) {
417 const options_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_wtf8) catch |err| switch (err) {
421418 error.KeyNotFound => return false,
422419 };
423420 defer options_key.closeKey();
......@@ -447,7 +444,7 @@ pub const Windows81Sdk = struct {
447444 /// Find path and version of Windows 8.1 SDK.
448445 /// Caller owns the result's fields.
449446 /// After finishing work, call `free(allocator)`.
450 fn find(allocator: std.mem.Allocator, roots_key: *const RegistryUtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk {
447 fn find(allocator: std.mem.Allocator, roots_key: *const RegistryWtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk {
451448 const path: []const u8 = path81: {
452449 const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", "KitsRoot81") catch |err| switch (err) {
453450 error.NotAString => return error.Windows81SdkNotFound,
......@@ -523,7 +520,7 @@ pub const ZigWindowsSDK = struct {
523520 if (builtin.os.tag != .windows) return error.NotFound;
524521
525522 //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed
526 const roots_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) {
523 const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) {
527524 error.KeyNotFound => return error.NotFound,
528525 };
529526 defer roots_key.closeKey();
......@@ -583,7 +580,7 @@ pub const ZigWindowsSDK = struct {
583580const MsvcLibDir = struct {
584581 fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir {
585582 const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}";
586 const setup_config_key = RegistryUtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) {
583 const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) {
587584 error.KeyNotFound => return error.PathNotFound,
588585 };
589586 defer setup_config_key.closeKey();
......@@ -805,13 +802,13 @@ const MsvcLibDir = struct {
805802 for (vs_versions) |vs_version| allocator.free(vs_version);
806803 allocator.free(vs_versions);
807804 }
808 var config_subkey_buf: [RegistryUtf16Le.key_name_max_len * 2]u8 = undefined;
805 var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined;
809806 const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {
810807 const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;
811808 defer allocator.free(privateregistry_absolute_path);
812809 if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue;
813810
814 const visualstudio_registry = RegistryUtf8.loadFromPath(privateregistry_absolute_path) catch continue;
811 const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue;
815812 defer visualstudio_registry.closeKey();
816813
817814 const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable;
......@@ -894,7 +891,7 @@ const MsvcLibDir = struct {
894891 }
895892 }
896893
897 const vs7_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound;
894 const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound;
898895 defer vs7_key.closeKey();
899896 try_vs7_key: {
900897 const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) {