(* '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 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%!"; let attrs = parse_attrs attrs in 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? *) | { 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. *) 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