// idfat.cc:  Show useful information about a FAT file system.

// DWF 2008-07-14
// g++ -Wall -Wextra -O2 -s -o idfat idfat.cc

#define __STDC_FORMAT_MACROS
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
#include <string.h>


struct __attribute__ ((__packed__)) BPB_continuation_old {
  uint8_t  BS_DrvNum;
  uint8_t  BS_Reserved1;
  uint8_t  BS_BootSig;
  uint32_t BS_VolID;
  char     BS_VolLab[11];
  char     BS_FilSysType[8];
};

struct __attribute__ ((__packed__)) BPB_continuation_FAT32 {
  uint32_t BPB_FATSz32;
  uint8_t  miscellaneous[31];
  char     BS_VolLab[11];
  char     BS_FilSysType[8];
};

struct __attribute__ ((__packed__)) BPB {
  uint8_t  BS_jmpBoot[3];
  char     BS_OEMName[8];
  uint16_t BPB_BytsPerSec;
  uint8_t  BPB_SecPerClus;
  uint16_t BPB_RsvdSecCnt;
  uint8_t  BPB_NumFATs;
  uint16_t BPB_RootEntCnt;
  uint16_t BPB_TotSec16;
  uint8_t  BPB_Media;
  uint16_t BPB_FATSz16;
  uint16_t BPB_SecPerTrk;
  uint16_t BPB_NumHeads;
  uint32_t BPB_HiddSec;
  uint32_t BPB_TotSec32;
  union {
    BPB_continuation_old   old;
    BPB_continuation_FAT32 FAT32;
  };
};

enum FATType {FAT12, FAT16, FAT32};


void bomb (char const * const fname) {
  fprintf (stderr, "%s does not contain a valid FAT file system.\n", fname);
  exit (-1);
}


void checkMagicNumber (FILE *fp, char const * const fname) {
  if (fseek (fp, 510L, SEEK_SET) == -1)
    bomb (fname);
  uint16_t magicnum;
  if (fread (&magicnum, sizeof(magicnum), 1, fp) != 1)
    bomb (fname);
  if (magicnum != 0xAA55)
    bomb (fname);
  rewind (fp);
}


bool containsFunnyChars (char const *s) {
  assert (s);
  for (; *s != '\0'; ++s)
    if ((*s < 0x20) || (*s & 0x80))
      return true;
  return false;
}


void printSize (uintmax_t bytes) {
  static char const prefixes[] = {'\0', 'K', 'M', 'G', 'T', 'P', 'E', '\0'};
  static uintmax_t const factor (1024);
  printf ("%25" PRIuMAX " B", bytes);
  if (bytes >= factor) {
    uint_fast8_t scale (0);
    uintmax_t divisor  (1);
    while ((prefixes[scale+1] != '\0') && (bytes / divisor >= factor)) {
      divisor *= factor;
      ++scale;
    }
    printf (" (%.2f %ciB)", (double)bytes/divisor, prefixes[scale]);
  }
  printf ("\n");
}


int main (int argc, char **argv) {

  if (argc != 2) {
    fprintf (stderr, "Usage: idfat /dev/xxxn\n");
    exit (-1);
  }
  char const * const fname (argv[1]);

  // READ ONLY
  FILE *fp = fopen (fname, "r");
  if (!fp) {
    perror (fname);
    exit (-1);
  }

  // Do all sanity checks BEFORE printing out any statistics.

  checkMagicNumber (fp, fname);

  BPB bpb;
  if (fread (&bpb, sizeof(bpb), 1, fp) != 1)
    bomb (fname);
  fclose (fp);

  // Avoid divide-by-zeros.
  if (bpb.BPB_BytsPerSec < 512 || bpb.BPB_SecPerClus == 0)
    bomb (fname);

  uint32_t FATSz;
  if (bpb.BPB_FATSz16)
    FATSz = bpb.BPB_FATSz16;        // Indicates FAT12 or FAT16
  else {
    FATSz = bpb.FAT32.BPB_FATSz32;  // Indicates FAT32
    if (bpb.BPB_RootEntCnt)         // Indicates FAT12 or FAT16
      bomb (fname);                 // Inconsistent
  }

  uint32_t TotSec =
    (bpb.BPB_TotSec16 ? bpb.BPB_TotSec16 : bpb.BPB_TotSec32);
  // BPB_BytsPerSec - 1 implements the specified "rounding up."
  uint32_t RootDirSectors =
    ((bpb.BPB_RootEntCnt * 32) + (bpb.BPB_BytsPerSec - 1))
    / bpb.BPB_BytsPerSec;
  uint32_t DataSec =
    TotSec - (bpb.BPB_RsvdSecCnt + (bpb.BPB_NumFATs * FATSz)
              + RootDirSectors);
  uint32_t CountofClusters = DataSec / bpb.BPB_SecPerClus;

  FATType ft;
  if (CountofClusters < 4085)
    ft = FAT12;
  else if (CountofClusters < 65525)
    ft = FAT16;
  else
    ft = FAT32;

  switch (ft) {
  case FAT12:
  case FAT16:
    if (bpb.BPB_FATSz16 == 0)
      bomb (fname); // Inconsistent
    break;
  case FAT32:
    if (bpb.BPB_FATSz16 != 0 || bpb.BPB_RootEntCnt != 0)
      bomb (fname); // Inconsistent
    break;
  default:
    assert (false);
  }

  char FilSysType[9];
  strncpy (FilSysType, (ft == FAT32 ? bpb.FAT32.BS_FilSysType : bpb.old.BS_FilSysType), 8);
  FilSysType[8] = '\0';
  for (int i(7); i>=0; --i)
    if (FilSysType[i] == ' ')
      FilSysType[i] = '\0';
    else
      break;
  if (containsFunnyChars (FilSysType))
    bomb (fname);

  // Beyond this point we are convinced that it is a valid FAT file system.

  printf ("File system type (normative):  %s\n",
	  (ft == FAT12 ?      "FAT12" :
          (ft == FAT32 ?      "FAT32" :
	  (bpb.BPB_TotSec16 ? "FAT16 (DOS 3.0-compatible)" :
	                      "FAT16 (DOS 3.31+)"))));
  printf ("Type string (informative):     %s\n", FilSysType);

  printf ("\nApplicable fdisk hex code(s):\n");
  switch (ft) {
  case FAT12:
    printf (" 1  FAT12\n");
    break;
  case FAT16:
    if (bpb.BPB_TotSec16)
      printf (" 4  FAT16 <32M\n");
    else
      printf (" 6  FAT16\n");
    printf (" e  W95 FAT16 (LBA)\n");
    break;
  case FAT32:
    printf (" b  W95 FAT32\n c  W95 FAT32 (LBA)\n");
    break;
  default:
    assert (false);
  }

  printf ("\nFile system size:      ");
  printSize ((uintmax_t)TotSec * bpb.BPB_BytsPerSec);
  printf ("Usable space:          ");
  printSize ((uintmax_t)CountofClusters * bpb.BPB_SecPerClus * bpb.BPB_BytsPerSec);
  printf ("Allocation unit size:  ");
  printSize ((uintmax_t)bpb.BPB_SecPerClus * bpb.BPB_BytsPerSec);

  return 0;
}
