Skip to content

Commit 003aa82

Browse files
committed
std.os.windows: add error.UnrecognizedVolume
Thanks to @matklad for finding this additional NTSTATUS possibility when calling GetFinalPathNameByHandle.
1 parent 57d6f78 commit 003aa82

File tree

3 files changed

+52
-11
lines changed

3 files changed

+52
-11
lines changed

lib/std/os.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5364,13 +5364,18 @@ pub const RealPathError = error{
53645364
/// intercepts file system operations and makes them significantly slower
53655365
/// in addition to possibly failing with this error code.
53665366
AntivirusInterference,
5367+
5368+
/// On Windows, the volume does not contain a recognized file system. File
5369+
/// system drivers might not be loaded, or the volume may be corrupt.
5370+
UnrecognizedVolume,
53675371
} || UnexpectedError;
53685372

53695373
/// Return the canonicalized absolute pathname.
53705374
/// Expands all symbolic links and resolves references to `.`, `..`, and
53715375
/// extra `/` characters in `pathname`.
53725376
/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
53735377
/// See also `realpathZ` and `realpathW`.
5378+
/// Calling this function is usually a bug.
53745379
pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
53755380
if (builtin.os.tag == .windows) {
53765381
const pathname_w = try windows.sliceToPrefixedFileW(null, pathname);
@@ -5383,6 +5388,7 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
53835388
}
53845389

53855390
/// Same as `realpath` except `pathname` is null-terminated.
5391+
/// Calling this function is usually a bug.
53865392
pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
53875393
if (builtin.os.tag == .windows) {
53885394
const pathname_w = try windows.cStrToPrefixedFileW(null, pathname);
@@ -5431,6 +5437,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
54315437
}
54325438

54335439
/// Same as `realpath` except `pathname` is UTF16LE-encoded.
5440+
/// Calling this function is usually a bug.
54345441
pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
54355442
const w = windows;
54365443

@@ -5479,6 +5486,7 @@ pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
54795486
/// This function is very host-specific and is not universally supported by all hosts.
54805487
/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
54815488
/// unsupported on WASI.
5489+
/// Calling this function is usually a bug.
54825490
pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
54835491
if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
54845492
@compileError("querying for canonical path of a handle is unsupported on this host");

lib/std/os/windows.zig

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,13 @@ pub fn CreateEventExW(attributes: ?*SECURITY_ATTRIBUTES, nameW: [*:0]const u16,
178178
}
179179
}
180180

181-
pub const DeviceIoControlError = error{ AccessDenied, Unexpected };
181+
pub const DeviceIoControlError = error{
182+
AccessDenied,
183+
/// The volume does not contain a recognized file system. File system
184+
/// drivers might not be loaded, or the volume may be corrupt.
185+
UnrecognizedVolume,
186+
Unexpected,
187+
};
182188

183189
/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.
184190
/// It implements similar behavior to `DeviceIoControl` and is meant to serve
@@ -234,6 +240,7 @@ pub fn DeviceIoControl(
234240
.ACCESS_DENIED => return error.AccessDenied,
235241
.INVALID_DEVICE_REQUEST => return error.AccessDenied, // Not supported by the underlying filesystem
236242
.INVALID_PARAMETER => unreachable,
243+
.UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume,
237244
else => return unexpectedStatus(rc),
238245
}
239246
}
@@ -688,20 +695,24 @@ pub fn CreateSymbolicLink(
688695
const target_is_absolute = std.fs.path.isAbsoluteWindowsWTF16(final_target_path);
689696
const symlink_data = SYMLINK_DATA{
690697
.ReparseTag = IO_REPARSE_TAG_SYMLINK,
691-
.ReparseDataLength = @as(u16, @intCast(buf_len - header_len)),
698+
.ReparseDataLength = @intCast(buf_len - header_len),
692699
.Reserved = 0,
693-
.SubstituteNameOffset = @as(u16, @intCast(final_target_path.len * 2)),
694-
.SubstituteNameLength = @as(u16, @intCast(final_target_path.len * 2)),
700+
.SubstituteNameOffset = @intCast(final_target_path.len * 2),
701+
.SubstituteNameLength = @intCast(final_target_path.len * 2),
695702
.PrintNameOffset = 0,
696-
.PrintNameLength = @as(u16, @intCast(final_target_path.len * 2)),
703+
.PrintNameLength = @intCast(final_target_path.len * 2),
697704
.Flags = if (!target_is_absolute) SYMLINK_FLAG_RELATIVE else 0,
698705
};
699706

700707
@memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data));
701708
@memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
702709
const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2;
703710
@memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
704-
_ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null);
711+
_ = DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null) catch |err| switch (err) {
712+
error.AccessDenied => return error.Unexpected,
713+
error.UnrecognizedVolume => return error.Unexpected,
714+
error.Unexpected => return error.Unexpected,
715+
};
705716
}
706717

