Dismounting Volumes in a Hibernate Once/Resume Many Configuration

 

by Daniel Simpson and Katherine Enos
Microsoft Corporation
March 2005
Applies to Microsoft® Windows® XP Embedded with Service Pack 2

Summary

This paper details how to programmatically dismount volumes in an Enhanced Write Filter (EWF)-enabled Hibernate Once/Resume Many configuration. This is an advanced configuration of EWF and requires a thorough understanding of EWF, Windows XP Embedded, and application development.

Contents

Introduction

Protecting Multiple Volumes in a Hibernate Once/Resume Many Configuration

Dismounting Volumes before Hibernation

Programmatically Dismounting Volumes

For More Information

Sample Code

Introduction

Using the Microsoft® Windows® Embedded with Service Pack 2 Hibernate Once/Resume Many functionality not only protects your system from unwanted write access, but it also provides for quicker boot times. However, one of the limitations of this implementation is that if you have more than one partition on your system, all of the partitions on your system must be protected by the Enhanced Write Filter (EWF). This white paper discusses how to configure your run-time image to support multiple partitions that do not need to be protected by EWF.

This white paper details advanced configurations of EWF and requires a thorough understanding of EWF, Hibernate Once/Resume Many, and mounting and dismounting file systems. It is important to understand these concepts before proceeding with the instructions in this white paper.

For more information about Enhanced Write Filter, see EWF Overview.

For more information about Hibernate Once/Resume Many, see Hibernation and EWF.

For more information about programmatically managing volumes for Windows, see Volume Management.

Protecting Multiple Volumes in a Hibernate Once/Resume Many Configuration

One of the limitations of implementing a Hibernate Once/Resume Many environment on your device is that all of the partitions on your system must be protected by EWF. Because the file system caches information about each partition on the system, that file system information is loaded when the system boots from the hibernation file. If a write is made to the system and that write is not captured in the hibernation file, the next time the system boots, the hibernation file will not match the contents of the partition and the system may become corrupt.

For example, on a system with two partition, C drive and D drive, you enable EWF. However, EWF is only enabled for C drive. D drive is not protected by EWF.

When you creates the hibernation file, information about the contents of C and D drives are included in the hibernation file. This is because the file system caches information about the attached volumes in the system.

When the system boots, it loads information in the hibernation file about both C and D drives.

You then delete several files from D drive. Because D drive is not protected by EWF, these files are deleted from the system.

The system reboots, and loads information from the hibernation file. Because the hibernation file still includes cached information about the contents of D drive, that information is loaded into RAM. Because the files that you deleted from D drive no longer exist in the system, the contents of the system's RAM and the contents of D drive do not match. There is now potential for the system to become corrupted. This is why EWF must protect all partitions in a Hibernate Once/Resume Many environment.

However, it is possible to flush the contents of a non-boot volume from system cache by unmounting the volume before you create the hibernation file.

Dismounting Volumes Before Hibernation

To ensure that the write cache for a volume is cleared before you create the hibernation file, you must dismount the volume. When a volume is dismounted, the write cache for that volume is flushed from system memory. It is then safe to create the hibernation file. By dismounting any volumes you do not want protected, you can ensure that any write cache data from the system is not written to the hibernation file.

Note   This does not guarantee that applications do not have cached state information from the dismounted volumes. It is the responsibility of the designer of the Windows XP Embedded-based system to monitor applications and verify that they do not contain any state information about the volume.

When the system starts from the hibernation file, no information is loaded about the volumes you dismounted. The system rediscovers additional volumes each and every time the system boots from the hibernation file. Because there is no information about the volume that is loaded from the hibernation file, you can safely write to an unprotected volume.

To dismount a volume, you can create an application that will lock and dismount the volume, and then create the hibernation file.

Programmatically Dismounting Volumes

To lock and dismount a volume, you must create an application that calls the DeviceIoControl function. This function sends control codes directly to the file system. By passing the parameters FSCTL_LOCK_VOLUME and FSCTL_DISMOUNT_VOLUME to the function, you can lock and dismount the volume before the hibernation file is created.

