2016-04-06 3 views
4

Я пытаюсь переместить файл, используя SetFileInformationByHandle. Этот метод был предложен Ниалом Дугласом в его разговоре CppCon2015 «Racing The File System» как способ атомарного перемещения/переименования файла. Однако я изо всех сил пытаюсь дать правильные аргументы; он всегда терпит неудачу и GetLastError возвращает ERROR_INVALID_PARAMETER.Перемещение файла с помощью SetFileInformationByHandle

Я попытался это с помощью следующих установок, с помощью Unicode Character Set:

  • VS2015U1, запуск ехе под Windows 10
  • VS2015U2, запуск ехе под управлением Windows Server 2012
  • VS2013 , запуск exe под Windows 7

Но поведение такое же. Я обязательно получил доступ к тестовым папкам и тестовому файлу.

#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?
+0

Те же проблемы, как [этот] (http://stackoverflow.com/questions/36217150/deleting-a-file-based-on-disk-id). –

+0

@HansPassant Ну, у меня не возникло проблем с использованием файлов 'SetFileInformationByHandle' для * удаления *. Однако я не могу использовать его для * перемещения * файлов. Кроме того, я не использую 'OpenFileById'. Не могли бы вы быть более конкретными относительно того, что сходство между вопросом, которое у меня есть, и вопросами/ответами, с которыми вы связались? – dyp

+1

Он ведет себя точно так же, удалите DELETE, чтобы увидеть это. Я предполагаю, что это особая проблема Win10, кто-то должен поговорить с MSFT об этом. Я буду добровольцем. –

ответ

2

меняю пару думает:

1) я не использую корневой дескриптор (я установить его в NULL)

2) я изменить свой FILE_RENAME_INFO код выделения памяти

ПРИМЕЧАНИЕ: проверил в окнах 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"; 
+0

Спасибо за ваш ответ. Я проверил его сейчас под Windows 10, и он работает; тесты на другие операционные системы должны будут ждать до понедельника. Я предполагаю, что ** не ** с использованием корневого дескриптора будет последней вещью, которую я пробовал ... В моем исходном исходном коде достаточно удалить только ручку root, чтобы она работала. «FileNameLength», кажется, игнорируется (также может быть установлено в 0). Для 'f_handle' требуется только право' DELETE'. – dyp

+1

Да, DELETE - это только флаг, необходимый при создании дескриптора файла, вы можете увидеть его по адресу: https://msdn.microsoft.com/en-us/library/windows/hardware/ff540344%28v=vs.85%29. aspx –

+0

Я не могу найти причину, по которой RootDirectory должен быть NULL, но я думаю: он работает в режиме ядра или потому, что FILE_RENAME_INFO является общим для SetFileInformationByHandle, NtSetInformationFile и ZwSetInformationFile, возможно, он имеет смысл использовать RootDirectory! = NULL в других функциях –

3

документация SetFileInformationByHandle с FileRenameInfo и FILE_RENAME_INFO содержит некоторые ошибки. должно быть установлено на количество символов, скопированных в FILE_RENAME_INFO.FileName, за исключением нулевого нуля, а FILE_RENAME_INFO.RootDirectory должно быть равно null, даже если перемещение файла из одного каталога в другой.

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

Спасибо за ваш ответ. Как вы определили, что документация содержит ошибки? По опыту, или у вас есть другой источник? – dyp

+0

[На другом сайте] (http://www.codeproject.com/Members/Espen-Harlinn) У меня немного другое ранжирование - и я думаю, вы обнаружите, что у меня есть некоторый опыт, связанный с Windows API. Совершенно очевидно, что что-то не совсем верно в отношении документации по использованию SetFileInformationByHandle и FILE_RENAME_INFO. Член FileName FILE_RENAME_INFO основан на Unicode, поэтому было естественным попробовать количество символов Unicode. –

+0

Ну, из моих тестов кажется, что элемент 'FileNameLength' полностью игнорируется, дескриптор' RootDirectory' кажется виновником. Мне было интересно, потому что я хотел бы узнать об альтернативных источниках информации о Windows API, иногда MSDN недостаточно (как здесь). Это ваш первый ответ на StackOverflow, и через час после ответа Джерардо (содержащего в основном те же подсказки) я сделал несколько подозрительных, я должен признать: – dyp