Interacting with FSDMGR

In interacting with the FSDMGR and its helper functions, there are two pseudo-handle types that will be used: HDSK and HVOL (these types are defined in fsdmgr.h). While there is a one-to-one mapping of HDSK to HVOL, they are not the same construct.
 
The HDSK is a reference to the disk object beneath the file system and is required for performing any disk-base I/O operations. The HDSK exists regardless of whether or not the partition is mounted.

Unlike the HDSK, the HVOL is a reference to the volume object associated with an instance of an FSD mounted on an HDSK. The HVOL can be though of as a virtual link between the mount point in the root file system exposed to user applications (e.g. “\Storage Card”) and a physical partition on a disk (“Part00” on “DSK1:”).

Mounting a Volume

To establish the link between an HVOL and an HDSK, the FSD_MountDisk export of an FSD is invoked and the HDSK parameter is passed to this function. During the FSD_MountDisk function, the FSD must call the helper function FSDMGR_RegisterVolume to instantiate a new HVOL, associate it with the specified HDSK, and assign it a mount point in the root file system.

When calling FSDMGR_RegisterVolume, the FSD can supply its own context to FSDMGR. This context will be passed back to the FSD on subsequent file API calls. Typically, the FSD will use this context to point to a heap-allocated structure describing a particular instance of the FSD associated with an HVOL and HDSK. A well-designed FSD should encapsulate all information in this context so that it can support multiple instances simultaneously using the same driver.

The following code sample illustrates how an FSD might implement its FSD_MountDisk function:

#include <windows.h>
#include <fsdmgr.h>

// A structure used to track an instance of a volume managed by this FSD.
typedef struct __InternalVolumeObject {

HDSK hDsk; // Disk psuedo-handle passed to FSD_MountDisk
HVOL hVol; // Volume psuedo-handle returned from FSDMGR_RegisterVolume.

CRITICAL_SECTION csVolume;
WCHAR szFolderName[MAX_PATH];
SHELLFILECHANGEFUNC_t pNotifyCallBack;

InternalFileObject* pOpenFileList; // List of currently open files.

/*
* Additional volume context information goes here.
*/

// A pointer to the next InternalVolumeObject object on the global
// volume list (g_pVolumeList).
struct __InternalVolumeObject* pNext;

} InternalVolumeObject;

// A global list to track all currently mounted volumes managed by this FSD.
static InternalVolumeObject* g_pVolumeList = NULL;

// A global critical section to protect the global volume list during
// asyncrhonous access from multiple threads. This critical section should
// be initialized in DllMain.
static CRITICAL_SECTION g_csVolumeList;

// DetectBootSectorFormat
//
// TBD: Define this function to detect a valid boot sector. This function
// will be dependent on the file system driver.
BOOL DetectBootSectorFormat (BYTE* pBootSector);

