Skip to content

Commit e5c636e

Browse files
committed
libutil: address review on the Windows deletePath walk
- Narrow the read-only comment to the mechanism that holds. It claimed store canonicalisation produces these files, which was not verified and may not hold on Windows at all; what does hold is that `chmod()` is `::_wchmod`, which maps a cleared write bit onto `FILE_ATTRIBUTE_READONLY`. - Use `OsString` rather than spelling `std::wstring` directly. - Carry the `FILE_FULL_DIR_INFO` alignment requirement in the buffer's element type instead of relying on `operator new` over-aligning a `char` buffer.
1 parent e71b787 commit e5c636e

1 file changed

Lines changed: 23 additions & 10 deletions

File tree

src/libutil/windows/file-system.cc

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ namespace {
8383
/**
8484
* Clear `FILE_ATTRIBUTE_READONLY` through an already-open handle.
8585
*
86-
* A file carrying it cannot be deleted, and the store is full of them:
87-
* canonicalisation chmods store contents to 0444, and `chmod()` on Windows is
88-
* `::_wchmod`, which turns a missing write bit into exactly this attribute.
86+
* A file carrying it cannot be deleted. Anything that clears the write bit
87+
* produces one, because `chmod()` on Windows is `::_wchmod`, which maps a
88+
* missing write bit onto exactly this attribute.
8989
*
9090
* This is the counterpart of the Unix walk relaxing permissions with
9191
* `fchmodatTryNoFollow` before it recurses. Doing it through the handle rather
@@ -165,18 +165,31 @@ bool deleteByHandle(Descriptor fd)
165165
* entries while an enumeration of the same directory is in flight is not
166166
* defined to visit each entry exactly once.
167167
*/
168-
std::vector<std::wstring> listByHandle(Descriptor fd, const std::filesystem::path & path)
168+
std::vector<OsString> listByHandle(Descriptor fd, const std::filesystem::path & path)
169169
{
170-
std::vector<std::wstring> names;
170+
std::vector<OsString> names;
171171

172-
/* Big enough that a typical directory needs one round trip, but the loop
173-
below does not depend on that. */
174-
std::vector<char> buf(64 * 1024);
172+
/* The entries are read into this and then cast to `FILE_FULL_DIR_INFO`,
173+
which has 8-byte members, so the storage has to be at least that aligned.
174+
A `char` buffer would only be 1-byte aligned as a type and would be
175+
relying on `operator new` handing back something better, which it does but
176+
does not have to at that type. Carry the requirement in the element type
177+
instead.
178+
179+
Sized so that a typical directory needs one round trip; the loop below
180+
does not depend on that. */
181+
struct alignas(alignof(FILE_FULL_DIR_INFO)) Chunk
182+
{
183+
char bytes[alignof(FILE_FULL_DIR_INFO)];
184+
};
185+
186+
std::vector<Chunk> buf(64 * 1024 / sizeof(Chunk));
187+
const auto bufBytes = buf.size() * sizeof(Chunk);
175188

176189
while (true) {
177190
checkInterrupt();
178191

179-
if (!GetFileInformationByHandleEx(fd, FileFullDirectoryInfo, buf.data(), buf.size())) {
192+
if (!GetFileInformationByHandleEx(fd, FileFullDirectoryInfo, buf.data(), bufBytes)) {
180193
auto lastError = GetLastError();
181194
if (lastError == ERROR_NO_MORE_FILES)
182195
break;
@@ -185,7 +198,7 @@ std::vector<std::wstring> listByHandle(Descriptor fd, const std::filesystem::pat
185198

186199
auto * info = reinterpret_cast<FILE_FULL_DIR_INFO *>(buf.data());
187200
while (true) {
188-
std::wstring name(info->FileName, info->FileNameLength / sizeof(wchar_t));
201+
OsString name(info->FileName, info->FileNameLength / sizeof(OsChar));
189202
if (name != L"." && name != L"..")
190203
names.push_back(std::move(name));
191204
if (info->NextEntryOffset == 0)

0 commit comments

Comments
 (0)