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"