1 ////////////////////////////////////////////////////////////////////////////////////
2 // W32STAPE.C -- Hercules Win32 SCSI Tape handling module
3 //
4 // (c) Copyright "Fish" (David B. Trout), 2005-2009. Released under
5 // the Q Public License (http://www.hercules-390.org/herclic.html)
6 // as modifications to Hercules.
7 ////////////////////////////////////////////////////////////////////////////////////
8 //
9 // This module contains only WIN32 support for SCSI tapes.
10 // Primary SCSI Tape support is in module 'scsitape.c'...
11 //
12 // PROGRAMMING NOTE: we rely on the known fact that
13 // 'NO_ERROR' == 0 and 'INVALID_HANDLE_VALUE' == -1.
14 //
15 ////////////////////////////////////////////////////////////////////////////////////
16
17 #include "hstdinc.h"
18
19 #define _W32STAPE_C_
20 #define _HTAPE_DLL_
21
22 #include "hercules.h"
23 #include "w32stape.h"
24 #include "tapedev.h" // (need IS_TAPE_BLKID_BOT)
25
26 #ifdef _MSVC_
27
28 ////////////////////////////////////////////////////////////////////////////////////
29 // Global data...
30
31 #define W32STAPE_MAX_FDNUMS (32) // (admitedly low, but easily increased)
32
33 typedef int ifd_t; // (internal fd)
34 typedef int ufd_t; // (user fd)
35
36 #define W32STAPE_IFD2UFD( ifd ) ((ufd_t)( (ifd) | 0x7F000000 ))
37 #define W32STAPE_UFD2IFD( ufd ) ((ifd_t)( (ufd) & ~0x7F000000 ))
38
39 static BYTE g_ifds [ W32STAPE_MAX_FDNUMS ] = {0}; // (0 == avail, 0xFF == used)
40 static HANDLE g_handles [ W32STAPE_MAX_FDNUMS ] = {0}; // (WIN32 handles)
41 static char* g_fnames [ W32STAPE_MAX_FDNUMS ] = {0}; // (for posterity)
42 static U32 g_fstats [ W32STAPE_MAX_FDNUMS ] = {0}; // (running status)
43 static U32 g_BOTmsk [ W32STAPE_MAX_FDNUMS ] = {0}; // (BOT block-id mask)
44 static U32 g_BOTbot [ W32STAPE_MAX_FDNUMS ] = {0}; // (BOT block-id value)
45
46 static TAPE_GET_DRIVE_PARAMETERS g_drive_parms [ W32STAPE_MAX_FDNUMS ] = {0}; // (drive parameters)
47
48 static LOCK g_lock; // (master global access lock)
49
50 #define lock() obtain_w32stape_lock()
51 #define unlock() release_lock( &g_lock )
52
obtain_w32stape_lock()53 static void obtain_w32stape_lock()
54 {
55 static int bDidInit = 0;
56 static int bInitBusy = 1;
57 if (!bDidInit)
58 {
59 bDidInit = 1;
60 initialize_lock ( &g_lock );
61 memset( g_ifds, 0x00, sizeof ( g_ifds ) );
62 memset( g_handles, 0x00, sizeof ( g_handles ) );
63 memset( g_fnames, 0x00, sizeof ( g_fnames ) );
64 memset( g_fstats, 0x00, sizeof ( g_fstats ) );
65 memset( g_BOTmsk, 0xFF, sizeof ( g_BOTmsk ) );
66 memset( g_BOTbot, 0x00, sizeof ( g_BOTbot ) );
67 bInitBusy = 0;
68 }
69 while (bInitBusy) Sleep(10);
70 obtain_lock ( &g_lock );
71 }
72
73 ////////////////////////////////////////////////////////////////////////////////////
74 // Allocate an internal fd number...
75
76 static
w32_alloc_ifd()77 ifd_t w32_alloc_ifd()
78 {
79 ifd_t ifd = -1;
80 errno = EMFILE;
81
82 lock();
83 {
84 BYTE* pifd_slot = memchr( g_ifds, 0, W32STAPE_MAX_FDNUMS );
85
86 if (pifd_slot)
87 {
88 int n = (int) (pifd_slot - g_ifds);
89
90 if ( n >= 0 && n < W32STAPE_MAX_FDNUMS )
91 {
92 *pifd_slot = 1;
93 errno = 0;
94 ifd = n;
95 }
96 }
97 }
98 unlock();
99
100 return ifd;
101 }
102
103 ////////////////////////////////////////////////////////////////////////////////////
104 // Release an internal fd number...
105
106 static
w32_free_ifd(ifd_t ifd)107 int w32_free_ifd( ifd_t ifd )
108 {
109 int rc = 0;
110 errno = 0;
111
112 lock();
113 {
114 if ( ifd >= 0 && ifd < W32STAPE_MAX_FDNUMS )
115 g_ifds [ ifd ] = 0;
116 else
117 {
118 rc = -1;
119 errno = EBADF;
120 }
121 }
122 unlock();
123
124 return rc;
125 }
126
127 ////////////////////////////////////////////////////////////////////////////////////
128 // Retrieve the status of the tape drive...
129
130 static
w32_get_tape_status(HANDLE hFile)131 DWORD w32_get_tape_status ( HANDLE hFile )
132 {
133 // ***************************************************************
134 // PROGRAMMING NOTE: it is THIS LOOP (retrieving the status
135 // of the tape drive) that takes UP TO *10* SECONDS TO COMPLETE
136 // if there is no tape mounted on the drive whereas it completes
137 // immediately when there IS a tape mounted! I have no idea why
138 // Windows behave so unusually/inefficiently in this way! - Fish
139 // ***************************************************************
140
141 DWORD dwTapeStatus;
142
143 // (NOTE: see also: KB 111837: "ERROR_BUS_RESET May Be Benign")
144
145 do dwTapeStatus = GetTapeStatus( hFile );
146 while (ERROR_BUS_RESET == dwTapeStatus);
147
148 return dwTapeStatus;
149 }
150
151 ////////////////////////////////////////////////////////////////////////////////////
152 // Open tape device...
153
154 DLL_EXPORT
w32_open_tape(const char * path,int oflag,...)155 ufd_t w32_open_tape ( const char* path, int oflag, ... )
156 {
157 ifd_t ifd;
158 HANDLE hFile;
159 char szTapeDeviceName[10];
160 const char* pszTapeDevNum;
161 DWORD dwDesiredAccess, dwSizeofDriveParms, dwRetCode;
162
163 // Reserve an fd number right away and bail if none available...
164 if ( (ifd = w32_alloc_ifd()) < 0 )
165 return -1;
166
167 // If they specified a Windows device name,
168 // use it as-is.
169
170 if (1
171 && strnfilenamecmp( path, "\\\\.\\", 4 ) == 0
172 && path [4] != 0
173 )
174 {
175 strlcpy( szTapeDeviceName, path, sizeof(szTapeDeviceName) );
176 }
177 else // (not a Windows device name)
178 {
179 // The device name is a Cygwin/*nix device name.
180 // Name must be either "/dev/nst0" or "/dev/st0"
181
182 if (1
183 && strnfilenamecmp( path, "/dev/", 5 ) == 0
184 && (
185 strnfilenamecmp( (pszTapeDevNum=path+8)-3, "nst", 3 ) == 0
186 ||
187 strnfilenamecmp( (pszTapeDevNum=path+7)-2, "st", 2 ) == 0
188 )
189 && strlen(pszTapeDevNum) == 1
190 && isdigit(*pszTapeDevNum)
191 )
192 {
193 // Change it to a Windows device name (e.g. \\.\Tape0)
194
195 strlcpy( szTapeDeviceName, WIN32_TAPE_DEVICE_NAME, sizeof(szTapeDeviceName) );
196 szTapeDeviceName[8] = *pszTapeDevNum;
197 szTapeDeviceName[9] = 0;
198
199 // PROGRAMMING NOTE: the "rewind at close" option (implied by
200 // virtue of the filename being "/dev/st0" and not "/dev/nst0")
201 // was handled (detected/remembered) by the higher-level caller.
202 }
203 else
204 {
205 VERIFY( w32_free_ifd( ifd ) == 0 );
206 errno = EINVAL; // (bad device name)
207 return -1; // (open failure)
208 }
209 }
210
211 // We only support O_BINARY with either O_RDWR or O_RDONLY
212
213 if (1
214 && (( O_BINARY | O_RDWR ) != oflag)
215 && (( O_BINARY | O_RDONLY) != oflag)
216 )
217 {
218 VERIFY( w32_free_ifd( ifd ) == 0 );
219 errno = EINVAL; // (invalid open flags)
220 return -1; // (open failure)
221 }
222
223 // Set desired access
224
225 dwDesiredAccess = GENERIC_READ;
226
227 if ( oflag & O_RDWR )
228 dwDesiredAccess |= GENERIC_WRITE;
229
230 // Open the tape drive...
231
232 hFile = CreateFile
233 (
234 szTapeDeviceName, // filename
235 dwDesiredAccess, // desired access
236 0, // share mode (0 == exclusive)
237 NULL, // security == default
238 OPEN_EXISTING, // "file" (device actually) must already exist
239 0, // no special access flags needed
240 NULL // not using template
241 );
242
243 if ( INVALID_HANDLE_VALUE == hFile )
244 {
245 int save_errno = w32_trans_w32error( GetLastError() );
246 VERIFY( w32_free_ifd( ifd ) == 0 );
247 errno = save_errno;
248 return -1;
249 }
250
251 // Save drive parameters for later...
252
253 memset( &g_drive_parms[ifd], 0, sizeof(TAPE_GET_DRIVE_PARAMETERS) );
254 dwSizeofDriveParms = sizeof(TAPE_GET_DRIVE_PARAMETERS);
255
256 do
257 {
258 dwRetCode = GetTapeParameters
259 (
260 hFile,
261 GET_TAPE_DRIVE_INFORMATION,
262 &dwSizeofDriveParms,
263 &g_drive_parms[ifd]
264 );
265 }
266 while ((NO_ERROR != dwRetCode) // (if not normal completion,
267 && // check for retry conditions)
268 (0
269 || ERROR_MEDIA_CHANGED == dwRetCode // (likely but unimportant; retry)
270 || ERROR_BUS_RESET == dwRetCode // (unlikely but possible; retry)
271 ));
272
273 // Did that work?
274
275 if (NO_ERROR != dwRetCode)
276 {
277 int save_errno = w32_trans_w32error( GetLastError() );
278 CloseHandle( hFile );
279 VERIFY( w32_free_ifd( ifd ) == 0 );
280 errno = save_errno;
281 return -1;
282 }
283
284 ASSERT( NO_ERROR == dwRetCode );
285 ASSERT( sizeof(TAPE_GET_DRIVE_PARAMETERS) == dwSizeofDriveParms );
286
287 // Save control info & return their file descriptor...
288
289 g_handles [ ifd ] = hFile; // (WIN32 handle)
290 g_fnames [ ifd ] = strdup( path ); // (for posterity)
291 g_fstats [ ifd ] = GMT_ONLINE (0xFFFFFFFF); // (initial status)
292 g_BOTmsk [ ifd ] = 0xFFFFFFFF; // (BOT block-id mask)
293 g_BOTbot [ ifd ] = 0x00000000; // (BOT block-id value)
294
295 return W32STAPE_IFD2UFD( ifd ); // (user fd result)
296 }
297
298 ////////////////////////////////////////////////////////////////////////////////////
299 // Define physical BOT block-id mask / value...
300
301 // PROGRAMMING NOTE: For the time being, we require 'tapedev.c' to provide to us
302 // the information we need in order to detect physical BOT (load-point). This is
303 // only until such time as I can add SCSI PassThru support to Hercules so that we
304 // can talk SCSI directly to the device ourselves (to determine such things as
305 // what type of device (manufacturer/model) we're dealing with, etc).
306
307 DLL_EXPORT
w32_define_BOT(ufd_t ufd,U32 msk,U32 bot)308 int w32_define_BOT ( ufd_t ufd, U32 msk, U32 bot )
309 {
310 ifd_t ifd = W32STAPE_UFD2IFD( ufd );
311
312 lock();
313
314 if (0
315 || ifd < 0
316 || ifd >= W32STAPE_MAX_FDNUMS
317 || g_ifds[ ifd ] == 0
318 )
319 {
320 unlock();
321 errno = EBADF;
322 return -1;
323 }
324
325 g_BOTmsk [ ifd ] = msk; // (BOT block-id mask)
326 g_BOTbot [ ifd ] = bot; // (BOT block-id value)
327
328 unlock();
329
330 return 0;
331 }
332
333 ////////////////////////////////////////////////////////////////////////////////////
334 // Post-process a tape i/o return code...
335 //
336 // Examine 'errno' (which should have been manually set to the return
337 // code from the current i/o) and update the internal status appropriately,
338 // depending on what type of error it was (tapemark, etc)...
339 //
340 // -------------------------------------------------------------
341 // *** THIS FUNCTION SHOULD BE CALLED AFTER EVERY TAPE I/O ***
342 // -------------------------------------------------------------
343 //
344 // An errno of 'EINTR' means the error was spurious (media changed, etc)
345 // and that the caller should try the same i/o again (retry their i/o).
346 //
347 // EXAMPLE:
348 //
349 // do
350 // {
351 // errno = SetTapePosition( ... );
352 // errno = w32_internal_rc ( pStat );
353 // }
354 // while ( EINTR == errno );
355 // return errno ? -1 : 0;
356 //
357 ////////////////////////////////////////////////////////////////////////////////////
358
359 // *** THIS FUNCTION SHOULD BE CALLED AFTER EVERY TAPE I/O ***
360
361 static
w32_internal_rc(U32 * pStat)362 int w32_internal_rc ( U32* pStat )
363 {
364 ASSERT( pStat ); // (sanity check)
365
366 // PROGRAMMING NOTE: the 'door open' (no tape in drive) and the
367 // 'write protected' statuses are "sticky" in that they never change
368 // until a new/different tape is mounted. All the other statuses
369 // however, change dynamically as one does i/o to the tape...
370
371 if (0
372 || ERROR_BUS_RESET == errno // (See KB 111837: "ERROR_BUS_RESET May Be Benign")
373 || ERROR_MEDIA_CHANGED == errno
374 || ERROR_DEVICE_NOT_CONNECTED == errno // (shouldn't occur but we'll check anyway)
375 || ERROR_DEV_NOT_EXIST == errno // (shouldn't occur but we'll check anyway)
376 || ERROR_FILE_NOT_FOUND == errno // (shouldn't occur but we'll check anyway)
377 )
378 {
379 *pStat &= ~GMT_DR_OPEN (0xFFFFFFFF);
380 *pStat &= ~GMT_WR_PROT (0xFFFFFFFF);
381 }
382
383 // (see PROGRAMMING NOTE above)
384
385 *pStat &= ~GMT_BOT (0xFFFFFFFF);
386 *pStat &= ~GMT_SM (0xFFFFFFFF);
387 *pStat &= ~GMT_EOF (0xFFFFFFFF);
388 *pStat &= ~GMT_EOT (0xFFFFFFFF);
389 *pStat &= ~GMT_EOD (0xFFFFFFFF);
390
391 if (0
392 || ERROR_BUS_RESET == errno // (spurious error; retry)
393 || ERROR_MEDIA_CHANGED == errno // (spurious error; retry)
394 // || ERROR_DEVICE_NOT_CONNECTED == errno // (PERM ERROR! NO RETRY!)
395 // || ERROR_DEV_NOT_EXIST == errno // (PERM ERROR! NO RETRY!)
396 // || ERROR_FILE_NOT_FOUND == errno // (PERM ERROR! NO RETRY!)
397 )
398 {
399 return EINTR; // (Interrupted system call; Retry)
400 }
401
402 // (see PROGRAMMING NOTE further above)
403
404 switch (errno)
405 {
406 default: break; // (leave errno set to whatever it already is)
407 case NO_ERROR: errno = 0; break; // (normal expected i/o result)
408
409 case ERROR_BEGINNING_OF_MEDIA: *pStat |= GMT_BOT (0xFFFFFFFF); errno = EIO; break;
410 case ERROR_END_OF_MEDIA: *pStat |= GMT_EOT (0xFFFFFFFF); errno = ENOSPC; break;
411
412 // "ERROR_END_OF_MEDIA"
413 //
414 // Msg: "The physical end of the tape has been reached."
415 //
416 // The EOT warning reflector has been reached or passed (i.e. you're
417 // now/still in the "EOT Warning Zone" area). Writing additional data
418 // and/or tapemarks may still be possible depending on the size of the
419 // EOT Warning Zone (as set by a SetTapeParameters call with a non-zero
420 // EOTWarningZoneSize value (if supported; see further below)) and
421 // how much data you've already written to the EOT Warning Zone area
422 // (i.e. once you're in the warning area, this "error" occurs after
423 // EACH and EVERY I/O [in the warning zone area] until the ABSOLUTE
424 // physical end-of-tape (ERROR_EOM_OVERFLOW) is reached; see below).
425 //
426 //
427 // ***********************
428 // ** IMPORTANT NOTE! **
429 // ***********************
430 //
431 // This is NOT actually an "error"!!!
432 //
433 //
434 // When this "error" occurs, your "ReadFile" and/or "WriteFile" call
435 // returns 'FALSE' even though ALL of your requested data was actually
436 // written successfully!! This can be verified by checking to ensure
437 // the returned "number of bytes written" actually matches the amount
438 // you asked to be written. If they're the same (and they ALWAYS will
439 // be for this specific "error" code), then it means this "error" is
440 // NOT actually an error at all, but rather just a WARNING instead!!
441 // (Had it been an actual i/o error, the error code would have been
442 // some other DIFFERENT error code value instead!!)
443 //
444 //
445 // ***********************
446 // ** ALSO IMPORTANT! **
447 // ***********************
448 // See also:
449 //
450 // http://fixunix.com/storage/205622-bug-dlttape-sys-no-eot-warning.html
451 //
452 // for ADDITIONAL IMPORTANT INFORMATION regarding always having to
453 // specifically request that this "error" code be returned to you:
454 //
455 // Even when a drive reports it does not support the setting of the
456 // the 'EOTWarningZoneSize' value (i.e. the FeaturesLow field of the
457 // GetTapeParameters call returns '0' for TAPE_DRIVE_SET_EOT_WZ_SIZE
458 // field), it may still be possible for "ERROR_END_OF_MEDIA" warnings
459 // to be generated anyway by simply calling SetTapeParameters with a
460 // non-zero 'EOTWarningZoneSize' value anyway.
461 //
462 // The reason for this is because some drives may not allow CHANGING
463 // the value (thus the reason for it reporting that setting the value
464 // is not supported), but may nevertheless still support the ENABLING
465 // of their own hard-coded internal value. That is to say, while the
466 // size of the warning zone may not be modifiable (as it may be hard-
467 // coded and thus unchangeable), the drive may still have the ability
468 // to REPORT reaching the EOT Warning zone IF SPECIFICALLY REQUESTED
469 // TO DO SO! (which is presumably what requesting a non-zero Warning
470 // Zone size would end up doing: i.e. even though such calls APPEAR
471 // to fail, they actually DO succeed in accomplishing SOMETHING, just
472 // not what you originally/specifically requested).
473 //
474 // Thus calling SetTapeParameters with a non-zero 'EOTWarningZoneSize'
475 // value might very well succeed anyway even though GetTapeParameters
476 // reports that doing so is not supported, and by so doing, may cause
477 // the drive to begin reporting of "ERROR_END_OF_MEDIA" (whereas not
478 // attempting to do so would end up leaving the drive in its default
479 // non-reporting mode. That is to say, you should ALWAYS try setting
480 // a non-zero 'EOTWarningZoneSize' value, ignoring any "unsupported"
481 // error code that may be returned from such a call.)
482
483 case ERROR_EOM_OVERFLOW: *pStat |= GMT_EOT (0xFFFFFFFF); errno = EIO; break;
484
485 // "ERROR_EOM_OVERFLOW"
486 //
487 // Msg: "Physical end of tape encountered."
488 //
489 // This error code means that the actual physical end-of-media has been
490 // reached, and no more data can be written to the tape. This includes
491 // tapemarks as well.
492 //
493 // ***********************
494 // ** IMPORTANT NOTE! **
495 // ***********************
496 //
497 // This is a HARD (UNRECOVERABLE) error!!
498 //
499 // To be programmatically informed of when you are coming close to the
500 // physical end-of-the-tape (such that you could be assured room still
501 // remained to write logical end-of-volume labels for example), simply
502 // call SetTapeParameters with a non-zero 'EOTWarningZoneSize' value
503 // and treat any "ERROR_END_OF_MEDIA" "errors" received when writing
504 // as warnings instead. (See prior discussion of "ERROR_END_OF_MEDIA"
505 // return code further above)
506
507 case ERROR_NO_DATA_DETECTED: *pStat |= GMT_EOD (0xFFFFFFFF); errno = EIO; break;
508 case ERROR_FILEMARK_DETECTED: *pStat |= GMT_EOF (0xFFFFFFFF); errno = EIO; break;
509 case ERROR_SETMARK_DETECTED: *pStat |= GMT_SM (0xFFFFFFFF); errno = EIO; break;
510 case ERROR_NOT_READY: *pStat |= GMT_DR_OPEN (0xFFFFFFFF); errno = ENOMEDIUM; break;
511 case ERROR_NO_MEDIA_IN_DRIVE: *pStat |= GMT_DR_OPEN (0xFFFFFFFF); errno = ENOMEDIUM; break;
512 case ERROR_WRITE_PROTECT: *pStat |= GMT_WR_PROT (0xFFFFFFFF); errno = EROFS; break;
513 }
514 return errno;
515 }
516
517 ////////////////////////////////////////////////////////////////////////////////////
518 // (forward references for private helper functions)
519
520 int w32_internal_mtop ( HANDLE hFile, U32* pStat, struct mtop* mtop, ifd_t ifd );
521 int w32_internal_mtget ( HANDLE hFile, U32* pStat, struct mtget* mtget, ifd_t ifd );
522 int w32_internal_mtpos ( HANDLE hFile, U32* pStat, DWORD* pdwLogPos,
523 DWORD* pdwAbsPos, ifd_t ifd );
524
525 ////////////////////////////////////////////////////////////////////////////////////
526 // Close tape device...
527
528 DLL_EXPORT
w32_close_tape(ufd_t ufd)529 int w32_close_tape ( ufd_t ufd )
530 {
531 ifd_t ifd = W32STAPE_UFD2IFD( ufd );
532 int rc = -1;
533 errno = EBADF;
534
535 lock();
536
537 if (1
538 && ifd >= 0
539 && ifd < W32STAPE_MAX_FDNUMS
540 && g_ifds[ ifd ] != 0
541 )
542 {
543 // Deallocate resources
544
545 HANDLE hFile = g_handles[ ifd ];
546 char* pName = g_fnames [ ifd ];
547
548 g_handles[ ifd ] = NULL;
549 g_fnames [ ifd ] = NULL;
550 g_fstats [ ifd ] = GMT_DR_OPEN (0xFFFFFFFF);
551 g_BOTmsk [ ifd ] = 0xFFFFFFFF;
552 g_BOTbot [ ifd ] = 0x00000000;
553
554 VERIFY( w32_free_ifd( ifd ) == 0 );
555
556 // Close the file...
557
558 free( pName );
559
560 errno = CloseHandle( hFile ) ? 0 : w32_trans_w32error( GetLastError() );
561 rc = errno ? -1 : 0;
562 }
563
564 unlock();
565
566 return rc;
567 }
568
569 ////////////////////////////////////////////////////////////////////////////////////
570 // Read tape...
571
572 DLL_EXPORT
w32_read_tape(ufd_t ufd,void * buf,size_t nbyte)573 ssize_t w32_read_tape ( ufd_t ufd, void* buf, size_t nbyte )
574 {
575 BOOL bSuccess;
576 DWORD dwBytesRead;
577 DWORD dwLastError;
578
579 ifd_t ifd = W32STAPE_UFD2IFD( ufd );
580 U32* pStat = NULL;
581 HANDLE hFile;
582
583 if (!buf)
584 {
585 errno = EINVAL;
586 return -1;
587 }
588
589 lock();
590
591 if (0
592 || ifd < 0
593 || ifd >= W32STAPE_MAX_FDNUMS
594 || g_ifds[ ifd ] == 0
595 )
596 {
597 unlock();
598 errno = EBADF;
599 return -1;
600 }
601
602 unlock();
603
604 hFile = g_handles[ ifd ];
605 pStat = &g_fstats[ ifd ];
606
607 // Do the i/o, save results, update device status
608 // (based on the results), then check results...
609
610 do
611 {
612 dwBytesRead = 0;
613 bSuccess = ReadFile( hFile, buf, (DWORD)nbyte, &dwBytesRead, NULL );
614 errno = (dwLastError = GetLastError());
615 errno = w32_internal_rc ( pStat );
616 }
617 while ( !bSuccess && EINTR == errno );
618
619 // Success? (see: "ERROR_END_OF_MEDIA" in function 'w32_internal_rc')
620
621 if (bSuccess || ERROR_END_OF_MEDIA == dwLastError)
622 {
623 ASSERT( bSuccess || ENOSPC == errno );
624 return ( (ssize_t) dwBytesRead );
625 }
626
627 ASSERT( !bSuccess && ERROR_END_OF_MEDIA != dwLastError && ENOSPC != errno );
628
629 // The i/o "failed". Check to see if it was just a tapemark...
630
631 if ( EIO == errno && GMT_EOF( *pStat ) )
632 {
633 ASSERT( ERROR_FILEMARK_DETECTED == dwLastError );
634 return 0; // (tapemark)
635 }
636
637 // EIO != errno || !GMT_EOF( *pStat ) --> bona fide i/o error...
638
639 ASSERT( ERROR_FILEMARK_DETECTED != dwLastError );
640 return -1;
641 }
642
643 ////////////////////////////////////////////////////////////////////////////////////
644 // Write tape...
645
646 DLL_EXPORT
w32_write_tape(ufd_t ufd,const void * buf,size_t nbyte)647 ssize_t w32_write_tape ( ufd_t ufd, const void* buf, size_t nbyte )
648 {
649 BOOL bSuccess;
650 DWORD dwBytesWritten;
651 DWORD dwLastError;
652
653 ifd_t ifd = W32STAPE_UFD2IFD( ufd );
654 U32* pStat = NULL;
655 HANDLE hFile;
656
657 if (!buf)
658 {
659 errno = EINVAL;
660 return -1;
661 }
662
663 lock();
664
665 if (0
666 || ifd < 0
667 || ifd >= W32STAPE_MAX_FDNUMS
668 || g_ifds[ ifd ] == 0
669 )
670 {
671 unlock();
672 errno = EBADF;
673 return -1;
674 }
675
676 unlock();
677
678 hFile = g_handles[ ifd ];
679 pStat = &g_fstats[ ifd ];
680
681 // Do the i/o, save results, update device status
682 // (based on the results), then check results...
683
684 do
685 {
686 dwBytesWritten = 0;
687 bSuccess = WriteFile( hFile, buf, (DWORD)nbyte, &dwBytesWritten, NULL );
688 errno = (dwLastError = GetLastError());
689 errno = w32_internal_rc ( pStat );
690 }
691 while ( !bSuccess && EINTR == errno );
692
693 // Success? (see: "ERROR_END_OF_MEDIA" in function 'w32_internal_rc')
694
695 if (bSuccess || ERROR_END_OF_MEDIA == dwLastError)
696 {
697 ASSERT( bSuccess || ENOSPC == errno );
698 ASSERT( ((size_t)dwBytesWritten) == nbyte ); // (MUST be true!!)
699 return ( (ssize_t) dwBytesWritten );
700 }
701
702 // I/O error...
703
704 ASSERT( !bSuccess && ERROR_END_OF_MEDIA != dwLastError && ENOSPC != errno );
705 return -1;
706 }
707
708 ////////////////////////////////////////////////////////////////////////////////////
709 // ioctl... (perform some type of control function, e.g. fsf, rewind, etc)
710
711 DLL_EXPORT
w32_ioctl_tape(ufd_t ufd,int request,...)712 int w32_ioctl_tape ( ufd_t ufd, int request, ... )
713 {
714 va_list vl;
715 void* ptr = NULL;
716 int rc = 0;
717 ifd_t ifd = W32STAPE_UFD2IFD( ufd );
718 U32* pStat = NULL;
719 HANDLE hFile;
720
721 lock();
722
723 if (0
724 || ifd < 0
725 || ifd >= W32STAPE_MAX_FDNUMS
726 || g_ifds[ ifd ] == 0
727 )
728 {
729 unlock();
730 errno = EBADF;
731 return -1;
732 }
733
734 unlock();
735
736 hFile = g_handles[ ifd ];
737 pStat = &g_fstats[ ifd ];
738
739 va_start ( vl, request );
740 ptr = va_arg( vl, void* );
741
742 if ( !ptr )
743 {
744 errno = EINVAL;
745 return -1;
746 }
747
748 switch (request)
749 {
750 case MTIOCTOP: // (perform tape operation)
751 {
752 struct mtop* mtop = ptr;
753 rc = w32_internal_mtop ( hFile, pStat, mtop, ifd );
754 }
755 break;
756
757 case MTIOCGET: // (retrieve tape status)
758 {
759 struct mtget* mtget = ptr;
760 memset( mtget, 0, sizeof(*mtget) );
761 rc = w32_internal_mtget ( hFile, pStat, mtget, ifd );
762 }
763 break;
764
765 case MTIOCPOS: // (retrieve tape position)
766 {
767 struct mtpos* mtpos = ptr;
768 memset( mtpos, 0, sizeof(*mtpos) );
769 rc = w32_internal_mtpos( hFile, pStat, &mtpos->mt_blkno, NULL, ifd );
770 }
771 break;
772
773 default: // (invalid/unsupported ioctl code)
774 {
775 errno = EINVAL;
776 rc = -1;
777 }
778 break;
779 }
780
781 return rc;
782 }
783
784 ////////////////////////////////////////////////////////////////////////////////////
785 // Private internal helper function... return 0 == success, -1 == failure
786
787 static
w32_internal_mtop(HANDLE hFile,U32 * pStat,struct mtop * mtop,ifd_t ifd)788 int w32_internal_mtop ( HANDLE hFile, U32* pStat, struct mtop* mtop, ifd_t ifd )
789 {
790 int rc = 0;
791
792 ASSERT( pStat && mtop ); // (sanity check)
793
794 // General technique: do the i/o, save results, update the
795 // device status (based on the results), then check results...
796
797 switch ( mtop->mt_op )
798 {
799 case MTLOAD: // (load media)
800 {
801 if ( 1 != mtop->mt_count )
802 {
803 errno = EINVAL;
804 rc = -1;
805 }
806 else
807 {
808 do
809 {
810 errno = PrepareTape( hFile, TAPE_LOAD, FALSE );
811 errno = w32_internal_rc ( pStat );
812 }
813 while ( EINTR == errno );
814 }
815 }
816 break;
817
818 case MTUNLOAD: // (unload media)
819 case MTOFFL: // (make media offline (same as unload))
820 {
821 if ( 1 != mtop->mt_count )
822 {
823 errno = EINVAL;
824 rc = -1;
825 }
826 else
827 {
828 do
829 {
830 errno = PrepareTape( hFile, TAPE_UNLOAD, FALSE );
831 errno = w32_internal_rc ( pStat );
832 }
833 while ( EINTR == errno );
834 }
835 }
836 break;
837
838 case MTSEEK: // (position media)
839 {
840 do
841 {
842 errno = SetTapePosition( hFile, TAPE_LOGICAL_BLOCK, 0, mtop->mt_count, 0, FALSE );
843 errno = w32_internal_rc ( pStat );
844 }
845 while ( EINTR == errno );
846 }
847 break;
848
849 case MTREW: // (rewind)
850 {
851 if ( 1 != mtop->mt_count )
852 {
853 errno = EINVAL;
854 rc = -1;
855 }
856 else
857 {
858 do
859 {
860 errno = SetTapePosition( hFile, TAPE_REWIND, 0, 0, 0, FALSE );
861 errno = w32_internal_rc ( pStat );
862 }
863 while ( EINTR == errno );
864 }
865 }
866 break;
867
868 case MTFSF: // (FORWARD space FILE)
869 case MTBSF: // (BACKWARD space FILE)
870 {
871 if ( !mtop->mt_count )
872 {
873 errno = EINVAL;
874 rc = -1;
875 }
876 else
877 {
878 LARGE_INTEGER liCount;
879
880 liCount.QuadPart = mtop->mt_count;
881
882 if ( MTBSF == mtop->mt_op )
883 liCount.QuadPart = -liCount.QuadPart; // (negative == backwards)
884
885 do
886 {
887 errno = SetTapePosition( hFile, TAPE_SPACE_FILEMARKS, 0, liCount.LowPart, liCount.HighPart, FALSE );
888 errno = w32_internal_rc ( pStat );
889 }
890 while ( EINTR == errno );
891 }
892 }
893 break;
894
895 case MTFSR: // (FORWARD space BLOCK)
896 case MTBSR: // (BACKWARD space BLOCK)
897 {
898 if ( !mtop->mt_count )
899 {
900 errno = EINVAL;
901 rc = -1;
902 }
903 else
904 {
905 LARGE_INTEGER liCount;
906
907 liCount.QuadPart = mtop->mt_count;
908
909 if ( MTBSR == mtop->mt_op )
910 liCount.QuadPart = -liCount.QuadPart; // (negative == backwards)
911
912 do
913 {
914 errno = SetTapePosition( hFile, TAPE_SPACE_RELATIVE_BLOCKS, 0, liCount.LowPart, liCount.HighPart, FALSE );
915 errno = w32_internal_rc ( pStat );
916 }
917 while ( EINTR == errno );
918 }
919 }
920 break;
921
922 case MTSETBLK: // (set blocksize)
923 {
924 TAPE_SET_MEDIA_PARAMETERS media_parms;
925
926 media_parms.BlockSize = mtop->mt_count;
927
928 do
929 {
930 errno = SetTapeParameters( hFile, SET_TAPE_MEDIA_INFORMATION, &media_parms );
931 errno = w32_internal_rc ( pStat );
932 }
933 while ( EINTR == errno );
934 }
935 break;
936
937 case MTEOTWARN: // (set EOT Warning Zone size in bytes)
938 {
939 TAPE_SET_DRIVE_PARAMETERS set_drive_parms;
940
941 set_drive_parms.ECC = g_drive_parms[ifd].ECC;
942 set_drive_parms.Compression = g_drive_parms[ifd].Compression;
943 set_drive_parms.DataPadding = g_drive_parms[ifd].DataPadding;
944 set_drive_parms.ReportSetmarks = g_drive_parms[ifd].ReportSetmarks;
945 set_drive_parms.EOTWarningZoneSize = mtop->mt_count;
946
947 do
948 {
949 errno = SetTapeParameters( hFile, SET_TAPE_DRIVE_INFORMATION, &set_drive_parms );
950 errno = w32_internal_rc ( pStat );
951 }
952 while ( EINTR == errno );
953 }
954 break;
955
956 case MTWEOF: // (write TAPEMARK)
957 {
958 if ( mtop->mt_count < 0 )
959 {
960 errno = EINVAL;
961 rc = -1;
962 }
963 else
964 {
965 // PROGRAMMING NOTE: We prefer "long" filemarks over any other type
966 // because, according to the SDK documentaion:
967 //
968 // "A short filemark contains a short erase gap that cannot be
969 // overwritten unless the write operation is performed from the
970 // beginning of the partition or from an earlier long filemark."
971 //
972 // "A long filemark contains a long erase gap that allows an
973 // application to position the tape at the beginning of the filemark
974 // and to overwrite the filemark and the erase gap."
975 //
976 // Thus if TAPE_LONG_FILEMARKS is not supported we try ONLY the generic
977 // TAPE_FILEMARKS variety and return an error if that fails; we do NOT
978 // ever attempt the TAPE_SHORT_FILEMARKS or TAPE_SETMARKS variety.
979
980 DWORD dwTapemarkType = TAPE_LONG_FILEMARKS;
981
982 if ( !( g_drive_parms[ifd].FeaturesHigh & TAPE_DRIVE_WRITE_LONG_FMKS ) )
983 dwTapemarkType = TAPE_FILEMARKS;
984
985 do
986 {
987 errno = WriteTapemark( hFile, dwTapemarkType, mtop->mt_count, FALSE );
988 errno = w32_internal_rc ( pStat );
989 }
990 while ( EINTR == errno );
991 }
992 }
993 break;
994
995 case MTERASE: // (write erase gap or erase entire tape (data security erase))
996 {
997 if (1
998 && 0 != mtop->mt_count // (0 == write erase gap at current position)
999 && 1 != mtop->mt_count // (1 == erases the remainder of entire tape)
1000 )
1001 {
1002 errno = EINVAL;
1003 rc = -1;
1004 }
1005 else
1006 {
1007 DWORD dwEraseType =
1008 mtop->mt_count ? TAPE_ERASE_LONG : TAPE_ERASE_SHORT;
1009
1010 do
1011 {
1012 errno = EraseTape( hFile, dwEraseType, FALSE );
1013 errno = w32_internal_rc ( pStat );
1014 }
1015 while ( EINTR == errno );
1016 }
1017 }
1018 break;
1019
1020 case MTNOP: // (no operation)
1021 {
1022 errno = 0;
1023 rc = 0;
1024 }
1025 break;
1026
1027 default: // (invalid/unsupported tape operation)
1028 {
1029 errno = EINVAL;
1030 rc = -1;
1031 }
1032 break;
1033 }
1034
1035 return (rc = (0 == errno || ENOSPC == errno) ? 0 : /* errno != 0 && errno != ENOSPC */ -1);
1036 }
1037
1038 ////////////////////////////////////////////////////////////////////////////////////
1039 // Private internal helper function... return 0 == success, -1 == failure
1040
1041 static
w32_internal_mtget(HANDLE hFile,U32 * pStat,struct mtget * mtget,ifd_t ifd)1042 int w32_internal_mtget ( HANDLE hFile, U32* pStat, struct mtget* mtget, ifd_t ifd )
1043 {
1044 TAPE_GET_MEDIA_PARAMETERS media_parms;
1045 DWORD dwRetCode, dwSize, dwLogicalPosition;
1046
1047 ASSERT( pStat && mtget );
1048
1049 mtget->mt_resid = 0; // (unknown/unsupported)
1050 mtget->mt_erreg = 0; // (unknown/unsupported)
1051 mtget->mt_fileno = -1; // (unknown/unsupported)
1052 mtget->mt_blkno = -1; // (unknown as of yet; set further below)
1053 mtget->mt_type = MT_ISSCSI2; // "Generic ANSI SCSI-2 tape unit"
1054 mtget->mt_gstat = -1; // (purposely invalid; set correctly below)
1055
1056 // Reset the mounted status; it will get set further below...
1057
1058 *pStat &= ~GMT_DR_OPEN (0xFFFFFFFF);
1059
1060 // Attempt to retrieve the status of the tape-drive...
1061
1062 dwRetCode = w32_get_tape_status( hFile );
1063
1064 // Windows returns 'ERROR_NOT_READY' if no tape is mounted
1065 // instead of the usual expected 'ERROR_NO_MEDIA_IN_DRIVE'
1066
1067 if ( ERROR_NOT_READY == dwRetCode )
1068 dwRetCode = ERROR_NO_MEDIA_IN_DRIVE;
1069
1070 // If there is not tape mounted OR a new tape was mounted,
1071 // then the following status bits are now unknown/obsolete
1072
1073 if (0
1074 || ERROR_NO_MEDIA_IN_DRIVE == dwRetCode
1075 || ERROR_MEDIA_CHANGED == dwRetCode
1076 )
1077 {
1078 // (these statuse are now obsolete)
1079 *pStat &= ~GMT_WR_PROT (0xFFFFFFFF);
1080 *pStat &= ~GMT_BOT (0xFFFFFFFF);
1081 *pStat &= ~GMT_EOT (0xFFFFFFFF);
1082 *pStat &= ~GMT_EOD (0xFFFFFFFF);
1083 *pStat &= ~GMT_EOF (0xFFFFFFFF);
1084 *pStat &= ~GMT_SM (0xFFFFFFFF);
1085 }
1086
1087 // There's no sense trying to get media parameters
1088 // unless there's some media loaded on the drive!
1089
1090 if ( ERROR_NO_MEDIA_IN_DRIVE == dwRetCode )
1091 {
1092 *pStat |= GMT_DR_OPEN (0xFFFFFFFF); // (no tape mounted in drive)
1093 mtget->mt_gstat = *pStat; // (return current status)
1094 return 0; // (nothing more we can do)
1095 }
1096
1097 // A tape appears to be mounted on the drive...
1098 // Retrieve the media parameters information...
1099
1100 dwSize = sizeof(media_parms);
1101 memset( &media_parms, 0, dwSize );
1102 dwRetCode = GetTapeParameters( hFile, GET_TAPE_MEDIA_INFORMATION, &dwSize, &media_parms );
1103 ASSERT( sizeof(media_parms) == dwSize );
1104
1105 if ( NO_ERROR == dwRetCode )
1106 {
1107 mtget->mt_dsreg = media_parms.BlockSize;
1108
1109 if (media_parms.WriteProtected)
1110 *pStat |= GMT_WR_PROT (0xFFFFFFFF);
1111 else
1112 *pStat &= ~GMT_WR_PROT (0xFFFFFFFF);
1113 }
1114 else
1115 mtget->mt_dsreg = 0; // (unknown; variable blocks presumed)
1116
1117 // Lastly, attempt to determine if we are at BOT (i.e. load-point)...
1118
1119 if ( 0 != ( errno = w32_internal_mtpos( hFile, pStat, &dwLogicalPosition, NULL, ifd ) ) )
1120 {
1121 mtget->mt_gstat = *pStat;
1122 return -1;
1123 }
1124
1125 mtget->mt_blkno = dwLogicalPosition;
1126
1127 if ( ( dwLogicalPosition & g_BOTmsk[ ifd ] ) == g_BOTbot[ ifd ] )
1128 *pStat |= GMT_BOT (0xFFFFFFFF);
1129 else
1130 *pStat &= ~GMT_BOT (0xFFFFFFFF);
1131
1132 mtget->mt_gstat = *pStat;
1133 return 0;
1134 }
1135
1136 ////////////////////////////////////////////////////////////////////////////////////
1137 // Private internal helper function... return 0 == success, -1 == failure
1138
1139 static
w32_internal_mtpos(HANDLE hFile,U32 * pStat,DWORD * pdwLogPos,DWORD * pdwAbsPos,ifd_t ifd)1140 int w32_internal_mtpos ( HANDLE hFile, U32* pStat, DWORD* pdwLogPos,
1141 DWORD* pdwAbsPos, ifd_t ifd )
1142 {
1143 DWORD dwDummyPartition, dwDummyPositionHigh;
1144
1145 ASSERT( pStat && pdwLogPos ); // (sanity check)
1146
1147 // PROGRAMMING NOTE: the SDK docs state that for the 'lpdwOffsetHigh'
1148 // parameter (i.e. dwDummyPositionHigh, the 5th paramater):
1149 //
1150 // "This parameter can be NULL if the
1151 // high-order bits are not required."
1152 //
1153 // But it LIES! Simple expirical observation reveals that ALL parameters
1154 // are in fact required. If any are NULL then 'GetTapePosition' crashes
1155 // and burns (which is unusual since usually when you pass invalid args
1156 // to an API it usually just returns an error code, but in this case it
1157 // doesn't. It actually crashes)
1158
1159 do
1160 {
1161 U32 dummy_stat = 0;
1162 errno = GetTapePosition
1163 (
1164 hFile,
1165 TAPE_LOGICAL_POSITION,
1166 &dwDummyPartition,
1167 pdwLogPos,
1168 &dwDummyPositionHigh
1169 );
1170 errno = w32_internal_rc ( &dummy_stat );
1171 }
1172 while ( EINTR == errno );
1173
1174 if (errno)
1175 return -1;
1176
1177 if (pdwAbsPos) // (may be NULL if they're not interested in it)
1178 {
1179 do
1180 {
1181 U32 dummy_stat = 0;
1182 errno = GetTapePosition
1183 (
1184 hFile,
1185 TAPE_ABSOLUTE_POSITION,
1186 &dwDummyPartition,
1187 pdwAbsPos,
1188 &dwDummyPositionHigh
1189 );
1190 errno = w32_internal_rc ( &dummy_stat );
1191 }
1192 while ( EINTR == errno );
1193
1194 if (errno)
1195 return -1;
1196 }
1197
1198 // PROGRAMMING NOTE: the Windows 'GetTapePosition' API returns either
1199 // a LOGICAL position value or an ABSOLUTE position value. Based on
1200 // trial and error it was determined the LOGICAL position corresponds
1201 // to the SCSI "READ POSITION" command's "first block location" value,
1202 // and the ABSOLUTE tape position appears to correspond to the SCSI
1203 // "last block location".
1204
1205 // Since what we want is what IBM calls the "Channel block ID" (which
1206 // itself appears to correspond to what the SCSI documentation refers
1207 // to as the "First block location"), then what we want here is what
1208 // Windows refers to as the LOGICAL position, not the ABSOLUTE (i.e.
1209 // device-relative) position I originally thought we needed/wanted.
1210
1211 if ( ( *pdwLogPos & g_BOTmsk[ ifd ] ) == g_BOTbot[ ifd ] )
1212 *pStat |= GMT_BOT (0xFFFFFFFF);
1213 else
1214 *pStat &= ~GMT_BOT (0xFFFFFFFF);
1215
1216 return 0;
1217 }
1218
1219 ////////////////////////////////////////////////////////////////////////////////////
1220
1221 #endif /* _MSVC_ */
1222