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 #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 };
00057 
00058 class PosSortedPtrList : public QPtrList<KArchiveFile> {
00059 protected:
00060     int compareItems( QPtrCollection::Item i1,
00061                       QPtrCollection::Item i2 )
00062     {
00063         int pos1 = static_cast<KArchiveFile*>( i1 )->position();
00064         int pos2 = static_cast<KArchiveFile*>( i2 )->position();
00065         return ( pos1 - pos2 );
00066     }
00067 };
00068 
00069 
00073 
00074 KArchive::KArchive( QIODevice * dev )
00075 {
00076     d = new KArchivePrivate;
00077     d->rootDir = 0;
00078     m_dev = dev;
00079     m_open = false;
00080 }
00081 
00082 KArchive::~KArchive()
00083 {
00084     if ( m_open )
00085         close();
00086     delete d->rootDir;
00087     delete d;
00088 }
00089 
00090 bool KArchive::open( int mode )
00091 {
00092     if ( m_dev && !m_dev->open( mode ) )
00093         return false;
00094 
00095     if ( m_open )
00096         close();
00097 
00098     m_mode = mode;
00099     m_open = true;
00100 
00101     Q_ASSERT( d->rootDir == 0L );
00102     d->rootDir = 0L;
00103 
00104     return openArchive( mode );
00105 }
00106 
00107 void KArchive::close()
00108 {
00109     if ( !m_open )
00110         return;
00111     // moved by holger to allow kzip to write the zip central dir
00112     // to the file in closeArchive()
00113     closeArchive();
00114 
00115     if ( m_dev )
00116         m_dev->close();
00117 
00118     delete d->rootDir;
00119     d->rootDir = 0;
00120     m_open = false;
00121 }
00122 
00123 const KArchiveDirectory* KArchive::directory() const
00124 {
00125     // rootDir isn't const so that parsing-on-demand is possible
00126     return const_cast<KArchive *>(this)->rootDir();
00127 }
00128 
00129 
00130 bool KArchive::addLocalFile( const QString& fileName, const QString& destName )
00131 {
00132     QFileInfo fileInfo( fileName );
00133     if ( !fileInfo.isFile() && !fileInfo.isSymLink() )
00134     {
00135         kdWarning() << "KArchive::addLocalFile " << fileName << " doesn't exist or is not a regular file." << endl;
00136         return false;
00137     }
00138 
00139     KDE_struct_stat fi;
00140     if (KDE_lstat(QFile::encodeName(fileName),&fi) == -1) {
00141         kdWarning() << "KArchive::addLocalFile stating " << fileName
00142             << " failed: " << strerror(errno) << endl;
00143         return false;
00144     }
00145 
00146     if (fileInfo.isSymLink()) {
00147         return writeSymLink(destName, fileInfo.readLink(), fileInfo.owner(),
00148                 fileInfo.group(), fi.st_mode, fi.st_atime, fi.st_mtime,
00149                 fi.st_ctime);
00150     }/*end if*/
00151 
00152     uint size = fileInfo.size();
00153 
00154     // the file must be opened before prepareWriting is called, otherwise
00155     // if the opening fails, no content will follow the already written
00156     // header and the tar file is effectively f*cked up
00157     QFile file( fileName );
00158     if ( !file.open( IO_ReadOnly ) )
00159     {
00160         kdWarning() << "KArchive::addLocalFile couldn't open file " << fileName << endl;
00161         return false;
00162     }
00163 
00164     if ( !prepareWriting( destName, fileInfo.owner(), fileInfo.group(), size,
00165             fi.st_mode, fi.st_atime, fi.st_mtime, fi.st_ctime ) )
00166     {
00167         kdWarning() << "KArchive::addLocalFile prepareWriting " << destName << " failed" << endl;
00168         return false;
00169     }
00170 
00171     // Read and write data in chunks to minimize memory usage
00172     QByteArray array(8*1024);
00173     int n;
00174     uint total = 0;
00175     while ( ( n = file.readBlock( array.data(), array.size() ) ) > 0 )
00176     {
00177         if ( !writeData( array.data(), n ) )
00178         {
00179             kdWarning() << "KArchive::addLocalFile writeData failed" << endl;
00180             return false;
00181         }
00182         total += n;
00183     }
00184     Q_ASSERT( total == size );
00185 
00186     if ( !doneWriting( size ) )
00187     {
00188         kdWarning() << "KArchive::addLocalFile doneWriting failed" << endl;
00189         return false;
00190     }
00191     return true;
00192 }
00193 
00194 bool KArchive::addLocalDirectory( const QString& path, const QString& destName )
00195 {
00196     QString dot = ".";
00197     QString dotdot = "..";
00198     QDir dir( path );
00199     if ( !dir.exists() )
00200         return false;
00201     QStringList files = dir.entryList();
00202     for ( QStringList::Iterator it = files.begin(); it != files.end(); ++it )
00203     {
00204         if ( *it != dot && *it != dotdot )
00205         {
00206             QString fileName = path + "/" + *it;
00207 //            kdDebug() << "storing " << fileName << endl;
00208             QString dest = destName.isEmpty() ? *it : (destName + "/" + *it);
00209             QFileInfo fileInfo( fileName );
00210 
00211             if ( fileInfo.isFile() || fileInfo.isSymLink() )
00212                 addLocalFile( fileName, dest );
00213             else if ( fileInfo.isDir() )
00214                 addLocalDirectory( fileName, dest );
00215             // We omit sockets
00216         }
00217     }
00218     return true;
00219 }
00220 
00221 bool KArchive::writeFile( const QString& name, const QString& user, const QString& group, uint size, const char* data )
00222 {
00223     mode_t perm = 0100644;
00224     time_t the_time = time(0);
00225     return writeFile(name,user,group,size,perm,the_time,the_time,the_time,data);
00226 }
00227 
00228 bool KArchive::prepareWriting( const QString& name, const QString& user,
00229                 const QString& group, uint size, mode_t perm,
00230                 time_t atime, time_t mtime, time_t ctime ) {
00231   PrepareWritingParams params;
00232   params.name = &name;
00233   params.user = &user;
00234   params.group = &group;
00235   params.size = size;
00236   params.perm = perm;
00237   params.atime = atime;
00238   params.mtime = mtime;
00239   params.ctime = ctime;
00240   virtual_hook(VIRTUAL_PREPARE_WRITING,&params);
00241   return params.retval;
00242 }
00243 
00244 bool KArchive::prepareWriting_impl(const QString &name, const QString &user,
00245                 const QString &group, uint size, mode_t /*perm*/,
00246                 time_t /*atime*/, time_t /*mtime*/, time_t /*ctime*/ ) {
00247   kdWarning(7040) << "New prepareWriting API not implemented in this class." << endl
00248         << "Falling back to old API (metadata information will be lost)" << endl;
00249   return prepareWriting(name,user,group,size);
00250 }
00251 
00252 bool KArchive::writeFile( const QString& name, const QString& user,
00253                 const QString& group, uint size, mode_t perm,
00254                 time_t atime, time_t mtime, time_t ctime,
00255                 const char* data ) {
00256   WriteFileParams params;
00257   params.name = &name;
00258   params.user = &user;
00259   params.group = &group;
00260   params.size = size;
00261   params.perm = perm;
00262   params.atime = atime;
00263   params.mtime = mtime;
00264   params.ctime = ctime;
00265   params.data = data;
00266   virtual_hook(VIRTUAL_WRITE_FILE,&params);
00267   return params.retval;
00268 }
00269 
00270 bool KArchive::writeFile_impl( const QString& name, const QString& user,
00271                 const QString& group, uint size, mode_t perm,
00272                 time_t atime, time_t mtime, time_t ctime,
00273                 const char* data ) {
00274 
00275     if ( !prepareWriting( name, user, group, size, perm, atime, mtime, ctime ) )
00276     {
00277         kdWarning() << "KArchive::writeFile prepareWriting failed" << endl;
00278         return false;
00279     }
00280 
00281     // Write data
00282     // Note: if data is 0L, don't call writeBlock, it would terminate the KFilterDev
00283     if ( data && size && !writeData( data, size ) )
00284     {
00285         kdWarning() << "KArchive::writeFile writeData failed" << endl;
00286         return false;
00287     }
00288 
00289     if ( !doneWriting( size ) )
00290     {
00291         kdWarning() << "KArchive::writeFile doneWriting failed" << endl;
00292         return false;
00293     }
00294     return true;
00295 }
00296 
00297 bool KArchive::writeDir(const QString& name, const QString& user,
00298                 const QString& group, mode_t perm,
00299                 time_t atime, time_t mtime, time_t ctime) {
00300   WriteDirParams params;
00301   params.name = &name;
00302   params.user = &user;
00303   params.group = &group;
00304   params.perm = perm;
00305   params.atime = atime;
00306   params.mtime = mtime;
00307   params.ctime = ctime;
00308   virtual_hook(VIRTUAL_WRITE_DIR,&params);
00309   return params.retval;
00310 }
00311 
00312 bool KArchive::writeDir_impl(const QString &name, const QString &user,
00313                 const QString &group, mode_t /*perm*/,
00314                 time_t /*atime*/, time_t /*mtime*/, time_t /*ctime*/ ) {
00315   kdWarning(7040) << "New writeDir API not implemented in this class." << endl
00316         << "Falling back to old API (metadata information will be lost)" << endl;
00317   return writeDir(name,user,group);
00318 }
00319 
00320 bool KArchive::writeSymLink(const QString &name, const QString &target,
00321                 const QString &user, const QString &group,
00322                 mode_t perm, time_t atime, time_t mtime, time_t ctime) {
00323   WriteSymlinkParams params;
00324   params.name = &name;
00325   params.target = &target;
00326   params.user = &user;
00327   params.group = &group;
00328   params.perm = perm;
00329   params.atime = atime;
00330   params.mtime = mtime;
00331   params.ctime = ctime;
00332   virtual_hook(VIRTUAL_WRITE_SYMLINK,&params);
00333   return params.retval;
00334 }
00335 
00336 bool KArchive::writeSymLink_impl(const QString &/*name*/,const QString &/*target*/,
00337                 const QString &/*user*/, const QString &/*group*/,
00338                 mode_t /*perm*/, time_t /*atime*/, time_t /*mtime*/,
00339                 time_t /*ctime*/) {
00340   kdWarning(7040) << "writeSymLink not implemented in this class." << endl
00341         << "No fallback available." << endl;
00342   // FIXME: better return true here for compatibility with KDE < 3.2
00343   return false;
00344 }
00345 
00346 bool KArchive::writeData( const char* data, uint size )
00347 {
00348     WriteDataParams params;
00349     params.data = data;
00350     params.size = size;
00351     virtual_hook( VIRTUAL_WRITE_DATA, &params );
00352     return params.retval;
00353 }
00354 
00355 bool KArchive::writeData_impl( const char* data, uint size )
00356 {
00357     Q_ASSERT( device() );
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.4.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed May 4 06:55:15 2005 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003