Rename folder using `MoveFileA` with open file handles of the files inside

Issue

I have a situation where I need to use MoveFileA() to rename a folder that contains some files with open file handles (in the same process).

It works if everything is done in the same thread, however, if those file handles were opened in another thread, then the rename operation will not work.

From what I can gather, Windows file handles are supposed to be shared across threads. So, why isn’t this working?

I have a small test program that demonstrates my problem. It completes without errors if I compile and run it as-is. But, if I comment out this line:

auto handles = create3Handles();

and uncomment this line:

auto handles = create3HandlesInThread();

then MoveFileA() fails and the program prints out:

Move folder failed (5)

#include <array>
#include <cstddef>
#include <filesystem>
#include <iostream>
#include <string>
#include <thread>

#include <windows.h>

using namespace std::literals;

static auto constexpr ParentDir = "test-dir";
static auto constexpr ParentDirAfter = "renamed-dir";

std::array<HANDLE, 3U> create3Handles() {
    auto handles = std::array<HANDLE, 3U>{};
    for (size_t i = 0U; i < std::size(handles); ++i) {
        auto filepath = std::string{ParentDir} + '/' + std::to_string(i) + ".txt";
        if (handles[i] = CreateFileA(filepath.c_str(),
                    GENERIC_WRITE,
                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                    nullptr,
                    CREATE_ALWAYS,
                    FILE_ATTRIBUTE_NORMAL,
                    nullptr); handles[i] == INVALID_HANDLE_VALUE) {
            std::cerr << "Create \"" << filepath << "\" failed (" << GetLastError() << ')' << std::endl;
        }

        if (BOOL ret = WriteFile(handles[i],
                    filepath.c_str(),
                    std::size(filepath) * sizeof(filepath[0]),
                    nullptr,
                    nullptr); ret == 0) {
            std::cerr << "Write \"" << filepath << "\" failed (" << GetLastError() << ')' << std::endl;
        }
    }

    return handles;
}

[[maybe_unused]] std::array<HANDLE, 3U> create3HandlesInThread() {
    auto handles = std::array<HANDLE, 3>{};

    auto t = std::thread{[&handles]() { handles = create3Handles(); }};
    t.join();

    return handles;
}

int main() {
    std::filesystem::remove_all({ ParentDir });
    std::filesystem::remove_all({ ParentDirAfter });

    if (BOOL ret = CreateDirectoryA(ParentDir, nullptr); !ret) {
        std::cerr << "Create parent folder failed" << GetLastError() << ')' << std::endl;
        return 1;
    }

    auto handles = create3Handles();
//    auto handles = create3HandlesInThread();

    if (BOOL ret = MoveFileA(ParentDir, ParentDirAfter); !ret) {
        std::cerr << "Move folder failed (" << GetLastError() << ')' << std::endl;
    }

    for (auto const &handle: handles) {
        if (BOOL ret = CloseHandle(handle); !ret) {
            std::cerr << "Close file handle failed (" << GetLastError() << ')' << std::endl;
        }
    }

    return 0;
}

Solution

Well turns out MoveFileA() shouldn’t be able to rename the folder in both cases, and my test program demonstrated that.

I don’t know what got to me or my PC because I could have sworn create3Handles() was working. Sorry for wasting everyone’s time.

Answered By – tearfur

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published