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

Windows paths now use WTF-16 <-> WTF-8 conversion everywhere, which is lossless. Previously, conversion of ill-formed UTF-16 paths would either fail or invoke illegal behavior.

WASI paths must be valid UTF-8, and the relevant function calls have been updated to handle the possibility of failure due to paths not being encoded/encodable as valid UTF-8.

Closes #18694
Closes #1774
Closes #2565
This commit is contained in:
Ryan Liptak 2024-02-13 16:56:50 -08:00
parent f6b6b8a4ae
commit 68b87918df
20 changed files with 1000 additions and 1109 deletions

View File

@ -69,7 +69,7 @@ pub const Environment = struct {
const val: ?[]const u8 = std.process.getEnvVarOwned(allocator, env_var_name) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
error.EnvironmentVariableNotFound => null,
error.InvalidUtf8 => null,
error.InvalidWtf8 => null,
};
@field(env, field.name) = val;
}

View File

@ -523,7 +523,8 @@ pub fn errorDescription(e: anyerror) []const u8 {
error.NotDir => "is not a directory",
error.NotOpenForReading => "file is not open for reading",
error.NotOpenForWriting => "file is not open for writing",
error.InvalidUtf8 => "input is not valid UTF-8",
error.InvalidUtf8 => "path is not valid UTF-8",
error.InvalidWtf8 => "path is not valid WTF-8",
error.FileBusy => "file is busy",
error.NameTooLong => "file name is too long",
error.AccessDenied => "access denied",

View File

@ -162,7 +162,7 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
const relative = try std.fs.path.relative(allocator, prefix, path);
errdefer allocator.free(relative);
var component_iterator = std.fs.path.NativeUtf8ComponentIterator.init(relative) catch {
var component_iterator = std.fs.path.NativeComponentIterator.init(relative) catch {
return error.NotASubPath;
};
if (component_iterator.root() != null) {

View File

@ -91,7 +91,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
},
.windows => {
var buf: [max_name_len]u16 = undefined;
const len = try std.unicode.utf8ToUtf16Le(&buf, name);
const len = try std.unicode.wtf8ToWtf16Le(&buf, name);
const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong;
// Note: NT allocates its own copy, no use-after-free here.
@ -157,17 +157,12 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
}
pub const GetNameError = error{
// For Windows, the name is converted from UTF16 to UTF8
CodepointTooLarge,
Utf8CannotEncodeSurrogateHalf,
DanglingSurrogateHalf,
ExpectedSecondSurrogateHalf,
UnexpectedSecondSurrogateHalf,
Unsupported,
Unexpected,
} || os.PrctlError || os.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError;
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 {
buffer_ptr[max_name_len] = 0;
var buffer: [:0]u8 = buffer_ptr;
@ -213,7 +208,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
)) {
.SUCCESS => {
const string = @as(*const os.windows.UNICODE_STRING, @ptrCast(&buf));
const len = try std.unicode.utf16LeToUtf8(buffer, string.Buffer[0 .. string.Length / 2]);
const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer[0 .. string.Length / 2]);
return if (len > 0) buffer[0..len] else null;
},
.NOT_IMPLEMENTED => return error.Unsupported,

View File

@ -129,10 +129,9 @@ pub const ChildProcess = struct {
/// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.
NoDevice,
/// Windows-only. One of:
/// * `cwd` was provided and it could not be re-encoded into UTF16LE, or
/// * The `PATH` or `PATHEXT` environment variable contained invalid UTF-8.
InvalidUtf8,
/// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
/// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
CurrentWorkingDirectoryUnlinked,
@ -767,7 +766,7 @@ pub const ChildProcess = struct {
};
var piProcInfo: windows.PROCESS_INFORMATION = undefined;
const cwd_w = if (self.cwd) |cwd| try unicode.utf8ToUtf16LeAllocZ(self.allocator, cwd) else null;
const cwd_w = if (self.cwd) |cwd| try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd) else null;
defer if (cwd_w) |cwd| self.allocator.free(cwd);
const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
@ -775,8 +774,8 @@ pub const ChildProcess = struct {
defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
const app_name_utf8 = self.argv[0];
const app_name_is_absolute = fs.path.isAbsolute(app_name_utf8);
const app_name_wtf8 = self.argv[0];
const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8);
// the cwd set in ChildProcess is in effect when choosing the executable path
// to match posix semantics
@ -785,11 +784,11 @@ pub const ChildProcess = struct {
// If the app name is absolute, then we need to use its dirname as the cwd
if (app_name_is_absolute) {
cwd_path_w_needs_free = true;
const dir = fs.path.dirname(app_name_utf8).?;
break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, dir);
const dir = fs.path.dirname(app_name_wtf8).?;
break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, dir);
} else if (self.cwd) |cwd| {
cwd_path_w_needs_free = true;
break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, cwd);
break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd);
} else {
break :x &[_:0]u16{}; // empty for cwd
}
@ -800,19 +799,19 @@ pub const ChildProcess = struct {
// into the basename and dirname and use the dirname as an addition to the cwd
// path. This is because NtQueryDirectoryFile cannot accept FileName params with
// path separators.
const app_basename_utf8 = fs.path.basename(app_name_utf8);
const app_basename_wtf8 = fs.path.basename(app_name_wtf8);
// If the app name is absolute, then the cwd will already have the app's dirname in it,
// so only populate app_dirname if app name is a relative path with > 0 path separators.
const maybe_app_dirname_utf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_utf8) else null;
const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_wtf8) else null;
const app_dirname_w: ?[:0]u16 = x: {
if (maybe_app_dirname_utf8) |app_dirname_utf8| {
break :x try unicode.utf8ToUtf16LeAllocZ(self.allocator, app_dirname_utf8);
if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_dirname_wtf8);
}
break :x null;
};
defer if (app_dirname_w != null) self.allocator.free(app_dirname_w.?);
const app_name_w = try unicode.utf8ToUtf16LeAllocZ(self.allocator, app_basename_utf8);
const app_name_w = try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_basename_wtf8);
defer self.allocator.free(app_name_w);
const cmd_line_w = argvToCommandLineWindows(self.allocator, self.argv) catch |err| switch (err) {
@ -1173,7 +1172,7 @@ const CreateProcessSupportedExtension = enum {
exe,
};
/// Case-insensitive UTF-16 lookup
/// Case-insensitive WTF-16 lookup
fn windowsCreateProcessSupportsExtension(ext: []const u16) ?CreateProcessSupportedExtension {
if (ext.len != 4) return null;
const State = enum {
@ -1237,7 +1236,7 @@ test "windowsCreateProcessSupportsExtension" {
try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null);
}
pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidUtf8, InvalidArg0 };
pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and
/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice.
@ -1320,7 +1319,7 @@ pub fn argvToCommandLineWindows(
}
}
return try unicode.utf8ToUtf16LeAllocZ(allocator, buf.items);
return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
}
test "argvToCommandLineWindows" {
@ -1386,7 +1385,7 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c
const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv);
defer std.testing.allocator.free(cmd_line_w);
const cmd_line = try unicode.utf16LeToUtf8Alloc(std.testing.allocator, cmd_line_w);
const cmd_line = try unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w);
defer std.testing.allocator.free(cmd_line);
try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
@ -1424,7 +1423,7 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons
"\\\\.\\pipe\\zig-childprocess-{d}-{d}",
.{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .Monotonic) },
) catch unreachable;
const len = std.unicode.utf8ToUtf16Le(&tmp_bufw, pipe_path) catch unreachable;
const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
tmp_bufw[len] = 0;
break :blk tmp_bufw[0..len :0];
};
@ -1521,10 +1520,10 @@ pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) !
var it = env_map.iterator();
var i: usize = 0;
while (it.next()) |pair| {
i += try unicode.utf8ToUtf16Le(result[i..], pair.key_ptr.*);
i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
result[i] = '=';
i += 1;
i += try unicode.utf8ToUtf16Le(result[i..], pair.value_ptr.*);
i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
result[i] = 0;
i += 1;
}

View File

