open Diskimage_impl
open Int63.Operators
+let id = "ntfs"
+
(* Private data functions. *)
let attach_private_data, get_private_data =
private_data_functions (fun {fs_cb = {fs_cb_uq = u}} -> u)
-let id = "ntfs"
+(* Type of the private data, basically all the metadata that we
+ * read from the NTFS volume.
+ *)
+type ntfs_fs = {
+ ntfs_blocksize : int63; (* Blocksize (cluster size) *)
+ ntfs_mft_lcn : int63; (* MFT location (bytes) *)
+ ntfs_mft_size : int63; (* MFT size (bytes) *)
+ ntfs_mft_records : ntfs_mft_record list; (* Files in MFT *)
+}
+and ntfs_mft_record = {
+ ntfs_filename : ntfs_filename option; (* Filename, if present. *)
+ ntfs_info : ntfs_info option; (* Standard information, if present. *)
+ ntfs_data : ntfs_data option; (* $Data stream, if present. *)
+}
+and ntfs_filename = {
+ ntfs_name : string; (* Filename (UTF-8 encoded). *)
+}
+and ntfs_info = {
+ ntfs_creation_time : int64;
+ ntfs_last_data_change_time : int64;
+ ntfs_last_mft_change_time : int64;
+ ntfs_last_access_time : int64;
+}
+and ntfs_data = {
+ ntfs_data_size : int63; (* Actual size of data. *)
+ ntfs_runlist : ntfs_runentry list; (* Runlist. *)
+}
+and ntfs_runentry =
+ (* VCN start,size => LCN / None if sparse hole *)
+ (int63 * int63) * int63 option
let rec probe dev =
let fs = probe_superblock dev in
let mft = parse_mft dev mft_lcn mft_size in
+ let priv = {
+ ntfs_blocksize = blocksize;
+ ntfs_mft_lcn = mft_lcn;
+ ntfs_mft_size = mft_size;
+ ntfs_mft_records = mft
+ } in
+
raise Not_found (* XXX *)
| { _ } -> raise Not_found (* Not an NTFS boot sector. *)
if !debug then
eprintf "got an MFT record, now parsing attributes ...\n%!";
- let attrs = parse_attrs attrs in
+ let mft_record = {
+ ntfs_filename = None;
+ ntfs_info = None;
+ ntfs_data = None
+ } in
+ let mft_record = parse_attrs attrs mft_record in
- parse_mft_records rest (* loop rest of MFT records *)
+ mft_record :: parse_mft_records 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?
*)
- | { 0x00000000_l : 32 } ->
- ()
+ | { 0x00000000_l : 32 } -> []
- | { _ } -> ()
+ | { _ } -> []
-and parse_attrs attrs =
+and parse_attrs attrs mft_record =
(* 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%!";
- ()
+ mft_record
| { attr_type : 32 : littleendian;
attr_size : 32 : littleendian;
attr : (Int32.to_int attr_size - 24) * 8 : bitstring;
rest : -1 : bitstring } ->
- (* XXX let attr = *) parse_resident_attr attr_type attr;
- parse_attrs rest
+ let mft_record = parse_resident_attr attr_type attr mft_record in
+ parse_attrs rest mft_record
| { attr_type : 32 : littleendian;
attr_size : 32 : littleendian;
rest : -1 : bitstring } ->
- (* XXX let attr = *)
- parse_nonresident_attr attr_type highest_vcn
- allocated_size data_size initialized_size
- mapping_pairs;
+ let data_size = Int63.of_int64 data_size in
+
+ let mft_record =
+ parse_nonresident_attr attr_type highest_vcn
+ allocated_size data_size initialized_size
+ mapping_pairs mft_record in
- parse_attrs rest
+ parse_attrs rest mft_record
(* Not matched above, so we don't know how to parse this attribute, but
* there is still enough information to skip to the next one.
Bitmatch.hexdump_bitstring Pervasives.stderr attrs
);
- parse_attrs rest
+ parse_attrs rest mft_record
(* Otherwise unparsable & unskippable attribute entry. *)
| { _ } ->
if !debug then
- eprintf "corrupt MFT attribute entry\n%!"
+ eprintf "corrupt MFT attribute entry\n%!";
+ mft_record
-and parse_resident_attr attr_type attr =
+and parse_resident_attr attr_type attr mft_record =
match attr_type with
| 0x10_l -> (* AT_STANDARD_INFORMATION *)
(bitmatch attr with
last_mft_change_time : 64;
last_access_time : 64
(* other stuff follows, just ignore it *) } ->
- if !debug then
- eprintf "creation time: %Lx, last_access_time: %Lx\n"
- creation_time last_access_time
+
+ let info = {
+ ntfs_creation_time = creation_time;
+ ntfs_last_data_change_time = last_data_change_time;
+ ntfs_last_mft_change_time = last_mft_change_time;
+ ntfs_last_access_time = last_access_time
+ } in
+ { mft_record with ntfs_info = Some info }
| { _ } ->
if !debug then
- eprintf "cannot parse AT_STANDARD_INFORMATION\n%!"
+ eprintf "cannot parse AT_STANDARD_INFORMATION\n%!";
+ mft_record
);
| 0x30_l -> (* AT_FILE_NAME *)
_ : 64; (* last change time *)
_ : 64; (* last MFT change time *)
_ : 64; (* last access time *)
- allocated_size : 64 : littleendian;
- data_size : 64 : littleendian;
+ _ : 64; (* allocated size *)
+ _ : 64; (* data size *)
_ : 32;
_ : 32;
name_len : 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
+ let filename = {
+ ntfs_name = name
+ } in
+ { mft_record with ntfs_filename = Some filename }
| { _ } ->
if !debug then
- eprintf "cannot parse AT_FILE_NAME\n%!"
+ eprintf "cannot parse AT_FILE_NAME\n%!";
+ mft_record
);
| _ -> (* unknown attribute - just ignore *)
if !debug then
- eprintf "unknown resident attribute %lx\n%!" attr_type
+ eprintf "unknown resident attribute %lx\n%!" attr_type;
+ mft_record
and parse_nonresident_attr attr_type highest_vcn
allocated_size data_size initialized_size
- mapping_pairs =
+ mapping_pairs mft_record =
match attr_type with
| 0x80_l -> (* AT_DATA, ie. the $Data stream *)
- if !debug then (
- eprintf "AT_DATA: size = %Ld bytes, highest_vcn = 0x%Lx\n"
- data_size highest_vcn;
- Bitmatch.hexdump_bitstring Pervasives.stderr mapping_pairs
- );
-
let lowest_vcn = ~^0 (* see assumption above *) in
let runlist = parse_runlist lowest_vcn ~^0 mapping_pairs in
if !debug then (
) runlist
);
+ let data = {
+ ntfs_data_size = data_size;
+ ntfs_runlist = runlist
+ } in
+ { mft_record with ntfs_data = Some data }
+
| _ ->
if !debug then
- eprintf "unknown non-resident attribute %lx\n%!" attr_type
+ eprintf "unknown non-resident attribute %lx\n%!" attr_type;
+ mft_record
(* mapping_pairs is not straightforward and not documented well. See
* ntfsprogs libntfs/runlist.c:ntfs_mapping_pairs_decompress
let lcn = lcn +^ deltalcn in
- eprintf "lcnlen = %d, vcnlen = %d\n" lcnlen vcnlen;
-
((vcn, deltavcn), Some lcn) ::
parse_runlist (vcn +^ deltavcn) lcn rest