1 /* OMATAPE.C    (c) Copyright Roger Bowler, 1999-2009                */
2 /*              Hercules Tape Device Handler for OMATAPE             */
3 
4 /* Original Author: Roger Bowler                                     */
5 /* Prime Maintainer: Ivan Warren                                     */
6 /* Secondary Maintainer: "Fish" (David B. Trout)                     */
7 
8 /*-------------------------------------------------------------------*/
9 /* This module contains the OMATAPE emulated tape format support.    */
10 /*                                                                   */
11 /* The subroutines in this module are called by the general tape     */
12 /* device handler (tapedev.c) when the tape format is OMATAPE.       */
13 /*                                                                   */
14 /* Messages issued by this module are prefixed HHCTA2nn              */
15 /*-------------------------------------------------------------------*/
16 
17 /*-------------------------------------------------------------------*/
18 /* Reference information:                                            */
19 /* SC53-1200 S/370 and S/390 Optical Media Attach/2 User's Guide     */
20 /* SC53-1201 S/370 and S/390 Optical Media Attach/2 Technical Ref    */
21 /*-------------------------------------------------------------------*/
22 
23 #include "hstdinc.h"
24 #include "hercules.h"  /* need Hercules control blocks               */
25 #include "tapedev.h"   /* Main tape handler header file              */
26 
27 //#define  ENABLE_TRACING_STMTS     // (Fish: DEBUGGING)
28 
29 #ifdef ENABLE_TRACING_STMTS
30   #if !defined(DEBUG)
31     #warning DEBUG required for ENABLE_TRACING_STMTS
32   #endif
33   // (TRACE, ASSERT, and VERIFY macros are #defined in hmacros.h)
34 #else
35   #undef  TRACE
36   #undef  ASSERT
37   #undef  VERIFY
38   #define TRACE       1 ? ((void)0) : logmsg
39   #define ASSERT(a)
40   #define VERIFY(a)   ((void)(a))
41 #endif
42 
43 /*-------------------------------------------------------------------*/
44 /* Read the OMA tape descriptor file                                 */
45 /*-------------------------------------------------------------------*/
read_omadesc(DEVBLK * dev)46 int read_omadesc (DEVBLK *dev)
47 {
48 int             rc;                     /* Return code               */
49 int             i;                      /* Array subscript           */
50 int             pathlen;                /* Length of TDF path name   */
51 int             tdfsize;                /* Size of TDF file in bytes */
52 int             filecount;              /* Number of files           */
53 int             stmt;                   /* TDF file statement number */
54 int             fd;                     /* TDF file descriptor       */
55 struct stat     statbuf;                /* TDF file information      */
56 U32             blklen;                 /* Fixed block length        */
57 int             tdfpos;                 /* Position in TDF buffer    */
58 char           *tdfbuf;                 /* -> TDF file buffer        */
59 char           *tdfrec;                 /* -> TDF record             */
60 char           *tdffilenm;              /* -> Filename in TDF record */
61 char           *tdfformat;              /* -> Format in TDF record   */
62 char           *tdfreckwd;              /* -> Keyword in TDF record  */
63 char           *tdfblklen;              /* -> Length in TDF record   */
64 OMATAPE_DESC   *tdftab;                 /* -> Tape descriptor array  */
65 BYTE            c;                      /* Work area for sscanf      */
66 char            pathname[MAX_PATH];     /* file path in host format  */
67 
68     /* Isolate the base path name of the TDF file */
69     for (pathlen = strlen(dev->filename); pathlen > 0; )
70     {
71         pathlen--;
72         if (dev->filename[pathlen-1] == '/') break;
73     }
74 #if 0
75     // JCS thinks this is bad
76     if (pathlen < 7
77         || strncasecmp(dev->filename+pathlen-7, "/tapes/", 7) != 0)
78     {
79         logmsg (_("HHCTA232I %4.4X: Invalid filename %s: "
80                 "TDF files must be in the TAPES subdirectory\n"),
81                 dev->devnum, dev->filename+pathlen);
82         return -1;
83     }
84     pathlen -= 7;
85 #endif
86 
87     /* Open the tape descriptor file */
88     hostpath(pathname, dev->filename, sizeof(pathname));
89     fd = hopen(pathname, O_RDONLY | O_BINARY);
90     if (fd < 0)
91     {
92         logmsg (_("HHCTA239E %4.4X: Error opening TDF file %s: %s\n"),
93                 dev->devnum, dev->filename, strerror(errno));
94         return -1;
95     }
96 
97     /* Determine the size of the tape descriptor file */
98     rc = fstat (fd, &statbuf);
99     if (rc < 0)
100     {
101         logmsg (_("HHCTA240E %4.4X: File %s fstat error: %s\n"),
102                 dev->devnum, dev->filename, strerror(errno));
103         close (fd);
104         return -1;
105     }
106     tdfsize = statbuf.st_size;
107 
108     /* Obtain a buffer for the tape descriptor file */
109     tdfbuf = malloc (tdfsize);
110     if (tdfbuf == NULL)
111     {
112         logmsg (_("HHCTA241E %4.4X: Cannot obtain buffer for TDF file %s: %s\n"),
113                 dev->devnum, dev->filename, strerror(errno));
114         close (fd);
115         return -1;
116     }
117 
118     /* Read the tape descriptor file into the buffer */
119     rc = read (fd, tdfbuf, tdfsize);
120     if (rc < tdfsize)
121     {
122         logmsg (_("HHCTA242E %4.4X: Error reading TDF file %s: %s\n"),
123                 dev->devnum, dev->filename, strerror(errno));
124         free (tdfbuf);
125         close (fd);
126         return -1;
127     }
128 
129     /* Close the tape descriptor file */
130     close (fd); fd = -1;
131 
132     /* Check that the first record is a TDF header */
133     if (memcmp(tdfbuf, "@TDF", 4) != 0)
134     {
135         logmsg (_("HHCTA243E %4.4X: %s is not a valid TDF file\n"),
136                 dev->devnum, dev->filename);
137         free (tdfbuf);
138         return -1;
139     }
140 
141     /* Count the number of linefeeds in the tape descriptor file
142        to determine the size of the descriptor array required */
143     for (i = 0, filecount = 0; i < tdfsize; i++)
144     {
145         if (tdfbuf[i] == '\n') filecount++;
146     } /* end for(i) */
147     /* ISW Add 1 to filecount to add an extra EOT marker */
148     filecount++;
149 
150     /* Obtain storage for the tape descriptor array */
151     tdftab = (OMATAPE_DESC*)malloc (filecount * sizeof(OMATAPE_DESC));
152     if (tdftab == NULL)
153     {
154         logmsg (_("HHCTA244E %4.4X: Cannot obtain buffer for TDF array: %s\n"),
155                 dev->devnum, strerror(errno));
156         free (tdfbuf);
157         return -1;
158     }
159 
160     /* Build the tape descriptor array */
161     for (filecount = 0, tdfpos = 0, stmt = 1; ; filecount++)
162     {
163         /* Clear the tape descriptor array entry */
164         memset (&(tdftab[filecount]), 0, sizeof(OMATAPE_DESC));
165 
166         /* Point past the next linefeed in the TDF file */
167         while (tdfpos < tdfsize && tdfbuf[tdfpos++] != '\n');
168         stmt++;
169 
170         /* Exit at end of TDF file */
171         if (tdfpos >= tdfsize) break;
172 
173         /* Mark the end of the TDF record with a null terminator */
174         tdfrec = tdfbuf + tdfpos;
175         while (tdfpos < tdfsize && tdfbuf[tdfpos]!='\r'
176             && tdfbuf[tdfpos]!='\n') tdfpos++;
177         c = tdfbuf[tdfpos];
178         if (tdfpos >= tdfsize) break;
179         tdfbuf[tdfpos] = '\0';
180 
181         /* Exit if TM or EOT record */
182         if (strcasecmp(tdfrec, "TM") == 0)
183         {
184             tdftab[filecount].format='X';
185             tdfbuf[tdfpos] = c;
186             continue;
187         }
188         if(strcasecmp(tdfrec, "EOT") == 0)
189         {
190             tdftab[filecount].format='E';
191             break;
192         }
193 
194         /* Parse the TDF record */
195         tdffilenm = strtok (tdfrec, " \t");
196         tdfformat = strtok (NULL, " \t");
197         tdfreckwd = strtok (NULL, " \t");
198         tdfblklen = strtok (NULL, " \t");
199 
200         /* Check for missing fields */
201         if (tdffilenm == NULL || tdfformat == NULL)
202         {
203             logmsg (_("HHCTA245E %4.4X: Filename or format missing in "
204                     "line %d of file %s\n"),
205                     dev->devnum, stmt, dev->filename);
206             free (tdftab);
207             free (tdfbuf);
208             return -1;
209         }
210 
211         /* Check that the file name is not too long */
212         if (pathlen + 1 + strlen(tdffilenm)
213                 > sizeof(tdftab[filecount].filename) - 1)
214         {
215             logmsg (_("HHCTA246E %4.4X: Filename %s too long in "
216                     "line %d of file %s\n"),
217                     dev->devnum, tdffilenm, stmt, dev->filename);
218             free (tdftab);
219             free (tdfbuf);
220             return -1;
221         }
222 
223         /* Convert the file name to Unix format */
224         for (i = 0; i < (int)strlen(tdffilenm); i++)
225         {
226             if (tdffilenm[i] == '\\')
227                 tdffilenm[i] = '/';
228 /* JCS */
229 //            else
230 //                tdffilenm[i] = tolower(tdffilenm[i]);
231         } /* end for(i) */
232 
233         /* Prefix the file name with the base path name and
234            save it in the tape descriptor array */
235         /* but only if the filename lacks a leading slash - JCS  */
236 /*
237         strncpy (tdftab[filecount].filename, dev->filename, pathlen);
238         if (tdffilenm[0] != '/')
239             stlrcat ( tdftab[filecount].filename, "/", sizeof(tdftab[filecount].filename) );
240         strlcat ( tdftab[filecount].filename, tdffilenm, sizeof(tdftab[filecount].filename) );
241 */
242         tdftab[filecount].filename[0] = 0;
243 
244         if ((tdffilenm[0] != '/') && (tdffilenm[1] != ':'))
245         {
246             strncpy (tdftab[filecount].filename, dev->filename, pathlen);
247             strlcat (tdftab[filecount].filename, "/", sizeof(tdftab[filecount].filename) );
248         }
249 
250         strlcat (tdftab[filecount].filename, tdffilenm, sizeof(tdftab[filecount].filename) );
251 
252         /* Check for valid file format code */
253         if (strcasecmp(tdfformat, "HEADERS") == 0)
254         {
255             tdftab[filecount].format = 'H';
256         }
257         else if (strcasecmp(tdfformat, "TEXT") == 0)
258         {
259             tdftab[filecount].format = 'T';
260         }
261         else if (strcasecmp(tdfformat, "FIXED") == 0)
262         {
263             /* Check for RECSIZE keyword */
264             if (tdfreckwd == NULL
265                 || strcasecmp(tdfreckwd, "RECSIZE") != 0)
266             {
267                 logmsg (_("HHCTA247E %4.4X: RECSIZE keyword missing in "
268                         "line %d of file %s\n"),
269                         dev->devnum, stmt, dev->filename);
270                 free (tdftab);
271                 free (tdfbuf);
272                 return -1;
273             }
274 
275             /* Check for valid fixed block length */
276             if (tdfblklen == NULL
277                 || sscanf(tdfblklen, "%u%c", &blklen, &c) != 1
278                 || blklen < 1 || blklen > MAX_BLKLEN)
279             {
280                 logmsg (_("HHCTA248E %4.4X: Invalid record size %s in "
281                         "line %d of file %s\n"),
282                         dev->devnum, tdfblklen, stmt, dev->filename);
283                 free (tdftab);
284                 free (tdfbuf);
285                 return -1;
286             }
287 
288             /* Set format and block length in descriptor array */
289             tdftab[filecount].format = 'F';
290             tdftab[filecount].blklen = blklen;
291         }
292         else
293         {
294             logmsg (_("HHCTA249E %4.4X: Invalid record format %s in "
295                     "line %d of file %s\n"),
296                     dev->devnum, tdfformat, stmt, dev->filename);
297             free (tdftab);
298             free (tdfbuf);
299             return -1;
300         }
301         tdfbuf[tdfpos] = c;
302     } /* end for(filecount) */
303     /* Force an EOT as last entry (filecount is correctly adjusted here) */
304     tdftab[filecount].format='E';
305 
306     /* Save the file count and TDF array pointer in the device block */
307     dev->omafiles = filecount+1;
308     dev->omadesc = tdftab;
309 
310     /* Release the TDF file buffer and exit */
311     free (tdfbuf);
312     return 0;
313 
314 } /* end function read_omadesc */
315 
316 /*-------------------------------------------------------------------*/
317 /* Open the OMATAPE file defined by the current file number          */
318 /*                                                                   */
319 /* The OMA tape descriptor file is read if necessary.                */
320 /* If successful, the file descriptor is stored in the device block  */
321 /* and the return value is zero.  Otherwise the return value is -1.  */
322 /*-------------------------------------------------------------------*/
open_omatape(DEVBLK * dev,BYTE * unitstat,BYTE code)323 int open_omatape (DEVBLK *dev, BYTE *unitstat,BYTE code)
324 {
325 int             fd;                     /* File descriptor integer   */
326 int             rc;                     /* Return code               */
327 OMATAPE_DESC   *omadesc;                /* -> OMA descriptor entry   */
328 char            pathname[MAX_PATH];     /* file path in host format  */
329 
330      /* Check for no tape in drive */
331      if (!strcmp (dev->filename, TAPE_UNLOADED))
332      {
333          build_senseX(TAPE_BSENSE_TAPEUNLOADED,dev,unitstat,code);
334          return -1;
335      }
336 
337     /* Read the OMA descriptor file if necessary */
338     if (dev->omadesc == NULL)
339     {
340         rc = read_omadesc (dev);
341         if (rc < 0)
342         {
343             build_senseX(TAPE_BSENSE_TAPELOADFAIL,dev,unitstat,code);
344             return -1;
345         }
346         dev->blockid = 0;
347     }
348 
349     dev->fenced = 0;
350 
351     /* Unit exception if beyond end of tape */
352     /* ISW: CHANGED PROCESSING - RETURN UNDEFINITE Tape Marks  */
353     /*       NOTE: The last entry in the TDF table is ALWAYS   */
354     /*              an EOT Condition                           */
355     /*              This is ensured by the TDF reading routine */
356 #if 0
357     if (dev->curfilen >= dev->omafiles)
358     {
359         logmsg (_("HHCTA250E %4.4X: Attempt to access beyond end of tape %s\n"),
360                 dev->devnum, dev->filename);
361 
362         build_senseX(TAPE_BSENSE_ENDOFTAPE,dev,unitstat,code);
363         return -1;
364     }
365 #else
366     if(dev->curfilen>dev->omafiles)
367     {
368         dev->curfilen=dev->omafiles;
369         return(0);
370     }
371 #endif
372 
373     /* Point to the current file entry in the OMA descriptor table */
374     omadesc = (OMATAPE_DESC*)(dev->omadesc);
375     omadesc += (dev->curfilen-1);
376     if(omadesc->format=='X')
377     {
378         return 0;
379     }
380     if(omadesc->format=='E')
381     {
382         return 0;
383     }
384 
385     /* Open the OMATAPE file */
386     hostpath(pathname, omadesc->filename, sizeof(pathname));
387     fd = hopen(pathname, O_RDONLY | O_BINARY);
388 
389     /* Check for successful open */
390     if (fd < 0 || lseek (fd, 0, SEEK_END) > LONG_MAX)
391     {
392         if (fd >= 0)            /* (if open was successful, then it) */
393             errno = EOVERFLOW;  /* (must have been a lseek overflow) */
394 
395         logmsg (_("HHCTA251E %4.4X: Error opening %s: %s\n"),
396                 dev->devnum, omadesc->filename, strerror(errno));
397 
398         if (fd >= 0)
399             close(fd);          /* (close the file if it was opened) */
400 
401         build_senseX(TAPE_BSENSE_TAPELOADFAIL,dev,unitstat,code);
402         return -1;
403     }
404 
405     /* OMA tapes are always read-only */
406     dev->readonly = 1;
407 
408     /* Store the file descriptor in the device block */
409     dev->fd = fd;
410     return 0;
411 
412 } /* end function open_omatape */
413 
414 /*-------------------------------------------------------------------*/
415 /* Read a block header from an OMA tape file in OMA headers format   */
416 /*                                                                   */
417 /* If successful, return value is zero, and the current block        */
418 /* length and previous and next header offsets are returned.         */
419 /* If error, return value is -1 and unitstat is set to CE+DE+UC      */
420 /*-------------------------------------------------------------------*/
readhdr_omaheaders(DEVBLK * dev,OMATAPE_DESC * omadesc,long blkpos,S32 * pcurblkl,S32 * pprvhdro,S32 * pnxthdro,BYTE * unitstat,BYTE code)421 int readhdr_omaheaders (DEVBLK *dev, OMATAPE_DESC *omadesc,
422                         long blkpos, S32 *pcurblkl, S32 *pprvhdro,
423                         S32 *pnxthdro, BYTE *unitstat,BYTE code)
424 {
425 int             rc;                     /* Return code               */
426 off_t           rcoff;                  /* Return code from lseek()  */
427 int             padding;                /* Number of padding bytes   */
428 OMATAPE_BLKHDR  omahdr;                 /* OMATAPE block header      */
429 S32             curblkl;                /* Length of current block   */
430 S32             prvhdro;                /* Offset of previous header */
431 S32             nxthdro;                /* Offset of next header     */
432 
433     /* Seek to start of block header */
434     rcoff = lseek (dev->fd, blkpos, SEEK_SET);
435     if (rcoff < 0)
436     {
437         /* Handle seek error condition */
438         logmsg (_("HHCTA252E %4.4X: Error seeking to offset "I32_FMTX" "
439                 "in file %s: %s\n"),
440                 dev->devnum, blkpos, omadesc->filename, strerror(errno));
441 
442         /* Set unit check with equipment check */
443         build_senseX(TAPE_BSENSE_LOCATEERR,dev,unitstat,code);
444         return -1;
445     }
446 
447     /* Read the 16-byte block header */
448     rc = read (dev->fd, &omahdr, sizeof(omahdr));
449 
450     /* Handle read error condition */
451     if (rc < 0)
452     {
453         logmsg (_("HHCTA253E %4.4X: Error reading block header "
454                 "at offset "I32_FMTX" in file %s: %s\n"),
455                 dev->devnum, blkpos, omadesc->filename,
456                 strerror(errno));
457 
458         /* Set unit check with equipment check */
459         build_senseX(TAPE_BSENSE_READFAIL,dev,unitstat,code);
460         return -1;
461     }
462 
463     /* Handle end of file within block header */
464     if (rc < (int)sizeof(omahdr))
465     {
466         logmsg (_("HHCTA254E %4.4X: Unexpected end of file in block header "
467                 "at offset "I32_FMTX" in file %s\n"),
468                 dev->devnum, blkpos, omadesc->filename);
469 
470         /* Set unit check with data check and partial record */
471         build_senseX(TAPE_BSENSE_BLOCKSHORT,dev,unitstat,code);
472         return -1;
473     }
474 
475     /* Extract the current block length and previous header offset */
476     curblkl = (S32)(((U32)(omahdr.curblkl[3]) << 24)
477                     | ((U32)(omahdr.curblkl[2]) << 16)
478                     | ((U32)(omahdr.curblkl[1]) << 8)
479                     | omahdr.curblkl[0]);
480     prvhdro = (S32)((U32)(omahdr.prvhdro[3]) << 24)
481                     | ((U32)(omahdr.prvhdro[2]) << 16)
482                     | ((U32)(omahdr.prvhdro[1]) << 8)
483                     | omahdr.prvhdro[0];
484 
485     /* Check for valid block header */
486     if (curblkl < -1 || curblkl == 0 || curblkl > MAX_BLKLEN
487         || memcmp(omahdr.omaid, "@HDF", 4) != 0)
488     {
489         logmsg (_("HHCTA255E %4.4X: Invalid block header "
490                 "at offset "I32_FMTX" in file %s\n"),
491                 dev->devnum, blkpos, omadesc->filename);
492 
493         build_senseX(TAPE_BSENSE_READFAIL,dev,unitstat,code);
494         return -1;
495     }
496 
497     /* Calculate the number of padding bytes which follow the data */
498     padding = (16 - (curblkl & 15)) & 15;
499 
500     /* Calculate the offset of the next block header */
501     nxthdro = blkpos + sizeof(OMATAPE_BLKHDR) + curblkl + padding;
502 
503     /* Return current block length and previous/next header offsets */
504     *pcurblkl = curblkl;
505     *pprvhdro = prvhdro;
506     *pnxthdro = nxthdro;
507     return 0;
508 
509 } /* end function readhdr_omaheaders */
510 
511 /*-------------------------------------------------------------------*/
512 /* Read a block from an OMA tape file in OMA headers format          */
513 /*                                                                   */
514 /* If successful, return value is block length read.                 */
515 /* If a tapemark was read, the file is closed, the current file      */
516 /* number in the device block is incremented so that the next file   */
517 /* will be opened by the next CCW, and the return value is zero.     */
518 /* If error, return value is -1 and unitstat is set to CE+DE+UC      */
519 /*-------------------------------------------------------------------*/
read_omaheaders(DEVBLK * dev,OMATAPE_DESC * omadesc,BYTE * buf,BYTE * unitstat,BYTE code)520 int read_omaheaders (DEVBLK *dev, OMATAPE_DESC *omadesc,
521                         BYTE *buf, BYTE *unitstat,BYTE code)
522 {
523 int             rc;                     /* Return code               */
524 long            blkpos;                 /* Offset to block header    */
525 S32             curblkl;                /* Length of current block   */
526 S32             prvhdro;                /* Offset of previous header */
527 S32             nxthdro;                /* Offset of next header     */
528 
529     /* Read the 16-byte block header */
530     blkpos = dev->nxtblkpos;
531     rc = readhdr_omaheaders (dev, omadesc, blkpos, &curblkl,
532                             &prvhdro, &nxthdro, unitstat,code);
533     if (rc < 0) return -1;
534 
535     /* Update the offsets of the next and previous blocks */
536     dev->nxtblkpos = nxthdro;
537     dev->prvblkpos = blkpos;
538 
539     /* Increment file number and return zero if tapemark */
540     if (curblkl == -1)
541     {
542         close (dev->fd);
543         dev->fd = -1;
544         dev->curfilen++;
545         dev->nxtblkpos = 0;
546         dev->prvblkpos = -1;
547         return 0;
548     }
549 
550     /* Read data block from tape file */
551     rc = read (dev->fd, buf, curblkl);
552 
553     /* Handle read error condition */
554     if (rc < 0)
555     {
556         logmsg (_("HHCTA256E %4.4X: Error reading data block "
557                 "at offset "I32_FMTX" in file %s: %s\n"),
558                 dev->devnum, blkpos, omadesc->filename,
559                 strerror(errno));
560 
561         /* Set unit check with equipment check */
562         build_senseX(TAPE_BSENSE_READFAIL,dev,unitstat,code);
563         return -1;
564     }
565 
566     /* Handle end of file within data block */
567     if (rc < curblkl)
568     {
569         logmsg (_("HHCTA257E %4.4X: Unexpected end of file in data block "
570                 "at offset "I32_FMTX" in file %s\n"),
571                 dev->devnum, blkpos, omadesc->filename);
572 
573         /* Set unit check with data check and partial record */
574         build_senseX(TAPE_BSENSE_BLOCKSHORT,dev,unitstat,code);
575         return -1;
576     }
577 
578     /* Return block length */
579     return curblkl;
580 
581 } /* end function read_omaheaders */
582 
583 /*-------------------------------------------------------------------*/
584 /* Read a block from an OMA tape file in fixed block format          */
585 /*                                                                   */
586 /* If successful, return value is block length read.                 */
587 /* If a tapemark was read, the file is closed, the current file      */
588 /* number in the device block is incremented so that the next file   */
589 /* will be opened by the next CCW, and the return value is zero.     */
590 /* If error, return value is -1 and unitstat is set to CE+DE+UC      */
591 /*-------------------------------------------------------------------*/
read_omafixed(DEVBLK * dev,OMATAPE_DESC * omadesc,BYTE * buf,BYTE * unitstat,BYTE code)592 int read_omafixed (DEVBLK *dev, OMATAPE_DESC *omadesc,
593                         BYTE *buf, BYTE *unitstat,BYTE code)
594 {
595 off_t           rcoff;                  /* Return code from lseek()  */
596 int             blklen;                 /* Block length              */
597 long            blkpos;                 /* Offset of block in file   */
598 
599     /* Initialize current block position */
600     blkpos = dev->nxtblkpos;
601 
602     /* Seek to new current block position */
603     rcoff = lseek (dev->fd, blkpos, SEEK_SET);
604     if (rcoff < 0)
605     {
606         /* Handle seek error condition */
607         logmsg (_("HHCTA258E %4.4X: Error seeking to offset "I32_FMTX" "
608                 "in file %s: %s\n"),
609                 dev->devnum, blkpos, omadesc->filename, strerror(errno));
610 
611         /* Set unit check with equipment check */
612         build_senseX(TAPE_BSENSE_LOCATEERR,dev,unitstat,code);
613         return -1;
614     }
615 
616     /* Read fixed length block or short final block */
617     blklen = read (dev->fd, buf, omadesc->blklen);
618 
619     /* Handle read error condition */
620     if (blklen < 0)
621     {
622         logmsg (_("HHCTA259E %4.4X: Error reading data block "
623                 "at offset "I32_FMTX" in file %s: %s\n"),
624                 dev->devnum, blkpos, omadesc->filename,
625                 strerror(errno));
626 
627         build_senseX(TAPE_BSENSE_READFAIL,dev,unitstat,code);
628         return -1;
629     }
630 
631     /* At end of file return zero to indicate tapemark */
632     if (blklen == 0)
633     {
634         close (dev->fd);
635         dev->fd = -1;
636         dev->curfilen++;
637         dev->nxtblkpos = 0;
638         dev->prvblkpos = -1;
639         return 0;
640     }
641 
642     /* Calculate the offsets of the next and previous blocks */
643     dev->nxtblkpos = blkpos + blklen;
644     dev->prvblkpos = blkpos;
645 
646     /* Return block length, or zero to indicate tapemark */
647     return blklen;
648 
649 } /* end function read_omafixed */
650 
651 /*-------------------------------------------------------------------*/
652 /* Read a block from an OMA tape file in ASCII text format           */
653 /*                                                                   */
654 /* If successful, return value is block length read.                 */
655 /* If a tapemark was read, the file is closed, the current file      */
656 /* number in the device block is incremented so that the next file   */
657 /* will be opened by the next CCW, and the return value is zero.     */
658 /* If error, return value is -1 and unitstat is set to CE+DE+UC      */
659 /*                                                                   */
660 /* The buf parameter points to the I/O buffer during a read          */
661 /* operation, or is NULL for a forward space block operation.        */
662 /*-------------------------------------------------------------------*/
read_omatext(DEVBLK * dev,OMATAPE_DESC * omadesc,BYTE * buf,BYTE * unitstat,BYTE code)663 int read_omatext (DEVBLK *dev, OMATAPE_DESC *omadesc,
664                         BYTE *buf, BYTE *unitstat,BYTE code)
665 {
666 int             rc;                     /* Return code               */
667 off_t           rcoff;                  /* Return code from lseek()  */
668 int             num;                    /* Number of characters read */
669 int             pos;                    /* Position in I/O buffer    */
670 long            blkpos;                 /* Offset of block in file   */
671 BYTE            c;                      /* Character work area       */
672 
673     /* Initialize current block position */
674     blkpos = dev->nxtblkpos;
675 
676     /* Seek to new current block position */
677     rcoff = lseek (dev->fd, blkpos, SEEK_SET);
678     if (rcoff < 0)
679     {
680         /* Handle seek error condition */
681         logmsg (_("HHCTA260E %4.4X: Error seeking to offset "I32_FMTX" "
682                 "in file %s: %s\n"),
683                 dev->devnum, blkpos, omadesc->filename, strerror(errno));
684 
685         /* Set unit check with equipment check */
686         build_senseX(TAPE_BSENSE_LOCATEERR,dev,unitstat,code);
687         return -1;
688     }
689 
690     /* Read data from tape file until end of line */
691     for (num = 0, pos = 0; ; )
692     {
693         rc = read (dev->fd, &c, 1);
694         if (rc < 1) break;
695 
696         /* Treat X'1A' as end of file */
697         if (c == '\x1A')
698         {
699             rc = 0;
700             break;
701         }
702 
703         /* Count characters read */
704         num++;
705 
706         /* Ignore carriage return character */
707         if (c == '\r') continue;
708 
709         /* Exit if newline character */
710         if (c == '\n') break;
711 
712         /* Ignore characters in excess of I/O buffer length */
713         if (pos >= MAX_BLKLEN) continue;
714 
715         /* Translate character to EBCDIC and copy to I/O buffer */
716         if (buf != NULL)
717             buf[pos] = host_to_guest(c);
718 
719         /* Count characters copied or skipped */
720         pos++;
721 
722     } /* end for(num) */
723 
724     /* At end of file return zero to indicate tapemark */
725     if (rc == 0 && num == 0)
726     {
727         close (dev->fd);
728         dev->fd = -1;
729         dev->curfilen++;
730         dev->nxtblkpos = 0;
731         dev->prvblkpos = -1;
732         return 0;
733     }
734 
735     /* Handle read error condition */
736     if (rc < 0)
737     {
738         logmsg (_("HHCTA261E %4.4X: Error reading data block "
739                 "at offset "I32_FMTX" in file %s: %s\n"),
740                 dev->devnum, blkpos, omadesc->filename,
741                 strerror(errno));
742 
743         build_senseX(TAPE_BSENSE_READFAIL,dev,unitstat,code);
744         return -1;
745     }
746 
747     /* Check for block not terminated by newline */
748     if (rc < 1)
749     {
750         logmsg (_("HHCTA262E %4.4X: Unexpected end of file in data block "
751                 "at offset "I32_FMTX" in file %s\n"),
752                 dev->devnum, blkpos, omadesc->filename);
753 
754         /* Set unit check with data check and partial record */
755         build_senseX(TAPE_BSENSE_BLOCKSHORT,dev,unitstat,code);
756         return -1;
757     }
758 
759     /* Check for invalid zero length block */
760     if (pos == 0)
761     {
762         logmsg (_("HHCTA263E %4.4X: Invalid zero length block "
763                 "at offset "I32_FMTX" in file %s\n"),
764                 dev->devnum, blkpos, omadesc->filename);
765 
766         /* Set unit check with equipment check */
767         build_senseX(TAPE_BSENSE_BLOCKSHORT,dev,unitstat,code);
768         return -1;
769     }
770 
771     /* Calculate the offsets of the next and previous blocks */
772     dev->nxtblkpos = blkpos + num;
773     dev->prvblkpos = blkpos;
774 
775     /* Return block length */
776     return pos;
777 
778 } /* end function read_omatext */
779 
780 /*-------------------------------------------------------------------*/
781 /* Read a block from an OMA - Selection of format done here          */
782 /*                                                                   */
783 /* If successful, return value is block length read.                 */
784 /* If a tapemark was read, the file is closed, the current file      */
785 /* number in the device block is incremented so that the next file   */
786 /* will be opened by the next CCW, and the return value is zero.     */
787 /* If error, return value is -1 and unitstat is set to CE+DE+UC      */
788 /*                                                                   */
789 /* The buf parameter points to the I/O buffer during a read          */
790 /* operation, or is NULL for a forward space block operation.        */
791 /*-------------------------------------------------------------------*/
read_omatape(DEVBLK * dev,BYTE * buf,BYTE * unitstat,BYTE code)792 int read_omatape (DEVBLK *dev,
793                         BYTE *buf, BYTE *unitstat,BYTE code)
794 {
795 int len;
796 OMATAPE_DESC *omadesc;
797     omadesc = (OMATAPE_DESC*)(dev->omadesc);
798     omadesc += (dev->curfilen-1);
799 
800     switch (omadesc->format)
801     {
802     default:
803     case 'H':
804         len = read_omaheaders (dev, omadesc, buf, unitstat,code);
805         break;
806     case 'F':
807         len = read_omafixed (dev, omadesc, buf, unitstat,code);
808         break;
809     case 'T':
810         len = read_omatext (dev, omadesc, buf, unitstat,code);
811         break;
812     case 'X':
813         len=0;
814         dev->curfilen++;
815         break;
816     case 'E':
817         len=0;
818         break;
819     } /* end switch(omadesc->format) */
820 
821     if (len >= 0)
822         dev->blockid++;
823 
824     return len;
825 }
826 
827 /*-------------------------------------------------------------------*/
828 /* Forward space to next file of OMA tape device                     */
829 /*                                                                   */
830 /* For OMA tape devices, the forward space file operation is         */
831 /* achieved by closing the current file, and incrementing the        */
832 /* current file number in the device block, which causes the         */
833 /* next file will be opened when the next CCW is processed.          */
834 /* If error, return value is -1 and unitstat is set to CE+DE+UC      */
835 /*-------------------------------------------------------------------*/
fsf_omatape(DEVBLK * dev,BYTE * unitstat,BYTE code)836 int fsf_omatape (DEVBLK *dev, BYTE *unitstat,BYTE code)
837 {
838     UNREFERENCED(unitstat);
839     UNREFERENCED(code);
840 
841     /* Close the current OMA file */
842     if (dev->fd >= 0)
843         close (dev->fd);
844     dev->fd = -1;
845     dev->nxtblkpos = 0;
846     dev->prvblkpos = -1;
847 
848     /* Increment the current file number */
849     dev->curfilen++;
850 
851     /* Return normal status */
852     return 0;
853 
854 } /* end function fsf_omatape */
855 
856 /*-------------------------------------------------------------------*/
857 /* Forward space over next block of OMA file in OMA headers format   */
858 /*                                                                   */
859 /* If successful, return value is the length of the block skipped.   */
860 /* If the block skipped was a tapemark, the return value is zero,    */
861 /* the file is closed, and the current file number in the device     */
862 /* block is incremented so that the next file belonging to the OMA   */
863 /* tape will be opened when the next CCW is executed.                */
864 /* If error, return value is -1 and unitstat is set to CE+DE+UC      */
865 /*-------------------------------------------------------------------*/
fsb_omaheaders(DEVBLK * dev,OMATAPE_DESC * omadesc,BYTE * unitstat,BYTE code)866 int fsb_omaheaders (DEVBLK *dev, OMATAPE_DESC *omadesc,
867                         BYTE *unitstat,BYTE code)
868 {
869 int             rc;                     /* Return code               */
870 long            blkpos;                 /* Offset of block header    */
871 S32             curblkl;                /* Length of current block   */
872 S32             prvhdro;                /* Offset of previous header */
873 S32             nxthdro;                /* Offset of next header     */
874 
875     /* Initialize current block position */
876     blkpos = dev->nxtblkpos;
877 
878     /* Read the 16-byte block header */
879     rc = readhdr_omaheaders (dev, omadesc, blkpos, &curblkl,
880                             &prvhdro, &nxthdro, unitstat,code);
881     if (rc < 0) return -1;
882 
883     /* Check if tapemark was skipped */
884     if (curblkl == -1)
885     {
886         /* Close the current OMA file */
887         if (dev->fd >= 0)
888             close (dev->fd);
889         dev->fd = -1;
890         dev->nxtblkpos = 0;
891         dev->prvblkpos = -1;
892 
893         /* Increment the file number */
894         dev->curfilen++;
895 
896         /* Return zero to indicate tapemark */
897         return 0;
898     }
899 
900     /* Update the offsets of the next and previous blocks */
901     dev->nxtblkpos = nxthdro;
902     dev->prvblkpos = blkpos;
903 
904     /* Return block length */
905     return curblkl;
906 
907 } /* end function fsb_omaheaders */
908 
909 /*-------------------------------------------------------------------*/
910 /* Forward space over next block of OMA file in fixed block format   */
911 /*                                                                   */
912 /* If successful, return value is the length of the block skipped.   */
913 /* If already at end of file, the return value is zero,              */
914 /* the file is closed, and the current file number in the device     */
915 /* block is incremented so that the next file belonging to the OMA   */
916 /* tape will be opened when the next CCW is executed.                */
917 /* If error, return value is -1 and unitstat is set to CE+DE+UC      */
918 /*-------------------------------------------------------------------*/
fsb_omafixed(DEVBLK * dev,OMATAPE_DESC * omadesc,BYTE * unitstat,BYTE code)919 int fsb_omafixed (DEVBLK *dev, OMATAPE_DESC *omadesc,
920                         BYTE *unitstat,BYTE code)
921 {
922 off_t           eofpos;                 /* Offset of end of file     */
923 off_t           blkpos;                 /* Offset of current block   */
924 int             curblkl;                /* Length of current block   */
925 
926     /* Initialize current block position */
927     blkpos = dev->nxtblkpos;
928 
929     /* Seek to end of file to determine file size */
930     eofpos = lseek (dev->fd, 0, SEEK_END);
931     if (eofpos < 0 || eofpos >= LONG_MAX)
932     {
933         /* Handle seek error condition */
934         if ( eofpos >= LONG_MAX) errno = EOVERFLOW;
935         logmsg (_("HHCTA264E %4.4X: Error seeking to end of file %s: %s\n"),
936                 dev->devnum, omadesc->filename, strerror(errno));
937 
938         /* Set unit check with equipment check */
939         build_senseX(TAPE_BSENSE_LOCATEERR,dev,unitstat,code);
940         return -1;
941     }
942 
943     /* Check if already at end of file */
944     if (blkpos >= eofpos)
945     {
946         /* Close the current OMA file */
947         if (dev->fd >= 0)
948             close (dev->fd);
949         dev->fd = -1;
950         dev->nxtblkpos = 0;
951         dev->prvblkpos = -1;
952 
953         /* Increment the file number */
954         dev->curfilen++;
955 
956         /* Return zero to indicate tapemark */
957         return 0;
958     }
959 
960     /* Calculate current block length */
961     curblkl = (int)(eofpos - blkpos);
962     if (curblkl > omadesc->blklen)
963         curblkl = omadesc->blklen;
964 
965     /* Update the offsets of the next and previous blocks */
966     dev->nxtblkpos = (long)(blkpos + curblkl);
967     dev->prvblkpos = (long)(blkpos);
968 
969     /* Return block length */
970     return curblkl;
971 
972 } /* end function fsb_omafixed */
973 
974 /*-------------------------------------------------------------------*/
975 /* Forward space to next block of OMA file                           */
976 /*                                                                   */
977 /* If successful, return value is the length of the block skipped.   */
978 /* If forward spaced over end of file, return value is 0.            */
979 /* If error, return value is -1 and unitstat is set to CE+DE+UC      */
980 /*-------------------------------------------------------------------*/
fsb_omatape(DEVBLK * dev,BYTE * unitstat,BYTE code)981 int fsb_omatape (DEVBLK *dev, BYTE *unitstat,BYTE code)
982 {
983 int             rc;                     /* Return code               */
984 OMATAPE_DESC   *omadesc;                /* -> OMA descriptor entry   */
985 
986     /* Point to the current file entry in the OMA descriptor table */
987     omadesc = (OMATAPE_DESC*)(dev->omadesc);
988     omadesc += (dev->curfilen-1);
989 
990     /* Forward space block depending on OMA file type */
991     switch (omadesc->format)
992     {
993     default:
994     case 'H':
995         rc = fsb_omaheaders (dev, omadesc, unitstat,code);
996         break;
997     case 'F':
998         rc = fsb_omafixed (dev, omadesc, unitstat,code);
999         break;
1000     case 'T':
1001         rc = read_omatext (dev, omadesc, NULL, unitstat,code);
1002         break;
1003     } /* end switch(omadesc->format) */
1004 
1005     if (rc >= 0) dev->blockid++;
1006 
1007     return rc;
1008 
1009 } /* end function fsb_omatape */
1010 
1011 /*-------------------------------------------------------------------*/
1012 /* Backspace to previous file of OMA tape device                     */
1013 /*                                                                   */
1014 /* If the current file number is 1, then backspace file simply       */
1015 /* closes the file, setting the current position to start of tape.   */
1016 /* Otherwise, the current file is closed, the current file number    */
1017 /* is decremented, the new file is opened, and the new file is       */
1018 /* repositioned to just before the tape mark at the end of the file. */
1019 /* If error, return value is -1 and unitstat is set to CE+DE+UC      */
1020 /*-------------------------------------------------------------------*/
bsf_omatape(DEVBLK * dev,BYTE * unitstat,BYTE code)1021 int bsf_omatape (DEVBLK *dev, BYTE *unitstat,BYTE code)
1022 {
1023 int             rc;                     /* Return code               */
1024 off_t           pos;                    /* File position             */
1025 OMATAPE_DESC   *omadesc;                /* -> OMA descriptor entry   */
1026 S32             curblkl;                /* Length of current block   */
1027 S32             prvhdro;                /* Offset of previous header */
1028 S32             nxthdro;                /* Offset of next header     */
1029 
1030     /* Close the current OMA file */
1031     if (dev->fd >= 0)
1032         close (dev->fd);
1033     dev->fd = -1;
1034     dev->nxtblkpos = 0;
1035     dev->prvblkpos = -1;
1036 
1037     /* Exit with tape at load point if currently on first file */
1038     if (dev->curfilen <= 1)
1039     {
1040         build_senseX(TAPE_BSENSE_LOADPTERR,dev,unitstat,code);
1041         return -1;
1042     }
1043 
1044     /* Decrement current file number */
1045     dev->curfilen--;
1046 
1047     /* Point to the current file entry in the OMA descriptor table */
1048     omadesc = (OMATAPE_DESC*)(dev->omadesc);
1049     omadesc += (dev->curfilen-1);
1050 
1051     /* Open the new current file */
1052     rc = open_omatape (dev, unitstat,code);
1053     if (rc < 0) return rc;
1054 
1055     /* Reposition before tapemark header at end of file, or
1056        to end of file for fixed block or ASCII text files */
1057     pos = 0;
1058     if ( 'H' == omadesc->format )
1059         pos -= sizeof(OMATAPE_BLKHDR);
1060 
1061     pos = lseek (dev->fd, pos, SEEK_END);
1062     if (pos < 0)
1063     {
1064         /* Handle seek error condition */
1065         logmsg (_("HHCTA265E %4.4X: Error seeking to end of file %s: %s\n"),
1066                 dev->devnum, omadesc->filename, strerror(errno));
1067 
1068         /* Set unit check with equipment check */
1069         build_senseX(TAPE_BSENSE_LOCATEERR,dev,unitstat,code);
1070         dev->sense[0] = SENSE_EC;
1071         *unitstat = CSW_CE | CSW_DE | CSW_UC;
1072         return -1;
1073     }
1074     dev->nxtblkpos = pos;
1075     dev->prvblkpos = -1;
1076 
1077     /* Determine the offset of the previous block */
1078     switch (omadesc->format)
1079     {
1080     case 'H':
1081         /* For OMA headers files, read the tapemark header
1082            and extract the previous block offset */
1083         rc = readhdr_omaheaders (dev, omadesc, pos, &curblkl,
1084                                 &prvhdro, &nxthdro, unitstat,code);
1085         if (rc < 0) return -1;
1086         dev->prvblkpos = prvhdro;
1087         break;
1088     case 'F':
1089         /* For OMA fixed block files, calculate the previous block
1090            offset allowing for a possible short final block */
1091         pos = (pos + omadesc->blklen - 1) / omadesc->blklen;
1092         dev->prvblkpos = (pos > 0 ? (pos - 1) * omadesc->blklen : -1);
1093         break;
1094     case 'T':
1095         /* For OMA ASCII text files, the previous block is unknown */
1096         dev->prvblkpos = -1;
1097         break;
1098     } /* end switch(omadesc->format) */
1099 
1100     /* Return normal status */
1101     return 0;
1102 
1103 } /* end function bsf_omatape */
1104 
1105 /*-------------------------------------------------------------------*/
1106 /* Backspace to previous block of OMA file                           */
1107 /*                                                                   */
1108 /* If successful, return value is +1.                                */
1109 /* If current position is at start of a file, then a backspace file  */
1110 /* operation is performed to reset the position to the end of the    */
1111 /* previous file, and the return value is zero.                      */
1112 /* If error, return value is -1 and unitstat is set to CE+DE+UC      */
1113 /*                                                                   */
1114 /* Note that for ASCII text files, the previous block position is    */
1115 /* known only if the previous CCW was a read or a write, so any      */
1116 /* attempt to issue more than one consecutive backspace block on     */
1117 /* an ASCII text file will fail with unit check status.              */
1118 /*-------------------------------------------------------------------*/
bsb_omatape(DEVBLK * dev,BYTE * unitstat,BYTE code)1119 int bsb_omatape (DEVBLK *dev, BYTE *unitstat,BYTE code)
1120 {
1121 int             rc;                     /* Return code               */
1122 OMATAPE_DESC   *omadesc;                /* -> OMA descriptor entry   */
1123 long            blkpos;                 /* Offset of block header    */
1124 S32             curblkl;                /* Length of current block   */
1125 S32             prvhdro;                /* Offset of previous header */
1126 S32             nxthdro;                /* Offset of next header     */
1127 
1128     /* Point to the current file entry in the OMA descriptor table */
1129     omadesc = (OMATAPE_DESC*)(dev->omadesc);
1130     omadesc += (dev->curfilen-1);
1131 
1132     /* Backspace file if current position is at start of file */
1133     if (dev->nxtblkpos == 0)
1134     {
1135         /* Unit check if already at start of tape */
1136         if (dev->curfilen <= 1)
1137         {
1138             build_senseX(TAPE_BSENSE_LOADPTERR,dev,unitstat,code);
1139             return -1;
1140         }
1141 
1142         /* Perform backspace file operation */
1143         rc = bsf_omatape (dev, unitstat,code);
1144         if (rc < 0) return -1;
1145 
1146         dev->blockid--;
1147 
1148         /* Return zero to indicate tapemark detected */
1149         return 0;
1150     }
1151 
1152     /* Unit check if previous block position is unknown */
1153     if (dev->prvblkpos < 0)
1154     {
1155         build_senseX(TAPE_BSENSE_LOADPTERR,dev,unitstat,code);
1156         return -1;
1157     }
1158 
1159     /* Backspace to previous block position */
1160     blkpos = dev->prvblkpos;
1161 
1162     /* Determine new previous block position */
1163     switch (omadesc->format)
1164     {
1165     case 'H':
1166         /* For OMA headers files, read the previous block header to
1167            extract the block length and new previous block offset */
1168         rc = readhdr_omaheaders (dev, omadesc, blkpos, &curblkl,
1169                                 &prvhdro, &nxthdro, unitstat,code);
1170         if (rc < 0) return -1;
1171         break;
1172     case 'F':
1173         /* For OMA fixed block files, calculate the new previous
1174            block offset by subtracting the fixed block length */
1175         if (blkpos >= omadesc->blklen)
1176             prvhdro = blkpos - omadesc->blklen;
1177         else
1178             prvhdro = -1;
1179         break;
1180     default:
1181     case 'T':
1182         /* For OMA ASCII text files, new previous block is unknown */
1183         prvhdro = -1;
1184         break;
1185     } /* end switch(omadesc->format) */
1186 
1187     /* Update the offsets of the next and previous blocks */
1188     dev->nxtblkpos = blkpos;
1189     dev->prvblkpos = prvhdro;
1190 
1191     dev->blockid--;
1192 
1193     /* Return +1 to indicate backspace successful */
1194     return +1;
1195 
1196 } /* end function bsb_omatape */
1197 
1198 /*-------------------------------------------------------------------*/
1199 /* Close an OMA tape file set                                        */
1200 /*                                                                   */
1201 /* All errors are ignored                                            */
1202 /*-------------------------------------------------------------------*/
close_omatape2(DEVBLK * dev)1203 void close_omatape2(DEVBLK *dev)
1204 {
1205     if (dev->fd >= 0)
1206         close (dev->fd);
1207     dev->fd=-1;
1208     if (dev->omadesc != NULL)
1209     {
1210         free (dev->omadesc);
1211         dev->omadesc = NULL;
1212     }
1213 
1214     /* Reset the device dependent fields */
1215     dev->nxtblkpos=0;
1216     dev->prvblkpos=-1;
1217     dev->curfilen=1;
1218     dev->blockid=0;
1219     dev->fenced = 0;
1220     dev->omafiles = 0;
1221     return;
1222 }
1223 
1224 /*-------------------------------------------------------------------*/
1225 /* Close an OMA tape file set                                        */
1226 /*                                                                   */
1227 /* All errors are ignored                                            */
1228 /* Change the filename to '*' - unloaded                             */
1229 /* TAPE REALLY UNLOADED                                              */
1230 /*-------------------------------------------------------------------*/
close_omatape(DEVBLK * dev)1231 void close_omatape(DEVBLK *dev)
1232 {
1233     close_omatape2(dev);
1234     strcpy(dev->filename,TAPE_UNLOADED);
1235     dev->blockid = 0;
1236     dev->fenced = 0;
1237     return;
1238 }
1239 
1240 /*-------------------------------------------------------------------*/
1241 /* Rewind an OMA tape file set                                       */
1242 /*                                                                   */
1243 /* All errors are ignored                                            */
1244 /*-------------------------------------------------------------------*/
rewind_omatape(DEVBLK * dev,BYTE * unitstat,BYTE code)1245 int rewind_omatape(DEVBLK *dev,BYTE *unitstat,BYTE code)
1246 {
1247     UNREFERENCED(unitstat);
1248     UNREFERENCED(code);
1249     close_omatape2(dev);
1250     dev->fenced = 0;
1251     return 0;
1252 }
1253