Programming SCSItape
SCSItape
Tape Basics
User's Manual
Programmer's Manual
Supported Operations
open()
close()
read()
write()
ioctl()
Glossary
Valid HTML 4.0!
Supported Operations
The SCSItape driver supports the following operations on tape devices: It doesn't support random access to records using lseek(), but allows positioning via ioctl().

^ open()
int
open(const char *path, int flags, .../*mode*/);
The driver performs the following actions when it receives a request to open a tape device. All of these must succeed in order for the device to be opened.
  • The driver checks if the device is available.
  • It queries the device to find out if it is ready to take commands (e.g., whether a medium is present).
  • It finds the device's block size limits and selects an initial block size. The driver initially attempts to use variable-block mode, unless the device does not support it. The block size can be modified using ioctl().
  • If the logical device name indicates a partition other than the one currently used by the drive, the driver tries to change the drive's active partition to the one selected by the device name.
  • The driver attempts to establish a set of device configuration options which it expects to use (namely, reporting of setmarks, behavior on encountering the early-warning condition, and data compression).
  • It checks the medium's write protection status and ensures that the flags agree with it. SCSItape only evaluates the read-write mode (O_RDONLY, O_WRONLY, O_RDWR) portion of the flags argument and ignores both all other flags (O_CREAT, O_APPEND etc.) and the optional mode.
If the operation succeeds, the driver attempts to prevent removal of the recording medium.

If the device is not ready (e.g., when no medium is loaded), the open() succeeds, but subsequent operations on the file will fail, unless the device has become ready in the meantime. The only exception is the ioctl() status inquiry request (MTIOCGET), which always succeeds and which will report that the device is off-line.

^ close()
int
close(int fd);
The driver's behavior upon closing a tape device depends on various factors:
  • The logical device name which was used to open the device.
  • Whether or not any write() operation was performed on the device.
  • Whether or not the tape has been repositioned after writing.
The intent behind all this is to make it simple to write programs which read from and write to tape in a linear (streaming) fashion, while still allowing programs which perform more complex operations to control exactly what gets written.
  • If the device has been written to:
    • If the device has not been repositioned after writing:
      • If it is a QIC device, one filemark is written to the medium.
      • Otherwise, two filemarks are written to the medium.
    • If the logical device name selects an auto-rewind device, the tape is rewound; otherwise it is repositioned after the (first) filemark just written.
  • If the device has not been written to, or if it has been repositioned after writing:
    • If the logical device name selects an auto-rewind device, the tape is rewound.
    • If the logical device name selects an AT&T-style no-auto-rewind device, and if the tape is not currently positioned at BOT, EOD, EOT, or after a filemark or setmark, the tape is repositioned after the next filemark following the current position.
    • If the logical device name selects a Berkeley-style no-auto-rewind device, the tape is not repositioned in any way.
After these operations have been performed, the driver releases the device and allows the medium to be removed.

NOTES:

  • Portable programs should not reposition the tape immediately after writing records. UNIX operating systems are not consistent in their behavior in this situation. The user program should write an appropriate number of filemarks before positioning.
  • When a tape device is opened for writing and immediately closed, the SCSItape driver does not create an empty file, as some UNIX versions do.
^ read()
ssize_t
read(int fd, void *buf, size_t count);
The read() operation reads successive records from the tape. Its effects depend on the currently selected block size; when the block size is zero, the driver operates in variable-block mode, otherwise in fixed-block mode.

Independent of the block size, the read() fails if the passed file descriptor fd doesn't refer to a device which has been opened for reading, or which is not ready (e.g., because no medium is present).

NOTES:

  • The details of end-of-file and error handling may differ between operating systems. Portable programs should not expect to be able to continue reading into the next file after end-of-file has been encountered.
  • Some operating systems require count to be an exact multiple of the block size when operating in fixed-block mode. Portable programs should not rely on the rounding behavior of SCSItape.
  • The detection of incorrect block lengths in variable-block mode seems to be unreliable. I have not been able to determine if this is a problem in the driver, in the BeOS SCSI interface layer, or in my tape drive. Tape operations should always be performed with consistent block sizes, for the time being.
