File indexing completed on 2024-04-28 04:49:21

0001 /*
0002     SPDX-FileCopyrightText: 2003-2009 Sebastian Trueg <trueg@k3b.org>
0003     SPDX-FileCopyrightText: 2010 Michal Malek <michalm@jabster.pl>
0004     SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "k3bexternalbinmanager.h"
0010 #include "k3bglobals.h"
0011 
0012 #include <KConfigGroup>
0013 #include <KProcess>
0014 
0015 #include <QDebug>
0016 #include <QDir>
0017 #include <QFileInfo>
0018 #include <QFile>
0019 #include <QtGlobal>
0020 #include <QRegularExpression>
0021 
0022 #ifndef Q_OS_WIN32
0023 #include <unistd.h>
0024 #include <sys/stat.h>
0025 #include <stdlib.h>
0026 #include <grp.h>
0027 #endif
0028 
0029 
0030 namespace {
0031     bool compareVersions( const K3b::ExternalBin* bin1, const K3b::ExternalBin* bin2 )
0032     {
0033         return bin1->version() > bin2->version();
0034     }
0035 
0036     const int EXECUTE_TIMEOUT = 5000; // in seconds
0037 }
0038 
0039 
0040 // ///////////////////////////////////////////////////////////
0041 //
0042 // K3BEXTERNALBIN
0043 //
0044 // ///////////////////////////////////////////////////////////
0045 
0046 class K3b::ExternalBin::Private
0047 {
0048 public:
0049     Private( ExternalProgram& pr, const QString& pa )
0050         : program( pr ), path( pa ) {}
0051 
0052     ExternalProgram& program;
0053     QString path;
0054     QString needGroup;
0055     Version version;
0056     QString copyright;
0057     QStringList features;
0058 };
0059 
0060 K3b::ExternalBin::ExternalBin( ExternalProgram& program, const QString& path )
0061     : d( new Private( program, path ) )
0062 {
0063 }
0064 
0065 
0066 K3b::ExternalBin::~ExternalBin()
0067 {
0068     delete d;
0069 }
0070 
0071 void K3b::ExternalBin::setVersion( const Version& version )
0072 {
0073     d->version = version;
0074 }
0075 
0076 const K3b::Version& K3b::ExternalBin::version() const
0077 {
0078     return d->version;
0079 }
0080 
0081 void K3b::ExternalBin::setNeedGroup( const QString& name )
0082 {
0083     d->needGroup = name;
0084 }
0085 
0086 const QString& K3b::ExternalBin::needGroup() const
0087 {
0088     return d->needGroup;
0089 }
0090 
0091 void K3b::ExternalBin::setCopyright( const QString& copyright )
0092 {
0093     d->copyright = copyright;
0094 }
0095 
0096 const QString& K3b::ExternalBin::copyright() const
0097 {
0098     return d->copyright;
0099 }
0100 
0101 
0102 bool K3b::ExternalBin::isEmpty() const
0103 {
0104     return !d->version.isValid();
0105 }
0106 
0107 
0108 const QString& K3b::ExternalBin::path() const
0109 {
0110     return d->path;
0111 }
0112 
0113 
0114 QString K3b::ExternalBin::name() const
0115 {
0116     return d->program.name();
0117 }
0118 
0119 
0120 bool K3b::ExternalBin::hasFeature( const QString& f ) const
0121 {
0122     return d->features.contains( f );
0123 }
0124 
0125 
0126 void K3b::ExternalBin::addFeature( const QString& f )
0127 {
0128     d->features.append( f );
0129 }
0130 
0131 
0132 QStringList K3b::ExternalBin::userParameters() const
0133 {
0134     return d->program.userParameters();
0135 }
0136 
0137 
0138 QStringList K3b::ExternalBin::features() const
0139 {
0140     return d->features;
0141 }
0142 
0143 
0144 K3b::ExternalProgram& K3b::ExternalBin::program() const
0145 {
0146     return d->program;
0147 }
0148 
0149 
0150 
0151 // ///////////////////////////////////////////////////////////
0152 //
0153 // K3BEXTERNALPROGRAM
0154 //
0155 // ///////////////////////////////////////////////////////////
0156 
0157 
0158 class K3b::ExternalProgram::Private
0159 {
0160 public:
0161     Private( const QString& n )
0162         : name( n ) {}
0163 
0164     QString name;
0165     QStringList userParameters;
0166     QList<const ExternalBin*> bins;
0167     QList<const ExternalBin*> gcBins;
0168     QString defaultBin;
0169 };
0170 
0171 
0172 K3b::ExternalProgram::ExternalProgram( const QString& name )
0173     : d( new Private( name ) )
0174 {
0175 }
0176 
0177 
0178 K3b::ExternalProgram::~ExternalProgram()
0179 {
0180     qDeleteAll(d->bins);
0181     qDeleteAll(d->gcBins);
0182     delete d;
0183 }
0184 
0185 
0186 const K3b::ExternalBin* K3b::ExternalProgram::mostRecentBin() const
0187 {
0188     if ( d->bins.isEmpty() ) {
0189         return 0;
0190     }
0191     else {
0192         return d->bins.first();
0193     }
0194 }
0195 
0196 
0197 const K3b::ExternalBin* K3b::ExternalProgram::defaultBin() const
0198 {
0199     if( d->bins.size() == 1 ) {
0200         return d->bins.first();
0201     }
0202     else {
0203         for( QList<const K3b::ExternalBin*>::const_iterator it = d->bins.constBegin(); it != d->bins.constEnd(); ++it ) {
0204             if( ( *it )->path() == d->defaultBin ) {
0205                 return *it;
0206             }
0207         }
0208         return 0;
0209     }
0210 }
0211 
0212 
0213 void K3b::ExternalProgram::addBin( K3b::ExternalBin* bin )
0214 {
0215     if( !d->bins.contains( bin ) ) {
0216         d->bins.append( bin );
0217 
0218         // the first bin in the list is always the one used
0219         // so we default to using the newest one
0220         std::sort(d->bins.begin(), d->bins.end(), compareVersions);
0221 
0222         const ExternalBin* defBin = defaultBin();
0223         if ( !defBin || bin->version() > defBin->version() ) {
0224             setDefault( bin );
0225         }
0226     }
0227 }
0228 
0229 
0230 void K3b::ExternalProgram::clear()
0231 {
0232     d->gcBins << d->bins;
0233     d->bins.clear();
0234 }
0235 
0236 
0237 void K3b::ExternalProgram::setDefault( const K3b::ExternalBin* bin )
0238 {
0239     for( QList<const K3b::ExternalBin*>::const_iterator it = d->bins.constBegin(); it != d->bins.constEnd(); ++it ) {
0240         if( *it == bin ) {
0241             d->defaultBin = (*it)->path();
0242             break;
0243         }
0244     }
0245 }
0246 
0247 
0248 void K3b::ExternalProgram::setDefault( const QString& path )
0249 {
0250     d->defaultBin = path;
0251 }
0252 
0253 
0254 QList<const K3b::ExternalBin*> K3b::ExternalProgram::bins() const
0255 {
0256     return d->bins;
0257 }
0258 
0259 
0260 bool K3b::ExternalProgram::supportsUserParameters() const
0261 {
0262     return true;
0263 }
0264 
0265 
0266 void K3b::ExternalProgram::addUserParameter( const QString& p )
0267 {
0268     if( !d->userParameters.contains( p ) )
0269         d->userParameters.append(p);
0270 }
0271 
0272 
0273 void K3b::ExternalProgram::setUserParameters( const QStringList& list )
0274 {
0275     d->userParameters = list;
0276 }
0277 
0278 
0279 QStringList K3b::ExternalProgram::userParameters() const
0280 {
0281     return d->userParameters;
0282 }
0283 
0284 
0285 QString K3b::ExternalProgram::name() const
0286 {
0287     return d->name;
0288 }
0289 
0290 
0291 // static
0292 QString K3b::ExternalProgram::buildProgramPath( const QString& dir, const QString& programName )
0293 {
0294     QString p = K3b::prepareDir( dir ) + programName;
0295 #ifdef Q_OS_WIN
0296     p += ".exe";
0297 #endif
0298     return p;
0299 }
0300 
0301 
0302 // ///////////////////////////////////////////////////////////
0303 //
0304 // SIMPLEEXTERNALPROGRAM
0305 //
0306 // ///////////////////////////////////////////////////////////
0307 
0308 class K3b::SimpleExternalProgram::Private
0309 {
0310 public:
0311 };
0312 
0313 
0314 K3b::SimpleExternalProgram::SimpleExternalProgram( const QString& name )
0315     : ExternalProgram( name ),
0316       d( new Private() )
0317 {
0318 }
0319 
0320 
0321 K3b::SimpleExternalProgram::~SimpleExternalProgram()
0322 {
0323     delete d;
0324 }
0325 
0326 
0327 QString K3b::SimpleExternalProgram::getProgramPath( const QString& dir ) const
0328 {
0329     return buildProgramPath( dir, name() );
0330 }
0331 
0332 
0333 bool K3b::SimpleExternalProgram::scan( const QString& p )
0334 {
0335     if( p.isEmpty() )
0336         return false;
0337 
0338     QString path = getProgramPath( p );
0339 
0340     if ( QFile::exists( path ) ) {
0341         K3b::ExternalBin* bin = new ExternalBin( *this, path );
0342 
0343         if ( ( !scanVersion( *bin ) || !scanFeatures( *bin ) ) && bin->needGroup().isEmpty() )  {
0344             delete bin;
0345             return false;
0346         }
0347 
0348         addBin( bin );
0349         return true;
0350     }
0351     else {
0352         return false;
0353     }
0354 }
0355 
0356 
0357 bool K3b::SimpleExternalProgram::scanVersion( ExternalBin& bin ) const
0358 {
0359     // probe version
0360     KProcess vp;
0361     vp.setOutputChannelMode( KProcess::MergedChannels );
0362     vp << bin.path() << "--version";
0363     if( vp.execute( EXECUTE_TIMEOUT ) < 0 ) {
0364         if( vp.error() == 0 ) {
0365             qDebug() << "Insufficient permissions for" << bin.path();
0366             // try to get real group or set fictive group to make
0367             // K3b::SystemProblemDialog::checkSystem work
0368             struct stat st;
0369             if( !::stat( QFile::encodeName(bin.path()), &st ) ) {
0370                 QString group( getgrgid( st.st_gid )->gr_name );
0371                 qDebug() << "Should be member of \"" << group << "\"";
0372                 bin.setNeedGroup( group.isEmpty() ? "N/A" : group );
0373             } else
0374                 bin.setNeedGroup( "N/A" );
0375         }
0376         return false;
0377     }
0378 
0379     // set empty group to make K3b::SystemProblemDialog::checkSystem work
0380     bin.setNeedGroup( "" );
0381     QString s = QString::fromLocal8Bit( vp.readAll() );
0382     bin.setVersion( parseVersion( s, bin ) );
0383     bin.setCopyright( parseCopyright( s, bin ) );
0384     return bin.version().isValid();
0385 }
0386 
0387 
0388 bool K3b::SimpleExternalProgram::scanFeatures( ExternalBin& bin ) const
0389 {
0390 #ifndef Q_OS_WIN32
0391     // check if we run as root
0392     struct stat s;
0393     if( !::stat( QFile::encodeName(bin.path()), &s ) ) {
0394         if( (s.st_mode & S_ISUID) && s.st_uid == 0 )
0395             bin.addFeature( "suidroot" );
0396     }
0397 #endif
0398 
0399     // probe features
0400     KProcess fp;
0401     fp.setOutputChannelMode( KProcess::MergedChannels );
0402     fp << bin.path() << "--help";
0403     if( fp.execute( EXECUTE_TIMEOUT ) < 0 )
0404         return false;
0405 
0406     parseFeatures( QString::fromLocal8Bit( fp.readAll() ), bin );
0407     return true;
0408 }
0409 
0410 
0411 K3b::Version K3b::SimpleExternalProgram::parseVersion( const QString& out, const ExternalBin& bin ) const
0412 {
0413     // we first look for the program name with first upper char so we do not catch
0414     // the warning messages on stderr (cdrecord sometimes produces those)
0415     QString programName = versionIdentifier( bin );
0416     QString programNameCap = programName[0].toUpper() + programName.mid( 1 );
0417     int pos = out.indexOf( programNameCap );
0418     if ( pos < 0 )
0419         pos = out.indexOf( programName );
0420 
0421     if( pos < 0 )
0422         return Version();
0423 
0424     return parseVersionAt( out, pos );
0425 }
0426 
0427 
0428 QString K3b::SimpleExternalProgram::parseCopyright( const QString& output, const ExternalBin& /*bin*/ ) const
0429 {
0430     int pos = output.indexOf( "(C)", 0 );
0431     if ( pos < 0 )
0432         return QString();
0433     pos += 4;
0434     int endPos = output.indexOf( '\n', pos );
0435     return output.mid( pos, endPos-pos );
0436 }
0437 
0438 
0439 void K3b::SimpleExternalProgram::parseFeatures( const QString& /*output*/, ExternalBin& /*bin*/ ) const
0440 {
0441     // do nothing
0442 }
0443 
0444 
0445 // static
0446 K3b::Version K3b::SimpleExternalProgram::parseVersionAt( const QString& data, int pos )
0447 {
0448     static const QRegularExpression sPosRx("\\d");
0449     int sPos = data.indexOf( sPosRx, pos );
0450     if( sPos < 0 )
0451         return Version();
0452 
0453     static const QRegularExpression endPosRx("[\\s,]");
0454     int endPos = data.indexOf( endPosRx, sPos + 1 );
0455     if( endPos < 0 )
0456         return Version();
0457 
0458     return data.mid( sPos, endPos - sPos );
0459 }
0460 
0461 
0462 QString K3b::SimpleExternalProgram::versionIdentifier( const ExternalBin& /*bin*/ ) const
0463 {
0464     return name();
0465 }
0466 
0467 
0468 // ///////////////////////////////////////////////////////////
0469 //
0470 // K3BEXTERNALBINMANAGER
0471 //
0472 // ///////////////////////////////////////////////////////////
0473 
0474 
0475 class K3b::ExternalBinManager::Private
0476 {
0477 public:
0478     QMap<QString, ExternalProgram*> programs;
0479     QStringList searchPath;
0480 
0481     static QString noPath;  // used for binPath() to return const string
0482 
0483     QString gatheredOutput;
0484 };
0485 
0486 
0487 QString K3b::ExternalBinManager::Private::noPath = "";
0488 
0489 
0490 K3b::ExternalBinManager::ExternalBinManager( QObject* parent )
0491     : QObject( parent ),
0492       d( new Private )
0493 {
0494 }
0495 
0496 
0497 K3b::ExternalBinManager::~ExternalBinManager()
0498 {
0499     clear();
0500     delete d;
0501 }
0502 
0503 
0504 bool K3b::ExternalBinManager::readConfig( const KConfigGroup& grp )
0505 {
0506     loadDefaultSearchPath();
0507 
0508     if( grp.hasKey( "search path" ) ) {
0509         setSearchPath( grp.readPathEntry( QString( "search path" ), QStringList() ) );
0510     }
0511 
0512     search();
0513 
0514     Q_FOREACH( K3b::ExternalProgram* p, d->programs ) {
0515         if( grp.hasKey( p->name() + " default" ) ) {
0516             p->setDefault( grp.readEntry( p->name() + " default", QString() ) );
0517         }
0518 
0519         QStringList list = grp.readEntry( p->name() + " user parameters", QStringList() );
0520         for( QStringList::const_iterator strIt = list.constBegin(); strIt != list.constEnd(); ++strIt )
0521             p->addUserParameter( *strIt );
0522 
0523         K3b::Version lastMax( grp.readEntry( p->name() + " last seen newest version", QString() ) );
0524         // now search for a newer version and use it (because it was installed after the last
0525         // K3b run and most users would probably expect K3b to use a newly installed version)
0526         const K3b::ExternalBin* newestBin = p->mostRecentBin();
0527         if( newestBin && newestBin->version() > lastMax )
0528             p->setDefault( newestBin );
0529     }
0530 
0531     return true;
0532 }
0533 
0534 
0535 bool K3b::ExternalBinManager::saveConfig( KConfigGroup grp )
0536 {
0537     grp.writePathEntry( "search path", d->searchPath );
0538 
0539     Q_FOREACH( K3b::ExternalProgram* p, d->programs ) {
0540         if( p->defaultBin() )
0541             grp.writeEntry( p->name() + " default", p->defaultBin()->path() );
0542 
0543         grp.writeEntry( p->name() + " user parameters", p->userParameters() );
0544 
0545         const K3b::ExternalBin* newestBin = p->mostRecentBin();
0546         if( newestBin )
0547             grp.writeEntry( p->name() + " last seen newest version", newestBin->version().toString() );
0548     }
0549 
0550     return true;
0551 }
0552 
0553 
0554 bool K3b::ExternalBinManager::foundBin( const QString& name )
0555 {
0556     if( d->programs.constFind( name ) == d->programs.constEnd() )
0557         return false;
0558     else
0559         return (d->programs[name]->defaultBin() != 0);
0560 }
0561 
0562 
0563 QString K3b::ExternalBinManager::binPath( const QString& name )
0564 {
0565     if( d->programs.constFind( name ) == d->programs.constEnd() )
0566         return Private::noPath;
0567 
0568     if( d->programs[name]->defaultBin() != 0 )
0569         return d->programs[name]->defaultBin()->path();
0570     else
0571         return Private::noPath;
0572 }
0573 
0574 
0575 const K3b::ExternalBin* K3b::ExternalBinManager::binObject( const QString& name )
0576 {
0577     if( d->programs.constFind( name ) == d->programs.constEnd() )
0578         return 0;
0579 
0580     return d->programs[name]->defaultBin();
0581 }
0582 
0583 
0584 QString K3b::ExternalBinManager::binNeedGroup( const QString& name )
0585 {
0586     if( d->programs.constFind( name ) == d->programs.constEnd() )
0587         return QString();
0588 
0589     if( d->programs[name]->defaultBin() != 0 )
0590         return d->programs[name]->defaultBin()->needGroup();
0591 
0592     return QString();
0593 }
0594 
0595 
0596 void K3b::ExternalBinManager::addProgram( K3b::ExternalProgram* p )
0597 {
0598     d->programs.insert( p->name(), p );
0599 }
0600 
0601 
0602 void K3b::ExternalBinManager::clear()
0603 {
0604     qDeleteAll( d->programs );
0605     d->programs.clear();
0606 }
0607 
0608 
0609 void K3b::ExternalBinManager::search()
0610 {
0611     if( d->searchPath.isEmpty() )
0612         loadDefaultSearchPath();
0613 
0614     Q_FOREACH( K3b::ExternalProgram* program, d->programs ) {
0615         program->clear();
0616     }
0617 
0618     // do not search one path twice
0619     QStringList paths;
0620 #ifdef Q_OS_WIN
0621     const QChar pathSep = QChar::fromLatin1( ';' );
0622 #else
0623     const QChar pathSep = QChar::fromLatin1( ':' );
0624 #endif
0625     const QStringList possiblePaths = QString::fromLatin1( qgetenv( "PATH" ) ).split( pathSep, Qt::SkipEmptyParts )
0626                                       + d->searchPath;
0627     foreach( QString p, possiblePaths ) {
0628         if (p.length() == 0)
0629             continue;
0630         if( p[p.length()-1] == '/' )
0631             p.truncate( p.length()-1 );
0632         if( !paths.contains( p ) && !paths.contains( p + '/' ) )
0633             paths.append(p);
0634     }
0635 
0636     Q_FOREACH( const QString& path, paths ) {
0637         Q_FOREACH( K3b::ExternalProgram* program, d->programs ) {
0638             program->scan( path );
0639         }
0640     }
0641 }
0642 
0643 
0644 K3b::ExternalProgram* K3b::ExternalBinManager::program( const QString& name ) const
0645 {
0646     if( d->programs.constFind( name ) == d->programs.constEnd() )
0647         return 0;
0648     else
0649         return d->programs[name];
0650 }
0651 
0652 
0653 QMap<QString, K3b::ExternalProgram*> K3b::ExternalBinManager::programs() const
0654 {
0655     return d->programs;
0656 }
0657 
0658 
0659 void K3b::ExternalBinManager::loadDefaultSearchPath()
0660 {
0661     static const char* const defaultSearchPaths[] = {
0662 #ifndef Q_OS_WIN32
0663                                                 "/usr/bin/",
0664                                                 "/usr/local/bin/",
0665                                                 "/usr/sbin/",
0666                                                 "/usr/local/sbin/",
0667                                                 "/sbin",
0668 #endif
0669                                                 0 };
0670 
0671     d->searchPath.clear();
0672     for( int i = 0; defaultSearchPaths[i]; ++i ) {
0673         d->searchPath.append( defaultSearchPaths[i] );
0674     }
0675 }
0676 
0677 
0678 QStringList K3b::ExternalBinManager::searchPath() const
0679 {
0680     return d->searchPath;
0681 }
0682 
0683 
0684 void K3b::ExternalBinManager::setSearchPath( const QStringList& list )
0685 {
0686     d->searchPath.clear();
0687     for( QStringList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it ) {
0688         d->searchPath.append( QDir::fromNativeSeparators( *it ) );
0689     }
0690 }
0691 
0692 
0693 void K3b::ExternalBinManager::addSearchPath( const QString& path )
0694 {
0695     QString aPath = QDir::fromNativeSeparators( path );
0696     if( !d->searchPath.contains( aPath ) )
0697         d->searchPath.append( aPath );
0698 }
0699 
0700 
0701 const K3b::ExternalBin* K3b::ExternalBinManager::mostRecentBinObject( const QString& name )
0702 {
0703     if( K3b::ExternalProgram* p = program( name ) )
0704         return p->mostRecentBin();
0705     else
0706         return 0;
0707 }
0708 
0709 #include "moc_k3bexternalbinmanager.cpp"