Transactional NTFS (TxF)

ちょっと別件で調べものしてて偶然 Transactional NTFS (TxF) なるものを発見。この機能を一言で説明すると「各種ファイル操作をトランザクションベースで実行することで各種ファイル操作を一括でのコミット及びロールバックを実現する機能」。

WinFSVista での採用が見送られてガッカリしてたけど、個人的に WinFS に一番期待していた機能が実は Vista には実装されてビックリ。てか、なんでみんなこの素晴らしい機能を華麗にスルーしてんだ? 検索してもロクに情報がヒットしないし。この機能といい、Property System といい、WinFS 自体の採用は見送られたけど、その実 WinFS で実現しようしていた feature はほとんど実装されている!?

あまりにみんなが華麗にスルーしてるんで本当に実装されてんのか心配になってちょっとサンプルコードを書いて確かめてみました。

# コンパイルには Windows SDK があれば十分です(コンパイラ等も付属してます)。

#if !defined(WINVER)
#define WINVER 0x600
#endif

#if !defined(_WIN32_WINNT)
#define _WIN32_WINNT WINVER
#endif

#include <windows.h>
#include <ktmw32.h>
#include <stdlib.h>
#include <iostream>
#include <string>

#pragma comment(lib, "ktmw32.lib")

//////////////////////////////////////////////////////////////////////////////
//
//  Win32 Error
//

class win32_error :public std::runtime_error
{
    DWORD code;
  public:
    win32_error(DWORD X_code = GetLastError())
        :std::runtime_error(win32_error::make_error_message(X_code)), code(X_code) { }
    static const std::string make_error_message(DWORD X_code = GetLastError());
    DWORD get_error_code() const { return code; }
    const char * get_error_message() const { return what(); }
};
const std::string win32_error::make_error_message(DWORD X_code)
{
    LPVOID	lpMsgBuf;
    FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, X_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPSTR)&lpMsgBuf, 0, NULL);
    const std::string result = (const char *)lpMsgBuf;
    LocalFree(lpMsgBuf);
    return result;
}

//////////////////////////////////////////////////////////////////////////////
//
//  command-line interface
//

int main(int argc, char *args[])
{
    argc, args; // 警告避け
    
    try
    {
    
        std::cout << "Command list:" << std::endl;
        std::cout << "\t" << "move src dst" << std::endl;
        std::cout << "\t" << "copy src dst" << std::endl;
        std::cout << "\t" << "del target" << std::endl;
        std::cout << "\t" << "dir" << std::endl;
        std::cout << "\t" << "commit" << std::endl;
        std::cout << "\t" << "rollback" << std::endl;
        std::cout << "\t" << "exit" << std::endl;
        std::cout << std::endl;
        
        HANDLE transaction = INVALID_HANDLE_VALUE;
        
        std::string current_command;
        std::string current_src;
        
        while(false == std::cin.eof())
        {
            if (INVALID_HANDLE_VALUE == transaction)
            {
                transaction = CreateTransaction(NULL, 0, 0, 0, 0, NULL, L"サンプル トランザクション");
                if (INVALID_HANDLE_VALUE == transaction)
                {
                    throw win32_error();
                }
            }
        
            try
            {
                std::string commandline;
                std::cin >> commandline;
                
                if (!current_command.empty())
                {
                    if
                    (
                        "move" == current_command ||
                        "copy" == current_command
                    )
                    {
                        if (current_src.empty())
                        {
                            current_src = commandline;
                        }
                        else
                        {
                            if ("move" == current_command)
                            {
                                if (!MoveFileTransacted(current_src.c_str(), commandline.c_str(), NULL, NULL, 0, transaction))
                                {
                                    throw win32_error();
                                }
                            }
                            else
                            if ("copy" == current_command)
                            {
                                BOOL cancel = FALSE;
                                if (!CopyFileTransacted(current_src.c_str(), commandline.c_str(), NULL, NULL, &cancel, 0, transaction))
                                {
                                    throw win32_error();
                                }
                            }
                            current_command.clear();
                            current_src.clear();
                        }
                    }
                    else
                    if ("del" == current_command)
                    {
                        if (!DeleteFileTransacted(commandline.c_str(), transaction))
                        {
                            throw win32_error();
                        }
                        current_command.clear();
                    }
                }
                else
                if
                (
                    "move" == commandline ||
                    "copy" == commandline ||
                    "del" == commandline
                )
                {
                    current_command = commandline;
                }
                else
                if ("dir" == commandline)
                {
                    WIN32_FIND_DATA find_data = { 0, };
                    HANDLE find_handle = FindFirstFileTransacted("*", FindExInfoStandard, &find_data, FindExSearchNameMatch, NULL, 0, transaction);
                    if (INVALID_HANDLE_VALUE == find_handle)
                    {
                        throw win32_error();
                    }
                    do
                    {
                        std::cout << find_data.cFileName << std::endl;
                    } while(FindNextFile(find_handle, &find_data));
                    FindClose(find_handle);
                }
                else
                if ("commit" == commandline)
                {
                    if (!CommitTransaction(transaction))
                    {
                        throw win32_error();
                    }
                    //  コミットを実行したトランザクションは継続して使えないので破棄
                    CloseHandle(transaction);
                    transaction = INVALID_HANDLE_VALUE;
                }
                else
                if ("rollback" == commandline)
                {
                    if (!RollbackTransaction(transaction))
                    {
                        throw win32_error();
                    }
                    //  ロールバックを実行したトランザクションは継続して使えないので破棄
                    CloseHandle(transaction);
                    transaction = INVALID_HANDLE_VALUE;
                }
                else
                if ("exit" == commandline)
                {
                    break;
                }
                else
                {
                    std::cout << commandline << " is unknown command!" << std::endl;
                }
            }
            catch(const std::exception &e)
            {
                std::cout << e.what() << std::endl;
                current_command.clear();
                current_src.clear();
            }
        }
        if (INVALID_HANDLE_VALUE != transaction)
        {
            CloseHandle(transaction);
        }
    }
    catch(const std::exception &e)
    {
        std::cout << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

実に素晴らしいです、この機能。
ここで使用している以外にも CreateFileTransacted() を始め多種多様な APIトランザクションに対応しているようです。

追記

ファイル関連だけでなくレジストリまわりも RegCreateKeyTransacted() などの API で対応しているようですね。誠に素晴らしい。

追記2

レジストリ関連のは Transactional Registry (TxR) と呼ばれているようです。