Associate opaque plugin ID with each major structure.
[virt-df.git] / lib / diskimage_lvm2.ml
1 (* 'df' command for virtual domains.
2
3    (C) Copyright 2007 Richard W.M. Jones, Red Hat Inc.
4    http://libvirt.org/
5
6    This program 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 2 of the License, or
9    (at your option) any later version.
10
11    This program 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.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20    Support for LVM2 PVs.
21 *)
22
23 open Printf
24 open ExtList
25
26 open Diskimage_utils
27 open Diskimage_lvm2_metadata
28
29 let plugin_id = "LVM2"
30
31 let sector_size = 512
32 let sector_size64 = 512L
33
34 (*----------------------------------------------------------------------*)
35 (* Block device which can do linear maps, same as the kernel dm-linear.c *)
36 class linear_map_device name extent_size segments =
37   (* The segments are passed containing (start_extent, extent_count, ...)
38    * but it's easier to deal with (start_extent, end_extent, ...) so
39    * rewrite them.
40    *)
41   let segments = List.map
42     (fun (start_extent, extent_count, dev, pvoffset) ->
43        (start_extent, start_extent +^ extent_count, dev, pvoffset)
44     ) segments in
45
46   (* Calculate the size of the device (in bytes).  Note that because
47    * of the random nature of the mapping this doesn't imply that we can
48    * satisfy any read request up to the full size.
49    *)
50   let size_in_extents =
51     List.fold_left max 0L
52       (List.map (fun (_, end_extent, _, _) -> end_extent) segments) in
53   let size = size_in_extents *^ extent_size in
54 object
55   inherit device
56   method name = name
57   method size = size
58
59   (* Read method checks which segment the request lies inside and
60    * maps it to the underlying device.  If there is no mapping then
61    * we have to return an error.
62    *
63    * The request must lie inside a single extent, otherwise this is
64    * also an error (XXX - should lift this restriction, however default
65    * extent size is 4 MB so we probably won't hit this very often).
66    *)
67   method read offset len =
68     let offset_in_extents = offset /^ extent_size in
69
70     (* Check we don't cross an extent boundary. *)
71     if (offset +^ Int64.of_int (len-1)) /^ extent_size <> offset_in_extents
72     then invalid_arg "linear_map_device: request crosses extent boundary";
73
74     if offset_in_extents < 0L || offset_in_extents >= size_in_extents then
75       invalid_arg "linear_map_device: read outside device";
76
77     let rec loop = function
78       | [] ->
79           invalid_arg "linear_map_device: offset not mapped"
80       | (start_extent, end_extent, dev, pvoffset) :: rest ->
81           if start_extent <= offset_in_extents &&
82              offset_in_extents < end_extent
83           then dev#read (offset +^ pvoffset *^ extent_size) len
84           else loop rest
85     in
86     loop segments
87 end
88
89 (*----------------------------------------------------------------------*)
90 (* Probe to see if it's an LVM2 PV. *)
91 let rec probe lvm_plugin_id dev =
92   try
93     let uuid, _ = read_pv_label dev in
94     if !debug then
95       eprintf "LVM2 detected PV UUID %s\n%!" uuid;
96     { lvm_plugin_id = lvm_plugin_id; pv_uuid = uuid }
97   with exn ->
98     if !debug then prerr_endline (Printexc.to_string exn);
99     raise Not_found
100
101 and read_pv_label dev =
102   (* Load the first 8 sectors.  I found by experimentation that
103    * the second sector contains the header ("LABELONE" etc) and
104    * the nineth sector contains some additional information about
105    * the location of the current metadata.
106    *)
107   let bits = dev#read_bitstring 0L (9 * sector_size) in
108
109   (*Bitmatch.hexdump_bitstring stdout bits;*)
110
111   bitmatch bits with
112   | {
113       (* sector 0 *)
114       sector0 : sector_size*8 : bitstring;
115
116       (* sector 1 *)
117       "LABELONE" : 64 : string;         (* "LABELONE" *)
118       _ : 128 : bitstring;              (* Seems to contain something. *)
119       "LVM2 001" : 64 : string;         (* "LVM2 001" *)
120       uuid : 256 : string;              (* UUID *)
121       endsect : (sector_size-64)*8 : bitstring; (* to end of second sector *)
122
123       (* sectors 2-7 *)
124       sectors234567 : sector_size*8 * 6 : bitstring;
125
126       (* sector 8 *)
127       _ : 320 : bitstring;              (* start of sector 8 *)
128       metadata_offset : 32 : littleendian; (* metadata offset *)
129       _ : 32 : bitstring;
130       metadata_length : 32 : littleendian (* length of metadata (bytes) *)
131     } ->
132
133       (* Metadata offset is relative to end of PV label. *)
134       let metadata_offset = metadata_offset +* 0x1000_l in
135       (* Metadata length appears to include the trailing \000 which
136        * we don't want.
137        *)
138       let metadata_length = metadata_length -* 1_l in
139
140       let metadata = read_metadata dev metadata_offset metadata_length in
141
142       uuid, metadata
143
144   | { _ } ->
145       invalid_arg
146         (sprintf "LVM2: read_pv_label: %s: not an LVM2 physical volume"
147            dev#name)
148
149 and read_metadata dev offset32 len32 =
150   if !debug then
151     eprintf "metadata: offset 0x%lx len %ld bytes\n%!" offset32 len32;
152
153   (* Check the offset and length are sensible. *)
154   let offset64 =
155     if offset32 <= Int32.max_int then Int64.of_int32 offset32
156     else invalid_arg "LVM2: read_metadata: metadata offset too large" in
157   let len64 =
158     if len32 <= 2_147_483_647_l then Int64.of_int32 len32
159     else invalid_arg "LVM2: read_metadata: metadata length too large" in
160
161   if offset64 <= 0x1200L || offset64 >= dev#size
162     || len64 <= 0L || offset64 +^ len64 >= dev#size then
163       invalid_arg "LVM2: read_metadata: bad metadata offset or length";
164
165   (* If it is outside the disk boundaries, this will throw an exception,
166    * otherwise it will read and return the metadata string.
167    *)
168   dev#read offset64 (Int64.to_int len64)
169
170 (*----------------------------------------------------------------------*)
171 (* We are passed a list of devices which we previously identified
172  * as PVs belonging to us.  From these produce a list of all LVs
173  * (as devices) and return them.  Note that we don't try to detect
174  * what is on these LVs - that will be done in the main code.
175  *)
176 let rec list devs =
177   (* Read the UUID and metadata (again) from each device to end up with
178    * an assoc list of PVs, keyed on the UUID.
179    *)
180   let pvs = List.map (
181     fun dev ->
182       let uuid, metadata = read_pv_label dev in
183       (uuid, (metadata, dev))
184   ) devs in
185
186   (* Parse the metadata using the external lexer/parser. *)
187   let pvs = List.map (
188     fun (uuid, (metadata, dev)) ->
189       uuid, (Diskimage_lvm2_lexer.parse_lvm2_metadata_from_string metadata,
190              dev)
191   ) pvs in
192
193   (* Print the parsed metadata. *)
194   if !debug then
195     List.iter (
196       fun (uuid, (metadata, dev)) ->
197         eprintf "metadata for PV UUID %s on %s:\n" uuid dev#name;
198         output_metadata stderr metadata
199     ) pvs;
200
201   (* Scan for volume groups.  The first entry in the metadata
202    * appears to be the volume group name.  This gives us a
203    * list of VGs and the metadata for each underlying PV.
204    *)
205   let vgnames =
206     List.filter_map (
207       function
208       | pvuuid, (((vgname, Metadata vgmeta) :: _), dev) ->
209           Some (vgname, (pvuuid, vgmeta))
210       | _ -> None
211     ) pvs in
212
213   let cmp ((a:string),_) ((b:string),_) = compare a b in
214   let vgnames = List.sort ~cmp vgnames in
215   let vgs = group_by vgnames in
216
217   (* Note that the metadata is supposed to be duplicated
218    * identically across all PVs (for redundancy purposes).
219    * In theory we should check this and use the 'seqno'
220    * field to find the latest metadata if it doesn't match,
221    * but in fact we don't check this.
222    *)
223   let vgs = List.map (
224     fun (vgname, metas) ->
225       let pvuuids = List.map fst metas in
226       let _, vgmeta = List.hd metas in (* just pick any metadata *)
227       vgname, (pvuuids, vgmeta)) vgs in
228
229   (* Print the VGs. *)
230   if !debug then
231     List.iter (
232       fun (vgname, (pvuuids, vgmeta)) ->
233         eprintf "VG %s is on PVs: %s\n%!" vgname (String.concat "," pvuuids)
234     ) vgs;
235
236   (* Some useful getter functions.  If these can't get a value
237    * from the metadata or if the type is wrong they raise Not_found.
238    *)
239   let rec get_int64 field meta =
240     match List.assoc field meta with
241     | Int i -> i
242     | _ -> raise Not_found
243   and get_int field meta min max =
244     match List.assoc field meta with
245     | Int i when Int64.of_int min <= i && i <= Int64.of_int max ->
246         Int64.to_int i
247     | _ -> raise Not_found
248   and get_string field meta =
249     match List.assoc field meta with
250     | String s -> s
251     | _ -> raise Not_found
252   and get_meta field meta =
253     match List.assoc field meta with
254     | Metadata md -> md
255     | _ -> raise Not_found
256   and get_stripes field meta =          (* List of (string,int) pairs. *)
257     match List.assoc field meta with
258     | List xs ->
259         let rec loop = function
260           | [] -> []
261           | String pvname :: Int offset :: xs ->
262               (pvname, offset) :: loop xs
263           | _ -> raise Not_found
264         in
265         loop xs
266     | _ -> raise Not_found
267   in
268
269   (* The volume groups refer to the physical volumes using their
270    * own naming system ("pv0", "pv1", etc.) instead of PV UUIDs.
271    *
272    * Each PV also has a start (in sectors) & count (in extents)
273    * of the writable area (the bit after the superblock and metadata)
274    * which normally starts at sector 384.
275    *
276    * Create a PV device (simple offset + size) and a map from PV
277    * names to these devices.
278    *)
279   let vgs = List.map (
280     fun (vgname, (pvuuids, vgmeta)) ->
281       let pvdevs, extent_size =
282         try
283           (* NB: extent_size is in sectors here - we convert to bytes. *)
284           let extent_size = get_int "extent_size" vgmeta 0 (1024*1024) in
285           let extent_size = Int64.of_int extent_size *^ sector_size64 in
286
287           (* Get the physical_volumes section of the metadata. *)
288           let pvdevs = get_meta "physical_volumes" vgmeta in
289
290           List.filter_map (
291             function
292             | (pvname, Metadata meta) ->
293                 (* Get the UUID. *)
294                 let pvuuid = get_string "id" meta in
295                 let pvuuid = canonical_uuid pvuuid in
296
297                 (* Get the underlying physical device. *)
298                 let _, dev = List.assoc pvuuid pvs in
299
300                 (* Construct a PV device. *)
301                 let pe_start = get_int64 "pe_start" meta in
302                 let pe_start = pe_start *^ sector_size64 in
303                 let pe_count = get_int64 "pe_count" meta in
304                 let pe_count = pe_count *^ extent_size in
305                 let pvdev = new offset_device pvuuid pe_start pe_count dev in
306
307                 Some (pvname, pvdev)
308             | _ ->
309                 None
310           ) pvdevs, extent_size
311         with
312           (* Something went wrong - just return an empty map. *)
313           Not_found -> [], 0L in
314       (vgname, (pvuuids, vgmeta, pvdevs, extent_size))
315   ) vgs in
316
317   (* Scan for logical volumes.  Each VG contains several LVs.
318    * This gives us a list of LVs within each VG (hence extends
319    * the vgs variable).
320    *)
321   let vgs = List.map (
322     fun (vgname, (pvuuids, vgmeta, pvdevs, extent_size)) ->
323       let lvs =
324         try
325           let lvs = get_meta "logical_volumes" vgmeta in
326           let lvs = List.filter_map (
327             function
328             | lvname, Metadata lvmeta ->
329                 (try
330                    let segment_count = get_int "segment_count" lvmeta 0 1024 in
331
332                    (* Get the segments for this LV. *)
333                    let segments = range 1 (segment_count+1) in
334                    let segments =
335                      List.map
336                        (fun i -> get_meta ("segment" ^ string_of_int i) lvmeta)
337                        segments in
338
339                    let segments =
340                      List.map (
341                        fun segmeta ->
342                          let start_extent =
343                            get_int64 "start_extent" segmeta in
344                          let extent_count =
345                            get_int64 "extent_count" segmeta in
346                          let segtype = get_string "type" segmeta in
347
348                          (* Can only handle striped segments at the
349                           * moment. XXX
350                           *)
351                          if segtype <> "striped" then raise Not_found;
352
353                          let stripe_count =
354                            get_int "stripe_count" segmeta 0 1024 in
355                          let stripes = get_stripes "stripes" segmeta in
356
357                          if List.length stripes <> stripe_count then
358                            raise Not_found;
359
360                          (* Can only handle linear striped segments at
361                           * the moment. XXX
362                           *)
363                          if stripe_count <> 1 then raise Not_found;
364                          let pvname, pvoffset = List.hd stripes in
365
366                          (start_extent, extent_count, pvname, pvoffset)
367                      ) segments in
368
369                    Some (lvname, segments)
370                  with
371                    (* Something went wrong with segments - omit this LV. *)
372                    Not_found -> None)
373             | _ -> None
374           ) lvs in
375
376           lvs
377         with
378           Not_found ->
379             (* Something went wrong - assume no LVs found. *)
380             [] in
381       (vgname, (pvuuids, vgmeta, pvdevs, extent_size, lvs))
382   ) vgs in
383
384   (* Print the LVs. *)
385   if !debug then (
386     List.iter (
387       fun (vgname, (pvuuids, vgmeta, pvdevs, extent_size, lvs)) ->
388         eprintf "VG %s: (extent_size = %Ld bytes)\n" vgname extent_size;
389         List.iter (
390           fun (lvname, segments) ->
391             eprintf "  %s/%s:\n" vgname lvname;
392             List.iter (
393               fun (start_extent, extent_count, pvname, pvoffset) ->
394                 eprintf "    start %Ld count %Ld at %s:%Ld\n"
395                   start_extent extent_count pvname pvoffset
396             ) segments
397         ) lvs
398     ) vgs;
399     flush stderr
400   );
401
402   (* Finally we can set up devices for the LVs. *)
403   let lvs =
404     List.map (
405       fun (vgname, (pvuuid, vgmeta, pvdevs, extent_size, lvs)) ->
406         try
407           List.map (
408             fun (lvname, segments) ->
409               let name = vgname ^ "/" ^ lvname in
410               let segments = List.map (
411                 fun (start_extent, extent_count, pvname, pvoffset) ->
412                   (* Get the PV device. *)
413                   let pvdev = List.assoc pvname pvdevs in
414
415                   (* Extents                 mapped to:             *)
416                   (start_extent, extent_count,          pvdev, pvoffset)
417               ) segments in
418
419               (* Create a linear mapping device. *)
420               let lv_dev = new linear_map_device name extent_size segments in
421
422               { lv_dev = lv_dev }
423           ) lvs
424         with
425           Not_found -> []
426     ) vgs in
427   let lvs = List.concat lvs in
428
429   (* Return the list of LV devices. *)
430   lvs