[filedisk] Support hot-swapping to another file
authorShao Miller <Shao.Miller@yrdsb.edu.on.ca>
Sat, 12 Jun 2010 23:39:51 +0000 (19:39 -0400)
committerShao Miller <Shao.Miller@yrdsb.edu.on.ca>
Sun, 13 Jun 2010 02:51:31 +0000 (22:51 -0400)
We can currently boot from a GRUB4DOS sector-mapped disk.
GRUB4DOS has the requirement that the sector range be
contiguous (a contiguous file will do).  In a filesystem,
things like defragmentation or other physical movement of
the image file is possible, which would likely spell
disaster if we continued to simply use the range of
sectors on the backing disk.

It is therefore a good idea to use the image file as soon
as possible, rather than the disk.  This commit introduces
the ability to hot-swap to the file pretty early on.  A
file lock is then had, and the backing filesystem and the
image file are protected from being pulled out from
underneath us.

Since GRUB4DOS doesn't pass the filename used (there might
not even be one; it could honestly just be a sector range),
we improvise a means by which filenames can be associated:

  #!GRUB4DOS MENU.LST
  title Boot disk image file
    # Establish a buffer for passing filenames
    map --rd-size=2048
    map --mem (rd)+4 (0x55)
    # Establish the sector-mapped disk
    root (hd0,0)
    map /WinXP.HDD (hd0)
    # Seal the INT 0x13 hook
    map --hook
    # Pass info via the buffer
    write (0x55) #!GRUB4DOS\x00v=1\x00WinXP.HDD\x00\x80\x00
    # Mount the FS in the disk image file and boot NTLDR
    root (hd0,0)
    chainloader /ntldr

In the above, (rd) is a built-in "disk" with a base and a
size.  By default, the base is at memory address 0.  We
adjust the size to be 2048 bytes.  We then use map --mem
to establish a RAM disk (which we are simply abusing as
a buffer) by copying 4 sectors, 512 bytes each, from (rd).
map --mem RAM disks are marked-up in the INT 0x15, AX=0xE820
memory map, so should be protected against accidental
corruption.  After establishing the sector-mapped disk,
we use the write command to put our parameters in the
buffer.  We put

a signature:

  #!GRUB4DOS\x00

a version:

  v=1\x00

a filename:

  WinXP.HDD\x00

the associated drive number (hd0 == 0x80, etc.):

  \x80

the next filename is an ASCII NUL, meaning no more files:

  \x00

The signature was chosen while keeping a particular future
possibility in mind: Passing menu.lst itself as the phony
RAM disk.  All of the path and disk information is in this
file, which makes it fairly convenient.  It would mean
needing to be able to distinguish the menu choice as well
as a extensive parsing system, but it's not impossible.

src/include/filedisk.h
src/winvblock/filedisk/filedisk.c
src/winvblock/filedisk/grub4dos.c

index bcc747b..df87ea1 100644 (file)
@@ -41,6 +41,8 @@ winvblock__def_struct ( filedisk__type )
   KSPIN_LOCK req_list_lock;
   KEVENT signal;
   disk__io_routine sync_io;
+  char *filepath;
+  UNICODE_STRING filepath_unicode;
 };
 
 extern irp__handler_decl (
@@ -87,4 +89,8 @@ extern filedisk__type_ptr filedisk__create_threaded (
 #  define filedisk__get_ptr( dev_ptr ) \
   ( ( filedisk__type_ptr ) ( disk__get_ptr ( dev_ptr ) )->ext )
 
+extern void STDCALL filedisk__hot_swap_thread (
+  IN void *StartContext
+ );
+
 #endif                         /* _filedisk_h */
index 69e7268..4ae3e23 100644 (file)
@@ -26,6 +26,8 @@
 
 #include <stdio.h>
 #include <ntddk.h>
+#include <initguid.h>
+#include <ntddstor.h>
 
 #include "winvblock.h"
 #include "portable.h"
@@ -152,7 +154,7 @@ irp__handler_decl ( filedisk__attach )
                      ( char * )&buf[sizeof ( mount__filedisk )] );
   status = RtlAnsiStringToUnicodeString ( &file_path2, &file_path1, TRUE );
   if ( !NT_SUCCESS ( status ) )
-    return status;
+    goto err_ansi_to_unicode;
   InitializeObjectAttributes ( &obj_attrs, &file_path2,
                               OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL,
                               NULL );
@@ -165,11 +167,10 @@ irp__handler_decl ( filedisk__attach )
                   FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
                   FILE_OPEN,
                   FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS |
-                  FILE_NO_INTERMEDIATE_BUFFERING |
                   FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 );
   RtlFreeUnicodeString ( &file_path2 );
   if ( !NT_SUCCESS ( status ) )
