File indexing completed on 2024-05-05 04:51:14

0001 /*
0002     SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 #include "k3bgrowisofswriter.h"
0006 
0007 #include "k3bcore.h"
0008 #include "k3bdevice.h"
0009 #include "k3bdevicehandler.h"
0010 #include "k3bprocess.h"
0011 #include "k3bexternalbinmanager.h"
0012 #include "k3bversion.h"
0013 #include "k3bdiskinfo.h"
0014 #include "k3bglobals.h"
0015 #include "k3bthroughputestimator.h"
0016 #include "k3bgrowisofshandler.h"
0017 #include "k3bglobalsettings.h"
0018 #include "k3bdeviceglobals.h"
0019 #include "k3b_i18n.h"
0020 
0021 #include <QDebug>
0022 #include <QFile>
0023 #include <QFileInfo>
0024 
0025 #include <unistd.h>
0026 
0027 
0028 class K3b::GrowisofsWriter::Private
0029 {
0030 public:
0031     Private()
0032         : writingMode( K3b::WritingModeAuto ),
0033           closeDvd(false),
0034           multiSession(false),
0035           growisofsBin( 0 ),
0036           trackSize(-1),
0037           layerBreak(0) {
0038     }
0039 
0040     K3b::WritingMode writingMode;
0041     bool closeDvd;
0042     bool multiSession;
0043     K3b::Process process;
0044     const K3b::ExternalBin* growisofsBin;
0045     QString image;
0046 
0047     bool success;
0048     bool canceled;
0049     bool finished;
0050 
0051     QTime lastSpeedCalculationTime;
0052     int lastSpeedCalculationBytes;
0053     int lastProgress;
0054     unsigned int lastProgressed;
0055     double lastWritingSpeed;
0056 
0057     bool writingStarted;
0058 
0059     K3b::ThroughputEstimator* speedEst;
0060     K3b::GrowisofsHandler* gh;
0061 
0062     // used in DAO with growisofs >= 5.15
0063     long trackSize;
0064 
0065     long layerBreak;
0066 
0067     unsigned long long overallSizeFromOutput;
0068     long long firstSizeFromOutput;
0069 
0070     QFile inputFile;
0071 
0072     QString multiSessionInfo;
0073 
0074     Device::MediaType burnedMediumType;
0075 
0076     K3b::Device::SpeedMultiplicator speedMultiplicator() const {
0077         return K3b::speedMultiplicatorForMediaType( burnedMediumType );
0078     }
0079 };
0080 
0081 
0082 K3b::GrowisofsWriter::GrowisofsWriter( K3b::Device::Device* dev, K3b::JobHandler* hdl,
0083                                         QObject* parent )
0084     : K3b::AbstractWriter( dev, hdl, parent )
0085 {
0086     d = new Private;
0087     d->speedEst = new K3b::ThroughputEstimator( this );
0088     connect( d->speedEst, SIGNAL(throughput(int)),
0089              this, SLOT(slotThroughput(int)) );
0090 
0091     d->gh = new K3b::GrowisofsHandler( this );
0092     connect( d->gh, SIGNAL(infoMessage(QString,int)),
0093              this,SIGNAL(infoMessage(QString,int)) );
0094     connect( d->gh, SIGNAL(newSubTask(QString)),
0095              this, SIGNAL(newSubTask(QString)) );
0096     connect( d->gh, SIGNAL(buffer(int)),
0097              this, SIGNAL(buffer(int)) );
0098     connect( d->gh, SIGNAL(deviceBuffer(int)),
0099              this, SIGNAL(deviceBuffer(int)) );
0100     connect( d->gh, SIGNAL(flushingCache()),
0101              this, SLOT(slotFlushingCache()) );
0102 
0103     d->process.setSplitStdout(true);
0104     d->process.setSuppressEmptyLines(true);
0105     d->process.setFlags( K3bQProcess::RawStdin );
0106     connect( &d->process, SIGNAL(stdoutLine(QString)), this, SLOT(slotReceivedStderr(QString)) );
0107     connect( &d->process, SIGNAL(stderrLine(QString)), this, SLOT(slotReceivedStderr(QString)) );
0108     connect( &d->process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessExited(int,QProcess::ExitStatus)) );
0109 }
0110 
0111 
0112 K3b::GrowisofsWriter::~GrowisofsWriter()
0113 {
0114     delete d;
0115 }
0116 
0117 
0118 bool K3b::GrowisofsWriter::active() const
0119 {
0120     return d->process.isRunning();
0121 }
0122 
0123 
0124 QIODevice* K3b::GrowisofsWriter::ioDevice() const
0125 {
0126     return &d->process;
0127 }
0128 
0129 
0130 bool K3b::GrowisofsWriter::prepareProcess()
0131 {
0132     d->growisofsBin = k3bcore->externalBinManager()->binObject( "growisofs" );
0133     if( !d->growisofsBin ) {
0134         emit infoMessage( i18n("Could not find %1 executable.",QString("growisofs")), MessageError );
0135         return false;
0136     }
0137 
0138     if( d->growisofsBin->version() < K3b::Version( 5, 10 ) ) {
0139         emit infoMessage( i18n("Growisofs version %1 is too old. "
0140                                "K3b needs at least version 5.10.", d->growisofsBin->version() ),
0141                           MessageError );
0142         return false;
0143     }
0144 
0145     emit debuggingOutput( QLatin1String( "Used versions" ), QString::fromLatin1( "growisofs: %1" ).arg(d->growisofsBin->version()) );
0146 
0147     if( !d->growisofsBin->copyright().isEmpty() )
0148         emit infoMessage( i18n("Using %1 %2 – Copyright © %3",QString("growisofs")
0149                                ,d->growisofsBin->version(),d->growisofsBin->copyright()), MessageInfo );
0150 
0151 
0152     if ( !d->growisofsBin->hasFeature( "buffer" ) ) {
0153         emit infoMessage( i18n( "Growisofs version %1 does not feature a software buffer which may result in an unstable burn process.", d->growisofsBin->version() ),
0154                           MessageWarning );
0155     }
0156 
0157     //
0158     // The growisofs bin is ready. Now we add the parameters
0159     //
0160     d->process.clearProgram();
0161 
0162     //
0163     // growisofs < 5.20 wants the tracksize to be a multiple of 16 (1 ECC block: 16*2048 bytes)
0164     // we simply pad ourselves.
0165     //
0166     // But since the writer itself properly pads or writes a longer lead-out we don't really need
0167     // to write zeros. We just tell growisofs to reserve a multiple of 16 blocks.
0168     // This is only releveant in DAO mode anyway.
0169     //
0170     // FIXME: seems as we also need this for double layer writing. Better make it the default and
0171     //        actually write the pad bytes. The only possibility I see right now is to add a padding option
0172     //        to the pipebuffer.
0173     int trackSizePadding = 0;
0174     if( d->trackSize > 0 && d->growisofsBin->version() < K3b::Version( 5, 20 ) ) {
0175         if( d->trackSize % 16 ) {
0176             trackSizePadding = (16 - d->trackSize%16);
0177             qDebug() << "(K3b::GrowisofsWriter) need to pad " << trackSizePadding << " blocks.";
0178         }
0179     }
0180 
0181 
0182     d->process << d->growisofsBin;
0183 
0184     QString s = burnDevice()->blockDeviceName() + '=';
0185     if( d->image.isEmpty() ) {
0186         // we always read from stdin
0187         s += "/dev/fd/0";
0188     }
0189     else
0190         s += d->image;
0191 
0192     if (d->multiSession && !d->multiSessionInfo.isEmpty()) {
0193         QStringList ms = d->multiSessionInfo.split(',');
0194         if (ms.size() == 2 && !ms[1].isNull()) {
0195             d->process << "-C" << d->multiSessionInfo;
0196         } else {
0197             emit infoMessage(i18n("Medium is not of multi-session type and does not contain ISO 9660. Cannot emulate multi-session on it."), MessageError);
0198         }
0199     }
0200 
0201     if( d->multiSession )
0202         d->process << "-M";
0203     else
0204         d->process << "-Z";
0205     d->process << s;
0206 
0207 
0208     if( !d->image.isEmpty() ) {
0209         d->inputFile.setFileName( d->image );
0210         d->trackSize = (QFileInfo(d->image).size() + 1024) / 2048;
0211         if( !d->inputFile.open( QIODevice::ReadOnly ) ) {
0212             emit infoMessage( i18n("Could not open file %1.",d->image), MessageError );
0213             return false;
0214         }
0215     }
0216 
0217     // now we use the force (luke ;) do not reload the dvd, K3b does that.
0218     d->process << "-use-the-force-luke=notray";
0219 
0220     // we check for existing filesystems ourselves, so we always force the overwrite...
0221     d->process << "-use-the-force-luke=tty";
0222 
0223     // we do the 4GB boundary check ourselves
0224     d->process << "-use-the-force-luke=4gms";
0225 
0226     bool dvdCompat = d->closeDvd;
0227 
0228     // DL writing with forced layer break
0229     if( d->layerBreak > 0 ) {
0230         d->process << "-use-the-force-luke=break:" + QString::number(d->layerBreak);
0231         dvdCompat = true;
0232     }
0233 
0234     // the tracksize parameter takes priority over the dao:tracksize parameter since growisofs 5.18
0235     else if( d->growisofsBin->hasFeature( "tracksize" ) && d->trackSize > 0 )
0236         d->process << "-use-the-force-luke=tracksize:" + QString::number(d->trackSize + trackSizePadding);
0237 
0238     if( simulate() )
0239         d->process << "-use-the-force-luke=dummy";
0240 
0241     if( d->writingMode == K3b::WritingModeSao ) {
0242         dvdCompat = true;
0243         if( d->growisofsBin->hasFeature( "daosize" ) && d->trackSize > 0 )
0244             d->process << "-use-the-force-luke=dao:" + QString::number(d->trackSize + trackSizePadding);
0245         else
0246             d->process << "-use-the-force-luke=dao";
0247         d->gh->reset( burnDevice(), true );
0248     }
0249     else
0250         d->gh->reset( burnDevice(), false );
0251 
0252     d->burnedMediumType = burnDevice()->mediaType();
0253     d->gh->setMediaType(d->burnedMediumType);
0254 
0255     //
0256     // Never use the -dvd-compat parameter with DVD+RW media
0257     // because the only thing it does is creating problems.
0258     // Normally this should be done in growisofs
0259     //
0260     int mediaType = burnDevice()->mediaType();
0261     if( dvdCompat &&
0262         mediaType != K3b::Device::MEDIA_DVD_PLUS_RW &&
0263         mediaType != K3b::Device::MEDIA_DVD_RW_OVWR )
0264         d->process << "-dvd-compat";
0265 
0266     //
0267     // Some DVD writers do not allow changing the writing speed so we allow
0268     // the user to ignore the speed setting
0269     //
0270     int speed = burnSpeed();
0271     if( speed >= 0 ) {
0272         if( speed == 0 ) {
0273             // try to determine the writeSpeed
0274             // if it fails determineOptimalWriteSpeed() will return 0 and
0275             // the choice is left to growisofs which means that the choice is
0276             // really left to the drive since growisofs does not change the speed
0277             // if no option is given
0278             speed = burnDevice()->determineMaximalWriteSpeed();
0279         }
0280 
0281         if( speed != 0 ) {
0282             d->process << QString("-speed=%1").arg( K3b::formatWritingSpeedFactor( speed, d->burnedMediumType ) );
0283         }
0284     }
0285 
0286     if( k3bcore->globalSettings()->overburn() )
0287         d->process << "-overburn";
0288 
0289     if( d->growisofsBin->hasFeature( "buffer" ) ) {
0290         bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize();
0291         int bufSize = ( manualBufferSize ? k3bcore->globalSettings()->bufferSize() : 32 );
0292         d->process << QString("-use-the-force-luke=bufsize:%1m").arg(bufSize);
0293     }
0294 
0295     // additional user parameters from config
0296     const QStringList& params = d->growisofsBin->userParameters();
0297     for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it )
0298         d->process << *it;
0299 
0300     emit debuggingOutput( "Burned media", K3b::Device::mediaTypeString(mediaType) );
0301 
0302     return true;
0303 }
0304 
0305 
0306 void K3b::GrowisofsWriter::start()
0307 {
0308     jobStarted();
0309 
0310     d->lastWritingSpeed = 0;
0311     d->lastProgressed = 0;
0312     d->lastProgress = 0;
0313     d->firstSizeFromOutput = -1;
0314     d->lastSpeedCalculationTime = QTime::currentTime();
0315     d->lastSpeedCalculationBytes = 0;
0316     d->writingStarted = false;
0317     d->canceled = false;
0318     d->speedEst->reset();
0319     d->finished = false;
0320 
0321     if( !prepareProcess() ) {
0322         jobFinished( false );
0323     }
0324     else {
0325 
0326         qDebug() << "***** " << d->growisofsBin->name() << " parameters:\n";
0327         QString s = d->process.joinedArgs();
0328         qDebug() << s << Qt::flush;
0329         emit debuggingOutput( d->growisofsBin->name() + " command:", s);
0330 
0331 
0332         emit newSubTask( i18n("Preparing write process...") );
0333 
0334         // FIXME: check the return value
0335         if( K3b::isMounted( burnDevice() ) ) {
0336             emit infoMessage( i18n("Unmounting medium"), MessageInfo );
0337             K3b::unmount( burnDevice() );
0338         }
0339 
0340         // block the device (including certain checks)
0341         k3bcore->blockDevice( burnDevice() );
0342 
0343         // lock the device for good in this process since it will
0344         // be opened in the growisofs process
0345         burnDevice()->close();
0346         burnDevice()->usageLock();
0347 
0348         if( !d->process.start( KProcess::SeparateChannels ) ) {
0349             // something went wrong when starting the program
0350             // it "should" be the executable
0351             qDebug() << "(K3b::GrowisofsWriter) could not start " << d->growisofsBin->path();
0352             emit infoMessage( i18n("Could not start %1.",d->growisofsBin->name()), K3b::Job::MessageError );
0353             jobFinished(false);
0354         }
0355         else {
0356             if( simulate() ) {
0357                 emit newTask( i18n("Simulating") );
0358                 emit infoMessage( i18n("Starting simulation..."),
0359                                   K3b::Job::MessageInfo );
0360             }
0361             else {
0362                 emit newTask( i18n("Writing") );
0363                 emit infoMessage( i18n("Starting disc write..."), K3b::Job::MessageInfo );
0364             }
0365 
0366             d->gh->handleStart();
0367         }
0368     }
0369 }
0370 
0371 
0372 void K3b::GrowisofsWriter::cancel()
0373 {
0374     if( active() ) {
0375         d->canceled = true;
0376         if ( d->process.isRunning() ) {
0377             d->process.closeWriteChannel();
0378         }
0379         d->process.terminate();
0380     }
0381 }
0382 
0383 
0384 void K3b::GrowisofsWriter::setWritingMode( K3b::WritingMode m )
0385 {
0386     d->writingMode = m;
0387 }
0388 
0389 
0390 void K3b::GrowisofsWriter::setTrackSize( long size )
0391 {
0392     d->trackSize = size;
0393 }
0394 
0395 
0396 void K3b::GrowisofsWriter::setLayerBreak( long lb )
0397 {
0398     d->layerBreak = lb;
0399 }
0400 
0401 
0402 void K3b::GrowisofsWriter::setCloseDvd( bool b )
0403 {
0404     d->closeDvd = b;
0405 }
0406 
0407 
0408 void K3b::GrowisofsWriter::setMultiSession( bool b )
0409 {
0410     d->multiSession = b;
0411 }
0412 
0413 
0414 void K3b::GrowisofsWriter::setImageToWrite( const QString& filename )
0415 {
0416     d->image = filename;
0417 }
0418 
0419 
0420 void K3b::GrowisofsWriter::slotReceivedStderr( const QString& line )
0421 {
0422     emit debuggingOutput( d->growisofsBin->name(), line );
0423 
0424     if( line.contains( "remaining" ) ) {
0425 
0426         if( !d->writingStarted ) {
0427             d->writingStarted = true;
0428             emit newSubTask( i18n("Writing data") );
0429         }
0430 
0431         // parse progress
0432         int pos = line.indexOf( '/' );
0433         unsigned long long done = line.left( pos ).toULongLong();
0434         bool ok = true;
0435         d->overallSizeFromOutput = line.mid( pos+1, line.indexOf( '(', pos ) - pos - 1 ).toULongLong( &ok );
0436         if( d->firstSizeFromOutput == -1 )
0437             d->firstSizeFromOutput = done;
0438         done -= d->firstSizeFromOutput;
0439         d->overallSizeFromOutput -= d->firstSizeFromOutput;
0440         if( ok ) {
0441             int p = (int)(100 * done / d->overallSizeFromOutput);
0442             if( p > d->lastProgress ) {
0443                 emit percent( p );
0444                 emit subPercent( p );
0445                 d->lastProgress = p;
0446             }
0447             if( (unsigned int)(done/1024/1024) > d->lastProgressed ) {
0448                 d->lastProgressed = (unsigned int)(done/1024/1024);
0449                 emit processedSize( d->lastProgressed, (int)(d->overallSizeFromOutput/1024/1024)  );
0450                 emit processedSubSize( d->lastProgressed, (int)(d->overallSizeFromOutput/1024/1024)  );
0451             }
0452 
0453             // try parsing write speed (since growisofs 5.11)
0454             pos = line.indexOf( '@' );
0455             if( pos != -1 ) {
0456                 pos += 1;
0457                 double speed = line.mid( pos, line.indexOf( 'x', pos ) - pos ).toDouble(&ok);
0458                 if (ok) {
0459                     if (d->lastWritingSpeed != speed) {
0460                         emit writeSpeed((int)(speed * d->speedMultiplicator()), d->speedMultiplicator());
0461                     }
0462                     d->lastWritingSpeed = speed;
0463                 }
0464                 else
0465                     qDebug() << "(K3b::GrowisofsWriter) speed parsing failed: '"
0466                              << line.mid( pos, line.indexOf( 'x', pos ) - pos ) << "'" << Qt::endl;
0467             }
0468             else {
0469                 d->speedEst->dataWritten( done/1024 );
0470             }
0471         }
0472         else
0473             qDebug() << "(K3b::GrowisofsWriter) progress parsing failed: '"
0474                      << line.mid( pos+1, line.indexOf( '(', pos ) - pos - 1 ).trimmed() << "'" << Qt::endl;
0475     }
0476 
0477     //  else
0478     // to be able to parse the ring buffer fill in growisofs 6.0 we need to do this all the time
0479     // FIXME: get rid of the K3b::GrowisofsHandler once it is sure that we do not need the K3b::GrowisofsImager anymore
0480     d->gh->handleLine( line );
0481 }
0482 
0483 
0484 void K3b::GrowisofsWriter::slotProcessExited( int exitCode, QProcess::ExitStatus )
0485 {
0486     d->inputFile.close();
0487 
0488     // release the device within this process
0489     burnDevice()->usageUnlock();
0490 
0491     // unblock the device
0492     k3bcore->unblockDevice( burnDevice() );
0493 
0494     if( d->canceled ) {
0495         if( !d->finished ) {
0496             d->finished = true;
0497             // this will unblock and eject the drive and emit the finished/canceled signals
0498             K3b::AbstractWriter::cancel();
0499         }
0500         return;
0501     }
0502 
0503     d->finished = true;
0504 
0505     // it seems that growisofs sometimes exits with a valid exit code while a write error occurred
0506     if( (exitCode == 0) && d->gh->error() != K3b::GrowisofsHandler::ERROR_WRITE_FAILED ) {
0507 
0508         int s = d->speedEst->average();
0509         if( s > 0 )
0510             emit infoMessage( ki18n("Average overall write speed: %1 KB/s (%2x)")
0511                               .subs( s )
0512                               .subs( ( double )s/( double )d->speedMultiplicator(), 0, 'g', 2 ).toString(), MessageInfo );
0513 
0514         if( simulate() )
0515             emit infoMessage( i18n("Simulation successfully completed"), K3b::Job::MessageSuccess );
0516         else
0517             emit infoMessage( i18n("Writing successfully completed"), K3b::Job::MessageSuccess );
0518 
0519         d->success = true;
0520     }
0521     else {
0522         if( !wasSourceUnreadable() )
0523             d->gh->handleExit( exitCode );
0524         d->success = false;
0525     }
0526 
0527     jobFinished(d->success);
0528 }
0529 
0530 
0531 void K3b::GrowisofsWriter::slotThroughput( int t )
0532 {
0533     emit writeSpeed( t, d->speedMultiplicator() );
0534 }
0535 
0536 
0537 void K3b::GrowisofsWriter::slotFlushingCache()
0538 {
0539     if( !d->canceled ) {
0540         //
0541         // growisofs's progress output stops before 100%, so we do it manually
0542         //
0543         emit percent( 100 );
0544         emit processedSize( d->overallSizeFromOutput/1024/1024,
0545                             d->overallSizeFromOutput/1024/1024 );
0546     }
0547 }
0548 
0549 
0550 void K3b::GrowisofsWriter::setMultiSessionInfo( const QString& info )
0551 {
0552     d->multiSessionInfo = info;
0553 }
0554 
0555 
0556 qint64 K3b::GrowisofsWriter::write( const char* data, qint64 maxSize )
0557 {
0558     return d->process.write( data, maxSize );
0559 }
0560 
0561 #include "moc_k3bgrowisofswriter.cpp"