[filedisk/grub4dos] Return STATUS_DEVICE_NOT_READY...
[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 "irp.h"
33 #include "wv_stdlib.h"
34 #include "wv_string.h"
35 #include "driver.h"
36 #include "bus.h"
37 #include "device.h"
38 #include "disk.h"
39 #include "libthread.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 /* From bus.c */
48 extern WVL_S_BUS_T WvBus;
49
50 /** Private. */
51 static WVL_FP_DISK_IO WvFilediskG4dPrevIo_;
52 static WVL_F_DISK_IO WvFilediskG4dIo_;
53
54 /**
55  * Check if a disk might be the matching backing disk for
56  * a GRUB4DOS sector-mapped disk.
57  *
58  * @v file              HANDLE to an open disk.
59  * @v filedisk_ptr      Points to the filedisk to match against.
60  */
61 static NTSTATUS STDCALL check_disk_match(
62     IN HANDLE file,
63     IN WV_SP_FILEDISK_T filedisk_ptr
64   ) {
65     WV_SP_MSVHD_FOOTER buf;
66     NTSTATUS status;
67     IO_STATUS_BLOCK io_status;
68     LARGE_INTEGER end_part;
69
70     /* Allocate a buffer for testing for a MS .VHD footer. */
71     buf = wv_malloc(sizeof *buf);
72     if (buf == NULL) {
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 -
85       sizeof *buf;
86     status = ZwReadFile(
87         file,
88         NULL,
89         NULL,
90         NULL,
91         &io_status,
92         buf,
93         sizeof *buf,
94         &end_part,
95         NULL
96       );
97     if (!NT_SUCCESS(status))
98       goto err_read;
99     /* Adjust the footer's byte ordering. */
100     msvhd__footer_swap_endian(buf);
101     /* Examine .VHD fields for validity. */
102     if (!wv_memcmpeq(&buf->cookie, "conectix", sizeof buf->cookie))
103       status = STATUS_UNSUCCESSFUL;
104     if (buf->file_ver.val != 0x10000)
105       status = STATUS_UNSUCCESSFUL;
106     if (buf->data_offset.val != 0xffffffff)
107       status = STATUS_UNSUCCESSFUL;
108     if (buf->orig_size.val != buf->cur_size.val)
109       status = STATUS_UNSUCCESSFUL;
110     if (buf->type.val != 2)
111       status = STATUS_UNSUCCESSFUL;
112     /* Match against our expected disk size. */
113     if (
114         (filedisk_ptr->disk->LBADiskSize * filedisk_ptr->disk->SectorSize ) !=
115         buf->cur_size.val
116       )
117       status = STATUS_UNSUCCESSFUL;
118
119     err_read:
120
121     wv_free(buf);
122     err_alloc:
123
124     return status;
125   }
126
127 /**
128  * Temporarily used by established disks in order to access the
129  * backing disk late(r) during the boot process.
130  */
131 static NTSTATUS STDCALL WvFilediskG4dIo_(
132     IN WVL_SP_DISK_T disk,
133     IN WVL_E_DISK_IO_MODE mode,
134     IN LONGLONG start_sector,
135     IN UINT32 sector_count,
136     IN PUCHAR buffer,
137     IN PIRP irp
138   ) {
139     WV_SP_FILEDISK_T filedisk_ptr;
140     NTSTATUS status;
141     GUID disk_guid = GUID_DEVINTERFACE_DISK;
142     PWSTR sym_links;
143     PWCHAR pos;
144     HANDLE file;
145
146     /* Establish pointer to the filedisk. */
147     filedisk_ptr = CONTAINING_RECORD(disk, WV_S_FILEDISK_T, disk);
148     /*
149      * Find the backing disk and use it.  We walk a list
150      * of unicode disk device names and check each one.
151      */
152     status = IoGetDeviceInterfaces(&disk_guid, NULL, 0, &sym_links);
153     if (!NT_SUCCESS(status))
154       goto dud;
155     pos = sym_links;
156     while (*pos != UNICODE_NULL) {
157         UNICODE_STRING path;
158         OBJECT_ATTRIBUTES obj_attrs;
159         IO_STATUS_BLOCK io_status;
160
161         RtlInitUnicodeString(&path, pos);
162         InitializeObjectAttributes(
163             &obj_attrs,
164             &path,
165             OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
166             NULL,
167             NULL
168           );
169         /* Try this disk. */
170         file = 0;
171         status = ZwCreateFile(
172             &file,
173             GENERIC_READ | GENERIC_WRITE,
174             &obj_attrs,
175             &io_status,
176             NULL,
177             FILE_ATTRIBUTE_NORMAL,
178             FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
179             FILE_OPEN,
180             FILE_NON_DIRECTORY_FILE |
181               FILE_RANDOM_ACCESS |
182               FILE_SYNCHRONOUS_IO_NONALERT,
183             NULL,
184             0
185           );
186         /* If we could open it, check it out. */
187         if (NT_SUCCESS(status))
188           status = check_disk_match(file, filedisk_ptr);
189         /* If we liked this disk as our backing disk, stop looking. */
190         if (NT_SUCCESS(status))
191           break;
192         /* We could not open this disk or didn't like it.  Try the next one. */
193         if (file)
194           ZwClose(file);
195         while (*pos != UNICODE_NULL)
196           pos++;
197         pos++;
198       } /* while */
199     wv_free(sym_links);
200     /* If we did not find the backing disk, we are a dud. */
201     if (!NT_SUCCESS(status))
202       goto dud;
203     /* Use the backing disk and restore the original read/write routine. */
204     filedisk_ptr->file = file;
205     filedisk_ptr->disk->disk_ops.Io = WvFilediskG4dPrevIo_;
206     /* Call the original read/write routine. */
207     return WvFilediskG4dPrevIo_(
208         filedisk_ptr->disk,
209         mode,
210         start_sector,
211         sector_count,
212         buffer,
213         irp
214       );
215
216     dud:
217     return WvlIrpComplete(irp, 0, STATUS_DEVICE_NOT_READY);
218   }
219
220 typedef struct WV_FILEDISK_GRUB4DOS_DRIVE_FILE_SET {
221     UCHAR int13_drive_num;
222     char *filepath;
223   }
224   WV_S_FILEDISK_GRUB4DOS_DRIVE_FILE_SET,
225   * WV_SP_FILEDISK_GRUB4DOS_DRIVE_FILE_SET;
226
227 static VOID STDCALL process_param_block(
228     const char * param_block,
229     WV_SP_FILEDISK_GRUB4DOS_DRIVE_FILE_SET sets
230   ) {
231     const char * end = param_block + 2047;
232     const char sig[] = "#!GRUB4DOS";
233     const char ver[] = "v=1";
234     int i = 0;
235
236     /* Check signature. */
237     if (!wv_memcmpeq(param_block, sig, sizeof sig)) {
238         DBG("RAM disk is not a parameter block.  Skipping.\n");
239         return;
240       }
241     param_block += sizeof sig;
242     /*
243      * Looks like a parameter block someone passed from GRUB4DOS.
244      * Check the version.
245      */
246     if (!wv_memcmpeq(param_block, ver, sizeof ver)) {
247         DBG("Parameter block version unsupported.  Skipping.\n");
248         return;
249       }
250     param_block += sizeof ver;
251     /*
252      * We are interested in {filepath, NUL, X} sets,
253      * where X is INT13h drive num.
254      */
255     RtlZeroMemory(sets, sizeof *sets * 8);
256     while (param_block < end && i != 8) {
257         const char *walker = param_block;
258
259         /* Walk to ASCII NUL terminator. */
260         while (walker != end && *walker)
261           walker++;
262         if (walker == param_block || walker == end)
263           /* End of filenames or run-away sequence. */
264           break;
265         walker++;
266         /* Make a note of the filename.  Skip initial '/' or '\'. */
267         if (*param_block == '/' || *param_block == '\\')
268           param_block++;
269         sets[i].filepath = wv_malloc(walker - param_block);
270         if (sets[i].filepath == NULL) {
271             DBG("Could not store filename\n");
272             /* Skip drive num. */
273             walker++;
274             param_block = walker;
275             continue;
276           } /* if */
277         RtlCopyMemory(sets[i].filepath, param_block, walker - param_block);
278         /* Replace '/' with '\'. */
279         {   char * rep = sets[i].filepath;
280
281             while (*rep) {
282                 if (*rep == '/')
283                   *rep = '\\';
284                 rep++;
285               }
286           } /* rep scope. */
287         /* The next byte is expected to be the INT 13h drive num. */
288         sets[i].int13_drive_num = *walker;
289         walker++;
290         i++;
291         param_block = walker;
292       } /* while */
293   }
294
295 VOID filedisk_grub4dos__find(void) {
296     PHYSICAL_ADDRESS PhysicalAddress;
297     PUCHAR PhysicalMemory;
298     WV_SP_PROBE_INT_VECTOR InterruptVector;
299     UINT32 Int13Hook;
300     WV_SP_PROBE_SAFE_MBR_HOOK SafeMbrHookPtr;
301     WV_SP_GRUB4DOS_DRIVE_MAPPING Grub4DosDriveMapSlotPtr;
302     UINT32 i;
303     BOOLEAN FoundGrub4DosMapping = FALSE;
304     WV_SP_FILEDISK_T filedisk_ptr;
305     const char sig[] = "GRUB4DOS";
306     /* Matches disks to files. */
307     WV_S_FILEDISK_GRUB4DOS_DRIVE_FILE_SET sets[8] = {0};
308
309     /*
310      * Find a GRUB4DOS sector-mapped disk.  Start by looking at the
311      * real-mode IDT and following the "SafeMBRHook" INT 0x13 hook.
312      */
313     PhysicalAddress.QuadPart = 0LL;
314     PhysicalMemory = MmMapIoSpace(PhysicalAddress, 0x100000, MmNonCached);
315     if (!PhysicalMemory) {
316         DBG("Could not map low memory\n");
317         return;
318       }
319     InterruptVector = (WV_SP_PROBE_INT_VECTOR) (
320         PhysicalMemory + 0x13 * sizeof *InterruptVector
321       );
322     /* Walk the "safe hook" chain of INT 13h hooks as far as possible. */
323     while (SafeMbrHookPtr = WvProbeGetSafeHook(
324         PhysicalMemory,
325         InterruptVector
326       )) {
327         /* Check signature. */
328         if (!wv_memcmpeq(SafeMbrHookPtr->VendorId, sig, sizeof sig - 1)) {
329             DBG("Non-GRUB4DOS INT 0x13 Safe Hook\n");
330             InterruptVector = &SafeMbrHookPtr->PrevHook;
331             continue;
332           }
333         Grub4DosDriveMapSlotPtr = (WV_SP_GRUB4DOS_DRIVE_MAPPING) (
334             PhysicalMemory +
335             (((UINT32) InterruptVector->Segment) << 4)
336             + 0x20
337           );
338         /*
339          * Search for parameter blocks, which are disguised as
340          * GRUB4DOS RAM disk mappings for 2048-byte memory regions.
341          */
342         i = 8;
343         while (i--) {
344             PHYSICAL_ADDRESS param_block_addr;
345             char * param_block;
346
347             if (Grub4DosDriveMapSlotPtr[i].DestDrive != 0xff)
348               continue;
349             param_block_addr.QuadPart =
350               Grub4DosDriveMapSlotPtr[i].SectorStart * 512;
351             param_block = MmMapIoSpace(param_block_addr, 2048, MmNonCached);
352             if (param_block == NULL) {
353                 DBG("Could not map potential G4D parameter block\n");
354                 continue;
355               }
356             /*
357              * Could be a parameter block.  Process it.  There can be only one.
358              */
359             process_param_block(param_block, sets);
360             MmUnmapIoSpace(param_block, 2048);
361             if (sets[0].filepath != NULL)
362               break;
363           } /* search for parameter blocks. */
364       /* Search for sector-mapped (typically file-backed) disks. */
365       i = 8;
366       while (i--) {
367           WVL_E_DISK_MEDIA_TYPE media_type;
368           UINT32 sector_size;
369
370           if (
371               (Grub4DosDriveMapSlotPtr[i].SectorCount == 0) ||
372               (Grub4DosDriveMapSlotPtr[i].DestDrive == 0xff)
373             ) {
374               DBG("Skipping non-sector-mapped GRUB4DOS disk\n");
375               continue;
376             }
377           DBG(
378               "GRUB4DOS SourceDrive: 0x%02x\n",
379               Grub4DosDriveMapSlotPtr[i].SourceDrive
380             );
381           DBG(
382               "GRUB4DOS DestDrive: 0x%02x\n",
383               Grub4DosDriveMapSlotPtr[i].DestDrive
384             );
385           DBG("GRUB4DOS MaxHead: %d\n", Grub4DosDriveMapSlotPtr[i].MaxHead);
386           DBG(
387               "GRUB4DOS MaxSector: %d\n",
388               Grub4DosDriveMapSlotPtr[i].MaxSector
389             );
390           DBG(
391               "GRUB4DOS DestMaxCylinder: %d\n",
392               Grub4DosDriveMapSlotPtr[i].DestMaxCylinder
393             );
394           DBG(
395               "GRUB4DOS DestMaxHead: %d\n",
396               Grub4DosDriveMapSlotPtr[i].DestMaxHead
397             );
398           DBG(
399               "GRUB4DOS DestMaxSector: %d\n",
400               Grub4DosDriveMapSlotPtr[i].DestMaxSector
401             );
402           DBG(
403               "GRUB4DOS SectorStart: 0x%08x\n",
404               Grub4DosDriveMapSlotPtr[i].SectorStart
405             );
406           DBG(
407               "GRUB4DOS SectorCount: %d\n",
408               Grub4DosDriveMapSlotPtr[i].SectorCount
409             );
410           /* Possible precision loss. */
411           if (Grub4DosDriveMapSlotPtr[i].SourceODD) {
412               media_type = WvlDiskMediaTypeOptical;
413               sector_size = 2048;
414             } else {
415               media_type =
416                 (Grub4DosDriveMapSlotPtr[i].SourceDrive & 0x80) ?
417                 WvlDiskMediaTypeHard :
418                 WvlDiskMediaTypeFloppy;
419               sector_size = 512;
420             }
421           /*
422            * Create the threaded, file-backed disk.  Hook the
423            * read/write routine so we can accessing the backing disk
424            * late(r) during the boot process.
425            */
426           filedisk_ptr = WvFilediskCreatePdo(media_type);
427           if (filedisk_ptr == NULL) {
428               DBG("Could not create GRUB4DOS disk!\n");
429               return;
430             }
431           /* Record the usual filedisk I/O function for later. */
432           WvFilediskG4dPrevIo_ = filedisk_ptr->disk->disk_ops.Io;
433           /* Hook the first I/O request. */
434           filedisk_ptr->disk->disk_ops.Io = WvFilediskG4dIo_;
435           /* Other parameters we know. */
436           filedisk_ptr->disk->Media = media_type;
437           filedisk_ptr->disk->SectorSize = sector_size;
438           /* Find an associated filename, if one exists. */
439           {   int j = 8;
440
441               while (j--) {
442                   if (
443                       (sets[j].int13_drive_num ==
444                         Grub4DosDriveMapSlotPtr[i].SourceDrive) &&
445                       (sets[j].filepath != NULL)
446                     )
447                     WvFilediskHotSwap(filedisk_ptr, sets[j].filepath);
448                 }
449             } /* j scope. */
450           DBG("Sector-mapped disk is type: %d\n", media_type);
451           /* Note the offset of the disk image from the backing disk's start. */
452           filedisk_ptr->offset.QuadPart =
453             Grub4DosDriveMapSlotPtr[i].SectorStart *
454             (Grub4DosDriveMapSlotPtr[i].DestODD ? 2048 : 512);
455           /*
456            * Size and geometry.  Please note that since we require a .VHD
457            * footer, we exclude this from the LBA disk size by truncating
458            * a 512-byte sector for HDD images, or a 2048-byte sector for .ISO.
459            */
460           {   ULONGLONG total_size =
461                 Grub4DosDriveMapSlotPtr[i].SectorCount *
462                 (Grub4DosDriveMapSlotPtr[i].DestODD ? 2048 : 512);
463
464               filedisk_ptr->disk->LBADiskSize =
465                 (total_size - filedisk_ptr->disk->SectorSize) /
466                 filedisk_ptr->disk->SectorSize;
467             } /* total_size scope. */
468           filedisk_ptr->disk->Heads = Grub4DosDriveMapSlotPtr[i].MaxHead + 1;
469           filedisk_ptr->disk->Sectors =
470             Grub4DosDriveMapSlotPtr[i].DestMaxSector;
471           filedisk_ptr->disk->Cylinders =
472             filedisk_ptr->disk->LBADiskSize /
473             (filedisk_ptr->disk->Heads * filedisk_ptr->disk->Sectors);
474           /*
475            * Set a filedisk "hash" and mark the drive as a boot-drive.
476            * The "hash" is 'G4DX', where X is the GRUB4DOS INT 13h
477            * drive number.  Note that mutiple G4D INT 13h chains will
478            * cause a "hash" collision!  Too bad for now.
479            */
480           filedisk_ptr->hash = 'G4DX';
481           ((PUCHAR) &filedisk_ptr->hash)[0] =
482             Grub4DosDriveMapSlotPtr[i].SourceDrive;
483           FoundGrub4DosMapping = TRUE;
484           filedisk_ptr->Dev->Boot = TRUE;
485           /* Add the filedisk to the bus. */
486           filedisk_ptr->disk->ParentBus = WvBus.Fdo;
487           if (!WvBusAddDev(filedisk_ptr->Dev))
488             WvDevFree(filedisk_ptr->Dev);
489         } /* search for sector-mapped disks. */
490       InterruptVector = &SafeMbrHookPtr->PrevHook;
491     } /* walk the safe hook chain. */
492
493     MmUnmapIoSpace(PhysicalMemory, 0x100000);
494     if (!FoundGrub4DosMapping) {
495         DBG("No GRUB4DOS sector-mapped disk mappings found\n");
496       }
497   }