/*

    File: hdaccess.c

    Copyright (C) 1998-2007 Christophe GRENIER <grenier@cgsecurity.org>
  
    This software 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 the Free Software Foundation, Inc., 51
    Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
 
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>	/* lseek, read, write, close */
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h> 	/* open */
#endif
#include <stdio.h>
#include <errno.h>
#include "types.h"
#ifdef HAVE_LINUX_TYPES_H
#include <linux/types.h>
#endif
#include "common.h"
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_DISKLABEL_H
#include <sys/disklabel.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_MOUNT_H
#include <sys/mount.h>	/* BLKFLSBUF */
#endif
#ifdef HAVE_LINUX_HDREG_H
#include <linux/hdreg.h>
#endif
#ifdef HAVE_SYS_DISK_H
#include <sys/disk.h>
#endif
#ifdef HAVE_FEATURES_H
#include <features.h>
#endif
#ifdef HAVE_SYS_DKIO_H
#include <sys/dkio.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
/* linux/fs.h may not be needed because sys/mount.h is present */
/* #ifdef HAVE_LINUX_FS_H */
/* #include <linux/fs.h> */
/* #endif */
#ifdef HAVE_WINDEF_H
#include <windef.h>
#endif
#ifdef HAVE_WINBASE_H
#include <stdarg.h>
#include <winbase.h>
#endif
#ifdef HAVE_WINIOCTL_H
#include <winioctl.h>
#endif
#ifdef HAVE_FNCTL_H
#include <fnctl.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>     /* atexit, posix_memalign */
#endif

#ifdef DJGPP
#include <go32.h>       /* dosmemget/put */
#include <dpmi.h>
#include <bios.h>       /* bios_k* */
#elif defined(__CYGWIN__)
#include <io.h>
#include <windows.h>
#include <winnt.h>
#endif
#include "fnctdsk.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
#include "ewf.h"
#include "log.h"

extern const arch_fnct_t arch_mac;

struct info_file_struct
{
  int handle;
  uint64_t offset;
  char file_name[DISKNAME_MAX];
  int mode;
  void *buffer;
  unsigned int buffer_size;
};

static void autoset_geometry(disk_t * disk_car, const unsigned char *buffer, const int debug);
static const char *file_description(disk_t *disk_car);
static const char *file_description_short(disk_t *disk_car);
static int file_clean(disk_t *disk_car);
static int file_read(disk_t *disk_car, const unsigned int count, void *nom_buffer, const uint64_t offset);
static int file_write(disk_t *disk_car, const unsigned int count, const void *nom_buffer, const uint64_t offset);
static int file_nowrite(disk_t *disk_car, const unsigned int count, const void *nom_buffer, const uint64_t offset);
#ifndef DJGPP
static disk_t *disk_get_geometry(const int hd_h, const char *device, const int debug);
#endif
#if defined(__CYGWIN__) || defined(__MINGW32__)
static disk_t *disk_get_geometry_win32(HANDLE handle, const char *device, const int debug);
#endif

static void compute_device_size(disk_t *disk_car);

#ifdef DJGPP
#define HD_RW_BUF_SIZ 0x10
#define HDPARM_BUF_SIZ 0x1A
#define MAX_IO_NBR 3
#define MAX_HD_ERR 100
#define PARA_CMD         8
static void free_dos_buffer(void);
static int alloc_dos_buffer(void);
static int hd_super(int cmd, disk_t *disk_car, unsigned long int hd_offset, int nsects, void *buffer);
static disk_t *hd_identify(const int debug, const unsigned int disk, const arch_fnct_t *arch, const int testdisk_mode);
static int hd_identify_enh_bios(disk_t *param_disk,const int debug);
static int check_enh_bios(const unsigned int disk, const int debug);
static int hd_report_error(disk_t *disk_car, const uint64_t hd_offset, const unsigned int nbr_sector, const int rc);
static const char *disk_description(disk_t *disk_car);
static const char *disk_description_short(disk_t *disk_car);
static int disk_read(disk_t *disk_car, const unsigned int count, void *nom_buffer, const uint64_t hd_offset);
static int disk_write(disk_t *disk_car, const unsigned int count, const void *nom_buffer, const uint64_t hd_offset);
static int disk_nowrite(disk_t *disk_car, const unsigned int count, const void *nom_buffer, const uint64_t offset);
static int disk_clean(disk_t *disk_car);
struct info_disk_struct
{
  unsigned int disk;
  CHS_t CHSR;	/* CHS low level */
  int mode_enh;
  unsigned int nbr_err;
  int bad_geometry;
};

static int dos_segment = 0;
static int dos_selector = 0;

static void free_dos_buffer(void)
{
  __dpmi_free_dos_memory(dos_selector);
  dos_segment = dos_selector = 0;
}

static int alloc_dos_buffer(void)
{
  if (dos_segment)
    return 0;
  if ((dos_segment = __dpmi_allocate_dos_memory(18*DEFAULT_SECTOR_SIZE/16, &dos_selector))== -1)
  {
    dos_segment = 0;
    return 1;
  }
#ifdef HAVE_ATEXIT
  atexit(free_dos_buffer);
#endif
  return 0;
}

static int hd_super(int cmd, disk_t *disk_car, unsigned long int hd_offset, int nsects, void *buffer)
{
  __dpmi_regs r;
  unsigned char buf[HD_RW_BUF_SIZ];
  int dos_segment2, dos_selector2, xfer2=nsects*DEFAULT_SECTOR_SIZE;
  struct info_disk_struct*data=disk_car->data;

  if((data->mode_enh==0)||(cmd==0))
  { /* Limite CHS = 1023,255,63 = 8,064Mo ~= 7.8 Go */
    int head, track, sector;
    if(data->CHSR.sector==0)
    {
      log_critical("hd_super: BUG CHSR.sector=0 !\n");
      return 1;
    }
    sector=(hd_offset%data->CHSR.sector)+1;
    hd_offset/=data->CHSR.sector;
    head=hd_offset%(data->CHSR.head+1);
    track=hd_offset/(data->CHSR.head+1);
    if(track<1024)
      return biosdisk(cmd, data->disk, head, track, sector, nsects, buffer);
    return 1;
  }
  if(dos_segment==0)
    if(alloc_dos_buffer())
      return 1;
  if ( (dos_segment2=__dpmi_allocate_dos_memory((xfer2 + 15) >> 4, &dos_selector2)) == -1 )
    return 1;
  *(uint16_t*)&buf[0]=HD_RW_BUF_SIZ;
  *(uint16_t*)&buf[2]=nsects;
  *(uint32_t*)&buf[0x4]=dos_segment2<<16;
  *(uint32_t*)&buf[0x8]=hd_offset;
  *(uint32_t*)&buf[0xC]=0;

  r.x.ds = dos_segment;
  r.x.si = 0;
  r.h.dl = data->disk;
  switch(cmd)
  {
    case 2:
      r.h.ah = 0x42;
      break;
    case 3:
      r.x.ax = 0x4300;
      dosmemput(buffer,xfer2,dos_segment2<<4);
      break;
	case 0x0C:
	  r.h.ah = 0x47;
	  break;
  }
  dosmemput(&buf, HD_RW_BUF_SIZ, dos_segment<<4);
  __dpmi_int(0x13, &r);
  if(cmd==2)
    dosmemget(dos_segment2<<4, xfer2, buffer);
  __dpmi_free_dos_memory(dos_selector2);
  return r.h.ah;
}

static int check_enh_bios(const unsigned int disk, const int debug)
{
  __dpmi_regs r;
  r.h.ah = 0x41;
  r.x.bx = 0x55AA;
  r.h.dl = disk;
  __dpmi_int(0x13, &r);
  if(r.x.bx != 0xAA55)  /* INT 13 Extensions not installed */
  {
    if(debug>0)
      log_warning("Disk %02X - INT 13 Extensions not installed\n",disk);
    return 0;
  }
  if(debug>0)
  {
    log_info("Disk %02X ",disk);
    switch(r.h.ah)
    {
      case 0x01:
	log_info("Enhanced BIOS 1.x");
	break;
      case 0x20:
	log_info("Enhanced BIOS 2.0 / EDD-1.0");
	break;
      case 0x21:
	log_info("Enhanced BIOS 2.1 / EDD-1.1");
	break;
      case 0x30:
	log_info("Enhanced BIOS EDD-3.0");
	break;
      default:
	log_info("Enhanced BIOS unknown %02X",r.h.ah);
	break;
    }
    if((r.x.cx & 1)!=0)
      log_info(" - R/W/I");
    if((r.x.cx & 4)!=0)
      log_info(" - Identify");
    log_info("\n");
  }
  return ((r.x.cx&1)!=0);
}

