[filedisk/grub4dos] Rename check_disk_match()...
[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 WvFilediskG4dCheckDiskMatch_(
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     /*
150      * These SCSI read/write IRPs should be completed in the thread context.
151      * Check if the IRP was already marked pending.
152      */
153     if (!(IoGetCurrentIrpStackLocation(irp)->Control & SL_PENDING_RETURNED)) {
154         /* Enqueue and signal work. */
155         IoMarkIrpPending(irp);
156         ExInterlockedInsertTailList(
157             filedisk_ptr->Irps,
158             &irp->Tail.Overlay.ListEntry,
159             filedisk_ptr->IrpsLock
160           );
161         KeSetEvent(&filedisk_ptr->Thread->Signal, 0, FALSE);
162         return STATUS_PENDING;
163       }
164
165     /*
166      * Find the backing disk and use it.  We walk a list
167      * of unicode disk device names and check each one.
168      */
169     status = IoGetDeviceInterfaces(&disk_guid, NULL, 0, &sym_links);
170     if (!NT_SUCCESS(status))
171       goto dud;
172     pos = sym_links;
173     while (*pos != UNICODE_NULL) {
174         UNICODE_STRING path;
175         OBJECT_ATTRIBUTES obj_attrs;
176         IO_STATUS_BLOCK io_status;
177
178         RtlInitUnicodeString(&path, pos);
179         InitializeObjectAttributes(
180             &obj_attrs,
181             &path,
182             OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
183             NULL,
184             NULL
185           );
186         /* Try this disk. */
187         file = 0;
188         status = ZwCreateFile(
189             &file,
190             GENERIC_READ | GENERIC_WRITE,
191             &obj_attrs,
192             &io_status,
193             NULL,
194             FILE_ATTRIBUTE_NORMAL,
195             FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
196             FILE_OPEN,
197             FILE_NON_DIRECTORY_FILE |
198               FILE_RANDOM_ACCESS |
199               FILE_SYNCHRONOUS_IO_NONALERT,
200             NULL,
201             0
202           );
203         /* If we could open it, check it out. */
204         if (NT_SUCCESS(status))
205           status = WvFilediskG4dCheckDiskMatch_(file, filedisk_ptr);
206         /* If we liked this disk as our backing disk, stop looking. */
207         if (NT_SUCCESS(status))
208           break;
209         /* We could not open this disk or didn't like it.  Try the next one. */
210         if (file)
211           ZwClose(file);
212         while (*pos != UNICODE_NULL)
213           pos++;
214         pos++;
215       } /* while */
216     wv_free(sym_links);
217     /* If we did not find the backing disk, we are a dud. */
218     if (!NT_SUCCESS(status))
219       goto dud;
220     /* Use the backing disk and restore the original read/write routine. */
221     filedisk_ptr->file = file;
222     filedisk_ptr->disk->disk_ops.Io = WvFilediskG4dPrevIo_;
223     /* Call the original read/write routine. */
224     return WvFilediskG4dPrevIo_(
225         filedisk_ptr->disk,
226         mode,
227         start_sector,
228         sector_count,
229         buffer,
230         irp
231       );
232
233     dud:
234     return WvlIrpComplete(irp, 0, STATUS_DEVICE_NOT_READY);
235   }
236
237 typedef struct WV_FILEDISK_GRUB4DOS_DRIVE_FILE_SET {
238     UCHAR int13_drive_num;
239     char *filepath;
240   }
241   WV_S_FILEDISK_GRUB4DOS_DRIVE_FILE_SET,
242   * WV_SP_FILEDISK_GRUB4DOS_DRIVE_FILE_SET;
243
244 static VOID STDCALL process_param_block(
245     const char * param_block,
246     WV_SP_FILEDISK_GRUB4DOS_DRIVE_FILE_SET sets
247   ) {
248     const char * end = param_block + 2047;
249     const char sig[] = "#!GRUB4DOS";
250     const char ver[] = "v=1";
251     int i = 0;
252
253     /* Check signature. */
254     if (!wv_memcmpeq(param_block, sig, sizeof sig)) {
255         DBG("RAM disk is not a parameter block.  Skipping.\n");
256         return;
257       }
258     param_block += sizeof sig;
259     /*
260      * Looks like a parameter block someone passed from GRUB4DOS.
261      * Check the version.
262      */
263     if (!wv_memcmpeq(param_block, ver, sizeof ver)) {
264         DBG("Parameter block version unsupported.  Skipping.\n");
265         return;
266       }
267     param_block += sizeof ver;
268     /*
269      * We are interested in {filepath, NUL, X} sets,
270      * where X is INT13h drive num.
271      */
272     RtlZeroMemory(sets, sizeof *sets * 8);
273     while (param_block < end && i != 8) {
274         const char *walker = param_block;
275
276         /* Walk to ASCII NUL terminator. */
277         while (walker != end && *walker)
278           walker++;
279         if (walker == param_block || walker == end)
280           /* End of filenames or run-away sequence. */
281           break;
282         walker++;
283         /* Make a note of the filename.  Skip initial '/' or '\'. */
284         if (*param_block == '/' || *param_block == '\\')
285           param_block++;
286         sets[i].filepath = wv_malloc(walker - param_block);
287         if (sets[i].filepath == NULL) {
288             DBG("Could not store filename\n");
289             /* Skip drive num. */
290             walker++;
291             param_block = walker;
292             continue;
293           } /* if */
294         RtlCopyMemory(sets[i].filepath, param_block, walker - param_block);
295         /* Replace '/' with '\'. */
296         {   char * rep = sets[i].filepath;
297
298             while (*rep) {
299                 if (*rep == '/')
300                   *rep = '\\';
301                 rep++;
302               }
303           } /* rep scope. */
304         /* The next byte is expected to be the INT 13h drive num. */
305         sets[i].int13_drive_num = *walker;
306         walker++;
307         i++;
308         param_block = walker;
309       } /* while */
310   }
311
312 VOID filedisk_grub4dos__find(void) {
313     PHYSICAL_ADDRESS PhysicalAddress;
314     PUCHAR PhysicalMemory;
315     WV_SP_PROBE_INT_VECTOR InterruptVector;
316     UINT32 Int13Hook;
317     WV_SP_PROBE_SAFE_MBR_HOOK SafeMbrHookPtr;
318     WV_SP_GRUB4DOS_DRIVE_MAPPING Grub4DosDriveMapSlotPtr;
319     UINT32 i;
320     BOOLEAN FoundGrub4DosMapping = FALSE;
321     WV_SP_FILEDISK_T filedisk_ptr;
322     const char sig[] = "GRUB4DOS";
323     /* Matches disks to files. */
324     WV_S_FILEDISK_GRUB4DOS_DRIVE_FILE_SET sets[8] = {0};
325
326     /*
327      * Find a GRUB4DOS sector-mapped disk.  Start by looking at the
328      * real-mode IDT and following the "SafeMBRHook" INT 0x13 hook.
329      */
330     PhysicalAddress.QuadPart = 0LL;
331     PhysicalMemory = MmMapIoSpace(PhysicalAddress, 0x100000, MmNonCached);
332     if (!PhysicalMemory) {
333         DBG("Could not map low memory\n");
334         return;
335       }
336     InterruptVector = (WV_SP_PROBE_INT_VECTOR) (
337         PhysicalMemory + 0x13 * sizeof *InterruptVector
338       );
339     /* Walk the "safe hook" chain of INT 13h hooks as far as possible. */
340     while (SafeMbrHookPtr = WvProbeGetSafeHook(
341         PhysicalMemory,
342         InterruptVector
343       )) {
344         /* Check signature. */
345         if (!wv_memcmpeq(SafeMbrHookPtr->VendorId, sig, sizeof sig - 1)) {
346             DBG("Non-GRUB4DOS INT 0x13 Safe Hook\n");
347             InterruptVector = &SafeMbrHookPtr->PrevHook;
348             continue;
349           }
350         Grub4DosDriveMapSlotPtr = (WV_SP_GRUB4DOS_DRIVE_MAPPING) (
351             PhysicalMemory +
352             (((UINT32) InterruptVector->Segment) << 4)
353             + 0x20
354           );
355         /*
356          * Search for parameter blocks, which are disguised as
357          * GRUB4DOS RAM disk mappings for 2048-byte memory regions.
358          */
359         i = 8;
360         while (i--) {
361             PHYSICAL_ADDRESS param_block_addr;
362             char * param_block;
363
364             if (Grub4DosDriveMapSlotPtr[i].DestDrive != 0xff)
365               continue;
366             param_block_addr.QuadPart =
367               Grub4DosDriveMapSlotPtr[i].SectorStart * 512;
368             param_block = MmMapIoSpace(param_block_addr, 2048, MmNonCached);
369             if (param_block == NULL) {
370                 DBG("Could not map potential G4D parameter block\n");
371                 continue;
372               }
373             /*
374              * Could be a parameter block.  Process it.  There can be only one.
375              */
376             process_param_block(param_block, sets);
377             MmUnmapIoSpace(param_block, 2048);
378             if (sets[0].filepath != NULL)
379               break;
380           } /* search for parameter blocks. */
381       /* Search for sector-mapped (typically file-backed) disks. */
382       i = 8;
383       while (i--) {
384           WVL_E_DISK_MEDIA_TYPE media_type;
385           UINT32 sector_size;
386
387           if (
388               (Grub4DosDriveMapSlotPtr[i].SectorCount == 0) ||
389               (Grub4DosDriveMapSlotPtr[i].DestDrive == 0xff)
390             ) {
391               DBG("Skipping non-sector-mapped GRUB4DOS disk\n");
392               continue;
393             }
394           DBG(
395               "GRUB4DOS SourceDrive: 0x%02x\n",
396               Grub4DosDriveMapSlotPtr[i].SourceDrive
397             );
398           DBG(
399               "GRUB4DOS DestDrive: 0x%02x\n",
400               Grub4DosDriveMapSlotPtr[i].DestDrive
401             );
402           DBG("GRUB4DOS MaxHead: %d\n", Grub4DosDriveMapSlotPtr[i].MaxHead);
403           DBG(
404               "GRUB4DOS MaxSector: %d\n",
405               Grub4DosDriveMapSlotPtr[i].MaxSector
406             );
407           DBG(
408               "GRUB4DOS DestMaxCylinder: %d\n",
409               Grub4DosDriveMapSlotPtr[i].DestMaxCylinder
410             );
411           DBG(
412               "GRUB4DOS DestMaxHead: %d\n",
413               Grub4DosDriveMapSlotPtr[i].DestMaxHead
414             );
415           DBG(
416               "GRUB4DOS DestMaxSector: %d\n",
417               Grub4DosDriveMapSlotPtr[i].DestMaxSector
418             );
419           DBG(
420               "GRUB4DOS SectorStart: 0x%08x\n",
421               Grub4DosDriveMapSlotPtr[i].SectorStart
422             );
423           DBG(
424               "GRUB4DOS SectorCount: %d\n",
425               Grub4DosDriveMapSlotPtr[i].SectorCount
426             );
427           /* Possible precision loss. */
428           if (Grub4DosDriveMapSlotPtr[i].SourceODD) {
429               media_type = WvlDiskMediaTypeOptical;
430               sector_size = 2048;
431             } else {
432               media_type =
433                 (Grub4DosDriveMapSlotPtr[i].SourceDrive & 0x80) ?
434                 WvlDiskMediaTypeHard :
435                 WvlDiskMediaTypeFloppy;
436               sector_size = 512;
437             }
438           /*
439            * Create the threaded, file-backed disk.  Hook the
440            * read/write routine so we can accessing the backing disk
441            * late(r) during the boot process.
442            */
443           filedisk_ptr = WvFilediskCreatePdo(media_type);
444           if (filedisk_ptr == NULL) {
445               DBG("Could not create GRUB4DOS disk!\n");
446               return;
447             }
448           /* Record the usual filedisk I/O function for later. */
449           WvFilediskG4dPrevIo_ = filedisk_ptr->disk->disk_ops.Io;
450           /* Hook the first I/O request. */
451           filedisk_ptr->disk->disk_ops.Io = WvFilediskG4dIo_;
452           /* Other parameters we know. */
453           filedisk_ptr->disk->Media = media_type;
454           filedisk_ptr->disk->SectorSize = sector_size;
455           /* Find an associated filename, if one exists. */
456           {   int j = 8;
457
458               while (j--) {
459                   if (
460                       (sets[j].int13_drive_num ==
461                         Grub4DosDriveMapSlotPtr[i].SourceDrive) &&
462                       (sets[j].filepath != NULL)
463                     )
464                     WvFilediskHotSwap(filedisk_ptr, sets[j].filepath);
465                 }
466             } /* j scope. */
467           DBG("Sector-mapped disk is type: %d\n", media_type);
468           /* Note the offset of the disk image from the backing disk's start. */
469           filedisk_ptr->offset.QuadPart =
470             Grub4DosDriveMapSlotPtr[i].SectorStart *
471             (Grub4DosDriveMapSlotPtr[i].DestODD ? 2048 : 512);
472           /*
473            * Size and geometry.  Please note that since we require a .VHD
474            * footer, we exclude this from the LBA disk size by truncating
475            * a 512-byte sector for HDD images, or a 2048-byte sector for .ISO.
476            */
477           {   ULONGLONG total_size =
478                 Grub4DosDriveMapSlotPtr[i].SectorCount *
479                 (Grub4DosDriveMapSlotPtr[i].DestODD ? 2048 : 512);
480
481               filedisk_ptr->disk->LBADiskSize =
482                 (total_size - filedisk_ptr->disk->SectorSize) /
483                 filedisk_ptr->disk->SectorSize;
484             } /* total_size scope. */
485           filedisk_ptr->disk->Heads = Grub4DosDriveMapSlotPtr[i].MaxHead + 1;
486           filedisk_ptr->disk->Sectors =
487             Grub4DosDriveMapSlotPtr[i].DestMaxSector;
488           filedisk_ptr->disk->Cylinders =
489             filedisk_ptr->disk->LBADiskSize /
490             (filedisk_ptr->disk->Heads * filedisk_ptr->disk->Sectors);
491           /*
492            * Set a filedisk "hash" and mark the drive as a boot-drive.
493            * The "hash" is 'G4DX', where X is the GRUB4DOS INT 13h
494            * drive number.  Note that mutiple G4D INT 13h chains will
495            * cause a "hash" collision!  Too bad for now.
496            */
497           filedisk_ptr->hash = 'G4DX';
498           ((PUCHAR) &filedisk_ptr->hash)[0] =
499             Grub4DosDriveMapSlotPtr[i].SourceDrive;
500           FoundGrub4DosMapping = TRUE;
501           filedisk_ptr->Dev->Boot = TRUE;
502           /* Add the filedisk to the bus. */
503           filedisk_ptr->disk->ParentBus = WvBus.Fdo;
504           if (!WvBusAddDev(filedisk_ptr->Dev))
505             WvDevFree(filedisk_ptr->Dev);
506         } /* search for sector-mapped disks. */
507       InterruptVector = &SafeMbrHookPtr->PrevHook;
508     } /* walk the safe hook chain. */
509
510     MmUnmapIoSpace(PhysicalMemory, 0x100000);
511     if (!FoundGrub4DosMapping) {
512         DBG("No GRUB4DOS sector-mapped disk mappings found\n");
513       }
514   }