This article was done with the IFS Kit provided by Microsoft through the MSDN partnership. All ressources below are packaged in this zip file.

DummyFS : A FileSystem step by step

Rémi Peyronnet – Février 2002

Introduction

This is a complet idiot guide to build a filesystem driver. The goal of this guide is to learn you the basics of a simple file system driver, and to help you to set up a good development environment.

I. Setting up a handy environnement

I. 1. Requirements

Before considering developping a file system driver, you must have the IFS DDK (Installable File System Driver Development Kit). For information about ordering this kit : http://www.microsoft.com/ddk/IFSkit/

I recently discovered a free ntifs.h, maintained by Bo Branten. This could be usefull if you consider developping a free project. But as there is not an official header, without documentation and warranty on this code, I strongly disencourage you to use it.

You also really should consider buying a book about kernel mode programming and NT file systems. There are some good references in the resources below. As I am a poor student, I do not bought one, but doing so, I do not know the ‘best practices’ of kernel mode development, so be carefull…

I. 2. Installing the DDK

Well, there is not much to say about that. The procedure is very simple. Once the setup.exe launched, select the items to be installed, and process. I recommend you to install the filesystem examples, and the OSR documentation. You should also consider installing other driver examples, as it would provide you extra kernel mode source code example.

You should also be aware of two details :

  • You will probably often use the command line. So you should provide a short and easy path name, as the default C:\WINDDK.
  • Building examples take a large amount of space. If you intend to do so, check there is an additionnal 200 Mo space on the drive you selected.

You have now all the tools installed. To compile an example, select the correct Build Environnement in the Start menu (IFS-DDK Kit 2600\Build Environments\Win XP Free Build Environment), go in the directory of the driver you want built, and type build. This build utility is great, as you will see. Do not use nmake /f, it does not work properly.

I. 3. Usefull tools and resources

OSR (Open Systems Resources) and SysInternals provide very usefull tools and materials. To be efficient in your driver’s development, these tools would be an invaluable help :

  • dbgMon or DebugView : DebugView is an application that lets you monitor debug output on your local system, or any computer on the network that you can reach via TCP/IP.
  • WinObj : WinObj displays information about the Object Manager’name space. You will be able tosee here (or not) your driver. This tool is the graphical equivalent of the Microsoft’s DDK utility objdir.
  • OSR Driver Loader : This GUI-based tool will make all the appropriate registry entries for your driver, and even allow you to start your driver without rebooting.
  • WinDbg : WinDbg is a free GUI kernel debugging tool for windows. You can perform powerfull tasks as remote kernel debugging, kernel mode connection and moreover, you can use the microsoft symbol server to retrieve the symbols you need, without any efforts. WinDbg has a graphical interface, but rely on command line tools, so you should consider read carefully the documentation and microsoft.public.windbg to know how to use the command line. A must have.

Also, the documentation is mandatory. Here are some resources :

  • Obviously the main resource is the IFS DDK Help.
  • The IFS DDK include some very interesting documentation from OSR. You will find this documentation in src/filesystem/OSR_docs.
  • NTFSD : NT-FSD is the mailing list of File System Development. You can access it via email, via web-based interface, or via news

I. 4. Integrate with Visual Studio

Visual Studio provides a very handy development environment. It helps you to develop faster and better. But surprisingly, Microsoft did not integrate the DDK with Visual Studio. The good point is that the build utility is really good, but the bad point is that there is no handy IDE. We will see here how to integrte the DDK in Visual Studio.

I. 4. a. The DDKBuild utility

The DDK introduce a new utility that replaces the makefile : build. That is very easy to use, very straight-forward and efficient. All the build information is stored in the sources and is highly configurable. But as it is not a standard makefile, it is a bit more difficult to use in Visual Studio.

In fact, it was. Today, the ddkbuild.bat utility makes this operation very easy :

  1. Create a new project, of type ‘Makefile’.
  2. As the command line, enter ‘ddkbuild.bat -XP free .’ and ‘/a’ for the rebuild all switch.
  3. Copy the ddkbuild.bat utility in the project folder. The ddkbuild utility uses the environnement variables to get the correct paths. Please check that the correct paths are provided. The ddkbuild can also provide support for multiple DDK versions (2K, XP,…), and both of the Free (debug) and Checked (release) builds.
  4. To complete the integration with VS, you can also generate the browsing information (the bsc file). To do that, simply add in your sources file the two lines : BROWSER_INFO=1 and BROWSERFILE=DummyFS.bsc. Then changes you project settings in Visual Studio (general tab, Browse info file name field)

