authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2022-10-04 23:30:15-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2022-10-05 03:26:13-07:00
log8cec8f6dddeec46f609afd42c6024c55e5c126d9
tree07af7bbc28128723ee47f5baa97b81086828bc50
parent5059384b570a4cb554215c8091ff00d77a0ebadf

fs.Dir.deleteTree: Reduce the number of failing deleteFile calls

There are two parts to this: 1. The deleteFile call on the sub_path has been moved outside the loop, since if the first call fails with `IsDir` then it's very likely that all the subsequent calls will do the same. Instead, if the `openIterableDir` call ever hits `NotDir` after the `deleteFile` hit `IsDir`, then we assume that the tree was deleted at some point and can consider the deleteTree a success. 2. Inside the `dir_it.next()` loop, we look at entry.kind and only try doing the relevant (deleteFile/openIterableDir) operation, but always fall back to the other if we get the relevant error (NotDir/IsDir).

1 files changed, 87 insertions(+), 84 deletions(-)

lib/std/fs.zig+87-84
......@@ -2046,41 +2046,38 @@ pub const Dir = struct {
20462046 /// this function recursively removes its entries and then tries again.
20472047 /// This operation is not atomic on most file systems.
20482048 pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2049 start_over: while (true) {
2050 var got_access_denied = false;
2051
2052 // First, try deleting the item as a file. This way we don't follow sym links.
2053 if (self.deleteFile(sub_path)) {
2054 return;
2055 } else |err| switch (err) {
2056 error.FileNotFound => return,
2057 error.IsDir => {},
2058 error.AccessDenied => got_access_denied = true,
2049 // First, try deleting the item as a file. This way we don't follow sym links.
2050 if (self.deleteFile(sub_path)) {
2051 return;
2052 } else |err| switch (err) {
2053 error.FileNotFound => return,
2054 error.IsDir => {},
2055 error.AccessDenied,
2056 error.InvalidUtf8,
2057 error.SymLinkLoop,
2058 error.NameTooLong,
2059 error.SystemResources,
2060 error.ReadOnlyFileSystem,
2061 error.NotDir,
2062 error.FileSystem,
2063 error.FileBusy,
2064 error.BadPathName,
2065 error.Unexpected,
2066 => |e| return e,
2067 }
20592068
2060 error.InvalidUtf8,
2061 error.SymLinkLoop,
2062 error.NameTooLong,
2063 error.SystemResources,
2064 error.ReadOnlyFileSystem,
2065 error.NotDir,
2066 error.FileSystem,
2067 error.FileBusy,
2068 error.BadPathName,
2069 error.Unexpected,
2070 => |e| return e,
2071 }
2069 start_over: while (true) {
20722070 var iterable_dir = self.openIterableDir(sub_path, .{ .no_follow = true }) catch |err| switch (err) {
20732071 error.NotDir => {
2074 if (got_access_denied) {
2075 return error.AccessDenied;
2076 }
2077 continue :start_over;
2072 // Somehow the sub_path got changed into a file while we were trying to delete the tree.
2073 // This implies that the dir at the sub_path was deleted at some point so we consider this
2074 // as a successful delete and return.
2075 return;
20782076 },
20792077 error.FileNotFound => {
20802078 // That's fine, we were trying to remove this directory anyway.
2081 continue :start_over;
2079 return;
20822080 },
2083
20842081 error.InvalidHandle,
20852082 error.AccessDenied,
20862083 error.SymLinkLoop,
......@@ -2112,63 +2109,69 @@ pub const Dir = struct {
21122109 // open it, and close the original directory. Repeat. Then start the entire operation over.
21132110
21142111 scan_dir: while (true) {
2115 var dir_it = iterable_dir.iterate();
2116 while (try dir_it.next()) |entry| {
2117 if (iterable_dir.dir.deleteFile(entry.name)) {
2118 continue;
2119 } else |err| switch (err) {
2120 error.FileNotFound => continue,
2121
2122 // Impossible because we do not pass any path separators.
2123 error.NotDir => unreachable,
2124
2125 error.IsDir => {},
2126 error.AccessDenied => got_access_denied = true,
2127
2128 error.InvalidUtf8,
2129 error.SymLinkLoop,
2130 error.NameTooLong,
2131 error.SystemResources,
2132 error.ReadOnlyFileSystem,
2133 error.FileSystem,
2134 error.FileBusy,
2135 error.BadPathName,
2136 error.Unexpected,
2137 => |e| return e,
2138 }
2139
2140 const new_dir = iterable_dir.dir.openIterableDir(entry.name, .{ .no_follow = true }) catch |err| switch (err) {
2141 error.NotDir => {
2142 if (got_access_denied) {
2143 return error.AccessDenied;
2144 }
2112 var dir_it = iterable_dir.iterateAssumeFirstIteration();
2113 dir_it: while (try dir_it.next()) |entry| {
2114 var treat_as_dir = entry.kind == .Directory;
2115 handle_entry: while (true) {
2116 if (treat_as_dir) {
2117 const new_dir = iterable_dir.dir.openIterableDir(entry.name, .{ .no_follow = true }) catch |err| switch (err) {
2118 error.NotDir => {
2119 treat_as_dir = false;
2120 continue :handle_entry;
2121 },
2122 error.FileNotFound => {
2123 // That's fine, we were trying to remove this directory anyway.
2124 continue :dir_it;
2125 },
2126
2127 error.InvalidHandle,
2128 error.AccessDenied,
2129 error.SymLinkLoop,
2130 error.ProcessFdQuotaExceeded,
2131 error.NameTooLong,
2132 error.SystemFdQuotaExceeded,
2133 error.NoDevice,
2134 error.SystemResources,
2135 error.Unexpected,
2136 error.InvalidUtf8,
2137 error.BadPathName,
2138 error.DeviceBusy,
2139 => |e| return e,
2140 };
2141 if (cleanup_dir_parent) |*d| d.close();
2142 cleanup_dir_parent = iterable_dir;
2143 iterable_dir = new_dir;
2144 mem.copy(u8, &dir_name_buf, entry.name);
2145 dir_name = dir_name_buf[0..entry.name.len];
21452146 continue :scan_dir;
2146 },
2147 error.FileNotFound => {
2148 // That's fine, we were trying to remove this directory anyway.
2149 continue :scan_dir;
2150 },
2151
2152 error.InvalidHandle,
2153 error.AccessDenied,
2154 error.SymLinkLoop,
2155 error.ProcessFdQuotaExceeded,
2156 error.NameTooLong,
2157 error.SystemFdQuotaExceeded,
2158 error.NoDevice,
2159 error.SystemResources,
2160 error.Unexpected,
2161 error.InvalidUtf8,
2162 error.BadPathName,
2163 error.DeviceBusy,
2164 => |e| return e,
2165 };
2166 if (cleanup_dir_parent) |*d| d.close();
2167 cleanup_dir_parent = iterable_dir;
2168 iterable_dir = new_dir;
2169 mem.copy(u8, &dir_name_buf, entry.name);
2170 dir_name = dir_name_buf[0..entry.name.len];
2171 continue :scan_dir;
2147 } else {
2148 if (iterable_dir.dir.deleteFile(entry.name)) {
2149 continue :dir_it;
2150 } else |err| switch (err) {
2151 error.FileNotFound => continue :dir_it,
2152
2153 // Impossible because we do not pass any path separators.
2154 error.NotDir => unreachable,
2155
2156 error.IsDir => {
2157 treat_as_dir = true;
2158 continue :handle_entry;
2159 },
2160
2161 error.AccessDenied,
2162 error.InvalidUtf8,
2163 error.SymLinkLoop,
2164 error.NameTooLong,
2165 error.SystemResources,
2166 error.ReadOnlyFileSystem,
2167 error.FileSystem,
2168 error.FileBusy,
2169 error.BadPathName,
2170 error.Unexpected,
2171 => |e| return e,
2172 }
2173 }
2174 }
21722175 }
21732176 // Reached the end of the directory entries, which means we successfully deleted all of them.
21742177 // Now to remove the directory itself.