/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #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"); }