I. 4. b. Have Intellisense

Intellisense is what completes your word when you type Ctrl-Space, what suggests you the members of a class when you type ‘.’ or ‘->’ and what shows you the parameter of a function when you type ‘(‘. This is very handy! All these information are created when editing the project’s files, and stored in the .ncb file. For instance, you will find three ncb : win32.ncb, crt.ncb and mfcatl.ncb in you devstudio/bin directory. But surprisingly, Microsoft does not seem to have added support to third party ncb, nor to the DDK. We will have to use of a small trick to add this support.

The contents of the NCB file are created when editing a file. For instance, when you add a method, Visual Studio parse the method and update its NCB. Also, when you add a .c or a .h in the project, the file is parsed and all the functions and classes are added. I observed the following behaviour :

  • A struct is added when its definition is found (ie in the .h file).
  • A standalone function is added only when its implementation is found. That means that the simple definition in the .h file is not enough : the .c file is needed. To fake this behaviour, you can replace all the ‘);’ string by ‘){}’ in the .h file you have. It transforms the declarations in an empty implementation, and so the function is added in the NCB. Of course, this modified .h won’t be anymore useable for your project, so make a copy.
  • The classes members and methods are handled properly, with or without implementation. So, there is nothing to do.

Also, I remarks that when you add a file, the contents is parsed and added to the NCB file. And it also add these entities to the ClassView tree. That is now very handy, because our real classes and functions are flooded in all these stuff. But what is great is when you remove the file you added, the classview information disappears, but not the intellisense information ! That is exactlyt what we want !

To summerize what you have to do with your project :

  1. Start with an empty project.
  2. Add all the .h files containing the intellisense information you want.
  3. Perform the replacement of ‘);’ by ‘){}’ to have intellisense information on standalone functions. Do not save !
  4. Remove all the h files to clean the classview tree.
  5. Close the project, and save the NCB file.

You should save your NCB file because sometimes it gets corrupt. If this is the case, you will just have to replace the corrupted NCB file with the saved one, without the need of doing again all this .h stuff.

Visual Studio does not provide support for third party NCB files ( I have not found any file where to add a specific NCB). But it loads three files win32.ncb, crt.ncb, mfcatl.ncb, located in the Microsoft Visual Studio\Common\MSDev98\Bin directory. But as it is not very frequent to use MFC when developping a kernel driver, I suggest you to replace temporarly the mfcatl.ncb file with your ncb file : this ncb will automatically be open for all your projects, and you will not need to do the .h stuff for each project. In addition, when your project’s NCB file is corrupt, you can safely delete it without any loss.

With a little more work you can get this compile, and generate a bsc file. But I do not know in what that can help you…

This method has been successfully tested on Visual Studio 6. As it is very dependant on the way Visual Studio behaves, this may not work on Visual Studio .Net.

II. Your very first driver

II. 1. A little explanation

Well, now everything is ready to begin the real stuff in good conditions. We will see in this section the base structure of a device driver. Note that there is nothing specific to a file system driver. This is only the very smallest device driver.

The structure is very simple : a device driver must export the DriverEntry entry point. The Device Manager will call this function when the driver is being loaded. This function will basically create a device object for the driver, and will register our driver. Additionnal initialization could be done.

We have to intialize our DriverObject with the correct values. A very important thing is the array MajorFunction. Here you indicate the callbacks function to use for a specific request. Here we will indicate the same dispatch function for all the request, and select a wide range of request to handle. This is only for testing purpose, to see when a specific function is called. As our driver does nothing, we do not actually see anything :-). We provide also a callback function called when the driver is being unloaded. This callback has a very special behaviour, especially with File System driver, and we will discuss this matter later. Then we create the device, with a specific name.

The DFSControl dispatch function does not do much. It simply retrieves the request, and print the request in the DebugMonitor. Note that you retrieve the information with IoGetCurrentIrpStackLocation and that you have to finish the request with filling the Irp struct correctly and with IoCompeteRequest and with returning STATUS_NOT_IMPLEMENTED (in our case).

