/*
 * compile with gcc -o newfat -Wall --pedantic newfat.c 
 *
 * this code is not pretty.   
 * some of the problems arise from it being a quick and dirty one afternoon 
 * coding session, some are actually deliberate decisions made because it is 
 * intended as code for students to look at 
 */ 
#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h> 
#include <sys/types.h>
#include <sys/stat.h> 
#include <unistd.h> 
#include <sys/mman.h>
#include <string.h>

typedef unsigned char BYTE      ;
typedef unsigned char SHORT [2] ; 
typedef unsigned char INT   [4] ; 

/* 
 * utility macros
 * note:  + in the conversions was changed to bitwise or because 
 * signs were propagating 
 * also since the macros were being used in places with different
 * values for "x", extra parens were added to resolve syntax errors
 */ 
#define SHORT2int(x) ((x[0]) | ((x[1])<<8))
#define INT2int(x)   ((x[0]) | ((x[1])<<8) | ((x[2])<<16) | ((x[3])<<24))
#define BYTE2int(x)  (x) 
#define MASK(x)      (x & 0xFF) 
#define SM(x,s)      MASK(x >> s) 
/*
 * these have not been tested 
 */ 
#define int2BYTE(x,y) (y[0] = SM(x,0))
#define int2SHORT(x,y) (y[0] = SM(x,0), y[1] = SM(x,8)) 
#define int2INT(x,y) (y[0] = SM(x,0), y[1] = SM(x,8), y[2]=SM(x,16), y[3]=SM(x,24)) 

#define bit(i,x) ((i)>>(x) & 0x1)   

/*
 * time and date utility macros 
 */ 
#define hours(i)   (((i)>>11)&0x1F) 
#define minutes(i) (((i)>>5)&0x2F) 
#define seconds(i) (2*(((i)>>5)&0x1F)) 

#define year(i)    (1980+(((i)>>9)&0x7F)) 
#define month(i)   (((i)>>5)&0xF) 
#define day(i)     (((i)>>5)&0x1F) 

/*
 * boot sector definition 
 */ 
struct fat_bs 
{
      BYTE  jmp[3] ; 
      BYTE  oem[8] ;
      SHORT bytes_per_sector ; 
      BYTE  sectors_per_cluster ; 
      SHORT reserved_sectors ; 
      BYTE  fat_count ; 
      SHORT max_root_dirs ;
      SHORT total_sectors_1 ;
      BYTE  media_descriptor ;
      SHORT sectors_per_fat_12_16 ;
      SHORT sectors_per_track ;
      SHORT head_count ;
      INT   hidden_sectors ;
      INT   total_sectors_2 ; 
      INT   sectors_per_fat ; 
      SHORT fat_flags ;
      SHORT version ;
      INT   root_cluster_start ;
      SHORT fs_info_sector ; 
      SHORT boot_sector_copy ; 
      BYTE  reserved[12] ; 
      BYTE  drive_number ;
      BYTE  reserved1 ;
      BYTE  sig ; 
      INT   id ; 
      BYTE  volume_label[11] ; 
      BYTE  fat_type[8] ;
      BYTE  boot_code[420] ; 
      BYTE  boot_sig[2] ; 
} ; 

/*
 * directory entry structure 
 */ 
struct dir_entry 
{
      BYTE  filename[8] ; 
      BYTE  file_ext[3] ;
      BYTE  attrs ; 
      BYTE  reserved ; 
      BYTE  creat_time_fine ; 
      SHORT creat_time_coarse ; 
      SHORT creat_date ; 
      SHORT last_access_date ; 
      SHORT high_2_cluster_number ; 
      SHORT last_modified_time ; 
      SHORT last_modified_date ; 
      SHORT low_2_cluster_number ; 
      INT   filesize ; 
} ; 

/*
 * digest structure - extract interesting info and keep here 
 * only the really essential stuff is kept here - everything
 * else is either used only rarely, or set at initialization time
 * and then pretty much ignored. 
 * 
 * everything could be done without this, but having it makes things
 * a bit easier
 * 
 * my next step (if I continued this code) would be to add an endianness-fixed 
 * single copy of the fat itself as an array and a write-back function
 * to copy it to both locations in the data structure 
 */ 
