Я пытаюсь переместить файл используя SetFileInformationByHandle
, Этот метод был предложен Найлом Дугласом в его выступлении на CppCon2015 «Racing The File System» как способ атомарного перемещения / переименования файла. Однако я изо всех сил пытаюсь предоставить правильные аргументы; это всегда терпит неудачу и GetLastError
возвращается ERROR_INVALID_PARAMETER
,
Я пробовал это со следующими настройками, используя набор символов Unicode:
Но поведение такое же. Я удостоверился, что у меня есть доступ к тестовым папкам и тестовому файлу.
#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
?Документация для SetFileInformationByHandle
с FileRenameInfo
а также FILE_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) я не использую дескриптор корня (я устанавливаю его в NULL)
2) я изменяю ваш код распределения памяти FILE_RENAME_INFO
ПРИМЕЧАНИЕ: проверено в Windows 8, перемещение файла на том же томе (диске)
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";