@ -31,18 +31,21 @@ pub const realpathW = os.realpathW;
pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
/// This represents the maximum size of a UTF-8 encoded file path that the
/// This represents the maximum size of a `[]u8` file path that the
/// operating system will accept. Paths, including those returned from file
/// system operations, may be longer than this length, but such paths cannot
/// be successfully passed back in other file system operations. However,
/// all path components returned by file system operations are assumed to
/// fit into a UTF-8 encoded array of this length.
/// fit into a `u8` array of this length.
/// The byte count includes room for a null sentinel byte.
/// On Windows, `[]u8` file paths are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `[]u8` file paths are encoded as valid UTF-8.
/// On other platforms, `[]u8` file paths are opaque sequences of bytes with no particular encoding.
pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
.linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten => os.PATH_MAX,
// Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
// If it would require 4 UTF-8 bytes, then there would be a surrogate
// pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
// Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes.
// If it would require 4 WTF-8 bytes, then there would be a surrogate
// pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
// +1 for the null byte at the end, which can be encoded in 1 byte.
.windows => os.windows.PATH_MAX_WIDE * 3 + 1,
// TODO work out what a reasonable value we should use here
@ -53,18 +56,21 @@ pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
@compileError("PATH_MAX not implemented for " ++ @tagName(builtin.os.tag)),
};
/// This represents the maximum size of a UTF-8 encoded file name component that
/// This represents the maximum size of a `[]u8` file name component that
/// the platform's common file systems support. File name components returned by file system
/// operations are likely to fit into a UTF-8 encoded array of this length, but
/// operations are likely to fit into a `u8` array of this length, but
/// (depending on the platform) this assumption may not hold for every configuration.
/// The byte count does not include a null sentinel byte.
/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, file name components are encoded as valid UTF-8.
/// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding.
pub const MAX_NAME_BYTES = switch (builtin.os.tag) {
.linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos => os.NAME_MAX,
// Haiku's NAME_MAX includes the null terminator, so subtract one.
.haiku => os.NAME_MAX - 1,
// Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
// If it would require 4 UTF-8 bytes, then there would be a surrogate
// pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
// Each WTF-16LE character may be expanded to 3 WTF-8 bytes.
// If it would require 4 WTF-8 bytes, then there would be a surrogate
// pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
.windows => os.windows.NAME_MAX * 3,
// For WASI, the MAX_NAME will depend on the host OS, so it needs to be
// as large as the largest MAX_NAME_BYTES (Windows) in order to work on any host OS.
@ -86,6 +92,9 @@ pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);
/// TODO remove the allocator requirement from this API
/// TODO move to Dir
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
if (cwd().symLink(existing_path, new_path, .{})) {
return;
@ -117,6 +126,9 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:
/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`
/// are absolute. See `Dir.updateFile` for a function that operates on both
/// absolute and relative paths.
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn updateFileAbsolute(
source_path: []const u8,
dest_path: []const u8,
@ -131,6 +143,9 @@ pub fn updateFileAbsolute(
/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
/// are absolute. See `Dir.copyFile` for a function that operates on both
/// absolute and relative paths.
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn copyFileAbsolute(
source_path: []const u8,
dest_path: []const u8,
@ -145,24 +160,30 @@ pub fn copyFileAbsolute(
/// Create a new directory, based on an absolute path.
/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
/// on both absolute and relative paths.
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
pub fn makeDirAbsolute(absolute_path: []const u8) !void {
assert(path.isAbsolute(absolute_path));
return os.mkdir(absolute_path, Dir.default_mode);
}
/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF-8-encoded string.
/// Same as `makeDirAbsolute` except the parameter is null-terminated.
pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
assert(path.isAbsoluteZ(absolute_path_z));
return os.mkdirZ(absolute_path_z, Dir.default_mode);
}
/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16-encoded string.
/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.
pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
assert(path.isAbsoluteWindowsW(absolute_path_w));
return os.mkdirW(absolute_path_w, Dir.default_mode);
}
/// Same as `Dir.deleteDir` except the path is absolute.
/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `dir_path` should be encoded as valid UTF-8.
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
pub fn deleteDirAbsolute(dir_path: []const u8) !void {
assert(path.isAbsolute(dir_path));
return os.rmdir(dir_path);
@ -181,6 +202,9 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {
}
/// Same as `Dir.rename` except the paths are absolute.
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
assert(path.isAbsolute(old_path));
assert(path.isAbsolute(new_path));
@ -211,7 +235,7 @@ pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_su
return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
}
/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
/// This function is Windows-only.
pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
@ -240,6 +264,9 @@ pub fn defaultWasiCwd() std.os.wasi.fd_t {
/// See `openDirAbsoluteZ` for a function that accepts a null-terminated path.
///
/// Asserts that the path parameter has no null bytes.
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
assert(path.isAbsolute(absolute_path));
return cwd().openDir(absolute_path, flags);
@ -262,6 +289,9 @@ pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptio
/// operates on both absolute and relative paths.
/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function
/// that accepts a null-terminated path.
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
assert(path.isAbsolute(absolute_path));
return cwd().openFile(absolute_path, flags);
@ -280,11 +310,13 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
}
/// Test accessing `path`.
/// `path` is UTF-8-encoded.
/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
/// For example, instead of testing if a file exists and then opening it, just
/// open it and handle the error for file not found.
/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
assert(path.isAbsolute(absolute_path));
try cwd().access(absolute_path, flags);
@ -306,6 +338,9 @@ pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir
/// operates on both absolute and relative paths.
/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function
/// that accepts a null-terminated path.
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
assert(path.isAbsolute(absolute_path));
return cwd().createFile(absolute_path, flags);
@ -327,6 +362,9 @@ pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFl
/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that
/// operates on both absolute and relative paths.
/// Asserts that the path parameter has no null bytes.
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
assert(path.isAbsolute(absolute_path));
return cwd().deleteFile(absolute_path);
@ -349,6 +387,9 @@ pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!
/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that
/// operates on both absolute and relative paths.
/// Asserts that the path parameter has no null bytes.
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
assert(path.isAbsolute(absolute_path));
const dirname = path.dirname(absolute_path) orelse return error{
@ -364,6 +405,9 @@ pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
}
/// Same as `Dir.readLink`, except it asserts the path is absolute.
/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `pathname` should be encoded as valid UTF-8.
/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
assert(path.isAbsolute(pathname));
return os.readlink(pathname, buffer);
@ -387,6 +431,9 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8)
/// one; the latter case is known as a dangling link.
/// If `sym_link_path` exists, it will not be overwritten.
/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn symLinkAbsolute(
target_path: []const u8,
sym_link_path: []const u8,
@ -402,7 +449,7 @@ pub fn symLinkAbsolute(
return os.symlink(target_path, sym_link_path);
}
/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 encoded.
/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 LE encoded.
/// Note that this function will by default try creating a symbolic link to a file. If you would
/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
@ -426,27 +473,14 @@ pub fn symLinkAbsoluteZ(
assert(path.isAbsoluteZ(target_path_c));
assert(path.isAbsoluteZ(sym_link_path_c));
if (builtin.os.tag == .windows) {
const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
const target_path_w = try os.windows.cStrToPrefixedFileW(null, target_path_c);
const sym_link_path_w = try os.windows.cStrToPrefixedFileW(null, sym_link_path_c);
return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
}
return os.symlinkZ(target_path_c, sym_link_path_c);
}
pub const OpenSelfExeError = error{
SharingViolation,
PathAlreadyExists,
FileNotFound,
AccessDenied,
PipeBusy,
NameTooLong,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
Unexpected,
} || os.OpenError || SelfExePathError || os.FlockError;
pub const OpenSelfExeError = os.OpenError || SelfExePathError || os.FlockError;
pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
if (builtin.os.tag == .linux) {
@ -469,7 +503,45 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags);
}
pub const SelfExePathError = os.ReadLinkError || os.SysCtlError || os.RealPathError;
// This is os.ReadLinkError || os.RealPathError with impossible errors excluded
pub const SelfExePathError = error{
FileNotFound,
AccessDenied,
NameTooLong,
NotSupported,
NotDir,
SymLinkLoop,
InputOutput,
FileTooBig,
IsDir,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
NoDevice,
SystemResources,
NoSpaceLeft,
FileSystem,
BadPathName,
DeviceBusy,
SharingViolation,
PipeBusy,
NotLink,
PathAlreadyExists,
InvalidHandle,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
/// On Windows, antivirus software is enabled by default. It can be
/// disabled, but Windows Update sometimes ignores the user's preference
/// and re-enables it. When enabled, antivirus software on Windows
/// intercepts file system operations and makes them significantly slower
/// in addition to possibly failing with this error code.
AntivirusInterference,
/// On Windows, the volume does not contain a recognized file system. File
/// system drivers might not be loaded, or the volume may be corrupt.
UnrecognizedVolume,
} || os.SysCtlError;
/// `selfExePath` except allocates the result on the heap.
/// Caller owns returned memory.
@ -491,6 +563,8 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {
/// This function may return an error if the current executable
/// was deleted after spawning.
/// Returned value is a slice of out_buffer.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
///
/// On Linux, depends on procfs being mounted. If the currently executing binary has
/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
@ -505,15 +579,31 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
if (rc != 0) return error.NameTooLong;
var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
const real_path = try std.os.realpathZ(&symlink_path_buf, &real_path_buf);
const real_path = std.os.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) {
error.InvalidWtf8 => unreachable, // Windows-only
error.NetworkNotFound => unreachable, // Windows-only
else => |e| return e,
};
if (real_path.len > out_buffer.len) return error.NameTooLong;
const result = out_buffer[0..real_path.len];
@memcpy(result, real_path);
return result;
}
switch (builtin.os.tag) {
.linux => return os.readlinkZ("/proc/self/exe", out_buffer),
.solaris, .illumos => return os.readlinkZ("/proc/self/path/a.out", out_buffer),
.linux => return os.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) {
error.InvalidUtf8 => unreachable, // WASI-only
error.InvalidWtf8 => unreachable, // Windows-only
error.UnsupportedReparsePointType => unreachable, // Windows-only
error.NetworkNotFound => unreachable, // Windows-only
else => |e| return e,
},
.solaris, .illumos => return os.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) {
error.InvalidUtf8 => unreachable, // WASI-only
error.InvalidWtf8 => unreachable, // Windows-only
error.UnsupportedReparsePointType => unreachable, // Windows-only
error.NetworkNotFound => unreachable, // Windows-only
else => |e| return e,
},
.freebsd, .dragonfly => {
var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC, os.KERN.PROC_PATHNAME, -1 };
var out_len: usize = out_buffer.len;
@ -537,7 +627,11 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
if (mem.indexOf(u8, argv0, "/") != null) {
// argv[0] is a path (relative or absolute): use realpath(3) directly
var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
const real_path = try os.realpathZ(os.argv[0], &real_path_buf);
const real_path = os.realpathZ(os.argv[0], &real_path_buf) catch |err| switch (err) {
error.InvalidWtf8 => unreachable, // Windows-only
error.NetworkNotFound => unreachable, // Windows-only
else => |e| return e,
};
if (real_path.len > out_buffer.len)
return error.NameTooLong;
const result = out_buffer[0..real_path.len];
@ -575,7 +669,10 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
// symlink, not the path that the symlink points to. We want the path
// that the symlink points to, though, so we need to get the realpath.
const pathname_w = try os.windows.wToPrefixedFileW(null, image_path_name);
return std.fs.cwd().realpathW(pathname_w.span(), out_buffer);
return std.fs.cwd().realpathW(pathname_w.span(), out_buffer) catch |err| switch (err) {
error.InvalidWtf8 => unreachable,
else => |e| return e,
};
},
else => @compileError("std.fs.selfExePath not supported for this target"),
}
@ -599,6 +696,8 @@ pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 {
/// Get the directory path that contains the current executable.
/// Returned value is a slice of out_buffer.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
const self_exe_path = try selfExePath(out_buffer);
// Assume that the OS APIs return absolute paths, and therefore dirname
@ -607,6 +706,8 @@ pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
}
/// `realpath`, except caller must free the returned memory.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
/// See also `Dir.realpath`.
pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
// Use of MAX_PATH_BYTES here is valid as the realpath function does not

View File

@ -9,7 +9,14 @@ pub const Entry = struct {
pub const Kind = File.Kind;
};
const IteratorError = error{ AccessDenied, SystemResources } || posix.UnexpectedError;
const IteratorError = error{
AccessDenied,
SystemResources,
/// WASI-only. The path of an entry could not be encoded as valid UTF-8.
/// WASI is unable to handle paths that cannot be encoded as well-formed UTF-8.
/// https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
InvalidUtf8,
} || posix.UnexpectedError;
pub const Iterator = switch (builtin.os.tag) {
.macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {
@ -445,13 +452,12 @@ pub const Iterator = switch (builtin.os.tag) {
self.index = self.buf.len;
}
const name_utf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' }))
if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' }))
continue;
// Trust that Windows gives us valid UTF-16LE
const name_utf8_len = std.unicode.utf16LeToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
const name_utf8 = self.name_data[0..name_utf8_len];
const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le);
const name_wtf8 = self.name_data[0..name_wtf8_len];
const kind: Entry.Kind = blk: {
const attrs = dir_info.FileAttributes;
if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
@ -459,7 +465,7 @@ pub const Iterator = switch (builtin.os.tag) {
break :blk .file;
};
return Entry{
.name = name_utf8,
.name = name_wtf8,
.kind = kind,
};
}
@ -516,6 +522,7 @@ pub const Iterator = switch (builtin.os.tag) {
.INVAL => unreachable,
.NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
else => |err| return posix.unexpectedErrno(err),
}
if (bufused == 0) return null;
@ -743,7 +750,11 @@ pub const OpenError = error{
SystemFdQuotaExceeded,
NoDevice,
SystemResources,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
BadPathName,
DeviceBusy,
/// On Windows, `\\server` or `\\server\share` was not found.
@ -759,6 +770,9 @@ pub fn close(self: *Dir) void {
/// To create a new file, see `createFile`.
/// Call `File.close` to release the resource.
/// Asserts that the path parameter has no null bytes.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
if (builtin.os.tag == .windows) {
const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
@ -911,6 +925,9 @@ pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File
/// Creates, opens, or overwrites a file with write access.
/// Call `File.close` on the result when done.
/// Asserts that the path parameter has no null bytes.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
if (builtin.os.tag == .windows) {
const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
@ -1060,18 +1077,21 @@ pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags)
/// Creates a single directory with a relative or absolute path.
/// To create multiple directories to make an entire path, see `makePath`.
/// To operate on only absolute paths, see `makeDirAbsolute`.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn makeDir(self: Dir, sub_path: []const u8) !void {
try posix.mkdirat(self.fd, sub_path, default_mode);
}
/// Creates a single directory with a relative or absolute null-terminated UTF-8-encoded path.
/// Same as `makeDir`, but `sub_path` is null-terminated.
/// To create multiple directories to make an entire path, see `makePath`.
/// To operate on only absolute paths, see `makeDirAbsoluteZ`.
pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
try posix.mkdiratZ(self.fd, sub_path, default_mode);
}
/// Creates a single directory with a relative or absolute null-terminated WTF-16-encoded path.
/// Creates a single directory with a relative or absolute null-terminated WTF-16 LE-encoded path.
/// To create multiple directories to make an entire path, see `makePath`.
/// To operate on only absolute paths, see `makeDirAbsoluteW`.
pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
@ -1083,6 +1103,9 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
/// Returns success if the path already exists and is a directory.
/// This function is not atomic, and if it returns an error, the file system may
/// have been modified regardless.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
///
/// Paths containing `..` components are handled differently depending on the platform:
/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
@ -1119,16 +1142,17 @@ pub fn makePath(self: Dir, sub_path: []const u8) !void {
}
}
/// Calls makeOpenDirAccessMaskW iteratively to make an entire path
/// Windows only. Calls makeOpenDirAccessMaskW iteratively to make an entire path
/// (i.e. creating any parent directories that do not exist).
/// Opens the dir if the path already exists and is a directory.
/// This function is not atomic, and if it returns an error, the file system may
/// have been modified regardless.
/// `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
const w = std.os.windows;
var it = try fs.path.componentIterator(sub_path);
// If there are no components in the path, then create a dummy component with the full path.
var component = it.last() orelse fs.path.NativeUtf8ComponentIterator.Component{
var component = it.last() orelse fs.path.NativeComponentIterator.Component{
.name = "",
.path = sub_path,
};
@ -1156,7 +1180,9 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no
/// This function performs `makePath`, followed by `openDir`.
/// If supported by the OS, this operation is atomic. It is not atomic on
/// all operating systems.
/// On Windows, this function performs `makeOpenPathAccessMaskW`.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
return switch (builtin.os.tag) {
.windows => {
@ -1185,6 +1211,10 @@ pub const RealPathError = posix.RealPathError;
/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
/// argument.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
/// This function is not universally supported by all platforms.
/// Currently supported hosts are: Linux, macOS, and Windows.
/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
@ -1224,6 +1254,7 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
error.FileLocksNotSupported => return error.Unexpected,
error.FileBusy => return error.Unexpected,
error.WouldBlock => return error.Unexpected,
error.InvalidUtf8 => unreachable, // WASI-only
else => |e| return e,
};
defer posix.close(fd);
@ -1246,7 +1277,8 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
return result;
}
/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded.
/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded.
/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// See also `Dir.realpath`, `realpathW`.
pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {
const w = std.os.windows;
@ -1272,16 +1304,7 @@ pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathErr
var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
var big_out_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
const end_index = std.unicode.utf16leToUtf8(&big_out_buf, wide_slice) catch |e| switch (e) {
// TODO: Windows file paths can be arbitrary arrays of u16 values and
// must not fail with InvalidUtf8.
error.DanglingSurrogateHalf,
error.ExpectedSecondSurrogateHalf,
error.UnexpectedSecondSurrogateHalf,
error.CodepointTooLarge,
error.Utf8CannotEncodeSurrogateHalf,
=> return error.InvalidUtf8,
};
const end_index = std.unicode.wtf16LeToWtf8(&big_out_buf, wide_slice);
if (end_index > out_buffer.len)
return error.NameTooLong;
const result = out_buffer[0..end_index];
@ -1344,6 +1367,9 @@ pub const OpenDirOptions = struct {
/// open until `close` is called on the result.
/// The directory cannot be iterated unless the `iterate` option is set to `true`.
///
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
/// Asserts that the path parameter has no null bytes.
pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
switch (builtin.os.tag) {
@ -1428,7 +1454,7 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) Open
}
}
/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
/// Same as `openDir` except the path parameter is WTF-16 LE encoded, NT-prefixed.
/// This function asserts the target OS is Windows.
pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
const w = std.os.windows;
@ -1518,6 +1544,9 @@ fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u3
pub const DeleteFileError = posix.UnlinkError;
/// Delete a file name and possibly the file it refers to, based on an open directory handle.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
/// Asserts that the path parameter has no null bytes.
pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
if (builtin.os.tag == .windows) {
@ -1553,7 +1582,7 @@ pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
};
}
/// Same as `deleteFile` except the parameter is WTF-16 encoded.
/// Same as `deleteFile` except the parameter is WTF-16 LE encoded.
pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
@ -1572,7 +1601,11 @@ pub const DeleteDirError = error{
NotDir,
SystemResources,
ReadOnlyFileSystem,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
BadPathName,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
@ -1581,6 +1614,9 @@ pub const DeleteDirError = error{
/// Returns `error.DirNotEmpty` if the directory is not empty.
/// To delete a directory recursively, see `deleteTree`.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
/// Asserts that the path parameter has no null bytes.
pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
if (builtin.os.tag == .windows) {
@ -1605,7 +1641,7 @@ pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
};
}
/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
/// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed.
/// This function is Windows-only.
pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
@ -1620,6 +1656,9 @@ pub const RenameError = posix.RenameError;
/// If new_sub_path already exists, it will be replaced.
/// Renaming a file over an existing directory or a directory
/// over an existing file will fail with `error.IsDir` or `error.NotDir`
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
}
@ -1629,7 +1668,7 @@ pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]co
return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
}
/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
/// This function is Windows-only.
pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
@ -1647,6 +1686,9 @@ pub const SymLinkFlags = struct {
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
/// one; the latter case is known as a dangling link.
/// If `sym_link_path` exists, it will not be overwritten.
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn symLink(
self: Dir,
target_path: []const u8,
@ -1662,7 +1704,7 @@ pub fn symLink(
// when converting to an NT namespaced path. CreateSymbolicLink in
// symLinkW will handle the necessary conversion.
var target_path_w: std.os.windows.PathSpace = undefined;
target_path_w.len = try std.unicode.utf8ToUtf16Le(&target_path_w.data, target_path);
target_path_w.len = try std.unicode.wtf8ToWtf16Le(&target_path_w.data, target_path);
target_path_w.data[target_path_w.len] = 0;
const sym_link_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sym_link_path);
return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
@ -1698,7 +1740,7 @@ pub fn symLinkZ(
}
/// Windows-only. Same as `symLink` except the pathname parameters
/// are null-terminated, WTF16 encoded.
/// are WTF16 LE encoded.
pub fn symLinkW(
self: Dir,
/// WTF-16, does not need to be NT-prefixed. The NT-prefixing
@ -1716,6 +1758,9 @@ pub const ReadLinkError = posix.ReadLinkError;
/// Read value of a symbolic link.
/// The return value is a slice of `buffer`, from index `0`.
/// Asserts that the path parameter has no null bytes.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
return self.readLinkWasi(sub_path, buffer);
@ -1733,7 +1778,7 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
return posix.readlinkat(self.fd, sub_path, buffer);
}
/// Same as `readLink`, except the `pathname` parameter is null-terminated.
/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.
pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
if (builtin.os.tag == .windows) {
const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
@ -1743,7 +1788,7 @@ pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
}
/// Windows-only. Same as `readLink` except the pathname parameter
/// is null-terminated, WTF16 encoded.
/// is WTF16 LE encoded.
pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
return std.os.windows.ReadLink(self.fd, sub_path_w, buffer);
}
@ -1753,6 +1798,9 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
/// the situation is ambiguous. It could either mean that the entire file was read, and
/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
/// entire file.
/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `file_path` should be encoded as valid UTF-8.
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
var file = try self.openFile(file_path, .{});
defer file.close();
@ -1763,6 +1811,9 @@ pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
/// On success, caller owns returned buffer.
/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `file_path` should be encoded as valid UTF-8.
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);
}
@ -1772,6 +1823,9 @@ pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8,
/// If `size_hint` is specified the initial buffer size is calculated using
/// that value, otherwise the effective file size is used instead.
/// Allows specifying alignment and a sentinel value.
/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `file_path` should be encoded as valid UTF-8.
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
pub fn readFileAllocOptions(
self: Dir,
allocator: mem.Allocator,
@ -1811,9 +1865,13 @@ pub const DeleteTreeError = error{
/// This error is unreachable if `sub_path` does not contain a path separator.
NotDir,
/// On Windows, file paths must be valid Unicode.
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
@ -1826,6 +1884,9 @@ pub const DeleteTreeError = error{
/// removes it. If it cannot be removed because it is a non-empty directory,
/// this function recursively removes its entries and then tries again.
/// This operation is not atomic on most file systems.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
@ -1879,6 +1940,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
error.SystemResources,
error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName,
error.NetworkNotFound,
error.DeviceBusy,
@ -1910,6 +1972,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
error.AccessDenied,
error.InvalidUtf8,
error.InvalidWtf8,
error.SymLinkLoop,
error.NameTooLong,
error.SystemResources,
@ -1973,6 +2036,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
error.SystemResources,
error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName,
error.NetworkNotFound,
error.DeviceBusy,
@ -1994,6 +2058,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
error.AccessDenied,
error.InvalidUtf8,
error.InvalidWtf8,
error.SymLinkLoop,
error.NameTooLong,
error.SystemResources,
@ -2022,6 +2087,9 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
/// This is slower than `deleteTree` but uses less stack space.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
}
@ -2074,6 +2142,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
error.SystemResources,
error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName,
error.NetworkNotFound,
error.DeviceBusy,
@ -2102,6 +2171,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
error.AccessDenied,
error.InvalidUtf8,
error.InvalidWtf8,
error.SymLinkLoop,
error.NameTooLong,
error.SystemResources,
@ -2171,6 +2241,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
error.SystemResources,
error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName,
error.DeviceBusy,
error.NetworkNotFound,
@ -2189,6 +2260,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
error.AccessDenied,
error.InvalidUtf8,
error.InvalidWtf8,
error.SymLinkLoop,
error.NameTooLong,
error.SystemResources,
@ -2209,6 +2281,9 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
pub const WriteFileError = File.WriteError || File.OpenError;
/// Deprecated: use `writeFile2`.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void {
return writeFile2(self, .{
.sub_path = sub_path,
@ -2218,6 +2293,9 @@ pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileErr
}
pub const WriteFileOptions = struct {
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
sub_path: []const u8,
data: []const u8,
flags: File.CreateFlags = .{},
@ -2232,8 +2310,10 @@ pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void {
pub const AccessError = posix.AccessError;
/// Test accessing `path`.
/// `path` is UTF-8-encoded.
/// Test accessing `sub_path`.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
/// For example, instead of testing if a file exists and then opening it, just
/// open it and handle the error for file not found.
@ -2268,9 +2348,9 @@ pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) Access
}
/// Same as `access` except asserts the target OS is Windows and the path parameter is
/// * WTF-16 encoded
/// * WTF-16 LE encoded
/// * null-terminated
/// * NtDll prefixed
/// * relative or has the NT namespace prefix
/// TODO currently this ignores `flags`.
pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
_ = flags;
@ -2292,6 +2372,9 @@ pub const PrevStatus = enum {
/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
/// Returns the previous status of the file before updating.
/// If any of the directories do not exist for dest_path, they are created.
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn updateFile(
source_dir: Dir,
source_path: []const u8,
@ -2343,6 +2426,9 @@ pub const CopyFileError = File.OpenError || File.StatError ||
/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
/// there is a possibility of power loss or application termination leaving temporary files present
/// in the same directory as dest_path.
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn copyFile(
source_dir: Dir,
source_path: []const u8,
@ -2430,6 +2516,9 @@ pub const AtomicFileOptions = struct {
/// Always call `AtomicFile.deinit` to clean up, regardless of whether
/// `AtomicFile.finish` succeeded. `dest_path` must remain valid until
/// `AtomicFile.deinit` is called.
/// On Windows, `dest_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `dest_path` should be encoded as valid UTF-8.
/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
if (fs.path.dirname(dest_path)) |dirname| {
const dir = if (options.make_path)
@ -2461,6 +2550,9 @@ pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError
/// Symlinks are followed.
///
/// `sub_path` may be absolute, in which case `self` is ignored.
/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
if (builtin.os.tag == .windows) {
var file = try self.openFile(sub_path, .{});

View File

@ -40,8 +40,11 @@ pub const OpenError = error{
AccessDenied,
PipeBusy,
NameTooLong,
/// On Windows, file paths must be valid Unicode.
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,

View File

@ -1,3 +1,17 @@
//! POSIX paths are arbitrary sequences of `u8` with no particular encoding.
//!
//! Windows paths are arbitrary sequences of `u16` (WTF-16).
//! For cross-platform APIs that deal with sequences of `u8`, Windows
//! paths are encoded by Zig as [WTF-8](https://simonsapin.github.io/wtf-8/).
//! WTF-8 is a superset of UTF-8 that allows encoding surrogate codepoints,
//! which enables lossless roundtripping when converting to/from WTF-16
//! (as long as the WTF-8 encoded surrogate codepoints do not form a pair).
//!
//! WASI paths are sequences of valid Unicode scalar values,
//! which means that WASI is unable to handle paths that cannot be
//! encoded as well-formed UTF-8/UTF-16.
//! https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
const builtin = @import("builtin");
const std = @import("../std.zig");
const debug = std.debug;
@ -438,7 +452,7 @@ fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool {
var it1 = mem.tokenizeScalar(u8, ns1, sep1);
var it2 = mem.tokenizeScalar(u8, ns2, sep2);
return windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?);
return windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?);
}
fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8) bool {
@ -458,7 +472,7 @@ fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8
var it1 = mem.tokenizeScalar(u8, p1, sep1);
var it2 = mem.tokenizeScalar(u8, p2, sep2);
return windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?) and windows.eqlIgnoreCaseUtf8(it1.next().?, it2.next().?);
return windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?) and windows.eqlIgnoreCaseWtf8(it1.next().?, it2.next().?);
},
}
}
@ -1099,7 +1113,7 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest());
const to_rest = to_it.rest();
if (to_it.next()) |to_component| {
if (windows.eqlIgnoreCaseUtf8(from_component, to_component))
if (windows.eqlIgnoreCaseWtf8(from_component, to_component))
continue;
}
var up_index_end = "..".len;
@ -1564,14 +1578,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
};
}
pub const NativeUtf8ComponentIterator = ComponentIterator(switch (native_os) {
pub const NativeComponentIterator = ComponentIterator(switch (native_os) {
.windows => .windows,
.uefi => .uefi,
else => .posix,
}, u8);
pub fn componentIterator(path: []const u8) !NativeUtf8ComponentIterator {
return NativeUtf8ComponentIterator.init(path);
pub fn componentIterator(path: []const u8) !NativeComponentIterator {
return NativeComponentIterator.init(path);
}
test "ComponentIterator posix" {
@ -1826,7 +1840,7 @@ test "ComponentIterator windows" {
}
}
test "ComponentIterator windows UTF-16" {
test "ComponentIterator windows WTF-16" {
// TODO: Fix on big endian architectures
if (builtin.cpu.arch.endian() != .little) {
return error.SkipZigTest;

View File

@ -26,39 +26,39 @@ const PathType = enum {
}
pub const TransformError = std.os.RealPathError || error{OutOfMemory};
pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8;
pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8;
pub fn getTransformFn(comptime path_type: PathType) TransformFn {
switch (path_type) {
.relative => return struct {
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
_ = allocator;
_ = dir;
return relative_path;
}
}.transform,
.absolute => return struct {
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
// The final path may not actually exist which would cause realpath to fail.
// So instead, we get the path of the dir and join it with the relative path.
var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
return fs.path.join(allocator, &.{ dir_path, relative_path });
return fs.path.joinZ(allocator, &.{ dir_path, relative_path });
}
}.transform,
.unc => return struct {
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
// Any drive absolute path (C:\foo) can be converted into a UNC path by
// using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path);
switch (windows_path_type) {
.unc_absolute => return fs.path.join(allocator, &.{ dir_path, relative_path }),
.unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }),
.drive_absolute => {
// `C:\<...>` -> `\\127.0.0.1\C$\<...>`
const prepended = "\\\\127.0.0.1\\";
var path = try fs.path.join(allocator, &.{ prepended, dir_path, relative_path });
var path = try fs.path.joinZ(allocator, &.{ prepended, dir_path, relative_path });
path[prepended.len + 1] = '$';
return path;
},
@ -96,7 +96,7 @@ const TestContext = struct {
/// Returns the `relative_path` transformed into the TestContext's `path_type`.
/// The result is allocated by the TestContext's arena and will be free'd during
/// `TestContext.deinit`.
pub fn transformPath(self: *TestContext, relative_path: []const u8) ![]const u8 {
pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 {
return self.transform_fn(self.arena.allocator(), self.dir, relative_path);
}
};
@ -1001,6 +1001,16 @@ test "openSelfExe" {
self_exe_file.close();
}
test "selfExePath" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
const buf_self_exe_path = try std.fs.selfExePath(&buf);
const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator);
defer testing.allocator.free(alloc_self_exe_path);
try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path);
}
test "deleteTree does not follow symlinks" {
var tmp = tmpDir(.{});
defer tmp.cleanup();
@ -1907,3 +1917,111 @@ test "delete a setAsCwd directory on Windows" {
// Close the parent "tmp" so we don't leak the HANDLE.
tmp.parent_dir.close();
}
test "invalid UTF-8/WTF-8 paths" {
const expected_err = switch (builtin.os.tag) {
.wasi => error.InvalidUtf8,
.windows => error.InvalidWtf8,
else => return error.SkipZigTest,
};
try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
// This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte
const invalid_path = try ctx.transformPath("\xFF");
try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.openFileZ(invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.createFileZ(invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path));
try testing.expectError(expected_err, ctx.dir.makeDirZ(invalid_path));
try testing.expectError(expected_err, ctx.dir.makePath(invalid_path));
try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.openDirZ(invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path));
try testing.expectError(expected_err, ctx.dir.deleteFileZ(invalid_path));
try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path));
try testing.expectError(expected_err, ctx.dir.deleteDirZ(invalid_path));
try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path));
try testing.expectError(expected_err, ctx.dir.renameZ(invalid_path, invalid_path));
try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.symLinkZ(invalid_path, invalid_path, .{}));
if (builtin.os.tag == .wasi) {
try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{}));
}
try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{}));
try testing.expectError(expected_err, ctx.dir.readLinkZ(invalid_path, &[_]u8{}));
if (builtin.os.tag == .wasi) {
try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{}));
}
try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{}));
try testing.expectError(expected_err, ctx.dir.readFileAlloc(testing.allocator, invalid_path, 0));
try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path));
try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path));
try testing.expectError(expected_err, ctx.dir.writeFile(invalid_path, ""));
try testing.expectError(expected_err, ctx.dir.writeFile2(.{
.sub_path = invalid_path,
.data = "",
}));
try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.accessZ(invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.updateFile(invalid_path, ctx.dir, invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.statFile(invalid_path));
if (builtin.os.tag != .wasi) {
try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{}));
try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{}));
try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path));
}
try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path));
try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path));
if (builtin.os.tag != .wasi and ctx.path_type != .relative) {
try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{}));
try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));
try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));
try testing.expectError(expected_err, fs.makeDirAbsoluteZ(invalid_path));
try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path));
try testing.expectError(expected_err, fs.deleteDirAbsoluteZ(invalid_path));
try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path));
try testing.expectError(expected_err, fs.renameAbsoluteZ(invalid_path, invalid_path));
try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{}));
try testing.expectError(expected_err, fs.openDirAbsoluteZ(invalid_path, .{}));
try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{}));
try testing.expectError(expected_err, fs.openFileAbsoluteZ(invalid_path, .{}));
try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{}));
try testing.expectError(expected_err, fs.accessAbsoluteZ(invalid_path, .{}));
try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{}));
try testing.expectError(expected_err, fs.createFileAbsoluteZ(invalid_path, .{}));
try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path));
try testing.expectError(expected_err, fs.deleteFileAbsoluteZ(invalid_path));
try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path));
var readlink_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf));
try testing.expectError(expected_err, fs.readLinkAbsoluteZ(invalid_path, &readlink_buf));
try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{}));
try testing.expectError(expected_err, fs.symLinkAbsoluteZ(invalid_path, invalid_path, .{}));
try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path));
}
}
}.impl);
}

