/*  dvdisaster: Additional error correction for optical media.
 *  Copyright (C) 2004-2010 Carsten Gnoerlich.
 *  Project home page: http://www.dvdisaster.com
 *  Email: carsten@dvdisaster.com  -or-  cgnoerlich@fsfe.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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA,
 *  or direct your browser at http://www.gnu.org.
 */

#include "dvdisaster.h"

#include "scsi-layer.h"
#include "udf.h"

/**
 ** Windows wrapper. 
 **
 * As of dvdisaster 0.73.1, Windows 2000 SP4 is the minimum
 * system requirement and thus only SPTI is supported.
 */

#ifdef SYS_MINGW

/*
 * The drive letter ordering is remembered for
 * historical reasons (it was once useful for ASPI mapping)
 */

static int drive_letters[26];
static int cd_dvd_drives = 0;

char* DefaultDevice()
{  static char picked[3] = "C:";
   char drive_letter;
   char device_path[16],buf[80];
   DeviceHandle *dh;

   for(drive_letter='C'; drive_letter<='Z'; drive_letter++)
   {  UINT drive_type;

      g_sprintf(device_path, "%c:\\", drive_letter);
      drive_type = GetDriveType(device_path);

      if(drive_type == DRIVE_CDROM)
      {  drive_letters[cd_dvd_drives] = drive_letter;
	 cd_dvd_drives++;

         if(cd_dvd_drives == 1)
	   *picked = drive_letter;

	 /* Collect drives accessible via SPTI */
	
	 sprintf(buf, "%c:", drive_letter);

	 if((dh = open_spti_device(buf)))
	 {  InquireDevice(dh, 1);

	    g_ptr_array_add(Closure->deviceNodes, g_strdup(buf));
	    sprintf(buf, "%c: %s", drive_letter, dh->devinfo);
	    g_ptr_array_add(Closure->deviceNames, g_strdup(buf));
	    CloseDevice(dh);
         } 
      }
   }

   if(cd_dvd_drives > 0)
     return g_strdup(picked);

   return g_strdup("No_Drive_found");
}

/*
 * Close the SPTI device.
 */

void CloseDevice(DeviceHandle *dh)
{
  if(dh->canReadDefective)
    SetRawMode(dh, MODE_PAGE_UNSET);

  if(dh->rawBuffer)
     FreeRawBuffer(dh->rawBuffer);

  if(dh->fd)             /* SPTI cleanup */
     CloseHandle(dh->fd);

  if(dh->rs02Header)
    g_free(dh->rs02Header);
  if(dh->typeDescr) 
    g_free(dh->typeDescr);
  if(dh->mediumDescr) 
     g_free(dh->mediumDescr);
  if(dh->isoInfo)
    FreeIsoInfo(dh->isoInfo);
  if(dh->defects)
    FreeBitmap(dh->defects);

  g_free(dh->device);
  g_free(dh);
}

/**
 ** The SPTI wrapper.
 **/

/* 
 * The Mingw includes are missing the following structs 
 * for sending SCSI packets via SPTI.
 */

typedef struct 
{ USHORT Length;
  UCHAR  ScsiStatus;
  UCHAR  PathId;
  UCHAR  TargetId;
  UCHAR  Lun;
  UCHAR  CdbLength;
  UCHAR  SenseInfoLength;
  UCHAR  DataIn;
  ULONG  DataTransferLength;
  ULONG  TimeOutValue;
  PVOID  DataBuffer;
  ULONG  SenseInfoOffset;
  UCHAR  Cdb[16];
} SCSI_PASS_THROUGH_DIRECT;

typedef struct 
{ SCSI_PASS_THROUGH_DIRECT spt;
  ULONG  Filler;
  UCHAR  SenseBuf[32];
} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER;

#define IOCTL_SCSI_BASE 0x00000004
#define IOCTL_SCSI_PASS_THROUGH_DIRECT  CTL_CODE( IOCTL_SCSI_BASE, 0x0405, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS )

/*
 * Open the device using SPTI.
 */

DeviceHandle* open_spti_device(char *device)
{  DeviceHandle *dh; 
   char device_path[16];

   if(   strlen(device) != 2
      || toupper(*device) < 'C'
      || toupper(*device) > 'Z'
      || device[1] != ':')
   {
      Stop(_("\nIllegal device name \"%s\" (use devices \"C:\" ... \"Z:\")"),device);
      return NULL;
   }
      
   dh = g_malloc0(sizeof(DeviceHandle));
   dh->device = g_strdup(device);

   g_sprintf(device_path, "\\\\.\\%c:", toupper(*device));

   dh->fd = CreateFile(device_path,                  
	               GENERIC_READ | GENERIC_WRITE,
	               FILE_SHARE_READ | FILE_SHARE_WRITE,
                       NULL, OPEN_EXISTING, 0, NULL );

   if(dh->fd == INVALID_HANDLE_VALUE)  /* Might be Win9x or missing priviledges */
   {  g_free(dh->device);
      g_free(dh);
      return NULL;
   }

   return dh;
}

/*
 * Send the SCSI command through SPTI.
 * At least W2K seems to have problems with SCSI commands timing out;
 * it even manages to do a spontaneous reboot because of that occasionally. 
 * So we set the time out value to a conservative 120 secs.
 */ 

static int send_spti_packet(HANDLE fd, unsigned char *cmd, int cdb_size, char *buf, int size, Sense *sense, int data_mode)
{  SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER ss;
   BOOL success;
   ULONG returned;

   memset(&ss, 0, sizeof(ss));

   ss.spt.Length          	= sizeof(SCSI_PASS_THROUGH_DIRECT);
   ss.spt.CdbLength       	= cdb_size;
   ss.spt.SenseInfoLength 	= 24;
   ss.spt.TargetId              = 1;
   switch(data_mode)
   {  case DATA_WRITE:
	ss.spt.DataIn          	= 0;  /* SCSI_IOCTL_DATA_OUT */
	break;
      case DATA_READ:
	ss.spt.DataIn          	= 1;  /* SCSI_IOCTL_DATA_IN */
	break;
      case DATA_NONE:
	ss.spt.DataIn          	= 2;  /* SCSI_IOCTL_DATA_UNSPECIFIED */
	break;
      default:
	Stop("illegal data_mode: %d",data_mode);
	return -1;
   }
   ss.spt.DataTransferLength 	= size;
   ss.spt.TimeOutValue    	= 120;  /* see comment above */
   ss.spt.DataBuffer      	= buf;
   ss.spt.SenseInfoOffset 	= offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf);
   memcpy(ss.spt.Cdb, cmd, cdb_size);

   success = DeviceIoControl(fd, IOCTL_SCSI_PASS_THROUGH_DIRECT,
			    &ss, sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER),
			    &ss, sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER),
			    &returned, FALSE);

   memcpy(sense, ss.SenseBuf, 24);  /* Hmm. What a kludge. */

   if(!success || ss.spt.ScsiStatus) return -1;
   return 0;
}

/*
 * Open the device
 */

DeviceHandle* OpenDevice(char *device)
{  DeviceHandle *dh = NULL; 

   if(   (*device >= 'c' && *device <= 'z')
      || (*device >= 'C' && *device <= 'Z'))
     dh = open_spti_device(Closure->device);

   if(!dh)
   {  Stop(_("\nCould not open device %s."), device);
      return NULL;
   }

   return dh;
}

/*
 * Translate Scsi wrapper into SPTI call
 */

int SendPacket(DeviceHandle *dh, unsigned char *cmd, int cdb_size, unsigned char *buf, int size, Sense *sense, int data_mode)
{
  return send_spti_packet(dh->fd, cmd, cdb_size, buf, size, sense, data_mode);
}

#endif /* SYS_MINGW */
