When reading/writing block devices, Cygwin emulates Linux, providing a byte-exact file position, albeit the underlying device drivers don't. Unfortunately this only worked correctly for reading. The raw_write method failed to revalidate the buffer after the read-modify-write cycle in case len is not a multiple of the sector length. This in turn resulted in lseek reporting a wrong file pointer. Also, fix a condition for invalidating the buffer after writing from a remaining read buffer. Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
		
			
				
	
	
		
			763 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			763 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* fhandler_floppy.cc.  See fhandler.h for a description of the
 | 
						|
   fhandler classes.
 | 
						|
 | 
						|
This file is part of Cygwin.
 | 
						|
 | 
						|
This software is a copyrighted work licensed under the terms of the
 | 
						|
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
 | 
						|
details. */
 | 
						|
 | 
						|
#include "winsup.h"
 | 
						|
#include <alloca.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <sys/param.h>
 | 
						|
#include <winioctl.h>
 | 
						|
#include <cygwin/rdevio.h>
 | 
						|
#include <cygwin/hdreg.h>
 | 
						|
#include <cygwin/fs.h>
 | 
						|
#include "cygerrno.h"
 | 
						|
#include "security.h"
 | 
						|
#include "path.h"
 | 
						|
#include "fhandler.h"
 | 
						|
#include "ntdll.h"
 | 
						|
 | 
						|
#define IS_EOM(err)	((err) == ERROR_INVALID_PARAMETER \
 | 
						|
			 || (err) == ERROR_SEEK \
 | 
						|
			 || (err) == ERROR_SECTOR_NOT_FOUND)
 | 
						|
 | 
						|
#define bytes_per_sector devbufalign
 | 
						|
 | 
						|
/**********************************************************************/
 | 
						|
/* fhandler_dev_floppy */
 | 
						|
 | 
						|
