2006-10-14 Yoshinori K. Okuji <okuji@enbug.org>

* DISTLIST: Added commands/echo.c, disk/lvm.c, disk/raid.c,
        include/grub/bitmap.h, include/grub/lvm.h, include/grub/raid.h,
        include/grub/i386/pc/vbeutil.h, include/grub/util/lvm.h,
        include/grub/util/raid.h, util/lvm.c, util/raid.c, video/bitmap.c,
        video/readers/tga.c and video/i386/pc/vbeutil.c.

2006-10-14  Jeroen Dekkers  <jeroen@dekkers.cx>

        Added support for RAID and LVM.

        * disk/lvm.c: New file.
        * disk/raid.c: Likewise.
        * include/grub/lvm.h: Likewise.
        * include/grub/raid.h: Likewise.
        * include/grub/util/lvm.h: Likewise.
        * include/grub/util/raid.h: Likewise.
        * util/lvm.c: Likewise.
        * util/raid.c: Likewise.

        * include/grub/disk.h (grub_disk_dev_id): Add
        GRUB_DISK_DEVICE_RAID_ID and GRUB_DISK_DEVICE_LVM_ID.
        (grub_disk_get_size): New prototype.
        * kern/disk.c (grub_disk_open): Check whether grub_partition_probe()
        returns a partition.
        (grub_disk_get_size): New function.

        * kern/i386/pc/init.c (make_install_device): Copy the prefix
        verbatim if grub_install_dos_part is -2.

        * util/i386/pc/getroot.c (grub_guess_root_device): Support RAID
        and LVM devices.

        * util/i386/pc/grub-setup.c (setup): New argument
        MUST_EMBED. Force embedding of GRUB when the argument is
        true. Close FILE before returning.
        (main): Add support for RAID and LVM.

        * conf/common.rmk: Add RAID and LVM modules.
        * conf/i386-pc.rmk (grub_setup_SOURCES): Add util/raid.c and
        util/lvm.c.
        (grub_emu_SOURCES): Add disk/raid.c and disk/lvm.c.

        * kern/misc.c (grub_strstr): New function.
        * include/grub/misc.h (grub_strstr): New prototype.
This commit is contained in:
okuji 2006-10-14 15:24:53 +00:00
parent 050548d018
commit 2b00217369
23 changed files with 2347 additions and 100 deletions

497
disk/lvm.c Normal file
View file

