//
// hfstools - a Macintosh filesystem access tool
// (C) Copyright 1993 by Equivalence
//
// This file part of hfs.
//
// hfs 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, or (at your option)
// any later version.
// 
// hfs 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 hfs; see the file COPYING.  If not, write to
// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  
//
//
// $Id: volume.cxx,v 1.12 1994/07/08 12:00:59 craigs Exp $
// $Log: volume.cxx,v $
// Revision 1.12  1994/07/08  12:00:59  craigs
// Fixed problem with displaying misleading error messages
// Changed part command to display drive name rather than partition name
//
// Revision 1.11  1994/07/02  05:04:21  craigs
// Added support for CDROM drives under MSDOS
//
// Revision 1.10  1994/06/30  14:59:42  craigs
// Removed return statements in EnumeratePartitions
//
// Revision 1.9  1994/06/30  14:54:31  craigs
// Removed return value to EnumeratePartitions
//
// Revision 1.8  1994/06/30  14:39:24  craigs
// Changes for bigger blocks and partitions
//
// Revision 1.7  1994/01/11  02:39:55  craigs
// Added newline to end of cannot open file message
//
// Revision 1.6  1994/01/11  00:45:48  craigs
// Added InvalidateCache call so Filesystem::Unmount will work properly
//
// Revision 1.5  1994/01/06  03:05:08  craigs
// Final checkin to include GNU header
//
// Revision 1.4  1993/12/23  15:19:30  craigs
// Changed to accept A: and B: as device names under Unix
//
// Revision 1.3  1993/11/24  21:36:05  craigs
// Various changes remove warnings under MSDOS/NT
//     by robertj
//
// Revision 1.2  1993/11/23  20:18:51  craigs
// Added MSDOS/Windows compatibility
//     by robertj
//
// Revision 1.1  1993/11/22  22:25:55  craigs
// Initial revision
//
//
//

#include "config.h"

#include <iostream.h>
#include <iomanip.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#if defined(_MSDOS) || defined(WIN32)
#include <io.h>
#include <malloc.h>
#if defined(WIN32)
inline int open(const char * n, int m) { return _open(n, m); }
inline int close(int f) { return _close(f); }
inline long lseek(int f, long p, int b) { return _lseek(f, p, b); }
inline int read(int f, void * p, unsigned int l) { return _read(f, p, l); }
inline int access(const char * f, int m) { return _access(f, m); }
#endif
#else
#include <unistd.h>
#include <sys/stat.h>
#endif

#include "mac.h"
#include "volume.h"
#include "error.h"
#include "misc.h"

//////////////////////////////////////
//
// Volume::Volume
//

Volume::Volume (const char * devname)

{
  deviceName = strdup(devname);
  partitionOffset = 0;
  partitionName   = NULL;
}

//////////////////////////////////////
//
// Volume::Mount
//

int Volume::Mount (int part_num)

{
  DriverDescriptorMap driverDesc;
  NewPartitionMap     partitionMap;

  // read the driver descriptor for the partition (if it has one)
  if (!Read(M_DRIVER_DESCRIPTOR_LBLOCK, &driverDesc))
    return E_FAULTYDESCRIPTOR;

  // if this is a partition descriptor, then process it
  if ((UINT)driverDesc.sbSig == DriverDescriptorMap::Sig) {

    // read the first partition map entry
    if (!Read(M_PARTITION_MAP_ENTRY_LBLOCK, &partitionMap))
      return E_FAULTYPARTITION;

    // if not correcty partitioned, finish
    if (partitionMap.pmSig != NewPartitionMap::Sig) 
      return E_FAULTYPARTITION;

    // if there are no partitions, finish
    if (partitionMap.pmMapBlkCnt <= 1) 
      return E_NOHFSPARTITIONS;

    // find either the first HFS partition or the one requested
    int i;
    int found = -1;
    int count = 0;
    ULONG offset = 0;
    int partitionCount = (int)partitionMap.pmMapBlkCnt;
    for (i = 0; i < partitionCount; i++) {
      if (!Read(M_PARTITION_MAP_ENTRY_LBLOCK+i, &partitionMap))
        return E_FAULTYPARTITION;
      if (strcmp((const char *)partitionMap.pmPartType, HFS_PART_ID) == 0) {
        count++;
        if ((found < 0) && ((part_num < 0) || (i >= part_num))) {
          found = i;
          offset          = partitionMap.pmPyPartStart;
          partitionName   = strdup((const char *)partitionMap.pmPartName);
        }
      }
    }

    // check for errors
    int msg = 0;
    if (found < 0) {
      cout << "No HFS partitions on device" << deviceName << "!\n";
      exit(0);
    } else {
      if ((part_num > 0) && (part_num != found)) {
        cout << "Specified partition " << part_num << " is not an HFS partition\n";
        cout << "Partition " << found << " (" << partitionMap.pmPartName << ") used instead\n";
        msg = 1;
      } else if ((count > 1) && (part_num < 0)) {
        cout << "warning: " << count << " HFS partitions found\n";
        cout << "Using partition " << found << " (" << partitionMap.pmPartName << ")\n";
        msg = 1;
      }
    }
    if (msg)
      cout << "Use \"hfs part\" to display all HFS partitions on device " << deviceName << endl;
    partitionOffset = offset;
  }
  
  // Invalidate cache as partition offset has changed and block numbers are wrong
  InvalidateCache();

  // read the MDB
  if (!Read(M_MASTER_MDB_LBLOCK, &mdb))
    return E_FAULTYMDB;

  // check the signature
  if (mdb.drSigWord != MDB::Sig_HFS) 
    return E_NOTHFS;

  // all is OK
  return E_OK;
}


