/* fhandler_tape.cc.  See fhandler.h for a description of the fhandler
   classes.

   Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
   2008, 2010 Red Hat, Inc.

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 "cygtls.h"
#include <stdlib.h>
#include <sys/mtio.h>
#include <sys/param.h>
#include <ddk/ntddstor.h>
#include "security.h"
#include "path.h"
#include "fhandler.h"
#include "dtable.h"
#include "cygheap.h"
#include "shared_info.h"
#include "sigproc.h"

/* Media changes and bus resets are sometimes reported and the function
   hasn't been executed.  We repeat all functions which return with one
   of these error codes. */
#define TAPE_FUNC(func) while ((lasterr = (func)) == ERROR_MEDIA_CHANGED) \
			  { \
			    initialize (drive, false); \
			    part (partition)->initialize (0); \
			  }

#define IS_BOT(err) ((err) == ERROR_BEGINNING_OF_MEDIA)

#define IS_EOF(err) ((err) == ERROR_FILEMARK_DETECTED \
		     || (err) == ERROR_SETMARK_DETECTED)

#define IS_SM(err)  ((err) == ERROR_SETMARK_DETECTED)

#define IS_EOD(err) ((err) == ERROR_END_OF_MEDIA \
		     || (err) == ERROR_EOM_OVERFLOW \
		     || (err) == ERROR_NO_DATA_DETECTED)

#define IS_EOM(err) ((err) == ERROR_END_OF_MEDIA \
		     || (err) == ERROR_EOM_OVERFLOW)

/**********************************************************************/
/* mtinfo_part */

void
mtinfo_part::initialize (long nblock)
{
  block = nblock;
  if (block == 0)
    file = fblock = 0;
  else
    file = fblock = -1;
  smark = false;
  emark = no_eof;
}

/**********************************************************************/
/* mtinfo_drive */

void
mtinfo_drive::initialize (int num, bool first_time)
{
  drive = num;
  partition = 0;
  block = -1;
  lock = unlocked;
  if (first_time)
    {
      buffer_writes (true);
      async_writes (false);
      two_fm (false);
      fast_eom (false);
      auto_lock (false);
      sysv (false);
      nowait (false);
    }
  for (int i = 0; i < MAX_PARTITION_NUM; ++i)
    part (i)->initialize ();
}

int
mtinfo_drive::get_dp (HANDLE mt)
{
  DWORD len = sizeof _dp;
  TAPE_FUNC (GetTapeParameters (mt, GET_TAPE_DRIVE_INFORMATION, &len, &_dp));
  return error ("get_dp");
}

int
mtinfo_drive::get_mp (HANDLE mt)
{
  DWORD len = sizeof _mp;
  TAPE_FUNC (GetTapeParameters (mt, GET_TAPE_MEDIA_INFORMATION, &len, &_mp));
  return error ("get_mp");
}

int
mtinfo_drive::open (HANDLE mt)
{
  get_dp (mt);
  get_mp (mt);
  get_pos (mt);
  if (partition < MAX_PARTITION_NUM && part (partition)->block != block)
    part (partition)->initialize (block);
  /* The following rewind in position 0 solves a problem which appears
   * in case of multi volume archives (at least on NT4): The last ReadFile
   * on the previous medium returns ERROR_NO_DATA_DETECTED.  After media
   * change, all subsequent ReadFile calls return ERROR_NO_DATA_DETECTED,
   * too.  The call to set_pos apparently reset some internal flags.
   * FIXME:  Is that really true or based on a misinterpretation? */
  if (!block)
    {
      debug_printf ("rewind in position 0");
      set_pos (mt, TAPE_REWIND, 0, false);
    }
  return error ("open");
}

int
mtinfo_drive::close (HANDLE mt, bool rewind)
{
  lasterr = 0;
  if (GetTapeStatus (mt) == ERROR_NO_MEDIA_IN_DRIVE)
    dirty = clean;
  if (dirty >= has_written)
    {
      /* If an async write is still pending, wait for completion. */
      if (dirty == async_write_pending)
	lasterr = async_wait (mt, NULL);
      if (!lasterr)
	{
	  /* if last operation was writing, write a filemark */
	  debug_printf ("writing filemark");
	  write_marks (mt, TAPE_FILEMARKS, two_fm () ? 2 : 1);
	  if (two_fm () && !lasterr && !rewind) /* Backspace over 2nd fmark. */
	    {
	      set_pos (mt, TAPE_SPACE_FILEMARKS, -1, false);
	      if (!lasterr)
		part (partition)->fblock = 0; /* That's obvious, isn't it? */
	    }
	}
    }
  else if (dirty == has_read && !rewind)
    {
      if (sysv ())
	{
	  /* Under SYSV semantics, the tape is moved past the next file mark
	     after read. */
	  if (part (partition)->emark == no_eof)
	    set_pos (mt, TAPE_SPACE_FILEMARKS, 1, false);
	  else if (part (partition)->emark == eof_hit)
	    part (partition)->emark = eof;
	}
      else
	{
	  /* Under BSD semantics, we must check if the filemark has been
	     inadvertendly crossed.  If so cross the filemark backwards
	     and position the tape right before EOF. */
	  if (part (partition)->emark == eof_hit)
	    set_pos (mt, TAPE_SPACE_FILEMARKS, -1, false);
	}
    }
  if (rewind)
    {
      debug_printf ("rewinding");
      set_pos (mt, TAPE_REWIND, 0, false);
    }
  if (auto_lock () && lock == auto_locked)
    prepare (mt, TAPE_UNLOCK);
  dirty = clean;
  return error ("close");
}

