karchive.cpp

00001 /* This file is part of the KDE libraries
00002    Copyright (C) 2000 David Faure <faure@kde.org>
00003    Copyright (C) 2003 Leo Savernik <l.savernik@aon.at>
00004 
00005    Moved from ktar.cpp by Roberto Teixeira <maragato@kde.org>
00006 
00007    This library is free software; you can redistribute it and/or
00008    modify it under the terms of the GNU Library General Public
00009    License version 2 as published by the Free Software Foundation.
00010 
00011    This library is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Library General Public License for more details.
00015 
00016    You should have received a copy of the GNU Library General Public License
00017    along with this library; see the file COPYING.LIB.  If not, write to
00018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019    Boston, MA 02110-1301, USA.
00020 */
00021 
00022 #include <stdio.h>
00023 #include <stdlib.h>
00024 #include <time.h>
00025 #include <unistd.h>
00026 #include <errno.h>
00027 #include <grp.h>
00028 #include <pwd.h>
00029 #include <assert.h>
00030 #include <sys/types.h>
00031 #include <sys/stat.h>
00032 
00033 #include <qptrlist.h>
00034 #include <qptrstack.h>
00035 #include <qvaluestack.h>
00036 #include <qmap.h>
00037 #include <qcstring.h>
00038 #include <qdir.h>
00039 #include <qfile.h>
00040 
00041 #include <kdebug.h>
00042 #include <kfilterdev.h>
00043 #include <kfilterbase.h>
00044 #include <kde_file.h>
00045 
00046 #include "karchive.h"
00047 #include "klimitediodevice.h"
00048 
00049 template class QDict<KArchiveEntry>;
00050 
00051 
00052 class KArchive::KArchivePrivate
00053 {
00054 public:
00055     KArchiveDirectory* rootDir;
00056     bool closeSucceeded;
00057 };
00058 
00059 class PosSortedPtrList : public QPtrList<KArchiveFile> {
00060 protected:
00061     int compareItems( QPtrCollection::Item i1,
00062                       QPtrCollection::Item i2 )
00063     {
00064         int pos1 = static_cast<KArchiveFile*>( i1 )->position();
00065         int pos2 = static_cast<KArchiveFile*>( i2 )->position();
00066         return ( pos1 - pos2 );
00067     }
00068 };
00069 
00070 
00074 
00075 KArchive::KArchive( QIODevice * dev )
00076 {
00077     d = new KArchivePrivate;
00078     d->rootDir = 0;
00079     m_dev = dev;
00080     m_open = false;
00081 }
00082 
00083 KArchive::~KArchive()
00084 {
00085     if ( m_open )
00086         close();
00087     delete d->rootDir;
00088     delete d;
00089 }
00090 
00091 bool KArchive::open( int mode )
00092 {
00093     if ( m_dev && !m_dev->open( mode ) )
00094         return false;
00095 
00096     if ( m_open )
00097         close();
00098 
00099     m_mode = mode;
00100     m_open = true;
00101 
00102     Q_ASSERT( d->rootDir == 0L );
00103     d->rootDir = 0L;
00104 
00105     return openArchive( mode );
00106 }
00107 
00108 void KArchive::close()
00109 {
00110     if ( !m_open )
00111         return;
00112     // moved by holger to allow kzip to write the zip central dir
00113     // to the file in closeArchive()
00114     d->closeSucceeded = closeArchive();
00115 
00116     if ( m_dev )
00117         m_dev->close();
00118 
00119     delete d->rootDir;
00120     d->rootDir = 0;
00121     m_open = false;
00122 }
00123 
00124 bool KArchive::closeSucceeded() const
00125 {
00126     return d->closeSucceeded;
00127 }
00128 
00129 const KArchiveDirectory* KArchive::directory() const
00130 {
00131     // rootDir isn't const so that parsing-on-demand is possible
00132     return const_cast<KArchive *>(this)->rootDir();
00133 }
00134 
00135 
00136 bool KArchive::addLocalFile( const QString& fileName, const QString& destName )
00137 {
00138     QFileInfo fileInfo( fileName );
00139     if ( !fileInfo.isFile() && !fileInfo.isSymLink() )
00140     {
00141         kdWarning() << "KArchive::addLocalFile " << fileName << " doesn't exist or is not a regular file." << endl;
00142         return false;
00143     }
00144 
00145     KDE_struct_stat fi;
00146     if (KDE_lstat(QFile::encodeName(fileName),&fi) == -1) {
00147         kdWarning() << "KArchive::addLocalFile stating " << fileName
00148             << " failed: " << strerror(errno) << endl;
00149         return false;
00150     }
00151 
00152     if (fileInfo.isSymLink()) {
00153         return writeSymLink(destName, fileInfo.readLink(), fileInfo.owner(),
00154                 fileInfo.group(), fi.st_mode, fi.st_atime, fi.st_mtime,
00155                 fi.st_ctime);
00156     }/*end if*/
00157 
00158     uint size = fileInfo.size();
00159 
00160     // the file must be opened before prepareWriting is called, otherwise
00161     // if the opening fails, no content will follow the already written
00162     // header and the tar file is effectively f*cked up
00163     QFile file( fileName );
00164     if ( !file.open( IO_ReadOnly ) )
00165     {
00166         kdWarning() << "KArchive::addLocalFile couldn't open file " << fileName << endl;
00167         return false;
00168     }
00169 
00170     if ( !prepareWriting( destName, fileInfo.owner(), fileInfo.group(), size,
00171             fi.st_mode, fi.st_atime, fi.st_mtime, fi.st_ctime ) )
00172     {
00173         kdWarning() << "KArchive::addLocalFile prepareWriting " << destName << " failed" << endl;
00174         return false;
00175     }
00176 
00177     // Read and write data in chunks to minimize memory usage
00178     QByteArray array(8*1024);
00179     int n;
00180     uint total = 0;
00181     while ( ( n = file.readBlock( array.data(), array.size() ) ) > 0 )
00182     {
00183         if ( !writeData( array.data(), n ) )
00184         {
00185             kdWarning() << "KArchive::addLocalFile writeData failed" << endl;
00186             return false;
00187         }
00188         total += n;
00189     }
00190     Q_ASSERT( total == size );
00191 
00192     if ( !doneWriting( size ) )
00193     {
00194         kdWarning() << "KArchive::addLocalFile doneWriting failed" << endl;
00195         return false;
00196     }
00197     return true;
00198 }
00199 
00200 bool KArchive::addLocalDirectory( const QString& path, const QString& destName )
00201 {
00202     QString dot = ".";
00203     QString dotdot = "..";
00204     QDir dir( path );
00205     if ( !dir.exists() )
00206         return false;
00207     QStringList files = dir.entryList();
00208     for ( QStringList::Iterator it = files.begin(); it != files.end(); ++it )
00209     {
00210         if ( *it != dot && *it != dotdot )
00211         {
00212             QString fileName = path + "/" + *it;
00213 //            kdDebug() << "storing " << fileName << endl;
00214             QString dest = destName.isEmpty() ? *it : (destName + "/" + *it);
00215             QFileInfo fileInfo( fileName );
00216 
00217             if ( fileInfo.isFile() || fileInfo.isSymLink() )
00218                 addLocalFile( fileName, dest );
00219             else if ( fileInfo.isDir() )
00220                 addLocalDirectory( fileName, dest );
00221             // We omit sockets
00222         }
00223     }
00224     return true;
00225 }
00226 
00227 bool KArchive::writeFile( const QString& name, const QString& user, const QString& group, uint size, const char* data )
00228 {
00229     mode_t perm = 0100644;
00230     time_t the_time = time(0);
00231     return writeFile(name,user,group,size,perm,the_time,the_time,the_time,data);
00232 }
00233 
00234 bool KArchive::prepareWriting( const QString& name, const QString& user,
00235                 const QString& group, uint size, mode_t perm,
00236                 time_t atime, time_t mtime, time_t ctime ) {
00237   PrepareWritingParams params;
00238   params.name = &name;
00239   params.user = &user;
00240   params.group = &group;
00241   params.size = size;
00242   params.perm = perm;
00243   params.atime = atime;
00244   params.mtime = mtime;
00245   params.ctime = ctime;
00246   virtual_hook(VIRTUAL_PREPARE_WRITING,&params);
00247   return params.retval;
00248 }
00249 
00250 bool KArchive::prepareWriting_impl(const QString &name, const QString &user,
00251                 const QString &group, uint size, mode_t /*perm*/,
00252                 time_t /*atime*/, time_t /*mtime*/, time_t /*ctime*/ ) {
00253   kdWarning(7040) << "New prepareWriting API not implemented in this class." << endl
00254         << "Falling back to old API (metadata information will be lost)" << endl;
00255   return prepareWriting(name,user,group,size);
00256 }
00257 
00258 bool KArchive::writeFile( const QString& name, const QString& user,
00259                 const QString& group, uint size, mode_t perm,
00260                 time_t atime, time_t mtime, time_t ctime,
00261                 const char* data ) {
00262   WriteFileParams params;
00263   params.name = &name;
00264   params.user = &user;
00265   params.group = &group;
00266   params.size = size;
00267   params.perm = perm;
00268   params.atime = atime;
00269   params.mtime = mtime;
00270   params.ctime = ctime;
00271   params.data = data;
00272   virtual_hook(VIRTUAL_WRITE_FILE,&params);
00273   return params.retval;
00274 }
00275 
00276 bool KArchive::writeFile_impl( const QString& name, const QString& user,
00277                 const QString& group, uint size, mode_t perm,
00278                 time_t atime, time_t mtime, time_t ctime,
00279                 const char* data ) {
00280 
00281     if ( !prepareWriting( name, user, group, size, perm, atime, mtime, ctime ) )
00282     {
00283         kdWarning() << "KArchive::writeFile prepareWriting failed" << endl;
00284         return false;
00285     }
00286 
00287     // Write data
00288     // Note: if data is 0L, don't call writeBlock, it would terminate the KFilterDev
00289     if ( data && size && !writeData( data, size ) )
00290     {
00291         kdWarning() << "KArchive::writeFile writeData failed" << endl;
00292         return false;
00293     }
00294 
00295     if ( !doneWriting( size ) )
00296     {
00297         kdWarning() << "KArchive::writeFile doneWriting failed" << endl;
00298         return false;
00299     }
00300     return true;
00301 }
00302 
00303 bool KArchive::writeDir(const QString& name, const QString& user,
00304                 const QString& group, mode_t perm,
00305                 time_t atime, time_t mtime, time_t ctime) {
00306   WriteDirParams params;
00307   params.name = &name;
00308   params.user = &user;
00309   params.group = &group;
00310   params.perm = perm;
00311   params.atime = atime;
00312   params.mtime = mtime;
00313   params.ctime = ctime;
00314   virtual_hook(VIRTUAL_WRITE_DIR,&params);
00315   return params.retval;
00316 }
00317 
00318 bool KArchive::writeDir_impl(const QString &name, const QString &user,
00319                 const QString &group, mode_t /*perm*/,
00320                 time_t /*atime*/, time_t /*mtime*/, time_t /*ctime*/ ) {
00321   kdWarning(7040) << "New writeDir API not implemented in this class." << endl
00322         << "Falling back to old API (metadata information will be lost)" << endl;
00323   return writeDir(name,user,group);
00324 }
00325 
00326 bool KArchive::writeSymLink(const QString &name, const QString &target,
00327                 const QString &user, const QString &group,
00328                 mode_t perm, time_t atime, time_t mtime, time_t ctime) {
00329   WriteSymlinkParams params;
00330   params.name = &name;
00331   params.target = &target;
00332   params.user = &user;
00333   params.group = &group;
00334   params.perm = perm;
00335   params.atime = atime;
00336   params.mtime = mtime;
00337   params.ctime = ctime;
00338   virtual_hook(VIRTUAL_WRITE_SYMLINK,&params);
00339   return params.retval;
00340 }
00341 
00342 bool KArchive::writeSymLink_impl(const QString &/*name*/,const QString &/*target*/,
00343                 const QString &/*user*/, const QString &/*group*/,
00344                 mode_t /*perm*/, time_t /*atime*/, time_t /*mtime*/,
00345                 time_t /*ctime*/) {
00346   kdWarning(7040) << "writeSymLink not implemented in this class." << endl
00347         << "No fallback available." << endl;
00348   // FIXME: better return true here for compatibility with KDE < 3.2
00349   return false;
00350 }
00351 
00352 bool KArchive::writeData( const char* data, uint size )
00353 {
00354     WriteDataParams params;
00355     params.data = data;
00356     params.size = size;
00357     virtual_hook( VIRTUAL_WRITE_DATA, &params );
00358     return params.retval;
00359 }
00360 
00361 bool KArchive::writeData_impl( const char* data, uint size )
00362 {
00363     Q_ASSERT( device() );
00364     return device()->writeBlock( data, size ) == (Q_LONG)size;
00365 }
00366 
00367 KArchiveDirectory * KArchive::rootDir()
00368 {
00369     if ( !d->rootDir )
00370     {
00371         //kdDebug() << "Making root dir " << endl;
00372         struct passwd* pw =  getpwuid( getuid() );
00373         struct group* grp = getgrgid( getgid() );
00374         QString username = pw ? QFile::decodeName(pw->pw_name) : QString::number( getuid() );
00375         QString groupname = grp ? QFile::decodeName(grp->gr_name) : QString::number( getgid() );
00376 
00377         d->rootDir = new KArchiveDirectory( this, QString::fromLatin1("/"), (int)(0777 + S_IFDIR), 0, username, groupname, QString::null );
00378     }
00379     return d->rootDir;
00380 }
00381 
00382 KArchiveDirectory * KArchive::findOrCreate( const QString & path )
00383 {
00384     //kdDebug() << "KArchive::findOrCreate " << path << endl;
00385     if ( path.isEmpty() || path == "/" || path == "." ) // root dir => found
00386     {
00387         //kdDebug() << "KArchive::findOrCreate returning rootdir" << endl;
00388         return rootDir();
00389     }
00390     // Important note : for tar files containing absolute paths
00391     // (i.e. beginning with "/"), this means the leading "/" will
00392     // be removed (no KDirectory for it), which is exactly the way
00393     // the "tar" program works (though it displays a warning about it)
00394     // See also KArchiveDirectory::entry().
00395 
00396     // Already created ? => found
00397     KArchiveEntry* ent = rootDir()->entry( path );
00398     if ( ent )
00399     {
00400         if ( ent->isDirectory() )
00401             //kdDebug() << "KArchive::findOrCreate found it" << endl;
00402             return (KArchiveDirectory *) ent;
00403         else
00404             kdWarning() << "Found " << path << " but it's not a directory" << endl;
00405     }
00406 
00407     // Otherwise go up and try again
00408     int pos = path.findRev( '/' );
00409     KArchiveDirectory * parent;
00410     QString dirname;
00411     if ( pos == -1 ) // no more slash => create in root dir
00412     {
00413         parent =  rootDir();
00414         dirname = path;
00415     }
00416     else
00417     {
00418         QString left = path.left( pos );
00419         dirname = path.mid( pos + 1 );
00420         parent = findOrCreate( left ); // recursive call... until we find an existing dir.
00421     }
00422 
00423     //kdDebug() << "KTar : found parent " << parent->name() << " adding " << dirname << " to ensure " << path << endl;
00424     // Found -> add the missing piece
00425     KArchiveDirectory * e = new KArchiveDirectory( this, dirname, d->rootDir->permissions(),
00426                                                    d->rootDir->date(), d->rootDir->user(),
00427                                                    d->rootDir->group(), QString::null );
00428     parent->addEntry( e );
00429     return e; // now a directory to <path> exists
00430 }
00431 
00432 void KArchive::setDevice( QIODevice * dev )
00433 {
00434     m_dev = dev;
00435 }
00436 
00437 void KArchive::setRootDir( KArchiveDirectory *rootDir )
00438 {
00439     Q_ASSERT( !d->rootDir ); // Call setRootDir only once during parsing please ;)
00440     d->rootDir = rootDir;
00441 }
00442 
00446 KArchiveEntry::KArchiveEntry( KArchive* t, const QString& name, int access, int date,
00447                       const QString& user, const QString& group, const
00448                       QString& symlink)
00449 {
00450   m_name = name;
00451   m_access = access;
00452   m_date = date;
00453   m_user = user;
00454   m_group = group;
00455   m_symlink = symlink;
00456   m_archive = t;
00457 
00458 }
00459 
00460 QDateTime KArchiveEntry::datetime() const
00461 {
00462   QDateTime d;
00463   d.setTime_t( m_date );
00464   return d;
00465 }
00466 
00470 
00471 KArchiveFile::KArchiveFile( KArchive* t, const QString& name, int access, int date,
00472                     const QString& user, const QString& group,
00473                     const QString & symlink,
00474                     int pos, int size )
00475   : KArchiveEntry( t, name, access, date, user, group, symlink )
00476 {
00477   m_pos = pos;
00478   m_size = size;
00479 }
00480 
00481 int KArchiveFile::position() const
00482 {
00483   return m_pos;
00484 }
00485 
00486 int KArchiveFile::size() const
00487 {
00488   return m_size;
00489 }
00490 
00491 QByteArray KArchiveFile::data() const
00492 {
00493   archive()->device()->at( m_pos );
00494 
00495   // Read content
00496   QByteArray arr( m_size );
00497   if ( m_size )
00498   {
00499     assert( arr.data() );
00500     int n = archive()->device()->readBlock( arr.data(), m_size );
00501     if ( n != m_size )
00502       arr.resize( n );
00503   }
00504   return arr;
00505 }
00506 
00507 // ** This should be a virtual method, and this code should be in ktar.cpp
00508 QIODevice *KArchiveFile::device() const
00509 {
00510     return new KLimitedIODevice( archive()->device(), m_pos, m_size );
00511 }
00512 
00513 void KArchiveFile::copyTo(const QString& dest) const
00514 {
00515   QFile f( dest + "/"  + name() );
00516   f.open( IO_ReadWrite | IO_Truncate );
00517   f.writeBlock( data() );
00518   f.close();
00519 }
00520 
00524 
00525 
00526 KArchiveDirectory::KArchiveDirectory( KArchive* t, const QString& name, int access,
00527                               int date,
00528                               const QString& user, const QString& group,
00529                               const QString &symlink)
00530   : KArchiveEntry( t, name, access, date, user, group, symlink )
00531 {
00532   m_entries.setAutoDelete( true );
00533 }
00534 
00535 QStringList KArchiveDirectory::entries() const
00536 {
00537   QStringList l;
00538 
00539   QDictIterator<KArchiveEntry> it( m_entries );
00540   for( ; it.current(); ++it )
00541     l.append( it.currentKey() );
00542 
00543   return l;
00544 }
00545 
00546 KArchiveEntry* KArchiveDirectory::entry( QString name )
00547   // not "const QString & name" since we want a local copy
00548   // (to remove leading slash if any)
00549 {
00550   int pos = name.find( '/' );
00551   if ( pos == 0 ) // ouch absolute path (see also KArchive::findOrCreate)
00552   {
00553     if (name.length()>1)
00554     {
00555       name = name.mid( 1 ); // remove leading slash
00556       pos = name.find( '/' ); // look again
00557     }
00558     else // "/"
00559       return this;
00560   }
00561   // trailing slash ? -> remove
00562   if ( pos != -1 && pos == (int)name.length()-1 )
00563   {
00564     name = name.left( pos );
00565     pos = name.find( '/' ); // look again
00566   }
00567   if ( pos != -1 )
00568   {
00569     QString left = name.left( pos );
00570     QString right = name.mid( pos + 1 );
00571 
00572     //kdDebug() << "KArchiveDirectory::entry left=" << left << " right=" << right << endl;
00573 
00574     KArchiveEntry* e = m_entries[ left ];
00575     if ( !e || !e->isDirectory() )
00576       return 0;
00577     return ((KArchiveDirectory*)e)->entry( right );
00578   }
00579 
00580   return m_entries[ name ];
00581 }
00582 
00583 const KArchiveEntry* KArchiveDirectory::entry( QString name ) const
00584 {
00585   return ((KArchiveDirectory*)this)->entry( name );
00586 }
00587 
00588 void KArchiveDirectory::addEntry( KArchiveEntry* entry )
00589 {
00590   Q_ASSERT( !entry->name().isEmpty() );
00591   if( m_entries[ entry->name() ] ) {
00592       kdWarning() << "KArchiveDirectory::addEntry: directory " << name()
00593                   << " has entry " << entry->name() << " already" << endl;
00594   }
00595   m_entries.insert( entry->name(), entry );
00596 }
00597 
00598 void KArchiveDirectory::copyTo(const QString& dest, bool recursiveCopy ) const
00599 {
00600   QDir root;
00601 
00602   PosSortedPtrList fileList;
00603   QMap<int, QString> fileToDir;
00604 
00605   QStringList::Iterator it;
00606 
00607   // placeholders for iterated items
00608   KArchiveDirectory* curDir;
00609   QString curDirName;
00610 
00611   QStringList dirEntries;
00612   KArchiveEntry* curEntry;
00613   KArchiveFile* curFile;
00614 
00615 
00616   QPtrStack<KArchiveDirectory> dirStack;
00617   QValueStack<QString> dirNameStack;
00618 
00619   dirStack.push( this );     // init stack at current directory
00620   dirNameStack.push( dest ); // ... with given path
00621   do {
00622     curDir = dirStack.pop();
00623     curDirName = dirNameStack.pop();
00624     root.mkdir(curDirName);
00625 
00626     dirEntries = curDir->entries();
00627     for ( it = dirEntries.begin(); it != dirEntries.end(); ++it ) {
00628       curEntry = curDir->entry(*it);
00629       if ( curEntry->isFile() ) {
00630         curFile = dynamic_cast<KArchiveFile*>( curEntry );
00631     if (curFile) {
00632           fileList.append( curFile );
00633           fileToDir.insert( curFile->position(), curDirName );
00634         }
00635       }
00636 
00637       if ( curEntry->isDirectory() )
00638         if ( recursiveCopy ) {
00639           KArchiveDirectory *ad = dynamic_cast<KArchiveDirectory*>( curEntry );
00640           if (ad) {
00641             dirStack.push( ad );
00642             dirNameStack.push( curDirName + "/" + curEntry->name() );
00643           }
00644         }
00645     }
00646   } while (!dirStack.isEmpty());
00647 
00648   fileList.sort();  // sort on m_pos, so we have a linear access
00649 
00650   KArchiveFile* f;
00651   for ( f = fileList.first(); f; f = fileList.next() ) {
00652     int pos = f->position();
00653     f->copyTo( fileToDir[pos] );
00654   }
00655 }
00656 
00657 void KArchive::virtual_hook( int id, void* data )
00658 {
00659     switch (id) {
00660       case VIRTUAL_WRITE_DATA: {
00661         WriteDataParams* params = reinterpret_cast<WriteDataParams *>(data);
00662         params->retval = writeData_impl( params->data, params->size );
00663         break;
00664       }
00665       case VIRTUAL_WRITE_SYMLINK: {
00666         WriteSymlinkParams *params = reinterpret_cast<WriteSymlinkParams *>(data);
00667         params->retval = writeSymLink_impl(*params->name,*params->target,
00668                 *params->user,*params->group,params->perm,
00669                 params->atime,params->mtime,params->ctime);
00670         break;
00671       }
00672       case VIRTUAL_WRITE_DIR: {
00673         WriteDirParams *params = reinterpret_cast<WriteDirParams *>(data);
00674         params->retval = writeDir_impl(*params->name,*params->user,
00675                 *params->group,params->perm,
00676                 params->atime,params->mtime,params->ctime);
00677         break;
00678       }
00679       case VIRTUAL_WRITE_FILE: {
00680         WriteFileParams *params = reinterpret_cast<WriteFileParams *>(data);
00681         params->retval = writeFile_impl(*params->name,*params->user,
00682                 *params->group,params->size,params->perm,
00683                 params->atime,params->mtime,params->ctime,
00684                     params->data);
00685         break;
00686       }
00687       case VIRTUAL_PREPARE_WRITING: {
00688         PrepareWritingParams *params = reinterpret_cast<PrepareWritingParams *>(data);
00689         params->retval = prepareWriting_impl(*params->name,*params->user,
00690                 *params->group,params->size,params->perm,
00691                 params->atime,params->mtime,params->ctime);
00692         break;
00693       }
00694       default:
00695         /*BASE::virtual_hook( id, data )*/;
00696     }/*end switch*/
00697 }
00698 
00699 void KArchiveEntry::virtual_hook( int, void* )
00700 { /*BASE::virtual_hook( id, data );*/ }
00701 
00702 void KArchiveFile::virtual_hook( int id, void* data )
00703 { KArchiveEntry::virtual_hook( id, data ); }
00704 
00705 void KArchiveDirectory::virtual_hook( int id, void* data )
00706 { KArchiveEntry::virtual_hook( id, data ); }
KDE Home | KDE Accessibility Home | Description of Access Keys