| /* diskfilter.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 <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/partition.h> |
| #ifdef GRUB_UTIL |
| #include <grub/i18n.h> |
| #include <grub/util/misc.h> |
| #endif |
| |
| GRUB_MOD_LICENSE ("GPLv3+"); |
| |
| /* Linked list of DISKFILTER arrays. */ |
| static struct grub_diskfilter_vg *array_list; |
| grub_raid5_recover_func_t grub_raid5_recover_func; |
| grub_raid6_recover_func_t grub_raid6_recover_func; |
| grub_diskfilter_t grub_diskfilter_list; |
| static int inscnt = 0; |
| static int lv_num = 0; |
| |
| static struct grub_diskfilter_lv * |
| find_lv (const char *name); |
| static int is_lv_readable (struct grub_diskfilter_lv *lv, int easily); |
| |
| |
| |
| static grub_err_t |
| is_node_readable (const struct grub_diskfilter_node *node, int easily) |
| { |
| /* Check whether we actually know the physical volume we want to |
| read from. */ |
| if (node->pv) |
| return !!(node->pv->disk); |
| if (node->lv) |
| return is_lv_readable (node->lv, easily); |
| return 0; |
| } |
| |
| static int |
| is_lv_readable (struct grub_diskfilter_lv *lv, int easily) |
| { |
| unsigned i, j; |
| if (!lv) |
| return 0; |
| for (i = 0; i < lv->segment_count; i++) |
| { |
| int need = lv->segments[i].node_count, have = 0; |
| switch (lv->segments[i].type) |
| { |
| case GRUB_DISKFILTER_RAID6: |
| if (!easily) |
| need--; |
| /* Fallthrough. */ |
| case GRUB_DISKFILTER_RAID4: |
| case GRUB_DISKFILTER_RAID5: |
| if (!easily) |
| need--; |
| /* Fallthrough. */ |
| case GRUB_DISKFILTER_STRIPED: |
| break; |
| |
| case GRUB_DISKFILTER_MIRROR: |
| need = 1; |
| break; |
| |
| case GRUB_DISKFILTER_RAID10: |
| { |
| unsigned int n; |
| n = lv->segments[i].layout & 0xFF; |
| if (n == 1) |
| n = (lv->segments[i].layout >> 8) & 0xFF; |
| need = lv->segments[i].node_count - n + 1; |
| } |
| break; |
| } |
| for (j = 0; j < lv->segments[i].node_count; j++) |
| { |
| if (is_node_readable (lv->segments[i].nodes + j, easily)) |
| have++; |
| if (have >= need) |
| break; |
| } |
| if (have < need) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static grub_err_t |
| insert_array (grub_disk_t disk, const struct grub_diskfilter_pv_id *id, |
| struct grub_diskfilter_vg *array, |
| grub_disk_addr_t start_sector, |
| grub_diskfilter_t diskfilter __attribute__ ((unused))); |
| |
| static int |
| is_valid_diskfilter_name (const char *name) |
| { |
| return (grub_memcmp (name, "md", sizeof ("md") - 1) == 0 |
| || grub_memcmp (name, "lvm/", sizeof ("lvm/") - 1) == 0 |
| || grub_memcmp (name, "lvmid/", sizeof ("lvmid/") - 1) == 0 |
| || grub_memcmp (name, "ldm/", sizeof ("ldm/") - 1) == 0); |
| } |
| |
| /* Helper for scan_disk. */ |
| static int |
| scan_disk_partition_iter (grub_disk_t disk, grub_partition_t p, void *data) |
| { |
| const char *name = data; |
| struct grub_diskfilter_vg *arr; |
| grub_disk_addr_t start_sector; |
| struct grub_diskfilter_pv_id id; |
| grub_diskfilter_t diskfilter; |
| |
| grub_dprintf ("diskfilter", "Scanning for DISKFILTER devices on disk %s\n", |
| name); |
| #ifdef GRUB_UTIL |
| grub_util_info ("Scanning for DISKFILTER devices on disk %s", name); |
| #endif |
| |
| disk->partition = p; |
| |
| for (arr = array_list; arr != NULL; arr = arr->next) |
| { |
| struct grub_diskfilter_pv *m; |
| for (m = arr->pvs; m; m = m->next) |
| if (m->disk && m->disk->id == disk->id |
| && m->disk->dev->id == disk->dev->id |
| && m->part_start == grub_partition_get_start (disk->partition) |
| && m->part_size == grub_disk_get_size (disk)) |
| return 0; |
| } |
| |
| for (diskfilter = grub_diskfilter_list; diskfilter; diskfilter = diskfilter->next) |
| { |
| #ifdef GRUB_UTIL |
| grub_util_info ("Scanning for %s devices on disk %s", |
| diskfilter->name, name); |
| #endif |
| id.uuid = 0; |
| id.uuidlen = 0; |
| arr = diskfilter->detect (disk, &id, &start_sector); |
| if (arr && |
| (! insert_array (disk, &id, arr, start_sector, diskfilter))) |
| { |
| if (id.uuidlen) |
| grub_free (id.uuid); |
| return 0; |
| } |
| if (arr && id.uuidlen) |
| grub_free (id.uuid); |
| |
| /* This error usually means it's not diskfilter, no need to display |
| it. */ |
| if (grub_errno != GRUB_ERR_OUT_OF_RANGE) |
| grub_print_error (); |
| |
| grub_errno = GRUB_ERR_NONE; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| scan_disk (const char *name, int accept_diskfilter) |
| { |
| grub_disk_t disk; |
| static int scan_depth = 0; |
| |
| if (!accept_diskfilter && is_valid_diskfilter_name (name)) |
| return 0; |
| |
| if (scan_depth > 100) |
| return 0; |
| |
| scan_depth++; |
| disk = grub_disk_open (name); |
| if (!disk) |
| { |
| grub_errno = GRUB_ERR_NONE; |
| scan_depth--; |
| return 0; |
| } |
| scan_disk_partition_iter (disk, 0, (void *) name); |
| grub_partition_iterate (disk, scan_disk_partition_iter, (void *) name); |
| grub_disk_close (disk); |
| scan_depth--; |
| return 0; |
| } |
| |
| static int |
| scan_disk_hook (const char *name, void *data __attribute__ ((unused))) |
| { |
| return scan_disk (name, 0); |
| } |
| |
| static void |
| scan_devices (const char *arname) |
| { |
| grub_disk_dev_t p; |
| grub_disk_pull_t pull; |
| struct grub_diskfilter_vg *vg; |
| struct grub_diskfilter_lv *lv = NULL; |
| int scan_depth; |
| int need_rescan; |
| |
| for (pull = 0; pull < GRUB_DISK_PULL_MAX; pull++) |
| for (p = grub_disk_dev_list; p; p = p->next) |
| if (p->id != GRUB_DISK_DEVICE_DISKFILTER_ID |
| && p->iterate) |
| { |
| if ((p->iterate) (scan_disk_hook, NULL, pull)) |
| return; |
| if (arname && is_lv_readable (find_lv (arname), 1)) |
| return; |
| } |
| |
| scan_depth = 0; |
| need_rescan = 1; |
| while (need_rescan && scan_depth++ < 100) |
| { |
| need_rescan = 0; |
| for (vg = array_list; vg; vg = vg->next) |
| { |
| if (vg->lvs) |
| for (lv = vg->lvs; lv; lv = lv->next) |
| if (!lv->scanned && lv->fullname && lv->became_readable_at) |
| { |
| scan_disk (lv->fullname, 1); |
| lv->scanned = 1; |
| need_rescan = 1; |
| } |
| } |
| } |
| |
| if (need_rescan) |
| grub_error (GRUB_ERR_UNKNOWN_DEVICE, "DISKFILTER scan depth exceeded"); |
| } |
| |
| static int |
| grub_diskfilter_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, |
| grub_disk_pull_t pull) |
| { |
| struct grub_diskfilter_vg *array; |
| int islcnt = 0; |
| |
| if (pull == GRUB_DISK_PULL_RESCAN) |
| { |
| islcnt = inscnt + 1; |
| scan_devices (NULL); |
| } |
| |
| if (pull != GRUB_DISK_PULL_NONE && pull != GRUB_DISK_PULL_RESCAN) |
| return 0; |
| |
| for (array = array_list; array; array = array->next) |
| { |
| struct grub_diskfilter_lv *lv; |
| if (array->lvs) |
| for (lv = array->lvs; lv; lv = lv->next) |
| if (lv->visible && lv->fullname && lv->became_readable_at >= islcnt) |
| { |
| if (hook (lv->fullname, hook_data)) |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| #ifdef GRUB_UTIL |
| static grub_disk_memberlist_t |
| grub_diskfilter_memberlist (grub_disk_t disk) |
| { |
| struct grub_diskfilter_lv *lv = disk->data; |
| grub_disk_memberlist_t list = NULL, tmp; |
| struct grub_diskfilter_pv *pv; |
| grub_disk_pull_t pull; |
| grub_disk_dev_t p; |
| struct grub_diskfilter_vg *vg; |
| struct grub_diskfilter_lv *lv2 = NULL; |
| |
| if (!lv->vg->pvs) |
| return NULL; |
| |
| pv = lv->vg->pvs; |
| while (pv && pv->disk) |
| pv = pv->next; |
| |
| for (pull = 0; pv && pull < GRUB_DISK_PULL_MAX; pull++) |
| for (p = grub_disk_dev_list; pv && p; p = p->next) |
| if (p->id != GRUB_DISK_DEVICE_DISKFILTER_ID |
| && p->iterate) |
| { |
| (p->iterate) (scan_disk_hook, NULL, pull); |
| while (pv && pv->disk) |
| pv = pv->next; |
| } |
| |
| for (vg = array_list; pv && vg; vg = vg->next) |
| { |
| if (vg->lvs) |
| for (lv2 = vg->lvs; pv && lv2; lv2 = lv2->next) |
| if (!lv2->scanned && lv2->fullname && lv2->became_readable_at) |
| { |
| scan_disk (lv2->fullname, 1); |
| lv2->scanned = 1; |
| while (pv && pv->disk) |
| pv = pv->next; |
| } |
| } |
| |
| for (pv = lv->vg->pvs; pv; pv = pv->next) |
| { |
| if (!pv->disk) |
| { |
| /* TRANSLATORS: This message kicks in during the detection of |
| which modules needs to be included in core image. This happens |
| in the case of degraded RAID and means that autodetection may |
| fail to include some of modules. It's an installation time |
| message, not runtime message. */ |
| grub_util_warn (_("Couldn't find physical volume `%s'." |
| " Some modules may be missing from core image."), |
| pv->name); |
| continue; |
| } |
| tmp = grub_malloc (sizeof (*tmp)); |
| tmp->disk = pv->disk; |
| tmp->next = list; |
| list = tmp; |
| } |
| |
| return list; |
| } |
| |
| void |
| grub_diskfilter_get_partmap (grub_disk_t disk, |
| void (*cb) (const char *pm, void *data), |
| void *data) |
| { |
| struct grub_diskfilter_lv *lv = disk->data; |
| struct grub_diskfilter_pv *pv; |
| |
| if (lv->vg->pvs) |
| for (pv = lv->vg->pvs; pv; pv = pv->next) |
| { |
| grub_size_t s; |
| if (!pv->disk) |
| { |
| /* TRANSLATORS: This message kicks in during the detection of |
| which modules needs to be included in core image. This happens |
| in the case of degraded RAID and means that autodetection may |
| fail to include some of modules. It's an installation time |
| message, not runtime message. */ |
| grub_util_warn (_("Couldn't find physical volume `%s'." |
| " Some modules may be missing from core image."), |
| pv->name); |
| continue; |
| } |
| for (s = 0; pv->partmaps[s]; s++) |
| cb (pv->partmaps[s], data); |
| } |
| } |
| |
| static const char * |
| grub_diskfilter_getname (struct grub_disk *disk) |
| { |
| struct grub_diskfilter_lv *array = disk->data; |
| |
| return array->vg->driver->name; |
| } |
| #endif |
| |
| static inline char |
| hex2ascii (int c) |
| { |
| if (c >= 10) |
| return 'a' + c - 10; |
| return c + '0'; |
| } |
| |
| static struct grub_diskfilter_lv * |
| find_lv (const char *name) |
| { |
| struct grub_diskfilter_vg *vg; |
| struct grub_diskfilter_lv *lv = NULL; |
| |
| for (vg = array_list; vg; vg = vg->next) |
| { |
| if (vg->lvs) |
| for (lv = vg->lvs; lv; lv = lv->next) |
| if (((lv->fullname && grub_strcmp (lv->fullname, name) == 0) |
| || (lv->idname && grub_strcmp (lv->idname, name) == 0)) |
| && is_lv_readable (lv, 0)) |
| return lv; |
| } |
| return NULL; |
| } |
| |
| static grub_err_t |
| grub_diskfilter_open (const char *name, grub_disk_t disk) |
| { |
| struct grub_diskfilter_lv *lv; |
| |
| if (!is_valid_diskfilter_name (name)) |
| return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown DISKFILTER device %s", |
| name); |
| |
| lv = find_lv (name); |
| |
| if (! lv) |
| { |
| scan_devices (name); |
| if (grub_errno) |
| { |
| grub_print_error (); |
| grub_errno = GRUB_ERR_NONE; |
| } |
| lv = find_lv (name); |
| } |
| |
| if (!lv) |
| return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown DISKFILTER device %s", |
| name); |
| |
| disk->id = lv->number; |
| disk->data = lv; |
| |
| disk->total_sectors = lv->size; |
| disk->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE; |
| return 0; |
| } |
| |
| static void |
| grub_diskfilter_close (grub_disk_t disk __attribute ((unused))) |
| { |
| return; |
| } |
| |
| static grub_err_t |
| read_lv (struct grub_diskfilter_lv *lv, grub_disk_addr_t sector, |
| grub_size_t size, char *buf); |
| |
| grub_err_t |
| grub_diskfilter_read_node (const struct grub_diskfilter_node *node, |
| grub_disk_addr_t sector, |
| grub_size_t size, char *buf) |
| { |
| /* Check whether we actually know the physical volume we want to |
| read from. */ |
| if (node->pv) |
| { |
| if (node->pv->disk) |
| return grub_disk_read (node->pv->disk, sector + node->start |
| + node->pv->start_sector, |
| 0, size << GRUB_DISK_SECTOR_BITS, buf); |
| else |
| return grub_error (GRUB_ERR_UNKNOWN_DEVICE, |
| N_("physical volume %s not found"), node->pv->name); |
| |
| } |
| if (node->lv) |
| return read_lv (node->lv, sector + node->start, size, buf); |
| return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown node '%s'", node->name); |
| } |
| |
| |
| static grub_err_t |
| validate_segment (struct grub_diskfilter_segment *seg); |
| |
| static grub_err_t |
| validate_lv (struct grub_diskfilter_lv *lv) |
| { |
| unsigned int i; |
| if (!lv) |
| return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown volume"); |
| |
| if (!lv->vg || lv->vg->extent_size == 0) |
| return grub_error (GRUB_ERR_READ_ERROR, "invalid volume"); |
| |
| for (i = 0; i < lv->segment_count; i++) |
| { |
| grub_err_t err; |
| err = validate_segment (&lv->segments[i]); |
| if (err) |
| return err; |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| |
| static grub_err_t |
| validate_node (const struct grub_diskfilter_node *node) |
| { |
| /* Check whether we actually know the physical volume we want to |
| read from. */ |
| if (node->pv) |
| return GRUB_ERR_NONE; |
| if (node->lv) |
| return validate_lv (node->lv); |
| return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown node '%s'", node->name); |
| } |
| |
| static grub_err_t |
| validate_segment (struct grub_diskfilter_segment *seg) |
| { |
| grub_err_t err; |
| |
| if (seg->stripe_size == 0 || seg->node_count == 0) |
| return grub_error(GRUB_ERR_BAD_FS, "invalid segment"); |
| |
| switch (seg->type) |
| { |
| case GRUB_DISKFILTER_RAID10: |
| { |
| grub_uint8_t near, far; |
| near = seg->layout & 0xFF; |
| far = (seg->layout >> 8) & 0xFF; |
| if ((seg->layout >> 16) == 0 && far == 0) |
| return grub_error(GRUB_ERR_BAD_FS, "invalid segment"); |
| if (near > seg->node_count) |
| return grub_error(GRUB_ERR_BAD_FS, "invalid segment"); |
| break; |
| } |
| |
| case GRUB_DISKFILTER_STRIPED: |
| case GRUB_DISKFILTER_MIRROR: |
| break; |
| |
| case GRUB_DISKFILTER_RAID4: |
| case GRUB_DISKFILTER_RAID5: |
| if (seg->node_count <= 1) |
| return grub_error(GRUB_ERR_BAD_FS, "invalid segment"); |
| break; |
| |
| case GRUB_DISKFILTER_RAID6: |
| if (seg->node_count <= 2) |
| return grub_error(GRUB_ERR_BAD_FS, "invalid segment"); |
| break; |
| |
| default: |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "unsupported RAID level %d", seg->type); |
| } |
| |
| unsigned i; |
| for (i = 0; i < seg->node_count; i++) |
| { |
| err = validate_node (&seg->nodes[i]); |
| if (err) |
| return err; |
| } |
| return GRUB_ERR_NONE; |
| |
| } |
| |
| static grub_err_t |
| read_segment (struct grub_diskfilter_segment *seg, grub_disk_addr_t sector, |
| grub_size_t size, char *buf) |
| { |
| grub_err_t err; |
| switch (seg->type) |
| { |
| case GRUB_DISKFILTER_STRIPED: |
| if (seg->node_count == 1) |
| return grub_diskfilter_read_node (&seg->nodes[0], |
| sector, size, buf); |
| /* Fallthrough. */ |
| case GRUB_DISKFILTER_MIRROR: |
| case GRUB_DISKFILTER_RAID10: |
| { |
| grub_disk_addr_t read_sector, far_ofs; |
| grub_uint64_t disknr, b, near, far, ofs; |
| unsigned int i, j; |
| |
| read_sector = grub_divmod64 (sector, seg->stripe_size, &b); |
| far = ofs = near = 1; |
| far_ofs = 0; |
| |
| if (seg->type == 1) |
| near = seg->node_count; |
| else if (seg->type == 10) |
| { |
| near = seg->layout & 0xFF; |
| far = (seg->layout >> 8) & 0xFF; |
| if (seg->layout >> 16) |
| { |
| ofs = far; |
| far_ofs = 1; |
| } |
| else |
| far_ofs = grub_divmod64 (seg->raid_member_size, |
| far * seg->stripe_size, 0); |
| |
| far_ofs *= seg->stripe_size; |
| } |
| |
| read_sector = grub_divmod64 (read_sector * near, |
| seg->node_count, |
| &disknr); |
| |
| ofs *= seg->stripe_size; |
| read_sector *= ofs; |
| |
| while (1) |
| { |
| grub_size_t read_size; |
| |
| read_size = seg->stripe_size - b; |
| if (read_size > size) |
| read_size = size; |
| |
| err = 0; |
| for (i = 0; i < near; i++) |
| { |
| unsigned int k; |
| |
| k = disknr; |
| err = 0; |
| for (j = 0; j < far; j++) |
| { |
| if (grub_errno == GRUB_ERR_READ_ERROR |
| || grub_errno == GRUB_ERR_UNKNOWN_DEVICE) |
| grub_errno = GRUB_ERR_NONE; |
| |
| err = grub_diskfilter_read_node (&seg->nodes[k], |
| read_sector |
| + j * far_ofs + b, |
| read_size, |
| buf); |
| if (! err) |
| break; |
| else if (err != GRUB_ERR_READ_ERROR |
| && err != GRUB_ERR_UNKNOWN_DEVICE) |
| return err; |
| k++; |
| if (k == seg->node_count) |
| k = 0; |
| } |
| |
| if (! err) |
| break; |
| |
| disknr++; |
| if (disknr == seg->node_count) |
| { |
| disknr = 0; |
| read_sector += ofs; |
| } |
| } |
| |
| if (err) |
| return err; |
| |
| buf += read_size << GRUB_DISK_SECTOR_BITS; |
| size -= read_size; |
| if (! size) |
| return GRUB_ERR_NONE; |
| |
| b = 0; |
| disknr += (near - i); |
| while (disknr >= seg->node_count) |
| { |
| disknr -= seg->node_count; |
| read_sector += ofs; |
| } |
| } |
| } |
| |
| case GRUB_DISKFILTER_RAID4: |
| case GRUB_DISKFILTER_RAID5: |
| case GRUB_DISKFILTER_RAID6: |
| { |
| grub_disk_addr_t read_sector; |
| grub_uint64_t b, p, n, disknr, e; |
| |
| /* n = 1 for level 4 and 5, 2 for level 6. */ |
| n = seg->type / 3; |
| |
| /* Find the first sector to read. */ |
| read_sector = grub_divmod64 (sector, seg->stripe_size, &b); |
| read_sector = grub_divmod64 (read_sector, seg->node_count - n, |
| &disknr); |
| if (seg->type >= 5) |
| { |
| grub_divmod64 (read_sector, seg->node_count, &p); |
| |
| if (! (seg->layout & GRUB_RAID_LAYOUT_RIGHT_MASK)) |
| p = seg->node_count - 1 - p; |
| |
| if (seg->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK) |
| { |
| disknr += p + n; |
| } |
| else |
| { |
| grub_uint32_t q; |
| |
| q = p + (n - 1); |
| if (q >= seg->node_count) |
| q -= seg->node_count; |
| |
| if (disknr >= p) |
| disknr += n; |
| else if (disknr >= q) |
| disknr += q + 1; |
| } |
| |
| if (disknr >= seg->node_count) |
| disknr -= seg->node_count; |
| } |
| else |
| p = seg->node_count - n; |
| read_sector *= seg->stripe_size; |
| |
| while (1) |
| { |
| grub_size_t read_size; |
| int next_level; |
| |
| read_size = seg->stripe_size - b; |
| if (read_size > size) |
| read_size = size; |
| |
| e = 0; |
| /* Reset read error. */ |
| if (grub_errno == GRUB_ERR_READ_ERROR |
| || grub_errno == GRUB_ERR_UNKNOWN_DEVICE) |
| grub_errno = GRUB_ERR_NONE; |
| |
| err = grub_diskfilter_read_node (&seg->nodes[disknr], |
| read_sector + b, |
| read_size, |
| buf); |
| |
| if ((err) && (err != GRUB_ERR_READ_ERROR |
| && err != GRUB_ERR_UNKNOWN_DEVICE)) |
| return err; |
| e++; |
| |
| if (err) |
| { |
| grub_errno = GRUB_ERR_NONE; |
| if (seg->type == GRUB_DISKFILTER_RAID6) |
| { |
| err = ((grub_raid6_recover_func) ? |
| (*grub_raid6_recover_func) (seg, disknr, p, |
| buf, read_sector + b, |
| read_size) : |
| grub_error (GRUB_ERR_BAD_DEVICE, |
| N_("module `%s' isn't loaded"), |
| "raid6rec")); |
| } |
| else |
| { |
| err = ((grub_raid5_recover_func) ? |
| (*grub_raid5_recover_func) (seg, disknr, |
| buf, read_sector + b, |
| read_size) : |
| grub_error (GRUB_ERR_BAD_DEVICE, |
| N_("module `%s' isn't loaded"), |
| "raid5rec")); |
| } |
| |
| if (err) |
| return err; |
| } |
| |
| buf += read_size << GRUB_DISK_SECTOR_BITS; |
| size -= read_size; |
| sector += read_size; |
| if (! size) |
| break; |
| |
| b = 0; |
| disknr++; |
| |
| if (seg->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK) |
| { |
| if (disknr == seg->node_count) |
| disknr = 0; |
| |
| next_level = (disknr == p); |
| } |
| else |
| { |
| if (disknr == p) |
| disknr += n; |
| |
| next_level = (disknr >= seg->node_count); |
| } |
| |
| if (next_level) |
| { |
| read_sector += seg->stripe_size; |
| |
| if (seg->type >= 5) |
| { |
| if (seg->layout & GRUB_RAID_LAYOUT_RIGHT_MASK) |
| p = (p == seg->node_count - 1) ? 0 : p + 1; |
| else |
| p = (p == 0) ? seg->node_count - 1 : p - 1; |
| |
| if (seg->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK) |
| { |
| disknr = p + n; |
| if (disknr >= seg->node_count) |
| disknr -= seg->node_count; |
| } |
| else |
| { |
| disknr -= seg->node_count; |
| if ((disknr >= p && disknr < p + n) |
| || (disknr + seg->node_count >= p |
| && disknr + seg->node_count < p + n)) |
| disknr = p + n; |
| if (disknr >= seg->node_count) |
| disknr -= seg->node_count; |
| } |
| } |
| else |
| disknr = 0; |
| } |
| } |
| } |
| return GRUB_ERR_NONE; |
| default: |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "unsupported RAID level %d", seg->type); |
| } |
| } |
| |
| static grub_err_t |
| read_lv (struct grub_diskfilter_lv *lv, grub_disk_addr_t sector, |
| grub_size_t size, char *buf) |
| { |
| if (!lv) |
| return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown volume"); |
| |
| while (size) |
| { |
| grub_err_t err = 0; |
| struct grub_diskfilter_vg *vg = lv->vg; |
| struct grub_diskfilter_segment *seg = lv->segments; |
| grub_uint64_t extent; |
| grub_uint64_t to_read; |
| |
| extent = grub_divmod64 (sector, vg->extent_size, NULL); |
| |
| /* Find the right segment. */ |
| { |
| unsigned int i; |
| for (i = 0; i < lv->segment_count; i++) |
| { |
| if ((seg->start_extent <= extent) |
| && ((seg->start_extent + seg->extent_count) > extent)) |
| break; |
| seg++; |
| } |
| if (i == lv->segment_count) |
| return grub_error (GRUB_ERR_READ_ERROR, "incorrect segment"); |
| } |
| to_read = ((seg->start_extent + seg->extent_count) |
| * vg->extent_size) - sector; |
| if (to_read > size) |
| to_read = size; |
| |
| err = read_segment (seg, sector - seg->start_extent * vg->extent_size, |
| to_read, buf); |
| if (err) |
| return err; |
| |
| size -= to_read; |
| sector += to_read; |
| buf += to_read << GRUB_DISK_SECTOR_BITS; |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| grub_diskfilter_read (grub_disk_t disk, grub_disk_addr_t sector, |
| grub_size_t size, char *buf) |
| { |
| return read_lv (disk->data, sector, size, buf); |
| } |
| |
| static grub_err_t |
| grub_diskfilter_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_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "diskfilter writes are not supported"); |
| } |
| |
| struct grub_diskfilter_vg * |
| grub_diskfilter_get_vg_by_uuid (grub_size_t uuidlen, char *uuid) |
| { |
| struct grub_diskfilter_vg *p; |
| |
| for (p = array_list; p != NULL; p = p->next) |
| if ((p->uuid_len == uuidlen) && |
| (! grub_memcmp (p->uuid, uuid, p->uuid_len))) |
| return p; |
| return NULL; |
| } |
| |
| grub_err_t |
| grub_diskfilter_vg_register (struct grub_diskfilter_vg *vg) |
| { |
| struct grub_diskfilter_lv *lv, *p; |
| struct grub_diskfilter_vg *vgp; |
| unsigned i; |
| |
| grub_dprintf ("diskfilter", "Found array %s\n", vg->name); |
| #ifdef GRUB_UTIL |
| grub_util_info ("Found array %s", vg->name); |
| #endif |
| |
| for (lv = vg->lvs; lv; lv = lv->next) |
| { |
| grub_err_t err; |
| |
| /* RAID 1 and single-disk RAID 0 don't use a chunksize but code |
| assumes one so set one. */ |
| for (i = 0; i < lv->segment_count; i++) |
| { |
| if (lv->segments[i].type == 1) |
| lv->segments[i].stripe_size = 64; |
| if (lv->segments[i].type == GRUB_DISKFILTER_STRIPED |
| && lv->segments[i].node_count == 1 |
| && lv->segments[i].stripe_size == 0) |
| lv->segments[i].stripe_size = 64; |
| } |
| |
| err = validate_lv(lv); |
| if (err) |
| return err; |
| lv->number = lv_num++; |
| |
| if (lv->fullname) |
| { |
| grub_size_t len; |
| int max_used_number = 0, need_new_name = 0; |
| len = grub_strlen (lv->fullname); |
| for (vgp = array_list; vgp; vgp = vgp->next) |
| for (p = vgp->lvs; p; p = p->next) |
| { |
| int cur_num; |
| char *num, *end; |
| if (!p->fullname) |
| continue; |
| if (grub_strncmp (p->fullname, lv->fullname, len) != 0) |
| continue; |
| if (p->fullname[len] == 0) |
| { |
| need_new_name = 1; |
| continue; |
| } |
| num = p->fullname + len + 1; |
| if (!grub_isdigit (num[0])) |
| continue; |
| cur_num = grub_strtoul (num, &end, 10); |
| if (end[0]) |
| continue; |
| if (cur_num > max_used_number) |
| max_used_number = cur_num; |
| } |
| if (need_new_name) |
| { |
| char *tmp; |
| tmp = grub_xasprintf ("%s_%d", lv->fullname, max_used_number + 1); |
| if (!tmp) |
| return grub_errno; |
| grub_free (lv->fullname); |
| lv->fullname = tmp; |
| } |
| } |
| } |
| /* Add our new array to the list. */ |
| vg->next = array_list; |
| array_list = vg; |
| return GRUB_ERR_NONE; |
| } |
| |
| struct grub_diskfilter_vg * |
| grub_diskfilter_make_raid (grub_size_t uuidlen, char *uuid, int nmemb, |
| const char *name, grub_uint64_t disk_size, |
| grub_uint64_t stripe_size, |
| int layout, int level) |
| { |
| struct grub_diskfilter_vg *array; |
| int i; |
| grub_size_t j; |
| grub_uint64_t totsize; |
| struct grub_diskfilter_pv *pv; |
| grub_err_t err; |
| |
| switch (level) |
| { |
| case 1: |
| totsize = disk_size; |
| break; |
| |
| case 10: |
| { |
| int n; |
| n = layout & 0xFF; |
| if (n == 1) |
| n = (layout >> 8) & 0xFF; |
| if (n == 0) |
| { |
| grub_free (uuid); |
| return NULL; |
| } |
| |
| totsize = grub_divmod64 (nmemb * disk_size, n, 0); |
| } |
| break; |
| |
| case 0: |
| case 4: |
| case 5: |
| case 6: |
| totsize = (nmemb - ((unsigned) level / 3U)) * disk_size; |
| break; |
| |
| default: |
| grub_free (uuid); |
| return NULL; |
| } |
| |
| array = grub_diskfilter_get_vg_by_uuid (uuidlen, uuid); |
| if (array) |
| { |
| if (array->lvs && array->lvs->size < totsize) |
| { |
| array->lvs->size = totsize; |
| if (array->lvs->segments) |
| array->lvs->segments->extent_count = totsize; |
| } |
| |
| if (array->lvs && array->lvs->segments |
| && array->lvs->segments->raid_member_size > disk_size) |
| array->lvs->segments->raid_member_size = disk_size; |
| |
| grub_free (uuid); |
| return array; |
| } |
| array = grub_zalloc (sizeof (*array)); |
| if (!array) |
| { |
| grub_free (uuid); |
| return NULL; |
| } |
| array->uuid = uuid; |
| array->uuid_len = uuidlen; |
| if (name) |
| { |
| /* Strip off the homehost if present. */ |
| char *colon = grub_strchr (name, ':'); |
| char *new_name = grub_xasprintf ("md/%s", |
| colon ? colon + 1 : name); |
| |
| if (! new_name) |
| goto fail; |
| |
| array->name = new_name; |
| } |
| |
| array->extent_size = 1; |
| array->lvs = grub_zalloc (sizeof (*array->lvs)); |
| if (!array->lvs) |
| goto fail; |
| array->lvs->segment_count = 1; |
| array->lvs->visible = 1; |
| if (array->name) |
| { |
| array->lvs->name = grub_strdup (array->name); |
| if (!array->lvs->name) |
| goto fail; |
| array->lvs->fullname = grub_strdup (array->name); |
| if (!array->lvs->fullname) |
| goto fail; |
| } |
| array->lvs->vg = array; |
| |
| array->lvs->idname = grub_malloc (sizeof ("mduuid/") + 2 * uuidlen); |
| if (!array->lvs->idname) |
| goto fail; |
| |
| grub_memcpy (array->lvs->idname, "mduuid/", sizeof ("mduuid/") - 1); |
| for (j = 0; j < uuidlen; j++) |
| { |
| array->lvs->idname[sizeof ("mduuid/") - 1 + 2 * j] |
| = hex2ascii (((unsigned char) uuid[j] >> 4)); |
| array->lvs->idname[sizeof ("mduuid/") - 1 + 2 * j + 1] |
| = hex2ascii (((unsigned char) uuid[j] & 0xf)); |
| } |
| array->lvs->idname[sizeof ("mduuid/") - 1 + 2 * uuidlen] = '\0'; |
| |
| array->lvs->size = totsize; |
| |
| array->lvs->segments = grub_zalloc (sizeof (*array->lvs->segments)); |
| if (!array->lvs->segments) |
| goto fail; |
| array->lvs->segments->stripe_size = stripe_size; |
| array->lvs->segments->layout = layout; |
| array->lvs->segments->start_extent = 0; |
| array->lvs->segments->extent_count = totsize; |
| array->lvs->segments->type = level; |
| array->lvs->segments->node_count = nmemb; |
| array->lvs->segments->raid_member_size = disk_size; |
| array->lvs->segments->nodes |
| = grub_zalloc (nmemb * sizeof (array->lvs->segments->nodes[0])); |
| array->lvs->segments->stripe_size = stripe_size; |
| for (i = 0; i < nmemb; i++) |
| { |
| pv = grub_zalloc (sizeof (*pv)); |
| if (!pv) |
| goto fail; |
| pv->id.uuidlen = 0; |
| pv->id.id = i; |
| pv->next = array->pvs; |
| array->pvs = pv; |
| array->lvs->segments->nodes[i].pv = pv; |
| } |
| |
| err = grub_diskfilter_vg_register (array); |
| if (err) |
| goto fail; |
| |
| return array; |
| |
| fail: |
| if (array->lvs) |
| { |
| grub_free (array->lvs->name); |
| grub_free (array->lvs->fullname); |
| grub_free (array->lvs->idname); |
| if (array->lvs->segments) |
| { |
| grub_free (array->lvs->segments->nodes); |
| grub_free (array->lvs->segments); |
| } |
| grub_free (array->lvs); |
| } |
| while (array->pvs) |
| { |
| pv = array->pvs->next; |
| grub_free (array->pvs); |
| array->pvs = pv; |
| } |
| grub_free (array->name); |
| grub_free (array->uuid); |
| grub_free (array); |
| return NULL; |
| } |
| |
| static grub_err_t |
| insert_array (grub_disk_t disk, const struct grub_diskfilter_pv_id *id, |
| struct grub_diskfilter_vg *array, |
| grub_disk_addr_t start_sector, |
| grub_diskfilter_t diskfilter __attribute__ ((unused))) |
| { |
| struct grub_diskfilter_pv *pv; |
| |
| grub_dprintf ("diskfilter", "Inserting %s (+%lld,%lld) into %s (%s)\n", disk->name, |
| (unsigned long long) grub_partition_get_start (disk->partition), |
| (unsigned long long) grub_disk_get_size (disk), |
| array->name, diskfilter->name); |
| #ifdef GRUB_UTIL |
| grub_util_info ("Inserting %s (+%" GRUB_HOST_PRIuLONG_LONG ",%" |
| GRUB_HOST_PRIuLONG_LONG ") into %s (%s)\n", disk->name, |
| (unsigned long long) grub_partition_get_start (disk->partition), |
| (unsigned long long) grub_disk_get_size (disk), |
| array->name, diskfilter->name); |
| array->driver = diskfilter; |
| #endif |
| |
| for (pv = array->pvs; pv; pv = pv->next) |
| if (id->uuidlen == pv->id.uuidlen |
| && id->uuidlen |
| ? (grub_memcmp (pv->id.uuid, id->uuid, id->uuidlen) == 0) |
| : (pv->id.id == id->id)) |
| { |
| struct grub_diskfilter_lv *lv; |
| /* FIXME: Check whether the update time of the superblocks are |
| the same. */ |
| if (pv->disk && grub_disk_get_size (disk) >= pv->part_size) |
| return GRUB_ERR_NONE; |
| pv->disk = grub_disk_open (disk->name); |
| if (!pv->disk) |
| return grub_errno; |
| /* This could happen to LVM on RAID, pv->disk points to the |
| raid device, we shouldn't change it. */ |
| pv->start_sector -= pv->part_start; |
| pv->part_start = grub_partition_get_start (disk->partition); |
| pv->part_size = grub_disk_get_size (disk); |
| |
| #ifdef GRUB_UTIL |
| { |
| grub_size_t s = 1; |
| grub_partition_t p; |
| for (p = disk->partition; p; p = p->parent) |
| s++; |
| pv->partmaps = xmalloc (s * sizeof (pv->partmaps[0])); |
| s = 0; |
| for (p = disk->partition; p; p = p->parent) |
| pv->partmaps[s++] = xstrdup (p->partmap->name); |
| pv->partmaps[s++] = 0; |
| } |
| #endif |
| if (start_sector != (grub_uint64_t)-1) |
| pv->start_sector = start_sector; |
| pv->start_sector += pv->part_start; |
| /* Add the device to the array. */ |
| for (lv = array->lvs; lv; lv = lv->next) |
| if (!lv->became_readable_at && lv->fullname && is_lv_readable (lv, 0)) |
| lv->became_readable_at = ++inscnt; |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| free_array (void) |
| { |
| while (array_list) |
| { |
| struct grub_diskfilter_vg *vg; |
| struct grub_diskfilter_pv *pv; |
| struct grub_diskfilter_lv *lv; |
| |
| vg = array_list; |
| array_list = array_list->next; |
| |
| while ((pv = vg->pvs)) |
| { |
| vg->pvs = pv->next; |
| grub_free (pv->name); |
| if (pv->disk) |
| grub_disk_close (pv->disk); |
| if (pv->id.uuidlen) |
| grub_free (pv->id.uuid); |
| #ifdef GRUB_UTIL |
| grub_free (pv->partmaps); |
| #endif |
| grub_free (pv->internal_id); |
| grub_free (pv); |
| } |
| |
| while ((lv = vg->lvs)) |
| { |
| unsigned i; |
| vg->lvs = lv->next; |
| grub_free (lv->fullname); |
| grub_free (lv->name); |
| grub_free (lv->idname); |
| for (i = 0; i < lv->segment_count; i++) |
| grub_free (lv->segments[i].nodes); |
| grub_free (lv->segments); |
| grub_free (lv->internal_id); |
| grub_free (lv); |
| } |
| |
| grub_free (vg->uuid); |
| grub_free (vg->name); |
| grub_free (vg); |
| } |
| |
| array_list = 0; |
| } |
| |
| #ifdef GRUB_UTIL |
| struct grub_diskfilter_pv * |
| grub_diskfilter_get_pv_from_disk (grub_disk_t disk, |
| struct grub_diskfilter_vg **vg_out) |
| { |
| struct grub_diskfilter_pv *pv; |
| struct grub_diskfilter_vg *vg; |
| |
| scan_disk (disk->name, 1); |
| for (vg = array_list; vg; vg = vg->next) |
| for (pv = vg->pvs; pv; pv = pv->next) |
| { |
| if (pv->disk && pv->disk->id == disk->id |
| && pv->disk->dev->id == disk->dev->id |
| && pv->part_start == grub_partition_get_start (disk->partition) |
| && pv->part_size == grub_disk_get_size (disk)) |
| { |
| if (vg_out) |
| *vg_out = vg; |
| return pv; |
| } |
| } |
| return NULL; |
| } |
| #endif |
| |
| static struct grub_disk_dev grub_diskfilter_dev = |
| { |
| .name = "diskfilter", |
| .id = GRUB_DISK_DEVICE_DISKFILTER_ID, |
| .iterate = grub_diskfilter_iterate, |
| .open = grub_diskfilter_open, |
| .close = grub_diskfilter_close, |
| .read = grub_diskfilter_read, |
| .write = grub_diskfilter_write, |
| #ifdef GRUB_UTIL |
| .memberlist = grub_diskfilter_memberlist, |
| .raidname = grub_diskfilter_getname, |
| #endif |
| .next = 0 |
| }; |
| |
| |
| GRUB_MOD_INIT(diskfilter) |
| { |
| grub_disk_dev_register (&grub_diskfilter_dev); |
| } |
| |
| GRUB_MOD_FINI(diskfilter) |
| { |
| grub_disk_dev_unregister (&grub_diskfilter_dev); |
| free_array (); |
| } |