@ -0,0 +1,497 @@
/* lvm.c - module to read Logical Volumes. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2006 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 2 of the License, or
* (at your option) any later version.
*
* This program 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/misc.h>
#include <grub/lvm.h>
static struct grub_lvm_vg *vgs;
static int lv_count;
/* Go the string STR and return the number after STR. *P will point
at the number. */
static int
grub_lvm_getvalue (char **p, char *str)
{
*p = grub_strstr (*p, str) + grub_strlen (str);
return grub_strtoul (*p, NULL, 10);
}
static int
grub_lvm_iterate (int (*hook) (const char *name))
{
struct grub_lvm_vg *vg;
for (vg = vgs; vg; vg = vg->next)
{
struct grub_lvm_lv *lv;
for (lv = vgs->lvs; lv; lv = lv->next)
if (hook (lv->name))
return 1;
}
return 0;
}
static grub_err_t
grub_lvm_open (const char *name, grub_disk_t disk)
{
struct grub_lvm_vg *vg;
struct grub_lvm_lv *lv = NULL;
for (vg = vgs; vg; vg = vg->next)
{
for (lv = vgs->lvs; lv; lv = lv->next)
if (! grub_strcmp (lv->name, name))
break;
if (lv)
break;
}
if (! lv)
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "Unknown device");
disk->has_partitions = 0;
disk->id = lv->number;
disk->data = lv;
disk->total_sectors = lv->size;
return 0;
}
static void
grub_lvm_close (grub_disk_t disk __attribute ((unused)))
{
return;
}
static grub_err_t
grub_lvm_read (grub_disk_t disk, grub_disk_addr_t sector,
grub_size_t size, char *buf)
{
grub_err_t err = 0;
struct grub_lvm_lv *lv = disk->data;
struct grub_lvm_vg *vg = lv->vg;
struct grub_lvm_segment *seg = lv->segments;
struct grub_lvm_pv *pv;
grub_uint64_t offset;
grub_uint64_t extent;
unsigned int i;
extent = grub_divmod64 (sector, vg->extent_size, NULL);
/* Find the right segment. */
for (i = 0; i < lv->segment_count; i++)
{
if ((seg->start_extent <= extent)
&& ((seg->start_extent + seg->extent_count) > extent))
{
break;
}
seg++;
}
if (seg->stripe_count == 1)
{
/* This segment is linear, so that's easy. We just need to find
out the offset in the physical volume and read SIZE bytes
from that. */
struct grub_lvm_stripe *stripe = seg->stripes;
grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
pv = stripe->pv;
seg_offset = ((grub_uint64_t) stripe->start
* (grub_uint64_t) vg->extent_size) + pv->start;
offset = sector - ((grub_uint64_t) seg->start_extent
* (grub_uint64_t) vg->extent_size) + seg_offset;
}
else
{
/* This is a striped segment. We have to find the right PV
similar to RAID0. */
struct grub_lvm_stripe *stripe = seg->stripes;
grub_uint32_t a, b;
grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
unsigned int stripenr;
offset = sector - ((grub_uint64_t) seg->start_extent
* (grub_uint64_t) vg->extent_size);
a = grub_divmod64 (offset, seg->stripe_size, NULL);
grub_divmod64 (a, seg->stripe_count, &stripenr);
a = grub_divmod64 (offset, seg->stripe_size * seg->stripe_count, NULL);
grub_divmod64 (offset, seg->stripe_size, &b);
offset = a * seg->stripe_size + b;
stripe += stripenr;
pv = stripe->pv;
seg_offset = ((grub_uint64_t) stripe->start
* (grub_uint64_t) vg->extent_size) + pv->start;
offset += seg_offset;
}
/* Check whether we actually know the physical volume we want to
read from. */
if (pv->disk)
err = grub_disk_read (pv->disk, offset, 0,
size << GRUB_DISK_SECTOR_BITS, buf);
else
err = grub_error (GRUB_ERR_UNKNOWN_DEVICE,
"Physical volume %s not found", pv->name);
return err;
}
static grub_err_t
grub_lvm_write (grub_disk_t disk __attribute ((unused)),
grub_disk_addr_t sector __attribute ((unused)),
grub_size_t size __attribute ((unused)),
const char *buf __attribute ((unused)))
{
return GRUB_ERR_NOT_IMPLEMENTED_YET;
}
static int
grub_lvm_scan_device (const char *name)
{
grub_err_t err;
grub_disk_t disk;
grub_uint64_t da_offset, da_size, mda_offset, mda_size;
char buf[GRUB_LVM_LABEL_SIZE];
char vg_id[GRUB_LVM_ID_STRLEN+1];
char pv_id[GRUB_LVM_ID_STRLEN+1];
char *metadatabuf, *p, *q, *vgname;
struct grub_lvm_label_header *lh = (struct grub_lvm_label_header *) buf;
struct grub_lvm_pv_header *pvh;
struct grub_lvm_disk_locn *dlocn;
struct grub_lvm_mda_header *mdah;
struct grub_lvm_raw_locn *rlocn;
unsigned int i, j, vgname_len;
struct grub_lvm_vg *vg;
struct grub_lvm_pv *pv;
disk = grub_disk_open (name);
if (!disk)
return 0;
/* Search for label. */
for (i = 0; i < GRUB_LVM_LABEL_SCAN_SECTORS; i++)
{
err = grub_disk_read (disk, i, 0, sizeof(buf), buf);
if (err)
goto fail;
if ((! grub_strncmp ((char *)lh->id, GRUB_LVM_LABEL_ID,
sizeof (lh->id)))
&& (! grub_strncmp ((char *)lh->type, GRUB_LVM_LVM2_LABEL,
sizeof (lh->type))))
break;
}
/* Return if we didn't find a label. */
if (i == GRUB_LVM_LABEL_SCAN_SECTORS)
goto fail;
pvh = (struct grub_lvm_pv_header *) (buf + grub_le_to_cpu32(lh->offset_xl));
for (i = 0, j = 0; i < GRUB_LVM_ID_LEN; i++)
{
pv_id[j++] = pvh->pv_uuid[i];
if ((i != 1) && (i != 29) && (i % 4 == 1))
pv_id[j++] = '-';
}
pv_id[j] = '\0';
dlocn = pvh->disk_areas_xl;
da_offset = grub_le_to_cpu64 (dlocn->offset);
da_size = grub_le_to_cpu64 (dlocn->size);
dlocn++;
/* Is it possible to have multiple data/metadata areas? I haven't
seen devices that have it. */
if (dlocn->offset)
{
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"We don't support multiple data areas");
goto fail;
}
dlocn++;
mda_offset = grub_le_to_cpu64 (dlocn->offset);
mda_size = grub_le_to_cpu64 (dlocn->size);
dlocn++;
if (dlocn->offset)
{
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"We don't support multiple metadata areas");
goto fail;
}
metadatabuf = grub_malloc (mda_size);
if (! metadatabuf)
goto fail;
err = grub_disk_read (disk, 0, mda_offset, mda_size, metadatabuf);
if (err)
goto fail2;
mdah = (struct grub_lvm_mda_header *) metadatabuf;
if ((grub_strncmp ((char *)mdah->magic, GRUB_LVM_FMTT_MAGIC,
sizeof (mdah->magic)))
|| (grub_le_to_cpu32 (mdah->version) != GRUB_LVM_FMTT_VERSION))
{
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"Unknown metadata header");
goto fail2;
}
rlocn = mdah->raw_locns;
p = q = metadatabuf + grub_le_to_cpu64 (rlocn->offset);
while (*q != ' ')
q++;
vgname_len = q - p;
vgname = grub_malloc (vgname_len + 1);
if (!vgname)
goto fail2;
grub_memcpy (vgname, p, vgname_len);
vgname[vgname_len] = '\0';
p = grub_strstr (q, "id = \"") + sizeof ("id = \"") - 1;
grub_memcpy (vg_id, p, GRUB_LVM_ID_STRLEN);
vg_id[GRUB_LVM_ID_STRLEN] = '\0';
for (vg = vgs; vg; vg = vg->next)
{
if (! grub_memcmp(vg_id, vg->id, GRUB_LVM_ID_STRLEN))
break;
}
if (! vg)
{
/* First time we see this volume group. We've to create the
whole volume group structure. */
vg = grub_malloc (sizeof (*vg));
if (! vg)
{
grub_free (vgname);
goto fail;
}
vg->name = vgname;
grub_memcpy (vg->id, vg_id, GRUB_LVM_ID_STRLEN+1);
vg->extent_size = grub_lvm_getvalue (&p, "extent_size = ");
vg->lvs = NULL;
vg->pvs = NULL;
vg->next = vgs;
vgs = vg;
p = grub_strstr (p, "physical_volumes {")
+ sizeof ("physical_volumes {") - 1;
/* Add all the pvs to the volume group. */
while (1)
{
int s;
while (grub_isspace (*p))
p++;
if (*p == '}')
break;
pv = grub_malloc (sizeof (*pv));
q = p;
while (*q != ' ')
q++;
s = q - p;
pv->name = grub_malloc (s + 1);
grub_memcpy (pv->name, p, s);
pv->name[s] = '\0';
p = grub_strstr (p, "id = \"") + sizeof("id = \"") - 1;
grub_memcpy (pv->id, p, GRUB_LVM_ID_STRLEN);
pv->id[GRUB_LVM_ID_STRLEN] = '\0';
pv->start = grub_lvm_getvalue (&p, "pe_start = ");
pv->disk = NULL;
pv->next = vg->pvs;
vg->pvs = pv;
p = grub_strchr (p, '}') + 1;
}
p = grub_strstr (p, "logical_volumes");
p += 18;
/* And add all the lvs to the volume group. */
while (1)
{
int s;
struct grub_lvm_lv *lv;
struct grub_lvm_segment *seg;
while (grub_isspace (*p))
p++;
if (*p == '}')
break;
lv = grub_malloc (sizeof (lv));
q = p;
while (*q != ' ')
q++;
s = q - p;
lv->name = grub_malloc (vgname_len + 1 + s + 1);
grub_memcpy (lv->name, vgname, vgname_len);
lv->name[vgname_len] = '-';
grub_memcpy (lv->name + vgname_len + 1, p, s);
lv->name[vgname_len + 1 + s] = '\0';
lv->size = 0;
lv->segment_count = grub_lvm_getvalue (&p, "segment_count = ");
lv->segments = grub_malloc (sizeof (*seg) * lv->segment_count);
seg = lv->segments;
for (i = 0; i < lv->segment_count; i++)
{
struct grub_lvm_stripe *stripe;
p = grub_strstr (p, "segment");
seg->start_extent = grub_lvm_getvalue (&p, "start_extent = ");
seg->extent_count = grub_lvm_getvalue (&p, "extent_count = ");
seg->stripe_count = grub_lvm_getvalue (&p, "stripe_count = ");
lv->size += seg->extent_count * vg->extent_size;
if (seg->stripe_count != 1)
seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
seg->stripes = grub_malloc (sizeof (*stripe)
* seg->stripe_count);
stripe = seg->stripes;
p = grub_strstr (p, "stripes = [")
+ sizeof("stripes = [") - 1;
for (j = 0; j < seg->stripe_count; j++)
{
char pvname[10];
q = p = grub_strchr (p, '"') + 1;
while (*q != '"')
q++;
s = q - p;
grub_memcpy (pvname, p, s);
pvname[s] = '\0';
for (pv = vg->pvs; pv; pv = pv->next)
{
if (! grub_strcmp (pvname, pv->name))
{
stripe->pv = pv;
break;
}
}
p = grub_strchr (p, ',') + 1;
stripe->start = grub_strtoul (p, NULL, 10);
stripe++;
}
seg++;
}
lv->number = lv_count++;
lv->vg = vg;
lv->next = vg->lvs;
vg->lvs = lv;
p = grub_strchr (p, '}') + 3;
}
}
else
{
grub_free (vgname);
}
/* Match the device we are currently reading from with the right
PV. */
for (pv = vg->pvs; pv; pv = pv->next)
{
if (! grub_memcmp (pv->id, pv_id, GRUB_LVM_ID_STRLEN))
{
pv->disk = grub_disk_open (name);
break;
}
}
fail2:
grub_free (metadatabuf);
fail:
grub_disk_close (disk);
return 0;
}
static struct grub_disk_dev grub_lvm_dev =
{
.name = "lvm",
.id = GRUB_DISK_DEVICE_LVM_ID,
.iterate = grub_lvm_iterate,
.open = grub_lvm_open,
.close = grub_lvm_close,
.read = grub_lvm_read,
.write = grub_lvm_write,
.next = 0
};
GRUB_MOD_INIT(lvm)
{
grub_device_iterate (&grub_lvm_scan_device);
grub_disk_dev_register (&grub_lvm_dev);
}
GRUB_MOD_FINI(lvm)
{
grub_disk_dev_unregister (&grub_lvm_dev);
/* FIXME: free the lvm list. */
}

