2009-06-10 Pavel Roskin <proski@gnu.org>
[grub2/bean.git] / disk / lvm.c
blob9f6c197fead70b51191c93ecd516a7ae1aadf891
1 /* lvm.c - module to read Logical Volumes. */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2006,2007,2008 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/>.
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>
27 static struct grub_lvm_vg *vg_list;
28 static int lv_count;
31 /* Go the string STR and return the number after STR. *P will point
32 at the number. In case STR is not found, *P will be NULL and the
33 return value will be 0. */
34 static int
35 grub_lvm_getvalue (char **p, char *str)
37 *p = grub_strstr (*p, str);
38 if (! *p)
39 return 0;
40 *p += grub_strlen (str);
41 return grub_strtoul (*p, NULL, 10);
44 static int
45 grub_lvm_iterate (int (*hook) (const char *name))
47 struct grub_lvm_vg *vg;
48 for (vg = vg_list; vg; vg = vg->next)
50 struct grub_lvm_lv *lv;
51 if (vg->lvs)
52 for (lv = vg->lvs; lv; lv = lv->next)
53 if (hook (lv->name))
54 return 1;
57 return 0;
60 #ifdef GRUB_UTIL
61 static grub_disk_memberlist_t
62 grub_lvm_memberlist (grub_disk_t disk)
64 struct grub_lvm_lv *lv = disk->data;
65 grub_disk_memberlist_t list = NULL, tmp;
66 struct grub_lvm_pv *pv;
68 if (lv->vg->pvs)
69 for (pv = lv->vg->pvs; pv; pv = pv->next)
71 tmp = grub_malloc (sizeof (*tmp));
72 tmp->disk = pv->disk;
73 tmp->next = list;
74 list = tmp;
77 return list;
79 #endif
81 static grub_err_t
82 grub_lvm_open (const char *name, grub_disk_t disk)
84 struct grub_lvm_vg *vg;
85 struct grub_lvm_lv *lv = NULL;
86 for (vg = vg_list; vg; vg = vg->next)
88 if (vg->lvs)
89 for (lv = vg->lvs; lv; lv = lv->next)
90 if (! grub_strcmp (lv->name, name))
91 break;
93 if (lv)
94 break;
97 if (! lv)
98 return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "Unknown LVM device %s", name);
100 disk->has_partitions = 0;
101 disk->id = lv->number;
102 disk->data = lv;
103 disk->total_sectors = lv->size;
105 return 0;
108 static void
109 grub_lvm_close (grub_disk_t disk __attribute ((unused)))
111 return;
114 static grub_err_t
115 grub_lvm_read (grub_disk_t disk, grub_disk_addr_t sector,
116 grub_size_t size, char *buf)
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;
127 extent = grub_divmod64 (sector, vg->extent_size, NULL);
129 /* Find the right segment. */
130 for (i = 0; i < lv->segment_count; i++)
132 if ((seg->start_extent <= extent)
133 && ((seg->start_extent + seg->extent_count) > extent))
135 break;
138 seg++;
141 if (seg->stripe_count == 1)
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. */
149 pv = stripe->pv;
150 seg_offset = ((grub_uint64_t) stripe->start
151 * (grub_uint64_t) vg->extent_size) + pv->start;
153 offset = sector - ((grub_uint64_t) seg->start_extent
154 * (grub_uint64_t) vg->extent_size) + seg_offset;
156 else
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;
165 offset = sector - ((grub_uint64_t) seg->start_extent
166 * (grub_uint64_t) vg->extent_size);
168 a = grub_divmod64 (offset, seg->stripe_size, NULL);
169 grub_divmod64 (a, seg->stripe_count, &stripenr);
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;
175 stripe += stripenr;
176 pv = stripe->pv;
178 seg_offset = ((grub_uint64_t) stripe->start
179 * (grub_uint64_t) vg->extent_size) + pv->start;
181 offset += seg_offset;
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);
193 return err;
196 static grub_err_t
197 grub_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)))
202 return GRUB_ERR_NOT_IMPLEMENTED_YET;
205 static int
206 grub_lvm_scan_device (const char *name)
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;
224 disk = grub_disk_open (name);
225 if (!disk)
226 return 0;
228 /* Search for label. */
229 for (i = 0; i < GRUB_LVM_LABEL_SCAN_SECTORS; i++)
231 err = grub_disk_read (disk, i, 0, sizeof(buf), buf);
232 if (err)
233 goto fail;
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;
242 /* Return if we didn't find a label. */
243 if (i == GRUB_LVM_LABEL_SCAN_SECTORS)
244 goto fail;
246 pvh = (struct grub_lvm_pv_header *) (buf + grub_le_to_cpu32(lh->offset_xl));
248 for (i = 0, j = 0; i < GRUB_LVM_ID_LEN; i++)
250 pv_id[j++] = pvh->pv_uuid[i];
251 if ((i != 1) && (i != 29) && (i % 4 == 1))
252 pv_id[j++] = '-';
254 pv_id[j] = '\0';
256 dlocn = pvh->disk_areas_xl;
257 da_offset = grub_le_to_cpu64 (dlocn->offset);
258 da_size = grub_le_to_cpu64 (dlocn->size);
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)
265 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
266 "We don't support multiple LVM data areas");
268 goto fail;
271 dlocn++;
272 mda_offset = grub_le_to_cpu64 (dlocn->offset);
273 mda_size = grub_le_to_cpu64 (dlocn->size);
274 dlocn++;
276 if (dlocn->offset)
278 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
279 "We don't support multiple LVM metadata areas");
281 goto fail;
284 /* Allocate buffer space for the circular worst-case scenario. */
285 metadatabuf = grub_malloc (2 * mda_size);
286 if (! metadatabuf)
287 goto fail;
289 err = grub_disk_read (disk, 0, mda_offset, mda_size, metadatabuf);
290 if (err)
291 goto fail2;
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))
298 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
299 "Unknown LVM metadata header");
300 goto fail2;
303 rlocn = mdah->raw_locns;
304 if (grub_le_to_cpu64 (rlocn->offset) + grub_le_to_cpu64 (rlocn->size) >
305 grub_le_to_cpu64 (mdah->size))
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));
314 p = q = metadatabuf + grub_le_to_cpu64 (rlocn->offset);
316 while (*q != ' ' && q < metadatabuf + mda_size)
317 q++;
319 if (q == metadatabuf + mda_size)
320 goto fail2;
322 vgname_len = q - p;
323 vgname = grub_malloc (vgname_len + 1);
324 if (!vgname)
325 goto fail2;
327 grub_memcpy (vgname, p, vgname_len);
328 vgname[vgname_len] = '\0';
330 p = grub_strstr (q, "id = \"");
331 if (p == NULL)
332 goto fail3;
333 p += sizeof ("id = \"") - 1;
334 grub_memcpy (vg_id, p, GRUB_LVM_ID_STRLEN);
335 vg_id[GRUB_LVM_ID_STRLEN] = '\0';
337 for (vg = vg_list; vg; vg = vg->next)
339 if (! grub_memcmp(vg_id, vg->id, GRUB_LVM_ID_STRLEN))
340 break;
343 if (! vg)
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)
349 goto fail3;
350 vg->name = vgname;
351 grub_memcpy (vg->id, vg_id, GRUB_LVM_ID_STRLEN+1);
353 vg->extent_size = grub_lvm_getvalue (&p, "extent_size = ");
354 if (p == NULL)
355 goto fail4;
357 vg->lvs = NULL;
358 vg->pvs = NULL;
360 p = grub_strstr (p, "physical_volumes {");
361 if (p)
363 p += sizeof ("physical_volumes {") - 1;
365 /* Add all the pvs to the volume group. */
366 while (1)
368 int s;
369 while (grub_isspace (*p))
370 p++;
372 if (*p == '}')
373 break;
375 pv = grub_malloc (sizeof (*pv));
376 q = p;
377 while (*q != ' ')
378 q++;
380 s = q - p;
381 pv->name = grub_malloc (s + 1);
382 grub_memcpy (pv->name, p, s);
383 pv->name[s] = '\0';
385 p = grub_strstr (p, "id = \"");
386 if (p == NULL)
387 goto pvs_fail;
388 p += sizeof("id = \"") - 1;
390 grub_memcpy (pv->id, p, GRUB_LVM_ID_STRLEN);
391 pv->id[GRUB_LVM_ID_STRLEN] = '\0';
393 pv->start = grub_lvm_getvalue (&p, "pe_start = ");
394 if (p == NULL)
395 goto pvs_fail;
397 p = grub_strchr (p, '}');
398 if (p == NULL)
399 goto pvs_fail;
400 p++;
402 pv->disk = NULL;
403 pv->next = vg->pvs;
404 vg->pvs = pv;
406 continue;
407 pvs_fail:
408 grub_free (pv->name);
409 grub_free (pv);
410 goto fail4;
414 p = grub_strstr (p, "logical_volumes");
415 if (p)
417 p += 18;
419 /* And add all the lvs to the volume group. */
420 while (1)
422 int s;
423 struct grub_lvm_lv *lv;
424 struct grub_lvm_segment *seg;
426 while (grub_isspace (*p))
427 p++;
429 if (*p == '}')
430 break;
432 lv = grub_malloc (sizeof (*lv));
434 q = p;
435 while (*q != ' ')
436 q++;
438 s = q - p;
439 lv->name = grub_malloc (vgname_len + 1 + s + 1);
440 grub_memcpy (lv->name, vgname, vgname_len);
441 lv->name[vgname_len] = '-';
442 grub_memcpy (lv->name + vgname_len + 1, p, s);
443 lv->name[vgname_len + 1 + s] = '\0';
445 lv->size = 0;
447 lv->segment_count = grub_lvm_getvalue (&p, "segment_count = ");
448 if (p == NULL)
449 goto lvs_fail;
450 lv->segments = grub_malloc (sizeof (*seg) * lv->segment_count);
451 seg = lv->segments;
453 for (i = 0; i < lv->segment_count; i++)
455 struct grub_lvm_stripe *stripe;
457 p = grub_strstr (p, "segment");
458 if (p == NULL)
459 goto lvs_segment_fail;
461 seg->start_extent = grub_lvm_getvalue (&p, "start_extent = ");
462 if (p == NULL)
463 goto lvs_segment_fail;
464 seg->extent_count = grub_lvm_getvalue (&p, "extent_count = ");
465 if (p == NULL)
466 goto lvs_segment_fail;
467 seg->stripe_count = grub_lvm_getvalue (&p, "stripe_count = ");
468 if (p == NULL)
469 goto lvs_segment_fail;
471 lv->size += seg->extent_count * vg->extent_size;
473 if (seg->stripe_count != 1)
474 seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
476 seg->stripes = grub_malloc (sizeof (*stripe)
477 * seg->stripe_count);
478 stripe = seg->stripes;
480 p = grub_strstr (p, "stripes = [");
481 if (p == NULL)
482 goto lvs_segment_fail2;
483 p += sizeof("stripes = [") - 1;
485 for (j = 0; j < seg->stripe_count; j++)
487 char *pvname;
489 p = grub_strchr (p, '"');
490 if (p == NULL)
491 continue;
492 q = ++p;
493 while (*q != '"')
494 q++;
496 s = q - p;
498 pvname = grub_malloc (s + 1);
499 if (pvname == NULL)
500 goto lvs_segment_fail2;
502 grub_memcpy (pvname, p, s);
503 pvname[s] = '\0';
505 if (vg->pvs)
506 for (pv = vg->pvs; pv; pv = pv->next)
508 if (! grub_strcmp (pvname, pv->name))
510 stripe->pv = pv;
511 break;
515 grub_free(pvname);
517 stripe->start = grub_lvm_getvalue (&p, ",");
518 if (p == NULL)
519 continue;
521 stripe++;
524 seg++;
526 continue;
527 lvs_segment_fail2:
528 grub_free (seg->stripes);
529 lvs_segment_fail:
530 goto fail4;
533 if (p != NULL)
534 p = grub_strchr (p, '}');
535 if (p == NULL)
536 goto lvs_fail;
537 p += 3;
539 lv->number = lv_count++;
540 lv->vg = vg;
541 lv->next = vg->lvs;
542 vg->lvs = lv;
544 continue;
545 lvs_fail:
546 grub_free (lv->name);
547 grub_free (lv);
548 goto fail4;
552 vg->next = vg_list;
553 vg_list = vg;
555 else
557 grub_free (vgname);
560 /* Match the device we are currently reading from with the right
561 PV. */
562 if (vg->pvs)
563 for (pv = vg->pvs; pv; pv = pv->next)
565 if (! grub_memcmp (pv->id, pv_id, GRUB_LVM_ID_STRLEN))
567 pv->disk = grub_disk_open (name);
568 break;
572 goto fail2;
574 /* Failure path. */
575 fail4:
576 grub_free (vg);
577 fail3:
578 grub_free (vgname);
580 /* Normal exit path. */
581 fail2:
582 grub_free (metadatabuf);
583 fail:
584 grub_disk_close (disk);
585 return 0;
588 static struct grub_disk_dev grub_lvm_dev =
590 .name = "lvm",
591 .id = GRUB_DISK_DEVICE_LVM_ID,
592 .iterate = grub_lvm_iterate,
593 .open = grub_lvm_open,
594 .close = grub_lvm_close,
595 .read = grub_lvm_read,
596 .write = grub_lvm_write,
597 #ifdef GRUB_UTIL
598 .memberlist = grub_lvm_memberlist,
599 #endif
600 .next = 0
604 GRUB_MOD_INIT(lvm)
606 grub_device_iterate (&grub_lvm_scan_device);
607 if (grub_errno)
609 grub_print_error ();
610 grub_errno = GRUB_ERR_NONE;
613 grub_disk_dev_register (&grub_lvm_dev);
616 GRUB_MOD_FINI(lvm)
618 grub_disk_dev_unregister (&grub_lvm_dev);
619 /* FIXME: free the lvm list. */