707718
pub const ReadLinkError = error{
@@ -769,7 +780,8 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
769780

770781
var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(REPARSE_DATA_BUFFER)) = undefined;
771782
_ = DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]) catch |err| switch (err) {
772-
error.AccessDenied => unreachable,
783+
error.AccessDenied => return error.Unexpected,
784+
error.UnrecognizedVolume => return error.Unexpected,
773785
else => |e| return e,
774786
};
775787

@@ -1084,6 +1096,9 @@ pub const GetFinalPathNameByHandleError = error{
10841096
BadPathName,
10851097
FileNotFound,
10861098
NameTooLong,
1099+
/// The volume does not contain a recognized file system. File system
1100+
/// drivers might not be loaded, or the volume may be corrupt.
1101+
UnrecognizedVolume,
10871102
Unexpected,
10881103
};
10891104

@@ -1174,13 +1189,13 @@ pub fn GetFinalPathNameByHandle(
11741189
};
11751190
defer CloseHandle(mgmt_handle);
11761191

1177-
var input_struct = @as(*MOUNTMGR_MOUNT_POINT, @ptrCast(&input_buf[0]));
1192+
var input_struct: *MOUNTMGR_MOUNT_POINT = @ptrCast(&input_buf[0]);
11781193
input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT);
11791194
input_struct.DeviceNameLength = @as(USHORT, @intCast(volume_name_u16.len * 2));
11801195
@memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr)));
11811196

11821197
DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, &input_buf, &output_buf) catch |err| switch (err) {
1183-
error.AccessDenied => unreachable,
1198+
error.AccessDenied => return error.Unexpected,
11841199
else => |e| return e,
11851200
};
11861201
const mount_points_struct = @as(*const MOUNTMGR_MOUNT_POINTS, @ptrCast(&output_buf[0]));
@@ -2203,7 +2218,7 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) !PathSpace {
22032218
.unc_absolute => nt_prefix.len + 2,
22042219
else => nt_prefix.len,
22052220
};
2206-
const buf_len = @as(u32, @intCast(path_space.data.len - path_buf_offset));
2221+
const buf_len: u32 = @intCast(path_space.data.len - path_buf_offset);
22072222
const path_to_get: [:0]const u16 = path_to_get: {
22082223
// If dir is null, then we don't need to bother with GetFinalPathNameByHandle because
22092224
// RtlGetFullPathName_U will resolve relative paths against the CWD for us.
@@ -2221,7 +2236,24 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) !PathSpace {
22212236
// canonicalize it. We do this by getting the path of the `dir`
22222237
// and appending the relative path to it.
22232238
var dir_path_buf: [PATH_MAX_WIDE:0]u16 = undefined;
2224-
const dir_path = try GetFinalPathNameByHandle(dir.?, .{}, &dir_path_buf);
2239+
const dir_path = GetFinalPathNameByHandle(dir.?, .{}, &dir_path_buf) catch |err| switch (err) {
2240+
// This mapping is not correct; it is actually expected
2241+
// that calling GetFinalPathNameByHandle might return
2242+
// error.UnrecognizedVolume, and in fact has been observed
2243+
// in the wild. The problem is that wToPrefixedFileW was
2244+
// never intended to make *any* OS syscall APIs. It's only
2245+
// supposed to convert a string to one that is eligible to
2246+
// be used in the ntdll syscalls.
2247+
//
2248+
// To solve this, this function needs to no longer call
2249+
// GetFinalPathNameByHandle under any conditions, or the
2250+
// calling function needs to get reworked to not need to
2251+
// call this function.
2252+
//
2253+
// This may involve making breaking API changes.
2254+
error.UnrecognizedVolume => return error.Unexpected,
2255+
else => |e| return e,
2256+
};
22252257
if (dir_path.len + 1 + path.len > PATH_MAX_WIDE) {
22262258
return error.NameTooLong;
22272259
}

src/link.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@ pub const File = struct {
543543
UnexpectedTable,
544544
UnexpectedValue,
545545
UnknownFeature,
546+
UnrecognizedVolume,
546547
Unseekable,
547548
UnsupportedCpuArchitecture,
548549
UnsupportedVersion,

0 commit comments

Comments
 (0)