[driver,bus] Move WvBus* subjects into new bus.c
[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 "portable.h"
31 #include "winvblock.h"
32 #include "wv_stdlib.h"
33 #include "wv_string.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   WV_SP_MSVHD_FOOTER 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 UINT32 sector_count,
133     IN PUCHAR 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 typedef struct WV_FILEDISK_GRUB4DOS_DRIVE_FILE_SET {
216     UCHAR int13_drive_num;
217     char *filepath;
218   }
219   WV_S_FILEDISK_GRUB4DOS_DRIVE_FILE_SET,
220   * WV_SP_FILEDISK_GRUB4DOS_DRIVE_FILE_SET;
221
222 static VOID STDCALL
223 process_param_block (
224   const char *param_block,
225   WV_SP_FILEDISK_GRUB4DOS_DRIVE_FILE_SET sets
226  )
227 {
228   const char *end = param_block + 2047;
229   const char sig[] = "#!GRUB4DOS";
230   const char ver[] = "v=1";
231   int i = 0;
232
233   /*
234    * Check signature
235    */
236   if (!wv_memcmpeq(param_block, sig, sizeof sig)) {
237       DBG ( "RAM disk is not a parameter block.  Skipping.\n" );
238       return;
239     }
240   param_block += sizeof ( sig );
241   /*
242    * Looks like a parameter block someone passed from GRUB4DOS.
243    * Check the version
244    */
245   if (!wv_memcmpeq(param_block, ver, sizeof ver)) {
246       DBG ( "Parameter block version unsupported.  Skipping.\n" );
247       return;
248     }
249   param_block += sizeof ( ver );
250   /*
251    * We are interested in {filepath, NUL, X} sets, where X is INT13h drive num
252    */
253   RtlZeroMemory ( sets, sizeof *sets * 8 );
254   while ( param_block < end && i != 8 )
255     {
256       const char *walker = param_block;
257
258       /*
259        * Walk to ASCII NUL terminator
260        */
261       while ( walker != end && *walker )
262         walker++;
263       if ( walker == param_block || walker == end )
264         /*
265          * End of filenames or run-away sequence
266          */
267         break;
268       walker++;
269       /*
270        * Make a note of the filename.  Skip initial '/' or '\'
271        */
272       if ( *param_block == '/' || *param_block == '\\' )
273         param_block++;
274       sets[i].filepath = wv_malloc(walker - param_block);
275       if ( sets[i].filepath == NULL )
276         {
277           DBG ( "Could not store filename\n" );
278           walker++;             /* Skip drive num */
279           param_block = walker;
280           continue;
281         }
282       RtlCopyMemory ( sets[i].filepath, param_block, walker - param_block );
283       /*
284        * Replace '/' with '\'
285        */
286       {
287         char *rep = sets[i].filepath;
288
289         while ( *rep )
290           {
291             if ( *rep == '/' )
292               *rep = '\\';
293             rep++;
294           }
295       }
296       /*
297        * The next byte is expected to be the INT 13h drive num
298        */
299       sets[i].int13_drive_num = *walker;
300       walker++;
301       i++;
302       param_block = walker;
303     }
304 }
305
306 VOID filedisk_grub4dos__find(void) {
307   PHYSICAL_ADDRESS PhysicalAddress;
308   PUCHAR PhysicalMemory;
309   WV_SP_PROBE_INT_VECTOR InterruptVector;
310   UINT32 Int13Hook;
311   WV_SP_PROBE_SAFE_MBR_HOOK SafeMbrHookPtr;
312   WV_SP_GRUB4DOS_DRIVE_MAPPING Grub4DosDriveMapSlotPtr;
313   UINT32 i;
314   BOOLEAN FoundGrub4DosMapping = FALSE;
315   WV_SP_FILEDISK_T filedisk_ptr;
316   const char sig[] = "GRUB4DOS";
317   /* Matches disks to files. */
318   WV_S_FILEDISK_GRUB4DOS_DRIVE_FILE_SET sets[8];
319
320   /*
321    * Find a GRUB4DOS sector-mapped disk.  Start by looking at the
322    * real-mode IDT and following the "SafeMBRHook" INT 0x13 hook
323    */
324   PhysicalAddress.QuadPart = 0LL;
325   PhysicalMemory = MmMapIoSpace ( PhysicalAddress, 0x100000, MmNonCached );
326   if ( !PhysicalMemory )
327     {
328       DBG ( "Could not map low memory\n" );
329       return;
330     }
331   InterruptVector =
332     (WV_SP_PROBE_INT_VECTOR) (PhysicalMemory + 0x13 * sizeof *InterruptVector);
333   /* Walk the "safe hook" chain of INT 13h hooks as far as possible. */
334   while (SafeMbrHookPtr = WvProbeGetSafeHook(PhysicalMemory, InterruptVector)) {
335       /*
336        * Check signature
337        */
338       if (!wv_memcmpeq(SafeMbrHookPtr->VendorId, sig, sizeof sig - 1)) {
339           DBG ( "Non-GRUB4DOS INT 0x13 Safe Hook\n" );
340           InterruptVector = &SafeMbrHookPtr->PrevHook;
341           continue;
342         }
343       Grub4DosDriveMapSlotPtr = (WV_SP_GRUB4DOS_DRIVE_MAPPING) (
344           PhysicalMemory +
345           (((UINT32) InterruptVector->Segment) << 4)
346           + 0x20
347         );
348       /*
349        * Search for parameter blocks, which are disguised as
350        * GRUB4DOS RAM disk mappings for 2048-byte memory regions
351        */
352       i = 8;
353       while ( i-- )
354         {
355           PHYSICAL_ADDRESS param_block_addr;
356           char *param_block;
357
358           if ( Grub4DosDriveMapSlotPtr[i].DestDrive != 0xff )
359             continue;
360           param_block_addr.QuadPart =
361             Grub4DosDriveMapSlotPtr[i].SectorStart * 512;
362           param_block = MmMapIoSpace ( param_block_addr, 2048, MmNonCached );
363           if ( param_block == NULL )
364             {
365               DBG ( "Could not map potential G4D parameter block\n" );
366               continue;
367             }
368           /*
369            * Could be a parameter block.  Process it.  There can be only one
370            */
371           process_param_block ( param_block, sets );
372           MmUnmapIoSpace ( param_block, 2048 );
373           if ( sets[0].filepath != NULL )
374             break;
375         }
376       /*
377        * Search for sector-mapped (typically file-backed) disks
378        */
379       i = 8;
380       while ( i-- )
381         {
382           if ( ( Grub4DosDriveMapSlotPtr[i].SectorCount == 0 )
383                || ( Grub4DosDriveMapSlotPtr[i].DestDrive == 0xff ) )
384             {
385               DBG ( "Skipping non-sector-mapped GRUB4DOS disk\n" );
386               continue;
387             }
388           DBG ( "GRUB4DOS SourceDrive: 0x%02x\n",
389                 Grub4DosDriveMapSlotPtr[i].SourceDrive );
390           DBG ( "GRUB4DOS DestDrive: 0x%02x\n",
391                 Grub4DosDriveMapSlotPtr[i].DestDrive );
392           DBG ( "GRUB4DOS MaxHead: %d\n", Grub4DosDriveMapSlotPtr[i].MaxHead );
393           DBG ( "GRUB4DOS MaxSector: %d\n",
394                 Grub4DosDriveMapSlotPtr[i].MaxSector );
395           DBG ( "GRUB4DOS DestMaxCylinder: %d\n",
396                 Grub4DosDriveMapSlotPtr[i].DestMaxCylinder );
397           DBG ( "GRUB4DOS DestMaxHead: %d\n",
398                 Grub4DosDriveMapSlotPtr[i].DestMaxHead );
399           DBG ( "GRUB4DOS DestMaxSector: %d\n",
400                 Grub4DosDriveMapSlotPtr[i].DestMaxSector );
401           DBG ( "GRUB4DOS SectorStart: 0x%08x\n",
402                 Grub4DosDriveMapSlotPtr[i].SectorStart );
403           DBG ( "GRUB4DOS SectorCount: %d\n",
404                 Grub4DosDriveMapSlotPtr[i].SectorCount );
405           /*
406            * Create the threaded, file-backed disk.  Hook the
407            * read/write routine so we can accessing the backing disk
408            * late(r) during the boot process
409            */
410           filedisk_ptr = filedisk__create_threaded (  );
411           if ( filedisk_ptr == NULL )
412             {
413               DBG ( "Could not create GRUB4DOS disk!\n" );
414               return;
415             }
416           sync_io = filedisk_ptr->sync_io;
417           filedisk_ptr->sync_io = io;
418           /*
419            * Find an associated filename, if one exists
420            */
421           {
422             int j = 8;
423
424             while ( j-- )
425               if ( sets[j].int13_drive_num ==
426                    Grub4DosDriveMapSlotPtr[i].SourceDrive
427                    && sets[j].filepath != NULL )
428                 filedisk_ptr->filepath = sets[j].filepath;
429             if ( filedisk_ptr->filepath != NULL )
430               {
431                 OBJECT_ATTRIBUTES obj_attrs;
432                 HANDLE thread_handle;
433
434                 InitializeObjectAttributes ( &obj_attrs, NULL,
435                                              OBJ_KERNEL_HANDLE, NULL, NULL );
436                 PsCreateSystemThread ( &thread_handle, THREAD_ALL_ACCESS,
437                                        &obj_attrs, NULL, NULL,
438                                        filedisk__hot_swap_thread,
439                                        filedisk_ptr );
440               }
441           }
442           /*
443            * Possible precision loss
444            */
445           if ( Grub4DosDriveMapSlotPtr[i].SourceODD )
446             {
447               filedisk_ptr->disk->Media = WvDiskMediaTypeOptical;
448               filedisk_ptr->disk->SectorSize = 2048;
449             }
450           else
451             {
452               filedisk_ptr->disk->Media =
453                 Grub4DosDriveMapSlotPtr[i].SourceDrive & 0x80 ?
454                 WvDiskMediaTypeHard : WvDiskMediaTypeFloppy;
455               filedisk_ptr->disk->SectorSize = 512;
456             }
457           DBG ( "Sector-mapped disk is type: %d\n",
458                 filedisk_ptr->disk->Media );
459           /*
460            * Note the offset of the disk image from the backing disk's start
461            */
462           filedisk_ptr->offset.QuadPart =
463             Grub4DosDriveMapSlotPtr[i].SectorStart *
464             ( Grub4DosDriveMapSlotPtr[i].DestODD ? 2048 : 512 );
465           /*
466            * Size and geometry.  Please note that since we require a .VHD
467            * footer, we exclude this from the LBA disk size by truncating
468            * a 512-byte sector for HDD images, or a 2048-byte sector for .ISO
469            */
470           {
471             ULONGLONG total_size =
472               Grub4DosDriveMapSlotPtr[i].SectorCount *
473               ( Grub4DosDriveMapSlotPtr[i].DestODD ? 2048 : 512 );
474             filedisk_ptr->disk->LBADiskSize =
475               ( total_size -
476                 filedisk_ptr->disk->SectorSize ) /
477               filedisk_ptr->disk->SectorSize;
478           }
479           filedisk_ptr->disk->Heads = Grub4DosDriveMapSlotPtr[i].MaxHead + 1;
480           filedisk_ptr->disk->Sectors =
481             Grub4DosDriveMapSlotPtr[i].DestMaxSector;
482           filedisk_ptr->disk->Cylinders =
483             filedisk_ptr->disk->LBADiskSize / ( filedisk_ptr->disk->Heads *
484                                                 filedisk_ptr->disk->Sectors );
485           /*
486            * Set a filedisk "hash" and mark the drive as a boot-drive.
487            * The "hash" is 'G4DX', where X is the GRUB4DOS INT 13h
488            * drive number.  Note that mutiple G4D INT 13h chains will
489            * cause a "hash" collision!  Too bad for now.
490            */
491           filedisk_ptr->hash = 'G4DX';
492           ( ( UCHAR * ) & filedisk_ptr->hash )[0] =
493             Grub4DosDriveMapSlotPtr[i].SourceDrive;
494           filedisk_ptr->disk->Dev->Boot = TRUE;
495           FoundGrub4DosMapping = TRUE;
496           /* Add the filedisk to the bus. */
497           if (!WvBusAddDev(filedisk_ptr->disk->Dev))
498       WvDevFree(filedisk_ptr->disk->Dev);
499         }
500       InterruptVector = &SafeMbrHookPtr->PrevHook;
501     }
502
503   MmUnmapIoSpace ( PhysicalMemory, 0x100000 );
504   if ( !FoundGrub4DosMapping )
505     {
506       DBG ( "No GRUB4DOS sector-mapped disk mappings found\n" );
507     }
508 }