struct fat_digest
{
      unsigned char *fat_whole ; 
      struct fat_bs *fat_bs ;
      unsigned char *fat_fat1 ;
      unsigned char *fat_fat2 ;
      unsigned char *fat_data ; 
      int  bytes_per_sector ; 
      int  bytes_per_cluster ; 
} ; 
/* 
 * convenience accessor/setter macros.  
 * none of the setter macros are used (or even tested) but were
 * generated automatically 
 */

#define get_bs_jmp(bs,buf) (getbytes(bs->jmp, buf,3))
#define set_bs_jmp(bs,val) (setbytes(val, bs->jmp,3))

#define get_bs_oem(bs,buf)     (getbytes(bs->oem, buf,8))
#define set_bs_oem(bs,val) (setbytes(val, bs->oem,8))

#define get_bs_bytes_per_sector(bs)     (SHORT2int(bs->bytes_per_sector))
#define set_bs_bytes_per_sector(bs,val) (int2SHORT(val, &(bs->bytes_per_sector)))

#define get_bs_sectors_per_cluster(bs) (BYTE2int(bs->sectors_per_cluster))
#define set_bs_sectors_per_cluster(bs,val) (int2BYTE(val, &(bs->sectors_per_cluster)))

#define get_bs_reserved_sectors(bs) (SHORT2int(bs->reserved_sectors))
#define set_bs_reserved_sectors(bs,val) (int2SHORT(val, &(bs->reserved_sectors)))

#define get_bs_fat_count(bs)     (BYTE2int(bs->fat_count))
#define set_bs_fat_count(bs,val) (int2BYTE(val, &(bs->fat_count)))

#define get_bs_max_root_dirs(bs) (SHORT2int(bs->max_root_dirs))
#define set_bs_max_root_dirs(bs,val) (int2SHORT(val, &(bs->max_root_dirs)))

#define get_bs_total_sectors_1(bs) (SHORT2int(bs->total_sectors_1))
#define set_bs_total_sectors_1(bs,val) (int2SHORT(val, &(bs->total_sectors_1)))

#define get_bs_media_descriptor(bs) (BYTE2int(bs->media_descriptor))
#define set_bs_media_descriptor(bs,val) (int2BYTE(val, &(bs->media_descriptor)))

#define get_bs_sectors_per_fat_12_16(bs) (SHORT2int(bs->sectors_per_fat_12_16))
#define set_bs_sectors_per_fat_12_16(bs,val) (int2SHORT(val, &(bs->sectors_per_fat_12_16)))

#define get_bs_sectors_per_track(bs) (SHORT2int(bs->sectors_per_track))
#define set_bs_sectors_per_track(bs,val) (int2SHORT(val, &(bs->sectors_per_track)))

#define get_bs_head_count(bs) (SHORT2int(bs->head_count))
#define set_bs_head_count(bs,val) (int2SHORT(val, &(bs->head_count)))

#define get_bs_hidden_sectors(bs) (INT2int(bs->hidden_sectors))
#define set_bs_hidden_sectors(bs,val) (int2INT(val, &(bs->hidden_sectors)))

#define get_bs_total_sectors_2(bs) (INT2int(bs->total_sectors_2))
#define set_bs_total_sectors_2(bs,val) (int2INT(val, &(bs->total_sectors_2)))

#define get_bs_sectors_per_fat(bs)     (INT2int(bs->sectors_per_fat))
#define set_bs_sectors_per_fat(bs,val) (int2INT(val, &(bs->sectors_per_fat)))

#define get_bs_fat_flags(bs) (SHORT2int(bs->fat_flags))
#define set_bs_fat_flags(bs,val) (int2SHORT(val, &(bs->fat_flags)))

#define get_bs_version(bs) (SHORT2int(bs->version))
#define set_bs_version(bs,val) (int2SHORT(val, &(bs->version)))

#define get_bs_root_cluster_start(bs) (INT2int(bs->root_cluster_start))
#define set_bs_root_cluster_start(bs,val) (int2INT(val, &(bs->root_cluster_start)))

#define get_bs_fs_info_sector(bs) (SHORT2int(bs->fs_info_sector))
#define set_bs_fs_info_sector(bs,val) (int2SHORT(val, &(bs->fs_info_sector)))

#define get_bs_boot_sector_copy(bs) (SHORT2int(bs->boot_sector_copy))
#define set_bs_boot_sector_copy(bs,val) (int2SHORT(val, &(bs->boot_sector_copy)))

#define get_bs_reserved(bs,buf)     (getbytes(bs->reserved,buf, 12))
#define set_bs_reserved(bs,val) (setbytes(val, bs->reserved,12))

