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

Comments (6)

  1. Anonymous says:

    Hello Andrew !

    I am implementing an FSD on a mobile 4.2 device. The FSD is for an ftp client over bluetooth. The notifications does not seem to work, however I am getting a pointer for the function, but if I delete a file in the explore, the delete function is called, but when I try to notify the explore from within the delete function nothing happens.

    If you have any suggestions it will be appreciated.

  2. ce_base says:

    Nothing immediately comes to mind. Do other notifications (besides DeleteFile) work properly? Have you prepended the mount point name to the path of the file being deleted (e.g. "\Storage Card" + "\FileName")?

  3. Anonymous says:

    Hi Andrew !

    I have also tried with creating a new folder, and I got the same result nothing happens on the file explore, even though I am using the fullpath.

    I am sending the notification code for make directory 🙂

    if(result == TRUE && ptrToRegisterFunc)

    {

    timeModified.dwLowDateTime = 0; timeModified.dwHighDateTime = 0;

    fullPathName = (TCHAR)malloc(MAX_PATH);

    memset((WCHAR)fullPathName, 0, MAX_PATH);

    FSDMGR_GetVolumeName( volumeH, fullPathName, MAX_PATH);

    wcscat(fullPathName, pwsPathName);

    fci.wEventId = SHCNE_MKDIR;

    fci.uFlags = SHCNF_PATH | SHCNF_FLUSHNOWAIT;

    fci.dwItem1 = (DWORD) fullPathName;

    fci.dwItem2 = 0;

    fci.dwAttributes = 0;

    fci.ftModified = timeModified;

    fci.nFileSize = 0;

    IFDBG(DebugOut(DEBUG_OUTPUT, TEXT("[CSR-FSD] Create directory(). Transmitting notification.

    FullPathname %s registerfunc %u"), fullPathName, ptrToRegisterFunc));

    __try {

    (ptrToRegisterFunc) (&fci);

    }

    __except (EXCEPTION_EXECUTE_HANDLER)

    {

    // Ignore an exception in the callback.

    }

    free(fullPathName);

    }

    The pointer value of ptrToRegisterFunc is (F000AB80) so I am getting a pointer for the function. I am using the file explore for a QTek 2020.

    I really appreciate your help.

  4. ce_base says:

    Are you sure that you’ve built the path properly? I cannot tell from the code if pwsPathName is already prefixed with a "\" or not. If it is not, you’ll end up with a malformed path "\storage cardfilename" instead of "\storage card\filename". Do any of the file notifications seem to work correctly?

    I’m actually only familiar with the way the shell handles notifications in Windows Mobile 5, so it is possible that the details I’ve described above aren’t 100% correct for older versions (PPC2003 in this case). Try putting the file attributes in your notification structure (and maybe even the file time) and see if it makes a difference. It certainly won’t hurt anything.

    Another thing to note: you have a potential buffer overrun in your code. You should make sure to malloc a buffer that is (sizeof (TCHAR) * MAX_PATH) bytes (and also make sure malloc doesn’t return NULL).

    Regards,

    Andrew

  5. Anonymous says:

    Hi Andrew !!

    I can now notify the file explore, and it was the file attribute entry that was wrong.

    However I am having trouble updating my path in the file explore, when I enter a new folder, and the FSDMGR calls MyFSD_FindFirst, MyFSD_FindNext etc. it seems that the path gets all wrong. Do you have any idea ?

    Thanks and best regards,

    Per Andersen

  6. ce_base says:

    I’m glad to hear you have notifications working. It sounds like the solution, then, was to add the approprite attributes to the notification structure when notifying of a new directory. I will update the blog entry to reflect this.

    What do you mean "the path gets all wrong"? FindFirstFile and FindNextFile are used by explorer to determine all of the files in a folder. When you create a new folder, say "Folder1", I’d expect you to to get a call to FindFirstFile with the search string "Folder1." Is this what you’re seeing? If there’s nothing in the folder (which there probably isn’t because it is a new folder), you should fail FindFirstFile by returning INVALID_HANDLE_VALUE and setting the error code to ERROR_NO_MORE_FILES using the SetLastError API. Note that this search string should not mach the folder itself or you’ll get called infinitely.

    Regards,

    Andrew