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:
parent
050548d018
commit
2b00217369
23 changed files with 2347 additions and 100 deletions
497
disk/lvm.c
Normal file
497
disk/lvm.c
Normal 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
543
disk/raid.c
Normal 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. */
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue