Added range library function.
[virt-top.git] / virt-df / virt_df.ml
1 (* 'df' command for virtual domains.
2    (C) Copyright 2007-2008 Richard W.M. Jones, Red Hat Inc.
3    http://libvirt.org/
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  *)
19
20 open Printf
21 open ExtList
22 open Unix
23
24 open Virt_df_gettext.Gettext
25
26 let debug = true     (* If true emit lots of debugging information. *)
27
28 let ( +* ) = Int32.add
29 let ( -* ) = Int32.sub
30 let ( ** ) = Int32.mul
31 let ( /* ) = Int32.div
32
33 let ( +^ ) = Int64.add
34 let ( -^ ) = Int64.sub
35 let ( *^ ) = Int64.mul
36 let ( /^ ) = Int64.div
37
38 let uri = ref None
39 let inodes = ref false
40 let human = ref false
41 let all = ref false
42 let test_files = ref []
43
44 class virtual device =
45 object (self)
46   method virtual read : int64 -> int -> string
47   method virtual size : int64
48   method virtual name : string
49
50   (* Helper method to read a chunk of data into a bitstring. *)
51   method read_bitstring offset len =
52     let str = self#read offset len in
53     (str, 0, len * 8)
54 end
55
56 (* A concrete device which just direct-maps a file or /dev device. *)
57 class block_device filename =
58   let fd = openfile filename [ O_RDONLY ] 0 in
59   let size = (LargeFile.fstat fd).LargeFile.st_size in
60 object (self)
61   inherit device
62   method read offset len =
63     ignore (LargeFile.lseek fd offset SEEK_SET);
64     let str = String.make len '\000' in
65     read fd str 0 len;
66     str
67   method size = size
68   method name = filename
69 end
70
71 (* The null device.  Any attempt to read generates an error. *)
72 let null_device : device =
73 object
74   inherit device
75   method read _ _ = assert false
76   method size = 0L
77   method name = "null"
78 end
79
80 type domain = {
81   dom_name : string;                    (* Domain name. *)
82   dom_id : int option;                  (* Domain ID (if running). *)
83   dom_disks : disk list;                (* Domain disks. *)
84   dom_lv_filesystems : filesystem list; (* Domain LV filesystems. *)
85 }
86 and disk = {
87   (* From the XML ... *)
88   d_type : string option;               (* The <disk type=...> *)
89   d_device : string;                    (* The <disk device=...> (eg "disk") *)
90   d_source : string;                    (* The <source file=... or dev> *)
91   d_target : string;                    (* The <target dev=...> (eg "hda") *)
92
93   (* About the device itself. *)
94   d_dev : device;                       (* Disk device. *)
95   d_content : disk_content;             (* What's on it. *)
96 }
97 and disk_content =
98   [ `Unknown                            (* Not probed or unknown. *)
99   | `Partitions of partitions           (* Contains partitions. *)
100   | `Filesystem of filesystem           (* Contains a filesystem directly. *)
101   | `PhysicalVolume of pv               (* Contains an LVM PV. *)
102   ]
103
104 (* Partitions. *)
105
106 and partitions = {
107   parts_name : string;                  (* Name of partitioning scheme. *)
108   parts : partition list                (* Partitions. *)
109 }
110 and partition = {
111   part_status : partition_status;       (* Bootable, etc. *)
112   part_type : int;                      (* Partition filesystem type. *)
113   part_dev : device;                    (* Partition device. *)
114   part_content : partition_content;     (* What's on it. *)
115 }
116 and partition_status = Bootable | Nonbootable | Malformed | NullEntry
117 and partition_content =
118   [ `Unknown                            (* Not probed or unknown. *)
119   | `Filesystem of filesystem           (* Filesystem. *)
120   | `PhysicalVolume of pv               (* Contains an LVM PV. *)
121   ]
122
123 (* Filesystems (also swap devices). *)
124 and filesystem = {
125   fs_name : string;                     (* Name of filesystem. *)
126   fs_block_size : int64;                (* Block size (bytes). *)
127   fs_blocks_total : int64;              (* Total blocks. *)
128   fs_is_swap : bool;                    (* If swap, following not valid. *)
129   fs_blocks_reserved : int64;           (* Blocks reserved for super-user. *)
130   fs_blocks_avail : int64;              (* Blocks free (available). *)
131   fs_blocks_used : int64;               (* Blocks in use. *)
132   fs_inodes_total : int64;              (* Total inodes. *)
133   fs_inodes_reserved : int64;           (* Inodes reserved for super-user. *)
134   fs_inodes_avail : int64;              (* Inodes free (available). *)
135   fs_inodes_used : int64;               (* Inodes in use. *)
136 }
137
138 (* Physical volumes. *)
139 and pv = {
140   lvm_plugin_id : lvm_plugin_id;        (* The LVM plug-in. *)
141   pv_uuid : string;                     (* UUID. *)
142 }
143
144 (* Logical volumes. *)
145 and lv = {
146   lv_dev : device;                      (* Logical volume device. *)
147 }
148
149 and lvm_plugin_id = string
150
151 (* Convert partition, filesystem types to printable strings for debugging. *)
152 let string_of_partition
153     { part_status = status; part_type = typ; part_dev = dev } =
154   sprintf "%s: %s partition type %d"
155     dev#name
156     (match status with
157      | Bootable -> "bootable"
158      | Nonbootable -> "nonbootable"
159      | Malformed -> "malformed"
160      | NullEntry -> "empty")
161     typ
162
163 let string_of_filesystem { fs_name = name; fs_is_swap = swap } =
164   if not swap then name
165   else name ^ " [swap]"
166
167 (* Register a partition scheme. *)
168 let partition_types = ref []
169 let partition_type_register (parts_name : string) probe_fn =
170   partition_types := (parts_name, probe_fn) :: !partition_types
171
172 (* Probe a device for partitions.  Returns [Some parts] or [None]. *)
173 let probe_for_partitions dev =
174   if debug then eprintf "probing for partitions on %s ...\n%!" dev#name;
175   let rec loop = function
176     | [] -> None
177     | (parts_name, probe_fn) :: rest ->
178         try Some (probe_fn dev)
179         with Not_found -> loop rest
180   in
181   let r = loop !partition_types in
182   if debug then (
183     match r with
184     | None -> eprintf "no partitions found on %s\n%!" dev#name
185     | Some { parts_name = name; parts = parts } ->
186         eprintf "found %d %s partitions on %s:\n"
187           (List.length parts) name dev#name;
188         List.iter (fun p -> eprintf "\t%s\n%!" (string_of_partition p)) parts
189   );
190   r
191
192 (* Register a filesystem type (or swap). *)
193 let filesystem_types = ref []
194 let filesystem_type_register (fs_name : string) probe_fn =
195   filesystem_types := (fs_name, probe_fn) :: !filesystem_types
196
197 (* Probe a device for a filesystem.  Returns [Some fs] or [None]. *)
198 let probe_for_filesystem dev =
199   if debug then eprintf "probing for a filesystem on %s ...\n%!" dev#name;
200   let rec loop = function
201     | [] -> None
202     | (fs_name, probe_fn) :: rest ->
203         try Some (probe_fn dev)
204         with Not_found -> loop rest
205   in
206   let r = loop !filesystem_types in
207   if debug then (
208     match r with
209     | None -> eprintf "no filesystem found on %s\n%!" dev#name
210     | Some fs ->
211         eprintf "found a filesystem on %s:\n" dev#name;
212         eprintf "\t%s\n%!" (string_of_filesystem fs)
213   );
214   r
215
216 (* Register a volume management type. *)
217 let lvm_types = ref []
218 let lvm_type_register (lvm_name : string) probe_fn list_lvs_fn =
219   lvm_types := (lvm_name, (probe_fn, list_lvs_fn)) :: !lvm_types
220
221 (* Probe a device for a PV.  Returns [Some lvm_name] or [None]. *)
222 let probe_for_pv dev =
223   if debug then eprintf "probing if %s is a PV ...\n%!" dev#name;
224   let rec loop = function
225     | [] -> None
226     | (lvm_name, (probe_fn, _)) :: rest ->
227         try Some (probe_fn lvm_name dev)
228         with Not_found -> loop rest
229   in
230   let r = loop !lvm_types in
231   if debug then (
232     match r with
233     | None -> eprintf "no PV found on %s\n%!" dev#name
234     | Some { lvm_plugin_id = name } ->
235         eprintf "%s contains a %s PV\n%!" dev#name name
236   );
237   r
238
239 let list_lvs lvm_name devs =
240   let _, list_lvs_fn = List.assoc lvm_name !lvm_types in
241   list_lvs_fn devs
242
243 (*----------------------------------------------------------------------*)
244
245 (* This version by Isaac Trotts. *)
246 let group_by ?(cmp = Pervasives.compare) ls =
247   let ls' =
248     List.fold_left
249       (fun acc (day1, x1) ->
250          match acc with
251              [] -> [day1, [x1]]
252            | (day2, ls2) :: acctl ->
253                if cmp day1 day2 = 0
254                then (day1, x1 :: ls2) :: acctl
255                else (day1, [x1]) :: acc)
256       []
257       ls
258   in
259   let ls' = List.rev ls' in
260   List.map (fun (x, xs) -> x, List.rev xs) ls'
261
262 let rec range a b =
263   if a < b then a :: range (a+1) b
264   else []