/*
 *  GRUB Utilities --  Utilities for GRUB Legacy, GRUB2 and GRUB for DOS
 *  Copyright (C) 2007 Bean (bean123@126.com)
 *
 *  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.
 */

#ifdef LINUX

#define _FILE_OFFSET_BITS 64	// This is required to enable 64-bit off_t
#include <unistd.h>

#endif

#include <stdio.h>
#include <fcntl.h>

#include "utils.h"

static unsigned char ebuf[512];

#if defined(WIN32)

#ifdef __GNUC__				// Mingw or Cygwin

#define u_off_t		off64_t
#define u_lseek		lseek64

#else

#define u_off_t		__int64
#define u_lseek		_lseeki64

#endif

#else

#define u_off_t		off_t		// In FreeBSD, off_t is 64-bit !
#define u_lseek		lseek

#endif

int go_sect(int hd,unsigned long sec)
{
  // Test if 64-bit seek is supported
  if (sizeof(u_off_t)>=8)
    {
      u_off_t bs,rs;

      bs=sec;
      bs<<=9;
      rs=u_lseek(hd,bs,SEEK_SET);
      return (bs!=rs);
    }
  else
    {
      unsigned long bs[2];

      bs[0]=sec<<9;
      bs[1]=sec>>23;
      if (bs[1])
        return 1;
      return (lseek(hd,bs[0],SEEK_SET)!=bs[0]);
    }
}

// Partition enumerator
// xe->cur is the current partition number, before the first call to xd_enum,
// it should be set to 0xFF
// xe->nxt is the target partition number, if it equals 0xFF, it means enumerate
// all partitions, otherwise, it means jump to the specific partition.
int xd_enum(int hd,xde_t* xe)
{
  int nn=512,kk=1,cc;

  for (cc=xe->cur;;)
    {
      if (cc==0xFF)
        {
          unsigned long pt[4][2];
          int i,j,np;

          if (go_sect(hd,0))
            return 1;
          if (read(hd,ebuf,nn)!=nn)
            return 1;
          if (valueat(ebuf,0x1FE,unsigned short)!=0xAA55)
            return 1;
          np=0;
          for (i=0x1BE;i<0x1FE;i+=16)
            if (ebuf[i+4])
              {
                if ((pt[np][1]=valueat(ebuf,i+12,unsigned long))==0)
                  return 1;
                pt[np++][0]=valueat(ebuf,i+8,unsigned long);
              }
          if (np==0)
            return 1;
          // Sort partition table base on start address
          for (i=0;i<np-1;i++)
            {
              int k=i;
              for (j=i+1;j<np;j++)
                if (pt[k][0]>pt[j][0]) k=j;
              if (k!=i)
                {
                  unsigned long tt;

                  tt=pt[i][0];
                  pt[i][0]=pt[k][0];
                  pt[k][0]=tt;
                  tt=pt[i][1];
                  pt[i][1]=pt[k][1];
                  pt[k][1]=tt;
                }
            }
          // Should have space for MBR
          if (pt[0][0]==0)
            return 1;
          // Check for partition overlap
          for (i=0;i<np-1;i++)
            if (pt[i][0]+pt[i][1]>pt[i+1][0])
              return 1;
          cc=0;
        }
      else if (kk)
        cc++;
      if ((unsigned char)cc>xe->nxt)
        return 1;
      if (cc<4)
        {
          if (xe->nxt<4)
            {
              // Empty partition
              if (! ebuf[xe->nxt*16+4+0x1BE])
                return 1;
              xe->cur=xe->nxt;
              xe->dfs=ebuf[xe->nxt*16+4+0x1BE];
              xe->bse=valueat(ebuf,xe->nxt*16+8+0x1BE,unsigned long);
              xe->len=valueat(ebuf,xe->nxt*16+12+0x1BE,unsigned long);
              return 0;
            }
          else if (xe->nxt!=0xFF)
            cc=4;
          else while (cc<4)
            {
              if (ebuf[cc*16+4+0x1BE])
                {
                  xe->cur=cc;
                  xe->dfs=ebuf[cc*16+4+0x1BE];
                  xe->bse=valueat(ebuf,cc*16+8+0x1BE,unsigned long);
                  xe->len=valueat(ebuf,cc*16+12+0x1BE,unsigned long);
                  return 0;
                }
              cc++;
            }
        }
      if ((cc==4) && (kk))
        {
          int i;

          // Scan for extended partition
          for (i=0;i<4;i++)
            if ((ebuf[i*16+4+0x1BE]==5) || (ebuf[i*16+4+0x1BE]==0xF)) break;
          if (i==4)
            return 1;
          xe->ebs=xe->bse=valueat(ebuf,i*16+8+0x1BE,unsigned long);
        }
      else
        {
          // Is end of extended partition chain ?
          if ((ebuf[4+0x1CE]!=0x5) && (ebuf[4+0x1CE]!=0xF) ||
              (valueat(ebuf,8+0x1CE,unsigned long)==0))
            return 1;
          xe->bse=xe->ebs+valueat(ebuf,8+0x1CE,unsigned long);
        }
      {
        int r;

        while (1)
          {
            if (go_sect(hd,xe->bse))
              return 1;

            if (read(hd,ebuf,nn)!=nn)
              return 1;

            if (valueat(ebuf,0x1FE,unsigned short)!=0xAA55)
              return 1;

            if ((ebuf[4+0x1BE]==5) || (ebuf[4+0x1BE]==0xF))
              if (valueat(ebuf,8+0x1BE,unsigned long)==0)
                return 1;
              else
                {
                  xe->bse=xe->ebs+valueat(ebuf,8+0x1BE,unsigned long);
                  continue;
                }
            break;
          }
        kk=(ebuf[4+0x1BE]!=0);
        if ((kk) && ((xe->nxt==0xFF) || (cc==xe->nxt)))
          {
            xe->cur=cc;
            xe->dfs=ebuf[4+0x1BE];
            xe->bse+=valueat(ebuf,8+0x1BE,unsigned long);
            xe->len=valueat(ebuf,12+0x1BE,unsigned long);
            return 0;
          }
      }
    }
}