// FSD_MountDisk
//
// Called to mount a new volume on a partition. If the FSD is unable to
// mount the partition, this function should return FALSE. Otherwise, it
// should return TRUE.
//
BOOL FSD_MountDisk (HDSK hDsk)
{
DWORD dwFormat = 0;
FSD_DISK_INFO fdi;
BYTE* pSectorBuffer = NULL;
InternalVolumeObject* pVolume = NULL;

    // Query disk information.
if (!FSDMGR_GetDiskInfo (hDsk, &fdi)) {
// Failed to retrieve disk geometry.
return FALSE;
}

    // Allocate a sector buffer.
pSectorBuffer = (BYTE*)LocalAlloc (LMEM_FIXED, fdi.cbSector);
if (!pSectorBuffer) {
return FALSE;
}

    // Read sector zero (bootsector) and evaluate the format.
if (!FSDMGR_ReadDisk (hDsk, 0, 1, pSectorBuffer, fdi.cbSector)) {
// Failed to read from disk.
LocalFree (pSectorBuffer);
return FALSE;
}

    // Check the registry to see if we should auto-format.
FSDMGR_GetRegistryValue (hDsk, L"Format", &dwFormat);

if (!DetectBootSectorFormat (pSectorBuffer)) {
DEBUGMSG (ZONE_INIT, (L"MyFSD!FSD_MountDisk: Invalid format!"));
// Did not recognize the disk format.
if (dwFormat) {
// NOTE: We must have a utility library specified in the
// registry and the library must export FormatVolumeEx for
// this format call to succeed.
if (!FSDMGR_FormatVolume (hDsk, NULL)) {
LocalFree (pSectorBuffer);
return FALSE;
}
}
LocalFree (pSectorBuffer);
return FALSE;
}

    LocalFree (pSectorBuffer);
pSectorBuffer = NULL;

    // At this point we have a valid format. Allocate a new volume
// structure and register it.

    pVolume = (InternalVolumeObject*)LocalAlloc (LPTR,
sizeof (InternalVolumeObject));
if (!pVolume) {
return FALSE;
}

    // Populate the volume structure.
pVolume->hDsk = hDsk;

    // Try to get the volume name from our "MountName" registry value.
if (!FSDMGR_GetRegistryString (hDsk, L"MountName",
&pVolume->szFolderName, MAX_PATH)) {

// Unable to read the registry value, try IOCTL_DISK_GETNAME.
if (!FSDMGR_DiskIoControl (hDsk, IOCTL_DISK_GETNAME, NULL, 0,
pVolume->szFolderName, sizeof (pVolume->szFolderName),
NULL, NULL)) {

// Still unable to find a mount name, so use our default string.
wcscpy (pVolume->szFolderName, L"Mounted Volume");
}
}

// Register the volume.
pVolume->hVol = FSDMGR_RegisterVolume (hDsk, pVolume->szFolderName,
pVolume);
if (!pVolume->hVol) {
// Failed to register the volume.
LocalFree (pVolume);
return FALSE;
}

    // Read back the real volume name we registered with. If we registered
// as "Hard Disk" and this mount name already existed, we might get back
// "Hard Disk2" as the true mount name. We will need this value later on
// for posting file notifications.
FSDMGR_GetVolumeName (pVolume->hVol, pVolume->szFolderName, MAX_PATH);
DEBUGMSG (ZONE_INIT, (L"MyFSD!FSD_MountDisk: Mounted new volume as \"%s\"",
pVolume->szFolderName));

    // Initialize a new critical section used to guard the pVolume object
// during asynchronous access.
InitializeCriticalSection (&pVolume->csVolume);

    // Add this volume to the front of our global volume list.
EnterCriticalSection (&g_csVolumeList);
pVolume->pNext = g_pVolumeList;
g_pVolumeList = pVolume;
LeaveCriticalSection (&g_csVolumeList);

// The volume was successfully registered; pVolume will be passed to
// subsequent API calls performed on this volume.

    return TRUE;
}

Dismounting a Volume

To remove the link between an HVOL and an HDSK, the FSD_UnmountDisk export of an FSD is invoked and the HDSK parameter is passed to this function. During this call, the FSD must deallocate all resources associated with this HDSK. This call occurs in response to programmatic partition dismounting (DismountStore or DismountPartition invoked by an application) or after media removal.

Typically, a global list of HDSK-to-Volume Context mappings must be maintained in the FSD so that the appropriate volume can be deregistered during FSD_UnmountDisk. This is necessary because FSD_UnmountDisk receives an HDSK parameter instead of a Volume-Context like other FSD APIs.

The following code sample illustrates how an FSD might implement its FSD_UnmountDisk function:

// FSD_UnmountDisk
//
// Called when an FSD is to be unloaded, typically due to a partition being
// dismounted either programatically or due to media removal. This function
// should always return TRUE.
//
BOOL FSD_UnmountDisk (HDSK hDsk)
{
InternalVolumeObject* pPrev = NULL;
InternalVolumeObject* pCurrent;

// Locate the volume object matching this disk.
EnterCriticalSection (&g_csVolumeList);
pCurrent = g_pVolumeList;
while (pCurrent) {
if (pCurrent->hDsk == hDsk) {
// Found a volume with a matching HDSK on our list.
break;
}
pPrev = pCurrent;
pCurrent = pCurrent->pNext;
}

    if (pCurrent) {
// Remove this item from the list.
if (pPrev) {
pPrev->pNext = pCurrent->pNext;
} else {
g_pVolumeList = pCurrent->pNext;
}
}

LeaveCriticalSection (&g_csVolumeList);

    if (!pCurrent) {
// This is not a disk we have mounted. This should never happen.
return FALSE;
}

    // Deregister the volume.
FSDMGR_DeregisterVolume (pCurrent);

    // Free the internal volume structure.
LocalFree (pCurrent);

// Always succeed FSD_UnmountDisk.
return TRUE;
}

File and Search Handle Context Objects

A HANDLE value is required to be returned from FSD_CreateFileW and FSD_FindFirstFileW APIs. A HANDLE is essentially just an entry in a table that refers to an object. The object associated with any handles returned by your FSD should be a heap allocated structure with information about the object. The following example shows what the objects associated with file and search handles might look like:

// Context representing a handle to a file. This structure encapsulates
// all information associated with a single handle. Multiple handles can
// refer to the same InternalFileObject object (assuming sharing modes
// are correct). A pointer to one of these structures will always be passed
// as the first parameter to all handle-based file APIs.
typedef struct __FileHandleContext {

    InternalVolumeObject* pVolume; // A reference to the parent volume.

    DWORD dwAccess; // GENERIC_READ, GENERIC_WRITE
DWORD dwSharing; // FILE_SHARE_READ, FILE_SHARE_WRITE

    LARGE_INTEGER FileOffset; // 64-bit file position.

    InternalFileObject* pFileObject; // Reference to the file object.

    // A pointer to the next FindHandleContxt object on a file object's
// file handle list (InternalFileObject.pHandleList).
struct __FileHandleContext* pNext;

} FileHandleContext;

// Context representing a handle to a directory enumeration instance.
// This structure encapsulates all information associated with a single
// search. A pointer to one of these structures will always be passed
// as the first parameter to all handle-based search APIs.
typedef struct __SearchHandleContext {

    InternalVolumeObject* pVolume; // A reference to the parent volume.

    DWORD dwEnumPosition; // Enumeration position.
WCHAR szSearchSpec[MAX_PATH]; // Search specification including wildcards.

} SearchHandleContext;

Creating Handle Context Object

A handle object should be allocated each time FSD_FindFirstFileW or FSD_CreateFileW is called and freed when FSD_FindClose or FSD_CloseFile is called, respectively. The following example shows how these functions might be definied:

// A structure used to track information about a specific file.
typedef struct __InternalFileObject {

    // A pointer to the parent volume that owns this open file.
InternalVolumeObject* pVolume;

    // A pointer to the head of the list of handles open to this file.
// Once this list goes to zero, this file object can be destroyed.
FileHandleContext* pHandleList;

    // A critical section to guard asyncrhonous access to this file
// object.
CRITICAL_SECTION csFile;

    /*
* Additional file object information goes here. This includes
* things such as a pointer to the file's directory entry, etc.
*/

    // A pointer to the next InternalFileObject object on the volume's
// open file list (InternalVolumeObject.pOpenFileList).
struct __InternalFileObject* pNext;

} InternalFileObject;

// Internal FSD function used to open a file. If the file is already open,
// this should return a pointer to the existing InternalFileObject structure.
// If the file is not currently open, instantiate a new InternalFileObject for
// it and add it to the InternalVolumeObject's open file list.
InternalFileObject* CreateOrOpenFileObject (InternalVolumeObject* pVolume,
LPCWSTR pszFileName, DWORD dwAccess, DWORD dwShareMode, DWORD dwCreate
DWORD dwFlagsAndAttributes);