static char * inttosize(ULONG size)

{
  static char buffer[20];

  const ULONG kilobyte = 1024;
  const ULONG megabyte = 1024 * kilobyte;
  const ULONG gigabyte = 1024 * megabyte;

  if (size < 2 * kilobyte) 
    sprintf (buffer, "%ul", size);
  else if (size < 2 * megabyte)
    sprintf (buffer, "%.1fK", size / (float)kilobyte);
  else if (size < 2 * gigabyte)
    sprintf (buffer, "%.1fM", size / (float)megabyte);
  else
    sprintf (buffer, "%.1fG", size / (float)gigabyte);

  return buffer;
}


//////////////////////////////////////
//
// Volume::EnumeratePartitions
//

int Volume::EnumeratePartitions(BOOL showAll)

{
  DriverDescriptorMap driverDesc;
  NewPartitionMap     partitionMap;
  OldPartitionMap     *oldPartitionMap;
  MDB                 mdb;

  // read the driver descriptor for the partition (if it has one)
  if (!Read(M_DRIVER_DESCRIPTOR_LBLOCK, &driverDesc))
    return E_READERROR;

  // if this is a driver descriptor, then process it
  if (driverDesc.sbSig != DriverDescriptorMap::Sig) 
    return E_NOHFSPARTITIONS;

  // read the first partition map entry
  if (!Read(M_PARTITION_MAP_ENTRY_LBLOCK, &partitionMap))
    return E_READERROR;

  // we cannot handle old style partitions - yet!!
  if ((int)partitionMap.pmSig == OldPartitionMap::Sig) {
    oldPartitionMap = (OldPartitionMap *)&partitionMap;
    cout << deviceName << " uses an old format parition table which is not surrent supported\n";
    exit (2);
  } else if (partitionMap.pmSig != NewPartitionMap::Sig) {
    cout << deviceName << " contains a driver descriptor but not partition map!!\n";
    exit (2);
  }

  // if there are no partitions, return 0
  if (partitionMap.pmMapBlkCnt <= 1) 
    return E_NOHFSPARTITIONS;

  // enumerate the partitions
  int first = 1;
  int displayed = 0;
  int j;
  BOOL is_hfs_partition;
  int partitionCount = (int)partitionMap.pmMapBlkCnt;
  for (int i = 0; i < partitionCount; i++) {
    if (!Read(M_PARTITION_MAP_ENTRY_LBLOCK+i, &partitionMap))
      return E_FAULTYPARTITION;
    if (is_hfs_partition = (strcmp((const char *)partitionMap.pmPartType, HFS_PART_ID) == 0))
      if (!Read(M_MASTER_MDB_LBLOCK + (ULONG)partitionMap.pmPyPartStart, &mdb))
        return E_FAULTYMDB;
    if (showAll || is_hfs_partition) {
      if (first) {
        cout << "No.  Size    Name";
        if (showAll) {
          cout << "                              Type" << endl;
          j = 3+2+6+2+32+2+19;
        } else {
          cout << endl;
          j = 3+2+6+2+32;
        }
        for (;j > 0; j--)
          cout << "-";
        cout << endl;
        first = 0;
      }
      cout << setw(3) << i << "  " << setw(6)
           << inttosize(partitionMap.pmPartBlkCnt*Volume::LogicalBlockSize)
           << "  " << setw(32) << setiosflags(ios::left);
      if (is_hfs_partition)
        cout << ptocstr(mdb.drVN);
      else
        cout << partitionMap.pmPartName;
      if (showAll) 
        cout << "  " << partitionMap.pmPartType;
      cout << endl;
      displayed++;
    }
  } 
  if (displayed == 0)
    cout << "No" << (showAll ? "" : " HFS") << " partitions found\n";

  return E_OK;
}