A handy command is DbgPrint. This allows you to print debug information that you will see with the DebugMon utility.

The sources file used to build the project is also very simple. We indicate the name and the type of our driver, some compiler option, the source file(SOURCES=), and the precompiled header(PRECOMPILED_*=). Note that the names in TARGETNAME, in FSD_SERVICE_PATH and in FSD_DRIVER_NAME are very important. You will see them in the WinObj tree.

II. 2. Your first run

This part is very important the first time. You must really understand what you are doing, else you will reboot a bunch of time. While developping a device driver, you should really consider the use of two computers : one for developping, one for testing and debugging. This will allow you to use the sophisticated remote kernel debugging mode, with stepping capabilities. Also, you will be able to search your error while the debuggee computer will be rebooting :-).

The OSR Driver Loader tool makes this step easy.

  1. Launch the utility, select your device driver, and click on register.
  2. Launch then the DebugMon utility, to see the output of your driver. Apply a filter if other drivers are outputting some verbose data.
  3. Click on the Start Driver button. You should see the Driver Entry debug message.
  4. Then click on the Stop Driver button. You should now see the Driver Unload message.

That is it ! You’ve done your first driver. You can play a dozen time with loadin/unloading it…

II. 3. Source File

/**
 * (C) 2002 - Rémi Peyronnet <[email protected]>
 * 
 * Source code based on the brillant documentation of OSR about the RecognizerFsControl
 * 1997 OSR Open Systems Resources, Inc. 
 * 
 */

/// Include the Precompiled Header
#include "PCH.h"

/// Constants
#define FSD_SERVICE_PATH L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\DummyFS0"
#define FSD_DRIVER_NAME L"\\FileSystem\\DummyFS0"

/// Global static objects
static PDRIVER_OBJECT DummyDriverObject;
static PDEVICE_OBJECT DummyDeviceObject;

/// Forward reference
///  The names are prefixed with DFS (DummyFS)
static VOID DFSUnload(PDRIVER_OBJECT);
static NTSTATUS DFSControl(PDEVICE_OBJECT, PIRP);

/// Some usefull defines
#define TracePrint DbgPrint

/** DriverEntry
 * This is a trivial driver, with a trivial entry point.
 * @brief This is the entry point for the driver.
 * @param DriverObject [IN] the driver object for this driver.
 * @param RegistryPath [IN] the path to this driver's key.
 * @return Success
 */
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    NTSTATUS code;
    UNICODE_STRING driverName;

    TracePrint("[DummyFS] Driver Entry (DummyFS - Step0)!\n");

    // Save driver object pointer
    DummyDriverObject = DriverObject;
    // Set up dispatch entry point.
    DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = DFSControl;

    // Other Entry Points - Testing

    DriverObject->MajorFunction[IRP_MJ_CREATE] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_PNP] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_POWER] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_READ] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_WRITE] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_CLEANUP] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = DFSControl;
    DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = DFSControl;

    // Set up unload entry point.
    DriverObject->DriverUnload = DFSUnload;
    // Initialize the name string for the file system Dummy device  object.
    RtlInitUnicodeString(&driverName, FSD_DRIVER_NAME);
    // Create the named device object.
    code = IoCreateDevice(  DummyDriverObject,
                            0,
                            &driverName,
                            FILE_DEVICE_CD_ROM_FILE_SYSTEM, /*FILE_DEVICE_DISK_FILE_SYSTEM, /*FILE_DEVICE_NETWORK_FILE_SYSTEM, */
                            0,
                            FALSE,
                            &DummyDeviceObject);
    if (!NT_SUCCESS(code))
    {
        DbgPrint("[DummyFS] Dummy failed to load, failure in IoCreateDevice call returned 0x%x\n", code);
        return (code);
    }
    // Register the device object as a file system.
    //IoRegisterFileSystem(DummyDeviceObject);
    // Done!
    TracePrint("[DummyFS] Dummy loaded !\n");
    return STATUS_SUCCESS;
}

