Commit d912253
committed
libutil: delete recursively through handles on Windows
`std::filesystem::remove_all` re-resolves the path at every step, so each
component can be swapped between the check and the removal. Mirror the Unix
walk instead: hold a handle to the parent, and do every operation relative to
it, so a name is resolved exactly once.
That also fixes a case `remove_all` cannot do at all. A tree containing a
directory symlink fails to delete, reporting `ENOENT` and leaving the tree in
place, because libstdc++'s `std::filesystem` on MinGW does not recognise reparse
points -- `symlink_status` calls a directory symlink a plain directory with no
error set. Opening with `FILE_OPEN_REPARSE_POINT` removes the link as a link and
leaves its target alone.
Win32 has no `openat`; `NtCreateFile` does, via `OBJECT_ATTRIBUTES::RootDirectory`,
which the previous commit exposed as `ntOpenAt`. Listing is
`GetFileInformationByHandleEx(FileFullDirectoryInfo)` on the directory's own
handle. Deletion is `SetFileInformationByHandle` with `FileDispositionInfo`. The
`FileDispositionInfoEx` form with POSIX semantics would unlink the name
immediately rather than on last-handle-close, but it needs a newer API level
than the `_WIN32_WINNT=0x0602` this project sets, and the difference does not
matter here: each handle is closed before its parent is deleted, so a child's
name is already gone by then.
One handle per entry carries classification, listing, the attribute change and
the deletion. Opening twice would resolve the name twice, which is the race being
removed.
Names are collected before anything is deleted, since deleting entries while an
enumeration of the same directory is in flight is not defined to visit each entry
exactly once.
The read-only attribute is cleared through that handle. A file carrying it cannot
be deleted, and the store is full of them, because canonicalisation chmods store
contents to 0444 and `chmod()` on Windows is `::_wchmod`, which turns a missing
write bit into that attribute. This is the counterpart of the Unix walk relaxing
permissions with `fchmodatTryNoFollow` before recursing, and doing it through the
handle means the object whose attribute changes is necessarily the one about to
be deleted.
`bytesFreed` is now reported rather than left at zero. Policy copied from the
Unix implementation instead of invented: count a non-directory's size at one or
two links and nothing at three or more, two being assumed to mean an optimised
store entry. Garbage collection previously reported freeing nothing on Windows
however much it deleted, and `gc.cc` says "Rely on deletePath() accounting".
Per-entry failures are collected and rethrown at the end, so one undeletable
entry does not abandon the rest of the tree. Also Unix's behaviour.
Assisted-by: Claude Code (claude-opus-5)1 parent 9eb4d68 commit d912253
1 file changed
Lines changed: 186 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
4 | 7 | | |
5 | 8 | | |
6 | 9 | | |
| |||
74 | 77 | | |
75 | 78 | | |
76 | 79 | | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
77 | 246 | | |
78 | 247 | | |
79 | 248 | | |
80 | 249 | | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
85 | 267 | | |
86 | 268 | | |
87 | 269 | | |
| |||
0 commit comments