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