static int hd_identify_enh_bios(disk_t *disk_car,const int debug)
{
  int compute_LBA=0;
  __dpmi_regs r;
  unsigned char buf[0x200];	/* Don't change it! */
  struct info_disk_struct*data=disk_car->data;
  buf[0]=HDPARM_BUF_SIZ;
  buf[1]=0;
  if(dos_segment==0)
    if(alloc_dos_buffer())
      return 1;
  r.h.ah = 0x48;
  r.x.ds = dos_segment;
  r.x.si = 0;
  r.h.dl = data->disk;
  dosmemput(&buf, HDPARM_BUF_SIZ, dos_segment<<4);
  __dpmi_int(0x13, &r);
  dosmemget(dos_segment<<4, HDPARM_BUF_SIZ, &buf);
  if(r.h.ah)
    return 1;
  disk_car->CHS.cylinder=*(uint16_t*)&buf[0x04];
  disk_car->CHS.head=*(uint16_t*)&buf[0x08];
  disk_car->CHS.sector=*(uint16_t*)&buf[0x0C];
  disk_car->disk_size=(*(uint32_t*)&buf[0x10])*(uint64_t)disk_car->sector_size;
  if(disk_car->disk_size==0)
  {
    if(disk_car->CHS.cylinder==0 || disk_car->CHS.head==0 || disk_car->CHS.sector==0)
    {
      if(debug>0)
	log_warning("hd_identify_enh_bios: No size returned by BIOS.\n");
      return 1;
    }
    else
    {
      disk_car->CHS.cylinder--;
      disk_car->CHS.head--;
      compute_LBA=1;
      disk_car->disk_size=(uint64_t)(disk_car->CHS.cylinder+1)*(disk_car->CHS.head+1)*disk_car->CHS.sector*disk_car->sector_size;
      if(debug>0)
	log_debug("Computes LBA from CHS\n");
    }
  }
  else
  {
    if(disk_car->CHS.cylinder>0 && disk_car->CHS.head>0 && disk_car->CHS.sector>0)
    {
      disk_car->CHS.cylinder--;
      disk_car->CHS.head--;
      /* Some bios are buggy */
      if(disk_car->disk_size>(uint64_t)(disk_car->CHS.cylinder+2)*(disk_car->CHS.head+1)*disk_car->CHS.sector*disk_car->sector_size)
      {
	disk_car->CHS.cylinder=(disk_car->disk_size/(disk_car->CHS.head+1))/disk_car->CHS.sector/disk_car->sector_size-1;
	if(debug>0)
	  log_debug("Computes C from number of sectors\n");
      }
    }
    else
    {
      if(debug>0)
	log_debug("Computes CHS from number of sectors\n");
      disk_car->CHS.head=255-1;
      disk_car->CHS.sector=63;
      disk_car->CHS.cylinder=(disk_car->disk_size/(disk_car->CHS.head+1))/disk_car->CHS.sector/disk_car->sector_size-1;
    }
  }
  if(disk_car->CHS.sector==0)
  {
    data->bad_geometry=1;
    disk_car->CHS.sector=1;
    log_critical("Incorrect number of sector\n");
  }
  if(disk_car->CHS.sector>63)
  {
/*    data->bad_geometry=1; */
    log_critical("Incorrect number of sector\n");
  }
  if(disk_car->CHS.head>255-1)
  {
    data->bad_geometry=1;
    log_critical("Incorrect number of head\n");
  }
  if(debug>0 || data->bad_geometry!=0)
    log_info("LBA %lu, computed %u (CHS=%u,%u,%u)\n",(long unsigned)(disk_car->disk_size/disk_car->sector_size), (disk_car->CHS.cylinder+1)*(disk_car->CHS.head+1)*disk_car->CHS.sector,disk_car->CHS.cylinder,disk_car->CHS.head,disk_car->CHS.sector);
  if(compute_LBA)
    disk_car->disk_size=(uint64_t)(disk_car->CHS.cylinder+1)*(disk_car->CHS.head+1)*disk_car->CHS.sector*disk_car->sector_size;
  else
  {
    if(disk_car->disk_size < (uint64_t)(disk_car->CHS.cylinder+1)*(disk_car->CHS.head+1)*disk_car->CHS.sector/disk_car->sector_size)
    {
      log_info("Computes LBA from CHS, previous value may be false.\n");
      disk_car->disk_size=(uint64_t)(disk_car->CHS.cylinder+1)*(disk_car->CHS.head+1)*disk_car->CHS.sector*disk_car->sector_size;
    }
  }
  disk_car->disk_real_size=disk_car->disk_size;
  data->CHSR.cylinder=disk_car->CHS.cylinder;
  data->CHSR.head=disk_car->CHS.head;
  data->CHSR.sector=disk_car->CHS.sector;
  if(debug>0)
  {
    log_info("hd_identify_enh_bios\n");
    log_info("%s\n",disk_description(disk_car));
    log_info("LBA size=%lu\n",(long unsigned)(disk_car->disk_size/disk_car->sector_size));
  }
  return 0;
}

static disk_t *hd_identify(const int debug, const unsigned int disk, const arch_fnct_t *arch, const int testdisk_mode)
{
  unsigned char buf[0x200];
  memset(buf,0,sizeof(buf));
  /* standard BIOS access */
  if(biosdisk(PARA_CMD,disk,0,0,1,1,buf))
    return NULL;
  if(debug>1)
    log_debug("Disk %02X %u max %02X\n",disk,buf[2],(0x80+buf[2]-1));
  if(disk>(unsigned int)(0x80+buf[2]-1))
    return NULL;
  {
    char device[100];
    struct info_disk_struct*data=(struct info_disk_struct*)MALLOC(sizeof(*data));
    disk_t *disk_car=(disk_t *)MALLOC(sizeof(*disk_car));
    data->disk=disk;
    data->bad_geometry=0;
    data->mode_enh=0;
    data->nbr_err=0;
    disk_car->arch=arch;
    snprintf(device,sizeof(device),"/dev/sda%u",disk);
    disk_car->device=strdup(device);
    disk_car->write_used=0;
    disk_car->autodetect=0;
    disk_car->sector_size=DEFAULT_SECTOR_SIZE;
    disk_car->description=disk_description;
    disk_car->description_short=disk_description_short;
    disk_car->read=disk_read;
    disk_car->write=((testdisk_mode&TESTDISK_O_RDWR)==TESTDISK_O_RDWR?disk_write:disk_nowrite);
    disk_car->clean=disk_clean;
    disk_car->data=data;
    disk_car->CHS.cylinder=((buf[0] & 0x0C0)<<2)|buf[1];
    disk_car->CHS.head=buf[3];
    disk_car->CHS.sector=buf[0] & 0x3F;
    if(disk_car->CHS.head>=255)
    { /* Problem found by G Rowe */
      log_critical("BIOS reports an invalid heads number\n");
      data->bad_geometry=1;
      disk_car->CHS.head=254;
    }
    if(disk_car->CHS.sector==0)
    { /* Problem found by Brian Barrett */
      log_critical("BIOS reports an invalid number of sector per head\n");
      data->bad_geometry=1;
      disk_car->CHS.sector=1;
    }
    disk_car->disk_size=(uint64_t)(disk_car->CHS.cylinder+1)*(disk_car->CHS.head+1)*disk_car->CHS.sector*disk_car->sector_size;
    disk_car->disk_real_size=disk_car->disk_size;
    data->CHSR.cylinder=disk_car->CHS.cylinder;
    data->CHSR.head=disk_car->CHS.head;
    data->CHSR.sector=disk_car->CHS.sector;
    if(debug>0)
      log_info("%s\n",disk_description(disk_car));
    if(check_enh_bios(disk,debug))
    {
      /* enhanced BIOS access */
      disk_t *param_disk_enh=(disk_t*)MALLOC(sizeof(*param_disk_enh));
      param_disk_enh->write_used=0;
      param_disk_enh->sector_size=disk_car->sector_size;
      param_disk_enh->data=data;
      data->mode_enh=1;
      if(!hd_identify_enh_bios(param_disk_enh,debug))
      {
	/* standard geometry H,S, compute C from LBA */
	disk_car->disk_size=param_disk_enh->disk_size;
	disk_car->disk_real_size=disk_car->disk_size;
	disk_car->CHS.cylinder=(disk_car->disk_size/(disk_car->CHS.head+1))/disk_car->CHS.sector/disk_car->sector_size-1;
      }
      else
	data->mode_enh=0;
      free(param_disk_enh);
    }
    return disk_car;
  }
}

static const char *disk_description(disk_t *disk_car)
{
  struct info_disk_struct*data=disk_car->data;
  char buffer_disk_size[100];
  snprintf(disk_car->description_txt, sizeof(disk_car->description_txt),"Disk %2x - %s - CHS %u %u %u%s",
      data->disk, size_to_unit(disk_car->disk_size,buffer_disk_size),
      disk_car->CHS.cylinder+1, disk_car->CHS.head+1, disk_car->CHS.sector,
      data->bad_geometry!=0?" (Buggy BIOS)":"");
  return disk_car->description_txt;
}

static const char *disk_description_short(disk_t *disk_car)
{
  struct info_disk_struct*data=disk_car->data;
  char buffer_disk_size[100];
  snprintf(disk_car->description_short_txt, sizeof(disk_car->description_txt),"Disk %2x - %s",
      data->disk, size_to_unit(disk_car->disk_size,buffer_disk_size));
  return disk_car->description_short_txt;
}

static int disk_read(disk_t *disk_car,const unsigned int count, void *nom_buffer, const uint64_t hd_offset)
{
  if(count%disk_car->sector_size!=0 || hd_offset%disk_car->sector_size!=0)
  {
    char*buffer=(char*)MALLOC((count/disk_car->sector_size+2)*disk_car->sector_size);
    /* log_debug("disk_read handle unaligned data\n"); */
    if(disk_read(disk_car,(unsigned)(((count/disk_car->sector_size)+2)*disk_car->sector_size),buffer,hd_offset/disk_car->sector_size*disk_car->sector_size))
    {
      free(buffer);
      return -1;
    }
    memcpy(nom_buffer,buffer+(hd_offset%disk_car->sector_size),count);
    free(buffer);
    return 0;
  }
  else
  {
    struct info_disk_struct*data=disk_car->data;
    if(hd_offset==0)
    {
      /* CHS=(0,0,1) */
      data->nbr_err=0;
    }
    if((data->CHSR.cylinder==0) || (hd_offset+count<=disk_car->disk_size))
    {
      int rc=0;
      unsigned int sector_offset=0;
      unsigned int read_size;
      for(sector_offset=0;(rc==0) && (sector_offset<count/disk_car->sector_size);sector_offset+=read_size)
      {
	int i;
	read_size=count/disk_car->sector_size-sector_offset>16?16:count/disk_car->sector_size-sector_offset;
	rc=4; /* sector not found/read error */
	for(i=0;(rc!=0) && (rc!=1) && (i<MAX_IO_NBR);i++)
	{
	  rc=hd_super(2, disk_car, hd_offset/disk_car->sector_size+sector_offset, read_size, (char*)nom_buffer+sector_offset*disk_car->sector_size);
	  if(rc!=0)
	  {
	    hd_super(0, disk_car, 0, 1, NULL);
	  }
	}
      }
      if(rc!=0)
      {
	log_error("disk_read");
	hd_report_error(disk_car,hd_offset,count/disk_car->sector_size,rc);
      }
      return rc;
    }
  }
  return 1;
}

static int disk_write(disk_t *disk_car,const unsigned int count, const void *nom_buffer, const uint64_t hd_offset)
{
  if(count%disk_car->sector_size!=0 || hd_offset%disk_car->sector_size!=0)
  {
    log_warning("disk_write unaligned data not supported\n");
    return 1;
  }
  else
  {
    struct info_disk_struct*data=disk_car->data;
    disk_car->write_used=1;
    int i,rc=4; /* sector not found/read error */
    for(i=0;(rc==4)&&(i<MAX_IO_NBR);i++)
    {
      rc=hd_super(3, disk_car, hd_offset/disk_car->sector_size, count/disk_car->sector_size, nom_buffer);
    }
    if(rc)
    {
      log_error("disk_write");
      hd_report_error(disk_car,hd_offset,count/disk_car->sector_size,rc);
    }
    return rc;
  }
}

static int disk_nowrite(disk_t *disk_car,const unsigned int count, const void *nom_buffer, const uint64_t offset)
{
  struct info_disk_struct *data=disk_car->data;
  log_warning("disk_nowrite(%d,%u,buffer,%lu(%u/%u/%u)) write refused\n", data->disk,
      (unsigned)(count/disk_car->sector_size),(long unsigned)(offset/disk_car->sector_size),
      offset2cylinder(disk_car,offset),offset2head(disk_car,offset),offset2sector(disk_car,offset));
  return -1;
}

static int disk_clean(disk_t *disk_car)
{
  if(disk_car->data!=NULL)
  {
    struct info_disk_struct *data=disk_car->data;
    free(disk_car->data);
    disk_car->data=NULL;
  }
  return 0;
}

