| author | |
| committer | |
| log | aad9870a284410b9b7fa4d5c5d84f4a4d697c71b |
| tree | 77385a222705df559a686f16ebbd541f4d306f05 |
| parent | 2b1c6633aaf1d59d4affd88e87a507a02836c478 |
The logic around follow_symlinks and asynchronous IO was first introduced in 66bbe4ec4c11839217d6a9d65771d60d45cd6bc1, and then cemented in 390194431e7fa439d67686e5a0e9efceed2d898a with the added comment:
> If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
As far as I can tell, this comment is erroneous. There is no documented incompatibility between OPEN_REPARSE_POINT and SYNCHRONOUS_IO_NONALERT, and empirically everything works fine when using SYNCHRONOUS_IO_NONALERT. In fact, in 68ed787751ed36af3db0c52031741a5c31413034 the ReadLink implementation (a main usage of OPEN_REPARSE_POINT) was specifically switched to *not* use asynchronous IO (although that was effectively reverted during the move to std.Io).
This also fixes a bug with `.follow_symlinks = false` since, before this commit, the returned File would always have `.nonblocking = false` even though that was not correct when `.follow_symlinks` was false.2 files changed, 41 insertions(+), 4 deletions(-)
lib/std/Io/Threaded.zig+4-4| ... | ... | @@ -5053,7 +5053,7 @@ pub fn dirOpenFileWtf16( |
| 5053 | 5053 | .VALID_FLAGS, |
| 5054 | 5054 | .OPEN, |
| 5055 | 5055 | .{ |
| 5056 | .IO = if (flags.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, | |
| 5056 | .IO = .SYNCHRONOUS_NONALERT, | |
| 5057 | 5057 | .NON_DIRECTORY_FILE = !allow_directory, |
| 5058 | 5058 | .OPEN_REPARSE_POINT = !flags.follow_symlinks, |
| 5059 | 5059 | }, |
| ... | ... | @@ -8136,7 +8136,7 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink |
| 8136 | 8136 | .{ |
| 8137 | 8137 | .DIRECTORY_FILE = false, |
| 8138 | 8138 | .NON_DIRECTORY_FILE = false, |
| 8139 | .IO = .ASYNCHRONOUS, | |
| 8139 | .IO = .SYNCHRONOUS_NONALERT, | |
| 8140 | 8140 | .OPEN_REPARSE_POINT = true, |
| 8141 | 8141 | }, |
| 8142 | 8142 | null, |
| ... | ... | @@ -8202,7 +8202,7 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink |
| 8202 | 8202 | |
| 8203 | 8203 | var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(windows.REPARSE_DATA_BUFFER)) = undefined; |
| 8204 | 8204 | switch ((try deviceIoControl(&.{ |
| 8205 | .file = .{ .handle = result_handle, .flags = .{ .nonblocking = true } }, | |
| 8205 | .file = .{ .handle = result_handle, .flags = .{ .nonblocking = false } }, | |
| 8206 | 8206 | .code = .GET_REPARSE_POINT, |
| 8207 | 8207 | .out = &reparse_buf, |
| 8208 | 8208 | })).u.Status) { |
| ... | ... | @@ -18995,7 +18995,7 @@ fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows |
| 18995 | 18995 | .{ |
| 18996 | 18996 | .DIRECTORY_FILE = options.filter == .dir_only, |
| 18997 | 18997 | .NON_DIRECTORY_FILE = options.filter == .non_directory_only, |
| 18998 | .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, | |
| 18998 | .IO = .SYNCHRONOUS_NONALERT, | |
| 18999 | 18999 | .OPEN_REPARSE_POINT = !options.follow_symlinks, |
| 19000 | 19000 | }, |
| 19001 | 19001 | null, |
lib/std/fs/test.zig+37| ... | ... | @@ -758,6 +758,43 @@ test "readFileAlloc" { |
| 758 | 758 | ); |
| 759 | 759 | } |
| 760 | 760 | |
| 761 | test "file operations with follow_symlinks=false" { | |
| 762 | const io = testing.io; | |
| 763 | ||
| 764 | var tmp_dir = tmpDir(.{}); | |
| 765 | defer tmp_dir.cleanup(); | |
| 766 | ||
| 767 | const contents = "this is a test.\nthis is a test.\nthis is a test.\nthis is a test.\n"; | |
| 768 | try tmp_dir.dir.writeFile(io, .{ | |
| 769 | .sub_path = "test_file", | |
| 770 | .data = contents, | |
| 771 | }); | |
| 772 | ||
| 773 | // Without lock | |
| 774 | { | |
| 775 | var file = try tmp_dir.dir.openFile(io, "test_file", .{ .follow_symlinks = false }); | |
| 776 | defer file.close(io); | |
| 777 | ||
| 778 | var file_reader = file.reader(io, &.{}); | |
| 779 | const actual_contents = try file_reader.interface.allocRemaining(testing.allocator, .unlimited); | |
| 780 | defer testing.allocator.free(actual_contents); | |
| 781 | ||
| 782 | try std.testing.expectEqualSlices(u8, contents, actual_contents); | |
| 783 | } | |
| 784 | ||
| 785 | // With lock | |
| 786 | { | |
| 787 | var file = try tmp_dir.dir.openFile(io, "test_file", .{ .follow_symlinks = false, .lock = .exclusive }); | |
| 788 | defer file.close(io); | |
| 789 | ||
| 790 | var file_reader = file.reader(io, &.{}); | |
| 791 | const actual_contents = try file_reader.interface.allocRemaining(testing.allocator, .unlimited); | |
| 792 | defer testing.allocator.free(actual_contents); | |
| 793 | ||
| 794 | try std.testing.expectEqualSlices(u8, contents, actual_contents); | |
| 795 | } | |
| 796 | } | |
| 797 | ||
| 761 | 798 | test "Dir.statFile" { |
| 762 | 799 | try testWithAllSupportedPathTypes(struct { |
| 763 | 800 | fn impl(ctx: *TestContext) !void { |