int
mtinfo_drive::read (HANDLE mt, HANDLE mt_evt, void *ptr, size_t &ulen)
{
  BOOL ret;
  DWORD bytes_read = 0;

  if (GetTapeStatus (mt) == ERROR_NO_MEDIA_IN_DRIVE)
    return lasterr = ERROR_NO_MEDIA_IN_DRIVE;
  if (lasterr == ERROR_BUS_RESET)
    {
      ulen = 0;
      goto out;
    }
  /* If an async write is still pending, wait for completion. */
  if (dirty == async_write_pending)
    lasterr = async_wait (mt, NULL);
  dirty = clean;
  if (part (partition)->emark == eof_hit)
    {
      part (partition)->emark = eof;
      lasterr = ulen = 0;
      goto out;
    }
  else if (part (partition)->emark == eod_hit)
    {
      part (partition)->emark = eod;
      lasterr = ulen = 0;
      goto out;
    }
  else if (part (partition)->emark == eod)
    {
      lasterr = ERROR_NO_DATA_DETECTED;
      ulen = (size_t) -1;
      goto out;
    }
  else if (part (partition)->emark == eom_hit)
    {
      part (partition)->emark = eom;
      lasterr = ulen = 0;
      goto out;
    }
  else if (part (partition)->emark == eom)
    {
      lasterr = ERROR_END_OF_MEDIA;
      ulen = (size_t) -1;
      goto out;
    }
  part (partition)->smark = false;
  if (auto_lock () && lock < auto_locked)
    prepare (mt, TAPE_LOCK, true);
  ov.Offset = ov.OffsetHigh = 0;
  ov.hEvent = mt_evt;
  ret = ReadFile (mt, ptr, ulen, &bytes_read, &ov);
  lasterr = ret ? 0 : GetLastError ();
  if (lasterr == ERROR_IO_PENDING)
    lasterr = async_wait (mt, &bytes_read);
  ulen = (size_t) bytes_read;
  if (bytes_read > 0)
    {
      long blocks_read = mp ()->BlockSize == 0
			 ? 1 : howmany (bytes_read, mp ()->BlockSize);
      block += blocks_read;
      part (partition)->block += blocks_read;
      if (part (partition)->fblock >= 0)
	part (partition)->fblock += blocks_read;
    }
  if (IS_EOF (lasterr))
    {
      block++;
      part (partition)->block++;
      if (part (partition)->file >= 0)
	part (partition)->file++;
      part (partition)->fblock = 0;
      part (partition)->smark = IS_SM (lasterr);
      part (partition)->emark = bytes_read > 0 ? eof_hit : eof;
      lasterr = 0;
    }
  else if (IS_EOD (lasterr))
    {
      if (part (partition)->emark == eof)
	part (partition)->emark = IS_EOM (lasterr) ? eom : eod;
      else
	{
	  part (partition)->emark = IS_EOM (lasterr) ? eom_hit : eod_hit;
	  lasterr = 0;
	}
    }
  else
    {
      part (partition)->emark = no_eof;
      /* This happens if the buffer is too small when in variable block
	 size mode.  Linux returns ENOMEM here.  We're doing the same. */
      if (lasterr == ERROR_MORE_DATA)
	lasterr = ERROR_NOT_ENOUGH_MEMORY;
    }
  if (!lasterr)
    dirty = has_read;
out:
  return error ("read");
}

int
mtinfo_drive::async_wait (HANDLE mt, DWORD *bytes_written)
{
  DWORD written;

  bool ret = GetOverlappedResult (mt, &ov, &written, TRUE);
  if (bytes_written)
    *bytes_written = written;
  return ret ? 0 : GetLastError ();
}

int
mtinfo_drive::write (HANDLE mt, HANDLE mt_evt, const void *ptr, size_t &len)
{
  BOOL ret;
  DWORD bytes_written = 0;
  int async_err = 0;

  if (GetTapeStatus (mt) == ERROR_NO_MEDIA_IN_DRIVE)
    return lasterr = ERROR_NO_MEDIA_IN_DRIVE;
  if (lasterr == ERROR_BUS_RESET)
    {
      len = 0;
      return error ("write");
    }
  if (dirty == async_write_pending)
    async_err = async_wait (mt, &bytes_written);
  dirty = clean;
  part (partition)->smark = false;
  if (auto_lock () && lock < auto_locked)
    prepare (mt, TAPE_LOCK, true);
  ov.Offset = ov.OffsetHigh = 0;
  ov.hEvent = mt_evt;
  ret = WriteFile (mt, ptr, len, &bytes_written, &ov);
  lasterr = ret ? 0: GetLastError ();
  if (lasterr == ERROR_IO_PENDING)
    {
      if (async_writes () && mp ()->BlockSize == 0)
	dirty = async_write_pending;
      else
	/* Wait for completion if a non-async write. */
	lasterr = async_wait (mt, &bytes_written);
    }
  len = (size_t) bytes_written;
  if (bytes_written > 0)
    {
      long blocks_written = mp ()->BlockSize == 0
			 ? 1 : howmany (bytes_written, mp ()->BlockSize);
      block += blocks_written;
      part (partition)->block += blocks_written;
      if (part (partition)->fblock >= 0)
	part (partition)->fblock += blocks_written;
    }
  if (!lasterr && async_err)
    lasterr = async_err;
  if (lasterr == ERROR_EOM_OVERFLOW)
    part (partition)->emark = eom;
  else if (lasterr == ERROR_END_OF_MEDIA)
    ; // FIXME?: part (partition)->emark = eom_hit;
  else
    {
      part (partition)->emark = no_eof;
      if (!lasterr)
	dirty = has_written;
      else if (lasterr == ERROR_IO_PENDING)
	dirty = async_write_pending;
    }
  return error ("write");
}

int
mtinfo_drive::get_pos (HANDLE mt, long *ppartition, long *pblock)
{
  DWORD p, low, high;

  TAPE_FUNC (GetTapePosition (mt, TAPE_LOGICAL_POSITION, &p, &low, &high));
  if (lasterr == ERROR_INVALID_FUNCTION)
    TAPE_FUNC (GetTapePosition (mt, TAPE_ABSOLUTE_POSITION, &p, &low, &high));
  if (!lasterr)
    {
      if (p > 0)
	partition = (long) p - 1;
      block = (long) low;
      if (ppartition)
	*ppartition= partition;
      if (pblock)
	*pblock = block;
    }
  else
    {
      partition = 0;
      block = -1;
    }
  return error ("get_pos");
}

int
mtinfo_drive::_set_pos (HANDLE mt, int mode, long count, int partition,
			BOOL dont_wait)
{
  /* If an async write is still pending, wait for completion. */
  if (dirty == async_write_pending)
    lasterr = async_wait (mt, NULL);
  dirty = clean;
  TAPE_FUNC (SetTapePosition (mt, mode, partition, count, count < 0 ? -1 : 0,
			      dont_wait));
  return lasterr;
}

