Удаление файла на основе идентификатора диска

Как описано здесь, используя SetFileInformationByHandle с FILE_DISPOSITION_INFO позволяет установить файл с открытым дескриптором для удаления при закрытии всех дескрипторов.

Однако я пытаюсь удалить файл на основе его индекса файла (идентификатор диска), полученного с помощью FILE_DISPOSITION_INFO и OpenFileById, чтобы безопасно удалять файлы/каталоги в каталоге, которые отличаются только в случае. Это безопасно в моем случае использования, как в системе NTFS, индексы файлов постоянный до удаления, отрицая использование ReplaceFile, которое обрабатывает текущая кодовая база.

Однако при попытке удалить дескриптор, я получаю ошибку 87 (ERROR_INVALID_PARAMETER). Если я удалю, используя дескриптор, созданный с помощью CreateFileW, я не сталкиваюсь с проблемами. Я не могу этого сделать, поскольку Windows не сможет отличить два файла/папки одного и того же случая, хотя NTFS может.

Я также знаю, что существует двусмысленность с файлами с жесткой привязкой, открытыми с помощью OpenFileById, поскольку файлы с жесткими ссылками имеют один и тот же идентификатор диска. Проблема с жестко привязанными файлами может считаться неактуальной для этого сценария. Я удалю только каталоги по идентификатору, который не может быть привязан к строчке.

Есть ли параметр или параметр, отсутствующий в моем OpenFileById вызов? Как-то в моем SetFileInformationByHandle вызов?

Дополнительные методы, которые я пробовал:

  • Вызов DuplicateHandle с OpenFileById, предоставляя DELETE для dwDesiredAccess и используя это. Тот же результат ERROR_INVALID_PARAMETER.
  • Используя ReOpenFile с OpenFileById, предоставляя DELETE для dwDesiredAccess и используя это. Тот же результат ERROR_INVALID_PARAMETER.
  • Используя ReOpenFile с OpenFileById, предоставляя DELETE для dwDesiredAccess и предоставляя флаг FILE_FLAG_DELETE_ON_CLOSE. Ошибка не указана, но файл остается после закрытия всех дескрипторов.

Вот минимальный, но полный пример, который воспроизводит проблему:

#include <stdio.h>
#include <sys/stat.h>
#include <Windows.h>

DWORD getFileID(LPCWSTR path, LARGE_INTEGER *id)
{
    HANDLE h = CreateFileW(path, 0, 0, 0, OPEN_EXISTING,
        FILE_FLAG_OPEN_REPARSE_POINT |
        FILE_FLAG_BACKUP_SEMANTICS |
        FILE_FLAG_POSIX_SEMANTICS,
        0);
    if (h == INVALID_HANDLE_VALUE)
        return GetLastError();

    BY_HANDLE_FILE_INFORMATION info;
    if (!GetFileInformationByHandle(h, &info))
    {
        DWORD err = GetLastError();
        CloseHandle(h);
        return err;
    }
    id->HighPart = info.nFileIndexHigh;
    id->LowPart = info.nFileIndexLow;
    CloseHandle(h);
    return ERROR_SUCCESS;
}

DWORD deleteFileHandle(HANDLE fileHandle)
{
    FILE_DISPOSITION_INFO info;
    info.DeleteFileW = TRUE;
    if (!SetFileInformationByHandle(
        fileHandle, FileDispositionInfo, &info, sizeof(info)))
    {
        return GetLastError();
    }
    return ERROR_SUCCESS;
}

int wmain(DWORD argc, LPWSTR argv[])
{
    if (argc != 3)
    {
        fwprintf(stderr, L"Arguments: <rootpath> <path>\n");
        return 1;
    }

    DWORD err;
    HANDLE rootHandle = CreateFileW(
        argv[1], 0, 0, 0, OPEN_EXISTING,
        FILE_FLAG_OPEN_REPARSE_POINT |
        FILE_FLAG_BACKUP_SEMANTICS |
        FILE_FLAG_POSIX_SEMANTICS,
        0);
    if (rootHandle == INVALID_HANDLE_VALUE)
    {
        err = GetLastError();
        fwprintf(stderr,
            L"Could not open root directory '%s', error code %d\n",
            argv[1], err);
        return err;
    }

    LARGE_INTEGER fileID;
    err = getFileID(argv[2], &fileID);
    if (err != ERROR_SUCCESS)
    {
        fwprintf(stderr,
            L"Could not get file ID of file/directory '%s', error code %d\n",
            argv[2], err);
        CloseHandle(rootHandle);
        return err;
    }
    fwprintf(stdout,
        L"The file ID of '%s' is %lld\n",
        argv[2], fileID.QuadPart);

    FILE_ID_DESCRIPTOR idStruct;
    idStruct.Type = FileIdType;
    idStruct.FileId = fileID;
    HANDLE fileHandle = OpenFileById(
        rootHandle, &idStruct, DELETE, FILE_SHARE_DELETE, 0,
        FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS);
    if (fileHandle == INVALID_HANDLE_VALUE)
    {
        err = GetLastError();
        CloseHandle(rootHandle);
        fwprintf(stderr,
            L"Could not open file by ID %lld, error code %d\n",
            fileID.QuadPart, err);
        return err;
    }

    err = deleteFileHandle(fileHandle);
    if (err != ERROR_SUCCESS)
    {
        fwprintf(stderr,
            L"Could not delete file by ID '%lld', error code %d\n",
            fileID.QuadPart, err);
    }

    CloseHandle(fileHandle);
    struct _stat _tmp;
    fwprintf(stdout,
        L"File was %ssuccessfully deleted\n",
        (_wstat(argv[2], &_tmp) == 0) ? L"not " : L"");
    CloseHandle(rootHandle);
    return err;
}

