1 (* 'df' command for virtual domains.
2 (C) Copyright 2007 Richard W.M. Jones, Red Hat Inc.
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.
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.
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.
28 (* Private data functions. *)
29 let attach_private_data, get_private_data =
30 private_data_functions (fun {fs_cb = {fs_cb_uq = u}} -> u)
35 let fs = probe_superblock dev in
38 and probe_superblock dev =
39 (* Load the boot sector / superblock. *)
40 let bits = dev#read_bitstring ~^0 ~^512 in
42 (* Most of this data comes from ntfsprogs' layout.h header file. *)
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. *)
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;
67 clusters_per_index_record : 8;
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. *)
74 let blocksize = bytes_per_sector * sectors_per_cluster in
77 eprintf "%s: NTFS boot sector with blocksize = %d, serial = %Lx\n%!"
78 dev#name blocksize volume_serial_number;
80 let blocksize = Int63.of_int blocksize in
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.
86 let fs_dev = new blocksize_overlay blocksize dev in
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
92 let mft = parse_mft dev mft_lcn mft_size in
94 raise Not_found (* XXX *)
96 | { _ } -> raise Not_found (* Not an NTFS boot sector. *)
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
102 (* ... and turn the MFT into records. *)
103 let records = parse_mft_records bits in
106 and parse_mft_records bits =
108 | { "FILE" : 32 : string;
109 (* Assume 3 USAs starting at offset 0x30. XXX? *)
110 0x30 : 16 : littleendian;
111 0x03 : 16 : littleendian;
113 _ : 16; (* sequence_number *)
114 _ : 16; (* link_count *)
115 _ : 16; (* attrs_offset *)
116 _ : 16; (* MFT_RECORD_FLAGS *)
117 bytes_in_use : 32 : littleendian;
118 record_size : 32 : littleendian;
119 _ : 64; (* base_mft_record *)
120 _ : 16; (* next_attr_instance *)
121 _ : 16; (* reserved *)
122 _ : 32; (* mft_record_number *)
123 _ : 64; (* USN, 3 * USAs -- see above. *)
125 (* The attributes. Subtract header size (0x30 bytes)
126 * and space for the USN/USAs (8 bytes).
128 attrs : (Int32.to_int record_size - 0x30 - 8)*8 : bitstring;
130 (* Subsequent MFT records: *)
131 rest : -1 : bitstring } ->
134 eprintf "got an MFT record, now parsing attributes ...\n%!";
136 let attrs = parse_attrs attrs in
138 parse_mft_records rest (* loop rest of MFT records *)
140 (* Just assume that the end of the list of MFT records
141 * is marked by all zeroes. This seems to be the
142 * case, but not sure if it is generally true.
145 | { 0x00000000_l : 32 } ->
150 and parse_attrs attrs =
151 (* Parse the MFT record attributes. *)
153 | { 0xFFFFFFFF_l : 32 : littleendian } -> (* AT_END *)
155 eprintf "found AT_END, end of attributes\n%!";
158 | { attr_type : 32 : littleendian;
159 attr_size : 32 : littleendian;
160 0 : 8; (* means attribute is resident *)
161 pad : 24*8 - 8 - 64 : bitstring; (* actually meaningful *)
162 attr : (Int32.to_int attr_size - 24) * 8 : bitstring;
163 rest : -1 : bitstring } ->
165 (* XXX let attr = *) parse_resident_attr attr_type attr;
168 | { attr_type : 32 : littleendian;
169 attr_size : 32 : littleendian;
170 1 : 8; (* non-resident attribute *)
171 0 : 8; (* name length, assume unnamed *)
172 _ : 16; (* name offset *)
174 _ : 16; (* instance number *)
175 0L : 64 : littleendian; (* lowest VCN, assume single extent *)
176 highest_vcn : 64 : littleendian; (* size in clusters - 1 *)
177 0x40 : 16 : littleendian; (* mapping pairs offset *)
178 0 : 8; (* assume not compressed *)
179 pad : 40 : bitstring; (* padding *)
180 allocated_size : 64 : littleendian; (* allocate size on disk *)
181 data_size : 64 : littleendian; (* byte size of the attribute *)
182 initialized_size : 64 : littleendian;
184 (* Table of virtual clusters to logical clusters. *)
185 mapping_pairs : (Int32.to_int attr_size - 0x40) * 8 : bitstring;
187 rest : -1 : bitstring } ->
190 parse_nonresident_attr attr_type highest_vcn
191 allocated_size data_size initialized_size
196 (* Not matched above, so we don't know how to parse this attribute, but
197 * there is still enough information to skip to the next one.
199 | { attr_type : 32 : littleendian;
200 attr_size : 32 : littleendian;
201 pad : (Int32.to_int attr_size - 8) * 8 : bitstring;
202 rest : -1 : bitstring } ->
205 eprintf "cannot parse MFT attribute entry\n%!";
206 Bitmatch.hexdump_bitstring Pervasives.stderr attrs
211 (* Otherwise unparsable & unskippable attribute entry. *)
214 eprintf "corrupt MFT attribute entry\n%!"
216 and parse_resident_attr attr_type attr =
218 | 0x10_l -> (* AT_STANDARD_INFORMATION *)
220 | { creation_time : 64;
221 last_data_change_time : 64;
222 last_mft_change_time : 64;
223 last_access_time : 64
224 (* other stuff follows, just ignore it *) } ->
226 eprintf "creation time: %Lx, last_access_time: %Lx\n"
227 creation_time last_access_time
231 eprintf "cannot parse AT_STANDARD_INFORMATION\n%!"
234 | 0x30_l -> (* AT_FILE_NAME *)
236 | { _ : 64; (* parent directory ref *)
237 _ : 64; (* creation time *)
238 _ : 64; (* last change time *)
239 _ : 64; (* last MFT change time *)
240 _ : 64; (* last access time *)
241 allocated_size : 64 : littleendian;
242 data_size : 64 : littleendian;
247 name : name_len*16 : string } ->
249 let name = ucs2_to_utf8 name name_len in
251 eprintf "filename: %s (size: %Ld bytes)\n" name data_size
255 eprintf "cannot parse AT_FILE_NAME\n%!"
258 | _ -> (* unknown attribute - just ignore *)
260 eprintf "unknown resident attribute %lx\n%!" attr_type
262 and parse_nonresident_attr attr_type highest_vcn
263 allocated_size data_size initialized_size
266 | 0x80_l -> (* AT_DATA, ie. the $Data stream *)
268 eprintf "AT_DATA: size = %Ld bytes, highest_vcn = 0x%Lx\n"
269 data_size highest_vcn;
270 Bitmatch.hexdump_bitstring Pervasives.stderr mapping_pairs
273 let lowest_vcn = ~^0 (* see assumption above *) in
274 let runlist = parse_runlist lowest_vcn ~^0 mapping_pairs in
276 eprintf "AT_DATA: runlist is:\n";
279 | ((vcn, deltavcn), Some lcn) ->
280 eprintf "\tVCNs %s..%s -> LCN %s\n"
281 (Int63.to_string vcn) (Int63.to_string (vcn +^ deltavcn -^ ~^1))
282 (Int63.to_string lcn)
283 | ((vcn, deltavcn), None) ->
284 eprintf "\tVCNs %s..%s -> sparse hole\n"
285 (Int63.to_string vcn) (Int63.to_string (vcn +^ deltavcn -^ ~^1))
291 eprintf "unknown non-resident attribute %lx\n%!" attr_type
293 (* mapping_pairs is not straightforward and not documented well. See
294 * ntfsprogs libntfs/runlist.c:ntfs_mapping_pairs_decompress
296 and parse_runlist vcn lcn bits =
298 | { 0 : 8 } -> (* end of table *)
303 deltavcn : vcnlen * 8 : littleendian;
304 rest : -1 : bitstring
305 } when vcnlen >= 1 && vcnlen <= 4 ->
307 let deltavcn = Int63.of_int64 deltavcn in
309 (* This is a sparse file hole. *)
310 ((vcn, deltavcn), None) ::
311 parse_runlist (vcn +^ deltavcn) lcn rest
313 | { (* Really these fields are signed, but we'll just limit it to
314 * sensible values in the when clause instead.
318 deltavcn : vcnlen * 8 : littleendian;
319 deltalcn : lcnlen * 8 : littleendian;
320 rest : -1 : bitstring
321 } when (vcnlen >= 1 && vcnlen <= 4) && (lcnlen >= 1 || lcnlen <= 4) ->
323 let deltavcn = Int63.of_int64 deltavcn in
324 let deltalcn = Int63.of_int64 deltalcn in (* XXX signed *)
326 let lcn = lcn +^ deltalcn in
328 eprintf "lcnlen = %d, vcnlen = %d\n" lcnlen vcnlen;
330 ((vcn, deltavcn), Some lcn) ::
331 parse_runlist (vcn +^ deltavcn) lcn rest
335 eprintf "unknown field in the runlist\n%!";
336 Bitmatch.hexdump_bitstring Pervasives.stderr bits
340 (* Poor man's little-endian UCS-2 to UTF-8 conversion.
341 * XXX Should use Camomile.
343 and ucs2_to_utf8 name len =
344 (* Calculate length of final string. *)
345 let outlen = ref 0 in
347 for i = 0 to len-1 do
350 let c0 = Char.code name.[j'] and c1 = Char.code name.[j'+1] in
351 let c = c0 + c1 * 256 in
352 if c < 128 then incr outlen
353 else if c < 0x800 then outlen := !outlen + 2
354 else outlen := !outlen + 3
356 let outstr = String.create !outlen in
358 for i = 0 to len-1 do
361 let c0 = Char.code name.[j'] and c1 = Char.code name.[j'+1] in
362 let c = c0 + c1 * 256 in
364 outstr.[!outlen] <- Char.chr c;
366 ) else if c < 0x800 then (
367 outstr.[!outlen] <- Char.chr (0b11000000 lor (c lsr 6));
368 outstr.[!outlen+1] <- Char.chr (0b10000000 lor (c land 0b00111111));
369 outlen := !outlen + 2
371 outstr.[!outlen] <- Char.chr (0b11100000 lor (c lsr 12));
372 outstr.[!outlen+1] <- Char.chr (0b10000000 lor ((c lsr 6) lor 0b00111111));
373 outstr.[!outlen+2] <- Char.chr (0b10000000 lor (c land 0b00111111));
374 outlen := !outlen + 3
379 and offset_is_free _ _ = false
384 fs_cb_uq = (incr i; !i);
386 fs_cb_printable_name = "Windows NTFS";
387 fs_cb_offset_is_free = offset_is_free;
390 (* Register the plugin. *)
391 let () = register_plugin ~filesystem:probe id