Separate functions
[virt-df.git] / lib / diskimage_ntfs.ml
1 (* 'df' command for virtual domains.
2    (C) Copyright 2007 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    Support for NTFS.
20 *)
21
22 open Unix
23 open Printf
24
25 open Diskimage_impl
26 open Int63.Operators
27
28 (* Private data functions. *)
29 let attach_private_data, get_private_data =
30   private_data_functions (fun {fs_cb = {fs_cb_uq = u}} -> u)
31
32 let id = "ntfs"
33
34 let rec probe dev =
35   let fs = probe_superblock dev in
36   fs
37
38 and probe_superblock dev =
39   (* Load the boot sector / superblock. *)
40   let bits = dev#read_bitstring ~^0 ~^512 in
41
42   (* Most of this data comes from ntfsprogs' layout.h header file. *)
43   bitmatch bits with
44   | { _ : 24;                           (* Jump to boot up code. *)
45       "NTFS    " : 64 : string;         (* NTFS OEM ID (magic). *)
46       bytes_per_sector : 16 : littleendian;
47       sectors_per_cluster : 8 : littleendian;
48       _ : 16;                           (* Reserved sectors - unused. *)
49       _ : 8;                            (* FATs - unused. *)
50       _ : 16;                           (* Root entries - unused. *)
51       _ : 16;                           (* Sectors - unused. *)
52       _ : 8;                            (* Media type, probably 'f8' = HDD *)
53       _ : 16;                           (* Sectors per FAT - unused. *)
54       _ : 16;                           (* Sectors per track. *)
55       _ : 16;                           (* Heads. *)
56       _ : 32;                           (* Hidden sectors. *)
57       _ : 32;                           (* Large sectors. *)
58       _ : 8;                            (* Physical drive, 0 = FDD, 0x80 = HDD*)
59       _ : 8;                            (* Current head. *)
60       _ : 8;                            (* Extended boot signature. *)
61       _ : 8;                            (* Reserved. *)
62       number_of_sectors : 64 : littleendian;
63       mft_lcn : 64 : littleendian;      (* MFT location in clusters. *)
64       mftmirr_lcn : 64 : littleendian;  (* MFT mirror location. *)
65       clusters_per_mft_record : 8;
66       _ : 24;
67       clusters_per_index_record : 8;
68       _ : 24;
69       volume_serial_number : 64 : littleendian;
70       checksum : 32 : littleendian;     (* Boot sector checksum. *)
71       code : 8 * 426 : bitstring;       (* Boot code. *)
72       0x55AA : 16 } ->                  (* End of bootsector magic. *)
73
74       let blocksize = bytes_per_sector * sectors_per_cluster in
75
76       if !debug then
77         eprintf "%s: NTFS boot sector with blocksize = %d, serial = %Lx\n%!"
78           dev#name blocksize volume_serial_number;
79
80       let blocksize = Int63.of_int blocksize in
81
82       (* The blocksize of the filesystem is likely to be quite different
83        * from that of the underlying device, so create an overlay device
84        * with the natural filesystem blocksize.
85        *)
86       let fs_dev = new blocksize_overlay blocksize dev in
87
88       (* Get the location and size of the Master File Table. *)
89       let mft_lcn = Int63.of_int64 mft_lcn *^ blocksize in
90       let mft_size = Int63.of_int clusters_per_mft_record *^ blocksize in
91
92       let mft = parse_mft dev mft_lcn mft_size in
93
94       raise Not_found                   (* XXX *)
95
96   | { _ } -> raise Not_found            (* Not an NTFS boot sector. *)
97
98 and parse_mft dev mft_lcn mft_size =
99   (* Read the whole of the MFT (which is an array of MFT records) ... *)
100   let bits = dev#read_bitstring mft_lcn mft_size in
101
102   (* ... and turn the MFT into records. *)
103   let rec loop bits =
104     if Bitmatch.bitstring_length bits > 0 then (
105       bitmatch bits with
106       | { "FILE" : 32 : string;
107           (* Assume 3 USAs starting at offset 0x30. XXX? *)
108           0x30 : 16 : littleendian;
109           0x03 : 16 : littleendian;
110           _ : 64;                       (* lsn *)
111           _ : 16;                       (* sequence_number *)
112           _ : 16;                       (* link_count *)
113           _ : 16;                       (* attrs_offset *)
114           _ : 16;                       (* MFT_RECORD_FLAGS *)
115           bytes_in_use : 32 : littleendian;
116           record_size : 32 : littleendian;
117           _ : 64;                       (* base_mft_record *)
118           _ : 16;                       (* next_attr_instance *)
119           _ : 16;                       (* reserved *)
120           _ : 32;                       (* mft_record_number *)
121           _ : 64;                       (* USN, 3 * USAs -- see above. *)
122
123           (* The attributes.  Subtract header size (0x30 bytes)
124            * and space for the USN/USAs (8 bytes).
125            *)
126           attrs : (Int32.to_int record_size - 0x30 - 8)*8 : bitstring;
127
128           (* Subsequent MFT records: *)
129           rest : -1 : bitstring } ->
130
131           if !debug then
132             eprintf "got an MFT record, now parsing attributes ...\n%!";
133
134           let attrs = parse_attrs attrs in
135
136           loop rest                     (* loop rest of MFT records *)
137
138       (* Just assume that the end of the list of MFT records
139        * is marked by all zeroes.  This seems to be the
140        * case, but not sure if it is generally true.
141        * XXX?
142        *)
143       | { 0x00000000_l : 32 } ->
144           ()
145     ) in
146   let mft_records = loop bits in
147   mft_records
148
149 and parse_attrs attrs =
150   (* Parse the MFT record attributes. *)
151   bitmatch attrs with
152   | { 0xFFFFFFFF_l : 32 : littleendian } -> (* AT_END *)
153       if !debug then
154         eprintf "found AT_END, end of attributes\n%!";
155       ()
156
157   | { attr_type : 32 : littleendian;
158       attr_size : 32 : littleendian;
159       0 : 8;                         (* means attribute is resident *)
160       pad : 24*8 - 8 - 64 : bitstring; (* actually meaningful *)
161       attr : (Int32.to_int attr_size - 24) * 8 : bitstring;
162       rest : -1 : bitstring } ->
163
164       (* XXX let attr = *) parse_resident_attr attr_type attr;
165       parse_attrs rest
166
167   | { attr_type : 32 : littleendian;
168       attr_size : 32 : littleendian;
169       1 : 8;                            (* non-resident attribute *)
170       pad : (Int32.to_int attr_size - 9) * 8 : bitstring;
171       rest : -1 : bitstring } ->
172       if !debug then
173         eprintf "cannot parse non-resident attr %lx\n%!" attr_type;
174       parse_attrs rest
175
176   | { _ } ->
177       if !debug then
178         eprintf "corrupt MFT attribute entry\n%!"
179
180 and parse_resident_attr attr_type attr =
181   match attr_type with
182   | 0x30_l ->                           (* AT_FILE_NAME *)
183       (bitmatch attr with
184        | { _ : 64;                      (* parent directory ref *)
185            _ : 64;                      (* creation time *)
186            _ : 64;                      (* last change time *)
187            _ : 64;                      (* last MFT change time *)
188            _ : 64;                      (* last access time *)
189            allocated_size : 64 : littleendian;
190            data_size : 64 : littleendian;
191            _ : 32;
192            _ : 32;
193            name_len : 8;
194            name_type_flags : 8;
195            name : name_len*16 : string } ->
196
197            let name = ucs2_to_utf8 name name_len in
198            if !debug then
199              eprintf "filename: %s (size: %Ld bytes)\n"
200                name data_size
201
202        | { _ } ->
203            if !debug then
204              eprintf "cannot parse AT_FILE_NAME\n%!";
205       );
206
207   | _ ->                                (* unknown attribute - just ignore *)
208       if !debug then
209         eprintf "unknown resident attribute %lx\n%!"
210           attr_type
211
212 (* Poor man's little-endian UCS-2 to UTF-8 conversion.
213  * XXX Should use Camomile.
214  *)
215 and ucs2_to_utf8 name len =
216   (* Calculate length of final string. *)
217   let outlen = ref 0 in
218   let j = ref 0 in
219   for i = 0 to len-1 do
220     let j' = !j in
221     j := j' + 2;
222     let c0 = Char.code name.[j'] and c1 = Char.code name.[j'+1] in
223     let c = c0 + c1 * 256 in
224     if c < 128 then incr outlen
225     else if c < 0x800 then outlen := !outlen + 2
226     else outlen := !outlen + 3
227   done;
228   let outstr = String.create !outlen in
229   j := 0; outlen := 0;
230   for i = 0 to len-1 do
231     let j' = !j in
232     j := j' + 2;
233     let c0 = Char.code name.[j'] and c1 = Char.code name.[j'+1] in
234     let c = c0 + c1 * 256 in
235     if c < 128 then (
236       outstr.[!outlen] <- Char.chr c;
237       incr outlen
238     ) else if c < 0x800 then (
239       outstr.[!outlen] <- Char.chr (0b11000000 lor (c lsr 6));
240       outstr.[!outlen+1] <- Char.chr (0b10000000 lor (c land 0b00111111));
241       outlen := !outlen + 2
242     ) else (
243       outstr.[!outlen] <- Char.chr (0b11100000 lor (c lsr 12));
244       outstr.[!outlen+1] <- Char.chr (0b10000000 lor ((c lsr 6) lor 0b00111111));
245       outstr.[!outlen+2] <- Char.chr (0b10000000 lor (c land 0b00111111));
246       outlen := !outlen + 3
247     )
248   done;
249   outstr
250
251 and offset_is_free _ _ = false
252
253 and callbacks =
254   let i = ref 0 in
255   fun () -> {
256     fs_cb_uq = (incr i; !i);
257     fs_cb_name = id;
258     fs_cb_printable_name = "Windows NTFS";
259     fs_cb_offset_is_free = offset_is_free;
260   }
261
262 (* Register the plugin. *)
263 let () = register_plugin ~filesystem:probe id