Implementing FSD Call-back File Notifications

In the past, the Windows CE file explorer required every File System Driver (FSD) to perform call-back notifications after every file system change. This notification mechanism allowed file explorer to quickly refresh open views when files were added, deleted, or modified from a directory. Starting with Windows CE version 4.2, this callback mechanism is no longer used by the file explorer. Instead, file explorer uses event-based file notifications (introduced in CE 4.0) which are automatically provided by the Windows CE Storage Manager (FSDMGR.DLL) for every FSD.

However, Windows Mobile platforms (including Windows Mobile 5) still require the older callback style notifications in order to refresh open explorer windows. FSD developers must implement these notification callbacks in order to properly integrate with Windows Mobile platforms. Since documentation on implementing this notification callback system is somewhat lacking, I've put together this brief rundown of the requirements.

Before notifications can be posted by an FSD, the file explorer will register a callback function pointer of the type SHELLFILECHANGEFUNC_t. An FSD will receive a call to FSD_RegisterFileSystemFunction register this callback function pointer. The following example illustrates how an FSD might implement the registration function:

BOOL FSD_RegisterFileSystemFunction (DWORD dwVolume,
SHELLFILECHANGEFUNC_t pNotifyCallBack)
{
// Parameter 1 is always a pointer to our volume structure.
InternalVolumeObject* pVolume = (InternalVolumeObject*)dwVolume;

    // Update the volume object's callback function pointer.
pVolume->pNotifyCallBack = pNotifyCallback; 

    return TRUE;
}

If there is a callback function registered, it should be called from every FSD function after any file change occurs. When calling this function, the FSD must pass a FILECHANGEINFO structure describing the change that occurred. Whenever this structure is populated with a file name, the file name must include the full path to the file, including the name of the mount point. It is recommended that the FSD retrieve the name of the mount point using the FSDMGR_GetVolumeName helper function during the call to FSD_MountDisk after calling FSDMGR_RegisterVolume. The folder name can then be cached in the volume object for later retrieval.

The values used to populate the FILECHANGEINFO structure depend on the file operation that is being processed. The structure contains attributes, modification time, and current file size. For notifications describing items that have been removed, the attributes, modification time, and size fields will be ignored. For notifications describing directories, the modification time and size fields will be ignored.

The following sections explain what each FILECHANGEINFO structure should contain for every file operation. Keep in mind that these notifications should only be posted to the callback function after the operation successfully completes. All definitions can be found in the extfile.h header file.

FSD_CreateFileW
Whenever a new file is created or an existing file is truncated, a notification must be generated. If an existing file is being opened without truncation, no notification should be generated.

A new file is being created:
FILECHANGEINFO.wEventId = SHCNE_CREATE

An existing file is being truncated:
FILECHANGEINFO.wEventId = SHCNE_UPDATEITEM

FILECHANGEINFO.uFlags = SHCNF_PATH | SHCNF_FLUSHNOWAIT
FILECHANGEINFO.dwItem1 = root name + full path to file
FILECHANGEINFO.dwItem2 = 0
FILECHANGEINFO.dwAttributes = current attributes of the new or truncated file
FILECHANGEINFO.ftModified = last modification time this file (now)
FILECHANGEINFO.nFileSize = current file size

FSD_CloseFile
Whenever a file that was opened for write access is closed, the following notification must be generated.

FILECHANGEINFO.wEventId = SHCNE_UPDATEITEM
FILECHANGEINFO.uFlags = SHCNF_PATH | SHCNF_FLUSHNOWAIT
FILECHANGEINFO.dwItem1 = root name + full path to file
FILECHANGEINFO.dwItem2 = 0
FILECHANGEINFO.dwAttributes = current attributes of the new or truncated file
FILECHANGEINFO.ftModified = last modification time this file
FILECHANGEINFO.nFileSize = current file size

FSD_CreateDirectoryW
Whenever a new directory is created, the following notification should be generated.

FILECHANGEINFO.wEventId = SHCNE_MKDIR
FILECHANGEINFO.uFlags = SHCNF_PATH | SHCNF_FLUSHNOWAIT
FILECHANGEINFO.dwItem1 = root name + full path to directory
FILECHANGEINFO.dwItem2 = 0
FILECHANGEINFO.dwAttributes = attributes of the directory (including FILE_ATTRIBUTE_DIRECTORY)
FILECHANGEINFO.ftModified = don’t care
FILECHANGEINFO.nFileSize = don’t care

