Add .gitignore file for git.
[virt-df.git] / lib / diskimage_fat.ml
index 1fbeac4..123d1da 100644 (file)
@@ -2,19 +2,20 @@
    (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 library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2 of the License, or (at your option) any later version,
+   with the OCaml linking exception described in ../COPYING.LIB.
 
-   This program is distributed in the hope that it will be useful,
+   This library 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.
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser 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.
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 
    Support for FAT32.
 *)
@@ -23,15 +24,188 @@ open Unix
 open Printf
 
 open Diskimage_impl
+open Int63.Operators
+
+let id = "fat"
+
+(* Private data that we'll save about the FAT filesystem. *)
+type fat = {
+  fat_dev : device;                    (* Device. *)
+  fat_blocksize : int63;               (* Cluster size. *)
+  fat_sectorsize : int63;              (* Sector size. *)
+  fat_sectors_per_cluster : int63;     (* Sectors per cluster. *)
+  fat_number_of_sectors : int63;       (* Total number of sectors in dev. *)
+  fat_reserved_sectors : int63;                (* Offset of the first FAT. *)
+  fat_sectors_per_fat : int63;         (* Size of each FAT. *)
+  fat_root_directory_cluster : int63;  (* Cluster offset of root dir. *)
+}
 
 (* Private data functions. *)
 let attach_private_data, get_private_data =
   private_data_functions (fun {fs_cb = {fs_cb_uq = u}} -> u)
 
-let id = "fat"
+(* FAT entries. *)
+type fat_entry =
+  | Free                               (* 0x0 *)
+  | Reserved1                          (* 0x1 *)
+  | Used of int63                      (* Used, points to next cluster *)
+  | Reserved of int63                  (* 0x_FFFFFF0 - 0x_FFFFFF6 *)
+  | Bad                                        (* Bad / reserved 0x_FFFFFF7 *)
+  | Last of int63                      (* Last cluster in file *)
 
 let rec probe dev =
-  raise Not_found                      (* XXX *)
+  let fs = probe_superblock dev in
+  fs
+
+and probe_superblock dev =
+    (* Load the boot sector / superblock. *)
+  let bits = dev#read_bitstring ~^0 ~^512 in
+
+  (* Standard stuff in a boot sector. *)
+  bitmatch bits with
+  | { _ : 24;                          (* Jump to boot up code. *)
+      oem_name : 64 : string;          (* OEM name. *)
+      bytes_per_sector : 16 : littleendian;
+      sectors_per_cluster : 8 : littleendian;
+      reserved_sectors : 16 : littleendian;
+      2 : 8;                           (* Number of FATs. *)
+      0 : 16;                          (* Root dir entries, 0 for FAT32 *)
+      0 : 16;                          (* Old number of sectors - unused. *)
+      _ : 8;                           (* Media type, probably 'f8' = HDD *)
+      _ : 16;                          (* Sectors per FAT, not for FAT32 *)
+      _ : 16;                          (* Sectors per track. *)
+      _ : 16;                          (* Heads. *)
+      _ : 32;                          (* Hidden sectors. *)
+      number_of_sectors : 32 : littleendian;
+      sectors_per_fat : 32 : littleendian;
+      _ : 16;                          (* FAT32 flags *)
+      _ : 16;                          (* FAT32 version *)
+      root_directory_cluster : 32 : littleendian;
+      _ : 16;                          (* FS information sector *)
+      _ : 16;                          (* Backup of boot sector *)
+      _ : 12*8 : bitstring;            (* Reserved *)
+      _ : 8;                           (* Physical drive number *)
+      _ : 8;                           (* Reserved *)
+      _ : 8;                           (* Extended boot signature *)
+      serial : 32 : littleendian;      (* Serial number. *)
+      volume_label : 88 : string;      (* Volume label. *)
+      "FAT32   " : 64 : string;                (* FAT32 identifier. *)
+      _ : 420*8 : bitstring;           (* Boot code. *)
+      0x55AA : 16 } ->                 (* End of bootsector magic. *)
+
+      let blocksize = bytes_per_sector * sectors_per_cluster in
+
+      (* FAT32 forbids cluster size > 32K. *)
+      if blocksize > 32768 then (
+       prerr_endline "FAT: cluster size > 32K";
+       raise Not_found
+      );
+
+      if !debug then
+       eprintf "%s: found FAT32 filesystem with clustersize = %d, label %s\n"
+         dev#name blocksize volume_label;
+
+      let blocksize = Int63.of_int blocksize in
+      let sectorsize = Int63.of_int bytes_per_sector in
+      let sectors_per_cluster = Int63.of_int sectors_per_cluster in
+      let number_of_sectors = Int63.of_int32 number_of_sectors in
+      let reserved_sectors = Int63.of_int reserved_sectors in
+      let sectors_per_fat = Int63.of_int32 sectors_per_fat in
+      let root_directory_cluster = Int63.of_int32 root_directory_cluster 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
+
+      let fat = {
+       fat_dev = fs_dev;
+       fat_blocksize = blocksize;
+       fat_sectorsize = sectorsize;
+       fat_sectors_per_cluster = sectors_per_cluster;
+       fat_number_of_sectors = number_of_sectors;
+       fat_reserved_sectors = reserved_sectors;
+       fat_sectors_per_fat = sectors_per_fat;
+       fat_root_directory_cluster = root_directory_cluster
+      } in
+
+      (* Parse the first FAT to get free space. *)
+      let blocks_used, blocks_avail = parse_fat_freespace fat in
+
+      (* Calculate reserved space. *)
+      let blocks_reserved =
+       let sectors_reserved = reserved_sectors +^ ~^2 *^ sectors_per_fat in
+       sectors_reserved *^ sectors_per_cluster in
+
+      (* Create a filesystem structure. *)
+      let fs = {
+       fs_cb = callbacks ();
+       fs_dev = fs_dev;
+       fs_blocksize = blocksize;
+       fs_blocks_total = number_of_sectors *^ sectorsize /^ blocksize;
+       fs_is_swap = false;
+       fs_blocks_reserved = blocks_reserved;
+       fs_blocks_avail = blocks_avail;
+       fs_blocks_used = blocks_used;
+       fs_inodes_total = ~^0;
+       fs_inodes_reserved = ~^0;
+       fs_inodes_avail = ~^0;
+       fs_inodes_used = ~^0;
+      } in
+
+      attach_private_data fs fat;
+      fs
+
+  | { _ } -> raise Not_found           (* Not FAT32 *)
+
+(* Find amount of free space by reading the FAT. *)
+and parse_fat_freespace fat =
+  let used = ref ~^0 and free = ref ~^0 in
+  iter_fat_entries fat (
+    fun cn ->
+      function
+      | Free -> free := !free +^ ~^1
+      | _ -> used := !used +^ ~^1
+  );
+  (!used, !free)
+
+(* Iterate over the FAT.  We ignore the first two FAT entries. *)
+and iter_fat_entries { fat_dev = dev; fat_sectorsize = sectorsize;
+                      fat_reserved_sectors = start;
+                      fat_sectors_per_fat = size } f =
+  let cn = ref ~^0 in
+
+  for i = 0 to (Int63.to_int size)-1 do
+    let sector =
+      dev#read_bitstring ((start +^ Int63.of_int i) *^ sectorsize) sectorsize in
+    let rec loop bits =
+      bitmatch bits with
+      | { e : 28 : littleendian; _ : 4; rest : -1 : bitstring } ->
+         let e =
+           match e with
+           | 0 -> Free
+           | 1 -> Reserved1
+           | (0xffffff0|0xffffff1|0xffffff2|0xffffff3|
+                  0xffffff4|0xffffff5|0xffffff6) as r ->
+               Reserved (Int63.of_int r)
+           | 0xffffff7 -> Bad
+           | (0xffffff8|0xffffff9|0xffffffa|0xffffffb|
+                  0xffffffc|0xffffffd|0xffffffe|
+                      0xfffffff) as r -> Last (Int63.of_int r)
+           | _ -> Used (Int63.of_int e) in
+
+         if !cn >= ~^2 then f !cn e;
+         cn := !cn +^ ~^1;
+         loop rest
+
+       | { _ } ->
+           if !debug then
+             eprintf "iter_fat_entries: unknown FAT entry, ignored\n%!";
+           ()
+    in
+    loop sector
+  done
 
 and offset_is_free _ _ = false