Introducing the Windows Kernel Transaction Manager, Transactional NTFS and Transactional Registry

Introduction

Windows Vista and later versions of Windows have a so called Kernel Transaction Manager (KTM). This makes it easy for application to use transactions. There are two big components built on top of the KTM: Transactional NTFS (TxF) and Transactional Registry (TxR). With TxF you can combine several file system operations into a transaction and either commit or rollback the transaction as a whole. TxR allows you to do the same thing with registry manipulations. File system transactions and registry transactions can be grouped into a single transaction. That way we can ensure that all file system modifications and all registry modifications are fully executed or none at all.

This article will give a very brief overview of TxF and TxR to give you an idea of what you can accomplish with it. Creating your own transaction manager other than TxF or TxR is also possible but requires you to create a Win32 transaction aware service (= resource manager) which is outside the scope of this introductory article.

What is a Transaction?

A transaction combines a number of operations into one block. When a transaction is committed, all operations in the transaction block are completed successfully. If a transaction is rolled back at any point, none of the operations inside the transaction block are executed. A transaction satisfies the so called ACID properties: Atomic, Consistent, Isolated and Durable.

  • Atomic: all operations in a transaction block are executed or none of them
  • Consistent: if the state was consistent before the transaction started, it should still be consistent after the transaction is finished
  • Isolated: changes made during a transaction are not visible to anything outside the transaction until the transaction is finished
  • Durable: once a transaction is committed, all changes are on non-volatile storage so they remain even after a system crash.

For example, suppose that your application needs to write some information to three separate files and that it requires that all changes are correctly made to all those files. If something goes wrong while updating the third file for example, your application will have to undo all the changes it made to the first two files. This means that your application will need to keep track of everything it did and needs rigorous error handling to be able to undo changes in case of any errors. Transactions make this much easier for the application developer. In this case a transaction can be used with TxF. In case something goes wrong while modifying the third file, your application simply asks the transaction to rollback and the system will automatically undo the changes the application made to the first and second files.

Note that it’s not a good idea to use transactional file operations all the time. Consider again the above example of modifying three files. Now suppose you have one additional file which is an error log. When something goes wrong during your file operations, the application will log an error to this error log. It should be clear that you do not want to include writing this error message to the error log in your transaction because if you do and you rollback your transaction the error message will disappear from that error log file.

Kernel Transaction Manager Basics

The Kernel Transaction Manager (KTM) is the main transaction engine of the operating system. This transaction engine is living in the kernel itself, but individual transaction can be either kernel-mode or user-mode. Transactions can even span multiple hosts by using a distributed transaction coordinator (DTC) but that falls outside the scope of this article.

The KTM consists of quite a few functions, but the three most important functions that you will use to work with transactions are as follows:

  • CreateTransaction: create a new transaction and return a handle to it.
  • RollbackTransaction: abort a transaction and rollback any changes made so the state is exactly the same as before the transaction started.
  • CommitTransaction: commit all the changes in a transaction and make them persistent.

Transactional NTFS (TxF) and Transactional Registry (TxR) are both built on top of the KTM and are discussed in the following sections.

Using Transactional NTFS (TxF)

This section will introduce you to Transactional NTFS (TxF) to give you an idea of its power. Before you can use the transaction engine, you need to include the following header file:

  #include <ktmw32.h>

You also need to link with the ktmw32 library. You can do this with the following pragma:

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

Now we can start using TxF. First we need to start a new transaction using the KTM functions of the previous section:

  HANDLE hTrans = CreateTransaction(NULL,0, 0, 0, 0, NULL, _T("My NTFS Transaction"));
  if (hTrans == INVALID_HANDLE_VALUE)
  {
  	cerr << "CreateTransaction failed" << endl;
  	return 1;
  }

The last argument is a string for which you can specify whatever you like. Later on, you can retrieve that string from a transaction handle by using GetTransactionInformation.

A transaction is now ready to be used. Transactional NTFS uses functions that you are familiar with but adds Transacted to the end of the function name. The following transacted functions are available:

  • CopyFileTransacted
  • CreateDirectoryTransacted
  • CreateFileTransacted
  • CreateHardLinkTransacted
  • CreateSymbolicLinkTransacted
  • DeleteFileTransacted
  • FindFirstFileNameTransactedW
  • FindFirstFileTransacted
  • FindFirstStreamTransactedW
  • GetCompressedFileSizeTransacted
  • GetFileAttributesTransacted
  • GetFullPathNameTransacted
  • GetLongPathNameTransacted
  • MoveFileTransacted
  • RemoveDirectoryTransacted
  • SetFileAttributesTransacted

Let’s use the CreateFileTransacted as example.

  USHORT view = 0xFFFE; // TXFS_MINIVERSION_DEFAULT_VIEW
  HANDLE hFile = CreateFileTransacted(_T("test.file"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL, hTrans, &view, NULL);
  if (hFile == INVALID_HANDLE_VALUE)
  {
  	cerr << "CreateFileTransacted failed" << endl;
  	return 1;
  }

After the file has been created in the context of our transaction, we can start writing to the file. The following code writes a simple string to the file and closes the file.

  DWORD dwWritten = 0;
  string str = "Test String";
  WriteFile(hFile, str.c_str(), str.length(), &dwWritten, NULL);
  CloseHandle(hFile);

After the above code, in a non-transacted context, the file “test.file” would be visible in the file system immediately. However, the above code is inside a transaction. When you open Windows Explorer and try to find our “test.file” file, you will not be able to find it yet because the transaction is still in progress. Note that there is no special WriteFile function like WriteFileTransacted or something like that. The handle hFile is a transacted file handle so WriteFile knows that it’s inside a transaction.

There are two ways to stop a transaction. The first is to commit the transaction as follows:

  CommitTransaction(hTrans);
  CloseHandle(hTrans);

After the transaction is committed, the created file will be visible in Windows Explorer with the correct contents. The second way to stop a transaction is to abort it and rollback any changes.

  RollbackTransaction(hTrans);
  CloseHandle(hTrans);

After rolling back the file will not become visible in Windows Explorer.

Using Transactional Registry (TxR)

Transactional Registry works very similar as Transactional NTFS. The following transacted registry functions are available:

  • RegCreateKeyTransacted
  • RegOpenKeyTransacted
  • RegDeleteKeyTransacted

These functions work similar as their non-transacted counterparts. They can be used in a similar flow as shown above for transacted file operations, so no example is given here.

Demo

The attached demo project shows how to use transacted file operations in practice. Its flow is as follows:

  • Create and start a transaction.
  • Create a file test.file inside the new transaction context.
  • Write “Test String” to the created file and close the file.
  • Create a second file test2.file inside the new transaction context.
  • Write “Second Test String” to the second file and close the second file.
  • Ask the user if the changes should be committed or rolled back.
  • Commit or rollback the transaction and close the transaction.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read