int
mtinfo_drive::set_pos (HANDLE mt, int mode, long count,
		       bool sfm_func)
{
  int err = 0;
  long undone = count;
  BOOL dont_wait = FALSE;

  switch (mode)
    {
      case TAPE_SPACE_RELATIVE_BLOCKS:
      case TAPE_SPACE_FILEMARKS:
      case TAPE_SPACE_SETMARKS:
	if (!count)
	  {
	    lasterr = 0;
	    goto out;
	  }
	break;
      case TAPE_ABSOLUTE_BLOCK:
      case TAPE_LOGICAL_BLOCK:
      case TAPE_REWIND:
	dont_wait = nowait () ? TRUE : FALSE;
	break;
    }
  if (mode == TAPE_SPACE_FILEMARKS)
    {
      while (!err && undone > 0)
	if (!(err = _set_pos (mt, mode, 1, 0, FALSE)) || IS_SM (err))
	  --undone;
      while (!err && undone < 0)
	if (!(err = _set_pos (mt, mode, -1, 0, FALSE)) || IS_SM (err))
	  ++undone;
    }
  else
    err = _set_pos (mt, mode, count, 0, dont_wait);
  switch (mode)
    {
      case TAPE_ABSOLUTE_BLOCK:
      case TAPE_LOGICAL_BLOCK:
	get_pos (mt);
	part (partition)->initialize (block);
	break;
      case TAPE_REWIND:
	if (!err)
	  {
	    block = 0;
	    part (partition)->initialize (0);
	  }
	else
	  {
	    get_pos (mt);
	    part (partition)->initialize (block);
	  }
	break;
      case TAPE_SPACE_END_OF_DATA:
	get_pos (mt);
	part (partition)->initialize (block);
	part (partition)->emark = IS_EOM (err) ? eom : eod;
	break;
      case TAPE_SPACE_FILEMARKS:
	if (!err || IS_SM (err))
	  {
	    get_pos (mt);
	    part (partition)->block = block;
	    if (count > 0)
	      {
		if (part (partition)->file >= 0)
		  part (partition)->file += count - undone;
		part (partition)->fblock = 0;
		part (partition)->smark = IS_SM (err);
	      }
	    else
	      {
		if (part (partition)->file >= 0)
		  part (partition)->file += count - undone;
		part (partition)->fblock = -1;
		part (partition)->smark = false;
	      }
	    if (sfm_func)
	      err = set_pos (mt, mode, count > 0 ? -1 : 1, false);
	    else
	      part (partition)->emark = count > 0 ? eof : no_eof;
	  }
	else if (IS_EOD (err))
	  {
	    get_pos (mt);
	    part (partition)->block = block;
	    if (part (partition)->file >= 0)
	      part (partition)->file += count - undone;
	    part (partition)->fblock = -1;
	    part (partition)->smark = false;
	    part (partition)->emark = IS_EOM (err) ? eom : eod;
	  }
	else if (IS_BOT (err))
	  {
	    block = 0;
	    part (partition)->initialize (0);
	  }
	else
	  {
	    get_pos (mt);
	    part (partition)->initialize (block);
	  }
	break;
      case TAPE_SPACE_RELATIVE_BLOCKS:
	if (!err)
	  {
	    block += count;
	    part (partition)->block += count;
	    if (part (partition)->fblock >= 0)
	      part (partition)->fblock += count;
	    part (partition)->smark = false;
	    part (partition)->emark = no_eof;
	  }
	else if (IS_EOF (err))
	  {
	    get_pos (mt);
	    part (partition)->block = block;
	    if (part (partition)->file >= 0)
	      part (partition)->file += count > 0 ? 1 : -1;
	    part (partition)->fblock = count > 0 ? 0 : -1;
	    part (partition)->smark = (count > 0 && IS_SM (err));
	    part (partition)->emark = count > 0 ? eof : no_eof;
	  }
	else if (IS_EOD (err))
	  {
	    get_pos (mt);
	    part (partition)->fblock = block - part (partition)->block;
	    part (partition)->block = block;
	    part (partition)->smark = false;
	    part (partition)->emark = IS_EOM (err) ? eom : eod;
	  }
	else if (IS_BOT (err))
	  {
	    block = 0;
	    part (partition)->initialize (0);
	  }
	break;
      case TAPE_SPACE_SETMARKS:
	get_pos (mt);
	part (partition)->block = block;
	if (!err)
	  {
	    part (partition)->file = -1;
	    part (partition)->fblock = -1;
	    part (partition)->smark = true;
	  }
	break;
    }
  lasterr = err;
out:
  return error ("set_pos");
}

int
mtinfo_drive::create_partitions (HANDLE mt, long count)
{
  if (dp ()->MaximumPartitionCount <= 1)
    return ERROR_INVALID_PARAMETER;
  if (set_pos (mt, TAPE_REWIND, 0, false))
    goto out;
  partition = 0;
  part (partition)->initialize (0);
  debug_printf ("Format tape with %s partition(s)", count <= 0 ? "one" : "two");
  if (get_feature (TAPE_DRIVE_INITIATOR))
    {
      if (count <= 0)
	TAPE_FUNC (CreateTapePartition (mt, TAPE_INITIATOR_PARTITIONS,
					count <= 0 ? 0 : 2, (DWORD) count));
    }
  else if (get_feature (TAPE_DRIVE_FIXED))
    {
      /* This is supposed to work for Tandberg SLR drivers up to version
	 1.6 which missed to set the TAPE_DRIVE_INITIATOR flag.  According
	 to Tandberg, CreateTapePartition(TAPE_FIXED_PARTITIONS) apparently
	 does not ignore the dwCount parameter.  Go figure! */
      TAPE_FUNC (CreateTapePartition (mt, TAPE_FIXED_PARTITIONS,
				      count <= 0 ? 0 : 2, (DWORD) count));
    }
  else
    lasterr = ERROR_INVALID_PARAMETER;
out:
  return error ("partition");
}

