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
);
278 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET
,
279 "We don't support multiple LVM metadata areas");
284 /* Allocate buffer space for the circular worst-case scenario. */
285 metadatabuf
= grub_malloc (2 * mda_size
);
289 err
= grub_disk_read (disk
, 0, mda_offset
, mda_size
, metadatabuf
);
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");
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
)
319 if (q
== metadatabuf
+ mda_size
)
323 vgname
= grub_malloc (vgname_len
+ 1);
327 grub_memcpy (vgname
, p
, vgname_len
);
328 vgname
[vgname_len
] = '\0';
330 p
= grub_strstr (q
, "id = \"");
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
))
345 /* First time we see this volume group. We've to create the
346 whole volume group structure. */
347 vg
= grub_malloc (sizeof (*vg
));
351 grub_memcpy (vg
->id
, vg_id
, GRUB_LVM_ID_STRLEN
+1);
353 vg
->extent_size
= grub_lvm_getvalue (&p
, "extent_size = ");
360 p
= grub_strstr (p
, "physical_volumes {");
363 p
+= sizeof ("physical_volumes {") - 1;
365 /* Add all the pvs to the volume group. */
369 while (grub_isspace (*p
))
375 pv
= grub_malloc (sizeof (*pv
));
381 pv
->name
= grub_malloc (s
+ 1);
382 grub_memcpy (pv
->name
, p
, s
);
385 p
= grub_strstr (p
, "id = \"");
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 = ");
397 p
= grub_strchr (p
, '}');
408 grub_free (pv
->name
);
414 p
= grub_strstr (p
, "logical_volumes");
419 /* And add all the lvs to the volume group. */
423 struct grub_lvm_lv
*lv
;
424 struct grub_lvm_segment
*seg
;
426 while (grub_isspace (*p
))
432 lv
= grub_malloc (sizeof (*lv
));
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';
447 lv
->segment_count
= grub_lvm_getvalue (&p
, "segment_count = ");
450 lv
->segments
= grub_malloc (sizeof (*seg
) * lv
->segment_count
);
453 for (i
= 0; i
< lv
->segment_count
; i
++)
455 struct grub_lvm_stripe
*stripe
;
457 p
= grub_strstr (p
, "segment");
459 goto lvs_segment_fail
;
461 seg
->start_extent
= grub_lvm_getvalue (&p
, "start_extent = ");
463 goto lvs_segment_fail
;
464 seg
->extent_count
= grub_lvm_getvalue (&p
, "extent_count = ");
466 goto lvs_segment_fail
;
467 seg
->stripe_count
= grub_lvm_getvalue (&p
, "stripe_count = ");
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 = [");
482 goto lvs_segment_fail2
;
483 p
+= sizeof("stripes = [") - 1;
485 for (j
= 0; j
< seg
->stripe_count
; j
++)
489 p
= grub_strchr (p
, '"');
498 pvname
= grub_malloc (s
+ 1);
500 goto lvs_segment_fail2
;
502 grub_memcpy (pvname
, p
, s
);
506 for (pv
= vg
->pvs
; pv
; pv
= pv
->next
)
508 if (! grub_strcmp (pvname
, pv
->name
))
517 stripe
->start
= grub_lvm_getvalue (&p
, ",");
528 grub_free (seg
->stripes
);
534 p
= grub_strchr (p
, '}');
539 lv
->number
= lv_count
++;
546 grub_free (lv
->name
);
560 /* Match the device we are currently reading from with the right
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
);
580 /* Normal exit path. */
582 grub_free (metadatabuf
);
584 grub_disk_close (disk
);
588 static struct grub_disk_dev grub_lvm_dev
=
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
,
598 .memberlist
= grub_lvm_memberlist
,
606 grub_device_iterate (&grub_lvm_scan_device
);
610 grub_errno
= GRUB_ERR_NONE
;
613 grub_disk_dev_register (&grub_lvm_dev
);
618 grub_disk_dev_unregister (&grub_lvm_dev
);
619 /* FIXME: free the lvm list. */