static int hd_report_error(disk_t *disk_car, const uint64_t hd_offset, const unsigned int nbr_sector, const int rc)
{
  struct info_disk_struct*data=disk_car->data;
  data->nbr_err++;
  log_error(" lba=%lu(%u/%u/%u) nbr_sector=%u, rc=%d\n",(long unsigned int)(hd_offset/disk_car->sector_size),
      offset2cylinder(disk_car,hd_offset),offset2head(disk_car,hd_offset),offset2sector(disk_car,hd_offset),
      nbr_sector,rc);
  switch(rc)
  {
    case 0x00: log_error("successful completion"); break;
    case 0x01: log_error("invalid function in AH or invalid parameter"); break;
    case 0x02: log_error("address mark not found"); break;
    case 0x03: log_error("disk write-protected"); break;
    case 0x04: log_error("sector not found/read error"); break;
    case 0x05: log_error("reset failed (hard disk)"); break;
    case 0x06: log_error("disk changed (floppy)"); break;
    case 0x07: log_error("drive parameter activity failed (hard disk)"); break;
    case 0x08: log_error("DMA overrun"); break;
    case 0x09: log_error("data boundary error (attempted DMA across 64K boundary or >80h sectors)"); break;
    case 0x0A: log_error("bad sector detected (hard disk)"); break;
    case 0x0B: log_error("bad track detected (hard disk)"); break;
    case 0x0C: log_error("unsupported track or invalid media"); break;
    case 0x0D: log_error("invalid number of sectors on format (PS/2 hard disk)"); break;
    case 0x0E: log_error("control data address mark detected (hard disk)"); break;
    case 0x0F: log_error("DMA arbitration level out of range (hard disk)"); break;
    case 0x10: log_error("uncorrectable CRC or ECC error on read"); break;
    case 0x11: log_error("data ECC corrected (hard disk)"); break;
    case 0x20: log_error("controller failure"); break;
    case 0x31: log_error("no media in drive (IBM/MS INT 13 extensions)"); break;
    case 0x32: log_error("incorrect drive type stored in CMOS (Compaq)"); break;
    case 0x40: log_error("seek failed"); break;
    case 0x80: log_error("timeout (not ready)"); break;
    case 0xAA: log_error("drive not ready (hard disk)"); break;
    case 0xB0: log_error("volume not locked in drive (INT 13 extensions)"); break;
    case 0xB1: log_error("volume locked in drive (INT 13 extensions)"); break;
    case 0xB2: log_error("volume not removable (INT 13 extensions)"); break;
    case 0xB3: log_error("volume in use (INT 13 extensions)"); break;
    case 0xB4: log_error("lock count exceeded (INT 13 extensions)"); break;
    case 0xB5: log_error("valid eject request failed (INT 13 extensions)"); break;
    case 0xB6: log_error("volume present but read protected (INT 13 extensions)"); break;
    case 0xBB: log_error("undefined error (hard disk)"); break;
    case 0xCC: log_error("write fault (hard disk)"); break;
    case 0xE0: log_error("status register error (hard disk)"); break;
    case 0xFF: log_error("sense operation failed (hard disk)"); break;
  }
  log_error("\n");
  return 0;
}
#endif

#if defined(__CYGWIN__) || defined(__MINGW32__)
list_disk_t *insert_new_disk_nodup(list_disk_t *list_disk, disk_t *disk_car, const char *device_name, const int debug);
list_disk_t *insert_new_disk_nodup(list_disk_t *list_disk, disk_t *disk_car, const char *device_name, const int debug)
{
  if(disk_car==NULL)
    return list_disk;
  {
    int disk_same_size_present=0;
    list_disk_t *cur;
    for(cur=list_disk;cur!=NULL;cur=cur->next)
    {
      if(cur->disk->disk_size==disk_car->disk_size && cur->disk->sector_size==disk_car->sector_size)
	disk_same_size_present=1;
    }
    if(disk_car->sector_size==512 && disk_same_size_present!=0)
    {
      if(debug>1)
	log_debug("%s is available but reject it to avoid duplicate disk.\n", device_name);
      if(disk_car->clean!=NULL)
	disk_car->clean(disk_car);
      free(disk_car->device);
      free(disk_car);
      return list_disk;
    }
    return insert_new_disk(list_disk,disk_car);
  }
}
#endif

list_disk_t *hd_parse(list_disk_t *list_disk, const int debug, const arch_fnct_t *arch, const int testdisk_mode)
{
  int i;
#ifdef DJGPP
  int ind_stop=0;
  for(i=0x80;(i<0x88)&&!ind_stop;i++)
  {
    disk_t *disk_car=hd_identify(debug,i,arch,testdisk_mode);
    if(disk_car)
      list_disk=insert_new_disk(list_disk,disk_car);
    else
      ind_stop=1;
  }
#elif defined(__CYGWIN__) || defined(__MINGW32__)
  {
    int do_insert=0;
    char device_hd[]="\\\\.\\PhysicalDrive0";
    char device_cdrom[]="\\\\.\\C:";
#if defined(__CYGWIN__)
    char device_scsi[]="/dev/sda";
    /* Disk */
    for(i=0;i<8;i++)
    {
      device_scsi[strlen(device_scsi)-1]='a'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_scsi,debug,arch,testdisk_mode));
    }
#endif
    /* Disk */
    if(list_disk==NULL) // || (testdisk_mode&TESTDISK_O_ALL)==TESTDISK_O_ALL)
      do_insert=1;
    {
      for(i=0;i<8;i++)
      {
	disk_t *disk_car;
	device_hd[strlen(device_hd)-1]='0'+i;
	disk_car=file_test_availability_win32(device_hd,debug,arch,testdisk_mode);
	if(do_insert>0 || (testdisk_mode&TESTDISK_O_ALL)==TESTDISK_O_ALL)
	  list_disk=insert_new_disk(list_disk,disk_car);
	else
	  list_disk=insert_new_disk_nodup(list_disk,disk_car,device_hd, debug);
      }
    }
    /* cdrom and digital camera */
    for(i='C';i<='Z';i++)
    {
      disk_t *disk_car;
      device_cdrom[strlen(device_cdrom)-2]=i;
      disk_car=file_test_availability_win32(device_cdrom,debug,arch,testdisk_mode);
      if((testdisk_mode&TESTDISK_O_ALL)==TESTDISK_O_ALL)
	list_disk=insert_new_disk(list_disk,disk_car);
      else
	list_disk=insert_new_disk_nodup(list_disk,disk_car,device_cdrom, debug);
    }
  }
#elif defined(__APPLE__)
  {
    char device_scsi[]="/dev/disk0";
    char device_raw[]="/dev/rdisk0";
    /* Disk */
    for(i=0;i<10;i++)
    {
      device_scsi[strlen(device_scsi)-1]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_scsi,debug,arch,testdisk_mode));
    }
    for(i=0;i<10;i++)
    {
      device_raw[strlen(device_raw)-1]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_raw,debug,arch,testdisk_mode));
    }
  }
#elif defined(TARGET_LINUX)
  {
    int j;
    char device[100];
    char device_ide[]="/dev/hda";
    char device_scsi[]="/dev/sda";
    char device_ida[]="/dev/ida/c0d0";
    char device_cciss[]="/dev/cciss/c0d0";
    char device_p_ide[]="/dev/pda";
    char device_i2o_hd[]="/dev/i2o/hda";
    /* Disk IDE */
    for(i=0;i<8;i++)
    {
      device_ide[strlen(device_ide)-1]='a'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_ide,debug,arch,testdisk_mode));
    }
    /* Disk SCSI */
    for(i=0;i<26;i++)
    {
      device_scsi[strlen(device_scsi)-1]='a'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_scsi,debug,arch,testdisk_mode));
    }
    /* Device RAID Compaq */
    for(j=0;j<8;j++)
    {
      device_ida[strlen(device_ida)-3]='0'+j;
      for(i=0;i<8;i++)
      {
	device_ida[strlen(device_ida)-1]='0'+i;
	list_disk=insert_new_disk(list_disk,file_test_availability(device_ida,debug,arch,testdisk_mode));
      }
    }
    for(i=0;i<8;i++)
    {
      device_cciss[strlen(device_cciss)-1]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_cciss,debug,arch,testdisk_mode));
    }
    /* Device RAID */
    for(i=0;i<10;i++)
    {
      snprintf(device,sizeof(device),"/dev/rd/c0d%u",i);
      list_disk=insert_new_disk(list_disk,file_test_availability(device,debug,arch,testdisk_mode));
    }
    /* Device RAID IDE */
    for(i=0;i<15;i++)
    {
      snprintf(device,sizeof(device),"/dev/ataraid/d%u",i);
      list_disk=insert_new_disk(list_disk,file_test_availability(device,debug,arch,testdisk_mode));
    }
    /* Parallel port IDE disk */
    for(i=0;i<4;i++)
    {
      device_p_ide[strlen(device_p_ide)-1]='a'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_p_ide,debug,arch,testdisk_mode));
    }
    /* I2O hard disk */
    for(i=0;i<26;i++)
    {
      device_i2o_hd[strlen(device_i2o_hd)-1]='a'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_i2o_hd,debug,arch,testdisk_mode));
    }
  }
#elif defined(TARGET_SOLARIS)
  {
    char rdsk[]="/dev/rdsk/c0t0d0s2";
    for(i=0;i<15;i++)
    {
      if(i!=7)
      {
	rdsk[13]='0'+i;
	list_disk=insert_new_disk(list_disk,file_test_availability(rdsk,debug,arch,testdisk_mode));
      }
    }
  }
#else
  {
    /* Need to check http://mattriffle.com/mirrors/freebsd/doc/en_US.ISO8859-1/books/handbook/disks-naming.html#DISK-NAMING-PHYSICAL-TABLE */
    char device_ide[]= "/dev/rwd0";	/* raw winchester disk */
    char device_ide2[]="/dev/rad0";
    char device_ide3[]="/dev/wd0d";    	/* NetBSD 1.6.2 IDE */
    char device_ide4[]="/dev/rwd0c";    /* OpenBSD 3.5 IDE */
    char device_scsi[]="/dev/rda0";	/* raw scsci disk */
    char device_scsi2[]="/dev/rsd0c";	/* OpenBSD 3.5 SCSI */
    char device_optdisk[]="/dev/rod0";
    char device_ide_hd[]="/dev/ad0";
    char device_scsi_hd[]="/dev/da0";
    char device_cd[]="/dev/acd0";
    /* wd da */
    /* Disk IDE */
    for(i=0;i<8;i++)
    {
      device_ide[strlen(device_ide)-1]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_ide,debug,arch,testdisk_mode));
    }
    for(i=0;i<8;i++)
    {
      device_ide2[strlen(device_ide2)-1]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_ide2,debug,arch,testdisk_mode));
    }
    for(i=0;i<8;i++)
    {
      device_ide3[strlen(device_ide3)-2]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_ide3,debug,arch,testdisk_mode));
    }
    for(i=0;i<8;i++)
    {
      device_ide4[strlen(device_ide4)-2]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_ide4,debug,arch,testdisk_mode));
    }
    for(i=0;i<8;i++)
    {
      device_ide_hd[strlen(device_ide_hd)-1]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_ide_hd,debug,arch,testdisk_mode));
    }
    /* Disk SCSI */
    for(i=0;i<8;i++)
    {
      device_scsi[strlen(device_scsi)-1]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_scsi,debug,arch,testdisk_mode));
    }
    for(i=0;i<8;i++)
    {
      device_scsi2[strlen(device_scsi2)-1]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_scsi2,debug,arch,testdisk_mode));
    }
    for(i=0;i<8;i++)
    {
      device_scsi_hd[strlen(device_scsi_hd)-1]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_scsi_hd,debug,arch,testdisk_mode));
    }
    /* optical disks */
    for(i=0;i<8;i++)
    {
      device_optdisk[strlen(device_scsi)-1]='a'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_optdisk,debug,arch,testdisk_mode));
    } 
    /* CD */
    for(i=0;i<8;i++)
    {
      device_cd[strlen(device_cd)-1]='0'+i;
      list_disk=insert_new_disk(list_disk,file_test_availability(device_cd,debug,arch,testdisk_mode));
    }
  }
