/* fhandler_tape.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 "cygtls.h" #include #include #include #include #include #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)); }