#define get_bs_drive_number(bs) (BYTE2int(bs->drive_number))
#define set_bs_drive_number(bs,val) (int2BYTE(val, &(bs->drive_number)))

#define get_bs_reserved1(bs) (BYTE2int(bs->reserved1))
#define set_bs_reserved1(bs,val) (int2BYTE(val, &(bs->reserved1)))

#define get_bs_id(bs) (INT2int(bs->id))
#define set_bs_id(bs,val) (int2INT(val, &(bs->id)))

#define get_bs_sig(bs) (BYTE2int(bs->id))
#define set_bs_sig(bs,val) (int2BYTE(val, &(bs->id)))

#define get_bs_volume_label(bs,buf)     (getbytes(bs->volume_label,buf, 11))
#define set_bs_volume_label(bs,val) (setbytes(val, bs->volume_label,11))

#define get_bs_fat_type(bs,buf)     (getbytes(bs->fat_type, buf,8))
#define set_bs_fat_type(bs,val) (setbytes(val, bs->fat_type8))

#define get_bs_boot_code(bs,buf) (getbytes(bs->boot_code, buf, 420))
#define set_bs_boot_code(bs,val) (setbytes(val, bs->boot_code,420))

#define get_bs_boot_sig(bs,buf)     (getbytes(bs->boot_sig,buf,2))
#define set_bs_boot_sig(bs,val) (setbytes(val, bs->boot_sig,2))

/*
 * these two bytes functions could almost (modulo the trailing 0 byte
 * in getbytes) be just memcpy's 
 * this way is just a tad more consistent 
 * 
 * these are here precisely to handle the trailing 0 byte 
 */ 

void getbytes(unsigned char *, unsigned char *, int) ;
void setbytes(unsigned char *, unsigned char *, int) ;

/* 
 * more prototypes 
 */ 
void print_fat_bs(struct fat_bs *bs);
unsigned char *get_cluster_start_at_fat_number(struct fat_digest *, int ) ; 
void print_fat(struct fat_digest *f) ; 
void print_root_dir(struct fat_digest *f)  ;
void print_dir(struct fat_digest *, int) ; 
struct fat_digest *gen_fat_digest(unsigned char *f) ; 
int get_cluster_size(struct fat_bs *) ; 
int get_fat_start(struct fat_bs *bs) ;
int get_fat_size(struct fat_bs *bs) ; 
int get_data_area_offset(struct fat_bs *bs)  ;
int get_next_cluster_at_fat(struct fat_digest *f, int fatno) ; 
unsigned char *get_cluster_start_at_fat_number(struct fat_digest *f, int fatno) ; 

int 
main(int argc, char **argv)
{
   int fd = open("sample.fat", O_RDONLY) ; 
   struct stat statbuf ;
   struct fat_digest *fat ;
   unsigned char *f ; 
   fstat(fd, &statbuf) ; 
   f = mmap(0, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0) ; 
   if ((int)f == -1) 
   {
      perror("failed to mmap\n") ; 
      exit(1) ; 
   }
   fat = gen_fat_digest(f) ; 
   print_fat_bs(fat->fat_bs) ;
   print_fat(fat) ; 
   print_root_dir(fat) ; 
   exit(0) ; 
} 

/*
 * initialize the fat_digest
 */ 
struct fat_digest *gen_fat_digest(unsigned char *f)
{
   struct fat_digest *fd = malloc(sizeof (struct fat_digest)) ; 
   fd -> fat_whole = f ; 
   fd -> fat_bs = (struct fat_bs *) f ; 
   fd -> bytes_per_sector = get_bs_bytes_per_sector(fd->fat_bs) ; 
   fd -> bytes_per_cluster = get_cluster_size(fd->fat_bs) ; 
   fd -> fat_fat1 = f + get_fat_start(fd->fat_bs) ; 
   fd -> fat_fat2 = fd -> fat_fat1 + get_fat_size(fd -> fat_bs) ; 
   fd -> fat_data = fd -> fat_fat1 + 2*get_fat_size(fd -> fat_bs) ; 
   return fd ; 
} 

