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