/** DFSUnload
 *
 * This is the function is called when the OS has determined
 * that their are no more references to the device Object.
 * It is our job to unregister ourselves as a File System and
 * delete the Device Object we created. 
 *
 * @param DeviceObject [IN] presumably our device object
 *
 */
static VOID DFSUnload(PDRIVER_OBJECT DriverObject)
{
    // Delete our device object.
    TracePrint("[DummyFS] Ask for unload.\n");
    if (DummyDeviceObject)
    {
        //IoUnregisterFileSystem(DummyDeviceObject);
        IoDeleteDevice(DummyDeviceObject);
        DummyDeviceObject = 0;
    }
    TracePrint("[DummyFS] Dummy unloaded.\n");
}

/** DFSControl
 *
 * This is the dispatch function. 
 * It handles all the requests to our driver, and dispatch them in 
 * the appropriates functions.
 *
 * @param DeviceObject [IN] presumably our device object
 * @param Irp [IN] the mount/load request
 * @return Status code :
 *   - STATUS_NOT_IMPLEMENTED - something other than we can do
 *   - SUCCESS - load was successfull
 */
static NTSTATUS DFSControl(PDEVICE_OBJECT DeviceObject, PIRP
                                    Irp)
{
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
    UNICODE_STRING driverName;
    NTSTATUS code;

    TracePrint("[DummyFS] DummyFsControl - Maj = %x, Min = %x\n", irpSp->MajorFunction, irpSp->MinorFunction);

    /// Check for our DeviceObject
    if (DeviceObject != DummyDeviceObject)
    {
        // Not ours.
        Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_NOT_IMPLEMENTED;
    }

    switch (irpSp->MinorFunction)
    {
        // Well, for now we don't do anything exciting :)
        default:
            // The function is not implemented
            TracePrint("[DummyFS] DummyFsControl - Not Implemented.\n");
            Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
            Irp->IoStatus.Information = 0;
            IoCompleteRequest(Irp, IO_NO_INCREMENT);
            return STATUS_NOT_IMPLEMENTED;
    }

    // Hum, we should never go there, but who knows...
    DbgPrint("[DummyFS] DummyFsControl end reached.");
    Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_NOT_IMPLEMENTED;
}


III. Your first File System Driver

III. 1. Explanation

In the Step 0, we have seen the basic structure of a Device Driver. In this step, we will see how to register our file system, and how to resolve the unload problems.

Register a filesystem is quite easy. This is done by IoRegisterFileSystem. Doing this, we increment the number of references to our Device Object. But the device manager will unload our driver only if there is zero reference and zero device objects attached to our driver.

To remove all these references and device objects, we will define and implement a user-defined IOCTL (IO Control). This IOCTL will be sent by another program we will develop (unloader). The processing of this IOCTL will be very simple :

  • IoUnregisterFileSystem : to unregister our filesystem
  • IoDeleteDevice : to be unloaded, a filesystem must have all the device deleted. It is a small difference compared to our device driver in the step 0. That is why if the only change you make in the step 0 is a IoRegisterFileSystem directly followed by a IoUnregisterFileSystem, the driver won’t be unloaded.

To be able to call the driver, a symbolic link has been developped. This will allow us to do a CreateFile(“\\.\NameOfTheSymbolicLink”) and to send our IOCTL with DeviceIOControl. Another method is to use the function IoGetDriverObjectPointer but I did not succeed in using it (error during the initialization of the DLL…) This symbolic link will be created in DriverEntry, and deleted in the Unload function. To finish with, the name of our driver must not be the same as our service. Also, you must add a CREATE and CLOSE major function. Here these functions will be really simple : the main goal is not creating file, but obtaining a handle on our driver. So these functions do nothing else but return STATUS_SUCCESS

Here you are, you have now a file system device driver that is capable of unloading. Be carefull in your development, because any error will be fatal, and you will be obliged to reboot…

III. 2. Source File

III. 2. a. DummyFS.c

/**
 * (C) 2002 - Rémi Peyronnet <[email protected]>
 * 
 * Source code based on the brillant documentation of OSR about the RecognizerFsControl
 * 1997 OSR Open Systems Resources, Inc. 
 * 
 */

