Transactional NTFS (part 1/2)

Transactional NTFS and DotNet

Starting from Windows Vista and Windows Server 2008, Microsoft introduced a great new feature called Transactional NTFS (TxF).

It allows developers to write file I/O functions that are guaranteed to either succeed completely or fail completely.
Unfortunately there are no classes in the .NET framework that would allows us to perform, such operations.
We need to resort to P/Invoke to use the newly introduced functions CreateTransaction, CommitTransaction, RollbackTransaction and DeleteFileTransacted.

class Win32Native
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            int nLength;
            IntPtr lpSecurityDescriptor;
            int bInheritHandle;
        }

        [DllImport("ktmw32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern SafeFileHandle CreateTransaction(SECURITY_ATTRIBUTES securityAttributes, IntPtr guid, int options, int isolationLevel, int isolationFlags, int milliSeconds, string description);

        [DllImport("ktmw32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool CommitTransaction(SafeFileHandle transaction);

        [DllImport("ktmw32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool RollbackTransaction(SafeFileHandle transaction);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool DeleteFileTransacted(string filename, SafeFileHandle transaction);
    }

Note that I am using SafeFileHandle in method signatures instead of IntPtr to hold unmanaged resources which guarantees that they will be properly disposed even if the AppDomain was to stop.

The next thing is to define a helper class which would allow us to easily invoke those functions:

public class FileManager : IDisposable
    {
        private bool _commited = false;
        private SafeFileHandle _tx = null;

        public FileManager()
        {
            _tx = Win32Native.CreateTransaction(new Win32Native.SECURITY_ATTRIBUTES(), IntPtr.Zero, 0, 0, 0, 0, null);
        }

        public bool DeleteFile(string filename)
        {
            return Win32Native.DeleteFileTransacted(filename, _tx);
        }

        public void Commit()
        {
            if (Win32Native.CommitTransaction(_tx))
                _commited = true;
        }

        private void Rollback()
        {
            Win32Native.RollbackTransaction(_tx);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (!_commited)
                {
                    Rollback();
                }
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }
    }

Sample with delete two files

Now suppose that you need to atomically delete two files: either both files are deleted or neither of them, but you never want to have only one of the two files deleted:

using (FileManager manager = new FileManager())
    {
        manager.DeleteFile("file1.txt");
        Console.WriteLine("file1.txt is marked for deletion in current transaction. Press Enter...");
        Console.ReadLine();

        //throw new Exception("something very bad happens here");

        manager.DeleteFile("file2.txt");
        Console.WriteLine("file2.txt is marked for deletion in current transaction.");

        manager.Commit();
    }

The method DeleteFile marks the file for deletion in the current transaction, but it will physically delete it only when the Commit method is called. Thanks to TxF and distributed transactions file and SQL operations can be performed inside the same transaction.

In part 2 we will see how to use the CreateFileTransacted function to perform atomic file read/writes.

Leave a comment

Your email address will not be published. Required fields are marked *