| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2006,2007,2008 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/disk.h> |
| #include <grub/partition.h> |
| #include <grub/mm.h> |
| #include <grub/types.h> |
| #include <grub/misc.h> |
| #include <grub/err.h> |
| #include <grub/term.h> |
| #include <grub/efi/api.h> |
| #include <grub/efi/efi.h> |
| #include <grub/efi/disk.h> |
| #include <grub/env.h> |
| |
| struct grub_efidisk_data |
| { |
| grub_efi_handle_t handle; |
| grub_efi_device_path_t *device_path; |
| grub_efi_device_path_t *last_device_path; |
| grub_efi_block_io_t *block_io; |
| struct grub_efidisk_data *next; |
| }; |
| |
| /* GUID. */ |
| static grub_efi_guid_t block_io_guid = GRUB_EFI_BLOCK_IO_GUID; |
| |
| static struct grub_efidisk_data *fd_devices; |
| static struct grub_efidisk_data *hd_devices; |
| static struct grub_efidisk_data *cd_devices; |
| |
| static struct grub_efidisk_data * |
| make_devices (void) |
| { |
| grub_efi_uintn_t num_handles; |
| grub_efi_handle_t *handles; |
| grub_efi_handle_t *handle; |
| struct grub_efidisk_data *devices = 0; |
| |
| /* Find handles which support the disk io interface. */ |
| handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &block_io_guid, |
| 0, &num_handles); |
| if (! handles) |
| return 0; |
| |
| /* Make a linked list of devices. */ |
| for (handle = handles; num_handles--; handle++) |
| { |
| grub_efi_device_path_t *dp; |
| grub_efi_device_path_t *ldp; |
| struct grub_efidisk_data *d; |
| grub_efi_block_io_t *bio; |
| |
| dp = grub_efi_get_device_path (*handle); |
| if (! dp) |
| continue; |
| |
| ldp = grub_efi_find_last_device_path (dp); |
| if (! ldp) |
| /* This is empty. Why? */ |
| continue; |
| |
| bio = grub_efi_open_protocol (*handle, &block_io_guid, |
| GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); |
| if (! bio) |
| /* This should not happen... Why? */ |
| continue; |
| |
| /* iPXE adds stub Block IO protocol to loaded image device handle. It is |
| completely non-functional and simply returns an error for every method. |
| So attempt to detect and skip it. Magic number is literal "iPXE" and |
| check block size as well */ |
| /* FIXME: shoud we close it? We do not do it elsewhere */ |
| if (bio->media && bio->media->media_id == 0x69505845U && |
| bio->media->block_size == 1) |
| continue; |
| |
| d = grub_malloc (sizeof (*d)); |
| if (! d) |
| { |
| /* Uggh. */ |
| grub_free (handles); |
| while (devices) |
| { |
| d = devices->next; |
| grub_free (devices); |
| devices = d; |
| } |
| return 0; |
| } |
| |
| d->handle = *handle; |
| d->device_path = dp; |
| d->last_device_path = ldp; |
| d->block_io = bio; |
| d->next = devices; |
| devices = d; |
| } |
| |
| grub_free (handles); |
| |
| return devices; |
| } |
| |
| /* Find the parent device. */ |
| static struct grub_efidisk_data * |
| find_parent_device (struct grub_efidisk_data *devices, |
| struct grub_efidisk_data *d) |
| { |
| grub_efi_device_path_t *dp, *ldp; |
| struct grub_efidisk_data *parent; |
| |
| dp = grub_efi_duplicate_device_path (d->device_path); |
| if (! dp) |
| return 0; |
| |
| ldp = grub_efi_find_last_device_path (dp); |
| ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE; |
| ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE; |
| ldp->length = sizeof (*ldp); |
| |
| for (parent = devices; parent; parent = parent->next) |
| { |
| /* Ignore itself. */ |
| if (parent == d) |
| continue; |
| |
| if (grub_efi_compare_device_paths (parent->device_path, dp) == 0) |
| break; |
| } |
| |
| grub_free (dp); |
| return parent; |
| } |
| |
| static int |
| is_child (struct grub_efidisk_data *child, |
| struct grub_efidisk_data *parent) |
| { |
| grub_efi_device_path_t *dp, *ldp; |
| int ret; |
| |
| dp = grub_efi_duplicate_device_path (child->device_path); |
| if (! dp) |
| return 0; |
| |
| ldp = grub_efi_find_last_device_path (dp); |
| ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE; |
| ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE; |
| ldp->length = sizeof (*ldp); |
| |
| ret = (grub_efi_compare_device_paths (dp, parent->device_path) == 0); |
| grub_free (dp); |
| return ret; |
| } |
| |
| #define FOR_CHILDREN(p, dev) for (p = dev; p; p = p->next) if (is_child (p, d)) |
| |
| /* Add a device into a list of devices in an ascending order. */ |
| static void |
| add_device (struct grub_efidisk_data **devices, struct grub_efidisk_data *d) |
| { |
| struct grub_efidisk_data **p; |
| struct grub_efidisk_data *n; |
| |
| for (p = devices; *p; p = &((*p)->next)) |
| { |
| int ret; |
| |
| ret = grub_efi_compare_device_paths (grub_efi_find_last_device_path ((*p)->device_path), |
| grub_efi_find_last_device_path (d->device_path)); |
| if (ret == 0) |
| ret = grub_efi_compare_device_paths ((*p)->device_path, |
| d->device_path); |
| if (ret == 0) |
| return; |
| else if (ret > 0) |
| break; |
| } |
| |
| n = grub_malloc (sizeof (*n)); |
| if (! n) |
| return; |
| |
| grub_memcpy (n, d, sizeof (*n)); |
| n->next = (*p); |
| (*p) = n; |
| } |
| |
| /* Name the devices. */ |
| static void |
| name_devices (struct grub_efidisk_data *devices) |
| { |
| struct grub_efidisk_data *d; |
| |
| /* First, identify devices by media device paths. */ |
| for (d = devices; d; d = d->next) |
| { |
| grub_efi_device_path_t *dp; |
| |
| dp = d->last_device_path; |
| if (! dp) |
| continue; |
| |
| if (GRUB_EFI_DEVICE_PATH_TYPE (dp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE) |
| { |
| int is_hard_drive = 0; |
| |
| switch (GRUB_EFI_DEVICE_PATH_SUBTYPE (dp)) |
| { |
| case GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE: |
| is_hard_drive = 1; |
| /* Intentionally fall through. */ |
| case GRUB_EFI_CDROM_DEVICE_PATH_SUBTYPE: |
| { |
| struct grub_efidisk_data *parent, *parent2; |
| |
| parent = find_parent_device (devices, d); |
| if (!parent) |
| { |
| #ifdef DEBUG_NAMES |
| grub_printf ("skipping orphaned partition: "); |
| grub_efi_print_device_path (d->device_path); |
| #endif |
| break; |
| } |
| parent2 = find_parent_device (devices, parent); |
| if (parent2) |
| { |
| #ifdef DEBUG_NAMES |
| grub_printf ("skipping subpartition: "); |
| grub_efi_print_device_path (d->device_path); |
| #endif |
| /* Mark itself as used. */ |
| d->last_device_path = 0; |
| break; |
| } |
| if (!parent->last_device_path) |
| { |
| d->last_device_path = 0; |
| break; |
| } |
| if (is_hard_drive) |
| { |
| #ifdef DEBUG_NAMES |
| grub_printf ("adding a hard drive by a partition: "); |
| grub_efi_print_device_path (parent->device_path); |
| #endif |
| add_device (&hd_devices, parent); |
| } |
| else |
| { |
| #ifdef DEBUG_NAMES |
| grub_printf ("adding a cdrom by a partition: "); |
| grub_efi_print_device_path (parent->device_path); |
| #endif |
| add_device (&cd_devices, parent); |
| } |
| |
| /* Mark the parent as used. */ |
| parent->last_device_path = 0; |
| } |
| /* Mark itself as used. */ |
| d->last_device_path = 0; |
| break; |
| |
| default: |
| #ifdef DEBUG_NAMES |
| grub_printf ("skipping other type: "); |
| grub_efi_print_device_path (d->device_path); |
| #endif |
| /* For now, ignore the others. */ |
| break; |
| } |
| } |
| else |
| { |
| #ifdef DEBUG_NAMES |
| grub_printf ("skipping non-media: "); |
| grub_efi_print_device_path (d->device_path); |
| #endif |
| } |
| } |
| |
| /* Let's see what can be added more. */ |
| for (d = devices; d; d = d->next) |
| { |
| grub_efi_device_path_t *dp; |
| grub_efi_block_io_media_t *m; |
| int is_floppy = 0; |
| |
| dp = d->last_device_path; |
| if (! dp) |
| continue; |
| |
| /* Ghosts proudly presented by Apple. */ |
| if (GRUB_EFI_DEVICE_PATH_TYPE (dp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE |
| && GRUB_EFI_DEVICE_PATH_SUBTYPE (dp) |
| == GRUB_EFI_VENDOR_MEDIA_DEVICE_PATH_SUBTYPE) |
| { |
| grub_efi_vendor_device_path_t *vendor = (grub_efi_vendor_device_path_t *) dp; |
| const struct grub_efi_guid apple = GRUB_EFI_VENDOR_APPLE_GUID; |
| |
| if (vendor->header.length == sizeof (*vendor) |
| && grub_memcmp (&vendor->vendor_guid, &apple, |
| sizeof (vendor->vendor_guid)) == 0 |
| && find_parent_device (devices, d)) |
| continue; |
| } |
| |
| m = d->block_io->media; |
| if (GRUB_EFI_DEVICE_PATH_TYPE (dp) == GRUB_EFI_ACPI_DEVICE_PATH_TYPE |
| && GRUB_EFI_DEVICE_PATH_SUBTYPE (dp) |
| == GRUB_EFI_ACPI_DEVICE_PATH_SUBTYPE) |
| { |
| grub_efi_acpi_device_path_t *acpi |
| = (grub_efi_acpi_device_path_t *) dp; |
| /* Floppy EISA ID. */ |
| if (acpi->hid == 0x60441d0 || acpi->hid == 0x70041d0 |
| || acpi->hid == 0x70141d1) |
| is_floppy = 1; |
| } |
| if (is_floppy) |
| { |
| #ifdef DEBUG_NAMES |
| grub_printf ("adding a floppy: "); |
| grub_efi_print_device_path (d->device_path); |
| #endif |
| add_device (&fd_devices, d); |
| } |
| else if (m->read_only && m->block_size > GRUB_DISK_SECTOR_SIZE) |
| { |
| /* This check is too heuristic, but assume that this is a |
| CDROM drive. */ |
| #ifdef DEBUG_NAMES |
| grub_printf ("adding a cdrom by guessing: "); |
| grub_efi_print_device_path (d->device_path); |
| #endif |
| add_device (&cd_devices, d); |
| } |
| else |
| { |
| /* The default is a hard drive. */ |
| #ifdef DEBUG_NAMES |
| grub_printf ("adding a hard drive by guessing: "); |
| grub_efi_print_device_path (d->device_path); |
| #endif |
| add_device (&hd_devices, d); |
| } |
| } |
| } |
| |
| static void |
| free_devices (struct grub_efidisk_data *devices) |
| { |
| struct grub_efidisk_data *p, *q; |
| |
| for (p = devices; p; p = q) |
| { |
| q = p->next; |
| grub_free (p); |
| } |
| } |
| |
| /* Enumerate all disks to name devices. */ |
| static void |
| enumerate_disks (void) |
| { |
| struct grub_efidisk_data *devices; |
| |
| devices = make_devices (); |
| if (! devices) |
| return; |
| |
| name_devices (devices); |
| free_devices (devices); |
| } |
| |
| static int |
| grub_efidisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, |
| grub_disk_pull_t pull) |
| { |
| struct grub_efidisk_data *d; |
| char buf[16]; |
| int count; |
| |
| switch (pull) |
| { |
| case GRUB_DISK_PULL_NONE: |
| for (d = hd_devices, count = 0; d; d = d->next, count++) |
| { |
| grub_snprintf (buf, sizeof (buf), "hd%d", count); |
| grub_dprintf ("efidisk", "iterating %s\n", buf); |
| if (hook (buf, hook_data)) |
| return 1; |
| } |
| break; |
| case GRUB_DISK_PULL_REMOVABLE: |
| for (d = fd_devices, count = 0; d; d = d->next, count++) |
| { |
| grub_snprintf (buf, sizeof (buf), "fd%d", count); |
| grub_dprintf ("efidisk", "iterating %s\n", buf); |
| if (hook (buf, hook_data)) |
| return 1; |
| } |
| |
| for (d = cd_devices, count = 0; d; d = d->next, count++) |
| { |
| grub_snprintf (buf, sizeof (buf), "cd%d", count); |
| grub_dprintf ("efidisk", "iterating %s\n", buf); |
| if (hook (buf, hook_data)) |
| return 1; |
| } |
| break; |
| default: |
| return 0; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| get_drive_number (const char *name) |
| { |
| unsigned long drive; |
| |
| if ((name[0] != 'f' && name[0] != 'h' && name[0] != 'c') || name[1] != 'd') |
| goto fail; |
| |
| drive = grub_strtoul (name + 2, 0, 10); |
| if (grub_errno != GRUB_ERR_NONE) |
| goto fail; |
| |
| return (int) drive ; |
| |
| fail: |
| grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a efidisk"); |
| return -1; |
| } |
| |
| static struct grub_efidisk_data * |
| get_device (struct grub_efidisk_data *devices, int num) |
| { |
| struct grub_efidisk_data *d; |
| |
| for (d = devices; d && num; d = d->next, num--) |
| ; |
| |
| if (num == 0) |
| return d; |
| |
| return 0; |
| } |
| |
| static grub_err_t |
| grub_efidisk_open (const char *name, struct grub_disk *disk) |
| { |
| int num; |
| struct grub_efidisk_data *d = 0; |
| grub_efi_block_io_media_t *m; |
| |
| grub_dprintf ("efidisk", "opening %s\n", name); |
| |
| num = get_drive_number (name); |
| if (num < 0) |
| return grub_errno; |
| |
| switch (name[0]) |
| { |
| case 'f': |
| d = get_device (fd_devices, num); |
| break; |
| case 'c': |
| d = get_device (cd_devices, num); |
| break; |
| case 'h': |
| d = get_device (hd_devices, num); |
| break; |
| default: |
| /* Never reach here. */ |
| break; |
| } |
| |
| if (! d) |
| return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such device"); |
| |
| disk->id = ((num << GRUB_CHAR_BIT) | name[0]); |
| m = d->block_io->media; |
| /* FIXME: Probably it is better to store the block size in the disk, |
| and total sectors should be replaced with total blocks. */ |
| grub_dprintf ("efidisk", |
| "m = %p, last block = %llx, block size = %x, io align = %x\n", |
| m, (unsigned long long) m->last_block, m->block_size, |
| m->io_align); |
| |
| /* Ensure required buffer alignment is a power of two (or is zero). */ |
| if (m->io_align & (m->io_align - 1)) |
| return grub_error (GRUB_ERR_IO, "invalid buffer alignment %d", m->io_align); |
| |
| disk->total_sectors = m->last_block + 1; |
| /* Don't increase this value due to bug in some EFI. */ |
| disk->max_agglomerate = 0xa0000 >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS); |
| if (m->block_size & (m->block_size - 1) || !m->block_size) |
| return grub_error (GRUB_ERR_IO, "invalid sector size %d", |
| m->block_size); |
| for (disk->log_sector_size = 0; |
| (1U << disk->log_sector_size) < m->block_size; |
| disk->log_sector_size++); |
| disk->data = d; |
| |
| grub_dprintf ("efidisk", "opening %s succeeded\n", name); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static void |
| grub_efidisk_close (struct grub_disk *disk __attribute__ ((unused))) |
| { |
| /* EFI disks do not allocate extra memory, so nothing to do here. */ |
| grub_dprintf ("efidisk", "closing %s\n", disk->name); |
| } |
| |
| static grub_efi_status_t |
| grub_efidisk_readwrite (struct grub_disk *disk, grub_disk_addr_t sector, |
| grub_size_t size, char *buf, int wr) |
| { |
| struct grub_efidisk_data *d; |
| grub_efi_block_io_t *bio; |
| grub_efi_status_t status; |
| grub_size_t io_align, num_bytes; |
| char *aligned_buf; |
| |
| d = disk->data; |
| bio = d->block_io; |
| |
| /* Set alignment to 1 if 0 specified */ |
| io_align = bio->media->io_align ? bio->media->io_align : 1; |
| num_bytes = size << disk->log_sector_size; |
| |
| if ((grub_addr_t) buf & (io_align - 1)) |
| { |
| aligned_buf = grub_memalign (io_align, num_bytes); |
| if (! aligned_buf) |
| return GRUB_EFI_OUT_OF_RESOURCES; |
| if (wr) |
| grub_memcpy (aligned_buf, buf, num_bytes); |
| } |
| else |
| { |
| aligned_buf = buf; |
| } |
| |
| status = efi_call_5 ((wr ? bio->write_blocks : bio->read_blocks), bio, |
| bio->media->media_id, (grub_efi_uint64_t) sector, |
| (grub_efi_uintn_t) num_bytes, aligned_buf); |
| |
| if ((grub_addr_t) buf & (io_align - 1)) |
| { |
| if (!wr) |
| grub_memcpy (buf, aligned_buf, num_bytes); |
| grub_free (aligned_buf); |
| } |
| |
| return status; |
| } |
| |
| static grub_err_t |
| grub_efidisk_read (struct grub_disk *disk, grub_disk_addr_t sector, |
| grub_size_t size, char *buf) |
| { |
| grub_efi_status_t status; |
| |
| grub_dprintf ("efidisk", |
| "reading 0x%lx sectors at the sector 0x%llx from %s\n", |
| (unsigned long) size, (unsigned long long) sector, disk->name); |
| |
| status = grub_efidisk_readwrite (disk, sector, size, buf, 0); |
| |
| if (status == GRUB_EFI_NO_MEDIA) |
| return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("no media in `%s'"), disk->name); |
| else if (status != GRUB_EFI_SUCCESS) |
| return grub_error (GRUB_ERR_READ_ERROR, |
| N_("failure reading sector 0x%llx from `%s'"), |
| (unsigned long long) sector, |
| disk->name); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| grub_efidisk_write (struct grub_disk *disk, grub_disk_addr_t sector, |
| grub_size_t size, const char *buf) |
| { |
| grub_efi_status_t status; |
| |
| grub_dprintf ("efidisk", |
| "writing 0x%lx sectors at the sector 0x%llx to %s\n", |
| (unsigned long) size, (unsigned long long) sector, disk->name); |
| |
| status = grub_efidisk_readwrite (disk, sector, size, (char *) buf, 1); |
| |
| if (status == GRUB_EFI_NO_MEDIA) |
| return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("no media in `%s'"), disk->name); |
| else if (status != GRUB_EFI_SUCCESS) |
| return grub_error (GRUB_ERR_WRITE_ERROR, |
| N_("failure writing sector 0x%llx to `%s'"), |
| (unsigned long long) sector, disk->name); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static struct grub_disk_dev grub_efidisk_dev = |
| { |
| .name = "efidisk", |
| .id = GRUB_DISK_DEVICE_EFIDISK_ID, |
| .iterate = grub_efidisk_iterate, |
| .open = grub_efidisk_open, |
| .close = grub_efidisk_close, |
| .read = grub_efidisk_read, |
| .write = grub_efidisk_write, |
| .next = 0 |
| }; |
| |
| void |
| grub_efidisk_fini (void) |
| { |
| free_devices (fd_devices); |
| free_devices (hd_devices); |
| free_devices (cd_devices); |
| fd_devices = 0; |
| hd_devices = 0; |
| cd_devices = 0; |
| grub_disk_dev_unregister (&grub_efidisk_dev); |
| } |
| |
| void |
| grub_efidisk_init (void) |
| { |
| grub_disk_firmware_fini = grub_efidisk_fini; |
| |
| enumerate_disks (); |
| grub_disk_dev_register (&grub_efidisk_dev); |
| } |
| |
| /* Some utility functions to map GRUB devices with EFI devices. */ |
| grub_efi_handle_t |
| grub_efidisk_get_device_handle (grub_disk_t disk) |
| { |
| struct grub_efidisk_data *d; |
| char type; |
| |
| if (disk->dev->id != GRUB_DISK_DEVICE_EFIDISK_ID) |
| return 0; |
| |
| d = disk->data; |
| type = disk->name[0]; |
| |
| switch (type) |
| { |
| case 'f': |
| /* This is the simplest case. */ |
| return d->handle; |
| |
| case 'c': |
| /* FIXME: probably this is not correct. */ |
| return d->handle; |
| |
| case 'h': |
| /* If this is the whole disk, just return its own data. */ |
| if (! disk->partition) |
| return d->handle; |
| |
| /* Otherwise, we must query the corresponding device to the firmware. */ |
| { |
| struct grub_efidisk_data *devices; |
| grub_efi_handle_t handle = 0; |
| struct grub_efidisk_data *c; |
| |
| devices = make_devices (); |
| FOR_CHILDREN (c, devices) |
| { |
| grub_efi_hard_drive_device_path_t *hd; |
| |
| hd = (grub_efi_hard_drive_device_path_t *) c->last_device_path; |
| |
| if ((GRUB_EFI_DEVICE_PATH_TYPE (c->last_device_path) |
| == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE) |
| && (GRUB_EFI_DEVICE_PATH_SUBTYPE (c->last_device_path) |
| == GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE) |
| && (grub_partition_get_start (disk->partition) |
| == (hd->partition_start << (disk->log_sector_size |
| - GRUB_DISK_SECTOR_BITS))) |
| && (grub_partition_get_len (disk->partition) |
| == (hd->partition_size << (disk->log_sector_size |
| - GRUB_DISK_SECTOR_BITS)))) |
| { |
| handle = c->handle; |
| break; |
| } |
| } |
| |
| free_devices (devices); |
| |
| if (handle != 0) |
| return handle; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| #define NEEDED_BUFLEN sizeof ("XdXXXXXXXXXX") |
| static inline int |
| get_diskname_from_path_real (const grub_efi_device_path_t *path, |
| struct grub_efidisk_data *head, |
| char *buf) |
| { |
| int count = 0; |
| struct grub_efidisk_data *d; |
| for (d = head, count = 0; d; d = d->next, count++) |
| if (grub_efi_compare_device_paths (d->device_path, path) == 0) |
| { |
| grub_snprintf (buf, NEEDED_BUFLEN - 1, "d%d", count); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static inline int |
| get_diskname_from_path (const grub_efi_device_path_t *path, |
| char *buf) |
| { |
| if (get_diskname_from_path_real (path, hd_devices, buf + 1)) |
| { |
| buf[0] = 'h'; |
| return 1; |
| } |
| |
| if (get_diskname_from_path_real (path, fd_devices, buf + 1)) |
| { |
| buf[0] = 'f'; |
| return 1; |
| } |
| |
| if (get_diskname_from_path_real (path, cd_devices, buf + 1)) |
| { |
| buf[0] = 'c'; |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Context for grub_efidisk_get_device_name. */ |
| struct grub_efidisk_get_device_name_ctx |
| { |
| char *partition_name; |
| grub_efi_hard_drive_device_path_t *hd; |
| }; |
| |
| /* Helper for grub_efidisk_get_device_name. |
| Find the identical partition. */ |
| static int |
| grub_efidisk_get_device_name_iter (grub_disk_t disk, |
| const grub_partition_t part, void *data) |
| { |
| struct grub_efidisk_get_device_name_ctx *ctx = data; |
| |
| if (grub_partition_get_start (part) |
| == (ctx->hd->partition_start << (disk->log_sector_size |
| - GRUB_DISK_SECTOR_BITS)) |
| && grub_partition_get_len (part) |
| == (ctx->hd->partition_size << (disk->log_sector_size |
| - GRUB_DISK_SECTOR_BITS))) |
| { |
| ctx->partition_name = grub_partition_get_name (part); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| char * |
| grub_efidisk_get_device_name (grub_efi_handle_t *handle) |
| { |
| grub_efi_device_path_t *dp, *ldp; |
| char device_name[NEEDED_BUFLEN]; |
| |
| dp = grub_efi_get_device_path (handle); |
| if (! dp) |
| return 0; |
| |
| ldp = grub_efi_find_last_device_path (dp); |
| if (! ldp) |
| return 0; |
| |
| if (GRUB_EFI_DEVICE_PATH_TYPE (ldp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE |
| && (GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) == GRUB_EFI_CDROM_DEVICE_PATH_SUBTYPE |
| || GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) == GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE)) |
| { |
| struct grub_efidisk_get_device_name_ctx ctx; |
| char *dev_name; |
| grub_efi_device_path_t *dup_dp; |
| grub_disk_t parent = 0; |
| |
| /* It is necessary to duplicate the device path so that GRUB |
| can overwrite it. */ |
| dup_dp = grub_efi_duplicate_device_path (dp); |
| if (! dup_dp) |
| return 0; |
| |
| while (1) |
| { |
| grub_efi_device_path_t *dup_ldp; |
| dup_ldp = grub_efi_find_last_device_path (dup_dp); |
| if (!(GRUB_EFI_DEVICE_PATH_TYPE (dup_ldp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE |
| && (GRUB_EFI_DEVICE_PATH_SUBTYPE (dup_ldp) == GRUB_EFI_CDROM_DEVICE_PATH_SUBTYPE |
| || GRUB_EFI_DEVICE_PATH_SUBTYPE (dup_ldp) == GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE))) |
| break; |
| |
| dup_ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE; |
| dup_ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE; |
| dup_ldp->length = sizeof (*dup_ldp); |
| } |
| |
| if (!get_diskname_from_path (dup_dp, device_name)) |
| { |
| grub_free (dup_dp); |
| return 0; |
| } |
| |
| parent = grub_disk_open (device_name); |
| grub_free (dup_dp); |
| |
| if (! parent) |
| return 0; |
| |
| /* Find a partition which matches the hard drive device path. */ |
| ctx.partition_name = NULL; |
| ctx.hd = (grub_efi_hard_drive_device_path_t *) ldp; |
| if (ctx.hd->partition_start == 0 |
| && (ctx.hd->partition_size << (parent->log_sector_size |
| - GRUB_DISK_SECTOR_BITS)) |
| == grub_disk_get_size (parent)) |
| { |
| dev_name = grub_strdup (parent->name); |
| } |
| else |
| { |
| grub_partition_iterate (parent, grub_efidisk_get_device_name_iter, |
| &ctx); |
| |
| if (! ctx.partition_name) |
| { |
| /* No partition found. In most cases partition is embed in |
| the root path anyway, so this is not critical. |
| This happens only if partition is on partmap that GRUB |
| doesn't need to access root. |
| */ |
| grub_disk_close (parent); |
| return grub_strdup (device_name); |
| } |
| |
| dev_name = grub_xasprintf ("%s,%s", parent->name, |
| ctx.partition_name); |
| grub_free (ctx.partition_name); |
| } |
| |
| { |
| // This block is a temporary workaround used by Chrome OS. We set |
| // some variables that we can use in the grub.cfg file to ensure that |
| // we get the kernel and rootfs from the boot device, regardless of |
| // which device that is. |
| grub_size_t tmpbuf_len = grub_strlen (parent->name) + 5; |
| char *tmpbuf = grub_malloc (tmpbuf_len); |
| if (! tmpbuf) |
| { |
| grub_free (dev_name); |
| grub_disk_close (parent); |
| return 0; |
| } |
| |
| grub_snprintf (tmpbuf, tmpbuf_len, "(%s,3)", parent->name); |
| grub_env_set ("grubpartA", tmpbuf); |
| grub_env_export ("grubpartA"); |
| grub_snprintf (tmpbuf, tmpbuf_len, "(%s,5)", parent->name); |
| grub_env_set ("grubpartB", tmpbuf); |
| grub_env_export ("grubpartB"); |
| grub_free (tmpbuf); |
| |
| grub_env_set ("grubdisk", parent->name); |
| grub_env_export ("grubdisk"); |
| |
| // Translate hd0 to sda, hd1 to sdb, etc. parent->name is always |
| // either "fdN", "hdN", or "cdN". This trick won't work if N is > 9. |
| int index = parent->name[2] - '0'; |
| |
| char devname[] = "sdXN"; |
| devname[2] = 'a' + index; |
| devname[3] = '3'; |
| grub_env_set ("linuxpartA", devname); |
| grub_env_export ("linuxpartA"); |
| devname[3] = '5'; |
| grub_env_set ("linuxpartB", devname); |
| grub_env_export ("linuxpartB"); |
| } |
| |
| grub_disk_close (parent); |
| |
| return dev_name; |
| } |
| /* This may be guessed device - floppy, cdrom or entire disk. */ |
| if (!get_diskname_from_path (dp, device_name)) |
| return 0; |
| return grub_strdup (device_name); |
| } |