#endif
  return list_disk;
}

#if defined(__CYGWIN__) || defined(__MINGW32__)
static disk_t *disk_get_geometry_win32(HANDLE handle, const char *device, const int debug)
{
  disk_t *disk_car=NULL;
  DISK_GEOMETRY geometry;
  DWORD gotbytes;
  if (DeviceIoControl( handle
	, IOCTL_DISK_GET_DRIVE_GEOMETRY
	, NULL
	, 0
	, &geometry
	, sizeof(geometry)
	, &gotbytes
	, NULL
	)) {
    disk_car=(disk_t *)MALLOC(sizeof(*disk_car));
    disk_car->CHS.cylinder= geometry.Cylinders.QuadPart-1;
    disk_car->CHS.head=geometry.TracksPerCylinder-1;
    disk_car->CHS.sector= geometry.SectorsPerTrack;
    disk_car->sector_size=geometry.BytesPerSector;
    disk_car->disk_size=(uint64_t)(disk_car->CHS.cylinder+1)*(disk_car->CHS.head+1)*disk_car->CHS.sector*disk_car->sector_size;
    disk_car->disk_real_size=disk_car->disk_size;
    if(debug>1)
    {
      log_debug("disk_get_geometry_win32(%s) ok\n",device);
      log_debug("CHS (%u, %u, %u), sector_size=%u\n", disk_car->CHS.cylinder+1, disk_car->CHS.head+1, disk_car->CHS.sector, disk_car->sector_size);
    }
  }
  else
  {
    if(debug>1)
    {
#ifdef __MINGW32__
      log_error("DeviceIoControl failed\n");
#else
      LPVOID buf;
      FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
	  , NULL
	  , GetLastError()
	  , MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)
	  , (LPTSTR)&buf
	  , 0
	  , NULL 
	  );
      log_error("DeviceIoControl failed: %s:%s\n", device,(const char*)buf);
      LocalFree(buf);
#endif
    }
  }
  return disk_car;
}
#endif

#ifndef DJGPP
static disk_t *disk_get_geometry(const int hd_h, const char *device, const int debug)
{
  disk_t *disk_car=NULL;
  unsigned int sector_size=0;
#ifdef BLKSSZGET
  {
    int arg=0;
    if (ioctl(hd_h, BLKSSZGET, &arg) == 0)
    {
      sector_size=arg;
      if(debug>1)
      {
	log_debug("disk_get_geometry BLKSSZGET %s sector_size=%u\n",device,sector_size);
      }
    }
  }
#endif
#ifdef DIOCGSECTORSIZE
  {
    unsigned int arg=0;
    if(ioctl(hd_h,DIOCGSECTORSIZE,&arg)==0)
    {
      sector_size=arg;
      if(debug>1)
      {
	log_debug("disk_get_geometry DIOCGSECTORSIZE %s sector_size=%u\n",device,sector_size);
      }
    }
  }
#endif
#ifdef BLKFLSBUF
  /* Little trick from Linux fdisk */
  /* Blocks are visible in more than one way:
     e.g. as block on /dev/hda and as block on /dev/hda3
     By a bug in the Linux buffer cache, we will see the old
     contents of /dev/hda when the change was made to /dev/hda3.
     In order to avoid this, discard all blocks on /dev/hda. */
  ioctl(hd_h, BLKFLSBUF);	/* ignore errors */
#endif
#ifdef HDIO_GETGEO_BIG
  {
    struct hd_big_geometry geometry_big;
    if (ioctl(hd_h, HDIO_GETGEO_BIG, &geometry_big)>=0)
    { /* I can get the geometry */
      if(debug>1)
      {
	log_debug("disk_get_geometry HDIO_GETGEO_BIG %s Ok (%u,%u,%u)\n",device,geometry_big.cylinders,geometry_big.heads,geometry_big.sectors);
      }
      disk_car=(disk_t *)MALLOC(sizeof(*disk_car));
      disk_car->CHS.cylinder= geometry_big.cylinders-1;
      disk_car->CHS.head=geometry_big.heads-1;
      disk_car->CHS.sector= geometry_big.sectors;
    }
    else
    {
      if(debug>1)
      {
	log_error("disk_get_geometry HDIO_GETGEO_BIG %s failed %s\n",device,strerror(errno));
      }
    }
  }
#endif
#ifdef HDIO_GETGEO
  if (disk_car==NULL)
  {
    struct hd_geometry geometry;
    if(ioctl(hd_h, HDIO_GETGEO, &geometry)>=0)
    { /* I can get the geometry */
      if(debug>1)
      {
	log_debug("disk_get_geometry HDIO_GETGEO %s Ok (%u,%u,%u)\n",device,geometry.cylinders,geometry.heads,geometry.sectors);
      }
      disk_car=(disk_t *)MALLOC(sizeof(*disk_car));
      disk_car->CHS.cylinder= geometry.cylinders-1;
      disk_car->CHS.head=geometry.heads-1;
      disk_car->CHS.sector= geometry.sectors;
    }
  }
#endif
#ifdef DKIOCGGEOM
  if(disk_car==NULL)
  {
    struct dk_geom dkgeom;
    if (ioctl (hd_h, DKIOCGGEOM, &dkgeom)>=0) {
      disk_car=(disk_t *)MALLOC(sizeof(*disk_car));
      disk_car->CHS.cylinder= dkgeom.dkg_ncyl-1;
      disk_car->CHS.head=dkgeom.dkg_nhead-1;
      disk_car->CHS.sector=dkgeom.dkg_nsect;
    }
  }
#endif
  if(disk_car!=NULL && disk_car->CHS.sector>0)
  {
#ifdef BLKGETSIZE64
    uint64_t longsectors64=0;
    if (ioctl(hd_h, BLKGETSIZE64, &longsectors64)>=0)
    {
      unsigned int cylinder_max;
      /* Handle a strange bug */
      if(debug>1)
      {
	log_debug("disk_get_geometry BLKGETSIZE64 %s size %llu\n", device, (long long unsigned)longsectors64);
      }
      if(sector_size<=0)
      {
	sector_size=DEFAULT_SECTOR_SIZE;
      }
      cylinder_max=longsectors64 / ((uint64_t)disk_car->CHS.head+1) / (uint64_t)disk_car->CHS.sector/(uint64_t)sector_size-1;
      if(disk_car->CHS.cylinder!= cylinder_max)
      {
	log_warning("disk_get_geometry BLKGETSIZE64 %s number of cylinders %u !=  %u (calculated)\n",
	    device,disk_car->CHS.cylinder+1,cylinder_max+1);
	disk_car->CHS.cylinder= cylinder_max;
      }
    }
    else
#endif
    {
#ifdef BLKGETSIZE
      unsigned long longsectors=0;
      if (ioctl(hd_h, BLKGETSIZE, &longsectors)>=0)
      {
	unsigned int cylinder_max;
	/* Handle a strange bug */
	if(sector_size<=0)
	{
	  sector_size=DEFAULT_SECTOR_SIZE;
	}
	if(DEFAULT_SECTOR_SIZE!=sector_size)
	{
	  log_warning("disk_get_geometry, TestDisk assumes BLKGETSIZE returns the number of 512 byte sectors.\n");
	}
	if(debug>1)
	{
	  log_debug("disk_get_geometry BLKGETSIZE %s, number of sectors=%lu\n",device,longsectors);
	}
	cylinder_max=(uint64_t)longsectors * DEFAULT_SECTOR_SIZE/(uint64_t)sector_size / ((uint64_t)disk_car->CHS.head+1) / ((uint64_t)disk_car->CHS.sector)-1;
	if(disk_car->CHS.cylinder!=cylinder_max)
	{
	  log_warning("disk_get_geometry BLKGETSIZE %s number of cylinders %u !=  %u (calculated)\n",
	      device,disk_car->CHS.cylinder+1,cylinder_max+1);
	  disk_car->CHS.cylinder=cylinder_max;
	}
      }
#endif
    }
  }
#if defined(__CYGWIN__) || defined(__MINGW32__)
  if(disk_car==NULL)
  {
    HANDLE handle;
#if defined(__CYGWIN__)
    handle=(HANDLE)get_osfhandle(hd_h);
#else
    handle=(HANDLE)_get_osfhandle(hd_h);
#endif
    disk_car=disk_get_geometry_win32(handle,device,debug);
    if(disk_car!=NULL)
      sector_size=disk_car->sector_size;
  }
#endif
#ifdef DIOCGDINFO
  if(disk_car==NULL)
  {
    struct disklabel geometry;
    if (ioctl(hd_h, DIOCGDINFO, &geometry)==0)
    { /* I can get the geometry */
      if(debug>1)
      {
	log_debug("disk_get_geometry DIOCGDINFO %s Ok\n",device);
      }
      disk_car=(disk_t *)MALLOC(sizeof(*disk_car));
      disk_car->CHS.cylinder=geometry.d_ncylinders-1;
      disk_car->CHS.head=geometry.d_ntracks-1;
      disk_car->CHS.sector=geometry.d_nsectors;
      sector_size=geometry.d_secsize;
    }
    else
    {
      if(debug>1)
      {
	log_error("disk_get_geometry DIOCGDINFO %s failed %s\n",device,strerror(errno));
      }
    }
  }
#endif
#ifdef DIOCGFWSECTORS
  if(disk_car==NULL)
  {
    int error;
    unsigned int u,sectors,heads,cyls;
    off_t o;
    error = ioctl(hd_h, DIOCGFWSECTORS, &u);
    if(error==0 && u>0)
    {
      sectors=u;
    }
    else
    {
      sectors=63;
      if(debug>1)
      {
	log_error("disk_get_geometry DIOCGFWSECTORS %s failed %s\n",device,strerror(errno));
      }
    }
    error = ioctl(hd_h, DIOCGFWHEADS, &u);
    if(error==0 && u>0)
    {
      heads=u;
    }
    else
    {
      heads=255;
      if(debug>1)
      {
	log_error("disk_get_geometry DIOCGFWHEADS %s failed %s\n",device,strerror(errno));
      }
    }
    error = ioctl(hd_h, DIOCGMEDIASIZE, &o);
    if(error==0)
    {
      if(sector_size<=0)
      {
	sector_size=DEFAULT_SECTOR_SIZE;
      }
      cyls = o / (sector_size * heads * sectors);
      disk_car=(disk_t *)MALLOC(sizeof(*disk_car));
      disk_car->CHS.cylinder=cyls-1;
      disk_car->CHS.head=heads-1;
      disk_car->CHS.sector=sectors;
    }
    else
    {
      if(debug>1)
      {
	log_error("disk_get_geometry DIOCGMEDIASIZE %s failed %s\n",device,strerror(errno));
      }
    }
  }
#endif
  if(disk_car==NULL)
  {
    if(sector_size>0)
    {
      disk_car=(disk_t *)MALLOC(sizeof(*disk_car));
      disk_car->CHS.cylinder=0;
      disk_car->CHS.head=0;
      disk_car->CHS.sector=0;
      disk_car->sector_size=sector_size;
    }
  }
  else
  {
    if(sector_size<=0)
    {
      sector_size=DEFAULT_SECTOR_SIZE;
    }
    disk_car->sector_size=sector_size;
  }
  return disk_car;
}
#endif

