1
/* lvm.c - module to read Logical Volumes. */
3
* GRUB -- GRand Unified Bootloader
4
* Copyright (C) 2006 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 2 of the License, or
9
* (at your option) any later version.
11
* This program 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, write to the Free Software
18
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22
#include <grub/disk.h>
25
#include <grub/misc.h>
28
static struct grub_lvm_vg *vgs;
32
/* Go the string STR and return the number after STR. *P will point
35
grub_lvm_getvalue (char **p, char *str)
37
*p = grub_strstr (*p, str) + grub_strlen (str);
38
return grub_strtoul (*p, NULL, 10);
42
grub_lvm_iterate (int (*hook) (const char *name))
44
struct grub_lvm_vg *vg;
45
for (vg = vgs; vg; vg = vg->next)
47
struct grub_lvm_lv *lv;
48
for (lv = vgs->lvs; lv; lv = lv->next)
57
grub_lvm_open (const char *name, grub_disk_t disk)
59
struct grub_lvm_vg *vg;
60
struct grub_lvm_lv *lv = NULL;
61
for (vg = vgs; vg; vg = vg->next)
63
for (lv = vgs->lvs; lv; lv = lv->next)
64
if (! grub_strcmp (lv->name, name))
72
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "Unknown device");
74
disk->has_partitions = 0;
75
disk->id = lv->number;
77
disk->total_sectors = lv->size;
83
grub_lvm_close (grub_disk_t disk __attribute ((unused)))
89
grub_lvm_read (grub_disk_t disk, grub_disk_addr_t sector,
90
grub_size_t size, char *buf)
93
struct grub_lvm_lv *lv = disk->data;
94
struct grub_lvm_vg *vg = lv->vg;
95
struct grub_lvm_segment *seg = lv->segments;
96
struct grub_lvm_pv *pv;
101
extent = grub_divmod64 (sector, vg->extent_size, NULL);
103
/* Find the right segment. */
104
for (i = 0; i < lv->segment_count; i++)
106
if ((seg->start_extent <= extent)
107
&& ((seg->start_extent + seg->extent_count) > extent))
115
if (seg->stripe_count == 1)
117
/* This segment is linear, so that's easy. We just need to find
118
out the offset in the physical volume and read SIZE bytes
120
struct grub_lvm_stripe *stripe = seg->stripes;
121
grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
124
seg_offset = ((grub_uint64_t) stripe->start
125
* (grub_uint64_t) vg->extent_size) + pv->start;
127
offset = sector - ((grub_uint64_t) seg->start_extent
128
* (grub_uint64_t) vg->extent_size) + seg_offset;
132
/* This is a striped segment. We have to find the right PV
134
struct grub_lvm_stripe *stripe = seg->stripes;
136
grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
137
unsigned int stripenr;
139
offset = sector - ((grub_uint64_t) seg->start_extent
140
* (grub_uint64_t) vg->extent_size);
142
a = grub_divmod64 (offset, seg->stripe_size, NULL);
143
grub_divmod64 (a, seg->stripe_count, &stripenr);
145
a = grub_divmod64 (offset, seg->stripe_size * seg->stripe_count, NULL);
146
grub_divmod64 (offset, seg->stripe_size, &b);
147
offset = a * seg->stripe_size + b;
152
seg_offset = ((grub_uint64_t) stripe->start
153
* (grub_uint64_t) vg->extent_size) + pv->start;
155
offset += seg_offset;
158
/* Check whether we actually know the physical volume we want to
161
err = grub_disk_read (pv->disk, offset, 0,
162
size << GRUB_DISK_SECTOR_BITS, buf);
164
err = grub_error (GRUB_ERR_UNKNOWN_DEVICE,
165
"Physical volume %s not found", pv->name);
171
grub_lvm_write (grub_disk_t disk __attribute ((unused)),
172
grub_disk_addr_t sector __attribute ((unused)),
173
grub_size_t size __attribute ((unused)),
174
const char *buf __attribute ((unused)))
176
return GRUB_ERR_NOT_IMPLEMENTED_YET;
180
grub_lvm_scan_device (const char *name)
184
grub_uint64_t da_offset, da_size, mda_offset, mda_size;
185
char buf[GRUB_LVM_LABEL_SIZE];
186
char vg_id[GRUB_LVM_ID_STRLEN+1];
187
char pv_id[GRUB_LVM_ID_STRLEN+1];
188
char *metadatabuf, *p, *q, *vgname;
189
struct grub_lvm_label_header *lh = (struct grub_lvm_label_header *) buf;
190
struct grub_lvm_pv_header *pvh;
191
struct grub_lvm_disk_locn *dlocn;
192
struct grub_lvm_mda_header *mdah;
193
struct grub_lvm_raw_locn *rlocn;
194
unsigned int i, j, vgname_len;
195
struct grub_lvm_vg *vg;
196
struct grub_lvm_pv *pv;
198
disk = grub_disk_open (name);
202
/* Search for label. */
203
for (i = 0; i < GRUB_LVM_LABEL_SCAN_SECTORS; i++)
205
err = grub_disk_read (disk, i, 0, sizeof(buf), buf);
209
if ((! grub_strncmp ((char *)lh->id, GRUB_LVM_LABEL_ID,
211
&& (! grub_strncmp ((char *)lh->type, GRUB_LVM_LVM2_LABEL,
216
/* Return if we didn't find a label. */
217
if (i == GRUB_LVM_LABEL_SCAN_SECTORS)
220
pvh = (struct grub_lvm_pv_header *) (buf + grub_le_to_cpu32(lh->offset_xl));
222
for (i = 0, j = 0; i < GRUB_LVM_ID_LEN; i++)
224
pv_id[j++] = pvh->pv_uuid[i];
225
if ((i != 1) && (i != 29) && (i % 4 == 1))
230
dlocn = pvh->disk_areas_xl;
231
da_offset = grub_le_to_cpu64 (dlocn->offset);
232
da_size = grub_le_to_cpu64 (dlocn->size);
235
/* Is it possible to have multiple data/metadata areas? I haven't
236
seen devices that have it. */
239
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
240
"We don't support multiple data areas");
246
mda_offset = grub_le_to_cpu64 (dlocn->offset);
247
mda_size = grub_le_to_cpu64 (dlocn->size);
252
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
253
"We don't support multiple metadata areas");
258
metadatabuf = grub_malloc (mda_size);
262
err = grub_disk_read (disk, 0, mda_offset, mda_size, metadatabuf);
266
mdah = (struct grub_lvm_mda_header *) metadatabuf;
267
if ((grub_strncmp ((char *)mdah->magic, GRUB_LVM_FMTT_MAGIC,
268
sizeof (mdah->magic)))
269
|| (grub_le_to_cpu32 (mdah->version) != GRUB_LVM_FMTT_VERSION))
271
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
272
"Unknown metadata header");
276
rlocn = mdah->raw_locns;
277
p = q = metadatabuf + grub_le_to_cpu64 (rlocn->offset);
283
vgname = grub_malloc (vgname_len + 1);
287
grub_memcpy (vgname, p, vgname_len);
288
vgname[vgname_len] = '\0';
290
p = grub_strstr (q, "id = \"") + sizeof ("id = \"") - 1;
291
grub_memcpy (vg_id, p, GRUB_LVM_ID_STRLEN);
292
vg_id[GRUB_LVM_ID_STRLEN] = '\0';
294
for (vg = vgs; vg; vg = vg->next)
296
if (! grub_memcmp(vg_id, vg->id, GRUB_LVM_ID_STRLEN))
302
/* First time we see this volume group. We've to create the
303
whole volume group structure. */
304
vg = grub_malloc (sizeof (*vg));
311
grub_memcpy (vg->id, vg_id, GRUB_LVM_ID_STRLEN+1);
313
vg->extent_size = grub_lvm_getvalue (&p, "extent_size = ");
320
p = grub_strstr (p, "physical_volumes {")
321
+ sizeof ("physical_volumes {") - 1;
323
/* Add all the pvs to the volume group. */
327
while (grub_isspace (*p))
333
pv = grub_malloc (sizeof (*pv));
339
pv->name = grub_malloc (s + 1);
340
grub_memcpy (pv->name, p, s);
343
p = grub_strstr (p, "id = \"") + sizeof("id = \"") - 1;
345
grub_memcpy (pv->id, p, GRUB_LVM_ID_STRLEN);
346
pv->id[GRUB_LVM_ID_STRLEN] = '\0';
348
pv->start = grub_lvm_getvalue (&p, "pe_start = ");
353
p = grub_strchr (p, '}') + 1;
356
p = grub_strstr (p, "logical_volumes");
359
/* And add all the lvs to the volume group. */
363
struct grub_lvm_lv *lv;
364
struct grub_lvm_segment *seg;
366
while (grub_isspace (*p))
372
lv = grub_malloc (sizeof (lv));
379
lv->name = grub_malloc (vgname_len + 1 + s + 1);
380
grub_memcpy (lv->name, vgname, vgname_len);
381
lv->name[vgname_len] = '-';
382
grub_memcpy (lv->name + vgname_len + 1, p, s);
383
lv->name[vgname_len + 1 + s] = '\0';
387
lv->segment_count = grub_lvm_getvalue (&p, "segment_count = ");
388
lv->segments = grub_malloc (sizeof (*seg) * lv->segment_count);
391
for (i = 0; i < lv->segment_count; i++)
393
struct grub_lvm_stripe *stripe;
395
p = grub_strstr (p, "segment");
397
seg->start_extent = grub_lvm_getvalue (&p, "start_extent = ");
398
seg->extent_count = grub_lvm_getvalue (&p, "extent_count = ");
399
seg->stripe_count = grub_lvm_getvalue (&p, "stripe_count = ");
401
lv->size += seg->extent_count * vg->extent_size;
403
if (seg->stripe_count != 1)
404
seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
406
seg->stripes = grub_malloc (sizeof (*stripe)
407
* seg->stripe_count);
408
stripe = seg->stripes;
410
p = grub_strstr (p, "stripes = [")
411
+ sizeof("stripes = [") - 1;
413
for (j = 0; j < seg->stripe_count; j++)
417
q = p = grub_strchr (p, '"') + 1;
422
grub_memcpy (pvname, p, s);
425
for (pv = vg->pvs; pv; pv = pv->next)
427
if (! grub_strcmp (pvname, pv->name))
434
p = grub_strchr (p, ',') + 1;
435
stripe->start = grub_strtoul (p, NULL, 10);
443
lv->number = lv_count++;
448
p = grub_strchr (p, '}') + 3;
456
/* Match the device we are currently reading from with the right
458
for (pv = vg->pvs; pv; pv = pv->next)
460
if (! grub_memcmp (pv->id, pv_id, GRUB_LVM_ID_STRLEN))
462
pv->disk = grub_disk_open (name);
468
grub_free (metadatabuf);
470
grub_disk_close (disk);
474
static struct grub_disk_dev grub_lvm_dev =
477
.id = GRUB_DISK_DEVICE_LVM_ID,
478
.iterate = grub_lvm_iterate,
479
.open = grub_lvm_open,
480
.close = grub_lvm_close,
481
.read = grub_lvm_read,
482
.write = grub_lvm_write,
489
grub_device_iterate (&grub_lvm_scan_device);
490
grub_disk_dev_register (&grub_lvm_dev);
495
grub_disk_dev_unregister (&grub_lvm_dev);
496
/* FIXME: free the lvm list. */