Introduction
TxF and TxR are another great Vista technology that allows you to manipulate files and the registry in a transacted way, in concert with other transactional operations on the system or the network. The goal of this article is to show some key players and basic approaches in the TxF/TxR world, and also using KTM with the System.Transactions
namespace.
KTM
NTFS and the Registry are now full members in the rich Windows transactional infrastructure. Create, Delete, and Update files and keys with the same atomicity, consistency, isolation, and durability that you get with SQL.
KTM stands for Kernel Transaction Manager. The word kernel, only refers to the fact the KTM's transaction engine is in the kernel. This doesn't imply the transaction can only run in kernel mode (i.e. KTM-transactions can be used in kernel and user mode) nor does it mean the transaction would be machine-local (i.e. the transaction is DTC-able).
On Vista , the KTM is in charge of TxF (Transactional NTFS) and TxR (Transactional Registry).
Advantages of Transactions
Transactions are about ACID: atomicity, consistency, isolation and durability.
- Atomicity: Reduce the number of coding errors in exception handlers.
- Isolation: Consistent view of data even if another thread is updating .
- Consistent: Reduced software induced data corruption.
- Durable: When commit completes, the change will happen even if there is a system failure.
ACID allow us to atomically coordinate file system operations with :
- Other File System Operations
- Registry Operations
- Database Operations
- Read-Committed Isolation
- Ensure Data Consistency
What does TxF/TxR Provides
Create more reliable applications by ensuring atomic operations and consistent file data; Eases test burden by reducing the number of error conditions to test; Easy programming model which allows modification of existing applications with minimal changes; Assist multi-user environments by providing isolation.
How to use TxF
A transacted change is not seen by the other transactions unless the transaction is in progress.
TxF supports the ability to concurrently read a snapshot of a file even while it is locked by a transaction when one or more changes are being made.
When a transacted operation modifies a file, that file is locked for the duration of the transaction for generic write access. No other writers outside of this transaction can access this file. Normal sharing modes apply to all file users.
TxF File Handles
Transactional NTFS (TxF) binds a file handle to a transaction. For operations that work on a handle, the actual function does not change. For file operations that take a name, there are explicit transacted functions for these operations. For example, instead of calling CreateFile, call CreateFileTransacted. This creates a transacted file handle, which can then be used for all file operations requiring a handle. All operations using this handle are transacted operations. After a transaction is completed, any transacted file handles that are associated with that transaction are no longer usable.
Accessing Vista Transaction API
We need to use Interop to access Vista API
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES {
int nLength;
IntPtr lpSecurityDescriptor;
int bInheritHandle;
}
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
[DllImport("Ktmw32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool CommitTransaction(IntPtr transaction);
[DllImport("Ktmw32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool RollbackTransaction(IntPtr transaction);
[DllImport("Ktmw32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr CreateTransaction(
SECURITY_ATTRIBUTES securityAttributes,
IntPtr guid, int options, int isolationLevel, int isolationFlags,
int milliSeconds, string description
);
This small peace of code give us access to Create Transactions using low level windows API.
We still need to create the "Invoke" for the Transacted methods that we want to use. These methods are described in the MSDN site. I've written a small class that implements the managed code interface for the following methods:
CreateFileTransacted
DeleteFileTransacted
CreateHardLinkTransacted
GetCompressedFileSizeTransacted
GetFileAttributesTransacted
SetFileAttributesTransacted
GetLongPathNameTransacted
MoveFileTransacted
CopyFileTransacted
CreateDirectoryTransacted
RemoveDirectoryTransacted
RegDeleteKeyTransacted
RegCreateKeyTransacted
RegOpenKeyTransacted
Using TxF
Basically we need to create a transaction, use a Method (transactional) to perform an operation and finally commit the transaction. In the following peace of code I'll demonstrate the opening of a file in a transactional way.
IntPtr tx = MCKTM.CreateTransaction(new MCKTM.SECURITY_ATTRIBUTES(),
IntPtr.Zero, 0, 0, 0, 0, null);
System.Text.StringBuilder sb = new StringBuilder(300);
Microsoft.Win32.SafeHandles.SafeFileHandle fh =
MCKTM.CreateFileTransacted(@"c:\Test.txt",
System.IO.FileAccess.Read,
System.IO.FileShare.Read,
new MCKTM.SECURITY_ATTRIBUTES(),
System.IO.FileMode.Open, (new MCKTM.EFileAttributes()),
IntPtr.Zero, tx, IntPtr.Zero, IntPtr.Zero);
if (fh != null) {
System.IO.FileStream fs = new System.IO.FileStream(fh,
System.IO.FileAccess.Read);
...
...
fs.Close();
MCKTM.CommitTransaction(tx);
} else {
MCKTM.RollbackTransaction(tx);
}
MCKTM.CloseHandle(tx);
Using TxR
Using Transactional Registry Operations is quite similar to TxF.
...
string key1 = "RegKey_01";
...
IntPtr tx = MCKTM.CreateTransaction(new MCKTM.SECURITY_ATTRIBUTES(),
IntPtr.Zero, 0, 0, 0, 0, null);
bool rollback = false;
if (MCKTM.RegDeleteKeyTransacted(MCKTM.HKEY_CURRENT_USER, key1,
MCKTM.RegSam.WOW64_32Key, 0, tx, IntPtr.Zero) != 0)
rollback = true;
if (!rollback)
MCKTM.CommitTransaction(tx);
else
MCKTM.RollbackTransaction(tx);
MCKTM.CloseHandle(tx);
Using System.Transaction with DTC.
What's the difference between using KTM and DTC ?
If you start a transaction in .Net and work on a file in NTFS in that transaction you don't pay for the DTC overhead (process switch). DTC automatically gets enlisted in the transaction if non-kernel resources like SQL get included in the transaction.
This happens seamlessly from the application's point of view. KTM is a fairly lightweight transaction manager and is designed to serve the needs of the kernel resources directly, as well as interface with DTC for broad transaction support.
Ok, so you saw how to interact with the KTM in a very low level way through interop. Basically, we did deal with the transaction directly instead of relying on the System.Transactions namespace. Now I'll demonstrated how to do a transactional file delete, but this time using System.Transactions
and the Distributed Transaction Coordinator (DTC).
Basically we want to :
using (TransactionScope ts = new TransactionScope()) {
DeleteFile(file1);
DeleteFile(file2);
tx.Complete();
}
Remember, this is useful because we might want to make a transaction spanning across TxF ... for instance, SQL Server database operations...
- Create some
DeleteFile
method - In that method, see whether we are in a transaction scope or not (
using Transaction.Current
). - If a transaction is in flight, we need to derive the KTM transaction handle from it (which is the most tricky part) and use it to call the transacted file delete function.
- Call the
DeleteFile
method inside a TransactionScope
to make it transactional.
Lets write the DeleteFile
(transacted) :
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("79427A2B-F895-40e0-BE79-B57DC82ED231")]
internal interface IKernelTransaction {
void GetHandle([Out] out IntPtr handle);
}
void DeleteFile(string file) {
if (Transaction.Current != null) {
IKernelTransaction tx =
(IKernelTransaction)TransactionInterop.GetDtcTransaction(
Transaction.Current);
IntPtr txh;
tx.GetHandle(out txh);
if (txh == IntPtr.Zero)
throw new Exception();
if (!DeleteFileTransacted(file, txh))
throw new Exception();
CloseHandle(txh);
} else {
File.Delete(file);
}
}
I've written a wrapper called GetTransactionFromDTC()
that substitutes the (IKernelTransaction).
All you need to do is :
void DeleteFile(string file) {
IntPtr tx = MCKTM.GetTransactionFromDTC();
if (tx!=IntPtr.Zero) {
if (!DeleteFileTransacted(file, txh))
throw new Exception();
CloseHandle(txh);
} else {
File.Delete(file);
}
}