| 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. |