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. *)
104 if Bitmatch.bitstring_length bits > 0 then (
106 | { "FILE" : 32 : string;
107 (* Assume 3 USAs starting at offset 0x30. XXX? *)
108 0x30 : 16 : littleendian;
109 0x03 : 16 : littleendian;
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. *)
123 (* The attributes. Subtract header size (0x30 bytes)
124 * and space for the USN/USAs (8 bytes).
126 attrs : (Int32.to_int record_size - 0x30 - 8)*8 : bitstring;
128 (* Subsequent MFT records: *)
129 rest : -1 : bitstring } ->
132 eprintf "got an MFT record, now parsing attributes ...\n%!";
134 let attrs = parse_attrs attrs in
136 loop rest (* loop rest of MFT records *)
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.
143 | { 0x00000000_l : 32 } ->
146 let mft_records = loop bits in
149 and parse_attrs attrs =
150 (* Parse the MFT record attributes. *)
152 | { 0xFFFFFFFF_l : 32 : littleendian } -> (* AT_END *)
154 eprintf "found AT_END, end of attributes\n%!";
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 } ->
164 (* XXX let attr = *) parse_resident_attr attr_type attr;
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 } ->
173 eprintf "cannot parse non-resident attr %lx\n%!" attr_type;
178 eprintf "corrupt MFT attribute entry\n%!"
180 and parse_resident_attr attr_type attr =
182 | 0x30_l -> (* AT_FILE_NAME *)
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;
195 name : name_len*16 : string } ->
197 let name = ucs2_to_utf8 name name_len in
199 eprintf "filename: %s (size: %Ld bytes)\n"
204 eprintf "cannot parse AT_FILE_NAME\n%!";
207 | _ -> (* unknown attribute - just ignore *)
209 eprintf "unknown resident attribute %lx\n%!"
212 (* Poor man's little-endian UCS-2 to UTF-8 conversion.
213 * XXX Should use Camomile.
215 and ucs2_to_utf8 name len =
216 (* Calculate length of final string. *)
217 let outlen = ref 0 in
219 for i = 0 to len-1 do
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
228 let outstr = String.create !outlen in
230 for i = 0 to len-1 do
233 let c0 = Char.code name.[j'] and c1 = Char.code name.[j'+1] in
234 let c = c0 + c1 * 256 in
236 outstr.[!outlen] <- Char.chr c;
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
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
251 and offset_is_free _ _ = false
256 fs_cb_uq = (incr i; !i);
258 fs_cb_printable_name = "Windows NTFS";
259 fs_cb_offset_is_free = offset_is_free;
262 (* Register the plugin. *)
263 let () = register_plugin ~filesystem:probe id