1 /* lvm.c - module to read Logical Volumes. */
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2006,2007,2008,2009,2011 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/>.
21 #include <grub/disk.h>
24 #include <grub/misc.h>
26 #include <grub/partition.h>
27 #include <grub/i18n.h>
30 #include <grub/emu/misc.h>
31 #include <grub/emu/hostdisk.h>
34 GRUB_MOD_LICENSE ("GPLv3+");
37 /* Go the string STR and return the number after STR. *P will point
38 at the number. In case STR is not found, *P will be NULL and the
39 return value will be 0. */
41 grub_lvm_getvalue (char **p
, const char *str
)
43 *p
= grub_strstr (*p
, str
);
46 *p
+= grub_strlen (str
);
47 return grub_strtoull (*p
, p
, 10);
52 grub_lvm_checkvalue (char **p
, char *str
, char *tmpl
)
54 int tmpllen
= grub_strlen (tmpl
);
55 *p
= grub_strstr (*p
, str
);
58 *p
+= grub_strlen (str
);
61 return (grub_memcmp (*p
+ 1, tmpl
, tmpllen
) == 0 && (*p
)[tmpllen
+ 1] == '"');
66 grub_lvm_check_flag (char *p
, const char *str
, const char *flag
)
68 grub_size_t len_str
= grub_strlen (str
), len_flag
= grub_strlen (flag
);
72 p
= grub_strstr (p
, str
);
76 if (grub_memcmp (p
, " = [", sizeof (" = [") - 1) != 0)
78 q
= p
+ sizeof (" = [") - 1;
81 while (grub_isspace (*q
))
86 if (grub_memcmp (q
, flag
, len_flag
) == 0 && q
[len_flag
] == '"')
98 static struct grub_diskfilter_vg
*
99 grub_lvm_detect (grub_disk_t disk
,
100 struct grub_diskfilter_pv_id
*id
,
101 grub_disk_addr_t
*start_sector
)
104 grub_uint64_t mda_offset
, mda_size
;
105 char buf
[GRUB_LVM_LABEL_SIZE
];
106 char vg_id
[GRUB_LVM_ID_STRLEN
+1];
107 char pv_id
[GRUB_LVM_ID_STRLEN
+1];
108 char *metadatabuf
, *p
, *q
, *vgname
;
109 struct grub_lvm_label_header
*lh
= (struct grub_lvm_label_header
*) buf
;
110 struct grub_lvm_pv_header
*pvh
;
111 struct grub_lvm_disk_locn
*dlocn
;
112 struct grub_lvm_mda_header
*mdah
;
113 struct grub_lvm_raw_locn
*rlocn
;
115 grub_size_t vgname_len
;
116 struct grub_diskfilter_vg
*vg
;
117 struct grub_diskfilter_pv
*pv
;
119 /* Search for label. */
120 for (i
= 0; i
< GRUB_LVM_LABEL_SCAN_SECTORS
; i
++)
122 err
= grub_disk_read (disk
, i
, 0, sizeof(buf
), buf
);
126 if ((! grub_strncmp ((char *)lh
->id
, GRUB_LVM_LABEL_ID
,
128 && (! grub_strncmp ((char *)lh
->type
, GRUB_LVM_LVM2_LABEL
,
133 /* Return if we didn't find a label. */
134 if (i
== GRUB_LVM_LABEL_SCAN_SECTORS
)
137 grub_util_info ("no LVM signature found");
142 pvh
= (struct grub_lvm_pv_header
*) (buf
+ grub_le_to_cpu32(lh
->offset_xl
));
144 for (i
= 0, j
= 0; i
< GRUB_LVM_ID_LEN
; i
++)
146 pv_id
[j
++] = pvh
->pv_uuid
[i
];
147 if ((i
!= 1) && (i
!= 29) && (i
% 4 == 1))
152 dlocn
= pvh
->disk_areas_xl
;
155 /* Is it possible to have multiple data/metadata areas? I haven't
156 seen devices that have it. */
159 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET
,
160 "we don't support multiple LVM data areas");
163 grub_util_info ("we don't support multiple LVM data areas\n");
169 mda_offset
= grub_le_to_cpu64 (dlocn
->offset
);
170 mda_size
= grub_le_to_cpu64 (dlocn
->size
);
172 /* It's possible to have multiple copies of metadata areas, we just use the
175 /* Allocate buffer space for the circular worst-case scenario. */
176 metadatabuf
= grub_malloc (2 * mda_size
);
180 err
= grub_disk_read (disk
, 0, mda_offset
, mda_size
, metadatabuf
);
184 mdah
= (struct grub_lvm_mda_header
*) metadatabuf
;
185 if ((grub_strncmp ((char *)mdah
->magic
, GRUB_LVM_FMTT_MAGIC
,
186 sizeof (mdah
->magic
)))
187 || (grub_le_to_cpu32 (mdah
->version
) != GRUB_LVM_FMTT_VERSION
))
189 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET
,
190 "unknown LVM metadata header");
192 grub_util_info ("unknown LVM metadata header\n");
197 rlocn
= mdah
->raw_locns
;
198 if (grub_le_to_cpu64 (rlocn
->offset
) + grub_le_to_cpu64 (rlocn
->size
) >
199 grub_le_to_cpu64 (mdah
->size
))
201 /* Metadata is circular. Copy the wrap in place. */
202 grub_memcpy (metadatabuf
+ mda_size
,
203 metadatabuf
+ GRUB_LVM_MDA_HEADER_SIZE
,
204 grub_le_to_cpu64 (rlocn
->offset
) +
205 grub_le_to_cpu64 (rlocn
->size
) -
206 grub_le_to_cpu64 (mdah
->size
));
208 p
= q
= metadatabuf
+ grub_le_to_cpu64 (rlocn
->offset
);
210 while (*q
!= ' ' && q
< metadatabuf
+ mda_size
)
213 if (q
== metadatabuf
+ mda_size
)
216 grub_util_info ("error parsing metadata\n");
222 vgname
= grub_malloc (vgname_len
+ 1);
226 grub_memcpy (vgname
, p
, vgname_len
);
227 vgname
[vgname_len
] = '\0';
229 p
= grub_strstr (q
, "id = \"");
233 grub_util_info ("couldn't find ID\n");
237 p
+= sizeof ("id = \"") - 1;
238 grub_memcpy (vg_id
, p
, GRUB_LVM_ID_STRLEN
);
239 vg_id
[GRUB_LVM_ID_STRLEN
] = '\0';
241 vg
= grub_diskfilter_get_vg_by_uuid (GRUB_LVM_ID_STRLEN
, vg_id
);
245 /* First time we see this volume group. We've to create the
246 whole volume group structure. */
247 vg
= grub_malloc (sizeof (*vg
));
251 vg
->uuid
= grub_malloc (GRUB_LVM_ID_STRLEN
);
254 grub_memcpy (vg
->uuid
, vg_id
, GRUB_LVM_ID_STRLEN
);
255 vg
->uuid_len
= GRUB_LVM_ID_STRLEN
;
257 vg
->extent_size
= grub_lvm_getvalue (&p
, "extent_size = ");
261 grub_util_info ("unknown extent size\n");
269 p
= grub_strstr (p
, "physical_volumes {");
272 p
+= sizeof ("physical_volumes {") - 1;
274 /* Add all the pvs to the volume group. */
278 while (grub_isspace (*p
))
284 pv
= grub_zalloc (sizeof (*pv
));
290 pv
->name
= grub_malloc (s
+ 1);
291 grub_memcpy (pv
->name
, p
, s
);
294 p
= grub_strstr (p
, "id = \"");
297 p
+= sizeof("id = \"") - 1;
299 pv
->id
.uuid
= grub_malloc (GRUB_LVM_ID_STRLEN
);
302 grub_memcpy (pv
->id
.uuid
, p
, GRUB_LVM_ID_STRLEN
);
303 pv
->id
.uuidlen
= GRUB_LVM_ID_STRLEN
;
305 pv
->start_sector
= grub_lvm_getvalue (&p
, "pe_start = ");
309 grub_util_info ("unknown pe_start\n");
314 p
= grub_strchr (p
, '}');
318 grub_util_info ("error parsing pe_start\n");
330 grub_free (pv
->name
);
336 p
= grub_strstr (p
, "logical_volumes {");
339 p
+= sizeof ("logical_volumes {") - 1;
341 /* And add all the lvs to the volume group. */
346 struct grub_diskfilter_lv
*lv
;
347 struct grub_diskfilter_segment
*seg
;
350 while (grub_isspace (*p
))
356 lv
= grub_zalloc (sizeof (*lv
));
363 lv
->name
= grub_strndup (p
, s
);
370 lv
->fullname
= grub_malloc (sizeof ("lvm/") - 1 + 2 * vgname_len
375 grub_memcpy (lv
->fullname
, "lvm/", sizeof ("lvm/") - 1);
376 optr
= lv
->fullname
+ sizeof ("lvm/") - 1;
377 for (iptr
= vgname
; iptr
< vgname
+ vgname_len
; iptr
++)
384 for (iptr
= p
; iptr
< p
+ s
; iptr
++)
391 lv
->idname
= grub_malloc (sizeof ("lvmid/")
392 + 2 * GRUB_LVM_ID_STRLEN
+ 1);
395 grub_memcpy (lv
->idname
, "lvmid/",
396 sizeof ("lvmid/") - 1);
397 grub_memcpy (lv
->idname
+ sizeof ("lvmid/") - 1,
398 vg_id
, GRUB_LVM_ID_STRLEN
);
399 lv
->idname
[sizeof ("lvmid/") - 1 + GRUB_LVM_ID_STRLEN
] = '/';
401 p
= grub_strstr (q
, "id = \"");
405 grub_util_info ("couldn't find ID\n");
409 p
+= sizeof ("id = \"") - 1;
410 grub_memcpy (lv
->idname
+ sizeof ("lvmid/") - 1
411 + GRUB_LVM_ID_STRLEN
+ 1,
412 p
, GRUB_LVM_ID_STRLEN
);
413 lv
->idname
[sizeof ("lvmid/") - 1 + 2 * GRUB_LVM_ID_STRLEN
+ 1] = '\0';
418 lv
->visible
= grub_lvm_check_flag (p
, "status", "VISIBLE");
419 is_pvmove
= grub_lvm_check_flag (p
, "status", "PVMOVE");
421 lv
->segment_count
= grub_lvm_getvalue (&p
, "segment_count = ");
425 grub_util_info ("unknown segment_count\n");
429 lv
->segments
= grub_malloc (sizeof (*seg
) * lv
->segment_count
);
432 for (i
= 0; i
< lv
->segment_count
; i
++)
435 p
= grub_strstr (p
, "segment");
439 grub_util_info ("unknown segment\n");
441 goto lvs_segment_fail
;
444 seg
->start_extent
= grub_lvm_getvalue (&p
, "start_extent = ");
448 grub_util_info ("unknown start_extent\n");
450 goto lvs_segment_fail
;
452 seg
->extent_count
= grub_lvm_getvalue (&p
, "extent_count = ");
456 grub_util_info ("unknown extent_count\n");
458 goto lvs_segment_fail
;
461 p
= grub_strstr (p
, "type = \"");
463 goto lvs_segment_fail
;
464 p
+= sizeof("type = \"") - 1;
466 lv
->size
+= seg
->extent_count
* vg
->extent_size
;
468 if (grub_memcmp (p
, "striped\"",
469 sizeof ("striped\"") - 1) == 0)
471 struct grub_diskfilter_node
*stripe
;
473 seg
->type
= GRUB_DISKFILTER_STRIPED
;
474 seg
->node_count
= grub_lvm_getvalue (&p
, "stripe_count = ");
478 grub_util_info ("unknown stripe_count\n");
480 goto lvs_segment_fail
;
483 if (seg
->node_count
!= 1)
484 seg
->stripe_size
= grub_lvm_getvalue (&p
, "stripe_size = ");
486 seg
->nodes
= grub_zalloc (sizeof (*stripe
)
490 p
= grub_strstr (p
, "stripes = [");
494 grub_util_info ("unknown stripes\n");
496 goto lvs_segment_fail2
;
498 p
+= sizeof("stripes = [") - 1;
500 for (j
= 0; j
< seg
->node_count
; j
++)
502 p
= grub_strchr (p
, '"');
511 stripe
->name
= grub_malloc (s
+ 1);
512 if (stripe
->name
== NULL
)
513 goto lvs_segment_fail2
;
515 grub_memcpy (stripe
->name
, p
, s
);
516 stripe
->name
[s
] = '\0';
520 stripe
->start
= grub_lvm_getvalue (&p
, ",")
528 else if (grub_memcmp (p
, "mirror\"", sizeof ("mirror\"") - 1)
531 seg
->type
= GRUB_DISKFILTER_MIRROR
;
532 seg
->node_count
= grub_lvm_getvalue (&p
, "mirror_count = ");
536 grub_util_info ("unknown mirror_count\n");
538 goto lvs_segment_fail
;
541 seg
->nodes
= grub_zalloc (sizeof (seg
->nodes
[0])
544 p
= grub_strstr (p
, "mirrors = [");
548 grub_util_info ("unknown mirrors\n");
550 goto lvs_segment_fail2
;
552 p
+= sizeof("mirrors = [") - 1;
554 for (j
= 0; j
< seg
->node_count
; j
++)
558 p
= grub_strchr (p
, '"');
567 lvname
= grub_malloc (s
+ 1);
569 goto lvs_segment_fail2
;
571 grub_memcpy (lvname
, p
, s
);
573 seg
->nodes
[j
].name
= lvname
;
576 /* Only first (original) is ok with in progress pvmove. */
580 else if (grub_memcmp (p
, "raid", sizeof ("raid") - 1)
581 == 0 && (p
[sizeof ("raid") - 1] >= '4'
582 && p
[sizeof ("raid") - 1] <= '6')
583 && p
[sizeof ("raidX") - 1] == '"')
585 switch (p
[sizeof ("raid") - 1])
588 seg
->type
= GRUB_DISKFILTER_RAID4
;
589 seg
->layout
= GRUB_RAID_LAYOUT_LEFT_ASYMMETRIC
;
592 seg
->type
= GRUB_DISKFILTER_RAID5
;
593 seg
->layout
= GRUB_RAID_LAYOUT_LEFT_SYMMETRIC
;
596 seg
->type
= GRUB_DISKFILTER_RAID6
;
597 seg
->layout
= (GRUB_RAID_LAYOUT_RIGHT_ASYMMETRIC
598 | GRUB_RAID_LAYOUT_MUL_FROM_POS
);
601 seg
->node_count
= grub_lvm_getvalue (&p
, "device_count = ");
606 grub_util_info ("unknown device_count\n");
608 goto lvs_segment_fail
;
611 seg
->stripe_size
= grub_lvm_getvalue (&p
, "stripe_size = ");
615 grub_util_info ("unknown stripe_size\n");
617 goto lvs_segment_fail
;
621 seg
->nodes
= grub_zalloc (sizeof (seg
->nodes
[0])
624 p
= grub_strstr (p
, "raids = [");
628 grub_util_info ("unknown mirrors\n");
630 goto lvs_segment_fail2
;
632 p
+= sizeof("raids = [") - 1;
634 for (j
= 0; j
< seg
->node_count
; j
++)
638 p
= grub_strchr (p
, '"');
639 p
= p
? grub_strchr (p
+ 1, '"') : 0;
640 p
= p
? grub_strchr (p
+ 1, '"') : 0;
649 lvname
= grub_malloc (s
+ 1);
651 goto lvs_segment_fail2
;
653 grub_memcpy (lvname
, p
, s
);
655 seg
->nodes
[j
].name
= lvname
;
658 if (seg
->type
== GRUB_DISKFILTER_RAID4
)
661 tmp
= seg
->nodes
[0].name
;
662 grub_memmove (seg
->nodes
, seg
->nodes
+ 1,
663 sizeof (seg
->nodes
[0])
664 * (seg
->node_count
- 1));
665 seg
->nodes
[seg
->node_count
- 1].name
= tmp
;
672 p2
= grub_strchr (p
, '"');
675 grub_util_info ("unknown LVM type %s\n", p
);
679 /* Found a non-supported type, give up and move on. */
688 grub_free (seg
->nodes
);
694 p
= grub_strchr (p
, '}');
701 grub_free (lv
->name
);
712 grub_free (lv
->name
);
720 struct grub_diskfilter_lv
*lv1
;
721 struct grub_diskfilter_lv
*lv2
;
722 for (lv1
= vg
->lvs
; lv1
; lv1
= lv1
->next
)
723 for (i
= 0; i
< lv1
->segment_count
; i
++)
724 for (j
= 0; j
< lv1
->segments
[i
].node_count
; j
++)
727 for (pv
= vg
->pvs
; pv
; pv
= pv
->next
)
729 if (! grub_strcmp (pv
->name
,
730 lv1
->segments
[i
].nodes
[j
].name
))
732 lv1
->segments
[i
].nodes
[j
].pv
= pv
;
736 if (lv1
->segments
[i
].nodes
[j
].pv
== NULL
)
737 for (lv2
= vg
->lvs
; lv2
; lv2
= lv2
->next
)
738 if (grub_strcmp (lv2
->name
,
739 lv1
->segments
[i
].nodes
[j
].name
) == 0)
740 lv1
->segments
[i
].nodes
[j
].lv
= lv2
;
744 if (grub_diskfilter_vg_register (vg
))
752 id
->uuid
= grub_malloc (GRUB_LVM_ID_STRLEN
);
755 grub_memcpy (id
->uuid
, pv_id
, GRUB_LVM_ID_STRLEN
);
756 id
->uuidlen
= GRUB_LVM_ID_STRLEN
;
757 grub_free (metadatabuf
);
768 grub_free (metadatabuf
);
775 static struct grub_diskfilter grub_lvm_dev
= {
777 .detect
= grub_lvm_detect
,
783 grub_diskfilter_register_back (&grub_lvm_dev
);
788 grub_diskfilter_unregister (&grub_lvm_dev
);