static const char *file_description(disk_t *disk_car)
{
  const struct info_file_struct *data=disk_car->data;
  char buffer_disk_size[100];
  snprintf(disk_car->description_txt, sizeof(disk_car->description_txt),"Disk %s - %s - CHS %u %u %u%s",
      data->file_name, size_to_unit(disk_car->disk_size,buffer_disk_size),
      disk_car->CHS.cylinder+1, disk_car->CHS.head+1, disk_car->CHS.sector,((data->mode&O_RDWR)==O_RDWR?"":" (RO)"));
  return disk_car->description_txt;
}

static const char *file_description_short(disk_t *disk_car)
{
  const struct info_file_struct *data=disk_car->data;
  char buffer_disk_size[100];
  snprintf(disk_car->description_short_txt, sizeof(disk_car->description_txt),"Disk %s - %s%s",
      data->file_name, size_to_unit(disk_car->disk_size,buffer_disk_size),((data->mode&O_RDWR)==O_RDWR?"":" (RO)"));
  return disk_car->description_short_txt;
}

static int file_clean(disk_t *disk_car)
{
  if(disk_car->data!=NULL)
  {
    struct info_file_struct *data=disk_car->data;
    /*
#ifdef BLKRRPART
    if (ioctl(data->handle, BLKRRPART, NULL)) {
      log_error("%s BLKRRPART failed\n",disk_car->description(disk_car));
    } else {
      log_debug("%s BLKRRPART ok\n",disk_car->description(disk_car));
    }
#endif
    */
    close(data->handle);
    if(data->buffer!=NULL)
    {
      free(data->buffer);
      data->buffer=NULL;
    }
    free(disk_car->data);
    disk_car->data=NULL;
  }
  return 0;
}

static int file_read(disk_t *disk_car,const unsigned int count, void *nom_buffer, const uint64_t offset)
{
  struct info_file_struct *data=disk_car->data;
  if(count%disk_car->sector_size!=0 || offset%disk_car->sector_size!=0
#ifdef O_DIRECT
      || ((data->mode&O_DIRECT)!=0 &&
	(nom_buffer!=data->buffer ||
	 data->buffer_size<((offset%disk_car->sector_size+count+disk_car->sector_size-1) / disk_car->sector_size * disk_car->sector_size))
	&& (((size_t)(nom_buffer)) && (disk_car->sector_size-1)!=0))
#endif
    )
  {
    if(data->buffer==NULL)
    {
      data->buffer_size=128*512;
    }
    while(data->buffer_size<((offset%disk_car->sector_size+count+disk_car->sector_size-1)
	  / disk_car->sector_size * disk_car->sector_size))
    {
      if(data->buffer!=NULL)
      {
	free(data->buffer);
	data->buffer=NULL;
      }
      data->buffer_size*=2;
    }
    if(data->buffer==NULL)
    {
      data->buffer=(char*)MALLOC(data->buffer_size);
    }
    /*
    log_debug("file_read handle unaligned data\n");
     */
    if(file_read(disk_car,(offset%disk_car->sector_size+count+disk_car->sector_size-1)
      / disk_car->sector_size * disk_car->sector_size, data->buffer,
      offset/disk_car->sector_size*disk_car->sector_size))
    {
      return -1;
    }
    memcpy(nom_buffer,(char*)data->buffer+(offset%disk_car->sector_size),count);
#ifdef DEBUG
    log_trace("memcpy\n");
#endif
    return 0;
  }
  {
    long int ret=-1;
#ifdef DEBUG
      log_debug("file_read(%d,%u,buffer,%lu(%u/%u/%u))\n", data->handle,
	  (unsigned)(count/disk_car->sector_size), (long unsigned int)(offset/disk_car->sector_size),
	  offset2cylinder(disk_car,offset), offset2head(disk_car,offset), offset2sector(disk_car,offset));
#endif
#if defined(HAVE_PREAD) && !defined(__CYGWIN__)
      ret=pread(data->handle,nom_buffer,count,offset+data->offset);
      if(ret<0 && errno == ENOSYS)
#endif
      {
#ifdef __MINGW32__
	if(_lseeki64(data->handle,offset+data->offset,SEEK_SET)==-1)
	{
	  log_error("file_read(%d,%u,buffer,%lu(%u/%u/%u)) seek err %s\n", data->handle,
	      (unsigned)(count/disk_car->sector_size), (long unsigned int)(offset/disk_car->sector_size),
	      offset2cylinder(disk_car,offset), offset2head(disk_car,offset), offset2sector(disk_car,offset),
	      strerror(errno));
	  return -1;
	}
#else
	if(lseek(data->handle,offset+data->offset,SEEK_SET)==(off_t)-1)
	{
	  log_error("file_read(%d,%u,buffer,%lu(%u/%u/%u)) lseek err %s\n", data->handle,
	      (unsigned)(count/disk_car->sector_size), (long unsigned int)(offset/disk_car->sector_size),
	      offset2cylinder(disk_car,offset), offset2head(disk_car,offset), offset2sector(disk_car,offset),
	      strerror(errno));
	  return -1;
	}
#endif
#if defined(__CYGWIN__)
	{
	  /* November 28, 2004, CGR: cygwin read function is about 10 times slower
	     because it reads 60k each time, so lets call ReadFile directly */
	  DWORD dwByteRead;
	  HANDLE handle=(HANDLE)get_osfhandle(data->handle);
	  ret=ReadFile(handle, nom_buffer,count,&dwByteRead,NULL);
	  if(ret==0)
	    ret=-1;
	  else
	    ret=dwByteRead;
	}
#else
	ret=read(data->handle, nom_buffer, count);
#endif
      }
    /* Test only */
    /*
    if(!(offset+count<=120*512 || offset>=(120+1)*512))
    {
      log_warning("Simulated read failure\n");
      return -1;
    }
    */
    if(ret!=count)
    {
      if(offset+count <= disk_car->disk_size && offset+count <= disk_car->disk_real_size)
      {
	log_error("file_read(%d,%u,buffer,%lu(%u/%u/%u)) read err: ", data->handle,
	    (unsigned)(count/disk_car->sector_size), (long unsigned)(offset/disk_car->sector_size),
	    offset2cylinder(disk_car,offset), offset2head(disk_car,offset), offset2sector(disk_car,offset));
	if(ret<0)
	  log_error("%s\n", strerror(errno));
	else if(ret==0)
	  log_error("read after end of file\n");
	else
	  log_error("Partial read\n");
      }
      if(ret<=0)
	return -1;
      memset((char*)nom_buffer+ret,0,count-ret);
    }
  }
  return 0;
}

static int file_write(disk_t *disk_car,const unsigned int count, const void *nom_buffer, const uint64_t offset)
{
  if(count%disk_car->sector_size!=0 || offset%disk_car->sector_size!=0)
  {
    int ret;
    char*buffer=(char*)MALLOC((count/disk_car->sector_size+2)*disk_car->sector_size);
    if(file_read(disk_car,(unsigned)(((count/disk_car->sector_size)+2)*disk_car->sector_size),buffer,offset/disk_car->sector_size*disk_car->sector_size))
    {
      log_error("read failed but try to write anyway");
      memset(buffer,0,(count/disk_car->sector_size+2)*disk_car->sector_size);
    }
    memcpy(buffer+(offset%disk_car->sector_size),nom_buffer,count);
    ret=file_write(disk_car,(unsigned)(((count/disk_car->sector_size)+2)*disk_car->sector_size),buffer,offset/disk_car->sector_size*disk_car->sector_size);
    free(buffer);
    return ret;
  }
  else
  {
    long int ret=-1;
    struct info_file_struct *data=disk_car->data;
/*    log_debug("file_write offset=%lu data->offset=%lu\n",(long unsigned)offset,(long unsigned)data->offset); */
#if defined(HAVE_PWRITE) && !defined(__CYGWIN__)
      ret=pwrite(data->handle,nom_buffer,count,offset+data->offset);
      if(ret<0 && errno == ENOSYS)
#endif
      {
#ifdef __MINGW32__
	if(_lseeki64(data->handle,offset+data->offset,SEEK_SET)==-1)
	{
	  log_error("file_write(%d,%u,buffer,%lu(%u/%u/%u)) seek err %s\n", data->handle,(unsigned)(count/disk_car->sector_size),(long unsigned)(offset/disk_car->sector_size),
	      offset2cylinder(disk_car,offset),offset2head(disk_car,offset),offset2sector(disk_car,offset),strerror(errno));
	  return -1;
	}
#else
	if(lseek(data->handle,offset+data->offset,SEEK_SET)==-1)
	{
	  log_error("file_write(%d,%u,buffer,%lu(%u/%u/%u)) seek err %s\n", data->handle,(unsigned)(count/disk_car->sector_size),(long unsigned)(offset/disk_car->sector_size),
	      offset2cylinder(disk_car,offset),offset2head(disk_car,offset),offset2sector(disk_car,offset),strerror(errno));
	  return -1;
	}
#endif
	ret=write(data->handle, nom_buffer, count);
      }
    disk_car->write_used=1;
    if(ret!=count)
    {
      log_error("file_write(%d,%u,buffer,%lu(%u/%u/%u)) write err %s\n", data->handle,(unsigned)(count/disk_car->sector_size),(long unsigned)(offset/disk_car->sector_size),
	  offset2cylinder(disk_car,offset),offset2head(disk_car,offset),offset2sector(disk_car,offset),(ret<0?strerror(errno):"File truncated"));
      return -1;
    }
    return 0;
  }
}

static int file_nowrite(disk_t *disk_car,const unsigned int count, const void *nom_buffer, const uint64_t offset)
{
  struct info_file_struct *data=disk_car->data;
  log_warning("file_nowrite(%d,%u,buffer,%lu(%u/%u/%u)) write refused\n", data->handle,
      (unsigned)(count/disk_car->sector_size),(long unsigned)(offset/disk_car->sector_size),
      offset2cylinder(disk_car,offset),offset2head(disk_car,offset),offset2sector(disk_car,offset));
  return -1;
}

