authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-07-14 23:06:55+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-07-22 08:51:22+02:00
logae8abedbeda33ff5cd93ca2fb21ef2f5453dfb37
treead5d0d6a067e32a151cf1dbc6e553ae8336dc129
parentd17c9b3591ba822df3af341a0d4cf5f004413f4a

Use NtCreateFile to get handle to reparse point


4 files changed, 131 insertions(+), 43 deletions(-)

lib/std/os.zig+26-18
......@@ -2394,36 +2394,37 @@ pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");
23942394/// See also `readlinkZ`.
23952395pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
23962396 const w = windows;
2397 const sharing = w.FILE_SHARE_DELETE | w.FILE_SHARE_READ | w.FILE_SHARE_WRITE;
2398 const disposition = w.OPEN_EXISTING;
2399 const flags = w.FILE_FLAG_BACKUP_SEMANTICS | w.FILE_FLAG_OPEN_REPARSE_POINT;
2400 const handle = w.CreateFileW(file_path, 0, sharing, null, disposition, flags, null) catch |err| {
2397
2398 const dir = if (std.fs.path.isAbsoluteWindowsW(file_path)) null else std.fs.cwd().fd;
2399 const handle = w.OpenAsReparsePoint(dir, file_path) catch |err| {
24012400 switch (err) {
24022401 error.SharingViolation => return error.AccessDenied,
2403 error.PathAlreadyExists => unreachable,
24042402 error.PipeBusy => unreachable,
2403 error.PathAlreadyExists => unreachable,
2404 error.NoDevice => return error.FileNotFound,
24052405 else => |e| return e,
24062406 }
24072407 };
2408 var reparse_buf align(@alignOf(w.REPARSE_DATA_BUFFER)) = [_]u8{0} ** (w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
2408
2409 var reparse_buf: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
24092410 _ = try w.DeviceIoControl(handle, w.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null);
2410 const reparse_struct = @ptrCast(*const w.REPARSE_DATA_BUFFER, &reparse_buf[0]);
2411 // std.debug.warn("\n\n{x}\n\n", .{reparse_buf});
2412 const reparse_struct = @ptrCast(*const w.REPARSE_DATA_BUFFER, @alignCast(@alignOf(w.REPARSE_DATA_BUFFER), &reparse_buf[0]));
24112413 switch (reparse_struct.ReparseTag) {
24122414 w.IO_REPARSE_TAG_SYMLINK => {
2413 const alignment = @alignOf(w.SymbolicLinkReparseBuffer);
2414 const buf = @ptrCast(*const w.SymbolicLinkReparseBuffer, @alignCast(alignment, &reparse_struct.DataBuffer[0]));
2415 const offset = buf.SubstituteNameOffset / 2;
2416 const len = buf.SubstituteNameLength / 2;
2417 const f = buf.Flags;
2415 const buf = @ptrCast(*const w.SymbolicLinkReparseBuffer, @alignCast(@alignOf(w.SymbolicLinkReparseBuffer), &reparse_struct.DataBuffer[0]));
2416 const offset = buf.SubstituteNameOffset >> 1;
2417 const len = buf.SubstituteNameLength >> 1;
24182418 const path_buf = @as([*]const u16, &buf.PathBuffer);
2419 std.debug.warn("got symlink => offset={}, len={}, flags = {}, {}\n", .{ offset, len, f, w.SYMLINK_FLAG_RELATIVE });
2420 // TODO handle absolute paths and namespace prefix
2421 const out_len = std.unicode.utf16leToUtf8(out_buffer, path_buf[offset .. offset + len]) catch unreachable;
2422 std.debug.warn("got symlink => utf8={}\n", .{out_buffer[0..out_len]});
2423 return out_buffer[0..out_len];
2419 const is_relative = buf.Flags & w.SYMLINK_FLAG_RELATIVE != 0;
2420 return parseReadlinkPath(path_buf[offset .. offset + len], is_relative, out_buffer);
24242421 },
24252422 w.IO_REPARSE_TAG_MOUNT_POINT => {
2426 @panic("TODO parse mount point");
2423 const buf = @ptrCast(*const w.MountPointReparseBuffer, @alignCast(@alignOf(w.MountPointReparseBuffer), &reparse_struct.DataBuffer[0]));
2424 const offset = buf.SubstituteNameOffset >> 1;
2425 const len = buf.SubstituteNameLength >> 1;
2426 const path_buf = @as([*]const u16, &buf.PathBuffer);
2427 return parseReadlinkPath(path_buf[offset .. offset + len], false, out_buffer);
24272428 },
24282429 else => |value| {
24292430 std.debug.warn("unsupported symlink type: {}", .{value});
......@@ -2432,6 +2433,13 @@ pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8
24322433 }
24332434}
24342435
2436fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {
2437 const out_len = std.unicode.utf16leToUtf8(out_buffer, path) catch unreachable;
2438 std.debug.warn("got symlink => utf8={}\n", .{out_buffer[0..out_len]});
2439 // TODO handle absolute paths and namespace prefix '/??/'
2440 return out_buffer[0..out_len];
2441}
2442
24352443/// Same as `readlink` except `file_path` is null-terminated.
24362444pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
24372445 if (builtin.os.tag == .windows) {
lib/std/os/test.zig+34-24
......@@ -43,32 +43,42 @@ test "fstatat" {
4343
4444test "readlink" {
4545 if (builtin.os.tag == .wasi) return error.SkipZigTest;
46
47 var cwd = fs.cwd();
48 try cwd.writeFile("file.txt", "nonsense");
49 try os.symlink("file.txt", "symlinked");
4650
47 var tmp = tmpDir(.{});
48 defer tmp.cleanup();
49
50 // create file
51 try tmp.dir.writeFile("file.txt", "nonsense");
52
53 // get paths
54 // TODO: use Dir's realpath function once that exists
55 var arena = ArenaAllocator.init(testing.allocator);
56 defer arena.deinit();
57
58 const base_path = blk: {
59 const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..]});
60 break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
61 };
62 const target_path = try fs.path.join(&arena.allocator, &[_][]const u8{base_path, "file.txt"});
63 const symlink_path = try fs.path.join(&arena.allocator, &[_][]const u8{base_path, "symlinked"});
64
65 // create symbolic link by path
66 try os.symlink(target_path, symlink_path);
67
68 // now, read the link and verify
6951 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
70 const given = try os.readlink(symlink_path, buffer[0..]);
71 expect(mem.eql(u8, symlink_path, given));
52 const given = try os.readlink("symlinked", buffer[0..]);
53 expect(mem.eql(u8, "file.txt", given));
54
55 // var tmp = tmpDir(.{});
56 // defer tmp.cleanup();
57
58 // // create file
59 // try tmp.dir.writeFile("file.txt", "nonsense");
60
61 // // get paths
62 // // TODO: use Dir's realpath function once that exists
63 // var arena = ArenaAllocator.init(testing.allocator);
64 // defer arena.deinit();
65
66 // const base_path = blk: {
67 // const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..]});
68 // break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
69 // };
70 // const target_path = try fs.path.join(&arena.allocator, &[_][]const u8{base_path, "file.txt"});
71 // const symlink_path = try fs.path.join(&arena.allocator, &[_][]const u8{base_path, "symlinked"});
72 // std.debug.warn("\ntarget_path={}\n", .{target_path});
73 // std.debug.warn("symlink_path={}\n", .{symlink_path});
74
75 // // create symbolic link by path
76 // try os.symlink(target_path, symlink_path);
77
78 // // now, read the link and verify
79 // var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
80 // const given = try os.readlink(symlink_path, buffer[0..]);
81 // expect(mem.eql(u8, symlink_path, given));
7282}
7383
7484test "readlinkat" {
lib/std/os/windows.zig+70
......@@ -1370,4 +1370,74 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError {
13701370 std.debug.dumpCurrentStackTrace(null);
13711371 }
13721372 return error.Unexpected;
1373}
1374
1375pub const OpenAsReparsePointError = error {
1376 FileNotFound,
1377 NoDevice,
1378 SharingViolation,
1379 AccessDenied,
1380 PipeBusy,
1381 PathAlreadyExists,
1382 Unexpected,
1383 NameTooLong,
1384};
1385
1386/// Open file as a reparse point
1387pub fn OpenAsReparsePoint(
1388 dir: ?HANDLE,
1389 sub_path_w: [*:0]const u16,
1390) OpenAsReparsePointError!HANDLE {
1391 const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) {
1392 error.Overflow => return error.NameTooLong,
1393 };
1394 var nt_name = UNICODE_STRING{
1395 .Length = path_len_bytes,
1396 .MaximumLength = path_len_bytes,
1397 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
1398 };
1399
1400 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
1401 // Windows does not recognize this, but it does work with empty string.
1402 nt_name.Length = 0;
1403 }
1404
1405 var attr = OBJECT_ATTRIBUTES{
1406 .Length = @sizeOf(OBJECT_ATTRIBUTES),
1407 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
1408 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1409 .ObjectName = &nt_name,
1410 .SecurityDescriptor = null,
1411 .SecurityQualityOfService = null,
1412 };
1413 var io: IO_STATUS_BLOCK = undefined;
1414 var result_handle: HANDLE = undefined;
1415 const rc = ntdll.NtCreateFile(
1416 &result_handle,
1417 FILE_READ_ATTRIBUTES,
1418 &attr,
1419 &io,
1420 null,
1421 FILE_ATTRIBUTE_NORMAL,
1422 FILE_SHARE_READ,
1423 FILE_OPEN,
1424 FILE_OPEN_REPARSE_POINT,
1425 null,
1426 0,
1427 );
1428 switch (rc) {
1429 .SUCCESS => return result_handle,
1430 .OBJECT_NAME_INVALID => unreachable,
1431 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1432 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1433 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
1434 .INVALID_PARAMETER => unreachable,
1435 .SHARING_VIOLATION => return error.SharingViolation,
1436 .ACCESS_DENIED => return error.AccessDenied,
1437 .PIPE_BUSY => return error.PipeBusy,
1438 .OBJECT_PATH_SYNTAX_BAD => unreachable,
1439 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
1440 .FILE_IS_A_DIRECTORY => unreachable,
1441 else => return unexpectedStatus(rc),
1442 }
13731443}
\ No newline at end of file
lib/std/os/windows/bits.zig+1-1
......@@ -1568,7 +1568,7 @@ pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: ULONG = 16 * 1024;
15681568pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8;
15691569pub const IO_REPARSE_TAG_SYMLINK: ULONG = 0xa000000c;
15701570pub const IO_REPARSE_TAG_MOUNT_POINT: ULONG = 0xa0000003;
1571pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1;
1571pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x00000001;
15721572
15731573pub const SYMBOLIC_LINK_FLAG_FILE: DWORD = 0x0;
15741574pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1;