#define EXT2_SUPER_MAGIC      0xEF53

int mbr_nhd, mbr_spt;

void split_chs(unsigned char* chs,unsigned long* c,unsigned long* h,unsigned long* s)
{
  *h=chs[0];
  *s=(chs[1] & 0x3F)-1;
  *c=((unsigned long)(chs[1]>>6))*256+chs[2];
}

int chk_chs(unsigned long nhd,unsigned long spt,unsigned long lba,unsigned char* chs)
{
  unsigned long c,h,s;

  split_chs(chs,&c,&h,&s);
  if (c==0x3FF)
    return ((nhd==h+1) && (spt==s+1));
  else
    return (c*nhd*spt+h*spt+s==lba);
}

int chk_mbr(unsigned char* buf)
{
  unsigned long nhd,spt,a1,a2,c2,h2,s2;
  int i;

  i=0x1BE;
  while ((i<0x1FE) && (buf[i+4]==0))
    i+=16;
  if (i>=0x1FE)
    return 0;
  a1=valueat(buf[i],8,unsigned long);
  a2=a1+valueat(buf[i],12,unsigned long)-1;
  if (a1>=a2)
    return 0;
  split_chs(buf+i+5,&c2,&h2,&s2);
  if (c2==0x3FF)
    {
      nhd=h2+1;
      spt=s2+1;
      if (! chk_chs(nhd,spt,a1,buf+i+1))
        return 0;
    }
  else
    {
      unsigned long c1,h1,s1;
      long n1,n2;

      split_chs(buf+i+1,&c1,&h1,&s1);
      if ((c1==0x3FF) || (c1>c2))
        return 0;
      n1=(long)(c1*a2)-(long)(c2*a1)-(long)(c1*s2)+(long)(c2*s1);
      n2=(long)(c1*h2)-(long)(c2*h1);
      if (n2<0)
        {
          n2=-n2;
          n1=-n1;
        }
      if ((n2==0) || (n1<=0) || (n1 % n2))
        return 0;
      spt=(unsigned long)(n1/n2);
      if (c2)
        {
          n1=(long)a2-(long)s2-(long)(h2*spt);
          n2=(long)(c2*spt);
          if ((n2==0) || (n1<=0) || (n1 % n2))
            return 0;
          nhd=(unsigned long)(n1/n2);
        }
      else
        nhd=h2+1;
    }
  if ((nhd==0) || (nhd>255) || (spt==0) || (spt>63))
    return 0;
  i+=16;
  while (i<0x1FE)
    {
      if (buf[i+4])
        {
          if ((! chk_chs(nhd,spt,valueat(buf[i],8,unsigned long),buf+i+1)) ||
              (! chk_chs(nhd,spt,valueat(buf[i],8,unsigned long)+valueat(buf[i],12,unsigned long)-1,buf+i+5)))
            return 0;
        }
      i+=16;
    }
  mbr_nhd=(int)nhd;
  mbr_spt=(int)spt;
  return 1;
}

