/* chainloader.c - boot another boot loader */ /* * VasEBoot -- GRand Unified Bootloader * Copyright (C) 2002,2004,2006,2007,2008 Free Software Foundation, Inc. * * VasEBoot 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. * * VasEBoot 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 VasEBoot. If not, see . */ /* TODO: support load options. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined (__i386__) || defined (__x86_64__) #include #include #endif VasEBoot_MOD_LICENSE ("GPLv3+"); static VasEBoot_dl_t my_mod; static VasEBoot_efi_physical_address_t address; static VasEBoot_efi_uintn_t pages; static VasEBoot_efi_device_path_t *file_path; static VasEBoot_efi_handle_t image_handle; static VasEBoot_efi_char16_t *cmdline; static VasEBoot_err_t VasEBoot_chainloader_unload (void) { VasEBoot_efi_boot_services_t *b; b = VasEBoot_efi_system_table->boot_services; efi_call_1 (b->unload_image, image_handle); efi_call_2 (b->free_pages, address, pages); VasEBoot_free (file_path); VasEBoot_free (cmdline); cmdline = 0; file_path = 0; VasEBoot_dl_unref (my_mod); return VasEBoot_ERR_NONE; } static VasEBoot_err_t VasEBoot_chainloader_boot (void) { VasEBoot_efi_boot_services_t *b; VasEBoot_efi_status_t status; VasEBoot_efi_uintn_t exit_data_size; VasEBoot_efi_char16_t *exit_data = NULL; b = VasEBoot_efi_system_table->boot_services; status = efi_call_3 (b->start_image, image_handle, &exit_data_size, &exit_data); if (status != VasEBoot_EFI_SUCCESS) { if (exit_data) { char *buf; buf = VasEBoot_malloc (exit_data_size * 4 + 1); if (buf) { *VasEBoot_utf16_to_utf8 ((VasEBoot_uint8_t *) buf, exit_data, exit_data_size) = 0; VasEBoot_error (VasEBoot_ERR_BAD_OS, buf); VasEBoot_free (buf); } } else VasEBoot_error (VasEBoot_ERR_BAD_OS, "unknown error"); } if (exit_data) efi_call_1 (b->free_pool, exit_data); VasEBoot_loader_unset (); return VasEBoot_errno; } static void copy_file_path (VasEBoot_efi_file_path_device_path_t *fp, const char *str, VasEBoot_efi_uint16_t len) { VasEBoot_efi_char16_t *p; VasEBoot_efi_uint16_t size; fp->header.type = VasEBoot_EFI_MEDIA_DEVICE_PATH_TYPE; fp->header.subtype = VasEBoot_EFI_FILE_PATH_DEVICE_PATH_SUBTYPE; size = VasEBoot_utf8_to_utf16 (fp->path_name, len * VasEBoot_MAX_UTF16_PER_UTF8, (const VasEBoot_uint8_t *) str, len, 0); for (p = fp->path_name; p < fp->path_name + size; p++) if (*p == '/') *p = '\\'; /* File Path is NULL terminated */ fp->path_name[size++] = '\0'; fp->header.length = size * sizeof (VasEBoot_efi_char16_t) + sizeof (*fp); } static VasEBoot_efi_device_path_t * make_file_path (VasEBoot_efi_device_path_t *dp, const char *filename) { char *dir_start; char *dir_end; VasEBoot_size_t size; VasEBoot_efi_device_path_t *d; dir_start = VasEBoot_strchr (filename, ')'); if (! dir_start) dir_start = (char *) filename; else dir_start++; dir_end = VasEBoot_strrchr (dir_start, '/'); if (! dir_end) { VasEBoot_error (VasEBoot_ERR_BAD_FILENAME, "invalid EFI file path"); return 0; } size = 0; d = dp; while (1) { size += VasEBoot_EFI_DEVICE_PATH_LENGTH (d); if ((VasEBoot_EFI_END_ENTIRE_DEVICE_PATH (d))) break; d = VasEBoot_EFI_NEXT_DEVICE_PATH (d); } /* File Path is NULL terminated. Allocate space for 2 extra characters */ /* FIXME why we split path in two components? */ file_path = VasEBoot_malloc (size + ((VasEBoot_strlen (dir_start) + 2) * VasEBoot_MAX_UTF16_PER_UTF8 * sizeof (VasEBoot_efi_char16_t)) + sizeof (VasEBoot_efi_file_path_device_path_t) * 2); if (! file_path) return 0; VasEBoot_memcpy (file_path, dp, size); /* Fill the file path for the directory. */ d = (VasEBoot_efi_device_path_t *) ((char *) file_path + ((char *) d - (char *) dp)); VasEBoot_efi_print_device_path (d); copy_file_path ((VasEBoot_efi_file_path_device_path_t *) d, dir_start, dir_end - dir_start); /* Fill the file path for the file. */ d = VasEBoot_EFI_NEXT_DEVICE_PATH (d); copy_file_path ((VasEBoot_efi_file_path_device_path_t *) d, dir_end + 1, VasEBoot_strlen (dir_end + 1)); /* Fill the end of device path nodes. */ d = VasEBoot_EFI_NEXT_DEVICE_PATH (d); d->type = VasEBoot_EFI_END_DEVICE_PATH_TYPE; d->subtype = VasEBoot_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE; d->length = sizeof (*d); return file_path; } static VasEBoot_err_t VasEBoot_cmd_chainloader (VasEBoot_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { VasEBoot_file_t file = 0; VasEBoot_ssize_t size; VasEBoot_efi_status_t status; VasEBoot_efi_boot_services_t *b; VasEBoot_device_t dev = 0; VasEBoot_efi_device_path_t *dp = 0; VasEBoot_efi_loaded_image_t *loaded_image; char *filename; void *boot_image = 0; VasEBoot_efi_handle_t dev_handle = 0; if (argc == 0) return VasEBoot_error (VasEBoot_ERR_BAD_ARGUMENT, N_("filename expected")); filename = argv[0]; VasEBoot_dl_ref (my_mod); /* Initialize some global variables. */ address = 0; image_handle = 0; file_path = 0; b = VasEBoot_efi_system_table->boot_services; file = VasEBoot_file_open (filename); if (! file) goto fail; /* Get the root device's device path. */ dev = VasEBoot_device_open (0); if (! dev) goto fail; if (dev->disk) dev_handle = VasEBoot_efidisk_get_device_handle (dev->disk); else if (dev->net && dev->net->server) { VasEBoot_net_network_level_address_t addr; struct VasEBoot_net_network_level_interface *inf; VasEBoot_net_network_level_address_t gateway; VasEBoot_err_t err; err = VasEBoot_net_resolve_address (dev->net->server, &addr); if (err) goto fail; err = VasEBoot_net_route_address (addr, &gateway, &inf); if (err) goto fail; dev_handle = VasEBoot_efinet_get_device_handle (inf->card); } if (dev_handle) dp = VasEBoot_efi_get_device_path (dev_handle); if (! dp) { VasEBoot_error (VasEBoot_ERR_BAD_DEVICE, "not a valid root device"); goto fail; } file_path = make_file_path (dp, filename); if (! file_path) goto fail; VasEBoot_printf ("file path: "); VasEBoot_efi_print_device_path (file_path); size = VasEBoot_file_size (file); if (!size) { VasEBoot_error (VasEBoot_ERR_BAD_OS, N_("premature end of file %s"), filename); goto fail; } pages = (((VasEBoot_efi_uintn_t) size + ((1 << 12) - 1)) >> 12); status = efi_call_4 (b->allocate_pages, VasEBoot_EFI_ALLOCATE_ANY_PAGES, VasEBoot_EFI_LOADER_CODE, pages, &address); if (status != VasEBoot_EFI_SUCCESS) { VasEBoot_dprintf ("chain", "Failed to allocate %u pages\n", (unsigned int) pages); VasEBoot_error (VasEBoot_ERR_OUT_OF_MEMORY, N_("out of memory")); goto fail; } boot_image = (void *) ((VasEBoot_addr_t) address); if (VasEBoot_file_read (file, boot_image, size) != size) { if (VasEBoot_errno == VasEBoot_ERR_NONE) VasEBoot_error (VasEBoot_ERR_BAD_OS, N_("premature end of file %s"), filename); goto fail; } #if defined (__i386__) || defined (__x86_64__) if (size >= (VasEBoot_ssize_t) sizeof (struct VasEBoot_macho_fat_header)) { struct VasEBoot_macho_fat_header *head = boot_image; if (head->magic == VasEBoot_cpu_to_le32_compile_time (VasEBoot_MACHO_FAT_EFI_MAGIC)) { VasEBoot_uint32_t i; struct VasEBoot_macho_fat_arch *archs = (struct VasEBoot_macho_fat_arch *) (head + 1); for (i = 0; i < VasEBoot_cpu_to_le32 (head->nfat_arch); i++) { if (VasEBoot_MACHO_CPUTYPE_IS_HOST_CURRENT (archs[i].cputype)) break; } if (i == VasEBoot_cpu_to_le32 (head->nfat_arch)) { VasEBoot_error (VasEBoot_ERR_BAD_OS, "no compatible arch found"); goto fail; } if (VasEBoot_cpu_to_le32 (archs[i].offset) > ~VasEBoot_cpu_to_le32 (archs[i].size) || VasEBoot_cpu_to_le32 (archs[i].offset) + VasEBoot_cpu_to_le32 (archs[i].size) > (VasEBoot_size_t) size) { VasEBoot_error (VasEBoot_ERR_BAD_OS, N_("premature end of file %s"), filename); goto fail; } boot_image = (char *) boot_image + VasEBoot_cpu_to_le32 (archs[i].offset); size = VasEBoot_cpu_to_le32 (archs[i].size); } } #endif status = efi_call_6 (b->load_image, 0, VasEBoot_efi_image_handle, file_path, boot_image, size, &image_handle); if (status != VasEBoot_EFI_SUCCESS) { if (status == VasEBoot_EFI_OUT_OF_RESOURCES) VasEBoot_error (VasEBoot_ERR_OUT_OF_MEMORY, "out of resources"); else VasEBoot_error (VasEBoot_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 = VasEBoot_efi_get_loaded_image (image_handle); if (! loaded_image) { VasEBoot_error (VasEBoot_ERR_BAD_OS, "no loaded image available"); goto fail; } loaded_image->device_handle = dev_handle; if (argc > 1) { int i, len; VasEBoot_efi_char16_t *p16; for (i = 1, len = 0; i < argc; i++) len += VasEBoot_strlen (argv[i]) + 1; len *= sizeof (VasEBoot_efi_char16_t); cmdline = p16 = VasEBoot_malloc (len); if (! cmdline) goto fail; for (i = 1; i < argc; i++) { char *p8; p8 = argv[i]; while (*p8) *(p16++) = *(p8++); *(p16++) = ' '; } *(--p16) = 0; loaded_image->load_options = cmdline; loaded_image->load_options_size = len; } VasEBoot_file_close (file); VasEBoot_device_close (dev); VasEBoot_loader_set (VasEBoot_chainloader_boot, VasEBoot_chainloader_unload, 0); return 0; fail: if (dev) VasEBoot_device_close (dev); if (file) VasEBoot_file_close (file); VasEBoot_free (file_path); if (address) efi_call_2 (b->free_pages, address, pages); VasEBoot_dl_unref (my_mod); return VasEBoot_errno; } static VasEBoot_command_t cmd; VasEBoot_MOD_INIT(chainloader) { cmd = VasEBoot_register_command ("chainloader", VasEBoot_cmd_chainloader, 0, N_("Load another boot loader.")); my_mod = mod; } VasEBoot_MOD_FINI(chainloader) { VasEBoot_unregister_command (cmd); }