Updated MANIFEST.
[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 ( +* ) = Int32.add
27 let ( -* ) = Int32.sub
28 let ( ** ) = Int32.mul
29 let ( /* ) = Int32.div
30
31 let ( +^ ) = Int64.add
32 let ( -^ ) = Int64.sub
33 let ( *^ ) = Int64.mul
34 let ( /^ ) = Int64.div
35
36 let debug = ref false
37 let uri = ref None
38 let inodes = ref false
39 let human = ref false
40 let all = ref false
41 let test_files = ref []
42
43 class virtual device =
44 object (self)
45   method virtual read : int64 -> int -> string
46   method virtual size : int64
47   method virtual name : string
48
49   (* Helper method to read a chunk of data into a bitstring. *)
50   method read_bitstring offset len =
51     let str = self#read offset len in
52     (str, 0, len * 8)
53 end
54
55 (* A concrete device which just direct-maps a file or /dev device. *)
56 class block_device filename =
57   let fd = openfile filename [ O_RDONLY ] 0 in
58   let size = (LargeFile.fstat fd).LargeFile.st_size in
59 object (self)
60   inherit device
61   method read offset len =
62     ignore (LargeFile.lseek fd offset SEEK_SET);
63     let str = String.make len '\000' in
64     read fd str 0 len;
65     str
66   method size = size
67   method name = filename
68 end
69
70 (* A linear offset/size from an underlying device. *)
71 class offset_device name start size (dev : device) =
72 object
73   inherit device
74   method name = name
75   method size = size
76   method read offset len =
77     if offset < 0L || len < 0 || offset +^ Int64.of_int len > size then
78       invalid_arg (
79         sprintf "%s: tried to read outside device boundaries (%Ld/%d/%Ld)"
80           name offset len size
81       );
82     dev#read (start+^offset) len
83 end
84
85 (* The null device.  Any attempt to read generates an error. *)
86 let null_device : device =
87 object
88   inherit device
89   method read _ _ = assert false
90   method size = 0L
91   method name = "null"
92 end
93
94 type domain = {
95   dom_name : string;                    (* Domain name. *)
96   dom_id : int option;                  (* Domain ID (if running). *)
97   dom_disks : disk list;                (* Domain disks. *)
98   dom_lv_filesystems :
99     (lv * filesystem) list;             (* Domain LV filesystems. *)
100 }
101 and disk = {
102   (* From the XML ... *)
103   d_type : string option;               (* The <disk type=...> *)
104   d_device : string;                    (* The <disk device=...> (eg "disk") *)
105   d_source : string;                    (* The <source file=... or dev> *)
106   d_target : string;                    (* The <target dev=...> (eg "hda") *)
107
108   (* About the device itself. *)
109   d_dev : device;                       (* Disk device. *)
110   d_content : disk_content;             (* What's on it. *)
111 }
112 and disk_content =
113   [ `Unknown                            (* Not probed or unknown. *)
114   | `Partitions of partitions           (* Contains partitions. *)
115   | `Filesystem of filesystem           (* Contains a filesystem directly. *)
116   | `PhysicalVolume of pv               (* Contains an LVM PV. *)
117   ]
118
119 (* Partitions. *)
120
121 and partitions = {
122   parts_name : string;                  (* Name of partitioning scheme. *)
123   parts : partition list                (* Partitions. *)
124 }
125 and partition = {
126   part_status : partition_status;       (* Bootable, etc. *)
127   part_type : int;                      (* Partition filesystem type. *)
128   part_dev : device;                    (* Partition device. *)
129   part_content : partition_content;     (* What's on it. *)
130 }
131 and partition_status = Bootable | Nonbootable | Malformed | NullEntry
132 and partition_content =
133   [ `Unknown                            (* Not probed or unknown. *)
134   | `Filesystem of filesystem           (* Filesystem. *)
135   | `PhysicalVolume of pv               (* Contains an LVM PV. *)
136   ]
137
138 (* Filesystems (also swap devices). *)
139 and filesystem = {
140   fs_name : string;                     (* Name of filesystem. *)
141   fs_block_size : int64;                (* Block size (bytes). *)
142   fs_blocks_total : int64;              (* Total blocks. *)
143   fs_is_swap : bool;                    (* If swap, following not valid. *)
144   fs_blocks_reserved : int64;           (* Blocks reserved for super-user. *)
145   fs_blocks_avail : int64;              (* Blocks free (available). *)
146   fs_blocks_used : int64;               (* Blocks in use. *)
147   fs_inodes_total : int64;              (* Total inodes. *)
148   fs_inodes_reserved : int64;           (* Inodes reserved for super-user. *)
149   fs_inodes_avail : int64;              (* Inodes free (available). *)
150   fs_inodes_used : int64;               (* Inodes in use. *)
151 }
152
153 (* Physical volumes. *)
154 and pv = {
155   lvm_plugin_id : lvm_plugin_id;        (* The LVM plug-in. *)
156   pv_uuid : string;                     (* UUID. *)
157 }
158
159 (* Logical volumes. *)
160 and lv = {
161   lv_dev : device;                      (* Logical volume device. *)
162 }
163
164 and lvm_plugin_id = string
165
166 (* Convert partition, filesystem types to printable strings for debugging. *)
167 let string_of_partition
168     { part_status = status; part_type = typ; part_dev = dev } =
169   sprintf "%s: %s partition type %d"
170     dev#name
171     (match status with
172      | Bootable -> "bootable"
173      | Nonbootable -> "nonbootable"
174      | Malformed -> "malformed"
175      | NullEntry -> "empty")
176     typ
177
178 let string_of_filesystem { fs_name = name; fs_is_swap = swap } =
179   if not swap then name
180   else name ^ " [swap]"
181
182 (* Convert a UUID (containing '-' chars) to canonical form. *)
183 let canonical_uuid uuid =
184   let uuid' = String.make 32 ' ' in
185   let j = ref 0 in
186   for i = 0 to String.length uuid - 1 do
187     if !j >= 32 then
188       invalid_arg (sprintf (f_ "canonical_uuid: UUID is too long: %s") uuid);
189     let c = uuid.[i] in
190     if c <> '-' then ( uuid'.[!j] <- c; incr j )
191   done;
192   if !j <> 32 then
193     invalid_arg (sprintf (f_ "canonical_uuid: invalid UUID: %s") uuid);
194   uuid'
195
196 (* Register a partition scheme. *)
197 let partition_types = ref []
198 let partition_type_register (parts_name : string) probe_fn =
199   partition_types := (parts_name, probe_fn) :: !partition_types
200
201 (* Probe a device for partitions.  Returns [Some parts] or [None]. *)
202 let probe_for_partitions dev =
203   if !debug then eprintf "probing for partitions on %s ...\n%!" dev#name;
204   let rec loop = function
205     | [] -> None
206     | (parts_name, probe_fn) :: rest ->
207         try Some (probe_fn dev)
208         with Not_found -> loop rest
209   in
210   let r = loop !partition_types in
211   if !debug then (
212     match r with
213     | None -> eprintf "no partitions found on %s\n%!" dev#name
214     | Some { parts_name = name; parts = parts } ->
215         eprintf "found %d %s partitions on %s:\n"
216           (List.length parts) name dev#name;
217         List.iter (fun p -> eprintf "\t%s\n%!" (string_of_partition p)) parts
218   );
219   r
220
221 (* Register a filesystem type (or swap). *)
222 let filesystem_types = ref []
223 let filesystem_type_register (fs_name : string) probe_fn =
224   filesystem_types := (fs_name, probe_fn) :: !filesystem_types
225
226 (* Probe a device for a filesystem.  Returns [Some fs] or [None]. *)
227 let probe_for_filesystem dev =
228   if !debug then eprintf "probing for a filesystem on %s ...\n%!" dev#name;
229   let rec loop = function
230     | [] -> None
231     | (fs_name, probe_fn) :: rest ->
232         try Some (probe_fn dev)
233         with Not_found -> loop rest
234   in
235   let r = loop !filesystem_types in
236   if !debug then (
237     match r with
238     | None -> eprintf "no filesystem found on %s\n%!" dev#name
239     | Some fs ->
240         eprintf "found a filesystem on %s:\n" dev#name;
241         eprintf "\t%s\n%!" (string_of_filesystem fs)
242   );
243   r
244
245 (* Register a volume management type. *)
246 let lvm_types = ref []
247 let lvm_type_register (lvm_name : string) probe_fn list_lvs_fn =
248   lvm_types := (lvm_name, (probe_fn, list_lvs_fn)) :: !lvm_types
249
250 (* Probe a device for a PV.  Returns [Some lvm_name] or [None]. *)
251 let probe_for_pv dev =
252   if !debug then eprintf "probing if %s is a PV ...\n%!" dev#name;
253   let rec loop = function
254     | [] -> None
255     | (lvm_name, (probe_fn, _)) :: rest ->
256         try Some (probe_fn lvm_name dev)
257         with Not_found -> loop rest
258   in
259   let r = loop !lvm_types in
260   if !debug then (
261     match r with
262     | None -> eprintf "no PV found on %s\n%!" dev#name
263     | Some { lvm_plugin_id = name } ->
264         eprintf "%s contains a %s PV\n%!" dev#name name
265   );
266   r
267
268 let list_lvs lvm_name devs =
269   let _, list_lvs_fn = List.assoc lvm_name !lvm_types in
270   list_lvs_fn devs
271
272 (*----------------------------------------------------------------------*)
273
274 (* This version by Isaac Trotts. *)
275 let group_by ?(cmp = Pervasives.compare) ls =
276   let ls' =
277     List.fold_left
278       (fun acc (day1, x1) ->
279          match acc with
280              [] -> [day1, [x1]]
281            | (day2, ls2) :: acctl ->
282                if cmp day1 day2 = 0
283                then (day1, x1 :: ls2) :: acctl
284                else (day1, [x1]) :: acc)
285       []
286       ls
287   in
288   let ls' = List.rev ls' in
289   List.map (fun (x, xs) -> x, List.rev xs) ls'
290
291 let rec range a b =
292   if a < b then a :: range (a+1) b
293   else []