Use the following program flow to lock and dismount a volume, and then hibernate the system. The application must also support unlocking the volume when the system boots from the hibernation file.

  1. Create an application to lock and dismount a volume you do not want protected by EWF. Use the DeviceIoControl function with the dwIoControlCode parameter FS_LOCK_VOLUME. This locks the volume. If there is an open file handle to the volume when this function is called, the volume fails to lock. By locking the volume before you dismount it, you can ensure that the volume is dismounted cleanly. For example:

    DeviceIoControl(
         hDevice,
         FSCTL_LOCK_VOLUME,
         NULL,
         0,
         NULL,
         0,
         &cbReturned,
         NULL
         )
    

    For more information, see DeviceIOControl and FSCTL_LOCK_VOLUME in the MSDN library.

  2. After the volume is locked, the volume can be dismounted. In your application, call the DeviceIoControl function with the dwIoControlCode parameter FSCTL_DISMOUNT_VOLUME to dismount the volume. This parameter dismounts the volume from the file system. If you do not lock the volume before you dismount it, any open file handles are invalidated and the volume forcibly dismounts.

    After the volume is dismounted, any information in the write cache about the volume is flushed, and you can safely write the hibernation file.

    For example, call the DeviceIoControl function with the following values:

    DeviceIoControl(
         hDevice,
         FSCTL_DISMOUNT_VOLUME,
         NULL,
         0,
         NULL,
         0,
         &cbReturned,
         NULL
         )
    

    For more information, see DeviceIOControl and FSCTL_DISMOUNT_VOLUME in the MSDN library.

  3. After the volume is dismounted, create the hibernation file and shut down the system.

    Use the SetSuspendState function of the Windows SDK. For example,

    SetSuspendState ( 
         Hibernate,
         ForceCritical,
         DisableWakeEvent ) 
    

    For more information, see SetSuspendState in the Windows SDK.

    -Or-

    You can use the power management functions included with Windows XP Embedded, for example:

    XPE_Hibernate( 
         bForceState 
    );
    

    To use the power management functions, your run-time image requires the following files: Xpepm.dll, Xpepm.lib, and Xpepm.h. All three files are located in the VALUEADD\MSFT\XPEPM folder on Disk 1 of the product media.

    For more information, see Power Management APIs.

  4. After the system hibernates, boot the system. The system resumes from the hibernation file.

  5. To allow the system to rediscover the volume, the volume first must be unlocked immediately after the system boots. . Use the DeviceIoControl function with the dwIoControlCode parameter FS_UNLOCK_VOLUME. This unlocks the volume.

    DeviceIoControl(
         hDevice,
         FSCTL_UNLOCK_VOLUME,
         NULL,
         0,
         NULL,
         0,
         &cbReturned,
         NULL
         )
    

    For more information, see DeviceIOControl and FSCTL_UNLOCK_VOLUME in the MSDN library.

    After the volume is unlocked, the system automatically rediscovers the volume and makes it accessible. Because the hibernation file does not include any information about the volume, you must unlock the volume on every reboot.

For More Information

Hibernation and EWF
Enhanced Write Filter
DeviceIOControl
FSCTL_DISMOUNT_VOLUME
FSCTL_LOCK_VOLUME
FSCTL_UNLOCK_VOLUME
Power Management APIs

Sample Code

The following sample code is provided as reference only and is not supported by Microsoft.

/******************************************************************************
 *  File: dismount_all_in_one.cpp
 *  Copyright: (c) Microsoft Corporation 2005
 *  Purpose: This sample application accompanies the Windows XP Embedded white
 *  paper called 'Dismounting Volumes in a Hibernate Once/Resume Many Configuration.'
 *  Description: This is a command-line application that:
 *       - Receives input from the command line specifying one or more volumes to be dismounted
 *       - Tests to see if hibernation is enabled
 *       - Locks the specified volumes
 *       - Dismounts the specified volumes
 *       - Causes the system to sleep before hibernation to show screen messages
 *       - Causes the system to hibernate for about 20 seconds
 *       - Uses a waitable timer to cause the system to resume from hibernation
 *       - Unlocks the volumes that were locked
 *  Requirements:
 *       - Windows XP Professional or a Windows XP Embedded-based run-time image
 *         that includes the required dependencies
 *       - Hibernation must be enabled
 *       - The powrprof.lib library must be added to the project
 *       - Early versions of the powrprof.h header file had problems that can
 *         prevent it being included in a .cpp file or included multiple times.
 *         Typically, early versions of the powrprof.h header file must be modified
 *         to resolve this problem; however, this file uses a #ifdef directive that 
 *         resolves this problem without modification of the powrprof.h header file.
 *         For more information about problems with the powrprof.h header file,
 *         see the Power Schemes topic in the Power Management SDK documentation
 *         at https://msdn.microsoft.com/library/default.asp?url=/library/en-us/power/base/power_schemes.asp
 *****************************************************************************/

