Programming the Windows Transactional File System (TxF)

One
of the most obvious uses of the The
Transactional File System (TxF)
is to write to a single file within the
scope of a transaction, eliminating the possibility of the file becoming
physically corrupt if the write operation does not complete successfully.
However, TxF is not limited to operations on single files, and it is
possible to complete transactions that span many files and directories. One of
the activities that is notoriously difficult to complete with guaranteed
correctness is operations that span multiple files – it is possible to get a
long way into a multi-file operation only to have the operation blocked by
another process that has an open handle to a file, and the manual rollback can
be similarly blocked by other processes that have subsequently opened file
handles (virus scanners often open handles on new and modified files for
scanning operations), disk space issues and process crashes.

The TxF provides a
simple and elegant solution to the problem of operations that span multiple
files and directories by providing transaction-aware directory and file enumeration
functions and transaction-aware functions for moving and deleting files. File
and directory enumeration use a similar handle-based approach to file read and
write operations, allowing a simple transition to a transactional model in
which only the initial enumeration function that returns the handle needs to
have a new Transacted function added. Other existing functions that take the
enumeration handle can be used as-is, with the handle storing all relevant
transaction information.

The code sample below
demonstrates the enumeration of a directory that involves the renaming of all
files found being carried out within the scope of the same transaction. After
creating a transaction handle, FindFirstFileTransacted
is used to begin the directory enumeration in a transacted manner and then the
standard FindNextFile
and FindClose
functions are used with the transaction-aware handle. As the sample code is
modifying files within the scope of a transaction to support simple roll-back
in the event of an error, it is important that the enumeration is transactional
as well so that changes made are visible by the enumeration functions. If the
standard FindFirstFile function was used, the transacted operations on the
files would not be visible until the transaction is committed.

//create transaction
HANDLE hTransaction = CreateTransaction(NULL, 0, 0, 0, 0,
 INFINITE, L"Directory Transaction");

TCHAR* pDirectoryName = _T("c:TestDir");

//add wild-card search character
TCHAR pDirectoryNameSearch[MAX_PATH];
_stprintf_s (pDirectoryNameSearch, MAX_PATH, _T("%s*"), pDirectoryName);

//begin enumeration of directory
WIN32_FIND_DATA ffd;
HANDLE hFind = FindFirstFileTransacted(pDirectoryNameSearch, FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, 0, hTransaction);

do
{
 if (hFind != INVALID_HANDLE_VALUE && !(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
 {
  TCHAR newFileNameWithPath[MAX_PATH];
 TCHAR originalFileNameWithPath[MAX_PATH];

 //get the full name of the file that was found
 if (_stprintf_s (newFileNameWithPath, MAX_PATH,
  _T("%sTransacatedRename_%s"), pDirectoryName, ffd.cFileName) == -1){
  RollbackTransaction(hTransaction);
  FindClose(hFind);
  CloseHandle(hTransaction);
  return;
 }

 //get the name of the new file
 if (_stprintf_s (originalFileNameWithPath, MAX_PATH,
  _T("%s%s"), pDirectoryName, ffd.cFileName) == -1){
  RollbackTransaction(hTransaction);
  FindClose(hFind);
  CloseHandle(hTransaction);
  return;
 }

 //rename the file using MoveFileTransacted
 if (!MoveFileTransacted(originalFileNameWithPath, newFileNameWithPath, NULL, NULL, 0, hTransaction)){
  RollbackTransaction(hTransaction);
  FindClose(hFind);
  CloseHandle(hTransaction);
  return;
 }
 }
}
while (FindNextFile(hFind, &ffd) != 0);

//close the Find handle before committing the transaction
FindClose(hFind);

//no errors - commit the transaction
BOOL transCommitResult = CommitTransaction(hTransaction);

//close and cleanup
CloseHandle(hTransaction);
hTransaction = NULL;

One of the more
interesting aspects of the code sample is that transaction rollback can be used
to deal with a wide range of error conditions – everything from the file rename
failing to problems with string concatenation operations.

The TxF provides
transactional support for a wide range of operations on the file system that
span well beyond the ability to write to a single file. As well as guaranteeing
data consistency, transactions can dramatically simplify error handling code by
simply rolling back all pending operations if errors are detected. When
conducting file operations on local NTFS drives in Windows Vista
and above, the TxF can be used to great effect to achieve elegant and
reliable applications.

More by Author

Must Read