File indexing completed on 2024-05-12 04:51:05
0001 /* 0002 SPDX-FileCopyrightText: 1998-2008 Sebastian Trueg <trueg@k3b.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include <config-k3b.h> 0008 #include "k3bglobals.h" 0009 0010 #include "k3bisoimager.h" 0011 #include "k3bdiritem.h" 0012 #include "k3bbootitem.h" 0013 #include "k3bdatadoc.h" 0014 #include "k3bdatapreparationjob.h" 0015 #include "k3bexternalbinmanager.h" 0016 #include "k3bdevice.h" 0017 #include "k3bprocess.h" 0018 #include "k3bcore.h" 0019 #include "k3bversion.h" 0020 #include "k3bfilesplitter.h" 0021 #include "k3bisooptions.h" 0022 #include "k3b_i18n.h" 0023 0024 #include <KIO/CopyJob> 0025 #include <KIO/Global> 0026 #include <KIO/Job> 0027 #include <KStringHandler> 0028 0029 #include <QDebug> 0030 #include <QDir> 0031 #include <QFile> 0032 #include <QRegExp> 0033 #include <QStandardPaths> 0034 #include <QTemporaryFile> 0035 #include <QApplication> 0036 0037 #include <sys/types.h> 0038 #include <sys/stat.h> 0039 #include <unistd.h> 0040 #include <utime.h> 0041 0042 0043 int K3b::IsoImager::s_imagerSessionCounter = 0; 0044 0045 0046 class K3b::IsoImager::Private 0047 { 0048 public: 0049 const K3b::ExternalBin* mkisofsBin; 0050 0051 enum LinkHandling { 0052 KEEP_ALL, 0053 FOLLOW, 0054 DISCARD_ALL, 0055 DISCARD_BROKEN 0056 }; 0057 0058 int usedLinkHandling; 0059 0060 bool knownError; 0061 0062 K3b::DataPreparationJob* dataPreparationJob; 0063 }; 0064 0065 0066 K3b::IsoImager::IsoImager( K3b::DataDoc* doc, K3b::JobHandler* hdl, QObject* parent ) 0067 : K3b::Job( hdl, parent ), 0068 m_pathSpecFile(0), 0069 m_rrHideFile(0), 0070 m_jolietHideFile(0), 0071 m_sortWeightFile(0), 0072 m_process( 0 ), 0073 m_doc( doc ), 0074 m_noDeepDirectoryRelocation( false ), 0075 m_importSession( false ), 0076 m_device(0), 0077 m_mkisofsPrintSizeResult( 0 ) 0078 { 0079 d = new Private(); 0080 d->dataPreparationJob = new K3b::DataPreparationJob( doc, this, this ); 0081 connectSubJob( d->dataPreparationJob, 0082 SLOT(slotDataPreparationDone(bool)), 0083 DEFAULT_SIGNAL_CONNECTION ); 0084 } 0085 0086 0087 K3b::IsoImager::~IsoImager() 0088 { 0089 qDebug(); 0090 cleanup(); 0091 delete d; 0092 } 0093 0094 0095 bool K3b::IsoImager::active() const 0096 { 0097 return K3b::Job::active(); 0098 } 0099 0100 0101 void K3b::IsoImager::slotReceivedStderr( const QString& line ) 0102 { 0103 parseMkisofsOutput( line ); 0104 emit debuggingOutput( "mkisofs", line ); 0105 } 0106 0107 0108 void K3b::IsoImager::handleMkisofsProgress( int p ) 0109 { 0110 emit percent( p ); 0111 } 0112 0113 0114 void K3b::IsoImager::handleMkisofsInfoMessage( const QString& line, int type ) 0115 { 0116 emit infoMessage( line, type ); 0117 if( type == MessageError ) 0118 d->knownError = true; 0119 } 0120 0121 0122 void K3b::IsoImager::slotProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) 0123 { 0124 qDebug(); 0125 0126 cleanup(); 0127 0128 if( m_canceled ) { 0129 emit canceled(); 0130 jobFinished(false); 0131 } 0132 else { 0133 if( exitStatus == QProcess::NormalExit ) { 0134 if( exitCode == 0 ) { 0135 jobFinished( !mkisofsReadError() ); 0136 } 0137 else { 0138 switch( exitCode ) { 0139 case 104: 0140 // connection reset by peer 0141 // This only happens if cdrecord does not finish successfully 0142 // so we may leave the error handling to it meaning we handle this 0143 // as a known error 0144 break; 0145 0146 case 2: 0147 // mkisofs seems to have a bug that prevents to use filenames 0148 // that contain one or more backslashes 0149 // mkisofs 1.14 has the bug, 1.15a40 not 0150 // TODO: find out the version that fixed the bug 0151 if( m_containsFilesWithMultibleBackslashes && d->mkisofsBin && 0152 !d->mkisofsBin->hasFeature( "backslashed_filenames" ) ) { 0153 emit infoMessage( i18n("Due to a bug in mkisofs <= 1.15a40, K3b is unable to handle " 0154 "filenames that contain more than one backslash:"), MessageError ); 0155 0156 break; 0157 } 0158 Q_FALLTHROUGH(); 0159 0160 default: 0161 if( !d->knownError && !mkisofsReadError() ) { 0162 emit infoMessage( i18n("%1 returned an unknown error (code %2).", QLatin1String("mkisofs"), exitCode ), 0163 K3b::Job::MessageError ); 0164 emit infoMessage( i18n("Please send me an email with the last output."), K3b::Job::MessageError ); 0165 } 0166 } 0167 0168 jobFinished( false ); 0169 } 0170 } 0171 else { 0172 emit infoMessage( i18n("%1 crashed.", QLatin1String("mkisofs")), MessageError ); 0173 jobFinished( false ); 0174 } 0175 } 0176 } 0177 0178 0179 void K3b::IsoImager::cleanup() 0180 { 0181 qDebug(); 0182 0183 // remove all temp files 0184 delete m_pathSpecFile; 0185 delete m_rrHideFile; 0186 delete m_jolietHideFile; 0187 delete m_sortWeightFile; 0188 0189 // remove boot-images-temp files 0190 for( QStringList::iterator it = m_tempFiles.begin(); 0191 it != m_tempFiles.end(); ++it ) 0192 QFile::remove( *it ); 0193 m_tempFiles.clear(); 0194 0195 m_pathSpecFile = m_jolietHideFile = m_rrHideFile = m_sortWeightFile = 0; 0196 0197 clearDummyDirs(); 0198 } 0199 0200 0201 void K3b::IsoImager::init() 0202 { 0203 jobStarted(); 0204 0205 cleanup(); 0206 0207 d->dataPreparationJob->start(); 0208 } 0209 0210 0211 void K3b::IsoImager::slotDataPreparationDone( bool success ) 0212 { 0213 if( success ) { 0214 // 0215 // We always calculate the image size. It does not take long and at least the mixed job needs it 0216 // anyway 0217 // 0218 startSizeCalculation(); 0219 } 0220 else { 0221 if( d->dataPreparationJob->hasBeenCanceled() ) { 0222 m_canceled = true; 0223 emit canceled(); 0224 } 0225 jobFinished( false ); 0226 } 0227 } 0228 0229 0230 void K3b::IsoImager::calculateSize() 0231 { 0232 jobStarted(); 0233 startSizeCalculation(); 0234 } 0235 0236 0237 void K3b::IsoImager::startSizeCalculation() 0238 { 0239 d->mkisofsBin = initMkisofs(); 0240 if( !d->mkisofsBin ) { 0241 jobFinished( false ); 0242 return; 0243 } 0244 0245 initVariables(); 0246 0247 delete m_process; 0248 m_process = new K3b::Process( this ); 0249 m_process->setSplitStdout(true); 0250 0251 emit debuggingOutput( QLatin1String( "Used versions" ), QString::fromLatin1( "mkisofs: %1").arg(d->mkisofsBin->version()) ); 0252 0253 *m_process << d->mkisofsBin; 0254 0255 if( !prepareMkisofsFiles() || 0256 !addMkisofsParameters(true) ) { 0257 cleanup(); 0258 jobFinished( false ); 0259 return; 0260 } 0261 0262 // add empty dummy dir since one path-spec is needed 0263 // ??? Seems it is not needed after all. At least mkisofs 1.14 and above don't need it. ??? 0264 // *m_process << dummyDir(); 0265 0266 qDebug() << "***** mkisofs calculate size parameters:"; 0267 QString s = m_process->joinedArgs(); 0268 qDebug() << s << Qt::endl << Qt::flush; 0269 emit debuggingOutput("mkisofs calculate size command:", s); 0270 0271 // since output changed during mkisofs version changes we grab both 0272 // stdout and stderr 0273 0274 // mkisofs version >= 1.15 (don't know about 1.14!) 0275 // the extends on stdout (as lonely number) 0276 // and error and warning messages on stderr 0277 0278 // mkisofs >= 1.13 0279 // everything is written to stderr 0280 // last line is: "Total extents scheduled to be written = XXXXX" 0281 0282 connect( m_process, SIGNAL(stdoutLine(QString)), 0283 this, SLOT(slotCollectMkisofsPrintSizeStdout(QString)) ); 0284 connect( m_process, SIGNAL(stderrLine(QString)), 0285 this, SLOT(slotCollectMkisofsPrintSizeStderr(QString)) ); 0286 connect( m_process, SIGNAL(finished(int,QProcess::ExitStatus)), 0287 this, SLOT(slotMkisofsPrintSizeFinished()) ); 0288 0289 // we also want error messages 0290 connect( m_process, SIGNAL(stderrLine(QString)), 0291 this, SLOT(slotReceivedStderr(QString)) ); 0292 0293 m_collectedMkisofsPrintSizeStdout = QString(); 0294 m_collectedMkisofsPrintSizeStderr = QString(); 0295 m_mkisofsPrintSizeResult = 0; 0296 0297 if( !m_process->start( KProcess::SeparateChannels ) ) { 0298 emit infoMessage( i18n("Could not start %1.",QString("mkisofs")), K3b::Job::MessageError ); 0299 cleanup(); 0300 0301 jobFinished( false ); 0302 return; 0303 } 0304 } 0305 0306 0307 void K3b::IsoImager::slotCollectMkisofsPrintSizeStderr( const QString& line ) 0308 { 0309 m_collectedMkisofsPrintSizeStderr.append( line + '\n' ); 0310 } 0311 0312 0313 void K3b::IsoImager::slotCollectMkisofsPrintSizeStdout( const QString& line ) 0314 { 0315 // newer versions of mkisofs output additional lines of junk before the size :( 0316 // so we only use the last line 0317 emit debuggingOutput( "mkisofs", line ); 0318 m_collectedMkisofsPrintSizeStdout = line; 0319 } 0320 0321 0322 void K3b::IsoImager::slotMkisofsPrintSizeFinished() 0323 { 0324 if( m_canceled ) { 0325 emit canceled(); 0326 jobFinished( false ); 0327 return; 0328 } 0329 0330 bool success = true; 0331 0332 // if m_collectedMkisofsPrintSizeStdout is not empty we have a recent version of 0333 // mkisofs and parsing is very easy (s.o.) 0334 if( !m_collectedMkisofsPrintSizeStdout.isEmpty() ) { 0335 qDebug() << "(K3b::IsoImager) iso size: " << m_collectedMkisofsPrintSizeStdout; 0336 m_mkisofsPrintSizeResult = m_collectedMkisofsPrintSizeStdout.toInt( &success ); 0337 } 0338 else { 0339 // parse the stderr output 0340 // I hope parsing the last line is enough! 0341 int pos = m_collectedMkisofsPrintSizeStderr.lastIndexOf( "extents scheduled to be written" ); 0342 0343 if( pos == -1 ) 0344 success = false; 0345 else 0346 m_mkisofsPrintSizeResult = m_collectedMkisofsPrintSizeStderr.mid( pos+33 ).toInt( &success ); 0347 } 0348 0349 emit debuggingOutput( "K3b::IsoImager", 0350 QString("mkisofs print size result: %1 (%2 bytes)") 0351 .arg(m_mkisofsPrintSizeResult) 0352 .arg(quint64(m_mkisofsPrintSizeResult)*2048ULL) ); 0353 0354 cleanup(); 0355 0356 0357 if( success ) { 0358 jobFinished( true ); 0359 } 0360 else { 0361 m_mkisofsPrintSizeResult = 0; 0362 qDebug() << "(K3b::IsoImager) Parsing mkisofs -print-size failed: " << m_collectedMkisofsPrintSizeStdout; 0363 emit infoMessage( i18n("Could not determine size of resulting image file."), MessageError ); 0364 jobFinished( false ); 0365 } 0366 } 0367 0368 0369 void K3b::IsoImager::initVariables() 0370 { 0371 m_containsFilesWithMultibleBackslashes = false; 0372 m_canceled = false; 0373 d->knownError = false; 0374 0375 // determine symlink handling 0376 // follow links supersedes discard all links which supersedes discard broken links 0377 // without rockridge we follow the links or discard all 0378 if( m_doc->isoOptions().followSymbolicLinks() ) 0379 d->usedLinkHandling = Private::FOLLOW; 0380 else if( m_doc->isoOptions().discardSymlinks() ) 0381 d->usedLinkHandling = Private::DISCARD_ALL; 0382 else if( m_doc->isoOptions().createRockRidge() ) { 0383 if( m_doc->isoOptions().discardBrokenSymlinks() ) 0384 d->usedLinkHandling = Private::DISCARD_BROKEN; 0385 else 0386 d->usedLinkHandling = Private::KEEP_ALL; 0387 } 0388 else { 0389 d->usedLinkHandling = Private::FOLLOW; 0390 } 0391 0392 m_sessionNumber = s_imagerSessionCounter++; 0393 } 0394 0395 0396 void K3b::IsoImager::start() 0397 { 0398 jobStarted(); 0399 0400 cleanup(); 0401 0402 d->mkisofsBin = initMkisofs(); 0403 if( !d->mkisofsBin ) { 0404 jobFinished( false ); 0405 return; 0406 } 0407 0408 initVariables(); 0409 0410 delete m_process; 0411 m_process = new K3b::Process( this ); 0412 m_process->setFlags( K3bQProcess::RawStdout ); 0413 0414 *m_process << d->mkisofsBin; 0415 0416 // prepare the filenames as written to the image 0417 m_doc->prepareFilenames(); 0418 0419 if( !prepareMkisofsFiles() || 0420 !addMkisofsParameters() ) { 0421 cleanup(); 0422 jobFinished( false ); 0423 return; 0424 } 0425 0426 connect( m_process, SIGNAL(finished(int,QProcess::ExitStatus)), 0427 this, SLOT(slotProcessExited(int,QProcess::ExitStatus)) ); 0428 connect( m_process, SIGNAL(stderrLine(QString)), 0429 this, SLOT(slotReceivedStderr(QString)) ); 0430 0431 qDebug() << "***** mkisofs parameters:\n"; 0432 QString s = m_process->joinedArgs(); 0433 qDebug() << s << Qt::endl << Qt::flush; 0434 emit debuggingOutput("mkisofs command:", s); 0435 0436 if( !m_process->start( KProcess::SeparateChannels ) ) { 0437 // something went wrong when starting the program 0438 // it "should" be the executable 0439 qDebug() << "(K3b::IsoImager) could not start mkisofs"; 0440 emit infoMessage( i18n("Could not start %1.", QLatin1String("mkisofs")), K3b::Job::MessageError ); 0441 jobFinished( false ); 0442 cleanup(); 0443 } 0444 } 0445 0446 0447 void K3b::IsoImager::cancel() 0448 { 0449 qDebug(); 0450 m_canceled = true; 0451 0452 if( m_process && m_process->isRunning() ) { 0453 qDebug() << "terminating process"; 0454 m_process->terminate(); 0455 } 0456 else if( active() ) { 0457 emit canceled(); 0458 jobFinished(false); 0459 } 0460 } 0461 0462 0463 void K3b::IsoImager::setMultiSessionInfo( const QString& info, K3b::Device::Device* dev ) 0464 { 0465 m_multiSessionInfo = info; 0466 m_device = dev; 0467 } 0468 0469 0470 QString K3b::IsoImager::multiSessionInfo() const 0471 { 0472 return m_multiSessionInfo; 0473 } 0474 0475 0476 K3b::Device::Device* K3b::IsoImager::multiSessionImportDevice() const 0477 { 0478 return m_device; 0479 } 0480 0481 0482 // iso9660 + RR use some latin1 variant. So we need to cut the desc fields 0483 // counting 8bit chars. The GUI should take care of restricting the length 0484 // and the charset 0485 static void truncateTheHardWay( QString& s, int max ) 0486 { 0487 QByteArray cs = s.toUtf8(); 0488 cs.truncate(max); 0489 s = QString::fromUtf8( cs ); 0490 } 0491 0492 0493 bool K3b::IsoImager::addMkisofsParameters( bool printSize ) 0494 { 0495 // add multisession info 0496 if( !m_multiSessionInfo.isEmpty() ) { 0497 *m_process << "-cdrecord-params" << m_multiSessionInfo; 0498 if( m_device && !m_doc->isoOptions().doNotImportSession() ) { 0499 *m_process << "-prev-session" << m_device->blockDeviceName(); 0500 } 0501 } 0502 0503 // add the arguments 0504 *m_process << "-gui"; 0505 *m_process << "-graft-points"; 0506 0507 if( printSize ) 0508 *m_process << "-print-size" << "-quiet"; 0509 0510 if( !m_doc->isoOptions().volumeID().isEmpty() ) { 0511 QString s = m_doc->isoOptions().volumeID(); 0512 truncateTheHardWay(s, 32); // ensure max length 0513 *m_process << "-volid" << s; 0514 } 0515 else { 0516 emit infoMessage( i18n("No volume id specified. Using default."), MessageWarning ); 0517 *m_process << "-volid" << "CDROM"; 0518 } 0519 0520 QString s = m_doc->isoOptions().volumeSetId(); 0521 truncateTheHardWay(s, 128); // ensure max length 0522 *m_process << "-volset" << s; 0523 0524 s = m_doc->isoOptions().applicationID(); 0525 truncateTheHardWay(s, 128); // ensure max length 0526 *m_process << "-appid" << s; 0527 0528 s = m_doc->isoOptions().publisher(); 0529 truncateTheHardWay(s, 128); // ensure max length 0530 *m_process << "-publisher" << s; 0531 0532 s = m_doc->isoOptions().preparer(); 0533 truncateTheHardWay(s, 128); // ensure max length 0534 *m_process << "-preparer" << s; 0535 0536 s = m_doc->isoOptions().systemId(); 0537 truncateTheHardWay(s, 32); // ensure max length 0538 *m_process << "-sysid" << s; 0539 0540 s = m_doc->isoOptions().abstractFile(); 0541 truncateTheHardWay(s, 37); // ensure max length 0542 if ( !s.isEmpty() ) 0543 *m_process << "-abstract" << s; 0544 0545 s = m_doc->isoOptions().copyrightFile(); 0546 truncateTheHardWay(s, 37); // ensure max length 0547 if ( !s.isEmpty() ) 0548 *m_process << "-copyright" << s; 0549 0550 s = m_doc->isoOptions().bibliographFile(); 0551 truncateTheHardWay(s, 37); // ensure max length 0552 if ( !s.isEmpty() ) 0553 *m_process << "-biblio" << s; 0554 0555 int volsetSize = m_doc->isoOptions().volumeSetSize(); 0556 int volsetSeqNo = m_doc->isoOptions().volumeSetNumber(); 0557 if( volsetSeqNo > volsetSize ) { 0558 qDebug() << "(K3b::IsoImager) invalid volume set sequence number: " << volsetSeqNo 0559 << " with volume set size: " << volsetSize << Qt::endl; 0560 volsetSeqNo = volsetSize; 0561 } 0562 *m_process << "-volset-size" << QString::number(volsetSize); 0563 *m_process << "-volset-seqno" << QString::number(volsetSeqNo); 0564 0565 if( m_sortWeightFile ) { 0566 *m_process << "-sort" << m_sortWeightFile->fileName(); 0567 } 0568 0569 if( m_doc->isoOptions().createRockRidge() ) { 0570 if( m_doc->isoOptions().preserveFilePermissions() ) 0571 *m_process << "-rock"; 0572 else 0573 *m_process << "-rational-rock"; 0574 if( m_rrHideFile ) 0575 *m_process << "-hide-list" << m_rrHideFile->fileName(); 0576 } 0577 0578 if( m_doc->isoOptions().createJoliet() ) { 0579 *m_process << "-joliet"; 0580 if( m_doc->isoOptions().jolietLong() ) 0581 *m_process << "-joliet-long"; 0582 if( m_jolietHideFile ) 0583 *m_process << "-hide-joliet-list" << m_jolietHideFile->fileName(); 0584 } 0585 0586 if( m_doc->isoOptions().doNotCacheInodes() ) 0587 *m_process << "-no-cache-inodes"; 0588 0589 // 0590 // Check if we have files > 2 GB and enable udf in that case. 0591 // 0592 bool filesGreaterThan2Gb = false; 0593 bool filesGreaterThan4Gb = false; 0594 K3b::DataItem* item = m_doc->root(); 0595 while( (item = item->nextSibling()) ) { 0596 if ( item->isFile() && item->size() >= 0xFFFFFFFFULL ) { 0597 filesGreaterThan4Gb = filesGreaterThan2Gb = true; 0598 break; 0599 } 0600 else if( item->isFile() && item->size() > 2LL*1024LL*1024LL*1024LL ) { 0601 filesGreaterThan2Gb = true; 0602 if ( filesGreaterThan4Gb ) 0603 break; 0604 } 0605 } 0606 0607 if ( filesGreaterThan4Gb ) { 0608 if ( d->mkisofsBin && !d->mkisofsBin->hasFeature( "no-4gb-limit" ) ) { 0609 emit infoMessage( i18n( "Found files bigger than 4 GB. K3b needs at least %1 to continue." , 0610 QString( "mkisofs >= 2.01.01a33 / genisoimage >= 1.1.4" ) ), 0611 MessageError ); 0612 return false; 0613 } 0614 } 0615 0616 // in genisoimage 1.1.3 "they" silently introduced this awful parameter 0617 if (filesGreaterThan2Gb && d->mkisofsBin && d->mkisofsBin->hasFeature("genisoimage") && d->mkisofsBin->version() >= K3b::Version(1, 1, 3)) { 0618 *m_process << "-allow-limited-size"; 0619 emit infoMessage(i18n("Found files bigger than 2 GB. These files will be fully accessible."), 0620 MessageInfo); 0621 } 0622 0623 bool udf = m_doc->isoOptions().createUdf(); 0624 if( !udf && filesGreaterThan2Gb ) { 0625 emit infoMessage( i18n("Enabling UDF extension."), MessageInfo ); 0626 udf = true; 0627 } 0628 if( udf ) 0629 *m_process << "-udf"; 0630 0631 if( m_doc->isoOptions().ISOuntranslatedFilenames() ) { 0632 *m_process << "-untranslated-filenames"; 0633 } 0634 else { 0635 if( m_doc->isoOptions().ISOallowPeriodAtBegin() ) 0636 *m_process << "-allow-leading-dots"; 0637 if( m_doc->isoOptions().ISOallow31charFilenames() ) 0638 *m_process << "-full-iso9660-filenames"; 0639 if( m_doc->isoOptions().ISOomitVersionNumbers() && !m_doc->isoOptions().ISOmaxFilenameLength() ) 0640 *m_process << "-omit-version-number"; 0641 if( m_doc->isoOptions().ISOrelaxedFilenames() ) 0642 *m_process << "-relaxed-filenames"; 0643 if( m_doc->isoOptions().ISOallowLowercase() ) 0644 *m_process << "-allow-lowercase"; 0645 if( m_doc->isoOptions().ISOnoIsoTranslate() ) 0646 *m_process << "-no-iso-translate"; 0647 if( m_doc->isoOptions().ISOallowMultiDot() ) 0648 *m_process << "-allow-multidot"; 0649 if( m_doc->isoOptions().ISOomitTrailingPeriod() ) 0650 *m_process << "-omit-period"; 0651 } 0652 0653 if( m_doc->isoOptions().ISOmaxFilenameLength() ) 0654 *m_process << "-max-iso9660-filenames"; 0655 0656 if( m_noDeepDirectoryRelocation ) 0657 *m_process << "-disable-deep-relocation"; 0658 0659 // We do our own following 0660 // if( m_doc->isoOptions().followSymbolicLinks() || !m_doc->isoOptions().createRockRidge() ) 0661 // *m_process << "-follow-links"; 0662 0663 if( m_doc->isoOptions().createTRANS_TBL() ) 0664 *m_process << "-translation-table"; 0665 if( m_doc->isoOptions().hideTRANS_TBL() ) 0666 *m_process << "-hide-joliet-trans-tbl"; 0667 0668 int isoLevel = m_doc->isoOptions().ISOLevel(); 0669 if ( filesGreaterThan4Gb && isoLevel < 3 ) { 0670 emit infoMessage( i18n( "Setting iso level to 3 to support files bigger than 4 GB." ), MessageWarning ); 0671 isoLevel = 3; 0672 } 0673 *m_process << "-iso-level" << QString::number( isoLevel ); 0674 0675 *m_process << "-path-list" << m_pathSpecFile->fileName(); 0676 0677 0678 // boot stuff 0679 if( !m_doc->bootImages().isEmpty() ) { 0680 bool first = true; 0681 QList<K3b::BootItem*> bootItems = m_doc->bootImages(); 0682 Q_FOREACH( K3b::BootItem* bootItem, bootItems ) { 0683 if( !first ) 0684 *m_process << "-eltorito-alt-boot"; 0685 0686 *m_process << "-eltorito-boot"; 0687 *m_process << bootItem->writtenPath(); 0688 0689 if( bootItem->imageType() == K3b::BootItem::HARDDISK ) { 0690 *m_process << "-hard-disk-boot"; 0691 } 0692 else if( bootItem->imageType() == K3b::BootItem::NONE ) { 0693 *m_process << "-no-emul-boot"; 0694 if( bootItem->loadSegment() > 0 ) 0695 *m_process << "-boot-load-seg" << QString::number(bootItem->loadSegment()); 0696 if( bootItem->loadSize() > 0 ) 0697 *m_process << "-boot-load-size" << QString::number(bootItem->loadSize()); 0698 } 0699 0700 if( bootItem->imageType() != K3b::BootItem::NONE && bootItem->noBoot() ) 0701 *m_process << "-no-boot"; 0702 if( bootItem->bootInfoTable() ) 0703 *m_process << "-boot-info-table"; 0704 0705 first = false; 0706 } 0707 0708 *m_process << "-eltorito-catalog" << m_doc->bootCataloge()->writtenPath(); 0709 } 0710 0711 0712 // additional parameters from config 0713 0714 if ( d->mkisofsBin) { 0715 const QStringList& params = d->mkisofsBin->userParameters(); 0716 for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) 0717 *m_process << *it; 0718 } 0719 0720 return true; 0721 } 0722 0723 0724 int K3b::IsoImager::writePathSpec() 0725 { 0726 delete m_pathSpecFile; 0727 m_pathSpecFile = new QTemporaryFile(); 0728 if ( m_pathSpecFile->open() ) { 0729 qDebug() << "Opened path spec file" << m_pathSpecFile->fileName(); 0730 QTextStream s( m_pathSpecFile ); 0731 0732 // recursive path spec writing 0733 return writePathSpecForDir( m_doc->root(), s ); 0734 } 0735 else { 0736 return -1; 0737 } 0738 } 0739 0740 0741 int K3b::IsoImager::writePathSpecForDir( K3b::DirItem* dirItem, QTextStream& stream ) 0742 { 0743 if( !m_noDeepDirectoryRelocation && dirItem->depth() > 7 ) { 0744 qDebug() << "(K3b::IsoImager) found directory depth > 7. Enabling no deep directory relocation."; 0745 m_noDeepDirectoryRelocation = true; 0746 } 0747 0748 // now create the graft points 0749 int num = 0; 0750 Q_FOREACH( K3b::DataItem* item, dirItem->children() ) { 0751 bool writeItem = item->writeToCd(); 0752 0753 if( item->isSymLink() ) { 0754 if( d->usedLinkHandling == Private::DISCARD_ALL || 0755 ( d->usedLinkHandling == Private::DISCARD_BROKEN && 0756 !item->isValid() ) ) 0757 writeItem = false; 0758 0759 else if( d->usedLinkHandling == Private::FOLLOW ) { 0760 QFileInfo f( K3b::resolveLink( item->localPath() ) ); 0761 if( !f.exists() ) { 0762 emit infoMessage( i18n("Could not follow link %1 to non-existing file %2. Skipping...", item->k3bName(), f.filePath()), MessageWarning ); 0763 writeItem = false; 0764 } 0765 else if( f.isDir() ) { 0766 emit infoMessage( i18n("Ignoring link %1 to folder %2. K3b is unable to follow links to folders.", item->k3bName(), f.filePath()), MessageWarning ); 0767 writeItem = false; 0768 } 0769 } 0770 } 0771 else if( item->isFile() ) { 0772 QFileInfo f( item->localPath() ); 0773 if( !f.exists() ) { 0774 emit infoMessage( i18n("Could not find file %1. Skipping...",item->localPath()), MessageWarning ); 0775 writeItem = false; 0776 } 0777 else if( !f.isReadable() ) { 0778 emit infoMessage( i18n("Could not read file %1. Skipping...",item->localPath()), MessageWarning ); 0779 writeItem = false; 0780 } 0781 } 0782 0783 if( writeItem ) { 0784 num++; 0785 0786 // some versions of mkisofs seem to have a bug that prevents to use filenames 0787 // that contain one or more backslashes 0788 if( item->writtenPath().contains("\\") ) 0789 m_containsFilesWithMultibleBackslashes = true; 0790 0791 0792 if( item->isDir() ) { 0793 stream << escapeGraftPoint( item->writtenPath() ) 0794 << "=" 0795 << escapeGraftPoint( dummyDir( static_cast<K3b::DirItem*>(item) ) ) << "\n"; 0796 0797 int x = writePathSpecForDir( dynamic_cast<K3b::DirItem*>(item), stream ); 0798 if( x >= 0 ) 0799 num += x; 0800 else 0801 return -1; 0802 } 0803 else { 0804 writePathSpecForFile( static_cast<K3b::FileItem*>(item), stream ); 0805 } 0806 } 0807 } 0808 0809 return num; 0810 } 0811 0812 0813 void K3b::IsoImager::writePathSpecForFile( K3b::FileItem* item, QTextStream& stream ) 0814 { 0815 stream << escapeGraftPoint( item->writtenPath() ) 0816 << "="; 0817 0818 if( m_doc->bootImages().contains( dynamic_cast<K3b::BootItem*>(item) ) ) { // boot-image-backup-hack 0819 0820 // create temp file 0821 QTemporaryFile temp; 0822 temp.setAutoRemove( false ); 0823 temp.open(); 0824 QString tempPath = temp.fileName(); 0825 temp.remove(); 0826 0827 KIO::CopyJob* copyJob = KIO::copyAs(QUrl::fromLocalFile(item->localPath()), QUrl::fromLocalFile(tempPath), KIO::HideProgressInfo); 0828 bool copyJobSucceed = true; 0829 connect(copyJob, &KJob::result, [&](KJob*) { 0830 if( copyJob->error() != KJob::NoError ) { 0831 emit infoMessage( i18n("Failed to backup boot image file %1",item->localPath()), MessageError ); 0832 copyJobSucceed = false; 0833 } 0834 } ); 0835 if( !copyJob->exec() || !copyJobSucceed ) { 0836 return; 0837 } 0838 0839 static_cast<K3b::BootItem*>(item)->setTempPath( tempPath ); 0840 0841 m_tempFiles.append(tempPath); 0842 stream << escapeGraftPoint( tempPath ) << "\n"; 0843 } 0844 else if( item->isSymLink() && d->usedLinkHandling == Private::FOLLOW ) 0845 stream << escapeGraftPoint( K3b::resolveLink( item->localPath() ) ) << "\n"; 0846 else 0847 stream << escapeGraftPoint( item->localPath() ) << "\n"; 0848 } 0849 0850 0851 bool K3b::IsoImager::writeRRHideFile() 0852 { 0853 delete m_rrHideFile; 0854 m_rrHideFile = new QTemporaryFile(); 0855 m_rrHideFile->open(); 0856 0857 QTextStream s( m_rrHideFile ); 0858 0859 K3b::DataItem* item = m_doc->root(); 0860 while( item ) { 0861 if( item->hideOnRockRidge() ) { 0862 if( !item->isDir() ) // hiding directories does not work (all dirs point to the dummy-dir) 0863 s << escapeGraftPoint( item->localPath() ) << Qt::endl; 0864 } 0865 item = item->nextSibling(); 0866 } 0867 0868 return true; 0869 } 0870 0871 0872 bool K3b::IsoImager::writeJolietHideFile() 0873 { 0874 delete m_jolietHideFile; 0875 m_jolietHideFile = new QTemporaryFile(); 0876 m_jolietHideFile->open(); 0877 0878 QTextStream s( m_jolietHideFile ); 0879 0880 K3b::DataItem* item = m_doc->root(); 0881 while( item ) { 0882 if( item->hideOnRockRidge() ) { 0883 if( !item->isDir() ) // hiding directories does not work (all dirs point to the dummy-dir but we could introduce a second hidden dummy dir) 0884 s << escapeGraftPoint( item->localPath() ) << Qt::endl; 0885 } 0886 item = item->nextSibling(); 0887 } 0888 0889 return true; 0890 } 0891 0892 0893 bool K3b::IsoImager::writeSortWeightFile() 0894 { 0895 delete m_sortWeightFile; 0896 m_sortWeightFile = new QTemporaryFile(); 0897 m_sortWeightFile->open(); 0898 0899 QTextStream s( m_sortWeightFile ); 0900 0901 // 0902 // We need to write the local path in combination with the sort weight 0903 // mkisofs will take care of multiple entries for one local file and always 0904 // use the highest weight 0905 // 0906 K3b::DataItem* item = m_doc->root(); 0907 while( (item = item->nextSibling()) ) { // we skip the root here 0908 if( item->sortWeight() != 0 ) { 0909 if( m_doc->bootImages().contains( dynamic_cast<K3b::BootItem*>(item) ) ) { // boot-image-backup-hack 0910 s << escapeGraftPoint( static_cast<K3b::BootItem*>(item)->tempPath() ) << " " << item->sortWeight() << Qt::endl; 0911 } 0912 else if( item->isDir() ) { 0913 // 0914 // Since we use dummy dirs for all directories in the filesystem and mkisofs uses the local path 0915 // for sorting we need to create a different dummy dir for every sort weight value. 0916 // 0917 s << escapeGraftPoint( dummyDir( static_cast<K3b::DirItem*>(item) ) ) << " " << item->sortWeight() << Qt::endl; 0918 } 0919 else 0920 s << escapeGraftPoint( item->localPath() ) << " " << item->sortWeight() << Qt::endl; 0921 } 0922 } 0923 0924 return true; 0925 } 0926 0927 0928 QString K3b::IsoImager::escapeGraftPoint( const QString& str ) 0929 { 0930 QString enc = str; 0931 0932 // 0933 // mkisofs manpage (-graft-points) is incorrect (as of mkisofs 2.01.01) 0934 // 0935 // Actually an equal sign needs to be escaped with one backslash only 0936 // Single backslashes inside a filename can be used without change 0937 // while single backslashes at the end of a filename need to be escaped 0938 // with two backslashes. 0939 // 0940 // There is one more problem though: the name in the iso tree can never 0941 // in any number of backslashes. mkisofs simply cannot handle it. So we 0942 // need to remove these slashes somewhere or ignore those files (we do 0943 // that in K3b::DataDoc::addUrls) 0944 // 0945 0946 // 0947 // we do not use QString::replace to have full control 0948 // this might be slow since QString::insert is slow but we don't care 0949 // since this is only called to prepare the iso creation which is not 0950 // time critical. :) 0951 // 0952 0953 int pos = 0; 0954 while( pos < enc.length() ) { 0955 // escape every equal sign with one backslash 0956 if( enc[pos] == '=' ) { 0957 enc.insert( pos, "\\" ); 0958 pos += 2; 0959 } 0960 else if( enc[pos] == '\\' ) { 0961 // escape every occurrence of two backslashes with two backslashes 0962 if( pos+1 < enc.length() && enc[pos+1] == '\\' ) { 0963 enc.insert( pos, "\\\\" ); 0964 pos += 4; 0965 } 0966 // escape the last single backslash in the filename (see above) 0967 else if( pos == enc.length()-1 ) { 0968 enc.insert( pos, "\\" ); 0969 pos += 2; 0970 } 0971 else 0972 ++pos; 0973 } 0974 else 0975 ++pos; 0976 } 0977 0978 // enc.replace( "\\\\", "\\\\\\\\" ); 0979 // enc.replace( "=", "\\=" ); 0980 0981 return enc; 0982 } 0983 0984 0985 bool K3b::IsoImager::prepareMkisofsFiles() 0986 { 0987 // write path spec file 0988 // ---------------------------------------------------- 0989 int num = writePathSpec(); 0990 if( num < 0 ) { 0991 emit infoMessage( i18n("Could not write temporary file"), K3b::Job::MessageError ); 0992 return false; 0993 } 0994 else if( num == 0 ) { 0995 emit infoMessage( i18n("No files to be written."), K3b::Job::MessageError ); 0996 return false; 0997 } 0998 0999 if( m_doc->isoOptions().createRockRidge() ) { 1000 if( !writeRRHideFile() ) { 1001 emit infoMessage( i18n("Could not write temporary file"), K3b::Job::MessageError ); 1002 return false; 1003 } 1004 } 1005 1006 if( m_doc->isoOptions().createJoliet() ) { 1007 if( !writeJolietHideFile() ) { 1008 emit infoMessage( i18n("Could not write temporary file"), K3b::Job::MessageError ); 1009 return false ; 1010 } 1011 } 1012 1013 if( !writeSortWeightFile() ) { 1014 emit infoMessage( i18n("Could not write temporary file"), K3b::Job::MessageError ); 1015 return false; 1016 } 1017 1018 return true; 1019 } 1020 1021 1022 QString K3b::IsoImager::dummyDir( K3b::DirItem* dir ) 1023 { 1024 // 1025 // since we use virtual folders in order to have folders with different weight factors and different 1026 // permissions we create different dummy dirs to be passed to mkisofs 1027 // 1028 1029 QString path = QStandardPaths::writableLocation( QStandardPaths::AppDataLocation ) + "/temp/"; 1030 QDir().mkpath(path); 1031 QDir _appDir( path ); 1032 1033 // 1034 // create a unique isoimager session id 1035 // This might become important in case we will allow multiple instances of the isoimager 1036 // to run at the same time. 1037 // 1038 QString jobId = qApp->sessionId() + '_' + QString::number( m_sessionNumber ); 1039 1040 if( !_appDir.cd( jobId ) ) { 1041 _appDir.mkdir( jobId ); 1042 _appDir.cd( jobId ); 1043 } 1044 1045 QString name( "dummydir_" ); 1046 name += QString::number( dir->sortWeight() ); 1047 1048 bool perm = false; 1049 k3b_struct_stat statBuf; 1050 if( !dir->localPath().isEmpty() ) { 1051 // permissions 1052 if( k3b_stat( QFile::encodeName(dir->localPath()), &statBuf ) == 0 ) { 1053 name += '_'; 1054 name += QString::number( statBuf.st_uid ); 1055 name += '_'; 1056 name += QString::number( statBuf.st_gid ); 1057 name += '_'; 1058 name += QString::number( statBuf.st_mode ); 1059 name += '_'; 1060 name += QString::number( statBuf.st_mtime ); 1061 1062 perm = true; 1063 } 1064 } 1065 1066 1067 if( !_appDir.cd( name ) ) { 1068 1069 qDebug() << "(K3b::IsoImager) creating dummy dir: " << _appDir.absolutePath() << "/" << name; 1070 1071 _appDir.mkdir( name ); 1072 _appDir.cd( name ); 1073 1074 if( perm ) { 1075 ::chmod( QFile::encodeName( _appDir.absolutePath() ), statBuf.st_mode ); 1076 ::chown( QFile::encodeName( _appDir.absolutePath() ), statBuf.st_uid, statBuf.st_gid ); 1077 struct utimbuf tb; 1078 tb.actime = tb.modtime = statBuf.st_mtime; 1079 ::utime( QFile::encodeName( _appDir.absolutePath() ), &tb ); 1080 } 1081 } 1082 1083 return _appDir.absolutePath() + '/'; 1084 } 1085 1086 1087 void K3b::IsoImager::clearDummyDirs() 1088 { 1089 QString jobId = qApp->sessionId() + '_' + QString::number( m_sessionNumber ); 1090 QString path = QStandardPaths::writableLocation( QStandardPaths::AppDataLocation ) + "/temp/"; 1091 QDir().mkpath(path); 1092 QDir appDir( path ); 1093 if( appDir.cd( jobId ) ) { 1094 QStringList dummyDirEntries = appDir.entryList( QStringList() << "dummydir*", QDir::Dirs ); 1095 for( QStringList::iterator it = dummyDirEntries.begin(); it != dummyDirEntries.end(); ++it ) 1096 appDir.rmdir( *it ); 1097 appDir.cdUp(); 1098 appDir.rmdir( jobId ); 1099 } 1100 } 1101 1102 1103 bool K3b::IsoImager::hasBeenCanceled() const 1104 { 1105 return m_canceled; 1106 } 1107 1108 1109 QIODevice* K3b::IsoImager::ioDevice() const 1110 { 1111 return m_process; 1112 } 1113 1114 #include "moc_k3bisoimager.cpp"