void print_fat_bs(struct fat_bs *bs)
{
    unsigned char buf[500] ; 
    printf("printing fat_bs at %08x\n", (int) bs) ; 
    get_bs_jmp(bs, buf);
    printf("  jmp -> %s\n", buf);
    printf("  jmp(hex bytes) -> %02x %02x %02x\n",
	   bs->jmp[0], bs->jmp[1], bs->jmp[2]) ; 
    get_bs_oem(bs, buf);
    printf("  oem -> %s\n", buf);
    printf("  bytes_per_sector-> %d\n", get_bs_bytes_per_sector(bs));
    printf("  sectors_per_cluster-> %d\n", get_bs_sectors_per_cluster(bs));
    printf("  reserved_sectors-> %d\n", get_bs_reserved_sectors(bs));
    printf("  fat_count-> %d\n", get_bs_fat_count(bs));
    printf("  max_root_dirs-> %d\n", get_bs_max_root_dirs(bs));
    printf("  total_sectors_1-> %d\n", get_bs_total_sectors_1(bs));
    printf("  media_descriptor-> %d\n", get_bs_media_descriptor(bs));
    printf("  sectors_per_fat_12_16-> %d\n", get_bs_sectors_per_fat_12_16(bs));
    printf("  sectors_per_track-> %d\n", get_bs_sectors_per_track(bs));
    printf("  head_count-> %d\n", get_bs_head_count(bs));
    printf("  hidden_sectors-> %d\n", get_bs_hidden_sectors(bs));
    printf("  total_sectors_2-> %d\n", get_bs_total_sectors_2(bs));
    printf("  sectors_per_fat-> %d\n", get_bs_sectors_per_fat(bs));
    printf("  fat_flags-> %d\n", get_bs_fat_flags(bs));
    printf("  version-> %d\n", get_bs_version(bs));
    printf("  root_cluster_start-> %d\n", get_bs_root_cluster_start(bs));
    printf("  fs_info_sector-> %d\n", get_bs_fs_info_sector(bs));
    printf("  boot_sector_copy-> %d\n", get_bs_boot_sector_copy(bs));
    get_bs_reserved(bs, buf);
    printf("  reserved -> %s\n", buf);
    printf("  drive_number-> %d\n", get_bs_drive_number(bs));
    printf("  reserved1-> %d\n", (int) get_bs_reserved1(bs));
    printf("  sig-> %d\n", (int) get_bs_sig(bs));
    printf("  id-> %d\n", get_bs_id(bs));
    get_bs_volume_label(bs, buf) ;
    printf("  volume_label -> %s\n", buf);
    get_bs_fat_type(bs,  buf);
    printf("  fat_type -> %s\n", buf);
#if 0
    get_bs_boot_code(bs, buf);
    printf("  boot_code -> %s\n", buf);
#endif 
    get_bs_boot_sig(bs, buf);
    printf("  boot_sig -> %02x%02x\n", buf[0], MASK(buf[1]));
    
}

void getbytes(unsigned char *from, unsigned char *to, int count) 
{
   memcpy(to,from, count) ;
   to[count] = '\0' ; 
}

void setbytes(unsigned char *from, unsigned char *to, int count)
{
   memcpy(to,from, count) ;
}

void print_dir_entry(struct dir_entry *d) 
{
   unsigned char buf[100] ; 
   short date ; 
   short low_cluster_start, high_cluster_start ; 
   int filesize ; 

   printf("directory entry at %08x\n", (int)d) ;
   getbytes(d->filename, buf, 8) ; 
   printf("first byte=%08x\n", buf[0]) ; 
   if (buf[0] == '\0') 
   {
      printf("empty\n") ; 
      return ; 
   }
   else 
   {
      printf("filename = [[%s.", buf) ;
      getbytes(d->file_ext, buf, 3) ; 
      printf("%s]]\n", buf) ; 
      
      printf("attrs=%d%d%d%d%d%d%d%d\n", bit(d->attrs,0),
	     bit(d->attrs,1),
	     bit(d->attrs,2),
	     bit(d->attrs,3),
	     bit(d->attrs,4),
	     bit(d->attrs,5),
	     bit(d->attrs,6),
	     bit(d->attrs,7)) ; 
      
      printf("create time (ms) = %d\n", d->creat_time_fine) ; 
      
      date = SHORT2int(d->creat_time_coarse) ; 
      printf("create time= %d:%d:%d (%04x)\n", 
	     hours(date), minutes(date), seconds(date), date); 

      date = SHORT2int(d->creat_date) ; 
      printf("create date= %d:%d:%d\n", 
	     year(date), month(date), day(date));  
      
      date = SHORT2int(d->last_access_date) ; 
      printf("access date= %d:%d:%d\n", 
	     year(date), month(date), day(date)); 
      
      high_cluster_start = SHORT2int(d->high_2_cluster_number) ; 
      
      date = SHORT2int(d->last_modified_time) ; 
      printf("mod time= %d:%d:%d\n", 
	     hours(date), minutes(date), seconds(date)); 
      
      date = SHORT2int(d->last_modified_date) ; 
      printf(" mod date= %d:%d:%d\n", 
	     year(date), month(date), day(date)); 
      
      low_cluster_start = SHORT2int(d->low_2_cluster_number) ; 
      printf("low cluster start=%d high_cluster start=%d cluster start=%d\n", 
	     low_cluster_start, high_cluster_start, 
	     low_cluster_start | (high_cluster_start << 16));
      filesize = INT2int(d->filesize) ; 
      printf("filesize = %d\n", filesize) ; 
   }
}