static void autoset_geometry(disk_t * disk_car, const unsigned char *buffer, const int debug)
{
  if(disk_car->arch->get_geometry_from_mbr!=NULL)
  {
    CHS_t geometry;
    geometry.cylinder=0;
    geometry.head=0;
    geometry.sector=0;
    disk_car->arch->get_geometry_from_mbr(buffer, debug, &geometry);
    disk_car->autodetect=1;
    if(geometry.sector>0)
    {
      disk_car->CHS.head=geometry.head;
      disk_car->CHS.sector=geometry.sector;
    }
    else
    {
      disk_car->CHS.head=255-1;
      disk_car->CHS.sector=63;
    }
  }
  /* Round up because file is often truncated. */
  if((disk_car->disk_size/disk_car->sector_size+(uint64_t)disk_car->CHS.sector*(disk_car->CHS.head+1)-1)/disk_car->CHS.sector/(disk_car->CHS.head+1)==0)
    disk_car->CHS.cylinder=0;
  else
    disk_car->CHS.cylinder=(disk_car->disk_size/disk_car->sector_size+(uint64_t)disk_car->CHS.sector*(disk_car->CHS.head+1)-1)/disk_car->CHS.sector/(disk_car->CHS.head+1)-1;
}

static void compute_device_size(disk_t *disk_car)
{
  /* This function can failed if there are bad sectors */
  uint64_t min_offset, max_offset;
  char *buffer=MALLOC(disk_car->sector_size);
  min_offset=0;
  max_offset=disk_car->sector_size;
  /* Search the maximum device size */
  while(disk_car->read(disk_car,1,buffer,max_offset)==0)
  {
    min_offset=max_offset;
    max_offset*=2;
  }
  /* Search the device size by dichotomy */
  while(min_offset<=max_offset)
  {
    uint64_t cur_offset;
    cur_offset=(min_offset+max_offset)/2/disk_car->sector_size*disk_car->sector_size;
    if(disk_car->read(disk_car,1,buffer,cur_offset)==0)
      min_offset=cur_offset+disk_car->sector_size;
    else
    {
      if(cur_offset>=disk_car->sector_size)
	max_offset=cur_offset-disk_car->sector_size;
      else
	break;
    }
  }
  if(disk_car->read(disk_car,1,buffer,min_offset)==0)
    min_offset+=disk_car->sector_size;
  disk_car->disk_size=min_offset;
  disk_car->disk_real_size=disk_car->disk_size;
  free(buffer);
}

disk_t *file_test_availability(const char *device, const int debug, const arch_fnct_t *arch, const int testdisk_mode)
{
  disk_t *disk_car=NULL;
  unsigned int offset=0;
  int hd_h=-1;
  int mode=0;
  int try_readonly=1;
  if((testdisk_mode&TESTDISK_O_RDWR)==TESTDISK_O_RDWR)
  {
    mode=O_RDWR|O_BINARY;
#ifdef O_LARGEFILE
    mode|=O_LARGEFILE;
#endif
#ifdef O_DIRECT
    if((testdisk_mode&TESTDISK_O_DIRECT)==TESTDISK_O_DIRECT)
      mode|=O_DIRECT;
#endif
    hd_h = open(device, mode);
    if(hd_h<0)
    {
      if(debug>1)
      {
	log_error("file_test_availability %s:%s\n", device,strerror(errno));
      }
      switch(errno)
      {
	case ENXIO:
	case ENOENT:
#ifdef ENOMEDIUM
	case ENOMEDIUM:
#endif
	  try_readonly=0;
	  break;
	default:
	  break;
      }
    }
  }
  if(hd_h<0 && try_readonly>0)
  {
    mode=O_RDONLY|O_BINARY;
#ifdef O_LARGEFILE
    mode|=O_LARGEFILE;
#endif
#ifdef O_DIRECT
    if((testdisk_mode&TESTDISK_O_DIRECT)!=0)
      mode|=O_DIRECT;
#endif
    hd_h = open(device, mode);
    if(hd_h<0)
    {
      if(debug>1)
      {
	log_error("file_test_availability %s:%s\n", device,strerror(errno));
      }
    }
  }

  if(hd_h>=0)
  {
    struct stat stat_rec;
    if(debug>1)
    {
      log_debug("file_test_availability %s: open ok\n",device);
    }
    if(fstat(hd_h,&stat_rec)<0)
    {
      if(debug>1)
      {
	log_error("file_test_availability %s: fstat failed\n",device);
      }
#ifdef __MINGW32__
      stat_rec.st_mode=S_IFBLK;
      stat_rec.st_size=0;
#else
      close(hd_h);
      return NULL;
#endif
    }
#ifndef DJGPP
    if(!S_ISREG(stat_rec.st_mode))
    {
      if(debug>1)
      {
	log_debug("file_test_availability %s: not a regular file\n",device);
      }
      disk_car=disk_get_geometry(hd_h, device, debug);
      if(disk_car!=NULL)
      {
	disk_car->arch=arch;
	disk_car->autodetect=0;
	disk_car->disk_size=(uint64_t)(disk_car->CHS.cylinder+1)*(disk_car->CHS.head+1)*disk_car->CHS.sector*disk_car->sector_size;
      }
    }
#endif
    if(disk_car==NULL)
    {
      disk_car=(disk_t *)MALLOC(sizeof(*disk_car));
      disk_car->arch=arch;
      disk_car->sector_size=DEFAULT_SECTOR_SIZE;
      disk_car->disk_size=0;
    }
    disk_car->disk_real_size=disk_car->disk_size;
    if(disk_car->disk_size==0)
    {
      unsigned char *buffer;
      const uint8_t evf_file_signature[8] = { 'E', 'V', 'F', 0x09, 0x0D, 0x0A, 0xFF, 0x00 };
      buffer=(unsigned char*)MALLOC(DEFAULT_SECTOR_SIZE);
      if(read(hd_h,buffer,DEFAULT_SECTOR_SIZE)<0)
      {
	memset(buffer,0,DEFAULT_SECTOR_SIZE);
      }
      if(memcmp(buffer,"DOSEMU",6)==0 && *(unsigned long*)(buffer+11)>0)
      {
	log_info("%s DOSEMU\n",device);
	disk_car->CHS.cylinder=*(unsigned long*)(buffer+15)-1;
	disk_car->CHS.head=*(unsigned long*)(buffer+7)-1;
	disk_car->CHS.sector=*(unsigned long*)(buffer+11);
	disk_car->disk_size=(uint64_t)(disk_car->CHS.cylinder+1)*(disk_car->CHS.head+1)*disk_car->CHS.sector*disk_car->sector_size;
	disk_car->disk_real_size=disk_car->disk_size;
	offset=*(unsigned long*)(buffer+19);
	disk_car->autodetect=0;
      }
      else if(memcmp(buffer, evf_file_signature, 8)==0)
      {
	free(buffer);
	close(hd_h);
#if defined(HAVE_LIBEWF_H) && defined(HAVE_LIBEWF)
	return fewf_init(device,debug,arch,testdisk_mode);
#else
	return NULL;
#endif
      }
      else
      {
	disk_car->CHS.cylinder=0;
	disk_car->CHS.head=255-1;
	disk_car->CHS.sector=63;
	if(stat_rec.st_size>offset)
	{
	  disk_car->disk_size=(uint64_t)(stat_rec.st_size-offset+disk_car->sector_size-1)/disk_car->sector_size*disk_car->sector_size;
	}
	else
	{
	  off_t pos;
	  pos=lseek(hd_h,0,SEEK_END);
	  if(pos>offset)
	    disk_car->disk_size=(uint64_t)(pos-offset);
	  else
	    disk_car->disk_size=0;
	}
	disk_car->disk_real_size=disk_car->disk_size;
	autoset_geometry(disk_car,buffer,debug);
      }
      free(buffer);
    }
    if(disk_car!=NULL)
    {
      struct info_file_struct *data;
      data=MALLOC(sizeof(*data));
      data->offset=offset;
      strncpy(data->file_name,device,sizeof(data->file_name));
      data->file_name[sizeof(data->file_name)-1]='\0';
      data->handle=hd_h;
#if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE)
      posix_fadvise(hd_h,0,0,POSIX_FADV_SEQUENTIAL);
#endif
      data->mode=mode;
      data->buffer=NULL;
      disk_car->device=strdup(device);
      disk_car->write_used=0;
      disk_car->description_txt[0]='\0';
      disk_car->description=file_description;
      disk_car->description_short=file_description_short;
      disk_car->read=file_read;
      disk_car->write=((mode&O_RDWR)==O_RDWR?file_write:file_nowrite);
      disk_car->clean=file_clean;
      disk_car->data=data;
      if(disk_car->disk_size==0)
      {
	/* Handle Mac */
	compute_device_size(disk_car);
	if(debug>1)
	{
	  log_debug("file_test_availability compute_device_size %s size %llu\n", device, (long long unsigned)disk_car->disk_size);
	}
        /* Round up because file is often truncated. */
	if((disk_car->disk_size/disk_car->sector_size+(uint64_t)disk_car->CHS.sector*(disk_car->CHS.head+1)-1)/disk_car->CHS.sector/(disk_car->CHS.head+1)==0)
	  disk_car->CHS.cylinder=0;
	else
	  disk_car->CHS.cylinder=(disk_car->disk_size/disk_car->sector_size+(uint64_t)disk_car->CHS.sector*(disk_car->CHS.head+1)-1)/disk_car->CHS.sector/(disk_car->CHS.head+1)-1;
}
      if(disk_car->disk_size==0)
      {
	log_warning("Warning: can't get size for %s\n",device);
	free(data);
	free(disk_car->device);
	free(disk_car);
	close(hd_h);
	return NULL;
      }
      return disk_car;
    }
    close(hd_h);
  }
  else if(strncmp(device,"/dev/",5)!=0)
  {
#if defined(HAVE_LIBEWF_H) && defined(HAVE_LIBEWF) && defined(HAVE_GLOB_H)
    return fewf_init(device,debug,arch,testdisk_mode);
#endif
  }
#ifdef DJGPP
  {
   int   dos_nr=0;
   /* Check for device name. It must be like /dev/sdaXX and *
    * XX is a value between 128 and 135 (0x80 to 0x88)      */
   if(strcmp(device, "/dev/sda128") == 0)
     dos_nr = 0x80;
   else if(strcmp(device, "/dev/sda129") == 0)
     dos_nr = 0x81;
   else if(strcmp(device, "/dev/sda130") == 0)
     dos_nr = 0x82;
   else if(strcmp(device, "/dev/sda131") == 0)
     dos_nr = 0x83;
   else if(strcmp(device, "/dev/sda132") == 0)
     dos_nr = 0x84;
   else if(strcmp(device, "/dev/sda133") == 0)
     dos_nr = 0x85;
   else if(strcmp(device, "/dev/sda134") == 0)
     dos_nr = 0x86;
   else if(strcmp(device, "/dev/sda135") == 0)
     dos_nr = 0x87;
   if(dos_nr>0)
     disk_car = hd_identify(debug, dos_nr, arch, testdisk_mode);
  }
#endif
  return disk_car;
}

