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