| ... | ... | @@ -731,6 +731,117 @@ pub fn CreateSymbolicLinkW( |
| 731 | 731 | _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null); |
| 732 | 732 | } |
| 733 | 733 | |
| 734 | pub const ReadLinkError = error{ |
| 735 | FileNotFound, |
| 736 | AccessDenied, |
| 737 | Unexpected, |
| 738 | NameTooLong, |
| 739 | UnsupportedReparsePointType, |
| 740 | InvalidUtf8, |
| 741 | BadPathName, |
| 742 | }; |
| 743 | |
| 744 | pub fn ReadLink( |
| 745 | dir: ?HANDLE, |
| 746 | sub_path: []const u8, |
| 747 | out_buffer: []u8, |
| 748 | ) ReadLinkError![]u8 { |
| 749 | const sub_path_w = try sliceToPrefixedFileW(sub_path); |
| 750 | return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer); |
| 751 | } |
| 752 | |
| 753 | pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 754 | const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) { |
| 755 | error.Overflow => return error.NameTooLong, |
| 756 | }; |
| 757 | var nt_name = UNICODE_STRING{ |
| 758 | .Length = path_len_bytes, |
| 759 | .MaximumLength = path_len_bytes, |
| 760 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), |
| 761 | }; |
| 762 | |
| 763 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { |
| 764 | // Windows does not recognize this, but it does work with empty string. |
| 765 | nt_name.Length = 0; |
| 766 | } |
| 767 | |
| 768 | var attr = OBJECT_ATTRIBUTES{ |
| 769 | .Length = @sizeOf(OBJECT_ATTRIBUTES), |
| 770 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir, |
| 771 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. |
| 772 | .ObjectName = &nt_name, |
| 773 | .SecurityDescriptor = null, |
| 774 | .SecurityQualityOfService = null, |
| 775 | }; |
| 776 | var io: IO_STATUS_BLOCK = undefined; |
| 777 | var result_handle: HANDLE = undefined; |
| 778 | const rc = ntdll.NtCreateFile( |
| 779 | &result_handle, |
| 780 | FILE_READ_ATTRIBUTES, |
| 781 | &attr, |
| 782 | &io, |
| 783 | null, |
| 784 | FILE_ATTRIBUTE_NORMAL, |
| 785 | FILE_SHARE_READ, |
| 786 | FILE_OPEN, |
| 787 | FILE_OPEN_REPARSE_POINT, |
| 788 | null, |
| 789 | 0, |
| 790 | ); |
| 791 | switch (rc) { |
| 792 | .SUCCESS => {}, |
| 793 | .OBJECT_NAME_INVALID => unreachable, |
| 794 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 795 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, |
| 796 | .NO_MEDIA_IN_DEVICE => return error.FileNotFound, |
| 797 | .INVALID_PARAMETER => unreachable, |
| 798 | .SHARING_VIOLATION => return error.AccessDenied, |
| 799 | .ACCESS_DENIED => return error.AccessDenied, |
| 800 | .PIPE_BUSY => return error.AccessDenied, |
| 801 | .OBJECT_PATH_SYNTAX_BAD => unreachable, |
| 802 | .OBJECT_NAME_COLLISION => unreachable, |
| 803 | .FILE_IS_A_DIRECTORY => unreachable, |
| 804 | else => return unexpectedStatus(rc), |
| 805 | } |
| 806 | defer CloseHandle(result_handle); |
| 807 | |
| 808 | var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; |
| 809 | _ = try DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null); |
| 810 | |
| 811 | const reparse_struct = @ptrCast(*const REPARSE_DATA_BUFFER, @alignCast(@alignOf(REPARSE_DATA_BUFFER), &reparse_buf[0])); |
| 812 | switch (reparse_struct.ReparseTag) { |
| 813 | IO_REPARSE_TAG_SYMLINK => { |
| 814 | const buf = @ptrCast(*const SYMBOLIC_LINK_REPARSE_BUFFER, @alignCast(@alignOf(SYMBOLIC_LINK_REPARSE_BUFFER), &reparse_struct.DataBuffer[0])); |
| 815 | const offset = buf.SubstituteNameOffset >> 1; |
| 816 | const len = buf.SubstituteNameLength >> 1; |
| 817 | const path_buf = @as([*]const u16, &buf.PathBuffer); |
| 818 | const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0; |
| 819 | return parseReadlinkPath(path_buf[offset .. offset + len], is_relative, out_buffer); |
| 820 | }, |
| 821 | IO_REPARSE_TAG_MOUNT_POINT => { |
| 822 | const buf = @ptrCast(*const MOUNT_POINT_REPARSE_BUFFER, @alignCast(@alignOf(MOUNT_POINT_REPARSE_BUFFER), &reparse_struct.DataBuffer[0])); |
| 823 | const offset = buf.SubstituteNameOffset >> 1; |
| 824 | const len = buf.SubstituteNameLength >> 1; |
| 825 | const path_buf = @as([*]const u16, &buf.PathBuffer); |
| 826 | return parseReadlinkPath(path_buf[offset .. offset + len], false, out_buffer); |
| 827 | }, |
| 828 | else => |value| { |
| 829 | std.debug.warn("unsupported symlink type: {}", .{value}); |
| 830 | return error.UnsupportedReparsePointType; |
| 831 | }, |
| 832 | } |
| 833 | } |
| 834 | |
| 835 | fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 { |
| 836 | const prefix = [_]u16{ '\\', '?', '?', '\\' }; |
| 837 | var start_index: usize = 0; |
| 838 | if (!is_relative and std.mem.startsWith(u16, path, &prefix)) { |
| 839 | start_index = prefix.len; |
| 840 | } |
| 841 | const out_len = std.unicode.utf16leToUtf8(out_buffer, path[start_index..]) catch unreachable; |
| 842 | return out_buffer[0..out_len]; |
| 843 | } |
| 844 | |
| 734 | 845 | pub const DeleteFileError = error{ |
| 735 | 846 | FileNotFound, |
| 736 | 847 | AccessDenied, |
| ... | ... | @@ -1343,21 +1454,6 @@ pub const PathSpace = struct { |
| 1343 | 1454 | pub fn span(self: PathSpace) [:0]const u16 { |
| 1344 | 1455 | return self.data[0..self.len :0]; |
| 1345 | 1456 | } |
| 1346 | | |
| 1347 | | fn ensureNtStyle(self: *PathSpace) void { |
| 1348 | | // > File I/O functions in the Windows API convert "/" to "\" as part of |
| 1349 | | // > converting the name to an NT-style name, except when using the "\\?\" |
| 1350 | | // > prefix as detailed in the following sections. |
| 1351 | | // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation |
| 1352 | | // Because we want the larger maximum path length for absolute paths, we |
| 1353 | | // convert forward slashes to backward slashes here. |
| 1354 | | for (self.data[0..self.len]) |*elem| { |
| 1355 | | if (elem.* == '/') { |
| 1356 | | elem.* = '\\'; |
| 1357 | | } |
| 1358 | | } |
| 1359 | | self.data[self.len] = 0; |
| 1360 | | } |
| 1361 | 1457 | }; |
| 1362 | 1458 | |
| 1363 | 1459 | /// Same as `sliceToPrefixedFileW` but accepts a pointer |
| ... | ... | @@ -1366,50 +1462,14 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { |
| 1366 | 1462 | return sliceToPrefixedFileW(mem.spanZ(s)); |
| 1367 | 1463 | } |
| 1368 | 1464 | |
| 1369 | | /// Same as `sliceToWin32PrefixedFileW` but accepts a pointer |
| 1370 | | /// to a null-terminated path. |
| 1371 | | pub fn cStrToWin32PrefixedFileW(s: [*:0]const u8) !PathSpace { |
| 1372 | | return sliceToWin32PrefixedFileW(mem.spanZ(s)); |
| 1373 | | } |
| 1374 | | |
| 1375 | 1465 | /// Converts the path `s` to WTF16, null-terminated. If the path is absolute, |
| 1376 | 1466 | /// it will get NT-style prefix `\??\` prepended automatically. For prepending |
| 1377 | 1467 | /// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead. |
| 1378 | 1468 | pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { |
| 1379 | | return sliceToPrefixedFileWInternal(s, PathPrefix.Nt); |
| 1380 | | } |
| 1381 | | |
| 1382 | | /// Converts the path `s` to WTF16, null-terminated. If the path is absolute, |
| 1383 | | /// it will get Win32-style extended prefix `\\?\` prepended automatically. For prepending |
| 1384 | | /// NT-style prefix, see `sliceToPrefixedFileW` instead. |
| 1385 | | pub fn sliceToWin32PrefixedFileW(s: []const u8) !PathSpace { |
| 1386 | | return sliceToPrefixedFileWInternal(s, PathPrefix.Win32); |
| 1387 | | } |
| 1388 | | |
| 1389 | | const PathPrefix = enum { |
| 1390 | | Win32, |
| 1391 | | Nt, |
| 1392 | | |
| 1393 | | fn toUtf8(self: PathPrefix) []const u8 { |
| 1394 | | return switch (self) { |
| 1395 | | .Win32 => "\\\\?\\", |
| 1396 | | .Nt => "\\??\\", |
| 1397 | | }; |
| 1398 | | } |
| 1399 | | |
| 1400 | | fn toUtf16(self: PathPrefix) []const u16 { |
| 1401 | | return switch (self) { |
| 1402 | | .Win32 => &[_]u16{ '\\', '\\', '?', '\\' }, |
| 1403 | | .Nt => &[_]u16{ '\\', '?', '?', '\\' }, |
| 1404 | | }; |
| 1405 | | } |
| 1406 | | }; |
| 1407 | | |
| 1408 | | fn sliceToPrefixedFileWInternal(s: []const u8, prefix: PathPrefix) !PathSpace { |
| 1409 | 1469 | // TODO https://github.com/ziglang/zig/issues/2765 |
| 1410 | 1470 | var path_space: PathSpace = undefined; |
| 1411 | | const prefix_utf8 = prefix.toUtf8(); |
| 1412 | | const prefix_index: usize = if (mem.startsWith(u8, s, prefix_utf8)) prefix_utf8.len else 0; |
| 1471 | const prefix = "\\??\\"; |
| 1472 | const prefix_index: usize = if (mem.startsWith(u8, s, prefix)) prefix.len else 0; |
| 1413 | 1473 | for (s[prefix_index..]) |byte| { |
| 1414 | 1474 | switch (byte) { |
| 1415 | 1475 | '*', '?', '"', '<', '>', '|' => return error.BadPathName, |
| ... | ... | @@ -1417,13 +1477,24 @@ fn sliceToPrefixedFileWInternal(s: []const u8, prefix: PathPrefix) !PathSpace { |
| 1417 | 1477 | } |
| 1418 | 1478 | } |
| 1419 | 1479 | const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: { |
| 1420 | | const prefix_utf16 = prefix.toUtf16(); |
| 1421 | | mem.copy(u16, path_space.data[0..], prefix_utf16); |
| 1422 | | break :blk prefix_utf16.len; |
| 1480 | const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' }; |
| 1481 | mem.copy(u16, path_space.data[0..], prefix_u16[0..]); |
| 1482 | break :blk prefix_u16.len; |
| 1423 | 1483 | }; |
| 1424 | 1484 | path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s); |
| 1425 | 1485 | if (path_space.len > path_space.data.len) return error.NameTooLong; |
| 1426 | | path_space.ensureNtStyle(); |
| 1486 | // > File I/O functions in the Windows API convert "/" to "\" as part of |
| 1487 | // > converting the name to an NT-style name, except when using the "\\?\" |
| 1488 | // > prefix as detailed in the following sections. |
| 1489 | // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation |
| 1490 | // Because we want the larger maximum path length for absolute paths, we |
| 1491 | // convert forward slashes to backward slashes here. |
| 1492 | for (path_space.data[0..path_space.len]) |*elem| { |
| 1493 | if (elem.* == '/') { |
| 1494 | elem.* = '\\'; |
| 1495 | } |
| 1496 | } |
| 1497 | path_space.data[path_space.len] = 0; |
| 1427 | 1498 | return path_space; |
| 1428 | 1499 | } |
| 1429 | 1500 | |
| ... | ... | @@ -1440,7 +1511,18 @@ pub fn wToPrefixedFileW(s: []const u16) !PathSpace { |
| 1440 | 1511 | path_space.len = start_index + s.len; |
| 1441 | 1512 | if (path_space.len > path_space.data.len) return error.NameTooLong; |
| 1442 | 1513 | mem.copy(u16, path_space.data[start_index..], s); |
| 1443 | | path_space.ensureNtStyle(); |
| 1514 | // > File I/O functions in the Windows API convert "/" to "\" as part of |
| 1515 | // > converting the name to an NT-style name, except when using the "\\?\" |
| 1516 | // > prefix as detailed in the following sections. |
| 1517 | // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation |
| 1518 | // Because we want the larger maximum path length for absolute paths, we |
| 1519 | // convert forward slashes to backward slashes here. |
| 1520 | for (path_space.data[0..path_space.len]) |*elem| { |
| 1521 | if (elem.* == '/') { |
| 1522 | elem.* = '\\'; |
| 1523 | } |
| 1524 | } |
| 1525 | path_space.data[path_space.len] = 0; |
| 1444 | 1526 | return path_space; |
| 1445 | 1527 | } |
| 1446 | 1528 | |
| ... | ... | @@ -1484,73 +1566,3 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { |
| 1484 | 1566 | } |
| 1485 | 1567 | return error.Unexpected; |
| 1486 | 1568 | } |
| 1487 | | |
| 1488 | | pub const OpenReparsePointError = error{ |
| 1489 | | FileNotFound, |
| 1490 | | NoDevice, |
| 1491 | | SharingViolation, |
| 1492 | | AccessDenied, |
| 1493 | | PipeBusy, |
| 1494 | | PathAlreadyExists, |
| 1495 | | Unexpected, |
| 1496 | | NameTooLong, |
| 1497 | | }; |
| 1498 | | |
| 1499 | | /// Open file as a reparse point |
| 1500 | | pub fn OpenReparsePoint( |
| 1501 | | dir: ?HANDLE, |
| 1502 | | sub_path_w: [*:0]const u16, |
| 1503 | | ) OpenReparsePointError!HANDLE { |
| 1504 | | const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) { |
| 1505 | | error.Overflow => return error.NameTooLong, |
| 1506 | | }; |
| 1507 | | var nt_name = UNICODE_STRING{ |
| 1508 | | .Length = path_len_bytes, |
| 1509 | | .MaximumLength = path_len_bytes, |
| 1510 | | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), |
| 1511 | | }; |
| 1512 | | |
| 1513 | | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { |
| 1514 | | // Windows does not recognize this, but it does work with empty string. |
| 1515 | | nt_name.Length = 0; |
| 1516 | | } |
| 1517 | | |
| 1518 | | var attr = OBJECT_ATTRIBUTES{ |
| 1519 | | .Length = @sizeOf(OBJECT_ATTRIBUTES), |
| 1520 | | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir, |
| 1521 | | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. |
| 1522 | | .ObjectName = &nt_name, |
| 1523 | | .SecurityDescriptor = null, |
| 1524 | | .SecurityQualityOfService = null, |
| 1525 | | }; |
| 1526 | | var io: IO_STATUS_BLOCK = undefined; |
| 1527 | | var result_handle: HANDLE = undefined; |
| 1528 | | const rc = ntdll.NtCreateFile( |
| 1529 | | &result_handle, |
| 1530 | | FILE_READ_ATTRIBUTES, |
| 1531 | | &attr, |
| 1532 | | &io, |
| 1533 | | null, |
| 1534 | | FILE_ATTRIBUTE_NORMAL, |
| 1535 | | FILE_SHARE_READ, |
| 1536 | | FILE_OPEN, |
| 1537 | | FILE_OPEN_REPARSE_POINT, |
| 1538 | | null, |
| 1539 | | 0, |
| 1540 | | ); |
| 1541 | | switch (rc) { |
| 1542 | | .SUCCESS => return result_handle, |
| 1543 | | .OBJECT_NAME_INVALID => unreachable, |
| 1544 | | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 1545 | | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, |
| 1546 | | .NO_MEDIA_IN_DEVICE => return error.NoDevice, |
| 1547 | | .INVALID_PARAMETER => unreachable, |
| 1548 | | .SHARING_VIOLATION => return error.SharingViolation, |
| 1549 | | .ACCESS_DENIED => return error.AccessDenied, |
| 1550 | | .PIPE_BUSY => return error.PipeBusy, |
| 1551 | | .OBJECT_PATH_SYNTAX_BAD => unreachable, |
| 1552 | | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, |
| 1553 | | .FILE_IS_A_DIRECTORY => unreachable, |
| 1554 | | else => return unexpectedStatus(rc), |
| 1555 | | } |
| 1556 | | } |