File indexing completed on 2024-05-05 04:50:58

0001 /*
0002     SPDX-FileCopyrightText: 2003-2009 Sebastian Trueg <trueg@k3b.org>
0003     SPDX-FileCopyrightText: 2009 Michal Malek <michalm@jabster.pl>
0004     SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "k3bverificationjob.h"
0010 
0011 #include "k3bdevice.h"
0012 #include "k3bdevicehandler.h"
0013 #include "k3bglobals.h"
0014 #include "k3bdatatrackreader.h"
0015 #include "k3bchecksumpipe.h"
0016 #include "k3biso9660.h"
0017 #include "k3b_i18n.h"
0018 
0019 #include <QDebug>
0020 #include <QList>
0021 
0022 
0023 namespace {
0024     class TrackEntry
0025     {
0026     public:
0027         TrackEntry()
0028             : trackNumber(0) {
0029         }
0030 
0031         TrackEntry( int tn, const QByteArray& cs, const K3b::Msf& msf )
0032             : trackNumber(tn),
0033               checksum(cs),
0034               length(msf) {
0035         }
0036 
0037         int trackNumber;
0038         QByteArray checksum;
0039         mutable K3b::Msf length; // it's a cache, let's make it modifiable
0040     };
0041 
0042     typedef QList<TrackEntry> TrackEntries;
0043 
0044     class NullSinkChecksumPipe : public K3b::ChecksumPipe
0045     {
0046     protected:
0047         qint64 writeData( const char* data, qint64 max ) override {
0048             ChecksumPipe::writeData( data, max );
0049             return max;
0050         }
0051     };
0052 }
0053 
0054 
0055 class K3b::VerificationJob::Private
0056 {
0057 public:
0058     Private( VerificationJob* job )
0059         : device(0),
0060           dataTrackReader(0),
0061           q(job){
0062     }
0063 
0064     void reloadMedium();
0065     Msf trackLength( const TrackEntry& trackEntry );
0066 
0067     bool canceled;
0068     K3b::Device::Device* device;
0069 
0070     K3b::Msf grownSessionSize;
0071 
0072     TrackEntries trackEntries;
0073     TrackEntries::const_iterator currentTrackEntry;
0074 
0075     K3b::Device::DiskInfo diskInfo;
0076     K3b::Device::Toc toc;
0077 
0078     K3b::DataTrackReader* dataTrackReader;
0079 
0080     K3b::Msf currentTrackSize;
0081     K3b::Msf totalSectors;
0082     K3b::Msf alreadyReadSectors;
0083 
0084     NullSinkChecksumPipe pipe;
0085 
0086     bool readSuccessful;
0087 
0088     bool mediumHasBeenReloaded;
0089 
0090     VerificationJob* q;
0091 };
0092 
0093 
0094 void K3b::VerificationJob::Private::reloadMedium()
0095 {
0096 #ifdef _GNUC_
0097 #warning FIXME: loks like the reload does not work
0098 #endif
0099     // many drives need to reload the medium to return to a proper state
0100     mediumHasBeenReloaded = true;
0101     emit q->infoMessage( i18n( "Need to reload medium to return to proper state." ), MessageInfo );
0102     QObject::connect( K3b::Device::sendCommand( Device::DeviceHandler::CommandReload|Device::DeviceHandler::CommandMediaInfo, device ),
0103                       SIGNAL(finished(K3b::Device::DeviceHandler*)),
0104                       q,
0105                       SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) );
0106 }
0107 
0108 
0109 K3b::Msf K3b::VerificationJob::Private::trackLength( const TrackEntry& trackEntry )
0110 {
0111     K3b::Msf& trackSize = trackEntry.length;
0112     const int& trackNum = trackEntry.trackNumber;
0113 
0114     K3b::Device::Track& track = toc[trackNum-1];
0115 
0116     if( trackSize == 0 ) {
0117         trackSize = track.length();
0118 
0119         if( diskInfo.mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) ) {
0120             K3b::Iso9660 isoF( device, track.firstSector().lba() );
0121             if( isoF.open() ) {
0122                 trackSize = isoF.primaryDescriptor().volumeSpaceSize;
0123             }
0124             else {
0125                 emit q->infoMessage( i18n("Unable to determine the ISO 9660 filesystem size."), MessageError );
0126                 return 0;
0127             }
0128         }
0129 
0130         //
0131         // A data track recorded in TAO mode has two run-out blocks which cannot be read and contain
0132         // zero data anyway. The problem is that I do not know of a valid method to determine if a track
0133         // was written in TAO (the control nibble does definitely not work, I never saw one which did not
0134         // equal 4).
0135         // So the solution for now is to simply try to read the last sector of a data track. If this is not
0136         // possible we assume it was written in TAO mode and reduce the length by 2 sectors
0137         //
0138         if( track.type() == K3b::Device::Track::TYPE_DATA &&
0139             diskInfo.mediaType() & K3b::Device::MEDIA_CD_ALL ) {
0140             // we try twice just to be sure
0141             unsigned char buffer[2048];
0142             if( !device->read10( buffer, 2048, track.lastSector().lba(), 1 ) &&
0143                 !device->read10( buffer, 2048, track.lastSector().lba(), 1 ) ) {
0144                 trackSize -= 2;
0145                 qDebug() << "(K3b::CdCopyJob) track " << trackNum << " probably TAO recorded.";
0146             }
0147         }
0148     }
0149 
0150     return trackSize;
0151 }
0152 
0153 
0154 K3b::VerificationJob::VerificationJob( K3b::JobHandler* hdl, QObject* parent )
0155     : K3b::Job( hdl, parent )
0156 {
0157     d = new Private( this );
0158     d->currentTrackEntry = d->trackEntries.constEnd();
0159 }
0160 
0161 
0162 K3b::VerificationJob::~VerificationJob()
0163 {
0164     delete d;
0165 }
0166 
0167 
0168 void K3b::VerificationJob::cancel()
0169 {
0170     d->canceled = true;
0171     if( d->dataTrackReader && d->dataTrackReader->active() ) {
0172         d->dataTrackReader->cancel();
0173     }
0174     else if( active() ) {
0175         emit canceled();
0176         jobFinished( false );
0177     }
0178 }
0179 
0180 
0181 void K3b::VerificationJob::addTrack( int trackNum, const QByteArray& checksum, const K3b::Msf& length )
0182 {
0183     d->trackEntries.append( TrackEntry( trackNum, checksum, length ) );
0184 }
0185 
0186 
0187 void K3b::VerificationJob::clear()
0188 {
0189     d->trackEntries.clear();
0190     d->grownSessionSize = 0;
0191 }
0192 
0193 
0194 void K3b::VerificationJob::setDevice( K3b::Device::Device* dev )
0195 {
0196     d->device = dev;
0197 }
0198 
0199 
0200 void K3b::VerificationJob::setGrownSessionSize( const K3b::Msf& s )
0201 {
0202     d->grownSessionSize = s;
0203 }
0204 
0205 
0206 void K3b::VerificationJob::start()
0207 {
0208     jobStarted();
0209 
0210     d->canceled = false;
0211     d->alreadyReadSectors = 0;
0212 
0213     waitForMedium( d->device,
0214                    K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE,
0215                    K3b::Device::MEDIA_WRITABLE );
0216 
0217     // make sure the job is initialized
0218     if( !d->trackEntries.isEmpty() ) {
0219         d->currentTrackEntry = d->trackEntries.constBegin();
0220     }
0221     else {
0222         emit infoMessage( i18n( "Internal Error: Verification job improperly initialized (%1)",
0223                                 i18n("no tracks added") ), MessageError );
0224         jobFinished( false );
0225         return;
0226     }
0227 
0228     emit newTask( i18n("Checking medium") );
0229 
0230     d->mediumHasBeenReloaded = false;
0231     connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::CommandLoad, d->device ),
0232              SIGNAL(finished(K3b::Device::DeviceHandler*)),
0233              this,
0234              SLOT(slotMediaLoaded()) );
0235 }
0236 
0237 
0238 void K3b::VerificationJob::slotMediaLoaded()
0239 {
0240     // we always need to wait for the medium. Otherwise the diskinfo below
0241     // may run before the drive is ready!
0242     waitForMedium( d->device,
0243                    K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE,
0244                    K3b::Device::MEDIA_WRITABLE );
0245 
0246     connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::CommandMediaInfo, d->device ),
0247              SIGNAL(finished(K3b::Device::DeviceHandler*)),
0248              this,
0249              SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) );
0250 }
0251 
0252 
0253 void K3b::VerificationJob::slotDiskInfoReady( K3b::Device::DeviceHandler* dh )
0254 {
0255     if( d->canceled ) {
0256         // signal already emitted in cancel()
0257         return;
0258     }
0259 
0260     if ( !dh->success() ) {
0261         blockingInformation( i18n("Please reload the medium and press 'OK'"),
0262                              i18n("Failed to reload the medium") );
0263     }
0264 
0265     d->diskInfo = dh->diskInfo();
0266     d->toc = dh->toc();
0267     d->totalSectors = 0;
0268 
0269     // just to be sure check if we actually have all the tracks
0270     for( TrackEntries::iterator it = d->trackEntries.begin(); it != d->trackEntries.end(); ++it ) {
0271 
0272         // 0 means "last track"
0273         if( it->trackNumber == 0 )
0274             it->trackNumber = d->toc.count();
0275 
0276         if( it->trackNumber <= 0 || it->trackNumber > d->toc.count() ) {
0277             if ( d->mediumHasBeenReloaded ) {
0278                 emit infoMessage( i18n("Internal Error: Verification job improperly initialized (%1)",
0279                                        i18n("specified track number '%1' not found on medium", it->trackNumber) ), MessageError );
0280                 jobFinished( false );
0281                 return;
0282             }
0283             else {
0284                 d->reloadMedium();
0285                 return;
0286             }
0287         }
0288 
0289         d->totalSectors += d->trackLength( *it );
0290     }
0291 
0292     Q_ASSERT( d->currentTrackEntry != d->trackEntries.constEnd() );
0293 
0294     if( d->currentTrackEntry->trackNumber >= d->toc.count() ) {
0295         readTrack();
0296     }
0297     else if( !d->mediumHasBeenReloaded ) {
0298         d->reloadMedium();
0299     }
0300     else {
0301         emit infoMessage( i18n("Internal Error: Verification job improperly initialized (%1)",
0302                                i18n("specified track number '%1' not found on medium", d->currentTrackEntry->trackNumber) ), MessageError );
0303         jobFinished( false );
0304     }
0305 }
0306 
0307 
0308 void K3b::VerificationJob::readTrack()
0309 {
0310     if( d->currentTrackEntry == d->trackEntries.constEnd() ) {
0311         jobFinished(true);
0312         return;
0313     }
0314 
0315     d->readSuccessful = true;
0316 
0317     d->currentTrackSize = d->trackLength( *d->currentTrackEntry );
0318     if( d->currentTrackSize == 0 ) {
0319         jobFinished(false);
0320         return;
0321     }
0322 
0323     emit newTask( i18n("Verifying track %1", d->currentTrackEntry->trackNumber ) );
0324 
0325     K3b::Device::Track& track = d->toc[ d->currentTrackEntry->trackNumber-1 ];
0326 
0327     d->pipe.open();
0328 
0329     if( track.type() == K3b::Device::Track::TYPE_DATA ) {
0330         if( !d->dataTrackReader ) {
0331             d->dataTrackReader = new K3b::DataTrackReader( this );
0332             connect( d->dataTrackReader, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) );
0333             connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotReaderFinished(bool)) );
0334             connect( d->dataTrackReader, SIGNAL(infoMessage(QString,int)), this, SIGNAL(infoMessage(QString,int)) );
0335             connect( d->dataTrackReader, SIGNAL(newTask(QString)), this, SIGNAL(newSubTask(QString)) );
0336             connect( d->dataTrackReader, SIGNAL(debuggingOutput(QString,QString)),
0337                      this, SIGNAL(debuggingOutput(QString,QString)) );
0338         }
0339 
0340         d->dataTrackReader->setDevice( d->device );
0341         d->dataTrackReader->setIgnoreErrors( false );
0342         d->dataTrackReader->setSectorSize( K3b::DataTrackReader::MODE1 );
0343         d->dataTrackReader->writeTo( &d->pipe );
0344 
0345         // in case a session was grown the track size does not say anything about the verification data size
0346         if( d->diskInfo.mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) &&
0347             d->grownSessionSize > 0 ) {
0348             K3b::Iso9660 isoF( d->device );
0349             if( isoF.open() ) {
0350                 int firstSector = isoF.primaryDescriptor().volumeSpaceSize - d->grownSessionSize.lba();
0351                 d->dataTrackReader->setSectorRange( firstSector,
0352                                                     isoF.primaryDescriptor().volumeSpaceSize -1 );
0353             }
0354             else {
0355                 emit infoMessage( i18n("Unable to determine the ISO 9660 filesystem size."), MessageError );
0356                 jobFinished( false );
0357                 return;
0358             }
0359         }
0360         else
0361             d->dataTrackReader->setSectorRange( track.firstSector(),
0362                                                 track.firstSector() + d->currentTrackSize -1 );
0363 
0364         d->pipe.open();
0365         d->dataTrackReader->start();
0366     }
0367     else {
0368         // FIXME: handle audio tracks
0369     }
0370 }
0371 
0372 
0373 void K3b::VerificationJob::slotReaderProgress( int p )
0374 {
0375     emit subPercent( p );
0376 
0377     emit percent( 100 * ( d->alreadyReadSectors.lba() + ( p*d->currentTrackSize.lba()/100 ) ) / d->totalSectors.lba() );
0378 }
0379 
0380 
0381 void K3b::VerificationJob::slotReaderFinished( bool success )
0382 {
0383     d->readSuccessful = success;
0384     if( d->readSuccessful && !d->canceled ) {
0385         d->alreadyReadSectors += d->trackLength( *d->currentTrackEntry );
0386 
0387         d->pipe.close();
0388 
0389         // compare the two sums
0390         if( d->currentTrackEntry->checksum != d->pipe.checksum() ) {
0391             emit infoMessage( i18n("Written data in track %1 differs from original.", d->currentTrackEntry->trackNumber), MessageError );
0392             jobFinished(false);
0393         }
0394         else {
0395             emit infoMessage( i18n("Written data verified."), MessageSuccess );
0396 
0397             ++d->currentTrackEntry;
0398             if( d->currentTrackEntry != d->trackEntries.constEnd() )
0399                 readTrack();
0400             else
0401                 jobFinished(true);
0402         }
0403     }
0404     else {
0405         jobFinished( false );
0406     }
0407 }
0408 
0409 #include "moc_k3bverificationjob.cpp"