File Coverage

blib/lib/TapeChanger/MTX.pm
Criterion Covered Total %
statement 9 197 4.5
branch 1 124 0.8
condition 1 22 4.5
subroutine 3 34 8.8
pod 25 28 89.2
total 39 405 9.6


line stmt bran cond sub pod time code
1             $VERSION = '1.01';
2              
3             package TapeChanger::MTX;
4             # -*- Perl -*- Fri Jan 16 11:07:17 CST 2004
5             ###############################################################################
6             # Written by Tim Skirvin
7             # Copyright 2001-2004, Tim Skirvin and UIUC Board of Trustees.
8             # Redistribution terms are below.
9             ###############################################################################
10             my $VERSION = '1.01';
11              
12             =head1 NAME
13              
14             TapeChanger::MTX - use 'mtx' to manipulate a tape library
15              
16             =head1 SYNOPSIS
17              
18             use TapeChanger::MTX;
19              
20             my $loaded = TapeChanger::MTX->loadedtape;
21             print "Currently loaded: $loaded\n" if ($loaded);
22              
23             TapeChanger::MTX->loadtape('next');
24             my $nowloaded = TapeChanger::MTX->loadedtape;
25             print "Currently loaded: $nowloaded\n" if ($nowloaded);
26            
27             See below for more available functions.
28              
29             =head1 DESCRIPTION
30              
31             TapeChanger::MTX is a module to manipulate a tape library using the 'mtx'
32             tape library program. It is meant to work with a simple shell/perl script
33             to load and unload tapes as appropriate, and to provide a interface for
34             more complicated programs to do the same. The below functions and
35             variables should do as good a job as explaining this as anything.
36              
37             =cut
38              
39             ###############################################################################
40             ### Initialization ############################################################
41             ###############################################################################
42              
43             require 5.6.0;
44 1     1   691 use strict;
  1         2  
  1         32  
45              
46             ###############################################################################
47             ### Variables #################################################################
48             ###############################################################################
49              
50             =head1 VARIABLES
51              
52             =over 4
53              
54             =cut
55            
56 1     1   3 use vars qw( $MTX $DRIVE $CONTROL $MT $EJECT $READY_TIME $DEBUG );
  1         2  
  1         2766  
