Make debug selectable at runtime.
[virt-top.git] / virt-df / virt_df_mbr.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 Master Boot Record partition scheme.
21 *)
22
23 open Printf
24 open Unix
25 open ExtList
26
27 open Virt_df_gettext.Gettext
28 open Virt_df
29
30 let sector_size = 512
31 let sector_size64 = 512L
32
33 (* Maximum number of extended partitions possible. *)
34 let max_extended_partitions = 100
35
36 (* Device representing a single partition.  It just acts as an offset
37  * into the underlying device.
38  *
39  * Notes:
40  * (1) 'start'/'size' are measured in sectors.
41  * (2) 'partno' is the partition number, starting at 1
42  *     (cf. /dev/hda1 is the first partition).
43  * (3) 'dev' is the underlying block device.
44  *)
45 class partition_device dev partno start size =
46   let devname = dev#name in
47   let name = sprintf "%s%d" devname partno in
48   let start = start *^ sector_size64 in
49   let size = size *^ sector_size64 in
50 object (self)
51   inherit device
52   method name = name
53   method size = size
54   method read offset len =
55     if offset < 0L || len < 0 || offset +^ Int64.of_int len > size then
56       invalid_arg (
57         sprintf "%s: tried to read outside partition boundaries (%Ld/%d/%Ld)"
58           name offset len size
59       );
60     dev#read (start+^offset) len
61 end
62
63 (** Probe the
64     {{:http://en.wikipedia.org/wiki/Master_boot_record}master boot record}
65     (if it is one) and read the partitions.
66
67     @raise Not_found if it is not an MBR.
68  *)
69 let rec probe_mbr dev =
70   (* Adjust size to sectors. *)
71   let size = dev#size /^ sector_size64 in
72
73   (* Read the first sector. *)
74   let bits =
75     try dev#read_bitstring 0L sector_size
76     with exn -> raise Not_found in
77
78   (* Does this match a likely-looking MBR? *)
79   bitmatch bits with
80   | padding : 3568 : bitstring;         (* padding to byte offset 446 *)
81     part0 : 128 : bitstring;            (* partitions *)
82     part1 : 128 : bitstring;
83     part2 : 128 : bitstring;
84     part3 : 128 : bitstring;
85     0x55 : 8; 0xAA : 8 ->               (* MBR signature *)
86
87       (* Parse the partition table entries. *)
88       let primaries =
89         List.mapi (parse_mbr_entry dev) [part0;part1;part2;part3] in
90
91 (*
92       (* Read extended partition data. *)
93       let extendeds = List.map (
94         function
95         | { part_type = 0x05 } as part ->
96             probe_extended_partition
97               max_extended_partitions fd part part.part_lba_start
98         | part -> []
99       ) primaries in
100       let extendeds = List.concat extendeds in
101       primaries @ extendeds
102 *)
103       { parts_name = "MBR"; parts = primaries }
104
105   | _ ->
106       raise Not_found                   (* not an MBR *)
107
108 (* Parse a single partition table entry.  See the table here:
109  * http://en.wikipedia.org/wiki/Master_boot_record
110  *)
111 and parse_mbr_entry dev i bits =
112   bitmatch bits with
113   | 0l : 32; 0l : 32; 0l : 32; 0l : 32 ->
114     { part_status = NullEntry; part_type = 0;
115       part_dev = null_device; part_content = `Unknown }
116
117   | 0 : 8; first_chs : 24;
118     part_type : 8; last_chs : 24;
119     first_lba : 32 : unsigned, littleendian;
120     part_size : 32 : unsigned, littleendian ->
121     make_mbr_entry Nonbootable dev (i+1) part_type first_lba part_size
122
123   | 0x80 : 8; first_chs : 24;
124     part_type : 8; last_chs : 24;
125     first_lba : 32 : unsigned, littleendian;
126     part_size : 32 : unsigned, littleendian ->
127     make_mbr_entry Bootable dev (i+1) part_type first_lba part_size
128
129   | _ ->
130       { part_status = Malformed; part_type = 0;
131         part_dev = null_device; part_content = `Unknown }
132
133 and make_mbr_entry part_status dev partno part_type first_lba part_size =
134   let first_lba = uint64_of_int32 first_lba in
135   let part_size = uint64_of_int32 part_size in
136   if !debug then
137     eprintf "make_mbr_entry: first_lba = %Lx part_size = %Lx\n%!"
138       first_lba part_size;
139   { part_status = part_status;
140     part_type = part_type;
141     part_dev = new partition_device dev partno first_lba part_size;
142     part_content = `Unknown }
143
144 (*
145 This code worked previously, but now needs some love ...
146 XXX
147
148 (* Probe an extended partition. *)
149 and probe_extended_partition max fd epart sect =
150   if max > 0 then (
151     (* Offset of the first EBR. *)
152     let ebr_offs = sect *^ sector_size in
153     (* EBR Signature? *)
154     LargeFile.lseek fd (ebr_offs +^ 510L) SEEK_SET;
155     let str = String.create 2 in
156     if read fd str 0 2 <> 2 || str.[0] != '\x55' || str.[1] != '\xAA' then
157       [] (* Not EBR *)
158     else (
159       (* Read the extended partition table entries (just 2 of them). *)
160       LargeFile.lseek fd (ebr_offs +^ 446L) SEEK_SET;
161       let str = String.create 32 in
162       if read fd str 0 32 <> 32 then
163         failwith (s_ "error reading extended partition")
164       else (
165         (* Extract partitions from the data. *)
166         let part1, part2 =
167           match List.map (get_partition str) [ 0; 16 ] with
168           | [p1;p2] -> p1,p2
169           | _ -> failwith (s_ "probe_extended_partition: internal error") in
170         (* First partition entry has offset to the start of this partition. *)
171         let part1 = { part1 with
172                         part_lba_start = sect +^ part1.part_lba_start } in
173         (* Second partition entry is zeroes if end of list, otherwise points
174          * to the next partition.
175          *)
176         if part2.part_status = NullEntry then
177           [part1]
178         else
179           part1 :: probe_extended_partition
180                      (max-1) fd epart (sect +^ part2.part_lba_start)
181       )
182     )
183   )
184   else []
185 *)
186
187 (* Ugh, fake a UInt32 -> UInt64 conversion without sign extension, until
188  * we get working UInt32/UInt64 modules in extlib.
189  *)
190 and uint64_of_int32 u32 =
191   let i64 = Int64.of_int32 u32 in
192   if u32 >= 0l then i64
193   else Int64.add i64 0x1_0000_0000_L
194
195 (* Register with main code. *)
196 let () = partition_type_register "MBR" probe_mbr