Use NtCreateFile to get handle to reparse point
parent
d17c9b3591
commit
ae8abedbed
|
@ -2394,36 +2394,37 @@ pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");
|
|||
/// See also `readlinkZ`.
|
||||
pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
const w = windows;
|
||||
const sharing = w.FILE_SHARE_DELETE | w.FILE_SHARE_READ | w.FILE_SHARE_WRITE;
|
||||
const disposition = w.OPEN_EXISTING;
|
||||
const flags = w.FILE_FLAG_BACKUP_SEMANTICS | w.FILE_FLAG_OPEN_REPARSE_POINT;
|
||||
const handle = w.CreateFileW(file_path, 0, sharing, null, disposition, flags, null) catch |err| {
|
||||
|
||||
const dir = if (std.fs.path.isAbsoluteWindowsW(file_path)) null else std.fs.cwd().fd;
|
||||
const handle = w.OpenAsReparsePoint(dir, file_path) catch |err| {
|
||||
switch (err) {
|
||||
error.SharingViolation => return error.AccessDenied,
|
||||
error.PathAlreadyExists => unreachable,
|
||||
error.PipeBusy => unreachable,
|
||||
error.PathAlreadyExists => unreachable,
|
||||
error.NoDevice => return error.FileNotFound,
|
||||
else => |e| return e,
|
||||
}
|
||||
};
|
||||
var reparse_buf align(@alignOf(w.REPARSE_DATA_BUFFER)) = [_]u8{0} ** (w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
||||
|
||||
var reparse_buf: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
|
||||
_ = try w.DeviceIoControl(handle, w.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null);
|
||||
const reparse_struct = @ptrCast(*const w.REPARSE_DATA_BUFFER, &reparse_buf[0]);
|
||||
// std.debug.warn("\n\n{x}\n\n", .{reparse_buf});
|
||||
const reparse_struct = @ptrCast(*const w.REPARSE_DATA_BUFFER, @alignCast(@alignOf(w.REPARSE_DATA_BUFFER), &reparse_buf[0]));
|
||||
switch (reparse_struct.ReparseTag) {
|
||||
w.IO_REPARSE_TAG_SYMLINK => {
|
||||
const alignment = @alignOf(w.SymbolicLinkReparseBuffer);
|
||||
const buf = @ptrCast(*const w.SymbolicLinkReparseBuffer, @alignCast(alignment, &reparse_struct.DataBuffer[0]));
|
||||
const offset = buf.SubstituteNameOffset / 2;
|
||||
const len = buf.SubstituteNameLength / 2;
|
||||
const f = buf.Flags;
|
||||
const buf = @ptrCast(*const w.SymbolicLinkReparseBuffer, @alignCast(@alignOf(w.SymbolicLinkReparseBuffer), &reparse_struct.DataBuffer[0]));
|
||||
const offset = buf.SubstituteNameOffset >> 1;
|
||||
const len = buf.SubstituteNameLength >> 1;
|
||||
const path_buf = @as([*]const u16, &buf.PathBuffer);
|
||||
std.debug.warn("got symlink => offset={}, len={}, flags = {}, {}\n", .{ offset, len, f, w.SYMLINK_FLAG_RELATIVE });
|
||||
// TODO handle absolute paths and namespace prefix
|
||||
const out_len = std.unicode.utf16leToUtf8(out_buffer, path_buf[offset .. offset + len]) catch unreachable;
|
||||
std.debug.warn("got symlink => utf8={}\n", .{out_buffer[0..out_len]});
|
||||
return out_buffer[0..out_len];
|
||||
const is_relative = buf.Flags & w.SYMLINK_FLAG_RELATIVE != 0;
|
||||
return parseReadlinkPath(path_buf[offset .. offset + len], is_relative, out_buffer);
|
||||
},
|
||||
w.IO_REPARSE_TAG_MOUNT_POINT => {
|
||||
@panic("TODO parse mount point");
|
||||
const buf = @ptrCast(*const w.MountPointReparseBuffer, @alignCast(@alignOf(w.MountPointReparseBuffer), &reparse_struct.DataBuffer[0]));
|
||||
const offset = buf.SubstituteNameOffset >> 1;
|
||||
const len = buf.SubstituteNameLength >> 1;
|
||||
const path_buf = @as([*]const u16, &buf.PathBuffer);
|
||||
return parseReadlinkPath(path_buf[offset .. offset + len], false, out_buffer);
|
||||
},
|
||||
else => |value| {
|
||||
std.debug.warn("unsupported symlink type: {}", .{value});
|
||||
|
@ -2432,6 +2433,13 @@ pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8
|
|||
}
|
||||
}
|
||||
|
||||
fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {
|
||||
const out_len = std.unicode.utf16leToUtf8(out_buffer, path) catch unreachable;
|
||||
std.debug.warn("got symlink => utf8={}\n", .{out_buffer[0..out_len]});
|
||||
// TODO handle absolute paths and namespace prefix '/??/'
|
||||
return out_buffer[0..out_len];
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
|
|
|
@ -43,32 +43,42 @@ test "fstatat" {
|
|||
|
||||
test "readlink" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
var cwd = fs.cwd();
|
||||
try cwd.writeFile("file.txt", "nonsense");
|
||||
try os.symlink("file.txt", "symlinked");
|
||||
|
||||
var tmp = tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
// create file
|
||||
try tmp.dir.writeFile("file.txt", "nonsense");
|
||||
|
||||
// get paths
|
||||
// TODO: use Dir's realpath function once that exists
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
const base_path = blk: {
|
||||
const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..]});
|
||||
break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
|
||||
};
|
||||
const target_path = try fs.path.join(&arena.allocator, &[_][]const u8{base_path, "file.txt"});
|
||||
const symlink_path = try fs.path.join(&arena.allocator, &[_][]const u8{base_path, "symlinked"});
|
||||
|
||||
// create symbolic link by path
|
||||
try os.symlink(target_path, symlink_path);
|
||||
|
||||
// now, read the link and verify
|
||||
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
const given = try os.readlink(symlink_path, buffer[0..]);
|
||||
expect(mem.eql(u8, symlink_path, given));
|
||||
const given = try os.readlink("symlinked", buffer[0..]);
|
||||
expect(mem.eql(u8, "file.txt", given));
|
||||
|
||||
// var tmp = tmpDir(.{});
|
||||
// defer tmp.cleanup();
|
||||
|
||||
// // create file
|
||||
// try tmp.dir.writeFile("file.txt", "nonsense");
|
||||
|
||||
// // get paths
|
||||
// // TODO: use Dir's realpath function once that exists
|
||||
// var arena = ArenaAllocator.init(testing.allocator);
|
||||
// defer arena.deinit();
|
||||
|
||||
// const base_path = blk: {
|
||||
// const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..]});
|
||||
// break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
|
||||
// };
|
||||
// const target_path = try fs.path.join(&arena.allocator, &[_][]const u8{base_path, "file.txt"});
|
||||
// const symlink_path = try fs.path.join(&arena.allocator, &[_][]const u8{base_path, "symlinked"});
|
||||
// std.debug.warn("\ntarget_path={}\n", .{target_path});
|
||||
// std.debug.warn("symlink_path={}\n", .{symlink_path});
|
||||
|
||||
// // create symbolic link by path
|
||||
// try os.symlink(target_path, symlink_path);
|
||||
|
||||
// // now, read the link and verify
|
||||
// var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
// const given = try os.readlink(symlink_path, buffer[0..]);
|
||||
// expect(mem.eql(u8, symlink_path, given));
|
||||
}
|
||||
|
||||
test "readlinkat" {
|
||||
|
|
|
@ -1370,4 +1370,74 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError {
|
|||
std.debug.dumpCurrentStackTrace(null);
|
||||
}
|
||||
return error.Unexpected;
|
||||
}
|
||||
|
||||
pub const OpenAsReparsePointError = error {
|
||||
FileNotFound,
|
||||
NoDevice,
|
||||
SharingViolation,
|
||||
AccessDenied,
|
||||
PipeBusy,
|
||||
PathAlreadyExists,
|
||||
Unexpected,
|
||||
NameTooLong,
|
||||
};
|
||||
|
||||
/// Open file as a reparse point
|
||||
pub fn OpenAsReparsePoint(
|
||||
dir: ?HANDLE,
|
||||
sub_path_w: [*:0]const u16,
|
||||
) OpenAsReparsePointError!HANDLE {
|
||||
const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) {
|
||||
error.Overflow => return error.NameTooLong,
|
||||
};
|
||||
var nt_name = UNICODE_STRING{
|
||||
.Length = path_len_bytes,
|
||||
.MaximumLength = path_len_bytes,
|
||||
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
|
||||
};
|
||||
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
|
||||
// Windows does not recognize this, but it does work with empty string.
|
||||
nt_name.Length = 0;
|
||||
}
|
||||
|
||||
var attr = OBJECT_ATTRIBUTES{
|
||||
.Length = @sizeOf(OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
|
||||
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
|
||||
.ObjectName = &nt_name,
|
||||
.SecurityDescriptor = null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
var io: IO_STATUS_BLOCK = undefined;
|
||||
var result_handle: HANDLE = undefined;
|
||||
const rc = ntdll.NtCreateFile(
|
||||
&result_handle,
|
||||
FILE_READ_ATTRIBUTES,
|
||||
&attr,
|
||||
&io,
|
||||
null,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
FILE_SHARE_READ,
|
||||
FILE_OPEN,
|
||||
FILE_OPEN_REPARSE_POINT,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => return result_handle,
|
||||
.OBJECT_NAME_INVALID => unreachable,
|
||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
|
||||
.NO_MEDIA_IN_DEVICE => return error.NoDevice,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.SHARING_VIOLATION => return error.SharingViolation,
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.PIPE_BUSY => return error.PipeBusy,
|
||||
.OBJECT_PATH_SYNTAX_BAD => unreachable,
|
||||
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
|
||||
.FILE_IS_A_DIRECTORY => unreachable,
|
||||
else => return unexpectedStatus(rc),
|
||||
}
|
||||
}
|
|
@ -1568,7 +1568,7 @@ pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: ULONG = 16 * 1024;
|
|||
pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8;
|
||||
pub const IO_REPARSE_TAG_SYMLINK: ULONG = 0xa000000c;
|
||||
pub const IO_REPARSE_TAG_MOUNT_POINT: ULONG = 0xa0000003;
|
||||
pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1;
|
||||
pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x00000001;
|
||||
|
||||
pub const SYMBOLIC_LINK_FLAG_FILE: DWORD = 0x0;
|
||||
pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1;
|
||||
|
|
Loading…
Reference in New Issue