void hd_update_geometry(disk_t *disk_car, const int allow_partial_last_cylinder, const int debug)
{
  unsigned char *buffer;
  uint64_t pos;
  CHS_t pos_CHS;
  buffer=MALLOC(disk_car->sector_size);
  if(disk_car->autodetect!=0)
  {
    if(disk_car->read(disk_car,disk_car->sector_size, buffer, 0)==0)
    {
      if(debug>1)
      {
	log_trace("autoset_geometry\n");
      }
      autoset_geometry(disk_car,buffer,1);
    }
  }
  dup_CHS(&pos_CHS,&disk_car->CHS);
  pos_CHS.cylinder++;
  if(allow_partial_last_cylinder) {
    pos_CHS.head=0;
    pos_CHS.sector=1;
  }
  pos=CHS2offset(disk_car,&pos_CHS);
#ifdef DJGPP
  if(disk_car->description==disk_description)
  {
    struct info_disk_struct*data=disk_car->data;
    data->CHSR.cylinder=0;
  }
#endif
  if((unsigned int)(disk_car->CHS.cylinder+1)!=0)	/* Avoid to wrap */
  {
    if(disk_car->read(disk_car,disk_car->sector_size, buffer, pos)==0)
    {
      disk_car->CHS.cylinder++;
      if(disk_car->disk_size < (uint64_t)(disk_car->CHS.cylinder+1)*(disk_car->CHS.head+1)*disk_car->CHS.sector*disk_car->sector_size)
      {
	disk_car->disk_size=(uint64_t)(disk_car->CHS.cylinder+1)*(disk_car->CHS.head+1)*disk_car->CHS.sector*disk_car->sector_size;
	log_info("Computes LBA from CHS for %s\n",disk_car->description(disk_car));
      }
    }
  }
#ifdef DJGPP
  if(disk_car->description==disk_description)
  {
    struct info_disk_struct*data=disk_car->data;
    data->CHSR.cylinder=disk_car->CHS.cylinder;
  }
#endif
  free(buffer);
}

void hd_update_all_geometry(const list_disk_t * list_disk, const int allow_partial_last_cylinder, const int debug)
{
  const list_disk_t *element_disk;
  if(debug>1)
  {
    log_trace("hd_update_all_geometry\n");
  }
  for(element_disk=list_disk;element_disk!=NULL;element_disk=element_disk->next)
    hd_update_geometry(element_disk->disk,allow_partial_last_cylinder,debug);
}

#if defined(__CYGWIN__) || defined(__MINGW32__)
// Try to handle cdrom
static const char *file_win32_description(disk_t *disk_car);
static const char *file_win32_description_short(disk_t *disk_car);
static int file_win32_clean(disk_t *disk_car);
static unsigned int file_win32_compute_sector_size(HANDLE handle, const unsigned int sector_size_default);
static int file_win32_read(disk_t *disk_car, const unsigned int count, void *nom_buffer, const uint64_t offset);
static int file_win32_write(disk_t *disk_car,const unsigned int count, const void *nom_buffer, const uint64_t offset);
static int file_win32_nowrite(disk_t *disk_car, const unsigned int count, const void *nom_buffer, const uint64_t offset);
uint64_t filewin32_getfilesize(HANDLE handle, const char *device);
uint64_t filewin32_setfilepointer(HANDLE handle, const char *device);

struct info_file_win32_struct
{
  HANDLE handle;
  uint64_t offset;
  char file_name[DISKNAME_MAX];
  int mode;
  void *buffer;
  unsigned int buffer_size;
};

uint64_t filewin32_getfilesize(HANDLE handle, const char *device)
{
  DWORD lpFileSizeLow;
  DWORD lpFileSizeHigh;
  lpFileSizeLow=GetFileSize(handle,&lpFileSizeHigh);
  if(lpFileSizeLow==INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
  {
    LPVOID lpMsgBuf;
    DWORD dw = GetLastError(); 
    FormatMessage(
	FORMAT_MESSAGE_ALLOCATE_BUFFER | 
	FORMAT_MESSAGE_FROM_SYSTEM,
	NULL,
	dw,
	MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
	(LPTSTR) &lpMsgBuf,
	0, NULL );
    log_error("filewin32_getfilesize(%s) GetFileSize err %s\n", device, (char*)lpMsgBuf);
    LocalFree(lpMsgBuf);
    return 0;
  }
  log_debug("filewin32_getfilesize(%s) ok\n",device);
  return lpFileSizeLow+((uint64_t)lpFileSizeHigh>>32);
}

uint64_t filewin32_setfilepointer(HANDLE handle, const char *device)
{
  LARGE_INTEGER li;
  li.QuadPart = 0;
  li.LowPart = SetFilePointer(handle, li.LowPart, &li.HighPart, FILE_END);
  if(li.LowPart==INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
  {
    LPVOID lpMsgBuf;
    DWORD dw = GetLastError(); 
    FormatMessage(
	FORMAT_MESSAGE_ALLOCATE_BUFFER | 
	FORMAT_MESSAGE_FROM_SYSTEM,
	NULL,
	dw,
	MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
	(LPTSTR) &lpMsgBuf,
	0, NULL );
    log_error("filewin32_setfilepointer(%s) SetFilePointer err %s\n", device, (char*)lpMsgBuf);
    LocalFree(lpMsgBuf);
    return 0;
  }
  log_debug("filewin32_setfilepointer(%s) ok\n",device);
  return li.LowPart+((uint64_t)li.HighPart>>32);
}

disk_t *file_test_availability_win32(const char *device, const int debug, const arch_fnct_t *arch, const int testdisk_mode)
{
  disk_t *disk_car=NULL;
  unsigned int offset=0;
  HANDLE handle=INVALID_HANDLE_VALUE;
  int mode=0;
  int try_readonly=1;
  if((testdisk_mode&TESTDISK_O_RDWR)==TESTDISK_O_RDWR)
  {
    mode = FILE_READ_DATA | FILE_WRITE_DATA;
    handle = CreateFile(device,mode, (FILE_SHARE_WRITE | FILE_SHARE_READ),
	NULL, OPEN_EXISTING, 0, NULL);
    if (handle == INVALID_HANDLE_VALUE)
    {
      if(debug>1)
      {
#ifdef __MINGW32__
	log_error("file_test_availability_win32 RW failed %s\n", device);
#else
	LPVOID buf;
	FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
	    , NULL
	    , GetLastError()
	    , MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)
	    , (LPTSTR)&buf
	    , 0
	    , NULL 
	    );
	log_error("file_test_availability_win32 RW failed: %s:%s\n", device,(const char*)buf);
	LocalFree(buf);
#endif
      }
      try_readonly=0;
    }
  }
  if(handle==INVALID_HANDLE_VALUE && try_readonly>0)
  {
    mode = FILE_READ_DATA;
    handle = CreateFile(device,mode, (FILE_SHARE_WRITE | FILE_SHARE_READ),
	NULL, OPEN_EXISTING, 0, NULL);
    if (handle == INVALID_HANDLE_VALUE)
    {
      if(debug>1)
      {
#ifdef __MINGW32__
	log_error("file_test_availability_win32 RO %s error\n", device);
#else
	LPVOID buf;
	FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
	    , NULL
	    , GetLastError()
	    , MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)
	    , (LPTSTR)&buf
	    , 0
	    , NULL 
	    );
	log_error("file_test_availability_win32 RO failed: %s:%s\n", device,(const char*)buf);
	LocalFree(buf);
#endif
      }
    }
  }
  if(handle!=INVALID_HANDLE_VALUE)
  {
    struct info_file_win32_struct *data;
    disk_car=disk_get_geometry_win32(handle,device,debug);
    if (disk_car==NULL)
    {
      uint64_t i64FreeBytesToCaller, i64TotalBytes, i64FreeBytes;
      DWORD dwSectPerClust, 
	    dwBytesPerSect, 
	    dwFreeClusters, 
	    dwTotalClusters;
      disk_car=(disk_t *)MALLOC(sizeof(*disk_car));
      disk_car->disk_size=0;
      disk_car->sector_size=0;
      disk_car->CHS.cylinder=0;
      disk_car->CHS.head=0;
      disk_car->CHS.sector=1;
      if(GetDiskFreeSpaceA (&device[4], 
	    &dwSectPerClust, 
	    &dwBytesPerSect,
	    &dwFreeClusters, 
	    &dwTotalClusters)!=0)
      {
	disk_car->sector_size=dwBytesPerSect;
      }
      if(GetDiskFreeSpaceEx (&device[4],
	  (PULARGE_INTEGER)&i64FreeBytesToCaller,
	  (PULARGE_INTEGER)&i64TotalBytes,
	  (PULARGE_INTEGER)&i64FreeBytes)!=0)
      {
	disk_car->disk_size=i64TotalBytes;
	if(disk_car->sector_size==0)
	  disk_car->sector_size=file_win32_compute_sector_size(handle,DEFAULT_SECTOR_SIZE);
      }
      else
      {
	disk_car->sector_size=file_win32_compute_sector_size(handle,DEFAULT_SECTOR_SIZE);
	disk_car->disk_size=filewin32_getfilesize(handle, device);
	if(disk_car->disk_size==0)
	  disk_car->disk_size=filewin32_setfilepointer(handle, device);
      }
    }
    disk_car->arch=arch;
    data=MALLOC(sizeof(*data));
    data->offset=offset;
    strncpy(data->file_name,device,sizeof(data->file_name));
    data->file_name[sizeof(data->file_name)-1]='\0';
    data->handle=handle;
    data->mode=mode;
    data->buffer=NULL;
    disk_car->device=strdup(device);
    disk_car->write_used=0;
    disk_car->description_txt[0]='\0';
    disk_car->description=file_win32_description;
    disk_car->description_short=file_win32_description_short;
    disk_car->read=file_win32_read;
    disk_car->write=((data->mode&FILE_WRITE_DATA)==FILE_WRITE_DATA?file_win32_write:file_win32_nowrite);
    disk_car->clean=file_win32_clean;
    disk_car->data=data;
    if(disk_car->disk_size==0)
    {
      compute_device_size(disk_car);
      if(debug>1)
      {
	log_debug("file_test_availability compute_device_size %s size %llu\n", device, (long long unsigned)disk_car->disk_size);
      }
    }
    if(disk_car->disk_size==0)
    {
      log_warning("Warning: can't get size for %s\n",device);
      free(data);
      free(disk_car->device);
      free(disk_car);
      CloseHandle(handle);
      return NULL;
    }
    disk_car->CHS.cylinder=(disk_car->disk_size/(disk_car->CHS.head+1))/disk_car->CHS.sector/disk_car->sector_size-1;
    return disk_car;
  }
  return NULL;
}

