691 lines
17 KiB
C
691 lines
17 KiB
C
/* raid.c - module to read RAID arrays. */
|
||
/*
|
||
* GRUB -- GRand Unified Bootloader
|
||
* Copyright (C) 2006,2007,2008,2009 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/raid.h>
|
||
|
||
/* Linked list of RAID arrays. */
|
||
static struct grub_raid_array *array_list;
|
||
grub_raid5_recover_func_t grub_raid5_recover_func;
|
||
grub_raid6_recover_func_t grub_raid6_recover_func;
|
||
|
||
|
||
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 4:
|
||
case 5:
|
||
case 6:
|
||
case 10:
|
||
{
|
||
unsigned int n;
|
||
|
||
if (array->level == 10)
|
||
{
|
||
n = array->layout & 0xFF;
|
||
if (n == 1)
|
||
n = (array->layout >> 8) & 0xFF;
|
||
|
||
n--;
|
||
}
|
||
else
|
||
n = array->level / 3;
|
||
|
||
if (array->nr_devs >= array->total_devs - n)
|
||
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;
|
||
}
|
||
|
||
#ifdef GRUB_UTIL
|
||
static grub_disk_memberlist_t
|
||
grub_raid_memberlist (grub_disk_t disk)
|
||
{
|
||
struct grub_raid_array *array = disk->data;
|
||
grub_disk_memberlist_t list = NULL, tmp;
|
||
unsigned int i;
|
||
|
||
for (i = 0; i < array->total_devs; i++)
|
||
if (array->device[i])
|
||
{
|
||
tmp = grub_malloc (sizeof (*tmp));
|
||
tmp->disk = array->device[i];
|
||
tmp->next = list;
|
||
list = tmp;
|
||
}
|
||
|
||
return list;
|
||
}
|
||
#endif
|
||
|
||
static grub_err_t
|
||
grub_raid_open (const char *name, grub_disk_t disk)
|
||
{
|
||
struct grub_raid_array *array;
|
||
unsigned n;
|
||
|
||
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 RAID device %s",
|
||
name);
|
||
|
||
disk->has_partitions = 1;
|
||
disk->id = array->number;
|
||
disk->data = array;
|
||
|
||
grub_dprintf ("raid", "%s: total_devs=%d, disk_size=%lld\n", name,
|
||
array->total_devs, (unsigned long long) array->disk_size);
|
||
|
||
switch (array->level)
|
||
{
|
||
case 1:
|
||
disk->total_sectors = array->disk_size;
|
||
break;
|
||
|
||
case 10:
|
||
n = array->layout & 0xFF;
|
||
if (n == 1)
|
||
n = (array->layout >> 8) & 0xFF;
|
||
|
||
disk->total_sectors = grub_divmod64 (array->total_devs *
|
||
array->disk_size,
|
||
n, 0);
|
||
break;
|
||
|
||
case 0:
|
||
case 4:
|
||
case 5:
|
||
case 6:
|
||
n = array->level / 3;
|
||
|
||
disk->total_sectors = (array->total_devs - n) * array->disk_size;
|
||
break;
|
||
}
|
||
|
||
grub_dprintf ("raid", "%s: level=%d, total_sectors=%lld\n", name,
|
||
array->level, (unsigned long long) disk->total_sectors);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void
|
||
grub_raid_close (grub_disk_t disk __attribute ((unused)))
|
||
{
|
||
return;
|
||
}
|
||
|
||
void
|
||
grub_raid_block_xor (char *buf1, const char *buf2, int size)
|
||
{
|
||
grub_size_t *p1;
|
||
const grub_size_t *p2;
|
||
|
||
p1 = (grub_size_t *) buf1;
|
||
p2 = (const grub_size_t *) buf2;
|
||
size /= GRUB_CPU_SIZEOF_VOID_P;
|
||
|
||
while (size)
|
||
{
|
||
*(p1++) ^= *(p2++);
|
||
size--;
|
||
}
|
||
}
|
||
|
||
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:
|
||
case 1:
|
||
case 10:
|
||
{
|
||
grub_disk_addr_t read_sector, far_ofs;
|
||
grub_uint32_t disknr, b, near, far, ofs;
|
||
|
||
read_sector = grub_divmod64 (sector, array->chunk_size, &b);
|
||
far = ofs = near = 1;
|
||
far_ofs = 0;
|
||
|
||
if (array->level == 1)
|
||
near = array->total_devs;
|
||
else if (array->level == 10)
|
||
{
|
||
near = array->layout & 0xFF;
|
||
far = (array->layout >> 8) & 0xFF;
|
||
if (array->layout >> 16)
|
||
{
|
||
ofs = far;
|
||
far_ofs = 1;
|
||
}
|
||
else
|
||
far_ofs = grub_divmod64 (array->disk_size,
|
||
far * array->chunk_size, 0);
|
||
|
||
far_ofs *= array->chunk_size;
|
||
}
|
||
|
||
read_sector = grub_divmod64 (read_sector * near, array->total_devs,
|
||
&disknr);
|
||
|
||
ofs *= array->chunk_size;
|
||
read_sector *= ofs;
|
||
|
||
while (1)
|
||
{
|
||
grub_size_t read_size;
|
||
unsigned int i, j;
|
||
|
||
read_size = array->chunk_size - b;
|
||
if (read_size > size)
|
||
read_size = size;
|
||
|
||
for (i = 0; i < near; i++)
|
||
{
|
||
unsigned int k;
|
||
|
||
k = disknr;
|
||
for (j = 0; j < far; j++)
|
||
{
|
||
if (array->device[k])
|
||
{
|
||
if (grub_errno == GRUB_ERR_READ_ERROR)
|
||
grub_errno = GRUB_ERR_NONE;
|
||
|
||
err = grub_disk_read (array->device[k],
|
||
read_sector + j * far_ofs + b,
|
||
0,
|
||
read_size << GRUB_DISK_SECTOR_BITS,
|
||
buf);
|
||
if (! err)
|
||
break;
|
||
else if (err != GRUB_ERR_READ_ERROR)
|
||
return err;
|
||
}
|
||
else
|
||
err = grub_error (GRUB_ERR_READ_ERROR,
|
||
"disk missing");
|
||
|
||
k++;
|
||
if (k == array->total_devs)
|
||
k = 0;
|
||
}
|
||
|
||
if (! err)
|
||
break;
|
||
|
||
disknr++;
|
||
if (disknr == array->total_devs)
|
||
{
|
||
disknr = 0;
|
||
read_sector += ofs;
|
||
}
|
||
}
|
||
|
||
if (err)
|
||
return err;
|
||
|
||
buf += read_size << GRUB_DISK_SECTOR_BITS;
|
||
size -= read_size;
|
||
if (! size)
|
||
break;
|
||
|
||
b = 0;
|
||
disknr += (near - i);
|
||
while (disknr >= array->total_devs)
|
||
{
|
||
disknr -= array->total_devs;
|
||
read_sector += ofs;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
case 4:
|
||
case 5:
|
||
case 6:
|
||
{
|
||
grub_disk_addr_t read_sector;
|
||
grub_uint32_t b, p, n, disknr, e;
|
||
|
||
/* n = 1 for level 4 and 5, 2 for level 6. */
|
||
n = array->level / 3;
|
||
|
||
/* Find the first sector to read. */
|
||
read_sector = grub_divmod64 (sector, array->chunk_size, &b);
|
||
read_sector = grub_divmod64 (read_sector, array->total_devs - n,
|
||
&disknr);
|
||
if (array->level >= 5)
|
||
{
|
||
grub_divmod64 (read_sector, array->total_devs, &p);
|
||
|
||
if (! (array->layout & GRUB_RAID_LAYOUT_RIGHT_MASK))
|
||
p = array->total_devs - 1 - p;
|
||
|
||
if (array->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK)
|
||
{
|
||
disknr += p + n;
|
||
}
|
||
else
|
||
{
|
||
grub_uint32_t q;
|
||
|
||
q = p + (n - 1);
|
||
if (q >= array->total_devs)
|
||
q -= array->total_devs;
|
||
|
||
if (disknr >= p)
|
||
disknr += n;
|
||
else if (disknr >= q)
|
||
disknr += q + 1;
|
||
}
|
||
|
||
if (disknr >= array->total_devs)
|
||
disknr -= array->total_devs;
|
||
}
|
||
else
|
||
p = array->total_devs - n;
|
||
|
||
read_sector *= array->chunk_size;
|
||
|
||
while (1)
|
||
{
|
||
grub_size_t read_size;
|
||
int next_level;
|
||
|
||
read_size = array->chunk_size - b;
|
||
if (read_size > size)
|
||
read_size = size;
|
||
|
||
e = 0;
|
||
if (array->device[disknr])
|
||
{
|
||
/* Reset read error. */
|
||
if (grub_errno == GRUB_ERR_READ_ERROR)
|
||
grub_errno = GRUB_ERR_NONE;
|
||
|
||
err = grub_disk_read (array->device[disknr],
|
||
read_sector + b, 0,
|
||
read_size << GRUB_DISK_SECTOR_BITS,
|
||
buf);
|
||
|
||
if ((err) && (err != GRUB_ERR_READ_ERROR))
|
||
break;
|
||
e++;
|
||
}
|
||
else
|
||
err = GRUB_ERR_READ_ERROR;
|
||
|
||
if (err)
|
||
{
|
||
if (array->nr_devs < array->total_devs - n + e)
|
||
break;
|
||
|
||
grub_errno = GRUB_ERR_NONE;
|
||
if (array->level == 6)
|
||
{
|
||
err = ((grub_raid6_recover_func) ?
|
||
(*grub_raid6_recover_func) (array, disknr, p,
|
||
buf, read_sector + b,
|
||
read_size) :
|
||
grub_error (GRUB_ERR_BAD_DEVICE,
|
||
"raid6rec is not loaded"));
|
||
}
|
||
else
|
||
{
|
||
err = ((grub_raid5_recover_func) ?
|
||
(*grub_raid5_recover_func) (array, disknr,
|
||
buf, read_sector + b,
|
||
read_size) :
|
||
grub_error (GRUB_ERR_BAD_DEVICE,
|
||
"raid5rec is not loaded"));
|
||
}
|
||
|
||
if (err)
|
||
break;
|
||
}
|
||
|
||
buf += read_size << GRUB_DISK_SECTOR_BITS;
|
||
size -= read_size;
|
||
if (! size)
|
||
break;
|
||
|
||
b = 0;
|
||
disknr++;
|
||
|
||
if (array->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK)
|
||
{
|
||
if (disknr == array->total_devs)
|
||
disknr = 0;
|
||
|
||
next_level = (disknr == p);
|
||
}
|
||
else
|
||
{
|
||
if (disknr == p)
|
||
disknr += n;
|
||
|
||
next_level = (disknr >= array->total_devs);
|
||
}
|
||
|
||
if (next_level)
|
||
{
|
||
read_sector += array->chunk_size;
|
||
|
||
if (array->level >= 5)
|
||
{
|
||
if (array->layout & GRUB_RAID_LAYOUT_RIGHT_MASK)
|
||
p = (p == array->total_devs - 1) ? 0 : p + 1;
|
||
else
|
||
p = (p == 0) ? array->total_devs - 1 : p - 1;
|
||
|
||
if (array->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK)
|
||
{
|
||
disknr = p + n;
|
||
if (disknr >= array->total_devs)
|
||
disknr -= array->total_devs;
|
||
}
|
||
else
|
||
{
|
||
disknr -= array->total_devs;
|
||
if (disknr == p)
|
||
disknr += n;
|
||
}
|
||
}
|
||
else
|
||
disknr = 0;
|
||
}
|
||
}
|
||
}
|
||
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 grub_err_t
|
||
insert_array (grub_disk_t disk, struct grub_raid_array *new_array,
|
||
const char *scanner_name)
|
||
{
|
||
struct grub_raid_array *array = 0, *p;
|
||
|
||
/* 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_len == new_array->uuid_len) &&
|
||
(! grub_memcmp (p->uuid, new_array->uuid, p->uuid_len)))
|
||
{
|
||
grub_free (new_array->uuid);
|
||
array = p;
|
||
|
||
/* Do some checks before adding the device to the 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. */
|
||
grub_dprintf ("raid", "array->nr_devs > array->total_devs (%d)?!?",
|
||
array->total_devs);
|
||
|
||
if (array->device[new_array->index] != NULL)
|
||
/* We found multiple devices with the same number. Again,
|
||
this shouldn't happen.*/
|
||
grub_dprintf ("raid", "Found two disks with the number %d?!?",
|
||
new_array->number);
|
||
|
||
if (new_array->disk_size < array->disk_size)
|
||
array->disk_size = new_array->disk_size;
|
||
break;
|
||
}
|
||
|
||
/* Add an array to the list if we didn't find any. */
|
||
if (!array)
|
||
{
|
||
array = grub_malloc (sizeof (*array));
|
||
if (!array)
|
||
{
|
||
grub_free (new_array->uuid);
|
||
return grub_errno;
|
||
}
|
||
|
||
*array = *new_array;
|
||
array->nr_devs = 0;
|
||
grub_memset (&array->device, 0, sizeof (array->device));
|
||
|
||
/* 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_asprintf ("md%d", array->number);
|
||
if (! array->name)
|
||
{
|
||
grub_free (array->uuid);
|
||
grub_free (array);
|
||
|
||
return grub_errno;
|
||
}
|
||
|
||
grub_dprintf ("raid", "Found array %s (%s)\n", array->name,
|
||
scanner_name);
|
||
|
||
/* Add our new array to the list. */
|
||
array->next = array_list;
|
||
array_list = array;
|
||
|
||
/* RAID 1 doesn't use a chunksize but code assumes one so set
|
||
one. */
|
||
if (array->level == 1)
|
||
array->chunk_size = 64;
|
||
}
|
||
|
||
/* Add the device to the array. */
|
||
array->device[new_array->index] = disk;
|
||
array->nr_devs++;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static grub_raid_t grub_raid_list;
|
||
|
||
static void
|
||
free_array (void)
|
||
{
|
||
struct grub_raid_array *array;
|
||
|
||
array = array_list;
|
||
while (array)
|
||
{
|
||
struct grub_raid_array *p;
|
||
int i;
|
||
|
||
p = array;
|
||
array = array->next;
|
||
|
||
for (i = 0; i < GRUB_RAID_MAX_DEVICES; i++)
|
||
if (p->device[i])
|
||
grub_disk_close (p->device[i]);
|
||
|
||
grub_free (p->uuid);
|
||
grub_free (p->name);
|
||
grub_free (p);
|
||
}
|
||
|
||
array_list = 0;
|
||
}
|
||
|
||
void
|
||
grub_raid_register (grub_raid_t raid)
|
||
{
|
||
auto int hook (const char *name);
|
||
int hook (const char *name)
|
||
{
|
||
grub_disk_t disk;
|
||
struct grub_raid_array array;
|
||
|
||
grub_dprintf ("raid", "Scanning for RAID devices on disk %s\n", name);
|
||
|
||
disk = grub_disk_open (name);
|
||
if (!disk)
|
||
return 0;
|
||
|
||
if ((disk->total_sectors != GRUB_ULONG_MAX) &&
|
||
(! grub_raid_list->detect (disk, &array)) &&
|
||
(! insert_array (disk, &array, grub_raid_list->name)))
|
||
return 0;
|
||
|
||
/* This error usually means it's not raid, no need to display
|
||
it. */
|
||
if (grub_errno != GRUB_ERR_OUT_OF_RANGE)
|
||
grub_print_error ();
|
||
|
||
grub_errno = GRUB_ERR_NONE;
|
||
|
||
grub_disk_close (disk);
|
||
|
||
return 0;
|
||
}
|
||
|
||
raid->next = grub_raid_list;
|
||
grub_raid_list = raid;
|
||
grub_device_iterate (&hook);
|
||
}
|
||
|
||
void
|
||
grub_raid_unregister (grub_raid_t raid)
|
||
{
|
||
grub_raid_t *p, q;
|
||
|
||
for (p = &grub_raid_list, q = *p; q; p = &(q->next), q = q->next)
|
||
if (q == raid)
|
||
{
|
||
*p = q->next;
|
||
break;
|
||
}
|
||
}
|
||
|
||
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,
|
||
#ifdef GRUB_UTIL
|
||
.memberlist = grub_raid_memberlist,
|
||
#endif
|
||
.next = 0
|
||
};
|
||
|
||
|
||
GRUB_MOD_INIT(raid)
|
||
{
|
||
grub_disk_dev_register (&grub_raid_dev);
|
||
}
|
||
|
||
GRUB_MOD_FINI(raid)
|
||
{
|
||
grub_disk_dev_unregister (&grub_raid_dev);
|
||
free_array ();
|
||
}
|