Skip to content

Commit 282ff8d

Browse files
authored
Merge pull request #18453 from squeek502/makepath-parent-components
Add more `Dir.makePath` tests and document how `..` is handled in sub_path
2 parents 42389cb + 51946f5 commit 282ff8d

File tree

2 files changed

+96
-7
lines changed

2 files changed

+96
-7
lines changed

lib/std/fs/Dir.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
11141122
pub 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

Lines changed: 88 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

1092+
fn expectDir(dir: Dir, path: []const u8) !void {
1093+
var d = try dir.openDir(path, .{});
1094+
d.close();
1095+
}
1096+
1097+
test "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+
1111+
test "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+
1123+
test "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+
1153+
test "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+
10921174
fn 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,15 +1578,14 @@ 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,
15021585
.lock_nonblocking = true,
15031586
});
15041587
file1.close();
15051588
try testing.expectError(error.WouldBlock, file2);
1506-
1507-
try fs.deleteFileAbsolute(filename);
15081589
}
15091590

15101591
test "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, .{

0 commit comments

Comments
 (0)