]> git.proxmox.com Git - grub2.git/blame - disk/lvm.c
merge from upstream (deviceiter regression produced odd effects on LVM upgrades)
[grub2.git] / disk / lvm.c
CommitLineData
2b002173 1/* lvm.c - module to read Logical Volumes. */
2/*
3 * GRUB -- GRand Unified Bootloader
58bc8bd5 4 * Copyright (C) 2006,2007,2008,2009 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
0973daeb 27#ifdef GRUB_UTIL
f07ccea7 28#include <grub/emu/misc.h>
0973daeb
VS
29#endif
30
4d42b77f 31static struct grub_lvm_vg *vg_list;
2b002173 32static int lv_count;
33
34\f
35/* Go the string STR and return the number after STR. *P will point
7ceeee39 36 at the number. In case STR is not found, *P will be NULL and the
37 return value will be 0. */
2b002173 38static int
39grub_lvm_getvalue (char **p, char *str)
40{
7ceeee39 41 *p = grub_strstr (*p, str);
42 if (! *p)
43 return 0;
44 *p += grub_strlen (str);
2b002173 45 return grub_strtoul (*p, NULL, 10);
46}
47
5944958c
SF
48static int
49grub_lvm_checkvalue (char **p, char *str, char *tmpl)
50{
51 int tmpllen = grub_strlen (tmpl);
52 *p = grub_strstr (*p, str);
53 if (! *p)
54 return 0;
55 *p += grub_strlen (str);
56 if (**p != '"')
57 return 0;
58 return (grub_memcmp (*p + 1, tmpl, tmpllen) == 0 && (*p)[tmpllen + 1] == '"');
59}
60
61static int
62grub_lvm_check_flag (char *p, char *str, char *flag)
63{
64 int len_str = grub_strlen (str), len_flag = grub_strlen (flag);
65 while (1)
66 {
67 char *q;
68 p = grub_strstr (p, str);
69 if (! p)
70 return 0;
71 p += len_str;
72 if (grub_memcmp (p, " = [", sizeof (" = [") - 1) != 0)
73 continue;
74 q = p + sizeof (" = [") - 1;
75 while (1)
76 {
77 while (grub_isspace (*q))
78 q++;
79 if (*q != '"')
80 return 0;
81 q++;
82 if (grub_memcmp (q, flag, len_flag) == 0 && q[len_flag] == '"')
83 return 1;
84 while (*q != '"')
85 q++;
86 q++;
87 if (*q == ']')
88 return 0;
89 q++;
90 }
91 }
92}
93
2b002173 94static int
95grub_lvm_iterate (int (*hook) (const char *name))
96{
97 struct grub_lvm_vg *vg;
4d42b77f 98 for (vg = vg_list; vg; vg = vg->next)
2b002173 99 {
100 struct grub_lvm_lv *lv;
7ceeee39 101 if (vg->lvs)
102 for (lv = vg->lvs; lv; lv = lv->next)
103 if (hook (lv->name))
104 return 1;
2b002173 105 }
106
107 return 0;
108}
109
b92f0c18 110#ifdef GRUB_UTIL
111static grub_disk_memberlist_t
112grub_lvm_memberlist (grub_disk_t disk)
113{
114 struct grub_lvm_lv *lv = disk->data;
115 grub_disk_memberlist_t list = NULL, tmp;
116 struct grub_lvm_pv *pv;
117
7ceeee39 118 if (lv->vg->pvs)
119 for (pv = lv->vg->pvs; pv; pv = pv->next)
120 {
0973daeb
VS
121 if (!pv->disk)
122 grub_util_error ("Couldn't find PV %s. Check your device.map",
123 pv->name);
7ceeee39 124 tmp = grub_malloc (sizeof (*tmp));
125 tmp->disk = pv->disk;
126 tmp->next = list;
127 list = tmp;
128 }
b92f0c18 129
130 return list;
131}
132#endif
133
2b002173 134static grub_err_t
135grub_lvm_open (const char *name, grub_disk_t disk)
136{
137 struct grub_lvm_vg *vg;
138 struct grub_lvm_lv *lv = NULL;
4d42b77f 139 for (vg = vg_list; vg; vg = vg->next)
2b002173 140 {
7ceeee39 141 if (vg->lvs)
142 for (lv = vg->lvs; lv; lv = lv->next)
143 if (! grub_strcmp (lv->name, name))
144 break;
2b002173 145
146 if (lv)
147 break;
148 }
149
150 if (! lv)
7fd0baee 151 return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown LVM device %s", name);
2b002173 152
153 disk->has_partitions = 0;
154 disk->id = lv->number;
155 disk->data = lv;
156 disk->total_sectors = lv->size;
b39f9d20 157
2b002173 158 return 0;
159}
160
161static void
162grub_lvm_close (grub_disk_t disk __attribute ((unused)))
163{
164 return;
165}
166
167static grub_err_t
168grub_lvm_read (grub_disk_t disk, grub_disk_addr_t sector,
169 grub_size_t size, char *buf)
170{
171 grub_err_t err = 0;
172 struct grub_lvm_lv *lv = disk->data;
173 struct grub_lvm_vg *vg = lv->vg;
174 struct grub_lvm_segment *seg = lv->segments;
175 struct grub_lvm_pv *pv;
176 grub_uint64_t offset;
177 grub_uint64_t extent;
178 unsigned int i;
179
180 extent = grub_divmod64 (sector, vg->extent_size, NULL);
181
182 /* Find the right segment. */
183 for (i = 0; i < lv->segment_count; i++)
184 {
185 if ((seg->start_extent <= extent)
186 && ((seg->start_extent + seg->extent_count) > extent))
187 {
188 break;
189 }
190
191 seg++;
192 }
193
194 if (seg->stripe_count == 1)
195 {
196 /* This segment is linear, so that's easy. We just need to find
197 out the offset in the physical volume and read SIZE bytes
198 from that. */
199 struct grub_lvm_stripe *stripe = seg->stripes;
200 grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
201
202 pv = stripe->pv;
203 seg_offset = ((grub_uint64_t) stripe->start
204 * (grub_uint64_t) vg->extent_size) + pv->start;
205
206 offset = sector - ((grub_uint64_t) seg->start_extent
207 * (grub_uint64_t) vg->extent_size) + seg_offset;
208 }
209 else
210 {
211 /* This is a striped segment. We have to find the right PV
212 similar to RAID0. */
213 struct grub_lvm_stripe *stripe = seg->stripes;
214 grub_uint32_t a, b;
215 grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
216 unsigned int stripenr;
217
218 offset = sector - ((grub_uint64_t) seg->start_extent
219 * (grub_uint64_t) vg->extent_size);
220
221 a = grub_divmod64 (offset, seg->stripe_size, NULL);
222 grub_divmod64 (a, seg->stripe_count, &stripenr);
223
224 a = grub_divmod64 (offset, seg->stripe_size * seg->stripe_count, NULL);
225 grub_divmod64 (offset, seg->stripe_size, &b);
226 offset = a * seg->stripe_size + b;
227
228 stripe += stripenr;
229 pv = stripe->pv;
b39f9d20 230
2b002173 231 seg_offset = ((grub_uint64_t) stripe->start
232 * (grub_uint64_t) vg->extent_size) + pv->start;
233
234 offset += seg_offset;
235 }
236
237 /* Check whether we actually know the physical volume we want to
238 read from. */
239 if (pv->disk)
240 err = grub_disk_read (pv->disk, offset, 0,
241 size << GRUB_DISK_SECTOR_BITS, buf);
242 else
243 err = grub_error (GRUB_ERR_UNKNOWN_DEVICE,
7fd0baee 244 "physical volume %s not found", pv->name);
b39f9d20 245
2b002173 246 return err;
247}
248
249static grub_err_t
250grub_lvm_write (grub_disk_t disk __attribute ((unused)),
251 grub_disk_addr_t sector __attribute ((unused)),
252 grub_size_t size __attribute ((unused)),
253 const char *buf __attribute ((unused)))
254{
255 return GRUB_ERR_NOT_IMPLEMENTED_YET;
256}
257
258static int
259grub_lvm_scan_device (const char *name)
260{
261 grub_err_t err;
262 grub_disk_t disk;
263 grub_uint64_t da_offset, da_size, mda_offset, mda_size;
264 char buf[GRUB_LVM_LABEL_SIZE];
265 char vg_id[GRUB_LVM_ID_STRLEN+1];
266 char pv_id[GRUB_LVM_ID_STRLEN+1];
267 char *metadatabuf, *p, *q, *vgname;
268 struct grub_lvm_label_header *lh = (struct grub_lvm_label_header *) buf;
269 struct grub_lvm_pv_header *pvh;
270 struct grub_lvm_disk_locn *dlocn;
271 struct grub_lvm_mda_header *mdah;
272 struct grub_lvm_raw_locn *rlocn;
273 unsigned int i, j, vgname_len;
274 struct grub_lvm_vg *vg;
275 struct grub_lvm_pv *pv;
b39f9d20 276
2b002173 277 disk = grub_disk_open (name);
278 if (!disk)
279 return 0;
280
281 /* Search for label. */
282 for (i = 0; i < GRUB_LVM_LABEL_SCAN_SECTORS; i++)
283 {
284 err = grub_disk_read (disk, i, 0, sizeof(buf), buf);
285 if (err)
286 goto fail;
b39f9d20 287
2b002173 288 if ((! grub_strncmp ((char *)lh->id, GRUB_LVM_LABEL_ID,
289 sizeof (lh->id)))
290 && (! grub_strncmp ((char *)lh->type, GRUB_LVM_LVM2_LABEL,
291 sizeof (lh->type))))
292 break;
293 }
294
295 /* Return if we didn't find a label. */
296 if (i == GRUB_LVM_LABEL_SCAN_SECTORS)
297 goto fail;
b39f9d20 298
2b002173 299 pvh = (struct grub_lvm_pv_header *) (buf + grub_le_to_cpu32(lh->offset_xl));
300
301 for (i = 0, j = 0; i < GRUB_LVM_ID_LEN; i++)
302 {
303 pv_id[j++] = pvh->pv_uuid[i];
304 if ((i != 1) && (i != 29) && (i % 4 == 1))
305 pv_id[j++] = '-';
306 }
307 pv_id[j] = '\0';
308
309 dlocn = pvh->disk_areas_xl;
310 da_offset = grub_le_to_cpu64 (dlocn->offset);
311 da_size = grub_le_to_cpu64 (dlocn->size);
312
313 dlocn++;
314 /* Is it possible to have multiple data/metadata areas? I haven't
315 seen devices that have it. */
316 if (dlocn->offset)
317 {
318 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
7fd0baee 319 "we don't support multiple LVM data areas");
b39f9d20 320
2b002173 321 goto fail;
322 }
323
324 dlocn++;
325 mda_offset = grub_le_to_cpu64 (dlocn->offset);
326 mda_size = grub_le_to_cpu64 (dlocn->size);
b39f9d20 327
f45d6cfc 328 /* It's possible to have multiple copies of metadata areas, we just use the
329 first one. */
2b002173 330
1b7748eb 331 /* Allocate buffer space for the circular worst-case scenario. */
332 metadatabuf = grub_malloc (2 * mda_size);
2b002173 333 if (! metadatabuf)
334 goto fail;
335
336 err = grub_disk_read (disk, 0, mda_offset, mda_size, metadatabuf);
337 if (err)
338 goto fail2;
339
340 mdah = (struct grub_lvm_mda_header *) metadatabuf;
341 if ((grub_strncmp ((char *)mdah->magic, GRUB_LVM_FMTT_MAGIC,
342 sizeof (mdah->magic)))
343 || (grub_le_to_cpu32 (mdah->version) != GRUB_LVM_FMTT_VERSION))
344 {
345 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
7fd0baee 346 "unknown LVM metadata header");
2b002173 347 goto fail2;
348 }
349
350 rlocn = mdah->raw_locns;
1b7748eb 351 if (grub_le_to_cpu64 (rlocn->offset) + grub_le_to_cpu64 (rlocn->size) >
352 grub_le_to_cpu64 (mdah->size))
353 {
354 /* Metadata is circular. Copy the wrap in place. */
355 grub_memcpy (metadatabuf + mda_size,
356 metadatabuf + GRUB_LVM_MDA_HEADER_SIZE,
357 grub_le_to_cpu64 (rlocn->offset) +
358 grub_le_to_cpu64 (rlocn->size) -
359 grub_le_to_cpu64 (mdah->size));
360 }
2b002173 361 p = q = metadatabuf + grub_le_to_cpu64 (rlocn->offset);
362
d1dff95d 363 while (*q != ' ' && q < metadatabuf + mda_size)
2b002173 364 q++;
365
d1dff95d 366 if (q == metadatabuf + mda_size)
367 goto fail2;
368
2b002173 369 vgname_len = q - p;
370 vgname = grub_malloc (vgname_len + 1);
371 if (!vgname)
372 goto fail2;
373
374 grub_memcpy (vgname, p, vgname_len);
375 vgname[vgname_len] = '\0';
376
7f8866ed 377 p = grub_strstr (q, "id = \"");
378 if (p == NULL)
379 goto fail3;
380 p += sizeof ("id = \"") - 1;
2b002173 381 grub_memcpy (vg_id, p, GRUB_LVM_ID_STRLEN);
382 vg_id[GRUB_LVM_ID_STRLEN] = '\0';
383
4d42b77f 384 for (vg = vg_list; vg; vg = vg->next)
2b002173 385 {
386 if (! grub_memcmp(vg_id, vg->id, GRUB_LVM_ID_STRLEN))
387 break;
388 }
389
390 if (! vg)
391 {
392 /* First time we see this volume group. We've to create the
393 whole volume group structure. */
394 vg = grub_malloc (sizeof (*vg));
395 if (! vg)
7f8866ed 396 goto fail3;
2b002173 397 vg->name = vgname;
398 grub_memcpy (vg->id, vg_id, GRUB_LVM_ID_STRLEN+1);
399
400 vg->extent_size = grub_lvm_getvalue (&p, "extent_size = ");
7ceeee39 401 if (p == NULL)
7f8866ed 402 goto fail4;
2b002173 403
404 vg->lvs = NULL;
405 vg->pvs = NULL;
2b002173 406
7ceeee39 407 p = grub_strstr (p, "physical_volumes {");
408 if (p)
2b002173 409 {
7ceeee39 410 p += sizeof ("physical_volumes {") - 1;
b39f9d20 411
7ceeee39 412 /* Add all the pvs to the volume group. */
413 while (1)
414 {
415 int s;
416 while (grub_isspace (*p))
417 p++;
b39f9d20 418
7ceeee39 419 if (*p == '}')
420 break;
b39f9d20 421
7ceeee39 422 pv = grub_malloc (sizeof (*pv));
423 q = p;
424 while (*q != ' ')
425 q++;
b39f9d20 426
7ceeee39 427 s = q - p;
428 pv->name = grub_malloc (s + 1);
429 grub_memcpy (pv->name, p, s);
430 pv->name[s] = '\0';
b39f9d20 431
b7279447 432 p = grub_strstr (p, "id = \"");
7ceeee39 433 if (p == NULL)
434 goto pvs_fail;
b7279447 435 p += sizeof("id = \"") - 1;
b39f9d20 436
7ceeee39 437 grub_memcpy (pv->id, p, GRUB_LVM_ID_STRLEN);
438 pv->id[GRUB_LVM_ID_STRLEN] = '\0';
b39f9d20 439
7ceeee39 440 pv->start = grub_lvm_getvalue (&p, "pe_start = ");
441 if (p == NULL)
442 goto pvs_fail;
b39f9d20 443
b7279447 444 p = grub_strchr (p, '}');
445 if (p == NULL)
446 goto pvs_fail;
447 p++;
de4fa71c 448
449 pv->disk = NULL;
450 pv->next = vg->pvs;
451 vg->pvs = pv;
b39f9d20 452
7ceeee39 453 continue;
454 pvs_fail:
455 grub_free (pv->name);
456 grub_free (pv);
7f8866ed 457 goto fail4;
7ceeee39 458 }
2b002173 459 }
460
461 p = grub_strstr (p, "logical_volumes");
7ceeee39 462 if (p)
2b002173 463 {
7ceeee39 464 p += 18;
b39f9d20 465
7ceeee39 466 /* And add all the lvs to the volume group. */
467 while (1)
2b002173 468 {
7ceeee39 469 int s;
5944958c 470 int skip_lv = 0;
7ceeee39 471 struct grub_lvm_lv *lv;
472 struct grub_lvm_segment *seg;
b39f9d20 473
7ceeee39 474 while (grub_isspace (*p))
475 p++;
b39f9d20 476
7ceeee39 477 if (*p == '}')
478 break;
b39f9d20 479
7ceeee39 480 lv = grub_malloc (sizeof (*lv));
b39f9d20 481
7ceeee39 482 q = p;
483 while (*q != ' ')
484 q++;
b39f9d20 485
7ceeee39 486 s = q - p;
487 lv->name = grub_malloc (vgname_len + 1 + s + 1);
488 grub_memcpy (lv->name, vgname, vgname_len);
489 lv->name[vgname_len] = '-';
490 grub_memcpy (lv->name + vgname_len + 1, p, s);
491 lv->name[vgname_len + 1 + s] = '\0';
b39f9d20 492
7ceeee39 493 lv->size = 0;
b39f9d20 494
5944958c
SF
495 if (!grub_lvm_check_flag (p, "status", "VISIBLE"))
496 {
497 skip_lv = 1;
498 goto lv_parsed;
499 }
500
7ceeee39 501 lv->segment_count = grub_lvm_getvalue (&p, "segment_count = ");
502 if (p == NULL)
503 goto lvs_fail;
504 lv->segments = grub_malloc (sizeof (*seg) * lv->segment_count);
505 seg = lv->segments;
b39f9d20 506
7ceeee39 507 for (i = 0; i < lv->segment_count; i++)
2b002173 508 {
7ceeee39 509 struct grub_lvm_stripe *stripe;
b39f9d20 510
7ceeee39 511 p = grub_strstr (p, "segment");
512 if (p == NULL)
513 goto lvs_segment_fail;
b39f9d20 514
7ceeee39 515 seg->start_extent = grub_lvm_getvalue (&p, "start_extent = ");
516 if (p == NULL)
517 goto lvs_segment_fail;
518 seg->extent_count = grub_lvm_getvalue (&p, "extent_count = ");
519 if (p == NULL)
520 goto lvs_segment_fail;
5944958c
SF
521
522 if (grub_lvm_checkvalue (&p, "type = ", "snapshot"))
523 {
524 /* Found a snapshot, give up and move on. */
525 skip_lv = 1;
526 break;
527 }
528
7ceeee39 529 seg->stripe_count = grub_lvm_getvalue (&p, "stripe_count = ");
530 if (p == NULL)
531 goto lvs_segment_fail;
b39f9d20 532
7ceeee39 533 lv->size += seg->extent_count * vg->extent_size;
b39f9d20 534
7ceeee39 535 if (seg->stripe_count != 1)
536 seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
b39f9d20 537
7ceeee39 538 seg->stripes = grub_malloc (sizeof (*stripe)
539 * seg->stripe_count);
540 stripe = seg->stripes;
b39f9d20 541
7ceeee39 542 p = grub_strstr (p, "stripes = [");
543 if (p == NULL)
544 goto lvs_segment_fail2;
545 p += sizeof("stripes = [") - 1;
b39f9d20 546
7ceeee39 547 for (j = 0; j < seg->stripe_count; j++)
2b002173 548 {
0a1150e2 549 char *pvname;
b39f9d20 550
7ceeee39 551 p = grub_strchr (p, '"');
552 if (p == NULL)
553 continue;
554 q = ++p;
555 while (*q != '"')
556 q++;
557
558 s = q - p;
b39f9d20 559
0a1150e2 560 pvname = grub_malloc (s + 1);
01979850 561 if (pvname == NULL)
562 goto lvs_segment_fail2;
b39f9d20 563
7ceeee39 564 grub_memcpy (pvname, p, s);
565 pvname[s] = '\0';
b39f9d20 566
7ceeee39 567 if (vg->pvs)
568 for (pv = vg->pvs; pv; pv = pv->next)
569 {
570 if (! grub_strcmp (pvname, pv->name))
571 {
572 stripe->pv = pv;
573 break;
574 }
575 }
b39f9d20 576
0a1150e2 577 grub_free(pvname);
b39f9d20 578
7ceeee39 579 stripe->start = grub_lvm_getvalue (&p, ",");
580 if (p == NULL)
581 continue;
b39f9d20 582
7ceeee39 583 stripe++;
2b002173 584 }
b39f9d20 585
7ceeee39 586 seg++;
587
588 continue;
589 lvs_segment_fail2:
590 grub_free (seg->stripes);
591 lvs_segment_fail:
7f8866ed 592 goto fail4;
2b002173 593 }
594
5944958c 595 lv_parsed:
af63ada2 596 if (p != NULL)
597 p = grub_strchr (p, '}');
7ceeee39 598 if (p == NULL)
599 goto lvs_fail;
600 p += 3;
b39f9d20 601
5944958c
SF
602 if (skip_lv)
603 {
604 grub_free (lv->name);
605 grub_free (lv);
606 continue;
607 }
608
de4fa71c 609 lv->number = lv_count++;
610 lv->vg = vg;
611 lv->next = vg->lvs;
612 vg->lvs = lv;
613
7ceeee39 614 continue;
615 lvs_fail:
616 grub_free (lv->name);
617 grub_free (lv);
7f8866ed 618 goto fail4;
2b002173 619 }
2b002173 620 }
de4fa71c 621
622 vg->next = vg_list;
623 vg_list = vg;
2b002173 624 }
625 else
626 {
627 grub_free (vgname);
628 }
629
630 /* Match the device we are currently reading from with the right
631 PV. */
7ceeee39 632 if (vg->pvs)
633 for (pv = vg->pvs; pv; pv = pv->next)
634 {
635 if (! grub_memcmp (pv->id, pv_id, GRUB_LVM_ID_STRLEN))
636 {
f45d6cfc 637 /* This could happen to LVM on RAID, pv->disk points to the
638 raid device, we shouldn't change it. */
639 if (! pv->disk)
640 pv->disk = grub_disk_open (name);
7ceeee39 641 break;
642 }
643 }
2b002173 644
7f8866ed 645 goto fail2;
646
647 /* Failure path. */
648 fail4:
649 grub_free (vg);
650 fail3:
651 grub_free (vgname);
652
653 /* Normal exit path. */
2b002173 654 fail2:
655 grub_free (metadatabuf);
656 fail:
657 grub_disk_close (disk);
658 return 0;
659}
660
661static struct grub_disk_dev grub_lvm_dev =
662 {
663 .name = "lvm",
664 .id = GRUB_DISK_DEVICE_LVM_ID,
665 .iterate = grub_lvm_iterate,
666 .open = grub_lvm_open,
667 .close = grub_lvm_close,
668 .read = grub_lvm_read,
669 .write = grub_lvm_write,
b92f0c18 670#ifdef GRUB_UTIL
671 .memberlist = grub_lvm_memberlist,
672#endif
2b002173 673 .next = 0
674 };
675
676\f
677GRUB_MOD_INIT(lvm)
678{
679 grub_device_iterate (&grub_lvm_scan_device);
1082b929 680 if (grub_errno)
681 {
682 grub_print_error ();
683 grub_errno = GRUB_ERR_NONE;
684 }
685
2b002173 686 grub_disk_dev_register (&grub_lvm_dev);
687}
688
689GRUB_MOD_FINI(lvm)
690{
691 grub_disk_dev_unregister (&grub_lvm_dev);
692 /* FIXME: free the lvm list. */
693}