summaryrefslogtreecommitdiffstats
path: root/grub-core/disk/ldm.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/disk/ldm.c')
-rw-r--r--grub-core/disk/ldm.c1110
1 files changed, 1110 insertions, 0 deletions
diff --git a/grub-core/disk/ldm.c b/grub-core/disk/ldm.c
new file mode 100644
index 0000000..4577a51
--- /dev/null
+++ b/grub-core/disk/ldm.c
@@ -0,0 +1,1110 @@
+/*
+ * 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/msdos_partition.h>
+#include <grub/gpt_partition.h>
+#include <grub/i18n.h>
+#include <grub/safemath.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];
+} GRUB_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;
+} GRUB_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 int
+check_ldm_partition (grub_disk_t disk __attribute__ ((unused)), const grub_partition_t p, void *data)
+{
+ int *has_ldm = data;
+
+ if (p->number >= 4)
+ return 1;
+ if (p->msdostype == GRUB_PC_PARTITION_TYPE_LDM)
+ {
+ *has_ldm = 1;
+ return 1;
+ }
+ return 0;
+}
+
+static int
+msdos_has_ldm_partition (grub_disk_t dsk)
+{
+ grub_err_t err;
+ int has_ldm = 0;
+
+ err = grub_partition_msdos_iterate (dsk, check_ldm_partition, &has_ldm);
+ if (err)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return 0;
+ }
+
+ return has_ldm;
+}
+
+static const grub_gpt_part_guid_t ldm_type = GRUB_GPT_PARTITION_TYPE_LDM;
+
+/* Helper for gpt_ldm_sector. */
+static int
+gpt_ldm_sector_iter (grub_disk_t disk, const grub_partition_t p, void *data)
+{
+ grub_disk_addr_t *sector = data;
+ 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;
+}
+
+static grub_disk_addr_t
+gpt_ldm_sector (grub_disk_t dsk)
+{
+ grub_disk_addr_t sector = 0;
+ grub_err_t err;
+
+ err = grub_gpt_partition_map_iterate (dsk, gpt_ldm_sector_iter, &sector);
+ 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);
+ grub_free (vg);
+ 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;
+ grub_size_t sz;
+ 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)
+ {
+ grub_free (lv);
+ 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_calloc (lv->segments->node_alloc,
+ sizeof (*lv->segments->nodes));
+ if (!lv->segments->nodes)
+ {
+ grub_free (lv);
+ 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;
+ }
+ if (grub_add (*ptr, 1, &sz))
+ {
+ grub_free (lv->internal_id);
+ grub_free (lv);
+ goto fail2;
+ }
+ lv->name = grub_malloc (sz);
+ 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_calloc (comp->segment_alloc,
+ sizeof (*comp->segments));
+ if (!comp->segments)
+ {
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ }
+ else
+ {
+ comp->segment_alloc = 1;
+ comp->segment_count = 1;
+ comp->segments = grub_malloc (sizeof (*comp->segments));
+ if (!comp->segments)
+ {
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ 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
+ {
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ ptr += *ptr + 1;
+ ptr++;
+ if (!(vblk[i].flags & 0x10))
+ {
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)
+ || ptr + *ptr + 1 >= vblk[i].dynamic
+ + sizeof (vblk[i].dynamic))
+ {
+ grub_free (comp->segments);
+ 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->segments);
+ 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_calloc (comp->segments->node_alloc,
+ sizeof (*comp->segments->nodes));
+ if (!lv->segments->nodes)
+ {
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ }
+
+ if (lv->segments->node_alloc == lv->segments->node_count)
+ {
+ void *t;
+ grub_size_t sz;
+
+ if (grub_mul (lv->segments->node_alloc, 2, &lv->segments->node_alloc) ||
+ grub_mul (lv->segments->node_alloc, sizeof (*lv->segments->nodes), &sz))
+ {
+ grub_free (comp->segments->nodes);
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+
+ t = grub_realloc (lv->segments->nodes, sz);
+ if (!t)
+ {
+ grub_free (comp->segments->nodes);
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ 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 node_index;
+ ptr += *ptr + 1;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic
+ + sizeof (vblk[i].dynamic))
+ {
+ goto fail2;
+ }
+ node_index = read_int (ptr + 1, *ptr);
+ if (node_index < comp->segments->node_count)
+ comp->segments->nodes[node_index] = part;
+ }
+ else
+ {
+ if (comp->segment_alloc == comp->segment_count)
+ {
+ void *t;
+ grub_size_t sz;
+
+ if (grub_mul (comp->segment_alloc, 2, &comp->segment_alloc) ||
+ grub_mul (comp->segment_alloc, sizeof (*comp->segments), &sz))
+ goto fail2;
+
+ t = grub_realloc (comp->segments, sz);
+ 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;
+ int has_ldm = msdos_has_ldm_partition (disk);
+ for (i = 0; i < 3; i++)
+ {
+ grub_disk_addr_t sector = LDM_LABEL_SECTOR;
+ switch (i)
+ {
+ case 0:
+ if (!has_ldm)
+ continue;
+ sector = LDM_LABEL_SECTOR;
+ break;
+ case 1:
+ /* LDM is never inside a partition. */
+ if (!has_ldm || disk->partition)
+ continue;
+ sector = grub_disk_native_sectors (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;
+ int has_ldm = msdos_has_ldm_partition (disk);
+ 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:
+ if (!has_ldm)
+ continue;
+ sector = LDM_LABEL_SECTOR;
+ break;
+ case 1:
+ /* LDM is never inside a partition. */
+ if (!has_ldm || disk->partition)
+ continue;
+ sector = grub_disk_native_sectors (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 currently 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_calloc (*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);
+}