/** Dummy FS - Step 1 - File System Installation
 * 
 * In the Step 0, we have seen the basic structure of a Device Driver.
 * In this step, we will see how to register our file system, and how to
 *  resolve the unload problems. 
 *
 * Register a filesystem is quite easy. This is done by IoRegisterFileSystem.
 * Doing this, we increment the number of references to our Device Object.
 * But the device manager will unload our driver only if there is zero
 * reference and zero device objects attached to our driver.
 *
 * To remove all these references and device objects, we will define 
 * and implement a user-defined IOCTL. This IOCTL will be sent by
 * another program we will develop (unloader). The processing of this IOCTL
 * will be very simple :
 *  - IoUnregisterFileSystem : to unregister our filesystem
 *  - IoDeleteDevice : to be unloaded, a filesystem must have all the device
 *        deleted. It is a small difference compared to our device driver 
 *        in the step 0. That is why if the only change you make in the step 0
 *        is a IoRegisterFileSystem directly followed by a IoUnregisterFileSystem,
 *        the driver won't be unloaded.
 * 
 * To be able to call the driver, a symbolic link has been developped. This will
 * allow us to do a CreateFile("\\.\NameOfTheSymbolicLink") and to send our IOCTL
 * with DeviceIOControl. Another method is to use the function IoGetDriverObjectPointer
 * but I did not succeed in using it (error during the initialization of the DLL...)
 * This symbolic link will be created in DriverEntry, and deleted in the Unload function.
 *
 * To finish with, the name of our driver must not be the same as our service.
 *
 */


/// Include the Precompiled Header
#include "PCH.h"

/// Constants
#define FSD_SERVICE_PATH L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\DummyFS1"
#define FSD_DRIVER_NAME L"\\FileSystem\\DummyFS1Driver"
#define FSD_DRIVER_DOS_NAME L"\\DosDevices\\DummyFS1"

/// IOCTL codes

#define IOCTL_DUMMYFS_UNREGISTERFS \
            CTL_CODE( FILE_DEVICE_UNKNOWN, 0x4212, METHOD_NEITHER, FILE_ANY_ACCESS)

/// External, non-confidential but unpublished NT entry points
NTSYSAPI NTSTATUS NTAPI ZwLoadDriver(IN PUNICODE_STRING DriverServiceName);
NTKERNELAPI VOID IoRegisterFileSystem(IN OUT PDEVICE_OBJECT DeviceObject);
NTKERNELAPI VOID IoUnregisterFileSystem(IN OUT PDEVICE_OBJECT DeviceObject);

/// Global static objects
static PDRIVER_OBJECT DummyDriverObject;
static PDEVICE_OBJECT DummyDeviceObject;

/// Forward reference
///  The names are prefixed with DFS (DummyFS)
static VOID DFSUnload(PDRIVER_OBJECT);
static NTSTATUS DFSControl(PDEVICE_OBJECT, PIRP);
static NTSTATUS DFSCreate(PDEVICE_OBJECT, PIRP);
static NTSTATUS DFSClose(PDEVICE_OBJECT, PIRP);
static NTSTATUS DFSDispatch(PDEVICE_OBJECT, PIRP);

/// Some usefull defines
#define TracePrint DbgPrint