/******************************************************************************
 *   C O N S T A N T S    A N D    M A C R O S
 ******************************************************************************/

# define _WIN32_WINNT 0x0501
# define WINVER 0x0501
# define MAX_ARRAY_SIZE 26


/******************************************************************************
 *   I N C L U D E    D I R E C T I V E S
 ******************************************************************************/

#include <windows.h>

#ifdef __cplusplus
extern "C" {
#endif
#include <powrprof.h>
#ifdef __cplusplus
}
#endif

#include <winioctl.h>
#include <tchar.h>
#include <strsafe.h>
#include <string.h>
#include <stdio.h>



/******************************************************************************
 *  P R I N T U S A G E
 ******************************************************************************/

void PrintUsage()
{
   _tprintf( _T("usage: dismount.exe <volume-path-list>\n"));
   _tprintf( _T("For example: d: f:\n") );
}


/******************************************************************************
 *  M A I N
 ******************************************************************************/

int _cdecl _tmain( int argc, TCHAR * argv[] )
{
   
   _TCHAR szNtDosVolume[7] = _T("\\\\.\\A:");
   DWORD cbReturned;   
   int iCounter;   
   int iVolume;
   int iNumberOfVolumes;
   TCHAR * psz;                     
   BOOLEAN Hibernate = TRUE;      
   BOOLEAN ForceCritical = TRUE;         
   BOOLEAN DisableWakeEvent = FALSE;   
   HANDLE hTimer = NULL;   
   LARGE_INTEGER liDueTime = {0};
   DWORD TimeToSleep = 5000;
   HANDLE lpHandles[MAX_ARRAY_SIZE];
   BOOLEAN Passed = FALSE;

   liDueTime.QuadPart = -200000000;

   // Initialize the array that will hold the handles to the volumes to be dismounted.

   for (iVolume = 0; iVolume < MAX_ARRAY_SIZE; iVolume++)
   {
      lpHandles[iVolume] = INVALID_HANDLE_VALUE;
   }


   // Determine whether the operating system supports hibernation.
   // If hibernation is not enabled, print an error to the screen and exit.

   if ( !IsPwrHibernateAllowed() ) 
   {
      _tprintf( _T( "This system does not support hibernation. Exiting. \n" ) );
      goto done;
   }


   // Check to ensure that the count of arguments is correct.

   if ( argc < 2 ) 
   {
      // Provide usage information.
      PrintUsage();
      goto done;
   }


   // Begin loop to get drive letter identifiers, open and validate handles
   // for each volume, and lock and dismount volumes.

   iVolume = 0;

   for ( iCounter = 1; iCounter < argc; iCounter++ ) 
   {
      psz = argv[iCounter];

      if ( * psz == _T('/')  || * psz == _T('-') ) 
      {
         psz++;

         if ( * psz == _T('?') ) 
         {
            PrintUsage();
            goto done;
         }
      }

      szNtDosVolume[4] = psz[0];


      // Retrieve a handle to the volume to be locked.
      lpHandles[iVolume] = CreateFile( szNtDosVolume, 
                        GENERIC_READ, 
                        FILE_SHARE_READ | FILE_SHARE_WRITE, 
                        NULL, 
                        OPEN_EXISTING, 
                        FILE_FLAG_NO_BUFFERING, 
                        NULL );


      // Test for an invalid handle.
      // If the handle is invalid, print an error to screen and exit. 

      if ( lpHandles[iVolume] == INVALID_HANDLE_VALUE ) 
      {
         _tprintf( _T( "Failed to open the volume %s with error %ld\n" ), psz, GetLastError() );
         goto done;
      }
   

      // Use the FSCTL_LOCK_VOLUME control code to lock the volume.
      // If the volume cannot be locked, print an error to the screen and exit.

      if ( !DeviceIoControl( lpHandles[iVolume], 
                        FSCTL_LOCK_VOLUME, 
                        NULL, 
                        0, 
                        NULL, 
                        0, 
                        &cbReturned, 
                        NULL ) ) 
      {
         _tprintf( _T( "Failed to lock the volume %s with error %ld\n" ), psz, GetLastError() );
         goto done;
      }


      // If the volume lock is successful, print a status message to the screen.

      else 
      {
         _tprintf( _T( "Locked %s\n" ), psz ); 
      }



      // Use the FSCTL_DISMOUNT_VOLUME control code to dismount the volume.
      // If the volume cannot be dismounted, print an error to the screen and exit.

      if ( !DeviceIoControl( lpHandles[iVolume], 
                        FSCTL_DISMOUNT_VOLUME, 
                        NULL, 
                        0, 
                        NULL, 
                        0, 
                        &cbReturned, 
                        NULL ) ) 
      {
         _tprintf( _T( "Failed to dismount the volume %s with error %ld\n" ), psz, GetLastError() );
         goto done;
      }


      // If the volume dismount is successful, print a status message to the screen.

      else 
      {
         _tprintf( _T( "Dismounted %s\n" ), psz ); 

         iVolume++;
      }

   }


   // Pause the thread by sleeping for 5000ms (5 seconds) to give time to display screen messages.
   Sleep(TimeToSleep);


   // Create a waitable timer to time the wake-up from hibernation.
   // The first parameter receives NULL, which gives the timer object a deafult security descriptor.
   // The second parameter receives FALSE, which creates a synchronization timer instead of a 
   // manual reset notification timer.
   // TimeToResume is the name of the timer.

    hTimer = CreateWaitableTimer( NULL, 
                          FALSE, 
                          "TimeToResume" 
                         );


   
   // If CreateWaitableTimer fails, it returns NULL.
   // If CreateWaitableTimer fails, print a failure message to the screen and exit.

    if ( hTimer == NULL )
    {
        _tprintf( _T( "CreateWaitableTimer failed. %ld\n" ), GetLastError() );
        goto done;
    }


   // Set the timer to wait for 20 seconds.
    
   if (!SetWaitableTimer( hTimer, 
                     &liDueTime, 
                     0, 
                     NULL, 
                     NULL, 
                     TRUE ) )
    {
        _tprintf( _T("SetWaitableTimer failed. %ld\n" ), GetLastError() );
        goto done;
    }


   // Set the suspend state. Here Hibernate is true, ForceCritical is true,
   // and DisableWakeEvent is false.
   // If the suspend state cannot be successfully set, print a failure message to the screen and exit.

   if ( !SetSuspendState ( Hibernate,
                     ForceCritical,
                     DisableWakeEvent ) )

   {
      _tprintf( _T("Failed to set the suspend state with error %ld\n" ), GetLastError() );
      goto done;
   }   


   // Save the count of valid handles to volumes and use it in the second loop.
   iNumberOfVolumes = iVolume;

   // Begin new loop to iterate through the array of handles to volumes
   // and unlock each volume.

   for ( iVolume = 0; iVolume < iNumberOfVolumes; iVolume++ )
   {
      // Use the FSCTL_UNLOCK_VOLUME control code to unlock the volume.
      // If the volume cannot be unlocked, print an error to the screen and exit.

      if ( !DeviceIoControl( lpHandles[iVolume], 
                        FSCTL_UNLOCK_VOLUME, 
                        NULL, 
                        0, 
                        NULL, 
                        0, 
                        &cbReturned, 
                        NULL ) ) 
      {
         _tprintf( _T("Failed to unlock the volume %s with error %ld\n" ), argv[ iVolume + 1], GetLastError() );
         goto done;
      }


      // If the volume is unlocked, print a status message to the screen
      else 
      {
         _tprintf( _T("Unlocked %s\n"), argv[ iVolume + 1] ); 
      }
   }

   Passed = TRUE;
   
done:

      // Close handle to the timer. 

      if ( ( hTimer != NULL ) )
      {
         if ( !CloseHandle( hTimer ) )
         {
            _tprintf( _T("Failed to close the handle to the waitable timer with error %ld\n"), GetLastError() );
         }
      }

      for ( iVolume = 0; iVolume < MAX_ARRAY_SIZE; iVolume++ )
      {
         // Close all valid handles contained in the array of handles.

         if ( lpHandles[iVolume] != INVALID_HANDLE_VALUE )
         {
            if ( !CloseHandle( lpHandles[iVolume] ))
            {
               _tprintf( _T("Failed to close a handle to a volume with error %ld\n"), GetLastError() );
            }
         }
      }

      return Passed ? 0 : 1;
}

© Microsoft Corporation. All rights reserved.