543
disk/raid.c Normal file
View file

@ -0,0 +1,543 @@
/* raid.c - module to read RAID arrays. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2006 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 2 of the License, or
* (at your option) any later version.
*
* This program 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/misc.h>
#include <grub/raid.h>
/* Linked list of RAID arrays. */
static struct grub_raid_array *array_list;
static char
grub_is_array_readable (struct grub_raid_array *array)
{
switch (array->level)
{
case 0:
if (array->nr_devs == array->total_devs)
return 1;
break;
case 1:
if (array->nr_devs >= 1)
return 1;
break;
case 5:
if (array->nr_devs >= array->total_devs - 1)
return 1;
break;
}
return 0;
}
static int
grub_raid_iterate (int (*hook) (const char *name))
{
struct grub_raid_array *array;
for (array = array_list; array != NULL; array = array->next)
{
if (grub_is_array_readable (array))
if (hook (array->name))
return 1;
}
return 0;
}
static grub_err_t
grub_raid_open (const char *name, grub_disk_t disk)
{
struct grub_raid_array *array;
for (array = array_list; array != NULL; array = array->next)
{
if (!grub_strcmp (array->name, name))
if (grub_is_array_readable (array))
break;
}
if (!array)
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "Unknown device");
/* FIXME: superblock version 1 supports partitions. */
disk->has_partitions = 0;
disk->id = array->number;
disk->data = array;
switch (array->level)
{
case 0:
/* FIXME: RAID0 disks can have different sizes! */
disk->total_sectors = array->total_devs * array->disk_size;
break;
case 1:
disk->total_sectors = array->disk_size;
break;
case 5:
disk->total_sectors = (array->total_devs - 1) * array->disk_size;
break;
}
return 0;
}
static void
grub_raid_close (grub_disk_t disk __attribute ((unused)))
{
return;
}
static grub_err_t
grub_raid_read (grub_disk_t disk, grub_disk_addr_t sector,
grub_size_t size, char *buf)
{
struct grub_raid_array *array = disk->data;
grub_err_t err = 0;
switch (array->level)
{
case 0:
{
grub_uint64_t a;
grub_uint32_t b;
unsigned int disknr;
grub_disk_addr_t read_sector;
grub_size_t read_size;
/* Find the first sector to read. */
a = grub_divmod64 (sector, array->chunk_size, NULL);
grub_divmod64 (a, array->total_devs, &disknr);
a = grub_divmod64 (sector, array->chunk_size * array->total_devs, NULL);
grub_divmod64 (sector, array->chunk_size, &b);
read_sector = a * array->chunk_size + b;
grub_divmod64 (read_sector, array->chunk_size, &b);
read_size = array->chunk_size - b;
if (read_size > size)
read_size = size;
while (1)
{
grub_uint32_t i;
err = grub_disk_read (array->device[disknr].disk, read_sector, 0,
read_size << GRUB_DISK_SECTOR_BITS, buf);
if (err)
break;
buf += read_size;
size -= read_size;
if (! size)
break;
if (size > array->chunk_size)
read_size = array->chunk_size;
else
read_size = size;
/* Check whether the sector was aligned on a chunk size
bounday. If this isn't the case, it's the first read
and the next read should be set back to start of the
boundary. */
grub_divmod64 (read_sector, array->chunk_size, &i);
read_sector -= i;
disknr++;
/* See whether the disk was the last disk, and start
reading from the first disk in that case. */
if (disknr == array->total_devs)
{
disknr = 0;
read_sector += array->chunk_size;
}
}
}
break;
case 1:
/* This is easy, we can read from any disk we want. We will loop
over all disks until we've found one that is available. In
case of errs, we will try the to read the next disk. */
{
unsigned int i = 0;
for (i = 0; i < array->total_devs; i++)
{
if (array->device[i].disk)
{
err = grub_disk_read (array->device[i].disk, sector, 0,
size << GRUB_DISK_SECTOR_BITS, buf);
if (!err)
break;
}
}
}
break;
case 5:
{
grub_uint64_t a;
grub_uint32_t b;
int disknr;
grub_disk_addr_t read_sector;
grub_size_t read_size;
/* Find the first sector to read. */
a = grub_divmod64 (sector, array->chunk_size, NULL);
grub_divmod64 (a, (array->total_devs - 1), &b);
disknr = b;
a = grub_divmod64 (sector, array->chunk_size * (array->total_devs - 1),
NULL);
grub_divmod64 (sector, array->chunk_size, &b);
read_sector = a * array->chunk_size + b;
grub_divmod64 (read_sector, array->chunk_size * array->total_devs, &b);
disknr -= (b / array->chunk_size);
if (disknr < 0)
disknr += array->total_devs;
grub_divmod64 (read_sector, array->chunk_size, &b);
read_size = array->chunk_size - b;
if (read_size > size)
read_size = size;
while (1)
{
grub_uint32_t i;
if (array->device[disknr].disk)
err = grub_disk_read (array->device[disknr].disk, read_sector, 0,
read_size << GRUB_DISK_SECTOR_BITS, buf);
/* If an error occurs when we already have an degraded
array we can't recover from that. */
if (err && ((array->total_devs - 1) == array->nr_devs))
break;
if (err || ! array->device[disknr].disk)
{
/* Either an error occured or the disk is not
available. We have to compute this block from the
blocks on the other hard disks. */
grub_size_t buf_size = read_size << GRUB_DISK_SECTOR_BITS;
char buf2[buf_size];
unsigned int j;
grub_memset (buf, 0, buf_size);
for (j = 0; j < array->total_devs; j++)
{
unsigned int k;
if (j != (unsigned int) disknr)
{
err = grub_disk_read (array->device[j].disk, read_sector,
0, buf_size, buf2);
if (err)
return err;
for (k = 0; k < buf_size; k++)
buf[k] = buf[k] ^ buf2[k];
}
}
}
buf += (read_size << GRUB_DISK_SECTOR_BITS);
size -= read_size;
if (! size)
break;
if (size > array->chunk_size)
read_size = array->chunk_size;
else
read_size = size;
/* Check whether the sector was aligned on a chunk size
bounday. If this isn't the case, it's the first read
and the next read should be set back to start of the
boundary. */
grub_divmod64 (read_sector, array->chunk_size, &i);
read_sector -= i;
disknr++;
grub_divmod64 (read_sector,
array->chunk_size * array->total_devs, &i);
if ((unsigned int) disknr == (array->total_devs - (i / array->chunk_size) - 1))
disknr++;
/* See whether the disk was the last disk, and start
reading from the first disk in that case. */
if ((unsigned int) disknr == array->total_devs)
{
disknr = 0;
read_sector += array->chunk_size;
grub_divmod64 (read_sector,
array->chunk_size * array->total_devs, &i);
if ((i / array->chunk_size) == (array->total_devs - 1))
disknr++;
}
}
}
break;
}
return err;
}
static grub_err_t
grub_raid_write (grub_disk_t disk __attribute ((unused)),
grub_disk_addr_t sector __attribute ((unused)),
grub_size_t size __attribute ((unused)),
const char *buf __attribute ((unused)))
{
return GRUB_ERR_NOT_IMPLEMENTED_YET;
}
static int
grub_raid_scan_device (const char *name)
{
grub_err_t err;
grub_disk_t disk;
grub_disk_addr_t sector;
grub_uint64_t size;
struct grub_raid_super_09 sb;
struct grub_raid_array *p, *array = NULL;
disk = grub_disk_open (name);
if (!disk)
return 0;
/* The sector where the RAID superblock is stored, if available. */
size = grub_disk_get_size (disk);
sector = GRUB_RAID_NEW_SIZE_SECTORS(size);
err = grub_disk_read (disk, sector, 0, GRUB_RAID_SB_BYTES, (char *) &sb);
grub_disk_close (disk);
if (err)
return 0;
/* Look whether there is a RAID superblock. */
if (sb.md_magic != GRUB_RAID_SB_MAGIC)
return 0;
/* FIXME: Also support version 1.0. */
if (sb.major_version != 0 || sb.minor_version != 90)
{
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"Unsupported RAID version: %d.%d",
sb.major_version, sb.minor_version);
return 0;
}
/* FIXME: Check the checksum. */
/* FIXME: Support all RAID levels. */
if (sb.level != 0 && sb.level != 1 && sb.level != 5)
{
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"Unsupported RAID level: %d",
sb.level);
return 0;
}
/* FIXME: Support all layouts. */
if (sb.level == 5 && sb.layout != 2)
{
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"Unsupported RAID5 layout: %d",
sb.layout);
return 0;
}
/* See whether the device is part of an array we have already seen a
device from. */
for (p = array_list; p != NULL; p = p->next)
{
if (p->uuid[0] == sb.set_uuid0 && p->uuid[1] == sb.set_uuid1
&& p->uuid[2] == sb.set_uuid2 && p->uuid[3] == sb.set_uuid3)
{
array = p;
break;
}
}
/* Do some checks before adding the device to the array. */
if (array)
{
/* FIXME: Check whether the update time of the superblocks are
the same. */
if (array->total_devs == array->nr_devs)
{
/* We found more members of the array than the array
actually has according to its superblock. This shouldn't
happen normally, but what is the sanest things to do in such
a case? */
grub_error (GRUB_ERR_BAD_NUMBER,
"array->nr_devs > array->total_devs (%d)?!?",
array->total_devs);
return 0;
}
if (array->device[sb.this_disk.number].name != 0)
{
/* We found multiple devices with the same number. Again,
this shouldn't happen.*/
grub_error (GRUB_ERR_BAD_NUMBER,
"Found two disks with the number %d?!?",
sb.this_disk.number);
return 0;
}
}
/* Add an array to the list if we didn't find any. */
if (!array)
{
array = grub_malloc (sizeof (*array));
if (!array)
return 0;
grub_memset (array, 0, sizeof (*array));
array->number = sb.md_minor;
array->version = sb.major_version;
array->level = sb.level;
array->layout = sb.layout;
array->total_devs = sb.nr_disks;
array->nr_devs = 0;
array->uuid[0] = sb.set_uuid0;
array->uuid[1] = sb.set_uuid1;
array->uuid[2] = sb.set_uuid2;
array->uuid[3] = sb.set_uuid3;
/* The superblock specifies the size in 1024-byte sectors. */
array->disk_size = sb.size * 2;
array->chunk_size = sb.chunk_size / 512;
/* Check whether we don't have multiple arrays with the same number. */
for (p = array_list; p != NULL; p = p->next)
{
if (p->number == array->number)
break;
}
if (p)
{
/* The number is already in use, so we need to find an new number. */
int i = 0;
while (1)
{
for (p = array_list; p != NULL; p = p->next)
{
if (p->number == i)
break;
}
if (!p)
{
/* We found an unused number. */
array->number = i;
break;
}
i++;
}
}
array->name = grub_malloc (13);
if (! array->name)
{
grub_free (array);
return 0;
}
grub_sprintf (array->name, "md%d", array->number);
/* Add our new array to the list. */
array->next = array_list;
array_list = array;
}
/* Add the device to the array. */
array->device[sb.this_disk.number].name = grub_strdup (name);
array->device[sb.this_disk.number].disk = grub_disk_open (name);
if (! array->device[sb.this_disk.number].name
|| ! array->device[sb.this_disk.number].disk)
{
grub_free (array->device[sb.this_disk.number].name);
/* Remove array from the list if we have just added it. */
if (array->nr_devs == 0)
{
array_list = array->next;
grub_free (array->name);
grub_free (array);
}
return 0;
}
array->nr_devs++;
return 0;
}
static struct grub_disk_dev grub_raid_dev =
{
.name = "raid",
.id = GRUB_DISK_DEVICE_RAID_ID,
.iterate = grub_raid_iterate,
.open = grub_raid_open,
.close = grub_raid_close,
.read = grub_raid_read,
.write = grub_raid_write,
.next = 0
};
GRUB_MOD_INIT(raid)
{
grub_device_iterate (&grub_raid_scan_device);
grub_disk_dev_register (&grub_raid_dev);
}
GRUB_MOD_FINI(raid)
{
grub_disk_dev_unregister (&grub_raid_dev);
/* FIXME: free the array list. */
}