int
mtinfo_drive::set_partition (HANDLE mt, long count)
{
  if (count < 0 || (unsigned long) count >= MAX_PARTITION_NUM)
    lasterr = ERROR_INVALID_PARAMETER;
  else if ((DWORD) count >= dp ()->MaximumPartitionCount)
    lasterr = ERROR_IO_DEVICE;
  else
    {
      int part_block = part (count)->block >= 0 ? part (count)->block : 0;
      int err = _set_pos (mt, TAPE_LOGICAL_BLOCK, part_block, count + 1, FALSE);
      if (err)
	{
	  int sav_block = block;
	  int sav_partition = partition;
	  get_pos (mt);
	  if (sav_partition != partition)
	    {
	      if (partition < MAX_PARTITION_NUM
		  && part (partition)->block != block)
		part (partition)->initialize (block);
	    }
	  else if (sav_block != block && partition < MAX_PARTITION_NUM)
	    part (partition)->initialize (block);
	  lasterr = err;
	}
      else
	{
	  partition = count;
	  if (part (partition)->block == -1)
	    part (partition)->initialize (0);
	}
    }
  return error ("set_partition");
}

int
mtinfo_drive::write_marks (HANDLE mt, int marktype, DWORD count)
{
  /* If an async write is still pending, wait for completion. */
  if (dirty == async_write_pending)
    {
      lasterr = async_wait (mt, NULL);
      dirty = has_written;
    }
  if (marktype != TAPE_SETMARKS)
    dirty = clean;
  if (marktype == TAPE_FILEMARKS
      && !get_feature (TAPE_DRIVE_WRITE_FILEMARKS))
    {
      if (get_feature (TAPE_DRIVE_WRITE_LONG_FMKS))
	marktype = TAPE_LONG_FILEMARKS;
      else
	marktype = TAPE_SHORT_FILEMARKS;
    }
  TAPE_FUNC (WriteTapemark (mt, marktype, count, FALSE));
  int err = lasterr;
  if (!err)
    {
      block += count;
      part (partition)->block += count;
      if (part (partition)->file >= 0)
	part (partition)->file += count;
      part (partition)->fblock = 0;
      part (partition)->emark = eof;
      part (partition)->smark = (marktype == TAPE_SETMARKS);
    }
  else
    {
      int sav_block = block;
      int sav_partition = partition;
      get_pos (mt);
      if (sav_partition != partition)
	{
	  if (partition < MAX_PARTITION_NUM
	      && part (partition)->block != block)
	    part (partition)->initialize (block);
	}
      else if (sav_block != block && partition < MAX_PARTITION_NUM)
	part (partition)->initialize (block);
      lasterr = err;
    }
  return error ("write_marks");
}

int
mtinfo_drive::erase (HANDLE mt, int mode)
{
  switch (mode)
    {
      case TAPE_ERASE_SHORT:
	if (!get_feature (TAPE_DRIVE_ERASE_SHORT))
	  mode = TAPE_ERASE_LONG;
	break;
      case TAPE_ERASE_LONG:
	if (!get_feature (TAPE_DRIVE_ERASE_LONG))
	  mode = TAPE_ERASE_SHORT;
	break;
    }
  TAPE_FUNC (EraseTape (mt, mode, nowait () ? TRUE : FALSE));
  part (partition)->initialize (0);
  return error ("erase");
}

int
mtinfo_drive::prepare (HANDLE mt, int action, bool is_auto)
{
  BOOL dont_wait = FALSE;

  /* If an async write is still pending, wait for completion. */
  if (dirty == async_write_pending)
    lasterr = async_wait (mt, NULL);
  dirty = clean;
  if (action == TAPE_UNLOAD || action == TAPE_LOAD || action == TAPE_TENSION)
    dont_wait = nowait () ? TRUE : FALSE;
  TAPE_FUNC (PrepareTape (mt, action, dont_wait));
  /* Reset buffer after all successful preparations but lock and unlock. */
  switch (action)
    {
      case TAPE_FORMAT:
      case TAPE_UNLOAD:
      case TAPE_LOAD:
	initialize (drive, false);
	break;
      case TAPE_TENSION:
	part (partition)->initialize (0);
	break;
      case TAPE_LOCK:
	lock = lasterr ? lock_error : is_auto ? auto_locked : locked;
	break;
      case TAPE_UNLOCK:
	lock = lasterr ? lock_error : unlocked;
	break;
    }
  return error ("prepare");
}

int
mtinfo_drive::set_compression (HANDLE mt, long count)
{
  if (!get_feature (TAPE_DRIVE_SET_COMPRESSION))
    return ERROR_INVALID_PARAMETER;
  TAPE_SET_DRIVE_PARAMETERS sdp =
    {
      dp ()->ECC,
      count ? TRUE : FALSE,
      dp ()->DataPadding,
      dp ()->ReportSetmarks,
      dp ()->EOTWarningZoneSize
    };
  TAPE_FUNC (SetTapeParameters (mt, SET_TAPE_DRIVE_INFORMATION, &sdp));
  int err = lasterr;
  if (!err)
    dp ()->Compression = sdp.Compression;
  else
    get_dp (mt);
  lasterr = err;
  return error ("set_compression");
}

int
mtinfo_drive::set_blocksize (HANDLE mt, long count)
{
  TAPE_SET_MEDIA_PARAMETERS smp = {count};
  TAPE_FUNC (SetTapeParameters (mt, SET_TAPE_MEDIA_INFORMATION, &smp));
  return error ("set_blocksize");
}

