@@ -283,8 +283,10 @@ pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_
283
283
return os .renameatW (old_dir .fd , old_sub_path_w , new_dir .fd , new_sub_path_w );
284
284
}
285
285
286
- pub const Dir = struct {
287
- fd : os.fd_t ,
286
+ /// A directory that can be iterated. It is *NOT* legal to initialize this with a regular `Dir`
287
+ /// that has been opened without iteration permission.
288
+ pub const IterableDir = struct {
289
+ dir : Dir ,
288
290
289
291
pub const Entry = struct {
290
292
name : []const u8 ,
@@ -779,7 +781,7 @@ pub const Dir = struct {
779
781
else = > @compileError ("unimplemented" ),
780
782
};
781
783
782
- pub fn iterate (self : Dir ) Iterator {
784
+ pub fn iterate (self : IterableDir ) Iterator {
783
785
switch (builtin .os .tag ) {
784
786
.macos ,
785
787
.ios ,
@@ -789,30 +791,30 @@ pub const Dir = struct {
789
791
.openbsd ,
790
792
.solaris ,
791
793
= > return Iterator {
792
- .dir = self ,
794
+ .dir = self . dir ,
793
795
.seek = 0 ,
794
796
.index = 0 ,
795
797
.end_index = 0 ,
796
798
.buf = undefined ,
797
799
.first_iter = true ,
798
800
},
799
801
.linux , .haiku = > return Iterator {
800
- .dir = self ,
802
+ .dir = self . dir ,
801
803
.index = 0 ,
802
804
.end_index = 0 ,
803
805
.buf = undefined ,
804
806
.first_iter = true ,
805
807
},
806
808
.windows = > return Iterator {
807
- .dir = self ,
809
+ .dir = self . dir ,
808
810
.index = 0 ,
809
811
.end_index = 0 ,
810
812
.first_iter = true ,
811
813
.buf = undefined ,
812
814
.name_data = undefined ,
813
815
},
814
816
.wasi = > return Iterator {
815
- .dir = self ,
817
+ .dir = self . dir ,
816
818
.cookie = os .wasi .DIRCOOKIE_START ,
817
819
.index = 0 ,
818
820
.end_index = 0 ,
@@ -833,11 +835,11 @@ pub const Dir = struct {
833
835
dir : Dir ,
834
836
basename : []const u8 ,
835
837
path : []const u8 ,
836
- kind : Dir .Entry.Kind ,
838
+ kind : IterableDir .Entry.Kind ,
837
839
};
838
840
839
841
const StackItem = struct {
840
- iter : Dir .Iterator ,
842
+ iter : IterableDir .Iterator ,
841
843
dirname_len : usize ,
842
844
};
843
845
@@ -857,7 +859,7 @@ pub const Dir = struct {
857
859
}
858
860
try self .name_buffer .appendSlice (base .name );
859
861
if (base .kind == .Directory ) {
860
- var new_dir = top .iter .dir .openDir (base .name , .{ . iterate = true }) catch | err | switch (err ) {
862
+ var new_dir = top .iter .dir .openIterableDir (base .name , .{}) catch | err | switch (err ) {
861
863
error .NameTooLong = > unreachable , // no path sep in base.name
862
864
else = > | e | return e ,
863
865
};
@@ -896,11 +898,10 @@ pub const Dir = struct {
896
898
};
897
899
898
900
/// Recursively iterates over a directory.
899
- /// `self` must have been opened with `OpenDirOptions{.iterate = true}`.
900
901
/// Must call `Walker.deinit` when done.
901
902
/// The order of returned file system entries is undefined.
902
903
/// `self` will not be closed after walking it.
903
- pub fn walk (self : Dir , allocator : Allocator ) ! Walker {
904
+ pub fn walk (self : IterableDir , allocator : Allocator ) ! Walker {
904
905
var name_buffer = std .ArrayList (u8 ).init (allocator );
905
906
errdefer name_buffer .deinit ();
906
907
@@ -918,6 +919,49 @@ pub const Dir = struct {
918
919
};
919
920
}
920
921
922
+ pub fn close (self : * IterableDir ) void {
923
+ self .dir .close ();
924
+ self .* = undefined ;
925
+ }
926
+
927
+ pub const ChmodError = File .ChmodError ;
928
+
929
+ /// Changes the mode of the directory.
930
+ /// The process must have the correct privileges in order to do this
931
+ /// successfully, or must have the effective user ID matching the owner
932
+ /// of the directory.
933
+ pub fn chmod (self : IterableDir , new_mode : File.Mode ) ChmodError ! void {
934
+ const file : File = .{
935
+ .handle = self .dir .fd ,
936
+ .capable_io_mode = .blocking ,
937
+ };
938
+ try file .chmod (new_mode );
939
+ }
940
+
941
+ /// Changes the owner and group of the directory.
942
+ /// The process must have the correct privileges in order to do this
943
+ /// successfully. The group may be changed by the owner of the directory to
944
+ /// any group of which the owner is a member. If the
945
+ /// owner or group is specified as `null`, the ID is not changed.
946
+ pub fn chown (self : IterableDir , owner : ? File.Uid , group : ? File.Gid ) ChownError ! void {
947
+ const file : File = .{
948
+ .handle = self .dir .fd ,
949
+ .capable_io_mode = .blocking ,
950
+ };
951
+ try file .chown (owner , group );
952
+ }
953
+
954
+ pub const ChownError = File .ChownError ;
955
+ };
956
+
957
+ pub const Dir = struct {
958
+ fd : os.fd_t ,
959
+
960
+ pub const iterate = @compileError ("only 'IterableDir' can be iterated; 'IterableDir' can be obtained with 'openIterableDir'" );
961
+ pub const walk = @compileError ("only 'IterableDir' can be walked; 'IterableDir' can be obtained with 'openIterableDir'" );
962
+ pub const chmod = @compileError ("only 'IterableDir' can have its mode changed; 'IterableDir' can be obtained with 'openIterableDir'" );
963
+ pub const chown = @compileError ("only 'IterableDir' can have its owner changed; 'IterableDir' can be obtained with 'openIterableDir'" );
964
+
921
965
pub const OpenError = error {
922
966
FileNotFound ,
923
967
NotDir ,
@@ -1334,6 +1378,15 @@ pub const Dir = struct {
1334
1378
return self .openDir (sub_path , open_dir_options );
1335
1379
}
1336
1380
1381
+ /// This function performs `makePath`, followed by `openIterableDir`.
1382
+ /// If supported by the OS, this operation is atomic. It is not atomic on
1383
+ /// all operating systems.
1384
+ pub fn makeOpenPathIterable (self : Dir , sub_path : []const u8 , open_dir_options : OpenDirOptions ) ! IterableDir {
1385
+ // TODO improve this implementation on Windows; we can avoid 1 call to NtClose
1386
+ try self .makePath (sub_path );
1387
+ return self .openIterableDir (sub_path , open_dir_options );
1388
+ }
1389
+
1337
1390
/// This function returns the canonicalized absolute pathname of
1338
1391
/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
1339
1392
/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
@@ -1483,10 +1536,6 @@ pub const Dir = struct {
1483
1536
/// such operations are Illegal Behavior.
1484
1537
access_sub_paths : bool = true ,
1485
1538
1486
- /// `true` means the opened directory can be scanned for the files and sub-directories
1487
- /// of the result. It means the `iterate` function can be called.
1488
- iterate : bool = false ,
1489
-
1490
1539
/// `true` means it won't dereference the symlinks.
1491
1540
no_follow : bool = false ,
1492
1541
};
@@ -1498,12 +1547,28 @@ pub const Dir = struct {
1498
1547
pub fn openDir (self : Dir , sub_path : []const u8 , args : OpenDirOptions ) OpenError ! Dir {
1499
1548
if (builtin .os .tag == .windows ) {
1500
1549
const sub_path_w = try os .windows .sliceToPrefixedFileW (sub_path );
1501
- return self .openDirW (sub_path_w .span ().ptr , args );
1550
+ return self .openDirW (sub_path_w .span ().ptr , args , false );
1502
1551
} else if (builtin .os .tag == .wasi and ! builtin .link_libc ) {
1503
1552
return self .openDirWasi (sub_path , args );
1504
1553
} else {
1505
1554
const sub_path_c = try os .toPosixPath (sub_path );
1506
- return self .openDirZ (& sub_path_c , args );
1555
+ return self .openDirZ (& sub_path_c , args , false );
1556
+ }
1557
+ }
1558
+
1559
+ /// Opens an iterable directory at the given path. The directory is a system resource that remains
1560
+ /// open until `close` is called on the result.
1561
+ ///
1562
+ /// Asserts that the path parameter has no null bytes.
1563
+ pub fn openIterableDir (self : Dir , sub_path : []const u8 , args : OpenDirOptions ) OpenError ! IterableDir {
1564
+ if (builtin .os .tag == .windows ) {
1565
+ const sub_path_w = try os .windows .sliceToPrefixedFileW (sub_path );
1566
+ return IterableDir { .dir = try self .openDirW (sub_path_w .span ().ptr , args , true ) };
1567
+ } else if (builtin .os .tag == .wasi and ! builtin .link_libc ) {
1568
+ return IterableDir { .dir = try self .openDirWasi (sub_path , args ) };
1569
+ } else {
1570
+ const sub_path_c = try os .toPosixPath (sub_path );
1571
+ return IterableDir { .dir = try self .openDirZ (& sub_path_c , args , true ) };
1507
1572
}
1508
1573
}
1509
1574
@@ -1556,13 +1621,13 @@ pub const Dir = struct {
1556
1621
}
1557
1622
1558
1623
/// Same as `openDir` except the parameter is null-terminated.
1559
- pub fn openDirZ (self : Dir , sub_path_c : [* :0 ]const u8 , args : OpenDirOptions ) OpenError ! Dir {
1624
+ pub fn openDirZ (self : Dir , sub_path_c : [* :0 ]const u8 , args : OpenDirOptions , iterable : bool ) OpenError ! Dir {
1560
1625
if (builtin .os .tag == .windows ) {
1561
1626
const sub_path_w = try os .windows .cStrToPrefixedFileW (sub_path_c );
1562
1627
return self .openDirW (sub_path_w .span ().ptr , args );
1563
1628
}
1564
1629
const symlink_flags : u32 = if (args .no_follow ) os .O .NOFOLLOW else 0x0 ;
1565
- if (! args . iterate ) {
1630
+ if (! iterable ) {
1566
1631
const O_PATH = if (@hasDecl (os .O , "PATH" )) os .O .PATH else 0 ;
1567
1632
return self .openDirFlagsZ (sub_path_c , os .O .DIRECTORY | os .O .RDONLY | os .O .CLOEXEC | O_PATH | symlink_flags );
1568
1633
} else {
@@ -1572,13 +1637,14 @@ pub const Dir = struct {
1572
1637
1573
1638
/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
1574
1639
/// This function asserts the target OS is Windows.
1575
- pub fn openDirW (self : Dir , sub_path_w : [* :0 ]const u16 , args : OpenDirOptions ) OpenError ! Dir {
1640
+ pub fn openDirW (self : Dir , sub_path_w : [* :0 ]const u16 , args : OpenDirOptions , iterable : bool ) OpenError ! Dir {
1576
1641
const w = os .windows ;
1577
1642
// TODO remove some of these flags if args.access_sub_paths is false
1578
1643
const base_flags = w .STANDARD_RIGHTS_READ | w .FILE_READ_ATTRIBUTES | w .FILE_READ_EA |
1579
1644
w .SYNCHRONIZE | w .FILE_TRAVERSE ;
1580
- const flags : u32 = if (args .iterate ) base_flags | w .FILE_LIST_DIRECTORY else base_flags ;
1581
- return self .openDirAccessMaskW (sub_path_w , flags , args .no_follow );
1645
+ const flags : u32 = if (iterable ) base_flags | w .FILE_LIST_DIRECTORY else base_flags ;
1646
+ var dir = try self .openDirAccessMaskW (sub_path_w , flags , args .no_follow );
1647
+ return dir ;
1582
1648
}
1583
1649
1584
1650
/// `flags` must contain `os.O.DIRECTORY`.
@@ -1958,7 +2024,7 @@ pub const Dir = struct {
1958
2024
error .Unexpected ,
1959
2025
= > | e | return e ,
1960
2026
}
1961
- var dir = self .openDir (sub_path , .{ . iterate = true , .no_follow = true }) catch | err | switch (err ) {
2027
+ var iterable_dir = self .openIterableDir (sub_path , .{ .no_follow = true }) catch | err | switch (err ) {
1962
2028
error .NotDir = > {
1963
2029
if (got_access_denied ) {
1964
2030
return error .AccessDenied ;
@@ -1984,11 +2050,11 @@ pub const Dir = struct {
1984
2050
error .DeviceBusy ,
1985
2051
= > | e | return e ,
1986
2052
};
1987
- var cleanup_dir_parent : ? Dir = null ;
2053
+ var cleanup_dir_parent : ? IterableDir = null ;
1988
2054
defer if (cleanup_dir_parent ) | * d | d .close ();
1989
2055
1990
2056
var cleanup_dir = true ;
1991
- defer if (cleanup_dir ) dir .close ();
2057
+ defer if (cleanup_dir ) iterable_dir .close ();
1992
2058
1993
2059
// Valid use of MAX_PATH_BYTES because dir_name_buf will only
1994
2060
// ever store a single path component that was returned from the
@@ -2001,9 +2067,9 @@ pub const Dir = struct {
2001
2067
// open it, and close the original directory. Repeat. Then start the entire operation over.
2002
2068
2003
2069
scan_dir : while (true ) {
2004
- var dir_it = dir .iterate ();
2070
+ var dir_it = iterable_dir .iterate ();
2005
2071
while (try dir_it .next ()) | entry | {
2006
- if (dir .deleteFile (entry .name )) {
2072
+ if (iterable_dir . dir .deleteFile (entry .name )) {
2007
2073
continue ;
2008
2074
} else | err | switch (err ) {
2009
2075
error .FileNotFound = > continue ,
@@ -2026,7 +2092,7 @@ pub const Dir = struct {
2026
2092
= > | e | return e ,
2027
2093
}
2028
2094
2029
- const new_dir = dir .openDir (entry .name , .{ . iterate = true , .no_follow = true }) catch | err | switch (err ) {
2095
+ const new_dir = iterable_dir . dir .openIterableDir (entry .name , .{ .no_follow = true }) catch | err | switch (err ) {
2030
2096
error .NotDir = > {
2031
2097
if (got_access_denied ) {
2032
2098
return error .AccessDenied ;
@@ -2053,19 +2119,19 @@ pub const Dir = struct {
2053
2119
= > | e | return e ,
2054
2120
};
2055
2121
if (cleanup_dir_parent ) | * d | d .close ();
2056
- cleanup_dir_parent = dir ;
2057
- dir = new_dir ;
2122
+ cleanup_dir_parent = iterable_dir ;
2123
+ iterable_dir = new_dir ;
2058
2124
mem .copy (u8 , & dir_name_buf , entry .name );
2059
2125
dir_name = dir_name_buf [0.. entry .name .len ];
2060
2126
continue :scan_dir ;
2061
2127
}
2062
2128
// Reached the end of the directory entries, which means we successfully deleted all of them.
2063
2129
// Now to remove the directory itself.
2064
- dir .close ();
2130
+ iterable_dir .close ();
2065
2131
cleanup_dir = false ;
2066
2132
2067
2133
if (cleanup_dir_parent ) | d | {
2068
- d .deleteDir (dir_name ) catch | err | switch (err ) {
2134
+ d .dir . deleteDir (dir_name ) catch | err | switch (err ) {
2069
2135
// These two things can happen due to file system race conditions.
2070
2136
error .FileNotFound , error .DirNotEmpty = > continue :start_over ,
2071
2137
else = > | e | return e ,
@@ -2246,37 +2312,6 @@ pub const Dir = struct {
2246
2312
return file .stat ();
2247
2313
}
2248
2314
2249
- pub const ChmodError = File .ChmodError ;
2250
-
2251
- /// Changes the mode of the directory.
2252
- /// The process must have the correct privileges in order to do this
2253
- /// successfully, or must have the effective user ID matching the owner
2254
- /// of the directory. Additionally, the directory must have been opened
2255
- /// with `OpenDirOptions{ .iterate = true }`.
2256
- pub fn chmod (self : Dir , new_mode : File.Mode ) ChmodError ! void {
2257
- const file : File = .{
2258
- .handle = self .fd ,
2259
- .capable_io_mode = .blocking ,
2260
- };
2261
- try file .chmod (new_mode );
2262
- }
2263
-
2264
- /// Changes the owner and group of the directory.
2265
- /// The process must have the correct privileges in order to do this
2266
- /// successfully. The group may be changed by the owner of the directory to
2267
- /// any group of which the owner is a member. Additionally, the directory
2268
- /// must have been opened with `OpenDirOptions{ .iterate = true }`. If the
2269
- /// owner or group is specified as `null`, the ID is not changed.
2270
- pub fn chown (self : Dir , owner : ? File.Uid , group : ? File.Gid ) ChownError ! void {
2271
- const file : File = .{
2272
- .handle = self .fd ,
2273
- .capable_io_mode = .blocking ,
2274
- };
2275
- try file .chown (owner , group );
2276
- }
2277
-
2278
- pub const ChownError = File .ChownError ;
2279
-
2280
2315
const Permissions = File .Permissions ;
2281
2316
pub const SetPermissionsError = File .SetPermissionsError ;
2282
2317
0 commit comments