authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-06 16:47:39-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-01-06 16:47:39-08:00
log282ff8d3bd4a0d870a98f145aa87039e0409b745
tree08fbc5f4193f95a4669676a872c070d07f42f3cd
parent42389cb9c58cfe71db055142eb7b8cc15193ddf9
parent51946f5adcc0e2a7c26f17ab7784e5e90aba2ea1
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #18453 from squeek502/makepath-parent-components

Add more `Dir.makePath` tests and document how `..` is handled in sub_path

2 files changed, 96 insertions(+), 7 deletions(-)

lib/std/fs/Dir.zig+8
......@@ -1111,6 +1111,14 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
11111111/// Returns success if the path already exists and is a directory.
11121112/// This function is not atomic, and if it returns an error, the file system may
11131113/// have been modified regardless.
1114///
1115/// Paths containing `..` components are handled differently depending on the platform:
1116/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
1117/// a `sub_path` like "first/../second" will resolve to "second" and only a
1118/// `./second` directory will be created.
1119/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
1120/// meaning a `sub_path` like "first/../second" will create both a `./first`
1121/// and a `./second` directory.
11141122pub fn makePath(self: Dir, sub_path: []const u8) !void {
11151123 var it = try fs.path.componentIterator(sub_path);
11161124 var component = it.last() orelse return;
lib/std/fs/test.zig+88-7
......@@ -545,7 +545,7 @@ test "Dir.Iterator but dir is deleted during iteration" {
545545 var iterator = subdir.iterate();
546546
547547 // Create something to iterate over within the subdir
548 try tmp.dir.makePath("subdir/b");
548 try tmp.dir.makePath("subdir" ++ fs.path.sep_str ++ "b");
549549
550550 // Then, before iterating, delete the directory that we're iterating.
551551 // This is a contrived reproduction, but this could happen outside of the program, in another thread, etc.
......@@ -754,7 +754,7 @@ test "deleteDir" {
754754 try testWithAllSupportedPathTypes(struct {
755755 fn impl(ctx: *TestContext) !void {
756756 const test_dir_path = try ctx.transformPath("test_dir");
757 const test_file_path = try ctx.transformPath("test_dir" ++ std.fs.path.sep_str ++ "test_file");
757 const test_file_path = try ctx.transformPath("test_dir" ++ fs.path.sep_str ++ "test_file");
758758
759759 // deleting a non-existent directory
760760 try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(test_dir_path));
......@@ -1089,6 +1089,88 @@ test "makePath in a directory that no longer exists" {
10891089 try testing.expectError(error.FileNotFound, tmp.dir.makePath("sub-path"));
10901090}
10911091
1092fn expectDir(dir: Dir, path: []const u8) !void {
1093 var d = try dir.openDir(path, .{});
1094 d.close();
1095}
1096
1097test "makepath existing directories" {
1098 var tmp = tmpDir(.{});
1099 defer tmp.cleanup();
1100
1101 try tmp.dir.makeDir("A");
1102 const tmpA = try tmp.dir.openDir("A", .{});
1103 try tmpA.makeDir("B");
1104
1105 const testPath = "A" ++ fs.path.sep_str ++ "B" ++ fs.path.sep_str ++ "C";
1106 try tmp.dir.makePath(testPath);
1107
1108 try expectDir(tmp.dir, testPath);
1109}
1110
1111test "makepath through existing valid symlink" {
1112 var tmp = tmpDir(.{});
1113 defer tmp.cleanup();
1114
1115 try tmp.dir.makeDir("realfolder");
1116 try tmp.dir.symLink("." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{});
1117
1118 try tmp.dir.makePath("working-symlink" ++ fs.path.sep_str ++ "in-realfolder");
1119
1120 try expectDir(tmp.dir, "realfolder" ++ fs.path.sep_str ++ "in-realfolder");
1121}
1122
1123test "makepath relative walks" {
1124 var tmp = tmpDir(.{});
1125 defer tmp.cleanup();
1126
1127 const relPath = try fs.path.join(testing.allocator, &.{
1128 "first", "..", "second", "..", "third", "..", "first", "A", "..", "B", "..", "C",
1129 });
1130 defer testing.allocator.free(relPath);
1131
1132 try tmp.dir.makePath(relPath);
1133
1134 // How .. is handled is different on Windows than non-Windows
1135 switch (builtin.os.tag) {
1136 .windows => {
1137 // On Windows, .. is resolved before passing the path to NtCreateFile,
1138 // meaning everything except `first/C` drops out.
1139 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C");
1140 try testing.expectError(error.FileNotFound, tmp.dir.access("second", .{}));
1141 try testing.expectError(error.FileNotFound, tmp.dir.access("third", .{}));
1142 },
1143 else => {
1144 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "A");
1145 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "B");
1146 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C");
1147 try expectDir(tmp.dir, "second");
1148 try expectDir(tmp.dir, "third");
1149 },
1150 }
1151}
1152
1153test "makepath ignores '.'" {
1154 var tmp = tmpDir(.{});
1155 defer tmp.cleanup();
1156
1157 // Path to create, with "." elements:
1158 const dotPath = try fs.path.join(testing.allocator, &.{
1159 "first", ".", "second", ".", "third",
1160 });
1161 defer testing.allocator.free(dotPath);
1162
1163 // Path to expect to find:
1164 const expectedPath = try fs.path.join(testing.allocator, &.{
1165 "first", "second", "third",
1166 });
1167 defer testing.allocator.free(expectedPath);
1168
1169 try tmp.dir.makePath(dotPath);
1170
1171 try expectDir(tmp.dir, expectedPath);
1172}
1173
10921174fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void {
10931175 // setup, create a dir and a nested file both with maxed filenames, and walk the dir
10941176 {
......@@ -1496,6 +1578,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
14961578 .lock = .exclusive,
14971579 .lock_nonblocking = true,
14981580 });
1581 defer fs.deleteFileAbsolute(filename) catch {};
14991582
15001583 const file2 = fs.createFileAbsolute(filename, .{
15011584 .lock = .exclusive,
......@@ -1503,8 +1586,6 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
15031586 });
15041587 file1.close();
15051588 try testing.expectError(error.WouldBlock, file2);
1506
1507 try fs.deleteFileAbsolute(filename);
15081589}
15091590
15101591test "walker" {
......@@ -1520,9 +1601,9 @@ test "walker" {
15201601 .{"dir2"},
15211602 .{"dir3"},
15221603 .{"dir4"},
1523 .{"dir3" ++ std.fs.path.sep_str ++ "sub1"},
1524 .{"dir3" ++ std.fs.path.sep_str ++ "sub2"},
1525 .{"dir3" ++ std.fs.path.sep_str ++ "sub2" ++ std.fs.path.sep_str ++ "subsub1"},
1604 .{"dir3" ++ fs.path.sep_str ++ "sub1"},
1605 .{"dir3" ++ fs.path.sep_str ++ "sub2"},
1606 .{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"},
15261607 });
15271608
15281609 const expected_basenames = std.ComptimeStringMap(void, .{