1
/* lvm.c - module to read Logical Volumes. */
3
* GRUB -- GRand Unified Bootloader
4
* Copyright (C) 2006,2007,2008,2009 Free Software Foundation, Inc.
6
* GRUB is free software: you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation, either version 3 of the License, or
9
* (at your option) any later version.
11
* GRUB is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
21
#include <grub/disk.h>
24
#include <grub/misc.h>
28
#include <grub/emu/misc.h>
31
static struct grub_lvm_vg *vg_list;
35
/* Go the string STR and return the number after STR. *P will point
36
at the number. In case STR is not found, *P will be NULL and the
37
return value will be 0. */
39
grub_lvm_getvalue (char **p, char *str)
41
*p = grub_strstr (*p, str);
44
*p += grub_strlen (str);
45
return grub_strtoul (*p, NULL, 10);
49
grub_lvm_checkvalue (char **p, char *str, char *tmpl)
51
int tmpllen = grub_strlen (tmpl);
52
*p = grub_strstr (*p, str);
55
*p += grub_strlen (str);
58
return (grub_memcmp (*p + 1, tmpl, tmpllen) == 0 && (*p)[tmpllen + 1] == '"');
62
grub_lvm_check_flag (char *p, char *str, char *flag)
64
int len_str = grub_strlen (str), len_flag = grub_strlen (flag);
68
p = grub_strstr (p, str);
72
if (grub_memcmp (p, " = [", sizeof (" = [") - 1) != 0)
74
q = p + sizeof (" = [") - 1;
77
while (grub_isspace (*q))
82
if (grub_memcmp (q, flag, len_flag) == 0 && q[len_flag] == '"')
95
grub_lvm_iterate (int (*hook) (const char *name))
97
struct grub_lvm_vg *vg;
98
for (vg = vg_list; vg; vg = vg->next)
100
struct grub_lvm_lv *lv;
102
for (lv = vg->lvs; lv; lv = lv->next)
111
static grub_disk_memberlist_t
112
grub_lvm_memberlist (grub_disk_t disk)
114
struct grub_lvm_lv *lv = disk->data;
115
grub_disk_memberlist_t list = NULL, tmp;
116
struct grub_lvm_pv *pv;
119
for (pv = lv->vg->pvs; pv; pv = pv->next)
122
grub_util_error ("Couldn't find PV %s. Check your device.map",
124
tmp = grub_malloc (sizeof (*tmp));
125
tmp->disk = pv->disk;
135
grub_lvm_open (const char *name, grub_disk_t disk)
137
struct grub_lvm_vg *vg;
138
struct grub_lvm_lv *lv = NULL;
139
for (vg = vg_list; vg; vg = vg->next)
142
for (lv = vg->lvs; lv; lv = lv->next)
143
if (! grub_strcmp (lv->name, name))
151
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown LVM device %s", name);
153
disk->id = lv->number;
155
disk->total_sectors = lv->size;
161
grub_lvm_close (grub_disk_t disk __attribute ((unused)))
167
grub_lvm_read (grub_disk_t disk, grub_disk_addr_t sector,
168
grub_size_t size, char *buf)
171
struct grub_lvm_lv *lv = disk->data;
172
struct grub_lvm_vg *vg = lv->vg;
173
struct grub_lvm_segment *seg = lv->segments;
174
struct grub_lvm_pv *pv;
175
grub_uint64_t offset;
176
grub_uint64_t extent;
179
extent = grub_divmod64 (sector, vg->extent_size, NULL);
181
/* Find the right segment. */
182
for (i = 0; i < lv->segment_count; i++)
184
if ((seg->start_extent <= extent)
185
&& ((seg->start_extent + seg->extent_count) > extent))
193
if (seg->stripe_count == 1)
195
/* This segment is linear, so that's easy. We just need to find
196
out the offset in the physical volume and read SIZE bytes
198
struct grub_lvm_stripe *stripe = seg->stripes;
199
grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
202
seg_offset = ((grub_uint64_t) stripe->start
203
* (grub_uint64_t) vg->extent_size) + pv->start;
205
offset = sector - ((grub_uint64_t) seg->start_extent
206
* (grub_uint64_t) vg->extent_size) + seg_offset;
210
/* This is a striped segment. We have to find the right PV
212
struct grub_lvm_stripe *stripe = seg->stripes;
214
grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
215
unsigned int stripenr;
217
offset = sector - ((grub_uint64_t) seg->start_extent
218
* (grub_uint64_t) vg->extent_size);
220
a = grub_divmod64 (offset, seg->stripe_size, NULL);
221
grub_divmod64 (a, seg->stripe_count, &stripenr);
223
a = grub_divmod64 (offset, seg->stripe_size * seg->stripe_count, NULL);
224
grub_divmod64 (offset, seg->stripe_size, &b);
225
offset = a * seg->stripe_size + b;
230
seg_offset = ((grub_uint64_t) stripe->start
231
* (grub_uint64_t) vg->extent_size) + pv->start;
233
offset += seg_offset;
236
/* Check whether we actually know the physical volume we want to
239
err = grub_disk_read (pv->disk, offset, 0,
240
size << GRUB_DISK_SECTOR_BITS, buf);
242
err = grub_error (GRUB_ERR_UNKNOWN_DEVICE,
243
"physical volume %s not found", pv->name);
249
grub_lvm_write (grub_disk_t disk __attribute ((unused)),
250
grub_disk_addr_t sector __attribute ((unused)),
251
grub_size_t size __attribute ((unused)),
252
const char *buf __attribute ((unused)))
254
return GRUB_ERR_NOT_IMPLEMENTED_YET;
258
grub_lvm_scan_device (const char *name)
262
grub_uint64_t mda_offset, mda_size;
263
char buf[GRUB_LVM_LABEL_SIZE];
264
char vg_id[GRUB_LVM_ID_STRLEN+1];
265
char pv_id[GRUB_LVM_ID_STRLEN+1];
266
char *metadatabuf, *p, *q, *vgname;
267
struct grub_lvm_label_header *lh = (struct grub_lvm_label_header *) buf;
268
struct grub_lvm_pv_header *pvh;
269
struct grub_lvm_disk_locn *dlocn;
270
struct grub_lvm_mda_header *mdah;
271
struct grub_lvm_raw_locn *rlocn;
272
unsigned int i, j, vgname_len;
273
struct grub_lvm_vg *vg;
274
struct grub_lvm_pv *pv;
277
grub_util_info ("scanning %s for LVM", name);
280
disk = grub_disk_open (name);
283
if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
284
grub_errno = GRUB_ERR_NONE;
288
/* Search for label. */
289
for (i = 0; i < GRUB_LVM_LABEL_SCAN_SECTORS; i++)
291
err = grub_disk_read (disk, i, 0, sizeof(buf), buf);
295
if ((! grub_strncmp ((char *)lh->id, GRUB_LVM_LABEL_ID,
297
&& (! grub_strncmp ((char *)lh->type, GRUB_LVM_LVM2_LABEL,
302
/* Return if we didn't find a label. */
303
if (i == GRUB_LVM_LABEL_SCAN_SECTORS)
306
grub_util_info ("no LVM signature found\n");
311
pvh = (struct grub_lvm_pv_header *) (buf + grub_le_to_cpu32(lh->offset_xl));
313
for (i = 0, j = 0; i < GRUB_LVM_ID_LEN; i++)
315
pv_id[j++] = pvh->pv_uuid[i];
316
if ((i != 1) && (i != 29) && (i % 4 == 1))
321
dlocn = pvh->disk_areas_xl;
324
/* Is it possible to have multiple data/metadata areas? I haven't
325
seen devices that have it. */
328
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
329
"we don't support multiple LVM data areas");
332
grub_util_info ("we don't support multiple LVM data areas\n");
338
mda_offset = grub_le_to_cpu64 (dlocn->offset);
339
mda_size = grub_le_to_cpu64 (dlocn->size);
341
/* It's possible to have multiple copies of metadata areas, we just use the
344
/* Allocate buffer space for the circular worst-case scenario. */
345
metadatabuf = grub_malloc (2 * mda_size);
349
err = grub_disk_read (disk, 0, mda_offset, mda_size, metadatabuf);
353
mdah = (struct grub_lvm_mda_header *) metadatabuf;
354
if ((grub_strncmp ((char *)mdah->magic, GRUB_LVM_FMTT_MAGIC,
355
sizeof (mdah->magic)))
356
|| (grub_le_to_cpu32 (mdah->version) != GRUB_LVM_FMTT_VERSION))
358
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
359
"unknown LVM metadata header");
361
grub_util_info ("unknown LVM metadata header\n");
366
rlocn = mdah->raw_locns;
367
if (grub_le_to_cpu64 (rlocn->offset) + grub_le_to_cpu64 (rlocn->size) >
368
grub_le_to_cpu64 (mdah->size))
370
/* Metadata is circular. Copy the wrap in place. */
371
grub_memcpy (metadatabuf + mda_size,
372
metadatabuf + GRUB_LVM_MDA_HEADER_SIZE,
373
grub_le_to_cpu64 (rlocn->offset) +
374
grub_le_to_cpu64 (rlocn->size) -
375
grub_le_to_cpu64 (mdah->size));
377
p = q = metadatabuf + grub_le_to_cpu64 (rlocn->offset);
379
while (*q != ' ' && q < metadatabuf + mda_size)
382
if (q == metadatabuf + mda_size)
385
grub_util_info ("error parsing metadata\n");
391
vgname = grub_malloc (vgname_len + 1);
395
grub_memcpy (vgname, p, vgname_len);
396
vgname[vgname_len] = '\0';
398
p = grub_strstr (q, "id = \"");
402
grub_util_info ("couldn't find ID\n");
406
p += sizeof ("id = \"") - 1;
407
grub_memcpy (vg_id, p, GRUB_LVM_ID_STRLEN);
408
vg_id[GRUB_LVM_ID_STRLEN] = '\0';
410
for (vg = vg_list; vg; vg = vg->next)
412
if (! grub_memcmp(vg_id, vg->id, GRUB_LVM_ID_STRLEN))
418
/* First time we see this volume group. We've to create the
419
whole volume group structure. */
420
vg = grub_malloc (sizeof (*vg));
424
grub_memcpy (vg->id, vg_id, GRUB_LVM_ID_STRLEN+1);
426
vg->extent_size = grub_lvm_getvalue (&p, "extent_size = ");
430
grub_util_info ("unknown extent size\n");
438
p = grub_strstr (p, "physical_volumes {");
441
p += sizeof ("physical_volumes {") - 1;
443
/* Add all the pvs to the volume group. */
447
while (grub_isspace (*p))
453
pv = grub_malloc (sizeof (*pv));
459
pv->name = grub_malloc (s + 1);
460
grub_memcpy (pv->name, p, s);
463
p = grub_strstr (p, "id = \"");
466
p += sizeof("id = \"") - 1;
468
grub_memcpy (pv->id, p, GRUB_LVM_ID_STRLEN);
469
pv->id[GRUB_LVM_ID_STRLEN] = '\0';
471
pv->start = grub_lvm_getvalue (&p, "pe_start = ");
475
grub_util_info ("unknown pe_start\n");
480
p = grub_strchr (p, '}');
484
grub_util_info ("error parsing pe_start\n");
496
grub_free (pv->name);
502
p = grub_strstr (p, "logical_volumes");
507
/* And add all the lvs to the volume group. */
512
struct grub_lvm_lv *lv;
513
struct grub_lvm_segment *seg;
515
while (grub_isspace (*p))
521
lv = grub_malloc (sizeof (*lv));
528
lv->name = grub_malloc (vgname_len + 1 + s + 1);
529
grub_memcpy (lv->name, vgname, vgname_len);
530
lv->name[vgname_len] = '-';
531
grub_memcpy (lv->name + vgname_len + 1, p, s);
532
lv->name[vgname_len + 1 + s] = '\0';
536
if (!grub_lvm_check_flag (p, "status", "VISIBLE"))
542
lv->segment_count = grub_lvm_getvalue (&p, "segment_count = ");
546
grub_util_info ("unknown segment_count\n");
550
lv->segments = grub_malloc (sizeof (*seg) * lv->segment_count);
553
for (i = 0; i < lv->segment_count; i++)
555
struct grub_lvm_stripe *stripe;
557
p = grub_strstr (p, "segment");
561
grub_util_info ("unknown segment\n");
563
goto lvs_segment_fail;
566
seg->start_extent = grub_lvm_getvalue (&p, "start_extent = ");
570
grub_util_info ("unknown start_extent\n");
572
goto lvs_segment_fail;
574
seg->extent_count = grub_lvm_getvalue (&p, "extent_count = ");
578
grub_util_info ("unknown extent_count\n");
580
goto lvs_segment_fail;
583
if (grub_lvm_checkvalue (&p, "type = ", "snapshot"))
585
/* Found a snapshot, give up and move on. */
590
seg->stripe_count = grub_lvm_getvalue (&p, "stripe_count = ");
594
grub_util_info ("unknown stripe_count\n");
596
goto lvs_segment_fail;
599
lv->size += seg->extent_count * vg->extent_size;
601
if (seg->stripe_count != 1)
602
seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
604
seg->stripes = grub_malloc (sizeof (*stripe)
605
* seg->stripe_count);
606
stripe = seg->stripes;
608
p = grub_strstr (p, "stripes = [");
612
grub_util_info ("unknown stripes\n");
614
goto lvs_segment_fail2;
616
p += sizeof("stripes = [") - 1;
618
for (j = 0; j < seg->stripe_count; j++)
622
p = grub_strchr (p, '"');
631
pvname = grub_malloc (s + 1);
633
goto lvs_segment_fail2;
635
grub_memcpy (pvname, p, s);
639
for (pv = vg->pvs; pv; pv = pv->next)
641
if (! grub_strcmp (pvname, pv->name))
650
stripe->start = grub_lvm_getvalue (&p, ",");
661
grub_free (seg->stripes);
668
p = grub_strchr (p, '}');
675
grub_free (lv->name);
680
lv->number = lv_count++;
687
grub_free (lv->name);
701
/* Match the device we are currently reading from with the right
704
for (pv = vg->pvs; pv; pv = pv->next)
706
if (! grub_memcmp (pv->id, pv_id, GRUB_LVM_ID_STRLEN))
708
/* This could happen to LVM on RAID, pv->disk points to the
709
raid device, we shouldn't change it. */
711
pv->disk = grub_disk_open (name);
724
/* Normal exit path. */
726
grub_free (metadatabuf);
728
grub_disk_close (disk);
729
if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
730
grub_errno = GRUB_ERR_NONE;
734
static struct grub_disk_dev grub_lvm_dev =
737
.id = GRUB_DISK_DEVICE_LVM_ID,
738
.iterate = grub_lvm_iterate,
739
.open = grub_lvm_open,
740
.close = grub_lvm_close,
741
.read = grub_lvm_read,
742
.write = grub_lvm_write,
744
.memberlist = grub_lvm_memberlist,
752
grub_device_iterate (&grub_lvm_scan_device);
756
grub_errno = GRUB_ERR_NONE;
759
grub_disk_dev_register (&grub_lvm_dev);
764
grub_disk_dev_unregister (&grub_lvm_dev);
766
/* FIXME: free the lvm list. */