-    return status;
+    goto err_file_open;
 
   filedisk_ptr->file = file;
 
@@ -196,11 +197,8 @@ irp__handler_decl ( filedisk__attach )
     ZwQueryInformationFile ( file, &io_status, &info, sizeof ( info ),
                             FileStandardInformation );
   if ( !NT_SUCCESS ( status ) )
-    {
-      ZwClose ( file );
-      free_filedisk ( filedisk_ptr->disk->device );
-      return status;
-    }
+    goto err_query_info;
+
   filedisk_ptr->disk->LBADiskSize =
     info.EndOfFile.QuadPart / filedisk_ptr->disk->SectorSize;
   filedisk_ptr->disk->Cylinders = params->cylinders;
@@ -222,6 +220,16 @@ irp__handler_decl ( filedisk__attach )
    */
   bus__add_child ( bus__boot (  ), filedisk_ptr->disk->device );
   return STATUS_SUCCESS;
+
+err_query_info:
+
+  ZwClose ( file );
+err_file_open:
+
+err_ansi_to_unicode:
+
+  free_filedisk ( filedisk_ptr->disk->device );
+  return status;
 }
 
 static
@@ -503,3 +511,200 @@ err_nofiledisk:
 
   return NULL;
 }
+
+/**
+ * Find and hot-swap to a backing file
+ *
+ * @v filedisk_ptr      Points to the filedisk needing to hot-swap
+ * @ret                 TRUE once the new file has been established, or FALSE
+ *
+ * We search all filesystems for a particular filename, then swap
+ * to using it as the backing store.  This is currently useful for
+ * sector-mapped disks which should really have a file-in-use lock
+ * for the file they represent.  Once the backing file is established,
+ * we return TRUE.  Otherwise, we return FALSE and the hot-swap thread
+ * will keep trying.
+ */
+static winvblock__bool STDCALL
+hot_swap (
+  filedisk__type_ptr filedisk_ptr
+ )
+{
+  NTSTATUS status;
+  GUID vol_guid = GUID_DEVINTERFACE_VOLUME;
+  PWSTR sym_links;
+  PWCHAR pos;
+
+  /*
+   * Find the backing volume and use it.  We walk a list
+   * of unicode volume device names and check each one for the file
+   */
+  status = IoGetDeviceInterfaces ( &vol_guid, NULL, 0, &sym_links );
+  if ( !NT_SUCCESS ( status ) )
+    return FALSE;
+  pos = sym_links;
+  status = STATUS_UNSUCCESSFUL;
+  while ( *pos != UNICODE_NULL )
+    {
+      UNICODE_STRING path;
+      PFILE_OBJECT vol_file_obj;
+      PDEVICE_OBJECT vol_dev_obj;
+      UNICODE_STRING vol_dos_name;
+      UNICODE_STRING filepath;
+      static const WCHAR obj_path_prefix[] = L"\\??\\";
+      static const WCHAR path_sep = L'\\';
+      OBJECT_ATTRIBUTES obj_attrs;
+      HANDLE file = 0;
+      IO_STATUS_BLOCK io_status;
+
+      RtlInitUnicodeString ( &path, pos );
+      /*
+       * Get some object pointers for the volume
+       */
+      status =
+       IoGetDeviceObjectPointer ( &path, FILE_READ_DATA, &vol_file_obj,
+                                  &vol_dev_obj );
+      if ( !NT_SUCCESS ( status ) )
+       goto err_obj_ptrs;
+      /*
+       * Get the DOS name
+       */
+      vol_dos_name.Buffer = NULL;
+      vol_dos_name.Length = vol_dos_name.MaximumLength = 0;
+      status =
+       RtlVolumeDeviceToDosName ( vol_file_obj->DeviceObject, &vol_dos_name );
+      if ( !NT_SUCCESS ( status ) )
+       goto err_dos_name;
+      /*
+       * Build the file path.  Ugh, what a mess
+       */
+      filepath.Length = filepath.MaximumLength =
+       sizeof ( obj_path_prefix ) - sizeof ( UNICODE_NULL ) +
+       vol_dos_name.Length + sizeof ( path_sep ) +
+       filedisk_ptr->filepath_unicode.Length;
+      filepath.Buffer = ExAllocatePool ( NonPagedPool, filepath.Length );
+      if ( filepath.Buffer == NULL )
+       {
+         status = STATUS_UNSUCCESSFUL;
+         goto err_alloc_buf;
+       }
+      {
+       char *buf = ( char * )filepath.Buffer;
+
+       RtlCopyMemory ( buf, obj_path_prefix,
+                       sizeof ( obj_path_prefix ) - sizeof ( UNICODE_NULL ) );
+       buf += sizeof ( obj_path_prefix ) - sizeof ( UNICODE_NULL );
+       RtlCopyMemory ( buf, vol_dos_name.Buffer, vol_dos_name.Length );
+       buf += vol_dos_name.Length;
+       RtlCopyMemory ( buf, &path_sep, sizeof ( path_sep ) );
+       buf += sizeof ( path_sep );
+       RtlCopyMemory ( buf, filedisk_ptr->filepath_unicode.Buffer,
+                       filedisk_ptr->filepath_unicode.Length );
+      }
+      InitializeObjectAttributes ( &obj_attrs, &filepath,
+                                  OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
+                                  NULL, NULL );
+      /*
+       * Look for the file on this volume
+       */
+      status =
+       ZwCreateFile ( &file, GENERIC_READ | GENERIC_WRITE, &obj_attrs,
+                      &io_status, NULL, FILE_ATTRIBUTE_NORMAL,
+                      FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
+                      FILE_OPEN,
+                      FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS |
+                      FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 );
+      if ( !NT_SUCCESS ( status ) )
+       goto err_open;
+      /*
+       * We could open it.  Do the hot-swap
+       */
+      {
+       HANDLE old = filedisk_ptr->file;
+
+       filedisk_ptr->file = 0;
+       filedisk_ptr->offset.QuadPart = 0;
+       filedisk_ptr->file = file;
+       ZwClose ( old );
+      }
+      RtlFreeUnicodeString ( &filedisk_ptr->filepath_unicode );
+      filedisk_ptr->filepath_unicode.Length = 0;
+
+    err_open:
+
+      ExFreePool ( filepath.Buffer );
+    err_alloc_buf:
+
+      ExFreePool ( vol_dos_name.Buffer );
+    err_dos_name:
+
+      ObDereferenceObject ( vol_file_obj );
+    err_obj_ptrs:
+      /*
+       * Walk to the next terminator
+       */
+      while ( *pos != UNICODE_NULL )
+       pos++;
+      /*
+       * If everything succeeded, stop.  Otherwise try the next volume
+       */
+      if ( !NT_SUCCESS ( status ) )
+       pos++;
+    }
+  ExFreePool ( sym_links );
+  return NT_SUCCESS ( status ) ? TRUE : FALSE;
+}
+
+void STDCALL
+filedisk__hot_swap_thread (
+  IN void *StartContext
+ )
+{
+  filedisk__type_ptr filedisk_ptr = StartContext;
+  KEVENT signal;
+  LARGE_INTEGER timeout;
+
+  KeInitializeEvent ( &signal, SynchronizationEvent, FALSE );
+  /*
+   * Wake up at least every second
+   */
+  timeout.QuadPart = -10000000LL;
+  /*
+   * The hot-swap loop
+   */
+  while ( TRUE )
+    {
+      /*
+       * Wait for work-to-do signal or the timeout
+       */
+      KeWaitForSingleObject ( &signal, Executive, KernelMode, FALSE,
+                             &timeout );
+      if ( filedisk_ptr->file == NULL )
+       continue;
+      /*
+       * Are we supposed to hot-swap to a file?  Check ANSI filepath
+       */
+      if ( filedisk_ptr->filepath != NULL )
+       {
+         ANSI_STRING tmp;
+         NTSTATUS status;
+
+         RtlInitAnsiString ( &tmp, filedisk_ptr->filepath );
+         filedisk_ptr->filepath_unicode.Buffer = NULL;
+         status =
+           RtlAnsiStringToUnicodeString ( &filedisk_ptr->filepath_unicode,
+                                          &tmp, TRUE );
+         if ( NT_SUCCESS ( status ) )
+           {
+             ExFreePool ( filedisk_ptr->filepath );
+             filedisk_ptr->filepath = NULL;
+           }
+       }
+      /*
+       * Are we supposed to hot-swap to a file?  Check unicode filepath
+       */
+      if ( filedisk_ptr->filepath_unicode.Length )
+       if ( hot_swap ( filedisk_ptr ) )
+         break;
+    }
+}
index e1abe9d..b8579ec 100644 (file)
@@ -148,6 +148,7 @@ disk__io_decl (
       UNICODE_STRING path;
       OBJECT_ATTRIBUTES obj_attrs;
       IO_STATUS_BLOCK io_status;
+
       RtlInitUnicodeString ( &path, pos );
       InitializeObjectAttributes ( &obj_attrs, &path,
                                   OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
@@ -205,6 +206,102 @@ dud:
   return STATUS_NO_MEDIA_IN_DEVICE;
 }
 
+winvblock__def_struct ( drive_file_set )
+{
+  winvblock__uint8 int13_drive_num;
+  char *filepath;
+};
+
+static void STDCALL
+process_param_block (
+  const char *param_block,
+  drive_file_set_ptr sets
+ )
+{
+  const char *end = param_block + 2047;
+  const char sig[] = "#!GRUB4DOS";
+  const char ver[] = "v=1";
+  int i = 0;
+
+  /*
+   * Check signature
+   */
+  if ( !
+       ( RtlCompareMemory ( param_block, sig, sizeof ( sig ) ) ==
+        sizeof ( sig ) ) )
+    {
+      DBG ( "RAM disk is not a parameter block.  Skipping.\n" );
+      return;
+    }
+  param_block += sizeof ( sig );
+  /*
+   * Looks like a parameter block someone passed from GRUB4DOS.
+   * Check the version
+   */
+  if ( !
+       ( RtlCompareMemory ( param_block, ver, sizeof ( ver ) ) ==
+        sizeof ( ver ) ) )
+    {
+      DBG ( "Parameter block version unsupported.  Skipping.\n" );
+      return;
+    }
+  param_block += sizeof ( ver );
+  /*
+   * We are interested in {filepath, NUL, X} sets, where X is INT13h drive num
+   */
+  RtlZeroMemory ( sets, sizeof ( drive_file_set ) * 8 );
+  while ( param_block < end && i != 8 )
+    {
+      const char *walker = param_block;
+
+      /*
+       * Walk to ASCII NUL terminator
+       */
+      while ( walker != end && *walker )
+       walker++;
+      if ( walker == param_block || walker == end )
+       /*
+        * End of filenames or run-away sequence
+        */
+       break;
+      walker++;
+      /*
+       * Make a note of the filename.  Skip initial '/' or '\'
+       */
+      if ( *param_block == '/' || *param_block == '\\' )
+       param_block++;
+      sets[i].filepath = ExAllocatePool ( NonPagedPool, walker - param_block );
+      if ( sets[i].filepath == NULL )
+       {
+         DBG ( "Could not store filename\n" );
+         walker++;             /* Skip drive num */
+         param_block = walker;
+         continue;
+       }
+      RtlCopyMemory ( sets[i].filepath, param_block, walker - param_block );
+      /*
+       * Replace '/' with '\'
+       */
+      {
+       char *rep = sets[i].filepath;
+
+       while ( *rep )
+         {
+           if ( *rep == '/' )
+             *rep = '\\';
+           rep++;
+         }
+      }
+      /*
+       * The next byte is expected to be the INT 13h drive num
+       */
+      sets[i].int13_drive_num = *walker;
+      walker++;
+      i++;
+      param_block = walker;
+    }
+}
+
 void
 filedisk_grub4dos__find (
   void
@@ -216,9 +313,12 @@ filedisk_grub4dos__find (
   winvblock__uint32 Int13Hook;
   safe_mbr_hook_ptr SafeMbrHookPtr;
   grub4dos__drive_mapping_ptr Grub4DosDriveMapSlotPtr;
-  winvblock__uint32 i = 8;
+  winvblock__uint32 i;
   winvblock__bool FoundGrub4DosMapping = FALSE;
   filedisk__type_ptr filedisk_ptr;
+  const char sig[] = "GRUB4DOS";
+  drive_file_set sets[8];      /* Matches disks to files */
+
   /*
    * Find a GRUB4DOS sector-mapped disk.  Start by looking at the
    * real-mode IDT and following the "SafeMBRHook" INT 0x13 hook
@@ -237,9 +337,13 @@ filedisk_grub4dos__find (
    */
   while ( SafeMbrHookPtr = get_safe_hook ( PhysicalMemory, InterruptVector ) )
     {
+      /*
+       * Check signature
+       */
       if ( !
-          ( RtlCompareMemory ( SafeMbrHookPtr->VendorID, "GRUB4DOS", 8 ) ==
-            8 ) )
+          ( RtlCompareMemory
+            ( SafeMbrHookPtr->VendorID, sig,
+              sizeof ( sig ) - 1 ) == sizeof ( sig ) - 1 ) )
        {
          DBG ( "Non-GRUB4DOS INT 0x13 Safe Hook\n" );
          InterruptVector = &SafeMbrHookPtr->PrevHook;
@@ -250,6 +354,38 @@ filedisk_grub4dos__find (
                                          ( ( ( winvblock__uint32 )
                                              InterruptVector->
                                              Segment ) << 4 ) + 0x20 );
+      /*
+       * Search for parameter blocks, which are disguised as
+       * GRUB4DOS RAM disk mappings for 2048-byte memory regions
+       */
+      i = 8;
+      while ( i-- )
+       {
+         PHYSICAL_ADDRESS param_block_addr;
+         char *param_block;
+
+         if ( Grub4DosDriveMapSlotPtr[i].DestDrive != 0xff )
+           continue;
+         param_block_addr.QuadPart =
+           Grub4DosDriveMapSlotPtr[i].SectorStart * 512;
+         param_block = MmMapIoSpace ( param_block_addr, 2048, MmNonCached );
+         if ( param_block == NULL )
+           {
+             DBG ( "Could not map potential G4D parameter block\n" );
+             continue;
+           }
+         /*
+          * Could be a parameter block.  Process it.  There can be only one
+          */
+         process_param_block ( param_block, sets );
+         MmUnmapIoSpace ( param_block, 2048 );
+         if ( sets[0].filepath != NULL )
+           break;
+       }
+      /*
+       * Search for sector-mapped (typically file-backed) disks
+       */
+      i = 8;
       while ( i-- )
        {
          if ( ( Grub4DosDriveMapSlotPtr[i].SectorCount == 0 )
@@ -277,7 +413,7 @@ filedisk_grub4dos__find (
                Grub4DosDriveMapSlotPtr[i].SectorCount );
          /*
           * Create the threaded, file-backed disk.  Hook the
-          * read/write routine so we can accessing the backing
+          * read/write routine so we can accessing the backing disk
           * late(r) during the boot process
           */
          filedisk_ptr = filedisk__create_threaded (  );
@@ -288,6 +424,30 @@ filedisk_grub4dos__find (
            }
          sync_io = filedisk_ptr->sync_io;
          filedisk_ptr->sync_io = io;
+         /*
+          * Find an associated filename, if one exists
+          */
+         {
+           int j = 8;
+
+           while ( j-- )
+             if ( sets[j].int13_drive_num ==
+                  Grub4DosDriveMapSlotPtr[i].SourceDrive
+                  && sets[j].filepath != NULL )
+               filedisk_ptr->filepath = sets[j].filepath;
+           if ( filedisk_ptr->filepath != NULL )
+             {
+               OBJECT_ATTRIBUTES obj_attrs;
+               HANDLE thread_handle;
+
+               InitializeObjectAttributes ( &obj_attrs, NULL,
+                                            OBJ_KERNEL_HANDLE, NULL, NULL );
+               PsCreateSystemThread ( &thread_handle, THREAD_ALL_ACCESS,
+                                      &obj_attrs, NULL, NULL,
+                                      filedisk__hot_swap_thread,
+                                      filedisk_ptr );
+             }
+         }
          /*
           * Possible precision loss
           */