klibloader.cpp
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 1999 Torben Weis <weis@kde.org> 00003 Copyright (C) 2000 Michael Matz <matz@kde.org> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License version 2 as published by the Free Software Foundation. 00008 00009 This library is distributed in the hope that it will be useful, 00010 but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 Library General Public License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to 00016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00017 Boston, MA 02110-1301, USA. 00018 */ 00019 #include "config.h" 00020 00021 #include <config.h> 00022 #include <qclipboard.h> 00023 #include <qfile.h> 00024 #include <qdir.h> 00025 #include <qtimer.h> 00026 #include <qobjectdict.h> 00027 00028 #include "kapplication.h" 00029 #include "klibloader.h" 00030 #include "kstandarddirs.h" 00031 #include "kdebug.h" 00032 #include "klocale.h" 00033 00034 #include "ltdl.h" 00035 00036 template class QAsciiDict<KLibrary>; 00037 00038 #include <stdlib.h> //getenv 00039 00040 00041 #if HAVE_DLFCN_H 00042 # include <dlfcn.h> 00043 #endif 00044 00045 #ifdef RTLD_GLOBAL 00046 # define LT_GLOBAL RTLD_GLOBAL 00047 #else 00048 # ifdef DL_GLOBAL 00049 # define LT_GLOBAL DL_GLOBAL 00050 # endif 00051 #endif /* !RTLD_GLOBAL */ 00052 #ifndef LT_GLOBAL 00053 # define LT_GLOBAL 0 00054 #endif /* !LT_GLOBAL */ 00055 00056 00057 class KLibLoaderPrivate 00058 { 00059 public: 00060 QPtrList<KLibWrapPrivate> loaded_stack; 00061 QPtrList<KLibWrapPrivate> pending_close; 00062 enum {UNKNOWN, UNLOAD, DONT_UNLOAD} unload_mode; 00063 00064 QString errorMessage; 00065 }; 00066 00067 KLibLoader* KLibLoader::s_self = 0; 00068 00069 // ------------------------------------------------------------------------- 00070 00071 KLibFactory::KLibFactory( QObject* parent, const char* name ) 00072 : QObject( parent, name ) 00073 { 00074 } 00075 00076 KLibFactory::~KLibFactory() 00077 { 00078 // kdDebug(150) << "Deleting KLibFactory " << this << endl; 00079 } 00080 00081 QObject* KLibFactory::create( QObject* parent, const char* name, const char* classname, const QStringList &args ) 00082 { 00083 QObject* obj = createObject( parent, name, classname, args ); 00084 if ( obj ) 00085 emit objectCreated( obj ); 00086 return obj; 00087 } 00088 00089 00090 QObject* KLibFactory::createObject( QObject*, const char*, const char*, const QStringList &) 00091 { 00092 return 0; 00093 } 00094 00095 00096 // ----------------------------------------------- 00097 00098 KLibrary::KLibrary( const QString& libname, const QString& filename, void * handle ) 00099 { 00100 /* Make sure, we have a KLibLoader */ 00101 (void) KLibLoader::self(); 00102 m_libname = libname; 00103 m_filename = filename; 00104 m_handle = handle; 00105 m_factory = 0; 00106 m_timer = 0; 00107 } 00108 00109 KLibrary::~KLibrary() 00110 { 00111 // kdDebug(150) << "Deleting KLibrary " << this << " " << m_libname << endl; 00112 if ( m_timer && m_timer->isActive() ) 00113 m_timer->stop(); 00114 00115 // If any object is remaining, delete 00116 if ( m_objs.count() > 0 ) 00117 { 00118 QPtrListIterator<QObject> it( m_objs ); 00119 for ( ; it.current() ; ++it ) 00120 { 00121 kdDebug(150) << "Factory still has object " << it.current() << " " << it.current()->name () << " Library = " << m_libname << endl; 00122 disconnect( it.current(), SIGNAL( destroyed() ), 00123 this, SLOT( slotObjectDestroyed() ) ); 00124 } 00125 m_objs.setAutoDelete(true); 00126 m_objs.clear(); 00127 } 00128 00129 if ( m_factory ) { 00130 // kdDebug(150) << " ... deleting the factory " << m_factory << endl; 00131 delete m_factory; 00132 m_factory = 0L; 00133 } 00134 } 00135 00136 QString KLibrary::name() const 00137 { 00138 return m_libname; 00139 } 00140 00141 QString KLibrary::fileName() const 00142 { 00143 return m_filename; 00144 } 00145 00146 KLibFactory* KLibrary::factory() 00147 { 00148 if ( m_factory ) 00149 return m_factory; 00150 00151 QCString symname; 00152 symname.sprintf("init_%s", name().latin1() ); 00153 00154 void* sym = symbol( symname ); 00155 if ( !sym ) 00156 { 00157 KLibLoader::self()->d->errorMessage = i18n( "The library %1 does not offer an %2 function." ).arg( name(), "init_" + name() ); 00158 kdWarning(150) << KLibLoader::self()->d->errorMessage << endl; 00159 return 0; 00160 } 00161 00162 typedef KLibFactory* (*t_func)(); 00163 t_func func = (t_func)sym; 00164 m_factory = func(); 00165 00166 if( !m_factory ) 00167 { 00168 KLibLoader::self()->d->errorMessage = i18n( "The library %1 does not offer a KDE compatible factory." ).arg( name() ); 00169 kdWarning(150) << KLibLoader::self()->d->errorMessage << endl; 00170 return 0; 00171 } 00172 00173 connect( m_factory, SIGNAL( objectCreated( QObject * ) ), 00174 this, SLOT( slotObjectCreated( QObject * ) ) ); 00175 00176 return m_factory; 00177 } 00178 00179 void* KLibrary::symbol( const char* symname ) const 00180 { 00181 void* sym = lt_dlsym( (lt_dlhandle) m_handle, symname ); 00182 if ( !sym ) 00183 { 00184 KLibLoader::self()->d->errorMessage = "KLibrary: " + QString::fromLocal8Bit( lt_dlerror() ); 00185 kdWarning(150) << KLibLoader::self()->d->errorMessage << endl; 00186 return 0; 00187 } 00188 00189 return sym; 00190 } 00191 00192 bool KLibrary::hasSymbol( const char* symname ) const 00193 { 00194 void* sym = lt_dlsym( (lt_dlhandle) m_handle, symname ); 00195 return (sym != 0L ); 00196 } 00197 00198 void KLibrary::unload() const 00199 { 00200 if (KLibLoader::s_self) 00201 KLibLoader::s_self->unloadLibrary(QFile::encodeName(name())); 00202 } 00203 00204 void KLibrary::slotObjectCreated( QObject *obj ) 00205 { 00206 if ( !obj ) 00207 return; 00208 00209 if ( m_timer && m_timer->isActive() ) 00210 m_timer->stop(); 00211 00212 if ( m_objs.containsRef( obj ) ) 00213 return; // we know this object already 00214 00215 connect( obj, SIGNAL( destroyed() ), 00216 this, SLOT( slotObjectDestroyed() ) ); 00217 00218 m_objs.append( obj ); 00219 } 00220 00221 void KLibrary::slotObjectDestroyed() 00222 { 00223 m_objs.removeRef( sender() ); 00224 00225 if ( m_objs.count() == 0 ) 00226 { 00227 // kdDebug(150) << "KLibrary: shutdown timer for " << name() << " started!" 00228 // << endl; 00229 00230 if ( !m_timer ) 00231 { 00232 m_timer = new QTimer( this, "klibrary_shutdown_timer" ); 00233 connect( m_timer, SIGNAL( timeout() ), 00234 this, SLOT( slotTimeout() ) ); 00235 } 00236 00237 // as long as it's not stable make the timeout short, for debugging 00238 // pleasure (matz) 00239 //m_timer->start( 1000*60, true ); 00240 m_timer->start( 1000*10, true ); 00241 } 00242 } 00243 00244 void KLibrary::slotTimeout() 00245 { 00246 if ( m_objs.count() != 0 ) 00247 return; 00248 00249 /* Don't go through KLibLoader::unloadLibrary(), because that uses the 00250 ref counter, but this timeout means to unconditionally close this library 00251 The destroyed() signal will take care to remove us from all lists. 00252 */ 00253 delete this; 00254 } 00255 00256 // ------------------------------------------------- 00257 00258 /* This helper class is needed, because KLibraries can go away without 00259 being unloaded. So we need some info about KLibraries even after its 00260 death. */ 00261 class KLibWrapPrivate 00262 { 00263 public: 00264 KLibWrapPrivate(KLibrary *l, lt_dlhandle h); 00265 00266 KLibrary *lib; 00267 enum {UNKNOWN, UNLOAD, DONT_UNLOAD} unload_mode; 00268 int ref_count; 00269 lt_dlhandle handle; 00270 QString name; 00271 QString filename; 00272 }; 00273 00274 KLibWrapPrivate::KLibWrapPrivate(KLibrary *l, lt_dlhandle h) 00275 : lib(l), ref_count(1), handle(h), name(l->name()), filename(l->fileName()) 00276 { 00277 unload_mode = UNKNOWN; 00278 if (lt_dlsym(handle, "__kde_do_not_unload") != 0) { 00279 // kdDebug(150) << "Will not unload " << name << endl; 00280 unload_mode = DONT_UNLOAD; 00281 } else if (lt_dlsym(handle, "__kde_do_unload") != 0) { 00282 unload_mode = UNLOAD; 00283 } 00284 } 00285 00286 KLibLoader* KLibLoader::self() 00287 { 00288 if ( !s_self ) 00289 s_self = new KLibLoader; 00290 return s_self; 00291 } 00292 00293 void KLibLoader::cleanUp() 00294 { 00295 if ( !s_self ) 00296 return; 00297 00298 delete s_self; 00299 s_self = 0L; 00300 } 00301 00302 KLibLoader::KLibLoader( QObject* parent, const char* name ) 00303 : QObject( parent, name ) 00304 { 00305 s_self = this; 00306 d = new KLibLoaderPrivate; 00307 lt_dlinit(); 00308 d->unload_mode = KLibLoaderPrivate::UNKNOWN; 00309 if (getenv("KDE_NOUNLOAD") != 0) 00310 d->unload_mode = KLibLoaderPrivate::DONT_UNLOAD; 00311 else if (getenv("KDE_DOUNLOAD") != 0) 00312 d->unload_mode = KLibLoaderPrivate::UNLOAD; 00313 d->loaded_stack.setAutoDelete( true ); 00314 } 00315 00316 KLibLoader::~KLibLoader() 00317 { 00318 // kdDebug(150) << "Deleting KLibLoader " << this << " " << name() << endl; 00319 00320 QAsciiDictIterator<KLibWrapPrivate> it( m_libs ); 00321 for (; it.current(); ++it ) 00322 { 00323 kdDebug(150) << "The KLibLoader contains the library " << it.current()->name 00324 << " (" << it.current()->lib << ")" << endl; 00325 d->pending_close.append(it.current()); 00326 } 00327 00328 close_pending(0); 00329 00330 delete d; 00331 d = 0L; 00332 } 00333 00334 static inline QCString makeLibName( const char* name ) 00335 { 00336 QCString libname(name); 00337 // only append ".la" if there is no extension 00338 // this allows to load non-libtool libraries as well 00339 // (mhk, 20000228) 00340 int pos = libname.findRev('/'); 00341 if (pos < 0) 00342 pos = 0; 00343 if (libname.find('.', pos) < 0) 00344 libname += ".la"; 00345 return libname; 00346 } 00347 00348 //static 00349 QString KLibLoader::findLibrary( const char * name, const KInstance * instance ) 00350 { 00351 QCString libname = makeLibName( name ); 00352 00353 // only look up the file if it is not an absolute filename 00354 // (mhk, 20000228) 00355 QString libfile; 00356 if (!QDir::isRelativePath(libname)) 00357 libfile = QFile::decodeName( libname ); 00358 else 00359 { 00360 libfile = instance->dirs()->findResource( "module", libname ); 00361 if ( libfile.isEmpty() ) 00362 { 00363 libfile = instance->dirs()->findResource( "lib", libname ); 00364 #ifndef NDEBUG 00365 if ( !libfile.isEmpty() && libname.left(3) == "lib" ) // don't warn for kdeinit modules 00366 kdDebug(150) << "library " << libname << " not found under 'module' but under 'lib'" << endl; 00367 #endif 00368 } 00369 } 00370 return libfile; 00371 } 00372 00373 00374 KLibrary* KLibLoader::globalLibrary( const char *name ) 00375 { 00376 KLibrary *tmp; 00377 int olt_dlopen_flag = lt_dlopen_flag; 00378 00379 lt_dlopen_flag |= LT_GLOBAL; 00380 kdDebug(150) << "Loading the next library global with flag " 00381 << lt_dlopen_flag 00382 << "." << endl; 00383 tmp = library(name); 00384 lt_dlopen_flag = olt_dlopen_flag; 00385 00386 return tmp; 00387 } 00388 00389 00390 KLibrary* KLibLoader::library( const char *name ) 00391 { 00392 if (!name) 00393 return 0; 00394 00395 KLibWrapPrivate* wrap = m_libs[name]; 00396 if (wrap) { 00397 /* Nothing to do to load the library. */ 00398 wrap->ref_count++; 00399 return wrap->lib; 00400 } 00401 00402 /* Test if this library was loaded at some time, but got 00403 unloaded meanwhile, whithout being dlclose()'ed. */ 00404 QPtrListIterator<KLibWrapPrivate> it(d->loaded_stack); 00405 for (; it.current(); ++it) { 00406 if (it.current()->name == name) 00407 wrap = it.current(); 00408 } 00409 00410 if (wrap) { 00411 d->pending_close.removeRef(wrap); 00412 if (!wrap->lib) { 00413 /* This lib only was in loaded_stack, but not in m_libs. */ 00414 wrap->lib = new KLibrary( name, wrap->filename, wrap->handle ); 00415 } 00416 wrap->ref_count++; 00417 } else { 00418 QString libfile = findLibrary( name ); 00419 if ( libfile.isEmpty() ) 00420 { 00421 const QCString libname = makeLibName( name ); 00422 #ifndef NDEBUG 00423 kdDebug(150) << "library=" << name << ": No file named " << libname << " found in paths." << endl; 00424 #endif 00425 d->errorMessage = i18n("Library files for \"%1\" not found in paths.").arg(libname); 00426 return 0; 00427 } 00428 00429 lt_dlhandle handle = lt_dlopen( QFile::encodeName(libfile) ); 00430 if ( !handle ) 00431 { 00432 const char* errmsg = lt_dlerror(); 00433 if(errmsg) 00434 d->errorMessage = QString::fromLocal8Bit(errmsg); 00435 else 00436 d->errorMessage = QString::null; 00437 return 0; 00438 } 00439 else 00440 d->errorMessage = QString::null; 00441 00442 KLibrary *lib = new KLibrary( name, libfile, handle ); 00443 wrap = new KLibWrapPrivate(lib, handle); 00444 d->loaded_stack.prepend(wrap); 00445 } 00446 m_libs.insert( name, wrap ); 00447 00448 connect( wrap->lib, SIGNAL( destroyed() ), 00449 this, SLOT( slotLibraryDestroyed() ) ); 00450 00451 return wrap->lib; 00452 } 00453 00454 QString KLibLoader::lastErrorMessage() const 00455 { 00456 return d->errorMessage; 00457 } 00458 00459 void KLibLoader::unloadLibrary( const char *libname ) 00460 { 00461 KLibWrapPrivate *wrap = m_libs[ libname ]; 00462 if (!wrap) 00463 return; 00464 if (--wrap->ref_count) 00465 return; 00466 00467 // kdDebug(150) << "closing library " << libname << endl; 00468 00469 m_libs.remove( libname ); 00470 00471 disconnect( wrap->lib, SIGNAL( destroyed() ), 00472 this, SLOT( slotLibraryDestroyed() ) ); 00473 close_pending( wrap ); 00474 } 00475 00476 KLibFactory* KLibLoader::factory( const char* name ) 00477 { 00478 KLibrary* lib = library( name ); 00479 if ( !lib ) 00480 return 0; 00481 00482 return lib->factory(); 00483 } 00484 00485 void KLibLoader::slotLibraryDestroyed() 00486 { 00487 const KLibrary *lib = static_cast<const KLibrary *>( sender() ); 00488 00489 QAsciiDictIterator<KLibWrapPrivate> it( m_libs ); 00490 for (; it.current(); ++it ) 00491 if ( it.current()->lib == lib ) 00492 { 00493 KLibWrapPrivate *wrap = it.current(); 00494 wrap->lib = 0; /* the KLibrary object is already away */ 00495 m_libs.remove( it.currentKey() ); 00496 close_pending( wrap ); 00497 return; 00498 } 00499 } 00500 00501 void KLibLoader::close_pending(KLibWrapPrivate *wrap) 00502 { 00503 if (wrap && !d->pending_close.containsRef( wrap )) 00504 d->pending_close.append( wrap ); 00505 00506 /* First delete all KLibrary objects in pending_close, but _don't_ unload 00507 the DSO behind it. */ 00508 QPtrListIterator<KLibWrapPrivate> it(d->pending_close); 00509 for (; it.current(); ++it) { 00510 wrap = it.current(); 00511 if (wrap->lib) { 00512 disconnect( wrap->lib, SIGNAL( destroyed() ), 00513 this, SLOT( slotLibraryDestroyed() ) ); 00514 KLibrary* to_delete = wrap->lib; 00515 wrap->lib = 0L; // unset first, because KLibrary dtor can cause 00516 delete to_delete; // recursive call to close_pending() 00517 } 00518 } 00519 00520 if (d->unload_mode == KLibLoaderPrivate::DONT_UNLOAD) { 00521 d->pending_close.clear(); 00522 return; 00523 } 00524 00525 bool deleted_one = false; 00526 while ((wrap = d->loaded_stack.first())) { 00527 /* Let's first see, if we want to try to unload this lib. 00528 If the env. var KDE_DOUNLOAD is set, we try to unload every lib. 00529 If not, we look at the lib itself, and unload it only, if it exports 00530 the symbol __kde_do_unload. */ 00531 if (d->unload_mode != KLibLoaderPrivate::UNLOAD 00532 && wrap->unload_mode != KLibWrapPrivate::UNLOAD) 00533 break; 00534 00535 /* Now ensure, that the libs are only unloaded in the reverse direction 00536 they were loaded. */ 00537 if (!d->pending_close.containsRef( wrap )) { 00538 if (!deleted_one) 00539 /* Only diagnose, if we really haven't deleted anything. */ 00540 // kdDebug(150) << "try to dlclose " << wrap->name << ": not yet" << endl; 00541 break; 00542 } 00543 00544 // kdDebug(150) << "try to dlclose " << wrap->name << ": yes, done." << endl; 00545 00546 if ( !deleted_one ) { 00547 /* Only do the hack once in this loop. 00548 WABA: *HACK* 00549 We need to make sure to clear the clipboard before unloading a DSO 00550 because the DSO could have defined an object derived from QMimeSource 00551 and placed that on the clipboard. */ 00552 /*kapp->clipboard()->clear();*/ 00553 00554 /* Well.. let's do something more subtle... convert the clipboard context 00555 to text. That should be safe as it only uses objects defined by Qt. */ 00556 if( kapp->clipboard()->ownsSelection()) { 00557 kapp->clipboard()->setText( 00558 kapp->clipboard()->text( QClipboard::Selection ), QClipboard::Selection ); 00559 } 00560 if( kapp->clipboard()->ownsClipboard()) { 00561 kapp->clipboard()->setText( 00562 kapp->clipboard()->text( QClipboard::Clipboard ), QClipboard::Clipboard ); 00563 } 00564 } 00565 00566 deleted_one = true; 00567 lt_dlclose(wrap->handle); 00568 d->pending_close.removeRef(wrap); 00569 /* loaded_stack is AutoDelete, so wrap is freed */ 00570 d->loaded_stack.remove(); 00571 } 00572 } 00573 00574 void KLibLoader::virtual_hook( int, void* ) 00575 { /*BASE::virtual_hook( id, data );*/ } 00576 00577 void KLibFactory::virtual_hook( int, void* ) 00578 { /*BASE::virtual_hook( id, data );*/ } 00579 00580 #include "klibloader.moc"