/** DriverEntry
 * This is a trivial driver, with a trivial entry point.
 * @brief This is the entry point for the driver.
 * @param DriverObject [IN] the driver object for this driver.
 * @param RegistryPath [IN] the path to this driver's key.
 * @return Success
 */
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    NTSTATUS code;
    UNICODE_STRING driverName, driverDosName;

    TracePrint("[DummyFS] Driver Entry (DummyFS - Step1) !\n");

    // Save driver object pointer
    DummyDriverObject = DriverObject;
    // Set up dispatch entry point.
    DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = DFSDispatch;

    // Other Entry Points - Testing

    DriverObject->MajorFunction[IRP_MJ_CREATE] = DFSCreate;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = DFSClose;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DFSControl;

    DriverObject->MajorFunction[IRP_MJ_PNP] = DFSDispatch;
    DriverObject->MajorFunction[IRP_MJ_POWER] = DFSDispatch;
    DriverObject->MajorFunction[IRP_MJ_READ] = DFSDispatch;
    DriverObject->MajorFunction[IRP_MJ_WRITE] = DFSDispatch;
    DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = DFSDispatch;
    DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = DFSDispatch;
    DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = DFSDispatch;
    DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = DFSDispatch;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = DFSDispatch;
    DriverObject->MajorFunction[IRP_MJ_CLEANUP] = DFSDispatch;
    DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = DFSDispatch;


    // Set up unload entry point.
    DriverObject->DriverUnload = DFSUnload;
    // Initialize the name string for the file system Dummy device  object.
    RtlInitUnicodeString(&driverName, FSD_DRIVER_NAME);
    // Create the named device object.
    code = IoCreateDevice(  DummyDriverObject,
                            0,
                            &driverName,
                            FILE_DEVICE_CD_ROM_FILE_SYSTEM, /*FILE_DEVICE_DISK_FILE_SYSTEM, /*FILE_DEVICE_NETWORK_FILE_SYSTEM, */
                            0,
                            FALSE,
                            &DummyDeviceObject);
    if (!NT_SUCCESS(code))
    {
        DbgPrint("[DummyFS] Dummy failed to load, failure in IoCreateDevice call returned 0x%x\n", code);
        return (code);
    }


    // Create a symbolic link to the DOS Name
    RtlInitUnicodeString(&driverDosName, FSD_DRIVER_DOS_NAME);
    code = IoCreateSymbolicLink(&driverDosName, &driverName);
    if (!NT_SUCCESS(code))
    {
        DbgPrint("[DummyFS] CreateSymbolicName failed, code = 0x%x\n", code);
        IoDeleteDevice(DummyDeviceObject);
        DummyDeviceObject = 0;
        return (code);
    }


    // Register the device object as a file system.

    IoRegisterFileSystem(DummyDeviceObject);
    //IoUnregisterFileSystem(DummyDeviceObject); 

    // Done!
    TracePrint("[DummyFS] Dummy loaded !\n");
    return STATUS_SUCCESS;
}

/** DFSUnload
 *
 * This is the function is called when the OS has determined
 * that their are no more references to the device Object.
 * It is our job to unregister ourselves as a File System and
 * delete the Device Object we created. 
 *
 * Some of the actions performed should already be done. In
 * fact, they must be done before, else the driver won't be
 * unloaded by the driver manager, and the driver will stay
 * in UNLOAD_PENDING mode.
 *
 * @param DeviceObject [IN] presumably our device object
 *
 */
static VOID DFSUnload(PDRIVER_OBJECT DriverObject)
{
    UNICODE_STRING driverDosName;

    // Delete our device object.
    TracePrint("[DummyFS] Ask for unload.\n");
    // Remove the symbolic link
    RtlInitUnicodeString(&driverDosName, FSD_DRIVER_DOS_NAME);
    IoDeleteSymbolicLink(&driverDosName);

    if (DummyDeviceObject)
    {
        // Unregister the File System - Should be already done
        IoUnregisterFileSystem(DummyDeviceObject);

        // Delete the Device - Should be done too
        IoDeleteDevice(DummyDeviceObject);
        DummyDeviceObject = 0;
    }
    TracePrint("[DummyFS] Dummy unloaded.\n");
}

/** DFSCreate
 *
 * This is the create function. 
 * It handles all the create requests to our driver.
 *
 * Currently it does only return SUCCESS if the DeviceObject is 
 * ours, in order the CreateFile("\\\\.\\\OurDevice") to work.
 * 
 *
 * @param DeviceObject [IN] presumably our device object
 * @param Irp [IN] the mount/load request
 * @return Status code :
 *   - STATUS_NOT_IMPLEMENTED - something other than we can do
 *   - SUCCESS - create was successfull
 */
static NTSTATUS DFSCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    TracePrint("[DummyFS] DFSCreate\n");
    if (DeviceObject == DummyDeviceObject)
    {
        // Our driver
        Irp->IoStatus.Status = STATUS_SUCCESS;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_SUCCESS;
    }

    // Not ours, we don't do anything from now.
    Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_NOT_IMPLEMENTED;
}

/** DFSClose
 *
 * This is the close function. 
 * It handles all the close requests to our driver.
 *
 * Currently it does only return SUCCESS if the DeviceObject is 
 * ours, in order the CloseHandle to work.
 * 
 *
 * @param DeviceObject [IN] presumably our device object
 * @param Irp [IN] the mount/load request
 * @return Status code :
 *   - STATUS_NOT_IMPLEMENTED - something other than we can do
 *   - SUCCESS - close was successfull
 */
