From 041d1ea37802bf7178a31a53f96c26efa6b8fb7b Mon Sep 17 00:00:00 2001 From: James Date: Fri, 16 Nov 2012 10:41:01 +0000 Subject: fish --- grub-core/disk/raid.c | 812 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 812 insertions(+) create mode 100644 grub-core/disk/raid.c (limited to 'grub-core/disk/raid.c') diff --git a/grub-core/disk/raid.c b/grub-core/disk/raid.c new file mode 100644 index 0000000..946e6d2 --- /dev/null +++ b/grub-core/disk/raid.c @@ -0,0 +1,812 @@ +/* raid.c - module to read RAID arrays. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007,2008,2009,2010 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 . + */ + +#include +#include +#include +#include +#include +#include +#ifdef GRUB_UTIL +#include +#endif + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* 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->members[i].device) + { + tmp = grub_malloc (sizeof (*tmp)); + tmp->disk = array->members[i].device; + tmp->next = list; + list = tmp; + } + + return list; +} + +static const char * +grub_raid_getname (struct grub_disk *disk) +{ + struct grub_raid_array *array = disk->data; + + return array->driver->name; +} +#endif + +static inline int +ascii2hex (char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return 0; +} + +static grub_err_t +grub_raid_open (const char *name, grub_disk_t disk) +{ + struct grub_raid_array *array; + unsigned n; + + if (grub_memcmp (name, "mduuid/", sizeof ("mduuid/") - 1) == 0) + { + const char *uuidstr = name + sizeof ("mduuid/") - 1; + grub_size_t uuid_len = grub_strlen (uuidstr) / 2; + grub_uint8_t uuidbin[uuid_len]; + unsigned i; + for (i = 0; i < uuid_len; i++) + uuidbin[i] = ascii2hex (uuidstr[2 * i + 1]) + | (ascii2hex (uuidstr[2 * i]) << 4); + + for (array = array_list; array != NULL; array = array->next) + { + if (uuid_len == (unsigned) array->uuid_len + && grub_memcmp (uuidbin, array->uuid, uuid_len) == 0) + if (grub_is_array_readable (array)) + break; + } + } + else + 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->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->members[k].device) + { + if (grub_errno == GRUB_ERR_READ_ERROR) + grub_errno = GRUB_ERR_NONE; + + err = grub_disk_read (array->members[k].device, + array->members[k].start_sector + + 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->members[disknr].device) + { + /* Reset read error. */ + if (grub_errno == GRUB_ERR_READ_ERROR) + grub_errno = GRUB_ERR_NONE; + + err = grub_disk_read (array->members[disknr].device, + array->members[disknr].start_sector + + 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, + grub_disk_addr_t start_sector, const char *scanner_name, + grub_raid_t raid __attribute__ ((unused))) +{ + 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. */ + + if (new_array->index >= array->allocated_devs) + { + void *tmp; + unsigned int newnum = 2 * (new_array->index + 1); + tmp = grub_realloc (array->members, newnum + * sizeof (array->members[0])); + if (!tmp) + return grub_errno; + array->members = tmp; + grub_memset (array->members + array->allocated_devs, + 0, (newnum - array->allocated_devs) + * sizeof (array->members[0])); + array->allocated_devs = newnum; + } + + /* 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. */ + return grub_error (GRUB_ERR_BAD_DEVICE, + "superfluous RAID member (%d found)", + array->total_devs); + + if (array->members[new_array->index].device != NULL) + /* We found multiple devices with the same number. Again, + this shouldn't happen. */ + return grub_error (GRUB_ERR_BAD_DEVICE, + "found two disks with the index %d for RAID %s", + new_array->index, array->name); + + 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; +#ifdef GRUB_UTIL + array->driver = raid; +#endif + array->allocated_devs = 32; + if (new_array->index >= array->allocated_devs) + array->allocated_devs = 2 * (new_array->index + 1); + + array->members = grub_zalloc (array->allocated_devs + * sizeof (array->members[0])); + + if (!array->members) + { + grub_free (new_array->uuid); + return grub_errno; + } + + if (! array->name) + { + for (p = array_list; p != NULL; p = p->next) + { + if (p->number == array->number) + break; + } + } + + if (array->name || p) + { + /* The number is already in use, so we need to find a new one. + (Or, in the case of named arrays, the array doesn't have its + own number, but we need one that doesn't clash for use as a key + in the disk cache. */ + int i = array->name ? 0x40000000 : 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++; + } + } + + /* mdraid 1.x superblocks have only a name stored not a number. + Use it directly as GRUB device. */ + if (! array->name) + { + array->name = grub_xasprintf ("md%d", array->number); + if (! array->name) + { + grub_free (array->members); + grub_free (array->uuid); + grub_free (array); + + return grub_errno; + } + } + else + { + /* Strip off the homehost if present. */ + char *colon = grub_strchr (array->name, ':'); + char *new_name = grub_xasprintf ("md/%s", + colon ? colon + 1 : array->name); + + if (! new_name) + { + grub_free (array->members); + grub_free (array->uuid); + grub_free (array); + + return grub_errno; + } + + grub_free (array->name); + array->name = new_name; + } + + grub_dprintf ("raid", "Found array %s (%s)\n", array->name, + scanner_name); +#ifdef GRUB_UTIL + grub_util_info ("Found array %s (%s)", array->name, + scanner_name); +#endif + + /* 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->members[new_array->index].device = disk; + array->members[new_array->index].start_sector = start_sector; + 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; + unsigned int i; + + p = array; + array = array->next; + + for (i = 0; i < p->allocated_devs; i++) + if (p->members[i].device) + grub_disk_close (p->members[i].device); + grub_free (p->members); + + 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_disk_addr_t start_sector; + + grub_dprintf ("raid", "Scanning for %s RAID devices on disk %s\n", + grub_raid_list->name, name); +#ifdef GRUB_UTIL + grub_util_info ("Scanning for %s RAID devices on disk %s", + grub_raid_list->name, name); +#endif + + disk = grub_disk_open (name); + if (!disk) + return 0; + + if ((disk->total_sectors != GRUB_ULONG_MAX) && + (! grub_raid_list->detect (disk, &array, &start_sector)) && + (! insert_array (disk, &array, start_sector, grub_raid_list->name, + grub_raid_list))) + 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, + .raidname = grub_raid_getname, +#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 (); +} -- cgit v1.2.3