| author | |
| committer | |
| log | 6c2eb0f131588be111652a755a4492ff72d16440 |
| tree | 0d317950da0694df32c4eb088278662f159e8736 |
| parent | 63ea3e172e2788856cfb69b2f6085930a1c69d5b |
| parent | 9fec608b3bbe3c00528e01bd09aa29f9b9f97415 |
| signature |
Fix handling of Windows (WTF-16) and WASI (UTF-8) paths, etc23 files changed, 1887 insertions(+), 472 deletions(-)
deps/aro/aro/Compilation.zig+1-1| ... | ... | @@ -69,7 +69,7 @@ pub const Environment = struct { |
| 69 | 69 | const val: ?[]const u8 = std.process.getEnvVarOwned(allocator, env_var_name) catch |err| switch (err) { |
| 70 | 70 | error.OutOfMemory => |e| return e, |
| 71 | 71 | error.EnvironmentVariableNotFound => null, |
| 72 | error.InvalidUtf8 => null, | |
| 72 | error.InvalidWtf8 => null, | |
| 73 | 73 | }; |
| 74 | 74 | @field(env, field.name) = val; |
| 75 | 75 | } |
deps/aro/aro/Driver.zig+2-1| ... | ... | @@ -523,7 +523,8 @@ pub fn errorDescription(e: anyerror) []const u8 { |
| 523 | 523 | error.NotDir => "is not a directory", |
| 524 | 524 | error.NotOpenForReading => "file is not open for reading", |
| 525 | 525 | error.NotOpenForWriting => "file is not open for writing", |
| 526 | error.InvalidUtf8 => "input is not valid UTF-8", | |
| 526 | error.InvalidUtf8 => "path is not valid UTF-8", | |
| 527 | error.InvalidWtf8 => "path is not valid WTF-8", | |
| 527 | 528 | error.FileBusy => "file is busy", |
| 528 | 529 | error.NameTooLong => "file name is too long", |
| 529 | 530 | error.AccessDenied => "access denied", |
lib/std/Build/Cache.zig+1-1| ... | ... | @@ -162,7 +162,7 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath { |
| 162 | 162 | fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 { |
| 163 | 163 | const relative = try std.fs.path.relative(allocator, prefix, path); |
| 164 | 164 | errdefer allocator.free(relative); |
| 165 | var component_iterator = std.fs.path.NativeUtf8ComponentIterator.init(relative) catch { | |
| 165 | var component_iterator = std.fs.path.NativeComponentIterator.init(relative) catch { | |
| 166 | 166 | return error.NotASubPath; |
| 167 | 167 | }; |
| 168 | 168 | if (component_iterator.root() != null) { |
lib/std/Thread.zig+4-9| ... | ... | @@ -91,7 +91,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { |
| 91 | 91 | }, |
| 92 | 92 | .windows => { |
| 93 | 93 | var buf: [max_name_len]u16 = undefined; |
| 94 | const len = try std.unicode.utf8ToUtf16Le(&buf, name); | |
| 94 | const len = try std.unicode.wtf8ToWtf16Le(&buf, name); | |
| 95 | 95 | const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong; |
| 96 | 96 | |
| 97 | 97 | // Note: NT allocates its own copy, no use-after-free here. |
| ... | ... | @@ -157,17 +157,12 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { |
| 157 | 157 | } |
| 158 | 158 | |
| 159 | 159 | pub const GetNameError = error{ |
| 160 | // For Windows, the name is converted from UTF16 to UTF8 | |
| 161 | CodepointTooLarge, | |
| 162 | Utf8CannotEncodeSurrogateHalf, | |
| 163 | DanglingSurrogateHalf, | |
| 164 | ExpectedSecondSurrogateHalf, | |
| 165 | UnexpectedSecondSurrogateHalf, | |
| 166 | ||
| 167 | 160 | Unsupported, |
| 168 | 161 | Unexpected, |
| 169 | 162 | } || os.PrctlError || os.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError; |
| 170 | 163 | |
| 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. | |
| 171 | 166 | pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 { |
| 172 | 167 | buffer_ptr[max_name_len] = 0; |
| 173 | 168 | var buffer: [:0]u8 = buffer_ptr; |
| ... | ... | @@ -213,7 +208,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co |
| 213 | 208 | )) { |
| 214 | 209 | .SUCCESS => { |
| 215 | 210 | const string = @as(*const os.windows.UNICODE_STRING, @ptrCast(&buf)); |
| 216 | const len = try std.unicode.utf16leToUtf8(buffer, string.Buffer[0 .. string.Length / 2]); | |
| 211 | const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer[0 .. string.Length / 2]); | |
| 217 | 212 | return if (len > 0) buffer[0..len] else null; |
| 218 | 213 | }, |
| 219 | 214 | .NOT_IMPLEMENTED => return error.Unsupported, |
lib/std/child_process.zig+21-22| ... | ... | @@ -129,10 +129,9 @@ pub const ChildProcess = struct { |
| 129 | 129 | /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV. |
| 130 | 130 | NoDevice, |
| 131 | 131 | |
| 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, | |
| 136 | 135 | |
| 137 | 136 | /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process. |
| 138 | 137 | CurrentWorkingDirectoryUnlinked, |
| ... | ... | @@ -767,7 +766,7 @@ pub const ChildProcess = struct { |
| 767 | 766 | }; |
| 768 | 767 | var piProcInfo: windows.PROCESS_INFORMATION = undefined; |
| 769 | 768 | |
| 770 | const cwd_w = if (self.cwd) |cwd| try unicode.utf8ToUtf16LeWithNull(self.allocator, cwd) else null; | |
| 769 | const cwd_w = if (self.cwd) |cwd| try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd) else null; | |
| 771 | 770 | defer if (cwd_w) |cwd| self.allocator.free(cwd); |
| 772 | 771 | const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; |
| 773 | 772 | |
| ... | ... | @@ -775,8 +774,8 @@ pub const ChildProcess = struct { |
| 775 | 774 | defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf); |
| 776 | 775 | const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null; |
| 777 | 776 | |
| 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); | |
| 780 | 779 | |
| 781 | 780 | // the cwd set in ChildProcess is in effect when choosing the executable path |
| 782 | 781 | // to match posix semantics |
| ... | ... | @@ -785,11 +784,11 @@ pub const ChildProcess = struct { |
| 785 | 784 | // If the app name is absolute, then we need to use its dirname as the cwd |
| 786 | 785 | if (app_name_is_absolute) { |
| 787 | 786 | cwd_path_w_needs_free = true; |
| 788 | const dir = fs.path.dirname(app_name_utf8).?; | |
| 789 | break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, dir); | |
| 787 | const dir = fs.path.dirname(app_name_wtf8).?; | |
| 788 | break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, dir); | |
| 790 | 789 | } else if (self.cwd) |cwd| { |
| 791 | 790 | cwd_path_w_needs_free = true; |
| 792 | break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, cwd); | |
| 791 | break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd); | |
| 793 | 792 | } else { |
| 794 | 793 | break :x &[_:0]u16{}; // empty for cwd |
| 795 | 794 | } |
| ... | ... | @@ -800,19 +799,19 @@ pub const ChildProcess = struct { |
| 800 | 799 | // into the basename and dirname and use the dirname as an addition to the cwd |
| 801 | 800 | // path. This is because NtQueryDirectoryFile cannot accept FileName params with |
| 802 | 801 | // path separators. |
| 803 | const app_basename_utf8 = fs.path.basename(app_name_utf8); | |
| 802 | const app_basename_wtf8 = fs.path.basename(app_name_wtf8); | |
| 804 | 803 | // If the app name is absolute, then the cwd will already have the app's dirname in it, |
| 805 | 804 | // so only populate app_dirname if app name is a relative path with > 0 path separators. |
| 806 | const maybe_app_dirname_utf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_utf8) else null; | |
| 805 | const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_wtf8) else null; | |
| 807 | 806 | const app_dirname_w: ?[:0]u16 = x: { |
| 808 | if (maybe_app_dirname_utf8) |app_dirname_utf8| { | |
| 809 | break :x try unicode.utf8ToUtf16LeWithNull(self.allocator, app_dirname_utf8); | |
| 807 | if (maybe_app_dirname_wtf8) |app_dirname_wtf8| { | |
| 808 | break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_dirname_wtf8); | |
| 810 | 809 | } |
| 811 | 810 | break :x null; |
| 812 | 811 | }; |
| 813 | 812 | defer if (app_dirname_w != null) self.allocator.free(app_dirname_w.?); |
| 814 | 813 | |
| 815 | const app_name_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, app_basename_utf8); | |
| 814 | const app_name_w = try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_basename_wtf8); | |
| 816 | 815 | defer self.allocator.free(app_name_w); |
| 817 | 816 | |
| 818 | 817 | const cmd_line_w = argvToCommandLineWindows(self.allocator, self.argv) catch |err| switch (err) { |
| ... | ... | @@ -1173,7 +1172,7 @@ const CreateProcessSupportedExtension = enum { |
| 1173 | 1172 | exe, |
| 1174 | 1173 | }; |
| 1175 | 1174 | |
| 1176 | /// Case-insensitive UTF-16 lookup | |
| 1175 | /// Case-insensitive WTF-16 lookup | |
| 1177 | 1176 | fn windowsCreateProcessSupportsExtension(ext: []const u16) ?CreateProcessSupportedExtension { |
| 1178 | 1177 | if (ext.len != 4) return null; |
| 1179 | 1178 | const State = enum { |
| ... | ... | @@ -1237,7 +1236,7 @@ test "windowsCreateProcessSupportsExtension" { |
| 1237 | 1236 | try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null); |
| 1238 | 1237 | } |
| 1239 | 1238 | |
| 1240 | pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidUtf8, InvalidArg0 }; | |
| 1239 | pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 }; | |
| 1241 | 1240 | |
| 1242 | 1241 | /// Serializes `argv` to a Windows command-line string suitable for passing to a child process and |
| 1243 | 1242 | /// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice. |
| ... | ... | @@ -1320,7 +1319,7 @@ pub fn argvToCommandLineWindows( |
| 1320 | 1319 | } |
| 1321 | 1320 | } |
| 1322 | 1321 | |
| 1323 | return try unicode.utf8ToUtf16LeWithNull(allocator, buf.items); | |
| 1322 | return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items); | |
| 1324 | 1323 | } |
| 1325 | 1324 | |
| 1326 | 1325 | test "argvToCommandLineWindows" { |
| ... | ... | @@ -1386,7 +1385,7 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c |
| 1386 | 1385 | const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv); |
| 1387 | 1386 | defer std.testing.allocator.free(cmd_line_w); |
| 1388 | 1387 | |
| 1389 | const cmd_line = try unicode.utf16leToUtf8Alloc(std.testing.allocator, cmd_line_w); | |
| 1388 | const cmd_line = try unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w); | |
| 1390 | 1389 | defer std.testing.allocator.free(cmd_line); |
| 1391 | 1390 | |
| 1392 | 1391 | try std.testing.expectEqualStrings(expected_cmd_line, cmd_line); |
| ... | ... | @@ -1424,7 +1423,7 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons |
| 1424 | 1423 | "\\\\.\\pipe\\zig-childprocess-{d}-{d}", |
| 1425 | 1424 | .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .Monotonic) }, |
| 1426 | 1425 | ) catch unreachable; |
| 1427 | const len = std.unicode.utf8ToUtf16Le(&tmp_bufw, pipe_path) catch unreachable; | |
| 1426 | const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable; | |
| 1428 | 1427 | tmp_bufw[len] = 0; |
| 1429 | 1428 | break :blk tmp_bufw[0..len :0]; |
| 1430 | 1429 | }; |
| ... | ... | @@ -1521,10 +1520,10 @@ pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ! |
| 1521 | 1520 | var it = env_map.iterator(); |
| 1522 | 1521 | var i: usize = 0; |
| 1523 | 1522 | while (it.next()) |pair| { |
| 1524 | i += try unicode.utf8ToUtf16Le(result[i..], pair.key_ptr.*); | |
| 1523 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); | |
| 1525 | 1524 | result[i] = '='; |
| 1526 | 1525 | i += 1; |
| 1527 | i += try unicode.utf8ToUtf16Le(result[i..], pair.value_ptr.*); | |
| 1526 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); | |
| 1528 | 1527 | result[i] = 0; |
| 1529 | 1528 | i += 1; |
| 1530 | 1529 | } |
lib/std/fs.zig+139-38| ... | ... | @@ -31,18 +31,21 @@ pub const realpathW = os.realpathW; |
| 31 | 31 | pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; |
| 32 | 32 | pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; |
| 33 | 33 | |
| 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 | |
| 35 | 35 | /// operating system will accept. Paths, including those returned from file |
| 36 | 36 | /// system operations, may be longer than this length, but such paths cannot |
| 37 | 37 | /// be successfully passed back in other file system operations. However, |
| 38 | 38 | /// all path components returned by file system operations are assumed to |
| 39 | /// fit into a UTF-8 encoded array of this length. | |
| 39 | /// fit into a `u8` array of this length. | |
| 40 | 40 | /// The byte count includes room for a null sentinel byte. |
| 41 | /// On Windows, `[]u8` file paths are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 42 | /// On WASI, `[]u8` file paths are encoded as valid UTF-8. | |
| 43 | /// On other platforms, `[]u8` file paths are opaque sequences of bytes with no particular encoding. | |
| 41 | 44 | pub const MAX_PATH_BYTES = switch (builtin.os.tag) { |
| 42 | 45 | .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten => os.PATH_MAX, |
| 43 | // Each UTF-16LE character may be expanded to 3 UTF-8 bytes. | |
| 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. | |
| 46 | 49 | // +1 for the null byte at the end, which can be encoded in 1 byte. |
| 47 | 50 | .windows => os.windows.PATH_MAX_WIDE * 3 + 1, |
| 48 | 51 | // TODO work out what a reasonable value we should use here |
| ... | ... | @@ -53,18 +56,21 @@ pub const MAX_PATH_BYTES = switch (builtin.os.tag) { |
| 53 | 56 | @compileError("PATH_MAX not implemented for " ++ @tagName(builtin.os.tag)), |
| 54 | 57 | }; |
| 55 | 58 | |
| 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 | |
| 57 | 60 | /// 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 | |
| 59 | 62 | /// (depending on the platform) this assumption may not hold for every configuration. |
| 60 | 63 | /// The byte count does not include a null sentinel byte. |
| 64 | /// On Windows, `[]u8` file name components are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 65 | /// On WASI, file name components are encoded as valid UTF-8. | |
| 66 | /// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding. | |
| 61 | 67 | pub const MAX_NAME_BYTES = switch (builtin.os.tag) { |
| 62 | 68 | .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos => os.NAME_MAX, |
| 63 | 69 | // Haiku's NAME_MAX includes the null terminator, so subtract one. |
| 64 | 70 | .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. | |
| 68 | 74 | .windows => os.windows.NAME_MAX * 3, |
| 69 | 75 | // For WASI, the MAX_NAME will depend on the host OS, so it needs to be |
| 70 | 76 | // as large as the largest MAX_NAME_BYTES (Windows) in order to work on any host OS. |
| ... | ... | @@ -86,6 +92,9 @@ pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null); |
| 86 | 92 | |
| 87 | 93 | /// TODO remove the allocator requirement from this API |
| 88 | 94 | /// 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. | |
| 89 | 98 | pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void { |
| 90 | 99 | if (cwd().symLink(existing_path, new_path, .{})) { |
| 91 | 100 | return; |
| ... | ... | @@ -117,6 +126,9 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: |
| 117 | 126 | /// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path` |
| 118 | 127 | /// are absolute. See `Dir.updateFile` for a function that operates on both |
| 119 | 128 | /// absolute and relative paths. |
| 129 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 130 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 131 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 120 | 132 | pub fn updateFileAbsolute( |
| 121 | 133 | source_path: []const u8, |
| 122 | 134 | dest_path: []const u8, |
| ... | ... | @@ -131,6 +143,9 @@ pub fn updateFileAbsolute( |
| 131 | 143 | /// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path` |
| 132 | 144 | /// are absolute. See `Dir.copyFile` for a function that operates on both |
| 133 | 145 | /// absolute and relative paths. |
| 146 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 147 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 148 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 134 | 149 | pub fn copyFileAbsolute( |
| 135 | 150 | source_path: []const u8, |
| 136 | 151 | dest_path: []const u8, |
| ... | ... | @@ -145,24 +160,30 @@ pub fn copyFileAbsolute( |
| 145 | 160 | /// Create a new directory, based on an absolute path. |
| 146 | 161 | /// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates |
| 147 | 162 | /// on both absolute and relative paths. |
| 163 | /// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 164 | /// On WASI, `absolute_path` should be encoded as valid UTF-8. | |
| 165 | /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. | |
| 148 | 166 | pub fn makeDirAbsolute(absolute_path: []const u8) !void { |
| 149 | 167 | assert(path.isAbsolute(absolute_path)); |
| 150 | 168 | return os.mkdir(absolute_path, Dir.default_mode); |
| 151 | 169 | } |
| 152 | 170 | |
| 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. | |
| 154 | 172 | pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void { |
| 155 | 173 | assert(path.isAbsoluteZ(absolute_path_z)); |
| 156 | 174 | return os.mkdirZ(absolute_path_z, Dir.default_mode); |
| 157 | 175 | } |
| 158 | 176 | |
| 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. | |
| 160 | 178 | pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void { |
| 161 | 179 | assert(path.isAbsoluteWindowsW(absolute_path_w)); |
| 162 | 180 | return os.mkdirW(absolute_path_w, Dir.default_mode); |
| 163 | 181 | } |
| 164 | 182 | |
| 165 | 183 | /// Same as `Dir.deleteDir` except the path is absolute. |
| 184 | /// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 185 | /// On WASI, `dir_path` should be encoded as valid UTF-8. | |
| 186 | /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. | |
| 166 | 187 | pub fn deleteDirAbsolute(dir_path: []const u8) !void { |
| 167 | 188 | assert(path.isAbsolute(dir_path)); |
| 168 | 189 | return os.rmdir(dir_path); |
| ... | ... | @@ -181,6 +202,9 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void { |
| 181 | 202 | } |
| 182 | 203 | |
| 183 | 204 | /// Same as `Dir.rename` except the paths are absolute. |
| 205 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 206 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 207 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 184 | 208 | pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void { |
| 185 | 209 | assert(path.isAbsolute(old_path)); |
| 186 | 210 | assert(path.isAbsolute(new_path)); |
| ... | ... | @@ -211,7 +235,7 @@ pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_su |
| 211 | 235 | return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z); |
| 212 | 236 | } |
| 213 | 237 | |
| 214 | /// Same as `rename` except the parameters are UTF16LE, NT prefixed. | |
| 238 | /// Same as `rename` except the parameters are WTF16LE, NT prefixed. | |
| 215 | 239 | /// This function is Windows-only. |
| 216 | 240 | pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void { |
| 217 | 241 | return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w); |
| ... | ... | @@ -240,6 +264,9 @@ pub fn defaultWasiCwd() std.os.wasi.fd_t { |
| 240 | 264 | /// See `openDirAbsoluteZ` for a function that accepts a null-terminated path. |
| 241 | 265 | /// |
| 242 | 266 | /// Asserts that the path parameter has no null bytes. |
| 267 | /// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 268 | /// On WASI, `absolute_path` should be encoded as valid UTF-8. | |
| 269 | /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. | |
| 243 | 270 | pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir { |
| 244 | 271 | assert(path.isAbsolute(absolute_path)); |
| 245 | 272 | return cwd().openDir(absolute_path, flags); |
| ... | ... | @@ -262,6 +289,9 @@ pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptio |
| 262 | 289 | /// operates on both absolute and relative paths. |
| 263 | 290 | /// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function |
| 264 | 291 | /// that accepts a null-terminated path. |
| 292 | /// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 293 | /// On WASI, `absolute_path` should be encoded as valid UTF-8. | |
| 294 | /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. | |
| 265 | 295 | pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File { |
| 266 | 296 | assert(path.isAbsolute(absolute_path)); |
| 267 | 297 | return cwd().openFile(absolute_path, flags); |
| ... | ... | @@ -280,11 +310,13 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi |
| 280 | 310 | } |
| 281 | 311 | |
| 282 | 312 | /// Test accessing `path`. |
| 283 | /// `path` is UTF-8-encoded. | |
| 284 | 313 | /// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function. |
| 285 | 314 | /// For example, instead of testing if a file exists and then opening it, just |
| 286 | 315 | /// open it and handle the error for file not found. |
| 287 | 316 | /// See `accessAbsoluteZ` for a function that accepts a null-terminated path. |
| 317 | /// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 318 | /// On WASI, `absolute_path` should be encoded as valid UTF-8. | |
| 319 | /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. | |
| 288 | 320 | pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void { |
| 289 | 321 | assert(path.isAbsolute(absolute_path)); |
| 290 | 322 | try cwd().access(absolute_path, flags); |
| ... | ... | @@ -306,6 +338,9 @@ pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir |
| 306 | 338 | /// operates on both absolute and relative paths. |
| 307 | 339 | /// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function |
| 308 | 340 | /// that accepts a null-terminated path. |
| 341 | /// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 342 | /// On WASI, `absolute_path` should be encoded as valid UTF-8. | |
| 343 | /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. | |
| 309 | 344 | pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File { |
| 310 | 345 | assert(path.isAbsolute(absolute_path)); |
| 311 | 346 | return cwd().createFile(absolute_path, flags); |
| ... | ... | @@ -327,6 +362,9 @@ pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFl |
| 327 | 362 | /// Asserts that the path is absolute. See `Dir.deleteFile` for a function that |
| 328 | 363 | /// operates on both absolute and relative paths. |
| 329 | 364 | /// Asserts that the path parameter has no null bytes. |
| 365 | /// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 366 | /// On WASI, `absolute_path` should be encoded as valid UTF-8. | |
| 367 | /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. | |
| 330 | 368 | pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void { |
| 331 | 369 | assert(path.isAbsolute(absolute_path)); |
| 332 | 370 | return cwd().deleteFile(absolute_path); |
| ... | ... | @@ -349,6 +387,9 @@ pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError! |
| 349 | 387 | /// Asserts that the path is absolute. See `Dir.deleteTree` for a function that |
| 350 | 388 | /// operates on both absolute and relative paths. |
| 351 | 389 | /// Asserts that the path parameter has no null bytes. |
| 390 | /// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 391 | /// On WASI, `absolute_path` should be encoded as valid UTF-8. | |
| 392 | /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. | |
| 352 | 393 | pub fn deleteTreeAbsolute(absolute_path: []const u8) !void { |
| 353 | 394 | assert(path.isAbsolute(absolute_path)); |
| 354 | 395 | const dirname = path.dirname(absolute_path) orelse return error{ |
| ... | ... | @@ -364,6 +405,9 @@ pub fn deleteTreeAbsolute(absolute_path: []const u8) !void { |
| 364 | 405 | } |
| 365 | 406 | |
| 366 | 407 | /// Same as `Dir.readLink`, except it asserts the path is absolute. |
| 408 | /// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 409 | /// On WASI, `pathname` should be encoded as valid UTF-8. | |
| 410 | /// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. | |
| 367 | 411 | pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { |
| 368 | 412 | assert(path.isAbsolute(pathname)); |
| 369 | 413 | return os.readlink(pathname, buffer); |
| ... | ... | @@ -387,6 +431,9 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8) |
| 387 | 431 | /// one; the latter case is known as a dangling link. |
| 388 | 432 | /// If `sym_link_path` exists, it will not be overwritten. |
| 389 | 433 | /// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`. |
| 434 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 435 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 436 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 390 | 437 | pub fn symLinkAbsolute( |
| 391 | 438 | target_path: []const u8, |
| 392 | 439 | sym_link_path: []const u8, |
| ... | ... | @@ -402,7 +449,7 @@ pub fn symLinkAbsolute( |
| 402 | 449 | return os.symlink(target_path, sym_link_path); |
| 403 | 450 | } |
| 404 | 451 | |
| 405 | /// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 encoded. | |
| 452 | /// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 LE encoded. | |
| 406 | 453 | /// Note that this function will by default try creating a symbolic link to a file. If you would |
| 407 | 454 | /// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`. |
| 408 | 455 | /// See also `symLinkAbsolute`, `symLinkAbsoluteZ`. |
| ... | ... | @@ -426,27 +473,14 @@ pub fn symLinkAbsoluteZ( |
| 426 | 473 | assert(path.isAbsoluteZ(target_path_c)); |
| 427 | 474 | assert(path.isAbsoluteZ(sym_link_path_c)); |
| 428 | 475 | 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); | |
| 432 | 479 | } |
| 433 | 480 | return os.symlinkZ(target_path_c, sym_link_path_c); |
| 434 | 481 | } |
| 435 | 482 | |
| 436 | pub 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; | |
| 483 | pub const OpenSelfExeError = os.OpenError || SelfExePathError || os.FlockError; | |
| 450 | 484 | |
| 451 | 485 | pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { |
| 452 | 486 | if (builtin.os.tag == .linux) { |
| ... | ... | @@ -469,7 +503,45 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { |
| 469 | 503 | return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags); |
| 470 | 504 | } |
| 471 | 505 | |
| 472 | pub const SelfExePathError = os.ReadLinkError || os.SysCtlError || os.RealPathError; | |
| 506 | // This is os.ReadLinkError || os.RealPathError with impossible errors excluded | |
| 507 | pub 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; | |
| 473 | 545 | |
| 474 | 546 | /// `selfExePath` except allocates the result on the heap. |
| 475 | 547 | /// Caller owns returned memory. |
| ... | ... | @@ -491,6 +563,8 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 { |
| 491 | 563 | /// This function may return an error if the current executable |
| 492 | 564 | /// was deleted after spawning. |
| 493 | 565 | /// Returned value is a slice of out_buffer. |
| 566 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 567 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 494 | 568 | /// |
| 495 | 569 | /// On Linux, depends on procfs being mounted. If the currently executing binary has |
| 496 | 570 | /// been deleted, the file path looks something like `/a/b/c/exe (deleted)`. |
| ... | ... | @@ -505,15 +579,31 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { |
| 505 | 579 | if (rc != 0) return error.NameTooLong; |
| 506 | 580 | |
| 507 | 581 | var real_path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 508 | const real_path = try std.os.realpathZ(&symlink_path_buf, &real_path_buf); | |
| 582 | const real_path = std.os.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) { | |
| 583 | error.InvalidWtf8 => unreachable, // Windows-only | |
| 584 | error.NetworkNotFound => unreachable, // Windows-only | |
| 585 | else => |e| return e, | |
| 586 | }; | |
| 509 | 587 | if (real_path.len > out_buffer.len) return error.NameTooLong; |
| 510 | 588 | const result = out_buffer[0..real_path.len]; |
| 511 | 589 | @memcpy(result, real_path); |
| 512 | 590 | return result; |
| 513 | 591 | } |
| 514 | 592 | 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 | }, | |
| 517 | 607 | .freebsd, .dragonfly => { |
| 518 | 608 | var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC, os.KERN.PROC_PATHNAME, -1 }; |
| 519 | 609 | var out_len: usize = out_buffer.len; |
| ... | ... | @@ -537,7 +627,11 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { |
| 537 | 627 | if (mem.indexOf(u8, argv0, "/") != null) { |
| 538 | 628 | // argv[0] is a path (relative or absolute): use realpath(3) directly |
| 539 | 629 | var real_path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 540 | const real_path = try os.realpathZ(os.argv[0], &real_path_buf); | |
| 630 | const real_path = os.realpathZ(os.argv[0], &real_path_buf) catch |err| switch (err) { | |
| 631 | error.InvalidWtf8 => unreachable, // Windows-only | |
| 632 | error.NetworkNotFound => unreachable, // Windows-only | |
| 633 | else => |e| return e, | |
| 634 | }; | |
| 541 | 635 | if (real_path.len > out_buffer.len) |
| 542 | 636 | return error.NameTooLong; |
| 543 | 637 | const result = out_buffer[0..real_path.len]; |
| ... | ... | @@ -575,7 +669,10 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { |
| 575 | 669 | // symlink, not the path that the symlink points to. We want the path |
| 576 | 670 | // that the symlink points to, though, so we need to get the realpath. |
| 577 | 671 | const pathname_w = try os.windows.wToPrefixedFileW(null, image_path_name); |
| 578 | return std.fs.cwd().realpathW(pathname_w.span(), out_buffer); | |
| 672 | return std.fs.cwd().realpathW(pathname_w.span(), out_buffer) catch |err| switch (err) { | |
| 673 | error.InvalidWtf8 => unreachable, | |
| 674 | else => |e| return e, | |
| 675 | }; | |
| 579 | 676 | }, |
| 580 | 677 | else => @compileError("std.fs.selfExePath not supported for this target"), |
| 581 | 678 | } |
| ... | ... | @@ -599,6 +696,8 @@ pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 { |
| 599 | 696 | |
| 600 | 697 | /// Get the directory path that contains the current executable. |
| 601 | 698 | /// Returned value is a slice of out_buffer. |
| 699 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 700 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 602 | 701 | pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 { |
| 603 | 702 | const self_exe_path = try selfExePath(out_buffer); |
| 604 | 703 | // Assume that the OS APIs return absolute paths, and therefore dirname |
| ... | ... | @@ -607,6 +706,8 @@ pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 { |
| 607 | 706 | } |
| 608 | 707 | |
| 609 | 708 | /// `realpath`, except caller must free the returned memory. |
| 709 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 710 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 610 | 711 | /// See also `Dir.realpath`. |
| 611 | 712 | pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 { |
| 612 | 713 | // Use of MAX_PATH_BYTES here is valid as the realpath function does not |
lib/std/fs/Dir.zig+128-36| ... | ... | @@ -9,7 +9,14 @@ pub const Entry = struct { |
| 9 | 9 | pub const Kind = File.Kind; |
| 10 | 10 | }; |
| 11 | 11 | |
| 12 | const IteratorError = error{ AccessDenied, SystemResources } || posix.UnexpectedError; | |
| 12 | const 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; | |
| 13 | 20 | |
| 14 | 21 | pub const Iterator = switch (builtin.os.tag) { |
| 15 | 22 | .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct { |
| ... | ... | @@ -445,13 +452,12 @@ pub const Iterator = switch (builtin.os.tag) { |
| 445 | 452 | self.index = self.buf.len; |
| 446 | 453 | } |
| 447 | 454 | |
| 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]; | |
| 449 | 456 | |
| 450 | if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' })) | |
| 457 | if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' })) | |
| 451 | 458 | 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]; | |
| 455 | 461 | const kind: Entry.Kind = blk: { |
| 456 | 462 | const attrs = dir_info.FileAttributes; |
| 457 | 463 | if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory; |
| ... | ... | @@ -459,7 +465,7 @@ pub const Iterator = switch (builtin.os.tag) { |
| 459 | 465 | break :blk .file; |
| 460 | 466 | }; |
| 461 | 467 | return Entry{ |
| 462 | .name = name_utf8, | |
| 468 | .name = name_wtf8, | |
| 463 | 469 | .kind = kind, |
| 464 | 470 | }; |
| 465 | 471 | } |
| ... | ... | @@ -516,6 +522,7 @@ pub const Iterator = switch (builtin.os.tag) { |
| 516 | 522 | .INVAL => unreachable, |
| 517 | 523 | .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration. |
| 518 | 524 | .NOTCAPABLE => return error.AccessDenied, |
| 525 | .ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8. | |
| 519 | 526 | else => |err| return posix.unexpectedErrno(err), |
| 520 | 527 | } |
| 521 | 528 | if (bufused == 0) return null; |
| ... | ... | @@ -743,7 +750,11 @@ pub const OpenError = error{ |
| 743 | 750 | SystemFdQuotaExceeded, |
| 744 | 751 | NoDevice, |
| 745 | 752 | SystemResources, |
| 753 | /// WASI-only; file paths must be valid UTF-8. | |
| 746 | 754 | InvalidUtf8, |
| 755 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 756 | /// https://simonsapin.github.io/wtf-8/ | |
| 757 | InvalidWtf8, | |
| 747 | 758 | BadPathName, |
| 748 | 759 | DeviceBusy, |
| 749 | 760 | /// On Windows, `\\server` or `\\server\share` was not found. |
| ... | ... | @@ -759,6 +770,9 @@ pub fn close(self: *Dir) void { |
| 759 | 770 | /// To create a new file, see `createFile`. |
| 760 | 771 | /// Call `File.close` to release the resource. |
| 761 | 772 | /// Asserts that the path parameter has no null bytes. |
| 773 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 774 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 775 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 762 | 776 | pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { |
| 763 | 777 | if (builtin.os.tag == .windows) { |
| 764 | 778 | const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path); |
| ... | ... | @@ -911,6 +925,9 @@ pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File |
| 911 | 925 | /// Creates, opens, or overwrites a file with write access. |
| 912 | 926 | /// Call `File.close` on the result when done. |
| 913 | 927 | /// Asserts that the path parameter has no null bytes. |
| 928 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 929 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 930 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 914 | 931 | pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { |
| 915 | 932 | if (builtin.os.tag == .windows) { |
| 916 | 933 | const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path); |
| ... | ... | @@ -1060,18 +1077,21 @@ pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags) |
| 1060 | 1077 | /// Creates a single directory with a relative or absolute path. |
| 1061 | 1078 | /// To create multiple directories to make an entire path, see `makePath`. |
| 1062 | 1079 | /// To operate on only absolute paths, see `makeDirAbsolute`. |
| 1080 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1081 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 1082 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 1063 | 1083 | pub fn makeDir(self: Dir, sub_path: []const u8) !void { |
| 1064 | 1084 | try posix.mkdirat(self.fd, sub_path, default_mode); |
| 1065 | 1085 | } |
| 1066 | 1086 | |
| 1067 | /// Creates a single directory with a relative or absolute null-terminated UTF-8-encoded path. | |
| 1087 | /// Same as `makeDir`, but `sub_path` is null-terminated. | |
| 1068 | 1088 | /// To create multiple directories to make an entire path, see `makePath`. |
| 1069 | 1089 | /// To operate on only absolute paths, see `makeDirAbsoluteZ`. |
| 1070 | 1090 | pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void { |
| 1071 | 1091 | try posix.mkdiratZ(self.fd, sub_path, default_mode); |
| 1072 | 1092 | } |
| 1073 | 1093 | |
| 1074 | /// Creates a single directory with a relative or absolute null-terminated WTF-16-encoded path. | |
| 1094 | /// Creates a single directory with a relative or absolute null-terminated WTF-16 LE-encoded path. | |
| 1075 | 1095 | /// To create multiple directories to make an entire path, see `makePath`. |
| 1076 | 1096 | /// To operate on only absolute paths, see `makeDirAbsoluteW`. |
| 1077 | 1097 | pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void { |
| ... | ... | @@ -1083,6 +1103,9 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void { |
| 1083 | 1103 | /// Returns success if the path already exists and is a directory. |
| 1084 | 1104 | /// This function is not atomic, and if it returns an error, the file system may |
| 1085 | 1105 | /// have been modified regardless. |
| 1106 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1107 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 1108 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 1086 | 1109 | /// |
| 1087 | 1110 | /// Paths containing `..` components are handled differently depending on the platform: |
| 1088 | 1111 | /// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning |
| ... | ... | @@ -1119,16 +1142,17 @@ pub fn makePath(self: Dir, sub_path: []const u8) !void { |
| 1119 | 1142 | } |
| 1120 | 1143 | } |
| 1121 | 1144 | |
| 1122 | /// Calls makeOpenDirAccessMaskW iteratively to make an entire path | |
| 1145 | /// Windows only. Calls makeOpenDirAccessMaskW iteratively to make an entire path | |
| 1123 | 1146 | /// (i.e. creating any parent directories that do not exist). |
| 1124 | 1147 | /// Opens the dir if the path already exists and is a directory. |
| 1125 | 1148 | /// This function is not atomic, and if it returns an error, the file system may |
| 1126 | 1149 | /// have been modified regardless. |
| 1150 | /// `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1127 | 1151 | fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir { |
| 1128 | 1152 | const w = std.os.windows; |
| 1129 | 1153 | var it = try fs.path.componentIterator(sub_path); |
| 1130 | 1154 | // If there are no components in the path, then create a dummy component with the full path. |
| 1131 | var component = it.last() orelse fs.path.NativeUtf8ComponentIterator.Component{ | |
| 1155 | var component = it.last() orelse fs.path.NativeComponentIterator.Component{ | |
| 1132 | 1156 | .name = "", |
| 1133 | 1157 | .path = sub_path, |
| 1134 | 1158 | }; |
| ... | ... | @@ -1156,7 +1180,9 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no |
| 1156 | 1180 | /// This function performs `makePath`, followed by `openDir`. |
| 1157 | 1181 | /// If supported by the OS, this operation is atomic. It is not atomic on |
| 1158 | 1182 | /// all operating systems. |
| 1159 | /// On Windows, this function performs `makeOpenPathAccessMaskW`. | |
| 1183 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1184 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 1185 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 1160 | 1186 | pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir { |
| 1161 | 1187 | return switch (builtin.os.tag) { |
| 1162 | 1188 | .windows => { |
| ... | ... | @@ -1185,6 +1211,10 @@ pub const RealPathError = posix.RealPathError; |
| 1185 | 1211 | /// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this |
| 1186 | 1212 | /// `Dir` handle and returns the canonicalized absolute pathname of `pathname` |
| 1187 | 1213 | /// argument. |
| 1214 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1215 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 1216 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1217 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 1188 | 1218 | /// This function is not universally supported by all platforms. |
| 1189 | 1219 | /// Currently supported hosts are: Linux, macOS, and Windows. |
| 1190 | 1220 | /// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`. |
| ... | ... | @@ -1224,6 +1254,7 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE |
| 1224 | 1254 | error.FileLocksNotSupported => return error.Unexpected, |
| 1225 | 1255 | error.FileBusy => return error.Unexpected, |
| 1226 | 1256 | error.WouldBlock => return error.Unexpected, |
| 1257 | error.InvalidUtf8 => unreachable, // WASI-only | |
| 1227 | 1258 | else => |e| return e, |
| 1228 | 1259 | }; |
| 1229 | 1260 | defer posix.close(fd); |
| ... | ... | @@ -1246,7 +1277,8 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE |
| 1246 | 1277 | return result; |
| 1247 | 1278 | } |
| 1248 | 1279 | |
| 1249 | /// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded. | |
| 1280 | /// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded. | |
| 1281 | /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1250 | 1282 | /// See also `Dir.realpath`, `realpathW`. |
| 1251 | 1283 | pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 { |
| 1252 | 1284 | const w = std.os.windows; |
| ... | ... | @@ -1272,16 +1304,7 @@ pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathErr |
| 1272 | 1304 | var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined; |
| 1273 | 1305 | const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &wide_buf); |
| 1274 | 1306 | var big_out_buf: [fs.MAX_PATH_BYTES]u8 = undefined; |
| 1275 | const end_index = std.unicode.utf16leToUtf8(&big_out_buf, wide_slice) catch |e| switch (e) { | |
| 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); | |
| 1285 | 1308 | if (end_index > out_buffer.len) |
| 1286 | 1309 | return error.NameTooLong; |
| 1287 | 1310 | const result = out_buffer[0..end_index]; |
| ... | ... | @@ -1344,6 +1367,9 @@ pub const OpenDirOptions = struct { |
| 1344 | 1367 | /// open until `close` is called on the result. |
| 1345 | 1368 | /// The directory cannot be iterated unless the `iterate` option is set to `true`. |
| 1346 | 1369 | /// |
| 1370 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1371 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 1372 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 1347 | 1373 | /// Asserts that the path parameter has no null bytes. |
| 1348 | 1374 | pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir { |
| 1349 | 1375 | switch (builtin.os.tag) { |
| ... | ... | @@ -1428,7 +1454,7 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) Open |
| 1428 | 1454 | } |
| 1429 | 1455 | } |
| 1430 | 1456 | |
| 1431 | /// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed. | |
| 1457 | /// Same as `openDir` except the path parameter is WTF-16 LE encoded, NT-prefixed. | |
| 1432 | 1458 | /// This function asserts the target OS is Windows. |
| 1433 | 1459 | pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir { |
| 1434 | 1460 | const w = std.os.windows; |
| ... | ... | @@ -1518,6 +1544,9 @@ fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u3 |
| 1518 | 1544 | pub const DeleteFileError = posix.UnlinkError; |
| 1519 | 1545 | |
| 1520 | 1546 | /// Delete a file name and possibly the file it refers to, based on an open directory handle. |
| 1547 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1548 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 1549 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 1521 | 1550 | /// Asserts that the path parameter has no null bytes. |
| 1522 | 1551 | pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { |
| 1523 | 1552 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -1553,7 +1582,7 @@ pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void { |
| 1553 | 1582 | }; |
| 1554 | 1583 | } |
| 1555 | 1584 | |
| 1556 | /// Same as `deleteFile` except the parameter is WTF-16 encoded. | |
| 1585 | /// Same as `deleteFile` except the parameter is WTF-16 LE encoded. | |
| 1557 | 1586 | pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void { |
| 1558 | 1587 | posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) { |
| 1559 | 1588 | error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR |
| ... | ... | @@ -1572,7 +1601,11 @@ pub const DeleteDirError = error{ |
| 1572 | 1601 | NotDir, |
| 1573 | 1602 | SystemResources, |
| 1574 | 1603 | ReadOnlyFileSystem, |
| 1604 | /// WASI-only; file paths must be valid UTF-8. | |
| 1575 | 1605 | InvalidUtf8, |
| 1606 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 1607 | /// https://simonsapin.github.io/wtf-8/ | |
| 1608 | InvalidWtf8, | |
| 1576 | 1609 | BadPathName, |
| 1577 | 1610 | /// On Windows, `\\server` or `\\server\share` was not found. |
| 1578 | 1611 | NetworkNotFound, |
| ... | ... | @@ -1581,6 +1614,9 @@ pub const DeleteDirError = error{ |
| 1581 | 1614 | |
| 1582 | 1615 | /// Returns `error.DirNotEmpty` if the directory is not empty. |
| 1583 | 1616 | /// To delete a directory recursively, see `deleteTree`. |
| 1617 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1618 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 1619 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 1584 | 1620 | /// Asserts that the path parameter has no null bytes. |
| 1585 | 1621 | pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { |
| 1586 | 1622 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -1605,7 +1641,7 @@ pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void { |
| 1605 | 1641 | }; |
| 1606 | 1642 | } |
| 1607 | 1643 | |
| 1608 | /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed. | |
| 1644 | /// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed. | |
| 1609 | 1645 | /// This function is Windows-only. |
| 1610 | 1646 | pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void { |
| 1611 | 1647 | posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) { |
| ... | ... | @@ -1620,6 +1656,9 @@ pub const RenameError = posix.RenameError; |
| 1620 | 1656 | /// If new_sub_path already exists, it will be replaced. |
| 1621 | 1657 | /// Renaming a file over an existing directory or a directory |
| 1622 | 1658 | /// over an existing file will fail with `error.IsDir` or `error.NotDir` |
| 1659 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1660 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 1661 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 1623 | 1662 | pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void { |
| 1624 | 1663 | return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path); |
| 1625 | 1664 | } |
| ... | ... | @@ -1629,7 +1668,7 @@ pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]co |
| 1629 | 1668 | return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z); |
| 1630 | 1669 | } |
| 1631 | 1670 | |
| 1632 | /// Same as `rename` except the parameters are UTF16LE, NT prefixed. | |
| 1671 | /// Same as `rename` except the parameters are WTF16LE, NT prefixed. | |
| 1633 | 1672 | /// This function is Windows-only. |
| 1634 | 1673 | pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void { |
| 1635 | 1674 | return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w); |
| ... | ... | @@ -1647,6 +1686,9 @@ pub const SymLinkFlags = struct { |
| 1647 | 1686 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent |
| 1648 | 1687 | /// one; the latter case is known as a dangling link. |
| 1649 | 1688 | /// If `sym_link_path` exists, it will not be overwritten. |
| 1689 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1690 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 1691 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 1650 | 1692 | pub fn symLink( |
| 1651 | 1693 | self: Dir, |
| 1652 | 1694 | target_path: []const u8, |
| ... | ... | @@ -1662,7 +1704,7 @@ pub fn symLink( |
| 1662 | 1704 | // when converting to an NT namespaced path. CreateSymbolicLink in |
| 1663 | 1705 | // symLinkW will handle the necessary conversion. |
| 1664 | 1706 | var target_path_w: std.os.windows.PathSpace = undefined; |
| 1665 | target_path_w.len = try std.unicode.utf8ToUtf16Le(&target_path_w.data, target_path); | |
| 1707 | target_path_w.len = try std.unicode.wtf8ToWtf16Le(&target_path_w.data, target_path); | |
| 1666 | 1708 | target_path_w.data[target_path_w.len] = 0; |
| 1667 | 1709 | const sym_link_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sym_link_path); |
| 1668 | 1710 | return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags); |
| ... | ... | @@ -1698,7 +1740,7 @@ pub fn symLinkZ( |
| 1698 | 1740 | } |
| 1699 | 1741 | |
| 1700 | 1742 | /// Windows-only. Same as `symLink` except the pathname parameters |
| 1701 | /// are null-terminated, WTF16 encoded. | |
| 1743 | /// are WTF16 LE encoded. | |
| 1702 | 1744 | pub fn symLinkW( |
| 1703 | 1745 | self: Dir, |
| 1704 | 1746 | /// WTF-16, does not need to be NT-prefixed. The NT-prefixing |
| ... | ... | @@ -1716,6 +1758,9 @@ pub const ReadLinkError = posix.ReadLinkError; |
| 1716 | 1758 | /// Read value of a symbolic link. |
| 1717 | 1759 | /// The return value is a slice of `buffer`, from index `0`. |
| 1718 | 1760 | /// Asserts that the path parameter has no null bytes. |
| 1761 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1762 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 1763 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 1719 | 1764 | pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 { |
| 1720 | 1765 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1721 | 1766 | return self.readLinkWasi(sub_path, buffer); |
| ... | ... | @@ -1733,7 +1778,7 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 { |
| 1733 | 1778 | return posix.readlinkat(self.fd, sub_path, buffer); |
| 1734 | 1779 | } |
| 1735 | 1780 | |
| 1736 | /// Same as `readLink`, except the `pathname` parameter is null-terminated. | |
| 1781 | /// Same as `readLink`, except the `sub_path_c` parameter is null-terminated. | |
| 1737 | 1782 | pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { |
| 1738 | 1783 | if (builtin.os.tag == .windows) { |
| 1739 | 1784 | const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c); |
| ... | ... | @@ -1743,7 +1788,7 @@ pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { |
| 1743 | 1788 | } |
| 1744 | 1789 | |
| 1745 | 1790 | /// Windows-only. Same as `readLink` except the pathname parameter |
| 1746 | /// is null-terminated, WTF16 encoded. | |
| 1791 | /// is WTF16 LE encoded. | |
| 1747 | 1792 | pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 { |
| 1748 | 1793 | return std.os.windows.ReadLink(self.fd, sub_path_w, buffer); |
| 1749 | 1794 | } |
| ... | ... | @@ -1753,6 +1798,9 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 { |
| 1753 | 1798 | /// the situation is ambiguous. It could either mean that the entire file was read, and |
| 1754 | 1799 | /// it exactly fits the buffer, or it could mean the buffer was not big enough for the |
| 1755 | 1800 | /// entire file. |
| 1801 | /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1802 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 1803 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 1756 | 1804 | pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 { |
| 1757 | 1805 | var file = try self.openFile(file_path, .{}); |
| 1758 | 1806 | defer file.close(); |
| ... | ... | @@ -1763,6 +1811,9 @@ pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 { |
| 1763 | 1811 | |
| 1764 | 1812 | /// On success, caller owns returned buffer. |
| 1765 | 1813 | /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. |
| 1814 | /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1815 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 1816 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 1766 | 1817 | pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 { |
| 1767 | 1818 | return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null); |
| 1768 | 1819 | } |
| ... | ... | @@ -1772,6 +1823,9 @@ pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, |
| 1772 | 1823 | /// If `size_hint` is specified the initial buffer size is calculated using |
| 1773 | 1824 | /// that value, otherwise the effective file size is used instead. |
| 1774 | 1825 | /// Allows specifying alignment and a sentinel value. |
| 1826 | /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1827 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 1828 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 1775 | 1829 | pub fn readFileAllocOptions( |
| 1776 | 1830 | self: Dir, |
| 1777 | 1831 | allocator: mem.Allocator, |
| ... | ... | @@ -1811,9 +1865,13 @@ pub const DeleteTreeError = error{ |
| 1811 | 1865 | /// This error is unreachable if `sub_path` does not contain a path separator. |
| 1812 | 1866 | NotDir, |
| 1813 | 1867 | |
| 1814 | /// On Windows, file paths must be valid Unicode. | |
| 1868 | /// WASI-only; file paths must be valid UTF-8. | |
| 1815 | 1869 | InvalidUtf8, |
| 1816 | 1870 | |
| 1871 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 1872 | /// https://simonsapin.github.io/wtf-8/ | |
| 1873 | InvalidWtf8, | |
| 1874 | ||
| 1817 | 1875 | /// On Windows, file paths cannot contain these characters: |
| 1818 | 1876 | /// '/', '*', '?', '"', '<', '>', '|' |
| 1819 | 1877 | BadPathName, |
| ... | ... | @@ -1826,6 +1884,9 @@ pub const DeleteTreeError = error{ |
| 1826 | 1884 | /// removes it. If it cannot be removed because it is a non-empty directory, |
| 1827 | 1885 | /// this function recursively removes its entries and then tries again. |
| 1828 | 1886 | /// This operation is not atomic on most file systems. |
| 1887 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1888 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 1889 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 1829 | 1890 | pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { |
| 1830 | 1891 | var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return; |
| 1831 | 1892 | |
| ... | ... | @@ -1879,6 +1940,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { |
| 1879 | 1940 | error.SystemResources, |
| 1880 | 1941 | error.Unexpected, |
| 1881 | 1942 | error.InvalidUtf8, |
| 1943 | error.InvalidWtf8, | |
| 1882 | 1944 | error.BadPathName, |
| 1883 | 1945 | error.NetworkNotFound, |
| 1884 | 1946 | error.DeviceBusy, |
| ... | ... | @@ -1910,6 +1972,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { |
| 1910 | 1972 | |
| 1911 | 1973 | error.AccessDenied, |
| 1912 | 1974 | error.InvalidUtf8, |
| 1975 | error.InvalidWtf8, | |
| 1913 | 1976 | error.SymLinkLoop, |
| 1914 | 1977 | error.NameTooLong, |
| 1915 | 1978 | error.SystemResources, |
| ... | ... | @@ -1973,6 +2036,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { |
| 1973 | 2036 | error.SystemResources, |
| 1974 | 2037 | error.Unexpected, |
| 1975 | 2038 | error.InvalidUtf8, |
| 2039 | error.InvalidWtf8, | |
| 1976 | 2040 | error.BadPathName, |
| 1977 | 2041 | error.NetworkNotFound, |
| 1978 | 2042 | error.DeviceBusy, |
| ... | ... | @@ -1994,6 +2058,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { |
| 1994 | 2058 | |
| 1995 | 2059 | error.AccessDenied, |
| 1996 | 2060 | error.InvalidUtf8, |
| 2061 | error.InvalidWtf8, | |
| 1997 | 2062 | error.SymLinkLoop, |
| 1998 | 2063 | error.NameTooLong, |
| 1999 | 2064 | error.SystemResources, |
| ... | ... | @@ -2022,6 +2087,9 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { |
| 2022 | 2087 | |
| 2023 | 2088 | /// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size. |
| 2024 | 2089 | /// This is slower than `deleteTree` but uses less stack space. |
| 2090 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2091 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 2092 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 2025 | 2093 | pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void { |
| 2026 | 2094 | return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file); |
| 2027 | 2095 | } |
| ... | ... | @@ -2074,6 +2142,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint |
| 2074 | 2142 | error.SystemResources, |
| 2075 | 2143 | error.Unexpected, |
| 2076 | 2144 | error.InvalidUtf8, |
| 2145 | error.InvalidWtf8, | |
| 2077 | 2146 | error.BadPathName, |
| 2078 | 2147 | error.NetworkNotFound, |
| 2079 | 2148 | error.DeviceBusy, |
| ... | ... | @@ -2102,6 +2171,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint |
| 2102 | 2171 | |
| 2103 | 2172 | error.AccessDenied, |
| 2104 | 2173 | error.InvalidUtf8, |
| 2174 | error.InvalidWtf8, | |
| 2105 | 2175 | error.SymLinkLoop, |
| 2106 | 2176 | error.NameTooLong, |
| 2107 | 2177 | error.SystemResources, |
| ... | ... | @@ -2171,6 +2241,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File |
| 2171 | 2241 | error.SystemResources, |
| 2172 | 2242 | error.Unexpected, |
| 2173 | 2243 | error.InvalidUtf8, |
| 2244 | error.InvalidWtf8, | |
| 2174 | 2245 | error.BadPathName, |
| 2175 | 2246 | error.DeviceBusy, |
| 2176 | 2247 | error.NetworkNotFound, |
| ... | ... | @@ -2189,6 +2260,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File |
| 2189 | 2260 | |
| 2190 | 2261 | error.AccessDenied, |
| 2191 | 2262 | error.InvalidUtf8, |
| 2263 | error.InvalidWtf8, | |
| 2192 | 2264 | error.SymLinkLoop, |
| 2193 | 2265 | error.NameTooLong, |
| 2194 | 2266 | error.SystemResources, |
| ... | ... | @@ -2209,6 +2281,9 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File |
| 2209 | 2281 | pub const WriteFileError = File.WriteError || File.OpenError; |
| 2210 | 2282 | |
| 2211 | 2283 | /// Deprecated: use `writeFile2`. |
| 2284 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2285 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 2286 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 2212 | 2287 | pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void { |
| 2213 | 2288 | return writeFile2(self, .{ |
| 2214 | 2289 | .sub_path = sub_path, |
| ... | ... | @@ -2218,6 +2293,9 @@ pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileErr |
| 2218 | 2293 | } |
| 2219 | 2294 | |
| 2220 | 2295 | pub const WriteFileOptions = struct { |
| 2296 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2297 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 2298 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 2221 | 2299 | sub_path: []const u8, |
| 2222 | 2300 | data: []const u8, |
| 2223 | 2301 | flags: File.CreateFlags = .{}, |
| ... | ... | @@ -2232,8 +2310,10 @@ pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void { |
| 2232 | 2310 | |
| 2233 | 2311 | pub const AccessError = posix.AccessError; |
| 2234 | 2312 | |
| 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. | |
| 2237 | 2317 | /// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function. |
| 2238 | 2318 | /// For example, instead of testing if a file exists and then opening it, just |
| 2239 | 2319 | /// open it and handle the error for file not found. |
| ... | ... | @@ -2268,9 +2348,9 @@ pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) Access |
| 2268 | 2348 | } |
| 2269 | 2349 | |
| 2270 | 2350 | /// Same as `access` except asserts the target OS is Windows and the path parameter is |
| 2271 | /// * WTF-16 encoded | |
| 2351 | /// * WTF-16 LE encoded | |
| 2272 | 2352 | /// * null-terminated |
| 2273 | /// * NtDll prefixed | |
| 2353 | /// * relative or has the NT namespace prefix | |
| 2274 | 2354 | /// TODO currently this ignores `flags`. |
| 2275 | 2355 | pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void { |
| 2276 | 2356 | _ = flags; |
| ... | ... | @@ -2292,6 +2372,9 @@ pub const PrevStatus = enum { |
| 2292 | 2372 | /// atime, and mode of the source file so that the next call to `updateFile` will not need a copy. |
| 2293 | 2373 | /// Returns the previous status of the file before updating. |
| 2294 | 2374 | /// If any of the directories do not exist for dest_path, they are created. |
| 2375 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2376 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 2377 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 2295 | 2378 | pub fn updateFile( |
| 2296 | 2379 | source_dir: Dir, |
| 2297 | 2380 | source_path: []const u8, |
| ... | ... | @@ -2343,6 +2426,9 @@ pub const CopyFileError = File.OpenError || File.StatError || |
| 2343 | 2426 | /// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available, |
| 2344 | 2427 | /// there is a possibility of power loss or application termination leaving temporary files present |
| 2345 | 2428 | /// in the same directory as dest_path. |
| 2429 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2430 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 2431 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 2346 | 2432 | pub fn copyFile( |
| 2347 | 2433 | source_dir: Dir, |
| 2348 | 2434 | source_path: []const u8, |
| ... | ... | @@ -2430,6 +2516,9 @@ pub const AtomicFileOptions = struct { |
| 2430 | 2516 | /// Always call `AtomicFile.deinit` to clean up, regardless of whether |
| 2431 | 2517 | /// `AtomicFile.finish` succeeded. `dest_path` must remain valid until |
| 2432 | 2518 | /// `AtomicFile.deinit` is called. |
| 2519 | /// On Windows, `dest_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2520 | /// On WASI, `dest_path` should be encoded as valid UTF-8. | |
| 2521 | /// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding. | |
| 2433 | 2522 | pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile { |
| 2434 | 2523 | if (fs.path.dirname(dest_path)) |dirname| { |
| 2435 | 2524 | const dir = if (options.make_path) |
| ... | ... | @@ -2461,6 +2550,9 @@ pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError |
| 2461 | 2550 | /// Symlinks are followed. |
| 2462 | 2551 | /// |
| 2463 | 2552 | /// `sub_path` may be absolute, in which case `self` is ignored. |
| 2553 | /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2554 | /// On WASI, `sub_path` should be encoded as valid UTF-8. | |
| 2555 | /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. | |
| 2464 | 2556 | pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat { |
| 2465 | 2557 | if (builtin.os.tag == .windows) { |
| 2466 | 2558 | var file = try self.openFile(sub_path, .{}); |
lib/std/fs/File.zig+4-1| ... | ... | @@ -40,8 +40,11 @@ pub const OpenError = error{ |
| 40 | 40 | AccessDenied, |
| 41 | 41 | PipeBusy, |
| 42 | 42 | NameTooLong, |
| 43 | /// On Windows, file paths must be valid Unicode. | |
| 43 | /// WASI-only; file paths must be valid UTF-8. | |
| 44 | 44 | InvalidUtf8, |
| 45 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 46 | /// https://simonsapin.github.io/wtf-8/ | |
| 47 | InvalidWtf8, | |
| 45 | 48 | /// On Windows, file paths cannot contain these characters: |
| 46 | 49 | /// '/', '*', '?', '"', '<', '>', '|' |
| 47 | 50 | BadPathName, |
lib/std/fs/path.zig+36-7| ... | ... | @@ -1,3 +1,17 @@ |
| 1 | //! POSIX paths are arbitrary sequences of `u8` with no particular encoding. | |
| 2 | //! | |
| 3 | //! Windows paths are arbitrary sequences of `u16` (WTF-16). | |
| 4 | //! For cross-platform APIs that deal with sequences of `u8`, Windows | |
| 5 | //! paths are encoded by Zig as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 6 | //! WTF-8 is a superset of UTF-8 that allows encoding surrogate codepoints, | |
| 7 | //! which enables lossless roundtripping when converting to/from WTF-16 | |
| 8 | //! (as long as the WTF-8 encoded surrogate codepoints do not form a pair). | |
| 9 | //! | |
| 10 | //! WASI paths are sequences of valid Unicode scalar values, | |
| 11 | //! which means that WASI is unable to handle paths that cannot be | |
| 12 | //! encoded as well-formed UTF-8/UTF-16. | |
| 13 | //! https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353 | |
| 14 | ||
| 1 | 15 | const builtin = @import("builtin"); |
| 2 | 16 | const std = @import("../std.zig"); |
| 3 | 17 | const debug = std.debug; |
| ... | ... | @@ -438,7 +452,7 @@ fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool { |
| 438 | 452 | var it1 = mem.tokenizeScalar(u8, ns1, sep1); |
| 439 | 453 | var it2 = mem.tokenizeScalar(u8, ns2, sep2); |
| 440 | 454 | |
| 441 | return windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?); | |
| 455 | return windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?); | |
| 442 | 456 | } |
| 443 | 457 | |
| 444 | 458 | fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8) bool { |
| ... | ... | @@ -458,7 +472,7 @@ fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8 |
| 458 | 472 | var it1 = mem.tokenizeScalar(u8, p1, sep1); |
| 459 | 473 | var it2 = mem.tokenizeScalar(u8, p2, sep2); |
| 460 | 474 | |
| 461 | return windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?) and windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?); | |
| 475 | return windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?) and windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?); | |
| 462 | 476 | }, |
| 463 | 477 | } |
| 464 | 478 | } |
| ... | ... | @@ -1099,7 +1113,7 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) ! |
| 1099 | 1113 | const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest()); |
| 1100 | 1114 | const to_rest = to_it.rest(); |
| 1101 | 1115 | if (to_it.next()) |to_component| { |
| 1102 | if (windows.eqlIgnoreCaseUtf8(from_component, to_component)) | |
| 1116 | if (windows.eqlIgnoreCaseWtf8(from_component, to_component)) | |
| 1103 | 1117 | continue; |
| 1104 | 1118 | } |
| 1105 | 1119 | var up_index_end = "..".len; |
| ... | ... | @@ -1564,14 +1578,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type { |
| 1564 | 1578 | }; |
| 1565 | 1579 | } |
| 1566 | 1580 | |
| 1567 | pub const NativeUtf8ComponentIterator = ComponentIterator(switch (native_os) { | |
| 1581 | pub const NativeComponentIterator = ComponentIterator(switch (native_os) { | |
| 1568 | 1582 | .windows => .windows, |
| 1569 | 1583 | .uefi => .uefi, |
| 1570 | 1584 | else => .posix, |
| 1571 | 1585 | }, u8); |
| 1572 | 1586 | |
| 1573 | pub fn componentIterator(path: []const u8) !NativeUtf8ComponentIterator { | |
| 1574 | return NativeUtf8ComponentIterator.init(path); | |
| 1587 | pub fn componentIterator(path: []const u8) !NativeComponentIterator { | |
| 1588 | return NativeComponentIterator.init(path); | |
| 1575 | 1589 | } |
| 1576 | 1590 | |
| 1577 | 1591 | test "ComponentIterator posix" { |
| ... | ... | @@ -1826,7 +1840,7 @@ test "ComponentIterator windows" { |
| 1826 | 1840 | } |
| 1827 | 1841 | } |
| 1828 | 1842 | |
| 1829 | test "ComponentIterator windows UTF-16" { | |
| 1843 | test "ComponentIterator windows WTF-16" { | |
| 1830 | 1844 | // TODO: Fix on big endian architectures |
| 1831 | 1845 | if (builtin.cpu.arch.endian() != .little) { |
| 1832 | 1846 | return error.SkipZigTest; |
| ... | ... | @@ -1925,3 +1939,18 @@ test "ComponentIterator roots" { |
| 1925 | 1939 | try std.testing.expectEqualStrings("//a/b//", it.root().?); |
| 1926 | 1940 | } |
| 1927 | 1941 | } |
| 1942 | ||
| 1943 | /// Format a path encoded as bytes for display as UTF-8. | |
| 1944 | /// Returns a Formatter for the given path. The path will be converted to valid UTF-8 | |
| 1945 | /// during formatting. This is a lossy conversion if the path contains any ill-formed UTF-8. | |
| 1946 | /// Ill-formed UTF-8 byte sequences are replaced by the replacement character (U+FFFD) | |
| 1947 | /// according to "U+FFFD Substitution of Maximal Subparts" from Chapter 3 of | |
| 1948 | /// the Unicode standard, and as specified by https://encoding.spec.whatwg.org/#utf-8-decoder | |
| 1949 | pub const fmtAsUtf8Lossy = std.unicode.fmtUtf8; | |
| 1950 | ||
| 1951 | /// Format a path encoded as WTF-16 LE for display as UTF-8. | |
| 1952 | /// Return a Formatter for a (potentially ill-formed) UTF-16 LE path. | |
| 1953 | /// The path will be converted to valid UTF-8 during formatting. This is | |
| 1954 | /// a lossy conversion if the path contains any unpaired surrogates. | |
| 1955 | /// Unpaired surrogates are replaced by the replacement character (U+FFFD). | |
| 1956 | pub const fmtWtf16LeAsUtf8Lossy = std.unicode.fmtUtf16Le; |
lib/std/fs/test.zig+126-8| ... | ... | @@ -26,39 +26,39 @@ const PathType = enum { |
| 26 | 26 | } |
| 27 | 27 | |
| 28 | 28 | pub const TransformError = std.os.RealPathError || error{OutOfMemory}; |
| 29 | pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8; | |
| 29 | pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8; | |
| 30 | 30 | |
| 31 | 31 | pub fn getTransformFn(comptime path_type: PathType) TransformFn { |
| 32 | 32 | switch (path_type) { |
| 33 | 33 | .relative => return struct { |
| 34 | fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 { | |
| 34 | fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { | |
| 35 | 35 | _ = allocator; |
| 36 | 36 | _ = dir; |
| 37 | 37 | return relative_path; |
| 38 | 38 | } |
| 39 | 39 | }.transform, |
| 40 | 40 | .absolute => return struct { |
| 41 | fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 { | |
| 41 | fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { | |
| 42 | 42 | // The final path may not actually exist which would cause realpath to fail. |
| 43 | 43 | // So instead, we get the path of the dir and join it with the relative path. |
| 44 | 44 | var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined; |
| 45 | 45 | const dir_path = try os.getFdPath(dir.fd, &fd_path_buf); |
| 46 | return fs.path.join(allocator, &.{ dir_path, relative_path }); | |
| 46 | return fs.path.joinZ(allocator, &.{ dir_path, relative_path }); | |
| 47 | 47 | } |
| 48 | 48 | }.transform, |
| 49 | 49 | .unc => return struct { |
| 50 | fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 { | |
| 50 | fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 { | |
| 51 | 51 | // Any drive absolute path (C:\foo) can be converted into a UNC path by |
| 52 | 52 | // using '127.0.0.1' as the server name and '<drive letter>$' as the share name. |
| 53 | 53 | var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined; |
| 54 | 54 | const dir_path = try os.getFdPath(dir.fd, &fd_path_buf); |
| 55 | 55 | const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path); |
| 56 | 56 | switch (windows_path_type) { |
| 57 | .unc_absolute => return fs.path.join(allocator, &.{ dir_path, relative_path }), | |
| 57 | .unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }), | |
| 58 | 58 | .drive_absolute => { |
| 59 | 59 | // `C:\<...>` -> `\\127.0.0.1\C$\<...>` |
| 60 | 60 | const prepended = "\\\\127.0.0.1\\"; |
| 61 | var path = try fs.path.join(allocator, &.{ prepended, dir_path, relative_path }); | |
| 61 | var path = try fs.path.joinZ(allocator, &.{ prepended, dir_path, relative_path }); | |
| 62 | 62 | path[prepended.len + 1] = '$'; |
| 63 | 63 | return path; |
| 64 | 64 | }, |
| ... | ... | @@ -96,7 +96,7 @@ const TestContext = struct { |
| 96 | 96 | /// Returns the `relative_path` transformed into the TestContext's `path_type`. |
| 97 | 97 | /// The result is allocated by the TestContext's arena and will be free'd during |
| 98 | 98 | /// `TestContext.deinit`. |
| 99 | pub fn transformPath(self: *TestContext, relative_path: []const u8) ![]const u8 { | |
| 99 | pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 { | |
| 100 | 100 | return self.transform_fn(self.arena.allocator(), self.dir, relative_path); |
| 101 | 101 | } |
| 102 | 102 | }; |
| ... | ... | @@ -1001,6 +1001,16 @@ test "openSelfExe" { |
| 1001 | 1001 | self_exe_file.close(); |
| 1002 | 1002 | } |
| 1003 | 1003 | |
| 1004 | test "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 | ||
| 1004 | 1014 | test "deleteTree does not follow symlinks" { |
| 1005 | 1015 | var tmp = tmpDir(.{}); |
| 1006 | 1016 | defer tmp.cleanup(); |
| ... | ... | @@ -1907,3 +1917,111 @@ test "delete a setAsCwd directory on Windows" { |
| 1907 | 1917 | // Close the parent "tmp" so we don't leak the HANDLE. |
| 1908 | 1918 | tmp.parent_dir.close(); |
| 1909 | 1919 | } |
| 1920 | ||
| 1921 | test "invalid UTF-8/WTF-8 paths" { | |
| 1922 | const expected_err = switch (builtin.os.tag) { | |
| 1923 | .wasi => error.InvalidUtf8, | |
| 1924 | .windows => error.InvalidWtf8, | |
| 1925 | else => return error.SkipZigTest, | |
| 1926 | }; | |
| 1927 | ||
| 1928 | try testWithAllSupportedPathTypes(struct { | |
| 1929 | fn impl(ctx: *TestContext) !void { | |
| 1930 | // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte | |
| 1931 | const invalid_path = try ctx.transformPath("\xFF"); | |
| 1932 | ||
| 1933 | try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{})); | |
| 1934 | try testing.expectError(expected_err, ctx.dir.openFileZ(invalid_path, .{})); | |
| 1935 | ||
| 1936 | try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{})); | |
| 1937 | try testing.expectError(expected_err, ctx.dir.createFileZ(invalid_path, .{})); | |
| 1938 | ||
| 1939 | try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path)); | |
| 1940 | try testing.expectError(expected_err, ctx.dir.makeDirZ(invalid_path)); | |
| 1941 | ||
| 1942 | try testing.expectError(expected_err, ctx.dir.makePath(invalid_path)); | |
| 1943 | try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{})); | |
| 1944 | ||
| 1945 | try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{})); | |
| 1946 | try testing.expectError(expected_err, ctx.dir.openDirZ(invalid_path, .{})); | |
| 1947 | ||
| 1948 | try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path)); | |
| 1949 | try testing.expectError(expected_err, ctx.dir.deleteFileZ(invalid_path)); | |
| 1950 | ||
| 1951 | try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path)); | |
| 1952 | try testing.expectError(expected_err, ctx.dir.deleteDirZ(invalid_path)); | |
| 1953 | ||
| 1954 | try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path)); | |
| 1955 | try testing.expectError(expected_err, ctx.dir.renameZ(invalid_path, invalid_path)); | |
| 1956 | ||
| 1957 | try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{})); | |
| 1958 | try testing.expectError(expected_err, ctx.dir.symLinkZ(invalid_path, invalid_path, .{})); | |
| 1959 | if (builtin.os.tag == .wasi) { | |
| 1960 | try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{})); | |
| 1961 | } | |
| 1962 | ||
| 1963 | try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{})); | |
| 1964 | try testing.expectError(expected_err, ctx.dir.readLinkZ(invalid_path, &[_]u8{})); | |
| 1965 | if (builtin.os.tag == .wasi) { | |
| 1966 | try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{})); | |
| 1967 | } | |
| 1968 | ||
| 1969 | try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{})); | |
| 1970 | try testing.expectError(expected_err, ctx.dir.readFileAlloc(testing.allocator, invalid_path, 0)); | |
| 1971 | ||
| 1972 | try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path)); | |
| 1973 | try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path)); | |
| 1974 | ||
| 1975 | try testing.expectError(expected_err, ctx.dir.writeFile(invalid_path, "")); | |
| 1976 | try testing.expectError(expected_err, ctx.dir.writeFile2(.{ | |
| 1977 | .sub_path = invalid_path, | |
| 1978 | .data = "", | |
| 1979 | })); | |
| 1980 | ||
| 1981 | try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{})); | |
| 1982 | try testing.expectError(expected_err, ctx.dir.accessZ(invalid_path, .{})); | |
| 1983 | ||
| 1984 | try testing.expectError(expected_err, ctx.dir.updateFile(invalid_path, ctx.dir, invalid_path, .{})); | |
| 1985 | try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{})); | |
| 1986 | ||
| 1987 | try testing.expectError(expected_err, ctx.dir.statFile(invalid_path)); | |
| 1988 | ||
| 1989 | if (builtin.os.tag != .wasi) { | |
| 1990 | try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{})); | |
| 1991 | try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{})); | |
| 1992 | try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path)); | |
| 1993 | } | |
| 1994 | ||
| 1995 | try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path)); | |
| 1996 | try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path)); | |
| 1997 | ||
| 1998 | if (builtin.os.tag != .wasi and ctx.path_type != .relative) { | |
| 1999 | try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{})); | |
| 2000 | try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{})); | |
| 2001 | try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path)); | |
| 2002 | try testing.expectError(expected_err, fs.makeDirAbsoluteZ(invalid_path)); | |
| 2003 | try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path)); | |
| 2004 | try testing.expectError(expected_err, fs.deleteDirAbsoluteZ(invalid_path)); | |
| 2005 | try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path)); | |
| 2006 | try testing.expectError(expected_err, fs.renameAbsoluteZ(invalid_path, invalid_path)); | |
| 2007 | try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{})); | |
| 2008 | try testing.expectError(expected_err, fs.openDirAbsoluteZ(invalid_path, .{})); | |
| 2009 | try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{})); | |
| 2010 | try testing.expectError(expected_err, fs.openFileAbsoluteZ(invalid_path, .{})); | |
| 2011 | try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{})); | |
| 2012 | try testing.expectError(expected_err, fs.accessAbsoluteZ(invalid_path, .{})); | |
| 2013 | try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{})); | |
| 2014 | try testing.expectError(expected_err, fs.createFileAbsoluteZ(invalid_path, .{})); | |
| 2015 | try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path)); | |
| 2016 | try testing.expectError(expected_err, fs.deleteFileAbsoluteZ(invalid_path)); | |
| 2017 | try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path)); | |
| 2018 | var readlink_buf: [fs.MAX_PATH_BYTES]u8 = undefined; | |
| 2019 | try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf)); | |
| 2020 | try testing.expectError(expected_err, fs.readLinkAbsoluteZ(invalid_path, &readlink_buf)); | |
| 2021 | try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{})); | |
| 2022 | try testing.expectError(expected_err, fs.symLinkAbsoluteZ(invalid_path, invalid_path, .{})); | |
| 2023 | try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path)); | |
| 2024 | } | |
| 2025 | } | |
| 2026 | }.impl); | |
| 2027 | } |
lib/std/os.zig+257-40| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | //! * Convert "errno"-style error codes into Zig errors. |
| 4 | 4 | //! * When null-terminated byte buffers are required, provide APIs which accept |
| 5 | 5 | //! slices as well as APIs which accept null-terminated byte buffers. Same goes |
| 6 | //! for UTF-16LE encoding. | |
| 6 | //! for WTF-16LE encoding. | |
| 7 | 7 | //! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide |
| 8 | 8 | //! cross platform abstracting. |
| 9 | 9 | //! * When there exists a corresponding libc function and linking libc, the libc |
| ... | ... | @@ -498,6 +498,7 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr |
| 498 | 498 | const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) { |
| 499 | 499 | error.NameTooLong => unreachable, |
| 500 | 500 | error.FileNotFound => unreachable, |
| 501 | error.InvalidUtf8 => unreachable, | |
| 501 | 502 | else => |e| return e, |
| 502 | 503 | }; |
| 503 | 504 | if ((stat.mode & S.IFMT) == S.IFLNK) |
| ... | ... | @@ -1614,9 +1615,16 @@ pub const OpenError = error{ |
| 1614 | 1615 | /// The underlying filesystem does not support file locks |
| 1615 | 1616 | FileLocksNotSupported, |
| 1616 | 1617 | |
| 1618 | /// Path contains characters that are disallowed by the underlying filesystem. | |
| 1617 | 1619 | BadPathName, |
| 1620 | ||
| 1621 | /// WASI-only; file paths must be valid UTF-8. | |
| 1618 | 1622 | InvalidUtf8, |
| 1619 | 1623 | |
| 1624 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 1625 | /// https://simonsapin.github.io/wtf-8/ | |
| 1626 | InvalidWtf8, | |
| 1627 | ||
| 1620 | 1628 | /// On Windows, `\\server` or `\\server\share` was not found. |
| 1621 | 1629 | NetworkNotFound, |
| 1622 | 1630 | |
| ... | ... | @@ -1634,6 +1642,9 @@ pub const OpenError = error{ |
| 1634 | 1642 | } || UnexpectedError; |
| 1635 | 1643 | |
| 1636 | 1644 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1645 | /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1646 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 1647 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 1637 | 1648 | /// See also `openZ`. |
| 1638 | 1649 | pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t { |
| 1639 | 1650 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -1646,6 +1657,9 @@ pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t { |
| 1646 | 1657 | } |
| 1647 | 1658 | |
| 1648 | 1659 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1660 | /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1661 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 1662 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 1649 | 1663 | /// See also `open`. |
| 1650 | 1664 | pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t { |
| 1651 | 1665 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -1687,6 +1701,9 @@ pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t { |
| 1687 | 1701 | |
| 1688 | 1702 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1689 | 1703 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 1704 | /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1705 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 1706 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 1690 | 1707 | /// See also `openatZ`. |
| 1691 | 1708 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t { |
| 1692 | 1709 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -1829,6 +1846,7 @@ pub fn openatWasi( |
| 1829 | 1846 | .EXIST => return error.PathAlreadyExists, |
| 1830 | 1847 | .BUSY => return error.DeviceBusy, |
| 1831 | 1848 | .NOTCAPABLE => return error.AccessDenied, |
| 1849 | .ILSEQ => return error.InvalidUtf8, | |
| 1832 | 1850 | else => |err| return unexpectedErrno(err), |
| 1833 | 1851 | } |
| 1834 | 1852 | } |
| ... | ... | @@ -1836,6 +1854,9 @@ pub fn openatWasi( |
| 1836 | 1854 | |
| 1837 | 1855 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1838 | 1856 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 1857 | /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1858 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 1859 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 1839 | 1860 | /// See also `openat`. |
| 1840 | 1861 | pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t { |
| 1841 | 1862 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -2156,13 +2177,23 @@ pub const SymLinkError = error{ |
| 2156 | 2177 | ReadOnlyFileSystem, |
| 2157 | 2178 | NotDir, |
| 2158 | 2179 | NameTooLong, |
| 2180 | ||
| 2181 | /// WASI-only; file paths must be valid UTF-8. | |
| 2159 | 2182 | InvalidUtf8, |
| 2183 | ||
| 2184 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 2185 | /// https://simonsapin.github.io/wtf-8/ | |
| 2186 | InvalidWtf8, | |
| 2187 | ||
| 2160 | 2188 | BadPathName, |
| 2161 | 2189 | } || UnexpectedError; |
| 2162 | 2190 | |
| 2163 | 2191 | /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. |
| 2164 | 2192 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent |
| 2165 | 2193 | /// one; the latter case is known as a dangling link. |
| 2194 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2195 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 2196 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 2166 | 2197 | /// If `sym_link_path` exists, it will not be overwritten. |
| 2167 | 2198 | /// See also `symlinkZ. |
| 2168 | 2199 | pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { |
| ... | ... | @@ -2200,6 +2231,10 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin |
| 2200 | 2231 | .NOMEM => return error.SystemResources, |
| 2201 | 2232 | .NOSPC => return error.NoSpaceLeft, |
| 2202 | 2233 | .ROFS => return error.ReadOnlyFileSystem, |
| 2234 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 2235 | return error.InvalidUtf8 | |
| 2236 | else | |
| 2237 | return unexpectedErrno(err), | |
| 2203 | 2238 | else => |err| return unexpectedErrno(err), |
| 2204 | 2239 | } |
| 2205 | 2240 | } |
| ... | ... | @@ -2208,6 +2243,9 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin |
| 2208 | 2243 | /// `target_path` **relative** to `newdirfd` directory handle. |
| 2209 | 2244 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent |
| 2210 | 2245 | /// one; the latter case is known as a dangling link. |
| 2246 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2247 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 2248 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 2211 | 2249 | /// If `sym_link_path` exists, it will not be overwritten. |
| 2212 | 2250 | /// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`. |
| 2213 | 2251 | pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { |
| ... | ... | @@ -2242,6 +2280,7 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c |
| 2242 | 2280 | .NOSPC => return error.NoSpaceLeft, |
| 2243 | 2281 | .ROFS => return error.ReadOnlyFileSystem, |
| 2244 | 2282 | .NOTCAPABLE => return error.AccessDenied, |
| 2283 | .ILSEQ => return error.InvalidUtf8, | |
| 2245 | 2284 | else => |err| return unexpectedErrno(err), |
| 2246 | 2285 | } |
| 2247 | 2286 | } |
| ... | ... | @@ -2270,6 +2309,10 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*: |
| 2270 | 2309 | .NOMEM => return error.SystemResources, |
| 2271 | 2310 | .NOSPC => return error.NoSpaceLeft, |
| 2272 | 2311 | .ROFS => return error.ReadOnlyFileSystem, |
| 2312 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 2313 | return error.InvalidUtf8 | |
| 2314 | else | |
| 2315 | return unexpectedErrno(err), | |
| 2273 | 2316 | else => |err| return unexpectedErrno(err), |
| 2274 | 2317 | } |
| 2275 | 2318 | } |
| ... | ... | @@ -2287,8 +2330,13 @@ pub const LinkError = UnexpectedError || error{ |
| 2287 | 2330 | NoSpaceLeft, |
| 2288 | 2331 | ReadOnlyFileSystem, |
| 2289 | 2332 | NotSameFileSystem, |
| 2333 | ||
| 2334 | /// WASI-only; file paths must be valid UTF-8. | |
| 2335 | InvalidUtf8, | |
| 2290 | 2336 | }; |
| 2291 | 2337 | |
| 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. | |
| 2292 | 2340 | pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void { |
| 2293 | 2341 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2294 | 2342 | return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags); |
| ... | ... | @@ -2310,10 +2358,16 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr |
| 2310 | 2358 | .ROFS => return error.ReadOnlyFileSystem, |
| 2311 | 2359 | .XDEV => return error.NotSameFileSystem, |
| 2312 | 2360 | .INVAL => unreachable, |
| 2361 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 2362 | return error.InvalidUtf8 | |
| 2363 | else | |
| 2364 | return unexpectedErrno(err), | |
| 2313 | 2365 | else => |err| return unexpectedErrno(err), |
| 2314 | 2366 | } |
| 2315 | 2367 | } |
| 2316 | 2368 | |
| 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. | |
| 2317 | 2371 | pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void { |
| 2318 | 2372 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2319 | 2373 | return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) { |
| ... | ... | @@ -2328,6 +2382,8 @@ pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void |
| 2328 | 2382 | |
| 2329 | 2383 | pub const LinkatError = LinkError || error{NotDir}; |
| 2330 | 2384 | |
| 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. | |
| 2331 | 2387 | pub fn linkatZ( |
| 2332 | 2388 | olddir: fd_t, |
| 2333 | 2389 | oldpath: [*:0]const u8, |
| ... | ... | @@ -2356,10 +2412,16 @@ pub fn linkatZ( |
| 2356 | 2412 | .ROFS => return error.ReadOnlyFileSystem, |
| 2357 | 2413 | .XDEV => return error.NotSameFileSystem, |
| 2358 | 2414 | .INVAL => unreachable, |
| 2415 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 2416 | return error.InvalidUtf8 | |
| 2417 | else | |
| 2418 | return unexpectedErrno(err), | |
| 2359 | 2419 | else => |err| return unexpectedErrno(err), |
| 2360 | 2420 | } |
| 2361 | 2421 | } |
| 2362 | 2422 | |
| 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. | |
| 2363 | 2425 | pub fn linkat( |
| 2364 | 2426 | olddir: fd_t, |
| 2365 | 2427 | oldpath: []const u8, |
| ... | ... | @@ -2399,6 +2461,7 @@ pub fn linkat( |
| 2399 | 2461 | .ROFS => return error.ReadOnlyFileSystem, |
| 2400 | 2462 | .XDEV => return error.NotSameFileSystem, |
| 2401 | 2463 | .INVAL => unreachable, |
| 2464 | .ILSEQ => return error.InvalidUtf8, | |
| 2402 | 2465 | else => |err| return unexpectedErrno(err), |
| 2403 | 2466 | } |
| 2404 | 2467 | } |
| ... | ... | @@ -2422,9 +2485,13 @@ pub const UnlinkError = error{ |
| 2422 | 2485 | SystemResources, |
| 2423 | 2486 | ReadOnlyFileSystem, |
| 2424 | 2487 | |
| 2425 | /// On Windows, file paths must be valid Unicode. | |
| 2488 | /// WASI-only; file paths must be valid UTF-8. | |
| 2426 | 2489 | InvalidUtf8, |
| 2427 | 2490 | |
| 2491 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 2492 | /// https://simonsapin.github.io/wtf-8/ | |
| 2493 | InvalidWtf8, | |
| 2494 | ||
| 2428 | 2495 | /// On Windows, file paths cannot contain these characters: |
| 2429 | 2496 | /// '/', '*', '?', '"', '<', '>', '|' |
| 2430 | 2497 | BadPathName, |
| ... | ... | @@ -2434,6 +2501,9 @@ pub const UnlinkError = error{ |
| 2434 | 2501 | } || UnexpectedError; |
| 2435 | 2502 | |
| 2436 | 2503 | /// Delete a name and possibly the file it refers to. |
| 2504 | /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2505 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 2506 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 2437 | 2507 | /// See also `unlinkZ`. |
| 2438 | 2508 | pub fn unlink(file_path: []const u8) UnlinkError!void { |
| 2439 | 2509 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| ... | ... | @@ -2450,7 +2520,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void { |
| 2450 | 2520 | } |
| 2451 | 2521 | } |
| 2452 | 2522 | |
| 2453 | /// Same as `unlink` except the parameter is a null terminated UTF8-encoded string. | |
| 2523 | /// Same as `unlink` except the parameter is null terminated. | |
| 2454 | 2524 | pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { |
| 2455 | 2525 | if (builtin.os.tag == .windows) { |
| 2456 | 2526 | const file_path_w = try windows.cStrToPrefixedFileW(null, file_path); |
| ... | ... | @@ -2473,11 +2543,15 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { |
| 2473 | 2543 | .NOTDIR => return error.NotDir, |
| 2474 | 2544 | .NOMEM => return error.SystemResources, |
| 2475 | 2545 | .ROFS => return error.ReadOnlyFileSystem, |
| 2546 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 2547 | return error.InvalidUtf8 | |
| 2548 | else | |
| 2549 | return unexpectedErrno(err), | |
| 2476 | 2550 | else => |err| return unexpectedErrno(err), |
| 2477 | 2551 | } |
| 2478 | 2552 | } |
| 2479 | 2553 | |
| 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. | |
| 2481 | 2555 | pub fn unlinkW(file_path_w: []const u16) UnlinkError!void { |
| 2482 | 2556 | windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd }) catch |err| switch (err) { |
| 2483 | 2557 | error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true |
| ... | ... | @@ -2491,6 +2565,9 @@ pub const UnlinkatError = UnlinkError || error{ |
| 2491 | 2565 | }; |
| 2492 | 2566 | |
| 2493 | 2567 | /// Delete a file name and possibly the file it refers to, based on an open directory handle. |
| 2568 | /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2569 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 2570 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 2494 | 2571 | /// Asserts that the path parameter has no null bytes. |
| 2495 | 2572 | pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { |
| 2496 | 2573 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -2528,6 +2605,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro |
| 2528 | 2605 | .ROFS => return error.ReadOnlyFileSystem, |
| 2529 | 2606 | .NOTEMPTY => return error.DirNotEmpty, |
| 2530 | 2607 | .NOTCAPABLE => return error.AccessDenied, |
| 2608 | .ILSEQ => return error.InvalidUtf8, | |
| 2531 | 2609 | |
| 2532 | 2610 | .INVAL => unreachable, // invalid flags, or pathname has . as last component |
| 2533 | 2611 | .BADF => unreachable, // always a race condition |
| ... | ... | @@ -2560,6 +2638,10 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr |
| 2560 | 2638 | .ROFS => return error.ReadOnlyFileSystem, |
| 2561 | 2639 | .EXIST => return error.DirNotEmpty, |
| 2562 | 2640 | .NOTEMPTY => return error.DirNotEmpty, |
| 2641 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 2642 | return error.InvalidUtf8 | |
| 2643 | else | |
| 2644 | return unexpectedErrno(err), | |
| 2563 | 2645 | |
| 2564 | 2646 | .INVAL => unreachable, // invalid flags, or pathname has . as last component |
| 2565 | 2647 | .BADF => unreachable, // always a race condition |
| ... | ... | @@ -2568,7 +2650,7 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr |
| 2568 | 2650 | } |
| 2569 | 2651 | } |
| 2570 | 2652 | |
| 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. | |
| 2572 | 2654 | pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void { |
| 2573 | 2655 | const remove_dir = (flags & AT.REMOVEDIR) != 0; |
| 2574 | 2656 | return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); |
| ... | ... | @@ -2594,7 +2676,11 @@ pub const RenameError = error{ |
| 2594 | 2676 | PathAlreadyExists, |
| 2595 | 2677 | ReadOnlyFileSystem, |
| 2596 | 2678 | RenameAcrossMountPoints, |
| 2679 | /// WASI-only; file paths must be valid UTF-8. | |
| 2597 | 2680 | InvalidUtf8, |
| 2681 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 2682 | /// https://simonsapin.github.io/wtf-8/ | |
| 2683 | InvalidWtf8, | |
| 2598 | 2684 | BadPathName, |
| 2599 | 2685 | NoDevice, |
| 2600 | 2686 | SharingViolation, |
| ... | ... | @@ -2610,6 +2696,9 @@ pub const RenameError = error{ |
| 2610 | 2696 | } || UnexpectedError; |
| 2611 | 2697 | |
| 2612 | 2698 | /// Change the name or location of a file. |
| 2699 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2700 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 2701 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 2613 | 2702 | pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { |
| 2614 | 2703 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2615 | 2704 | return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path); |
| ... | ... | @@ -2624,7 +2713,7 @@ pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { |
| 2624 | 2713 | } |
| 2625 | 2714 | } |
| 2626 | 2715 | |
| 2627 | /// Same as `rename` except the parameters are null-terminated byte arrays. | |
| 2716 | /// Same as `rename` except the parameters are null-terminated. | |
| 2628 | 2717 | pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void { |
| 2629 | 2718 | if (builtin.os.tag == .windows) { |
| 2630 | 2719 | const old_path_w = try windows.cStrToPrefixedFileW(null, old_path); |
| ... | ... | @@ -2653,11 +2742,15 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi |
| 2653 | 2742 | .NOTEMPTY => return error.PathAlreadyExists, |
| 2654 | 2743 | .ROFS => return error.ReadOnlyFileSystem, |
| 2655 | 2744 | .XDEV => return error.RenameAcrossMountPoints, |
| 2745 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 2746 | return error.InvalidUtf8 | |
| 2747 | else | |
| 2748 | return unexpectedErrno(err), | |
| 2656 | 2749 | else => |err| return unexpectedErrno(err), |
| 2657 | 2750 | } |
| 2658 | 2751 | } |
| 2659 | 2752 | |
| 2660 | /// Same as `rename` except the parameters are null-terminated UTF16LE encoded byte arrays. | |
| 2753 | /// Same as `rename` except the parameters are null-terminated and WTF16LE encoded. | |
| 2661 | 2754 | /// Assumes target is Windows. |
| 2662 | 2755 | pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void { |
| 2663 | 2756 | const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; |
| ... | ... | @@ -2665,6 +2758,9 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v |
| 2665 | 2758 | } |
| 2666 | 2759 | |
| 2667 | 2760 | /// Change the name or location of a file based on an open directory handle. |
| 2761 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2762 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 2763 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 2668 | 2764 | pub fn renameat( |
| 2669 | 2765 | old_dir_fd: fd_t, |
| 2670 | 2766 | old_path: []const u8, |
| ... | ... | @@ -2710,11 +2806,12 @@ pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!vo |
| 2710 | 2806 | .ROFS => return error.ReadOnlyFileSystem, |
| 2711 | 2807 | .XDEV => return error.RenameAcrossMountPoints, |
| 2712 | 2808 | .NOTCAPABLE => return error.AccessDenied, |
| 2809 | .ILSEQ => return error.InvalidUtf8, | |
| 2713 | 2810 | else => |err| return unexpectedErrno(err), |
| 2714 | 2811 | } |
| 2715 | 2812 | } |
| 2716 | 2813 | |
| 2717 | /// Same as `renameat` except the parameters are null-terminated byte arrays. | |
| 2814 | /// Same as `renameat` except the parameters are null-terminated. | |
| 2718 | 2815 | pub fn renameatZ( |
| 2719 | 2816 | old_dir_fd: fd_t, |
| 2720 | 2817 | old_path: [*:0]const u8, |
| ... | ... | @@ -2749,6 +2846,10 @@ pub fn renameatZ( |
| 2749 | 2846 | .NOTEMPTY => return error.PathAlreadyExists, |
| 2750 | 2847 | .ROFS => return error.ReadOnlyFileSystem, |
| 2751 | 2848 | .XDEV => return error.RenameAcrossMountPoints, |
| 2849 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 2850 | return error.InvalidUtf8 | |
| 2851 | else | |
| 2852 | return unexpectedErrno(err), | |
| 2752 | 2853 | else => |err| return unexpectedErrno(err), |
| 2753 | 2854 | } |
| 2754 | 2855 | } |
| ... | ... | @@ -2860,6 +2961,9 @@ pub fn renameatW( |
| 2860 | 2961 | } |
| 2861 | 2962 | } |
| 2862 | 2963 | |
| 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. | |
| 2863 | 2967 | pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2864 | 2968 | if (builtin.os.tag == .windows) { |
| 2865 | 2969 | const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path); |
| ... | ... | @@ -2891,14 +2995,16 @@ pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirErr |
| 2891 | 2995 | .NOTDIR => return error.NotDir, |
| 2892 | 2996 | .ROFS => return error.ReadOnlyFileSystem, |
| 2893 | 2997 | .NOTCAPABLE => return error.AccessDenied, |
| 2998 | .ILSEQ => return error.InvalidUtf8, | |
| 2894 | 2999 | else => |err| return unexpectedErrno(err), |
| 2895 | 3000 | } |
| 2896 | 3001 | } |
| 2897 | 3002 | |
| 3003 | /// Same as `mkdirat` except the parameters are null-terminated. | |
| 2898 | 3004 | pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { |
| 2899 | 3005 | if (builtin.os.tag == .windows) { |
| 2900 | 3006 | const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path); |
| 2901 | return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); | |
| 3007 | return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); | |
| 2902 | 3008 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2903 | 3009 | return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode); |
| 2904 | 3010 | } |
| ... | ... | @@ -2920,10 +3026,15 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr |
| 2920 | 3026 | .ROFS => return error.ReadOnlyFileSystem, |
| 2921 | 3027 | // dragonfly: when dir_fd is unlinked from filesystem |
| 2922 | 3028 | .NOTCONN => return error.FileNotFound, |
| 3029 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 3030 | return error.InvalidUtf8 | |
| 3031 | else | |
| 3032 | return unexpectedErrno(err), | |
| 2923 | 3033 | else => |err| return unexpectedErrno(err), |
| 2924 | 3034 | } |
| 2925 | 3035 | } |
| 2926 | 3036 | |
| 3037 | /// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded. | |
| 2927 | 3038 | pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void { |
| 2928 | 3039 | _ = mode; |
| 2929 | 3040 | const sub_dir_handle = windows.OpenFile(sub_path_w, .{ |
| ... | ... | @@ -2955,7 +3066,11 @@ pub const MakeDirError = error{ |
| 2955 | 3066 | NoSpaceLeft, |
| 2956 | 3067 | NotDir, |
| 2957 | 3068 | ReadOnlyFileSystem, |
| 3069 | /// WASI-only; file paths must be valid UTF-8. | |
| 2958 | 3070 | InvalidUtf8, |
| 3071 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 3072 | /// https://simonsapin.github.io/wtf-8/ | |
| 3073 | InvalidWtf8, | |
| 2959 | 3074 | BadPathName, |
| 2960 | 3075 | NoDevice, |
| 2961 | 3076 | /// On Windows, `\\server` or `\\server\share` was not found. |
| ... | ... | @@ -2964,6 +3079,9 @@ pub const MakeDirError = error{ |
| 2964 | 3079 | |
| 2965 | 3080 | /// Create a directory. |
| 2966 | 3081 | /// `mode` is ignored on Windows and WASI. |
| 3082 | /// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 3083 | /// On WASI, `dir_path` should be encoded as valid UTF-8. | |
| 3084 | /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. | |
| 2967 | 3085 | pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2968 | 3086 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2969 | 3087 | return mkdirat(wasi.AT.FDCWD, dir_path, mode); |
| ... | ... | @@ -2976,7 +3094,10 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2976 | 3094 | } |
| 2977 | 3095 | } |
| 2978 | 3096 | |
| 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. | |
| 2980 | 3101 | pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { |
| 2981 | 3102 | if (builtin.os.tag == .windows) { |
| 2982 | 3103 | const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path); |
| ... | ... | @@ -2999,11 +3120,15 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { |
| 2999 | 3120 | .NOSPC => return error.NoSpaceLeft, |
| 3000 | 3121 | .NOTDIR => return error.NotDir, |
| 3001 | 3122 | .ROFS => return error.ReadOnlyFileSystem, |
| 3123 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 3124 | return error.InvalidUtf8 | |
| 3125 | else | |
| 3126 | return unexpectedErrno(err), | |
| 3002 | 3127 | else => |err| return unexpectedErrno(err), |
| 3003 | 3128 | } |
| 3004 | 3129 | } |
| 3005 | 3130 | |
| 3006 | /// Windows-only. Same as `mkdir` but the parameters is WTF16 encoded. | |
| 3131 | /// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded. | |
| 3007 | 3132 | pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void { |
| 3008 | 3133 | _ = mode; |
| 3009 | 3134 | const sub_dir_handle = windows.OpenFile(dir_path_w, .{ |
| ... | ... | @@ -3031,13 +3156,20 @@ pub const DeleteDirError = error{ |
| 3031 | 3156 | NotDir, |
| 3032 | 3157 | DirNotEmpty, |
| 3033 | 3158 | ReadOnlyFileSystem, |
| 3159 | /// WASI-only; file paths must be valid UTF-8. | |
| 3034 | 3160 | InvalidUtf8, |
| 3161 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 3162 | /// https://simonsapin.github.io/wtf-8/ | |
| 3163 | InvalidWtf8, | |
| 3035 | 3164 | BadPathName, |
| 3036 | 3165 | /// On Windows, `\\server` or `\\server\share` was not found. |
| 3037 | 3166 | NetworkNotFound, |
| 3038 | 3167 | } || UnexpectedError; |
| 3039 | 3168 | |
| 3040 | 3169 | /// Deletes an empty directory. |
| 3170 | /// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 3171 | /// On WASI, `dir_path` should be encoded as valid UTF-8. | |
| 3172 | /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. | |
| 3041 | 3173 | pub fn rmdir(dir_path: []const u8) DeleteDirError!void { |
| 3042 | 3174 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3043 | 3175 | return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) { |
| ... | ... | @@ -3055,6 +3187,9 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void { |
| 3055 | 3187 | } |
| 3056 | 3188 | |
| 3057 | 3189 | /// Same as `rmdir` except the parameter is null-terminated. |
| 3190 | /// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 3191 | /// On WASI, `dir_path` should be encoded as valid UTF-8. | |
| 3192 | /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. | |
| 3058 | 3193 | pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { |
| 3059 | 3194 | if (builtin.os.tag == .windows) { |
| 3060 | 3195 | const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path); |
| ... | ... | @@ -3077,11 +3212,15 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { |
| 3077 | 3212 | .EXIST => return error.DirNotEmpty, |
| 3078 | 3213 | .NOTEMPTY => return error.DirNotEmpty, |
| 3079 | 3214 | .ROFS => return error.ReadOnlyFileSystem, |
| 3215 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 3216 | return error.InvalidUtf8 | |
| 3217 | else | |
| 3218 | return unexpectedErrno(err), | |
| 3080 | 3219 | else => |err| return unexpectedErrno(err), |
| 3081 | 3220 | } |
| 3082 | 3221 | } |
| 3083 | 3222 | |
| 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. | |
| 3085 | 3224 | pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void { |
| 3086 | 3225 | return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) { |
| 3087 | 3226 | error.IsDir => unreachable, |
| ... | ... | @@ -3098,21 +3237,25 @@ pub const ChangeCurDirError = error{ |
| 3098 | 3237 | SystemResources, |
| 3099 | 3238 | NotDir, |
| 3100 | 3239 | BadPathName, |
| 3101 | ||
| 3102 | /// On Windows, file paths must be valid Unicode. | |
| 3240 | /// WASI-only; file paths must be valid UTF-8. | |
| 3103 | 3241 | InvalidUtf8, |
| 3242 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 3243 | /// https://simonsapin.github.io/wtf-8/ | |
| 3244 | InvalidWtf8, | |
| 3104 | 3245 | } || UnexpectedError; |
| 3105 | 3246 | |
| 3106 | 3247 | /// Changes the current working directory of the calling process. |
| 3107 | /// `dir_path` is recommended to be a UTF-8 encoded string. | |
| 3248 | /// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 3249 | /// On WASI, `dir_path` should be encoded as valid UTF-8. | |
| 3250 | /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. | |
| 3108 | 3251 | pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { |
| 3109 | 3252 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3110 | 3253 | @compileError("WASI does not support os.chdir"); |
| 3111 | 3254 | } 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]); | |
| 3116 | 3259 | } else { |
| 3117 | 3260 | const dir_path_c = try toPosixPath(dir_path); |
| 3118 | 3261 | return chdirZ(&dir_path_c); |
| ... | ... | @@ -3120,12 +3263,15 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { |
| 3120 | 3263 | } |
| 3121 | 3264 | |
| 3122 | 3265 | /// Same as `chdir` except the parameter is null-terminated. |
| 3266 | /// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 3267 | /// On WASI, `dir_path` should be encoded as valid UTF-8. | |
| 3268 | /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. | |
| 3123 | 3269 | pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { |
| 3124 | 3270 | 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]); | |
| 3129 | 3275 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3130 | 3276 | return chdir(mem.span(dir_path)); |
| 3131 | 3277 | } |
| ... | ... | @@ -3139,11 +3285,15 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { |
| 3139 | 3285 | .NOENT => return error.FileNotFound, |
| 3140 | 3286 | .NOMEM => return error.SystemResources, |
| 3141 | 3287 | .NOTDIR => return error.NotDir, |
| 3288 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 3289 | return error.InvalidUtf8 | |
| 3290 | else | |
| 3291 | return unexpectedErrno(err), | |
| 3142 | 3292 | else => |err| return unexpectedErrno(err), |
| 3143 | 3293 | } |
| 3144 | 3294 | } |
| 3145 | 3295 | |
| 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. | |
| 3147 | 3297 | pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void { |
| 3148 | 3298 | windows.SetCurrentDirectory(dir_path) catch |err| switch (err) { |
| 3149 | 3299 | error.NoDevice => return error.FileSystem, |
| ... | ... | @@ -3183,7 +3333,11 @@ pub const ReadLinkError = error{ |
| 3183 | 3333 | SystemResources, |
| 3184 | 3334 | NotLink, |
| 3185 | 3335 | NotDir, |
| 3336 | /// WASI-only; file paths must be valid UTF-8. | |
| 3186 | 3337 | InvalidUtf8, |
| 3338 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 3339 | /// https://simonsapin.github.io/wtf-8/ | |
| 3340 | InvalidWtf8, | |
| 3187 | 3341 | BadPathName, |
| 3188 | 3342 | /// Windows-only. This error may occur if the opened reparse point is |
| 3189 | 3343 | /// of unsupported type. |
| ... | ... | @@ -3193,7 +3347,13 @@ pub const ReadLinkError = error{ |
| 3193 | 3347 | } || UnexpectedError; |
| 3194 | 3348 | |
| 3195 | 3349 | /// Read value of a symbolic link. |
| 3350 | /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 3351 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 3352 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 3196 | 3353 | /// The return value is a slice of `out_buffer` from index 0. |
| 3354 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 3355 | /// On WASI, the result is encoded as UTF-8. | |
| 3356 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 3197 | 3357 | pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 3198 | 3358 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3199 | 3359 | return readlinkat(wasi.AT.FDCWD, file_path, out_buffer); |
| ... | ... | @@ -3206,7 +3366,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 3206 | 3366 | } |
| 3207 | 3367 | } |
| 3208 | 3368 | |
| 3209 | /// Windows-only. Same as `readlink` except `file_path` is WTF16 encoded. | |
| 3369 | /// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded. | |
| 3370 | /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 3210 | 3371 | /// See also `readlinkZ`. |
| 3211 | 3372 | pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 3212 | 3373 | return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer); |
| ... | ... | @@ -3215,7 +3376,7 @@ pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 3215 | 3376 | /// Same as `readlink` except `file_path` is null-terminated. |
| 3216 | 3377 | pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 3217 | 3378 | if (builtin.os.tag == .windows) { |
| 3218 | const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path); | |
| 3379 | const file_path_w = try windows.cStrToPrefixedFileW(null, file_path); | |
| 3219 | 3380 | return readlinkW(file_path_w.span(), out_buffer); |
| 3220 | 3381 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3221 | 3382 | return readlink(mem.sliceTo(file_path, 0), out_buffer); |
| ... | ... | @@ -3232,12 +3393,22 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 |
| 3232 | 3393 | .NOENT => return error.FileNotFound, |
| 3233 | 3394 | .NOMEM => return error.SystemResources, |
| 3234 | 3395 | .NOTDIR => return error.NotDir, |
| 3396 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 3397 | return error.InvalidUtf8 | |
| 3398 | else | |
| 3399 | return unexpectedErrno(err), | |
| 3235 | 3400 | else => |err| return unexpectedErrno(err), |
| 3236 | 3401 | } |
| 3237 | 3402 | } |
| 3238 | 3403 | |
| 3239 | 3404 | /// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle. |
| 3405 | /// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 3406 | /// On WASI, `file_path` should be encoded as valid UTF-8. | |
| 3407 | /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. | |
| 3240 | 3408 | /// The return value is a slice of `out_buffer` from index 0. |
| 3409 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 3410 | /// On WASI, the result is encoded as UTF-8. | |
| 3411 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 3241 | 3412 | /// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. |
| 3242 | 3413 | pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 3243 | 3414 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| ... | ... | @@ -3267,11 +3438,13 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read |
| 3267 | 3438 | .NOMEM => return error.SystemResources, |
| 3268 | 3439 | .NOTDIR => return error.NotDir, |
| 3269 | 3440 | .NOTCAPABLE => return error.AccessDenied, |
| 3441 | .ILSEQ => return error.InvalidUtf8, | |
| 3270 | 3442 | else => |err| return unexpectedErrno(err), |
| 3271 | 3443 | } |
| 3272 | 3444 | } |
| 3273 | 3445 | |
| 3274 | /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. | |
| 3446 | /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded. | |
| 3447 | /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 3275 | 3448 | /// See also `readlinkat`. |
| 3276 | 3449 | pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 3277 | 3450 | return windows.ReadLink(dirfd, file_path, out_buffer); |
| ... | ... | @@ -3298,6 +3471,10 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read |
| 3298 | 3471 | .NOENT => return error.FileNotFound, |
| 3299 | 3472 | .NOMEM => return error.SystemResources, |
| 3300 | 3473 | .NOTDIR => return error.NotDir, |
| 3474 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 3475 | return error.InvalidUtf8 | |
| 3476 | else | |
| 3477 | return unexpectedErrno(err), | |
| 3301 | 3478 | else => |err| return unexpectedErrno(err), |
| 3302 | 3479 | } |
| 3303 | 3480 | } |
| ... | ... | @@ -4274,10 +4451,18 @@ pub fn fstat_wasi(fd: fd_t) FStatError!wasi.filestat_t { |
| 4274 | 4451 | } |
| 4275 | 4452 | } |
| 4276 | 4453 | |
| 4277 | pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLinkLoop }; | |
| 4454 | pub const FStatAtError = FStatError || error{ | |
| 4455 | NameTooLong, | |
| 4456 | FileNotFound, | |
| 4457 | SymLinkLoop, | |
| 4458 | /// WASI-only; file paths must be valid UTF-8. | |
| 4459 | InvalidUtf8, | |
| 4460 | }; | |
| 4278 | 4461 | |
| 4279 | 4462 | /// Similar to `fstat`, but returns stat of a resource pointed to by `pathname` |
| 4280 | 4463 | /// which is relative to `dirfd` handle. |
| 4464 | /// On WASI, `pathname` should be encoded as valid UTF-8. | |
| 4465 | /// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. | |
| 4281 | 4466 | /// See also `fstatatZ` and `fstatat_wasi`. |
| 4282 | 4467 | pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { |
| 4283 | 4468 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| ... | ... | @@ -4294,6 +4479,7 @@ pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat |
| 4294 | 4479 | } |
| 4295 | 4480 | |
| 4296 | 4481 | /// WASI-only. Same as `fstatat` but targeting WASI. |
| 4482 | /// `pathname` should be encoded as valid UTF-8. | |
| 4297 | 4483 | /// See also `fstatat`. |
| 4298 | 4484 | pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t) FStatAtError!wasi.filestat_t { |
| 4299 | 4485 | var stat: wasi.filestat_t = undefined; |
| ... | ... | @@ -4308,6 +4494,7 @@ pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t |
| 4308 | 4494 | .NOENT => return error.FileNotFound, |
| 4309 | 4495 | .NOTDIR => return error.FileNotFound, |
| 4310 | 4496 | .NOTCAPABLE => return error.AccessDenied, |
| 4497 | .ILSEQ => return error.InvalidUtf8, | |
| 4311 | 4498 | else => |err| return unexpectedErrno(err), |
| 4312 | 4499 | } |
| 4313 | 4500 | } |
| ... | ... | @@ -4337,6 +4524,10 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S |
| 4337 | 4524 | .LOOP => return error.SymLinkLoop, |
| 4338 | 4525 | .NOENT => return error.FileNotFound, |
| 4339 | 4526 | .NOTDIR => return error.FileNotFound, |
| 4527 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 4528 | return error.InvalidUtf8 | |
| 4529 | else | |
| 4530 | return unexpectedErrno(err), | |
| 4340 | 4531 | else => |err| return unexpectedErrno(err), |
| 4341 | 4532 | } |
| 4342 | 4533 | } |
| ... | ... | @@ -4693,12 +4884,17 @@ pub const AccessError = error{ |
| 4693 | 4884 | FileBusy, |
| 4694 | 4885 | SymLinkLoop, |
| 4695 | 4886 | ReadOnlyFileSystem, |
| 4696 | ||
| 4697 | /// On Windows, file paths must be valid Unicode. | |
| 4887 | /// WASI-only; file paths must be valid UTF-8. | |
| 4698 | 4888 | InvalidUtf8, |
| 4889 | /// Windows-only; file paths provided by the user must be valid WTF-8. | |
| 4890 | /// https://simonsapin.github.io/wtf-8/ | |
| 4891 | InvalidWtf8, | |
| 4699 | 4892 | } || UnexpectedError; |
| 4700 | 4893 | |
| 4701 | 4894 | /// check user's permissions for a file |
| 4895 | /// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 4896 | /// On WASI, `path` should be encoded as valid UTF-8. | |
| 4897 | /// On other platforms, `path` is an opaque sequence of bytes with no particular encoding. | |
| 4702 | 4898 | /// TODO currently this assumes `mode` is `F.OK` on Windows. |
| 4703 | 4899 | pub fn access(path: []const u8, mode: u32) AccessError!void { |
| 4704 | 4900 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -4740,12 +4936,16 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { |
| 4740 | 4936 | .FAULT => unreachable, |
| 4741 | 4937 | .IO => return error.InputOutput, |
| 4742 | 4938 | .NOMEM => return error.SystemResources, |
| 4939 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 4940 | return error.InvalidUtf8 | |
| 4941 | else | |
| 4942 | return unexpectedErrno(err), | |
| 4743 | 4943 | else => |err| return unexpectedErrno(err), |
| 4744 | 4944 | } |
| 4745 | 4945 | } |
| 4746 | 4946 | |
| 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`. | |
| 4749 | 4949 | /// TODO currently this ignores `mode`. |
| 4750 | 4950 | pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!void { |
| 4751 | 4951 | _ = mode; |
| ... | ... | @@ -4762,6 +4962,9 @@ pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!v |
| 4762 | 4962 | } |
| 4763 | 4963 | |
| 4764 | 4964 | /// Check user's permissions for a file, based on an open directory handle. |
| 4965 | /// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 4966 | /// On WASI, `path` should be encoded as valid UTF-8. | |
| 4967 | /// On other platforms, `path` is an opaque sequence of bytes with no particular encoding. | |
| 4765 | 4968 | /// TODO currently this ignores `mode` and `flags` on Windows. |
| 4766 | 4969 | pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void { |
| 4767 | 4970 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -4832,6 +5035,10 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces |
| 4832 | 5035 | .FAULT => unreachable, |
| 4833 | 5036 | .IO => return error.InputOutput, |
| 4834 | 5037 | .NOMEM => return error.SystemResources, |
| 5038 | .ILSEQ => |err| if (builtin.os.tag == .wasi) | |
| 5039 | return error.InvalidUtf8 | |
| 5040 | else | |
| 5041 | return unexpectedErrno(err), | |
| 4835 | 5042 | else => |err| return unexpectedErrno(err), |
| 4836 | 5043 | } |
| 4837 | 5044 | } |
| ... | ... | @@ -5339,8 +5546,9 @@ pub const RealPathError = error{ |
| 5339 | 5546 | /// On WASI, the current CWD may not be associated with an absolute path. |
| 5340 | 5547 | InvalidHandle, |
| 5341 | 5548 | |
| 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, | |
| 5344 | 5552 | |
| 5345 | 5553 | /// On Windows, `\\server` or `\\server\share` was not found. |
| 5346 | 5554 | NetworkNotFound, |
| ... | ... | @@ -5362,8 +5570,12 @@ pub const RealPathError = error{ |
| 5362 | 5570 | /// Return the canonicalized absolute pathname. |
| 5363 | 5571 | /// Expands all symbolic links and resolves references to `.`, `..`, and |
| 5364 | 5572 | /// extra `/` characters in `pathname`. |
| 5573 | /// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 5574 | /// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. | |
| 5365 | 5575 | /// The return value is a slice of `out_buffer`, but not necessarily from the beginning. |
| 5366 | 5576 | /// See also `realpathZ` and `realpathW`. |
| 5577 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 5578 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 5367 | 5579 | /// Calling this function is usually a bug. |
| 5368 | 5580 | pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 5369 | 5581 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -5402,6 +5614,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP |
| 5402 | 5614 | error.WouldBlock => unreachable, |
| 5403 | 5615 | error.FileBusy => unreachable, // not asking for write permissions |
| 5404 | 5616 | error.InvalidHandle => unreachable, // WASI-only |
| 5617 | error.InvalidUtf8 => unreachable, // WASI-only | |
| 5405 | 5618 | else => |e| return e, |
| 5406 | 5619 | }; |
| 5407 | 5620 | defer close(fd); |
| ... | ... | @@ -5425,7 +5638,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP |
| 5425 | 5638 | return mem.sliceTo(result_path, 0); |
| 5426 | 5639 | } |
| 5427 | 5640 | |
| 5428 | /// Same as `realpath` except `pathname` is UTF16LE-encoded. | |
| 5641 | /// Same as `realpath` except `pathname` is WTF16LE-encoded. | |
| 5642 | /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 5429 | 5643 | /// Calling this function is usually a bug. |
| 5430 | 5644 | pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 5431 | 5645 | const w = windows; |
| ... | ... | @@ -5475,6 +5689,8 @@ pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool { |
| 5475 | 5689 | /// This function is very host-specific and is not universally supported by all hosts. |
| 5476 | 5690 | /// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is |
| 5477 | 5691 | /// unsupported on WASI. |
| 5692 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 5693 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 5478 | 5694 | /// Calling this function is usually a bug. |
| 5479 | 5695 | pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 5480 | 5696 | if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) { |
| ... | ... | @@ -5485,10 +5701,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 5485 | 5701 | var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 5486 | 5702 | const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]); |
| 5487 | 5703 | |
| 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); | |
| 5492 | 5705 | return out_buffer[0..end_index]; |
| 5493 | 5706 | }, |
| 5494 | 5707 | .macos, .ios, .watchos, .tvos => { |
| ... | ... | @@ -5512,8 +5725,12 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 5512 | 5725 | |
| 5513 | 5726 | const target = readlinkZ(proc_path, out_buffer) catch |err| { |
| 5514 | 5727 | switch (err) { |
| 5515 | error.UnsupportedReparsePointType => unreachable, // Windows only, | |
| 5516 | 5728 | error.NotLink => unreachable, |
| 5729 | error.BadPathName => unreachable, | |
| 5730 | error.InvalidUtf8 => unreachable, // WASI-only | |
| 5731 | error.InvalidWtf8 => unreachable, // Windows-only | |
| 5732 | error.UnsupportedReparsePointType => unreachable, // Windows-only | |
| 5733 | error.NetworkNotFound => unreachable, // Windows-only | |
| 5517 | 5734 | else => |e| return e, |
| 5518 | 5735 | } |
| 5519 | 5736 | }; |
lib/std/os/windows.zig+51-37| ... | ... | @@ -1,8 +1,8 @@ |
| 1 | 1 | //! This file contains thin wrappers around Windows-specific APIs, with these |
| 2 | 2 | //! specific goals in mind: |
| 3 | 3 | //! * 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. | |
| 6 | 6 | |
| 7 | 7 | const builtin = @import("builtin"); |
| 8 | 8 | const std = @import("../std.zig"); |
| ... | ... | @@ -548,7 +548,6 @@ pub fn WriteFile( |
| 548 | 548 | |
| 549 | 549 | pub const SetCurrentDirectoryError = error{ |
| 550 | 550 | NameTooLong, |
| 551 | InvalidUtf8, | |
| 552 | 551 | FileNotFound, |
| 553 | 552 | NotDir, |
| 554 | 553 | AccessDenied, |
| ... | ... | @@ -587,24 +586,24 @@ pub const GetCurrentDirectoryError = error{ |
| 587 | 586 | }; |
| 588 | 587 | |
| 589 | 588 | /// The result is a slice of `buffer`, indexed from 0. |
| 589 | /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 590 | 590 | pub 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); | |
| 593 | 593 | if (result == 0) { |
| 594 | 594 | switch (kernel32.GetLastError()) { |
| 595 | 595 | else => |err| return unexpectedError(err), |
| 596 | 596 | } |
| 597 | 597 | } |
| 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]; | |
| 601 | 600 | 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| { | |
| 604 | 603 | const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable; |
| 605 | 604 | if (end_index + seq_len >= buffer.len) |
| 606 | 605 | return error.NameTooLong; |
| 607 | end_index += std.unicode.utf8Encode(codepoint, buffer[end_index..]) catch unreachable; | |
| 606 | end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable; | |
| 608 | 607 | } |
| 609 | 608 | return buffer[0..end_index]; |
| 610 | 609 | } |
| ... | ... | @@ -812,6 +811,8 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin |
| 812 | 811 | } |
| 813 | 812 | } |
| 814 | 813 | |
| 814 | /// Asserts that there is enough space is `out_buffer`. | |
| 815 | /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 815 | 816 | fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 { |
| 816 | 817 | const win32_namespace_path = path: { |
| 817 | 818 | if (is_relative) break :path path; |
| ... | ... | @@ -821,7 +822,7 @@ fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u |
| 821 | 822 | }; |
| 822 | 823 | break :path win32_path.span(); |
| 823 | 824 | }; |
| 824 | const out_len = std.unicode.utf16leToUtf8(out_buffer, win32_namespace_path) catch unreachable; | |
| 825 | const out_len = std.unicode.wtf16LeToWtf8(out_buffer, win32_namespace_path); | |
| 825 | 826 | return out_buffer[0..out_len]; |
| 826 | 827 | } |
| 827 | 828 | |
| ... | ... | @@ -1942,13 +1943,13 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool { |
| 1942 | 1943 | if (@inComptime() or builtin.os.tag != .windows) { |
| 1943 | 1944 | // This function compares the strings code unit by code unit (aka u16-to-u16), |
| 1944 | 1945 | // 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 | |
| 1946 | 1947 | // version in the conversion table since only codepoints <= max(u16) are eligible |
| 1947 | 1948 | // for conversion at all. |
| 1948 | 1949 | if (a.len != b.len) return false; |
| 1949 | 1950 | |
| 1950 | 1951 | 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 | |
| 1952 | 1953 | // endianness for the uppercasing |
| 1953 | 1954 | const a_c_native = std.mem.littleToNative(u16, a_c); |
| 1954 | 1955 | const b_c_native = std.mem.littleToNative(u16, b_c); |
| ... | ... | @@ -1975,18 +1976,18 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool { |
| 1975 | 1976 | return ntdll.RtlEqualUnicodeString(&a_string, &b_string, TRUE) == TRUE; |
| 1976 | 1977 | } |
| 1977 | 1978 | |
| 1978 | /// Compares two UTF-8 strings using the equivalent functionality of | |
| 1979 | /// Compares two WTF-8 strings using the equivalent functionality of | |
| 1979 | 1980 | /// `RtlEqualUnicodeString` (with case insensitive comparison enabled). |
| 1980 | 1981 | /// This function can be called on any target. |
| 1981 | /// Assumes `a` and `b` are valid UTF-8. | |
| 1982 | pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool { | |
| 1982 | /// Assumes `a` and `b` are valid WTF-8. | |
| 1983 | pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool { | |
| 1983 | 1984 | // A length equality check is not possible here because there are |
| 1984 | 1985 | // some codepoints that have a different length uppercase UTF-8 representations |
| 1985 | 1986 | // than their lowercase counterparts, e.g. U+0250 (2 bytes) <-> U+2C6F (3 bytes). |
| 1986 | 1987 | // There are 7 such codepoints in the uppercase data used by Windows. |
| 1987 | 1988 | |
| 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(); | |
| 1990 | 1991 | |
| 1991 | 1992 | // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a |
| 1992 | 1993 | // redundant copy of the uppercase data. |
| ... | ... | @@ -1996,8 +1997,8 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool { |
| 1996 | 1997 | }; |
| 1997 | 1998 | |
| 1998 | 1999 | 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; | |
| 2001 | 2002 | |
| 2002 | 2003 | if (a_cp <= std.math.maxInt(u16) and b_cp <= std.math.maxInt(u16)) { |
| 2003 | 2004 | if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) { |
| ... | ... | @@ -2008,26 +2009,26 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool { |
| 2008 | 2009 | } |
| 2009 | 2010 | } |
| 2010 | 2011 | // 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; | |
| 2012 | 2013 | |
| 2013 | 2014 | return true; |
| 2014 | 2015 | } |
| 2015 | 2016 | |
| 2016 | 2017 | fn testEqlIgnoreCase(comptime expect_eql: bool, comptime a: []const u8, comptime b: []const u8) !void { |
| 2017 | try std.testing.expectEqual(expect_eql, eqlIgnoreCaseUtf8(a, b)); | |
| 2018 | try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWtf8(a, b)); | |
| 2018 | 2019 | try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWTF16( |
| 2019 | 2020 | std.unicode.utf8ToUtf16LeStringLiteral(a), |
| 2020 | 2021 | std.unicode.utf8ToUtf16LeStringLiteral(b), |
| 2021 | 2022 | )); |
| 2022 | 2023 | |
| 2023 | try comptime std.testing.expect(expect_eql == eqlIgnoreCaseUtf8(a, b)); | |
| 2024 | try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWtf8(a, b)); | |
| 2024 | 2025 | try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWTF16( |
| 2025 | 2026 | std.unicode.utf8ToUtf16LeStringLiteral(a), |
| 2026 | 2027 | std.unicode.utf8ToUtf16LeStringLiteral(b), |
| 2027 | 2028 | )); |
| 2028 | 2029 | } |
| 2029 | 2030 | |
| 2030 | test "eqlIgnoreCaseWTF16/Utf8" { | |
| 2031 | test "eqlIgnoreCaseWTF16/Wtf8" { | |
| 2031 | 2032 | try testEqlIgnoreCase(true, "\x01 a B Λ ɐ", "\x01 A b λ Ɐ"); |
| 2032 | 2033 | // does not do case-insensitive comparison for codepoints >= U+10000 |
| 2033 | 2034 | try testEqlIgnoreCase(false, "𐓏", "𐓷"); |
| ... | ... | @@ -2117,20 +2118,32 @@ pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize { |
| 2117 | 2118 | return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]); |
| 2118 | 2119 | } |
| 2119 | 2120 | |
| 2121 | pub const Wtf8ToPrefixedFileWError = error{InvalidWtf8} || Wtf16ToPrefixedFileWError; | |
| 2122 | ||
| 2120 | 2123 | /// Same as `sliceToPrefixedFileW` but accepts a pointer |
| 2121 | /// to a null-terminated path. | |
| 2122 | pub 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/ | |
| 2126 | pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWError!PathSpace { | |
| 2123 | 2127 | return sliceToPrefixedFileW(dir, mem.sliceTo(s, 0)); |
| 2124 | 2128 | } |
| 2125 | 2129 | |
| 2126 | /// Same as `wToPrefixedFileW` but accepts a UTF-8 encoded path. | |
| 2127 | pub 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/ | |
| 2132 | pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace { | |
| 2128 | 2133 | var temp_path: PathSpace = undefined; |
| 2129 | temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, path); | |
| 2134 | temp_path.len = try std.unicode.wtf8ToWtf16Le(&temp_path.data, path); | |
| 2130 | 2135 | temp_path.data[temp_path.len] = 0; |
| 2131 | 2136 | return wToPrefixedFileW(dir, temp_path.span()); |
| 2132 | 2137 | } |
| 2133 | 2138 | |
| 2139 | pub const Wtf16ToPrefixedFileWError = error{ | |
| 2140 | AccessDenied, | |
| 2141 | BadPathName, | |
| 2142 | FileNotFound, | |
| 2143 | NameTooLong, | |
| 2144 | Unexpected, | |
| 2145 | }; | |
| 2146 | ||
| 2134 | 2147 | /// Converts the `path` to WTF16, null-terminated. If the path contains any |
| 2135 | 2148 | /// namespace prefix, or is anything but a relative path (rooted, drive relative, |
| 2136 | 2149 | /// etc) the result will have the NT-style prefix `\??\`. |
| ... | ... | @@ -2142,7 +2155,7 @@ pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace { |
| 2142 | 2155 | /// is non-null, or the CWD if it is null. |
| 2143 | 2156 | /// - Special case device names like COM1, NUL, etc are not handled specially (TODO) |
| 2144 | 2157 | /// - . and space are not stripped from the end of relative paths (potential TODO) |
| 2145 | pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) !PathSpace { | |
| 2158 | pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!PathSpace { | |
| 2146 | 2159 | const nt_prefix = [_]u16{ '\\', '?', '?', '\\' }; |
| 2147 | 2160 | switch (getNamespacePrefix(u16, path)) { |
| 2148 | 2161 | // TODO: Figure out a way to design an API that can avoid the copy for .nt, |
| ... | ... | @@ -2312,7 +2325,7 @@ pub const NamespacePrefix = enum { |
| 2312 | 2325 | nt, |
| 2313 | 2326 | }; |
| 2314 | 2327 | |
| 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. | |
| 2316 | 2329 | pub fn getNamespacePrefix(comptime T: type, path: []const T) NamespacePrefix { |
| 2317 | 2330 | if (path.len < 4) return .none; |
| 2318 | 2331 | var all_backslash = switch (mem.littleToNative(T, path[0])) { |
| ... | ... | @@ -2366,7 +2379,7 @@ pub const UnprefixedPathType = enum { |
| 2366 | 2379 | |
| 2367 | 2380 | /// Get the path type of a path that is known to not have any namespace prefixes |
| 2368 | 2381 | /// (`\\?\`, `\\.\`, `\??\`). |
| 2369 | /// If `T` is `u16`, then `path` should be encoded as UTF-16LE. | |
| 2382 | /// If `T` is `u16`, then `path` should be encoded as WTF-16LE. | |
| 2370 | 2383 | pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathType { |
| 2371 | 2384 | if (path.len < 1) return .relative; |
| 2372 | 2385 | |
| ... | ... | @@ -2420,7 +2433,7 @@ test getUnprefixedPathType { |
| 2420 | 2433 | /// Functionality is based on the ReactOS test cases found here: |
| 2421 | 2434 | /// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c |
| 2422 | 2435 | /// |
| 2423 | /// `path` should be encoded as UTF-16LE. | |
| 2436 | /// `path` should be encoded as WTF-16LE. | |
| 2424 | 2437 | pub fn ntToWin32Namespace(path: []const u16) !PathSpace { |
| 2425 | 2438 | if (path.len > PATH_MAX_WIDE) return error.NameTooLong; |
| 2426 | 2439 | |
| ... | ... | @@ -2530,7 +2543,6 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError { |
| 2530 | 2543 | if (std.os.unexpected_error_tracing) { |
| 2531 | 2544 | // 614 is the length of the longest windows error description |
| 2532 | 2545 | var buf_wstr: [614]WCHAR = undefined; |
| 2533 | var buf_utf8: [614]u8 = undefined; | |
| 2534 | 2546 | const len = kernel32.FormatMessageW( |
| 2535 | 2547 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
| 2536 | 2548 | null, |
| ... | ... | @@ -2540,8 +2552,10 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError { |
| 2540 | 2552 | buf_wstr.len, |
| 2541 | 2553 | null, |
| 2542 | 2554 | ); |
| 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 | }); | |
| 2545 | 2559 | std.debug.dumpCurrentStackTrace(@returnAddress()); |
| 2546 | 2560 | } |
| 2547 | 2561 | return error.Unexpected; |
lib/std/os/windows/test.zig+2-2| ... | ... | @@ -30,7 +30,7 @@ fn testToPrefixedFileNoOracle(comptime path: []const u8, comptime expected_path: |
| 30 | 30 | const expected_path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(expected_path); |
| 31 | 31 | const actual_path = try windows.wToPrefixedFileW(null, path_utf16); |
| 32 | 32 | std.testing.expectEqualSlices(u16, expected_path_utf16, actual_path.span()) catch |e| { |
| 33 | std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16le(actual_path.span()), std.unicode.fmtUtf16le(expected_path_utf16) }); | |
| 33 | std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16Le(actual_path.span()), std.unicode.fmtUtf16le(expected_path_utf16) }); | |
| 34 | 34 | return e; |
| 35 | 35 | }; |
| 36 | 36 | } |
| ... | ... | @@ -48,7 +48,7 @@ fn testToPrefixedFileOnlyOracle(comptime path: []const u8) !void { |
| 48 | 48 | const zig_result = try windows.wToPrefixedFileW(null, path_utf16); |
| 49 | 49 | const win32_api_result = try RtlDosPathNameToNtPathName_U(path_utf16); |
| 50 | 50 | std.testing.expectEqualSlices(u16, win32_api_result.span(), zig_result.span()) catch |e| { |
| 51 | std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16le(zig_result.span()), std.unicode.fmtUtf16le(win32_api_result.span()) }); | |
| 51 | std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16Le(zig_result.span()), std.unicode.fmtUtf16le(win32_api_result.span()) }); | |
| 52 | 52 | return e; |
| 53 | 53 | }; |
| 54 | 54 | } |
lib/std/process.zig+92-64| ... | ... | @@ -16,11 +16,15 @@ pub const changeCurDir = os.chdir; |
| 16 | 16 | pub const changeCurDirC = os.chdirC; |
| 17 | 17 | |
| 18 | 18 | /// The result is a slice of `out_buffer`, from index `0`. |
| 19 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 20 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 19 | 21 | pub fn getCwd(out_buffer: []u8) ![]u8 { |
| 20 | 22 | return os.getcwd(out_buffer); |
| 21 | 23 | } |
| 22 | 24 | |
| 23 | 25 | /// Caller must free the returned memory. |
| 26 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 27 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 24 | 28 | pub fn getCwdAlloc(allocator: Allocator) ![]u8 { |
| 25 | 29 | // The use of MAX_PATH_BYTES here is just a heuristic: most paths will fit |
| 26 | 30 | // in stack_buf, avoiding an extra allocation in the common case. |
| ... | ... | @@ -76,7 +80,7 @@ pub const EnvMap = struct { |
| 76 | 80 | _ = self; |
| 77 | 81 | if (builtin.os.tag == .windows) { |
| 78 | 82 | var h = std.hash.Wyhash.init(0); |
| 79 | var it = std.unicode.Utf8View.initUnchecked(s).iterator(); | |
| 83 | var it = std.unicode.Wtf8View.initUnchecked(s).iterator(); | |
| 80 | 84 | while (it.nextCodepoint()) |cp| { |
| 81 | 85 | const cp_upper = upcase(cp); |
| 82 | 86 | h.update(&[_]u8{ |
| ... | ... | @@ -93,8 +97,8 @@ pub const EnvMap = struct { |
| 93 | 97 | pub fn eql(self: @This(), a: []const u8, b: []const u8) bool { |
| 94 | 98 | _ = self; |
| 95 | 99 | 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(); | |
| 98 | 102 | while (true) { |
| 99 | 103 | const c_a = it_a.nextCodepoint() orelse break; |
| 100 | 104 | const c_b = it_b.nextCodepoint() orelse return false; |
| ... | ... | @@ -129,8 +133,9 @@ pub const EnvMap = struct { |
| 129 | 133 | /// Same as `put` but the key and value become owned by the EnvMap rather |
| 130 | 134 | /// than being copied. |
| 131 | 135 | /// If `putMove` fails, the ownership of key and value does not transfer. |
| 132 | /// On Windows `key` must be a valid UTF-8 string. | |
| 136 | /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string. | |
| 133 | 137 | pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void { |
| 138 | assert(std.unicode.wtf8ValidateSlice(key)); | |
| 134 | 139 | const get_or_put = try self.hash_map.getOrPut(key); |
| 135 | 140 | if (get_or_put.found_existing) { |
| 136 | 141 | self.free(get_or_put.key_ptr.*); |
| ... | ... | @@ -141,8 +146,9 @@ pub const EnvMap = struct { |
| 141 | 146 | } |
| 142 | 147 | |
| 143 | 148 | /// `key` and `value` are copied into the EnvMap. |
| 144 | /// On Windows `key` must be a valid UTF-8 string. | |
| 149 | /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string. | |
| 145 | 150 | pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void { |
| 151 | assert(std.unicode.wtf8ValidateSlice(key)); | |
| 146 | 152 | const value_copy = try self.copy(value); |
| 147 | 153 | errdefer self.free(value_copy); |
| 148 | 154 | const get_or_put = try self.hash_map.getOrPut(key); |
| ... | ... | @@ -159,23 +165,26 @@ pub const EnvMap = struct { |
| 159 | 165 | |
| 160 | 166 | /// Find the address of the value associated with a key. |
| 161 | 167 | /// The returned pointer is invalidated if the map resizes. |
| 162 | /// On Windows `key` must be a valid UTF-8 string. | |
| 168 | /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string. | |
| 163 | 169 | pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 { |
| 170 | assert(std.unicode.wtf8ValidateSlice(key)); | |
| 164 | 171 | return self.hash_map.getPtr(key); |
| 165 | 172 | } |
| 166 | 173 | |
| 167 | 174 | /// Return the map's copy of the value associated with |
| 168 | 175 | /// a key. The returned string is invalidated if this |
| 169 | 176 | /// key is removed from the map. |
| 170 | /// On Windows `key` must be a valid UTF-8 string. | |
| 177 | /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string. | |
| 171 | 178 | pub fn get(self: EnvMap, key: []const u8) ?[]const u8 { |
| 179 | assert(std.unicode.wtf8ValidateSlice(key)); | |
| 172 | 180 | return self.hash_map.get(key); |
| 173 | 181 | } |
| 174 | 182 | |
| 175 | 183 | /// Removes the item from the map and frees its value. |
| 176 | 184 | /// This invalidates the value returned by get() for this key. |
| 177 | /// On Windows `key` must be a valid UTF-8 string. | |
| 185 | /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string. | |
| 178 | 186 | pub fn remove(self: *EnvMap, key: []const u8) void { |
| 187 | assert(std.unicode.wtf8ValidateSlice(key)); | |
| 179 | 188 | const kv = self.hash_map.fetchRemove(key) orelse return; |
| 180 | 189 | self.free(kv.key); |
| 181 | 190 | self.free(kv.value); |
| ... | ... | @@ -239,18 +248,34 @@ test "EnvMap" { |
| 239 | 248 | |
| 240 | 249 | try testing.expectEqual(@as(EnvMap.Size, 1), env.count()); |
| 241 | 250 | |
| 242 | // test Unicode case-insensitivity on Windows | |
| 243 | 251 | if (builtin.os.tag == .windows) { |
| 252 | // test Unicode case-insensitivity on Windows | |
| 244 | 253 | try env.put("КИРиллИЦА", "something else"); |
| 245 | 254 | try testing.expectEqualStrings("something else", env.get("кириллица").?); |
| 255 | ||
| 256 | // and WTF-8 that's not valid UTF-8 | |
| 257 | const wtf8_with_surrogate_pair = try std.unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{ | |
| 258 | std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate | |
| 259 | }); | |
| 260 | defer testing.allocator.free(wtf8_with_surrogate_pair); | |
| 261 | ||
| 262 | try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair); | |
| 263 | try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); | |
| 246 | 264 | } |
| 247 | 265 | } |
| 248 | 266 | |
| 267 | pub const GetEnvMapError = error{ | |
| 268 | OutOfMemory, | |
| 269 | /// WASI-only. `environ_sizes_get` or `environ_get` | |
| 270 | /// failed for an unexpected reason. | |
| 271 | Unexpected, | |
| 272 | }; | |
| 273 | ||
| 249 | 274 | /// Returns a snapshot of the environment variables of the current process. |
| 250 | 275 | /// Any modifications to the resulting EnvMap will not be reflected in the environment, and |
| 251 | 276 | /// likewise, any future modifications to the environment will not be reflected in the EnvMap. |
| 252 | 277 | /// Caller owns resulting `EnvMap` and should call its `deinit` fn when done. |
| 253 | pub fn getEnvMap(allocator: Allocator) !EnvMap { | |
| 278 | pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap { | |
| 254 | 279 | var result = EnvMap.init(allocator); |
| 255 | 280 | errdefer result.deinit(); |
| 256 | 281 | |
| ... | ... | @@ -269,7 +294,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap { |
| 269 | 294 | |
| 270 | 295 | while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} |
| 271 | 296 | const key_w = ptr[key_start..i]; |
| 272 | const key = try std.unicode.utf16leToUtf8Alloc(allocator, key_w); | |
| 297 | const key = try std.unicode.wtf16LeToWtf8Alloc(allocator, key_w); | |
| 273 | 298 | errdefer allocator.free(key); |
| 274 | 299 | |
| 275 | 300 | if (ptr[i] == '=') i += 1; |
| ... | ... | @@ -277,7 +302,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap { |
| 277 | 302 | const value_start = i; |
| 278 | 303 | while (ptr[i] != 0) : (i += 1) {} |
| 279 | 304 | const value_w = ptr[value_start..i]; |
| 280 | const value = try std.unicode.utf16leToUtf8Alloc(allocator, value_w); | |
| 305 | const value = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_w); | |
| 281 | 306 | errdefer allocator.free(value); |
| 282 | 307 | |
| 283 | 308 | i += 1; // skip over null byte |
| ... | ... | @@ -355,25 +380,28 @@ pub const GetEnvVarOwnedError = error{ |
| 355 | 380 | OutOfMemory, |
| 356 | 381 | EnvironmentVariableNotFound, |
| 357 | 382 | |
| 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, | |
| 360 | 386 | }; |
| 361 | 387 | |
| 362 | 388 | /// Caller must free returned memory. |
| 389 | /// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/), | |
| 390 | /// then `error.InvalidWtf8` is returned. | |
| 391 | /// On Windows, the value is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 392 | /// On other platforms, the value is an opaque sequence of bytes with no particular encoding. | |
| 363 | 393 | pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { |
| 364 | 394 | if (builtin.os.tag == .windows) { |
| 365 | 395 | const result_w = blk: { |
| 366 | const key_w = try std.unicode.utf8ToUtf16LeWithNull(allocator, key); | |
| 367 | defer allocator.free(key_w); | |
| 396 | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); | |
| 397 | const stack_allocator = stack_alloc.get(); | |
| 398 | const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); | |
| 399 | defer stack_allocator.free(key_w); | |
| 368 | 400 | |
| 369 | 401 | break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound; |
| 370 | 402 | }; |
| 371 | return std.unicode.utf16leToUtf8Alloc(allocator, result_w) catch |err| switch (err) { | |
| 372 | error.DanglingSurrogateHalf => return error.InvalidUtf8, | |
| 373 | error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8, | |
| 374 | error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8, | |
| 375 | else => |e| return e, | |
| 376 | }; | |
| 403 | // wtf16LeToWtf8Alloc can only fail with OutOfMemory | |
| 404 | return std.unicode.wtf16LeToWtf8Alloc(allocator, result_w); | |
| 377 | 405 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 378 | 406 | var envmap = getEnvMap(allocator) catch return error.OutOfMemory; |
| 379 | 407 | defer envmap.deinit(); |
| ... | ... | @@ -385,6 +413,7 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError |
| 385 | 413 | } |
| 386 | 414 | } |
| 387 | 415 | |
| 416 | /// On Windows, `key` must be valid UTF-8. | |
| 388 | 417 | pub fn hasEnvVarConstant(comptime key: []const u8) bool { |
| 389 | 418 | if (builtin.os.tag == .windows) { |
| 390 | 419 | const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key); |
| ... | ... | @@ -396,11 +425,22 @@ pub fn hasEnvVarConstant(comptime key: []const u8) bool { |
| 396 | 425 | } |
| 397 | 426 | } |
| 398 | 427 | |
| 399 | pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool { | |
| 428 | pub const HasEnvVarError = error{ | |
| 429 | OutOfMemory, | |
| 430 | ||
| 431 | /// On Windows, environment variable keys provided by the user must be valid WTF-8. | |
| 432 | /// https://simonsapin.github.io/wtf-8/ | |
| 433 | InvalidWtf8, | |
| 434 | }; | |
| 435 | ||
| 436 | /// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/), | |
| 437 | /// then `error.InvalidWtf8` is returned. | |
| 438 | pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { | |
| 400 | 439 | if (builtin.os.tag == .windows) { |
| 401 | 440 | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); |
| 402 | const key_w = try std.unicode.utf8ToUtf16LeWithNull(stack_alloc.get(), key); | |
| 403 | defer stack_alloc.allocator.free(key_w); | |
| 441 | const stack_allocator = stack_alloc.get(); | |
| 442 | const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); | |
| 443 | defer stack_allocator.free(key_w); | |
| 404 | 444 | return std.os.getenvW(key_w) != null; |
| 405 | 445 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 406 | 446 | var envmap = getEnvMap(allocator) catch return error.OutOfMemory; |
| ... | ... | @@ -411,9 +451,22 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool |
| 411 | 451 | } |
| 412 | 452 | } |
| 413 | 453 | |
| 414 | test "os.getEnvVarOwned" { | |
| 415 | const ga = std.testing.allocator; | |
| 416 | try testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV")); | |
| 454 | test getEnvVarOwned { | |
| 455 | try testing.expectError( | |
| 456 | error.EnvironmentVariableNotFound, | |
| 457 | getEnvVarOwned(std.testing.allocator, "BADENV"), | |
| 458 | ); | |
| 459 | } | |
| 460 | ||
| 461 | test hasEnvVarConstant { | |
| 462 | if (builtin.os.tag == .wasi and !builtin.link_libc) return error.SkipZigTest; | |
| 463 | ||
| 464 | try testing.expect(!hasEnvVarConstant("BADENV")); | |
| 465 | } | |
| 466 | ||
| 467 | test hasEnvVar { | |
| 468 | const has_env = try hasEnvVar(std.testing.allocator, "BADENV"); | |
| 469 | try testing.expect(!has_env); | |
| 417 | 470 | } |
| 418 | 471 | |
| 419 | 472 | pub const ArgIteratorPosix = struct { |
| ... | ... | @@ -531,6 +584,7 @@ pub const ArgIteratorWasi = struct { |
| 531 | 584 | pub const ArgIteratorWindows = struct { |
| 532 | 585 | allocator: Allocator, |
| 533 | 586 | /// Owned by the iterator. |
| 587 | /// Encoded as WTF-8. | |
| 534 | 588 | cmd_line: []const u8, |
| 535 | 589 | index: usize = 0, |
| 536 | 590 | /// Owned by the iterator. Long enough to hold the entire `cmd_line` plus a null terminator. |
| ... | ... | @@ -538,20 +592,14 @@ pub const ArgIteratorWindows = struct { |
| 538 | 592 | start: usize = 0, |
| 539 | 593 | end: usize = 0, |
| 540 | 594 | |
| 541 | pub const InitError = error{ OutOfMemory, InvalidCmdLine }; | |
| 595 | pub const InitError = error{OutOfMemory}; | |
| 542 | 596 | |
| 543 | /// `cmd_line_w` *must* be an UTF16-LE-encoded string. | |
| 597 | /// `cmd_line_w` *must* be a WTF16-LE-encoded string. | |
| 544 | 598 | /// |
| 545 | /// The iterator makes a copy of `cmd_line_w` converted UTF-8 and keeps it; it does *not* take | |
| 599 | /// The iterator makes a copy of `cmd_line_w` converted WTF-8 and keeps it; it does *not* take | |
| 546 | 600 | /// ownership of `cmd_line_w`. |
| 547 | 601 | pub fn init(allocator: Allocator, cmd_line_w: [*:0]const u16) InitError!ArgIteratorWindows { |
| 548 | const cmd_line = std.unicode.utf16leToUtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0)) catch |err| switch (err) { | |
| 549 | error.DanglingSurrogateHalf, | |
| 550 | error.ExpectedSecondSurrogateHalf, | |
| 551 | error.UnexpectedSecondSurrogateHalf, | |
| 552 | => return error.InvalidCmdLine, | |
| 553 | error.OutOfMemory => return error.OutOfMemory, | |
| 554 | }; | |
| 602 | const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0)); | |
| 555 | 603 | errdefer allocator.free(cmd_line); |
| 556 | 604 | |
| 557 | 605 | const buffer = try allocator.alloc(u8, cmd_line.len + 1); |
| ... | ... | @@ -566,6 +614,7 @@ pub const ArgIteratorWindows = struct { |
| 566 | 614 | |
| 567 | 615 | /// Returns the next argument and advances the iterator. Returns `null` if at the end of the |
| 568 | 616 | /// command-line string. The iterator owns the returned slice. |
| 617 | /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 569 | 618 | pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 { |
| 570 | 619 | return self.nextWithStrategy(next_strategy); |
| 571 | 620 | } |
| ... | ... | @@ -777,7 +826,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type { |
| 777 | 826 | pub const Self = @This(); |
| 778 | 827 | |
| 779 | 828 | pub const InitError = error{OutOfMemory}; |
| 780 | pub const InitUtf16leError = error{ OutOfMemory, InvalidCmdLine }; | |
| 781 | 829 | |
| 782 | 830 | /// cmd_line_utf8 MUST remain valid and constant while using this instance |
| 783 | 831 | pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self { |
| ... | ... | @@ -805,30 +853,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type { |
| 805 | 853 | }; |
| 806 | 854 | } |
| 807 | 855 | |
| 808 | /// cmd_line_utf16le MUST be encoded UTF16-LE, and is converted to UTF-8 in an internal buffer | |
| 809 | pub fn initUtf16le(allocator: Allocator, cmd_line_utf16le: [*:0]const u16) InitUtf16leError!Self { | |
| 810 | const utf16le_slice = mem.sliceTo(cmd_line_utf16le, 0); | |
| 811 | const cmd_line = std.unicode.utf16leToUtf8Alloc(allocator, utf16le_slice) catch |err| switch (err) { | |
| 812 | error.ExpectedSecondSurrogateHalf, | |
| 813 | error.DanglingSurrogateHalf, | |
| 814 | error.UnexpectedSecondSurrogateHalf, | |
| 815 | => return error.InvalidCmdLine, | |
| 816 | ||
| 817 | error.OutOfMemory => return error.OutOfMemory, | |
| 818 | }; | |
| 819 | errdefer allocator.free(cmd_line); | |
| 820 | ||
| 821 | const buffer = try allocator.alloc(u8, cmd_line.len + 1); | |
| 822 | errdefer allocator.free(buffer); | |
| 823 | ||
| 824 | return Self{ | |
| 825 | .allocator = allocator, | |
| 826 | .cmd_line = cmd_line, | |
| 827 | .free_cmd_line_on_deinit = true, | |
| 828 | .buffer = buffer, | |
| 829 | }; | |
| 830 | } | |
| 831 | ||
| 832 | 856 | // Skips over whitespace in the cmd_line. |
| 833 | 857 | // Returns false if the terminating sentinel is reached, true otherwise. |
| 834 | 858 | // Also skips over comments (if supported). |
| ... | ... | @@ -1021,6 +1045,8 @@ pub const ArgIterator = struct { |
| 1021 | 1045 | |
| 1022 | 1046 | /// Get the next argument. Returns 'null' if we are at the end. |
| 1023 | 1047 | /// Returned slice is pointing to the iterator's internal buffer. |
| 1048 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1049 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 1024 | 1050 | pub fn next(self: *ArgIterator) ?([:0]const u8) { |
| 1025 | 1051 | return self.inner.next(); |
| 1026 | 1052 | } |
| ... | ... | @@ -1057,6 +1083,8 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator |
| 1057 | 1083 | } |
| 1058 | 1084 | |
| 1059 | 1085 | /// Caller must call argsFree on result. |
| 1086 | /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 1087 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | |
| 1060 | 1088 | pub fn argsAlloc(allocator: Allocator) ![][:0]u8 { |
| 1061 | 1089 | // TODO refactor to only make 1 allocation. |
| 1062 | 1090 | var it = try argsWithAllocator(allocator); |
| ... | ... | @@ -1201,7 +1229,7 @@ test "ArgIteratorWindows" { |
| 1201 | 1229 | } |
| 1202 | 1230 | |
| 1203 | 1231 | fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void { |
| 1204 | const cmd_line_w = try std.unicode.utf8ToUtf16LeWithNull(testing.allocator, cmd_line); | |
| 1232 | const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line); | |
| 1205 | 1233 | defer testing.allocator.free(cmd_line_w); |
| 1206 | 1234 | |
| 1207 | 1235 | // next |
lib/std/unicode.zig+914-104| ... | ... | @@ -39,7 +39,16 @@ pub fn utf8ByteSequenceLength(first_byte: u8) !u3 { |
| 39 | 39 | /// out: the out buffer to write to. Must have a len >= utf8CodepointSequenceLength(c). |
| 40 | 40 | /// Errors: if c cannot be encoded in UTF-8. |
| 41 | 41 | /// Returns: the number of bytes written to out. |
| 42 | pub fn utf8Encode(c: u21, out: []u8) !u3 { | |
| 42 | pub fn utf8Encode(c: u21, out: []u8) error{ Utf8CannotEncodeSurrogateHalf, CodepointTooLarge }!u3 { | |
| 43 | return utf8EncodeImpl(c, out, .cannot_encode_surrogate_half); | |
| 44 | } | |
| 45 | ||
| 46 | const Surrogates = enum { | |
| 47 | cannot_encode_surrogate_half, | |
| 48 | can_encode_surrogate_half, | |
| 49 | }; | |
| 50 | ||
| 51 | fn utf8EncodeImpl(c: u21, out: []u8, comptime surrogates: Surrogates) !u3 { | |
| 43 | 52 | const length = try utf8CodepointSequenceLength(c); |
| 44 | 53 | assert(out.len >= length); |
| 45 | 54 | switch (length) { |
| ... | ... | @@ -53,7 +62,9 @@ pub fn utf8Encode(c: u21, out: []u8) !u3 { |
| 53 | 62 | out[1] = @as(u8, @intCast(0b10000000 | (c & 0b111111))); |
| 54 | 63 | }, |
| 55 | 64 | 3 => { |
| 56 | if (0xd800 <= c and c <= 0xdfff) return error.Utf8CannotEncodeSurrogateHalf; | |
| 65 | if (surrogates == .cannot_encode_surrogate_half and isSurrogateCodepoint(c)) { | |
| 66 | return error.Utf8CannotEncodeSurrogateHalf; | |
| 67 | } | |
| 57 | 68 | out[0] = @as(u8, @intCast(0b11100000 | (c >> 12))); |
| 58 | 69 | out[1] = @as(u8, @intCast(0b10000000 | ((c >> 6) & 0b111111))); |
| 59 | 70 | out[2] = @as(u8, @intCast(0b10000000 | (c & 0b111111))); |
| ... | ... | @@ -116,12 +127,22 @@ pub fn utf8Decode2(bytes: []const u8) Utf8Decode2Error!u21 { |
| 116 | 127 | return value; |
| 117 | 128 | } |
| 118 | 129 | |
| 119 | const Utf8Decode3Error = error{ | |
| 120 | Utf8ExpectedContinuation, | |
| 121 | Utf8OverlongEncoding, | |
| 130 | const Utf8Decode3Error = Utf8Decode3AllowSurrogateHalfError || error{ | |
| 122 | 131 | Utf8EncodesSurrogateHalf, |
| 123 | 132 | }; |
| 124 | 133 | pub fn utf8Decode3(bytes: []const u8) Utf8Decode3Error!u21 { |
| 134 | const value = try utf8Decode3AllowSurrogateHalf(bytes); | |
| 135 | ||
| 136 | if (0xd800 <= value and value <= 0xdfff) return error.Utf8EncodesSurrogateHalf; | |
| 137 | ||
| 138 | return value; | |
| 139 | } | |
| 140 | ||
| 141 | const Utf8Decode3AllowSurrogateHalfError = error{ | |
| 142 | Utf8ExpectedContinuation, | |
| 143 | Utf8OverlongEncoding, | |
| 144 | }; | |
| 145 | pub fn utf8Decode3AllowSurrogateHalf(bytes: []const u8) Utf8Decode3AllowSurrogateHalfError!u21 { | |
| 125 | 146 | assert(bytes.len == 3); |
| 126 | 147 | assert(bytes[0] & 0b11110000 == 0b11100000); |
| 127 | 148 | var value: u21 = bytes[0] & 0b00001111; |
| ... | ... | @@ -135,7 +156,6 @@ pub fn utf8Decode3(bytes: []const u8) Utf8Decode3Error!u21 { |
| 135 | 156 | value |= bytes[2] & 0b00111111; |
| 136 | 157 | |
| 137 | 158 | if (value < 0x800) return error.Utf8OverlongEncoding; |
| 138 | if (0xd800 <= value and value <= 0xdfff) return error.Utf8EncodesSurrogateHalf; | |
| 139 | 159 | |
| 140 | 160 | return value; |
| 141 | 161 | } |
| ... | ... | @@ -213,6 +233,10 @@ pub fn utf8CountCodepoints(s: []const u8) !usize { |
| 213 | 233 | |
| 214 | 234 | /// Returns true if the input consists entirely of UTF-8 codepoints |
| 215 | 235 | pub fn utf8ValidateSlice(input: []const u8) bool { |
| 236 | return utf8ValidateSliceImpl(input, .cannot_encode_surrogate_half); | |
| 237 | } | |
| 238 | ||
| 239 | fn utf8ValidateSliceImpl(input: []const u8, comptime surrogates: Surrogates) bool { | |
| 216 | 240 | var remaining = input; |
| 217 | 241 | |
| 218 | 242 | const chunk_len = std.simd.suggestVectorLength(u8) orelse 1; |
| ... | ... | @@ -240,9 +264,15 @@ pub fn utf8ValidateSlice(input: []const u8) bool { |
| 240 | 264 | const xx = 0xF1; // invalid: size 1 |
| 241 | 265 | const as = 0xF0; // ASCII: size 1 |
| 242 | 266 | const s1 = 0x02; // accept 0, size 2 |
| 243 | const s2 = 0x13; // accept 1, size 3 | |
| 267 | const s2 = switch (surrogates) { | |
| 268 | .cannot_encode_surrogate_half => 0x13, // accept 1, size 3 | |
| 269 | .can_encode_surrogate_half => 0x03, // accept 0, size 3 | |
| 270 | }; | |
| 244 | 271 | const s3 = 0x03; // accept 0, size 3 |
| 245 | const s4 = 0x23; // accept 2, size 3 | |
| 272 | const s4 = switch (surrogates) { | |
| 273 | .cannot_encode_surrogate_half => 0x23, // accept 2, size 3 | |
| 274 | .can_encode_surrogate_half => 0x03, // accept 0, size 3 | |
| 275 | }; | |
| 246 | 276 | const s5 = 0x34; // accept 3, size 4 |
| 247 | 277 | const s6 = 0x04; // accept 0, size 4 |
| 248 | 278 | const s7 = 0x44; // accept 4, size 4 |
| ... | ... | @@ -458,7 +488,9 @@ pub const Utf16LeIterator = struct { |
| 458 | 488 | }; |
| 459 | 489 | } |
| 460 | 490 | |
| 461 | pub fn nextCodepoint(it: *Utf16LeIterator) !?u21 { | |
| 491 | pub const NextCodepointError = error{ DanglingSurrogateHalf, ExpectedSecondSurrogateHalf, UnexpectedSecondSurrogateHalf }; | |
| 492 | ||
| 493 | pub fn nextCodepoint(it: *Utf16LeIterator) NextCodepointError!?u21 { | |
| 462 | 494 | assert(it.i <= it.bytes.len); |
| 463 | 495 | if (it.i == it.bytes.len) return null; |
| 464 | 496 | var code_units: [2]u16 = undefined; |
| ... | ... | @@ -770,11 +802,139 @@ fn testDecode(bytes: []const u8) !u21 { |
| 770 | 802 | return utf8Decode(bytes); |
| 771 | 803 | } |
| 772 | 804 | |
| 773 | /// Caller must free returned memory. | |
| 774 | pub fn utf16leToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8 { | |
| 805 | /// Print the given `utf8` string, encoded as UTF-8 bytes. | |
| 806 | /// Ill-formed UTF-8 byte sequences are replaced by the replacement character (U+FFFD) | |
| 807 | /// according to "U+FFFD Substitution of Maximal Subparts" from Chapter 3 of | |
| 808 | /// the Unicode standard, and as specified by https://encoding.spec.whatwg.org/#utf-8-decoder | |
| 809 | fn formatUtf8( | |
| 810 | utf8: []const u8, | |
| 811 | comptime fmt: []const u8, | |
| 812 | options: std.fmt.FormatOptions, | |
| 813 | writer: anytype, | |
| 814 | ) !void { | |
| 815 | _ = fmt; | |
| 816 | _ = options; | |
| 817 | var buf: [300]u8 = undefined; // just an arbitrary size | |
| 818 | var u8len: usize = 0; | |
| 819 | ||
| 820 | // This implementation is based on this specification: | |
| 821 | // https://encoding.spec.whatwg.org/#utf-8-decoder | |
| 822 | var codepoint: u21 = 0; | |
| 823 | var cont_bytes_seen: u3 = 0; | |
| 824 | var cont_bytes_needed: u3 = 0; | |
| 825 | var lower_boundary: u8 = 0x80; | |
| 826 | var upper_boundary: u8 = 0xBF; | |
| 827 | ||
| 828 | var i: usize = 0; | |
| 829 | while (i < utf8.len) { | |
| 830 | const byte = utf8[i]; | |
| 831 | if (cont_bytes_needed == 0) { | |
| 832 | switch (byte) { | |
| 833 | 0x00...0x7F => { | |
| 834 | buf[u8len] = byte; | |
| 835 | u8len += 1; | |
| 836 | }, | |
| 837 | 0xC2...0xDF => { | |
| 838 | cont_bytes_needed = 1; | |
| 839 | codepoint = byte & 0b00011111; | |
| 840 | }, | |
| 841 | 0xE0...0xEF => { | |
| 842 | if (byte == 0xE0) lower_boundary = 0xA0; | |
| 843 | if (byte == 0xED) upper_boundary = 0x9F; | |
| 844 | cont_bytes_needed = 2; | |
| 845 | codepoint = byte & 0b00001111; | |
| 846 | }, | |
| 847 | 0xF0...0xF4 => { | |
| 848 | if (byte == 0xF0) lower_boundary = 0x90; | |
| 849 | if (byte == 0xF4) upper_boundary = 0x8F; | |
| 850 | cont_bytes_needed = 3; | |
| 851 | codepoint = byte & 0b00000111; | |
| 852 | }, | |
| 853 | else => { | |
| 854 | u8len += utf8Encode(replacement_character, buf[u8len..]) catch unreachable; | |
| 855 | }, | |
| 856 | } | |
| 857 | // consume the byte | |
| 858 | i += 1; | |
| 859 | } else if (byte < lower_boundary or byte > upper_boundary) { | |
| 860 | codepoint = 0; | |
| 861 | cont_bytes_needed = 0; | |
| 862 | cont_bytes_seen = 0; | |
| 863 | lower_boundary = 0x80; | |
| 864 | upper_boundary = 0xBF; | |
| 865 | u8len += utf8Encode(replacement_character, buf[u8len..]) catch unreachable; | |
| 866 | // do not consume the current byte, it should now be treated as a possible start byte | |
| 867 | } else { | |
| 868 | lower_boundary = 0x80; | |
| 869 | upper_boundary = 0xBF; | |
| 870 | codepoint <<= 6; | |
| 871 | codepoint |= byte & 0b00111111; | |
| 872 | cont_bytes_seen += 1; | |
| 873 | // consume the byte | |
| 874 | i += 1; | |
| 875 | ||
| 876 | if (cont_bytes_seen == cont_bytes_needed) { | |
| 877 | const codepoint_len = cont_bytes_seen + 1; | |
| 878 | const codepoint_start_i = i - codepoint_len; | |
| 879 | @memcpy(buf[u8len..][0..codepoint_len], utf8[codepoint_start_i..][0..codepoint_len]); | |
| 880 | u8len += codepoint_len; | |
| 881 | ||
| 882 | codepoint = 0; | |
| 883 | cont_bytes_needed = 0; | |
| 884 | cont_bytes_seen = 0; | |
| 885 | } | |
| 886 | } | |
| 887 | // make sure there's always enough room for another maximum length UTF-8 codepoint | |
| 888 | if (u8len + 4 > buf.len) { | |
| 889 | try writer.writeAll(buf[0..u8len]); | |
| 890 | u8len = 0; | |
| 891 | } | |
| 892 | } | |
| 893 | if (cont_bytes_needed != 0) { | |
| 894 | // we know there's enough room because we always flush | |
| 895 | // if there's less than 4 bytes remaining in the buffer. | |
| 896 | u8len += utf8Encode(replacement_character, buf[u8len..]) catch unreachable; | |
| 897 | } | |
| 898 | try writer.writeAll(buf[0..u8len]); | |
| 899 | } | |
| 900 | ||
| 901 | /// Return a Formatter for a (potentially ill-formed) UTF-8 string. | |
| 902 | /// Ill-formed UTF-8 byte sequences are replaced by the replacement character (U+FFFD) | |
| 903 | /// according to "U+FFFD Substitution of Maximal Subparts" from Chapter 3 of | |
| 904 | /// the Unicode standard, and as specified by https://encoding.spec.whatwg.org/#utf-8-decoder | |
| 905 | pub fn fmtUtf8(utf8: []const u8) std.fmt.Formatter(formatUtf8) { | |
| 906 | return .{ .data = utf8 }; | |
| 907 | } | |
| 908 | ||
| 909 | test "fmtUtf8" { | |
| 910 | const expectFmt = testing.expectFmt; | |
| 911 | try expectFmt("", "{}", .{fmtUtf8("")}); | |
| 912 | try expectFmt("foo", "{}", .{fmtUtf8("foo")}); | |
| 913 | try expectFmt("𐐷", "{}", .{fmtUtf8("𐐷")}); | |
| 914 | ||
| 915 | // Table 3-8. U+FFFD for Non-Shortest Form Sequences | |
| 916 | try expectFmt("��������A", "{}", .{fmtUtf8("\xC0\xAF\xE0\x80\xBF\xF0\x81\x82A")}); | |
| 917 | ||
| 918 | // Table 3-9. U+FFFD for Ill-Formed Sequences for Surrogates | |
| 919 | try expectFmt("��������A", "{}", .{fmtUtf8("\xED\xA0\x80\xED\xBF\xBF\xED\xAFA")}); | |
| 920 | ||
| 921 | // Table 3-10. U+FFFD for Other Ill-Formed Sequences | |
| 922 | try expectFmt("�����A��B", "{}", .{fmtUtf8("\xF4\x91\x92\x93\xFFA\x80\xBFB")}); | |
| 923 | ||
| 924 | // Table 3-11. U+FFFD for Truncated Sequences | |
| 925 | try expectFmt("����A", "{}", .{fmtUtf8("\xE1\x80\xE2\xF0\x91\x92\xF1\xBFA")}); | |
| 926 | } | |
| 927 | ||
| 928 | fn utf16LeToUtf8ArrayListImpl( | |
| 929 | array_list: *std.ArrayList(u8), | |
| 930 | utf16le: []const u16, | |
| 931 | comptime surrogates: Surrogates, | |
| 932 | ) (switch (surrogates) { | |
| 933 | .cannot_encode_surrogate_half => Utf16LeToUtf8AllocError, | |
| 934 | .can_encode_surrogate_half => mem.Allocator.Error, | |
| 935 | })!void { | |
| 775 | 936 | // optimistically guess that it will all be ascii. |
| 776 | var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len); | |
| 777 | errdefer result.deinit(); | |
| 937 | try array_list.ensureTotalCapacityPrecise(utf16le.len); | |
| 778 | 938 | |
| 779 | 939 | var remaining = utf16le; |
| 780 | 940 | if (builtin.zig_backend != .stage2_x86_64) { |
| ... | ... | @@ -796,68 +956,76 @@ pub fn utf16leToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8 |
| 796 | 956 | // We allocated enough space to encode every UTF-16 code unit |
| 797 | 957 | // as ASCII, so if the entire string is ASCII then we are |
| 798 | 958 | // guaranteed to have enough space allocated |
| 799 | result.appendSliceAssumeCapacity(&ascii_bytes); | |
| 959 | array_list.appendSliceAssumeCapacity(&ascii_bytes); | |
| 800 | 960 | remaining = remaining[chunk_len..]; |
| 801 | 961 | } |
| 802 | 962 | } |
| 803 | 963 | |
| 804 | var out_index: usize = result.items.len; | |
| 805 | var it = Utf16LeIterator.init(remaining); | |
| 806 | while (try it.nextCodepoint()) |codepoint| { | |
| 807 | const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable; | |
| 808 | try result.resize(result.items.len + utf8_len); | |
| 809 | assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len); | |
| 810 | out_index += utf8_len; | |
| 964 | var out_index: usize = array_list.items.len; | |
| 965 | switch (surrogates) { | |
| 966 | .cannot_encode_surrogate_half => { | |
| 967 | var it = Utf16LeIterator.init(remaining); | |
| 968 | while (try it.nextCodepoint()) |codepoint| { | |
| 969 | const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable; | |
| 970 | try array_list.resize(array_list.items.len + utf8_len); | |
| 971 | assert((utf8Encode(codepoint, array_list.items[out_index..]) catch unreachable) == utf8_len); | |
| 972 | out_index += utf8_len; | |
| 973 | } | |
| 974 | }, | |
| 975 | .can_encode_surrogate_half => { | |
| 976 | var it = Wtf16LeIterator.init(remaining); | |
| 977 | while (it.nextCodepoint()) |codepoint| { | |
| 978 | const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable; | |
| 979 | try array_list.resize(array_list.items.len + utf8_len); | |
| 980 | assert((wtf8Encode(codepoint, array_list.items[out_index..]) catch unreachable) == utf8_len); | |
| 981 | out_index += utf8_len; | |
| 982 | } | |
| 983 | }, | |
| 811 | 984 | } |
| 985 | } | |
| 986 | ||
| 987 | pub const Utf16LeToUtf8AllocError = mem.Allocator.Error || Utf16LeToUtf8Error; | |
| 988 | ||
| 989 | pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) Utf16LeToUtf8AllocError!void { | |
| 990 | return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .cannot_encode_surrogate_half); | |
| 991 | } | |
| 992 | ||
| 993 | /// Deprecated; renamed to utf16LeToUtf8Alloc | |
| 994 | pub const utf16leToUtf8Alloc = utf16LeToUtf8Alloc; | |
| 995 | ||
| 996 | /// Caller must free returned memory. | |
| 997 | pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![]u8 { | |
| 998 | // optimistically guess that it will all be ascii. | |
| 999 | var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len); | |
| 1000 | errdefer result.deinit(); | |
| 1001 | ||
| 1002 | try utf16LeToUtf8ArrayList(&result, utf16le); | |
| 812 | 1003 | |
| 813 | 1004 | return result.toOwnedSlice(); |
| 814 | 1005 | } |
| 815 | 1006 | |
| 1007 | /// Deprecated; renamed to utf16LeToUtf8AllocZ | |
| 1008 | pub const utf16leToUtf8AllocZ = utf16LeToUtf8AllocZ; | |
| 1009 | ||
| 816 | 1010 | /// Caller must free returned memory. |
| 817 | pub fn utf16leToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) ![:0]u8 { | |
| 1011 | pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![:0]u8 { | |
| 818 | 1012 | // optimistically guess that it will all be ascii (and allocate space for the null terminator) |
| 819 | 1013 | var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len + 1); |
| 820 | 1014 | errdefer result.deinit(); |
| 821 | 1015 | |
| 822 | var remaining = utf16le; | |
| 823 | if (builtin.zig_backend != .stage2_x86_64) { | |
| 824 | const chunk_len = std.simd.suggestVectorLength(u16) orelse 1; | |
| 825 | const Chunk = @Vector(chunk_len, u16); | |
| 826 | ||
| 827 | // Fast path. Check for and encode ASCII characters at the start of the input. | |
| 828 | while (remaining.len >= chunk_len) { | |
| 829 | const chunk: Chunk = remaining[0..chunk_len].*; | |
| 830 | const mask: Chunk = @splat(std.mem.nativeToLittle(u16, 0x7F)); | |
| 831 | if (@reduce(.Or, chunk | mask != mask)) { | |
| 832 | // found a non ASCII code unit | |
| 833 | break; | |
| 834 | } | |
| 835 | const chunk_byte_len = chunk_len * 2; | |
| 836 | const chunk_bytes: @Vector(chunk_byte_len, u8) = (std.mem.sliceAsBytes(remaining)[0..chunk_byte_len]).*; | |
| 837 | const deinterlaced_bytes = std.simd.deinterlace(2, chunk_bytes); | |
| 838 | const ascii_bytes: [chunk_len]u8 = deinterlaced_bytes[0]; | |
| 839 | // We allocated enough space to encode every UTF-16 code unit | |
| 840 | // as ASCII, so if the entire string is ASCII then we are | |
| 841 | // guaranteed to have enough space allocated | |
| 842 | result.appendSliceAssumeCapacity(&ascii_bytes); | |
| 843 | remaining = remaining[chunk_len..]; | |
| 844 | } | |
| 845 | } | |
| 1016 | try utf16LeToUtf8ArrayList(&result, utf16le); | |
| 846 | 1017 | |
| 847 | var out_index = result.items.len; | |
| 848 | var it = Utf16LeIterator.init(remaining); | |
| 849 | while (try it.nextCodepoint()) |codepoint| { | |
| 850 | const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable; | |
| 851 | try result.resize(result.items.len + utf8_len); | |
| 852 | assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len); | |
| 853 | out_index += utf8_len; | |
| 854 | } | |
| 855 | 1018 | return result.toOwnedSliceSentinel(0); |
| 856 | 1019 | } |
| 857 | 1020 | |
| 1021 | pub const Utf16LeToUtf8Error = Utf16LeIterator.NextCodepointError; | |
| 1022 | ||
| 858 | 1023 | /// Asserts that the output buffer is big enough. |
| 859 | 1024 | /// Returns end byte index into utf8. |
| 860 | pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !usize { | |
| 1025 | fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surrogates) (switch (surrogates) { | |
| 1026 | .cannot_encode_surrogate_half => Utf16LeToUtf8Error, | |
| 1027 | .can_encode_surrogate_half => error{}, | |
| 1028 | })!usize { | |
| 861 | 1029 | var end_index: usize = 0; |
| 862 | 1030 | |
| 863 | 1031 | var remaining = utf16le; |
| ... | ... | @@ -883,30 +1051,58 @@ pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !usize { |
| 883 | 1051 | } |
| 884 | 1052 | } |
| 885 | 1053 | |
| 886 | var it = Utf16LeIterator.init(remaining); | |
| 887 | while (try it.nextCodepoint()) |codepoint| { | |
| 888 | end_index += try utf8Encode(codepoint, utf8[end_index..]); | |
| 1054 | switch (surrogates) { | |
| 1055 | .cannot_encode_surrogate_half => { | |
| 1056 | var it = Utf16LeIterator.init(remaining); | |
| 1057 | while (try it.nextCodepoint()) |codepoint| { | |
| 1058 | end_index += utf8Encode(codepoint, utf8[end_index..]) catch |err| switch (err) { | |
| 1059 | // The maximum possible codepoint encoded by UTF-16 is U+10FFFF, | |
| 1060 | // which is within the valid codepoint range. | |
| 1061 | error.CodepointTooLarge => unreachable, | |
| 1062 | // We know the codepoint was valid in UTF-16, meaning it is not | |
| 1063 | // an unpaired surrogate codepoint. | |
| 1064 | error.Utf8CannotEncodeSurrogateHalf => unreachable, | |
| 1065 | }; | |
| 1066 | } | |
| 1067 | }, | |
| 1068 | .can_encode_surrogate_half => { | |
| 1069 | var it = Wtf16LeIterator.init(remaining); | |
| 1070 | while (it.nextCodepoint()) |codepoint| { | |
| 1071 | end_index += wtf8Encode(codepoint, utf8[end_index..]) catch |err| switch (err) { | |
| 1072 | // The maximum possible codepoint encoded by UTF-16 is U+10FFFF, | |
| 1073 | // which is within the valid codepoint range. | |
| 1074 | error.CodepointTooLarge => unreachable, | |
| 1075 | }; | |
| 1076 | } | |
| 1077 | }, | |
| 889 | 1078 | } |
| 890 | 1079 | return end_index; |
| 891 | 1080 | } |
| 892 | 1081 | |
| 893 | test "utf16leToUtf8" { | |
| 1082 | /// Deprecated; renamed to utf16LeToUtf8 | |
| 1083 | pub const utf16leToUtf8 = utf16LeToUtf8; | |
| 1084 | ||
| 1085 | pub fn utf16LeToUtf8(utf8: []u8, utf16le: []const u16) Utf16LeToUtf8Error!usize { | |
| 1086 | return utf16LeToUtf8Impl(utf8, utf16le, .cannot_encode_surrogate_half); | |
| 1087 | } | |
| 1088 | ||
| 1089 | test utf16LeToUtf8 { | |
| 894 | 1090 | var utf16le: [2]u16 = undefined; |
| 895 | 1091 | const utf16le_as_bytes = mem.sliceAsBytes(utf16le[0..]); |
| 896 | 1092 | |
| 897 | 1093 | { |
| 898 | 1094 | mem.writeInt(u16, utf16le_as_bytes[0..2], 'A', .little); |
| 899 | 1095 | mem.writeInt(u16, utf16le_as_bytes[2..4], 'a', .little); |
| 900 | const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le); | |
| 901 | defer std.testing.allocator.free(utf8); | |
| 1096 | const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le); | |
| 1097 | defer testing.allocator.free(utf8); | |
| 902 | 1098 | try testing.expect(mem.eql(u8, utf8, "Aa")); |
| 903 | 1099 | } |
| 904 | 1100 | |
| 905 | 1101 | { |
| 906 | 1102 | mem.writeInt(u16, utf16le_as_bytes[0..2], 0x80, .little); |
| 907 | 1103 | mem.writeInt(u16, utf16le_as_bytes[2..4], 0xffff, .little); |
| 908 | const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le); | |
| 909 | defer std.testing.allocator.free(utf8); | |
| 1104 | const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le); | |
| 1105 | defer testing.allocator.free(utf8); | |
| 910 | 1106 | try testing.expect(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf")); |
| 911 | 1107 | } |
| 912 | 1108 | |
| ... | ... | @@ -914,8 +1110,8 @@ test "utf16leToUtf8" { |
| 914 | 1110 | // the values just outside the surrogate half range |
| 915 | 1111 | mem.writeInt(u16, utf16le_as_bytes[0..2], 0xd7ff, .little); |
| 916 | 1112 | mem.writeInt(u16, utf16le_as_bytes[2..4], 0xe000, .little); |
| 917 | const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le); | |
| 918 | defer std.testing.allocator.free(utf8); | |
| 1113 | const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le); | |
| 1114 | defer testing.allocator.free(utf8); | |
| 919 | 1115 | try testing.expect(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80")); |
| 920 | 1116 | } |
| 921 | 1117 | |
| ... | ... | @@ -923,8 +1119,8 @@ test "utf16leToUtf8" { |
| 923 | 1119 | // smallest surrogate pair |
| 924 | 1120 | mem.writeInt(u16, utf16le_as_bytes[0..2], 0xd800, .little); |
| 925 | 1121 | mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdc00, .little); |
| 926 | const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le); | |
| 927 | defer std.testing.allocator.free(utf8); | |
| 1122 | const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le); | |
| 1123 | defer testing.allocator.free(utf8); | |
| 928 | 1124 | try testing.expect(mem.eql(u8, utf8, "\xf0\x90\x80\x80")); |
| 929 | 1125 | } |
| 930 | 1126 | |
| ... | ... | @@ -932,31 +1128,30 @@ test "utf16leToUtf8" { |
| 932 | 1128 | // largest surrogate pair |
| 933 | 1129 | mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdbff, .little); |
| 934 | 1130 | mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdfff, .little); |
| 935 | const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le); | |
| 936 | defer std.testing.allocator.free(utf8); | |
| 1131 | const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le); | |
| 1132 | defer testing.allocator.free(utf8); | |
| 937 | 1133 | try testing.expect(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf")); |
| 938 | 1134 | } |
| 939 | 1135 | |
| 940 | 1136 | { |
| 941 | 1137 | mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdbff, .little); |
| 942 | 1138 | mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdc00, .little); |
| 943 | const utf8 = try utf16leToUtf8Alloc(std.testing.allocator, &utf16le); | |
| 944 | defer std.testing.allocator.free(utf8); | |
| 1139 | const utf8 = try utf16LeToUtf8Alloc(testing.allocator, &utf16le); | |
| 1140 | defer testing.allocator.free(utf8); | |
| 945 | 1141 | try testing.expect(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80")); |
| 946 | 1142 | } |
| 947 | 1143 | |
| 948 | 1144 | { |
| 949 | 1145 | mem.writeInt(u16, utf16le_as_bytes[0..2], 0xdcdc, .little); |
| 950 | 1146 | mem.writeInt(u16, utf16le_as_bytes[2..4], 0xdcdc, .little); |
| 951 | const result = utf16leToUtf8Alloc(std.testing.allocator, &utf16le); | |
| 952 | try std.testing.expectError(error.UnexpectedSecondSurrogateHalf, result); | |
| 1147 | const result = utf16LeToUtf8Alloc(testing.allocator, &utf16le); | |
| 1148 | try testing.expectError(error.UnexpectedSecondSurrogateHalf, result); | |
| 953 | 1149 | } |
| 954 | 1150 | } |
| 955 | 1151 | |
| 956 | pub fn utf8ToUtf16LeWithNull(allocator: mem.Allocator, utf8: []const u8) ![:0]u16 { | |
| 1152 | fn utf8ToUtf16LeArrayListImpl(array_list: *std.ArrayList(u16), utf8: []const u8, comptime surrogates: Surrogates) !void { | |
| 957 | 1153 | // optimistically guess that it will not require surrogate pairs |
| 958 | var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len + 1); | |
| 959 | errdefer result.deinit(); | |
| 1154 | try array_list.ensureTotalCapacityPrecise(utf8.len); | |
| 960 | 1155 | |
| 961 | 1156 | var remaining = utf8; |
| 962 | 1157 | // Need support for std.simd.interlace |
| ... | ... | @@ -974,33 +1169,65 @@ pub fn utf8ToUtf16LeWithNull(allocator: mem.Allocator, utf8: []const u8) ![:0]u1 |
| 974 | 1169 | } |
| 975 | 1170 | const zeroes: Chunk = @splat(0); |
| 976 | 1171 | const utf16_chunk: [chunk_len * 2]u8 align(@alignOf(u16)) = std.simd.interlace(.{ chunk, zeroes }); |
| 977 | result.appendSliceAssumeCapacity(std.mem.bytesAsSlice(u16, &utf16_chunk)); | |
| 1172 | array_list.appendSliceAssumeCapacity(std.mem.bytesAsSlice(u16, &utf16_chunk)); | |
| 978 | 1173 | remaining = remaining[chunk_len..]; |
| 979 | 1174 | } |
| 980 | 1175 | } |
| 981 | 1176 | |
| 982 | const view = try Utf8View.init(remaining); | |
| 1177 | const view = switch (surrogates) { | |
| 1178 | .cannot_encode_surrogate_half => try Utf8View.init(remaining), | |
| 1179 | .can_encode_surrogate_half => try Wtf8View.init(remaining), | |
| 1180 | }; | |
| 983 | 1181 | var it = view.iterator(); |
| 984 | 1182 | while (it.nextCodepoint()) |codepoint| { |
| 985 | 1183 | if (codepoint < 0x10000) { |
| 986 | 1184 | const short = @as(u16, @intCast(codepoint)); |
| 987 | try result.append(mem.nativeToLittle(u16, short)); | |
| 1185 | try array_list.append(mem.nativeToLittle(u16, short)); | |
| 988 | 1186 | } else { |
| 989 | 1187 | const high = @as(u16, @intCast((codepoint - 0x10000) >> 10)) + 0xD800; |
| 990 | 1188 | const low = @as(u16, @intCast(codepoint & 0x3FF)) + 0xDC00; |
| 991 | 1189 | var out: [2]u16 = undefined; |
| 992 | 1190 | out[0] = mem.nativeToLittle(u16, high); |
| 993 | 1191 | out[1] = mem.nativeToLittle(u16, low); |
| 994 | try result.appendSlice(out[0..]); | |
| 1192 | try array_list.appendSlice(out[0..]); | |
| 995 | 1193 | } |
| 996 | 1194 | } |
| 1195 | } | |
| 1196 | ||
| 1197 | pub fn utf8ToUtf16LeArrayList(array_list: *std.ArrayList(u16), utf8: []const u8) error{ InvalidUtf8, OutOfMemory }!void { | |
| 1198 | return utf8ToUtf16LeArrayListImpl(array_list, utf8, .cannot_encode_surrogate_half); | |
| 1199 | } | |
| 1200 | ||
| 1201 | pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![]u16 { | |
| 1202 | // optimistically guess that it will not require surrogate pairs | |
| 1203 | var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len); | |
| 1204 | errdefer result.deinit(); | |
| 1205 | ||
| 1206 | try utf8ToUtf16LeArrayListImpl(&result, utf8, .cannot_encode_surrogate_half); | |
| 1207 | ||
| 1208 | return result.toOwnedSlice(); | |
| 1209 | } | |
| 1210 | ||
| 1211 | /// Deprecated; renamed to utf8ToUtf16LeAllocZ | |
| 1212 | pub const utf8ToUtf16LeWithNull = utf8ToUtf16LeAllocZ; | |
| 1213 | ||
| 1214 | pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![:0]u16 { | |
| 1215 | // optimistically guess that it will not require surrogate pairs | |
| 1216 | var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len + 1); | |
| 1217 | errdefer result.deinit(); | |
| 1218 | ||
| 1219 | try utf8ToUtf16LeArrayListImpl(&result, utf8, .cannot_encode_surrogate_half); | |
| 997 | 1220 | |
| 998 | 1221 | return result.toOwnedSliceSentinel(0); |
| 999 | 1222 | } |
| 1000 | 1223 | |
| 1001 | 1224 | /// Returns index of next character. If exact fit, returned index equals output slice length. |
| 1002 | 1225 | /// Assumes there is enough space for the output. |
| 1003 | pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize { | |
| 1226 | pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) error{InvalidUtf8}!usize { | |
| 1227 | return utf8ToUtf16LeImpl(utf16le, utf8, .cannot_encode_surrogate_half); | |
| 1228 | } | |
| 1229 | ||
| 1230 | pub fn utf8ToUtf16LeImpl(utf16le: []u16, utf8: []const u8, comptime surrogates: Surrogates) !usize { | |
| 1004 | 1231 | var dest_i: usize = 0; |
| 1005 | 1232 | |
| 1006 | 1233 | var remaining = utf8; |
| ... | ... | @@ -1027,9 +1254,15 @@ pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize { |
| 1027 | 1254 | |
| 1028 | 1255 | var src_i: usize = 0; |
| 1029 | 1256 | while (src_i < remaining.len) { |
| 1030 | const n = utf8ByteSequenceLength(remaining[src_i]) catch return error.InvalidUtf8; | |
| 1257 | const n = utf8ByteSequenceLength(remaining[src_i]) catch return switch (surrogates) { | |
| 1258 | .cannot_encode_surrogate_half => error.InvalidUtf8, | |
| 1259 | .can_encode_surrogate_half => error.InvalidWtf8, | |
| 1260 | }; | |
| 1031 | 1261 | const next_src_i = src_i + n; |
| 1032 | const codepoint = utf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8; | |
| 1262 | const codepoint = switch (surrogates) { | |
| 1263 | .cannot_encode_surrogate_half => utf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8, | |
| 1264 | .can_encode_surrogate_half => wtf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidWtf8, | |
| 1265 | }; | |
| 1033 | 1266 | if (codepoint < 0x10000) { |
| 1034 | 1267 | const short = @as(u16, @intCast(codepoint)); |
| 1035 | 1268 | utf16le[dest_i] = mem.nativeToLittle(u16, short); |
| ... | ... | @@ -1064,21 +1297,59 @@ test "utf8ToUtf16Le" { |
| 1064 | 1297 | } |
| 1065 | 1298 | } |
| 1066 | 1299 | |
| 1067 | test "utf8ToUtf16LeWithNull" { | |
| 1300 | test utf8ToUtf16LeArrayList { | |
| 1301 | { | |
| 1302 | var list = std.ArrayList(u16).init(testing.allocator); | |
| 1303 | defer list.deinit(); | |
| 1304 | try utf8ToUtf16LeArrayList(&list, "𐐷"); | |
| 1305 | try testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc", mem.sliceAsBytes(list.items)); | |
| 1306 | } | |
| 1307 | { | |
| 1308 | var list = std.ArrayList(u16).init(testing.allocator); | |
| 1309 | defer list.deinit(); | |
| 1310 | try utf8ToUtf16LeArrayList(&list, "\u{10FFFF}"); | |
| 1311 | try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", mem.sliceAsBytes(list.items)); | |
| 1312 | } | |
| 1313 | { | |
| 1314 | var list = std.ArrayList(u16).init(testing.allocator); | |
| 1315 | defer list.deinit(); | |
| 1316 | const result = utf8ToUtf16LeArrayList(&list, "\xf4\x90\x80\x80"); | |
| 1317 | try testing.expectError(error.InvalidUtf8, result); | |
| 1318 | } | |
| 1319 | } | |
| 1320 | ||
| 1321 | test utf8ToUtf16LeAlloc { | |
| 1322 | { | |
| 1323 | const utf16 = try utf8ToUtf16LeAlloc(testing.allocator, "𐐷"); | |
| 1324 | defer testing.allocator.free(utf16); | |
| 1325 | try testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc", mem.sliceAsBytes(utf16[0..])); | |
| 1326 | } | |
| 1327 | { | |
| 1328 | const utf16 = try utf8ToUtf16LeAlloc(testing.allocator, "\u{10FFFF}"); | |
| 1329 | defer testing.allocator.free(utf16); | |
| 1330 | try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", mem.sliceAsBytes(utf16[0..])); | |
| 1331 | } | |
| 1332 | { | |
| 1333 | const result = utf8ToUtf16LeAlloc(testing.allocator, "\xf4\x90\x80\x80"); | |
| 1334 | try testing.expectError(error.InvalidUtf8, result); | |
| 1335 | } | |
| 1336 | } | |
| 1337 | ||
| 1338 | test utf8ToUtf16LeAllocZ { | |
| 1068 | 1339 | { |
| 1069 | const utf16 = try utf8ToUtf16LeWithNull(testing.allocator, "𐐷"); | |
| 1340 | const utf16 = try utf8ToUtf16LeAllocZ(testing.allocator, "𐐷"); | |
| 1070 | 1341 | defer testing.allocator.free(utf16); |
| 1071 | 1342 | try testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc", mem.sliceAsBytes(utf16[0..])); |
| 1072 | 1343 | try testing.expect(utf16[2] == 0); |
| 1073 | 1344 | } |
| 1074 | 1345 | { |
| 1075 | const utf16 = try utf8ToUtf16LeWithNull(testing.allocator, "\u{10FFFF}"); | |
| 1346 | const utf16 = try utf8ToUtf16LeAllocZ(testing.allocator, "\u{10FFFF}"); | |
| 1076 | 1347 | defer testing.allocator.free(utf16); |
| 1077 | 1348 | try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", mem.sliceAsBytes(utf16[0..])); |
| 1078 | 1349 | try testing.expect(utf16[2] == 0); |
| 1079 | 1350 | } |
| 1080 | 1351 | { |
| 1081 | const result = utf8ToUtf16LeWithNull(testing.allocator, "\xf4\x90\x80\x80"); | |
| 1352 | const result = utf8ToUtf16LeAllocZ(testing.allocator, "\xf4\x90\x80\x80"); | |
| 1082 | 1353 | try testing.expectError(error.InvalidUtf8, result); |
| 1083 | 1354 | } |
| 1084 | 1355 | } |
| ... | ... | @@ -1127,8 +1398,9 @@ test "calculate utf16 string length of given utf8 string in u16" { |
| 1127 | 1398 | try comptime testCalcUtf16LeLen(); |
| 1128 | 1399 | } |
| 1129 | 1400 | |
| 1130 | /// Print the given `utf16le` string | |
| 1131 | fn formatUtf16le( | |
| 1401 | /// Print the given `utf16le` string, encoded as UTF-8 bytes. | |
| 1402 | /// Unpaired surrogates are replaced by the replacement character (U+FFFD). | |
| 1403 | fn formatUtf16Le( | |
| 1132 | 1404 | utf16le: []const u16, |
| 1133 | 1405 | comptime fmt: []const u8, |
| 1134 | 1406 | options: std.fmt.FormatOptions, |
| ... | ... | @@ -1136,13 +1408,14 @@ fn formatUtf16le( |
| 1136 | 1408 | ) !void { |
| 1137 | 1409 | _ = fmt; |
| 1138 | 1410 | _ = options; |
| 1139 | var buf: [300]u8 = undefined; // just a random size I chose | |
| 1411 | var buf: [300]u8 = undefined; // just an arbitrary size | |
| 1140 | 1412 | var it = Utf16LeIterator.init(utf16le); |
| 1141 | 1413 | var u8len: usize = 0; |
| 1142 | 1414 | while (it.nextCodepoint() catch replacement_character) |codepoint| { |
| 1143 | 1415 | u8len += utf8Encode(codepoint, buf[u8len..]) catch |
| 1144 | 1416 | utf8Encode(replacement_character, buf[u8len..]) catch unreachable; |
| 1145 | if (u8len + 3 >= buf.len) { | |
| 1417 | // make sure there's always enough room for another maximum length UTF-8 codepoint | |
| 1418 | if (u8len + 4 > buf.len) { | |
| 1146 | 1419 | try writer.writeAll(buf[0..u8len]); |
| 1147 | 1420 | u8len = 0; |
| 1148 | 1421 | } |
| ... | ... | @@ -1150,22 +1423,27 @@ fn formatUtf16le( |
| 1150 | 1423 | try writer.writeAll(buf[0..u8len]); |
| 1151 | 1424 | } |
| 1152 | 1425 | |
| 1153 | /// Return a Formatter for a Utf16le string | |
| 1154 | pub fn fmtUtf16le(utf16le: []const u16) std.fmt.Formatter(formatUtf16le) { | |
| 1426 | /// Deprecated; renamed to fmtUtf16Le | |
| 1427 | pub const fmtUtf16le = fmtUtf16Le; | |
| 1428 | ||
| 1429 | /// Return a Formatter for a (potentially ill-formed) UTF-16 LE string, | |
| 1430 | /// which will be converted to UTF-8 during formatting. | |
| 1431 | /// Unpaired surrogates are replaced by the replacement character (U+FFFD). | |
| 1432 | pub fn fmtUtf16Le(utf16le: []const u16) std.fmt.Formatter(formatUtf16Le) { | |
| 1155 | 1433 | return .{ .data = utf16le }; |
| 1156 | 1434 | } |
| 1157 | 1435 | |
| 1158 | test "fmtUtf16le" { | |
| 1159 | const expectFmt = std.testing.expectFmt; | |
| 1160 | try expectFmt("", "{}", .{fmtUtf16le(utf8ToUtf16LeStringLiteral(""))}); | |
| 1161 | try expectFmt("foo", "{}", .{fmtUtf16le(utf8ToUtf16LeStringLiteral("foo"))}); | |
| 1162 | try expectFmt("𐐷", "{}", .{fmtUtf16le(utf8ToUtf16LeStringLiteral("𐐷"))}); | |
| 1163 | try expectFmt("", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\xff\xd7", native_endian)})}); | |
| 1164 | try expectFmt("�", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\x00\xd8", native_endian)})}); | |
| 1165 | try expectFmt("�", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\xff\xdb", native_endian)})}); | |
| 1166 | try expectFmt("�", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\x00\xdc", native_endian)})}); | |
| 1167 | try expectFmt("�", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\xff\xdf", native_endian)})}); | |
| 1168 | try expectFmt("", "{}", .{fmtUtf16le(&[_]u16{std.mem.readInt(u16, "\x00\xe0", native_endian)})}); | |
| 1436 | test "fmtUtf16Le" { | |
| 1437 | const expectFmt = testing.expectFmt; | |
| 1438 | try expectFmt("", "{}", .{fmtUtf16Le(utf8ToUtf16LeStringLiteral(""))}); | |
| 1439 | try expectFmt("foo", "{}", .{fmtUtf16Le(utf8ToUtf16LeStringLiteral("foo"))}); | |
| 1440 | try expectFmt("𐐷", "{}", .{fmtUtf16Le(utf8ToUtf16LeStringLiteral("𐐷"))}); | |
| 1441 | try expectFmt("", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\xff\xd7", native_endian)})}); | |
| 1442 | try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\x00\xd8", native_endian)})}); | |
| 1443 | try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\xff\xdb", native_endian)})}); | |
| 1444 | try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\x00\xdc", native_endian)})}); | |
| 1445 | try expectFmt("�", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\xff\xdf", native_endian)})}); | |
| 1446 | try expectFmt("", "{}", .{fmtUtf16Le(&[_]u16{std.mem.readInt(u16, "\x00\xe0", native_endian)})}); | |
| 1169 | 1447 | } |
| 1170 | 1448 | |
| 1171 | 1449 | test "utf8ToUtf16LeStringLiteral" { |
| ... | ... | @@ -1248,3 +1526,535 @@ test "utf8 valid codepoint" { |
| 1248 | 1526 | try testUtf8ValidCodepoint(); |
| 1249 | 1527 | try comptime testUtf8ValidCodepoint(); |
| 1250 | 1528 | } |
| 1529 | ||
| 1530 | /// Returns true if the codepoint is a surrogate (U+DC00 to U+DFFF) | |
| 1531 | pub fn isSurrogateCodepoint(c: u21) bool { | |
| 1532 | return switch (c) { | |
| 1533 | 0xD800...0xDFFF => true, | |
| 1534 | else => false, | |
| 1535 | }; | |
| 1536 | } | |
| 1537 | ||
| 1538 | /// Encodes the given codepoint into a WTF-8 byte sequence. | |
| 1539 | /// c: the codepoint. | |
| 1540 | /// out: the out buffer to write to. Must have a len >= utf8CodepointSequenceLength(c). | |
| 1541 | /// Errors: if c cannot be encoded in WTF-8. | |
| 1542 | /// Returns: the number of bytes written to out. | |
| 1543 | pub fn wtf8Encode(c: u21, out: []u8) error{CodepointTooLarge}!u3 { | |
| 1544 | return utf8EncodeImpl(c, out, .can_encode_surrogate_half); | |
| 1545 | } | |
| 1546 | ||
| 1547 | const Wtf8DecodeError = Utf8Decode2Error || Utf8Decode3AllowSurrogateHalfError || Utf8Decode4Error; | |
| 1548 | ||
| 1549 | pub fn wtf8Decode(bytes: []const u8) Wtf8DecodeError!u21 { | |
| 1550 | return switch (bytes.len) { | |
| 1551 | 1 => @as(u21, bytes[0]), | |
| 1552 | 2 => utf8Decode2(bytes), | |
| 1553 | 3 => utf8Decode3AllowSurrogateHalf(bytes), | |
| 1554 | 4 => utf8Decode4(bytes), | |
| 1555 | else => unreachable, | |
| 1556 | }; | |
| 1557 | } | |
| 1558 | ||
| 1559 | /// Returns true if the input consists entirely of WTF-8 codepoints | |
| 1560 | /// (all the same restrictions as UTF-8, but allows surrogate codepoints | |
| 1561 | /// U+D800 to U+DFFF). | |
| 1562 | /// Does not check for well-formed WTF-8, meaning that this function | |
| 1563 | /// does not check that all surrogate halves are unpaired. | |
| 1564 | pub fn wtf8ValidateSlice(input: []const u8) bool { | |
| 1565 | return utf8ValidateSliceImpl(input, .can_encode_surrogate_half); | |
| 1566 | } | |
| 1567 | ||
| 1568 | test "validate WTF-8 slice" { | |
| 1569 | try testValidateWtf8Slice(); | |
| 1570 | try comptime testValidateWtf8Slice(); | |
| 1571 | ||
| 1572 | // We skip a variable (based on recommended vector size) chunks of | |
| 1573 | // ASCII characters. Let's make sure we're chunking correctly. | |
| 1574 | const str = [_]u8{'a'} ** 550 ++ "\xc0"; | |
| 1575 | for (0..str.len - 3) |i| { | |
| 1576 | try testing.expect(!wtf8ValidateSlice(str[i..])); | |
| 1577 | } | |
| 1578 | } | |
| 1579 | fn testValidateWtf8Slice() !void { | |
| 1580 | // These are valid/invalid under both UTF-8 and WTF-8 rules. | |
| 1581 | try testing.expect(wtf8ValidateSlice("abc")); | |
| 1582 | try testing.expect(wtf8ValidateSlice("abc\xdf\xbf")); | |
| 1583 | try testing.expect(wtf8ValidateSlice("")); | |
| 1584 | try testing.expect(wtf8ValidateSlice("a")); | |
| 1585 | try testing.expect(wtf8ValidateSlice("abc")); | |
| 1586 | try testing.expect(wtf8ValidateSlice("Ж")); | |
| 1587 | try testing.expect(wtf8ValidateSlice("ЖЖ")); | |
| 1588 | try testing.expect(wtf8ValidateSlice("брэд-ЛГТМ")); | |
| 1589 | try testing.expect(wtf8ValidateSlice("☺☻☹")); | |
| 1590 | try testing.expect(wtf8ValidateSlice("a\u{fffdb}")); | |
| 1591 | try testing.expect(wtf8ValidateSlice("\xf4\x8f\xbf\xbf")); | |
| 1592 | try testing.expect(wtf8ValidateSlice("abc\xdf\xbf")); | |
| 1593 | ||
| 1594 | try testing.expect(!wtf8ValidateSlice("abc\xc0")); | |
| 1595 | try testing.expect(!wtf8ValidateSlice("abc\xc0abc")); | |
| 1596 | try testing.expect(!wtf8ValidateSlice("aa\xe2")); | |
| 1597 | try testing.expect(!wtf8ValidateSlice("\x42\xfa")); | |
| 1598 | try testing.expect(!wtf8ValidateSlice("\x42\xfa\x43")); | |
| 1599 | try testing.expect(!wtf8ValidateSlice("abc\xc0")); | |
| 1600 | try testing.expect(!wtf8ValidateSlice("abc\xc0abc")); | |
| 1601 | try testing.expect(!wtf8ValidateSlice("\xf4\x90\x80\x80")); | |
| 1602 | try testing.expect(!wtf8ValidateSlice("\xf7\xbf\xbf\xbf")); | |
| 1603 | try testing.expect(!wtf8ValidateSlice("\xfb\xbf\xbf\xbf\xbf")); | |
| 1604 | try testing.expect(!wtf8ValidateSlice("\xc0\x80")); | |
| 1605 | ||
| 1606 | // But surrogate codepoints are only valid in WTF-8. | |
| 1607 | try testing.expect(wtf8ValidateSlice("\xed\xa0\x80")); | |
| 1608 | try testing.expect(wtf8ValidateSlice("\xed\xbf\xbf")); | |
| 1609 | } | |
| 1610 | ||
| 1611 | /// Wtf8View iterates the code points of a WTF-8 encoded string, | |
| 1612 | /// including surrogate halves. | |
| 1613 | /// | |
| 1614 | /// ``` | |
| 1615 | /// var wtf8 = (try std.unicode.Wtf8View.init("hi there")).iterator(); | |
| 1616 | /// while (wtf8.nextCodepointSlice()) |codepoint| { | |
| 1617 | /// // note: codepoint could be a surrogate half which is invalid | |
| 1618 | /// // UTF-8, avoid printing or otherwise sending/emitting this directly | |
| 1619 | /// } | |
| 1620 | /// ``` | |
| 1621 | pub const Wtf8View = struct { | |
| 1622 | bytes: []const u8, | |
| 1623 | ||
| 1624 | pub fn init(s: []const u8) error{InvalidWtf8}!Wtf8View { | |
| 1625 | if (!wtf8ValidateSlice(s)) { | |
| 1626 | return error.InvalidWtf8; | |
| 1627 | } | |
| 1628 | ||
| 1629 | return initUnchecked(s); | |
| 1630 | } | |
| 1631 | ||
| 1632 | pub fn initUnchecked(s: []const u8) Wtf8View { | |
| 1633 | return Wtf8View{ .bytes = s }; | |
| 1634 | } | |
| 1635 | ||
| 1636 | pub inline fn initComptime(comptime s: []const u8) Wtf8View { | |
| 1637 | return comptime if (init(s)) |r| r else |err| switch (err) { | |
| 1638 | error.InvalidWtf8 => { | |
| 1639 | @compileError("invalid wtf8"); | |
| 1640 | }, | |
| 1641 | }; | |
| 1642 | } | |
| 1643 | ||
| 1644 | pub fn iterator(s: Wtf8View) Wtf8Iterator { | |
| 1645 | return Wtf8Iterator{ | |
| 1646 | .bytes = s.bytes, | |
| 1647 | .i = 0, | |
| 1648 | }; | |
| 1649 | } | |
| 1650 | }; | |
| 1651 | ||
| 1652 | /// Asserts that `bytes` is valid WTF-8 | |
| 1653 | pub const Wtf8Iterator = struct { | |
| 1654 | bytes: []const u8, | |
| 1655 | i: usize, | |
| 1656 | ||
| 1657 | pub fn nextCodepointSlice(it: *Wtf8Iterator) ?[]const u8 { | |
| 1658 | if (it.i >= it.bytes.len) { | |
| 1659 | return null; | |
| 1660 | } | |
| 1661 | ||
| 1662 | const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable; | |
| 1663 | it.i += cp_len; | |
| 1664 | return it.bytes[it.i - cp_len .. it.i]; | |
| 1665 | } | |
| 1666 | ||
| 1667 | pub fn nextCodepoint(it: *Wtf8Iterator) ?u21 { | |
| 1668 | const slice = it.nextCodepointSlice() orelse return null; | |
| 1669 | return wtf8Decode(slice) catch unreachable; | |
| 1670 | } | |
| 1671 | ||
| 1672 | /// Look ahead at the next n codepoints without advancing the iterator. | |
| 1673 | /// If fewer than n codepoints are available, then return the remainder of the string. | |
| 1674 | pub fn peek(it: *Wtf8Iterator, n: usize) []const u8 { | |
| 1675 | const original_i = it.i; | |
| 1676 | defer it.i = original_i; | |
| 1677 | ||
| 1678 | var end_ix = original_i; | |
| 1679 | var found: usize = 0; | |
| 1680 | while (found < n) : (found += 1) { | |
| 1681 | const next_codepoint = it.nextCodepointSlice() orelse return it.bytes[original_i..]; | |
| 1682 | end_ix += next_codepoint.len; | |
| 1683 | } | |
| 1684 | ||
| 1685 | return it.bytes[original_i..end_ix]; | |
| 1686 | } | |
| 1687 | }; | |
| 1688 | ||
| 1689 | pub fn wtf16LeToWtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) mem.Allocator.Error!void { | |
| 1690 | return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .can_encode_surrogate_half); | |
| 1691 | } | |
| 1692 | ||
| 1693 | /// Caller must free returned memory. | |
| 1694 | pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![]u8 { | |
| 1695 | // optimistically guess that it will all be ascii. | |
| 1696 | var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len); | |
| 1697 | errdefer result.deinit(); | |
| 1698 | ||
| 1699 | try wtf16LeToWtf8ArrayList(&result, wtf16le); | |
| 1700 | ||
| 1701 | return result.toOwnedSlice(); | |
| 1702 | } | |
| 1703 | ||
| 1704 | /// Caller must free returned memory. | |
| 1705 | pub fn wtf16LeToWtf8AllocZ(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![:0]u8 { | |
| 1706 | // optimistically guess that it will all be ascii (and allocate space for the null terminator) | |
| 1707 | var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len + 1); | |
| 1708 | errdefer result.deinit(); | |
| 1709 | ||
| 1710 | try wtf16LeToWtf8ArrayList(&result, wtf16le); | |
| 1711 | ||
| 1712 | return result.toOwnedSliceSentinel(0); | |
| 1713 | } | |
| 1714 | ||
| 1715 | pub fn wtf16LeToWtf8(wtf8: []u8, wtf16le: []const u16) usize { | |
| 1716 | return utf16LeToUtf8Impl(wtf8, wtf16le, .can_encode_surrogate_half) catch |err| switch (err) {}; | |
| 1717 | } | |
| 1718 | ||
| 1719 | pub fn wtf8ToWtf16LeArrayList(array_list: *std.ArrayList(u16), wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }!void { | |
| 1720 | return utf8ToUtf16LeArrayListImpl(array_list, wtf8, .can_encode_surrogate_half); | |
| 1721 | } | |
| 1722 | ||
| 1723 | pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u16 { | |
| 1724 | // optimistically guess that it will not require surrogate pairs | |
| 1725 | var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len); | |
| 1726 | errdefer result.deinit(); | |
| 1727 | ||
| 1728 | try utf8ToUtf16LeArrayListImpl(&result, wtf8, .can_encode_surrogate_half); | |
| 1729 | ||
| 1730 | return result.toOwnedSlice(); | |
| 1731 | } | |
| 1732 | ||
| 1733 | pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u16 { | |
| 1734 | // optimistically guess that it will not require surrogate pairs | |
| 1735 | var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len + 1); | |
| 1736 | errdefer result.deinit(); | |
| 1737 | ||
| 1738 | try utf8ToUtf16LeArrayListImpl(&result, wtf8, .can_encode_surrogate_half); | |
| 1739 | ||
| 1740 | return result.toOwnedSliceSentinel(0); | |
| 1741 | } | |
| 1742 | ||
| 1743 | /// Returns index of next character. If exact fit, returned index equals output slice length. | |
| 1744 | /// Assumes there is enough space for the output. | |
| 1745 | pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{InvalidWtf8}!usize { | |
| 1746 | return utf8ToUtf16LeImpl(wtf16le, wtf8, .can_encode_surrogate_half); | |
| 1747 | } | |
| 1748 | ||
| 1749 | /// Surrogate codepoints (U+D800 to U+DFFF) are replaced by the Unicode replacement | |
| 1750 | /// character (U+FFFD). | |
| 1751 | /// All surrogate codepoints and the replacement character are encoded as three | |
| 1752 | /// bytes, meaning the input and output slices will always be the same length. | |
| 1753 | /// In-place conversion is supported when `utf8` and `wtf8` refer to the same slice. | |
| 1754 | /// Note: If `wtf8` is entirely composed of well-formed UTF-8, then no conversion is necessary. | |
| 1755 | /// `utf8ValidateSlice` can be used to check if lossy conversion is worthwhile. | |
| 1756 | /// If `wtf8` is not valid WTF-8, then `error.InvalidWtf8` is returned. | |
| 1757 | pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) error{InvalidWtf8}!void { | |
| 1758 | assert(utf8.len >= wtf8.len); | |
| 1759 | ||
| 1760 | const in_place = utf8.ptr == wtf8.ptr; | |
| 1761 | const replacement_char_bytes = comptime blk: { | |
| 1762 | var buf: [3]u8 = undefined; | |
| 1763 | assert((utf8Encode(replacement_character, &buf) catch unreachable) == 3); | |
| 1764 | break :blk buf; | |
| 1765 | }; | |
| 1766 | ||
| 1767 | var dest_i: usize = 0; | |
| 1768 | const view = try Wtf8View.init(wtf8); | |
| 1769 | var it = view.iterator(); | |
| 1770 | while (it.nextCodepointSlice()) |codepoint_slice| { | |
| 1771 | // All surrogate codepoints are encoded as 3 bytes | |
| 1772 | if (codepoint_slice.len == 3) { | |
| 1773 | const codepoint = wtf8Decode(codepoint_slice) catch unreachable; | |
| 1774 | if (isSurrogateCodepoint(codepoint)) { | |
| 1775 | @memcpy(utf8[dest_i..][0..replacement_char_bytes.len], &replacement_char_bytes); | |
| 1776 | dest_i += replacement_char_bytes.len; | |
| 1777 | continue; | |
| 1778 | } | |
| 1779 | } | |
| 1780 | if (!in_place) { | |
| 1781 | @memcpy(utf8[dest_i..][0..codepoint_slice.len], codepoint_slice); | |
| 1782 | } | |
| 1783 | dest_i += codepoint_slice.len; | |
| 1784 | } | |
| 1785 | } | |
| 1786 | ||
| 1787 | pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u8 { | |
| 1788 | const utf8 = try allocator.alloc(u8, wtf8.len); | |
| 1789 | errdefer allocator.free(utf8); | |
| 1790 | ||
| 1791 | try wtf8ToUtf8Lossy(utf8, wtf8); | |
| 1792 | ||
| 1793 | return utf8; | |
| 1794 | } | |
| 1795 | ||
| 1796 | pub fn wtf8ToUtf8LossyAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u8 { | |
| 1797 | const utf8 = try allocator.allocSentinel(u8, wtf8.len, 0); | |
| 1798 | errdefer allocator.free(utf8); | |
| 1799 | ||
| 1800 | try wtf8ToUtf8Lossy(utf8, wtf8); | |
| 1801 | ||
| 1802 | return utf8; | |
| 1803 | } | |
| 1804 | ||
| 1805 | test wtf8ToUtf8Lossy { | |
| 1806 | var buf: [32]u8 = undefined; | |
| 1807 | ||
| 1808 | const invalid_utf8 = "\xff"; | |
| 1809 | try testing.expectError(error.InvalidWtf8, wtf8ToUtf8Lossy(&buf, invalid_utf8)); | |
| 1810 | ||
| 1811 | const ascii = "abcd"; | |
| 1812 | try wtf8ToUtf8Lossy(&buf, ascii); | |
| 1813 | try testing.expectEqualStrings("abcd", buf[0..ascii.len]); | |
| 1814 | ||
| 1815 | const high_surrogate_half = "ab\xed\xa0\xbdcd"; | |
| 1816 | try wtf8ToUtf8Lossy(&buf, high_surrogate_half); | |
| 1817 | try testing.expectEqualStrings("ab\u{FFFD}cd", buf[0..high_surrogate_half.len]); | |
| 1818 | ||
| 1819 | const low_surrogate_half = "ab\xed\xb2\xa9cd"; | |
| 1820 | try wtf8ToUtf8Lossy(&buf, low_surrogate_half); | |
| 1821 | try testing.expectEqualStrings("ab\u{FFFD}cd", buf[0..low_surrogate_half.len]); | |
| 1822 | ||
| 1823 | // If the WTF-8 is not well-formed, each surrogate half is converted into a separate | |
| 1824 | // replacement character instead of being interpreted as a surrogate pair. | |
| 1825 | const encoded_surrogate_pair = "ab\xed\xa0\xbd\xed\xb2\xa9cd"; | |
| 1826 | try wtf8ToUtf8Lossy(&buf, encoded_surrogate_pair); | |
| 1827 | try testing.expectEqualStrings("ab\u{FFFD}\u{FFFD}cd", buf[0..encoded_surrogate_pair.len]); | |
| 1828 | ||
| 1829 | // in place | |
| 1830 | @memcpy(buf[0..low_surrogate_half.len], low_surrogate_half); | |
| 1831 | const slice = buf[0..low_surrogate_half.len]; | |
| 1832 | try wtf8ToUtf8Lossy(slice, slice); | |
| 1833 | try testing.expectEqualStrings("ab\u{FFFD}cd", slice); | |
| 1834 | } | |
| 1835 | ||
| 1836 | test wtf8ToUtf8LossyAlloc { | |
| 1837 | const invalid_utf8 = "\xff"; | |
| 1838 | try testing.expectError(error.InvalidWtf8, wtf8ToUtf8LossyAlloc(testing.allocator, invalid_utf8)); | |
| 1839 | ||
| 1840 | { | |
| 1841 | const ascii = "abcd"; | |
| 1842 | const utf8 = try wtf8ToUtf8LossyAlloc(testing.allocator, ascii); | |
| 1843 | defer testing.allocator.free(utf8); | |
| 1844 | try testing.expectEqualStrings("abcd", utf8); | |
| 1845 | } | |
| 1846 | ||
| 1847 | { | |
| 1848 | const surrogate_half = "ab\xed\xa0\xbdcd"; | |
| 1849 | const utf8 = try wtf8ToUtf8LossyAlloc(testing.allocator, surrogate_half); | |
| 1850 | defer testing.allocator.free(utf8); | |
| 1851 | try testing.expectEqualStrings("ab\u{FFFD}cd", utf8); | |
| 1852 | } | |
| 1853 | ||
| 1854 | { | |
| 1855 | // If the WTF-8 is not well-formed, each surrogate half is converted into a separate | |
| 1856 | // replacement character instead of being interpreted as a surrogate pair. | |
| 1857 | const encoded_surrogate_pair = "ab\xed\xa0\xbd\xed\xb2\xa9cd"; | |
| 1858 | const utf8 = try wtf8ToUtf8LossyAlloc(testing.allocator, encoded_surrogate_pair); | |
| 1859 | defer testing.allocator.free(utf8); | |
| 1860 | try testing.expectEqualStrings("ab\u{FFFD}\u{FFFD}cd", utf8); | |
| 1861 | } | |
| 1862 | } | |
| 1863 | ||
| 1864 | test wtf8ToUtf8LossyAllocZ { | |
| 1865 | const invalid_utf8 = "\xff"; | |
| 1866 | try testing.expectError(error.InvalidWtf8, wtf8ToUtf8LossyAllocZ(testing.allocator, invalid_utf8)); | |
| 1867 | ||
| 1868 | { | |
| 1869 | const ascii = "abcd"; | |
| 1870 | const utf8 = try wtf8ToUtf8LossyAllocZ(testing.allocator, ascii); | |
| 1871 | defer testing.allocator.free(utf8); | |
| 1872 | try testing.expectEqualStrings("abcd", utf8); | |
| 1873 | } | |
| 1874 | ||
| 1875 | { | |
| 1876 | const surrogate_half = "ab\xed\xa0\xbdcd"; | |
| 1877 | const utf8 = try wtf8ToUtf8LossyAllocZ(testing.allocator, surrogate_half); | |
| 1878 | defer testing.allocator.free(utf8); | |
| 1879 | try testing.expectEqualStrings("ab\u{FFFD}cd", utf8); | |
| 1880 | } | |
| 1881 | ||
| 1882 | { | |
| 1883 | // If the WTF-8 is not well-formed, each surrogate half is converted into a separate | |
| 1884 | // replacement character instead of being interpreted as a surrogate pair. | |
| 1885 | const encoded_surrogate_pair = "ab\xed\xa0\xbd\xed\xb2\xa9cd"; | |
| 1886 | const utf8 = try wtf8ToUtf8LossyAllocZ(testing.allocator, encoded_surrogate_pair); | |
| 1887 | defer testing.allocator.free(utf8); | |
| 1888 | try testing.expectEqualStrings("ab\u{FFFD}\u{FFFD}cd", utf8); | |
| 1889 | } | |
| 1890 | } | |
| 1891 | ||
| 1892 | pub const Wtf16LeIterator = struct { | |
| 1893 | bytes: []const u8, | |
| 1894 | i: usize, | |
| 1895 | ||
| 1896 | pub fn init(s: []const u16) Wtf16LeIterator { | |
| 1897 | return Wtf16LeIterator{ | |
| 1898 | .bytes = std.mem.sliceAsBytes(s), | |
| 1899 | .i = 0, | |
| 1900 | }; | |
| 1901 | } | |
| 1902 | ||
| 1903 | /// If the next codepoint is encoded by a surrogate pair, returns the | |
| 1904 | /// codepoint that the surrogate pair represents. | |
| 1905 | /// If the next codepoint is an unpaired surrogate, returns the codepoint | |
| 1906 | /// of the unpaired surrogate. | |
| 1907 | pub fn nextCodepoint(it: *Wtf16LeIterator) ?u21 { | |
| 1908 | assert(it.i <= it.bytes.len); | |
| 1909 | if (it.i == it.bytes.len) return null; | |
| 1910 | var code_units: [2]u16 = undefined; | |
| 1911 | code_units[0] = std.mem.readInt(u16, it.bytes[it.i..][0..2], .little); | |
| 1912 | it.i += 2; | |
| 1913 | surrogate_pair: { | |
| 1914 | if (utf16IsHighSurrogate(code_units[0])) { | |
| 1915 | if (it.i >= it.bytes.len) break :surrogate_pair; | |
| 1916 | code_units[1] = std.mem.readInt(u16, it.bytes[it.i..][0..2], .little); | |
| 1917 | const codepoint = utf16DecodeSurrogatePair(&code_units) catch break :surrogate_pair; | |
| 1918 | it.i += 2; | |
| 1919 | return codepoint; | |
| 1920 | } | |
| 1921 | } | |
| 1922 | return code_units[0]; | |
| 1923 | } | |
| 1924 | }; | |
| 1925 | ||
| 1926 | test "non-well-formed WTF-8 does not roundtrip" { | |
| 1927 | // This encodes the surrogate pair U+D83D U+DCA9. | |
| 1928 | // The well-formed version of this would be U+1F4A9 which is \xF0\x9F\x92\xA9. | |
| 1929 | const non_well_formed_wtf8 = "\xed\xa0\xbd\xed\xb2\xa9"; | |
| 1930 | ||
| 1931 | var wtf16_buf: [2]u16 = undefined; | |
| 1932 | const wtf16_len = try wtf8ToWtf16Le(&wtf16_buf, non_well_formed_wtf8); | |
| 1933 | const wtf16 = wtf16_buf[0..wtf16_len]; | |
| 1934 | ||
| 1935 | try testing.expectEqualSlices(u16, &[_]u16{ | |
| 1936 | mem.nativeToLittle(u16, 0xD83D), // high surrogate | |
| 1937 | mem.nativeToLittle(u16, 0xDCA9), // low surrogate | |
| 1938 | }, wtf16); | |
| 1939 | ||
| 1940 | var wtf8_buf: [4]u8 = undefined; | |
| 1941 | const wtf8_len = wtf16LeToWtf8(&wtf8_buf, wtf16); | |
| 1942 | const wtf8 = wtf8_buf[0..wtf8_len]; | |
| 1943 | ||
| 1944 | // Converting to WTF-16 and back results in well-formed WTF-8, | |
| 1945 | // but it does not match the input WTF-8 | |
| 1946 | try testing.expectEqualSlices(u8, "\xf0\x9f\x92\xa9", wtf8); | |
| 1947 | } | |
| 1948 | ||
| 1949 | fn testRoundtripWtf8(wtf8: []const u8) !void { | |
| 1950 | // Buffer | |
| 1951 | { | |
| 1952 | var wtf16_buf: [32]u16 = undefined; | |
| 1953 | const wtf16_len = try wtf8ToWtf16Le(&wtf16_buf, wtf8); | |
| 1954 | const wtf16 = wtf16_buf[0..wtf16_len]; | |
| 1955 | ||
| 1956 | var roundtripped_buf: [32]u8 = undefined; | |
| 1957 | const roundtripped_len = wtf16LeToWtf8(&roundtripped_buf, wtf16); | |
| 1958 | const roundtripped = roundtripped_buf[0..roundtripped_len]; | |
| 1959 | ||
| 1960 | try testing.expectEqualSlices(u8, wtf8, roundtripped); | |
| 1961 | } | |
| 1962 | // Alloc | |
| 1963 | { | |
| 1964 | const wtf16 = try wtf8ToWtf16LeAlloc(testing.allocator, wtf8); | |
| 1965 | defer testing.allocator.free(wtf16); | |
| 1966 | ||
| 1967 | const roundtripped = try wtf16LeToWtf8Alloc(testing.allocator, wtf16); | |
| 1968 | defer testing.allocator.free(roundtripped); | |
| 1969 | ||
| 1970 | try testing.expectEqualSlices(u8, wtf8, roundtripped); | |
| 1971 | } | |
| 1972 | // AllocZ | |
| 1973 | { | |
| 1974 | const wtf16 = try wtf8ToWtf16LeAllocZ(testing.allocator, wtf8); | |
| 1975 | defer testing.allocator.free(wtf16); | |
| 1976 | ||
| 1977 | const roundtripped = try wtf16LeToWtf8AllocZ(testing.allocator, wtf16); | |
| 1978 | defer testing.allocator.free(roundtripped); | |
| 1979 | ||
| 1980 | try testing.expectEqualSlices(u8, wtf8, roundtripped); | |
| 1981 | } | |
| 1982 | } | |
| 1983 | ||
| 1984 | test "well-formed WTF-8 roundtrips" { | |
| 1985 | try testRoundtripWtf8("\xed\x9f\xbf"); // not a surrogate half | |
| 1986 | try testRoundtripWtf8("\xed\xa0\xbd"); // high surrogate | |
| 1987 | try testRoundtripWtf8("\xed\xb2\xa9"); // low surrogate | |
| 1988 | try testRoundtripWtf8("\xed\xa0\xbd \xed\xb2\xa9"); // <high surrogate><space><low surrogate> | |
| 1989 | try testRoundtripWtf8("\xed\xa0\x80\xed\xaf\xbf"); // <high surrogate><high surrogate> | |
| 1990 | try testRoundtripWtf8("\xed\xa0\x80\xee\x80\x80"); // <high surrogate><not surrogate> | |
| 1991 | try testRoundtripWtf8("\xed\x9f\xbf\xed\xb0\x80"); // <not surrogate><low surrogate> | |
| 1992 | try testRoundtripWtf8("a\xed\xb0\x80"); // <not surrogate><low surrogate> | |
| 1993 | try testRoundtripWtf8("\xf0\x9f\x92\xa9"); // U+1F4A9, encoded as a surrogate pair in WTF-16 | |
| 1994 | } | |
| 1995 | ||
| 1996 | fn testRoundtripWtf16(wtf16le: []const u16) !void { | |
| 1997 | // Buffer | |
| 1998 | { | |
| 1999 | var wtf8_buf: [32]u8 = undefined; | |
| 2000 | const wtf8_len = wtf16LeToWtf8(&wtf8_buf, wtf16le); | |
| 2001 | const wtf8 = wtf8_buf[0..wtf8_len]; | |
| 2002 | ||
| 2003 | var roundtripped_buf: [32]u16 = undefined; | |
| 2004 | const roundtripped_len = try wtf8ToWtf16Le(&roundtripped_buf, wtf8); | |
| 2005 | const roundtripped = roundtripped_buf[0..roundtripped_len]; | |
| 2006 | ||
| 2007 | try testing.expectEqualSlices(u16, wtf16le, roundtripped); | |
| 2008 | } | |
| 2009 | // Alloc | |
| 2010 | { | |
| 2011 | const wtf8 = try wtf16LeToWtf8Alloc(testing.allocator, wtf16le); | |
| 2012 | defer testing.allocator.free(wtf8); | |
| 2013 | ||
| 2014 | const roundtripped = try wtf8ToWtf16LeAlloc(testing.allocator, wtf8); | |
| 2015 | defer testing.allocator.free(roundtripped); | |
| 2016 | ||
| 2017 | try testing.expectEqualSlices(u16, wtf16le, roundtripped); | |
| 2018 | } | |
| 2019 | // AllocZ | |
| 2020 | { | |
| 2021 | const wtf8 = try wtf16LeToWtf8AllocZ(testing.allocator, wtf16le); | |
| 2022 | defer testing.allocator.free(wtf8); | |
| 2023 | ||
| 2024 | const roundtripped = try wtf8ToWtf16LeAllocZ(testing.allocator, wtf8); | |
| 2025 | defer testing.allocator.free(roundtripped); | |
| 2026 | ||
| 2027 | try testing.expectEqualSlices(u16, wtf16le, roundtripped); | |
| 2028 | } | |
| 2029 | } | |
| 2030 | ||
| 2031 | test "well-formed WTF-16 roundtrips" { | |
| 2032 | try testRoundtripWtf16(&[_]u16{ | |
| 2033 | std.mem.nativeToLittle(u16, 0xD83D), // high surrogate | |
| 2034 | std.mem.nativeToLittle(u16, 0xDCA9), // low surrogate | |
| 2035 | }); | |
| 2036 | try testRoundtripWtf16(&[_]u16{ | |
| 2037 | std.mem.nativeToLittle(u16, 0xD83D), // high surrogate | |
| 2038 | std.mem.nativeToLittle(u16, ' '), // not surrogate | |
| 2039 | std.mem.nativeToLittle(u16, 0xDCA9), // low surrogate | |
| 2040 | }); | |
| 2041 | try testRoundtripWtf16(&[_]u16{ | |
| 2042 | std.mem.nativeToLittle(u16, 0xD800), // high surrogate | |
| 2043 | std.mem.nativeToLittle(u16, 0xDBFF), // high surrogate | |
| 2044 | }); | |
| 2045 | try testRoundtripWtf16(&[_]u16{ | |
| 2046 | std.mem.nativeToLittle(u16, 0xD800), // high surrogate | |
| 2047 | std.mem.nativeToLittle(u16, 0xE000), // not surrogate | |
| 2048 | }); | |
| 2049 | try testRoundtripWtf16(&[_]u16{ | |
| 2050 | std.mem.nativeToLittle(u16, 0xD7FF), // not surrogate | |
| 2051 | std.mem.nativeToLittle(u16, 0xDC00), // low surrogate | |
| 2052 | }); | |
| 2053 | try testRoundtripWtf16(&[_]u16{ | |
| 2054 | std.mem.nativeToLittle(u16, 0x61), // not surrogate | |
| 2055 | std.mem.nativeToLittle(u16, 0xDC00), // low surrogate | |
| 2056 | }); | |
| 2057 | try testRoundtripWtf16(&[_]u16{ | |
| 2058 | std.mem.nativeToLittle(u16, 0xDC00), // low surrogate | |
| 2059 | }); | |
| 2060 | } |
lib/std/zig/system.zig+8-4| ... | ... | @@ -639,7 +639,8 @@ pub fn abiAndDynamicLinkerFromFile( |
| 639 | 639 | var link_buf: [std.os.PATH_MAX]u8 = undefined; |
| 640 | 640 | const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) { |
| 641 | 641 | error.NameTooLong => unreachable, |
| 642 | error.InvalidUtf8 => unreachable, // Windows only | |
| 642 | error.InvalidUtf8 => unreachable, // WASI only | |
| 643 | error.InvalidWtf8 => unreachable, // Windows only | |
| 643 | 644 | error.BadPathName => unreachable, // Windows only |
| 644 | 645 | error.UnsupportedReparsePointType => unreachable, // Windows only |
| 645 | 646 | error.NetworkNotFound => unreachable, // Windows only |
| ... | ... | @@ -730,7 +731,8 @@ test glibcVerFromLinkName { |
| 730 | 731 | fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion { |
| 731 | 732 | var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { |
| 732 | 733 | error.NameTooLong => unreachable, |
| 733 | error.InvalidUtf8 => unreachable, | |
| 734 | error.InvalidUtf8 => unreachable, // WASI only | |
| 735 | error.InvalidWtf8 => unreachable, // Windows-only | |
| 734 | 736 | error.BadPathName => unreachable, |
| 735 | 737 | error.DeviceBusy => unreachable, |
| 736 | 738 | error.NetworkNotFound => unreachable, // Windows-only |
| ... | ... | @@ -761,7 +763,8 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion { |
| 761 | 763 | const glibc_so_basename = "libc.so.6"; |
| 762 | 764 | var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) { |
| 763 | 765 | error.NameTooLong => unreachable, |
| 764 | error.InvalidUtf8 => unreachable, // Windows only | |
| 766 | error.InvalidUtf8 => unreachable, // WASI only | |
| 767 | error.InvalidWtf8 => unreachable, // Windows only | |
| 765 | 768 | error.BadPathName => unreachable, // Windows only |
| 766 | 769 | error.PipeBusy => unreachable, // Windows-only |
| 767 | 770 | error.SharingViolation => unreachable, // Windows-only |
| ... | ... | @@ -998,7 +1001,8 @@ fn detectAbiAndDynamicLinker( |
| 998 | 1001 | error.NameTooLong => unreachable, |
| 999 | 1002 | error.PathAlreadyExists => unreachable, |
| 1000 | 1003 | error.SharingViolation => unreachable, |
| 1001 | error.InvalidUtf8 => unreachable, | |
| 1004 | error.InvalidUtf8 => unreachable, // WASI only | |
| 1005 | error.InvalidWtf8 => unreachable, // Windows only | |
| 1002 | 1006 | error.BadPathName => unreachable, |
| 1003 | 1007 | error.PipeBusy => unreachable, |
| 1004 | 1008 | error.FileLocksNotSupported => unreachable, |
lib/std/zig/system/NativePaths.zig+2-2| ... | ... | @@ -41,7 +41,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths { |
| 41 | 41 | } |
| 42 | 42 | } |
| 43 | 43 | } else |err| switch (err) { |
| 44 | error.InvalidUtf8 => {}, | |
| 44 | error.InvalidWtf8 => unreachable, | |
| 45 | 45 | error.EnvironmentVariableNotFound => {}, |
| 46 | 46 | error.OutOfMemory => |e| return e, |
| 47 | 47 | } |
| ... | ... | @@ -73,7 +73,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths { |
| 73 | 73 | } |
| 74 | 74 | } |
| 75 | 75 | } else |err| switch (err) { |
| 76 | error.InvalidUtf8 => {}, | |
| 76 | error.InvalidWtf8 => unreachable, | |
| 77 | 77 | error.EnvironmentVariableNotFound => {}, |
| 78 | 78 | error.OutOfMemory => |e| return e, |
| 79 | 79 | } |
lib/std/zig/system/windows.zig+1-1| ... | ... | @@ -160,7 +160,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void { |
| 160 | 160 | => { |
| 161 | 161 | var buf = @field(args, field.name).value_buf; |
| 162 | 162 | const entry = @as(*align(1) const std.os.windows.UNICODE_STRING, @ptrCast(table[i + 1].EntryContext)); |
| 163 | const len = try std.unicode.utf16leToUtf8(buf, entry.Buffer[0 .. entry.Length / 2]); | |
| 163 | const len = try std.unicode.utf16LeToUtf8(buf, entry.Buffer[0 .. entry.Length / 2]); | |
| 164 | 164 | buf[len] = 0; |
| 165 | 165 | }, |
| 166 | 166 |
src/Module.zig+1| ... | ... | @@ -2662,6 +2662,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { |
| 2662 | 2662 | }) catch |err| switch (err) { |
| 2663 | 2663 | error.NotDir => unreachable, // no dir components |
| 2664 | 2664 | error.InvalidUtf8 => unreachable, // it's a hex encoded name |
| 2665 | error.InvalidWtf8 => unreachable, // it's a hex encoded name | |
| 2665 | 2666 | error.BadPathName => unreachable, // it's a hex encoded name |
| 2666 | 2667 | error.NameTooLong => unreachable, // it's a fixed size name |
| 2667 | 2668 | error.PipeBusy => unreachable, // it's not a pipe |
src/libc_installation.zig+8-2| ... | ... | @@ -246,7 +246,10 @@ pub const LibCInstallation = struct { |
| 246 | 246 | const allocator = args.allocator; |
| 247 | 247 | |
| 248 | 248 | // Detect infinite loops. |
| 249 | var env_map = try std.process.getEnvMap(allocator); | |
| 249 | var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { | |
| 250 | error.Unexpected => unreachable, // WASI-only | |
| 251 | else => |e| return e, | |
| 252 | }; | |
| 250 | 253 | defer env_map.deinit(); |
| 251 | 254 | const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: { |
| 252 | 255 | if (std.mem.eql(u8, phase, "1")) { |
| ... | ... | @@ -572,7 +575,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { |
| 572 | 575 | const allocator = args.allocator; |
| 573 | 576 | |
| 574 | 577 | // Detect infinite loops. |
| 575 | var env_map = try std.process.getEnvMap(allocator); | |
| 578 | var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) { | |
| 579 | error.Unexpected => unreachable, // WASI-only | |
| 580 | else => |e| return e, | |
| 581 | }; | |
| 576 | 582 | defer env_map.deinit(); |
| 577 | 583 | const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: { |
| 578 | 584 | if (std.mem.eql(u8, phase, "1")) { |
src/main.zig+1-1| ... | ... | @@ -5756,7 +5756,7 @@ fn readSourceFileToEndAlloc( |
| 5756 | 5756 | // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8 |
| 5757 | 5757 | if (mem.startsWith(u8, source_code, "\xff\xfe")) { |
| 5758 | 5758 | const source_code_utf16_le = mem.bytesAsSlice(u16, source_code); |
| 5759 | const source_code_utf8 = std.unicode.utf16leToUtf8AllocZ(allocator, source_code_utf16_le) catch |err| switch (err) { | |
| 5759 | const source_code_utf8 = std.unicode.utf16LeToUtf8AllocZ(allocator, source_code_utf16_le) catch |err| switch (err) { | |
| 5760 | 5760 | error.DanglingSurrogateHalf => error.UnsupportedEncoding, |
| 5761 | 5761 | error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding, |
| 5762 | 5762 | error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding, |
src/windows_sdk.zig+87-90| ... | ... | @@ -84,26 +84,26 @@ fn iterateAndFilterBySemVer( |
| 84 | 84 | return dirs_filtered_slice; |
| 85 | 85 | } |
| 86 | 86 | |
| 87 | const RegistryUtf8 = struct { | |
| 87 | const RegistryWtf8 = struct { | |
| 88 | 88 | key: windows.HKEY, |
| 89 | 89 | |
| 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, | |
| 96 | 96 | }; |
| 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]; | |
| 99 | 99 | }; |
| 100 | 100 | |
| 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 }; | |
| 103 | 103 | } |
| 104 | 104 | |
| 105 | 105 | /// Closes key, after that usage is invalid |
| 106 | pub fn closeKey(self: *const RegistryUtf8) void { | |
| 106 | pub fn closeKey(self: *const RegistryWtf8) void { | |
| 107 | 107 | const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key); |
| 108 | 108 | const return_code: windows.Win32Error = @enumFromInt(return_code_int); |
| 109 | 109 | switch (return_code) { |
| ... | ... | @@ -114,71 +114,68 @@ const RegistryUtf8 = struct { |
| 114 | 114 | |
| 115 | 115 | /// Get string from registry. |
| 116 | 116 | /// Caller owns result. |
| 117 | pub fn getString(self: *const RegistryUtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 { | |
| 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]; | |
| 123 | 123 | }; |
| 124 | 124 | |
| 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]; | |
| 130 | 130 | }; |
| 131 | 131 | |
| 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); | |
| 135 | 135 | |
| 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); | |
| 141 | 138 | |
| 142 | return value_utf8; | |
| 139 | return value_wtf8; | |
| 143 | 140 | } |
| 144 | 141 | |
| 145 | 142 | /// Get DWORD (u32) from registry. |
| 146 | pub fn getDword(self: *const RegistryUtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 { | |
| 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]; | |
| 152 | 149 | }; |
| 153 | 150 | |
| 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]; | |
| 159 | 156 | }; |
| 160 | 157 | |
| 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); | |
| 163 | 160 | } |
| 164 | 161 | |
| 165 | 162 | /// Under private space with flags: |
| 166 | 163 | /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS. |
| 167 | 164 | /// 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]; | |
| 174 | 171 | }; |
| 175 | 172 | |
| 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 }; | |
| 178 | 175 | } |
| 179 | 176 | }; |
| 180 | 177 | |
| 181 | const RegistryUtf16Le = struct { | |
| 178 | const RegistryWtf16Le = struct { | |
| 182 | 179 | key: windows.HKEY, |
| 183 | 180 | |
| 184 | 181 | /// Includes root key (f.e. HKEY_LOCAL_MACHINE). |
| ... | ... | @@ -191,11 +188,11 @@ const RegistryUtf16Le = struct { |
| 191 | 188 | /// Under HKEY_LOCAL_MACHINE with flags: |
| 192 | 189 | /// KEY_QUERY_VALUE, KEY_WOW64_32KEY, and KEY_ENUMERATE_SUB_KEYS. |
| 193 | 190 | /// After finishing work, call `closeKey`. |
| 194 | fn openKey(hkey: windows.HKEY, key_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le { | |
| 191 | fn openKey(hkey: windows.HKEY, key_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le { | |
| 195 | 192 | var key: windows.HKEY = undefined; |
| 196 | 193 | const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW( |
| 197 | 194 | hkey, |
| 198 | key_utf16le, | |
| 195 | key_wtf16le, | |
| 199 | 196 | 0, |
| 200 | 197 | windows.KEY_QUERY_VALUE | windows.KEY_WOW64_32KEY | windows.KEY_ENUMERATE_SUB_KEYS, |
| 201 | 198 | &key, |
| ... | ... | @@ -207,11 +204,11 @@ const RegistryUtf16Le = struct { |
| 207 | 204 | |
| 208 | 205 | else => return error.KeyNotFound, |
| 209 | 206 | } |
| 210 | return RegistryUtf16Le{ .key = key }; | |
| 207 | return RegistryWtf16Le{ .key = key }; | |
| 211 | 208 | } |
| 212 | 209 | |
| 213 | 210 | /// Closes key, after that usage is invalid |
| 214 | fn closeKey(self: *const RegistryUtf16Le) void { | |
| 211 | fn closeKey(self: *const RegistryWtf16Le) void { | |
| 215 | 212 | const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key); |
| 216 | 213 | const return_code: windows.Win32Error = @enumFromInt(return_code_int); |
| 217 | 214 | switch (return_code) { |
| ... | ... | @@ -221,25 +218,25 @@ const RegistryUtf16Le = struct { |
| 221 | 218 | } |
| 222 | 219 | |
| 223 | 220 | /// Get string ([:0]const u16) from registry. |
| 224 | fn getString(self: *const RegistryUtf16Le, allocator: std.mem.Allocator, subkey_utf16le: [:0]const u16, value_name_utf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 { | |
| 221 | fn getString(self: *const RegistryWtf16Le, allocator: std.mem.Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 { | |
| 225 | 222 | var actual_type: windows.ULONG = undefined; |
| 226 | 223 | |
| 227 | 224 | // Calculating length to allocate |
| 228 | var value_utf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters. | |
| 225 | var value_wtf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters. | |
| 229 | 226 | var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW( |
| 230 | 227 | self.key, |
| 231 | subkey_utf16le, | |
| 232 | value_name_utf16le, | |
| 228 | subkey_wtf16le, | |
| 229 | value_name_wtf16le, | |
| 233 | 230 | RRF.RT_REG_SZ, |
| 234 | 231 | &actual_type, |
| 235 | 232 | null, |
| 236 | &value_utf16le_buf_size, | |
| 233 | &value_wtf16le_buf_size, | |
| 237 | 234 | ); |
| 238 | 235 | |
| 239 | 236 | // Check returned code and type |
| 240 | 237 | var return_code: windows.Win32Error = @enumFromInt(return_code_int); |
| 241 | 238 | switch (return_code) { |
| 242 | .SUCCESS => std.debug.assert(value_utf16le_buf_size != 0), | |
| 239 | .SUCCESS => std.debug.assert(value_wtf16le_buf_size != 0), | |
| 243 | 240 | .MORE_DATA => unreachable, // We are only reading length |
| 244 | 241 | .FILE_NOT_FOUND => return error.ValueNameNotFound, |
| 245 | 242 | .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY |
| ... | ... | @@ -250,17 +247,17 @@ const RegistryUtf16Le = struct { |
| 250 | 247 | else => return error.NotAString, |
| 251 | 248 | } |
| 252 | 249 | |
| 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); | |
| 255 | 252 | |
| 256 | 253 | return_code_int = windows.advapi32.RegGetValueW( |
| 257 | 254 | self.key, |
| 258 | subkey_utf16le, | |
| 259 | value_name_utf16le, | |
| 255 | subkey_wtf16le, | |
| 256 | value_name_wtf16le, | |
| 260 | 257 | RRF.RT_REG_SZ, |
| 261 | 258 | &actual_type, |
| 262 | value_utf16le_buf.ptr, | |
| 263 | &value_utf16le_buf_size, | |
| 259 | value_wtf16le_buf.ptr, | |
| 260 | &value_wtf16le_buf_size, | |
| 264 | 261 | ); |
| 265 | 262 | |
| 266 | 263 | // Check returned code and (just in case) type again. |
| ... | ... | @@ -277,28 +274,28 @@ const RegistryUtf16Le = struct { |
| 277 | 274 | else => return error.NotAString, |
| 278 | 275 | } |
| 279 | 276 | |
| 280 | const value_utf16le: []const u16 = value_utf16le: { | |
| 277 | const value_wtf16le: []const u16 = value_wtf16le: { | |
| 281 | 278 | // note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space |
| 282 | 279 | // we will just search for zero termination and forget length |
| 283 | 280 | // 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); | |
| 286 | 283 | }; |
| 287 | 284 | |
| 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; | |
| 290 | 287 | } |
| 291 | 288 | |
| 292 | 289 | /// Get DWORD (u32) from registry. |
| 293 | fn getDword(self: *const RegistryUtf16Le, subkey_utf16le: [:0]const u16, value_name_utf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 { | |
| 290 | fn getDword(self: *const RegistryWtf16Le, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 { | |
| 294 | 291 | var actual_type: windows.ULONG = undefined; |
| 295 | 292 | var reg_size: u32 = @sizeOf(u32); |
| 296 | 293 | var reg_value: u32 = 0; |
| 297 | 294 | |
| 298 | 295 | const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW( |
| 299 | 296 | self.key, |
| 300 | subkey_utf16le, | |
| 301 | value_name_utf16le, | |
| 297 | subkey_wtf16le, | |
| 298 | value_name_wtf16le, | |
| 302 | 299 | RRF.RT_REG_DWORD, |
| 303 | 300 | &actual_type, |
| 304 | 301 | &reg_value, |
| ... | ... | @@ -324,11 +321,11 @@ const RegistryUtf16Le = struct { |
| 324 | 321 | /// Under private space with flags: |
| 325 | 322 | /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS. |
| 326 | 323 | /// After finishing work, call `closeKey`. |
| 327 | fn loadFromPath(absolute_path_as_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le { | |
| 324 | fn loadFromPath(absolute_path_as_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le { | |
| 328 | 325 | var key: windows.HKEY = undefined; |
| 329 | 326 | |
| 330 | 327 | const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW( |
| 331 | absolute_path_as_utf16le, | |
| 328 | absolute_path_as_wtf16le, | |
| 332 | 329 | &key, |
| 333 | 330 | windows.KEY_QUERY_VALUE | windows.KEY_ENUMERATE_SUB_KEYS, |
| 334 | 331 | 0, |
| ... | ... | @@ -340,7 +337,7 @@ const RegistryUtf16Le = struct { |
| 340 | 337 | else => return error.KeyNotFound, |
| 341 | 338 | } |
| 342 | 339 | |
| 343 | return RegistryUtf16Le{ .key = key }; | |
| 340 | return RegistryWtf16Le{ .key = key }; | |
| 344 | 341 | } |
| 345 | 342 | }; |
| 346 | 343 | |
| ... | ... | @@ -352,7 +349,7 @@ pub const Windows10Sdk = struct { |
| 352 | 349 | /// Caller owns the result's fields. |
| 353 | 350 | /// After finishing work, call `free(allocator)`. |
| 354 | 351 | fn find(allocator: std.mem.Allocator) error{ OutOfMemory, Windows10SdkNotFound, PathTooLong, VersionTooLong }!Windows10Sdk { |
| 355 | const v10_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) { | |
| 352 | const v10_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) { | |
| 356 | 353 | error.KeyNotFound => return error.Windows10SdkNotFound, |
| 357 | 354 | }; |
| 358 | 355 | defer v10_key.closeKey(); |
| ... | ... | @@ -413,11 +410,11 @@ pub const Windows10Sdk = struct { |
| 413 | 410 | /// Check whether this version is enumerated in registry. |
| 414 | 411 | fn isValidVersion(windows10sdk: *const Windows10Sdk) bool { |
| 415 | 412 | var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; |
| 416 | const reg_query_as_utf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) { | |
| 413 | const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) { | |
| 417 | 414 | error.NoSpaceLeft => return false, |
| 418 | 415 | }; |
| 419 | 416 | |
| 420 | const options_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_utf8) catch |err| switch (err) { | |
| 417 | const options_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_wtf8) catch |err| switch (err) { | |
| 421 | 418 | error.KeyNotFound => return false, |
| 422 | 419 | }; |
| 423 | 420 | defer options_key.closeKey(); |
| ... | ... | @@ -447,7 +444,7 @@ pub const Windows81Sdk = struct { |
| 447 | 444 | /// Find path and version of Windows 8.1 SDK. |
| 448 | 445 | /// Caller owns the result's fields. |
| 449 | 446 | /// After finishing work, call `free(allocator)`. |
| 450 | fn find(allocator: std.mem.Allocator, roots_key: *const RegistryUtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk { | |
| 447 | fn find(allocator: std.mem.Allocator, roots_key: *const RegistryWtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk { | |
| 451 | 448 | const path: []const u8 = path81: { |
| 452 | 449 | const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", "KitsRoot81") catch |err| switch (err) { |
| 453 | 450 | error.NotAString => return error.Windows81SdkNotFound, |
| ... | ... | @@ -523,7 +520,7 @@ pub const ZigWindowsSDK = struct { |
| 523 | 520 | if (builtin.os.tag != .windows) return error.NotFound; |
| 524 | 521 | |
| 525 | 522 | //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed |
| 526 | const roots_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) { | |
| 523 | const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) { | |
| 527 | 524 | error.KeyNotFound => return error.NotFound, |
| 528 | 525 | }; |
| 529 | 526 | defer roots_key.closeKey(); |
| ... | ... | @@ -583,7 +580,7 @@ pub const ZigWindowsSDK = struct { |
| 583 | 580 | const MsvcLibDir = struct { |
| 584 | 581 | fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { |
| 585 | 582 | const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}"; |
| 586 | const setup_config_key = RegistryUtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) { | |
| 583 | const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) { | |
| 587 | 584 | error.KeyNotFound => return error.PathNotFound, |
| 588 | 585 | }; |
| 589 | 586 | defer setup_config_key.closeKey(); |
| ... | ... | @@ -805,13 +802,13 @@ const MsvcLibDir = struct { |
| 805 | 802 | for (vs_versions) |vs_version| allocator.free(vs_version); |
| 806 | 803 | allocator.free(vs_versions); |
| 807 | 804 | } |
| 808 | var config_subkey_buf: [RegistryUtf16Le.key_name_max_len * 2]u8 = undefined; | |
| 805 | var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined; | |
| 809 | 806 | const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| { |
| 810 | 807 | const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue; |
| 811 | 808 | defer allocator.free(privateregistry_absolute_path); |
| 812 | 809 | if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue; |
| 813 | 810 | |
| 814 | const visualstudio_registry = RegistryUtf8.loadFromPath(privateregistry_absolute_path) catch continue; | |
| 811 | const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue; | |
| 815 | 812 | defer visualstudio_registry.closeKey(); |
| 816 | 813 | |
| 817 | 814 | const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable; |
| ... | ... | @@ -894,7 +891,7 @@ const MsvcLibDir = struct { |
| 894 | 891 | } |
| 895 | 892 | } |
| 896 | 893 | |
| 897 | const vs7_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound; | |
| 894 | const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound; | |
| 898 | 895 | defer vs7_key.closeKey(); |
| 899 | 896 | try_vs7_key: { |
| 900 | 897 | const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) { |
test/standalone/windows_spawn/main.zig+1-1| ... | ... | @@ -17,7 +17,7 @@ pub fn main() anyerror!void { |
| 17 | 17 | |
| 18 | 18 | const tmp_absolute_path = try tmp.dir.realpathAlloc(allocator, "."); |
| 19 | 19 | defer allocator.free(tmp_absolute_path); |
| 20 | const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeWithNull(allocator, tmp_absolute_path); | |
| 20 | const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeAllocZ(allocator, tmp_absolute_path); | |
| 21 | 21 | defer allocator.free(tmp_absolute_path_w); |
| 22 | 22 | const cwd_absolute_path = try std.fs.cwd().realpathAlloc(allocator, "."); |
| 23 | 23 | defer allocator.free(cwd_absolute_path); |