fhandler_dev_floppy::fhandler_dev_floppy ()
 | 
						|
  : fhandler_dev_raw (), status ()
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
fhandler_dev_floppy::get_drive_info (struct hd_geometry *geo)
 | 
						|
{
 | 
						|
  char dbuf[256];
 | 
						|
  char pbuf[256];
 | 
						|
 | 
						|
  DISK_GEOMETRY_EX *dix = NULL;
 | 
						|
  DISK_GEOMETRY *di = NULL;
 | 
						|
  PARTITION_INFORMATION_EX *pix = NULL;
 | 
						|
  PARTITION_INFORMATION *pi = NULL;
 | 
						|
  DWORD bytes_read = 0;
 | 
						|
 | 
						|
  /* Always try using the new EX ioctls first.  If not available, fall back
 | 
						|
     to trying the non-EX ioctls which are unfortunately not implemented in
 | 
						|
     the floppy driver. */
 | 
						|
  if (get_major () != DEV_FLOPPY_MAJOR)
 | 
						|
    {
 | 
						|
      if (!DeviceIoControl (get_handle (),
 | 
						|
			    IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, NULL, 0,
 | 
						|
			    dbuf, 256, &bytes_read, NULL))
 | 
						|
	__seterrno ();
 | 
						|
      else
 | 
						|
	{
 | 
						|
	  dix = (DISK_GEOMETRY_EX *) dbuf;
 | 
						|
	  di = &dix->Geometry;
 | 
						|
	}
 | 
						|
    }
 | 
						|
  if (!di)
 | 
						|
    {
 | 
						|
      if (!DeviceIoControl (get_handle (),
 | 
						|
			    IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0,
 | 
						|
			    dbuf, 256, &bytes_read, NULL))
 | 
						|
	{
 | 
						|
	  __seterrno ();
 | 
						|
	  return -1;
 | 
						|
	}
 | 
						|
      di = (DISK_GEOMETRY *) dbuf;
 | 
						|
    }
 | 
						|
  if (dix) /* Don't try IOCTL_DISK_GET_PARTITION_INFO_EX if
 | 
						|
	      IOCTL_DISK_GET_DRIVE_GEOMETRY_EX didn't work. 
 | 
						|
	      Probably a floppy.*/
 | 
						|
    {
 | 
						|
      if (!DeviceIoControl (get_handle (),
 | 
						|
			    IOCTL_DISK_GET_PARTITION_INFO_EX, NULL, 0,
 | 
						|
			    pbuf, 256, &bytes_read, NULL))
 | 
						|
	__seterrno ();
 | 
						|
      else
 | 
						|
	pix = (PARTITION_INFORMATION_EX *) pbuf;
 | 
						|
    }
 | 
						|
  if (!pix && get_major () != DEV_FLOPPY_MAJOR)
 | 
						|
    {
 | 
						|
      /* It's unlikely that this code path will be used at all.  Either the
 | 
						|
	 _EX call already worked, or it's a floppy.  But it doesn't hurt to
 | 
						|
	 keep the code in. */
 | 
						|
      if (!DeviceIoControl (get_handle (),
 | 
						|
			    IOCTL_DISK_GET_PARTITION_INFO, NULL, 0,
 | 
						|
			    pbuf, 256, &bytes_read, NULL))
 | 
						|
	__seterrno ();
 | 
						|
      else
 | 
						|
	pi = (PARTITION_INFORMATION *) pbuf;
 | 
						|
    }
 | 
						|
  debug_printf ("disk geometry: (%D cyl)*(%u trk)*(%u sec)*(%u bps)",
 | 
						|
		 di->Cylinders.QuadPart,
 | 
						|
		 di->TracksPerCylinder,
 | 
						|
		 di->SectorsPerTrack,
 | 
						|
		 di->BytesPerSector);
 | 
						|
  bytes_per_sector = di->BytesPerSector;
 | 
						|
  if (pix)
 | 
						|
    {
 | 
						|
      debug_printf ("partition info: offset %D  length %D",
 | 
						|
		    pix->StartingOffset.QuadPart,
 | 
						|
		    pix->PartitionLength.QuadPart);
 | 
						|
      drive_size = pix->PartitionLength.QuadPart;
 | 
						|
    }
 | 
						|
  else if (pi)
 | 
						|
    {
 | 
						|
      debug_printf ("partition info: offset %D  length %D",
 | 
						|
		    pi->StartingOffset.QuadPart,
 | 
						|
		    pi->PartitionLength.QuadPart);
 | 
						|
      drive_size = pi->PartitionLength.QuadPart;
 | 
						|
    }
 | 
						|
  else	/* Floppy drive. */
 | 
						|
    drive_size = di->Cylinders.QuadPart * di->TracksPerCylinder
 | 
						|
		 * di->SectorsPerTrack * di->BytesPerSector;
 | 
						|
  if (geo)
 | 
						|
    {
 | 
						|
      geo->heads = di->TracksPerCylinder;
 | 
						|
      geo->sectors = di->SectorsPerTrack;
 | 
						|
      geo->cylinders = di->Cylinders.LowPart;
 | 
						|
      if (pix)
 | 
						|
	geo->start = pix->StartingOffset.QuadPart >> 9ULL;
 | 
						|
      else if (pi)
 | 
						|
	geo->start = pi->StartingOffset.QuadPart >> 9ULL;
 | 
						|
      else
 | 
						|
	geo->start = 0;
 | 
						|
    }
 | 
						|
  debug_printf ("drive size: %D", drive_size);
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* Wrapper functions for ReadFile and WriteFile to simplify error handling. */
 | 
						|
BOOL
 | 
						|
fhandler_dev_floppy::read_file (void *buf, DWORD to_read, DWORD *read, int *err)
 | 
						|
{
 | 
						|
  BOOL ret;
 | 
						|
 | 
						|
  *err = 0;
 | 
						|
  if (!(ret = ReadFile (get_handle (), buf, to_read, read, 0)))
 | 
						|
    *err = GetLastError ();
 | 
						|
  syscall_printf ("%d (err %d) = ReadFile (%p, %p, to_read %u, read %u, 0)",
 | 
						|
		  ret, *err, get_handle (), buf, to_read, *read);
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* See comment in write_file below. */
 | 
						|
BOOL
 | 
						|
fhandler_dev_floppy::lock_partition (DWORD to_write)
 | 
						|
{
 | 
						|
  DWORD bytes_read;
 | 
						|
 | 
						|
  /* The simple case.  We have only a single partition open anyway.
 | 
						|
     Try to lock the partition so that a subsequent write succeeds.
 | 
						|
     If there's some file handle open on one of the affected partitions,
 | 
						|
     this fails, but that's how it works...
 | 
						|
     The high partition major numbers don't have a partition 0. */
 | 
						|
  if (get_major () >= DEV_SD_HIGHPART_START || get_minor () % 16 != 0)
 | 
						|
    {
 | 
						|
      if (!DeviceIoControl (get_handle (), FSCTL_LOCK_VOLUME,
 | 
						|
			   NULL, 0, NULL, 0, &bytes_read, NULL))
 | 
						|
	{
 | 
						|
	  debug_printf ("DeviceIoControl (FSCTL_LOCK_VOLUME) failed, %E");
 | 
						|
	  return FALSE;
 | 
						|
	}
 | 
						|
      return TRUE;
 | 
						|
    }
 | 
						|
 | 
						|
  /* The tricky case.  We're writing to the entire disk.  What this code
 | 
						|
     basically does is to find out if the current write operation affects
 | 
						|
     one or more partitions on the disk.  If so, it tries to lock all these
 | 
						|
     partitions and stores the handles for a subsequent close(). */
 | 
						|
  NTSTATUS status;
 | 
						|
  IO_STATUS_BLOCK io;
 | 
						|
  FILE_POSITION_INFORMATION fpi;
 | 
						|
  /* Allocate space for 4 times the maximum partition count we can handle.
 | 
						|
     The reason is that for *every* single logical drive in an extended
 | 
						|
     partition on an MBR drive, 3 filler entries with partition number set
 | 
						|
     to 0 are added into the partition table returned by
 | 
						|
     IOCTL_DISK_GET_DRIVE_LAYOUT_EX.  The first of them reproduces the data
 | 
						|
     of the next partition entry, if any, except for the partiton number.
 | 
						|
     Then two entries with everything set to 0 follow.  Well, the
 | 
						|
     documentation states that for MBR drives the number of partition entries
 | 
						|
     in the PARTITION_INFORMATION_EX array is always a multiple of 4, but,
 | 
						|
     nevertheless, how crappy is that layout? */
 | 
						|
  const DWORD size = sizeof (DRIVE_LAYOUT_INFORMATION_EX)
 | 
						|
		     + 4 * MAX_PARTITIONS * sizeof (PARTITION_INFORMATION_EX);
 | 
						|
  PDRIVE_LAYOUT_INFORMATION_EX pdlix = (PDRIVE_LAYOUT_INFORMATION_EX)
 | 
						|
				       alloca (size);
 | 
						|
  BOOL found = FALSE;
 | 
						|
 | 
						|
  /* Fetch current file pointer position on disk. */
 | 
						|
  status = NtQueryInformationFile (get_handle (), &io, &fpi, sizeof fpi,
 | 
						|
				   FilePositionInformation);
 | 
						|
  if (!NT_SUCCESS (status))
 | 
						|
    {
 | 
						|
      debug_printf ("NtQueryInformationFile(FilePositionInformation): %y",
 | 
						|
		    status);
 | 
						|
      return FALSE;
 | 
						|
    }
 | 
						|
  /* Fetch drive layout to get start and end positions of partitions on disk. */
 | 
						|
  if (!DeviceIoControl (get_handle (), IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0,
 | 
						|
			pdlix, size, &bytes_read, NULL))
 | 
						|
    {
 | 
						|
      debug_printf ("DeviceIoControl(IOCTL_DISK_GET_DRIVE_LAYOUT_EX): %E");
 | 
						|
      return FALSE;
 | 
						|
    }
 | 
						|
  /* Scan through partition info to find the partition(s) into which we're
 | 
						|
     currently trying to write. */
 | 
						|
  PARTITION_INFORMATION_EX *ppie = pdlix->PartitionEntry;
 | 
						|
  for (DWORD i = 0; i < pdlix->PartitionCount; ++i, ++ppie)
 | 
						|
    {
 | 
						|
      /* A partition number of 0 denotes an extended partition or one of the
 | 
						|
	 aforementioned filler entries.  Just skip. */
 | 
						|
      if (ppie->PartitionNumber == 0)
 | 
						|
	continue;
 | 
						|
      /* Check if our writing range affects this partition. */
 | 
						|
      if (fpi.CurrentByteOffset.QuadPart   < ppie->StartingOffset.QuadPart
 | 
						|
					     + ppie->PartitionLength.QuadPart
 | 
						|
	  && ppie->StartingOffset.QuadPart < fpi.CurrentByteOffset.QuadPart
 | 
						|
					     + to_write)
 | 
						|
	{
 | 
						|
	  /* Yes.  Now check if we can handle it.  We can only handle
 | 
						|
	     up to MAX_PARTITIONS partitions.  The partition numbering is
 | 
						|
	     one-based, so we decrement the partition number by 1 when using
 | 
						|
	     as index into the partition array. */
 | 
						|
	  DWORD &part_no = ppie->PartitionNumber;
 | 
						|
	  if (part_no >= MAX_PARTITIONS)
 | 
						|
	    return FALSE;
 | 
						|
	  found = TRUE;
 | 
						|
	  debug_printf ("%u %D->%D : %D->%D", part_no,
 | 
						|
			ppie->StartingOffset.QuadPart,
 | 
						|
			ppie->StartingOffset.QuadPart
 | 
						|
			+ ppie->PartitionLength.QuadPart,
 | 
						|
			fpi.CurrentByteOffset.QuadPart,
 | 
						|
			fpi.CurrentByteOffset.QuadPart + to_write);
 | 
						|
	  /* Do we already have partitions?  If not, create it. */
 | 
						|
	  if (!partitions)
 | 
						|
	    {
 | 
						|
	      partitions = (part_t *) ccalloc_abort (HEAP_FHANDLER, 1,
 | 
						|
						     sizeof (part_t));
 | 
						|
	      partitions->refcnt = 1;
 | 
						|
	    }
 | 
						|
	  /* Next, check if the partition is already open.  If so, skip it. */
 | 
						|
	  if (partitions->hdl[part_no - 1])
 | 
						|
	    continue;
 | 
						|
	  /* Now open the partition and lock it. */
 | 
						|
	  WCHAR part[MAX_PATH], *p;
 | 
						|
	  NTSTATUS status;
 | 
						|
	  UNICODE_STRING upart;
 | 
						|
	  OBJECT_ATTRIBUTES attr;
 | 
						|
	  IO_STATUS_BLOCK io;
 | 
						|
 | 
						|
	  sys_mbstowcs (part, MAX_PATH, get_win32_name ());
 | 
						|
	  p = wcschr (part, L'\0') - 1;
 | 
						|
	  __small_swprintf (p, L"%d", part_no);
 | 
						|
	  RtlInitUnicodeString (&upart, part);
 | 
						|
	  InitializeObjectAttributes (&attr, &upart,
 | 
						|
				      OBJ_CASE_INSENSITIVE
 | 
						|
				      | ((get_flags () & O_CLOEXEC)
 | 
						|
					 ? 0 : OBJ_INHERIT),
 | 
						|
				      NULL, NULL);
 | 
						|
	  status = NtOpenFile (&partitions->hdl[part_no - 1],
 | 
						|
			       GENERIC_READ | GENERIC_WRITE, &attr,
 | 
						|
			       &io, FILE_SHARE_READ | FILE_SHARE_WRITE, 0);
 | 
						|
	  if (!NT_SUCCESS (status))
 | 
						|
	    {
 | 
						|
	      debug_printf ("NtCreateFile(%W): %y", part, status);
 | 
						|
	      return FALSE;
 | 
						|
	    }
 | 
						|
	  if (!DeviceIoControl (partitions->hdl[part_no - 1], FSCTL_LOCK_VOLUME,
 | 
						|
				NULL, 0, NULL, 0, &bytes_read, NULL))
 | 
						|
	    {
 | 
						|
	      debug_printf ("DeviceIoControl (%W, FSCTL_LOCK_VOLUME) "
 | 
						|
			    "failed, %E", part);
 | 
						|
	      return FALSE;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    }
 | 
						|
  /* If we didn't find a single matching partition, the "Access denied"
 | 
						|
     had another reason, so return FALSE in that case. */
 | 
						|
  return found;
 | 
						|
}
 | 
						|
 | 
						|
BOOL
 | 
						|
fhandler_dev_floppy::write_file (const void *buf, DWORD to_write,
 | 
						|
				 DWORD *written, int *err)
 | 
						|
{
 | 
						|
  BOOL ret;
 | 
						|
 | 
						|
  *err = 0;
 | 
						|
  if (!(ret = WriteFile (get_handle (), buf, to_write, written, 0)))
 | 
						|
    *err = GetLastError ();
 | 
						|
  /* When writing to a disk or partition an "Access denied" error may
 | 
						|
     occur due to the raw disk write restriction.
 | 
						|
     See http://support.microsoft.com/kb/942448 for details.
 | 
						|
     What we do here is to lock the affected partition(s) and retry. */
 | 
						|
  if (*err == ERROR_ACCESS_DENIED
 | 
						|
      && get_major () != DEV_FLOPPY_MAJOR
 | 
						|
      && get_major () != DEV_CDROM_MAJOR
 | 
						|
      && (get_flags () & O_ACCMODE) != O_RDONLY
 | 
						|
      && lock_partition (to_write))
 | 
						|
    {
 | 
						|
      *err = 0;
 | 
						|
      if (!(ret = WriteFile (get_handle (), buf, to_write, written, 0)))
 | 
						|
	*err = GetLastError ();
 | 
						|
    }
 | 
						|
  syscall_printf ("%d (err %d) = WriteFile (%p, %p, write %u, written %u, 0)",
 | 
						|
		  ret, *err, get_handle (), buf, to_write, *written);
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
fhandler_dev_floppy::open (int flags, mode_t)
 | 
						|
{
 | 
						|
  int ret = fhandler_dev_raw::open (flags);
 | 
						|
 | 
						|
  if (ret)
 | 
						|
    {
 | 
						|
      DWORD bytes_read;
 | 
						|
 | 
						|
      if (get_drive_info (NULL))
 | 
						|
	{
 | 
						|
	  close ();
 | 
						|
	  return 0;
 | 
						|
	}
 | 
						|
      if (!(flags & O_DIRECT))
 | 
						|
	{
 | 
						|
	  /* Create sector-aligned buffer.  As default buffer size, we're using
 | 
						|
	     some big, sector-aligned value.  Since direct blockdev IO is
 | 
						|
	     usually non-buffered and non-cached, the performance without
 | 
						|
	     buffering is worse than access to a file system on same device.
 | 
						|
	     Whoever uses O_DIRECT has my condolences. */
 | 
						|
	  devbufsiz = MAX (16 * bytes_per_sector, 65536);
 | 
						|
	  devbufalloc = new char [devbufsiz + devbufalign];
 | 
						|
	  devbuf = (char *) roundup2 ((uintptr_t) devbufalloc,
 | 
						|
				      (uintptr_t) devbufalign);
 | 
						|
	}
 | 
						|
 | 
						|
      /* If we're not trying to access a floppy disk, make sure we're actually
 | 
						|
         allowed to read *all* of the device or volume.  This is actually
 | 
						|
	 documented in the MSDN CreateFile man page. */
 | 
						|
      if (get_major () != DEV_FLOPPY_MAJOR
 | 
						|
	  && !DeviceIoControl (get_handle (), FSCTL_ALLOW_EXTENDED_DASD_IO,
 | 
						|
			       NULL, 0, NULL, 0, &bytes_read, NULL))
 | 
						|
	debug_printf ("DeviceIoControl (FSCTL_ALLOW_EXTENDED_DASD_IO) "
 | 
						|
		      "failed, %E");
 | 
						|
    }
 | 
						|
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
fhandler_dev_floppy::close ()
 | 
						|
{
 | 
						|
  int ret = fhandler_dev_raw::close ();
 | 
						|
 | 
						|
  if (partitions && InterlockedDecrement (&partitions->refcnt) == 0)
 | 
						|
    {
 | 
						|
      for (int i = 0; i < MAX_PARTITIONS; ++i)
 | 
						|
	if (partitions->hdl[i])
 | 
						|
	  NtClose (partitions->hdl[i]);
 | 
						|
      cfree (partitions);
 | 
						|
    }
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
fhandler_dev_floppy::dup (fhandler_base *child, int flags)
 | 
						|
{
 | 
						|
  int ret = fhandler_dev_raw::dup (child, flags);
 | 
						|
 | 
						|
  if (!ret && partitions)
 | 
						|
    InterlockedIncrement (&partitions->refcnt);
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
inline off_t
 | 
						|
fhandler_dev_floppy::get_current_position ()
 | 
						|
{
 | 
						|
  LARGE_INTEGER off = { QuadPart: 0LL };
 | 
						|
  off.LowPart = SetFilePointer (get_handle (), 0, &off.HighPart, FILE_CURRENT);
 | 
						|
  return off.QuadPart;
 | 
						|
}
 | 
						|
 | 
						|
void __reg3
 | 
						|
fhandler_dev_floppy::raw_read (void *ptr, size_t& ulen)
 | 
						|
{
 | 
						|
  DWORD bytes_read = 0;
 | 
						|
  DWORD read2;
 | 
						|
  DWORD bytes_to_read;
 | 
						|
  int ret;
 | 
						|
  size_t len = ulen;
 | 
						|
  char *tgt;
 | 
						|
  char *p = (char *) ptr;
 | 
						|
 | 
						|
  /* Checking a previous end of media */
 | 
						|
  if (eom_detected () && !lastblk_to_read ())
 | 
						|
    {
 | 
						|
      set_errno (ENOSPC);
 | 
						|
      goto err;
 | 
						|
    }
 | 
						|
 | 
						|
  if (devbuf)
 | 
						|
    {
 | 
						|
      while (len > 0)
 | 
						|
	{
 | 
						|
	  if (devbufstart < devbufend)
 | 
						|
	    {
 | 
						|
	      bytes_to_read = MIN (len, devbufend - devbufstart);
 | 
						|
	      debug_printf ("read %u bytes from buffer (rest %u)",
 | 
						|
			    bytes_to_read,
 | 
						|
			    devbufend - devbufstart - bytes_to_read);
 | 
						|
	      memcpy (p, devbuf + devbufstart, bytes_to_read);
 | 
						|
	      len -= bytes_to_read;
 | 
						|
	      p += bytes_to_read;
 | 
						|
	      bytes_read += bytes_to_read;
 | 
						|
	      devbufstart += bytes_to_read;
 | 
						|
 | 
						|
	      if (lastblk_to_read ())
 | 
						|
		{
 | 
						|
		  lastblk_to_read (false);
 | 
						|
		  break;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	  if (len > 0)
 | 
						|
	    {
 | 
						|
	      if (len >= devbufsiz)
 | 
						|
		{
 | 
						|
		  bytes_to_read = (len / bytes_per_sector) * bytes_per_sector;
 | 
						|
		  tgt = p;
 | 
						|
		}
 | 
						|
	      else
 | 
						|
		{
 | 
						|
		  tgt = devbuf;
 | 
						|
		  bytes_to_read = devbufsiz;
 | 
						|
		}
 | 
						|
	      off_t current_position = get_current_position ();
 | 
						|
	      if (current_position + bytes_to_read >= drive_size)
 | 
						|
		bytes_to_read = drive_size - current_position;
 | 
						|
	      if (!bytes_to_read)
 | 
						|
		break;
 | 
						|
 | 
						|
	      debug_printf ("read %u bytes from pos %U %s", bytes_to_read,
 | 
						|
			    current_position,
 | 
						|
			    len < devbufsiz ? "into buffer" : "directly");
 | 
						|
	      if (!read_file (tgt, bytes_to_read, &read2, &ret))
 | 
						|
		{
 | 
						|
		  if (!IS_EOM (ret))
 | 
						|
		    {
 | 
						|
		      __seterrno ();
 | 
						|
		      goto err;
 | 
						|
		    }
 | 
						|
 | 
						|
		  eom_detected (true);
 | 
						|
 | 
						|
		  if (!read2)
 | 
						|
		    {
 | 
						|
		      if (!bytes_read)
 | 
						|
			{
 | 
						|
			  debug_printf ("return -1, set errno to ENOSPC");
 | 
						|
			  set_errno (ENOSPC);
 | 
						|
			  goto err;
 | 
						|
			}
 | 
						|
		      break;
 | 
						|
		    }
 | 
						|
		  lastblk_to_read (true);
 | 
						|
		}
 | 
						|
	      if (!read2)
 | 
						|
	       break;
 | 
						|
	      if (tgt == devbuf)
 | 
						|
		{
 | 
						|
		  devbufstart = 0;
 | 
						|
		  devbufend = read2;
 | 
						|
		}
 | 
						|
	      else
 | 
						|
		{
 | 
						|
		  len -= read2;
 | 
						|
		  p += read2;
 | 
						|
		  bytes_read += read2;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
      off_t current_position = get_current_position ();
 | 
						|
      bytes_to_read = len;
 | 
						|
      if (current_position + bytes_to_read >= drive_size)
 | 
						|
	bytes_to_read = drive_size - current_position;
 | 
						|
      debug_printf ("read %u bytes from pos %U directly", bytes_to_read,
 | 
						|
		    current_position);
 | 
						|
      if (bytes_to_read && !read_file (p, bytes_to_read, &bytes_read, &ret))
 | 
						|
	{
 | 
						|
	  if (!IS_EOM (ret))
 | 
						|
	    {
 | 
						|
	      __seterrno ();
 | 
						|
	      goto err;
 | 
						|
	    }
 | 
						|
	  if (bytes_read)
 | 
						|
	    eom_detected (true);
 | 
						|
	  else
 | 
						|
	    {
 | 
						|
	      debug_printf ("return -1, set errno to ENOSPC");
 | 
						|
	      set_errno (ENOSPC);
 | 
						|
	      goto err;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  ulen = (size_t) bytes_read;
 | 
						|
  return;
 | 
						|
 | 
						|
err:
 | 
						|
  ulen = (size_t) -1;
 | 
						|
}
 | 
						|
 | 
						|
ssize_t __reg3
 | 
						|
fhandler_dev_floppy::raw_write (const void *ptr, size_t len)
 | 
						|
{
 | 
						|
  DWORD bytes_written = 0;
 | 
						|
  char *p = (char *) ptr;
 | 
						|
  int ret;
 | 
						|
 | 
						|
  /* Checking a previous end of media */
 | 
						|
  if (eom_detected ())
 | 
						|
    {
 | 
						|
      set_errno (ENOSPC);
 | 
						|
      return -1;
 | 
						|
    }
 | 
						|
 | 
						|
  if (!len)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  if (devbuf)
 | 
						|
    {
 | 
						|
      DWORD cplen, written;
 | 
						|
 | 
						|
      /* First check if we have an active read buffer.  If so, try to fit in
 | 
						|
      	 the start of the input buffer and write out the entire result.
 | 
						|
	 This also covers the situation after lseek since lseek fills the read
 | 
						|
	 buffer in case we seek to an address which is not sector aligned. */
 | 
						|
      if (devbufend && devbufstart < devbufend)
 | 
						|
      	{
 | 
						|
	  off_t current_pos = get_current_position ();
 | 
						|
	  cplen = MIN (len, devbufend - devbufstart);
 | 
						|
	  memcpy (devbuf + devbufstart, p, cplen);
 | 
						|
	  LARGE_INTEGER off = { QuadPart:current_pos - devbufend };
 | 
						|
	  if (!SetFilePointerEx (get_handle (), off, NULL, FILE_BEGIN))
 | 
						|
	    {
 | 
						|
	      devbufstart = devbufend = 0;
 | 
						|
	      __seterrno ();
 | 
						|
	      return -1;
 | 
						|
	    }
 | 
						|
	  if (!write_file (devbuf, devbufend, &written, &ret))
 | 
						|
	    {
 | 
						|
	      devbufstart = devbufend = 0;
 | 
						|
	      goto err;
 | 
						|
	    }
 | 
						|
	  /* Align pointers, lengths, etc. */
 | 
						|
	  cplen = MIN (cplen, written);
 | 
						|
	  devbufstart += cplen;
 | 
						|
	  if (devbufstart >= devbufend)
 | 
						|
	    devbufstart = devbufend = 0;
 | 
						|
	  p += cplen;
 | 
						|
	  len -= cplen;
 | 
						|
	  bytes_written += cplen;
 | 
						|
	}
 | 
						|
      /* As long as there's still something left in the input buffer ... */
 | 
						|
      while (len)
 | 
						|
	{
 | 
						|
	  /* Compute the length to write.  The problem is that the underlying
 | 
						|
	     driver may require sector aligned read/write.  So we copy the data
 | 
						|
	     over to devbuf, which is guaranteed to be sector aligned. */
 | 
						|
	  cplen = MIN (len, devbufsiz);
 | 
						|
	  if (cplen >= bytes_per_sector)
 | 
						|
	    /* If the remaining len is >= sector size, write out the maximum
 | 
						|
	       possible multiple of the sector size which fits into devbuf. */
 | 
						|
	    cplen = rounddown (cplen, bytes_per_sector);
 | 
						|
	  else
 | 
						|
	    {
 | 
						|
	      /* If len < sector size, read in the next sector, seek back,
 | 
						|
		 and just copy the new data over the old one before writing. */
 | 
						|
	      LARGE_INTEGER off = { QuadPart:get_current_position () };
 | 
						|
	      if (!read_file (devbuf, bytes_per_sector, &written, &ret))
 | 
						|
		goto err;
 | 
						|
	      if (!SetFilePointerEx (get_handle (), off, NULL, FILE_BEGIN))
 | 
						|
		{
 | 
						|
		  __seterrno ();
 | 
						|
		  return -1;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	  memcpy (devbuf, p, cplen);
 | 
						|
	  if (!write_file (devbuf, MAX (cplen, bytes_per_sector), &written,
 | 
						|
			   &ret))
 | 
						|
	    {
 | 
						|
	      bytes_written += MIN (cplen, written);
 | 
						|
	      goto err;
 | 
						|
	    }
 | 
						|
	  cplen = MIN (cplen, written);
 | 
						|
	  p += cplen;
 | 
						|
	  len -= cplen;
 | 
						|
	  bytes_written += cplen;
 | 
						|
	  /* If we overwrote, revalidate devbuf.  It still contains the
 | 
						|
	     content from the above read/modify/write.  Revalidating makes
 | 
						|
	     sure lseek reports the correct position. */
 | 
						|
	  if (cplen < bytes_per_sector)
 | 
						|
	    {
 | 
						|
	      devbufstart = cplen;
 | 
						|
	      devbufend = bytes_per_sector;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
      return bytes_written;
 | 
						|
    }
 | 
						|
  
 | 
						|
  /* In O_DIRECT case, just write. */
 | 
						|
  if (write_file (p, len, &bytes_written, &ret))
 | 
						|
    return bytes_written;
 | 
						|
 | 
						|
err:
 | 
						|
  if (IS_EOM (ret))
 | 
						|
    {
 | 
						|
      eom_detected (true);
 | 
						|
      if (!bytes_written)
 | 
						|
	set_errno (ENOSPC);
 | 
						|
    }
 | 
						|
  else if (!bytes_written)
 | 
						|
    __seterrno ();
 | 
						|
  return bytes_written ?: -1;
 | 
						|
}
 | 
						|
 | 
						|
off_t
 | 
						|
fhandler_dev_floppy::lseek (off_t offset, int whence)
 | 
						|
{
 | 
						|
  char buf[bytes_per_sector];
 | 
						|
  off_t current_pos = (off_t) -1;
 | 
						|
  LARGE_INTEGER sector_aligned_offset;
 | 
						|
  size_t bytes_left;
 | 
						|
 | 
						|
  if (whence == SEEK_END)
 | 
						|
    {
 | 
						|
      offset += drive_size;
 | 
						|
      whence = SEEK_SET;
 | 
						|
    }
 | 
						|
  else if (whence == SEEK_CUR)
 | 
						|
    {
 | 
						|
      current_pos = get_current_position ();
 | 
						|
      off_t exact_pos = current_pos - (devbufend - devbufstart);
 | 
						|
      /* Shortcut when used to get current position. */
 | 
						|
      if (offset == 0)
 | 
						|
      	return exact_pos;
 | 
						|
      offset += exact_pos;
 | 
						|
      whence = SEEK_SET;
 | 
						|
    }
 | 
						|
 | 
						|
  if (whence != SEEK_SET || offset < 0 || offset > drive_size)
 | 
						|
    {
 | 
						|
      set_errno (EINVAL);
 | 
						|
      return -1;
 | 
						|
    }
 | 
						|
 | 
						|
  /* If new position is in buffered range, adjust buffer and return */
 | 
						|
  if (devbufstart < devbufend)
 | 
						|
    {
 | 
						|
      if (current_pos == (off_t) -1)
 | 
						|
	current_pos = get_current_position ();
 | 
						|
      if (current_pos - devbufend <= offset && offset <= current_pos)
 | 
						|
	{
 | 
						|
	  devbufstart = devbufend - (current_pos - offset);
 | 
						|
	  return offset;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  sector_aligned_offset.QuadPart = rounddown (offset, bytes_per_sector);
 | 
						|
  bytes_left = offset - sector_aligned_offset.QuadPart;
 | 
						|
 | 
						|
  /* Invalidate buffer. */
 | 
						|
  devbufstart = devbufend = 0;
 | 
						|
 | 
						|
  if (!SetFilePointerEx (get_handle (), sector_aligned_offset, NULL,
 | 
						|
			 FILE_BEGIN))
 | 
						|
    {
 | 
						|
      __seterrno ();
 | 
						|
      return -1;
 | 
						|
    }
 | 
						|
 | 
						|
  eom_detected (false);
 | 
						|
 | 
						|
  if (bytes_left)
 | 
						|
    {
 | 
						|
      raw_read (buf, bytes_left);
 | 
						|
      if (bytes_left == (size_t) -1)
 | 
						|
	return -1;
 | 
						|
    }
 | 
						|
 | 
						|
  return sector_aligned_offset.QuadPart + bytes_left;
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
fhandler_dev_floppy::ioctl (unsigned int cmd, void *buf)
 | 
						|
{
 | 
						|
  int ret = 0;
 | 
						|
  DWORD bytes_read;
 | 
						|
 | 
						|
  switch (cmd)
 | 
						|
    {
 | 
						|
    case HDIO_GETGEO:
 | 
						|
      debug_printf ("HDIO_GETGEO");
 | 
						|
      ret = get_drive_info ((struct hd_geometry *) buf);
 | 
						|
      break;
 | 
						|
    case BLKGETSIZE:
 | 
						|
    case BLKGETSIZE64:
 | 
						|
      debug_printf ("BLKGETSIZE");
 | 
						|
      if (cmd == BLKGETSIZE)
 | 
						|
	*(long *)buf = drive_size >> 9UL;
 | 
						|
      else
 | 
						|
	*(off_t *)buf = drive_size;
 | 
						|
      break;
 | 
						|
    case BLKRRPART:
 | 
						|
      debug_printf ("BLKRRPART");
 | 
						|
      if (!DeviceIoControl (get_handle (), IOCTL_DISK_UPDATE_PROPERTIES,
 | 
						|
			    NULL, 0, NULL, 0, &bytes_read, NULL))
 | 
						|
	{
 | 
						|
	  __seterrno ();
 | 
						|
	  ret = -1;
 | 
						|
	}
 | 
						|
      else
 | 
						|
	get_drive_info (NULL);
 | 
						|
      break;
 | 
						|
    case BLKSSZGET:
 | 
						|
      debug_printf ("BLKSSZGET");
 | 
						|
      *(int *)buf = (int) bytes_per_sector;
 | 
						|
      break;
 | 
						|
    case BLKIOMIN:
 | 
						|
      debug_printf ("BLKIOMIN");
 | 
						|
      *(int *)buf = (int) bytes_per_sector;
 | 
						|
      break;
 | 
						|
    case BLKIOOPT:
 | 
						|
      debug_printf ("BLKIOOPT");
 | 
						|
      *(int *)buf = (int) bytes_per_sector;
 | 
						|
      break;
 | 
						|
    case BLKPBSZGET:
 | 
						|
      debug_printf ("BLKPBSZGET");
 | 
						|
      *(int *)buf = (int) bytes_per_sector;
 | 
						|
      break;
 | 
						|
    case BLKALIGNOFF:
 | 
						|
      debug_printf ("BLKALIGNOFF");
 | 
						|
      *(int *)buf = 0;
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      ret = fhandler_dev_raw::ioctl (cmd, buf);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 |