static const char *file_win32_description(disk_t *disk_car)
{
  struct info_file_win32_struct *data=disk_car->data;
  char buffer_disk_size[100];
  if(data->file_name[0]=='\\' && data->file_name[1]=='\\' && data->file_name[2]=='.' && data->file_name[3]=='\\' && data->file_name[5]==':')
    snprintf(disk_car->description_txt, sizeof(disk_car->description_txt),"Drive %c: - %s - CHS %u %u %u%s",
	data->file_name[4], size_to_unit(disk_car->disk_size,buffer_disk_size),
	disk_car->CHS.cylinder+1, disk_car->CHS.head+1, disk_car->CHS.sector,
	((data->mode&FILE_WRITE_DATA)==FILE_WRITE_DATA?"":" (RO)"));
  else
    snprintf(disk_car->description_txt, sizeof(disk_car->description_txt),"Disk %s - %s - CHS %u %u %u%s",
	data->file_name, size_to_unit(disk_car->disk_size,buffer_disk_size),
	disk_car->CHS.cylinder+1, disk_car->CHS.head+1, disk_car->CHS.sector,
	((data->mode&FILE_WRITE_DATA)==FILE_WRITE_DATA?"":" (RO)"));
  return disk_car->description_txt;
}

static const char *file_win32_description_short(disk_t *disk_car)
{
  struct info_file_win32_struct *data=disk_car->data;
  char buffer_disk_size[100];
  if(data->file_name[0]=='\\' && data->file_name[1]=='\\' && data->file_name[2]=='.' && data->file_name[3]=='\\' && data->file_name[5]==':')
    snprintf(disk_car->description_short_txt, sizeof(disk_car->description_txt),"Drive %c: - %s%s",
	data->file_name[4], size_to_unit(disk_car->disk_size,buffer_disk_size),
	((data->mode&FILE_WRITE_DATA)==FILE_WRITE_DATA?"":" (RO)"));
  else
    snprintf(disk_car->description_short_txt, sizeof(disk_car->description_txt),"Disk %s - %s%s",
	data->file_name, size_to_unit(disk_car->disk_size,buffer_disk_size),
	((data->mode&FILE_WRITE_DATA)==FILE_WRITE_DATA?"":" (RO)"));
  return disk_car->description_short_txt;
}

static int file_win32_clean(disk_t *disk_car)
{
  if(disk_car->data!=NULL)
  {
    struct info_file_win32_struct *data=disk_car->data;
    CloseHandle(data->handle);
    if(data->buffer!=NULL)
    {
      free(data->buffer);
      data->buffer=NULL;
    }
    free(disk_car->data);
    disk_car->data=NULL;
  }
  return 0;
}

static unsigned int file_win32_compute_sector_size(HANDLE handle, const unsigned int sector_size_default)
{
  unsigned int sector_size;
  char *buffer=MALLOC(4096);
  for(sector_size=sector_size_default;sector_size<=4096;sector_size*=2)
  {
    long int ret;
    DWORD dwByteRead;
    ret=ReadFile(handle, buffer,sector_size,&dwByteRead,NULL);
    if(ret && dwByteRead==sector_size)
    {
      free(buffer);
      return sector_size;
    }
  }
  free(buffer);
  return sector_size_default;
}

static int file_win32_read(disk_t *disk_car,const unsigned int count, void *nom_buffer, const uint64_t offset)
{
  struct info_file_win32_struct *data=disk_car->data;
  if(count%disk_car->sector_size!=0 || offset%disk_car->sector_size!=0
#ifdef O_DIRECT
      || ((data->mode&O_DIRECT)!=0 && nom_buffer!=data->buffer)
#endif
    )
  {
    if(data->buffer==NULL)
    {
      data->buffer_size=128*512;
    }
    while(data->buffer_size<((offset%disk_car->sector_size+count+disk_car->sector_size-1)
	  / disk_car->sector_size * disk_car->sector_size))
    {
      if(data->buffer!=NULL)
      {
	free(data->buffer);
	data->buffer=NULL;
      }
      data->buffer_size*=2;
    }
    if(data->buffer==NULL)
    {
      data->buffer=(char*)MALLOC(data->buffer_size);
    }
    /*
    log_debug("file_win32_read handle unaligned data\n");
     */
    if(file_win32_read(disk_car,(offset%disk_car->sector_size+count+disk_car->sector_size-1)
      / disk_car->sector_size * disk_car->sector_size, data->buffer,
      offset/disk_car->sector_size*disk_car->sector_size))
    {
      return -1;
    }
    memcpy(nom_buffer,(char*)data->buffer+(offset%disk_car->sector_size),count);
    return 0;
  }
  {
    long int ret;
    LARGE_INTEGER li;
#ifdef DEBUG
    log_debug("file_win32_read(%d,%u,buffer,%lu(%u/%u/%u))\n", (unsigned int)data->handle,
	(unsigned)(count/disk_car->sector_size), (long unsigned int)(offset/disk_car->sector_size),
	offset2cylinder(disk_car,offset), offset2head(disk_car,offset), offset2sector(disk_car,offset));
#endif
    li.QuadPart = offset+data->offset;
    li.LowPart = SetFilePointer(data->handle, li.LowPart, &li.HighPart, FILE_BEGIN);
    if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
    {
      LPVOID lpMsgBuf;
      DWORD dw = GetLastError(); 
      FormatMessage(
	  FORMAT_MESSAGE_ALLOCATE_BUFFER | 
	  FORMAT_MESSAGE_FROM_SYSTEM,
	  NULL,
	  dw,
	  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
	  (LPTSTR) &lpMsgBuf,
	  0, NULL );
      log_error("file_win32_read(%d,%u,buffer,%lu(%u/%u/%u)) seek err %s\n", (unsigned int)data->handle,
	  (unsigned)(count/disk_car->sector_size), (long unsigned int)(offset/disk_car->sector_size),
	  offset2cylinder(disk_car,offset), offset2head(disk_car,offset), offset2sector(disk_car,offset),
	  (char*)lpMsgBuf);
      LocalFree(lpMsgBuf);
      return -1;
    }
    {
      DWORD dwByteRead;
      ret=ReadFile(data->handle, nom_buffer,count,&dwByteRead,NULL);
      if(ret)
	ret=dwByteRead;
    }
    if(ret!=count)
    {
      if(ret>0 || offset<disk_car->disk_size)
      {
	log_error("file_win32_read(%d,%u,buffer,%lu(%u/%u/%u)) read err: ", (unsigned int)data->handle,
	    (unsigned)(count/disk_car->sector_size), (long unsigned)(offset/disk_car->sector_size),
	    offset2cylinder(disk_car,offset), offset2head(disk_car,offset), offset2sector(disk_car,offset));
	if(ret<0)
	{
	  LPVOID lpMsgBuf;
	  DWORD dw = GetLastError(); 
	  FormatMessage(
	      FORMAT_MESSAGE_ALLOCATE_BUFFER | 
	      FORMAT_MESSAGE_FROM_SYSTEM,
	      NULL,
	      dw,
	      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
	      (LPTSTR) &lpMsgBuf,
	      0, NULL );
	  log_error("%s\n", (char*)lpMsgBuf);
	  LocalFree(lpMsgBuf);
	}
	else if(ret==0)
	  log_error("read after end of file\n");
	else
	  log_error("Partial read\n");
      }
      if(ret<=0)
	return -1;
      memset((char*)nom_buffer+ret,0,count-ret);
    }
  }
  return 0;
}

static int file_win32_write(disk_t *disk_car,const unsigned int count, const void *nom_buffer, const uint64_t offset)
{
  if(count%disk_car->sector_size!=0 || offset%disk_car->sector_size!=0)
  {
    int ret;
    char*buffer=(char*)MALLOC((count/disk_car->sector_size+2)*disk_car->sector_size);
    if(file_read(disk_car,(unsigned)(((count/disk_car->sector_size)+2)*disk_car->sector_size),buffer,offset/disk_car->sector_size*disk_car->sector_size))
    {
      log_error("read failed but try to write anyway");
      memset(buffer,0,(count/disk_car->sector_size+2)*disk_car->sector_size);
    }
    memcpy(buffer+(offset%disk_car->sector_size),nom_buffer,count);
    ret=file_win32_write(disk_car,(unsigned)(((count/disk_car->sector_size)+2)*disk_car->sector_size),buffer,offset/disk_car->sector_size*disk_car->sector_size);
    free(buffer);
    return ret;
  }
  else
  {
    long int ret;
    LARGE_INTEGER li;
    struct info_file_win32_struct *data=disk_car->data;
#ifdef DEBUG
    log_error("file_win32_write(%d,%u,buffer,%lu(%u/%u/%u))\n", (unsigned int)data->handle,
	(unsigned)(count/disk_car->sector_size), (long unsigned int)(offset/disk_car->sector_size),
	offset2cylinder(disk_car,offset), offset2head(disk_car,offset), offset2sector(disk_car,offset));
#endif
    li.QuadPart = offset+data->offset;
    li.LowPart = SetFilePointer(data->handle, li.LowPart, &li.HighPart, FILE_BEGIN);
    if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
    {
      LPVOID lpMsgBuf;
      DWORD dw = GetLastError(); 
      FormatMessage(
	  FORMAT_MESSAGE_ALLOCATE_BUFFER | 
	  FORMAT_MESSAGE_FROM_SYSTEM,
	  NULL,
	  dw,
	  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
	  (LPTSTR) &lpMsgBuf,
	  0, NULL );
      log_error("file_win32_write(%d,%u,buffer,%lu(%u/%u/%u)) seek err %s\n", (unsigned int)data->handle,
	  (unsigned)(count/disk_car->sector_size), (long unsigned int)(offset/disk_car->sector_size),
	  offset2cylinder(disk_car,offset), offset2head(disk_car,offset), offset2sector(disk_car,offset),
	  (char*)lpMsgBuf);
      LocalFree(lpMsgBuf);
      return -1;
    }
    {
      DWORD dwByteRead;
      ret=WriteFile(data->handle, nom_buffer,count,&dwByteRead,NULL);
      if(ret)
	ret=dwByteRead;
    }
    disk_car->write_used=1;
    if(ret!=count)
    {
      log_error("file_win32_write(%u,buffer,%lu(%u/%u/%u)) write err\n", 
	  (unsigned)(count/disk_car->sector_size), (long unsigned)(offset/disk_car->sector_size),
	  offset2cylinder(disk_car,offset),offset2head(disk_car,offset),offset2sector(disk_car,offset));
      return -1;
    }
    return 0;
  }
}

static int file_win32_nowrite(disk_t *disk_car,const unsigned int count, const void *nom_buffer, const uint64_t offset)
{
  const struct info_file_win32_struct *data=disk_car->data;
  log_warning("file_win32_nowrite(%d,%u,buffer,%lu(%u/%u/%u)) write refused\n", (unsigned int)data->handle,
      (unsigned)(count/disk_car->sector_size),(long unsigned)(offset/disk_car->sector_size),
      offset2cylinder(disk_car,offset),offset2head(disk_car,offset),offset2sector(disk_car,offset));
  return -1;
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1