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

   Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
   2010, 2011, 2012, 2013, 2014, 2015 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 <devioctl.h>
#include <ntddstor.h>
#include "security.h"
#include "path.h"
#include "fhandler.h"
#include "dtable.h"
#include "cygheap.h"
#include "shared_info.h"
#include "sigproc.h"
#include "child_info.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 (int64_t 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)
{
  /* First access after opening the device can return BUS RESET, but we
     need the drive parameters, so just try again. */
  while (get_dp (mt) == ERROR_BUS_RESET)
    ;
  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, LPOVERLAPPED pov, 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 = pov;
  ov->Offset = ov->OffsetHigh = 0;
  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)
    {
      int32_t 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, LPOVERLAPPED pov, 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 = pov;
  ov->Offset = ov->OffsetHigh = 0;
  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)
    {
      int32_t 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, int32_t *ppartition, int64_t *pblock)
{
  DWORD p;
  ULARGE_INTEGER b;

  TAPE_FUNC (GetTapePosition (mt, TAPE_LOGICAL_POSITION, &p,
			      &b.LowPart, &b.HighPart));
  if (lasterr == ERROR_INVALID_FUNCTION)
    TAPE_FUNC (GetTapePosition (mt, TAPE_ABSOLUTE_POSITION, &p,
				&b.LowPart, &b.HighPart));
  if (!lasterr)
    {
      if (p > 0)
	partition = (int32_t) p - 1;
      block = (int64_t) b.QuadPart;
      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, int64_t 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;
  LARGE_INTEGER c = { QuadPart:count };
  TAPE_FUNC (SetTapePosition (mt, mode, partition, c.LowPart, c.HighPart,
			      dont_wait));
  return lasterr;
}

int
mtinfo_drive::set_pos (HANDLE mt, int mode, int64_t count, bool sfm_func)
{
  int err = 0;
  int64_t 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, int32_t 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))
    {
      TAPE_FUNC (CreateTapePartition (mt, TAPE_INITIATOR_PARTITIONS,
				      count <= 0 ? 0 : 2, (DWORD) count));
    }
  else if (get_feature (TAPE_DRIVE_SELECT))
    {
      TAPE_FUNC (CreateTapePartition (mt, TAPE_SELECT_PARTITIONS,
				      count <= 0 ? 0 : 2, 0));
    }
  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, int32_t count)
{
  if (count < 0 || (uint32_t) count >= MAX_PARTITION_NUM)
    lasterr = ERROR_INVALID_PARAMETER;
  else if ((DWORD) count >= dp ()->MaximumPartitionCount)
    lasterr = ERROR_IO_DEVICE;
  else
    {
      uint64_t 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)
	{
	  int64_t sav_block = block;
	  int32_t 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
    {
      int64_t sav_block = block;
      int32_t 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, int32_t count)
{
  if (!get_feature (TAPE_DRIVE_SET_COMPRESSION))
    return ERROR_INVALID_PARAMETER;
  TAPE_SET_DRIVE_PARAMETERS sdp =
    {
      dp ()->ECC,
      (BOOLEAN) (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, DWORD count)
{
  TAPE_SET_MEDIA_PARAMETERS smp = {count};
  TAPE_FUNC (SetTapeParameters (mt, SET_TAPE_MEDIA_INFORMATION, &smp));
  /* Make sure to update blocksize info! */
  return lasterr ? error ("set_blocksize") : get_mp (mt);
}

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
      || get_mp (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;

  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 & 0xffff)
		      | ((mp ()->PartitionCount & 0xffff) << 16);
      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 = mp ()->Capacity.QuadPart;
      get->mt_remaining = mp ()->Remaining.QuadPart;
  }

  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, int32_t options)
{
  int32_t 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)
{
  __try
    {
      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 = (int) 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 = (long) block;
    }
  __except (NO_ERROR)
    {
      lasterr = ERROR_NOACCESS;
    }
  __endtry
  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 (false)) return (err_ret_val);

inline bool
fhandler_dev_tape::_lock (bool cancelable)
{
  /* O_NONBLOCK is only valid in a read or write call.  Only those are
     cancelable. */
  DWORD timeout = cancelable && is_nonblocking () ? 0 : INFINITE;
  switch (cygwait (mt_mtx, timeout,
		   cw_sig | cw_sig_restart | cw_cancel | cw_cancel_self))
    {
    case WAIT_OBJECT_0:
      return true;
    case WAIT_TIMEOUT:
      set_errno (EAGAIN);
      return false;
    default:
      __seterrno ();
      return false;
    }
}

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 ().get_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;
	  devbufalign = 1;
	  devbufalloc = devbuf = new char [devbufsiz];
	}
    }
  else
    ReleaseMutex (mt_mtx);
  return ret;
}

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

  if (!have_execed)
    {
      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 (ov.hEvent)
    CloseHandle (ov.hEvent);
  CloseHandle (mt_mtx);
  return ret ? -1 : cret;
}

void __reg3
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 (true))
    {
      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 %lu bytes from buffer (rest %lu)",
			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 (!ov.hEvent
	      && !(ov.hEvent = 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 %lu bytes from tape (rest %lu)",
			    block_fit, len - block_fit);
	      ret = mt.drive (driveno ())->read (get_handle (), &ov, 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 %lu bytes from tape (one block)", block_size);
	      ret = mt.drive (driveno ())->read (get_handle (), &ov, 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 (!ov.hEvent
	  && !(ov.hEvent = CreateEvent (&sec_none, TRUE, FALSE, NULL)))
	debug_printf ("Creating event failed, %E");
      bytes_read = ulen;
      ret = mt.drive (driveno ())->read (get_handle (), &ov, ptr, bytes_read);
    }
  ulen = (ret ? (size_t) -1 : bytes_read);
  unlock ();
}

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

off_t
fhandler_dev_tape::lseek (off_t offset, int whence)
{
#if 1
  /* On Linux lseek on tapes is a no-op.  For now, let's keep the old code
     intact but commented out, should incompatibilities arise. */
  return 0;
#else
  struct mtop op;
  struct mtpos pos;
  DWORD block_size;
  off_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);
#endif
}

int __reg2
fhandler_dev_tape::fstat (struct stat *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, int flags)
{
  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 %p, %E",
		    get_name (), mt_mtx);
      __seterrno ();
      return unlock (-1);
    }
  fh->ov.hEvent = NULL;
  if (ov.hEvent &&
      !DuplicateHandle (GetCurrentProcess (), ov.hEvent,
			GetCurrentProcess (), &fh->ov.hEvent,
			0, TRUE, DUPLICATE_SAME_ACCESS))
    {
      debug_printf ("dup(%s) failed, event handle %p, %E",
		    get_name (), ov.hEvent);
      __seterrno ();
      return unlock (-1);
    }
  return unlock (fhandler_dev_raw::dup (child, flags));
}

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

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 (ov.hEvent)
    set_no_inheritance (ov.hEvent, 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));
}