//////////////////////////////////////
//
// FileVolume::FileVolume
//

FileVolume::FileVolume (const char * devname)
  : Volume(devname)

{
  if ((fd = open (devname, O_RDONLY)) < 0) {
    cerr << "error: cannot open the file \""
         << devname
         << "\" for use as a Macintosh volume\n";
    exit (1);
  }
}


//////////////////////////////////////
//
// FileVolume::~FileVolume
//

FileVolume::~FileVolume ()

{
  close(fd);
}

//////////////////////////////////////
//
// FileVolume::Read
//

int FileVolume::Read (ULONG logical_block, void * buffer) 

{
  if (lseek (fd, (partitionOffset + logical_block) << LogicalBlockShift, 0) < 0)
    return 0;
  return read (fd, buffer, LogicalBlockSize) == LogicalBlockSize;
}

//////////////////////////////////////
//
// FileVolume::Write
//

int FileVolume::Write (ULONG , void * )

{
//  if (lseek (fd, logical_block << LogicalBlockShift, 0) < 0)
//    return 0;
//  return write (fd, buffer, LogicalBlockSize) == LogicalBlockSize;
  return -1;
}


//////////////////////////////////////
//
// DeviceVolume::DeviceVolume
//

DeviceVolume::DeviceVolume (const char * devname)
  : Volume (devname)

