potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

C++20&WinAPI DeviceIoControl(FSCTL_FILESYSTEM_GET_STATISTICS_EX)のラッパークラス

C++20におけるWinAPIのDeviceIoControlFSCTL_FILESYSTEM_GET_STATISTICS_EX)のラッパークラスです。ファイルは本体であるDeviceIoControlForFileSystemStatisticsExを定義するDeviceIoControlForFileSystemStatisticsEx.hpp、再利用可能そうなコードを集めたUtility.hpp、プログラムの開始関数を含むmain.cppに分割しています。

DeviceIoControlForFileSystemStatisticsEx.hpp

#pragma once

#include <system_error>
#include <span>
#include <vector>

class DeviceIoControlForFileSystemStatisticsEx
{
public:
    DeviceIoControlForFileSystemStatisticsEx(HANDLE hFile)
    {
        if (hFile == INVALID_HANDLE_VALUE)
        {
            throw_system_error();
        }

        data.resize(sizeof(FILESYSTEM_STATISTICS_EX));
        DWORD bytes_returned;
        // data.size()はDWORDの範囲に収まるため、static_castを使用。
        if (!::DeviceIoControl(hFile, FSCTL_FILESYSTEM_GET_STATISTICS_EX,
            nullptr, 0, data.data(), static_cast<DWORD>(data.size()), &bytes_returned, nullptr))
        {
            if (::GetLastError() != ERROR_MORE_DATA)
            {
                data.clear();
                throw_system_error();
            }
        }

        data.resize(reinterpret_cast<FILESYSTEM_STATISTICS_EX*>(data.data())->SizeOfCompleteStructure);
        if (!::DeviceIoControl(hFile, FSCTL_FILESYSTEM_GET_STATISTICS_EX,
            nullptr, 0, data.data(), static_cast<DWORD>(data.size()), &bytes_returned, nullptr))
        {
            data.clear();
            throw_system_error();
        }
    }

    std::span<const std::byte> data_complete() const noexcept
    {
        return std::span(data);
    }

    std::span<const std::byte> data_specified() const noexcept
    {
        return std::span<const std::byte>(
            std::next(data.cbegin(), sizeof(FILESYSTEM_STATISTICS_EX)),
            data.cend());
    }

    const FILESYSTEM_STATISTICS_EX& filesystem_stats_ex() const noexcept
    {
        return *reinterpret_cast<const FILESYSTEM_STATISTICS_EX*>(data.data());
    }

    DWORD size_complete() const noexcept
    {
        return filesystem_stats_ex().SizeOfCompleteStructure;
    }

    DWORD size_specified() const noexcept
    {
        return filesystem_stats_ex().SizeOfCompleteStructure - sizeof(FILESYSTEM_STATISTICS_EX);
    }

private:
    template <DWORD TYPE, typename STRUCT>
    size_t specific_stats_size() const noexcept
    {
        const auto& fs_stats = filesystem_stats_ex();
        if (fs_stats.FileSystemType != TYPE)
        {
            return 0;
        }
        return size_specified() / sizeof(STRUCT);
    }

    static void throw_system_error(int code = GetLastError())
    {
        throw std::system_error(code, std::system_category());
    }
public:
    size_t ntfs_stats_ex_size() const noexcept
    {
        return specific_stats_size<FILESYSTEM_STATISTICS_TYPE_NTFS, NTFS_STATISTICS_EX>();
    }
    size_t fat_stats_size() const noexcept
    {
        return specific_stats_size<FILESYSTEM_STATISTICS_TYPE_FAT, FAT_STATISTICS>();
    }
    size_t exfat_stats_size() const noexcept
    {
        return specific_stats_size<FILESYSTEM_STATISTICS_TYPE_EXFAT, EXFAT_STATISTICS>();
    }

private:
    template <typename STRUCT>
    std::span<const STRUCT> specific_stats_ex(size_t size) const noexcept
    {
        auto begin = reinterpret_cast<const STRUCT*>(data_specified().data());
        return std::span<const STRUCT>(begin, size);
    }
public:
    std::span<const NTFS_STATISTICS_EX> ntfs_stats_ex() const noexcept
    {
        return specific_stats_ex<NTFS_STATISTICS_EX>(ntfs_stats_ex_size());
    }
    std::span<const FAT_STATISTICS> fat_stats() const noexcept
    {
        return specific_stats_ex<FAT_STATISTICS>(fat_stats_size());
    }
    std::span<const EXFAT_STATISTICS> exfat_stats() const noexcept
    {
        return specific_stats_ex<EXFAT_STATISTICS>(exfat_stats_size());
    }

private:
    std::vector<std::byte> data;

    // デフォルト・コピー・ムーブコンストラクタを許可しない。
    DeviceIoControlForFileSystemStatisticsEx() = delete;
    DeviceIoControlForFileSystemStatisticsEx(const DeviceIoControlForFileSystemStatisticsEx&) = delete;
    DeviceIoControlForFileSystemStatisticsEx(DeviceIoControlForFileSystemStatisticsEx&&) = delete;
};

main.cpp

#define STRICT
#define NOMINMAX
#include <Windows.h>

#include "DeviceIoControlForFileSystemStatisticsEx.hpp"
#include "Utility.hpp"

int main()
{
    for (wchar_t letter : GetLogicalDriveLetters())
    {
        auto file_handle = CreateFileForVolume(letter);
        if (file_handle.get() == INVALID_HANDLE_VALUE)
        {
            continue;
        }

        auto fs_stats = DeviceIoControlForFileSystemStatisticsEx(file_handle.get());
        auto ntfs_stats = fs_stats.ntfs_stats_ex();
        auto fat_stats = fs_stats.fat_stats();
        auto exfat_stats = fs_stats.exfat_stats();
    }
}

Utility.hpp

#pragma once

#include <iostream>
#include <memory>

using namespace std;

using unique_file_handle = unique_ptr<remove_pointer_t<HANDLE>, decltype(&CloseHandle)>;

inline auto make_unique_file_handle(HANDLE handle) noexcept
{
    return unique_file_handle(handle, &CloseHandle);
}

unique_file_handle CreateFileForVolume(
    wchar_t letter,
    DWORD desired_access = GENERIC_READ,
    DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
    noexcept
{
    wchar_t path[3]{ letter, L':', L'\0' };
    return make_unique_file_handle(::CreateFileW(
        path,
        desired_access,
        share_mode,
        nullptr,
        OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS,
        nullptr));
}

template <bool UpperCase = true>
vector<wchar_t> GetLogicalDriveLetters()
{
    vector<wchar_t> letters;
    DWORD dw = ::GetLogicalDrives();
    for (DWORD i = 0; i < 32; i++)
    {
        if (dw & (1 << i))
        {
            if constexpr (UpperCase)
            {
                letters.emplace_back(static_cast<wchar_t>(L'A' + i));
            }
            else
            {
                letters.emplace_back(static_cast<wchar_t>(L'a' + i));
            }
        }
    }
    return move(letters);
}