/* chainloader.c - boot another boot loader */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 2002,2004,2006 Free Software Foundation, Inc. * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* TODO: support load options. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static grub_dl_t my_mod; static grub_efi_physical_address_t address; static grub_efi_uintn_t pages; static grub_efi_device_path_t *file_path; static grub_efi_handle_t image_handle; static grub_err_t grub_chainloader_unload (void) { grub_efi_boot_services_t *b; b = grub_efi_system_table->boot_services; b->unload_image (image_handle); b->free_pages (address, pages); grub_free (file_path); grub_dl_unref (my_mod); return GRUB_ERR_NONE; } static grub_err_t grub_chainloader_boot (void) { grub_efi_boot_services_t *b; grub_efi_status_t status; grub_efi_uintn_t exit_data_size; grub_efi_char16_t *exit_data; b = grub_efi_system_table->boot_services; status = b->start_image (image_handle, &exit_data_size, &exit_data); if (status != GRUB_EFI_SUCCESS) { if (exit_data) { char *buf; buf = grub_malloc (exit_data_size * 4 + 1); if (buf) { *grub_utf16_to_utf8 ((grub_uint8_t *) buf, exit_data, exit_data_size) = 0; grub_error (GRUB_ERR_BAD_OS, buf); grub_free (buf); } else grub_error (GRUB_ERR_BAD_OS, "unknown error"); } } if (exit_data) b->free_pool (exit_data); grub_chainloader_unload (); return grub_errno; } static void copy_file_path (grub_efi_file_path_device_path_t *fp, const char *str, grub_efi_uint16_t len) { grub_efi_char16_t *p; grub_efi_uint16_t size; fp->header.type = GRUB_EFI_MEDIA_DEVICE_PATH_TYPE; fp->header.subtype = GRUB_EFI_FILE_PATH_DEVICE_PATH_SUBTYPE; size = len * sizeof (grub_efi_char16_t) + sizeof (*fp); fp->header.length[0] = (grub_efi_uint8_t) (size & 0xff); fp->header.length[1] = (grub_efi_uint8_t) (size >> 8); for (p = fp->path_name; len > 0; len--, p++, str++) { /* FIXME: this assumes that the path is in ASCII. */ *p = (grub_efi_char16_t) (*str == '/' ? '\\' : *str); } } static grub_efi_device_path_t * make_file_path (grub_efi_device_path_t *dp, const char *filename) { char *dir_start; char *dir_end; grub_size_t size; grub_efi_device_path_t *d; dir_start = grub_strchr (filename, ')'); if (! dir_start) dir_start = (char *) filename; else dir_start++; dir_end = grub_strrchr (dir_start, '/'); if (! dir_end) { grub_error (GRUB_ERR_BAD_FILENAME, "invalid EFI file path"); return 0; } size = 0; d = dp; while (1) { size += GRUB_EFI_DEVICE_PATH_LENGTH (d); if ((GRUB_EFI_END_ENTIRE_DEVICE_PATH (d))) break; d = GRUB_EFI_NEXT_DEVICE_PATH (d); } file_path = grub_malloc (size + ((grub_strlen (dir_start) + 1) * sizeof (grub_efi_char16_t)) + sizeof (grub_efi_file_path_device_path_t) * 2); if (! file_path) return 0; grub_memcpy (file_path, dp, size); /* Fill the file path for the directory. */ d = (grub_efi_device_path_t *) ((char *) file_path + ((char *) d - (char *) dp)); grub_efi_print_device_path (d); copy_file_path ((grub_efi_file_path_device_path_t *) d, dir_start, dir_end - dir_start); /* Fill the file path for the file. */ d = GRUB_EFI_NEXT_DEVICE_PATH (d); copy_file_path ((grub_efi_file_path_device_path_t *) d, dir_end + 1, grub_strlen (dir_end + 1)); /* Fill the end of device path nodes. */ d = GRUB_EFI_NEXT_DEVICE_PATH (d); d->type = GRUB_EFI_END_DEVICE_PATH_TYPE; d->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE; d->length[0] = sizeof (*d); d->length[1] = 0; return file_path; } void grub_chainloader_cmd (const char *filename) { grub_file_t file = 0; grub_ssize_t size; grub_efi_status_t status; grub_efi_boot_services_t *b; grub_efi_handle_t dev_handle = 0; grub_device_t dev = 0; grub_efi_device_path_t *dp = 0; grub_efi_loaded_image_t *loaded_image; grub_dl_ref (my_mod); /* Initialize some global variables. */ address = 0; image_handle = 0; file_path = 0; b = grub_efi_system_table->boot_services; file = grub_file_open (filename); if (! file) goto fail; /* Get the root device's device path. */ dev = grub_device_open (0); if (! dev) goto fail; if (dev->disk) { dev_handle = grub_efidisk_get_device_handle (dev->disk); if (dev_handle) dp = grub_efi_get_device_path (dev_handle); } if (! dev->disk || ! dev_handle || ! dp) { grub_error (GRUB_ERR_BAD_DEVICE, "not a valid root device"); goto fail; } file_path = make_file_path (dp, filename); if (! file_path) goto fail; grub_printf ("file path: "); grub_efi_print_device_path (file_path); size = grub_file_size (file); pages = (((grub_efi_uintn_t) size + ((1 << 12) - 1)) >> 12); status = b->allocate_pages (GRUB_EFI_ALLOCATE_ANY_PAGES, GRUB_EFI_LOADER_CODE, pages, &address); if (status != GRUB_EFI_SUCCESS) { grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate %u pages", pages); goto fail; } if (grub_file_read (file, (void *) ((grub_addr_t) address), size) != size) { if (grub_errno == GRUB_ERR_NONE) grub_error (GRUB_ERR_BAD_OS, "too small"); goto fail; } status = b->load_image (0, grub_efi_image_handle, file_path, (void *) ((grub_addr_t) address), size, &image_handle); if (status != GRUB_EFI_SUCCESS) { if (status == GRUB_EFI_OUT_OF_RESOURCES) grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of resources"); else grub_error (GRUB_ERR_BAD_OS, "cannot load image"); goto fail; } /* LoadImage does not set a device handler when the image is loaded from memory, so it is necessary to set it explicitly here. This is a mess. */ loaded_image = grub_efi_get_loaded_image (image_handle); if (! loaded_image) { grub_error (GRUB_ERR_BAD_OS, "no loaded image available"); goto fail; } loaded_image->device_handle = dev_handle; grub_file_close (file); grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 0); return; fail: if (dev) grub_device_close (dev); if (file) grub_file_close (file); if (file_path) grub_free (file_path); if (address) b->free_pages (address, pages); grub_dl_unref (my_mod); } static void grub_rescue_cmd_chainloader (int argc, char *argv[]) { if (argc == 0) grub_error (GRUB_ERR_BAD_ARGUMENT, "no file specified"); else grub_chainloader_cmd (argv[0]); } static const char loader_name[] = "chainloader"; GRUB_MOD_INIT(chainloader) { grub_rescue_register_command (loader_name, grub_rescue_cmd_chainloader, "load another boot loader"); my_mod = mod; } GRUB_MOD_FINI(chainloader) { grub_rescue_unregister_command (loader_name); }