int
mtinfo_drive::get_status (HANDLE mt, struct mtget *get)
{
  int notape = 0;
  DWORD tstat;

  if (!get)
    return ERROR_INVALID_PARAMETER;

  if ((tstat = GetTapeStatus (mt)) == ERROR_NO_MEDIA_IN_DRIVE)
    notape = 1;

  memset (get, 0, sizeof *get);

  get->mt_type = MT_ISUNKNOWN;

  if (!notape && get_feature (TAPE_DRIVE_SET_BLOCK_SIZE))
    get->mt_dsreg = (mp ()->BlockSize << MT_ST_BLKSIZE_SHIFT)
		    & MT_ST_BLKSIZE_MASK;
  else
    get->mt_dsreg = (dp ()->DefaultBlockSize << MT_ST_BLKSIZE_SHIFT)
		    & MT_ST_BLKSIZE_MASK;

  if (wincap.has_ioctl_storage_get_media_types_ex ())
    {
      DWORD size = sizeof (GET_MEDIA_TYPES) + 10 * sizeof (DEVICE_MEDIA_INFO);
      void *buf = alloca (size);
      if (DeviceIoControl (mt, IOCTL_STORAGE_GET_MEDIA_TYPES_EX,
			   NULL, 0, buf, size, &size, NULL)
	  || GetLastError () == ERROR_MORE_DATA)
	{
	  PGET_MEDIA_TYPES gmt = (PGET_MEDIA_TYPES) buf;
	  for (DWORD i = 0; i < gmt->MediaInfoCount; ++i)
	    {
	      PDEVICE_MEDIA_INFO dmi = &gmt->MediaInfo[i];
	      get->mt_type = dmi->DeviceSpecific.TapeInfo.MediaType;
#define TINFO DeviceSpecific.TapeInfo
	      if (dmi->TINFO.MediaCharacteristics & MEDIA_CURRENTLY_MOUNTED)
		{
		  get->mt_type = dmi->DeviceSpecific.TapeInfo.MediaType;
		  if (dmi->TINFO.BusType == BusTypeScsi)
		    get->mt_dsreg |=
		      (dmi->TINFO.BusSpecificData.ScsiInformation.DensityCode
		       << MT_ST_DENSITY_SHIFT)
		      & MT_ST_DENSITY_MASK;
		  break;
		}
#undef TINFO
	    }
	}
    }

  if (!notape)
    {
      get->mt_resid = partition;
      get->mt_fileno = part (partition)->file;
      get->mt_blkno = part (partition)->fblock;

      if (get->mt_blkno != 0)
	/* nothing to do */;
      else if (get->mt_fileno == 0)
	get->mt_gstat |= GMT_BOT (-1);
      else
	get->mt_gstat |= GMT_EOF (-1);
      if (part (partition)->emark >= eod_hit)
	get->mt_gstat |= GMT_EOD (-1);
      if (part (partition)->emark >= eom_hit)
	get->mt_gstat |= GMT_EOT (-1);

      if (part (partition)->smark)
	get->mt_gstat |= GMT_SM (-1);

      get->mt_gstat |= GMT_ONLINE (-1);

      if (mp ()->WriteProtected)
	get->mt_gstat |= GMT_WR_PROT (-1);

      get->mt_capacity = get_ll (mp ()->Capacity);
      get->mt_remaining = get_ll (mp ()->Remaining);
  }

  if (notape)
    get->mt_gstat |= GMT_DR_OPEN (-1);

  if (buffer_writes ())
    get->mt_gstat |= GMT_IM_REP_EN (-1);	/* TODO: Async writes */

  if (tstat == ERROR_DEVICE_REQUIRES_CLEANING)
    get->mt_gstat |= GMT_CLN (-1);

  /* Cygwin specials: */
  if (dp ()->ReportSetmarks)
    get->mt_gstat |= GMT_REP_SM (-1);
  if (dp ()->DataPadding)
    get->mt_gstat |= GMT_PADDING (-1);
  if (dp ()->ECC)
    get->mt_gstat |= GMT_HW_ECC (-1);
  if (dp ()->Compression)
    get->mt_gstat |= GMT_HW_COMP (-1);
  if (two_fm ())
    get->mt_gstat |= GMT_TWO_FM (-1);
  if (fast_eom ())
    get->mt_gstat |= GMT_FAST_MTEOM (-1);
  if (auto_lock ())
    get->mt_gstat |= GMT_AUTO_LOCK (-1);
  if (sysv ())
    get->mt_gstat |= GMT_SYSV (-1);
  if (nowait ())
    get->mt_gstat |= GMT_NOWAIT (-1);
  if (async_writes ())
    get->mt_gstat |= GMT_ASYNC (-1);

  get->mt_erreg = 0;				/* FIXME: No softerr counting */

  get->mt_minblksize = dp ()->MinimumBlockSize;
  get->mt_maxblksize = dp ()->MaximumBlockSize;
  get->mt_defblksize = dp ()->DefaultBlockSize;
  get->mt_featureslow = dp ()->FeaturesLow;
  get->mt_featureshigh = dp ()->FeaturesHigh;
  get->mt_eotwarningzonesize = dp ()->EOTWarningZoneSize;

  return 0;
}

int
mtinfo_drive::set_options (HANDLE mt, long options)
{
  long what = (options & MT_ST_OPTIONS);
  bool call_setparams = false;
  bool set;
  TAPE_SET_DRIVE_PARAMETERS sdp =
    {
      dp ()->ECC,
      dp ()->Compression,
      dp ()->DataPadding,
      dp ()->ReportSetmarks,
      dp ()->EOTWarningZoneSize
    };

  lasterr = 0;
  switch (what)
    {
      case 0:
	if (options == 0 || options == 1)
	  {
	    buffer_writes ((options == 1));
	  }
	break;
      case MT_ST_BOOLEANS:
	buffer_writes (!!(options & MT_ST_BUFFER_WRITES));
	async_writes (!!(options & MT_ST_ASYNC_WRITES));
	two_fm (!!(options & MT_ST_TWO_FM));
	fast_eom (!!(options & MT_ST_FAST_MTEOM));
	auto_lock (!!(options & MT_ST_AUTO_LOCK));
	sysv (!!(options & MT_ST_SYSV));
	nowait (!!(options & MT_ST_NOWAIT));
	if (get_feature (TAPE_DRIVE_SET_ECC))
	  sdp.ECC = !!(options & MT_ST_ECC);
	if (get_feature (TAPE_DRIVE_SET_PADDING))
	  sdp.DataPadding = !!(options & MT_ST_PADDING);
	if (get_feature (TAPE_DRIVE_SET_REPORT_SMKS))
	  sdp.ReportSetmarks = !!(options & MT_ST_REPORT_SM);
	if (sdp.ECC != dp ()->ECC || sdp.DataPadding != dp ()->DataPadding
	    || sdp.ReportSetmarks != dp ()->ReportSetmarks)
	  call_setparams = true;
	break;
      case MT_ST_SETBOOLEANS:
      case MT_ST_CLEARBOOLEANS:
	set = (what == MT_ST_SETBOOLEANS);
	if (options & MT_ST_BUFFER_WRITES)
	  buffer_writes (set);
	if (options & MT_ST_ASYNC_WRITES)
	  async_writes (set);
	if (options & MT_ST_TWO_FM)
	  two_fm (set);
	if (options & MT_ST_FAST_MTEOM)
	  fast_eom (set);
	if (options & MT_ST_AUTO_LOCK)
	  auto_lock (set);
	if (options & MT_ST_SYSV)
	  sysv (set);
	if (options & MT_ST_NOWAIT)
	  nowait (set);
	if (options & MT_ST_ECC)
	  sdp.ECC = set;
	if (options & MT_ST_PADDING)
	  sdp.DataPadding = set;
	if (options & MT_ST_REPORT_SM)
	  sdp.ReportSetmarks = set;
	if (sdp.ECC != dp ()->ECC || sdp.DataPadding != dp ()->DataPadding
	    || sdp.ReportSetmarks != dp ()->ReportSetmarks)
	  call_setparams = true;
	break;
      case MT_ST_EOT_WZ_SIZE:
	if (get_feature (TAPE_DRIVE_SET_EOT_WZ_SIZE))
	  {
	    sdp.EOTWarningZoneSize = (options & ~MT_ST_OPTIONS);
	    if (sdp.EOTWarningZoneSize != dp ()->EOTWarningZoneSize)
	      call_setparams = true;
	  }
	break;
    }
  if (call_setparams)
    {
      TAPE_FUNC (SetTapeParameters (mt, SET_TAPE_DRIVE_INFORMATION, &sdp));
      int err = lasterr;
      if (!err)
	{
	  dp ()->ECC = sdp.ECC;
	  dp ()->DataPadding = sdp.DataPadding;
	  dp ()->ReportSetmarks = sdp.ReportSetmarks;
	}
      else
	get_dp (mt);
      lasterr = err;
    }
  return error ("set_options");
}

