]> git.proxmox.com Git - grub2.git/blame - disk/lvm.c
added support for --enable-grub-emu-modules build
[grub2.git] / disk / lvm.c
CommitLineData
2b002173 1/* lvm.c - module to read Logical Volumes. */
2/*
3 * GRUB -- GRand Unified Bootloader
58bc8bd5 4 * Copyright (C) 2006,2007,2008,2009 Free Software Foundation, Inc.
2b002173 5 *
5a79f472 6 * GRUB is free software: you can redistribute it and/or modify
2b002173 7 * it under the terms of the GNU General Public License as published by
5a79f472 8 * the Free Software Foundation, either version 3 of the License, or
2b002173 9 * (at your option) any later version.
10 *
5a79f472 11 * GRUB is distributed in the hope that it will be useful,
2b002173 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.
15 *
16 * You should have received a copy of the GNU General Public License
5a79f472 17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2b002173 18 */
19
20#include <grub/dl.h>
21#include <grub/disk.h>
22#include <grub/mm.h>
23#include <grub/err.h>
24#include <grub/misc.h>
25#include <grub/lvm.h>
26
0973daeb
VS
27#ifdef GRUB_UTIL
28#include <grub/util/misc.h>
29#endif
30
4d42b77f 31static struct grub_lvm_vg *vg_list;
2b002173 32static int lv_count;
33
34\f
35/* Go the string STR and return the number after STR. *P will point
7ceeee39 36 at the number. In case STR is not found, *P will be NULL and the
37 return value will be 0. */
2b002173 38static int
39grub_lvm_getvalue (char **p, char *str)
40{
7ceeee39 41 *p = grub_strstr (*p, str);
42 if (! *p)
43 return 0;
44 *p += grub_strlen (str);
2b002173 45 return grub_strtoul (*p, NULL, 10);
46}
47
48static int
49grub_lvm_iterate (int (*hook) (const char *name))
50{
51 struct grub_lvm_vg *vg;
4d42b77f 52 for (vg = vg_list; vg; vg = vg->next)
2b002173 53 {
54 struct grub_lvm_lv *lv;
7ceeee39 55 if (vg->lvs)
56 for (lv = vg->lvs; lv; lv = lv->next)
57 if (hook (lv->name))
58 return 1;
2b002173 59 }
60
61 return 0;
62}
63
b92f0c18 64#ifdef GRUB_UTIL
65static grub_disk_memberlist_t
66grub_lvm_memberlist (grub_disk_t disk)
67{
68 struct grub_lvm_lv *lv = disk->data;
69 grub_disk_memberlist_t list = NULL, tmp;
70 struct grub_lvm_pv *pv;
71
7ceeee39 72 if (lv->vg->pvs)
73 for (pv = lv->vg->pvs; pv; pv = pv->next)
74 {
0973daeb
VS
75 if (!pv->disk)
76 grub_util_error ("Couldn't find PV %s. Check your device.map",
77 pv->name);
7ceeee39 78 tmp = grub_malloc (sizeof (*tmp));
79 tmp->disk = pv->disk;
80 tmp->next = list;
81 list = tmp;
82 }
b92f0c18 83
84 return list;
85}
86#endif
87
2b002173 88static grub_err_t
89grub_lvm_open (const char *name, grub_disk_t disk)
90{
91 struct grub_lvm_vg *vg;
92 struct grub_lvm_lv *lv = NULL;
4d42b77f 93 for (vg = vg_list; vg; vg = vg->next)
2b002173 94 {
7ceeee39 95 if (vg->lvs)
96 for (lv = vg->lvs; lv; lv = lv->next)
97 if (! grub_strcmp (lv->name, name))
98 break;
2b002173 99
100 if (lv)
101 break;
102 }
103
104 if (! lv)
7fd0baee 105 return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown LVM device %s", name);
2b002173 106
107 disk->has_partitions = 0;
108 disk->id = lv->number;
109 disk->data = lv;
110 disk->total_sectors = lv->size;
b39f9d20 111
2b002173 112 return 0;
113}
114
115static void
116grub_lvm_close (grub_disk_t disk __attribute ((unused)))
117{
118 return;
119}
120
121static grub_err_t
122grub_lvm_read (grub_disk_t disk, grub_disk_addr_t sector,
123 grub_size_t size, char *buf)
124{
125 grub_err_t err = 0;
126 struct grub_lvm_lv *lv = disk->data;
127 struct grub_lvm_vg *vg = lv->vg;
128 struct grub_lvm_segment *seg = lv->segments;
129 struct grub_lvm_pv *pv;
130 grub_uint64_t offset;
131 grub_uint64_t extent;
132 unsigned int i;
133
134 extent = grub_divmod64 (sector, vg->extent_size, NULL);
135
136 /* Find the right segment. */
137 for (i = 0; i < lv->segment_count; i++)
138 {
139 if ((seg->start_extent <= extent)
140 && ((seg->start_extent + seg->extent_count) > extent))
141 {
142 break;
143 }
144
145 seg++;
146 }
147
148 if (seg->stripe_count == 1)
149 {
150 /* This segment is linear, so that's easy. We just need to find
151 out the offset in the physical volume and read SIZE bytes
152 from that. */
153 struct grub_lvm_stripe *stripe = seg->stripes;
154 grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
155
156 pv = stripe->pv;
157 seg_offset = ((grub_uint64_t) stripe->start
158 * (grub_uint64_t) vg->extent_size) + pv->start;
159
160 offset = sector - ((grub_uint64_t) seg->start_extent
161 * (grub_uint64_t) vg->extent_size) + seg_offset;
162 }
163 else
164 {
165 /* This is a striped segment. We have to find the right PV
166 similar to RAID0. */
167 struct grub_lvm_stripe *stripe = seg->stripes;
168 grub_uint32_t a, b;
169 grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
170 unsigned int stripenr;
171
172 offset = sector - ((grub_uint64_t) seg->start_extent
173 * (grub_uint64_t) vg->extent_size);
174
175 a = grub_divmod64 (offset, seg->stripe_size, NULL);
176 grub_divmod64 (a, seg->stripe_count, &stripenr);
177
178 a = grub_divmod64 (offset, seg->stripe_size * seg->stripe_count, NULL);
179 grub_divmod64 (offset, seg->stripe_size, &b);
180 offset = a * seg->stripe_size + b;
181
182 stripe += stripenr;
183 pv = stripe->pv;
b39f9d20 184
2b002173 185 seg_offset = ((grub_uint64_t) stripe->start
186 * (grub_uint64_t) vg->extent_size) + pv->start;
187
188 offset += seg_offset;
189 }
190
191 /* Check whether we actually know the physical volume we want to
192 read from. */
193 if (pv->disk)
194 err = grub_disk_read (pv->disk, offset, 0,
195 size << GRUB_DISK_SECTOR_BITS, buf);
196 else
197 err = grub_error (GRUB_ERR_UNKNOWN_DEVICE,
7fd0baee 198 "physical volume %s not found", pv->name);
b39f9d20 199
2b002173 200 return err;
201}
202
203static grub_err_t
204grub_lvm_write (grub_disk_t disk __attribute ((unused)),
205 grub_disk_addr_t sector __attribute ((unused)),
206 grub_size_t size __attribute ((unused)),
207 const char *buf __attribute ((unused)))
208{
209 return GRUB_ERR_NOT_IMPLEMENTED_YET;
210}
211
212static int
213grub_lvm_scan_device (const char *name)
214{
215 grub_err_t err;
216 grub_disk_t disk;
217 grub_uint64_t da_offset, da_size, mda_offset, mda_size;
218 char buf[GRUB_LVM_LABEL_SIZE];
219 char vg_id[GRUB_LVM_ID_STRLEN+1];
220 char pv_id[GRUB_LVM_ID_STRLEN+1];
221 char *metadatabuf, *p, *q, *vgname;
222 struct grub_lvm_label_header *lh = (struct grub_lvm_label_header *) buf;
223 struct grub_lvm_pv_header *pvh;
224 struct grub_lvm_disk_locn *dlocn;
225 struct grub_lvm_mda_header *mdah;
226 struct grub_lvm_raw_locn *rlocn;
227 unsigned int i, j, vgname_len;
228 struct grub_lvm_vg *vg;
229 struct grub_lvm_pv *pv;
b39f9d20 230
2b002173 231 disk = grub_disk_open (name);
232 if (!disk)
233 return 0;
234
235 /* Search for label. */
236 for (i = 0; i < GRUB_LVM_LABEL_SCAN_SECTORS; i++)
237 {
238 err = grub_disk_read (disk, i, 0, sizeof(buf), buf);
239 if (err)
240 goto fail;
b39f9d20 241
2b002173 242 if ((! grub_strncmp ((char *)lh->id, GRUB_LVM_LABEL_ID,
243 sizeof (lh->id)))
244 && (! grub_strncmp ((char *)lh->type, GRUB_LVM_LVM2_LABEL,
245 sizeof (lh->type))))
246 break;
247 }
248
249 /* Return if we didn't find a label. */
250 if (i == GRUB_LVM_LABEL_SCAN_SECTORS)
251 goto fail;
b39f9d20 252
2b002173 253 pvh = (struct grub_lvm_pv_header *) (buf + grub_le_to_cpu32(lh->offset_xl));
254
255 for (i = 0, j = 0; i < GRUB_LVM_ID_LEN; i++)
256 {
257 pv_id[j++] = pvh->pv_uuid[i];
258 if ((i != 1) && (i != 29) && (i % 4 == 1))
259 pv_id[j++] = '-';
260 }
261 pv_id[j] = '\0';
262
263 dlocn = pvh->disk_areas_xl;
264 da_offset = grub_le_to_cpu64 (dlocn->offset);
265 da_size = grub_le_to_cpu64 (dlocn->size);
266
267 dlocn++;
268 /* Is it possible to have multiple data/metadata areas? I haven't
269 seen devices that have it. */
270 if (dlocn->offset)
271 {
272 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
7fd0baee 273 "we don't support multiple LVM data areas");
b39f9d20 274
2b002173 275 goto fail;
276 }
277
278 dlocn++;
279 mda_offset = grub_le_to_cpu64 (dlocn->offset);
280 mda_size = grub_le_to_cpu64 (dlocn->size);
b39f9d20 281
f45d6cfc 282 /* It's possible to have multiple copies of metadata areas, we just use the
283 first one. */
2b002173 284
1b7748eb 285 /* Allocate buffer space for the circular worst-case scenario. */
286 metadatabuf = grub_malloc (2 * mda_size);
2b002173 287 if (! metadatabuf)
288 goto fail;
289
290 err = grub_disk_read (disk, 0, mda_offset, mda_size, metadatabuf);
291 if (err)
292 goto fail2;
293
294 mdah = (struct grub_lvm_mda_header *) metadatabuf;
295 if ((grub_strncmp ((char *)mdah->magic, GRUB_LVM_FMTT_MAGIC,
296 sizeof (mdah->magic)))
297 || (grub_le_to_cpu32 (mdah->version) != GRUB_LVM_FMTT_VERSION))
298 {
299 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
7fd0baee 300 "unknown LVM metadata header");
2b002173 301 goto fail2;
302 }
303
304 rlocn = mdah->raw_locns;
1b7748eb 305 if (grub_le_to_cpu64 (rlocn->offset) + grub_le_to_cpu64 (rlocn->size) >
306 grub_le_to_cpu64 (mdah->size))
307 {
308 /* Metadata is circular. Copy the wrap in place. */
309 grub_memcpy (metadatabuf + mda_size,
310 metadatabuf + GRUB_LVM_MDA_HEADER_SIZE,
311 grub_le_to_cpu64 (rlocn->offset) +
312 grub_le_to_cpu64 (rlocn->size) -
313 grub_le_to_cpu64 (mdah->size));
314 }
2b002173 315 p = q = metadatabuf + grub_le_to_cpu64 (rlocn->offset);
316
d1dff95d 317 while (*q != ' ' && q < metadatabuf + mda_size)
2b002173 318 q++;
319
d1dff95d 320 if (q == metadatabuf + mda_size)
321 goto fail2;
322
2b002173 323 vgname_len = q - p;
324 vgname = grub_malloc (vgname_len + 1);
325 if (!vgname)
326 goto fail2;
327
328 grub_memcpy (vgname, p, vgname_len);
329 vgname[vgname_len] = '\0';
330
7f8866ed 331 p = grub_strstr (q, "id = \"");
332 if (p == NULL)
333 goto fail3;
334 p += sizeof ("id = \"") - 1;
2b002173 335 grub_memcpy (vg_id, p, GRUB_LVM_ID_STRLEN);
336 vg_id[GRUB_LVM_ID_STRLEN] = '\0';
337
4d42b77f 338 for (vg = vg_list; vg; vg = vg->next)
2b002173 339 {
340 if (! grub_memcmp(vg_id, vg->id, GRUB_LVM_ID_STRLEN))
341 break;
342 }
343
344 if (! vg)
345 {
346 /* First time we see this volume group. We've to create the
347 whole volume group structure. */
348 vg = grub_malloc (sizeof (*vg));
349 if (! vg)
7f8866ed 350 goto fail3;
2b002173 351 vg->name = vgname;
352 grub_memcpy (vg->id, vg_id, GRUB_LVM_ID_STRLEN+1);
353
354 vg->extent_size = grub_lvm_getvalue (&p, "extent_size = ");
7ceeee39 355 if (p == NULL)
7f8866ed 356 goto fail4;
2b002173 357
358 vg->lvs = NULL;
359 vg->pvs = NULL;
2b002173 360
7ceeee39 361 p = grub_strstr (p, "physical_volumes {");
362 if (p)
2b002173 363 {
7ceeee39 364 p += sizeof ("physical_volumes {") - 1;
b39f9d20 365
7ceeee39 366 /* Add all the pvs to the volume group. */
367 while (1)
368 {
369 int s;
370 while (grub_isspace (*p))
371 p++;
b39f9d20 372
7ceeee39 373 if (*p == '}')
374 break;
b39f9d20 375
7ceeee39 376 pv = grub_malloc (sizeof (*pv));
377 q = p;
378 while (*q != ' ')
379 q++;
b39f9d20 380
7ceeee39 381 s = q - p;
382 pv->name = grub_malloc (s + 1);
383 grub_memcpy (pv->name, p, s);
384 pv->name[s] = '\0';
b39f9d20 385
b7279447 386 p = grub_strstr (p, "id = \"");
7ceeee39 387 if (p == NULL)
388 goto pvs_fail;
b7279447 389 p += sizeof("id = \"") - 1;
b39f9d20 390
7ceeee39 391 grub_memcpy (pv->id, p, GRUB_LVM_ID_STRLEN);
392 pv->id[GRUB_LVM_ID_STRLEN] = '\0';
b39f9d20 393
7ceeee39 394 pv->start = grub_lvm_getvalue (&p, "pe_start = ");
395 if (p == NULL)
396 goto pvs_fail;
b39f9d20 397
b7279447 398 p = grub_strchr (p, '}');
399 if (p == NULL)
400 goto pvs_fail;
401 p++;
de4fa71c 402
403 pv->disk = NULL;
404 pv->next = vg->pvs;
405 vg->pvs = pv;
b39f9d20 406
7ceeee39 407 continue;
408 pvs_fail:
409 grub_free (pv->name);
410 grub_free (pv);
7f8866ed 411 goto fail4;
7ceeee39 412 }
2b002173 413 }
414
415 p = grub_strstr (p, "logical_volumes");
7ceeee39 416 if (p)
2b002173 417 {
7ceeee39 418 p += 18;
b39f9d20 419
7ceeee39 420 /* And add all the lvs to the volume group. */
421 while (1)
2b002173 422 {
7ceeee39 423 int s;
424 struct grub_lvm_lv *lv;
425 struct grub_lvm_segment *seg;
b39f9d20 426
7ceeee39 427 while (grub_isspace (*p))
428 p++;
b39f9d20 429
7ceeee39 430 if (*p == '}')
431 break;
b39f9d20 432
7ceeee39 433 lv = grub_malloc (sizeof (*lv));
b39f9d20 434
7ceeee39 435 q = p;
436 while (*q != ' ')
437 q++;
b39f9d20 438
7ceeee39 439 s = q - p;
440 lv->name = grub_malloc (vgname_len + 1 + s + 1);
441 grub_memcpy (lv->name, vgname, vgname_len);
442 lv->name[vgname_len] = '-';
443 grub_memcpy (lv->name + vgname_len + 1, p, s);
444 lv->name[vgname_len + 1 + s] = '\0';
b39f9d20 445
7ceeee39 446 lv->size = 0;
b39f9d20 447
7ceeee39 448 lv->segment_count = grub_lvm_getvalue (&p, "segment_count = ");
449 if (p == NULL)
450 goto lvs_fail;
451 lv->segments = grub_malloc (sizeof (*seg) * lv->segment_count);
452 seg = lv->segments;
b39f9d20 453
7ceeee39 454 for (i = 0; i < lv->segment_count; i++)
2b002173 455 {
7ceeee39 456 struct grub_lvm_stripe *stripe;
b39f9d20 457
7ceeee39 458 p = grub_strstr (p, "segment");
459 if (p == NULL)
460 goto lvs_segment_fail;
b39f9d20 461
7ceeee39 462 seg->start_extent = grub_lvm_getvalue (&p, "start_extent = ");
463 if (p == NULL)
464 goto lvs_segment_fail;
465 seg->extent_count = grub_lvm_getvalue (&p, "extent_count = ");
466 if (p == NULL)
467 goto lvs_segment_fail;
468 seg->stripe_count = grub_lvm_getvalue (&p, "stripe_count = ");
469 if (p == NULL)
470 goto lvs_segment_fail;
b39f9d20 471
7ceeee39 472 lv->size += seg->extent_count * vg->extent_size;
b39f9d20 473
7ceeee39 474 if (seg->stripe_count != 1)
475 seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
b39f9d20 476
7ceeee39 477 seg->stripes = grub_malloc (sizeof (*stripe)
478 * seg->stripe_count);
479 stripe = seg->stripes;
b39f9d20 480
7ceeee39 481 p = grub_strstr (p, "stripes = [");
482 if (p == NULL)
483 goto lvs_segment_fail2;
484 p += sizeof("stripes = [") - 1;
b39f9d20 485
7ceeee39 486 for (j = 0; j < seg->stripe_count; j++)
2b002173 487 {
0a1150e2 488 char *pvname;
b39f9d20 489
7ceeee39 490 p = grub_strchr (p, '"');
491 if (p == NULL)
492 continue;
493 q = ++p;
494 while (*q != '"')
495 q++;
496
497 s = q - p;
b39f9d20 498
0a1150e2 499 pvname = grub_malloc (s + 1);
01979850 500 if (pvname == NULL)
501 goto lvs_segment_fail2;
b39f9d20 502
7ceeee39 503 grub_memcpy (pvname, p, s);
504 pvname[s] = '\0';
b39f9d20 505
7ceeee39 506 if (vg->pvs)
507 for (pv = vg->pvs; pv; pv = pv->next)
508 {
509 if (! grub_strcmp (pvname, pv->name))
510 {
511 stripe->pv = pv;
512 break;
513 }
514 }
b39f9d20 515
0a1150e2 516 grub_free(pvname);
b39f9d20 517
7ceeee39 518 stripe->start = grub_lvm_getvalue (&p, ",");
519 if (p == NULL)
520 continue;
b39f9d20 521
7ceeee39 522 stripe++;
2b002173 523 }
b39f9d20 524
7ceeee39 525 seg++;
526
527 continue;
528 lvs_segment_fail2:
529 grub_free (seg->stripes);
530 lvs_segment_fail:
7f8866ed 531 goto fail4;
2b002173 532 }
533
af63ada2 534 if (p != NULL)
535 p = grub_strchr (p, '}');
7ceeee39 536 if (p == NULL)
537 goto lvs_fail;
538 p += 3;
b39f9d20 539
de4fa71c 540 lv->number = lv_count++;
541 lv->vg = vg;
542 lv->next = vg->lvs;
543 vg->lvs = lv;
544
7ceeee39 545 continue;
546 lvs_fail:
547 grub_free (lv->name);
548 grub_free (lv);
7f8866ed 549 goto fail4;
2b002173 550 }
2b002173 551 }
de4fa71c 552
553 vg->next = vg_list;
554 vg_list = vg;
2b002173 555 }
556 else
557 {
558 grub_free (vgname);
559 }
560
561 /* Match the device we are currently reading from with the right
562 PV. */
7ceeee39 563 if (vg->pvs)
564 for (pv = vg->pvs; pv; pv = pv->next)
565 {
566 if (! grub_memcmp (pv->id, pv_id, GRUB_LVM_ID_STRLEN))
567 {
f45d6cfc 568 /* This could happen to LVM on RAID, pv->disk points to the
569 raid device, we shouldn't change it. */
570 if (! pv->disk)
571 pv->disk = grub_disk_open (name);
7ceeee39 572 break;
573 }
574 }
2b002173 575
7f8866ed 576 goto fail2;
577
578 /* Failure path. */
579 fail4:
580 grub_free (vg);
581 fail3:
582 grub_free (vgname);
583
584 /* Normal exit path. */
2b002173 585 fail2:
586 grub_free (metadatabuf);
587 fail:
588 grub_disk_close (disk);
589 return 0;
590}
591
592static struct grub_disk_dev grub_lvm_dev =
593 {
594 .name = "lvm",
595 .id = GRUB_DISK_DEVICE_LVM_ID,
596 .iterate = grub_lvm_iterate,
597 .open = grub_lvm_open,
598 .close = grub_lvm_close,
599 .read = grub_lvm_read,
600 .write = grub_lvm_write,
b92f0c18 601#ifdef GRUB_UTIL
602 .memberlist = grub_lvm_memberlist,
603#endif
2b002173 604 .next = 0
605 };
606
607\f
608GRUB_MOD_INIT(lvm)
609{
610 grub_device_iterate (&grub_lvm_scan_device);
1082b929 611 if (grub_errno)
612 {
613 grub_print_error ();
614 grub_errno = GRUB_ERR_NONE;
615 }
616
2b002173 617 grub_disk_dev_register (&grub_lvm_dev);
618}
619
620GRUB_MOD_FINI(lvm)
621{
622 grub_disk_dev_unregister (&grub_lvm_dev);
623 /* FIXME: free the lvm list. */
624}