View File

@ -1,719 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const event = std.event;
const assert = std.debug.assert;
const testing = std.testing;
const os = std.os;
const mem = std.mem;
const windows = os.windows;
const Loop = event.Loop;
const fd_t = os.fd_t;
const File = std.fs.File;
const Allocator = mem.Allocator;
const global_event_loop = Loop.instance orelse
@compileError("std.fs.Watch currently only works with event-based I/O");
const WatchEventId = enum {
CloseWrite,
Delete,
};
const WatchEventError = error{
UserResourceLimitReached,
SystemResources,
AccessDenied,
Unexpected, // TODO remove this possibility
};
pub fn Watch(comptime V: type) type {
return struct {
channel: event.Channel(Event.Error!Event),
os_data: OsData,
allocator: Allocator,
const OsData = switch (builtin.os.tag) {
// TODO https://github.com/ziglang/zig/issues/3778
.macos, .freebsd, .netbsd, .dragonfly, .openbsd => KqOsData,
.linux => LinuxOsData,
.windows => WindowsOsData,
else => @compileError("Unsupported OS"),
};
const KqOsData = struct {
table_lock: event.Lock,
file_table: FileTable,
const FileTable = std.StringHashMapUnmanaged(*Put);
const Put = struct {
putter_frame: @Frame(kqPutEvents),
cancelled: bool = false,
value: V,
};
};
const WindowsOsData = struct {
table_lock: event.Lock,
dir_table: DirTable,
cancelled: bool = false,
const DirTable = std.StringHashMapUnmanaged(*Dir);
const FileTable = std.StringHashMapUnmanaged(V);
const Dir = struct {
putter_frame: @Frame(windowsDirReader),
file_table: FileTable,
dir_handle: os.windows.HANDLE,
};
};
const LinuxOsData = struct {
putter_frame: @Frame(linuxEventPutter),
inotify_fd: i32,
wd_table: WdTable,
table_lock: event.Lock,
cancelled: bool = false,
const WdTable = std.AutoHashMapUnmanaged(i32, Dir);
const FileTable = std.StringHashMapUnmanaged(V);
const Dir = struct {
dirname: []const u8,
file_table: FileTable,
};
};
const Self = @This();
pub const Event = struct {
id: Id,
data: V,
dirname: []const u8,
basename: []const u8,
pub const Id = WatchEventId;
pub const Error = WatchEventError;
};
pub fn init(allocator: Allocator, event_buf_count: usize) !*Self {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
switch (builtin.os.tag) {
.linux => {
const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
errdefer os.close(inotify_fd);
self.* = Self{
.allocator = allocator,
.channel = undefined,
.os_data = OsData{
.putter_frame = undefined,
.inotify_fd = inotify_fd,
.wd_table = OsData.WdTable.init(allocator),
.table_lock = event.Lock{},
},
};
const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
self.channel.init(buf);
self.os_data.putter_frame = async self.linuxEventPutter();
return self;
},
.windows => {
self.* = Self{
.allocator = allocator,
.channel = undefined,
.os_data = OsData{
.table_lock = event.Lock{},
.dir_table = OsData.DirTable.init(allocator),
},
};
const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
self.channel.init(buf);
return self;
},
.macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
self.* = Self{
.allocator = allocator,
.channel = undefined,
.os_data = OsData{
.table_lock = event.Lock{},
.file_table = OsData.FileTable.init(allocator),
},
};
const buf = try allocator.alloc(Event.Error!Event, event_buf_count);
self.channel.init(buf);
return self;
},
else => @compileError("Unsupported OS"),
}
}
pub fn deinit(self: *Self) void {
switch (builtin.os.tag) {
.macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
var it = self.os_data.file_table.iterator();
while (it.next()) |entry| {
const key = entry.key_ptr.*;
const value = entry.value_ptr.*;
value.cancelled = true;
// @TODO Close the fd here?
await value.putter_frame;
self.allocator.free(key);
self.allocator.destroy(value);
}
},
.linux => {
self.os_data.cancelled = true;
{
// Remove all directory watches linuxEventPutter will take care of
// cleaning up the memory and closing the inotify fd.
var dir_it = self.os_data.wd_table.keyIterator();
while (dir_it.next()) |wd_key| {
const rc = os.linux.inotify_rm_watch(self.os_data.inotify_fd, wd_key.*);
// Errno can only be EBADF, EINVAL if either the inotify fs or the wd are invalid
std.debug.assert(rc == 0);
}
}
await self.os_data.putter_frame;
},
.windows => {
self.os_data.cancelled = true;
var dir_it = self.os_data.dir_table.iterator();
while (dir_it.next()) |dir_entry| {
if (windows.kernel32.CancelIoEx(dir_entry.value.dir_handle, null) != 0) {
// We canceled the pending ReadDirectoryChangesW operation, but our
// frame is still suspending, now waiting indefinitely.
// Thus, it is safe to resume it ourslves
resume dir_entry.value.putter_frame;
} else {
std.debug.assert(windows.kernel32.GetLastError() == .NOT_FOUND);
// We are at another suspend point, we can await safely for the
// function to exit the loop
await dir_entry.value.putter_frame;
}
self.allocator.free(dir_entry.key_ptr.*);
var file_it = dir_entry.value.file_table.keyIterator();
while (file_it.next()) |file_entry| {
self.allocator.free(file_entry.*);
}
dir_entry.value.file_table.deinit(self.allocator);
self.allocator.destroy(dir_entry.value_ptr.*);
}
self.os_data.dir_table.deinit(self.allocator);
},
else => @compileError("Unsupported OS"),
}
self.allocator.free(self.channel.buffer_nodes);
self.channel.deinit();
self.allocator.destroy(self);
}
pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
switch (builtin.os.tag) {
.macos, .freebsd, .netbsd, .dragonfly, .openbsd => return addFileKEvent(self, file_path, value),
.linux => return addFileLinux(self, file_path, value),
.windows => return addFileWindows(self, file_path, value),
else => @compileError("Unsupported OS"),
}
}
fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const realpath = try os.realpath(file_path, &realpath_buf);
const held = self.os_data.table_lock.acquire();
defer held.release();
const gop = try self.os_data.file_table.getOrPut(self.allocator, realpath);
errdefer assert(self.os_data.file_table.remove(realpath));
if (gop.found_existing) {
const prev_value = gop.value_ptr.value;
gop.value_ptr.value = value;
return prev_value;
}
gop.key_ptr.* = try self.allocator.dupe(u8, realpath);
errdefer self.allocator.free(gop.key_ptr.*);
gop.value_ptr.* = try self.allocator.create(OsData.Put);
errdefer self.allocator.destroy(gop.value_ptr.*);
gop.value_ptr.* = .{
.putter_frame = undefined,
.value = value,
};
// @TODO Can I close this fd and get an error from bsdWaitKev?
const flags = if (comptime builtin.target.isDarwin()) os.O.SYMLINK | os.O.EVTONLY else 0;
const fd = try os.open(realpath, flags, 0);
gop.value_ptr.putter_frame = async self.kqPutEvents(fd, gop.key_ptr.*, gop.value_ptr.*);
return null;
}
fn kqPutEvents(self: *Self, fd: os.fd_t, file_path: []const u8, put: *OsData.Put) void {
global_event_loop.beginOneEvent();
defer {
global_event_loop.finishOneEvent();
// @TODO: Remove this if we force close otherwise
os.close(fd);
}
// We need to manually do a bsdWaitKev to access the fflags.
var resume_node = event.Loop.ResumeNode.Basic{
.base = .{
.id = .Basic,
.handle = @frame(),
.overlapped = event.Loop.ResumeNode.overlapped_init,
},
.kev = undefined,
};
var kevs = [1]os.Kevent{undefined};
const kev = &kevs[0];
while (!put.cancelled) {
kev.* = os.Kevent{
.ident = @as(usize, @intCast(fd)),
.filter = os.EVFILT_VNODE,
.flags = os.EV_ADD | os.EV_ENABLE | os.EV_CLEAR | os.EV_ONESHOT |
os.NOTE_WRITE | os.NOTE_DELETE | os.NOTE_REVOKE,
.fflags = 0,
.data = 0,
.udata = @intFromPtr(&resume_node.base),
};
suspend {
global_event_loop.beginOneEvent();
errdefer global_event_loop.finishOneEvent();
const empty_kevs = &[0]os.Kevent{};
_ = os.kevent(global_event_loop.os_data.kqfd, &kevs, empty_kevs, null) catch |err| switch (err) {
error.EventNotFound,
error.ProcessNotFound,
error.Overflow,
=> unreachable,
error.AccessDenied, error.SystemResources => |e| {
self.channel.put(e);
continue;
},
};
}
if (kev.flags & os.EV_ERROR != 0) {
self.channel.put(os.unexpectedErrno(os.errno(kev.data)));
continue;
}
if (kev.fflags & os.NOTE_DELETE != 0 or kev.fflags & os.NOTE_REVOKE != 0) {
self.channel.put(Self.Event{
.id = .Delete,
.data = put.value,
.dirname = std.fs.path.dirname(file_path) orelse "/",
.basename = std.fs.path.basename(file_path),
});
} else if (kev.fflags & os.NOTE_WRITE != 0) {
self.channel.put(Self.Event{
.id = .CloseWrite,
.data = put.value,
.dirname = std.fs.path.dirname(file_path) orelse "/",
.basename = std.fs.path.basename(file_path),
});
}
}
}
fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
const basename = std.fs.path.basename(file_path);
const wd = try os.inotify_add_watch(
self.os_data.inotify_fd,
dirname,
os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_DELETE | os.linux.IN_EXCL_UNLINK,
);
// wd is either a newly created watch or an existing one.
const held = self.os_data.table_lock.acquire();
defer held.release();
const gop = try self.os_data.wd_table.getOrPut(self.allocator, wd);
errdefer assert(self.os_data.wd_table.remove(wd));
if (!gop.found_existing) {
gop.value_ptr.* = OsData.Dir{
.dirname = try self.allocator.dupe(u8, dirname),
.file_table = OsData.FileTable.init(self.allocator),
};
}
const dir = gop.value_ptr;
const file_table_gop = try dir.file_table.getOrPut(self.allocator, basename);
errdefer assert(dir.file_table.remove(basename));
if (file_table_gop.found_existing) {
const prev_value = file_table_gop.value_ptr.*;
file_table_gop.value_ptr.* = value;
return prev_value;
} else {
file_table_gop.key_ptr.* = try self.allocator.dupe(u8, basename);
file_table_gop.value_ptr.* = value;
return null;
}
}
fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
// TODO we might need to convert dirname and basename to canonical file paths ("short"?)
const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
var dirname_path_space: windows.PathSpace = undefined;
dirname_path_space.len = try std.unicode.utf8ToUtf16Le(&dirname_path_space.data, dirname);
dirname_path_space.data[dirname_path_space.len] = 0;
const basename = std.fs.path.basename(file_path);
var basename_path_space: windows.PathSpace = undefined;
basename_path_space.len = try std.unicode.utf8ToUtf16Le(&basename_path_space.data, basename);
basename_path_space.data[basename_path_space.len] = 0;
const held = self.os_data.table_lock.acquire();
defer held.release();
const gop = try self.os_data.dir_table.getOrPut(self.allocator, dirname);
errdefer assert(self.os_data.dir_table.remove(dirname));
if (gop.found_existing) {
const dir = gop.value_ptr.*;
const file_gop = try dir.file_table.getOrPut(self.allocator, basename);
errdefer assert(dir.file_table.remove(basename));
if (file_gop.found_existing) {
const prev_value = file_gop.value_ptr.*;
file_gop.value_ptr.* = value;
return prev_value;
} else {
file_gop.value_ptr.* = value;
file_gop.key_ptr.* = try self.allocator.dupe(u8, basename);
return null;
}
} else {
const dir_handle = try windows.OpenFile(dirname_path_space.span(), .{
.dir = std.fs.cwd().fd,
.access_mask = windows.FILE_LIST_DIRECTORY,
.creation = windows.FILE_OPEN,
.io_mode = .evented,
.filter = .dir_only,
});
errdefer windows.CloseHandle(dir_handle);
const dir = try self.allocator.create(OsData.Dir);
errdefer self.allocator.destroy(dir);
gop.key_ptr.* = try self.allocator.dupe(u8, dirname);
errdefer self.allocator.free(gop.key_ptr.*);
dir.* = OsData.Dir{
.file_table = OsData.FileTable.init(self.allocator),
.putter_frame = undefined,
.dir_handle = dir_handle,
};
gop.value_ptr.* = dir;
try dir.file_table.put(self.allocator, try self.allocator.dupe(u8, basename), value);
dir.putter_frame = async self.windowsDirReader(dir, gop.key_ptr.*);
return null;
}
}
fn windowsDirReader(self: *Self, dir: *OsData.Dir, dirname: []const u8) void {
defer os.close(dir.dir_handle);
var resume_node = Loop.ResumeNode.Basic{
.base = Loop.ResumeNode{
.id = .Basic,
.handle = @frame(),
.overlapped = windows.OVERLAPPED{
.Internal = 0,
.InternalHigh = 0,
.DUMMYUNIONNAME = .{
.DUMMYSTRUCTNAME = .{
.Offset = 0,
.OffsetHigh = 0,
},
},
.hEvent = null,
},
},
};
var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
global_event_loop.beginOneEvent();
defer global_event_loop.finishOneEvent();
while (!self.os_data.cancelled) main_loop: {
suspend {
_ = windows.kernel32.ReadDirectoryChangesW(
dir.dir_handle,
&event_buf,
event_buf.len,
windows.FALSE, // watch subtree
windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
null, // number of bytes transferred (unused for async)
&resume_node.base.overlapped,
null, // completion routine - unused because we use IOCP
);
}
var bytes_transferred: windows.DWORD = undefined;
if (windows.kernel32.GetOverlappedResult(
dir.dir_handle,
&resume_node.base.overlapped,
&bytes_transferred,
windows.FALSE,
) == 0) {
const potential_error = windows.kernel32.GetLastError();
const err = switch (potential_error) {
.OPERATION_ABORTED, .IO_INCOMPLETE => err_blk: {
if (self.os_data.cancelled)
break :main_loop
else
break :err_blk windows.unexpectedError(potential_error);
},
else => |err| windows.unexpectedError(err),
};
self.channel.put(err);
} else {
var ptr: [*]u8 = &event_buf;
const end_ptr = ptr + bytes_transferred;
while (@intFromPtr(ptr) < @intFromPtr(end_ptr)) {
const ev = @as(*const windows.FILE_NOTIFY_INFORMATION, @ptrCast(ptr));
const emit = switch (ev.Action) {
windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
windows.FILE_ACTION_MODIFIED => .CloseWrite,
else => null,
};
if (emit) |id| {
const basename_ptr = @as([*]u16, @ptrCast(ptr + @sizeOf(windows.FILE_NOTIFY_INFORMATION)));
const basename_utf16le = basename_ptr[0 .. ev.FileNameLength / 2];
var basename_data: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const basename = basename_data[0 .. std.unicode.utf16LeToUtf8(&basename_data, basename_utf16le) catch unreachable];
if (dir.file_table.getEntry(basename)) |entry| {
self.channel.put(Event{
.id = id,
.data = entry.value_ptr.*,
.dirname = dirname,
.basename = entry.key_ptr.*,
});
}
}
if (ev.NextEntryOffset == 0) break;
ptr = @alignCast(ptr + ev.NextEntryOffset);
}
}
}
}
pub fn removeFile(self: *Self, file_path: []const u8) !?V {
switch (builtin.os.tag) {
.linux => {
const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
const basename = std.fs.path.basename(file_path);
const held = self.os_data.table_lock.acquire();
defer held.release();
const dir = self.os_data.wd_table.get(dirname) orelse return null;
if (dir.file_table.fetchRemove(basename)) |file_entry| {
self.allocator.free(file_entry.key);
return file_entry.value;
}
return null;
},
.windows => {
const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else ".";
const basename = std.fs.path.basename(file_path);
const held = self.os_data.table_lock.acquire();
defer held.release();
const dir = self.os_data.dir_table.get(dirname) orelse return null;
if (dir.file_table.fetchRemove(basename)) |file_entry| {
self.allocator.free(file_entry.key);
return file_entry.value;
}
return null;
},
.macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const realpath = try os.realpath(file_path, &realpath_buf);
const held = self.os_data.table_lock.acquire();
defer held.release();
const entry = self.os_data.file_table.getEntry(realpath) orelse return null;
entry.value_ptr.cancelled = true;
// @TODO Close the fd here?
await entry.value_ptr.putter_frame;
self.allocator.free(entry.key_ptr.*);
self.allocator.destroy(entry.value_ptr.*);
assert(self.os_data.file_table.remove(realpath));
},
else => @compileError("Unsupported OS"),
}
}
fn linuxEventPutter(self: *Self) void {
global_event_loop.beginOneEvent();
defer {
std.debug.assert(self.os_data.wd_table.count() == 0);
self.os_data.wd_table.deinit(self.allocator);
os.close(self.os_data.inotify_fd);
self.allocator.free(self.channel.buffer_nodes);
self.channel.deinit();
global_event_loop.finishOneEvent();
}
var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
while (!self.os_data.cancelled) {
const bytes_read = global_event_loop.read(self.os_data.inotify_fd, &event_buf, false) catch unreachable;
var ptr: [*]u8 = &event_buf;
const end_ptr = ptr + bytes_read;
while (@intFromPtr(ptr) < @intFromPtr(end_ptr)) {
const ev = @as(*const os.linux.inotify_event, @ptrCast(ptr));
if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
const basename = std.mem.span(@as([*:0]u8, @ptrCast(basename_ptr)));
const dir = &self.os_data.wd_table.get(ev.wd).?;
if (dir.file_table.getEntry(basename)) |file_value| {
self.channel.put(Event{
.id = .CloseWrite,
.data = file_value.value_ptr.*,
.dirname = dir.dirname,
.basename = file_value.key_ptr.*,
});
}
} else if (ev.mask & os.linux.IN_IGNORED == os.linux.IN_IGNORED) {
// Directory watch was removed
const held = self.os_data.table_lock.acquire();
defer held.release();
if (self.os_data.wd_table.fetchRemove(ev.wd)) |wd_entry| {
var file_it = wd_entry.value.file_table.keyIterator();
while (file_it.next()) |file_entry| {
self.allocator.free(file_entry.*);
}
self.allocator.free(wd_entry.value.dirname);
wd_entry.value.file_table.deinit(self.allocator);
}
} else if (ev.mask & os.linux.IN_DELETE == os.linux.IN_DELETE) {
// File or directory was removed or deleted
const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
const basename = std.mem.span(@as([*:0]u8, @ptrCast(basename_ptr)));
const dir = &self.os_data.wd_table.get(ev.wd).?;
if (dir.file_table.getEntry(basename)) |file_value| {
self.channel.put(Event{
.id = .Delete,
.data = file_value.value_ptr.*,
.dirname = dir.dirname,
.basename = file_value.key_ptr.*,
});
}
}
ptr = @alignCast(ptr + @sizeOf(os.linux.inotify_event) + ev.len);
}
}
}
};
}
const test_tmp_dir = "std_event_fs_test";
test "write a file, watch it, write it again, delete it" {
if (!std.io.is_async) return error.SkipZigTest;
// TODO https://github.com/ziglang/zig/issues/1908
if (builtin.single_threaded) return error.SkipZigTest;
try std.fs.cwd().makePath(test_tmp_dir);
defer std.fs.cwd().deleteTree(test_tmp_dir) catch {};
return testWriteWatchWriteDelete(std.testing.allocator);
}
fn testWriteWatchWriteDelete(allocator: Allocator) !void {
const file_path = try std.fs.path.join(allocator, &[_][]const u8{ test_tmp_dir, "file.txt" });
defer allocator.free(file_path);
const contents =
\\line 1
\\line 2
;
const line2_offset = 7;
// first just write then read the file
try std.fs.cwd().writeFile(file_path, contents);
const read_contents = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
defer allocator.free(read_contents);
try testing.expectEqualSlices(u8, contents, read_contents);
// now watch the file
var watch = try Watch(void).init(allocator, 0);
defer watch.deinit();
try testing.expect((try watch.addFile(file_path, {})) == null);
var ev = async watch.channel.get();
var ev_consumed = false;
defer if (!ev_consumed) {
_ = await ev;
};
// overwrite line 2
const file = try std.fs.cwd().openFile(file_path, .{ .mode = .read_write });
{
defer file.close();
const write_contents = "lorem ipsum";
var iovec = [_]os.iovec_const{.{
.iov_base = write_contents,
.iov_len = write_contents.len,
}};
_ = try file.pwritevAll(&iovec, line2_offset);
}
switch ((try await ev).id) {
.CloseWrite => {
ev_consumed = true;
},
.Delete => @panic("wrong event"),
}
const contents_updated = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
defer allocator.free(contents_updated);
try testing.expectEqualSlices(u8,
\\line 1
\\lorem ipsum
, contents_updated);
ev = async watch.channel.get();
ev_consumed = false;
try std.fs.cwd().deleteFile(file_path);
switch ((try await ev).id) {
.Delete => {
ev_consumed = true;
},
.CloseWrite => @panic("wrong event"),
}
}
// TODO Test: Add another file watch, remove the old file watch, get an event in the new

