| 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,7 +5053,7 @@ pub fn dirOpenFileWtf16( |
| 5053 | .VALID_FLAGS, | 5053 | .VALID_FLAGS, |
| 5054 | .OPEN, | 5054 | .OPEN, |
| 5055 | .{ | 5055 | .{ |
| 5056 | .IO = if (flags.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, | 5056 | .IO = .SYNCHRONOUS_NONALERT, |
| 5057 | .NON_DIRECTORY_FILE = !allow_directory, | 5057 | .NON_DIRECTORY_FILE = !allow_directory, |
| 5058 | .OPEN_REPARSE_POINT = !flags.follow_symlinks, | 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,7 +8136,7 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink |
| 8136 | .{ | 8136 | .{ |
| 8137 | .DIRECTORY_FILE = false, | 8137 | .DIRECTORY_FILE = false, |
| 8138 | .NON_DIRECTORY_FILE = false, | 8138 | .NON_DIRECTORY_FILE = false, |
| 8139 | .IO = .ASYNCHRONOUS, | 8139 | .IO = .SYNCHRONOUS_NONALERT, |
| 8140 | .OPEN_REPARSE_POINT = true, | 8140 | .OPEN_REPARSE_POINT = true, |
| 8141 | }, | 8141 | }, |
| 8142 | null, | 8142 | null, |
| ... | @@ -8202,7 +8202,7 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink | ... | @@ -8202,7 +8202,7 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink |
| 8202 | 8202 | ||
| 8203 | var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(windows.REPARSE_DATA_BUFFER)) = undefined; | 8203 | var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(windows.REPARSE_DATA_BUFFER)) = undefined; |
| 8204 | switch ((try deviceIoControl(&.{ | 8204 | switch ((try deviceIoControl(&.{ |
| 8205 | .file = .{ .handle = result_handle, .flags = .{ .nonblocking = true } }, | 8205 | .file = .{ .handle = result_handle, .flags = .{ .nonblocking = false } }, |
| 8206 | .code = .GET_REPARSE_POINT, | 8206 | .code = .GET_REPARSE_POINT, |
| 8207 | .out = &reparse_buf, | 8207 | .out = &reparse_buf, |
| 8208 | })).u.Status) { | 8208 | })).u.Status) { |
| ... | @@ -18995,7 +18995,7 @@ fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows | ... | @@ -18995,7 +18995,7 @@ fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows |
| 18995 | .{ | 18995 | .{ |
| 18996 | .DIRECTORY_FILE = options.filter == .dir_only, | 18996 | .DIRECTORY_FILE = options.filter == .dir_only, |
| 18997 | .NON_DIRECTORY_FILE = options.filter == .non_directory_only, | 18997 | .NON_DIRECTORY_FILE = options.filter == .non_directory_only, |
| 18998 | .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, | 18998 | .IO = .SYNCHRONOUS_NONALERT, |
| 18999 | .OPEN_REPARSE_POINT = !options.follow_symlinks, | 18999 | .OPEN_REPARSE_POINT = !options.follow_symlinks, |
| 19000 | }, | 19000 | }, |
| 19001 | null, | 19001 | null, |
lib/std/fs/test.zig+37| ... | @@ -758,6 +758,43 @@ test "readFileAlloc" { | ... | @@ -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 | test "Dir.statFile" { | 798 | test "Dir.statFile" { |
| 762 | try testWithAllSupportedPathTypes(struct { | 799 | try testWithAllSupportedPathTypes(struct { |
| 763 | fn impl(ctx: *TestContext) !void { | 800 | fn impl(ctx: *TestContext) !void { |