Variable-Block Mode Reading
In variable-block mode, each call to read() attempts to read a single record from the medium. The count argument gives the expected record length to be read. It is an error if count is smaller than the smallest record length supported by the device. If count is larger than the device's largest supported block size, it is silently rounded down.

The value returned from read() indicates that an error occurred, or else it contains the number of bytes which were transferred to buf. Even if no error occurs, a number of exceptional situations are indicated by the combination of the returned value and the status information obtained via MTIOCGET:

  • If read() returns zero, a filemark or setmark has been encountered. The tape is positioned after the filemark or setmark. MTIOCGET can be used to find out which kind of tape mark was present on the medium.
  • If the returned value is smaller than count, a record of less than the expected length was read. No data has been lost.
  • If the returned value is equal to count, but the residual count reported by MTIOCGET is less than zero, a record of more than the expected number of bytes was encountered. The residual count indicates how much data was lost. To recover, the user program must back up and reread the record with a larger count.
Fixed-Block Mode Reading
In fixed-block mode, the count argument is rounded to the next smaller integral multiple of the selected block size. The driver then attempts to read as many records of this size as necessary to transfer that many bytes.

The return value indicates that an error occurred, or else it contains the number of bytes which were transferred to buf. Even if no error occurs, a number of exceptional situations are indicated by the combination of the returned value and the status information obtained via MTIOCGET:

  • If read() returns zero, the driver has encountered a filemark, a setmark, or a record of a length other than the current block size. The tape is positioned after the filemark, setmark or record. MTIOCGET can be used to determine the exact status. To recover from reading an incorrect-length record, the user program must back up, change the block size, and reread the record.
  • If the returned value is smaller than count, the request was terminated prematurely. The next call to read will indicate the cause of the problem. If the next tape operation is not a read(), this information will be lost.
^ write()
ssize_t
write(int fd, const void *buf, size_t count);
The write() operation writes new records to the medium. Like read(), its effects depend on the current block size; when the user selected zero-size blocks, the driver operates in variable-block mode, otherwise in fixed-block mode.

Independent of the block size, the write() fails if the passed file descriptor fd doesn't refer to a device which has been opened for writing, or which is not ready (e.g., because no medium is present).

If the driver encounters logical end-of-tape ("early warning") during a write(), it signals this with an EIO (B_IO_ERROR) error code, or, in fixed-block mode, by returning a value smaller than the requested count (see below). All further calls to write() will be rejected. This write barrier can be crossed by issuing an MTIOCGET status request, after which the driver will accept all writes until physical EOT is encountered. If the latter happens, data may be lost, the tape may come off the reel, the sky may fall on the user's head, or other general nastiness may ensue, depending on the tape drive.

NOTES:

  • The details of end-of-medium and error handling may differ between operating systems. Portable programs should not expect to be able to continue writing after logical EOT has been encountered.
  • Some operating systems require count to be an exact multiple of the block size when operating in fixed-block mode. Portable programs should not rely on the rounding behavior of SCSItape.
  • Most drives will buffer data, allowing the user program to set up the next record while past ones are still being written out. This makes it much easier to keep the tape in motion, which is important for drives of the "streaming" type. To force the drive to flush all buffered data to tape, the user program can issue an MTIOCTOP command MTNOP or MTWEOF with an mt_count of zero.
Variable-Block Mode Writing
In variable-block mode, each call to write() attempts to write a single record to the medium. The count argument contains the number of bytes to be written. It is an error if count is smaller than the smallest record length supported by the device. If count is larger than the device's largest supported block size, it is silently rounded down.

The value returned from write() indicates that an error occurred, or else it contains the number of bytes written.

Fixed-Block Mode Writing
In fixed-block mode, the count argument is rounded to the next smaller integral multiple of the selected block size. The driver then attempts to write as many records of this size as necessary to transfer that many bytes.

