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