void print_fat (struct fat_digest *f)
{
   struct fat_bs *fbs = f -> fat_bs ; 
   int sector_size = f -> bytes_per_sector ;
   unsigned char *fat1 = f -> fat_fat1 ;
   int fat_entry_count = get_bs_sectors_per_fat(fbs)*sector_size/4 ; 
   int i ; 
   int fe ; 
   unsigned char *fp ; 
   printf("fbs at %08x fat at %08x fat offset=%08x\n", 
	  (int) fbs,  (int)fat1, (int)get_fat_start(fbs)) ; 
   for (i = 0 ; i < fat_entry_count ; i+=4)
   {
      fp = fat1 + i ; 
      fe = INT2int(fp) ; 
      if (fe != 0) 
      {
	printf("fat entry at offset (fat) %08x (start) %08x => %08x\n", 
	       i, (int)fp-(int)fbs, (int) fe) ; 
      }
   } 
}

void print_root_dir(struct fat_digest *f) 
{
   print_dir(f, get_bs_root_cluster_start(f -> fat_bs)) ; 
}

/*
 * doesn't handle directories longer than one cluster 
 */ 
void print_dir(struct fat_digest *f, int cluster)  
{ 
   struct fat_bs *fbs = f -> fat_bs ;    
   int i ; 
   struct dir_entry *rds = (struct dir_entry *) get_cluster_start_at_fat_number(f, cluster);

   printf("dir at %08x (offset from start=%08x)\n", (int)rds, (int)rds - (int) f) ; 
   printf("cluster start = %d\n", get_bs_root_cluster_start(fbs)) ; 
   printf("%d entries??\n", f -> bytes_per_cluster/sizeof(struct dir_entry)) ; 
   printf("cluster size=%d dir_entry size = %d\n", 
	  f->bytes_per_cluster, 
	  sizeof(struct dir_entry)) ; 
   for (i = 0 ; i < f->bytes_per_cluster / sizeof(struct dir_entry) ; i++)
   {
      if ((rds+i)->filename[0] == 0) break ; 
      print_dir_entry(rds+i) ; 
   } 
   /*
    * untested 
    */ 
   i = get_next_cluster_at_fat(f, cluster) ; 
   if (i > 0 && i < 0xfffffff8 ) 
   {
      print_dir(f, i);
   } 
}
 

/*
 * convenience functions.   Could well be macros (and the macros at
 * the top could mostly be functions).  Making them functions alleviates
 * macro weirdness 
 */ 

int get_cluster_size(struct fat_bs *bs)  
{ 
   return get_bs_bytes_per_sector(bs)*get_bs_sectors_per_cluster(bs) ; 
}

int get_fat_start(struct fat_bs *bs)
{
   return get_bs_bytes_per_sector(bs)*get_bs_reserved_sectors(bs); 
}  

int get_fat_size(struct fat_bs *bs)
{
   return get_bs_bytes_per_sector(bs)*get_bs_sectors_per_fat(bs); 
} 

int get_data_area_offset(struct fat_bs *bs) 
{
   return get_fat_start(bs) + get_fat_size(bs) * get_bs_fat_count(bs) ; 
}

int get_next_cluster_at_fat(struct fat_digest *f, int fatno) 
{
   unsigned char *entry_addr = f -> fat_fat1 + 4*(fatno - 2) ; 
   return INT2int(entry_addr) ; 
} 

unsigned char *get_cluster_start_at_fat_number(struct fat_digest *f, int fatno) 
{
   return (f -> fat_data + (fatno - 2)*f->bytes_per_cluster) ; 
} 
