Files
mt-st/mt.c
Kai Mäkisara 0c7307cc7a Add new density codes for IBM 3592 drives
Codes are added for the newer drives. The TS11xx names are added to
the desciptions of codes for non-encrypted tape.

Signed-off-by: Kai Mäkisara <Kai.Makisara@kolumbus.fi>
2025-01-07 16:29:51 +01:00

798 lines
31 KiB
C

/* This file contains the source of the 'mt' program intended for
Linux systems. The program supports the basic mt commands found
in most Unix-like systems. In addition to this the program
supports several commands designed for use with the Linux SCSI
tape drive.
Maintained by Iustin Pop (iustin@k1024.org).
Copyright by Kai Mäkisara, 1998 - 2008. The program may be distributed
according to the GNU Public License.
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include "mtio.h"
#include "version.h"
#ifndef DEFTAPE
#define DEFTAPE "/dev/tape" /* default tape device */
#endif /* DEFTAPE */
typedef struct cmdef_tr cmdef_tr;
typedef int (*cmdfunc)(int, struct cmdef_tr *, int, char **);
struct cmdef_tr {
char *cmd_name;
int cmd_code;
cmdfunc cmd_function;
int cmd_count_bits;
unsigned char cmd_fdtype;
unsigned char arg_cnt;
int error_tests;
};
#define NO_FD 0
#define FD_RDONLY 1
#define FD_RDWR 2
#define NO_ARGS 0
#define ONE_ARG 1
#define TWO_ARGS 2
#define MANY_ARGS 255
#define DO_BOOLEANS 1002
#define SET_BOOLEANS 1003
#define CLEAR_BOOLEANS 1004
#define ET_ONLINE 1
#define ET_WPROT 2
static void usage(int explain, int exitcode) __attribute__((noreturn));
static void version() __attribute__((noreturn));
static int do_standard(int, cmdef_tr *, int, char **);
static int do_drvbuffer(int, cmdef_tr *, int, char **);
static int do_options(int, cmdef_tr *, int, char **);
static int do_tell(int, cmdef_tr *, int, char **);
static int do_partseek(int, cmdef_tr *, int, char **);
static int do_status(int, cmdef_tr *, int, char **);
static int print_densities(int, cmdef_tr *, int, char **);
static int do_asf(int, cmdef_tr *, int, char **);
static int do_show_options(int, cmdef_tr *, int, char **);
static void test_error(int, cmdef_tr *);
/* Formatting note: the tables below were formatted using Emacs's
* extended align regex, using <,\(\s-+\)[A-Za-z0-9"]> as complex align
* regex (without <>), and repeated=y. The final curly brace is aligned
* separately. */
static cmdef_tr cmds[] = {
/* clang-format off */
{ "weof", MTWEOF, do_standard, 0, FD_RDWR, ONE_ARG, ET_ONLINE | ET_WPROT },
{ "wset", MTWSM, do_standard, 0, FD_RDWR, ONE_ARG, ET_ONLINE | ET_WPROT },
{ "eof", MTWEOF, do_standard, 0, FD_RDWR, ONE_ARG, ET_ONLINE },
{ "fsf", MTFSF, do_standard, 0, FD_RDONLY, ONE_ARG, ET_ONLINE },
{ "fsfm", MTFSFM, do_standard, 0, FD_RDONLY, ONE_ARG, ET_ONLINE },
{ "bsf", MTBSF, do_standard, 0, FD_RDONLY, ONE_ARG, ET_ONLINE },
{ "bsfm", MTBSFM, do_standard, 0, FD_RDONLY, ONE_ARG, ET_ONLINE },
{ "fsr", MTFSR, do_standard, 0, FD_RDONLY, ONE_ARG, ET_ONLINE },
{ "bsr", MTBSR, do_standard, 0, FD_RDONLY, ONE_ARG, ET_ONLINE },
{ "fss", MTFSS, do_standard, 0, FD_RDONLY, ONE_ARG, ET_ONLINE },
{ "bss", MTBSS, do_standard, 0, FD_RDONLY, ONE_ARG, ET_ONLINE },
{ "rewind", MTREW, do_standard, 0, FD_RDONLY, NO_ARGS, ET_ONLINE },
{ "offline", MTOFFL, do_standard, 0, FD_RDONLY, NO_ARGS, ET_ONLINE },
{ "rewoffl", MTOFFL, do_standard, 0, FD_RDONLY, NO_ARGS, ET_ONLINE },
{ "eject", MTOFFL, do_standard, 0, FD_RDONLY, NO_ARGS, ET_ONLINE },
{ "retension", MTRETEN, do_standard, 0, FD_RDONLY, NO_ARGS, ET_ONLINE },
{ "eod", MTEOM, do_standard, 0, FD_RDONLY, NO_ARGS, ET_ONLINE },
{ "seod", MTEOM, do_standard, 0, FD_RDONLY, NO_ARGS, ET_ONLINE },
{ "seek", MTSEEK, do_standard, 0, FD_RDONLY, ONE_ARG, ET_ONLINE },
{ "tell", MTTELL, do_tell, 0, FD_RDONLY, NO_ARGS, ET_ONLINE },
{ "status", MTNOP, do_status, 0, FD_RDONLY, NO_ARGS, 0 },
{ "erase", MTERASE, do_standard, 0, FD_RDWR, ONE_ARG, ET_ONLINE },
{ "setblk", MTSETBLK, do_standard, 0, FD_RDONLY, ONE_ARG, 0 },
{ "lock", MTLOCK, do_standard, 0, FD_RDONLY, NO_ARGS, ET_ONLINE },
{ "unlock", MTUNLOCK, do_standard, 0, FD_RDONLY, NO_ARGS, ET_ONLINE },
{ "load", MTLOAD, do_standard, 0, FD_RDONLY, ONE_ARG, 0 },
{ "compression", MTCOMPRESSION, do_standard, 0, FD_RDONLY, ONE_ARG, 0 },
{ "setdensity", MTSETDENSITY, do_standard, 0, FD_RDONLY, ONE_ARG, 0 },
{ "drvbuffer", MTSETDRVBUFFER, do_drvbuffer, 0, FD_RDONLY, ONE_ARG, 0 },
{ "stwrthreshold", MTSETDRVBUFFER, do_drvbuffer, MT_ST_WRITE_THRESHOLD, FD_RDONLY, ONE_ARG, 0 },
{ "stoptions", DO_BOOLEANS, do_options, 0, FD_RDONLY, MANY_ARGS, 0 },
{ "stsetoptions", SET_BOOLEANS, do_options, 0, FD_RDONLY, MANY_ARGS, 0 },
{ "stclearoptions", CLEAR_BOOLEANS, do_options, 0, FD_RDONLY, MANY_ARGS, 0 },
{ "defblksize", MTSETDRVBUFFER, do_drvbuffer, MT_ST_DEF_BLKSIZE, FD_RDONLY, ONE_ARG, 0 },
{ "defdensity", MTSETDRVBUFFER, do_drvbuffer, MT_ST_DEF_DENSITY, FD_RDONLY, ONE_ARG, 0 },
{ "defdrvbuffer", MTSETDRVBUFFER, do_drvbuffer, MT_ST_DEF_DRVBUFFER, FD_RDONLY, ONE_ARG, 0 },
{ "defcompression", MTSETDRVBUFFER, do_drvbuffer, MT_ST_DEF_COMPRESSION, FD_RDONLY, ONE_ARG, 0 },
{ "stsetcln", MTSETDRVBUFFER, do_drvbuffer, MT_ST_SET_CLN, FD_RDONLY, ONE_ARG, 0 },
{ "sttimeout", MTSETDRVBUFFER, do_drvbuffer, MT_ST_SET_TIMEOUT, FD_RDONLY, ONE_ARG, 0 },
{ "stlongtimeout", MTSETDRVBUFFER, do_drvbuffer, MT_ST_SET_LONG_TIMEOUT, FD_RDONLY, ONE_ARG, 0 },
{ "densities", 0, print_densities, 0, NO_FD, NO_ARGS, 0 },
{ "setpartition", MTSETPART, do_standard, 0, FD_RDONLY, ONE_ARG, ET_ONLINE },
{ "mkpartition", MTMKPART, do_standard, 0, FD_RDWR, ONE_ARG, ET_ONLINE },
{ "partseek", 0, do_partseek, 0, FD_RDONLY, TWO_ARGS, ET_ONLINE },
{ "asf", 0, do_asf, MTREW, FD_RDONLY, ONE_ARG, ET_ONLINE },
{ "stshowoptions", 0, do_show_options, 0, FD_RDONLY, ONE_ARG, 0 },
{ NULL, 0, 0, 0, NO_FD, NO_ARGS, 0 },
/* clang-format on */
};
static struct densities {
int code;
char *name;
} density_tbl[] = {
/* clang-format off */
/* Information taken from https://www.t10.org/ftp/x3t9.2/document.93/93-013r0.pdf:
* NZRI: Non-Return to Zero, change on ones
* GCR: Group Code Recording
* PE: Phase Encoding
* IMFM: Inverted Modified Frequency Modulation
* MFM: Modified Frequency Modulation
* DDS: DAT Data Storage
* RLL: Run Length Limited
*/
{ 0x00, "default" },
{ 0x01, "NRZI (800 bpi) 9 Track Reel" },
{ 0x02, "PE (1600 bpi) 9 Track Reel" },
{ 0x03, "GCR (6250 bpi) 9 Track Reel" },
{ 0x04, "QIC-11" },
{ 0x05, "QIC-45/60 (GCR, 8000 bpi)" },
{ 0x06, "PE (3200 bpi) 9 Track Reel" },
{ 0x07, "IMFM (6400 bpi)" },
{ 0x08, "GCR (8000 bpi)" },
{ 0x09, "3480/3490E, GCR (37871 bpi)" },
{ 0x0a, "MFM (6667 bpi)" },
{ 0x0b, "PE (1600 bpi)" },
{ 0x0c, "GCR (12960 bpi)" },
{ 0x0d, "GCR (25380 bpi)" },
{ 0x0f, "QIC-120 (GCR 10000 bpi)" },
{ 0x10, "QIC-150/250 (GCR 10000 bpi)" },
{ 0x11, "QIC-320/525 (GCR 16000 bpi)" },
{ 0x12, "QIC-1350 (RLL 51667 bpi)" },
{ 0x13, "DDS (61000 bpi)" },
{ 0x14, "EXB-8200 (RLL 43245 bpi)" },
{ 0x15, "EXB-8500 or QIC-1000" },
{ 0x16, "MFM 10000 bpi" },
{ 0x17, "MFM 42500 bpi" },
{ 0x18, "TZ86" },
{ 0x19, "DLT 10GB" },
{ 0x1a, "DLT 20GB" },
{ 0x1b, "DLT 35GB" },
{ 0x1c, "QIC-385M" },
{ 0x1d, "QIC-410M" },
{ 0x1e, "QIC-1000C" },
{ 0x1f, "QIC-2100C" },
{ 0x20, "QIC-6GB" },
{ 0x21, "QIC-20GB" },
{ 0x22, "QIC-2GB" },
{ 0x23, "QIC-875" },
{ 0x24, "DDS-2" },
{ 0x25, "DDS-3" },
{ 0x26, "DDS-4 or QIC-4GB" },
{ 0x27, "Exabyte Mammoth" },
{ 0x28, "Exabyte Mammoth-2" },
{ 0x29, "QIC-3080MC, IBM 3590 B" },
{ 0x2a, "IBM 3590 E" },
{ 0x30, "AIT-1 or MLR3" },
{ 0x31, "AIT-2" },
{ 0x32, "AIT-3 or SLR7" },
{ 0x33, "SLR6" },
{ 0x34, "SLR100" },
{ 0x40, "DLT1 40 GB, or Ultrium" },
{ 0x41, "DLT 40GB, or Ultrium2" },
{ 0x42, "LTO-2" },
{ 0x44, "LTO-3" },
{ 0x45, "QIC-3095-MC (TR-4)" },
{ 0x46, "LTO-4" },
{ 0x47, "DDS-5 or TR-5" },
{ 0x48, "SDLT220" },
{ 0x49, "SDLT320" },
{ 0x4a, "SDLT600, T10000A" },
{ 0x4b, "T10000B" },
{ 0x4c, "T10000C" },
{ 0x4d, "T10000D" },
{ 0x51, "IBM 3592 J1A" },
{ 0x52, "IBM 3592 E05 (TS1120)" },
{ 0x53, "IBM 3592 E06 (TS1130)" },
{ 0x54, "IBM 3592 E07 (TS1140)" },
{ 0x55, "IBM 3592 E08 (TS1150)" },
{ 0x56, "IBM 3592 55F (TS1155)" },
{ 0x57, "IBM 3592 60F (TS1160)" },
{ 0x58, "LTO-5" },
{ 0x59, "IBM 3592 70F (TS1170)" },
{ 0x5a, "LTO-6" },
{ 0x5c, "LTO-7" },
{ 0x5d, "LTO-7-M8" },
{ 0x5e, "LTO-8" },
{ 0x60, "LTO-9" },
{ 0x71, "IBM 3592 J1A, encrypted" },
{ 0x72, "IBM 3592 E05, encrypted" },
{ 0x73, "IBM 3592 E06, encrypted" },
{ 0x74, "IBM 3592 E07, encrypted" },
{ 0x75, "IBM 3592 E08, encrypted" },
{ 0x76, "IBM 3592 55F, encrypted" },
{ 0x77, "IBM 3592 60F, encrypted" },
{ 0x79, "IBM 3592 70F, encrypted" },
{ 0x80, "DLT 15GB uncomp. or Ecrix" },
{ 0x81, "DLT 15GB compressed" },
{ 0x82, "DLT 20GB uncompressed" },
{ 0x83, "DLT 20GB compressed" },
{ 0x84, "DLT 35GB uncompressed" },
{ 0x85, "DLT 35GB compressed" },
{ 0x86, "DLT1 40 GB uncompressed" },
{ 0x87, "DLT1 40 GB compressed" },
{ 0x88, "DLT 40GB uncompressed" },
{ 0x89, "DLT 40GB compressed" },
{ 0x8c, "EXB-8505 compressed" },
{ 0x90, "SDLT110 uncompr/EXB-8205 compr" },
{ 0x91, "SDLT110 compressed" },
{ 0x92, "SDLT160 uncompressed" },
{ 0x93, "SDLT160 compressed" }
/* clang-format on */
};
#define NBR_DENSITIES (sizeof(density_tbl) / sizeof(struct densities))
static struct booleans {
char *name;
unsigned long bitmask;
char *expl;
} boolean_tbl[] = {
/* clang-format off */
{ "buffer-writes", MT_ST_BUFFER_WRITES, "buffered writes" },
{ "async-writes", MT_ST_ASYNC_WRITES, "asynchronous writes" },
{ "read-ahead", MT_ST_READ_AHEAD, "read-ahead for fixed block size" },
{ "debug", MT_ST_DEBUGGING, "debugging (if compiled into driver)" },
{ "two-fms", MT_ST_TWO_FM, "write two filemarks when file closed" },
{ "fast-eod", MT_ST_FAST_MTEOM, "space directly to eod (and lose file number)" },
{ "auto-lock", MT_ST_AUTO_LOCK, "automatically lock/unlock drive door" },
{ "def-writes", MT_ST_DEF_WRITES, "the block size and density are for writes" },
{ "can-bsr", MT_ST_CAN_BSR, "drive can space backwards well" },
{ "no-blklimits", MT_ST_NO_BLKLIMS, "drive doesn't support read block limits" },
{ "can-partitions", MT_ST_CAN_PARTITIONS, "drive can handle partitioned tapes" },
{ "scsi2logical", MT_ST_SCSI2LOGICAL, "logical block addresses used with SCSI-2" },
{ "no-wait", MT_ST_NOWAIT, "immediate mode for rewind, etc." },
{ "weof-no-wait", MT_ST_NOWAIT_EOF, "immediate mode for writing filemarks" },
#ifdef MT_ST_SYSV
{ "sysv", MT_ST_SYSV, "enable the SystemV semantics" },
#endif
{ "sili", MT_ST_SILI, "enable SILI for variable block mode" },
{ "cleaning", MT_ST_SET_CLN, "set the cleaning bit location and mask" },
{ NULL, 0, NULL }
/* clang-format on */
};
static char *tape_name; /* The tape name for messages */
int main(int argc, char **argv)
{
int mtfd, i, argn, oflags;
unsigned int len;
char *cmdstr;
cmdef_tr *comp, *comp2;
for (argn = 1; argn < argc; argn++)
if (*argv[argn] == '-')
switch (*(argv[argn] + 1)) {
case 'f':
case 't':
argn += 1;
if (argn >= argc) {
usage(0, 1);
}
tape_name = argv[argn];
break;
case 'h':
usage(1, 0);
break;
case 'v':
version();
break;
case '-':
if (*(argv[argn] + 1) == '-' && *(argv[argn] + 2) == 'v') {
version();
}
/* Fall through */
default:
usage(0, 1);
}
else
break;
if (tape_name == NULL && (tape_name = getenv("TAPE")) == NULL) {
struct stat stbuf;
tape_name = DEFTAPE;
if (!stat(tape_name, &stbuf) && !S_ISCHR(stbuf.st_mode)) {
fprintf(stderr, "The default '%s' is not a character device.\n\n", tape_name);
usage(1, 1);
}
}
if (argn >= argc) {
usage(0, 1);
}
cmdstr = argv[argn++];
len = strlen(cmdstr);
for (comp = cmds; comp->cmd_name != NULL; comp++)
if (strncmp(cmdstr, comp->cmd_name, len) == 0)
break;
if (comp->cmd_name == NULL) {
fprintf(stderr, "mt: unknown command \"%s\"\n", cmdstr);
usage(1, 1);
}
if (len != strlen(comp->cmd_name)) {
for (comp2 = comp + 1; comp2->cmd_name != NULL; comp2++)
if (strncmp(cmdstr, comp2->cmd_name, len) == 0)
break;
if (comp2->cmd_name != NULL) {
fprintf(stderr, "mt: ambiguous command \"%s\"\n", cmdstr);
usage(1, 1);
}
}
if (comp->arg_cnt != MANY_ARGS && comp->arg_cnt < argc - argn) {
fprintf(stderr, "mt: too many arguments for the command '%s'.\n", comp->cmd_name);
exit(1);
}
if (comp->cmd_fdtype != NO_FD) {
oflags = comp->cmd_fdtype == FD_RDONLY ? O_RDONLY : O_RDWR;
if ((comp->error_tests & ET_ONLINE) == 0)
oflags |= O_NONBLOCK;
if ((mtfd = open(tape_name, oflags)) < 0) {
perror(tape_name);
exit(1);
}
} else
mtfd = (-1);
if (comp->cmd_function != NULL) {
i = comp->cmd_function(mtfd, comp, argc - argn,
(argc - argn > 0 ? argv + argn : NULL));
if (i) {
if (errno == ENOSYS)
fprintf(stderr, "mt: Command not supported by this kernel.\n");
else if (comp->error_tests != 0)
test_error(mtfd, comp);
}
} else {
fprintf(stderr, "mt: Internal error: command without function.\n");
i = 1;
}
if (mtfd >= 0)
close(mtfd);
return i;
}
static void version()
{
printf("mt-st v. %s\n", VERSION);
printf("default tape device: %s\n", DEFTAPE);
exit(0);
}
static void usage(int explain, int exit_code)
{
int ind;
int counter = 0;
fprintf(stderr, "usage: mt [-v] [--version] [-h] [ -f device ] command [ "
"count ]\n");
fprintf(stderr, "default tape device: %s\n", DEFTAPE);
if (explain) {
for (ind = 0; cmds[ind].cmd_name != NULL;) {
if (ind == 0)
counter = fprintf(stderr, "commands: ");
else
counter = fprintf(stderr, " ");
for (; cmds[ind].cmd_name != NULL; ind++) {
counter += fprintf(stderr, "%s", cmds[ind].cmd_name);
if (cmds[ind + 1].cmd_name != NULL)
counter += fprintf(stderr, ", ");
else
counter += fprintf(stderr, ".");
if (counter >= 70 || cmds[ind + 1].cmd_name == NULL) {
fprintf(stderr, "\n");
ind++;
break;
}
}
}
}
exit(exit_code);
}
/* Do a command that simply feeds an argument to the MTIOCTOP ioctl */
static int do_standard(int mtfd, cmdef_tr *cmd, int argc, char **argv)
{
int multiplier, max_count;
struct mtop mt_com;
char *endp;
mt_com.mt_op = cmd->cmd_code;
mt_com.mt_count = (argc > 0 ? strtol(*argv, &endp, 0) : 1);
if (argc > 0 && endp != *argv) {
multiplier = 1;
if (*endp == 'k')
multiplier = 1024;
else if (*endp == 'M')
multiplier = 1024 * 1024;
else if (*endp == 'G')
multiplier = 1024 * 1024 * 1024;
else if (*endp != 0) {
fprintf(stderr, "mt: illegal count unit.\n");
return 3;
}
max_count = INT_MAX / multiplier;
if (abs(mt_com.mt_count) > max_count) {
fprintf(stderr, "mt: repeat count too large.\n");
return 3;
}
mt_com.mt_count *= multiplier;
}
mt_com.mt_count |= cmd->cmd_count_bits;
if (mt_com.mt_op != MTMKPART && mt_com.mt_count < 0) {
fprintf(stderr, "mt: negative repeat count\n");
return 1;
}
if (ioctl(mtfd, MTIOCTOP, (char *)&mt_com) < 0) {
perror(tape_name);
return 2;
}
return 0;
}
/* The the drive buffering and other things with this (highly overloaded)
ioctl function. (See also do_options below.) */
static int do_drvbuffer(int mtfd, cmdef_tr *cmd, int argc, char **argv)
{
struct mtop mt_com;
mt_com.mt_op = MTSETDRVBUFFER;
mt_com.mt_count = (argc > 0 ? strtol(*argv, NULL, 0) : 1);
if ((cmd->cmd_count_bits & MT_ST_OPTIONS) == MT_ST_DEF_OPTIONS)
mt_com.mt_count &= 0xfffff;
#ifdef MT_ST_TIMEOUTS
else if ((cmd->cmd_count_bits & MT_ST_OPTIONS) == MT_ST_TIMEOUTS)
mt_com.mt_count &= 0x7ffffff;
#endif
else
mt_com.mt_count &= 0xfffffff;
mt_com.mt_count |= cmd->cmd_count_bits;
if (ioctl(mtfd, MTIOCTOP, (char *)&mt_com) < 0) {
perror(tape_name);
return 2;
}
return 0;
}
/* Set the tape driver options */
static int do_options(int mtfd, cmdef_tr *cmd, int argc, char **argv)
{
int i, an;
unsigned int len;
struct mtop mt_com;
mt_com.mt_op = MTSETDRVBUFFER;
if (argc == 0)
mt_com.mt_count = 0;
else if (isdigit(**argv))
mt_com.mt_count = strtol(*argv, NULL, 0) & ~MT_ST_OPTIONS;
else
for (an = 0, mt_com.mt_count = 0; an < argc; an++) {
len = strlen(argv[an]);
for (i = 0; boolean_tbl[i].name != NULL; i++)
if (!strncmp(boolean_tbl[i].name, argv[an], len)) {
mt_com.mt_count |= boolean_tbl[i].bitmask;
break;
}
if (boolean_tbl[i].name == NULL) {
fprintf(stderr, "Illegal property name '%s'.\n", argv[an]);
fprintf(stderr, "The implemented property names are:\n");
for (i = 0; boolean_tbl[i].name != NULL; i++)
fprintf(stderr, " %9s -> %s\n", boolean_tbl[i].name,
boolean_tbl[i].expl);
return 1;
}
if (len != strlen(boolean_tbl[i].name))
for (i++; boolean_tbl[i].name != NULL; i++)
if (!strncmp(boolean_tbl[i].name, argv[an], len)) {
fprintf(stderr, "Property name '%s' ambiguous.\n", argv[an]);
return 1;
}
}
switch (cmd->cmd_code) {
case DO_BOOLEANS:
mt_com.mt_count |= MT_ST_BOOLEANS;
break;
case SET_BOOLEANS:
mt_com.mt_count |= MT_ST_SETBOOLEANS;
break;
case CLEAR_BOOLEANS:
mt_com.mt_count |= MT_ST_CLEARBOOLEANS;
break;
}
if (ioctl(mtfd, MTIOCTOP, (char *)&mt_com) < 0) {
perror(tape_name);
return 2;
}
return 0;
}
/* Tell where the tape is */
static int do_tell(int mtfd,
cmdef_tr *cmd __attribute__((unused)),
int argc __attribute__((unused)),
char **argv __attribute__((unused)))
{
struct mtpos mt_pos;
if (ioctl(mtfd, MTIOCPOS, (char *)&mt_pos) < 0) {
perror(tape_name);
return 2;
}
printf("At block %ld.\n", mt_pos.mt_blkno);
return 0;
}
/* Position the tape to a specific location within a specified partition */
static int do_partseek(int mtfd, cmdef_tr *cmd __attribute__((unused)), int argc, char **argv)
{
struct mtop mt_com;
mt_com.mt_op = MTSETPART;
mt_com.mt_count = (argc > 0 ? strtol(*argv, NULL, 0) : 0);
if (ioctl(mtfd, MTIOCTOP, (char *)&mt_com) < 0) {
perror(tape_name);
return 2;
}
mt_com.mt_op = MTSEEK;
mt_com.mt_count = (argc > 1 ? strtol(argv[1], NULL, 0) : 0);
if (ioctl(mtfd, MTIOCTOP, (char *)&mt_com) < 0) {
perror(tape_name);
return 2;
}
return 0;
}
/* Position to start of file n. This might be implemented more intelligently
some day. */
static int do_asf(int mtfd, cmdef_tr *cmd __attribute__((unused)), int argc, char **argv)
{
struct mtop mt_com;
mt_com.mt_op = MTREW;
mt_com.mt_count = 1;
if (ioctl(mtfd, MTIOCTOP, (char *)&mt_com) < 0) {
perror(tape_name);
return 2;
}
mt_com.mt_count = (argc > 0 ? strtol(*argv, NULL, 0) : 0);
if (mt_com.mt_count > 0) {
mt_com.mt_op = MTFSF;
if (ioctl(mtfd, MTIOCTOP, (char *)&mt_com) < 0) {
perror(tape_name);
return 2;
}
}
return 0;
}
/*** Decipher the status ***/
static int do_status(int mtfd,
cmdef_tr *cmd __attribute__((unused)),
int argc __attribute__((unused)),
char **argv __attribute__((unused)))
{
struct mtget status;
int dens;
unsigned int i;
char *type, *density;
if (ioctl(mtfd, MTIOCGET, (char *)&status) < 0) {
perror(tape_name);
return 2;
}
if (status.mt_type == MT_ISSCSI1)
type = "SCSI 1";
else if (status.mt_type == MT_ISSCSI2)
type = "SCSI 2";
else if (status.mt_type == MT_ISONSTREAM_SC)
type = "OnStream SC-, DI-, DP-, or USB";
else
type = NULL;
if (type == NULL) {
if (status.mt_type & 0x800000)
printf("qic-117 drive type = 0x%05lx\n", status.mt_type & 0x1ffff);
else if (status.mt_type == 0)
printf("IDE-Tape (type code 0) ?\n");
else
printf("Unknown tape drive type (type code %ld)\n", status.mt_type);
printf("File number=%d, block number=%d.\n", status.mt_fileno, status.mt_blkno);
printf("mt_resid: %ld, mt_erreg: 0x%lx\n", status.mt_resid, status.mt_erreg);
printf("mt_dsreg: 0x%lx, mt_gstat: 0x%lx\n", status.mt_dsreg, status.mt_gstat);
} else {
printf("%s tape drive:\n", type);
if (status.mt_type == MT_ISSCSI2)
printf("File number=%d, block number=%d, partition=%ld.\n",
status.mt_fileno, status.mt_blkno, (status.mt_resid & 0xff));
else
printf("File number=%d, block number=%d.\n", status.mt_fileno,
status.mt_blkno);
if (status.mt_type == MT_ISSCSI1 || status.mt_type == MT_ISSCSI2 ||
status.mt_type == MT_ISONSTREAM_SC) {
dens = (status.mt_dsreg & MT_ST_DENSITY_MASK) >> MT_ST_DENSITY_SHIFT;
density = "no translation";
for (i = 0; i < NBR_DENSITIES; i++)
if (density_tbl[i].code == dens) {
density = density_tbl[i].name;
break;
}
printf("Tape block size %ld bytes. Density code 0x%x (%s).\n",
((status.mt_dsreg & MT_ST_BLKSIZE_MASK) >> MT_ST_BLKSIZE_SHIFT),
dens, density);
printf("Soft error count since last status=%ld\n",
(status.mt_erreg & MT_ST_SOFTERR_MASK) >> MT_ST_SOFTERR_SHIFT);
}
}
printf("General status bits on (%lx):\n", status.mt_gstat);
if (GMT_EOF(status.mt_gstat))
printf(" EOF");
if (GMT_BOT(status.mt_gstat))
printf(" BOT");
if (GMT_EOT(status.mt_gstat))
printf(" EOT");
if (GMT_SM(status.mt_gstat))
printf(" SM");
if (GMT_EOD(status.mt_gstat))
printf(" EOD");
if (GMT_WR_PROT(status.mt_gstat))
printf(" WR_PROT");
if (GMT_ONLINE(status.mt_gstat))
printf(" ONLINE");
if (GMT_D_6250(status.mt_gstat))
printf(" D_6250");
if (GMT_D_1600(status.mt_gstat))
printf(" D_1600");
if (GMT_D_800(status.mt_gstat))
printf(" D_800");
if (GMT_DR_OPEN(status.mt_gstat))
printf(" DR_OPEN");
if (GMT_IM_REP_EN(status.mt_gstat))
printf(" IM_REP_EN");
if (GMT_CLN(status.mt_gstat))
printf(" CLN");
printf("\n");
return 0;
}
/* From linux/drivers/scsi/st.[ch] */
#define ST_NBR_MODE_BITS 2
#define ST_NBR_MODES (1 << ST_NBR_MODE_BITS)
#define ST_MODE_SHIFT (7 - ST_NBR_MODE_BITS)
#define ST_MODE_MASK ((ST_NBR_MODES - 1) << ST_MODE_SHIFT)
#define TAPE_NR(minor) \
((((minor) & ~255) >> (ST_NBR_MODE_BITS + 1)) | \
((minor) & ((1 << ST_MODE_SHIFT) - 1)))
#define TAPE_MODE(minor) (((minor) & ST_MODE_MASK) >> ST_MODE_SHIFT)
static const char *st_formats[] = { "", "r", "k", "s", "l", "t", "o", "u",
"m", "v", "p", "x", "a", "y", "q", "z" };
/* Show the options if visible in sysfs */
static int do_show_options(int mtfd,
cmdef_tr *cmd __attribute__((unused)),
int argc __attribute__((unused)),
char **argv __attribute__((unused)))
{
int i, fd, options, tapeminor, tapeno, tapemode;
struct stat stat;
char fname[100], buf[20];
if (fstat(mtfd, &stat) < 0) {
perror(tape_name);
return 1;
}
if (!(stat.st_mode & S_IFCHR)) {
fprintf(stderr, "mt: not a character device.\n");
return 1;
}
tapeminor = minor(stat.st_rdev);
tapeno = TAPE_NR(tapeminor);
tapemode = TAPE_MODE(tapeminor);
tapemode <<= 4 - ST_NBR_MODE_BITS; /* from st.c */
sprintf(fname, "/sys/class/scsi_tape/st%d%s/options", tapeno, st_formats[tapemode]);
/* printf("Trying file '%s' (st_rdev 0x%lx).\n", fname, stat.st_rdev); */
if ((fd = open(fname, O_RDONLY)) < 0 || read(fd, buf, 20) < 0) {
fprintf(stderr, "Can't read the sysfs file '%s'.\n", fname);
if (fd >= 0)
close(fd);
return 2;
}
close(fd);
options = strtol(buf, NULL, 0);
printf("The options set:");
for (i = 0; boolean_tbl[i].name != NULL; i++)
if (options & boolean_tbl[i].bitmask)
printf(" %s", boolean_tbl[i].name);
printf("\n");
return 0;
}
/* Print a list of possible density codes */
static int print_densities(int fd __attribute__((unused)),
cmdef_tr *cmd __attribute__((unused)),
int argc __attribute__((unused)),
char **argv __attribute__((unused)))
{
unsigned int i, offset;
printf("Some SCSI tape density codes:\ncode explanation "
" code explanation\n");
offset = (NBR_DENSITIES + 1) / 2;
for (i = 0; i < offset; i++) {
printf("0x%02x %-28s", density_tbl[i].code, density_tbl[i].name);
if (i + offset < NBR_DENSITIES)
printf(" 0x%02x %s\n", density_tbl[i + offset].code,
density_tbl[i + offset].name);
else
printf("\n");
}
return 0;
}
/* Try to find out why the command failed */
static void test_error(int mtfd, cmdef_tr *cmd)
{
struct mtget status;
if (ioctl(mtfd, MTIOCGET, (char *)&status) < 0)
return;
if (status.mt_type != MT_ISSCSI1 && status.mt_type != MT_ISSCSI2)
return;
if ((cmd->error_tests & ET_ONLINE) && !GMT_ONLINE(status.mt_gstat))
fprintf(stderr,
"mt: The device is offline (not powered on, no tape ?).\n");
if ((cmd->error_tests & ET_WPROT) && GMT_WR_PROT(status.mt_gstat))
fprintf(stderr, "mt: The tape is write-protected.\n");
}