TxF (Transactional NTFS) を使って RDBMS, MSMQ,,, と相互運用させる (RTM 版でばっちり動作します!)

環境:
Windows Vista
Visual Studio 2005

(In this post, English follows)
============================

こんにちは。
サイクリングもできず少し運動不足ですが、ようやく Redmond, Seattle 出張から帰ってきました。

いろいろ収穫の多い一週間でしたが、もっとも収穫が大きかったのはこのネタです。(昨年11月の Community Ring 参加者の方は必見です!)

結論から記述すると、以前 RC 版でご紹介した TxF のネタ ですが、この際にできなかったカーネルトランザクションの DTC への Enlist が、RTM 版では完璧に動作します!
実際のビジネスでの活用を考えると、これが出来るか出来ないかは、大変大きな違いですね。SharePoint のように、データベースに登録して、さらにファイルも登録するようなアプリケーションを作る際に、一貫性を保持するための処理やバッチを構築された経験のある方も居られるかもしれませんが、もうこのようなことは TxF が全部やってくれるわけです。(ちなみに、SharePoint はファイルを SQL Server に登録していますが。)

ここでご紹介する内容ですが、Microsoft (U.S.) のテクニカルエバンジェリストで、TxF チームとも近しく活動されている Jason Olson さんより手取り足取り教授してもらいました。是非皆さんにフィードバックさせて頂きます。(実は私自身 RTM 版でうまく動かせなかったので、Community Ring のソースコードまでチェックして頂きました。Olson さん、ありがとうございます!)
より詳しい情報を知りたい方は、是非 Olson さんのブログ(以下)を覗いてみてください。(ビデオキャストなどもあるようです。)

http://blogs.msdn.com/jolson/

http://www.managed-world.com/

では、ここでは簡単ではありますが、とっかかりとして、その方法をご紹介します。
尚、この内容は、RTM 版でないとちゃんと動作しませんので、ご注意ください。(もう RC 版を持っている人は少ないかもしれませんが、、、)

まず、ソースコードとしては、以前 (RC版) でご紹介したときと変わりません。はじめて TxF のコードを見る方のために、再掲しておきます。ここでは TranHandle というクラスを作成しています。(尚、このサンプルでは、39行目、55行目、57行目に接続先のリソースのパス情報などを埋め込んでしまっていますので、適宜変更してください。)

[Sample Source]

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Transactions;

namespace TxFProject
{
    public class TranHandle
    {
        const ulong WRITE_DAC = (0x00040000L);
        const ulong WRITE_OWNER = (0x00080000L);
        const UInt32 FILE_SHARE_READ = 0x00000001;
        const UInt32 FILE_SHARE_WRITE = 0x00000002;
        const UInt32 FILE_SHARE_DELETE = 0x00000004;
        const UInt32 FILE_ATTRIBUTE_NORMAL = 0x00000080;
        const int CREATE_NEW = 1;
        const int CREATE_ALWAYS = 2;

        public void FireTrans(bool isCommited)
        {
            // Win32 CreateTransaction set timeout to be infinite.
            // So, I will do the same.
            using (TransactionScope s = new TransactionScope(TransactionScopeOption.Required, new TimeSpan(0)))
            {
                SafeTransactionHandle txHandle;
                IKernelTransaction kernelTx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(Transaction.Current);
                kernelTx.GetHandle(out txHandle);

                // Update Win32 file 1
                #region NTFS
                SafeFileHandle fileHandle1 = CreateFileTransacted(@”C:workhello.txt”,
                    (int)WRITE_DAC,
                    (int)(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
                    System.IntPtr.Zero,
                    CREATE_ALWAYS,
                    (int)FILE_ATTRIBUTE_NORMAL,
                    new SafeFileHandle(System.IntPtr.Zero, false),
                    txHandle,
                    System.IntPtr.Zero,
                    System.IntPtr.Zero);
                if(fileHandle1.IsInvalid)
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                #endregion

                // Update database
                #region DBMS
                SqlConnection conDB = new SqlConnection(@”Data Source=tsmatsuz31SQLEXPRESS;Integrated Security=SSPI;Initial Catalog=HOL_DB”);
                conDB.Open();
                SqlCommand cmdSql = new SqlCommand(“insert into TestTbl (ID, Name) values (1, ‘Test Data’)”, conDB);
                cmdSql.ExecuteNonQuery();
                conDB.Close();
                #endregion

                if (isCommited)
                    s.Complete();
                else
                    s.Dispose();

            }
        }

        [DllImport(“Kernel32.Dll”,
               CallingConvention = CallingConvention.StdCall,
               CharSet = CharSet.Unicode)]
        internal static extern SafeFileHandle CreateFileTransacted(
            String lpFileName,
            int dwDesiredAccess,
            int dwShareMode,
            IntPtr lpSecurityAttributes,
            int dwCreationDisposition,
            int dwFlagsAndAttributes,
            SafeFileHandle hTemplateFile,
            SafeTransactionHandle txHandle,
            IntPtr miniVersion,
            IntPtr extendedOpenInformation);

        [ComImport]
        [Guid(“79427A2B-F895-40e0-BE79-B57DC82ED231”)]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IKernelTransaction
        {
            void GetHandle(out SafeTransactionHandle ktmHandle);
        }

    }