{
  memset(cache, 0, sizeof(cache));
  countLRU = 1;

#if defined(_MSDOS)

  BYTE drive = (BYTE)(toupper(devname[0]) - 'A');
  _asm {
    mov ax,0x150b
    mov bx,0
    mov cl,drive
    mov ch,0
    int 0x2f
    cmp bx,0xadad
    jnz noCDROM
    cmp ax,0
    jz  noCDROM
  }
  isCDROM = TRUE;
  driveNum = drive;       // Put disk parameters into the object variables
  return;

noCDROM:
  isCDROM = FALSE;

  BYTE maxHead;
  BYTE maxSector;
  short maxTrack;
  
  _asm {
    mov  ah,8           // Code to get the disk parameters
    mov  dl,drive
    int  0x13
    jc   invalid_drive  // Disk does not exist
    mov  maxHead,dh     // Get the disk parameters to where C can get at them
    mov  ah,cl
    and  cl,0x3f
    mov  maxSector,cl
    mov  al,5
    shr  ah,cl
    mov  al,ch
    mov  maxTrack,ax
  }
  driveNum = drive;       // Put disk parameters into the object variables
  numHeads = maxHead + 1;
  numTracks = maxTrack + 1;
  numSectors = maxSector;
  return;

invalid_drive:

#elif defined(WIN32)

  char drivename[20];
  if (isalpha(devname[0])) {
    sprintf(drivename, "\\\\.\\%s", devname);
    devname = drivename;
  }
  hDrive = CreateFile(devname,
                  GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
  if (hDrive != INVALID_HANDLE_VALUE) {
    DWORD c;
    if (DeviceIoControl(hDrive, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &c, NULL))
      return;
  }

#else

  if ((fd = open ( (tolower(devname[0]) == 'a') ? DRIVE_A_DEVICE : DRIVE_B_DEVICE, O_RDWR)) > 0)
    return;

#endif

  cerr << "error: cannot open the device \""
       << devname
       << "\" for use as a Macintosh volume\n";
  exit (1);
}


#if defined(_MSDOS) 

#if defined(_WINDOWS)
extern "C" ULONG __pascal GlobalDosAlloc(ULONG);
extern "C" UINT __pascal GlobalDosFree(ULONG);
#endif

static BOOL ReadCDROM(UINT drive, ULONG phys_block, void * buffer)
{
  BOOL retVal = FALSE;
  char __far * _buffer;
  ULONG sector = phys_block >> 2;

#if defined(_WINDOWS)
  ULONG dosAlloc = GlobalDosAlloc(2048);
  _buffer = (char __far *)((dosAlloc&0xffff) << 16);
#else
  _buffer = (char __far *)_fmalloc(2048);
#endif

  _asm {
    mov ax,0x1508;
    les bx,_buffer   // address to read into
    mov cx,drive
    mov dx,1
    mov si,word ptr [sector+2]
    mov di,word ptr sector
    int 0x2f
    jc  read_bad
  }
  _fmemcpy(buffer, &_buffer[(phys_block&3)<<9], 512);
  retVal = TRUE;

read_bad:
#if defined(_WINDOWS)
  GlobalDosFree(dosAlloc);
#else
  _ffree(_buffer);
#endif

  return retVal;
}

static BOOL ReadDisk(BYTE drive, BYTE sector, BYTE head, UINT track, void * buffer)
{
  _asm {
    mov  ah,2         // function code
    mov  al,1         // count of sectors
    les  bx,buffer    // address to read into
    mov  dx,track     // track number
    mov  ch,dl
    mov  cl,5
    shl  dh,5
    mov  cl,sector    // sector number
    or   cl,dh
    mov  dh,head      // head number
    mov  dl,drive     // drive number
    int  0x13
    jc   read_bad
  }
  return TRUE;

read_bad:
  return FALSE;
}


#endif


//////////////////////////////////////
//
// DeviceVolume::Read
//

BOOL DeviceVolume::Read (ULONG logical_block, void * buffer) 

{
  Cache * c = cache;
  for (int i = 0; i < CacheSize; i++, c++) {
    if (c->lastUsed != 0 && c->block == logical_block) {
      memcpy(buffer, c->buffer, LogicalBlockSize);
      c->lastUsed = countLRU++;
      return TRUE;
    }
  }

#if defined(_MSDOS) 

  ULONG phys_block = logical_block + partitionOffset;
  if (isCDROM) {
    if (!ReadCDROM(driveNum, phys_block, buffer))
      return FALSE;
  }  
  else {
    if (!ReadDisk((BYTE)driveNum,
                 (BYTE)(phys_block%numSectors + 1),
                 (BYTE)((phys_block/numSectors)%numHeads),
                 (UINT)(phys_block/numSectors/numHeads), buffer))
      return FALSE;
  }

#elif defined(WIN32)

  if (SetFilePointer(hDrive,
           (logical_block + partitionOffset) << LogicalBlockShift, NULL, FILE_BEGIN) == 0xffffffff)
      return FALSE;
  DWORD bytesRead;
  if (!ReadFile(hDrive, buffer, LogicalBlockSize, &bytesRead, NULL))
    return FALSE;

#else

  if (lseek (fd, (logical_block + partitionOffset) << LogicalBlockShift, 0) < 0)
    return FALSE;
  if (read (fd, buffer, LogicalBlockSize) != LogicalBlockSize)
    return FALSE;

#endif

  Cache * cLRU = c = cache;
  for (i = 1; i < CacheSize; i++) {
    c++;
    if (cLRU->lastUsed > c->lastUsed)
      cLRU = c;
  }
  cLRU->block = logical_block;
  cLRU->lastUsed = countLRU++;
  memcpy(cLRU->buffer, buffer, LogicalBlockSize);

  return TRUE;
}

      
//////////////////////////////////////
//
// DeviceVolume::Write
//

int DeviceVolume::Write (ULONG logical_block, void * )

{
  // invalidate the read cache for this block
  Cache * c = cache;
  for (int i = 0; i < CacheSize; i++, c++) {
    if (c->lastUsed != 0 && c->block == logical_block) {
      c->lastUsed = 0;
      i = CacheSize;
    }
  }

  cerr << "error: DeviceVolume::Write not yet implemented\n";
  exit(0);

  return 0;
}



//////////////////////////////////////
//
// DeviceVolume::InvalidateCache
//

void DeviceVolume::InvalidateCache()

{
  memset(cache, 0, sizeof(cache));
  countLRU = 1;
}


//////////////////////////////////////
//
// DeviceVolume::IsDeviceName
//

BOOL DeviceVolume::IsDeviceName(const char * devname)
{
#if defined(_MSDOS)
  return isalpha(devname[0]) && devname[1] == ':' && devname[2] == '\0';
#elif defined(WIN32)
  if (devname[0] == '\\' && devname[1] == '\\') {
    if ((devname = strchr(devname+2, '\\')) == NULL)
      return FALSE;
    devname++;
  }
  return isalpha(devname[0]) && devname[1] == ':' && devname[2] == '\0';
#else
  return devname[1] == ':' && devname[2] == '\0' && strchr("ab", tolower(devname[0])) != NULL;
#endif

//  struct stat s;
//  return stat(devname, &s) == 0 && S_ISBLK(s.st_mode);
}
