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