kio Library API Documentation

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