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