int get_fstype(unsigned char* buf)
{
  if (chk_mbr(buf))
    return FST_MBR;

  // The first sector of EXT2 might not contain the 0xAA55 signature
  if (valueat(buf[1024],56,unsigned short)==EXT2_SUPER_MAGIC)
    return FST_EXT2;
  if (valueat(buf[0],0x1FE,unsigned short)!=0xAA55)
    return FST_OTHER;
  if (! strncmp(&buf[0x36],"FAT",3))
    return ((buf[0x26]==0x28) || (buf[0x26]==0x29))?FST_FAT16:FST_OTHER;
  if (! strncmp(&buf[0x52],"FAT32",5))
    return ((buf[0x42]==0x28) || (buf[0x42]==0x29))?FST_FAT32:FST_OTHER;
  if (! strncmp(&buf[0x3],"NTFS",4))
    return ((buf[0]==0xEB) && (buf[1]==0x52))?FST_NTFS:FST_OTHER;
  return FST_OTHER;
}

char* fst2str(int fs)
{
  switch (fs) {
  case FST_OTHER:
    return "Other";
  case FST_MBR:
    return "MBR";
  case FST_FAT16:
    return "FAT12/FAT16";
  case FST_FAT32:
    return "FAT32";
  case FST_NTFS:
    return "NTFS";
  case FST_EXT2:
    return "EXT2/EXT3";
  default:
    return "Unknown";
  }
}

typedef struct {
  int id;
  char* str;
} fstab_t;

static fstab_t fstab[]= {
  {0x1,"FAT12"},
  {0x4,"FAT16"},
  {0x5,"Extended"},
  {0x6,"FAT16B"},
  {0x7,"NTFS"},
  {0xB,"FAT32"},
  {0xC,"FAT32X"},
  {0xE,"FAT16X"},
  {0xF,"ExtendedX"},
  {0x11,"(H)FAT12"},
  {0x14,"(H)FAT16"},
  {0x16,"(H)FAT16B"},
  {0x17,"(H)NTFS"},
  {0x1B,"(H)FAT32"},
  {0x1C,"(H)FAT32X"},
  {0x1E,"(H)FAT16X"},
  {0x82,"Swap"},
  {0x83,"Ext2"},
  {0xA5,"FBSD"},
  {0,"Other"}};

char* dfs2str(int fs)
{
  int i;

  for (i=0;fstab[i].id;i++)
    if (fs==fstab[i].id)
      return fstab[i].str;
  return fstab[i].str;
}
