1/*
2 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
3 * Use is subject to license terms.
4 */
5
6/*
7 * Copyright (c) 1980 Regents of the University of California.
8 * All rights reserved.  The Berkeley Software License Agreement
9 * specifies the terms and conditions for redistribution.
10 */
11
12#pragma ident	"%Z%%M%	%I%	%E% SMI"
13
14/*
15 * mt -- magnetic tape manipulation program
16 */
17#include <stdio.h>
18#include <ctype.h>
19
20#include <errno.h>
21#include <sys/types.h>
22#include <sys/mtio.h>
23#include <sys/ioctl.h>
24#include <sys/param.h>
25#include <sys/buf.h>
26#include <sys/conf.h>
27#include <sys/file.h>
28#include <sys/uio.h>
29#include <string.h>
30#include <stdlib.h>
31#include <unistd.h>
32#include <sys/stat.h>
33#include <sys/scsi/targets/stdef.h>
34#include <fcntl.h>
35
36
37#define	equal(s1, s2)	(strcmp(s1, s2) == 0)
38#define	MTASF		100	/* absolute file positioning; first file is 0 */
39
40/*
41 * This can't be DEFTAPE in mtio.h because that is currently the rewinding
42 * unit which makes 'mt fsf' a questionable activity at best.
43 */
44#define	DEFAULT_NRW_TAPE	"/dev/rmt/0n"
45
46static int print_config(int mtfd);
47static char *print_key(short key_code);
48static void printreg(char *, ushort_t, char *);
49static int status(int mtfd, struct mtget *);
50
51/* Pseudo flag for open even if drive is not ready (Unloaded) or reserved */
52#define	O_UNLOAD (O_RDWR | O_NDELAY)
53
54static const struct commands {
55	char *c_name;
56	int c_code;
57	int c_oflag;
58	int c_usecnt;
59} com[] = {
60	{ "weof",		MTWEOF,			O_RDWR,		1 },
61	{ "eof",		MTWEOF,			O_RDWR,		1 },
62	{ "fsf",		MTFSF,			O_RDONLY,	1 },
63	{ "bsf",		MTBSF,			O_RDONLY,	1 },
64	{ "asf",		MTASF,			O_RDONLY,	1 },
65	{ "fsr",		MTFSR,			O_RDONLY,	1 },
66	{ "bsr",		MTBSR,			O_RDONLY,	1 },
67	{ "rewind",		MTREW,			O_RDONLY,	0 },
68	{ "offline",		MTOFFL,			O_RDONLY,	0 },
69	{ "rewoffl",		MTOFFL,			O_RDONLY,	0 },
70	{ "status",		MTNOP,			O_RDONLY,	0 },
71	{ "retension",		MTRETEN,		O_RDONLY,	0 },
72	{ "erase",		MTERASE,		O_RDWR,		0 },
73	{ "eom",		MTEOM,			O_RDONLY,	0 },
74	{ "nbsf",		MTNBSF,			O_RDONLY,	1 },
75	{ "reserve",		MTIOCRESERVE,		O_RDONLY,	0 },
76	{ "release",		MTIOCRELEASE,		O_RDONLY,	0 },
77	{ "forcereserve",	MTIOCFORCERESERVE,	O_UNLOAD,	0 },
78	{ "config",		MTIOCGETDRIVETYPE,	O_UNLOAD,	0 },
79	{ "fssf",		MTFSSF,			O_RDONLY,	1 },
80	{ "bssf",		MTBSSF,			O_RDONLY,	1 },
81	{ "tell",		MTTELL,			O_RDONLY,	0 },
82	{ "seek",		MTSEEK,			O_RDONLY,	1 },
83	{ "load",		MTLOAD,			O_UNLOAD,	0 },
84	{ "lock",		MTLOCK,			O_RDONLY,	0 },
85	{ "unlock",		MTUNLOCK,		O_RDONLY,	0 },
86	{ 0 }
87};
88
89
90int
91main(int argc, char **argv)
92{
93	char *cp;
94	char *tape;
95	int mtfd;
96	struct commands const *comp;
97	struct mtget mt_status;
98	struct mtlop mt_com;
99
100	if (argc > 2 && (equal(argv[1], "-t") || equal(argv[1], "-f"))) {
101		argc -= 2;
102		tape = argv[2];
103		argv += 2;
104	} else {
105		tape = getenv("TAPE");
106		if (tape == NULL) {
107			tape = DEFAULT_NRW_TAPE;
108		}
109	}
110
111	if (argc < 2) {
112		(void) fprintf(stderr,
113		    "usage: mt [ -f device ] command [ count ]\n");
114		return (1);
115	}
116
117	cp = argv[1];
118	for (comp = com; comp->c_name != NULL; comp++) {
119		if (strncmp(cp, comp->c_name, strlen(cp)) == 0) {
120			break;
121		}
122	}
123
124	if (comp->c_name == NULL) {
125		(void) fprintf(stderr, "mt: unknown command: %s\n", cp);
126		return (1);
127	}
128
129	mtfd = open(tape, comp->c_oflag);
130	if (mtfd < 0) {
131
132		/*
133		 * Provide additional error message decoding since
134		 * we need additional error codes to fix them problem.
135		 */
136		if (errno == EIO) {
137			(void) fprintf(stderr,
138			    "%s: no tape loaded or drive offline\n", tape);
139		} else if (errno == EACCES) {
140			(void) fprintf(stderr,
141			    "%s: write protected or reserved.\n", tape);
142		} else {
143			perror(tape);
144		}
145		return (1);
146	}
147
148	if (comp->c_code == MTIOCFORCERESERVE ||
149	    comp->c_code == MTIOCRESERVE ||
150	    comp->c_code == MTIOCRELEASE)  {
151		/*
152		 * Handle all MTIOC ioctls used in
153		 * reservation/release/takeownership.
154		 */
155		if (ioctl(mtfd, comp->c_code) < 0) {
156			perror("mt");
157			return (2);
158		}
159	} else if (comp->c_code == MTASF) {
160		/*
161		 * Handle absolute file positioning.  Ask tape driver
162		 * where tape is and then skip to desired file.  If
163		 * driver doesn't support get location ioctl, rewind
164		 * the tape and then space to the desired file.
165		 */
166		int usecnt;
167		daddr_t mt_fileno;
168
169		usecnt = argc > 2 && comp->c_usecnt;
170		mt_fileno = usecnt ? atol(argv[2]) : 1;
171		if (mt_fileno < 0) {
172			(void) fprintf(stderr, "mt: negative file number\n");
173			return (1);
174		}
175		(void) ioctl(mtfd, MTIOCGET, (char *)&mt_status);
176		if (ioctl(mtfd, MTIOCGET, (char *)&mt_status) < 0) {
177			perror("mt");
178			return (2);
179		}
180		/*
181		 * Check if device supports reporting current file
182		 * tape file position.  If not, rewind the tape, and
183		 * space forward.
184		 *
185		 * If file number is -1 tape position is unknown!
186		 */
187		if ((mt_status.mt_flags & MTF_ASF) == 0 ||
188		    (mt_status.mt_fileno == -1)) {
189			/* printf("mt: rewind\n"); */
190			mt_com.mt_count = 1;
191			mt_com.mt_op = MTREW;
192			if (ioctl(mtfd, MTIOCLTOP, &mt_com) < 0) {
193				(void) fprintf(stderr, "%s %s %ld ",
194				    tape, comp->c_name, mt_fileno);
195				perror("mt");
196				return (2);
197			}
198			/* Needed to rewind which worked now correct fileno */
199			mt_status.mt_fileno = 0;
200			mt_status.mt_blkno = 0;
201		}
202		if (mt_fileno < mt_status.mt_fileno) {
203			mt_com.mt_op = MTNBSF;
204			mt_com.mt_count = mt_status.mt_fileno - mt_fileno;
205			/* printf("mt: bsf= %d\n", mt_com.mt_count); */
206		} else {
207			mt_com.mt_op = MTFSF;
208			mt_com.mt_count =  mt_fileno - mt_status.mt_fileno;
209			/* printf("mt: fsf= %d\n", mt_com.mt_count); */
210		}
211		if (ioctl(mtfd, MTIOCLTOP, &mt_com) < 0) {
212			(void) fprintf(stderr, "%s %s %ld ", tape, comp->c_name,
213			    mt_fileno);
214			perror("failed");
215			return (2);
216		}
217	} else if (comp->c_code == MTIOCGETDRIVETYPE) {
218		return (print_config(mtfd));
219
220	/* Handle regular mag tape ioctls */
221	} else if (comp->c_code != MTNOP) {
222		int usecnt;
223
224		mt_com.mt_op = comp->c_code;
225		usecnt = argc > 2 && comp->c_usecnt;
226		mt_com.mt_count = (usecnt ? atoll(argv[2]) : 1);
227		if (mt_com.mt_count < 0) {
228			(void) fprintf(stderr, "mt: negative %s count\n",
229			    comp->c_name);
230			return (1);
231		}
232		if (ioctl(mtfd, MTIOCLTOP, &mt_com) < 0) {
233			/*
234			 * If we asked for a seek and it returns a tell
235			 * we attempted to seek more then there was.
236			 */
237			if (mt_com.mt_op == MTTELL &&
238			    comp->c_code == MTSEEK) {
239				(void) printf("partial seek:at block = %llu.\n",
240				    mt_com.mt_count);
241			} else {
242				(void) fprintf(stderr, "%s %s %lld ", tape,
243				    comp->c_name, mt_com.mt_count);
244				perror("failed");
245			}
246			return (2);
247		}
248		if (mt_com.mt_op == MTTELL) {
249			(void) printf("At block = %llu.\n", mt_com.mt_count);
250		}
251
252
253	/* Handle status ioctl */
254	} else {
255		if (ioctl(mtfd, MTIOCGET, (char *)&mt_status) < 0) {
256			perror("mt");
257			return (2);
258		}
259		return (status(mtfd, &mt_status));
260	}
261	return (0);
262}
263
264static int
265print_config(int mtfd)
266{
267	struct mtdrivetype mdt;
268	struct mtdrivetype_request mdt_req;
269	char cfgname[48];
270	char tmp[2];
271	char *name;
272	int i;
273
274	mdt_req.size = sizeof (mdt);
275	mdt_req.mtdtp = &mdt;
276
277	if (ioctl(mtfd, MTIOCGETDRIVETYPE, &mdt_req) != 0) {
278		perror("mt config");
279		return (2);
280	}
281
282	/*
283	 * remove trailing spaces from product id.
284	 */
285	for (i = VIDPIDLEN; i; i--) {
286		if (isspace(mdt.vid[i]) || mdt.vid[i] == '*') {
287			mdt.vid[i] = 0;
288		} else if (mdt.vid[i] == 0) {
289			continue;
290		} else {
291			break;
292		}
293	}
294
295	/*
296	 * If this is a generic name display the Vid and Pid instead.
297	 */
298	if (strstr(mdt.name, "Vendor '") == NULL) {
299		name = mdt.name;
300	} else {
301		name = mdt.vid;
302	}
303
304	/*
305	 * Attempt to create a configuration name using vid and pid.
306	 */
307	(void) strcpy(cfgname, "CFG");
308
309	for (tmp[1] = i = 0; i < VIDPIDLEN; i++) {
310		if (!isalnum(name[i]))
311			continue;
312		if (isspace(name[i]))
313			continue;
314		tmp[0] = toupper(name[i]);
315		(void) strncat(cfgname, tmp, 1);
316	}
317
318	(void) printf("\"%s\", \"%s\", \"%s\";\n", mdt.vid, name, cfgname);
319
320	/*
321	 * Don't want show some bits, ST_DYNAMIC is set in the driver
322	 * so one can tell that its not a compiled in config.
323	 * The ST_LONG_ERASE and ST_LONG_TIMEOUTS are not displayed
324	 * becouse the timeout values below already reflect them being
325	 * set.
326	 * Also ST_KNOWS_MEDIA is not displayed as it can not be configured
327	 * from an st.conf entry.
328	 */
329	(void) printf("%s = 2,0x%X,%d,0x%X,", cfgname,
330	    mdt.type, mdt.bsize, mdt.options &
331	    ~(ST_DYNAMIC | ST_LONG_ERASE | ST_LONG_TIMEOUTS | ST_KNOWS_MEDIA));
332
333	(void) printf("4,0x%2.2X,0x%2.2X,0x%2.2X,0x%2.2X,%d,",
334	    mdt.densities[0], mdt.densities[1], mdt.densities[2],
335	    mdt.densities[3], mdt.default_density >> 3);
336
337	(void) printf("%d,%d,%d,%d,%d,%d,%d;\n", mdt.non_motion_timeout,
338	    mdt.io_timeout, mdt.rewind_timeout, mdt.space_timeout,
339	    mdt.load_timeout, mdt.unload_timeout, mdt.erase_timeout);
340
341	return (0);
342}
343
344/*
345 * Interpret the status buffer returned
346 */
347static int
348status(int mtfd, struct mtget *bp)
349{
350	struct mtdrivetype mdt;
351	struct mtdrivetype_request mdt_req;
352	const char *name = (char *)NULL;
353
354	/*
355	 * Make a call to MTIOCGETDRIVETYPE ioctl, Also use old method
356	 * of MT_TAPE_INFO for now, but MT_TAPE_INFO should dissapear in 2.7
357	 */
358	mdt_req.size = sizeof (struct mtdrivetype);
359	mdt_req.mtdtp = &mdt;
360
361	if (ioctl(mtfd, MTIOCGETDRIVETYPE, &mdt_req) == 0) {
362		name = mdt.name;
363		if (strstr(mdt.name, "Vendor '") != NULL) {
364			(void) printf("Unconfigured Drive: ");
365		}
366	} else {
367		perror("mt drivetype");
368		return (2);
369	}
370
371	/* Handle SCSI tape drives specially. */
372	if ((bp->mt_flags & MTF_SCSI)) {
373		if (name == (char *)NULL) {
374			name = "SCSI";
375		}
376
377
378		(void) printf("%s tape drive:\n", name);
379
380		(void) printf("   sense key(0x%x)= %s   residual= %ld   ",
381		    bp->mt_erreg, print_key(bp->mt_erreg), bp->mt_resid);
382		(void) printf("retries= %d\n", bp->mt_dsreg);
383		/*
384		 * Can overflow the signed numbers.
385		 * fileno will be -1 on error but all other positions are
386		 * positive. blkno will never be negative.
387		 */
388		if (bp->mt_fileno == -1) {
389			(void) printf("   file no= -1   block no= %lu\n",
390			    (unsigned long)bp->mt_blkno);
391		} else {
392			(void) printf("   file no= %lu   block no= %lu\n",
393			    (unsigned long)bp->mt_fileno,
394			    (unsigned long)bp->mt_blkno);
395		}
396		if ((bp->mt_flags & MTF_WORM_MEDIA) != 0) {
397			(void) printf("   WORM media\n");
398		}
399	} else {
400		/* Handle non-SCSI drives here. */
401		if (name == NULL) {
402			(void) printf("unknown tape drive type (0x%x)\n",
403			    mdt.type);
404			return (2);
405		}
406		(void) printf("%s tape drive:\n   residual= %ld", name,
407		    bp->mt_resid);
408		printreg("   ds", (ushort_t)bp->mt_dsreg, 0);
409		printreg("   er", (ushort_t)bp->mt_erreg, 0);
410		(void) putchar('\n');
411	}
412	return (0);
413}
414
415
416/*
417 * Define SCSI sense key error messages.
418 *
419 * The first 16 sense keys are SCSI standard
420 * sense keys. The keys after this are
421 * Sun Specifice 'sense' keys- e.g., crap.
422 */
423
424static char *sense_keys[] = {
425	"No Additional Sense",		/* 0x00 */
426	"Soft Error",			/* 0x01 */
427	"Not Ready",			/* 0x02 */
428	"Media Error",			/* 0x03 */
429	"Hardware Error",		/* 0x04 */
430	"Illegal Request",		/* 0x05 */
431	"Unit Attention",		/* 0x06 */
432	"Write Protected",		/* 0x07 */
433	"Blank Check",			/* 0x08 */
434	"Vendor Unique",		/* 0x09 */
435	"Copy Aborted",			/* 0x0a */
436	"Aborted Command",		/* 0x0b */
437	"Equal Error",			/* 0x0c */
438	"Volume Overflow",		/* 0x0d */
439	"Miscompare Error",		/* 0x0e */
440	"Reserved",			/* 0x0f */
441#ifdef	sun
442	"fatal",			/* 0x10 */
443	"timeout",			/* 0x11 */
444	"EOF",				/* 0x12 */
445	"EOT",				/* 0x13 */
446	"length error",			/* 0x14 */
447	"BOT",				/* 0x15 */
448	"wrong tape media",		/* 0x16 */
449#endif
450};
451
452/*
453 * Return the text string associated with the sense key value.
454 */
455static char *
456print_key(short key_code)
457{
458	static char unknown[32];
459
460	if ((key_code >= 0) &&
461	    (key_code < (sizeof (sense_keys) / sizeof (sense_keys[0])))) {
462		return (sense_keys[key_code]);
463	}
464
465	(void) sprintf(unknown, "unknown sense key: 0x%x",
466	    (unsigned int) key_code);
467	return (unknown);
468}
469
470
471/*
472 * Print a register a la the %b format of the kernel's printf
473 */
474static void
475printreg(char *s, ushort_t v, char *bits)
476{
477	int i, any = 0;
478	char c;
479
480	if (bits && *bits == 8) {
481		(void) printf("%s = %o", s, v);
482	} else {
483		(void) printf("%s = %x", s, v);
484	}
485	bits++;
486	if (v && bits) {
487		(void) putchar('<');
488		while ((i = *bits++) != 0) {
489			if (v & (1 << (i-1))) {
490				if (any) {
491					(void) putchar(',');
492				}
493				any = 1;
494				for (; (c = *bits) > 32; bits++) {
495					(void) putchar(c);
496				}
497			} else {
498				for (; *bits > 32; bits++)
499					;
500			}
501		}
502		(void) putchar('>');
503	}
504}
505