@@ -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,52 @@ 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
+ iterable : @TypeOf (iterable_safety ) = iterable_safety ,
960
+
961
+ const iterable_safety = if (builtin .mode == .Debug ) false else {};
962
+
963
+ pub const iterate = @compileError ("only 'IterableDir' can be iterated; 'IterableDir' can be obtained with 'openIterableDir' or by opening with 'iterate = true' and using 'intoIterable'" );
964
+ pub const walk = @compileError ("only 'IterableDir' can be walked; 'IterableDir' can be obtained with 'openIterableDir' or by opening with 'iterate = true' and using 'intoIterable'" );
965
+ pub const chmod = @compileError ("only 'IterableDir' can have its mode changed; 'IterableDir' can be obtained with 'openIterableDir' or by opening with 'iterate = true' and using 'intoIterable'" );
966
+ pub const chown = @compileError ("only 'IterableDir' can have its owner changed; 'IterableDir' can be obtained with 'openIterableDir' or by opening with 'iterate = true' and using 'intoIterable'" );
967
+
921
968
pub const OpenError = error {
922
969
FileNotFound ,
923
970
NotDir ,
@@ -1507,6 +1554,26 @@ pub const Dir = struct {
1507
1554
}
1508
1555
}
1509
1556
1557
+ /// Opens an iterable directory at the given path. The directory is a system resource that remains
1558
+ /// open until `close` is called on the result.
1559
+ ///
1560
+ /// Asserts that the path parameter has no null bytes.
1561
+ pub fn openIterableDir (self : Dir , sub_path : []const u8 , args : OpenDirOptions ) OpenError ! IterableDir {
1562
+ var adjusted_args = args ;
1563
+ adjusted_args .iterate = true ;
1564
+ const new_dir = try self .openDir (sub_path , adjusted_args );
1565
+ return IterableDir { .dir = new_dir };
1566
+ }
1567
+
1568
+ /// Convert `self` into an iterable directory.
1569
+ /// Asserts that `self` was opened with `iterate = true`.
1570
+ pub fn intoIterable (self : Dir ) IterableDir {
1571
+ if (builtin .mode == .Debug ) {
1572
+ assert (self .iterable );
1573
+ }
1574
+ return .{ .dir = self };
1575
+ }
1576
+
1510
1577
/// Same as `openDir` except only WASI.
1511
1578
pub fn openDirWasi (self : Dir , sub_path : []const u8 , args : OpenDirOptions ) OpenError ! Dir {
1512
1579
const w = os .wasi ;
@@ -1552,7 +1619,7 @@ pub const Dir = struct {
1552
1619
error .FileBusy = > unreachable , // can't happen for directories
1553
1620
else = > | e | return e ,
1554
1621
};
1555
- return Dir { .fd = fd };
1622
+ return Dir { .fd = fd , . iterable = if ( builtin . mode == .Debug ) args . iterate else {} };
1556
1623
}
1557
1624
1558
1625
/// Same as `openDir` except the parameter is null-terminated.
@@ -1566,7 +1633,9 @@ pub const Dir = struct {
1566
1633
const O_PATH = if (@hasDecl (os .O , "PATH" )) os .O .PATH else 0 ;
1567
1634
return self .openDirFlagsZ (sub_path_c , os .O .DIRECTORY | os .O .RDONLY | os .O .CLOEXEC | O_PATH | symlink_flags );
1568
1635
} else {
1569
- return self .openDirFlagsZ (sub_path_c , os .O .DIRECTORY | os .O .RDONLY | os .O .CLOEXEC | symlink_flags );
1636
+ var dir = try self .openDirFlagsZ (sub_path_c , os .O .DIRECTORY | os .O .RDONLY | os .O .CLOEXEC | symlink_flags );
1637
+ if (builtin .mode == .Debug ) dir .iterable = true ;
1638
+ return dir ;
1570
1639
}
1571
1640
}
1572
1641
@@ -1578,7 +1647,9 @@ pub const Dir = struct {
1578
1647
const base_flags = w .STANDARD_RIGHTS_READ | w .FILE_READ_ATTRIBUTES | w .FILE_READ_EA |
1579
1648
w .SYNCHRONIZE | w .FILE_TRAVERSE ;
1580
1649
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 );
1650
+ var dir = try self .openDirAccessMaskW (sub_path_w , flags , args .no_follow );
1651
+ if (builtin .mode == .Debug ) dir .iterable = args .iterate ;
1652
+ return dir ;
1582
1653
}
1583
1654
1584
1655
/// `flags` must contain `os.O.DIRECTORY`.
@@ -1958,7 +2029,7 @@ pub const Dir = struct {
1958
2029
error .Unexpected ,
1959
2030
= > | e | return e ,
1960
2031
}
1961
- var dir = self .openDir (sub_path , .{ . iterate = true , .no_follow = true }) catch | err | switch (err ) {
2032
+ var iterable_dir = self .openIterableDir (sub_path , .{ .no_follow = true }) catch | err | switch (err ) {
1962
2033
error .NotDir = > {
1963
2034
if (got_access_denied ) {
1964
2035
return error .AccessDenied ;
@@ -1984,11 +2055,11 @@ pub const Dir = struct {
1984
2055
error .DeviceBusy ,
1985
2056
= > | e | return e ,
1986
2057
};
1987
- var cleanup_dir_parent : ? Dir = null ;
2058
+ var cleanup_dir_parent : ? IterableDir = null ;
1988
2059
defer if (cleanup_dir_parent ) | * d | d .close ();
1989
2060
1990
2061
var cleanup_dir = true ;
1991
- defer if (cleanup_dir ) dir .close ();
2062
+ defer if (cleanup_dir ) iterable_dir .close ();
1992
2063
1993
2064
// Valid use of MAX_PATH_BYTES because dir_name_buf will only
1994
2065
// ever store a single path component that was returned from the
@@ -2001,9 +2072,9 @@ pub const Dir = struct {
2001
2072
// open it, and close the original directory. Repeat. Then start the entire operation over.
2002
2073
2003
2074
scan_dir : while (true ) {
2004
- var dir_it = dir .iterate ();
2075
+ var dir_it = iterable_dir .iterate ();
2005
2076
while (try dir_it .next ()) | entry | {
2006
- if (dir .deleteFile (entry .name )) {
2077
+ if (iterable_dir . dir .deleteFile (entry .name )) {
2007
2078
continue ;
2008
2079
} else | err | switch (err ) {
2009
2080
error .FileNotFound = > continue ,
@@ -2026,7 +2097,7 @@ pub const Dir = struct {
2026
2097
= > | e | return e ,
2027
2098
}
2028
2099
2029
- const new_dir = dir .openDir (entry .name , .{ . iterate = true , .no_follow = true }) catch | err | switch (err ) {
2100
+ const new_dir = iterable_dir . dir .openIterableDir (entry .name , .{ .no_follow = true }) catch | err | switch (err ) {
2030
2101
error .NotDir = > {
2031
2102
if (got_access_denied ) {
2032
2103
return error .AccessDenied ;
@@ -2053,19 +2124,19 @@ pub const Dir = struct {
2053
2124
= > | e | return e ,
2054
2125
};
2055
2126
if (cleanup_dir_parent ) | * d | d .close ();
2056
- cleanup_dir_parent = dir ;
2057
- dir = new_dir ;
2127
+ cleanup_dir_parent = iterable_dir ;
2128
+ iterable_dir = new_dir ;
2058
2129
mem .copy (u8 , & dir_name_buf , entry .name );
2059
2130
dir_name = dir_name_buf [0.. entry .name .len ];
2060
2131
continue :scan_dir ;
2061
2132
}
2062
2133
// Reached the end of the directory entries, which means we successfully deleted all of them.
2063
2134
// Now to remove the directory itself.
2064
- dir .close ();
2135
+ iterable_dir .close ();
2065
2136
cleanup_dir = false ;
2066
2137
2067
2138
if (cleanup_dir_parent ) | d | {
2068
- d .deleteDir (dir_name ) catch | err | switch (err ) {
2139
+ d .dir . deleteDir (dir_name ) catch | err | switch (err ) {
2069
2140
// These two things can happen due to file system race conditions.
2070
2141
error .FileNotFound , error .DirNotEmpty = > continue :start_over ,
2071
2142
else = > | e | return e ,
@@ -2246,37 +2317,6 @@ pub const Dir = struct {
2246
2317
return file .stat ();
2247
2318
}
2248
2319
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
2320
const Permissions = File .Permissions ;
2281
2321
pub const SetPermissionsError = File .SetPermissionsError ;
2282
2322
0 commit comments