int
mtinfo_drive::ioctl (HANDLE mt, unsigned int cmd, void *buf)
{
  myfault efault;
  if (efault.faulted ())
    return ERROR_NOACCESS;
  if (cmd == MTIOCTOP)
    {
      struct mtop *op = (struct mtop *) buf;
      if (lasterr == ERROR_BUS_RESET)
	{
	  /* If a bus reset occurs, block further access to this device
	     until the user rewinds, unloads or in any other way tries
	     to maintain a well-known tape position. */
	  if (op->mt_op != MTREW && op->mt_op != MTOFFL
	      && op->mt_op != MTRETEN && op->mt_op != MTERASE
	      && op->mt_op != MTSEEK && op->mt_op != MTEOM)
	    return ERROR_BUS_RESET;
	  /* Try to maintain last lock state after bus reset. */
	  if (lock >= auto_locked && PrepareTape (mt, TAPE_LOCK, FALSE))
	    {
	      debug_printf ("Couldn't relock drive after bus reset.");
	      lock = unlocked;
	    }
	}
      switch (op->mt_op)
	{
	  case MTRESET:
	    break;
	  case MTFSF:
	    set_pos (mt, TAPE_SPACE_FILEMARKS, op->mt_count, false);
	    break;
	  case MTBSF:
	    set_pos (mt, TAPE_SPACE_FILEMARKS, -op->mt_count, false);
	    break;
	  case MTFSR:
	    set_pos (mt, TAPE_SPACE_RELATIVE_BLOCKS, op->mt_count, false);
	    break;
	  case MTBSR:
	    set_pos (mt, TAPE_SPACE_RELATIVE_BLOCKS, -op->mt_count, false);
	    break;
	  case MTWEOF:
	    write_marks (mt, TAPE_FILEMARKS, op->mt_count);
	    break;
	  case MTREW:
	    set_pos (mt, TAPE_REWIND, 0, false);
	    break;
	  case MTOFFL:
	  case MTUNLOAD:
	    prepare (mt, TAPE_UNLOAD);
	    break;
	  case MTNOP:
	    lasterr = 0;
	    break;
	  case MTRETEN:
	    if (!get_feature (TAPE_DRIVE_TENSION))
	      lasterr = ERROR_INVALID_PARAMETER;
	    else if (!set_pos (mt, TAPE_REWIND, 0, false))
	      prepare (mt, TAPE_TENSION);
	    break;
	  case MTBSFM:
	    set_pos (mt, TAPE_SPACE_FILEMARKS, -op->mt_count, true);
	    break;
	  case MTFSFM:
	    set_pos (mt, TAPE_SPACE_FILEMARKS, op->mt_count, true);
	    break;
	  case MTEOM:
	    if (fast_eom () && get_feature (TAPE_DRIVE_END_OF_DATA))
	      set_pos (mt, TAPE_SPACE_END_OF_DATA, 0, false);
	    else
	      set_pos (mt, TAPE_SPACE_FILEMARKS, 32767, false);
	    break;
	  case MTERASE:
	    erase (mt, TAPE_ERASE_LONG);
	    break;
	  case MTRAS1:
	  case MTRAS2:
	  case MTRAS3:
	    lasterr = ERROR_INVALID_PARAMETER;
	    break;
	  case MTSETBLK:
	    if (!get_feature (TAPE_DRIVE_SET_BLOCK_SIZE))
	      {
		lasterr = ERROR_INVALID_PARAMETER;
		break;
	      }
	    if ((DWORD) op->mt_count == mp ()->BlockSize)
	      {
		/* Nothing has changed. */
		lasterr = 0;
		break;
	      }
	    if ((op->mt_count == 0 && !get_feature (TAPE_DRIVE_VARIABLE_BLOCK))
		|| (op->mt_count > 0
		    && ((DWORD) op->mt_count < dp ()->MinimumBlockSize
			|| (DWORD) op->mt_count > dp ()->MaximumBlockSize)))
	      {
		lasterr = ERROR_INVALID_PARAMETER;
		break;
	      }
	    if (set_blocksize (mt, op->mt_count)
		&& lasterr == ERROR_INVALID_FUNCTION)
	      lasterr = ERROR_INVALID_BLOCK_LENGTH;
	    break;
	  case MTSEEK:
	    if (get_feature (TAPE_DRIVE_LOGICAL_BLK))
	      set_pos (mt, TAPE_LOGICAL_BLOCK, op->mt_count, false);
	    else if (!get_pos (mt))
	      set_pos (mt, TAPE_SPACE_RELATIVE_BLOCKS,
		       op->mt_count - block, false);
	    break;
	  case MTTELL:
	    if (!get_pos (mt))
	      op->mt_count = block;
	    break;
	  case MTFSS:
	    set_pos (mt, TAPE_SPACE_SETMARKS, op->mt_count, false);
	    break;
	  case MTBSS:
	    set_pos (mt, TAPE_SPACE_SETMARKS, -op->mt_count, false);
	    break;
	  case MTWSM:
	    write_marks (mt, TAPE_SETMARKS, op->mt_count);
	    break;
	  case MTLOCK:
	    prepare (mt, TAPE_LOCK);
	    break;
	  case MTUNLOCK:
	    prepare (mt, TAPE_UNLOCK);
	    break;
	  case MTLOAD:
	    prepare (mt, TAPE_LOAD);
	    break;
	  case MTCOMPRESSION:
	    set_compression (mt, op->mt_count);
	    break;
	  case MTSETPART:
	    set_partition (mt, op->mt_count);
	    break;
	  case MTMKPART:
	    create_partitions (mt, op->mt_count);
	    break;
	  case MTSETDRVBUFFER:
	    set_options (mt, op->mt_count);
	    break;
	  case MTSETDENSITY:
	  default:
	    lasterr = ERROR_INVALID_PARAMETER;
	    break;
	}
    }
  else if (cmd == MTIOCGET)
    get_status (mt, (struct mtget *) buf);
  else if (cmd == MTIOCPOS && !get_pos (mt))
    ((struct mtpos *) buf)->mt_blkno = block;

  return lasterr;
}

