| author | |
| committer | |
| log | ae8abedbeda33ff5cd93ca2fb21ef2f5453dfb37 |
| tree | ad5d0d6a067e32a151cf1dbc6e553ae8336dc129 |
| parent | d17c9b3591ba822df3af341a0d4cf5f004413f4a |
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"); | ... | @@ -2394,36 +2394,37 @@ pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); |
| 2394 | /// See also `readlinkZ`. | 2394 | /// See also `readlinkZ`. |
| 2395 | pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { | 2395 | pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 2396 | const w = windows; | 2396 | const w = windows; |
| 2397 | const sharing = w.FILE_SHARE_DELETE | w.FILE_SHARE_READ | w.FILE_SHARE_WRITE; | 2397 | |
| 2398 | const disposition = w.OPEN_EXISTING; | 2398 | const dir = if (std.fs.path.isAbsoluteWindowsW(file_path)) null else std.fs.cwd().fd; |
| 2399 | const flags = w.FILE_FLAG_BACKUP_SEMANTICS | w.FILE_FLAG_OPEN_REPARSE_POINT; | 2399 | const handle = w.OpenAsReparsePoint(dir, file_path) catch |err| { |
| 2400 | const handle = w.CreateFileW(file_path, 0, sharing, null, disposition, flags, null) catch |err| { | ||
| 2401 | switch (err) { | 2400 | switch (err) { |
| 2402 | error.SharingViolation => return error.AccessDenied, | 2401 | error.SharingViolation => return error.AccessDenied, |
| 2403 | error.PathAlreadyExists => unreachable, | ||
| 2404 | error.PipeBusy => unreachable, | 2402 | error.PipeBusy => unreachable, |
| 2403 | error.PathAlreadyExists => unreachable, | ||
| 2404 | error.NoDevice => return error.FileNotFound, | ||
| 2405 | else => |e| return e, | 2405 | else => |e| return e, |
| 2406 | } | 2406 | } |
| 2407 | }; | 2407 | }; |
| 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; | ||
| 2409 | _ = try w.DeviceIoControl(handle, w.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null); | 2410 | _ = 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])); | ||
| 2411 | switch (reparse_struct.ReparseTag) { | 2413 | switch (reparse_struct.ReparseTag) { |
| 2412 | w.IO_REPARSE_TAG_SYMLINK => { | 2414 | w.IO_REPARSE_TAG_SYMLINK => { |
| 2413 | const alignment = @alignOf(w.SymbolicLinkReparseBuffer); | 2415 | const buf = @ptrCast(*const w.SymbolicLinkReparseBuffer, @alignCast(@alignOf(w.SymbolicLinkReparseBuffer), &reparse_struct.DataBuffer[0])); |
| 2414 | const buf = @ptrCast(*const w.SymbolicLinkReparseBuffer, @alignCast(alignment, &reparse_struct.DataBuffer[0])); | 2416 | const offset = buf.SubstituteNameOffset >> 1; |
| 2415 | const offset = buf.SubstituteNameOffset / 2; | 2417 | const len = buf.SubstituteNameLength >> 1; |
| 2416 | const len = buf.SubstituteNameLength / 2; | ||
| 2417 | const f = buf.Flags; | ||
| 2418 | const path_buf = @as([*]const u16, &buf.PathBuffer); | 2418 | 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 }); | 2419 | const is_relative = buf.Flags & w.SYMLINK_FLAG_RELATIVE != 0; |
| 2420 | // TODO handle absolute paths and namespace prefix | 2420 | return parseReadlinkPath(path_buf[offset .. offset + len], is_relative, out_buffer); |
| 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]; | ||
| 2424 | }, | 2421 | }, |
| 2425 | w.IO_REPARSE_TAG_MOUNT_POINT => { | 2422 | 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); | ||
| 2427 | }, | 2428 | }, |
| 2428 | else => |value| { | 2429 | else => |value| { |
| 2429 | std.debug.warn("unsupported symlink type: {}", .{value}); | 2430 | std.debug.warn("unsupported symlink type: {}", .{value}); |
| ... | @@ -2432,6 +2433,13 @@ pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 | ... | @@ -2432,6 +2433,13 @@ pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 |
| 2432 | } | 2433 | } |
| 2433 | } | 2434 | } |
| 2434 | 2435 | ||
| 2436 | fn 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 | |||
| 2435 | /// Same as `readlink` except `file_path` is null-terminated. | 2443 | /// Same as `readlink` except `file_path` is null-terminated. |
| 2436 | pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { | 2444 | pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2437 | if (builtin.os.tag == .windows) { | 2445 | if (builtin.os.tag == .windows) { |
lib/std/os/test.zig+34-24| ... | @@ -43,32 +43,42 @@ test "fstatat" { | ... | @@ -43,32 +43,42 @@ test "fstatat" { |
| 43 | 43 | ||
| 44 | test "readlink" { | 44 | test "readlink" { |
| 45 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 45 | 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"); | ||
| 46 | 50 | ||
| 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 | ||
| 69 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; | 51 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; |
| 70 | const given = try os.readlink(symlink_path, buffer[0..]); | 52 | const given = try os.readlink("symlinked", buffer[0..]); |
| 71 | expect(mem.eql(u8, symlink_path, given)); | 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)); | ||
| 72 | } | 82 | } |
| 73 | 83 | ||
| 74 | test "readlinkat" { | 84 | test "readlinkat" { |
lib/std/os/windows.zig+70| ... | @@ -1370,4 +1370,74 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { | ... | @@ -1370,4 +1370,74 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { |
| 1370 | std.debug.dumpCurrentStackTrace(null); | 1370 | std.debug.dumpCurrentStackTrace(null); |
| 1371 | } | 1371 | } |
| 1372 | return error.Unexpected; | 1372 | return error.Unexpected; |
| 1373 | } | ||
| 1374 | |||
| 1375 | pub 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 | ||
| 1387 | pub 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 | } | ||
| 1373 | } | 1443 | } |
| \ 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; | ... | @@ -1568,7 +1568,7 @@ pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: ULONG = 16 * 1024; |
| 1568 | pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8; | 1568 | pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8; |
| 1569 | pub const IO_REPARSE_TAG_SYMLINK: ULONG = 0xa000000c; | 1569 | pub const IO_REPARSE_TAG_SYMLINK: ULONG = 0xa000000c; |
| 1570 | pub const IO_REPARSE_TAG_MOUNT_POINT: ULONG = 0xa0000003; | 1570 | pub const IO_REPARSE_TAG_MOUNT_POINT: ULONG = 0xa0000003; |
| 1571 | pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1; | 1571 | pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x00000001; |
| 1572 | 1572 | ||
| 1573 | pub const SYMBOLIC_LINK_FLAG_FILE: DWORD = 0x0; | 1573 | pub const SYMBOLIC_LINK_FLAG_FILE: DWORD = 0x0; |
| 1574 | pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1; | 1574 | pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1; |