/* chainloader.c - boot another boot loader */ /* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2002,2004,2006,2007,2008 Free Software Foundation, Inc. * * VAS_EBOOT 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. * * VAS_EBOOT 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 VAS_EBOOT. If not, see . */ /* TODO: support load options. */ #include #include #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 VAS_EBOOT_MOD_LICENSE ("GPLv3+"); static VasEBoot_dl_t my_mod; static VasEBoot_err_t VasEBoot_chainloader_unload (void *context) { VasEBoot_efi_handle_t image_handle = (VasEBoot_efi_handle_t) context; VasEBoot_efi_loaded_image_t *loaded_image; loaded_image = VasEBoot_efi_get_loaded_image (image_handle); if (loaded_image != NULL) VasEBoot_free (loaded_image->load_options); VasEBoot_efi_unload_image (image_handle); VasEBoot_dl_unref (my_mod); return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t VasEBoot_chainloader_boot (void *context) { VasEBoot_efi_handle_t image_handle = (VasEBoot_efi_handle_t) context; 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 = VasEBoot_efi_start_image (image_handle, &exit_data_size, &exit_data); if (status != VAS_EBOOT_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 (VAS_EBOOT_ERR_BAD_OS, "%s", buf); VasEBoot_free (buf); } } else VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, "unknown error"); } if (exit_data) b->free_pool (exit_data); VasEBoot_loader_unset (); return VasEBoot_errno; } static VasEBoot_err_t copy_file_path (VasEBoot_efi_file_path_device_path_t *fp, const char *str, VasEBoot_efi_uint16_t len) { VasEBoot_efi_char16_t *p, *path_name; VasEBoot_efi_uint16_t size; fp->header.type = VAS_EBOOT_EFI_MEDIA_DEVICE_PATH_TYPE; fp->header.subtype = VAS_EBOOT_EFI_FILE_PATH_DEVICE_PATH_SUBTYPE; path_name = VasEBoot_calloc (len, VAS_EBOOT_MAX_UTF16_PER_UTF8 * sizeof (*path_name)); if (!path_name) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_MEMORY, "failed to allocate path buffer"); size = VasEBoot_utf8_to_utf16 (path_name, len * VAS_EBOOT_MAX_UTF16_PER_UTF8, (const VasEBoot_uint8_t *) str, len, 0); for (p = path_name; p < path_name + size; p++) if (*p == '/') *p = '\\'; VasEBoot_memcpy (fp->path_name, path_name, size * sizeof (*fp->path_name)); /* File Path is NULL terminated */ fp->path_name[size++] = '\0'; fp->header.length = size * sizeof (VasEBoot_efi_char16_t) + sizeof (*fp); VasEBoot_free (path_name); return VAS_EBOOT_ERR_NONE; } 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, *file_path; 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 (VAS_EBOOT_ERR_BAD_FILENAME, "invalid EFI file path"); return 0; } size = 0; d = dp; while (d) { VasEBoot_size_t len = VAS_EBOOT_EFI_DEVICE_PATH_LENGTH (d); if (len < 4) { VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "malformed EFI Device Path node has length=%" PRIuVAS_EBOOT_SIZE, len); return NULL; } size += len; if ((VAS_EBOOT_EFI_END_ENTIRE_DEVICE_PATH (d))) break; d = VAS_EBOOT_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) * VAS_EBOOT_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)); if (copy_file_path ((VasEBoot_efi_file_path_device_path_t *) d, dir_start, dir_end - dir_start) != VAS_EBOOT_ERR_NONE) { fail: VasEBoot_free (file_path); return 0; } /* Fill the file path for the file. */ d = VAS_EBOOT_EFI_NEXT_DEVICE_PATH (d); if (copy_file_path ((VasEBoot_efi_file_path_device_path_t *) d, dir_end + 1, VasEBoot_strlen (dir_end + 1)) != VAS_EBOOT_ERR_NONE) goto fail; /* Fill the end of device path nodes. */ d = VAS_EBOOT_EFI_NEXT_DEVICE_PATH (d); d->type = VAS_EBOOT_EFI_END_DEVICE_PATH_TYPE; d->subtype = VAS_EBOOT_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 = NULL, *file_path = NULL; VasEBoot_efi_loaded_image_t *loaded_image; char *filename; void *boot_image = 0; VasEBoot_efi_handle_t dev_handle = 0; VasEBoot_efi_physical_address_t address = 0; VasEBoot_efi_uintn_t pages = 0; VasEBoot_efi_char16_t *cmdline = NULL; VasEBoot_efi_handle_t image_handle = NULL; if (argc == 0) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("filename expected")); filename = argv[0]; VasEBoot_dl_ref (my_mod); b = VasEBoot_efi_system_table->boot_services; file = VasEBoot_file_open (filename, VAS_EBOOT_FILE_TYPE_EFI_CHAINLOADED_IMAGE); if (! file) goto fail; /* Get the root device's device path. */ dev = VasEBoot_device_open (0); if (dev == NULL) ; else 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 != NULL) { file_path = make_file_path (dp, filename); if (file_path == NULL) goto fail; } size = VasEBoot_file_size (file); if (!size) { VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("premature end of file %s"), filename); goto fail; } pages = (VasEBoot_efi_uintn_t) VAS_EBOOT_EFI_BYTES_TO_PAGES (size); status = b->allocate_pages (VAS_EBOOT_EFI_ALLOCATE_ANY_PAGES, VAS_EBOOT_EFI_LOADER_CODE, pages, &address); if (status != VAS_EBOOT_EFI_SUCCESS) { VasEBoot_dprintf ("chain", "Failed to allocate %u pages\n", (unsigned int) pages); VasEBoot_error (VAS_EBOOT_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 == VAS_EBOOT_ERR_NONE) VasEBoot_error (VAS_EBOOT_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 (VAS_EBOOT_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 (VAS_EBOOT_MACHO_CPUTYPE_IS_HOST_CURRENT (archs[i].cputype)) break; } if (i == VasEBoot_cpu_to_le32 (head->nfat_arch)) { VasEBoot_error (VAS_EBOOT_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 (VAS_EBOOT_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 image_handle = VasEBoot_efi_get_last_verified_image_handle (); if (image_handle == NULL) { status = VasEBoot_efi_load_image (0, VasEBoot_efi_image_handle, file_path, boot_image, size, &image_handle); if (status != VAS_EBOOT_EFI_SUCCESS) { if (status == VAS_EBOOT_EFI_OUT_OF_RESOURCES) VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_MEMORY, "out of resources"); else VasEBoot_error (VAS_EBOOT_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 (VAS_EBOOT_ERR_BAD_OS, "no loaded image available"); goto fail; } loaded_image->device_handle = dev_handle; /* Build load options with arguments from chainloader command line. */ 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); /* We're finished with the source image buffer and file path now. */ b->free_pages (address, pages); VasEBoot_free (file_path); VasEBoot_loader_set_ex (VasEBoot_chainloader_boot, VasEBoot_chainloader_unload, image_handle, 0); return 0; fail: if (dev) VasEBoot_device_close (dev); if (file) VasEBoot_file_close (file); VasEBoot_free (cmdline); VasEBoot_free (file_path); if (address) b->free_pages (address, pages); if (image_handle != NULL) VasEBoot_efi_unload_image (image_handle); VasEBoot_dl_unref (my_mod); return VasEBoot_errno; } static VasEBoot_command_t cmd; VAS_EBOOT_MOD_INIT(chainloader) { cmd = VasEBoot_register_command ("chainloader", VasEBoot_cmd_chainloader, 0, N_("Load another boot loader.")); my_mod = mod; } VAS_EBOOT_MOD_FINI(chainloader) { VasEBoot_unregister_command (cmd); }