FSD_RemoveDirectoryW
Whenever a new directory is removed, the following notification should be generated.

FILECHANGEINFO.wEventId = SHCNE_RMDIR
FILECHANGEINFO.uFlags = SHCNF_PATH | SHCNF_FLUSHNOWAIT
FILECHANGEINFO.dwItem1 = root name + full path to directory
FILECHANGEINFO.dwItem2 = 0
FILECHANGEINFO.dwAttributes = don’t care
FILECHANGEINFO.ftModified = don’t care
FILECHANGEINFO.nFileSize = don’t care

FSD_SetFileAttributesW
Whenever the attributes of a file or directory are changed, the following notification should be generated.

FILECHANGEINFO.wEventId = SHCNE_UPDATEITEM
FILECHANGEINFO.uFlags = SHCNF_PATH | SHCNF_FLUSHNOWAIT
FILECHANGEINFO.dwItem1 = root name + full path to file
FILECHANGEINFO.dwItem2 = 0
FILECHANGEINFO.dwAttributes = current attributes of the new or truncated file
FILECHANGEINFO.ftModified = last modification time this file (don’t care for directory)
FILECHANGEINFO.nFileSize = current file size (don’t care for directory)

FSD_DeleteFileW
Whenever a file is deleted, the following notification should be generated.

FILECHANGEINFO.wEventId = SHCNE_DELETE
FILECHANGEINFO.uFlags = SHCNF_PATH | SHCNF_FLUSHNOWAIT
FILECHANGEINFO.dwItem1 = root name + full path to file
FILECHANGEINFO.dwItem2 = 0
FILECHANGEINFO.dwAttributes = don’t care
FILECHANGEINFO.ftModified = don’t care
FILECHANGEINFO.nFileSize = don’t care

FSD_MoveFileW
Whenever a file or directory is moved or renamed, the following notification should be generated.
For MoveFileW on a directory:
FILECHANGEINFO.wEventId = SHCNE_RENAMEFOLDER

For MoveFileW on a file:
FILECHANGEINFO.wEventId = SHCNE_RENAMEITEM

FILECHANGEINFO.uFlags = SHCNF_PATH | SHCNF_FLUSHNOWAIT
FILECHANGEINFO.dwItem1 = root name + full path to old file
FILECHANGEINFO.dwItem2 = root name + full path to new file
FILECHANGEINFO.dwAttributes = don’t care
FILECHANGEINFO.ftModified = don’t care
FILECHANGEINFO.nFileSize = don’t care

The following example illustrates how an FSD would generate a notification for a delete file operation:

BOOL FSD_DeleteFileW (DWORD dwVolume, const WCHAR* pFileName)
{
// Parameter 1 is always a pointer to our volume structure.
InternalVolumeObject* pVolume = (InternalVolumeObject*)dwVolume;

    // Perform the internal delete file call to delete the file on
// the volume.
BOOL fResult = InternalDeleteFile (pVolume, pFileName);

    if (fResult && pVolume->pNotifyCallBack) {

        // Post a file notification on successful file deletion.

        // NOTE: This functionality could be offloaded to a helper function
// and invoked by multiple functions posting notifications.

        FILECHANGEINFO fci;
WCHAR FullPathName[MAX_PATH];

        // Build the full path name using the root folder name + the file name
if (SUCCEEDED (StringCchPrintfW(FullPathName, MAX_PATH, L"%s\\%s",
pVolume->RootFolderName, pFileName))) {

            fci.wEventId = SHCNE_DELETE;
fci.uFlags = SHCNF_PATH | SHCNF_FLUSHNOWAIT;

            fci.dwItem1 = (DWORD)FullPathName;
fci.dwItem2 = 0;
fci.dwAttributes = INVALID_FILE_ATTRIBUTES;
fci.ftModified = 0;
fci.nFileSize = 0;

            __try {

// Perform the callback.
(pVolume->pNotifyCallBack) (&fci);

            __except (EXCEPTION_EXECUTE_HANDLER) {

                // Ignore an exception in the callback.
}
}
}

    return fResult;
}

Questions/Comments? Please don't hesitate to send feedback.

-Andrew