Любое решение должно работать с Vista и выше. Предложения по улучшению кода также приветствуются.

Ответ 1

Чтобы сделать работу FILE_DISPOSITION_INFO, вам нужно указать доступ DELETE в функции CreateFile, как указано в https://msdn.microsoft.com/en-us/library/windows/desktop/aa365539(v=VS.85).aspx:

Необходимо указать соответствующие флаги доступа, когда создать файл для использования с SetFileInformationByHandle. Например, если приложение использует FILE_DISPOSITION_INFO с элементом DeleteFile установленный в значение ИСТИНА, для этого файла потребуется DELETE-запрос, запрошенный в вызове к функции CreateFile. Чтобы увидеть пример этого, см. Пример Раздел кода. Дополнительные сведения о разрешениях файлов см. В разделе "Файл". Безопасность и права доступа. То есть.

//...
  HANDLE hFile = CreateFile( TEXT("tempfile"), 
                             GENERIC_READ | GENERIC_WRITE | DELETE,  //Specify DELETE access!
                             0 /* exclusive access */,
                             NULL, 
                             CREATE_ALWAYS,
                             0, 
                             NULL);

Но кажется, что дескриптор, созданный с помощью OpenFileById(), не может использоваться, потому что функция не может принять флаг DELETE.
Из https://msdn.microsoft.com/en-us/library/windows/desktop/aa365432(v=vs.85).aspx в OpenFileById() его можно прочитать: dwDesired

Доступ [в]
   Доступ к объекту. Доступ можно читать, писать или и то, и другое.

Даже при установке DELETE или GENERIC_ALL функция не работает.
Если вы замените дескриптор, переданный на SetFileInformationByHandle, с помощью функции, созданной с помощью функции CreateFile, имеющей флаг DELETE, как указано выше, он работает.

Ответ 2

Существует версия режима пользователя режима ядра ZwCreateFile, называемая NTCreteFile, которая, помимо прочего, даст вам все права доступа, которые вы не можете получить с помощью OpenFileById (но вы можете получить с помощью CreateFile). Он может делать все, что может CreateFile и многое другое. Например, он может даже создавать каталоги.

Хорошая часть - это очень хакерский (но интересный) способ указать идентификатор файла в аргументе POBJECT_ATTRIBUTES, так что вы получите лучшее из всех миров... кроме того, что это еще более неудобный API для вызова, чем ваши неуклюжие API-интерфейсы Windows.

Существует две версии документации. Один из них:

https://msdn.microsoft.com/en-us/library/bb432380(v=vs.85).aspx

и один в:

https://msdn.microsoft.com/en-us/library/windows/hardware/ff556465(v=vs.85).aspx

... который ссылается на документацию ZwCreateFile по адресу:

https://msdn.microsoft.com/en-us/library/windows/hardware/ff566424(v=vs.85).aspx

Причина, по которой я указываю, состоит в том, что в первой статье отсутствуют некоторые полезные свойства (например, открытие файлов по идентификатору), которые задокументированы в последней статье. Я нашел это общепринятым и обнаружил, что большая часть документированной функциональности Zwxxx фактически существует в эквивалентных, но не полностью документированных функциях NTxxx. Поэтому вы должны держать свой рот в порядке, чтобы получить необходимую функциональность.

Ответ 3

Вы изучали FILE_FLAG_POSIX_SEMANTICS? Это позволит вам открывать файлы, которые отличаются только в случае использования CreateFile.

Изменить: я предполагаю, что сначала должен был прочитать ваш код, поскольку вижу, что вы используете указанный флаг.

Ответ 4

Предположим, что файлы XXX и xxx и вы хотите удалить XXX.

  • MoveFile ( "XXX", "Я думаю, что это XXX" )
  • Если XXX получил переименование, то DeleteFile ( "Я думаю, это XXX" )
  • В противном случае DeleteFile ( "XXX" ); MoveFile ( "Я думаю, это XXX", "xxx" )

Что касается OpenFileById, как вы отметили, существует потенциальная двусмысленность с файлом с несколькими именами (ака жестких ссылок). Разрешение доступа к DELETE может привести к хаосу с этим, при этом неожиданное имя будет удалено (если в файловой системе было выбрано какое-либо). Я подозреваю, что они выбрали простой случай, когда не разрешался доступ к DELETE.

Аналогичный аргумент может быть сделан для разрешения жестких ссылок на каталоги. Конечно, вы могли бы сделать это некоторое время правильно, но как только вы создали цикл, все становится намного сложнее...