Function wToPrefixedFileW [src]
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 \??\.
Similar to RtlDosPathNameToNtPathName_U with a few differences:
Does not allocate on the heap.
Relative paths are kept as relative unless they contain too many ..
components, in which case they are resolved against the dir if it
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)
Prototype
pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!PathSpace
Parameters
dir: ?HANDLE
path: [:0]const u16
Possible Errors
Source
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,
// since it is always returned fully unmodified.
.nt, .verbatim => {
var path_space: PathSpace = undefined;
path_space.data[0..nt_prefix.len].* = nt_prefix;
const len_after_prefix = path.len - nt_prefix.len;
@memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]);
path_space.len = path.len;
path_space.data[path_space.len] = 0;
return path_space;
},
.local_device, .fake_verbatim => {
var path_space: PathSpace = undefined;
const path_byte_len = ntdll.RtlGetFullPathName_U(
path.ptr,
path_space.data.len * 2,
&path_space.data,
null,
);
if (path_byte_len == 0) {
// TODO: This may not be the right error
return error.BadPathName;
} else if (path_byte_len / 2 > path_space.data.len) {
return error.NameTooLong;
}
path_space.len = path_byte_len / 2;
// Both prefixes will be normalized but retained, so all
// we need to do now is replace them with the NT prefix
path_space.data[0..nt_prefix.len].* = nt_prefix;
return path_space;
},
.none => {
const path_type = getUnprefixedPathType(u16, path);
var path_space: PathSpace = undefined;
relative: {
if (path_type == .relative) {
// TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc.
// See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
// TODO: Potentially strip all trailing . and space characters from the
// end of the path. This is something that both RtlDosPathNameToNtPathName_U
// and RtlGetFullPathName_U do. Technically, trailing . and spaces
// are allowed, but such paths may not interact well with Windows (i.e.
// files with these paths can't be deleted from explorer.exe, etc).
// This could be something that normalizePath may want to do.
@memcpy(path_space.data[0..path.len], path);
// Try to normalize, but if we get too many parent directories,
// then we need to start over and use RtlGetFullPathName_U instead.
path_space.len = normalizePath(u16, path_space.data[0..path.len]) catch |err| switch (err) {
error.TooManyParentDirs => break :relative,
};
path_space.data[path_space.len] = 0;
return path_space;
}
}
// We now know we are going to return an absolute NT path, so
// we can unconditionally prefix it with the NT prefix.
path_space.data[0..nt_prefix.len].* = nt_prefix;
if (path_type == .root_local_device) {
// `\\.` and `\\?` always get converted to `\??\` exactly, so
// we can just stop here
path_space.len = nt_prefix.len;
path_space.data[path_space.len] = 0;
return path_space;
}
const path_buf_offset = switch (path_type) {
// UNC paths will always start with `\\`. However, we want to
// end up with something like `\??\UNC\server\share`, so to get
// RtlGetFullPathName to write into the spot we want the `server`
// part to end up, we need to provide an offset such that
// the `\\` part gets written where the `C\` of `UNC\` will be
// in the final NT path.
.unc_absolute => nt_prefix.len + 2,
else => nt_prefix.len,
};
const buf_len: u32 = @intCast(path_space.data.len - path_buf_offset);
const path_to_get: [:0]const u16 = path_to_get: {
// If dir is null, then we don't need to bother with GetFinalPathNameByHandle because
// RtlGetFullPathName_U will resolve relative paths against the CWD for us.
if (path_type != .relative or dir == null) {
break :path_to_get path;
}
// We can also skip GetFinalPathNameByHandle if the handle matches
// the handle returned by fs.cwd()
if (dir.? == std.fs.cwd().fd) {
break :path_to_get path;
}
// At this point, we know we have a relative path that had too many
// `..` components to be resolved by normalizePath, so we need to
// convert it into an absolute path and let RtlGetFullPathName_U
// canonicalize it. We do this by getting the path of the `dir`
// and appending the relative path to it.
var dir_path_buf: [PATH_MAX_WIDE:0]u16 = undefined;
const dir_path = GetFinalPathNameByHandle(dir.?, .{}, &dir_path_buf) catch |err| switch (err) {
// This mapping is not correct; it is actually expected
// that calling GetFinalPathNameByHandle might return
// error.UnrecognizedVolume, and in fact has been observed
// in the wild. The problem is that wToPrefixedFileW was
// never intended to make *any* OS syscall APIs. It's only
// supposed to convert a string to one that is eligible to
// be used in the ntdll syscalls.
//
// To solve this, this function needs to no longer call
// GetFinalPathNameByHandle under any conditions, or the
// calling function needs to get reworked to not need to
// call this function.
//
// This may involve making breaking API changes.
error.UnrecognizedVolume => return error.Unexpected,
else => |e| return e,
};
if (dir_path.len + 1 + path.len > PATH_MAX_WIDE) {
return error.NameTooLong;
}
// We don't have to worry about potentially doubling up path separators
// here since RtlGetFullPathName_U will handle canonicalizing it.
dir_path_buf[dir_path.len] = '\\';
@memcpy(dir_path_buf[dir_path.len + 1 ..][0..path.len], path);
const full_len = dir_path.len + 1 + path.len;
dir_path_buf[full_len] = 0;
break :path_to_get dir_path_buf[0..full_len :0];
};
const path_byte_len = ntdll.RtlGetFullPathName_U(
path_to_get.ptr,
buf_len * 2,
path_space.data[path_buf_offset..].ptr,
null,
);
if (path_byte_len == 0) {
// TODO: This may not be the right error
return error.BadPathName;
} else if (path_byte_len / 2 > buf_len) {
return error.NameTooLong;
}
path_space.len = path_buf_offset + (path_byte_len / 2);
if (path_type == .unc_absolute) {
// Now add in the UNC, the `C` should overwrite the first `\` of the
// FullPathName, ultimately resulting in `\??\UNC\`
std.debug.assert(path_space.data[path_buf_offset] == '\\');
std.debug.assert(path_space.data[path_buf_offset + 1] == '\\');
const unc = [_]u16{ 'U', 'N', 'C' };
path_space.data[nt_prefix.len..][0..unc.len].* = unc;
}
return path_space;
},
}
}