The return value indicates that an error occurred, or else it contains the number of bytes written. This value may be smaller than the requested count, even absent any rounding, if the drive encounters logical EOT before all records could be written.

^ ioctl()
int
ioctl(int fd, int op, .../*argument*/);
The ioctl() operation allows arbitrary commands (encoded as an integer op code) to be passed to a tape device. It is inherently non-portable, but the SCSItape driver attempts to support a set of operations which is found on many UNIX systems. A program which restricts itself to these commands should work if it doesn't rely on specific encoded values or data structure formats.

The command opcodes and corresponding data types and macros are defined in the header files mtio.h (for tape-specific commands) and <Drivers.h> (for standard BeOS commands). The driver supports the following commands:

MTIOCGET
This command can be used to inquire the status of a tape device.
MTIOCTOP
This command is used to perform tape operations like repositioning or erasing.
MTIOCPOS
This command is used to inquire the current tape position.
MTIOCSETPOS
This command allows repositioning of the tape to a position previously returned by the MTIOCPOS command.
B_EJECT_DEVICE
This is a common BeOS device control opcode. The tape driver implements it by issuing an MTIOCTOP MTOFFL command, which rewinds and unloads the tape. Wether or not it is actually ejected depends on the drive mechanism.
B_GET_ICON
This is a common BeOS device control opcode. It is recognized but not implemented yet. Does anybody have a nice tape drive or cartridge icon?
MTIOCGET
The argument to the MTIOCGET command must be a pointer to a struct mtget, which is defined in mtio.h as follows:
   struct mtget {
     int32  mt_resid; /* residual count */
     uint32 mt_gstat; /* generic status */
     uint32 mt_bsize; /* current block size */
   };
	
The fields of the structure are filled in by the ioctl() call and contain the following information:
mt_resid
The residual count left by the previous tape operation. After a read() or write(), this field contains the number of bytes which were not transferred. When a user program reads a tape with blocks of unknown size, this information may be used to detect if data was lost.
After an MTIOCTOP command MTWEOF or MTWSS, this field contains the number of tape marks which were not written. After a tape positioning command, this field contains the number of records or tape marks not skipped (e.g., because BOT or EOD was encountered).
If the residual count is non-zero, the mt_gstat field should be consulted to determine the cause of the problem.
mt_gstat
This field contains encoded status information. Several macros are defined in mtio.h to help decode this information:
GMT_BOT(x)
Non-zero if the previous tape operation encountered BOT.
GMT_EOD(x)
Non-zero if the previous tape operation encountered EOD.
GMT_EOT(x)
Non-zero if the previous tape operation encountered (logical) EOT. As explained above, when this condition arises during a write(), as a side effect the MTIOCGET command lifts the "write barrier" so that writing past this point is allowed.
GMT_EOF(x)
Non-zero if the previous tape operation encountered a filemark.
GMT_SM(x)
Non-zero if the previous tape operation encountered a setmark.
GMT_ONLINE(x)
Non-zero if the device is ready (medium present etc.).
GMT_WR_PROT(x)
Non-zero if a write-protected volume is loaded.
GMT_COMPRESS(x)
Non-zero if the device will perform data compression on writing. (Data compression is always active when reading, as long as the drive supports it.)
GMT_DENSITY(x)
Returns the desity of the loaded medium. Various symbolic constants for common densities are defined in mtio.h.
mt_bsize
This field contains the current block size. If it is zero, the driver operates in variable-block mode, otherwise it operates in fixed-block mode. The mode and block size influence the behavior of the read() and write() operations.
MTIOCTOP
The argument to the MTIOCTOP command must be a pointer to a struct mtop, which is defined in mtio.h as follows:
   struct mtop {
      uint32 mt_op;    /* operation         */
      int32  mt_count; /* numeric parameter */
   };
	
