chainloader: patch in BPB's sectors_per_track and num_heads
[grub.git] / grub-core / loader / i386 / pc / chainloader.c
1 /* chainloader.c - boot another boot loader */
2 /*
3  *  GRUB  --  GRand Unified Bootloader
4  *  Copyright (C) 2002,2004,2007,2009,2010  Free Software Foundation, Inc.
5  *
6  *  GRUB 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  *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <grub/loader.h>
21 #include <grub/machine/chainloader.h>
22 #include <grub/machine/biosdisk.h>
23 #include <grub/machine/memory.h>
24 #include <grub/file.h>
25 #include <grub/err.h>
26 #include <grub/device.h>
27 #include <grub/disk.h>
28 #include <grub/misc.h>
29 #include <grub/types.h>
30 #include <grub/partition.h>
31 #include <grub/memory.h>
32 #include <grub/dl.h>
33 #include <grub/command.h>
34 #include <grub/msdos_partition.h>
35 #include <grub/machine/biosnum.h>
36 #include <grub/cpu/floppy.h>
37 #include <grub/i18n.h>
38 #include <grub/video.h>
39 #include <grub/mm.h>
40 #include <grub/fat.h>
41 #include <grub/ntfs.h>
42 #include <grub/i386/relocator.h>
43
44 GRUB_MOD_LICENSE ("GPLv3+");
45
46 static grub_dl_t my_mod;
47 static int boot_drive;
48 static grub_addr_t boot_part_addr;
49 static struct grub_relocator *rel;
50
51 typedef enum
52   {
53     GRUB_CHAINLOADER_FORCE = 0x1,
54     GRUB_CHAINLOADER_BPB = 0x2,
55   } grub_chainloader_flags_t;
56
57 static grub_err_t
58 grub_chainloader_boot (void)
59 {
60   struct grub_relocator16_state state = { 
61     .edx = boot_drive,
62     .esi = boot_part_addr,
63     .ds = 0,
64     .es = 0,
65     .fs = 0,
66     .gs = 0,
67     .ss = 0,
68     .cs = 0,
69     .sp = GRUB_MEMORY_MACHINE_BOOT_LOADER_ADDR,
70     .ip = GRUB_MEMORY_MACHINE_BOOT_LOADER_ADDR,
71     .a20 = 0
72   };
73   grub_video_set_mode ("text", 0, 0);
74
75   return grub_relocator16_boot (rel, state);
76 }
77
78 static grub_err_t
79 grub_chainloader_unload (void)
80 {
81   grub_relocator_unload (rel);
82   rel = NULL;
83   grub_dl_unref (my_mod);
84   return GRUB_ERR_NONE;
85 }
86
87 void
88 grub_chainloader_patch_bpb (void *bs, grub_device_t dev, grub_uint8_t dl)
89 {
90   grub_uint32_t part_start = 0, heads = 0, sectors = 0;
91   if (dev && dev->disk)
92     {
93       part_start = grub_partition_get_start (dev->disk->partition);
94       if (dev->disk->data)
95         {
96           heads = ((struct grub_biosdisk_data *)(dev->disk->data))->heads;
97           sectors = ((struct grub_biosdisk_data *)(dev->disk->data))->sectors;
98         }
99     }
100   if (grub_memcmp ((char *) &((struct grub_ntfs_bpb *) bs)->oem_name,
101                    "NTFS", 4) == 0)
102     {
103       struct grub_ntfs_bpb *bpb = (struct grub_ntfs_bpb *) bs;
104       bpb->num_hidden_sectors = grub_cpu_to_le32 (part_start);
105       bpb->bios_drive = dl;
106       return;
107     }
108
109   do
110     {
111       struct grub_fat_bpb *bpb = (struct grub_fat_bpb *) bs;
112       if (grub_strncmp((const char *) bpb->version_specific.fat12_or_fat16.fstype, "FAT12", 5)
113           && grub_strncmp((const char *) bpb->version_specific.fat12_or_fat16.fstype, "FAT16", 5)
114           && grub_strncmp((const char *) bpb->version_specific.fat32.fstype, "FAT32", 5))
115         break;
116
117       if (grub_le_to_cpu16 (bpb->bytes_per_sector) < 512
118           || (grub_le_to_cpu16 (bpb->bytes_per_sector)
119               & (grub_le_to_cpu16 (bpb->bytes_per_sector) - 1)))
120         break;
121           
122       if (bpb->sectors_per_cluster == 0
123           || (bpb->sectors_per_cluster & (bpb->sectors_per_cluster - 1)))
124         break;
125
126       if (bpb->num_reserved_sectors == 0)
127         break;
128       if (bpb->num_total_sectors_16 == 0 && bpb->num_total_sectors_32 == 0)
129         break;
130
131       if (bpb->num_fats == 0)
132         break;
133
134       if (bpb->sectors_per_fat_16)
135         {
136           bpb->num_hidden_sectors = grub_cpu_to_le32 (part_start);
137           bpb->version_specific.fat12_or_fat16.num_ph_drive = dl;
138           if (sectors)
139             bpb->sectors_per_track = grub_cpu_to_le16 (sectors);
140           if (heads)
141             bpb->num_heads = grub_cpu_to_le16 (heads);
142           return;
143         }
144       if (bpb->version_specific.fat32.sectors_per_fat_32)
145         {
146           bpb->num_hidden_sectors = grub_cpu_to_le32 (part_start);
147           bpb->version_specific.fat32.num_ph_drive = dl;
148           if (sectors)
149             bpb->sectors_per_track = grub_cpu_to_le16 (sectors);
150           if (heads)
151             bpb->num_heads = grub_cpu_to_le16 (heads);
152           return;
153         }
154       break;
155     }
156   while (0);
157 }
158
159 static void
160 grub_chainloader_cmd (const char *filename, grub_chainloader_flags_t flags)
161 {
162   grub_file_t file = 0;
163   grub_uint16_t signature;
164   grub_device_t dev;
165   int drive = -1;
166   grub_addr_t part_addr = 0;
167   grub_uint8_t *bs, *ptable;
168
169   rel = grub_relocator_new ();
170   if (!rel)
171     goto fail;
172
173   grub_dl_ref (my_mod);
174
175   grub_file_filter_disable_compression ();
176   file = grub_file_open (filename);
177   if (! file)
178     goto fail;
179
180   {
181     grub_relocator_chunk_t ch;
182     grub_err_t err;
183
184     err = grub_relocator_alloc_chunk_addr (rel, &ch, 0x7C00,
185                                            GRUB_DISK_SECTOR_SIZE);
186     if (err)
187       goto fail;
188     bs = get_virtual_current_address (ch);
189     err = grub_relocator_alloc_chunk_addr (rel, &ch,
190                                            GRUB_MEMORY_MACHINE_PART_TABLE_ADDR,
191                                            64);
192     if (err)
193       goto fail;
194     ptable = get_virtual_current_address (ch);
195   }
196
197   /* Read the first block.  */
198   if (grub_file_read (file, bs, GRUB_DISK_SECTOR_SIZE)
199       != GRUB_DISK_SECTOR_SIZE)
200     {
201       if (!grub_errno)
202         grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
203                     filename);
204
205       goto fail;
206     }
207
208   /* Check the signature.  */
209   signature = *((grub_uint16_t *) (bs + GRUB_DISK_SECTOR_SIZE - 2));
210   if (signature != grub_le_to_cpu16 (0xaa55)
211       && ! (flags & GRUB_CHAINLOADER_FORCE))
212     {
213       grub_error (GRUB_ERR_BAD_OS, "invalid signature");
214       goto fail;
215     }
216
217   grub_file_close (file);
218
219   /* Obtain the partition table from the root device.  */
220   drive = grub_get_root_biosnumber ();
221   dev = grub_device_open (0);
222   if (dev && dev->disk && dev->disk->partition)
223     {
224       grub_disk_t disk = dev->disk;
225
226       if (disk)
227         {
228           grub_partition_t p = disk->partition;
229
230           if (p && grub_strcmp (p->partmap->name, "msdos") == 0)
231             {
232               disk->partition = p->parent;
233               grub_disk_read (disk, p->offset, 446, 64, ptable);
234               part_addr = (GRUB_MEMORY_MACHINE_PART_TABLE_ADDR
235                            + (p->index << 4));
236               disk->partition = p;
237             }
238         }
239     }
240
241   if (flags & GRUB_CHAINLOADER_BPB)
242     grub_chainloader_patch_bpb ((void *) 0x7C00, dev, drive);
243
244   if (dev)
245     grub_device_close (dev);
246  
247   /* Ignore errors. Perhaps it's not fatal.  */
248   grub_errno = GRUB_ERR_NONE;
249
250   boot_drive = drive;
251   boot_part_addr = part_addr;
252
253   grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 1);
254   return;
255
256  fail:
257
258   if (file)
259     grub_file_close (file);
260
261   grub_dl_unref (my_mod);
262 }
263
264 static grub_err_t
265 grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)),
266                       int argc, char *argv[])
267 {
268   grub_chainloader_flags_t flags = 0;
269
270   while (argc > 0)
271     {
272       if (grub_strcmp (argv[0], "--force") == 0)
273         {
274           flags |= GRUB_CHAINLOADER_FORCE;
275           argc--;
276           argv++;
277           continue;
278         }
279       if (grub_strcmp (argv[0], "--bpb") == 0)
280         {
281           flags |= GRUB_CHAINLOADER_BPB;
282           argc--;
283           argv++;
284           continue;
285         }
286       break;
287     }
288
289   if (argc == 0)
290     return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
291
292   grub_chainloader_cmd (argv[0], flags);
293
294   return grub_errno;
295 }
296
297 static grub_command_t cmd;
298
299 GRUB_MOD_INIT(chainloader)
300 {
301   cmd = grub_register_command ("chainloader", grub_cmd_chainloader,
302                                N_("[--force|--bpb] FILE"),
303                                N_("Load another boot loader."));
304   my_mod = mod;
305 }
306
307 GRUB_MOD_FINI(chainloader)
308 {
309   grub_unregister_command (cmd);
310 }