potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

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);
}