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"