    [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
    public sealed class SafeTransactionHandle : SafeHandleZeroOrMinusOneIsInvalid
    {

        private SafeTransactionHandle()
            : base(true)
        {
        }

        public SafeTransactionHandle(IntPtr preexistingHandle, bool ownsHandle)
            : base(ownsHandle)
        {
            SetHandle(preexistingHandle);
        }

        [DllImport(“Kernel32.dll”, SetLastError = true)]
        [ResourceExposure(ResourceScope.Machine)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        private static extern bool CloseHandle(IntPtr handle);

        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        override protected bool ReleaseHandle()
        {
            return CloseHandle(handle);
        }

    }

}

で、あとは、このクラスを呼び出すコードを記述して実行すればOKです。
ちなみに、Visual Studio は、管理者モードで開いておきましょう。

TranHandle obj = new TranHandle();
obj.FireTrans(true);

と、ここまでは以前と何も変わらないように思いますが、動かなかった方は、ここで注意が必要です。
実は、セキュリティ上の理由に依り、システムドライブを使っている場合にはそのままでは動作しないように設計されているそうです。もう少しちゃんと説明しましょう。カーネルトランザクションは、KTM リソースマネージャと呼ばれるもので管理されていて、NTFS のドライブにはそれぞれ管理しているリソースマネージャが存在しています。システムドライブの場合を想像してみてください。当然、そのリソースマネージャを起動するのは OS ですが、リソースマネージャを起動する以前に、その OS を起動するためにファイルの読み/書きが発生します。
にわとりと卵ですが、要は、この隙をついてマルウェアが進入してしまうことを防ぐため、このような仕様になっているのです。

では、システムドライブしかない場合には、どうするか? 別のリソースマネージャ(セカンダリリソースマネージャ)を作成して、そこでトランザクション (TxF) を管理させるようにします。
このサンプルコードの場合、管理者権限で、以下のコマンドを実行します。

fsutil resource create c:work
fsutil resource start c:work

これで実行すれば、DTC に参画した TxF を動かすことができます! 

さらに夢は膨らむのですが、「じゃあ次に、WCF で採用されている WS-AtomicTransaction と連携できないだろうか?」 と思いますよね。(WS-AT は DTC と相互運用できます。)
勿論、Vista の段階で可能です! これから私も動かしたいと思いますが、Olson さんにデモを見せてもらいました。(これら WS-AT と TxF を組み合わせれば、リモートサーバ上のファイル操作に対してもトランザクションのコミット/ロールバックが可能です。)
ここでは詳細を記載しませんでしたが、SMB2 プロトコル もいけるそうです。

なんとパワフル!

ここでは簡単なとっかかりの紹介でしたが、USB での利用、IsolationLevel など、是非いろいろ試してみてください。

======= English from here =======

Hello, Now I returned my home from business trip (Redmond and Seattle).

I got much technical knowledge in this business trip, but this TxF is most useful information !

I introduced TxF using RC version (here), and TxF and DTC conflicted at that time. But using RTM, this works very well !
In actual business senario, this functionality is very useful and powerful, because if you handle both database and files (for example, like SharePoint), you can keep these consistency. It’ so useful. (Note: however SharePoint uses SQL Server for file storage …)

Now I explain about TxF and DTC, but I got much, much and much help from Jason Olson (Technical evangelist, Microsoft (U.S.). he is working closely with TxF team.).
For more information, please check his blogs (It include a lot of webcast.)

http://blogs.msdn.com/jolson/

http://www.managed-world.com/

(Thank you, Jason Olson-san, to give me much advise (for Japanese developers) .)

Now let’s start TxF with DTC ! (This sample works with Windows Vista RTM. If you have RC1, please promote to RTM version.)

I have already introduced my very simple source code example, but I attach it (below) again.
This implements a class named “TranHandle”. (This sample is not separated filename, connection string, and others. Please change the value of line 39, 55 and 57. Sorry…)

[Sample Source]

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Transactions;

namespace TxFProject
{
    public class TranHandle
    {
        const ulong WRITE_DAC = (0x00040000L);
        const ulong WRITE_OWNER = (0x00080000L);
        const UInt32 FILE_SHARE_READ = 0x00000001;
        const UInt32 FILE_SHARE_WRITE = 0x00000002;
        const UInt32 FILE_SHARE_DELETE = 0x00000004;
        const UInt32 FILE_ATTRIBUTE_NORMAL = 0x00000080;
        const int CREATE_NEW = 1;
        const int CREATE_ALWAYS = 2;

        public void FireTrans(bool isCommited)
        {
            // Win32 CreateTransaction set timeout to be infinite.
            // So, I will do the same.
            using (TransactionScope s = new TransactionScope(TransactionScopeOption.Required, new TimeSpan(0)))
            {
                SafeTransactionHandle txHandle;
                IKernelTransaction kernelTx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(Transaction.Current);
                kernelTx.GetHandle(out txHandle);

                // Update Win32 file 1
                #region NTFS
                SafeFileHandle fileHandle1 = CreateFileTransacted(@”C:workhello.txt”,
                    (int)WRITE_DAC,
                    (int)(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
                    System.IntPtr.Zero,
                    CREATE_ALWAYS,
                    (int)FILE_ATTRIBUTE_NORMAL,
                    new SafeFileHandle(System.IntPtr.Zero, false),
                    txHandle,
                    System.IntPtr.Zero,
                    System.IntPtr.Zero);
                if(fileHandle1.IsInvalid)
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                #endregion

                // Update database
                #region DBMS
                SqlConnection conDB = new SqlConnection(@”Data Source=tsmatsuz31SQLEXPRESS;Integrated Security=SSPI;Initial Catalog=HOL_DB”);
                conDB.Open();
                SqlCommand cmdSql = new SqlCommand(“insert into TestTbl (ID, Name) values (1, ‘Test Data’)”, conDB);
                cmdSql.ExecuteNonQuery();
                conDB.Close();
                #endregion

                if (isCommited)
                    s.Complete();
                else
                    s.Dispose();

            }
        }

        [DllImport(“Kernel32.Dll”,
               CallingConvention = CallingConvention.StdCall,
               CharSet = CharSet.Unicode)]
        internal static extern SafeFileHandle CreateFileTransacted(
            String lpFileName,
            int dwDesiredAccess,
            int dwShareMode,
            IntPtr lpSecurityAttributes,
            int dwCreationDisposition,
            int dwFlagsAndAttributes,
            SafeFileHandle hTemplateFile,
            SafeTransactionHandle txHandle,
            IntPtr miniVersion,
            IntPtr extendedOpenInformation);

        [ComImport]
        [Guid(“79427A2B-F895-40e0-BE79-B57DC82ED231”)]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IKernelTransaction
        {
            void GetHandle(out SafeTransactionHandle ktmHandle);
        }

    }

    [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
    public sealed class SafeTransactionHandle : SafeHandleZeroOrMinusOneIsInvalid
    {

        private SafeTransactionHandle()
            : base(true)
        {
        }

        public SafeTransactionHandle(IntPtr preexistingHandle, bool ownsHandle)
            : base(ownsHandle)
        {
            SetHandle(preexistingHandle);
        }

        [DllImport(“Kernel32.dll”, SetLastError = true)]
        [ResourceExposure(ResourceScope.Machine)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        private static extern bool CloseHandle(IntPtr handle);

        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        override protected bool ReleaseHandle()
        {
            return CloseHandle(handle);
        }

    }

}

Now let’s call “FireTrans” method in your application and try. (Of cource, excute Visual Studio as Administrator mode.)

TranHandle obj = new TranHandle();
obj.FireTrans(true);

Does it work ? If error occured, please relax, there’s some reason.
If you are using system drive (almost like C:), you need to use a secondary Resource Manager for security reason. The Resource Manager could be launched by the operating system, but the operating system must read system drive’s files in execution. Some malware or virus could attach this execution interval using DTC. This is the reason that the secondary RM is needed for security.
So let’s create the secondary Resource Manager using fsutil command,

fsutil resource create c:work
fsutil resource start c:work

You completed !

Moreover, you can use TxF + WS-AT (WS-AtomicTransaction, you can use in WCF, NETFX3.), because WS-AT can communicate with DTC. So you can handle remote NTFS transaction, that’s so powerful ! (Olson-san showed me a demo.)
And SMB2 protocol (see Windows Server blog), also OK. You can work with TxF, SQL, MSMQ, WS-AT, SMB2, …, so fantastic !

This blog post introduces such a simple example, but please try about TxF Isolation Level, or TxF using with USB, or other things in Windows Vista. 

Thanks

 

Advertisements

One thought on “TxF (Transactional NTFS) を使って RDBMS, MSMQ,,, と相互運用させる (RTM 版でばっちり動作します!)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s