static NTSTATUS DFSClose(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    TracePrint("[DummyFS] DFSClose\n");

    if (DeviceObject == DummyDeviceObject)
    {
        // Our driver
        Irp->IoStatus.Status = STATUS_SUCCESS;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_SUCCESS;
    }

    // Not ours, we don't do anything from now.
    Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_NOT_IMPLEMENTED;
}


/** DFSControl
 *
 * This is the dispatch function for IOCTL codes. 
 * It handles all the IOCTLS to our driver.
 *
 * @param DeviceObject [IN] presumably our device object
 * @param Irp [IN] the mount/load request
 * @return Status code :
 *   - STATUS_NOT_IMPLEMENTED - something other than we can do
 *   - SUCCESS - IOCTL was successfull
 */
static NTSTATUS DFSControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
    UNICODE_STRING driverName;
    NTSTATUS code;

    //TracePrint("[DummyFS] DFSControl - IOCTL = 0x%x\n", irpSp->Parameters.DeviceIoControl.IoControlCode);

    /// Check for our DeviceObject
    if (DeviceObject != DummyDeviceObject)
    {
        // Not ours.
        Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_NOT_IMPLEMENTED;
    }

    switch (irpSp->Parameters.DeviceIoControl.IoControlCode)
    {
        case IOCTL_DUMMYFS_UNREGISTERFS :
            // Unregister our FileSystem
            //  This is mandatory to remove the reference on our device object,
            //  else it won't be possible to unload our driver.
            TracePrint("[DummyFS] DFSControl - IOCTL DummyFS_UnregisterFS\n");

            Irp->IoStatus.Status = STATUS_SUCCESS;
            Irp->IoStatus.Information = 0;
            IoCompleteRequest(Irp, IO_NO_INCREMENT);

            // Prepare the driver to be unloaded : 
            //  - unregister the FS
            //  - for a FSD to be unloaded, all the device must be deleted, so delete it.
            // It may be an idea to call DFSUnload instead of duplicate the code.
            IoUnregisterFileSystem(DummyDeviceObject);
            IoDeleteDevice(DummyDeviceObject);
            DummyDeviceObject = 0;
            return STATUS_SUCCESS;

        // Well, for now we don't do anything exciting :)
        default:
            // The function is not implemented
            TracePrint("[DummyFS] DFSControl - Unknown IOCTL = 0x%x\n", irpSp->Parameters.DeviceIoControl.IoControlCode);
            Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
            Irp->IoStatus.Information = 0;
            IoCompleteRequest(Irp, IO_NO_INCREMENT);
            return STATUS_NOT_IMPLEMENTED;
    }

    // Hum, we should never go there, but who knows...
    DbgPrint("[DummyFS] DummyFsControl end reached.");
    Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_NOT_IMPLEMENTED;
}

/** DFSDispatch
 *
 * This is the dispatch function. 
 * It handles all the requests to our driver, and dispatch them in 
 * the appropriates functions.
 *
 * @param DeviceObject [IN] presumably our device object
 * @param Irp [IN] the mount/load request
 * @return Status code :
 *   - STATUS_NOT_IMPLEMENTED - something other than we can do
 *   - SUCCESS - load was successfull
 */
static NTSTATUS DFSDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
    UNICODE_STRING driverName;
    NTSTATUS code;

    TracePrint("[DummyFS] DFsDispatch - Maj = %x, Min = %x\n", irpSp->MajorFunction, irpSp->MinorFunction);

    /// Check for our DeviceObject
    if (DeviceObject != DummyDeviceObject)
    {
        // Not ours.
        Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_NOT_IMPLEMENTED;
    }

    switch (irpSp->MajorFunction)
    {
        // Well, for now we don't do anything exciting :)
        default:
            // The function is not implemented
            TracePrint("[DummyFS] DummyFsControl - Not Implemented.\n");
            Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
            Irp->IoStatus.Information = 0;
            IoCompleteRequest(Irp, IO_NO_INCREMENT);
            return STATUS_NOT_IMPLEMENTED;
    }

    // Hum, we should never go there, but who knows...
    DbgPrint("[DummyFS] DummyFsControl end reached.");
    Irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_NOT_IMPLEMENTED;
}


