(* 'df' command for virtual domains. (C) Copyright 2007 Richard W.M. Jones, Red Hat Inc. http://libvirt.org/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Support for NTFS. *) open Unix open Printf open Diskimage_impl open Int63.Operators (* Private data functions. *) let attach_private_data, get_private_data = private_data_functions (fun {fs_cb = {fs_cb_uq = u}} -> u) let id = "ntfs" let rec probe dev = 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. *) bitmatch bits with | { _ : 24; (* Jump to boot up code. *) "NTFS " : 64 : string; (* NTFS OEM ID (magic). *) bytes_per_sector : 16 : littleendian; sectors_per_cluster : 8 : littleendian; _ : 16; (* Reserved sectors - unused. *) _ : 8; (* FATs - unused. *) _ : 16; (* Root entries - unused. *) _ : 16; (* Sectors - unused. *) _ : 8; (* Media type, probably 'f8' = HDD *) _ : 16; (* Sectors per FAT - unused. *) _ : 16; (* Sectors per track. *) _ : 16; (* Heads. *) _ : 32; (* Hidden sectors. *) _ : 32; (* Large sectors. *) _ : 8; (* Physical drive, 0 = FDD, 0x80 = HDD*) _ : 8; (* Current head. *) _ : 8; (* Extended boot signature. *) _ : 8; (* Reserved. *) number_of_sectors : 64 : littleendian; mft_lcn : 64 : littleendian; (* MFT location in clusters. *) mftmirr_lcn : 64 : littleendian; (* MFT mirror location. *) clusters_per_mft_record : 8; _ : 24; clusters_per_index_record : 8; _ : 24; volume_serial_number : 64 : littleendian; checksum : 32 : littleendian; (* Boot sector checksum. *) code : 8 * 426 : bitstring; (* Boot code. *) 0x55AA : 16 } -> (* End of bootsector magic. *) let blocksize = bytes_per_sector * sectors_per_cluster in if !debug then eprintf "%s: NTFS boot sector with blocksize = %d, serial = %Lx\n%!" 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 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 records = parse_mft_records bits in records and parse_mft_records bits = 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%!"; let attrs = parse_attrs attrs in 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 } -> () | { _ } -> () 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 *) 0 : 8; (* name length, assume unnamed *) _ : 16; (* name offset *) _ : 16; (* flags *) _ : 16; (* instance number *) 0L : 64 : littleendian; (* lowest VCN, assume single extent *) highest_vcn : 64 : littleendian; (* size in clusters - 1 *) 0x40 : 16 : littleendian; (* mapping pairs offset *) 0 : 8; (* assume not compressed *) pad : 40 : bitstring; (* padding *) allocated_size : 64 : littleendian; (* allocate size on disk *) data_size : 64 : littleendian; (* byte size of the attribute *) initialized_size : 64 : littleendian; (* Table of virtual clusters to logical clusters. *) mapping_pairs : (Int32.to_int attr_size - 0x40) * 8 : bitstring; rest : -1 : bitstring } -> (* XXX let attr = *) parse_nonresident_attr attr_type highest_vcn allocated_size data_size initialized_size mapping_pairs; parse_attrs rest (* 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. *) | { attr_type : 32 : littleendian; attr_size : 32 : littleendian; pad : (Int32.to_int attr_size - 8) * 8 : bitstring; rest : -1 : bitstring } -> if !debug then ( eprintf "cannot parse MFT attribute entry\n%!"; Bitmatch.hexdump_bitstring Pervasives.stderr attrs ); parse_attrs rest (* Otherwise unparsable & unskippable attribute entry. *) | { _ } -> if !debug then eprintf "corrupt MFT attribute entry\n%!" and parse_resident_attr attr_type attr = match attr_type with | 0x10_l -> (* AT_STANDARD_INFORMATION *) (bitmatch attr with | { creation_time : 64; last_data_change_time : 64; 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 | { _ } -> if !debug then eprintf "cannot parse AT_STANDARD_INFORMATION\n%!" ); | 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 and parse_nonresident_attr attr_type highest_vcn allocated_size data_size initialized_size mapping_pairs = 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 ( eprintf "AT_DATA: runlist is:\n"; List.iter ( function | ((vcn, deltavcn), Some lcn) -> eprintf "\tVCNs %s..%s -> LCN %s\n" (Int63.to_string vcn) (Int63.to_string (vcn +^ deltavcn -^ ~^1)) (Int63.to_string lcn) | ((vcn, deltavcn), None) -> eprintf "\tVCNs %s..%s -> sparse hole\n" (Int63.to_string vcn) (Int63.to_string (vcn +^ deltavcn -^ ~^1)) ) runlist ); | _ -> if !debug then eprintf "unknown non-resident attribute %lx\n%!" attr_type (* mapping_pairs is not straightforward and not documented well. See * ntfsprogs libntfs/runlist.c:ntfs_mapping_pairs_decompress *) and parse_runlist vcn lcn bits = bitmatch bits with | { 0 : 8 } -> (* end of table *) [] | { 0 : 4; vcnlen : 4; deltavcn : vcnlen * 8 : littleendian; rest : -1 : bitstring } when vcnlen >= 1 && vcnlen <= 4 -> let deltavcn = Int63.of_int64 deltavcn in (* This is a sparse file hole. *) ((vcn, deltavcn), None) :: parse_runlist (vcn +^ deltavcn) lcn rest | { (* Really these fields are signed, but we'll just limit it to * sensible values in the when clause instead. *) lcnlen : 4; vcnlen : 4; deltavcn : vcnlen * 8 : littleendian; deltalcn : lcnlen * 8 : littleendian; rest : -1 : bitstring } when (vcnlen >= 1 && vcnlen <= 4) && (lcnlen >= 1 || lcnlen <= 4) -> let deltavcn = Int63.of_int64 deltavcn in let deltalcn = Int63.of_int64 deltalcn in (* XXX signed *) let lcn = lcn +^ deltalcn in eprintf "lcnlen = %d, vcnlen = %d\n" lcnlen vcnlen; ((vcn, deltavcn), Some lcn) :: parse_runlist (vcn +^ deltavcn) lcn rest | { _ } -> if !debug then ( eprintf "unknown field in the runlist\n%!"; Bitmatch.hexdump_bitstring Pervasives.stderr bits ); [] (* Poor man's little-endian UCS-2 to UTF-8 conversion. * XXX Should use Camomile. *) and ucs2_to_utf8 name len = (* Calculate length of final string. *) let outlen = ref 0 in let j = ref 0 in for i = 0 to len-1 do let j' = !j in j := j' + 2; let c0 = Char.code name.[j'] and c1 = Char.code name.[j'+1] in let c = c0 + c1 * 256 in if c < 128 then incr outlen else if c < 0x800 then outlen := !outlen + 2 else outlen := !outlen + 3 done; let outstr = String.create !outlen in j := 0; outlen := 0; for i = 0 to len-1 do let j' = !j in j := j' + 2; let c0 = Char.code name.[j'] and c1 = Char.code name.[j'+1] in let c = c0 + c1 * 256 in if c < 128 then ( outstr.[!outlen] <- Char.chr c; incr outlen ) else if c < 0x800 then ( outstr.[!outlen] <- Char.chr (0b11000000 lor (c lsr 6)); outstr.[!outlen+1] <- Char.chr (0b10000000 lor (c land 0b00111111)); outlen := !outlen + 2 ) else ( outstr.[!outlen] <- Char.chr (0b11100000 lor (c lsr 12)); outstr.[!outlen+1] <- Char.chr (0b10000000 lor ((c lsr 6) lor 0b00111111)); outstr.[!outlen+2] <- Char.chr (0b10000000 lor (c land 0b00111111)); outlen := !outlen + 3 ) done; outstr and offset_is_free _ _ = false and callbacks = let i = ref 0 in fun () -> { fs_cb_uq = (incr i; !i); fs_cb_name = id; fs_cb_printable_name = "Windows NTFS"; fs_cb_offset_is_free = offset_is_free; } (* Register the plugin. *) let () = register_plugin ~filesystem:probe id