57              
58             =item $TapeChanger::MTX::MT
59             =item $TapeChanger::MTX::MTX
60              
61             What is the location of the 'mt' and 'mtx' binaries? Can be set with
62             '$MT' and '$MTX' in ~/.mtxrc, or defaults to '/usr/sbin/mt' and
63             '/usr/local/sbin/mtx'.
64              
65             =cut
66              
67             $MT = "/usr/bin/mt";
68             $MTX = "/usr/local/sbin/mtx";
69              
70             =item $TapeChanger::MTX::DRIVE
71              
72             =item $TapeChanger::MTX::CONTROL
73              
74             What are the names of the tape (DRIVE) and changer (CONTROL) device
75             nodes? Can be set with $DRIVE or $CONTROL in ~/.mtxrc, or default to
76             '/dev/rmt/0' and '/dev/changer' respectively.
77              
78             =cut
79              
80             $DRIVE = "/dev/rmt/0";
81             $CONTROL = "/dev/changer";
82              
83             =item $TapeChanger::MTX::EJECT
84              
85             Does the tape drive have to eject the tape before the changer retrieves
86             it? It's okay to say 'yes' if it's not necessary, in most cases. Can be
87             set with $EJECT in ~/.mtxrc, or defaults to '1'.
88              
89             =cut
90              
91             $EJECT = 1;
92              
93             =item $TapeChanger::MTX::READY_TIME
94              
95             How long should we wait to see if the drive is ready, in seconds, after
96             mounting a volume? Can be set with $READY_TIME in ~/.mtxrc, or defaults
97             to 60.
98              
99             =cut
100              
101             $READY_TIME = 60;
102              
103             =item $TapeChanger::MTX::DEBUG
104              
105             Print debugging information? Set to '0' for normal verbosity, '1' for
106             debugging information, or '-1' for 'quiet mode' (be as quiet as possible).
107              
108             =back
109              
110             =cut
111              
112             $DEBUG = 0;
113              
114             ###############################################################################
115             ### Internal Variables ########################################################
116             ###############################################################################
117              
118             ## Define where .mtxrc actually is. Doesn't get edited locally, so I'm not
119             our $MTXRC = "$ENV{HOME}/.mtxrc";
120              
121             ## Default value for the internal "@RETURN".
122             our @RETURN = ('');
123              
124             ###############################################################################
125             ### Functions #################################################################
126             ###############################################################################
127              
128             =head1 USAGE
129              
130             This module uses the following functions:
131              
132             =over 4
133              
134             =cut
135              
136             =item tape_cmd ( COMMAND )
137              
138             =item mt_cmd ( COMMAND )
139              
140             Runs 'mtx' and 'mt' as appropriate. C is the command you're
141             trying to send to them. Uses 'warn()' to print the commands to the screen
142             if $TapeChanger::MTX::DEBUG is set.
143              
144             =cut
145              
146 0     0 1 0 sub tape_cmd { shift->_run("$MTX -f $CONTROL @_") }
147 0     0 1 0 sub mt_cmd { shift->_run("$MT -f $DRIVE @_") }
148              
149             ### _run( STRING )
150             # Actually does the work of 'tape_cmd' and 'mt_cmd'. Just runs the
151             # command that's supposed to be run. Puts the return text into @RETURN
152             # for future reference.
153              
154             sub _run {
155 0     0   0 my ($self, $string) = @_;
156 0 0       0 warn "$string\n" if debug();
157 0         0 my @return;
158 0 0 0     0 my $return = open (CMD, "$string 2>&1 |") or
159             (warn "Couldn't run $string: $!\n" and return undef);
160 0 0       0 if (debug()) { foreach () { print; chomp; push @return, $_ } }
  0         0  
  0         0  
  0         0  
  0         0  
161 0         0 else { @return = ; chomp @return; }
  0         0  
162 0         0 close(CMD);
163 0   0     0 @RETURN = @return || ('');
164 0 0       0 wantarray ? @return : join("\n", @return);
165             }
166              
167             =item numdrives ()
168              
169             =item numslots ()
170              
171             =item loadedtape ()
172              
173             =item numloaded ()
174              
175             =item nummailslots ()
176              
177             Returns the number of drives, number of slots, currently loaded tape,
178             number of loaded tapes, and number of Import/Export slots, respectively,
179             by parsing B. Not all of these will apply to all tape
180             drives.
181              
182             =cut
183              
184 0 0   0 1 0 sub numdrives { (shift->_getchangerparms)[0] || 0 }
185 0 0   0 1 0 sub numslots { (shift->_getchangerparms)[1] || 0 }
186 0 0   0 1 0 sub loadedtape { (shift->_getchangerparms)[2] || 0 }
187 0 0   0 1 0 sub numloaded { (shift->_getchangerparms)[3] || 0 }
188 0 0   0 1 0 sub nummailslots { (shift->_getchangerparms)[4] || 0 }
189              
190             ### _getchangerparms ()
191             # Does the work for the above functions.
192             sub _getchangerparms {
193 0     0   0 my ($self) = @_;
194 0         0 my @status = split("\n", $self->tape_cmd('status'));
195 0 0       0 unless ($? eq 0) { return (0, 0, 0, 0, 0) }
  0         0  
196            
197 0         0 my ($numdrives, $numslots, $loadedtape, $numloaded, $mailslots) = 0;
198 0         0 foreach (@status)
199             {
200 0 0       0 if (/^Data Transfer Element/)
201             {
202 0         0 $numdrives++;
203 0 0       0 if (/\(Storage Element (\d+) Loaded\).*$/)
204 0         0 { $loadedtape = $1, $numloaded ++ };
205             }
206             else
207             {
208 0 0       0 if (/^\s*Storage Element \d+/) { $numslots++ };
  0         0  
209 0 0       0 if (/^\s*Storage Element \d+ IMPORT\/EXPORT:/) { $mailslots++ };
  0         0  
210             };
211             }
212 0         0 ($numdrives, $numslots, $loadedtape, $numloaded, $mailslots);
213             }
214              
215             =item slothash ()
216              
217             Returns a hash table (not hashref) of information about each slot. The
218             keys of the hash are the slot numbers, and the values are arrayrefs that
219             contain three fields:
220            
221             SlotType "Import/Export" or empty string
222             Full "Full" or "Empty"
223             VolumeTag Tape barcode, if it exists
224              
225             =cut
226              
227             sub slothash {
228 0     0 1 0 my $self = shift;
229 0         0 my %slots;
230 0         0 my @status = split("\n", $self->tape_cmd('status'));
231 0         0 my @slot;
232 0 0       0 unless ($? eq 0) { return undef }
  0         0  
233 0         0 foreach (@status) {
234 0 0       0 if (/^\s*Storage Element (\d+)(\s([^:]*))*:([^(:|\s)]*)\s*(:VolumeTag=([^\s]*))*.*/) {
235             # $1-slot number, $3-slot type, $4-Full or Empty, $6 Volume tag if exist
236 0         0 @slot=($3,$4,$6);
237 0         0 $slots{$1}=[@slot]
238             }
239             }
240 0         0 %slots;
241             }
242              
243             =item drivehash ()
244              
245             As with B, but looks at the drives instead of the slots.
246              
247             =cut
248              
249             sub drivehash()
250             {
251 0     0 1 0 my ($self) = shift;
252 0         0 my %drives;
253 0         0 my @status = split("\n", $self->tape_cmd('status'));
254 0         0 my @drive;
255 0 0       0 unless ($? eq 0) { return undef }
  0         0  
256 0         0 foreach (@status) {
257 0 0       0 if (/Data Transfer Element (\d+):([^\s|\(]*)(\s*\(Storage Element (\d+) Loaded\))*(:VolumeTag = ([^\s]*))*.*/) {
258             # $1-drive number, $2-Full, $4-Element loaded ,$6-VolumeTag
259 0         0 @drive=($2,$4,$6);
260 0         0 $drives{$1}=[@drive];
261             }
262             }
263 0         0 %drives;
264             }
265              
266             =item loadtape ( SLOT [, DRIVE] )
267              
268             Loads a tape into the tape changer, and waits until the drive is again
269             ready to be written to. C can be any of the following (with the
270             relevant function indicated):
271              
272             current C
273             prev C
274             next C
275             first C
276             last C
277             0 C<_ejectdrive()>
278             1..99 Loads the specified tape number, ejecting whatever is
279             currently in the drive.
280              
281             C is the drive to load, and defaults to 0. Returns 0 if
282             successful, an error string otherwise.
283              
284             =cut
285              
286             sub loadtape {
287 0   0 0 1 0 my ($self, $slot, $drive) = @_; $drive ||= 0;
  0         0  
288            
289 0 0       0 if (lc $slot eq 'current') { $self->loadedtape }
  0 0       0  
    0          
    0          
    0          
    0          
290 0         0 elsif (lc $slot eq 'prev') { $self->loadprevtape($drive) }
291 0         0 elsif (lc $slot eq 'next') { $self->loadnexttape($drive) }
292 0         0 elsif (lc $slot eq 'first') { $self->loadfirsttape($drive) }
293 0         0 elsif (lc $slot eq 'last') { $self->loadlasttape($drive) }
294 0         0 elsif (lc $slot =~ /^(\d+)$/) { $self->_doloadtape($1, $drive) }
295 0         0 else { return "No valid slot specified" }
296              
297 0 0       0 $self->checkdrive || return "Drive wouldn't report ready: @RETURN\n";
298             }
299              
300             ### _doloadtape( SLOT, DRIVE )
301             # Does the actual work for loading tapes, when it's not done by mtx itself.
302             sub _doloadtape {
303 0   0 0   0 my ($self, $slot, $drive) = @_; $slot ||= 0;
  0         0  
304 0   0     0 my $loaded = $self->loadedtape || 0;
305 0 0       0 return 1 if ($slot eq $loaded);
306 0 0       0 if ($loaded) { $self->_ejectdrive && $self->tape_cmd('unload') }
  0 0       0  
307 0   0     0 $loaded = $self->loadedtape || 0;
308 0 0       0 return "Couldn't unload tape $loaded" if $loaded;
309 0 0       0 $slot ? $self->tape_cmd('load', $slot, $drive) : "No slot to load";
310             }
311              
312             =item loadnexttape ()
313              
314             =item loadprevtape ()
315              
316             =item loadfirsttape ()
317              
318             =item loadlasttape ()
319              
320             Loads the next, previous, first, and last tapes in the changer
321             respectively. Use B, B,
322             B, and B, respectively.
323              
324             =cut
325              
326             sub loadnexttape {
327 0     0 1 0 my $self = shift;
328 0         0 $self->_ejectdrive();
329 0         0 $self->tape_cmd('next', @_)
330             }
331             sub loadprevtape {
332 0     0 1 0 my $self = shift;
333 0         0 $self->_ejectdrive();
334 0         0 $self->tape_cmd('previous', @_)
335             }
336             sub loadfirsttape {
337 0     0 1 0 my $self = shift;
338 0         0 $self->_ejectdrive();
339 0         0 $self->tape_cmd('first', @_)
340             }
341             sub loadlasttape {
342 0     0 1 0 my $self = shift;
343 0         0 $self->_ejectdrive();
344 0         0 $self->tape_cmd('last', @_)
345             }
346              
347             =item transfertape ( FROM, TO )
348              
349             Transfers a tape from slot C to slot C. Returns 0 on success.
350             Makes sure the necessary slots are empty/full as appropriate.
351              
352             =cut
353              
354             sub transfertape {
355 0     0 1 0 my ($self, $from, $to) = @_;
356 0         0 my %slots = $self->slothash;
357            
358 0 0       0 if ($slots{$from}[1] eq 'Empty') {
359 0         0 print "Cannot transfer from Empty slot\n";
360 0         0 return 1;
361             }
362 0 0       0 if ($slots{$to}[1] eq 'Full') {
363 0         0 print "Cannot transfer to Full slot\n";
364 0         0 return 1;
365             }
366 0         0 $self->tape_cmd('transfer', $from, $to);
367             }
368              
369             =item tagtoslot ( TAG )
370              
371             Returns the slot that the tape with volume tag C is in, or '0' if
372             it's not in the tape changer.
373              
374             =cut
375              
376             sub tagtoslot {
377 0     0 1 0 my ($self, $tag) = @_;
378 0         0 chomp($tag);
379 0         0 my @status = split("\n", $self->tape_cmd('status'));
380 0 0       0 unless ($? eq 0) { return 0 }
  0         0  
381            
382 0         0 my $slot;
383 0         0 foreach( @status ) {
384 0 0       0 if (/^\s*Storage Element (\d+)[^:]*:Full :VolumeTag=$tag/) { $slot = $1 }
  0         0  
385             }
386 0 0       0 $slot || 0;
387             }
388              
389             =item slottotag ( SLOT )
390              
391             Returns the volume tag of the tape in slot C, or '' if there is no
392             tag or tape.
393              
394             =cut
395              
396             sub slottotag {
397 0     0 1 0 my ($self, $slot) = @_;
398            
399 0         0 my @status = split("\n", $self->tape_cmd('status'));
400 0 0       0 unless ($? eq 0) { return 0 }
  0         0  
401              
402 0         0 my $tag = "";
403 0         0 foreach(@status) {
404 0 0       0 if (/^\s*Storage Element $slot[^:]*:Full :VolumeTag=(.*)/) { $tag = $1 }
  0         0  
405             }
406 0         0 return $tag;
407             }
408              
409             =item tagtodrive ( TAG )
410              
411             Returns the drive that the tape with volume tag C is in, or '-1' if
412             it's not in a drive.
413              
414             =cut
415              
416             sub tagtodrive {
417 0     0 1 0 my ($self, $tag) = @_;
418              
419 0         0 chomp($tag);
420 0         0 my @status = split("\n", $self->tape_cmd('status'));
421 0 0       0 unless ($? eq 0) { return -1 }
  0         0  
422            
423 0         0 my $drive;
424 0         0 foreach(@status) {
425 0 0       0 if (/^Data Transfer Element (\d+):Full (Storage Element \d+ Loaded):VolumeTag = $tag/) { $drive=$1 }
  0         0  
426             };
427 0   0     0 return $drive || -1;
428             }
429              
430             =item drivetotag ( DRIVE )
431              
432             Returns the volume tag of the tape in drive C, or '' if there is no
433             tag or tape.
434              
435             =cut
436              
437             sub drivetotag {
438 0     0 1 0 my ($self, $drive) = @_;
439              
440 0         0 my @status = split("\n", $self->tape_cmd('status'));
441 0 0       0 unless ($? eq 0) { return '' }
  0         0  
442              
443 0         0 my $tag;
444 0         0 foreach (@status) {
445 0 0       0 if (/^Data Transfer Element $drive:Full \(Storage Element \d+ Loaded\):VolumeTag = ([^\s]*)/) { $tag=$1 }
  0         0  
446             }
447 0   0     0 return $tag || "";
448             }
449              
450             =item ejecttape ()
451              
452             Ejects the tape, by first ejecting the tape from the drive
453             (B then B) and then returning it to its
454             slot (B). Returns 1 if successful, 0 otherwise.
455              
456             =cut
457              
458             sub ejecttape {
459 0   0 0 1 0 my ($self, $drive) = @_; $drive ||= 0;
  0         0  
460 0         0 my ($drives, $slots, $loaded) = $self->_getchangerparms;
461 0 0       0 if ($loaded) {
462 0         0 $self->_ejectdrive($drive);
463 0         0 $self->tape_cmd('unload');
464 0 0       0 return $? ? 0 : 1;
465 0         0 } else { return 1 } # Already unloaded
466             }
467              
468             ### _ejectdrive ( [DRIVE] )
469             # Does the rewinding, and that's it
470             sub _ejectdrive {
471 0     0   0 my ($self) = @_;
472 0         0 my $loaded = $self->loadedtape;
473 0 0       0 return 1 unless $loaded;
474 0 0       0 if ($EJECT) {
475 0         0 $self->mt_cmd('rewind');
476 0 0       0 if ($? ne 0) { # rewind failed
477 0 0       0 return 0 if ($RETURN[0] !~ /no tape/); # not because there was no tape
478             }
479 0         0 $self->mt_cmd('offline');
480             }
481 0         0 1;
482             }
483              
484             =item resetchanger ()
485              
486             Resets the changer, ejecting the tape and loading the first one from the
487             changer.
488              
489             =cut
490              
491             sub resetchanger {
492 0     0 1 0 my ($self) = @_;
493 0         0 $self->_ejectdrive;
494 0         0 $self->loadtape('first');
495             }
496              
497             =item checkdrive ()
498              
499             Checks to see if the drive is ready or not, by waiting for up to
500             $TapeChanger::MTX::READY_TIME seconds to see if it can get status
501             information using B. Returns 1 if so, 0 otherwise.
502              
503             =cut
504              
505             sub checkdrive {
506 0     0 1 0 my ($self) = @_;
507 0         0 my $start = time; # We're using clock-seconds here
508 0         0 while (time - $start < $READY_TIME) {
509 0         0 $self->mt_cmd('status');
510 0 0       0 return 1 unless $?;
511 0         0 sleep 1;
512             }
513 0         0 return 0;
514             }
515              
516             =item reportstatus
517              
518             Returns a string containing the loaded tape and the drive that it's
519             mounted on.
520              
521             =cut
522              
523 0   0 0 1 0 sub reportstatus { (shift->loadedtape || 'unloaded') . " $DRIVE" }
524              
525             =item inventory ()
526              
527             Runs a tape inventroy, if supported by the tape changer. This works out
528             volume tags and such.
529              
530             =cut
531              
532 0     0 1 0 sub inventory { shift->tape_cmd('inventory'); }
533              
534              
535             =item cannot_run ()
536              
537             Does some quick checks to see if you're actually capable of using this
538             module, based on your user permissions. Returns a list of problems if
539             there are any, 0 otherwise.
540              
541             =cut
542              
543             sub cannot_run {
544 0     0 1 0 my @problems;
545              
546 0 0       0 unless (-x $MTX) { push @problems, "Can't run $MTX" }
  0         0  
547 0 0       0 unless (-x $MT) { push @problems, "Can't run $MT" }
  0         0  
548 0 0       0 unless (-r $DRIVE) { push @problems, "Can't read from $DRIVE" }
  0         0  
549 0 0       0 unless (-w $DRIVE) { push @problems, "Can't write to $DRIVE" }
  0         0  
550 0 0       0 unless (-r $CONTROL) { push @problems, "Can't read from $CONTROL" }
  0         0  
551 0 0       0 unless (-w $CONTROL) { push @problems, "Can't write to $CONTROL" }
  0         0  
552            
553 0 0       0 return scalar @problems ? @problems : ();
554             }
555              
556             =back
557              
558             =cut
559              
560             ###############################################################################
561             ### Internal Subroutines ######################################################
562             ###############################################################################
563              
564             sub doit {
565 1   50 1 0 4 my $file = shift || return undef;
566 1 50       18 if (-f $file) {
567 0         0 my $return = do $file;
568 0 0       0 unless ($return) {
569 0 0       0 warn "couldn't parse $file: $@" if $@;
570 0 0       0 warn "couldn't do $file: $!" unless defined $return;
571 0 0       0 warn "couldn't run $file" unless $return;
572             }
573 0         0 $return;
574 1         3 } else { return undef }
575             }
576              
577 0 0   0 0   sub debug { $DEBUG > 0 ? 1 : 0 }
578 0 0   0 0   sub quiet { $DEBUG < 0 ? 1 : 0 }
579              
580             ###############################################################################
581             ### main() ####################################################################
582             ###############################################################################
583              
584             doit($MTXRC); # Override the defaults with what's in $MTXRC
585             1;
586              
587             =head1 NOTES
588              
589             ~/.mtxrc is automatically loaded when this module is used, if it exists,
590             using do(). This could cause security problems if you're trying to use
591             this with setuid() programs - so just don't do that. If you want someone
592             to have permission to mess with the tape drive and/or changer, let them
593             have that permission directly.
594              
595             =head1 REQUIREMENTS
596              
597             Perl 5.6.0 or better, an installed 'mtx' binary, and a tape changer and
598             reader connected to the system.
599              
600             =head1 TODO
601              
602             Theoretically allows multiple drives per changer and I/E slots, but I
603             haven't tested it, so I may have missed something. 'load previous'
604             doesn't actually work, because mtx doesn't support it (though the help
605             says it does).
606              
607             =head1 SEE ALSO
608              
609             B, B, B. Inspired by B, which comes
610             with the AMANDA tape backup package (http://www.amanda.org), and MTX,
611             available at http://mtx.sourceforge.net.
612              
613             =head1 AUTHOR
614              
615             Tim Skirvin .
616              
617             =head1 THANKS TO...
618              
619             Code for multi-slot tape drives and volume tags from Hubert Mikulicz
620             .
621              
622             =head1 LICENSE
623              
624             This code is distributed under the University of Illinois Open Source
625             License. See
626             C for
627             details.
628              
629             =head1 COPYRIGHT
630              
631             Copyright 2001-2004 by the University of Illinois Board of Trustees and
632             Tim Skirvin .
633              
634             =cut
635              
636             ##### Version History
637             # v0.5b Fri Nov 9 15:39:15 CST 2001
638             ### Initial version, based off old mtx-changer code (also self-written).
639             ### Documentation and such are written.
640             # v0.51b Tue Nov 13 09:16:49 CST 2001
641             ### Took out support for multiple drives in the 'eject' option, because it
642             ### operates weirdly. 'reportstatus' is a bit different.
643             # v0.60b Tue Nov 13 16:00:29 CST 2001
644             ### Fixed 'nexttape' and such to eject the drive first.
645             # v0.61b Fri Dec 14 15:22:25 CST 2001
646             ### Took out 'eject from drive #' from eject(), because it didn't work.
647             # v0.70b Fri Feb 1 13:13:08 CST 2002
648             ### Fixed _doloadtape() to eject the tape first.
649             # v0.71b Fri Feb 1 13:38:13 CST 2002
650             ### Changed _doloadtape() again to check the return status
651             # v1.00 Fri Jan 16 11:07:23 CST 2004
652             ### Might as well make this v1.0 some time. Added a fair bit of contributed
653             ### code to support multi-slot tape drives and volume tags.
654             # v1.01 Mon Mar 01 16:57:54 CST 2004
655             ### Doesn't echo STDERR in _run() anymore, which makes things look
656             ### cleaner, unless we're debugging.