]> git.proxmox.com Git - grub2.git/blame - disk/lvm.c
2008-04-14 Vesa Jaaskelainen <chaac@nic.fi>
[grub2.git] / disk / lvm.c
CommitLineData
2b002173 1/* lvm.c - module to read Logical Volumes. */
2/*
3 * GRUB -- GRand Unified Bootloader
5a79f472 4 * Copyright (C) 2006,2007 Free Software Foundation, Inc.
2b002173 5 *
5a79f472 6 * GRUB is free software: you can redistribute it and/or modify
2b002173 7 * it under the terms of the GNU General Public License as published by
5a79f472 8 * the Free Software Foundation, either version 3 of the License, or
2b002173 9 * (at your option) any later version.
10 *
5a79f472 11 * GRUB is distributed in the hope that it will be useful,
2b002173 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.
15 *
16 * You should have received a copy of the GNU General Public License
5a79f472 17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2b002173 18 */
19
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>
26
4d42b77f 27static struct grub_lvm_vg *vg_list;
2b002173 28static int lv_count;
29
30\f
31/* Go the string STR and return the number after STR. *P will point
7ceeee39 32 at the number. In case STR is not found, *P will be NULL and the
33 return value will be 0. */
2b002173 34static int
35grub_lvm_getvalue (char **p, char *str)
36{
7ceeee39 37 *p = grub_strstr (*p, str);
38 if (! *p)
39 return 0;
40 *p += grub_strlen (str);
2b002173 41 return grub_strtoul (*p, NULL, 10);
42}
43
44static int
45grub_lvm_iterate (int (*hook) (const char *name))
46{
47 struct grub_lvm_vg *vg;
4d42b77f 48 for (vg = vg_list; vg; vg = vg->next)
2b002173 49 {
50 struct grub_lvm_lv *lv;
7ceeee39 51 if (vg->lvs)
52 for (lv = vg->lvs; lv; lv = lv->next)
53 if (hook (lv->name))
54 return 1;
2b002173 55 }
56
57 return 0;
58}
59
b92f0c18 60#ifdef GRUB_UTIL
61static grub_disk_memberlist_t
62grub_lvm_memberlist (grub_disk_t disk)
63{
64 struct grub_lvm_lv *lv = disk->data;
65 grub_disk_memberlist_t list = NULL, tmp;
66 struct grub_lvm_pv *pv;
67
7ceeee39 68 if (lv->vg->pvs)
69 for (pv = lv->vg->pvs; pv; pv = pv->next)
70 {
71 tmp = grub_malloc (sizeof (*tmp));
72 tmp->disk = pv->disk;
73 tmp->next = list;
74 list = tmp;
75 }
b92f0c18 76
77 return list;
78}
79#endif
80
2b002173 81static grub_err_t
82grub_lvm_open (const char *name, grub_disk_t disk)
83{
84 struct grub_lvm_vg *vg;
85 struct grub_lvm_lv *lv = NULL;
4d42b77f 86 for (vg = vg_list; vg; vg = vg->next)
2b002173 87 {
7ceeee39 88 if (vg->lvs)
89 for (lv = vg->lvs; lv; lv = lv->next)
90 if (! grub_strcmp (lv->name, name))
91 break;
2b002173 92
93 if (lv)
94 break;
95 }
96
97 if (! lv)
98 return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "Unknown device");
99
100 disk->has_partitions = 0;
101 disk->id = lv->number;
102 disk->data = lv;
103 disk->total_sectors = lv->size;
104
105 return 0;
106}
107
108static void
109grub_lvm_close (grub_disk_t disk __attribute ((unused)))
110{
111 return;
112}
113
114static grub_err_t
115grub_lvm_read (grub_disk_t disk, grub_disk_addr_t sector,
116 grub_size_t size, char *buf)
117{
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;
126
127 extent = grub_divmod64 (sector, vg->extent_size, NULL);
128
129 /* Find the right segment. */
130 for (i = 0; i < lv->segment_count; i++)
131 {
132 if ((seg->start_extent <= extent)
133 && ((seg->start_extent + seg->extent_count) > extent))
134 {
135 break;
136 }
137
138 seg++;
139 }
140
141 if (seg->stripe_count == 1)
142 {
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. */
148
149 pv = stripe->pv;
150 seg_offset = ((grub_uint64_t) stripe->start
151 * (grub_uint64_t) vg->extent_size) + pv->start;
152
153 offset = sector - ((grub_uint64_t) seg->start_extent
154 * (grub_uint64_t) vg->extent_size) + seg_offset;
155 }
156 else
157 {
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;
164
165 offset = sector - ((grub_uint64_t) seg->start_extent
166 * (grub_uint64_t) vg->extent_size);
167
168 a = grub_divmod64 (offset, seg->stripe_size, NULL);
169 grub_divmod64 (a, seg->stripe_count, &stripenr);
170
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;
174
175 stripe += stripenr;
176 pv = stripe->pv;
177
178 seg_offset = ((grub_uint64_t) stripe->start
179 * (grub_uint64_t) vg->extent_size) + pv->start;
180
181 offset += seg_offset;
182 }
183
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);
192
193 return err;
194}
195
196static grub_err_t
197grub_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)))
201{
202 return GRUB_ERR_NOT_IMPLEMENTED_YET;
203}
204
205static int
206grub_lvm_scan_device (const char *name)
207{
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;
223
224 disk = grub_disk_open (name);
225 if (!disk)
226 return 0;
227
228 /* Search for label. */
229 for (i = 0; i < GRUB_LVM_LABEL_SCAN_SECTORS; i++)
230 {
231 err = grub_disk_read (disk, i, 0, sizeof(buf), buf);
232 if (err)
233 goto fail;
234
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;
240 }
241
242 /* Return if we didn't find a label. */
243 if (i == GRUB_LVM_LABEL_SCAN_SECTORS)
244 goto fail;
245
246 pvh = (struct grub_lvm_pv_header *) (buf + grub_le_to_cpu32(lh->offset_xl));
247
248 for (i = 0, j = 0; i < GRUB_LVM_ID_LEN; i++)
249 {
250 pv_id[j++] = pvh->pv_uuid[i];
251 if ((i != 1) && (i != 29) && (i % 4 == 1))
252 pv_id[j++] = '-';
253 }
254 pv_id[j] = '\0';
255
256 dlocn = pvh->disk_areas_xl;
257 da_offset = grub_le_to_cpu64 (dlocn->offset);
258 da_size = grub_le_to_cpu64 (dlocn->size);
259
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)
264 {
265 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
266 "We don't support multiple data areas");
267
268 goto fail;
269 }
270
271 dlocn++;
272 mda_offset = grub_le_to_cpu64 (dlocn->offset);
273 mda_size = grub_le_to_cpu64 (dlocn->size);
274 dlocn++;
275
276 if (dlocn->offset)
277 {
278 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
279 "We don't support multiple metadata areas");
280
281 goto fail;
282 }
283
284 metadatabuf = grub_malloc (mda_size);
285 if (! metadatabuf)
286 goto fail;
287
288 err = grub_disk_read (disk, 0, mda_offset, mda_size, metadatabuf);
289 if (err)
290 goto fail2;
291
292 mdah = (struct grub_lvm_mda_header *) metadatabuf;
293 if ((grub_strncmp ((char *)mdah->magic, GRUB_LVM_FMTT_MAGIC,
294 sizeof (mdah->magic)))
295 || (grub_le_to_cpu32 (mdah->version) != GRUB_LVM_FMTT_VERSION))
296 {
297 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
298 "Unknown metadata header");
299 goto fail2;
300 }
301
302 rlocn = mdah->raw_locns;
303 p = q = metadatabuf + grub_le_to_cpu64 (rlocn->offset);
304
305 while (*q != ' ')
306 q++;
307
308 vgname_len = q - p;
309 vgname = grub_malloc (vgname_len + 1);
310 if (!vgname)
311 goto fail2;
312
313 grub_memcpy (vgname, p, vgname_len);
314 vgname[vgname_len] = '\0';
315
316 p = grub_strstr (q, "id = \"") + sizeof ("id = \"") - 1;
317 grub_memcpy (vg_id, p, GRUB_LVM_ID_STRLEN);
318 vg_id[GRUB_LVM_ID_STRLEN] = '\0';
319
4d42b77f 320 for (vg = vg_list; vg; vg = vg->next)
2b002173 321 {
322 if (! grub_memcmp(vg_id, vg->id, GRUB_LVM_ID_STRLEN))
323 break;
324 }
325
326 if (! vg)
327 {
328 /* First time we see this volume group. We've to create the
329 whole volume group structure. */
330 vg = grub_malloc (sizeof (*vg));
331 if (! vg)
332 {
333 grub_free (vgname);
334 goto fail;
335 }
336 vg->name = vgname;
337 grub_memcpy (vg->id, vg_id, GRUB_LVM_ID_STRLEN+1);
338
339 vg->extent_size = grub_lvm_getvalue (&p, "extent_size = ");
7ceeee39 340 if (p == NULL)
341 goto fail2;
2b002173 342
343 vg->lvs = NULL;
344 vg->pvs = NULL;
4d42b77f 345 vg->next = vg_list;
346 vg_list = vg;
2b002173 347
7ceeee39 348 p = grub_strstr (p, "physical_volumes {");
349 if (p)
2b002173 350 {
7ceeee39 351 p += sizeof ("physical_volumes {") - 1;
2b002173 352
7ceeee39 353 /* Add all the pvs to the volume group. */
354 while (1)
355 {
356 int s;
357 while (grub_isspace (*p))
358 p++;
359
360 if (*p == '}')
361 break;
362
363 pv = grub_malloc (sizeof (*pv));
364 q = p;
365 while (*q != ' ')
366 q++;
367
368 s = q - p;
369 pv->name = grub_malloc (s + 1);
370 grub_memcpy (pv->name, p, s);
371 pv->name[s] = '\0';
372
373 p = grub_strstr (p, "id = \"") + sizeof("id = \"") - 1;
374 if (p == NULL)
375 goto pvs_fail;
376
377 grub_memcpy (pv->id, p, GRUB_LVM_ID_STRLEN);
378 pv->id[GRUB_LVM_ID_STRLEN] = '\0';
379
380 pv->start = grub_lvm_getvalue (&p, "pe_start = ");
381 if (p == NULL)
382 goto pvs_fail;
383 pv->disk = NULL;
384 pv->next = vg->pvs;
385 vg->pvs = pv;
386
387 p = grub_strchr (p, '}') + 1;
388
389 continue;
390 pvs_fail:
391 grub_free (pv->name);
392 grub_free (pv);
393 goto fail2;
394 }
2b002173 395 }
396
397 p = grub_strstr (p, "logical_volumes");
7ceeee39 398 if (p)
2b002173 399 {
7ceeee39 400 p += 18;
2b002173 401
7ceeee39 402 /* And add all the lvs to the volume group. */
403 while (1)
2b002173 404 {
7ceeee39 405 int s;
406 struct grub_lvm_lv *lv;
407 struct grub_lvm_segment *seg;
2b002173 408
7ceeee39 409 while (grub_isspace (*p))
410 p++;
411
412 if (*p == '}')
413 break;
414
415 lv = grub_malloc (sizeof (*lv));
2b002173 416
7ceeee39 417 q = p;
418 while (*q != ' ')
419 q++;
2b002173 420
7ceeee39 421 s = q - p;
422 lv->name = grub_malloc (vgname_len + 1 + s + 1);
423 grub_memcpy (lv->name, vgname, vgname_len);
424 lv->name[vgname_len] = '-';
425 grub_memcpy (lv->name + vgname_len + 1, p, s);
426 lv->name[vgname_len + 1 + s] = '\0';
427
428 lv->size = 0;
429
430 lv->segment_count = grub_lvm_getvalue (&p, "segment_count = ");
431 if (p == NULL)
432 goto lvs_fail;
433 lv->segments = grub_malloc (sizeof (*seg) * lv->segment_count);
434 seg = lv->segments;
435
436 for (i = 0; i < lv->segment_count; i++)
2b002173 437 {
7ceeee39 438 struct grub_lvm_stripe *stripe;
2b002173 439
7ceeee39 440 p = grub_strstr (p, "segment");
441 if (p == NULL)
442 goto lvs_segment_fail;
443
444 seg->start_extent = grub_lvm_getvalue (&p, "start_extent = ");
445 if (p == NULL)
446 goto lvs_segment_fail;
447 seg->extent_count = grub_lvm_getvalue (&p, "extent_count = ");
448 if (p == NULL)
449 goto lvs_segment_fail;
450 seg->stripe_count = grub_lvm_getvalue (&p, "stripe_count = ");
451 if (p == NULL)
452 goto lvs_segment_fail;
453
454 lv->size += seg->extent_count * vg->extent_size;
455
456 if (seg->stripe_count != 1)
457 seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
2b002173 458
7ceeee39 459 seg->stripes = grub_malloc (sizeof (*stripe)
460 * seg->stripe_count);
461 stripe = seg->stripes;
462
463 p = grub_strstr (p, "stripes = [");
464 if (p == NULL)
465 goto lvs_segment_fail2;
466 p += sizeof("stripes = [") - 1;
467
468 for (j = 0; j < seg->stripe_count; j++)
2b002173 469 {
0a1150e2 470 char *pvname;
7ceeee39 471
472 p = grub_strchr (p, '"');
473 if (p == NULL)
474 continue;
475 q = ++p;
476 while (*q != '"')
477 q++;
478
479 s = q - p;
0a1150e2 480 pvname = grub_malloc (s + 1);
7ceeee39 481 grub_memcpy (pvname, p, s);
482 pvname[s] = '\0';
483
484 if (vg->pvs)
485 for (pv = vg->pvs; pv; pv = pv->next)
486 {
487 if (! grub_strcmp (pvname, pv->name))
488 {
489 stripe->pv = pv;
490 break;
491 }
492 }
493
0a1150e2 494 grub_free(pvname);
495
7ceeee39 496 stripe->start = grub_lvm_getvalue (&p, ",");
497 if (p == NULL)
498 continue;
499
500 stripe++;
2b002173 501 }
2b002173 502
7ceeee39 503 seg++;
504
505 continue;
506 lvs_segment_fail2:
507 grub_free (seg->stripes);
508 lvs_segment_fail:
509 goto fail2;
2b002173 510 }
511
7ceeee39 512 lv->number = lv_count++;
513 lv->vg = vg;
514 lv->next = vg->lvs;
515 vg->lvs = lv;
516
517 p = grub_strchr (p, '}');
518 if (p == NULL)
519 goto lvs_fail;
520 p += 3;
521
522 continue;
523 lvs_fail:
524 grub_free (lv->name);
525 grub_free (lv);
526 goto fail2;
2b002173 527 }
2b002173 528 }
529 }
530 else
531 {
532 grub_free (vgname);
533 }
534
535 /* Match the device we are currently reading from with the right
536 PV. */
7ceeee39 537 if (vg->pvs)
538 for (pv = vg->pvs; pv; pv = pv->next)
539 {
540 if (! grub_memcmp (pv->id, pv_id, GRUB_LVM_ID_STRLEN))
541 {
542 pv->disk = grub_disk_open (name);
543 break;
544 }
545 }
2b002173 546
547 fail2:
548 grub_free (metadatabuf);
549 fail:
550 grub_disk_close (disk);
551 return 0;
552}
553
554static struct grub_disk_dev grub_lvm_dev =
555 {
556 .name = "lvm",
557 .id = GRUB_DISK_DEVICE_LVM_ID,
558 .iterate = grub_lvm_iterate,
559 .open = grub_lvm_open,
560 .close = grub_lvm_close,
561 .read = grub_lvm_read,
562 .write = grub_lvm_write,
b92f0c18 563#ifdef GRUB_UTIL
564 .memberlist = grub_lvm_memberlist,
565#endif
2b002173 566 .next = 0
567 };
568
569\f
570GRUB_MOD_INIT(lvm)
571{
572 grub_device_iterate (&grub_lvm_scan_device);
573 grub_disk_dev_register (&grub_lvm_dev);
574}
575
576GRUB_MOD_FINI(lvm)
577{
578 grub_disk_dev_unregister (&grub_lvm_dev);
579 /* FIXME: free the lvm list. */
580}