View File

@ -3,7 +3,7 @@
//! * Convert "errno"-style error codes into Zig errors.
//! * When null-terminated byte buffers are required, provide APIs which accept
//! slices as well as APIs which accept null-terminated byte buffers. Same goes
//! for UTF-16LE encoding.
//! for WTF-16LE encoding.
//! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
//! cross platform abstracting.
//! * When there exists a corresponding libc function and linking libc, the libc
@ -498,6 +498,7 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr
const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {
error.NameTooLong => unreachable,
error.FileNotFound => unreachable,
error.InvalidUtf8 => unreachable,
else => |e| return e,
};
if ((stat.mode & S.IFMT) == S.IFLNK)
@ -1614,9 +1615,16 @@ pub const OpenError = error{
/// The underlying filesystem does not support file locks
FileLocksNotSupported,
/// Path contains characters that are disallowed by the underlying filesystem.
BadPathName,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
@ -1634,6 +1642,9 @@ pub const OpenError = error{
} || UnexpectedError;
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `file_path` should be encoded as valid UTF-8.
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
/// See also `openZ`.
pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
if (builtin.os.tag == .windows) {
@ -1646,6 +1657,9 @@ pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
}
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `file_path` should be encoded as valid UTF-8.
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
/// See also `open`.
pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
if (builtin.os.tag == .windows) {
@ -1687,6 +1701,9 @@ pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `file_path` should be encoded as valid UTF-8.
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
/// See also `openatZ`.
pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t {
if (builtin.os.tag == .windows) {
@ -1829,6 +1846,7 @@ pub fn openatWasi(
.EXIST => return error.PathAlreadyExists,
.BUSY => return error.DeviceBusy,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8,
else => |err| return unexpectedErrno(err),
}
}
@ -1836,6 +1854,9 @@ pub fn openatWasi(
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `file_path` should be encoded as valid UTF-8.
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
/// See also `openat`.
pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t {
if (builtin.os.tag == .windows) {
@ -2156,13 +2177,23 @@ pub const SymLinkError = error{
ReadOnlyFileSystem,
NotDir,
NameTooLong,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
BadPathName,
} || UnexpectedError;
/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
/// one; the latter case is known as a dangling link.
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
/// If `sym_link_path` exists, it will not be overwritten.
/// See also `symlinkZ.
pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
@ -2200,6 +2231,10 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
.NOMEM => return error.SystemResources,
.NOSPC => return error.NoSpaceLeft,
.ROFS => return error.ReadOnlyFileSystem,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
@ -2208,6 +2243,9 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
/// `target_path` **relative** to `newdirfd` directory handle.
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
/// one; the latter case is known as a dangling link.
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
/// If `sym_link_path` exists, it will not be overwritten.
/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
@ -2242,6 +2280,7 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c
.NOSPC => return error.NoSpaceLeft,
.ROFS => return error.ReadOnlyFileSystem,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8,
else => |err| return unexpectedErrno(err),
}
}
@ -2270,6 +2309,10 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:
.NOMEM => return error.SystemResources,
.NOSPC => return error.NoSpaceLeft,
.ROFS => return error.ReadOnlyFileSystem,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
@ -2287,8 +2330,13 @@ pub const LinkError = UnexpectedError || error{
NoSpaceLeft,
ReadOnlyFileSystem,
NotSameFileSystem,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
};
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags);
@ -2310,10 +2358,16 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr
.ROFS => return error.ReadOnlyFileSystem,
.XDEV => return error.NotSameFileSystem,
.INVAL => unreachable,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) {
@ -2328,6 +2382,8 @@ pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void
pub const LinkatError = LinkError || error{NotDir};
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn linkatZ(
olddir: fd_t,
oldpath: [*:0]const u8,
@ -2356,10 +2412,16 @@ pub fn linkatZ(
.ROFS => return error.ReadOnlyFileSystem,
.XDEV => return error.NotSameFileSystem,
.INVAL => unreachable,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn linkat(
olddir: fd_t,
oldpath: []const u8,
@ -2399,6 +2461,7 @@ pub fn linkat(
.ROFS => return error.ReadOnlyFileSystem,
.XDEV => return error.NotSameFileSystem,
.INVAL => unreachable,
.ILSEQ => return error.InvalidUtf8,
else => |err| return unexpectedErrno(err),
}
}
@ -2422,9 +2485,13 @@ pub const UnlinkError = error{
SystemResources,
ReadOnlyFileSystem,
/// On Windows, file paths must be valid Unicode.
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
@ -2434,6 +2501,9 @@ pub const UnlinkError = error{
} || UnexpectedError;
/// Delete a name and possibly the file it refers to.
/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `file_path` should be encoded as valid UTF-8.
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
/// See also `unlinkZ`.
pub fn unlink(file_path: []const u8) UnlinkError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
@ -2450,7 +2520,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
}
}
/// Same as `unlink` except the parameter is a null terminated UTF8-encoded string.
/// Same as `unlink` except the parameter is null terminated.
pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
@ -2473,11 +2543,15 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
.NOTDIR => return error.NotDir,
.NOMEM => return error.SystemResources,
.ROFS => return error.ReadOnlyFileSystem,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded.
/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 LE encoded.
pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd }) catch |err| switch (err) {
error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true
@ -2491,6 +2565,9 @@ pub const UnlinkatError = UnlinkError || error{
};
/// Delete a file name and possibly the file it refers to, based on an open directory handle.
/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `file_path` should be encoded as valid UTF-8.
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
/// Asserts that the path parameter has no null bytes.
pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
if (builtin.os.tag == .windows) {
@ -2528,6 +2605,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
.ROFS => return error.ReadOnlyFileSystem,
.NOTEMPTY => return error.DirNotEmpty,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8,
.INVAL => unreachable, // invalid flags, or pathname has . as last component
.BADF => unreachable, // always a race condition
@ -2560,6 +2638,10 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
.ROFS => return error.ReadOnlyFileSystem,
.EXIST => return error.DirNotEmpty,
.NOTEMPTY => return error.DirNotEmpty,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
.INVAL => unreachable, // invalid flags, or pathname has . as last component
.BADF => unreachable, // always a race condition
@ -2568,7 +2650,7 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
}
}
/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only.
pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
const remove_dir = (flags & AT.REMOVEDIR) != 0;
return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
@ -2594,7 +2676,11 @@ pub const RenameError = error{
PathAlreadyExists,
ReadOnlyFileSystem,
RenameAcrossMountPoints,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
BadPathName,
NoDevice,
SharingViolation,
@ -2610,6 +2696,9 @@ pub const RenameError = error{
} || UnexpectedError;
/// Change the name or location of a file.
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path);
@ -2624,7 +2713,7 @@ pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
}
}
/// Same as `rename` except the parameters are null-terminated byte arrays.
/// Same as `rename` except the parameters are null-terminated.
pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {
if (builtin.os.tag == .windows) {
const old_path_w = try windows.cStrToPrefixedFileW(null, old_path);
@ -2653,11 +2742,15 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi
.NOTEMPTY => return error.PathAlreadyExists,
.ROFS => return error.ReadOnlyFileSystem,
.XDEV => return error.RenameAcrossMountPoints,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
/// Same as `rename` except the parameters are null-terminated UTF16LE encoded byte arrays.
/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded.
/// Assumes target is Windows.
pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {
const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
@ -2665,6 +2758,9 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v
}
/// Change the name or location of a file based on an open directory handle.
/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn renameat(
old_dir_fd: fd_t,
old_path: []const u8,
@ -2710,11 +2806,12 @@ pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!vo
.ROFS => return error.ReadOnlyFileSystem,
.XDEV => return error.RenameAcrossMountPoints,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8,
else => |err| return unexpectedErrno(err),
}
}
/// Same as `renameat` except the parameters are null-terminated byte arrays.
/// Same as `renameat` except the parameters are null-terminated.
pub fn renameatZ(
old_dir_fd: fd_t,
old_path: [*:0]const u8,
@ -2749,6 +2846,10 @@ pub fn renameatZ(
.NOTEMPTY => return error.PathAlreadyExists,
.ROFS => return error.ReadOnlyFileSystem,
.XDEV => return error.RenameAcrossMountPoints,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
@ -2860,6 +2961,9 @@ pub fn renameatW(
}
}
/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.
pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .windows) {
const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path);
@ -2891,14 +2995,16 @@ pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirErr
.NOTDIR => return error.NotDir,
.ROFS => return error.ReadOnlyFileSystem,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8,
else => |err| return unexpectedErrno(err),
}
}
/// Same as `mkdirat` except the parameters are null-terminated.
pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .windows) {
const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path);
return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);
}
@ -2920,10 +3026,15 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
.ROFS => return error.ReadOnlyFileSystem,
// dragonfly: when dir_fd is unlinked from filesystem
.NOTCONN => return error.FileNotFound,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
/// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded.
pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
_ = mode;
const sub_dir_handle = windows.OpenFile(sub_path_w, .{
@ -2955,7 +3066,11 @@ pub const MakeDirError = error{
NoSpaceLeft,
NotDir,
ReadOnlyFileSystem,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
BadPathName,
NoDevice,
/// On Windows, `\\server` or `\\server\share` was not found.
@ -2964,6 +3079,9 @@ pub const MakeDirError = error{
/// Create a directory.
/// `mode` is ignored on Windows and WASI.
/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `dir_path` should be encoded as valid UTF-8.
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
return mkdirat(wasi.AT.FDCWD, dir_path, mode);
@ -2976,7 +3094,10 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
}
}
/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
/// Same as `mkdir` but the parameter is null-terminated.
/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `dir_path` should be encoded as valid UTF-8.
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .windows) {
const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
@ -2999,11 +3120,15 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
.NOSPC => return error.NoSpaceLeft,
.NOTDIR => return error.NotDir,
.ROFS => return error.ReadOnlyFileSystem,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
/// Windows-only. Same as `mkdir` but the parameters is WTF16 encoded.
/// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded.
pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
_ = mode;
const sub_dir_handle = windows.OpenFile(dir_path_w, .{
@ -3031,13 +3156,20 @@ pub const DeleteDirError = error{
NotDir,
DirNotEmpty,
ReadOnlyFileSystem,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
BadPathName,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
} || UnexpectedError;
/// Deletes an empty directory.
/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `dir_path` should be encoded as valid UTF-8.
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {
@ -3055,6 +3187,9 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
}
/// Same as `rmdir` except the parameter is null-terminated.
/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `dir_path` should be encoded as valid UTF-8.
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
if (builtin.os.tag == .windows) {
const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
@ -3077,11 +3212,15 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
.EXIST => return error.DirNotEmpty,
.NOTEMPTY => return error.DirNotEmpty,
.ROFS => return error.ReadOnlyFileSystem,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
/// Windows-only. Same as `rmdir` except the parameter is WTF16 encoded.
/// Windows-only. Same as `rmdir` except the parameter is WTF-16 LE encoded.
pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
error.IsDir => unreachable,
@ -3098,21 +3237,25 @@ pub const ChangeCurDirError = error{
SystemResources,
NotDir,
BadPathName,
/// On Windows, file paths must be valid Unicode.
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
} || UnexpectedError;
/// Changes the current working directory of the calling process.
/// `dir_path` is recommended to be a UTF-8 encoded string.
/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `dir_path` should be encoded as valid UTF-8.
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
@compileError("WASI does not support os.chdir");
} else if (builtin.os.tag == .windows) {
var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
if (len > utf16_dir_path.len) return error.NameTooLong;
return chdirW(utf16_dir_path[0..len]);
var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], dir_path);
if (len > wtf16_dir_path.len) return error.NameTooLong;
return chdirW(wtf16_dir_path[0..len]);
} else {
const dir_path_c = try toPosixPath(dir_path);
return chdirZ(&dir_path_c);
@ -3120,12 +3263,15 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
}
/// Same as `chdir` except the parameter is null-terminated.
/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `dir_path` should be encoded as valid UTF-8.
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
if (builtin.os.tag == .windows) {
var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], mem.span(dir_path));
if (len > utf16_dir_path.len) return error.NameTooLong;
return chdirW(utf16_dir_path[0..len]);
var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], mem.span(dir_path));
if (len > wtf16_dir_path.len) return error.NameTooLong;
return chdirW(wtf16_dir_path[0..len]);
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
return chdir(mem.span(dir_path));
}
@ -3139,11 +3285,15 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
.NOENT => return error.FileNotFound,
.NOMEM => return error.SystemResources,
.NOTDIR => return error.NotDir,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
/// Windows-only. Same as `chdir` except the parameter is WTF16 encoded.
/// Windows-only. Same as `chdir` except the parameter is WTF16 LE encoded.
pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void {
windows.SetCurrentDirectory(dir_path) catch |err| switch (err) {
error.NoDevice => return error.FileSystem,
@ -3183,7 +3333,11 @@ pub const ReadLinkError = error{
SystemResources,
NotLink,
NotDir,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
BadPathName,
/// Windows-only. This error may occur if the opened reparse point is
/// of unsupported type.
@ -3193,7 +3347,13 @@ pub const ReadLinkError = error{
} || UnexpectedError;
/// Read value of a symbolic link.
/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `file_path` should be encoded as valid UTF-8.
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
/// The return value is a slice of `out_buffer` from index 0.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, the result is encoded as UTF-8.
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
return readlinkat(wasi.AT.FDCWD, file_path, out_buffer);
@ -3206,7 +3366,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
}
}
/// Windows-only. Same as `readlink` except `file_path` is WTF16 encoded.
/// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded.
/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// See also `readlinkZ`.
pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
@ -3215,7 +3376,7 @@ pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
/// Same as `readlink` except `file_path` is null-terminated.
pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
return readlinkW(file_path_w.span(), out_buffer);
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
return readlink(mem.sliceTo(file_path, 0), out_buffer);
@ -3232,12 +3393,22 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
.NOENT => return error.FileNotFound,
.NOMEM => return error.SystemResources,
.NOTDIR => return error.NotDir,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle.
/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `file_path` should be encoded as valid UTF-8.
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
/// The return value is a slice of `out_buffer` from index 0.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, the result is encoded as UTF-8.
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
@ -3267,11 +3438,13 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
.NOMEM => return error.SystemResources,
.NOTDIR => return error.NotDir,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8,
else => |err| return unexpectedErrno(err),
}
}
/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.
/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded.
/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// See also `readlinkat`.
pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
return windows.ReadLink(dirfd, file_path, out_buffer);
@ -3298,6 +3471,10 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
.NOENT => return error.FileNotFound,
.NOMEM => return error.SystemResources,
.NOTDIR => return error.NotDir,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
@ -4274,10 +4451,18 @@ pub fn fstat_wasi(fd: fd_t) FStatError!wasi.filestat_t {
}
}
pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLinkLoop };
pub const FStatAtError = FStatError || error{
NameTooLong,
FileNotFound,
SymLinkLoop,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
};
/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`
/// which is relative to `dirfd` handle.
/// On WASI, `pathname` should be encoded as valid UTF-8.
/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
/// See also `fstatatZ` and `fstatat_wasi`.
pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
@ -4294,6 +4479,7 @@ pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat
}
/// WASI-only. Same as `fstatat` but targeting WASI.
/// `pathname` should be encoded as valid UTF-8.
/// See also `fstatat`.
pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t) FStatAtError!wasi.filestat_t {
var stat: wasi.filestat_t = undefined;
@ -4308,6 +4494,7 @@ pub fn fstatat_wasi(dirfd: fd_t, pathname: []const u8, flags: wasi.lookupflags_t
.NOENT => return error.FileNotFound,
.NOTDIR => return error.FileNotFound,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8,
else => |err| return unexpectedErrno(err),
}
}
@ -4337,6 +4524,10 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S
.LOOP => return error.SymLinkLoop,
.NOENT => return error.FileNotFound,
.NOTDIR => return error.FileNotFound,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
@ -4693,12 +4884,17 @@ pub const AccessError = error{
FileBusy,
SymLinkLoop,
ReadOnlyFileSystem,
/// On Windows, file paths must be valid Unicode.
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
} || UnexpectedError;
/// check user's permissions for a file
/// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `path` should be encoded as valid UTF-8.
/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
/// TODO currently this assumes `mode` is `F.OK` on Windows.
pub fn access(path: []const u8, mode: u32) AccessError!void {
if (builtin.os.tag == .windows) {
@ -4740,12 +4936,16 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
.FAULT => unreachable,
.IO => return error.InputOutput,
.NOMEM => return error.SystemResources,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
/// Otherwise use `access` or `accessC`.
/// Call from Windows-specific code if you already have a WTF-16LE encoded, null terminated string.
/// Otherwise use `access` or `accessZ`.
/// TODO currently this ignores `mode`.
pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!void {
_ = mode;
@ -4762,6 +4962,9 @@ pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!v
}
/// Check user's permissions for a file, based on an open directory handle.
/// On Windows, `path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `path` should be encoded as valid UTF-8.
/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
/// TODO currently this ignores `mode` and `flags` on Windows.
pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
if (builtin.os.tag == .windows) {
@ -4832,6 +5035,10 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces
.FAULT => unreachable,
.IO => return error.InputOutput,
.NOMEM => return error.SystemResources,
.ILSEQ => |err| if (builtin.os.tag == .wasi)
return error.InvalidUtf8
else
return unexpectedErrno(err),
else => |err| return unexpectedErrno(err),
}
}
@ -5339,8 +5546,9 @@ pub const RealPathError = error{
/// On WASI, the current CWD may not be associated with an absolute path.
InvalidHandle,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
@ -5362,8 +5570,12 @@ pub const RealPathError = error{
/// Return the canonicalized absolute pathname.
/// Expands all symbolic links and resolves references to `.`, `..`, and
/// extra `/` characters in `pathname`.
/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
/// See also `realpathZ` and `realpathW`.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
/// Calling this function is usually a bug.
pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
if (builtin.os.tag == .windows) {
@ -5402,6 +5614,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
error.WouldBlock => unreachable,
error.FileBusy => unreachable, // not asking for write permissions
error.InvalidHandle => unreachable, // WASI-only
error.InvalidUtf8 => unreachable, // WASI-only
else => |e| return e,
};
defer close(fd);
@ -5425,7 +5638,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
return mem.sliceTo(result_path, 0);
}
/// Same as `realpath` except `pathname` is UTF16LE-encoded.
/// Same as `realpath` except `pathname` is WTF16LE-encoded.
/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// Calling this function is usually a bug.
pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
const w = windows;
@ -5475,6 +5689,8 @@ pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
/// This function is very host-specific and is not universally supported by all hosts.
/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
/// unsupported on WASI.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
/// Calling this function is usually a bug.
pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
@ -5485,10 +5701,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
// TODO: Windows file paths can be arbitrary arrays of u16 values
// and must not fail with InvalidUtf8.
const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch
return error.InvalidUtf8;
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
return out_buffer[0..end_index];
},
.macos, .ios, .watchos, .tvos => {
@ -5512,8 +5725,12 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
const target = readlinkZ(proc_path, out_buffer) catch |err| {
switch (err) {
error.UnsupportedReparsePointType => unreachable, // Windows only,
error.NotLink => unreachable,
error.BadPathName => unreachable,
error.InvalidUtf8 => unreachable, // WASI-only
error.InvalidWtf8 => unreachable, // Windows-only
error.UnsupportedReparsePointType => unreachable, // Windows-only
error.NetworkNotFound => unreachable, // Windows-only
else => |e| return e,
}
};

View File

@ -1,8 +1,8 @@
//! This file contains thin wrappers around Windows-specific APIs, with these
//! specific goals in mind:
//! * Convert "errno"-style error codes into Zig errors.
//! * When null-terminated or UTF16LE byte buffers are required, provide APIs which accept
//! slices as well as APIs which accept null-terminated UTF16LE byte buffers.
//! * When null-terminated or WTF16LE byte buffers are required, provide APIs which accept
//! slices as well as APIs which accept null-terminated WTF16LE byte buffers.
const builtin = @import("builtin");
const std = @import("../std.zig");
@ -548,7 +548,6 @@ pub fn WriteFile(
pub const SetCurrentDirectoryError = error{
NameTooLong,
InvalidUtf8,
FileNotFound,
NotDir,
AccessDenied,
@ -587,24 +586,24 @@ pub const GetCurrentDirectoryError = error{
};
/// The result is a slice of `buffer`, indexed from 0.
/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
var utf16le_buf: [PATH_MAX_WIDE]u16 = undefined;
const result = kernel32.GetCurrentDirectoryW(utf16le_buf.len, &utf16le_buf);
var wtf16le_buf: [PATH_MAX_WIDE]u16 = undefined;
const result = kernel32.GetCurrentDirectoryW(wtf16le_buf.len, &wtf16le_buf);
if (result == 0) {
switch (kernel32.GetLastError()) {
else => |err| return unexpectedError(err),
}
}
assert(result <= utf16le_buf.len);
const utf16le_slice = utf16le_buf[0..result];
// Trust that Windows gives us valid UTF-16LE.
assert(result <= wtf16le_buf.len);
const wtf16le_slice = wtf16le_buf[0..result];
var end_index: usize = 0;
var it = std.unicode.Utf16LeIterator.init(utf16le_slice);
while (it.nextCodepoint() catch unreachable) |codepoint| {
var it = std.unicode.Wtf16LeIterator.init(wtf16le_slice);
while (it.nextCodepoint()) |codepoint| {
const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable;
if (end_index + seq_len >= buffer.len)
return error.NameTooLong;
end_index += std.unicode.utf8Encode(codepoint, buffer[end_index..]) catch unreachable;
end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable;
}
return buffer[0..end_index];
}
@ -812,6 +811,8 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
}
}
/// Asserts that there is enough space is `out_buffer`.
/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {
const win32_namespace_path = path: {
if (is_relative) break :path path;
@ -821,7 +822,7 @@ fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u
};
break :path win32_path.span();
};
const out_len = std.unicode.utf16LeToUtf8(out_buffer, win32_namespace_path) catch unreachable;
const out_len = std.unicode.wtf16LeToWtf8(out_buffer, win32_namespace_path);
return out_buffer[0..out_len];
}
@ -1942,13 +1943,13 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {
if (@inComptime() or builtin.os.tag != .windows) {
// This function compares the strings code unit by code unit (aka u16-to-u16),
// so any length difference implies inequality. In other words, there's no possible
// conversion that changes the number of UTF-16 code units needed for the uppercase/lowercase
// conversion that changes the number of WTF-16 code units needed for the uppercase/lowercase
// version in the conversion table since only codepoints <= max(u16) are eligible
// for conversion at all.
if (a.len != b.len) return false;
for (a, b) |a_c, b_c| {
// The slices are always UTF-16 LE, so need to convert the elements to native
// The slices are always WTF-16 LE, so need to convert the elements to native
// endianness for the uppercasing
const a_c_native = std.mem.littleToNative(u16, a_c);
const b_c_native = std.mem.littleToNative(u16, b_c);
@ -1975,18 +1976,18 @@ pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {
return ntdll.RtlEqualUnicodeString(&a_string, &b_string, TRUE) == TRUE;
}
/// Compares two UTF-8 strings using the equivalent functionality of
/// Compares two WTF-8 strings using the equivalent functionality of
/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
/// This function can be called on any target.
/// Assumes `a` and `b` are valid UTF-8.
pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
/// Assumes `a` and `b` are valid WTF-8.
pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
// A length equality check is not possible here because there are
// some codepoints that have a different length uppercase UTF-8 representations
// than their lowercase counterparts, e.g. U+0250 (2 bytes) <-> U+2C6F (3 bytes).
// There are 7 such codepoints in the uppercase data used by Windows.
var a_utf8_it = std.unicode.Utf8View.initUnchecked(a).iterator();
var b_utf8_it = std.unicode.Utf8View.initUnchecked(b).iterator();
var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();
var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator();
// Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
// redundant copy of the uppercase data.
@ -1996,8 +1997,8 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
};
while (true) {
const a_cp = a_utf8_it.nextCodepoint() orelse break;
const b_cp = b_utf8_it.nextCodepoint() orelse return false;
const a_cp = a_wtf8_it.nextCodepoint() orelse break;
const b_cp = b_wtf8_it.nextCodepoint() orelse return false;
if (a_cp <= std.math.maxInt(u16) and b_cp <= std.math.maxInt(u16)) {
if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) {
@ -2008,26 +2009,26 @@ pub fn eqlIgnoreCaseUtf8(a: []const u8, b: []const u8) bool {
}
}
// Make sure there are no leftover codepoints in b
if (b_utf8_it.nextCodepoint() != null) return false;
if (b_wtf8_it.nextCodepoint() != null) return false;
return true;
}
fn testEqlIgnoreCase(comptime expect_eql: bool, comptime a: []const u8, comptime b: []const u8) !void {
try std.testing.expectEqual(expect_eql, eqlIgnoreCaseUtf8(a, b));
try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWtf8(a, b));
try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWTF16(
std.unicode.utf8ToUtf16LeStringLiteral(a),
std.unicode.utf8ToUtf16LeStringLiteral(b),
));
try comptime std.testing.expect(expect_eql == eqlIgnoreCaseUtf8(a, b));
try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWtf8(a, b));
try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWTF16(
std.unicode.utf8ToUtf16LeStringLiteral(a),
std.unicode.utf8ToUtf16LeStringLiteral(b),
));
}
test "eqlIgnoreCaseWTF16/Utf8" {
test "eqlIgnoreCaseWTF16/Wtf8" {
try testEqlIgnoreCase(true, "\x01 a B Λ ɐ", "\x01 A b λ Ɐ");
// does not do case-insensitive comparison for codepoints >= U+10000
try testEqlIgnoreCase(false, "𐓏", "𐓷");
@ -2117,20 +2118,32 @@ pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize {
return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]);
}
pub const Wtf8ToPrefixedFileWError = error{InvalidWtf8} || Wtf16ToPrefixedFileWError;
/// Same as `sliceToPrefixedFileW` but accepts a pointer
/// to a null-terminated path.
pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) !PathSpace {
/// to a null-terminated WTF-8 encoded path.
/// https://simonsapin.github.io/wtf-8/
pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWError!PathSpace {
return sliceToPrefixedFileW(dir, mem.sliceTo(s, 0));
}
/// Same as `wToPrefixedFileW` but accepts a UTF-8 encoded path.
pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace {
/// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path.
/// https://simonsapin.github.io/wtf-8/
pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace {
var temp_path: PathSpace = undefined;
temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, path);
temp_path.len = try std.unicode.wtf8ToWtf16Le(&temp_path.data, path);
temp_path.data[temp_path.len] = 0;
return wToPrefixedFileW(dir, temp_path.span());
}
pub const Wtf16ToPrefixedFileWError = error{
AccessDenied,
BadPathName,
FileNotFound,
NameTooLong,
Unexpected,
};
/// Converts the `path` to WTF16, null-terminated. If the path contains any
/// namespace prefix, or is anything but a relative path (rooted, drive relative,
/// etc) the result will have the NT-style prefix `\??\`.
@ -2142,7 +2155,7 @@ pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) !PathSpace {
/// is non-null, or the CWD if it is null.
/// - Special case device names like COM1, NUL, etc are not handled specially (TODO)
/// - . and space are not stripped from the end of relative paths (potential TODO)
pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) !PathSpace {
pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!PathSpace {
const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };
switch (getNamespacePrefix(u16, path)) {
// TODO: Figure out a way to design an API that can avoid the copy for .nt,
@ -2312,7 +2325,7 @@ pub const NamespacePrefix = enum {
nt,
};
/// If `T` is `u16`, then `path` should be encoded as UTF-16LE.
/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
pub fn getNamespacePrefix(comptime T: type, path: []const T) NamespacePrefix {
if (path.len < 4) return .none;
var all_backslash = switch (mem.littleToNative(T, path[0])) {
@ -2366,7 +2379,7 @@ pub const UnprefixedPathType = enum {
/// Get the path type of a path that is known to not have any namespace prefixes
/// (`\\?\`, `\\.\`, `\??\`).
/// If `T` is `u16`, then `path` should be encoded as UTF-16LE.
/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathType {
if (path.len < 1) return .relative;
@ -2420,7 +2433,7 @@ test getUnprefixedPathType {
/// Functionality is based on the ReactOS test cases found here:
/// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c
///
/// `path` should be encoded as UTF-16LE.
/// `path` should be encoded as WTF-16LE.
pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
if (path.len > PATH_MAX_WIDE) return error.NameTooLong;
@ -2530,7 +2543,6 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {
if (std.os.unexpected_error_tracing) {
// 614 is the length of the longest windows error description
var buf_wstr: [614]WCHAR = undefined;
var buf_utf8: [614]u8 = undefined;
const len = kernel32.FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
null,
@ -2540,8 +2552,10 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {
buf_wstr.len,
null,
);
_ = std.unicode.utf16LeToUtf8(&buf_utf8, buf_wstr[0..len]) catch unreachable;
std.debug.print("error.Unexpected: GetLastError({}): {s}\n", .{ @intFromEnum(err), buf_utf8[0..len] });
std.debug.print("error.Unexpected: GetLastError({}): {}\n", .{
@intFromEnum(err),
std.unicode.fmtUtf16Le(buf_wstr[0..len]),
});
std.debug.dumpCurrentStackTrace(@returnAddress());
}
return error.Unexpected;

View File

@ -16,11 +16,15 @@ pub const changeCurDir = os.chdir;
pub const changeCurDirC = os.chdirC;
/// The result is a slice of `out_buffer`, from index `0`.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
pub fn getCwd(out_buffer: []u8) ![]u8 {
return os.getcwd(out_buffer);
}
/// Caller must free the returned memory.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
pub fn getCwdAlloc(allocator: Allocator) ![]u8 {
// The use of MAX_PATH_BYTES here is just a heuristic: most paths will fit
// in stack_buf, avoiding an extra allocation in the common case.
@ -76,7 +80,7 @@ pub const EnvMap = struct {
_ = self;
if (builtin.os.tag == .windows) {
var h = std.hash.Wyhash.init(0);
var it = std.unicode.Utf8View.initUnchecked(s).iterator();
var it = std.unicode.Wtf8View.initUnchecked(s).iterator();
while (it.nextCodepoint()) |cp| {
const cp_upper = upcase(cp);
h.update(&[_]u8{
@ -93,8 +97,8 @@ pub const EnvMap = struct {
pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
_ = self;
if (builtin.os.tag == .windows) {
var it_a = std.unicode.Utf8View.initUnchecked(a).iterator();
var it_b = std.unicode.Utf8View.initUnchecked(b).iterator();
var it_a = std.unicode.Wtf8View.initUnchecked(a).iterator();
var it_b = std.unicode.Wtf8View.initUnchecked(b).iterator();
while (true) {
const c_a = it_a.nextCodepoint() orelse break;
const c_b = it_b.nextCodepoint() orelse return false;
@ -129,8 +133,9 @@ pub const EnvMap = struct {
/// Same as `put` but the key and value become owned by the EnvMap rather
/// than being copied.
/// If `putMove` fails, the ownership of key and value does not transfer.
/// On Windows `key` must be a valid UTF-8 string.
/// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {
assert(std.unicode.wtf8ValidateSlice(key));
const get_or_put = try self.hash_map.getOrPut(key);
if (get_or_put.found_existing) {
self.free(get_or_put.key_ptr.*);
@ -141,8 +146,9 @@ pub const EnvMap = struct {
}
/// `key` and `value` are copied into the EnvMap.
/// On Windows `key` must be a valid UTF-8 string.
/// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {
assert(std.unicode.wtf8ValidateSlice(key));
const value_copy = try self.copy(value);
errdefer self.free(value_copy);
const get_or_put = try self.hash_map.getOrPut(key);
@ -159,23 +165,26 @@ pub const EnvMap = struct {
/// Find the address of the value associated with a key.
/// The returned pointer is invalidated if the map resizes.
/// On Windows `key` must be a valid UTF-8 string.
/// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {
assert(std.unicode.wtf8ValidateSlice(key));
return self.hash_map.getPtr(key);
}
/// Return the map's copy of the value associated with
/// a key. The returned string is invalidated if this
/// key is removed from the map.
/// On Windows `key` must be a valid UTF-8 string.
/// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {
assert(std.unicode.wtf8ValidateSlice(key));
return self.hash_map.get(key);
}
/// Removes the item from the map and frees its value.
/// This invalidates the value returned by get() for this key.
/// On Windows `key` must be a valid UTF-8 string.
/// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
pub fn remove(self: *EnvMap, key: []const u8) void {
assert(std.unicode.wtf8ValidateSlice(key));
const kv = self.hash_map.fetchRemove(key) orelse return;
self.free(kv.key);
self.free(kv.value);
@ -239,18 +248,34 @@ test "EnvMap" {
try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
// test Unicode case-insensitivity on Windows
if (builtin.os.tag == .windows) {
// test Unicode case-insensitivity on Windows
try env.put("КИРиллИЦА", "something else");
try testing.expectEqualStrings("something else", env.get("кириллица").?);
// and WTF-8 that's not valid UTF-8
const wtf8_with_surrogate_pair = try std.unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
});
defer testing.allocator.free(wtf8_with_surrogate_pair);
try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
}
}
pub const GetEnvMapError = error{
OutOfMemory,
/// WASI-only. `environ_sizes_get` or `environ_get`
/// failed for an unexpected reason.
Unexpected,
};
/// Returns a snapshot of the environment variables of the current process.
/// Any modifications to the resulting EnvMap will not be reflected in the environment, and
/// likewise, any future modifications to the environment will not be reflected in the EnvMap.
/// Caller owns resulting `EnvMap` and should call its `deinit` fn when done.
pub fn getEnvMap(allocator: Allocator) !EnvMap {
pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap {
var result = EnvMap.init(allocator);
errdefer result.deinit();
@ -269,7 +294,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {
while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
const key_w = ptr[key_start..i];
const key = try std.unicode.utf16LeToUtf8Alloc(allocator, key_w);
const key = try std.unicode.wtf16LeToWtf8Alloc(allocator, key_w);
errdefer allocator.free(key);
if (ptr[i] == '=') i += 1;
@ -277,7 +302,7 @@ pub fn getEnvMap(allocator: Allocator) !EnvMap {
const value_start = i;
while (ptr[i] != 0) : (i += 1) {}
const value_w = ptr[value_start..i];
const value = try std.unicode.utf16LeToUtf8Alloc(allocator, value_w);
const value = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_w);
errdefer allocator.free(value);
i += 1; // skip over null byte
@ -355,25 +380,26 @@ pub const GetEnvVarOwnedError = error{
OutOfMemory,
EnvironmentVariableNotFound,
/// See https://github.com/ziglang/zig/issues/1774
InvalidUtf8,
/// On Windows, environment variable keys provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
};
/// Caller must free returned memory.
/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/),
/// then `error.InvalidWtf8` is returned.
/// On Windows, the value is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the value is an opaque sequence of bytes with no particular encoding.
pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
if (builtin.os.tag == .windows) {
const result_w = blk: {
const key_w = try std.unicode.utf8ToUtf16LeAllocZ(allocator, key);
const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(allocator, key);
defer allocator.free(key_w);
break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
};
return std.unicode.utf16LeToUtf8Alloc(allocator, result_w) catch |err| switch (err) {
error.DanglingSurrogateHalf => return error.InvalidUtf8,
error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,
error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,
else => |e| return e,
};
// wtf16LeToWtf8Alloc can only fail with OutOfMemory
return std.unicode.wtf16LeToWtf8Alloc(allocator, result_w);
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
defer envmap.deinit();
@ -385,6 +411,7 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
}
}
/// On Windows, `key` must be valid UTF-8.
pub fn hasEnvVarConstant(comptime key: []const u8) bool {
if (builtin.os.tag == .windows) {
const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);
@ -396,11 +423,22 @@ pub fn hasEnvVarConstant(comptime key: []const u8) bool {
}
}
pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool {
pub const HasEnvVarError = error{
OutOfMemory,
/// On Windows, environment variable keys provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
};
/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/),
/// then `error.InvalidWtf8` is returned.
pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
if (builtin.os.tag == .windows) {
var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
const key_w = try std.unicode.utf8ToUtf16LeAllocZ(stack_alloc.get(), key);
defer stack_alloc.allocator.free(key_w);
const stack_allocator = stack_alloc.get();
const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
defer stack_allocator.free(key_w);
return std.os.getenvW(key_w) != null;
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
@ -411,9 +449,22 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool
}
}
test "os.getEnvVarOwned" {
const ga = std.testing.allocator;
try testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV"));
test getEnvVarOwned {
try testing.expectError(
error.EnvironmentVariableNotFound,
getEnvVarOwned(std.testing.allocator, "BADENV"),
);
}
test hasEnvVarConstant {
if (builtin.os.tag == .wasi and !builtin.link_libc) return error.SkipZigTest;
try testing.expect(!hasEnvVarConstant("BADENV"));
}
test hasEnvVar {
const has_env = try hasEnvVar(std.testing.allocator, "BADENV");
try testing.expect(!has_env);
}
pub const ArgIteratorPosix = struct {
@ -531,6 +582,7 @@ pub const ArgIteratorWasi = struct {
pub const ArgIteratorWindows = struct {
allocator: Allocator,
/// Owned by the iterator.
/// Encoded as WTF-8.
cmd_line: []const u8,
index: usize = 0,
/// Owned by the iterator. Long enough to hold the entire `cmd_line` plus a null terminator.
@ -538,20 +590,14 @@ pub const ArgIteratorWindows = struct {
start: usize = 0,
end: usize = 0,
pub const InitError = error{ OutOfMemory, InvalidCmdLine };
pub const InitError = error{OutOfMemory};
/// `cmd_line_w` *must* be an UTF16-LE-encoded string.
/// `cmd_line_w` *must* be a WTF16-LE-encoded string.
///
/// The iterator makes a copy of `cmd_line_w` converted UTF-8 and keeps it; it does *not* take
/// The iterator makes a copy of `cmd_line_w` converted WTF-8 and keeps it; it does *not* take
/// ownership of `cmd_line_w`.
pub fn init(allocator: Allocator, cmd_line_w: [*:0]const u16) InitError!ArgIteratorWindows {
const cmd_line = std.unicode.utf16LeToUtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0)) catch |err| switch (err) {
error.DanglingSurrogateHalf,
error.ExpectedSecondSurrogateHalf,
error.UnexpectedSecondSurrogateHalf,
=> return error.InvalidCmdLine,
error.OutOfMemory => return error.OutOfMemory,
};
const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0));
errdefer allocator.free(cmd_line);
const buffer = try allocator.alloc(u8, cmd_line.len + 1);
@ -566,6 +612,7 @@ pub const ArgIteratorWindows = struct {
/// Returns the next argument and advances the iterator. Returns `null` if at the end of the
/// command-line string. The iterator owns the returned slice.
/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 {
return self.nextWithStrategy(next_strategy);
}
@ -777,7 +824,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
pub const Self = @This();
pub const InitError = error{OutOfMemory};
pub const InitUtf16leError = error{ OutOfMemory, InvalidCmdLine };
/// cmd_line_utf8 MUST remain valid and constant while using this instance
pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
@ -805,30 +851,6 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
};
}
/// cmd_line_utf16le MUST be encoded UTF16-LE, and is converted to UTF-8 in an internal buffer
pub fn initUtf16le(allocator: Allocator, cmd_line_utf16le: [*:0]const u16) InitUtf16leError!Self {
const utf16le_slice = mem.sliceTo(cmd_line_utf16le, 0);
const cmd_line = std.unicode.utf16LeToUtf8Alloc(allocator, utf16le_slice) catch |err| switch (err) {
error.ExpectedSecondSurrogateHalf,
error.DanglingSurrogateHalf,
error.UnexpectedSecondSurrogateHalf,
=> return error.InvalidCmdLine,
error.OutOfMemory => return error.OutOfMemory,
};
errdefer allocator.free(cmd_line);
const buffer = try allocator.alloc(u8, cmd_line.len + 1);
errdefer allocator.free(buffer);
return Self{
.allocator = allocator,
.cmd_line = cmd_line,
.free_cmd_line_on_deinit = true,
.buffer = buffer,
};
}
// Skips over whitespace in the cmd_line.
// Returns false if the terminating sentinel is reached, true otherwise.
// Also skips over comments (if supported).
@ -1021,6 +1043,8 @@ pub const ArgIterator = struct {
/// Get the next argument. Returns 'null' if we are at the end.
/// Returned slice is pointing to the iterator's internal buffer.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
pub fn next(self: *ArgIterator) ?([:0]const u8) {
return self.inner.next();
}
@ -1057,6 +1081,8 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator
}
/// Caller must call argsFree on result.
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
// TODO refactor to only make 1 allocation.
var it = try argsWithAllocator(allocator);
@ -1201,7 +1227,7 @@ test "ArgIteratorWindows" {
}
fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {
const cmd_line_w = try std.unicode.utf8ToUtf16LeAllocZ(testing.allocator, cmd_line);
const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);
defer testing.allocator.free(cmd_line_w);
// next

View File

@ -488,7 +488,9 @@ pub const Utf16LeIterator = struct {
};
}
pub fn nextCodepoint(it: *Utf16LeIterator) !?u21 {
pub const NextCodepointError = error{ DanglingSurrogateHalf, ExpectedSecondSurrogateHalf, UnexpectedSecondSurrogateHalf };
pub fn nextCodepoint(it: *Utf16LeIterator) NextCodepointError!?u21 {
assert(it.i <= it.bytes.len);
if (it.i == it.bytes.len) return null;
var code_units: [2]u16 = undefined;
@ -923,7 +925,14 @@ test "fmtUtf8" {
try expectFmt("<EFBFBD><EFBFBD><EFBFBD><EFBFBD>A", "{}", .{fmtUtf8("\xE1\x80\xE2\xF0\x91\x92\xF1\xBFA")});
}
fn utf16LeToUtf8ArrayListImpl(array_list: *std.ArrayList(u8), utf16le: []const u16, comptime surrogates: Surrogates) !void {
fn utf16LeToUtf8ArrayListImpl(
array_list: *std.ArrayList(u8),
utf16le: []const u16,
comptime surrogates: Surrogates,
) (switch (surrogates) {
.cannot_encode_surrogate_half => Utf16LeToUtf8AllocError,
.can_encode_surrogate_half => mem.Allocator.Error,
})!void {
// optimistically guess that it will all be ascii.
try array_list.ensureTotalCapacityPrecise(utf16le.len);
@ -975,7 +984,9 @@ fn utf16LeToUtf8ArrayListImpl(array_list: *std.ArrayList(u8), utf16le: []const u
}
}
pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) !void {
pub const Utf16LeToUtf8AllocError = mem.Allocator.Error || Utf16LeToUtf8Error;
pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) Utf16LeToUtf8AllocError!void {
return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .cannot_encode_surrogate_half);
}
@ -983,7 +994,7 @@ pub fn utf16LeToUtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u
pub const utf16leToUtf8Alloc = utf16LeToUtf8Alloc;
/// Caller must free returned memory.
pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8 {
pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![]u8 {
// optimistically guess that it will all be ascii.
var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len);
errdefer result.deinit();
@ -997,7 +1008,7 @@ pub fn utf16LeToUtf8Alloc(allocator: mem.Allocator, utf16le: []const u16) ![]u8
pub const utf16leToUtf8AllocZ = utf16LeToUtf8AllocZ;
/// Caller must free returned memory.
pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) ![:0]u8 {
pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) Utf16LeToUtf8AllocError![:0]u8 {
// optimistically guess that it will all be ascii (and allocate space for the null terminator)
var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len + 1);
errdefer result.deinit();
@ -1007,9 +1018,14 @@ pub fn utf16LeToUtf8AllocZ(allocator: mem.Allocator, utf16le: []const u16) ![:0]
return result.toOwnedSliceSentinel(0);
}
pub const Utf16LeToUtf8Error = Utf16LeIterator.NextCodepointError;
/// Asserts that the output buffer is big enough.
/// Returns end byte index into utf8.
fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surrogates) !usize {
fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surrogates) (switch (surrogates) {
.cannot_encode_surrogate_half => Utf16LeToUtf8Error,
.can_encode_surrogate_half => error{},
})!usize {
var end_index: usize = 0;
var remaining = utf16le;
@ -1043,7 +1059,9 @@ fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surr
// The maximum possible codepoint encoded by UTF-16 is U+10FFFF,
// which is within the valid codepoint range.
error.CodepointTooLarge => unreachable,
else => |e| return e,
// We know the codepoint was valid in UTF-16, meaning it is not
// an unpaired surrogate codepoint.
error.Utf8CannotEncodeSurrogateHalf => unreachable,
};
}
},
@ -1064,7 +1082,7 @@ fn utf16LeToUtf8Impl(utf8: []u8, utf16le: []const u16, comptime surrogates: Surr
/// Deprecated; renamed to utf16LeToUtf8
pub const utf16leToUtf8 = utf16LeToUtf8;
pub fn utf16LeToUtf8(utf8: []u8, utf16le: []const u16) !usize {
pub fn utf16LeToUtf8(utf8: []u8, utf16le: []const u16) Utf16LeToUtf8Error!usize {
return utf16LeToUtf8Impl(utf8, utf16le, .cannot_encode_surrogate_half);
}
@ -1176,11 +1194,11 @@ fn utf8ToUtf16LeArrayListImpl(array_list: *std.ArrayList(u16), utf8: []const u8,
}
}
pub fn utf8ToUtf16LeArrayList(array_list: *std.ArrayList(u16), utf8: []const u8) !void {
pub fn utf8ToUtf16LeArrayList(array_list: *std.ArrayList(u16), utf8: []const u8) error{ InvalidUtf8, OutOfMemory }!void {
return utf8ToUtf16LeArrayListImpl(array_list, utf8, .cannot_encode_surrogate_half);
}
pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) ![]u16 {
pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![]u16 {
// optimistically guess that it will not require surrogate pairs
var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len);
errdefer result.deinit();
@ -1193,7 +1211,7 @@ pub fn utf8ToUtf16LeAlloc(allocator: mem.Allocator, utf8: []const u8) ![]u16 {
/// Deprecated; renamed to utf8ToUtf16LeAllocZ
pub const utf8ToUtf16LeWithNull = utf8ToUtf16LeAllocZ;
pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) ![:0]u16 {
pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) error{ InvalidUtf8, OutOfMemory }![:0]u16 {
// optimistically guess that it will not require surrogate pairs
var result = try std.ArrayList(u16).initCapacity(allocator, utf8.len + 1);
errdefer result.deinit();
@ -1205,7 +1223,7 @@ pub fn utf8ToUtf16LeAllocZ(allocator: mem.Allocator, utf8: []const u8) ![:0]u16
/// Returns index of next character. If exact fit, returned index equals output slice length.
/// Assumes there is enough space for the output.
pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize {
pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) error{InvalidUtf8}!usize {
return utf8ToUtf16LeImpl(utf16le, utf8, .cannot_encode_surrogate_half);
}
@ -1236,11 +1254,14 @@ pub fn utf8ToUtf16LeImpl(utf16le: []u16, utf8: []const u8, comptime surrogates:
var src_i: usize = 0;
while (src_i < remaining.len) {
const n = utf8ByteSequenceLength(remaining[src_i]) catch return error.InvalidUtf8;
const n = utf8ByteSequenceLength(remaining[src_i]) catch return switch (surrogates) {
.cannot_encode_surrogate_half => error.InvalidUtf8,
.can_encode_surrogate_half => error.InvalidWtf8,
};
const next_src_i = src_i + n;
const codepoint = switch (surrogates) {
.cannot_encode_surrogate_half => utf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8,
.can_encode_surrogate_half => wtf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidUtf8,
.can_encode_surrogate_half => wtf8Decode(remaining[src_i..next_src_i]) catch return error.InvalidWtf8,
};
if (codepoint < 0x10000) {
const short = @as(u16, @intCast(codepoint));
@ -1600,9 +1621,9 @@ fn testValidateWtf8Slice() !void {
pub const Wtf8View = struct {
bytes: []const u8,
pub fn init(s: []const u8) !Wtf8View {
pub fn init(s: []const u8) error{InvalidWtf8}!Wtf8View {
if (!wtf8ValidateSlice(s)) {
return error.InvalidUtf8;
return error.InvalidWtf8;
}
return initUnchecked(s);
@ -1614,8 +1635,8 @@ pub const Wtf8View = struct {
pub inline fn initComptime(comptime s: []const u8) Wtf8View {
return comptime if (init(s)) |r| r else |err| switch (err) {
error.InvalidUtf8 => {
@compileError("invalid utf8 detected in wtf8 string");
error.InvalidWtf8 => {
@compileError("invalid wtf8");
},
};
}
@ -1665,12 +1686,12 @@ pub const Wtf8Iterator = struct {
}
};
pub fn wtf16LeToWtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) !void {
pub fn wtf16LeToWtf8ArrayList(array_list: *std.ArrayList(u8), utf16le: []const u16) mem.Allocator.Error!void {
return utf16LeToUtf8ArrayListImpl(array_list, utf16le, .can_encode_surrogate_half);
}
/// Caller must free returned memory.
pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) ![]u8 {
pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![]u8 {
// optimistically guess that it will all be ascii.
var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len);
errdefer result.deinit();
@ -1681,7 +1702,7 @@ pub fn wtf16LeToWtf8Alloc(allocator: mem.Allocator, wtf16le: []const u16) ![]u8
}
/// Caller must free returned memory.
pub fn wtf16LeToWtf8AllocZ(allocator: mem.Allocator, wtf16le: []const u16) ![:0]u8 {
pub fn wtf16LeToWtf8AllocZ(allocator: mem.Allocator, wtf16le: []const u16) mem.Allocator.Error![:0]u8 {
// optimistically guess that it will all be ascii (and allocate space for the null terminator)
var result = try std.ArrayList(u8).initCapacity(allocator, wtf16le.len + 1);
errdefer result.deinit();
@ -1695,11 +1716,11 @@ pub fn wtf16LeToWtf8(wtf8: []u8, wtf16le: []const u16) usize {
return utf16LeToUtf8Impl(wtf8, wtf16le, .can_encode_surrogate_half) catch |err| switch (err) {};
}
pub fn wtf8ToWtf16LeArrayList(array_list: *std.ArrayList(u16), wtf8: []const u8) !void {
pub fn wtf8ToWtf16LeArrayList(array_list: *std.ArrayList(u16), wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }!void {
return utf8ToUtf16LeArrayListImpl(array_list, wtf8, .can_encode_surrogate_half);
}
pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u16 {
pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u16 {
// optimistically guess that it will not require surrogate pairs
var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len);
errdefer result.deinit();
@ -1709,7 +1730,7 @@ pub fn wtf8ToWtf16LeAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u16 {
return result.toOwnedSlice();
}
pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u16 {
pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u16 {
// optimistically guess that it will not require surrogate pairs
var result = try std.ArrayList(u16).initCapacity(allocator, wtf8.len + 1);
errdefer result.deinit();
@ -1721,7 +1742,7 @@ pub fn wtf8ToWtf16LeAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u16
/// Returns index of next character. If exact fit, returned index equals output slice length.
/// Assumes there is enough space for the output.
pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) !usize {
pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{InvalidWtf8}!usize {
return utf8ToUtf16LeImpl(wtf16le, wtf8, .can_encode_surrogate_half);
}
@ -1732,7 +1753,8 @@ pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) !usize {
/// In-place conversion is supported when `utf8` and `wtf8` refer to the same slice.
/// Note: If `wtf8` is entirely composed of well-formed UTF-8, then no conversion is necessary.
/// `utf8ValidateSlice` can be used to check if lossy conversion is worthwhile.
pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) !void {
/// If `wtf8` is not valid WTF-8, then `error.InvalidWtf8` is returned.
pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) error{InvalidWtf8}!void {
assert(utf8.len >= wtf8.len);
const in_place = utf8.ptr == wtf8.ptr;
@ -1762,7 +1784,7 @@ pub fn wtf8ToUtf8Lossy(utf8: []u8, wtf8: []const u8) !void {
}
}
pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u8 {
pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![]u8 {
const utf8 = try allocator.alloc(u8, wtf8.len);
errdefer allocator.free(utf8);
@ -1771,7 +1793,7 @@ pub fn wtf8ToUtf8LossyAlloc(allocator: mem.Allocator, wtf8: []const u8) ![]u8 {
return utf8;
}
pub fn wtf8ToUtf8LossyAllocZ(allocator: mem.Allocator, wtf8: []const u8) ![:0]u8 {
pub fn wtf8ToUtf8LossyAllocZ(allocator: mem.Allocator, wtf8: []const u8) error{ InvalidWtf8, OutOfMemory }![:0]u8 {
const utf8 = try allocator.allocSentinel(u8, wtf8.len, 0);
errdefer allocator.free(utf8);

View File

@ -639,7 +639,8 @@ pub fn abiAndDynamicLinkerFromFile(
var link_buf: [std.os.PATH_MAX]u8 = undefined;
const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {
error.NameTooLong => unreachable,
error.InvalidUtf8 => unreachable, // Windows only
error.InvalidUtf8 => unreachable, // WASI only
error.InvalidWtf8 => unreachable, // Windows only
error.BadPathName => unreachable, // Windows only
error.UnsupportedReparsePointType => unreachable, // Windows only
error.NetworkNotFound => unreachable, // Windows only
@ -730,7 +731,8 @@ test glibcVerFromLinkName {
fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
error.NameTooLong => unreachable,
error.InvalidUtf8 => unreachable,
error.InvalidUtf8 => unreachable, // WASI only
error.InvalidWtf8 => unreachable, // Windows-only
error.BadPathName => unreachable,
error.DeviceBusy => unreachable,
error.NetworkNotFound => unreachable, // Windows-only
@ -761,7 +763,8 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
const glibc_so_basename = "libc.so.6";
var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
error.NameTooLong => unreachable,
error.InvalidUtf8 => unreachable, // Windows only
error.InvalidUtf8 => unreachable, // WASI only
error.InvalidWtf8 => unreachable, // Windows only
error.BadPathName => unreachable, // Windows only
error.PipeBusy => unreachable, // Windows-only
error.SharingViolation => unreachable, // Windows-only
@ -998,7 +1001,8 @@ fn detectAbiAndDynamicLinker(
error.NameTooLong => unreachable,
error.PathAlreadyExists => unreachable,
error.SharingViolation => unreachable,
error.InvalidUtf8 => unreachable,
error.InvalidUtf8 => unreachable, // WASI only
error.InvalidWtf8 => unreachable, // Windows only
error.BadPathName => unreachable,
error.PipeBusy => unreachable,
error.FileLocksNotSupported => unreachable,

View File

@ -41,7 +41,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
}
}
} else |err| switch (err) {
error.InvalidUtf8 => {},
error.InvalidWtf8 => unreachable,
error.EnvironmentVariableNotFound => {},
error.OutOfMemory => |e| return e,
}
@ -73,7 +73,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
}
}
} else |err| switch (err) {
error.InvalidUtf8 => {},
error.InvalidWtf8 => unreachable,
error.EnvironmentVariableNotFound => {},
error.OutOfMemory => |e| return e,
}

View File

@ -2662,6 +2662,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
}) catch |err| switch (err) {
error.NotDir => unreachable, // no dir components
error.InvalidUtf8 => unreachable, // it's a hex encoded name
error.InvalidWtf8 => unreachable, // it's a hex encoded name
error.BadPathName => unreachable, // it's a hex encoded name
error.NameTooLong => unreachable, // it's a fixed size name
error.PipeBusy => unreachable, // it's not a pipe

View File

@ -246,7 +246,10 @@ pub const LibCInstallation = struct {
const allocator = args.allocator;
// Detect infinite loops.
var env_map = try std.process.getEnvMap(allocator);
var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
error.Unexpected => unreachable, // WASI-only
else => |e| return e,
};
defer env_map.deinit();
const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
if (std.mem.eql(u8, phase, "1")) {
@ -572,7 +575,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
const allocator = args.allocator;
// Detect infinite loops.
var env_map = try std.process.getEnvMap(allocator);
var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
error.Unexpected => unreachable, // WASI-only
else => |e| return e,
};
defer env_map.deinit();
const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
if (std.mem.eql(u8, phase, "1")) {

View File

@ -84,26 +84,26 @@ fn iterateAndFilterBySemVer(
return dirs_filtered_slice;
}
const RegistryUtf8 = struct {
const RegistryWtf8 = struct {
key: windows.HKEY,
/// Assert that `key` is valid UTF-8 string
pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryUtf8 {
const key_utf16le: [:0]const u16 = key_utf16le: {
var key_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
const key_utf16le_len: usize = std.unicode.utf8ToUtf16Le(key_utf16le_buf[0..], key) catch |err| switch (err) {
error.InvalidUtf8 => unreachable,
/// Assert that `key` is valid WTF-8 string
pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryWtf8 {
const key_wtf16le: [:0]const u16 = key_wtf16le: {
var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) {
error.InvalidWtf8 => unreachable,
};
key_utf16le_buf[key_utf16le_len] = 0;
break :key_utf16le key_utf16le_buf[0..key_utf16le_len :0];
key_wtf16le_buf[key_wtf16le_len] = 0;
break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0];
};
const registry_utf16le = try RegistryUtf16Le.openKey(hkey, key_utf16le);
return RegistryUtf8{ .key = registry_utf16le.key };
const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le);
return RegistryWtf8{ .key = registry_wtf16le.key };
}
/// Closes key, after that usage is invalid
pub fn closeKey(self: *const RegistryUtf8) void {
pub fn closeKey(self: *const RegistryWtf8) void {
const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
const return_code: windows.Win32Error = @enumFromInt(return_code_int);
switch (return_code) {
@ -114,71 +114,68 @@ const RegistryUtf8 = struct {
/// Get string from registry.
/// Caller owns result.
pub fn getString(self: *const RegistryUtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
const subkey_utf16le: [:0]const u16 = subkey_utf16le: {
var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;
subkey_utf16le_buf[subkey_utf16le_len] = 0;
break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];
pub fn getString(self: *const RegistryWtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
};
const value_name_utf16le: [:0]const u16 = value_name_utf16le: {
var value_name_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
const value_name_utf16le_len: usize = std.unicode.utf8ToUtf16Le(value_name_utf16le_buf[0..], value_name) catch unreachable;
value_name_utf16le_buf[value_name_utf16le_len] = 0;
break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];
const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
};
const registry_utf16le = RegistryUtf16Le{ .key = self.key };
const value_utf16le = try registry_utf16le.getString(allocator, subkey_utf16le, value_name_utf16le);
defer allocator.free(value_utf16le);
const registry_wtf16le = RegistryWtf16Le{ .key = self.key };
const value_wtf16le = try registry_wtf16le.getString(allocator, subkey_wtf16le, value_name_wtf16le);
defer allocator.free(value_wtf16le);
const value_utf8: []u8 = std.unicode.utf16LeToUtf8Alloc(allocator, value_utf16le) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => return error.StringNotFound,
};
errdefer allocator.free(value_utf8);
const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_wtf16le);
errdefer allocator.free(value_wtf8);
return value_utf8;
return value_wtf8;
}
/// Get DWORD (u32) from registry.
pub fn getDword(self: *const RegistryUtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
const subkey_utf16le: [:0]const u16 = subkey_utf16le: {
var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;
subkey_utf16le_buf[subkey_utf16le_len] = 0;
break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];
pub fn getDword(self: *const RegistryWtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
};
const value_name_utf16le: [:0]const u16 = value_name_utf16le: {
var value_name_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
const value_name_utf16le_len: usize = std.unicode.utf8ToUtf16Le(value_name_utf16le_buf[0..], value_name) catch unreachable;
value_name_utf16le_buf[value_name_utf16le_len] = 0;
break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];
const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
};
const registry_utf16le = RegistryUtf16Le{ .key = self.key };
return try registry_utf16le.getDword(subkey_utf16le, value_name_utf16le);
const registry_wtf16le = RegistryWtf16Le{ .key = self.key };
return try registry_wtf16le.getDword(subkey_wtf16le, value_name_wtf16le);
}
/// Under private space with flags:
/// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
/// After finishing work, call `closeKey`.
pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryUtf8 {
const absolute_path_utf16le: [:0]const u16 = absolute_path_utf16le: {
var absolute_path_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
const absolute_path_utf16le_len: usize = std.unicode.utf8ToUtf16Le(absolute_path_utf16le_buf[0..], absolute_path) catch unreachable;
absolute_path_utf16le_buf[absolute_path_utf16le_len] = 0;
break :absolute_path_utf16le absolute_path_utf16le_buf[0..absolute_path_utf16le_len :0];
pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryWtf8 {
const absolute_path_wtf16le: [:0]const u16 = absolute_path_wtf16le: {
var absolute_path_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
const absolute_path_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(absolute_path_wtf16le_buf[0..], absolute_path) catch unreachable;
absolute_path_wtf16le_buf[absolute_path_wtf16le_len] = 0;
break :absolute_path_wtf16le absolute_path_wtf16le_buf[0..absolute_path_wtf16le_len :0];
};
const registry_utf16le = try RegistryUtf16Le.loadFromPath(absolute_path_utf16le);
return RegistryUtf8{ .key = registry_utf16le.key };
const registry_wtf16le = try RegistryWtf16Le.loadFromPath(absolute_path_wtf16le);
return RegistryWtf8{ .key = registry_wtf16le.key };
}
};
const RegistryUtf16Le = struct {
const RegistryWtf16Le = struct {
key: windows.HKEY,
/// Includes root key (f.e. HKEY_LOCAL_MACHINE).
@ -191,11 +188,11 @@ const RegistryUtf16Le = struct {
/// Under HKEY_LOCAL_MACHINE with flags:
/// KEY_QUERY_VALUE, KEY_WOW64_32KEY, and KEY_ENUMERATE_SUB_KEYS.
/// After finishing work, call `closeKey`.
fn openKey(hkey: windows.HKEY, key_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le {
fn openKey(hkey: windows.HKEY, key_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le {
var key: windows.HKEY = undefined;
const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW(
hkey,
key_utf16le,
key_wtf16le,
0,
windows.KEY_QUERY_VALUE | windows.KEY_WOW64_32KEY | windows.KEY_ENUMERATE_SUB_KEYS,
&key,
@ -207,11 +204,11 @@ const RegistryUtf16Le = struct {
else => return error.KeyNotFound,
}
return RegistryUtf16Le{ .key = key };
return RegistryWtf16Le{ .key = key };
}
/// Closes key, after that usage is invalid
fn closeKey(self: *const RegistryUtf16Le) void {
fn closeKey(self: *const RegistryWtf16Le) void {
const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
const return_code: windows.Win32Error = @enumFromInt(return_code_int);
switch (return_code) {
@ -221,25 +218,25 @@ const RegistryUtf16Le = struct {
}
/// Get string ([:0]const u16) from registry.
fn getString(self: *const RegistryUtf16Le, allocator: std.mem.Allocator, subkey_utf16le: [:0]const u16, value_name_utf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 {
fn getString(self: *const RegistryWtf16Le, allocator: std.mem.Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 {
var actual_type: windows.ULONG = undefined;
// Calculating length to allocate
var value_utf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters.
var value_wtf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters.
var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
self.key,
subkey_utf16le,
value_name_utf16le,
subkey_wtf16le,
value_name_wtf16le,
RRF.RT_REG_SZ,
&actual_type,
null,
&value_utf16le_buf_size,
&value_wtf16le_buf_size,
);
// Check returned code and type
var return_code: windows.Win32Error = @enumFromInt(return_code_int);
switch (return_code) {
.SUCCESS => std.debug.assert(value_utf16le_buf_size != 0),
.SUCCESS => std.debug.assert(value_wtf16le_buf_size != 0),
.MORE_DATA => unreachable, // We are only reading length
.FILE_NOT_FOUND => return error.ValueNameNotFound,
.INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
@ -250,17 +247,17 @@ const RegistryUtf16Le = struct {
else => return error.NotAString,
}
const value_utf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_utf16le_buf_size, 2) catch unreachable);
errdefer allocator.free(value_utf16le_buf);
const value_wtf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable);
errdefer allocator.free(value_wtf16le_buf);
return_code_int = windows.advapi32.RegGetValueW(
self.key,
subkey_utf16le,
value_name_utf16le,
subkey_wtf16le,
value_name_wtf16le,
RRF.RT_REG_SZ,
&actual_type,
value_utf16le_buf.ptr,
&value_utf16le_buf_size,
value_wtf16le_buf.ptr,
&value_wtf16le_buf_size,
);
// Check returned code and (just in case) type again.
@ -277,28 +274,28 @@ const RegistryUtf16Le = struct {
else => return error.NotAString,
}
const value_utf16le: []const u16 = value_utf16le: {
const value_wtf16le: []const u16 = value_wtf16le: {
// note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space
// we will just search for zero termination and forget length
// Windows sure is strange
const value_utf16le_overestimated: [*:0]const u16 = @ptrCast(value_utf16le_buf.ptr);
break :value_utf16le std.mem.span(value_utf16le_overestimated);
const value_wtf16le_overestimated: [*:0]const u16 = @ptrCast(value_wtf16le_buf.ptr);
break :value_wtf16le std.mem.span(value_wtf16le_overestimated);
};
_ = allocator.resize(value_utf16le_buf, value_utf16le.len);
return value_utf16le;
_ = allocator.resize(value_wtf16le_buf, value_wtf16le.len);
return value_wtf16le;
}
/// Get DWORD (u32) from registry.
fn getDword(self: *const RegistryUtf16Le, subkey_utf16le: [:0]const u16, value_name_utf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
fn getDword(self: *const RegistryWtf16Le, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
var actual_type: windows.ULONG = undefined;
var reg_size: u32 = @sizeOf(u32);
var reg_value: u32 = 0;
const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
self.key,
subkey_utf16le,
value_name_utf16le,
subkey_wtf16le,
value_name_wtf16le,
RRF.RT_REG_DWORD,
&actual_type,
&reg_value,
@ -324,11 +321,11 @@ const RegistryUtf16Le = struct {
/// Under private space with flags:
/// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
/// After finishing work, call `closeKey`.
fn loadFromPath(absolute_path_as_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le {
fn loadFromPath(absolute_path_as_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le {
var key: windows.HKEY = undefined;
const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW(
absolute_path_as_utf16le,
absolute_path_as_wtf16le,
&key,
windows.KEY_QUERY_VALUE | windows.KEY_ENUMERATE_SUB_KEYS,
0,
@ -340,7 +337,7 @@ const RegistryUtf16Le = struct {
else => return error.KeyNotFound,
}
return RegistryUtf16Le{ .key = key };
return RegistryWtf16Le{ .key = key };
}
};
@ -352,7 +349,7 @@ pub const Windows10Sdk = struct {
/// Caller owns the result's fields.
/// After finishing work, call `free(allocator)`.
fn find(allocator: std.mem.Allocator) error{ OutOfMemory, Windows10SdkNotFound, PathTooLong, VersionTooLong }!Windows10Sdk {
const v10_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) {
const v10_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) {
error.KeyNotFound => return error.Windows10SdkNotFound,
};
defer v10_key.closeKey();
@ -413,11 +410,11 @@ pub const Windows10Sdk = struct {
/// Check whether this version is enumerated in registry.
fn isValidVersion(windows10sdk: *const Windows10Sdk) bool {
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const reg_query_as_utf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) {
const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) {
error.NoSpaceLeft => return false,
};
const options_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_utf8) catch |err| switch (err) {
const options_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, reg_query_as_wtf8) catch |err| switch (err) {
error.KeyNotFound => return false,
};
defer options_key.closeKey();
@ -447,7 +444,7 @@ pub const Windows81Sdk = struct {
/// Find path and version of Windows 8.1 SDK.
/// Caller owns the result's fields.
/// After finishing work, call `free(allocator)`.
fn find(allocator: std.mem.Allocator, roots_key: *const RegistryUtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk {
fn find(allocator: std.mem.Allocator, roots_key: *const RegistryWtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk {
const path: []const u8 = path81: {
const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", "KitsRoot81") catch |err| switch (err) {
error.NotAString => return error.Windows81SdkNotFound,
@ -523,7 +520,7 @@ pub const ZigWindowsSDK = struct {
if (builtin.os.tag != .windows) return error.NotFound;
//note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed
const roots_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) {
const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, WINDOWS_KIT_REG_KEY) catch |err| switch (err) {
error.KeyNotFound => return error.NotFound,
};
defer roots_key.closeKey();
@ -583,7 +580,7 @@ pub const ZigWindowsSDK = struct {
const MsvcLibDir = struct {
fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir {
const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}";
const setup_config_key = RegistryUtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) {
const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) {
error.KeyNotFound => return error.PathNotFound,
};
defer setup_config_key.closeKey();
@ -805,13 +802,13 @@ const MsvcLibDir = struct {
for (vs_versions) |vs_version| allocator.free(vs_version);
allocator.free(vs_versions);
}
var config_subkey_buf: [RegistryUtf16Le.key_name_max_len * 2]u8 = undefined;
var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined;
const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {
const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;
defer allocator.free(privateregistry_absolute_path);
if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue;
const visualstudio_registry = RegistryUtf8.loadFromPath(privateregistry_absolute_path) catch continue;
const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue;
defer visualstudio_registry.closeKey();
const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable;
@ -894,7 +891,7 @@ const MsvcLibDir = struct {
}
}
const vs7_key = RegistryUtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound;
const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound;
defer vs7_key.closeKey();
try_vs7_key: {
const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) {