| ... | @@ -731,6 +731,117 @@ pub fn CreateSymbolicLinkW( | ... | @@ -731,6 +731,117 @@ pub fn CreateSymbolicLinkW( |
| 731 | _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null); | 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 | pub const DeleteFileError = error{ | 845 | pub const DeleteFileError = error{ |
| 735 | FileNotFound, | 846 | FileNotFound, |
| 736 | AccessDenied, | 847 | AccessDenied, |
| ... | @@ -1343,21 +1454,6 @@ pub const PathSpace = struct { | ... | @@ -1343,21 +1454,6 @@ pub const PathSpace = struct { |
| 1343 | pub fn span(self: PathSpace) [:0]const u16 { | 1454 | pub fn span(self: PathSpace) [:0]const u16 { |
| 1344 | return self.data[0..self.len :0]; | 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 | /// Same as `sliceToPrefixedFileW` but accepts a pointer | 1459 | /// Same as `sliceToPrefixedFileW` but accepts a pointer |
| ... | @@ -1366,50 +1462,14 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { | ... | @@ -1366,50 +1462,14 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { |
| 1366 | return sliceToPrefixedFileW(mem.spanZ(s)); | 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 | /// Converts the path `s` to WTF16, null-terminated. If the path is absolute, | 1465 | /// Converts the path `s` to WTF16, null-terminated. If the path is absolute, |
| 1376 | /// it will get NT-style prefix `\??\` prepended automatically. For prepending | 1466 | /// it will get NT-style prefix `\??\` prepended automatically. For prepending |
| 1377 | /// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead. | 1467 | /// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead. |
| 1378 | pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { | 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 | // TODO https://github.com/ziglang/zig/issues/2765 | 1469 | // TODO https://github.com/ziglang/zig/issues/2765 |
| 1410 | var path_space: PathSpace = undefined; | 1470 | var path_space: PathSpace = undefined; |
| 1411 | const prefix_utf8 = prefix.toUtf8(); | 1471 | const prefix = "\\??\\"; |
| 1412 | const prefix_index: usize = if (mem.startsWith(u8, s, prefix_utf8)) prefix_utf8.len else 0; | 1472 | const prefix_index: usize = if (mem.startsWith(u8, s, prefix)) prefix.len else 0; |
| 1413 | for (s[prefix_index..]) |byte| { | 1473 | for (s[prefix_index..]) |byte| { |
| 1414 | switch (byte) { | 1474 | switch (byte) { |
| 1415 | '*', '?', '"', '<', '>', '|' => return error.BadPathName, | 1475 | '*', '?', '"', '<', '>', '|' => return error.BadPathName, |
| ... | @@ -1417,13 +1477,24 @@ fn sliceToPrefixedFileWInternal(s: []const u8, prefix: PathPrefix) !PathSpace { | ... | @@ -1417,13 +1477,24 @@ fn sliceToPrefixedFileWInternal(s: []const u8, prefix: PathPrefix) !PathSpace { |
| 1417 | } | 1477 | } |
| 1418 | } | 1478 | } |
| 1419 | const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: { | 1479 | const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: { |
| 1420 | const prefix_utf16 = prefix.toUtf16(); | 1480 | const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' }; |
| 1421 | mem.copy(u16, path_space.data[0..], prefix_utf16); | 1481 | mem.copy(u16, path_space.data[0..], prefix_u16[0..]); |
| 1422 | break :blk prefix_utf16.len; | 1482 | break :blk prefix_u16.len; |
| 1423 | }; | 1483 | }; |
| 1424 | path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s); | 1484 | path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s); |
| 1425 | if (path_space.len > path_space.data.len) return error.NameTooLong; | 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 | return path_space; | 1498 | return path_space; |
| 1428 | } | 1499 | } |
| 1429 | | 1500 | |
| ... | @@ -1440,7 +1511,18 @@ pub fn wToPrefixedFileW(s: []const u16) !PathSpace { | ... | @@ -1440,7 +1511,18 @@ pub fn wToPrefixedFileW(s: []const u16) !PathSpace { |
| 1440 | path_space.len = start_index + s.len; | 1511 | path_space.len = start_index + s.len; |
| 1441 | if (path_space.len > path_space.data.len) return error.NameTooLong; | 1512 | if (path_space.len > path_space.data.len) return error.NameTooLong; |
| 1442 | mem.copy(u16, path_space.data[start_index..], s); | 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 | return path_space; | 1526 | return path_space; |
| 1445 | } | 1527 | } |
| 1446 | | 1528 | |
| ... | @@ -1484,73 +1566,3 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { | ... | @@ -1484,73 +1566,3 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { |
| 1484 | } | 1566 | } |
| 1485 | return error.Unexpected; | 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 | } | | |