Backing Up File Data with CBFS Filter
In this article, we will describe how an application can use CBFilter to make copies of file data before a file is modified, deleted, or renamed.
When it is difficult to distinguish between legitimate and unauthorized modifications to a file, creating a backup copy of the file data can help safeguard against corruption or loss.
To create a data copy, the application must first detect any changes and access the existing file data to perform the copy. This involves a multistage process.
Initial Setup
The first step is to track file changes, which requires handling the following events: BeforeCreateFile, BeforeOpenFile, AfterCreateFile, AfterOpenFile, BeforeRenameOrMoveFile, BeforeDeleteFile, BeforeWriteFile, BeforeSetFileSize, BeforeSetAllocationSize, and BeforeSetFileSecurity.
A sample call to set up the necessary access rule can look like this:
filter.AddFilterRule("C:\\path\\to\\directory\\*.*", Constants.ACCESS_NONE,
Constants.FS_CE_BEFORE_CREATE | Constants.FS_CE_BEFORE_OPEN | Constants.FS_CE_AFTER_CREATE | Constants.FS_CE_AFTER_OPEN | Constants.FS_CE_BEFORE_RENAME | Constants.FS_CE_BEFORE_DELETE | Constants.FS_CE_BEFORE_WRITE | Constants.FS_CE_BEFORE_SET_SIZES | Constants.FS_CE_BEFORE_SET_SECURITY, Constants.FS_NE_NONE);
In this example, we set a rule for all files in a directory recursively, but more granular adjustments to the rule conditions (e.g., using the AddFilterRuleEx method) are also possible.
The BeforeRenameOrMoveFile, BeforeDeleteFile, BeforeWriteFile, BeforeSetFileSize, and BeforeSetAllocationSize events signal changes to the file data, indicating when the application should intervene to copy the file data.
The BeforeCreateFile and BeforeOpenFile events should be monitored to detect potential changes, particularly if the caller intends to truncate the file during opening.
The BeforeSetFileSecurity event can prevent modifications to the file's security attributes—actions that could block a user’s access and render the data unreachable. Although handling this event is not necessary for backup purposes, it is crucial for ensuring data availability.
The AfterCreateFile and AfterOpenFile events are used to obtain a direct handle to the file (more on this below).
You can add the rule before or after starting the filter with StartFilter, but you must start it to receive the events.
Additionally, you need to enable the AllowFileAccessInBeforeOpen configuration setting:
filter.AddFilterRule("C:\\path\\to\\directory\\*.*", Constants.ACCESS_NONE,
Constants.FS_CE_BEFORE_CREATE | Constants.FS_CE_BEFORE_OPEN | Constants.FS_CE_AFTER_CREATE | Constants.FS_CE_AFTER_OPEN | Constants.FS_CE_BEFORE_RENAME | Constants.FS_CE_BEFORE_DELETE | Constants.FS_CE_BEFORE_WRITE | Constants.FS_CE_BEFORE_SET_SIZES | Constants.FS_CE_BEFORE_SET_SECURITY, Constants.FS_NE_NONE);
This configuration setting is essential for proper handling of the BeforeCreateFile and BeforeOpenFile events, allowing you to open a file in these handlers to facilitate copying.
Alongside the handlers for these events, you must declare a handler for the CleanupContext event (this event does not require a rule and lacks a flag). The use of this event handler is explained later in this article.
Reacting to Requests
The second step involves actual event handling, which includes four parts:
- Checking whether the file is about to be truncated in the BeforeCreateFile and BeforeOpenFile events.
- Opening a direct file handle in the AfterCreateFile and AfterOpenFile events.
- Reacting to modification requests.
- Closing the file handle.
Handling BeforeCreateFile and BeforeOpenFile Events
In the BeforeCreateFile and BeforeOpenFile event handlers, you should inspect the CreateDisposition parameter. If it is set to FILE_DISPOSITION_TRUNCATE_EXISTING, this indicates that the file will be truncated as soon as it is opened, resulting in the loss of its data. Therefore, you must take immediate action to back up the data within the corresponding event handler. To do this, use the CreateFileDirect or CreateFileDirectAsStream methods to gain direct access to the file data. You should then copy the data immediately, closing the file before returning from the event handler. Details about this process are covered later in this article. Additionally, remember to review the section on event timeouts.
It is not necessary to check other parameters in these event handlers, as this will be handled in the AfterCreateFile and AfterOpenFile event handlers.
Opening Direct File Handle
Unless the file was already copied in the BeforeCreateFile or BeforeOpen event handlers, the AfterCreateFile and AfterOpenFile event handlers should check the request parameters to determine if the data are about to be modified. If a modification is indicated, a direct file handle should be prepared. Specifically, the event handlers should examine the DesiredAccess and Options parameters. If these parameters suggest that the caller application intends to change the file (e.g., by writing to it or deleting it), it is time to take action. Several values within these parameters may indicate the intention to modify the data; please refer to the documentation for detailed descriptions of each.
The goal of handling these events is to have a direct handle to the file ready for use in case a file modification request is received. A sample call to the CreateFileDirect method to open a handle may look like this:
filter.AddFilterRule("C:\\path\\to\\directory\\*.*", Constants.ACCESS_NONE,
Constants.FS_CE_BEFORE_CREATE | Constants.FS_CE_BEFORE_OPEN | Constants.FS_CE_AFTER_CREATE | Constants.FS_CE_AFTER_OPEN | Constants.FS_CE_BEFORE_RENAME | Constants.FS_CE_BEFORE_DELETE | Constants.FS_CE_BEFORE_WRITE | Constants.FS_CE_BEFORE_SET_SIZES | Constants.FS_CE_BEFORE_SET_SECURITY, Constants.FS_NE_NONE);
This call opens a direct file handle suitable for reading the file. The handle will not prevent other applications from opening the file for reading, writing, or deletion (as indicated by the SHARE flags). We are opening an existing file without buffering.
The GENERIC_READ, OPEN_EXISTING, and FILE_FLAG_\* constants are defined in the Windows software development kit (SDK) and are detailed in the CreateFile Windows API function documentation.
The obtained direct handle can be used with Windows API functions, such as ReadFile Windows API function, and should be closed using the CloseHandle Windows API function when it is no longer needed.
The Synchronize parameter must be set to True whenever you open the file for which the event was fired.
The FILE_FLAG_NO_BUFFERING flag indicates that the file cache should not be involved and that reading is performed directly from the filesystem. In this mode, all file reads must be done in blocks that are multiples of 4096 bytes in size to match the system's page size—a requirement for nonbuffered file operations.
Although it may be tempting to simplify handling and assume that a file could be modified, this is not always the case. A file might be opened solely to read its attributes and then be closed immediately; thus, opening a direct handle for each such small operation can have a negative impact on system performance. Additionally, files are frequently opened just for reading.
The obtained file handle must be stored so that it can be used in subsequent events. For this purpose, you can utilize the FileContext parameter.
Note: A file may be opened multiple times in parallel, meaning that two or more application-opened handles may exist for the same file. For backup purposes, only one direct file handle is needed. Therefore, you do not need to open a new handle if one is already present in FileContext.
Copying File Data
The third part involves copying the data. This copying occurs in the handlers for the BeforeRenameOrMoveFile, BeforeDeleteFile, BeforeWriteFile, BeforeSetFileSize, and BeforeSetAllocationSize requests, as well as, in some cases, in the BeforeCreateFile and BeforeOpenFile handlers, as previously discussed.
To perform the copying, the relevant event handlers utilize the direct file handle stored in the FileContext parameter. They can call the ReadFile Windows API function to read blocks of data from the file and write them to another location. Remember that these blocks must be multiples of 4096 bytes.
Once the data are copied, the request can proceed. Alternatively, you may choose to deny the request by completing it with an error if the data-copying fails; however, doing this within the BeforeWriteFile event handler may confuse the cache manager.
Depending on the file size and other specifics, it may be more efficient to copy the data right when the file is opened for writing. This can be accomplished in the AfterCreateFile and AfterOpenFile event handlers, which can save you from having to handle multiple operational events.
In any case, you must ensure that the copying process does not take too much time. For more information, see the section Event Timeouts.
Closing File Handle
A direct file handle can be closed immediately after copying the file. If, however, copying was not necessary and you still have an open file handle, it is essential to close that handle. The most convenient way to do this is by handling the CleanupContext event and closing the handle in the corresponding event handler. For this event, the driver manages reference counting and handles files opened multiple times. The driver will trigger an event with a nonzero FileContext only after the last handle to the file is closed.
If you close the direct handle after copying, remember to remove the handle from the FileContext. This ensures that the CleanupContext event is not fired with an already-invalid direct handle in FileContext.
Event Timeouts
To prevent situations in which an event handler becomes stuck and blocks the system, CBFilter implements a timeout mechanism. The desired timeout is specified as a parameter when filtering is initiated by an application (during a call to the StartFilter method). When the driver makes a call to user mode, it starts a timer and expects the call to complete within the predefined timeout. If the call does not complete in time, execution continues, and the call is treated as abandoned—any returned data (if applicable) will be ignored.
The only exceptions to this are the BeforeCreateFile and BeforeOpenFile events; if a call to these handlers times out, the file is not created or opened, serving as a security measure.
If a handler requires additional time to complete event handling, it can call the ResetTimeout method to reset or disable the timer for that specific event.
Handling Large Files: Alternative Approaches
When copying large files or sending data to a slow medium (such as across a network), it is easy to exceed the timeout. In this case, two options are available.
The first option is to start copying the file when it is opened, and then attempt to delay file changes in the subsequent update events. However, this may not always be effective if the file is opened and immediately modified, as there may not be enough time to handle the operation.
The second option is to copy the modified file blocks first, allowing the other blocks to be copied asynchronously in a separate worker thread. This approach is not ideal when a large file is truncated or overwritten, as the entire data stream must then be copied.
In both cases, the event handler should keep track of the elapsed time and use the ResetTimeout method when necessary to prevent timeouts.
Another approach to safeguard file data is to completely move the file and create a placeholder in its place. This should be done when the file is opened, specifically in the BeforeCreateFile and BeforeOpenFile event handlers. This method is effective when the file is being deleted or overwritten. It will not work, however, if the file is opened for both reading and writing, as the application may need to read existing data and modify only part of the file.
Getting Started with CBFilter
You can find an evaluation version of the SDK for your platform and programming language in the Download Center. After downloading and installing the SDK, you will receive a library, sample code, and comprehensive documentation on your system. The documentation includes a "Getting Started" section with programming instructions. Additionally, free technical support is available during the evaluation phase.
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@callback.com.