[bus] Make callers of bus__add_child responsible...
[people/sha0/winvblock.git] / src / winvblock / filedisk / grub4dos.c
1 /**
2  * Copyright (C) 2009-2010, 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
27 #include <ntddk.h>
28 #include <initguid.h>
29 #include <ntddstor.h>
30
31 #include "winvblock.h"
32 #include "wv_stdlib.h"
33 #include "wv_string.h"
34 #include "portable.h"
35 #include "irp.h"
36 #include "driver.h"
37 #include "device.h"
38 #include "disk.h"
39 #include "bus.h"
40 #include "filedisk.h"
41 #include "debug.h"
42 #include "probe.h"
43 #include "grub4dos.h"
44 #include "byte.h"
45 #include "msvhd.h"
46
47 static disk__io_routine sync_io;
48
49 /**
50  * Check if a disk might be the matching backing disk for
51  * a GRUB4DOS sector-mapped disk
52  *
53  * @v file              HANDLE to an open disk
54  * @v filedisk_ptr      Points to the filedisk to match against
55  */
56 static NTSTATUS STDCALL
57 check_disk_match (
58   IN HANDLE file,
59   IN filedisk__type_ptr filedisk_ptr
60  )
61 {
62   msvhd__footer_ptr buf;
63   NTSTATUS status;
64   IO_STATUS_BLOCK io_status;
65   LARGE_INTEGER end_part;
66
67   /*
68    * Allocate a buffer for testing for a MS .VHD footer
69    */
70   buf = wv_malloc(sizeof *buf);
71   if ( buf == NULL )
72     {
73       status = STATUS_INSUFFICIENT_RESOURCES;
74       goto err_alloc;
75     }
76   /*
77    * Read in the buffer.  Note that we adjust for the .VHD footer (plus
78    * prefixed padding for an .ISO) that we truncated from the reported disk
79    * size earlier when the disk mapping was found
80    */
81   end_part.QuadPart =
82     filedisk_ptr->offset.QuadPart +
83     ( filedisk_ptr->disk->LBADiskSize * filedisk_ptr->disk->SectorSize ) +
84     filedisk_ptr->disk->SectorSize - sizeof ( *buf );
85   status =
86     ZwReadFile ( file, NULL, NULL, NULL, &io_status, buf, sizeof ( *buf ),
87                  &end_part, NULL );
88   if ( !NT_SUCCESS ( status ) )
89     goto err_read;
90   /*
91    * Adjust the footer's byte ordering
92    */
93   msvhd__footer_swap_endian ( buf );
94   /*
95    * Examine .VHD fields for validity
96    */
97   if (!wv_memcmpeq(&buf->cookie, "conectix", sizeof buf->cookie ))
98     status = STATUS_UNSUCCESSFUL;
99   if ( buf->file_ver.val != 0x10000 )
100     status = STATUS_UNSUCCESSFUL;
101   if ( buf->data_offset.val != 0xffffffff )
102     status = STATUS_UNSUCCESSFUL;
103   if ( buf->orig_size.val != buf->cur_size.val )
104     status = STATUS_UNSUCCESSFUL;
105   if ( buf->type.val != 2 )
106     status = STATUS_UNSUCCESSFUL;
107   /*
108    * Match against our expected disk size
109    */
110   if ( ( filedisk_ptr->disk->LBADiskSize * filedisk_ptr->disk->SectorSize ) !=
111        buf->cur_size.val )
112     status = STATUS_UNSUCCESSFUL;
113
114 err_read:
115
116   wv_free(buf);
117 err_alloc:
118
119   return status;
120 }
121
122 /**
123  * Temporarily used by established disks in order to access the
124  * backing disk late(r) during the boot process
125  */
126 static
127 disk__io_decl (
128   io
129  )
130 {
131   filedisk__type_ptr filedisk_ptr;
132   NTSTATUS status;
133   GUID disk_guid = GUID_DEVINTERFACE_DISK;
134   PWSTR sym_links;
135   PWCHAR pos;
136   HANDLE file;
137
138   filedisk_ptr = filedisk__get_ptr ( dev_ptr );
139   /*
140    * Find the backing disk and use it.  We walk a list
141    * of unicode disk device names and check each one
142    */
143   status = IoGetDeviceInterfaces ( &disk_guid, NULL, 0, &sym_links );
144   if ( !NT_SUCCESS ( status ) )
145     goto dud;
146   pos = sym_links;
147   while ( *pos != UNICODE_NULL )
148     {
149       UNICODE_STRING path;
150       OBJECT_ATTRIBUTES obj_attrs;
151       IO_STATUS_BLOCK io_status;
152
153       RtlInitUnicodeString ( &path, pos );
154       InitializeObjectAttributes ( &obj_attrs, &path,
155                                    OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
156                                    NULL, NULL );
157       /*
158        * Try this disk
159        */
160       file = 0;
161       status =
162         ZwCreateFile ( &file, GENERIC_READ | GENERIC_WRITE, &obj_attrs,
163                        &io_status, NULL, FILE_ATTRIBUTE_NORMAL,
164                        FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
165                        FILE_OPEN,
166                        FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS |
167                        FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 );
168       /*
169        * If we could open it, check it out
170        */
171       if ( NT_SUCCESS ( status ) )
172         status = check_disk_match ( file, filedisk_ptr );
173       /*
174        * If we liked this disk as our backing disk, stop looking
175        */
176       if ( NT_SUCCESS ( status ) )
177         break;
178       /*
179        * We could not open this disk or didn't like it.  Try the next one
180        */
181       if ( file )
182         ZwClose ( file );
183       while ( *pos != UNICODE_NULL )
184         pos++;
185       pos++;
186     }
187   wv_free(sym_links);
188   /*
189    * If we did not find the backing disk, we are a dud
190    */
191   if ( !NT_SUCCESS ( status ) )
192     goto dud;
193   /*
194    *  Use the backing disk and restore the original read/write routine
195    */
196   filedisk_ptr->file = file;
197   filedisk_ptr->sync_io = sync_io;
198   /*
199    * Call the original read/write routine
200    */
201   return sync_io ( dev_ptr, mode, start_sector, sector_count, buffer, irp );
202
203 dud:
204   irp->IoStatus.Information = 0;
205   irp->IoStatus.Status = STATUS_NO_MEDIA_IN_DEVICE;
206   IoCompleteRequest ( irp, IO_NO_INCREMENT );
207   return STATUS_NO_MEDIA_IN_DEVICE;
208 }
209
210 winvblock__def_struct ( drive_file_set )
211 {
212   winvblock__uint8 int13_drive_num;
213   char *filepath;
214 };
215
216 static void STDCALL
217 process_param_block (
218   const char *param_block,
219   drive_file_set_ptr sets
220  )
221 {
222   const char *end = param_block + 2047;
223   const char sig[] = "#!GRUB4DOS";
224   const char ver[] = "v=1";
225   int i = 0;
226
227   /*
228    * Check signature
229    */
230   if (!wv_memcmpeq(param_block, sig, sizeof sig)) {
231       DBG ( "RAM disk is not a parameter block.  Skipping.\n" );
232       return;
233     }
234   param_block += sizeof ( sig );
235   /*
236    * Looks like a parameter block someone passed from GRUB4DOS.
237    * Check the version
238    */
239   if (!wv_memcmpeq(param_block, ver, sizeof ver)) {
240       DBG ( "Parameter block version unsupported.  Skipping.\n" );
241       return;
242     }
243   param_block += sizeof ( ver );
244   /*
245    * We are interested in {filepath, NUL, X} sets, where X is INT13h drive num
246    */
247   RtlZeroMemory ( sets, sizeof ( drive_file_set ) * 8 );
248   while ( param_block < end && i != 8 )
249     {
250       const char *walker = param_block;
251
252       /*
253        * Walk to ASCII NUL terminator
254        */
255       while ( walker != end && *walker )
256         walker++;
257       if ( walker == param_block || walker == end )
258         /*
259          * End of filenames or run-away sequence
260          */
261         break;
262       walker++;
263       /*
264        * Make a note of the filename.  Skip initial '/' or '\'
265        */
266       if ( *param_block == '/' || *param_block == '\\' )
267         param_block++;
268       sets[i].filepath = wv_malloc(walker - param_block);
269       if ( sets[i].filepath == NULL )
270         {
271           DBG ( "Could not store filename\n" );
272           walker++;             /* Skip drive num */
273           param_block = walker;
274           continue;
275         }
276       RtlCopyMemory ( sets[i].filepath, param_block, walker - param_block );
277       /*
278        * Replace '/' with '\'
279        */
280       {
281         char *rep = sets[i].filepath;
282
283         while ( *rep )
284           {
285             if ( *rep == '/' )
286               *rep = '\\';
287             rep++;
288           }
289       }
290       /*
291        * The next byte is expected to be the INT 13h drive num
292        */
293       sets[i].int13_drive_num = *walker;
294       walker++;
295       i++;
296       param_block = walker;
297     }
298 }
299
300 void
301 filedisk_grub4dos__find (
302   void
303  )
304 {
305   PHYSICAL_ADDRESS PhysicalAddress;
306   winvblock__uint8_ptr PhysicalMemory;
307   int_vector_ptr InterruptVector;
308   winvblock__uint32 Int13Hook;
309   safe_mbr_hook_ptr SafeMbrHookPtr;
310   grub4dos__drive_mapping_ptr Grub4DosDriveMapSlotPtr;
311   winvblock__uint32 i;
312   winvblock__bool FoundGrub4DosMapping = FALSE;
313   filedisk__type_ptr filedisk_ptr;
314   const char sig[] = "GRUB4DOS";
315   drive_file_set sets[8];       /* Matches disks to files */
316
317   /*
318    * Find a GRUB4DOS sector-mapped disk.  Start by looking at the
319    * real-mode IDT and following the "SafeMBRHook" INT 0x13 hook
320    */
321   PhysicalAddress.QuadPart = 0LL;
322   PhysicalMemory = MmMapIoSpace ( PhysicalAddress, 0x100000, MmNonCached );
323   if ( !PhysicalMemory )
324     {
325       DBG ( "Could not map low memory\n" );
326       return;
327     }
328   InterruptVector =
329     ( int_vector_ptr ) ( PhysicalMemory + 0x13 * sizeof ( int_vector ) );
330   /*
331    * Walk the "safe hook" chain of INT 13h hooks as far as possible
332    */
333   while ( SafeMbrHookPtr = get_safe_hook ( PhysicalMemory, InterruptVector ) )
334     {
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 =
344         ( grub4dos__drive_mapping_ptr ) ( PhysicalMemory +
345                                           ( ( ( winvblock__uint32 )
346                                               InterruptVector->
347                                               Segment ) << 4 ) + 0x20 );
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 = disk__media_optical;
448               filedisk_ptr->disk->SectorSize = 2048;
449             }
450           else
451             {
452               filedisk_ptr->disk->media =
453                 Grub4DosDriveMapSlotPtr[i].SourceDrive & 0x80 ?
454                 disk__media_hard : disk__media_floppy;
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           ( ( winvblock__uint8 * ) & filedisk_ptr->hash )[0] =
493             Grub4DosDriveMapSlotPtr[i].SourceDrive;
494           filedisk_ptr->disk->BootDrive = TRUE;
495           FoundGrub4DosMapping = TRUE;
496           /* Add the filedisk to the bus. */
497           if (!bus__add_child(driver__bus(), filedisk_ptr->disk->device))
498       device__free(filedisk_ptr->disk->device);
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 }