1 /* lvm.c - module to read Logical Volumes. */
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/>.
21 #include <grub/disk.h>
24 #include <grub/misc.h>
27 static struct grub_lvm_vg
*vg_list
;
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. */
35 grub_lvm_getvalue (char **p
, char *str
)
37 *p
= grub_strstr (*p
, str
);
40 *p
+= grub_strlen (str
);
41 return grub_strtoul (*p
, NULL
, 10);
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
;
52 for (lv
= vg
->lvs
; lv
; lv
= lv
->next
)
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
;
69 for (pv
= lv
->vg
->pvs
; pv
; pv
= pv
->next
)
71 tmp
= grub_malloc (sizeof (*tmp
));
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
)
89 for (lv
= vg
->lvs
; lv
; lv
= lv
->next
)
90 if (! grub_strcmp (lv
->name
, name
))
98 return grub_error (GRUB_ERR_UNKNOWN_DEVICE
, "Unknown LVM device %s", name
);
100 disk
->has_partitions
= 0;
101 disk
->id
= lv
->number
;
103 disk
->total_sectors
= lv
->size
;
109 grub_lvm_close (grub_disk_t disk
__attribute ((unused
)))
115 grub_lvm_read (grub_disk_t disk
, grub_disk_addr_t sector
,
116 grub_size_t size
, char *buf
)
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
;
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
))
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
146 struct grub_lvm_stripe
*stripe
= seg
->stripes
;
147 grub_uint64_t seg_offset
; /* Offset of the segment in PV device. */
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
;
158 /* This is a striped segment. We have to find the right PV
160 struct grub_lvm_stripe
*stripe
= seg
->stripes
;
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
;
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
187 err
= grub_disk_read (pv
->disk
, offset
, 0,
188 size
<< GRUB_DISK_SECTOR_BITS
, buf
);
190 err
= grub_error (GRUB_ERR_UNKNOWN_DEVICE
,
191 "Physical volume %s not found", pv
->name
);
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
;
206 grub_lvm_scan_device (const char *name
)
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
);
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
);
235 if ((! grub_strncmp ((char *)lh
->id
, GRUB_LVM_LABEL_ID
,
237 && (! grub_strncmp ((char *)lh
->type
, GRUB_LVM_LVM2_LABEL
,
242 /* Return if we didn't find a label. */
243 if (i
== GRUB_LVM_LABEL_SCAN_SECTORS
)
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))
256 dlocn
= pvh
->disk_areas_xl
;
257 da_offset
= grub_le_to_cpu64 (dlocn
->offset
);
258 da_size
= grub_le_to_cpu64 (dlocn
->size
);
261 /* Is it possible to have multiple data/metadata areas? I haven't
262 seen devices that have it. */
265 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET
,
266 "We don't support multiple LVM data areas");
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
278 /* Allocate buffer space for the circular worst-case scenario. */
279 metadatabuf
= grub_malloc (2 * mda_size
);
283 err
= grub_disk_read (disk
, 0, mda_offset
, mda_size
, metadatabuf
);
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");
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
)
313 if (q
== metadatabuf
+ mda_size
)
317 vgname
= grub_malloc (vgname_len
+ 1);
321 grub_memcpy (vgname
, p
, vgname_len
);
322 vgname
[vgname_len
] = '\0';
324 p
= grub_strstr (q
, "id = \"");
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
))
339 /* First time we see this volume group. We've to create the
340 whole volume group structure. */
341 vg
= grub_malloc (sizeof (*vg
));
345 grub_memcpy (vg
->id
, vg_id
, GRUB_LVM_ID_STRLEN
+1);
347 vg
->extent_size
= grub_lvm_getvalue (&p
, "extent_size = ");
354 p
= grub_strstr (p
, "physical_volumes {");
357 p
+= sizeof ("physical_volumes {") - 1;
359 /* Add all the pvs to the volume group. */
363 while (grub_isspace (*p
))
369 pv
= grub_malloc (sizeof (*pv
));
375 pv
->name
= grub_malloc (s
+ 1);
376 grub_memcpy (pv
->name
, p
, s
);
379 p
= grub_strstr (p
, "id = \"");
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 = ");
391 p
= grub_strchr (p
, '}');
402 grub_free (pv
->name
);
408 p
= grub_strstr (p
, "logical_volumes");
413 /* And add all the lvs to the volume group. */
417 struct grub_lvm_lv
*lv
;
418 struct grub_lvm_segment
*seg
;
420 while (grub_isspace (*p
))
426 lv
= grub_malloc (sizeof (*lv
));
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';
441 lv
->segment_count
= grub_lvm_getvalue (&p
, "segment_count = ");
444 lv
->segments
= grub_malloc (sizeof (*seg
) * lv
->segment_count
);
447 for (i
= 0; i
< lv
->segment_count
; i
++)
449 struct grub_lvm_stripe
*stripe
;
451 p
= grub_strstr (p
, "segment");
453 goto lvs_segment_fail
;
455 seg
->start_extent
= grub_lvm_getvalue (&p
, "start_extent = ");
457 goto lvs_segment_fail
;
458 seg
->extent_count
= grub_lvm_getvalue (&p
, "extent_count = ");
460 goto lvs_segment_fail
;
461 seg
->stripe_count
= grub_lvm_getvalue (&p
, "stripe_count = ");
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 = [");
476 goto lvs_segment_fail2
;
477 p
+= sizeof("stripes = [") - 1;
479 for (j
= 0; j
< seg
->stripe_count
; j
++)
483 p
= grub_strchr (p
, '"');
492 pvname
= grub_malloc (s
+ 1);
494 goto lvs_segment_fail2
;
496 grub_memcpy (pvname
, p
, s
);
500 for (pv
= vg
->pvs
; pv
; pv
= pv
->next
)
502 if (! grub_strcmp (pvname
, pv
->name
))
511 stripe
->start
= grub_lvm_getvalue (&p
, ",");
522 grub_free (seg
->stripes
);
528 p
= grub_strchr (p
, '}');
533 lv
->number
= lv_count
++;
540 grub_free (lv
->name
);
554 /* Match the device we are currently reading from with the right
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. */
564 pv
->disk
= grub_disk_open (name
);
577 /* Normal exit path. */
579 grub_free (metadatabuf
);
581 grub_disk_close (disk
);
585 static struct grub_disk_dev grub_lvm_dev
=
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
,
595 .memberlist
= grub_lvm_memberlist
,
603 grub_device_iterate (&grub_lvm_scan_device
);
607 grub_errno
= GRUB_ERR_NONE
;
610 grub_disk_dev_register (&grub_lvm_dev
);
615 grub_disk_dev_unregister (&grub_lvm_dev
);
616 /* FIXME: free the lvm list. */