File indexing completed on 2025-02-09 05:40:27
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"