[grub4dos] Rename grub4dos__drive_mapping to WV_S_GRUB4DOS_DRIVE_MAPPING
[people/sha0/winvblock.git] / src / winvblock / filedisk / grub4dos.c
1 /**
2  * Copyright (C) 2009-2011, Shao Miller <shao.miller@yrdsb.edu.on.ca>.
3  *
4  * This file is part of WinVBlock, derived from WinAoE.
5  *
6  * WinVBlock is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * WinVBlock is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with WinVBlock.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /**
21  * @file
22  *
23  * GRUB4DOS file-backed disk specifics.
24  */
25
26 #include <ntddk.h>
27 #include <initguid.h>
28 #include <ntddstor.h>
29
30 #include "winvblock.h"
31 #include "wv_stdlib.h"
32 #include "wv_string.h"
33 #include "portable.h"
34 #include "driver.h"
35 #include "bus.h"
36 #include "device.h"
37 #include "disk.h"
38 #include "filedisk.h"
39 #include "debug.h"
40 #include "probe.h"
41 #include "grub4dos.h"
42 #include "byte.h"
43 #include "msvhd.h"
44
45 /* Globals. */
46 static WV_FP_DISK_IO sync_io;
47
48 /* Forward declarations. */
49 static WV_F_DISK_IO io;
50
51 /**
52  * Check if a disk might be the matching backing disk for
53  * a GRUB4DOS sector-mapped disk
54  *
55  * @v file              HANDLE to an open disk
56  * @v filedisk_ptr      Points to the filedisk to match against
57  */
58 static NTSTATUS STDCALL
59 check_disk_match (
60   IN HANDLE file,
61   IN WV_SP_FILEDISK_T filedisk_ptr
62  )
63 {
64   msvhd__footer_ptr buf;
65   NTSTATUS status;
66   IO_STATUS_BLOCK io_status;
67   LARGE_INTEGER end_part;
68
69   /*
70    * Allocate a buffer for testing for a MS .VHD footer
71    */
72   buf = wv_malloc(sizeof *buf);
73   if ( buf == NULL )
74     {
75       status = STATUS_INSUFFICIENT_RESOURCES;
76       goto err_alloc;
77     }
78   /*
79    * Read in the buffer.  Note that we adjust for the .VHD footer (plus
80    * prefixed padding for an .ISO) that we truncated from the reported disk
81    * size earlier when the disk mapping was found
82    */
83   end_part.QuadPart =
84     filedisk_ptr->offset.QuadPart +
85     ( filedisk_ptr->disk->LBADiskSize * filedisk_ptr->disk->SectorSize ) +
86     filedisk_ptr->disk->SectorSize - sizeof ( *buf );
87   status =
88     ZwReadFile ( file, NULL, NULL, NULL, &io_status, buf, sizeof ( *buf ),
89                  &end_part, NULL );
90   if ( !NT_SUCCESS ( status ) )
91     goto err_read;
92   /*
93    * Adjust the footer's byte ordering
94    */
95   msvhd__footer_swap_endian ( buf );
96   /*
97    * Examine .VHD fields for validity
98    */
99   if (!wv_memcmpeq(&buf->cookie, "conectix", sizeof buf->cookie ))
100     status = STATUS_UNSUCCESSFUL;
101   if ( buf->file_ver.val != 0x10000 )
102     status = STATUS_UNSUCCESSFUL;
103   if ( buf->data_offset.val != 0xffffffff )
104     status = STATUS_UNSUCCESSFUL;
105   if ( buf->orig_size.val != buf->cur_size.val )
106     status = STATUS_UNSUCCESSFUL;
107   if ( buf->type.val != 2 )
108     status = STATUS_UNSUCCESSFUL;
109   /*
110    * Match against our expected disk size
111    */
112   if ( ( filedisk_ptr->disk->LBADiskSize * filedisk_ptr->disk->SectorSize ) !=
113        buf->cur_size.val )
114     status = STATUS_UNSUCCESSFUL;
115
116 err_read:
117
118   wv_free(buf);
119 err_alloc:
120
121   return status;
122 }
123
124 /**
125  * Temporarily used by established disks in order to access the
126  * backing disk late(r) during the boot process.
127  */
128 static NTSTATUS STDCALL io(
129     IN WV_SP_DEV_T dev_ptr,
130     IN WV_E_DISK_IO_MODE mode,
131     IN LONGLONG start_sector,
132     IN winvblock__uint32 sector_count,
133     IN winvblock__uint8_ptr buffer,
134     IN PIRP irp
135   ) {
136   WV_SP_FILEDISK_T filedisk_ptr;
137   NTSTATUS status;
138   GUID disk_guid = GUID_DEVINTERFACE_DISK;
139   PWSTR sym_links;
140   PWCHAR pos;
141   HANDLE file;
142
143   filedisk_ptr = filedisk__get_ptr ( dev_ptr );
144   /*
145    * Find the backing disk and use it.  We walk a list
146    * of unicode disk device names and check each one
147    */
148   status = IoGetDeviceInterfaces ( &disk_guid, NULL, 0, &sym_links );
149   if ( !NT_SUCCESS ( status ) )
150     goto dud;
151   pos = sym_links;
152   while ( *pos != UNICODE_NULL )
153     {
154       UNICODE_STRING path;
155       OBJECT_ATTRIBUTES obj_attrs;
156       IO_STATUS_BLOCK io_status;
157
158       RtlInitUnicodeString ( &path, pos );
159       InitializeObjectAttributes ( &obj_attrs, &path,
160                                    OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
161                                    NULL, NULL );
162       /*
163        * Try this disk
164        */
165       file = 0;
166       status =
167         ZwCreateFile ( &file, GENERIC_READ | GENERIC_WRITE, &obj_attrs,
168                        &io_status, NULL, FILE_ATTRIBUTE_NORMAL,
169                        FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
170                        FILE_OPEN,
171                        FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS |
172                        FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 );
173       /*
174        * If we could open it, check it out
175        */
176       if ( NT_SUCCESS ( status ) )
177         status = check_disk_match ( file, filedisk_ptr );
178       /*
179        * If we liked this disk as our backing disk, stop looking
180        */
181       if ( NT_SUCCESS ( status ) )
182         break;
183       /*
184        * We could not open this disk or didn't like it.  Try the next one
185        */
186       if ( file )
187         ZwClose ( file );
188       while ( *pos != UNICODE_NULL )
189         pos++;
190       pos++;
191     }
192   wv_free(sym_links);
193   /*
194    * If we did not find the backing disk, we are a dud
195    */
196   if ( !NT_SUCCESS ( status ) )
197     goto dud;
198   /*
199    *  Use the backing disk and restore the original read/write routine
200    */
201   filedisk_ptr->file = file;
202   filedisk_ptr->sync_io = sync_io;
203   /*
204    * Call the original read/write routine
205    */
206   return sync_io ( dev_ptr, mode, start_sector, sector_count, buffer, irp );
207
208 dud:
209   irp->IoStatus.Information = 0;
210   irp->IoStatus.Status = STATUS_NO_MEDIA_IN_DEVICE;
211   IoCompleteRequest ( irp, IO_NO_INCREMENT );
212   return STATUS_NO_MEDIA_IN_DEVICE;
213 }
214
215 winvblock__def_struct ( drive_file_set )
216 {
217   winvblock__uint8 int13_drive_num;
218   char *filepath;
219 };
220
221 static void STDCALL
222 process_param_block (
223   const char *param_block,
224   drive_file_set_ptr sets
225  )
226 {
227   const char *end = param_block + 2047;
228   const char sig[] = "#!GRUB4DOS";
229   const char ver[] = "v=1";
230   int i = 0;
231
232   /*
233    * Check signature
234    */
235   if (!wv_memcmpeq(param_block, sig, sizeof sig)) {
236       DBG ( "RAM disk is not a parameter block.  Skipping.\n" );
237       return;
238     }
239   param_block += sizeof ( sig );
240   /*
241    * Looks like a parameter block someone passed from GRUB4DOS.
242    * Check the version
243    */
244   if (!wv_memcmpeq(param_block, ver, sizeof ver)) {
245       DBG ( "Parameter block version unsupported.  Skipping.\n" );
246       return;
247     }
248   param_block += sizeof ( ver );
249   /*
250    * We are interested in {filepath, NUL, X} sets, where X is INT13h drive num
251    */
252   RtlZeroMemory ( sets, sizeof ( drive_file_set ) * 8 );
253   while ( param_block < end && i != 8 )
254     {
255       const char *walker = param_block;
256
257       /*
258        * Walk to ASCII NUL terminator
259        */
260       while ( walker != end && *walker )
261         walker++;
262       if ( walker == param_block || walker == end )
263         /*
264          * End of filenames or run-away sequence
265          */
266         break;
267       walker++;
268       /*
269        * Make a note of the filename.  Skip initial '/' or '\'
270        */
271       if ( *param_block == '/' || *param_block == '\\' )
272         param_block++;
273       sets[i].filepath = wv_malloc(walker - param_block);
274       if ( sets[i].filepath == NULL )
275         {
276           DBG ( "Could not store filename\n" );
277           walker++;             /* Skip drive num */
278           param_block = walker;
279           continue;
280         }
281       RtlCopyMemory ( sets[i].filepath, param_block, walker - param_block );
282       /*
283        * Replace '/' with '\'
284        */
285       {
286         char *rep = sets[i].filepath;
287
288         while ( *rep )
289           {
290             if ( *rep == '/' )
291               *rep = '\\';
292             rep++;
293           }
294       }
295       /*
296        * The next byte is expected to be the INT 13h drive num
297        */
298       sets[i].int13_drive_num = *walker;
299       walker++;
300       i++;
301       param_block = walker;
302     }
303 }
304
305 void
306 filedisk_grub4dos__find (
307   void
308  )
309 {
310   PHYSICAL_ADDRESS PhysicalAddress;
311   winvblock__uint8_ptr PhysicalMemory;
312   WV_SP_PROBE_INT_VECTOR InterruptVector;
313   winvblock__uint32 Int13Hook;
314   WV_SP_PROBE_SAFE_MBR_HOOK SafeMbrHookPtr;
315   WV_SP_GRUB4DOS_DRIVE_MAPPING Grub4DosDriveMapSlotPtr;
316   winvblock__uint32 i;
317   winvblock__bool FoundGrub4DosMapping = FALSE;
318   WV_SP_FILEDISK_T filedisk_ptr;
319   const char sig[] = "GRUB4DOS";
320   drive_file_set sets[8];       /* Matches disks to files */
321
322   /*
323    * Find a GRUB4DOS sector-mapped disk.  Start by looking at the
324    * real-mode IDT and following the "SafeMBRHook" INT 0x13 hook
325    */
326   PhysicalAddress.QuadPart = 0LL;
327   PhysicalMemory = MmMapIoSpace ( PhysicalAddress, 0x100000, MmNonCached );
328   if ( !PhysicalMemory )
329     {
330       DBG ( "Could not map low memory\n" );
331       return;
332     }
333   InterruptVector =
334     (WV_SP_PROBE_INT_VECTOR) (PhysicalMemory + 0x13 * sizeof *InterruptVector);
335   /* Walk the "safe hook" chain of INT 13h hooks as far as possible. */
336   while (SafeMbrHookPtr = WvProbeGetSafeHook(PhysicalMemory, InterruptVector)) {
337       /*
338        * Check signature
339        */
340       if (!wv_memcmpeq(SafeMbrHookPtr->VendorId, sig, sizeof sig - 1)) {
341           DBG ( "Non-GRUB4DOS INT 0x13 Safe Hook\n" );
342           InterruptVector = &SafeMbrHookPtr->PrevHook;
343           continue;
344         }
345       Grub4DosDriveMapSlotPtr = (WV_SP_GRUB4DOS_DRIVE_MAPPING) (
346           PhysicalMemory +
347           (((winvblock__uint32) InterruptVector->Segment) << 4)
348           + 0x20
349         );
350       /*
351        * Search for parameter blocks, which are disguised as
352        * GRUB4DOS RAM disk mappings for 2048-byte memory regions
353        */
354       i = 8;
355       while ( i-- )
356         {
357           PHYSICAL_ADDRESS param_block_addr;
358           char *param_block;
359
360           if ( Grub4DosDriveMapSlotPtr[i].DestDrive != 0xff )
361             continue;
362           param_block_addr.QuadPart =
363             Grub4DosDriveMapSlotPtr[i].SectorStart * 512;
364           param_block = MmMapIoSpace ( param_block_addr, 2048, MmNonCached );
365           if ( param_block == NULL )
366             {
367               DBG ( "Could not map potential G4D parameter block\n" );
368               continue;
369             }
370           /*
371            * Could be a parameter block.  Process it.  There can be only one
372            */
373           process_param_block ( param_block, sets );
374           MmUnmapIoSpace ( param_block, 2048 );
375           if ( sets[0].filepath != NULL )
376             break;
377         }
378       /*
379        * Search for sector-mapped (typically file-backed) disks
380        */
381       i = 8;
382       while ( i-- )
383         {
384           if ( ( Grub4DosDriveMapSlotPtr[i].SectorCount == 0 )
385                || ( Grub4DosDriveMapSlotPtr[i].DestDrive == 0xff ) )
386             {
387               DBG ( "Skipping non-sector-mapped GRUB4DOS disk\n" );
388               continue;
389             }
390           DBG ( "GRUB4DOS SourceDrive: 0x%02x\n",
391                 Grub4DosDriveMapSlotPtr[i].SourceDrive );
392           DBG ( "GRUB4DOS DestDrive: 0x%02x\n",
393                 Grub4DosDriveMapSlotPtr[i].DestDrive );
394           DBG ( "GRUB4DOS MaxHead: %d\n", Grub4DosDriveMapSlotPtr[i].MaxHead );
395           DBG ( "GRUB4DOS MaxSector: %d\n",
396                 Grub4DosDriveMapSlotPtr[i].MaxSector );
397           DBG ( "GRUB4DOS DestMaxCylinder: %d\n",
398                 Grub4DosDriveMapSlotPtr[i].DestMaxCylinder );
399           DBG ( "GRUB4DOS DestMaxHead: %d\n",
400                 Grub4DosDriveMapSlotPtr[i].DestMaxHead );
401           DBG ( "GRUB4DOS DestMaxSector: %d\n",
402                 Grub4DosDriveMapSlotPtr[i].DestMaxSector );
403           DBG ( "GRUB4DOS SectorStart: 0x%08x\n",
404                 Grub4DosDriveMapSlotPtr[i].SectorStart );
405           DBG ( "GRUB4DOS SectorCount: %d\n",
406                 Grub4DosDriveMapSlotPtr[i].SectorCount );
407           /*
408            * Create the threaded, file-backed disk.  Hook the
409            * read/write routine so we can accessing the backing disk
410            * late(r) during the boot process
411            */
412           filedisk_ptr = filedisk__create_threaded (  );
413           if ( filedisk_ptr == NULL )
414             {
415               DBG ( "Could not create GRUB4DOS disk!\n" );
416               return;
417             }
418           sync_io = filedisk_ptr->sync_io;
419           filedisk_ptr->sync_io = io;
420           /*
421            * Find an associated filename, if one exists
422            */
423           {
424             int j = 8;
425
426             while ( j-- )
427               if ( sets[j].int13_drive_num ==
428                    Grub4DosDriveMapSlotPtr[i].SourceDrive
429                    && sets[j].filepath != NULL )
430                 filedisk_ptr->filepath = sets[j].filepath;
431             if ( filedisk_ptr->filepath != NULL )
432               {
433                 OBJECT_ATTRIBUTES obj_attrs;
434                 HANDLE thread_handle;
435
436                 InitializeObjectAttributes ( &obj_attrs, NULL,
437                                              OBJ_KERNEL_HANDLE, NULL, NULL );
438                 PsCreateSystemThread ( &thread_handle, THREAD_ALL_ACCESS,
439                                        &obj_attrs, NULL, NULL,
440                                        filedisk__hot_swap_thread,
441                                        filedisk_ptr );
442               }
443           }
444           /*
445            * Possible precision loss
446            */
447           if ( Grub4DosDriveMapSlotPtr[i].SourceODD )
448             {
449               filedisk_ptr->disk->Media = WvDiskMediaTypeOptical;
450               filedisk_ptr->disk->SectorSize = 2048;
451             }
452           else
453             {
454               filedisk_ptr->disk->Media =
455                 Grub4DosDriveMapSlotPtr[i].SourceDrive & 0x80 ?
456                 WvDiskMediaTypeHard : WvDiskMediaTypeFloppy;
457               filedisk_ptr->disk->SectorSize = 512;
458             }
459           DBG ( "Sector-mapped disk is type: %d\n",
460                 filedisk_ptr->disk->Media );
461           /*
462            * Note the offset of the disk image from the backing disk's start
463            */
464           filedisk_ptr->offset.QuadPart =
465             Grub4DosDriveMapSlotPtr[i].SectorStart *
466             ( Grub4DosDriveMapSlotPtr[i].DestODD ? 2048 : 512 );
467           /*
468            * Size and geometry.  Please note that since we require a .VHD
469            * footer, we exclude this from the LBA disk size by truncating
470            * a 512-byte sector for HDD images, or a 2048-byte sector for .ISO
471            */
472           {
473             ULONGLONG total_size =
474               Grub4DosDriveMapSlotPtr[i].SectorCount *
475               ( Grub4DosDriveMapSlotPtr[i].DestODD ? 2048 : 512 );
476             filedisk_ptr->disk->LBADiskSize =
477               ( total_size -
478                 filedisk_ptr->disk->SectorSize ) /
479               filedisk_ptr->disk->SectorSize;
480           }
481           filedisk_ptr->disk->Heads = Grub4DosDriveMapSlotPtr[i].MaxHead + 1;
482           filedisk_ptr->disk->Sectors =
483             Grub4DosDriveMapSlotPtr[i].DestMaxSector;
484           filedisk_ptr->disk->Cylinders =
485             filedisk_ptr->disk->LBADiskSize / ( filedisk_ptr->disk->Heads *
486                                                 filedisk_ptr->disk->Sectors );
487           /*
488            * Set a filedisk "hash" and mark the drive as a boot-drive.
489            * The "hash" is 'G4DX', where X is the GRUB4DOS INT 13h
490            * drive number.  Note that mutiple G4D INT 13h chains will
491            * cause a "hash" collision!  Too bad for now.
492            */
493           filedisk_ptr->hash = 'G4DX';
494           ( ( winvblock__uint8 * ) & filedisk_ptr->hash )[0] =
495             Grub4DosDriveMapSlotPtr[i].SourceDrive;
496           filedisk_ptr->disk->Dev->Boot = TRUE;
497           FoundGrub4DosMapping = TRUE;
498           /* Add the filedisk to the bus. */
499           if (!WvDriverBusAddDev(filedisk_ptr->disk->Dev))
500       WvDevFree(filedisk_ptr->disk->Dev);
501         }
502       InterruptVector = &SafeMbrHookPtr->PrevHook;
503     }
504
505   MmUnmapIoSpace ( PhysicalMemory, 0x100000 );
506   if ( !FoundGrub4DosMapping )
507     {
508       DBG ( "No GRUB4DOS sector-mapped disk mappings found\n" );
509     }
510 }