// Internal FSD funciton used to remove a reference from a file object.
void CloseFileObject (InternalFileObject* pFile);

// FSD_CreateFileW
//
// Create or open a file. Parallels the Win32 CreateFileW API.
//
HANDLE FSD_CreateFileW (
DWORD dwVolume,
HANDLE hProcess,
LPCWSTR pszFileName,
DWORD dwAccess,
DWORD dwShareMode,
PSECURITY_ATTRIBUTES pSecurityAttributes,
DWORD dwCreate,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile )
{
// Parameter 1 is always a pointer to our volume structure.
InternalVolumeObject* pVolume = (InternalVolumeObject*)dwVolume;

// Invoke our internal function to create or open the file on this
// volume.
InternalFileObject* pFile = CreateOrOpenFileObject (pVolume, pszFileName,
dwAccess, dwShareMode, dwCreate, dwFlagsAndAttributes);

    if (!pFile) {
// Failed to open or create the file.
return INVALID_HANDLE_VALUE;
}

// Allocate a new handle context.
FileHandleContext* pFileHandle;
pFileHandle = (FileHandleContext*)LocalAlloc (LMEM_FIXED,
sizeof (FileHandleContext));

    if (!pFileHandle) {
CloseFileObject (pFile);
return INVALID_HANDLE_VALUE;
}

    // Populate the file handle context structure.
pFileHandle->pFile = pFile;
pFileHandle->dwShareMode = dwShareMode;
pFileHandle->dwAccess = dwAccess;
pFileHandle->FilePosition.QuadPart = 0;

    // Add this handle to the open file's handle list.
EnterCriticalSection (&pFile->csFile);
pFileHandle->pNext = pFile->pHandleList;
pFile->pHandleList = pNext;
LeaveCriticalSection (&pFile->csFile);

    // Use the FSDMGR helper function to convert the pointer to an
// InternalFileObject into a handle before returning.
return FSDMGR_CreateFileHandle (pVolume->hVol, hProcess, (PFILE)pFile);
}

In the above example, the CreateOrOpenFileObject and CloseFileObject functions would be FSD specific and would be implemented by the FSD developer. The FSD_FindFirstFileW would be implemented similarly, but it would use the FSDMGR_CreateSearchHandle helper function to create the handle to be returned instead.

Destroying Handle Context Objects

Handle cleanup is performed in the FSD_FindClose and FSD_CloseFile functions. The following example illustrates how this functionality might be implemented for file handles:

// FSD_CloseFile
//
// Cleanup an open handle to a file and commit any buffered portions
// of the open file.
//
BOOL FSD_CloseFile (DWORD dwFile)
{
// Parameter 1 is always a pointer to our file handle structure.
FileHandleContext* pFileHandle = (FileHandleContext*)dwFile;

// Get a reference to the current open file.
InternalFileObject* pFile = pFileHandle->pFile;

    FileHandleContext* pPrev = NULL
FileHandleContext* pCurrent;

EnterCriticalSection (&pFile->csFile);
pCurrent = pFile->pHandleList;
while (pCurrent) {
if (pCurrent == pFileHandle) {
// Found the requested handle on the list.
break;
}
pPrev = pCurrent;
pCurrent = pCurrent->pNext;
}

    if (pCurrent) {
// Remove this handle from the list.
if (pPrev) {
pPrev->pNext = pCurrent->pNext;
} else {
pFile->pHandleList = pCurrent->pNext;
}
}

    LeaveCriticalSection (&pFile->csFile);

    if (!pCurrent) {
// This is not a handle we're aware of. This should never happen.
return FALSE;
}

    // Free the closed handle object.
LocalFree (pFileHandle);

    // Cleanup the file object. This will do nothing if there are remaining
// references to the file from other open handles.
CloseFileObject (pFile);

// Always succeed, even if the file could not be flushed/committed.
return TRUE;
}

Questions/comments?

-Andrew