2009-11-07 Robert Millan <rmh.grub@aybabtu.com>
[grub2/jjazz.git] / disk / lvm.c
blob126b49439f49321956ccbc93e6b61e6fb5ab1bda
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);
275 /* It's possible to have multiple copies of metadata areas, we just use the
276 first one. */
278 /* Allocate buffer space for the circular worst-case scenario. */
279 metadatabuf = grub_malloc (2 * mda_size);
280 if (! metadatabuf)
281 goto fail;
283 err = grub_disk_read (disk, 0, mda_offset, mda_size, metadatabuf);
284 if (err)
285 goto fail2;
287 mdah = (struct grub_lvm_mda_header *) metadatabuf;
288 if ((grub_strncmp ((char *)mdah->magic, GRUB_LVM_FMTT_MAGIC,
289 sizeof (mdah->magic)))
290 || (grub_le_to_cpu32 (mdah->version) != GRUB_LVM_FMTT_VERSION))
292 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
293 "Unknown LVM metadata header");
294 goto fail2;
297 rlocn = mdah->raw_locns;
298 if (grub_le_to_cpu64 (rlocn->offset) + grub_le_to_cpu64 (rlocn->size) >
299 grub_le_to_cpu64 (mdah->size))
301 /* Metadata is circular. Copy the wrap in place. */
302 grub_memcpy (metadatabuf + mda_size,
303 metadatabuf + GRUB_LVM_MDA_HEADER_SIZE,
304 grub_le_to_cpu64 (rlocn->offset) +
305 grub_le_to_cpu64 (rlocn->size) -
306 grub_le_to_cpu64 (mdah->size));
308 p = q = metadatabuf + grub_le_to_cpu64 (rlocn->offset);
310 while (*q != ' ' && q < metadatabuf + mda_size)
311 q++;
313 if (q == metadatabuf + mda_size)
314 goto fail2;
316 vgname_len = q - p;
317 vgname = grub_malloc (vgname_len + 1);
318 if (!vgname)
319 goto fail2;
321 grub_memcpy (vgname, p, vgname_len);
322 vgname[vgname_len] = '\0';
324 p = grub_strstr (q, "id = \"");
325 if (p == NULL)
326 goto fail3;
327 p += sizeof ("id = \"") - 1;
328 grub_memcpy (vg_id, p, GRUB_LVM_ID_STRLEN);
329 vg_id[GRUB_LVM_ID_STRLEN] = '\0';
331 for (vg = vg_list; vg; vg = vg->next)
333 if (! grub_memcmp(vg_id, vg->id, GRUB_LVM_ID_STRLEN))
334 break;
337 if (! vg)
339 /* First time we see this volume group. We've to create the
340 whole volume group structure. */
341 vg = grub_malloc (sizeof (*vg));
342 if (! vg)
343 goto fail3;
344 vg->name = vgname;
345 grub_memcpy (vg->id, vg_id, GRUB_LVM_ID_STRLEN+1);
347 vg->extent_size = grub_lvm_getvalue (&p, "extent_size = ");
348 if (p == NULL)
349 goto fail4;
351 vg->lvs = NULL;
352 vg->pvs = NULL;
354 p = grub_strstr (p, "physical_volumes {");
355 if (p)
357 p += sizeof ("physical_volumes {") - 1;
359 /* Add all the pvs to the volume group. */
360 while (1)
362 int s;
363 while (grub_isspace (*p))
364 p++;
366 if (*p == '}')
367 break;
369 pv = grub_malloc (sizeof (*pv));
370 q = p;
371 while (*q != ' ')
372 q++;
374 s = q - p;
375 pv->name = grub_malloc (s + 1);
376 grub_memcpy (pv->name, p, s);
377 pv->name[s] = '\0';
379 p = grub_strstr (p, "id = \"");
380 if (p == NULL)
381 goto pvs_fail;
382 p += sizeof("id = \"") - 1;
384 grub_memcpy (pv->id, p, GRUB_LVM_ID_STRLEN);
385 pv->id[GRUB_LVM_ID_STRLEN] = '\0';
387 pv->start = grub_lvm_getvalue (&p, "pe_start = ");
388 if (p == NULL)
389 goto pvs_fail;
391 p = grub_strchr (p, '}');
392 if (p == NULL)
393 goto pvs_fail;
394 p++;
396 pv->disk = NULL;
397 pv->next = vg->pvs;
398 vg->pvs = pv;
400 continue;
401 pvs_fail:
402 grub_free (pv->name);
403 grub_free (pv);
404 goto fail4;
408 p = grub_strstr (p, "logical_volumes");
409 if (p)
411 p += 18;
413 /* And add all the lvs to the volume group. */
414 while (1)
416 int s;
417 struct grub_lvm_lv *lv;
418 struct grub_lvm_segment *seg;
420 while (grub_isspace (*p))
421 p++;
423 if (*p == '}')
424 break;
426 lv = grub_malloc (sizeof (*lv));
428 q = p;
429 while (*q != ' ')
430 q++;
432 s = q - p;
433 lv->name = grub_malloc (vgname_len + 1 + s + 1);
434 grub_memcpy (lv->name, vgname, vgname_len);
435 lv->name[vgname_len] = '-';
436 grub_memcpy (lv->name + vgname_len + 1, p, s);
437 lv->name[vgname_len + 1 + s] = '\0';
439 lv->size = 0;
441 lv->segment_count = grub_lvm_getvalue (&p, "segment_count = ");
442 if (p == NULL)
443 goto lvs_fail;
444 lv->segments = grub_malloc (sizeof (*seg) * lv->segment_count);
445 seg = lv->segments;
447 for (i = 0; i < lv->segment_count; i++)
449 struct grub_lvm_stripe *stripe;
451 p = grub_strstr (p, "segment");
452 if (p == NULL)
453 goto lvs_segment_fail;
455 seg->start_extent = grub_lvm_getvalue (&p, "start_extent = ");
456 if (p == NULL)
457 goto lvs_segment_fail;
458 seg->extent_count = grub_lvm_getvalue (&p, "extent_count = ");
459 if (p == NULL)
460 goto lvs_segment_fail;
461 seg->stripe_count = grub_lvm_getvalue (&p, "stripe_count = ");
462 if (p == NULL)
463 goto lvs_segment_fail;
465 lv->size += seg->extent_count * vg->extent_size;
467 if (seg->stripe_count != 1)
468 seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
470 seg->stripes = grub_malloc (sizeof (*stripe)
471 * seg->stripe_count);
472 stripe = seg->stripes;
474 p = grub_strstr (p, "stripes = [");
475 if (p == NULL)
476 goto lvs_segment_fail2;
477 p += sizeof("stripes = [") - 1;
479 for (j = 0; j < seg->stripe_count; j++)
481 char *pvname;
483 p = grub_strchr (p, '"');
484 if (p == NULL)
485 continue;
486 q = ++p;
487 while (*q != '"')
488 q++;
490 s = q - p;
492 pvname = grub_malloc (s + 1);
493 if (pvname == NULL)
494 goto lvs_segment_fail2;
496 grub_memcpy (pvname, p, s);
497 pvname[s] = '\0';
499 if (vg->pvs)
500 for (pv = vg->pvs; pv; pv = pv->next)
502 if (! grub_strcmp (pvname, pv->name))
504 stripe->pv = pv;
505 break;
509 grub_free(pvname);
511 stripe->start = grub_lvm_getvalue (&p, ",");
512 if (p == NULL)
513 continue;
515 stripe++;
518 seg++;
520 continue;
521 lvs_segment_fail2:
522 grub_free (seg->stripes);
523 lvs_segment_fail:
524 goto fail4;
527 if (p != NULL)
528 p = grub_strchr (p, '}');
529 if (p == NULL)
530 goto lvs_fail;
531 p += 3;
533 lv->number = lv_count++;
534 lv->vg = vg;
535 lv->next = vg->lvs;
536 vg->lvs = lv;
538 continue;
539 lvs_fail:
540 grub_free (lv->name);
541 grub_free (lv);
542 goto fail4;
546 vg->next = vg_list;
547 vg_list = vg;
549 else
551 grub_free (vgname);
554 /* Match the device we are currently reading from with the right
555 PV. */
556 if (vg->pvs)
557 for (pv = vg->pvs; pv; pv = pv->next)
559 if (! grub_memcmp (pv->id, pv_id, GRUB_LVM_ID_STRLEN))
561 /* This could happen to LVM on RAID, pv->disk points to the
562 raid device, we shouldn't change it. */
563 if (! pv->disk)
564 pv->disk = grub_disk_open (name);
565 break;
569 goto fail2;
571 /* Failure path. */
572 fail4:
573 grub_free (vg);
574 fail3:
575 grub_free (vgname);
577 /* Normal exit path. */
578 fail2:
579 grub_free (metadatabuf);
580 fail:
581 grub_disk_close (disk);
582 return 0;
585 static struct grub_disk_dev grub_lvm_dev =
587 .name = "lvm",
588 .id = GRUB_DISK_DEVICE_LVM_ID,
589 .iterate = grub_lvm_iterate,
590 .open = grub_lvm_open,
591 .close = grub_lvm_close,
592 .read = grub_lvm_read,
593 .write = grub_lvm_write,
594 #ifdef GRUB_UTIL
595 .memberlist = grub_lvm_memberlist,
596 #endif
597 .next = 0
601 GRUB_MOD_INIT(lvm)
603 grub_device_iterate (&grub_lvm_scan_device);
604 if (grub_errno)
606 grub_print_error ();
607 grub_errno = GRUB_ERR_NONE;
610 grub_disk_dev_register (&grub_lvm_dev);
613 GRUB_MOD_FINI(lvm)
615 grub_disk_dev_unregister (&grub_lvm_dev);
616 /* FIXME: free the lvm list. */