Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moving a file using SetFileInformationByHandle

I'm trying to move a file using SetFileInformationByHandle. This technique has been proposed by Niall Douglas in his CppCon2015 talk "Racing The File System" as a way to atomically move/rename a file. However, I'm struggling to provide correct arguments; it always fails and GetLastError returns ERROR_INVALID_PARAMETER.

I've tried this with the following setups, using the Unicode Character Set:

  • VS2015U1, running the exe under Windows 10
  • VS2015U2, running the exe under Windows Server 2012
  • VS2013, running the exe under Windows 7

But the behaviour is the same. I made sure to have access to the test folders and test file.

#include <sdkddkver.h>
#include <windows.h>

#include <cstring>
#include <iostream>
#include <memory>

int main()
{
    auto const& filepath = L"C:\\remove_tests\\file.txt";
    auto const& destpath = L"C:\\remove_tests\\other.txt";
    // unclear if that's the "root directory"
    auto const& rootdir = L"C:\\remove_tests";

    // handles will be leaked but that should be irrelevant here
    auto const f_handle = CreateFile(filepath,
        GENERIC_READ | GENERIC_WRITE | DELETE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    if (f_handle == INVALID_HANDLE_VALUE)
    {
        auto const err = GetLastError();
        std::cerr << "failed to create test file: " << err;
        return err;
    }

    auto const parent_dir_handle = CreateFile(rootdir,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
        NULL);

    if (parent_dir_handle == INVALID_HANDLE_VALUE)
    {
        auto const err = GetLastError();
        std::cerr << "failed to get handle to parent directory: " << err;
        return err;
    }

    auto const destpath_bytes_with_null = sizeof(destpath);
    // unclear if we need to subtract the one wchar_t of FileNameLength:
    auto const struct_size = sizeof(FILE_RENAME_INFO) + destpath_bytes_with_null;
    auto const buf = std::make_unique<char[]>(struct_size);

    auto const fri = reinterpret_cast<FILE_RENAME_INFO*>(buf.get());
    fri->ReplaceIfExists =  TRUE; // as described by Niall Douglas
    fri->RootDirectory = parent_dir_handle;
    // with or without null terminator?
    fri->FileNameLength = destpath_bytes_with_null;
    std::memcpy(fri->FileName, destpath, destpath_bytes_with_null);

    BOOL res = SetFileInformationByHandle(f_handle, FileRenameInfo,
                                          fri, struct_size);
    if (!res)
    {
        auto const err = GetLastError();
        std::cerr << "failed to rename file: " << err;
        return err;
    }
    else
        std::cout << "success";
}

In particular, my questions are:

  • What is the "root directory" as required by FILE_RENAME_INFO?
  • Which permissions are required for the handles?
  • What's the underlying problem of the ERROR_INVALID_PARAMETER produced by SetFileInformationByHandle?
like image 903
dyp Avatar asked Apr 06 '16 11:04

dyp


2 Answers

The documentation for SetFileInformationByHandle with FileRenameInfo and FILE_RENAME_INFO contains some errors. FILE_RENAME_INFO.FileNameLength must be set to the number of characters copied to FILE_RENAME_INFO.FileName excluding the terminating zero, and FILE_RENAME_INFO.RootDirectory must be null, even if moving the file from one directory to another.

#include <sdkddkver.h>
#include <windows.h>

#include <cstring>
#include <iostream>
#include <memory>

int _tmain( int argc, _TCHAR* argv [] )
{
    wchar_t* filename = L"C:\\remove_tests\\file.txt";
    wchar_t* destFilename = L"C:\\remove_tests2\\other.txt";

    // handles will be leaked but that should be irrelevant here
    auto fileHandle = CreateFile( filename,
                                  GENERIC_READ | GENERIC_WRITE | DELETE,
                                FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                      NULL,
                                      OPEN_EXISTING,
                                      FILE_ATTRIBUTE_NORMAL,
                                      NULL );

    if ( fileHandle == INVALID_HANDLE_VALUE )
    {
        auto const err = GetLastError( );
        std::cerr << "failed to create test file: " << err;
        return err;
    }

    auto destFilenameLength = wcslen( destFilename );

    auto bufferSize = sizeof( FILE_RENAME_INFO ) + ( destFilenameLength*sizeof( wchar_t ));
    auto buffer = _alloca( bufferSize );
    memset( buffer, 0, bufferSize );

    auto const fri = reinterpret_cast<FILE_RENAME_INFO*>( buffer );
    fri->ReplaceIfExists = TRUE;

    fri->FileNameLength = destFilenameLength;
    wmemcpy( fri->FileName, destFilename, destFilenameLength );

    BOOL res = SetFileInformationByHandle( fileHandle, FileRenameInfo, fri, bufferSize );
    if ( !res )
    {
        auto const err = GetLastError( );
        std::cerr << "failed to rename file: " << err;
        return err;
    }
    else
        std::cout << "success";
}
like image 59
Espen Harlinn Avatar answered Oct 11 '22 01:10

Espen Harlinn


I change a couple of thinks:

1) i dont use root handle (i set it to NULL)

2) i change your FILE_RENAME_INFO memory allocation code

NOTE: checked in windows 8, moving file in the same volume (disk)

auto const& filepath = L"C:\\remove_tests\\file.txt";
auto const& destpath = L"C:\\remove_tests\\other.txt";
// unclear if that's the "root directory"
auto const& rootdir = L"C:\\remove_tests";

// handles will be leaked but that should be irrelevant here
auto const f_handle = CreateFile(filepath,
    GENERIC_READ | GENERIC_WRITE | DELETE,
      0,
    NULL,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    NULL);

if (f_handle == INVALID_HANDLE_VALUE)
{
    auto const err = GetLastError();
    std::cerr << "failed to create test file: " << err;
    return err;
}

/*auto const parent_dir_handle = CreateFile(rootdir,
    GENERIC_READ | GENERIC_WRITE | DELETE,
      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
    NULL);

if (parent_dir_handle == INVALID_HANDLE_VALUE)
{
    auto const err = GetLastError();
    std::cerr << "failed to get handle to parent directory: " << err;
    return err;
}*/

 auto const destpath_bytes_withOUT_null = _tcslen(destpath);
// unclear if we need to subtract the one wchar_t of FileNameLength:
auto const struct_size = sizeof(FILE_RENAME_INFO) + (destpath_bytes_withOUT_null + 1) * sizeof(WCHAR);
FILE_RENAME_INFO* fri = (FILE_RENAME_INFO*)new BYTE[struct_size];

fri->ReplaceIfExists =  TRUE; // as described by Niall Douglas
fri->RootDirectory = NULL;//parent_dir_handle;
// with or without null terminator?
fri->FileNameLength = destpath_bytes_withOUT_null;// No include null
 _tcscpy_s(fri->FileName, destpath_bytes_withOUT_null + 1, destpath);

BOOL res = SetFileInformationByHandle(f_handle, FileRenameInfo,
                                      fri, struct_size);

 delete fri;
if (!res)
{
    auto const err = GetLastError();
    std::cerr << "failed to rename file: " << err;
    return err;
}
else
    std::cout << "success";
like image 26
Ing. Gerardo Sánchez Avatar answered Oct 11 '22 03:10

Ing. Gerardo Sánchez