The mt_op field specifies one of several possible operations to be performed; mt_count is used by some of them. The SCSItape driver can handle the following tape commands (all codes are define in mtio.h):
MTWEOF (Write EOF), MTWSS (Write Setmark)
The device must be open for writing; the value of mt_count must be non-negative. The driver writes mt_count filemarks (for MTWEOF) or setmarks (for MTWSS). If the count is zero, this command forces the drive to write all buffered data to the tape.
MTFSR, MTBSR (Forward/Backward Space Record)
These commands reposition the tape by skipping mt_count records, filemarks and/or setmarks in the indicated direction (forward for MTFSR, backward for MTBSR). If mt_count is negative, the direction of motion is reversed.
MTFSF, MTBSF (Forward/Backward Space File)
These commands reposition the tape by skipping mt_count filemarks and/or setmarks in the indicated direction (forward for MTFSF, backward for MTBSF). If mt_count is negative, the direction of motion is reversed.
MTFSS, MTBSS (Forward/Backward Space Setmark)
These commands reposition the tape by skipping mt_count setmarks in the indicated direction (forward for MTFSS, backward for MTBSS). If mt_count is negative, the direction of motion is reversed.
MTEOD (space to EOD)
This command repositions the tape to the end of recorded data. The next write operation will append to the tape.
MTREW (Rewind)
This command rewinds the tape.
MTOFFL (Off-line)
This command rewinds the tape and unloads the medium. It may cause the drive to eject the medium (if the hardware supports this). Even if the medium is not ejected, some drives (notably QIC drives) may require physical intervention before another open() can succeed.
MTRETEN (Retension)
This command is primarily intended for QIC media. It causes the drive to take the necessary steps to ensure the tape is correctly tensioned (typically, the entire tape is wound from the supply to the take-up reel and back again). This operation should be applied when a medium has not been used for some time.
MTERASE (Erase)
This command performs a "long" erase operation, writing over the entire medium. The device must be open for writing.
MTSETBSIZ (Set Block Size)
This command sets the block (record) size used by the driver. The value of mt_count must be non-negative. If it is zero, subsequent read() and write() operations will use variable-block mode; otherwise they will use fixed-block mode. If the tape drive doesn't accept the given block size, the operation will return an error code; MTIOCGET can be used to check if the drive replaced it by an acceptable alternative size.
MTNOP (No Operation)
Does nothing. Wait, I lied: If the device is opened for writing, this command forces the drive to write all buffered data to the medium.
MTCACHE, MTNOCACHE
These operations are defined for compatibility reasons; they succeed, but don't do anything.
NOTES:
  • The behavior of the spacing commands MTFSR, MTBSR, MTFSF and MTBSF is not consistent between operating systems. Some UNIX systems will terminate a spacing command when a hierarchically superior tape mark is encountered, instead of including it in the count. Portable programs should check the tape status using MTIOCGET after these commands.
  • Spacing, retensioning and erasing operations can take a long time. Some QIC drives will require hours for certain commands.
  • The MTERASE command may not be safe for data security purposes. Check your tape drive hardware manual before entrusting military secrects or Scientology scriptures to it.
MTIOCPOS
The argument to the MTIOCPOS command must be a pointer to a struct mtpos, which is defined in mtio.h as follows:
   struct mtpos {
     uint32 mt_blkno; /* logical block number */
   };
	
If the operation is successful, a value indicating the current tape position is stored in the mt_blkno field.

NOTE: This command may not be supported by the tape drive.

MTIOCSETPOS
The argument to the MTIOCSETPOS command must be a pointer to a struct mtpos, which is defined in mtio.h as follows:
   struct mtpos {
     uint32 mt_blkno; /* logical block number */
   };
	
The mt_blkno field should contain a value obtained from the MTIOCPOS command. If the operation succeeds, the tape will be repositioned accordingly.

NOTE: This command may not be supported by the tape drive.


Copyright © 1998 Jens Kilian. This file may be distributed under the conditions of the GNU General Public License.
$Id: Programmer.html,v 1.7 1999/04/18 10:22:04 jjk Exp $