III. 2. b. Unloader

/**
 * (C) 2002 - Rémi Peyronnet <[email protected]>
 * 
 * Source code based on the brillant documentation of OSR about the RecognizerFsControl
 * 1997 OSR Open Systems Resources, Inc. 
 * 
 */

/// Includes
#include <stdio.h>
#include <assert.h>
#include <windows.h>
#include <winioctl.h>
//#include <ntifs.h>
//#include <ntddk.h>

//#include "dummyfs.h"

/// Constants 
#define FSD_SERVICE_PATH L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\DummyFS1"
#define FSD_DRIVER_NAME L"\\FileSystem\\DummyFS1"
// ROOT\LEGACY_DUMMYFS\0000


/// IOCTL codes
#define IOCTL_DUMMYFS_UNREGISTERFS \
            CTL_CODE( FILE_DEVICE_UNKNOWN, 0x4212, METHOD_NEITHER, FILE_ANY_ACCESS)


/// External, non-confidential but unpublished NT entry points
/*
NTSYSAPI NTSTATUS NTAPI ZwLoadDriver(IN PUNICODE_STRING DriverServiceName);
NTSYSAPI NTSTATUS NTAPI ZwUnloadDriver(IN PUNICODE_STRING DriverServiceName);
NTKERNELAPI VOID IoRegisterFileSystem(IN OUT PDEVICE_OBJECT DeviceObject);
NTKERNELAPI VOID IoUnregisterFileSystem(IN OUT PDEVICE_OBJECT DeviceObject);

/// Global static objects
static PDRIVER_OBJECT DummyDriverObject;
static PDEVICE_OBJECT DummyDeviceObject;
*/

/** Main
 */
void __cdecl main(int argc, char ** argv)
{
    int size;

    /*
    NTSTATUS code;
    UNICODE_STRING driverName;
    UNICODE_STRING driverServiceName;
    PFILE_OBJECT fileObject;
    PDEVICE_OBJECT deviceObject;
    */

    // First Try : CreateFile
    HANDLE hDriver;
    char * driverFileName;

    if (argc != 1)
    {
        driverFileName = argv[1];
    }
    else
    {
        driverFileName = "\\\\.\\DummyFS1";

    }


    /// Send a sppecial IOCTL to the driver to unregister it.

    //  - Open the Driver
    printf("[Unloader] Opening %s.\n",driverFileName);
    hDriver = CreateFile(driverFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hDriver == INVALID_HANDLE_VALUE)
    {
        printf("[Unloader] CreateFile failed.\n");
    }

    // - Send the IOCTL

    DeviceIoControl(hDriver, IOCTL_DUMMYFS_UNREGISTERFS, NULL, 0, NULL, 0, &size, NULL);

    CloseHandle(hDriver);



    /*

    DbgPrint("[Unloader] Starting...\n");

    // Initialize the name string for the file system Dummy device  object.
    RtlInitUnicodeString(&driverName, FSD_DRIVER_NAME);

    // Get the Device Pointer
    fileObject = NULL;
    deviceObject = NULL;
    
    code = IoGetDeviceObjectPointer(&driverName, FILE_READ_ATTRIBUTES, &fileObject, &deviceObject);
    if (!NT_SUCCESS(code)) {
        DbgPrint("[Unloader] IoGetDeviceObjectPointer Error 0x%x\n", code);
    }
    

    */

    /*
    /// Unload the Driver 
    // Initialize the name string for the file system Dummy device  object.
    RtlInitUnicodeString(&driverServiceName, FSD_SERVICE_PATH);

    // Tenter : IoGetDeviceObjectPointer ....
    code = ZwUnloadDriver(&driverServiceName);
    if (!NT_SUCCESS(code)) {
        DbgPrint("[Unloader] Error 0x%x\n", code);
        return;
    }
    */


    return;
}



Conclusion

I hope this has been usefull to you, and that like me you will have pleasure to develop a file system driver. That is a hard work, but as it is very technical, it is fascinating. Also, it unleashes your possibilities with your computer.

Références