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