let id = "ntfs"
let rec probe dev =
- (* Load the boot sector. *)
+ let fs = probe_superblock dev in
+ fs
+
+and probe_superblock dev =
+ (* Load the boot sector / superblock. *)
let bits = dev#read_bitstring ~^0 ~^512 in
(* Most of this data comes from ntfsprogs' layout.h header file. *)
dev#name blocksize volume_serial_number;
let blocksize = Int63.of_int blocksize in
+
+ (* The blocksize of the filesystem is likely to be quite different
+ * from that of the underlying device, so create an overlay device
+ * with the natural filesystem blocksize.
+ *)
+ let fs_dev = new blocksize_overlay blocksize dev in
+
+ (* Get the location and size of the Master File Table. *)
let mft_lcn = Int63.of_int64 mft_lcn *^ blocksize in
let mft_size = Int63.of_int clusters_per_mft_record *^ blocksize in
- (* Read the whole of the MFT. *)
- let bits = dev#read_bitstring mft_lcn mft_size in
- (* ... and turn the MFT into records. *)
- let rec loop bits =
- if Bitmatch.bitstring_length bits > 0 then (
- bitmatch bits with
- | { "FILE" : 32 : string;
- (* Assume 3 USAs starting at offset 0x30. XXX? *)
- 0x30 : 16 : littleendian;
- 0x03 : 16 : littleendian;
- _ : 64; (* lsn *)
- _ : 16; (* sequence_number *)
- _ : 16; (* link_count *)
- _ : 16; (* attrs_offset *)
- _ : 16; (* MFT_RECORD_FLAGS *)
- bytes_in_use : 32 : littleendian;
- record_size : 32 : littleendian;
- _ : 64; (* base_mft_record *)
- _ : 16; (* next_attr_instance *)
- _ : 16; (* reserved *)
- _ : 32; (* mft_record_number *)
- _ : 64; (* USN, 3 * USAs -- see above. *)
-
- (* The attributes. Subtract header size (0x30 bytes)
- * and space for the USN/USAs (8 bytes).
- *)
- attrs : (Int32.to_int record_size - 0x30 - 8)*8 : bitstring;
-
- (* Subsequent MFT records: *)
- rest : -1 : bitstring } ->
-
- if !debug then
- eprintf "got an MFT record, now parsing attributes ...\n%!";
-
- (* Parse the MFT record attributes. *)
- let rec loop2 attrs =
- bitmatch attrs with
- | { 0xFFFFFFFF_l : 32 : littleendian } -> (* AT_END *)
- if !debug then
- eprintf "found AT_END, end of attributes\n%!";
- ()
-
- | { attr_type : 32 : littleendian;
- attr_size : 32 : littleendian;
- 0 : 8; (* means attribute is resident *)
- pad : 24*8 - 8 - 64 : bitstring; (* actually meaningful *)
- attr : (Int32.to_int attr_size - 24) * 8 : bitstring;
- rest : -1 : bitstring } ->
-
- (match attr_type with
- | 0x30_l -> (* AT_FILE_NAME *)
- (bitmatch attr with
- | { _ : 64; (* parent directory ref *)
- _ : 64; (* creation time *)
- _ : 64; (* last change time *)
- _ : 64; (* last MFT change time *)
- _ : 64; (* last access time *)
- allocated_size : 64 : littleendian;
- data_size : 64 : littleendian;
- _ : 32;
- _ : 32;
- name_len : 8;
- name_type_flags : 8;
- name : name_len*16 : string } ->
-
- let name = ucs2_to_utf8 name name_len in
- if !debug then
- eprintf "filename: %s (size: %Ld bytes)\n"
- name data_size
-
- | { _ } ->
- if !debug then
- eprintf "cannot parse AT_FILE_NAME\n%!";
- );
- | _ ->
- if !debug then
- eprintf "unknown resident attribute %lx\n%!"
- attr_type
- );
-
- loop2 rest
-
- | { attr_type : 32 : littleendian;
- attr_size : 32 : littleendian;
- 1 : 8; (* non-resident attribute *)
- pad : (Int32.to_int attr_size - 9) * 8 : bitstring;
- rest : -1 : bitstring } ->
- if !debug then
- eprintf "cannot parse non-resident attr %lx\n%!"
- attr_type;
- loop2 rest
-
- | { _ } ->
- if !debug then
- eprintf "corrupt MFT attribute entry\n%!"
- in
- loop2 attrs;
-
- loop rest (* loop rest of MFT records *)
-
- (* Just assume that the end of the list of MFT records
- * is marked by all zeroes. This seems to be the
- * case, but not sure if it is generally true.
- * XXX?
+ let mft = parse_mft dev mft_lcn mft_size in
+
+ raise Not_found (* XXX *)
+
+ | { _ } -> raise Not_found (* Not an NTFS boot sector. *)
+
+and parse_mft dev mft_lcn mft_size =
+ (* Read the whole of the MFT (which is an array of MFT records) ... *)
+ let bits = dev#read_bitstring mft_lcn mft_size in
+
+ (* ... and turn the MFT into records. *)
+ let rec loop bits =
+ if Bitmatch.bitstring_length bits > 0 then (
+ bitmatch bits with
+ | { "FILE" : 32 : string;
+ (* Assume 3 USAs starting at offset 0x30. XXX? *)
+ 0x30 : 16 : littleendian;
+ 0x03 : 16 : littleendian;
+ _ : 64; (* lsn *)
+ _ : 16; (* sequence_number *)
+ _ : 16; (* link_count *)
+ _ : 16; (* attrs_offset *)
+ _ : 16; (* MFT_RECORD_FLAGS *)
+ bytes_in_use : 32 : littleendian;
+ record_size : 32 : littleendian;
+ _ : 64; (* base_mft_record *)
+ _ : 16; (* next_attr_instance *)
+ _ : 16; (* reserved *)
+ _ : 32; (* mft_record_number *)
+ _ : 64; (* USN, 3 * USAs -- see above. *)
+
+ (* The attributes. Subtract header size (0x30 bytes)
+ * and space for the USN/USAs (8 bytes).
*)
- | { 0x00000000_l : 32 } ->
- ()
- ) in
- let mft_records = loop bits in
+ attrs : (Int32.to_int record_size - 0x30 - 8)*8 : bitstring;
-
+ (* Subsequent MFT records: *)
+ rest : -1 : bitstring } ->
+ if !debug then
+ eprintf "got an MFT record, now parsing attributes ...\n%!";
+ let attrs = parse_attrs attrs in
- raise Not_found;
+ loop rest (* loop rest of MFT records *)
- | { _ } -> raise Not_found (* Not an NTFS boot sector. *)
+ (* Just assume that the end of the list of MFT records
+ * is marked by all zeroes. This seems to be the
+ * case, but not sure if it is generally true.
+ * XXX?
+ *)
+ | { 0x00000000_l : 32 } ->
+ ()
+ ) in
+ let mft_records = loop bits in
+ mft_records
+
+and parse_attrs attrs =
+ (* Parse the MFT record attributes. *)
+ bitmatch attrs with
+ | { 0xFFFFFFFF_l : 32 : littleendian } -> (* AT_END *)
+ if !debug then
+ eprintf "found AT_END, end of attributes\n%!";
+ ()
+
+ | { attr_type : 32 : littleendian;
+ attr_size : 32 : littleendian;
+ 0 : 8; (* means attribute is resident *)
+ pad : 24*8 - 8 - 64 : bitstring; (* actually meaningful *)
+ attr : (Int32.to_int attr_size - 24) * 8 : bitstring;
+ rest : -1 : bitstring } ->
+
+ (* XXX let attr = *) parse_resident_attr attr_type attr;
+ parse_attrs rest
+
+ | { attr_type : 32 : littleendian;
+ attr_size : 32 : littleendian;
+ 1 : 8; (* non-resident attribute *)
+ pad : (Int32.to_int attr_size - 9) * 8 : bitstring;
+ rest : -1 : bitstring } ->
+ if !debug then
+ eprintf "cannot parse non-resident attr %lx\n%!" attr_type;
+ parse_attrs rest
+ | { _ } ->
+ if !debug then
+ eprintf "corrupt MFT attribute entry\n%!"
+
+and parse_resident_attr attr_type attr =
+ match attr_type with
+ | 0x30_l -> (* AT_FILE_NAME *)
+ (bitmatch attr with
+ | { _ : 64; (* parent directory ref *)
+ _ : 64; (* creation time *)
+ _ : 64; (* last change time *)
+ _ : 64; (* last MFT change time *)
+ _ : 64; (* last access time *)
+ allocated_size : 64 : littleendian;
+ data_size : 64 : littleendian;
+ _ : 32;
+ _ : 32;
+ name_len : 8;
+ name_type_flags : 8;
+ name : name_len*16 : string } ->
+
+ let name = ucs2_to_utf8 name name_len in
+ if !debug then
+ eprintf "filename: %s (size: %Ld bytes)\n"
+ name data_size
+
+ | { _ } ->
+ if !debug then
+ eprintf "cannot parse AT_FILE_NAME\n%!";
+ );
+
+ | _ -> (* unknown attribute - just ignore *)
+ if !debug then
+ eprintf "unknown resident attribute %lx\n%!"
+ attr_type
(* Poor man's little-endian UCS-2 to UTF-8 conversion.
* XXX Should use Camomile.