/**********************************************************************/
/* mtinfo */

void
mtinfo::initialize ()
{
  for (unsigned i = 0; i < MAX_DRIVE_NUM; ++i)
    drive (i)->initialize (i, true);
}

/**********************************************************************/
/* fhandler_dev_tape */

#define mt	(cygwin_shared->mt)

#define lock(err_ret_val) if (!_lock ()) return err_ret_val;

inline bool
fhandler_dev_tape::_lock ()
{
  HANDLE obj[2] = { mt_mtx, signal_arrived };
  BOOL ret = WaitForMultipleObjects (2, obj, FALSE, INFINITE) == WAIT_OBJECT_0;
  if (!ret)
    {
      debug_printf ("signal_arrived"); \
      set_errno (EINTR);
    }
  return ret;
}

inline int
fhandler_dev_tape::unlock (int ret)
{
  ReleaseMutex (mt_mtx);
  return ret;
}

fhandler_dev_tape::fhandler_dev_tape ()
  : fhandler_dev_raw ()
{
  debug_printf ("unit: %d", dev ().minor);
}

int
fhandler_dev_tape::open (int flags, mode_t)
{
  int ret;

  if (driveno () >= MAX_DRIVE_NUM)
    {
      set_errno (ENOENT);
      return 0;
    }
  if (!(mt_mtx = CreateMutex (&sec_all, !!(flags & O_CLOEXEC), NULL)))
    {
      __seterrno ();
      return 0;
    }

  /* The O_SYNC flag is not supported by the tape driver.  Use the
     MT_ST_BUFFER_WRITES and MT_ST_ASYNC_WRITES flags in the drive
     settings instead.  In turn, the MT_ST_BUFFER_WRITES is translated
     into O_SYNC, which controls the FILE_WRITE_THROUGH flag in the
     NtCreateFile call in fhandler_base::open. */
  flags &= ~O_SYNC;
  if (!mt.drive (driveno ())->buffer_writes ())
    flags |= O_SYNC;

  ret = fhandler_dev_raw::open (flags);
  if (ret)
    {
      mt.drive (driveno ())->open (get_handle ());

      /* In append mode, seek to beginning of next filemark */
      if (flags & O_APPEND)
	mt.drive (driveno ())->set_pos (get_handle (),
					 TAPE_SPACE_FILEMARKS, 1, true);

      if (!(flags & O_DIRECT))
	{
	  devbufsiz = mt.drive (driveno ())->dp ()->MaximumBlockSize;
	  devbuf = new char [devbufsiz];
	}
      devbufstart = devbufend = 0;
    }
  else
    ReleaseMutex (mt_mtx);
  return ret;
}

int
fhandler_dev_tape::close ()
{
  int ret = 0;
  int cret = 0;

  if (!hExeced)
    {
      lock (-1);
      ret = mt.drive (driveno ())->close (get_handle (), is_rewind_device ());
      if (ret)
	__seterrno_from_win_error (ret);
      cret = fhandler_dev_raw::close ();
      unlock (0);
    }
  if (mt_evt)
    CloseHandle (mt_evt);
  CloseHandle (mt_mtx);
  return ret ? -1 : cret;
}

