// KDat - a tar-based DAT archiver // Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net // Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include #include #include #include #include #include #include #include #include #include #include #include "KDatMainWindow.h" #include "Options.h" #include "Tape.h" #include "TapeManager.h" #include "TapeDrive.h" #include "kdat.h" #include "TapeDrive.moc" TapeDrive* TapeDrive::_instance = 0; TapeDrive* TapeDrive::instance() { if ( !_instance ) { _instance = new TapeDrive(); } return _instance; } TapeDrive::TapeDrive() : _fd ( -1 ), _readOnly( TRUE ), _tape( 0 ), _writeBuf( 0 ), _readBuf( 0 ), _writeBufIdx( 0 ), _readBufIdx( Options::instance()->getTapeBlockSize() ) { setBlockSize( Options::instance()->getTapeBlockSize() ); _writeBuf = new char[ Options::instance()->getTapeBlockSize() ]; _readBuf = new char[ Options::instance()->getTapeBlockSize() ]; } TapeDrive::~TapeDrive() { if ( Options::instance()->getLockOnMount() ) { unlock(); } delete [] _writeBuf; delete [] _readBuf; } void TapeDrive::flush() { _readBufIdx = Options::instance()->getTapeBlockSize(); if ( _writeBufIdx <= 0 ) { return; } memset( _writeBuf + _writeBufIdx, 0, Options::instance()->getTapeBlockSize() - _writeBufIdx ); int ret = ::write( _fd, _writeBuf, Options::instance()->getTapeBlockSize() ); if ( ret < 0 ) { printf( "TapeDrive::flush() -- write failed!\n" ); } _writeBufIdx = 0; } bool TapeDrive::load() { if ( _fd < 0 ) { return FALSE; } #ifdef MTLOAD struct mtop tapeOp; tapeOp.mt_op = MTLOAD; tapeOp.mt_count = 1; int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); if ( ret < 0 ) { printf( "TapeDrive::lock() -- ioctl( MTLOAD ) failed!\n" ); } return ret >= 0; #else return TRUE; #endif } bool TapeDrive::lock() { if ( _fd < 0 ) { return FALSE; } #ifdef MTLOCK struct mtop tapeOp; tapeOp.mt_op = MTLOCK; tapeOp.mt_count = 1; int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); if ( ret < 0 ) { printf( "TapeDrive::lock() -- ioctl( MTLOCK ) failed!\n" ); } return ret >= 0; #else return TRUE; #endif } bool TapeDrive::unlock() { if ( _fd < 0 ) { return FALSE; } #ifdef MTUNLOCK struct mtop tapeOp; tapeOp.mt_op = MTUNLOCK; tapeOp.mt_count = 1; int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); if ( ret < 0 ) { printf( "TapeDrive::unlock() -- ioctl( MTUNLOCK ) failed!\n" ); } return ret >= 0; #else return TRUE; #endif } bool TapeDrive::isReadOnly() { return _readOnly; } bool TapeDrive::isTapePresent() { open(); if ( _fd < 0 ) { return FALSE; } // Get tape status. struct mtget tapeStatus; int ret = ioctl( _fd, MTIOCGET, &tapeStatus ); if ( ret < 0 ) { return FALSE; } // Check for the presence of a tape. // if ( !GMT_DR_OPEN( tapeStatus.mt_gstat ) ) { if ( GMT_ONLINE( tapeStatus.mt_gstat ) ) { // Lock the tape drive door. //struct mtop tapeOp; //tapeOp.mt_op = MTLOCK; //tapeOp.mt_count = 1; //int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); //if ( ret < 0 ) { // printf( "TapeDrive::isTapePresent() -- ioctl( MTLOCK ) failed!\n" ); //} if ( _readOnly ) { emit sigStatus( i18n( "Tape mounted readonly." ) ); } else { emit sigStatus( i18n( "Tape mounted read/write." ) ); } return TRUE; } else { return FALSE; } } void TapeDrive::eject() { if ( _fd < 0 ) { return; } flush(); struct mtop tapeOp; tapeOp.mt_op = MTOFFL; tapeOp.mt_count = 1; int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); if ( ret < 0 ) { printf( "TapeDrive::ejectTape() -- ioctl( MTOFFL ) failed!\n" ); } } Tape* TapeDrive::readHeader() { _tape = NULL; // Rewind tape. emit sigStatus( "Rewinding tape..." ); if ( !rewind() ) { KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Rewinding tape failed." )); return NULL; } // KDat magic string. emit sigStatus( i18n( "Reading magic string..." ) ); char magic[9]; if ( read( magic, 9 ) < 9 ) { KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading magic string failed." )); return NULL; } if ( strncmp( "KDatMAGIC", magic, 9 ) ) { // Bad magic. return NULL; } // Read version number. emit sigStatus( i18n( "Reading version number..." ) ); int version; if ( read( (char*)&version, 4 ) < 4 ) { KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading version number failed." )); return NULL; } if ( version > KDAT_TAPE_HEADER_VERSION ) { KMessageBox::information( KDatMainWindow::getInstance(), i18n( "Tape was formatted by a more recent version of KDat. Consider upgrading." )); } // Read tape ID. emit sigStatus( i18n( "Reading tape ID..." ) ); int len; if ( read( (char*)&len, 4 ) < 4 ) { KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape ID length failed." )); return NULL; } char* tapeID = new char[len]; if ( read( tapeID, len ) < len ) { KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape ID failed." )); delete [] tapeID; return NULL; } _tape = TapeManager::instance()->findTape( tapeID ); delete [] tapeID; return _tape; } bool TapeDrive::rewind() { if ( _fd < 0 ) { return FALSE; } flush(); struct mtop tapeOp; tapeOp.mt_op = MTREW; tapeOp.mt_count = 1; int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); if ( ret < 0 ) { printf( "TapeDrive::rewind() -- ioctl( MTREW ) failed!\n" ); } return ret >= 0; } int TapeDrive::getFile() { if ( _fd < 0 ) { return -1; } struct mtget tapePos; if ( ioctl( _fd, MTIOCGET, &tapePos ) < 0 ) { printf( "TapeDrive::getFile() -- ioctl( MTIOCGET ) failed!\n" ); return -1; } return tapePos.mt_fileno; } int TapeDrive::getBlock() { if ( _fd < 0 ) { return -1; } struct mtget tapePos; if ( ioctl( _fd, MTIOCGET, &tapePos ) < 0 ) { printf( "TapeDrive::getBlock() -- ioctl( MTIOCGET ) failed!\n" ); return -1; } //%%% I don't think this makes sense anymore because the tape buffer size == the tape block size. // Need to subtract off the blocks that the application has not "read" yet. //int unread = ( Options::instance()->getTapeBlockSize() - _readBufIdx ) / Options::instance()->getTapeBlockSize(); //return tapePos.mt_blkno - unread; return tapePos.mt_blkno; } bool TapeDrive::nextFile( int count ) { if ( _fd < 0 ) { return FALSE; } flush(); struct mtop tapeOp; tapeOp.mt_op = MTFSF; tapeOp.mt_count = count; int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); if ( ret < 0 ) { printf( "TapeDrive::nextFile() -- ioctl( MTFSF ) failed!\n" ); } return ret >= 0; } bool TapeDrive::prevFile( int count ) { if ( _fd < 0 ) { return FALSE; } flush(); struct mtop tapeOp; tapeOp.mt_op = MTBSF; tapeOp.mt_count = count; int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); if ( ret < 0 ) { printf( "TapeDrive::prevFile() -- ioctl( MTBSF ) failed!\n" ); } return ret >= 0; } bool TapeDrive::nextRecord( int count ) { if ( _fd < 0 ) { return FALSE; } flush(); struct mtop tapeOp; tapeOp.mt_op = MTFSR; tapeOp.mt_count = count; bool status = ( ioctl( _fd, MTIOCTOP, &tapeOp ) >= 0 ); if ( !status ) { // Try reading count * TapeBlockSize bytes. char *buf = new char[Options::instance()->getTapeBlockSize()]; int bytes = count * Options::instance()->getTapeBlockSize(); int ret; while ( bytes > 0 ) { ret = read( buf, bytes > Options::instance()->getTapeBlockSize() ? Options::instance()->getTapeBlockSize() : bytes ); if ( ret <= 0 ) { status = FALSE; break; } bytes -= ret; } delete [] buf; status = TRUE; } return status; } bool TapeDrive::prevRecord( int count ) { if ( _fd < 0 ) { return FALSE; } flush(); struct mtop tapeOp; tapeOp.mt_op = MTBSR; tapeOp.mt_count = count; int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); if ( ret < 0 ) { printf( "TapeDrive::prevRecord() -- ioctl( MTBSR ) failed!\n" ); } return ret >= 0; } void TapeDrive::close() { if ( _fd < 0 ) { return; } flush(); ::close( _fd ); } void TapeDrive::open() { close(); // Open the tape device. _fd = ::open( TQFile::encodeName(Options::instance()->getTapeDevice()), O_RDWR ); if ( _fd < 0 ) { _fd = ::open( TQFile::encodeName(Options::instance()->getTapeDevice()), O_RDONLY ); if ( _fd < 0 ) { return; } else { _readOnly = TRUE; } } else { _readOnly = FALSE; } // Set the tape block size after the device is opened. setBlockSize( Options::instance()->getTapeBlockSize() ); } int TapeDrive::read( char* buf, int len ) { if ( _fd < 0 ) { return -1; } //printf( "TapeDrive::read() -- _readBufIdx = %d\n", _readBufIdx ); int remain = Options::instance()->getTapeBlockSize() - _readBufIdx; if ( len <= remain ) { memcpy( buf, _readBuf + _readBufIdx, len ); _readBufIdx += len; } else { memcpy( buf, _readBuf + _readBufIdx, remain ); _readBufIdx = Options::instance()->getTapeBlockSize(); int need = Options::instance()->getTapeBlockSize(); int count = 0; while ( need > 0 ) { int ret = ::read( _fd, _readBuf + count, Options::instance()->getTapeBlockSize() ); if ( ret <= 0 ) return ret; need -= ret; count += ret; } memcpy( buf + remain, _readBuf, len - remain ); _readBufIdx = len - remain; } //int ret = ::read( _fd, buf, len ); return len; } int TapeDrive::write( const char* buf, int len ) { if ( _fd < 0 ) { return -1; } int bufIdx = 0; while ( len + _writeBufIdx - bufIdx > Options::instance()->getTapeBlockSize() ) { memcpy( _writeBuf + _writeBufIdx, buf + bufIdx, Options::instance()->getTapeBlockSize() - _writeBufIdx ); int ret = ::write( _fd, _writeBuf, Options::instance()->getTapeBlockSize() ); if ( ret < 0 ) { printf( "TapeDrive::write() -- write failed!\n" ); return ret; } bufIdx += Options::instance()->getTapeBlockSize() - _writeBufIdx; _writeBufIdx = 0; } if ( bufIdx < len ) { memcpy( _writeBuf + _writeBufIdx, buf + bufIdx, len - bufIdx ); _writeBufIdx += len - bufIdx; } return len; } bool TapeDrive::seek( int file, int tarBlock ) { // printf( "TapeDrive::seek() -- file = %d, block = %d\n", file, tarBlock ); if ( _fd < 0 ) { printf( "bailing1\n" ); return FALSE; } flush(); // Go to the desired archive. emit sigStatus( i18n( "Skipping to archive..." ) ); int curFile = getFile(); // printf( "TapeDrive::seek() -- curFile = %d\n", curFile ); if ( curFile < 0 ) { emit sigStatus( i18n( "Rewinding tape..." ) ); rewind(); curFile = 0; } int fileDiff = file - curFile; if ( fileDiff > 0 ) { nextFile( fileDiff ); } else if ( fileDiff < 0 ) { prevFile( -fileDiff + 1 ); nextFile( 1 ); } int tapeBlock = tarBlock / ( Options::instance()->getTapeBlockSize() / 512 ); // printf( "TapeDrive::seek() -- desired tapeBlock = %d\n", tapeBlock ); // Go to the desired record within the archive. emit sigStatus( i18n( "Skipping to block..." ) ); int curBlock = getBlock(); // printf( "TapeDrive::seek() -- curBlock = %d\n", curBlock ); if ( curBlock < 0 ) { emit sigStatus( i18n( "Rewinding tape..." ) ); rewind(); nextFile( file ); if ( ( curBlock = getBlock() ) < 0 ) { printf( "bailing2\n" ); return FALSE; } } if ( tapeBlock > curBlock ) { if ( !nextRecord( tapeBlock - curBlock ) ) { printf( "bailing3\n" ); return FALSE; } } else if ( tapeBlock < curBlock ) { if ( tapeBlock == 0 ) { if ( !prevFile( 1 ) ) { printf( "bailing6\n" ); return FALSE; } if ( !nextFile( 1 ) ) { printf( "bailing7\n" ); return FALSE; } } else { if ( !prevRecord( curBlock - tapeBlock + 1 ) ) { printf( "bailing4\n" ); return FALSE; } if ( !nextRecord( 1 ) ) { printf( "bailing5\n" ); return FALSE; } } } // printf( "TapeDrive::seek() -- now: file = %d, block = %d\n", getFile(), getBlock() ); // If tapeBlockSize > 512, we may need to skip some tar blocks. // printf ( "TapeDrive::seek() -- skipping %d tar blocks.\n", tarBlock - tapeBlock * ( Options::instance()->getTapeBlockSize() / 512 ) ); char *buf = new char[ Options::instance()->getTapeBlockSize() ]; read( buf, 512 * ( tarBlock - tapeBlock * ( Options::instance()->getTapeBlockSize() / 512 ) ) ); delete [] buf; return TRUE; } bool TapeDrive::pastEOF() { if ( _fd < 0 ) { return FALSE; } struct mtget tapeStatus; if ( ioctl( _fd, MTIOCGET, &tapeStatus ) < 0 ) { printf( "TapeDrive::pastEOF() -- ioctl( MTIOCGET ) failed!\n" ); return FALSE; } return GMT_EOF( tapeStatus.mt_gstat ); } bool TapeDrive::setBlockSize( int blockSize ) { if ( _fd < 0 ) { return FALSE; } #ifdef MTSETBLK flush(); int ret = 0; if ( Options::instance()->getVariableBlockSize() ) { struct mtop tapeOp; tapeOp.mt_op = MTSETBLK; tapeOp.mt_count = blockSize; ret = ioctl( _fd, MTIOCTOP, &tapeOp ); if ( ret < 0 ) { printf( "TapeDrive::setBlockSize() -- ioctl( MTSETBLK ) failed!\n" ); } } _readBufIdx = blockSize; _writeBufIdx = 0; delete [] _readBuf; _readBuf = new char[ blockSize ]; delete [] _writeBuf; _writeBuf = new char[ blockSize ]; return ret >= 0; #else // some systems (e.g. HP-UX) encode block size into device file names // so setting the block size by software does not make sense return TRUE; #endif }