summaryrefslogtreecommitdiffstats
path: root/grub-core/loader/i386/pc/chainloader.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/loader/i386/pc/chainloader.c')
-rw-r--r--grub-core/loader/i386/pc/chainloader.c310
1 files changed, 310 insertions, 0 deletions
diff --git a/grub-core/loader/i386/pc/chainloader.c b/grub-core/loader/i386/pc/chainloader.c
new file mode 100644
index 0000000..976fea7
--- /dev/null
+++ b/grub-core/loader/i386/pc/chainloader.c
@@ -0,0 +1,310 @@
+/* chainloader.c - boot another boot loader */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2002,2004,2007,2009,2010 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/loader.h>
+#include <grub/machine/chainloader.h>
+#include <grub/machine/biosdisk.h>
+#include <grub/machine/memory.h>
+#include <grub/file.h>
+#include <grub/err.h>
+#include <grub/device.h>
+#include <grub/disk.h>
+#include <grub/misc.h>
+#include <grub/types.h>
+#include <grub/partition.h>
+#include <grub/memory.h>
+#include <grub/dl.h>
+#include <grub/command.h>
+#include <grub/msdos_partition.h>
+#include <grub/machine/biosnum.h>
+#include <grub/cpu/floppy.h>
+#include <grub/i18n.h>
+#include <grub/video.h>
+#include <grub/mm.h>
+#include <grub/fat.h>
+#include <grub/ntfs.h>
+#include <grub/i386/relocator.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static grub_dl_t my_mod;
+static int boot_drive;
+static grub_addr_t boot_part_addr;
+static struct grub_relocator *rel;
+
+typedef enum
+ {
+ GRUB_CHAINLOADER_FORCE = 0x1,
+ GRUB_CHAINLOADER_BPB = 0x2,
+ } grub_chainloader_flags_t;
+
+static grub_err_t
+grub_chainloader_boot (void)
+{
+ struct grub_relocator16_state state = {
+ .edx = boot_drive,
+ .esi = boot_part_addr,
+ .ds = 0,
+ .es = 0,
+ .fs = 0,
+ .gs = 0,
+ .ss = 0,
+ .cs = 0,
+ .sp = GRUB_MEMORY_MACHINE_BOOT_LOADER_ADDR,
+ .ip = GRUB_MEMORY_MACHINE_BOOT_LOADER_ADDR,
+ .a20 = 0
+ };
+ grub_video_set_mode ("text", 0, 0);
+
+ return grub_relocator16_boot (rel, state);
+}
+
+static grub_err_t
+grub_chainloader_unload (void)
+{
+ grub_relocator_unload (rel);
+ rel = NULL;
+ grub_dl_unref (my_mod);
+ return GRUB_ERR_NONE;
+}
+
+void
+grub_chainloader_patch_bpb (void *bs, grub_device_t dev, grub_uint8_t dl)
+{
+ grub_uint32_t part_start = 0, heads = 0, sectors = 0;
+ if (dev && dev->disk)
+ {
+ part_start = grub_partition_get_start (dev->disk->partition);
+ if (dev->disk->data)
+ {
+ heads = ((struct grub_biosdisk_data *)(dev->disk->data))->heads;
+ sectors = ((struct grub_biosdisk_data *)(dev->disk->data))->sectors;
+ }
+ }
+ if (grub_memcmp ((char *) &((struct grub_ntfs_bpb *) bs)->oem_name,
+ "NTFS", 4) == 0)
+ {
+ struct grub_ntfs_bpb *bpb = (struct grub_ntfs_bpb *) bs;
+ bpb->num_hidden_sectors = grub_cpu_to_le32 (part_start);
+ bpb->bios_drive = dl;
+ return;
+ }
+
+ do
+ {
+ struct grub_fat_bpb *bpb = (struct grub_fat_bpb *) bs;
+ if (grub_strncmp((const char *) bpb->version_specific.fat12_or_fat16.fstype, "FAT12", 5)
+ && grub_strncmp((const char *) bpb->version_specific.fat12_or_fat16.fstype, "FAT16", 5)
+ && grub_strncmp((const char *) bpb->version_specific.fat32.fstype, "FAT32", 5))
+ break;
+
+ if (grub_le_to_cpu16 (bpb->bytes_per_sector) < 512
+ || (grub_le_to_cpu16 (bpb->bytes_per_sector)
+ & (grub_le_to_cpu16 (bpb->bytes_per_sector) - 1)))
+ break;
+
+ if (bpb->sectors_per_cluster == 0
+ || (bpb->sectors_per_cluster & (bpb->sectors_per_cluster - 1)))
+ break;
+
+ if (bpb->num_reserved_sectors == 0)
+ break;
+ if (bpb->num_total_sectors_16 == 0 && bpb->num_total_sectors_32 == 0)
+ break;
+
+ if (bpb->num_fats == 0)
+ break;
+
+ if (bpb->sectors_per_fat_16)
+ {
+ bpb->num_hidden_sectors = grub_cpu_to_le32 (part_start);
+ bpb->version_specific.fat12_or_fat16.num_ph_drive = dl;
+ if (sectors)
+ bpb->sectors_per_track = grub_cpu_to_le16 (sectors);
+ if (heads)
+ bpb->num_heads = grub_cpu_to_le16 (heads);
+ return;
+ }
+ if (bpb->version_specific.fat32.sectors_per_fat_32)
+ {
+ bpb->num_hidden_sectors = grub_cpu_to_le32 (part_start);
+ bpb->version_specific.fat32.num_ph_drive = dl;
+ if (sectors)
+ bpb->sectors_per_track = grub_cpu_to_le16 (sectors);
+ if (heads)
+ bpb->num_heads = grub_cpu_to_le16 (heads);
+ return;
+ }
+ break;
+ }
+ while (0);
+}
+
+static void
+grub_chainloader_cmd (const char *filename, grub_chainloader_flags_t flags)
+{
+ grub_file_t file = 0;
+ grub_uint16_t signature;
+ grub_device_t dev;
+ int drive = -1;
+ grub_addr_t part_addr = 0;
+ grub_uint8_t *bs, *ptable;
+
+ rel = grub_relocator_new ();
+ if (!rel)
+ goto fail;
+
+ grub_dl_ref (my_mod);
+
+ file = grub_file_open (filename, GRUB_FILE_TYPE_PCCHAINLOADER
+ | GRUB_FILE_TYPE_NO_DECOMPRESS);
+ if (! file)
+ goto fail;
+
+ {
+ grub_relocator_chunk_t ch;
+ grub_err_t err;
+
+ err = grub_relocator_alloc_chunk_addr (rel, &ch, 0x7C00,
+ GRUB_DISK_SECTOR_SIZE);
+ if (err)
+ goto fail;
+ bs = get_virtual_current_address (ch);
+ err = grub_relocator_alloc_chunk_addr (rel, &ch,
+ GRUB_MEMORY_MACHINE_PART_TABLE_ADDR,
+ 64);
+ if (err)
+ goto fail;
+ ptable = get_virtual_current_address (ch);
+ }
+
+ /* Read the first block. */
+ if (grub_file_read (file, bs, GRUB_DISK_SECTOR_SIZE)
+ != GRUB_DISK_SECTOR_SIZE)
+ {
+ if (!grub_errno)
+ grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
+ filename);
+
+ goto fail;
+ }
+
+ /* Check the signature. */
+ signature = *((grub_uint16_t *) (bs + GRUB_DISK_SECTOR_SIZE - 2));
+ if (signature != grub_le_to_cpu16 (0xaa55)
+ && ! (flags & GRUB_CHAINLOADER_FORCE))
+ {
+ grub_error (GRUB_ERR_BAD_OS, "invalid signature");
+ goto fail;
+ }
+
+ grub_file_close (file);
+
+ /* Obtain the partition table from the root device. */
+ drive = grub_get_root_biosnumber ();
+ dev = grub_device_open (0);
+ if (dev && dev->disk && dev->disk->partition)
+ {
+ grub_disk_t disk = dev->disk;
+
+ if (disk)
+ {
+ grub_partition_t p = disk->partition;
+
+ if (p && grub_strcmp (p->partmap->name, "msdos") == 0)
+ {
+ disk->partition = p->parent;
+ grub_disk_read (disk, p->offset, 446, 64, ptable);
+ part_addr = (GRUB_MEMORY_MACHINE_PART_TABLE_ADDR
+ + (p->index << 4));
+ disk->partition = p;
+ }
+ }
+ }
+
+ if (flags & GRUB_CHAINLOADER_BPB)
+ grub_chainloader_patch_bpb ((void *) 0x7C00, dev, drive);
+
+ if (dev)
+ grub_device_close (dev);
+
+ /* Ignore errors. Perhaps it's not fatal. */
+ grub_errno = GRUB_ERR_NONE;
+
+ boot_drive = drive;
+ boot_part_addr = part_addr;
+
+ grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 1);
+ return;
+
+ fail:
+
+ if (file)
+ grub_file_close (file);
+
+ grub_dl_unref (my_mod);
+}
+
+static grub_err_t
+grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)),
+ int argc, char *argv[])
+{
+ grub_chainloader_flags_t flags = 0;
+
+ while (argc > 0)
+ {
+ if (grub_strcmp (argv[0], "--force") == 0)
+ {
+ flags |= GRUB_CHAINLOADER_FORCE;
+ argc--;
+ argv++;
+ continue;
+ }
+ if (grub_strcmp (argv[0], "--bpb") == 0)
+ {
+ flags |= GRUB_CHAINLOADER_BPB;
+ argc--;
+ argv++;
+ continue;
+ }
+ break;
+ }
+
+ if (argc == 0)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
+
+ grub_chainloader_cmd (argv[0], flags);
+
+ return grub_errno;
+}
+
+static grub_command_t cmd;
+
+GRUB_MOD_INIT(chainloader)
+{
+ cmd = grub_register_command ("chainloader", grub_cmd_chainloader,
+ N_("[--force|--bpb] FILE"),
+ N_("Load another boot loader."));
+ my_mod = mod;
+}
+
+GRUB_MOD_FINI(chainloader)
+{
+ grub_unregister_command (cmd);
+}