void __stdcall
fhandler_dev_tape::raw_read (void *ptr, size_t &ulen)
{
  char *buf = (char *) ptr;
  size_t len = ulen;
  size_t block_size;
  size_t bytes_to_read;
  size_t bytes_read = 0;
  int ret = 0;

  if (lastblk_to_read ())
    {
      lastblk_to_read (false);
      ulen = 0;
      return;
    }
  if (!_lock ())
    {
      ulen = (size_t) -1;
      return;
    }
  block_size = mt.drive (driveno ())->mp ()->BlockSize;
  if (devbuf)
    {
      if (devbufend > devbufstart)
	{
	  bytes_to_read = min (len, devbufend - devbufstart);
	  debug_printf ("read %d bytes from buffer (rest %d)",
			bytes_to_read, devbufend - devbufstart - bytes_to_read);
	  memcpy (buf, devbuf + devbufstart, bytes_to_read);
	  len -= bytes_to_read;
	  bytes_read += bytes_to_read;
	  buf += bytes_to_read;
	  devbufstart += bytes_to_read;
	  if (devbufstart == devbufend)
	    devbufstart = devbufend = 0;
	  /* If a switch to variable block_size occured, just return the buffer
	     remains until the buffer is empty, then proceed with usual variable
	     block size handling (one block per read call). */
	  if (!block_size)
	    len = 0;
	}
      if (len > 0)
	{
	  if (!mt_evt && !(mt_evt = CreateEvent (&sec_none, TRUE, FALSE, NULL)))
	    debug_printf ("Creating event failed, %E");
	  size_t block_fit = !block_size ? len : rounddown(len,  block_size);
	  if (block_fit)
	    {
	      debug_printf ("read %d bytes from tape (rest %d)",
			    block_fit, len - block_fit);
	      ret = mt.drive (driveno ())->read (get_handle (), mt_evt, buf,
						  block_fit);
	      if (ret)
		__seterrno_from_win_error (ret);
	      else if (block_fit)
		{
		  len -= block_fit;
		  bytes_read += block_fit;
		  buf += block_fit;
		  /* Only one block in each read call, please. */
		  if (!block_size)
		    len = 0;
		}
	      else {
		len = 0;
		if (bytes_read)
		  lastblk_to_read (true);
	      }
	    }
	  if (!ret && len > 0)
	    {
	      debug_printf ("read %d bytes from tape (one block)", block_size);
	      ret = mt.drive (driveno ())->read (get_handle (), mt_evt, devbuf,
						  block_size);
	      if (ret)
		__seterrno_from_win_error (ret);
	      else if (block_size)
		{
		  devbufstart = len;
		  devbufend = block_size;
		  bytes_read += len;
		  memcpy (buf, devbuf, len);
		}
	      else if (bytes_read)
		lastblk_to_read (true);
	    }
	}
    }
  else
    {
      if (!mt_evt && !(mt_evt = CreateEvent (&sec_none, TRUE, FALSE, NULL)))
	debug_printf ("Creating event failed, %E");
      bytes_read = ulen;
      ret = mt.drive (driveno ())->read (get_handle (), mt_evt, ptr,
					  bytes_read);
    }
  ulen = (ret ? (size_t) -1 : bytes_read);
  unlock ();
}

ssize_t __stdcall
fhandler_dev_tape::raw_write (const void *ptr, size_t len)
{
  lock (-1);
  if (!mt_evt && !(mt_evt = CreateEvent (&sec_none, TRUE, FALSE, NULL)))
    debug_printf ("Creating event failed, %E");
  int ret = mt.drive (driveno ())->write (get_handle (), mt_evt, ptr, len);
  if (ret)
    __seterrno_from_win_error (ret);
  return unlock (ret ? -1 : (int) len);
}

_off64_t
fhandler_dev_tape::lseek (_off64_t offset, int whence)
{
  struct mtop op;
  struct mtpos pos;
  DWORD block_size;
  _off64_t ret = ILLEGAL_SEEK;

  lock (ILLEGAL_SEEK);

  debug_printf ("lseek (%s, %d, %d)", get_name (), offset, whence);

  block_size = mt.drive (driveno ())->mp ()->BlockSize;
  if (block_size == 0)
    {
      set_errno (EIO);
      goto out;
    }

  if (ioctl (MTIOCPOS, &pos))
    goto out;

  switch (whence)
    {
      case SEEK_END:
	op.mt_op = MTFSF;
	op.mt_count = 1;
	if (ioctl (MTIOCTOP, &op))
	  goto out;
	break;
      case SEEK_SET:
	if (whence == SEEK_SET && offset < 0)
	  {
	    set_errno (EINVAL);
	    goto out;
	  }
	break;
      case SEEK_CUR:
	break;
      default:
	set_errno (EINVAL);
	goto out;
    }

  op.mt_op = MTFSR;
  op.mt_count = offset / block_size
		- (whence == SEEK_SET ? pos.mt_blkno : 0);

  if (op.mt_count < 0)
    {
      op.mt_op = MTBSR;
      op.mt_count = -op.mt_count;
    }

  if (ioctl (MTIOCTOP, &op) || ioctl (MTIOCPOS, &pos))
    goto out;

  ret = pos.mt_blkno * block_size;

out:
  return unlock (ret);
}

int
fhandler_dev_tape::fstat (struct __stat64 *buf)
{
  int ret;

  if (driveno () >= MAX_DRIVE_NUM)
    {
      set_errno (ENOENT);
      return -1;
    }
  if (!(ret = fhandler_base::fstat (buf)))
    buf->st_blocks = 0;
  return ret;
}

int
fhandler_dev_tape::dup (fhandler_base *child)
{
  lock (-1);
  fhandler_dev_tape *fh = (fhandler_dev_tape *) child;
  if (!DuplicateHandle (GetCurrentProcess (), mt_mtx,
  			GetCurrentProcess (), &fh->mt_mtx,
			0, TRUE, DUPLICATE_SAME_ACCESS))
    {
      debug_printf ("dup(%s) failed, mutex handle %x, %E",
		    get_name (), mt_mtx);
      __seterrno ();
      return unlock (-1);
    }
  fh->mt_evt = NULL;
  if (mt_evt &&
      !DuplicateHandle (GetCurrentProcess (), mt_evt,
			GetCurrentProcess (), &fh->mt_evt,
			0, TRUE, DUPLICATE_SAME_ACCESS))
    {
      debug_printf ("dup(%s) failed, event handle %x, %E",
		    get_name (), mt_evt);
      __seterrno ();
      return unlock (-1);
    }
  return unlock (fhandler_dev_raw::dup (child));
}

void
fhandler_dev_tape::fixup_after_fork (HANDLE parent)
{
  fhandler_dev_raw::fixup_after_fork (parent);
  fork_fixup (parent, mt_mtx, "mt_mtx");
  if (mt_evt)
    fork_fixup (parent, mt_evt, "mt_evt");
}

void
fhandler_dev_tape::set_close_on_exec (bool val)
{
  fhandler_dev_raw::set_close_on_exec (val);
  set_no_inheritance (mt_mtx, val);
  if (mt_evt)
    set_no_inheritance (mt_evt, val);
}

int
fhandler_dev_tape::ioctl (unsigned int cmd, void *buf)
{
  int ret = 0;
  lock (-1);
  if (cmd == MTIOCTOP || cmd == MTIOCGET || cmd == MTIOCPOS)
    {
      ret = mt.drive (driveno ())->ioctl (get_handle (), cmd, buf);
      if (ret)
	__seterrno_from_win_error (ret);
      return unlock (ret ? -1 : 0);
    }
  return unlock (fhandler_dev_raw::ioctl (cmd, buf));
}