使用SetFileInformationByHandle移动文件

时间:2016-04-06 11:46:51

标签: c++ windows winapi file-rename file-move

我正在尝试使用SetFileInformationByHandle移动文件。 Niall Douglas在他的CppCon2015谈话“赛车文件系统”中提出了这种技术,作为原子地移动/重命名文件的一种方式。但是,我很难提供正确的论据;它始终失败,GetLastError返回ERROR_INVALID_PARAMETER

我已尝试使用Unicode字符集进行以下设置:

  • VS2015U1,在Windows 10下运行exe
  • VS2015U2,在Windows Server 2012下运行exe
  • VS2013,在Windows 7下运行exe

但行为是一样的。我确保可以访问测试文件夹和测试文件。

#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";
}

特别是,我的问题是:

  • FILE_RENAME_INFO所需的“根目录”是什么?
  • 句柄需要哪些权限?
  • ERROR_INVALID_PARAMETER生成的SetFileInformationByHandle的潜在问题是什么?

2 个答案:

答案 0 :(得分:3)

SetFileInformationByHandle FileRenameInfoFILE_RENAME_INFO的文档包含一些错误。 必须将FILE_RENAME_INFO.FileNameLength设置为复制到FILE_RENAME_INFO.FileName的字符数,不包括终止零,并且FILE_RENAME_INFO.RootDirectory必须为空,即使将文件从一个目录移动到另一个目录也是如此。

#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";
}

答案 1 :(得分:2)

我改变了几个想法:

1)我不使用根句柄(我将其设置为NULL)

2)我改变你的FILE_RENAME_INFO内存分配代码

注意:在Windows 8中检查,在同一卷(磁盘)中移动文件

clone()