| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2006,2007,2008,2009,2011 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/dl.h> |
| #include <grub/disk.h> |
| #include <grub/mm.h> |
| #include <grub/err.h> |
| #include <grub/misc.h> |
| #include <grub/diskfilter.h> |
| #include <grub/gpt_partition.h> |
| #include <grub/i18n.h> |
| |
| #ifdef GRUB_UTIL |
| #include <grub/emu/misc.h> |
| #include <grub/emu/hostdisk.h> |
| #endif |
| |
| GRUB_MOD_LICENSE ("GPLv3+"); |
| |
| #define LDM_GUID_STRLEN 64 |
| #define LDM_NAME_STRLEN 32 |
| |
| typedef grub_uint8_t *grub_ldm_id_t; |
| |
| enum { STRIPE = 1, SPANNED = 2, RAID5 = 3 }; |
| |
| #define LDM_LABEL_SECTOR 6 |
| struct grub_ldm_vblk { |
| char magic[4]; |
| grub_uint8_t unused1[12]; |
| grub_uint16_t update_status; |
| grub_uint8_t flags; |
| grub_uint8_t type; |
| grub_uint32_t unused2; |
| grub_uint8_t dynamic[104]; |
| } __attribute__ ((packed)); |
| #define LDM_VBLK_MAGIC "VBLK" |
| |
| enum |
| { |
| STATUS_CONSISTENT = 0, |
| STATUS_STILL_ACTIVE = 1, |
| STATUS_NOT_ACTIVE_YET = 2 |
| }; |
| |
| enum |
| { |
| ENTRY_COMPONENT = 0x32, |
| ENTRY_PARTITION = 0x33, |
| ENTRY_DISK = 0x34, |
| ENTRY_VOLUME = 0x51, |
| }; |
| |
| struct grub_ldm_label |
| { |
| char magic[8]; |
| grub_uint32_t unused1; |
| grub_uint16_t ver_major; |
| grub_uint16_t ver_minor; |
| grub_uint8_t unused2[32]; |
| char disk_guid[LDM_GUID_STRLEN]; |
| char host_guid[LDM_GUID_STRLEN]; |
| char group_guid[LDM_GUID_STRLEN]; |
| char group_name[LDM_NAME_STRLEN]; |
| grub_uint8_t unused3[11]; |
| grub_uint64_t pv_start; |
| grub_uint64_t pv_size; |
| grub_uint64_t config_start; |
| grub_uint64_t config_size; |
| } __attribute__ ((packed)); |
| |
| |
| #define LDM_MAGIC "PRIVHEAD" |
| |
| |
| |
| static inline grub_uint64_t |
| read_int (grub_uint8_t *in, grub_size_t s) |
| { |
| grub_uint8_t *ptr2; |
| grub_uint64_t ret; |
| ret = 0; |
| for (ptr2 = in; ptr2 < in + s; ptr2++) |
| { |
| ret <<= 8; |
| ret |= *ptr2; |
| } |
| return ret; |
| } |
| |
| static const grub_gpt_part_type_t ldm_type = GRUB_GPT_PARTITION_TYPE_LDM; |
| |
| static grub_disk_addr_t |
| gpt_ldm_sector (grub_disk_t dsk) |
| { |
| grub_disk_addr_t sector = 0; |
| grub_err_t err; |
| auto int hook (grub_disk_t disk, const grub_partition_t p); |
| int hook (grub_disk_t disk, const grub_partition_t p) |
| { |
| struct grub_gpt_partentry gptdata; |
| grub_partition_t p2; |
| |
| p2 = disk->partition; |
| disk->partition = p->parent; |
| if (grub_disk_read (disk, p->offset, p->index, |
| sizeof (gptdata), &gptdata)) |
| { |
| disk->partition = p2; |
| return 0; |
| } |
| disk->partition = p2; |
| |
| if (! grub_memcmp (&gptdata.type, &ldm_type, 16)) |
| { |
| sector = p->start + p->len - 1; |
| return 1; |
| } |
| return 0; |
| } |
| err = grub_gpt_partition_map_iterate (dsk, hook); |
| if (err) |
| { |
| grub_errno = GRUB_ERR_NONE; |
| return 0; |
| } |
| return sector; |
| } |
| |
| static struct grub_diskfilter_vg * |
| make_vg (grub_disk_t disk, |
| const struct grub_ldm_label *label) |
| { |
| grub_disk_addr_t startsec, endsec, cursec; |
| struct grub_diskfilter_vg *vg; |
| grub_err_t err; |
| |
| /* First time we see this volume group. We've to create the |
| whole volume group structure. */ |
| vg = grub_malloc (sizeof (*vg)); |
| if (! vg) |
| return NULL; |
| vg->extent_size = 1; |
| vg->name = grub_malloc (LDM_NAME_STRLEN + 1); |
| vg->uuid = grub_malloc (LDM_GUID_STRLEN + 1); |
| if (! vg->uuid || !vg->name) |
| { |
| grub_free (vg->uuid); |
| grub_free (vg->name); |
| return NULL; |
| } |
| grub_memcpy (vg->uuid, label->group_guid, LDM_GUID_STRLEN); |
| grub_memcpy (vg->name, label->group_name, LDM_NAME_STRLEN); |
| vg->name[LDM_NAME_STRLEN] = 0; |
| vg->uuid[LDM_GUID_STRLEN] = 0; |
| vg->uuid_len = grub_strlen (vg->uuid); |
| |
| vg->lvs = NULL; |
| vg->pvs = NULL; |
| |
| startsec = grub_be_to_cpu64 (label->config_start); |
| endsec = startsec + grub_be_to_cpu64 (label->config_size); |
| |
| /* First find disks. */ |
| for (cursec = startsec + 0x12; cursec < endsec; cursec++) |
| { |
| struct grub_ldm_vblk vblk[GRUB_DISK_SECTOR_SIZE |
| / sizeof (struct grub_ldm_vblk)]; |
| unsigned i; |
| err = grub_disk_read (disk, cursec, 0, |
| sizeof(vblk), &vblk); |
| if (err) |
| goto fail2; |
| |
| for (i = 0; i < ARRAY_SIZE (vblk); i++) |
| { |
| struct grub_diskfilter_pv *pv; |
| grub_uint8_t *ptr; |
| if (grub_memcmp (vblk[i].magic, LDM_VBLK_MAGIC, |
| sizeof (vblk[i].magic)) != 0) |
| continue; |
| if (grub_be_to_cpu16 (vblk[i].update_status) |
| != STATUS_CONSISTENT |
| && grub_be_to_cpu16 (vblk[i].update_status) |
| != STATUS_STILL_ACTIVE) |
| continue; |
| if (vblk[i].type != ENTRY_DISK) |
| continue; |
| pv = grub_zalloc (sizeof (*pv)); |
| if (!pv) |
| goto fail2; |
| |
| pv->disk = 0; |
| ptr = vblk[i].dynamic; |
| if (ptr + *ptr + 1 >= vblk[i].dynamic |
| + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (pv); |
| goto fail2; |
| } |
| pv->internal_id = grub_malloc (ptr[0] + 2); |
| if (!pv->internal_id) |
| { |
| grub_free (pv); |
| goto fail2; |
| } |
| grub_memcpy (pv->internal_id, ptr, (grub_size_t) ptr[0] + 1); |
| pv->internal_id[(grub_size_t) ptr[0] + 1] = 0; |
| |
| ptr += *ptr + 1; |
| if (ptr + *ptr + 1 >= vblk[i].dynamic |
| + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (pv); |
| goto fail2; |
| } |
| /* ptr = name. */ |
| ptr += *ptr + 1; |
| if (ptr + *ptr + 1 |
| >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (pv); |
| goto fail2; |
| } |
| pv->id.uuidlen = *ptr; |
| pv->id.uuid = grub_malloc (pv->id.uuidlen + 1); |
| grub_memcpy (pv->id.uuid, ptr + 1, pv->id.uuidlen); |
| pv->id.uuid[pv->id.uuidlen] = 0; |
| |
| pv->next = vg->pvs; |
| vg->pvs = pv; |
| } |
| } |
| |
| /* Then find LVs. */ |
| for (cursec = startsec + 0x12; cursec < endsec; cursec++) |
| { |
| struct grub_ldm_vblk vblk[GRUB_DISK_SECTOR_SIZE |
| / sizeof (struct grub_ldm_vblk)]; |
| unsigned i; |
| err = grub_disk_read (disk, cursec, 0, |
| sizeof(vblk), &vblk); |
| if (err) |
| goto fail2; |
| |
| for (i = 0; i < ARRAY_SIZE (vblk); i++) |
| { |
| struct grub_diskfilter_lv *lv; |
| grub_uint8_t *ptr; |
| if (grub_memcmp (vblk[i].magic, LDM_VBLK_MAGIC, |
| sizeof (vblk[i].magic)) != 0) |
| continue; |
| if (grub_be_to_cpu16 (vblk[i].update_status) |
| != STATUS_CONSISTENT |
| && grub_be_to_cpu16 (vblk[i].update_status) |
| != STATUS_STILL_ACTIVE) |
| continue; |
| if (vblk[i].type != ENTRY_VOLUME) |
| continue; |
| lv = grub_zalloc (sizeof (*lv)); |
| if (!lv) |
| goto fail2; |
| |
| lv->vg = vg; |
| lv->segment_count = 1; |
| lv->segment_alloc = 1; |
| lv->visible = 1; |
| lv->segments = grub_zalloc (sizeof (*lv->segments)); |
| if (!lv->segments) |
| goto fail2; |
| lv->segments->start_extent = 0; |
| lv->segments->type = GRUB_DISKFILTER_MIRROR; |
| lv->segments->node_count = 0; |
| lv->segments->node_alloc = 8; |
| lv->segments->nodes = grub_zalloc (sizeof (*lv->segments->nodes) |
| * lv->segments->node_alloc); |
| if (!lv->segments->nodes) |
| goto fail2; |
| ptr = vblk[i].dynamic; |
| if (ptr + *ptr + 1 >= vblk[i].dynamic |
| + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (lv); |
| goto fail2; |
| } |
| lv->internal_id = grub_malloc ((grub_size_t) ptr[0] + 2); |
| if (!lv->internal_id) |
| { |
| grub_free (lv); |
| goto fail2; |
| } |
| grub_memcpy (lv->internal_id, ptr, ptr[0] + 1); |
| lv->internal_id[ptr[0] + 1] = 0; |
| |
| ptr += *ptr + 1; |
| if (ptr + *ptr + 1 >= vblk[i].dynamic |
| + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (lv); |
| goto fail2; |
| } |
| lv->name = grub_malloc (*ptr + 1); |
| if (!lv->name) |
| { |
| grub_free (lv->internal_id); |
| grub_free (lv); |
| goto fail2; |
| } |
| grub_memcpy (lv->name, ptr + 1, *ptr); |
| lv->name[*ptr] = 0; |
| lv->fullname = grub_xasprintf ("ldm/%s/%s", |
| vg->uuid, lv->name); |
| if (!lv->fullname) |
| { |
| grub_free (lv->internal_id); |
| grub_free (lv->name); |
| grub_free (lv); |
| goto fail2; |
| } |
| ptr += *ptr + 1; |
| if (ptr + *ptr + 1 |
| >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (lv->internal_id); |
| grub_free (lv->name); |
| grub_free (lv); |
| goto fail2; |
| } |
| /* ptr = volume type. */ |
| ptr += *ptr + 1; |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (lv->internal_id); |
| grub_free (lv->name); |
| grub_free (lv); |
| goto fail2; |
| } |
| /* ptr = flags. */ |
| ptr += *ptr + 1; |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (lv->internal_id); |
| grub_free (lv->name); |
| grub_free (lv); |
| goto fail2; |
| } |
| |
| /* Skip state, type, unknown, volume number, zeros, flags. */ |
| ptr += 14 + 1 + 1 + 1 + 3 + 1; |
| /* ptr = number of children. */ |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (lv->internal_id); |
| grub_free (lv->name); |
| grub_free (lv); |
| goto fail2; |
| } |
| ptr += *ptr + 1; |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (lv->internal_id); |
| grub_free (lv->name); |
| grub_free (lv); |
| goto fail2; |
| } |
| |
| /* Skip 2 more fields. */ |
| ptr += 8 + 8; |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic) |
| || ptr + *ptr + 1>= vblk[i].dynamic |
| + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (lv->internal_id); |
| grub_free (lv->name); |
| grub_free (lv); |
| goto fail2; |
| } |
| lv->size = read_int (ptr + 1, *ptr); |
| lv->segments->extent_count = lv->size; |
| |
| lv->next = vg->lvs; |
| vg->lvs = lv; |
| } |
| } |
| |
| /* Now the components. */ |
| for (cursec = startsec + 0x12; cursec < endsec; cursec++) |
| { |
| struct grub_ldm_vblk vblk[GRUB_DISK_SECTOR_SIZE |
| / sizeof (struct grub_ldm_vblk)]; |
| unsigned i; |
| err = grub_disk_read (disk, cursec, 0, |
| sizeof(vblk), &vblk); |
| if (err) |
| goto fail2; |
| |
| for (i = 0; i < ARRAY_SIZE (vblk); i++) |
| { |
| struct grub_diskfilter_lv *comp; |
| struct grub_diskfilter_lv *lv; |
| grub_uint8_t type; |
| |
| grub_uint8_t *ptr; |
| if (grub_memcmp (vblk[i].magic, LDM_VBLK_MAGIC, |
| sizeof (vblk[i].magic)) != 0) |
| continue; |
| if (grub_be_to_cpu16 (vblk[i].update_status) |
| != STATUS_CONSISTENT |
| && grub_be_to_cpu16 (vblk[i].update_status) |
| != STATUS_STILL_ACTIVE) |
| continue; |
| if (vblk[i].type != ENTRY_COMPONENT) |
| continue; |
| comp = grub_zalloc (sizeof (*comp)); |
| if (!comp) |
| goto fail2; |
| comp->visible = 0; |
| comp->name = 0; |
| comp->fullname = 0; |
| |
| ptr = vblk[i].dynamic; |
| if (ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| goto fail2; |
| } |
| comp->internal_id = grub_malloc ((grub_size_t) ptr[0] + 2); |
| if (!comp->internal_id) |
| { |
| grub_free (comp); |
| goto fail2; |
| } |
| grub_memcpy (comp->internal_id, ptr, ptr[0] + 1); |
| comp->internal_id[ptr[0] + 1] = 0; |
| |
| ptr += *ptr + 1; |
| if (ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (comp->internal_id); |
| grub_free (comp); |
| goto fail2; |
| } |
| /* ptr = name. */ |
| ptr += *ptr + 1; |
| if (ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (comp->internal_id); |
| grub_free (comp); |
| goto fail2; |
| } |
| /* ptr = state. */ |
| ptr += *ptr + 1; |
| type = *ptr++; |
| /* skip zeros. */ |
| ptr += 4; |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (comp->internal_id); |
| grub_free (comp); |
| goto fail2; |
| } |
| |
| /* ptr = number of children. */ |
| ptr += *ptr + 1; |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (comp->internal_id); |
| grub_free (comp); |
| goto fail2; |
| } |
| ptr += 8 + 8; |
| if (ptr + *ptr + 1 >= vblk[i].dynamic |
| + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (comp->internal_id); |
| grub_free (comp); |
| goto fail2; |
| } |
| for (lv = vg->lvs; lv; lv = lv->next) |
| { |
| if (lv->internal_id[0] == ptr[0] |
| && grub_memcmp (lv->internal_id + 1, ptr + 1, ptr[0]) == 0) |
| break; |
| } |
| if (!lv) |
| { |
| grub_free (comp->internal_id); |
| grub_free (comp); |
| continue; |
| } |
| comp->size = lv->size; |
| if (type == SPANNED) |
| { |
| comp->segment_alloc = 8; |
| comp->segment_count = 0; |
| comp->segments = grub_malloc (sizeof (*comp->segments) |
| * comp->segment_alloc); |
| if (!comp->segments) |
| goto fail2; |
| } |
| else |
| { |
| comp->segment_alloc = 1; |
| comp->segment_count = 1; |
| comp->segments = grub_malloc (sizeof (*comp->segments)); |
| if (!comp->segments) |
| goto fail2; |
| comp->segments->start_extent = 0; |
| comp->segments->extent_count = lv->size; |
| comp->segments->layout = 0; |
| if (type == STRIPE) |
| comp->segments->type = GRUB_DISKFILTER_STRIPED; |
| else if (type == RAID5) |
| { |
| comp->segments->type = GRUB_DISKFILTER_RAID5; |
| comp->segments->layout = GRUB_RAID_LAYOUT_SYMMETRIC_MASK; |
| } |
| else |
| goto fail2; |
| ptr += *ptr + 1; |
| ptr++; |
| if (!(vblk[i].flags & 0x10)) |
| goto fail2; |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic) |
| || ptr + *ptr + 1 >= vblk[i].dynamic |
| + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (comp->internal_id); |
| grub_free (comp); |
| goto fail2; |
| } |
| comp->segments->stripe_size = read_int (ptr + 1, *ptr); |
| ptr += *ptr + 1; |
| if (ptr + *ptr + 1 >= vblk[i].dynamic |
| + sizeof (vblk[i].dynamic)) |
| { |
| grub_free (comp->internal_id); |
| grub_free (comp); |
| goto fail2; |
| } |
| comp->segments->node_count = read_int (ptr + 1, *ptr); |
| comp->segments->node_alloc = comp->segments->node_count; |
| comp->segments->nodes = grub_zalloc (sizeof (*comp->segments->nodes) |
| * comp->segments->node_alloc); |
| if (!lv->segments->nodes) |
| goto fail2; |
| } |
| |
| if (lv->segments->node_alloc == lv->segments->node_count) |
| { |
| void *t; |
| lv->segments->node_alloc *= 2; |
| t = grub_realloc (lv->segments->nodes, |
| sizeof (*lv->segments->nodes) |
| * lv->segments->node_alloc); |
| if (!t) |
| goto fail2; |
| lv->segments->nodes = t; |
| } |
| lv->segments->nodes[lv->segments->node_count].pv = 0; |
| lv->segments->nodes[lv->segments->node_count].start = 0; |
| lv->segments->nodes[lv->segments->node_count++].lv = comp; |
| comp->next = vg->lvs; |
| vg->lvs = comp; |
| } |
| } |
| /* Partitions. */ |
| for (cursec = startsec + 0x12; cursec < endsec; cursec++) |
| { |
| struct grub_ldm_vblk vblk[GRUB_DISK_SECTOR_SIZE |
| / sizeof (struct grub_ldm_vblk)]; |
| unsigned i; |
| err = grub_disk_read (disk, cursec, 0, |
| sizeof(vblk), &vblk); |
| if (err) |
| goto fail2; |
| |
| for (i = 0; i < ARRAY_SIZE (vblk); i++) |
| { |
| struct grub_diskfilter_lv *comp; |
| struct grub_diskfilter_node part; |
| grub_disk_addr_t start, size; |
| |
| grub_uint8_t *ptr; |
| part.name = 0; |
| if (grub_memcmp (vblk[i].magic, LDM_VBLK_MAGIC, |
| sizeof (vblk[i].magic)) != 0) |
| continue; |
| if (grub_be_to_cpu16 (vblk[i].update_status) |
| != STATUS_CONSISTENT |
| && grub_be_to_cpu16 (vblk[i].update_status) |
| != STATUS_STILL_ACTIVE) |
| continue; |
| if (vblk[i].type != ENTRY_PARTITION) |
| continue; |
| part.lv = 0; |
| part.pv = 0; |
| |
| ptr = vblk[i].dynamic; |
| if (ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| goto fail2; |
| } |
| /* ID */ |
| ptr += *ptr + 1; |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| goto fail2; |
| } |
| /* ptr = name. */ |
| ptr += *ptr + 1; |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| goto fail2; |
| } |
| |
| /* skip zeros and logcommit id. */ |
| ptr += 4 + 8; |
| if (ptr + 16 >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| goto fail2; |
| } |
| part.start = read_int (ptr, 8); |
| start = read_int (ptr + 8, 8); |
| ptr += 16; |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic) |
| || ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| goto fail2; |
| } |
| size = read_int (ptr + 1, *ptr); |
| ptr += *ptr + 1; |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic) |
| || ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| goto fail2; |
| } |
| |
| for (comp = vg->lvs; comp; comp = comp->next) |
| if (comp->internal_id[0] == ptr[0] |
| && grub_memcmp (ptr + 1, comp->internal_id + 1, |
| comp->internal_id[0]) == 0) |
| goto out; |
| continue; |
| out: |
| if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic) |
| || ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic)) |
| { |
| goto fail2; |
| } |
| ptr += *ptr + 1; |
| struct grub_diskfilter_pv *pv; |
| for (pv = vg->pvs; pv; pv = pv->next) |
| if (pv->internal_id[0] == ptr[0] |
| && grub_memcmp (pv->internal_id + 1, ptr + 1, ptr[0]) == 0) |
| part.pv = pv; |
| |
| if (comp->segment_alloc == 1) |
| { |
| unsigned index; |
| ptr += *ptr + 1; |
| if (ptr + *ptr + 1 >= vblk[i].dynamic |
| + sizeof (vblk[i].dynamic)) |
| { |
| goto fail2; |
| } |
| index = read_int (ptr + 1, *ptr); |
| if (index < comp->segments->node_count) |
| comp->segments->nodes[index] = part; |
| } |
| else |
| { |
| if (comp->segment_alloc == comp->segment_count) |
| { |
| void *t; |
| comp->segment_alloc *= 2; |
| t = grub_realloc (comp->segments, |
| comp->segment_alloc |
| * sizeof (*comp->segments)); |
| if (!t) |
| goto fail2; |
| comp->segments = t; |
| } |
| comp->segments[comp->segment_count].start_extent = start; |
| comp->segments[comp->segment_count].extent_count = size; |
| comp->segments[comp->segment_count].type = GRUB_DISKFILTER_STRIPED; |
| comp->segments[comp->segment_count].node_count = 1; |
| comp->segments[comp->segment_count].node_alloc = 1; |
| comp->segments[comp->segment_count].nodes |
| = grub_malloc (sizeof (*comp->segments[comp->segment_count].nodes)); |
| if (!comp->segments[comp->segment_count].nodes) |
| goto fail2; |
| comp->segments[comp->segment_count].nodes[0] = part; |
| comp->segment_count++; |
| } |
| } |
| } |
| if (grub_diskfilter_vg_register (vg)) |
| goto fail2; |
| return vg; |
| fail2: |
| { |
| struct grub_diskfilter_lv *lv, *next_lv; |
| struct grub_diskfilter_pv *pv, *next_pv; |
| for (lv = vg->lvs; lv; lv = next_lv) |
| { |
| unsigned i; |
| for (i = 0; i < lv->segment_count; i++) |
| grub_free (lv->segments[i].nodes); |
| |
| next_lv = lv->next; |
| grub_free (lv->segments); |
| grub_free (lv->internal_id); |
| grub_free (lv->name); |
| grub_free (lv->fullname); |
| grub_free (lv); |
| } |
| for (pv = vg->pvs; pv; pv = next_pv) |
| { |
| next_pv = pv->next; |
| grub_free (pv->id.uuid); |
| grub_free (pv); |
| } |
| } |
| grub_free (vg->uuid); |
| grub_free (vg); |
| return NULL; |
| } |
| |
| static struct grub_diskfilter_vg * |
| grub_ldm_detect (grub_disk_t disk, |
| struct grub_diskfilter_pv_id *id, |
| grub_disk_addr_t *start_sector) |
| { |
| grub_err_t err; |
| struct grub_ldm_label label; |
| struct grub_diskfilter_vg *vg; |
| |
| #ifdef GRUB_UTIL |
| grub_util_info ("scanning %s for LDM", disk->name); |
| #endif |
| |
| { |
| int i; |
| for (i = 0; i < 3; i++) |
| { |
| grub_disk_addr_t sector = LDM_LABEL_SECTOR; |
| switch (i) |
| { |
| case 0: |
| sector = LDM_LABEL_SECTOR; |
| break; |
| case 1: |
| /* LDM is never inside a partition. */ |
| if (disk->partition) |
| continue; |
| sector = grub_disk_get_size (disk); |
| if (sector == GRUB_DISK_SIZE_UNKNOWN) |
| continue; |
| sector--; |
| break; |
| /* FIXME: try the third copy. */ |
| case 2: |
| sector = gpt_ldm_sector (disk); |
| if (!sector) |
| continue; |
| break; |
| } |
| err = grub_disk_read (disk, sector, 0, |
| sizeof(label), &label); |
| if (err) |
| return NULL; |
| if (grub_memcmp (label.magic, LDM_MAGIC, sizeof (label.magic)) == 0 |
| && grub_be_to_cpu16 (label.ver_major) == 0x02 |
| && grub_be_to_cpu16 (label.ver_minor) >= 0x0b |
| && grub_be_to_cpu16 (label.ver_minor) <= 0x0c) |
| break; |
| } |
| |
| /* Return if we didn't find a label. */ |
| if (i == 3) |
| { |
| #ifdef GRUB_UTIL |
| grub_util_info ("no LDM signature found"); |
| #endif |
| return NULL; |
| } |
| } |
| |
| id->uuid = grub_malloc (LDM_GUID_STRLEN + 1); |
| if (!id->uuid) |
| return NULL; |
| grub_memcpy (id->uuid, label.disk_guid, LDM_GUID_STRLEN); |
| id->uuid[LDM_GUID_STRLEN] = 0; |
| id->uuidlen = grub_strlen ((char *) id->uuid); |
| *start_sector = grub_be_to_cpu64 (label.pv_start); |
| |
| { |
| grub_size_t s; |
| for (s = 0; s < LDM_GUID_STRLEN && label.group_guid[s]; s++); |
| vg = grub_diskfilter_get_vg_by_uuid (s, label.group_guid); |
| if (! vg) |
| vg = make_vg (disk, &label); |
| } |
| |
| if (!vg) |
| { |
| grub_free (id->uuid); |
| return NULL; |
| } |
| return vg; |
| } |
| |
| #ifdef GRUB_UTIL |
| |
| char * |
| grub_util_get_ldm (grub_disk_t disk, grub_disk_addr_t start) |
| { |
| struct grub_diskfilter_pv *pv = NULL; |
| struct grub_diskfilter_vg *vg = NULL; |
| struct grub_diskfilter_lv *res = 0, *lv, *res_lv = 0; |
| |
| pv = grub_diskfilter_get_pv_from_disk (disk, &vg); |
| |
| if (!pv) |
| return NULL; |
| |
| for (lv = vg->lvs; lv; lv = lv->next) |
| if (lv->segment_count == 1 && lv->segments->node_count == 1 |
| && lv->segments->type == GRUB_DISKFILTER_STRIPED |
| && lv->segments->nodes->pv == pv |
| && lv->segments->nodes->start + pv->start_sector == start) |
| { |
| res_lv = lv; |
| break; |
| } |
| if (!res_lv) |
| return NULL; |
| for (lv = vg->lvs; lv; lv = lv->next) |
| if (lv->segment_count == 1 && lv->segments->node_count == 1 |
| && lv->segments->type == GRUB_DISKFILTER_MIRROR |
| && lv->segments->nodes->lv == res_lv) |
| { |
| res = lv; |
| break; |
| } |
| if (res && res->fullname) |
| return grub_strdup (res->fullname); |
| return NULL; |
| } |
| |
| int |
| grub_util_is_ldm (grub_disk_t disk) |
| { |
| int i; |
| for (i = 0; i < 3; i++) |
| { |
| grub_disk_addr_t sector = LDM_LABEL_SECTOR; |
| grub_err_t err; |
| struct grub_ldm_label label; |
| |
| switch (i) |
| { |
| case 0: |
| sector = LDM_LABEL_SECTOR; |
| break; |
| case 1: |
| /* LDM is never inside a partition. */ |
| if (disk->partition) |
| continue; |
| sector = grub_disk_get_size (disk); |
| if (sector == GRUB_DISK_SIZE_UNKNOWN) |
| continue; |
| sector--; |
| break; |
| /* FIXME: try the third copy. */ |
| case 2: |
| sector = gpt_ldm_sector (disk); |
| if (!sector) |
| continue; |
| break; |
| } |
| err = grub_disk_read (disk, sector, 0, sizeof(label), &label); |
| if (err) |
| { |
| grub_errno = GRUB_ERR_NONE; |
| return 0; |
| } |
| /* This check is more relaxed on purpose. */ |
| if (grub_memcmp (label.magic, LDM_MAGIC, sizeof (label.magic)) == 0) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| grub_err_t |
| grub_util_ldm_embed (struct grub_disk *disk, unsigned int *nsectors, |
| unsigned int max_nsectors, |
| grub_embed_type_t embed_type, |
| grub_disk_addr_t **sectors) |
| { |
| struct grub_diskfilter_pv *pv = NULL; |
| struct grub_diskfilter_vg *vg; |
| struct grub_diskfilter_lv *lv; |
| unsigned i; |
| |
| if (embed_type != GRUB_EMBED_PCBIOS) |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "LDM curently supports only PC-BIOS embedding"); |
| if (disk->partition) |
| return grub_error (GRUB_ERR_BUG, "disk isn't LDM"); |
| pv = grub_diskfilter_get_pv_from_disk (disk, &vg); |
| if (!pv) |
| return grub_error (GRUB_ERR_BUG, "disk isn't LDM"); |
| for (lv = vg->lvs; lv; lv = lv->next) |
| { |
| struct grub_diskfilter_lv *comp; |
| |
| if (!lv->visible || !lv->fullname) |
| continue; |
| |
| if (lv->segment_count != 1) |
| continue; |
| if (lv->segments->type != GRUB_DISKFILTER_MIRROR |
| || lv->segments->node_count != 1 |
| || lv->segments->start_extent != 0 |
| || lv->segments->extent_count != lv->size) |
| continue; |
| |
| comp = lv->segments->nodes->lv; |
| if (!comp) |
| continue; |
| |
| if (comp->segment_count != 1 || comp->size != lv->size) |
| continue; |
| if (comp->segments->type != GRUB_DISKFILTER_STRIPED |
| || comp->segments->node_count != 1 |
| || comp->segments->start_extent != 0 |
| || comp->segments->extent_count != lv->size) |
| continue; |
| |
| /* How to implement proper check is to be discussed. */ |
| #if 1 |
| if (1) |
| continue; |
| #else |
| if (grub_strcmp (lv->name, "Volume5") != 0) |
| continue; |
| #endif |
| if (lv->size < *nsectors) |
| return grub_error (GRUB_ERR_OUT_OF_RANGE, |
| /* TRANSLATORS: it's a partition for embedding, |
| not a partition embed into something. GRUB |
| install tools put core.img into a place |
| usable for bootloaders (called generically |
| "embedding zone") and this operation is |
| called "embedding". */ |
| N_("your LDM embedding Partition is too small;" |
| " embedding won't be possible")); |
| *nsectors = lv->size; |
| if (*nsectors > max_nsectors) |
| *nsectors = max_nsectors; |
| *sectors = grub_malloc (*nsectors * sizeof (**sectors)); |
| if (!*sectors) |
| return grub_errno; |
| for (i = 0; i < *nsectors; i++) |
| (*sectors)[i] = (lv->segments->nodes->start |
| + comp->segments->nodes->start |
| + comp->segments->nodes->pv->start_sector + i); |
| return GRUB_ERR_NONE; |
| } |
| |
| return grub_error (GRUB_ERR_FILE_NOT_FOUND, |
| /* TRANSLATORS: it's a partition for embedding, |
| not a partition embed into something. */ |
| N_("this LDM has no Embedding Partition;" |
| " embedding won't be possible")); |
| } |
| #endif |
| |
| static struct grub_diskfilter grub_ldm_dev = { |
| .name = "ldm", |
| .detect = grub_ldm_detect, |
| .next = 0 |
| }; |
| |
| GRUB_MOD_INIT (ldm) |
| { |
| grub_diskfilter_register_back (&grub_ldm_dev); |
| } |
| |
| GRUB_MOD_FINI (ldm) |
| { |
| grub_diskfilter_unregister (&grub_ldm_dev); |
| } |