| # kio Library API Documentation
Overview | Class Hierarchy | Classes | Classes (annotated) | Members | Source Files

# ktar.cpp

    00001 /* This file is part of the KDE libraries
    00002    Copyright (C) 2000 David Faure <faure@kde.org>
    00003
    00004    This library is free software; you can redistribute it and/or
    00005    modify it under the terms of the GNU Library General Public
    00006    License version 2 as published by the Free Software Foundation.
    00007
    00008    This library is distributed in the hope that it will be useful,
    00009    but WITHOUT ANY WARRANTY; without even the implied warranty of
    00010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    00011    Library General Public License for more details.
    00012
    00013    You should have received a copy of the GNU Library General Public License
    00014    along with this library; see the file COPYING.LIB.  If not, write to
    00015    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    00016    Boston, MA 02111-1307, USA.
    00017 */
    00018
    00019 //#include <stdio.h>
    00020 #include <stdlib.h> // strtol
    00021 #include <time.h> // time()
    00022 /*#include <unistd.h>
    00023 #include <grp.h>
    00024 #include <pwd.h>*/
    00025 #include <assert.h>
    00026
    00027 #include <qcstring.h>
    00028 #include <qdir.h>
    00029 #include <qfile.h>
    00030 #include <kdebug.h>
    00031 #include <kmimetype.h>
    00032
    00033 #include <kfilterdev.h>
    00034 #include <kfilterbase.h>
    00035
    00036 #include "ktar.h"
    00037
    00041
    00042 class KTar::KTarPrivate
    00043 {
    00044 public:
    00045     KTarPrivate() {}
    00046     QStringList dirList;
    00047 };
    00048
    00049 KTar::KTar( const QString& filename, const QString & _mimetype )
    00050     : KArchive( 0L )
    00051 {
    00052     m_filename = filename;
    00053     d = new KTarPrivate;
    00054     QString mimetype( _mimetype );
    00055     bool forced = true;
    00056     if ( mimetype.isEmpty() )
    00057     {
    00058         if ( QFile::exists( filename ) )
    00059             mimetype = KMimeType::findByFileContent( filename )->name();
    00060         else
    00061             mimetype = KMimeType::findByPath( filename, 0, true )->name();
    00062         kdDebug(7041) << "KTar::KTar mimetype=" << mimetype << endl;
    00063
    00064         // Don't move to prepareDevice - the other constructor theoretically allows ANY filter
    00065         if ( mimetype == "application/x-tgz" || mimetype == "application/x-targz" || // the latter is deprecated but might still be around
    00066              mimetype == "application/x-webarchive" )
    00067             // that's a gzipped tar file, so ask for gzip filter
    00068             mimetype = "application/x-gzip";
    00069         else if ( mimetype == "application/x-tbz" ) // that's a bzipped2 tar file, so ask for bz2 filter
    00070             mimetype = "application/x-bzip2";
    00071         else
    00072         {
    00073             // Something else. Check if it's not really gzip though (e.g. for KOffice docs)
    00074             QFile file( filename );
    00075             if ( file.open( IO_ReadOnly ) )
    00076             {
    00077                 unsigned char firstByte = file.getch();
    00078                 unsigned char secondByte = file.getch();
    00079                 unsigned char thirdByte = file.getch();
    00080                 if ( firstByte == 0037 && secondByte == 0213 )
    00081                     mimetype = "application/x-gzip";
    00082                 else if ( firstByte == 'B' && secondByte == 'Z' && thirdByte == 'h' )
    00083                     mimetype = "application/x-bzip2";
    00084                 else if ( firstByte == 'P' && secondByte == 'K' && thirdByte == 3 )
    00085                 {
    00086                     unsigned char fourthByte = file.getch();
    00087                     if ( fourthByte == 4 )
    00088                         mimetype = "application/x-zip";
    00089                 }
    00090             }
    00091         }
    00092         forced = false;
    00093     }
    00094
    00095     prepareDevice( filename, mimetype, forced );
    00096 }
    00097
    00098 void KTar::prepareDevice( const QString & filename,
    00099                             const QString & mimetype, bool forced )
    00100 {
    00101   if( "application/x-tar" == mimetype )
    00102       setDevice( new QFile( filename ) );
    00103   else
    00104   {
    00105     if( "application/x-gzip" == mimetype
    00106        || "application/x-bzip2" == mimetype)
    00107         forced = true;
    00108
    00109     QIODevice *dev = KFilterDev::deviceForFile( filename, mimetype, forced );
    00110     if( dev )
    00111       setDevice( dev );
    00112   }
    00113 }
    00114
    00115 KTar::KTar( QIODevice * dev )
    00116     : KArchive( dev )
    00117 {
    00118     d = new KTarPrivate;
    00119 }
    00120
    00121 KTar::~KTar()
    00122 {
    00123     // mjarrett: Closes to prevent ~KArchive from aborting w/o device
    00124     if( isOpened() )
    00125         close();
    00126     if ( !m_filename.isEmpty() )
    00127         delete device(); // we created it ourselves
    00128     delete d;
    00129 }
    00130
    00131 void KTar::setOrigFileName( const QCString & fileName )
    00132 {
    00133     if ( !isOpened() || !(mode() & IO_WriteOnly) )
    00134     {
    00135         kdWarning(7041) << "KTar::setOrigFileName: File must be opened for writing first.\n";
    00136         return;
    00137     }
    00138     static_cast<KFilterDev *>(device())->setOrigFileName( fileName );
    00139 }
    00140
    00141
    00142 bool KTar::openArchive( int mode )
    00143 {
    00144     if ( !(mode & IO_ReadOnly) )
    00145         return true;
    00146
    00147     // We'll use the permission and user/group of d->rootDir
    00148     // for any directory we emulate (see findOrCreate)
    00149     //struct stat buf;
    00150     //stat( m_filename, &buf );
    00151
    00152     d->dirList.clear();
    00153     QIODevice* dev = device();
    00154
    00155     // read dir infos
    00156     char buffer[ 0x200 ];
    00157     bool ende = false;
    00158     do
    00159     {
    00160         // Read header
    00161         int n = dev->readBlock( buffer, 0x200 );
    00162         if ( n == 0x200 && buffer[0] != 0 )
    00163         {
    00164             // Make sure this is actually a tar header
    00165             if (strncmp(buffer + 257, "ustar", 5))
    00166             {
    00167                 // The magic isn't there (broken/old tars), but maybe a correct checksum?
    00168                 QCString s;
    00169
    00170                 int check = 0;
    00171                 for( uint j = 0; j < 0x200; ++j )
    00172                     check += buffer[j];
    00173
    00174                 // adjust checksum to count the checksum fields as blanks
    00175                 for( uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++ )
    00176                     check -= buffer[148 + j];
    00177                 check += 8 * ' ';
    00178
    00179                 s.sprintf("%o", check );
    00180
    00181                 // only compare those of the 6 checksum digits that mean something,
    00182                 // because the other digits are filled with all sorts of different chars by different tars ...
    00183                 if( strncmp( buffer + 148 + 6 - s.length(), s.data(), s.length() ) )
    00184                 {
    00185                     kdWarning(7041) << "KTar: invalid TAR file. Header is: " << QCString( buffer+257, 5 ) << endl;
    00186                     return false;
    00187                 }
    00188             }
    00189
    00190             QString name( QString::fromLocal8Bit(buffer) );
    00191
    00192             // If filename is longer than 100 (0x64) chars, then tar uses ././@LongLink (David)
    00193             if ( name == "././@LongLink" )
    00194             {
    00195                 // in this case, here's what happens (according to od -cx !)
    00196                 // 1) the filename is stored in the next 512b buffer
    00197                 n = dev->readBlock( buffer, 0x200 );
    00198                 if ( n == 0x200 && buffer[0] != 0 )
    00199                 {
    00200                     name = QString::fromLocal8Bit(buffer);
    00201                     // 2) read yet another 512b buffer, for permissions, time, size ...
    00202                     n = dev->readBlock( buffer, 0x200 );
    00203                     if (!( n == 0x200 && buffer[0] != 0 ))
    00204                         break;
    00205                 }
    00206                 else
    00207                     break;
    00208             }
    00209
    00210             bool isdir = false;
    00211             QString nm;
    00212
    00213             if ( name.right(1) == "/" )
    00214             {
    00215                 isdir = true;
    00216                 name = name.left( name.length() - 1 );
    00217             }
    00218
    00219             int pos = name.findRev( '/' );
    00220             if ( pos == -1 )
    00221                 nm = name;
    00222             else
    00223                 nm = name.mid( pos + 1 );
    00224
    00225             // read access
    00226             buffer[ 0x6b ] = 0;
    00227             char *dummy;
    00228             const char* p = buffer + 0x64;
    00229             while( *p == ' ' ) ++p;
    00230             int access = (int)strtol( p, &dummy, 8 );
    00231
    00232             // read user and group
    00233             QString user( buffer + 0x109 );
    00234             QString group( buffer + 0x129 );
    00235             // read symlink dest (if any)
    00236             QString symlink(buffer + 0x9d );
    00237
    00238             // read time
    00239             buffer[ 0x93 ] = 0;
    00240             p = buffer + 0x88;
    00241             while( *p == ' ' ) ++p;
    00242             int time = (int)strtol( p, &dummy, 8 );
    00243
    00244             // read type flag
    00245             char typeflag = buffer[ 0x9c ];
    00246             // '0' for files, '1' hard link, '2' symlink, '5' for directory (and 'L' for longlink)
    00247             // and 'D' for GNU tar extension DUMPDIR
    00248             if ( typeflag == '1' )
    00249                 isdir = true;
    00250
    00251             bool isDumpDir = false;
    00252             if ( typeflag == 'D' )
    00253             {
    00254                 isdir = false;
    00255                 isDumpDir = true;
    00256             }
    00257             //bool islink = ( typeflag == '1' || typeflag == '2' );
    00258             //kdDebug(7041) << "typeflag=" << typeflag << " islink=" << islink << endl;
    00259
    00260             if (isdir)
    00261                 access |= S_IFDIR; // f*cking broken tar files
    00262
    00263             KArchiveEntry* e;
    00264             if ( isdir )
    00265             {
    00266                 //kdDebug(7041) << "KArchive::open directory " << nm << endl;
    00267                 e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
    00268             }
    00269             else
    00270             {
    00271                 // read size
    00272                 buffer[ 0x88 ] = 0; // was 0x87, but 0x88 fixes BR #26437
    00273                 char *dummy;
    00274                 const char* p = buffer + 0x7c;
    00275                 while( *p == ' ' ) ++p;
    00276                 int size = (int)strtol( p, &dummy, 8 );
    00277
    00278                 // for isDumpDir we will skip the additional info about that dirs contents
    00279                 if ( isDumpDir )
    00280                 {
    00281                     e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
    00282                 }
    00283                 else
    00284                 {
    00285
    00286                     // Let's hack around hard links. Our classes don't support that, so make them symlinks
    00287                     if ( typeflag == '1' )
    00288                     {
    00289                         size = nm.length(); // in any case, we don't want to skip the real size, hence this resetting of size
    00290                         kdDebug(7041) << "HARD LINK, setting size to " << size << endl;
    00291                     }
    00292
    00293                     //kdDebug(7041) << "KArchive::open file " << nm << " size=" << size << endl;
    00294
    00295                     e = new KArchiveFile( this, nm, access, time, user, group, symlink,
    00296                                           dev->at(), size );
    00297                 }
    00298
    00299                 // Skip contents + align bytes
    00300                 int rest = size % 0x200;
    00301                 int skip = size + (rest ? 0x200 - rest : 0);
    00302                 //kdDebug(7041) << "KArchive::open, at()=" << dev->at() << " rest=" << rest << " skipping " << skip << endl;
    00303                 if (! dev->at( dev->at() + skip ) )
    00304                     kdWarning(7041) << "KArchive::open skipping " << skip << " failed" << endl;
    00305             }
    00306
    00307             if ( pos == -1 )
    00308             {
    00309                 if ( nm == "." ) // special case
    00310                 {
    00311                     Q_ASSERT( isdir );
    00312                     if ( isdir )
    00313                         setRootDir( static_cast<KArchiveDirectory *>( e ) );
    00314                 }
    00315                 else
    00316                     rootDir()->addEntry( e );
    00317             }
    00318             else
    00319             {
    00320                 // In some tar files we can find dir/./file => call cleanDirPath
    00321                 QString path = QDir::cleanDirPath( name.left( pos ) );
    00322                 // Ensure container directory exists, create otherwise
    00323                 KArchiveDirectory * d = findOrCreate( path );
    00324                 d->addEntry( e );
    00325             }
    00326         }
    00327         else
    00328         {
    00329             //qDebug("Terminating. Read %d bytes, first one is %d", n, buffer[0]);
    00330             ende = true;
    00331         }
    00332     } while( !ende );
    00333     return true;
    00334 }
    00335
    00336 bool KTar::closeArchive()
    00337 {
    00338     d->dirList.clear();
    00339     return true;
    00340 }
    00341
    00342 bool KTar::writeDir( const QString& name, const QString& user, const QString& group )
    00343 {
    00344     if ( !isOpened() )
    00345     {
    00346         kdWarning(7041) << "KTar::writeDir: You must open the tar file before writing to it\n";
    00347         return false;
    00348     }
    00349
    00350     if ( !(mode() & IO_WriteOnly) )
    00351     {
    00352         kdWarning(7041) << "KTar::writeDir: You must open the tar file for writing\n";
    00353         return false;
    00354     }
    00355
    00356     // In some tar files we can find dir/./ => call cleanDirPath
    00357     QString dirName ( QDir::cleanDirPath( name ) );
    00358
    00359     // Need trailing '/'
    00360     if ( dirName.right(1) != "/" )
    00361         dirName += "/";
    00362
    00363     if ( d->dirList.contains( dirName ) )
    00364         return true; // already there
    00365
    00366     char buffer[ 0x201 ];
    00367     memset( buffer, 0, 0x200 );
    00368
    00369     // If more than 100 chars, we need to use the LongLink trick
    00370     if ( dirName.length() > 99 )
    00371     {
    00372         strcpy( buffer, "././@LongLink" );
    00373         fillBuffer( buffer, "     0", dirName.length()+1, 'L', user.local8Bit(), group.local8Bit() );
    00374         device()->writeBlock( buffer, 0x200 );
    00375         strncpy( buffer, QFile::encodeName(dirName), 0x200 );
    00376         buffer[0x200] = 0;
    00377         // write long name
    00378         device()->writeBlock( buffer, 0x200 );
    00379         // not even needed to reclear the buffer, tar doesn't do it
    00380     }
    00381     else
    00382     {
    00383         // Write name
    00384         strncpy( buffer, QFile::encodeName(dirName), 0x200 );
    00385         buffer[0x200] = 0;
    00386     }
    00387
    00388     fillBuffer( buffer, " 40755", 0, 0x35, user.local8Bit(), group.local8Bit());
    00389
    00390     // Write header
    00391     device()->writeBlock( buffer, 0x200 );
    00392
    00393     d->dirList.append( dirName ); // contains trailing slash
    00394     return true; // TODO if wanted, better error control
    00395 }
    00396
    00397 bool KTar::prepareWriting( const QString& name, const QString& user, const QString& group, uint size )
    00398 {
    00399     if ( !isOpened() )
    00400     {
    00401         kdWarning(7041) << "KArchive::writeFile: You must open the tar file before writing to it\n";
    00402         return false;
    00403     }
    00404
    00405     if ( !(mode() & IO_WriteOnly) )
    00406     {
    00407         kdWarning(7041) << "KArchive::writeFile: You must open the tar file for writing\n";
    00408         return false;
    00409     }
    00410
    00411     // In some tar files we can find dir/./file => call cleanDirPath
    00412     QString fileName ( QDir::cleanDirPath( name ) );
    00413
    00414     /*
    00415       // Create toplevel dirs
    00416       // Commented out by David since it's not necessary, and if anybody thinks it is,
    00417       // he needs to implement a findOrCreate equivalent in writeDir.
    00418       // But as KTar and the "tar" program both handle tar files without
    00419       // dir entries, there's really no need for that
    00420       QString tmp ( fileName );
    00421       int i = tmp.findRev( '/' );
    00422       if ( i != -1 )
    00423       {
    00424       QString d = tmp.left( i + 1 ); // contains trailing slash
    00425       if ( !m_dirList.contains( d ) )
    00426       {
    00427       tmp = tmp.mid( i + 1 );
    00428       writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
    00429       }
    00430       }
    00431     */
    00432
    00433     char buffer[ 0x201 ];
    00434     memset( buffer, 0, 0x200 );
    00435
    00436     // If more than 100 chars, we need to use the LongLink trick
    00437     if ( fileName.length() > 99 )
    00438     {
    00439         strcpy( buffer, "././@LongLink" );
    00440         fillBuffer( buffer, "     0", fileName.length()+1, 'L', user.local8Bit(), group.local8Bit() );
    00441         device()->writeBlock( buffer, 0x200 );
    00442
    00443         strncpy( buffer, QFile::encodeName(fileName), 0x200 );
    00444         buffer[0x200] = 0;
    00445         // write long name
    00446         device()->writeBlock( buffer, 0x200 );
    00447         // not even needed to reclear the buffer, tar doesn't do it
    00448     }
    00449     else
    00450     {
    00451         // Write name
    00452         strncpy( buffer, QFile::encodeName(fileName), 0x200 );
    00453         buffer[0x200] = 0;
    00454     }
    00455
    00456     fillBuffer( buffer, "100644", size, 0x30, user.local8Bit(), group.local8Bit() );
    00457
    00458     // Write header
    00459     return device()->writeBlock( buffer, 0x200 ) == 0x200;
    00460 }
    00461
    00462 bool KTar::doneWriting( uint size )
    00463 {
    00464     // Write alignment
    00465     int rest = size % 0x200;
    00466     if ( rest )
    00467     {
    00468         char buffer[ 0x201 ];
    00469         for( uint i = 0; i < 0x200; ++i )
    00470             buffer[i] = 0;
    00471         Q_LONG nwritten = device()->writeBlock( buffer, 0x200 - rest );
    00472         return nwritten == 0x200 - rest;
    00473     }
    00474     return true;
    00475 }
    00476
    00477 /*** Some help from the tar sources
    00478 struct posix_header
    00479 {                               byte offset
    00480   char name[100];               *   0 *     0x0
    00481   char mode[8];                 * 100 *     0x64
    00482   char uid[8];                  * 108 *     0x6c
    00483   char gid[8];                  * 116 *     0x74
    00484   char size[12];                * 124 *     0x7c
    00485   char mtime[12];               * 136 *     0x88
    00486   char chksum[8];               * 148 *     0x94
    00487   char typeflag;                * 156 *     0x9c
    00488   char linkname[100];           * 157 *     0x9d
    00489   char magic[6];                * 257 *     0x101
    00490   char version[2];              * 263 *     0x107
    00491   char uname[32];               * 265 *     0x109
    00492   char gname[32];               * 297 *     0x129
    00493   char devmajor[8];             * 329 *     0x149
    00494   char devminor[8];             * 337 *     ...
    00495   char prefix[155];             * 345 *
    00496                                 * 500 *
    00497 };
    00498 */
    00499
    00500 void KTar::fillBuffer( char * buffer,
    00501     const char * mode, int size, char typeflag, const char * uname, const char * gname )
    00502 {
    00503   // mode (as in stat())
    00504   assert( strlen(mode) == 6 );
    00505   strcpy( buffer+0x64, mode );
    00506   buffer[ 0x6a ] = ' ';
    00507   buffer[ 0x6b ] = '\0';
    00508
    00509   // dummy uid
    00510   strcpy( buffer + 0x6c, "   765 ");
    00511   // dummy gid
    00512   strcpy( buffer + 0x74, "   144 ");
    00513
    00514   // size
    00515   QCString s;
    00516   s.sprintf("%o", size); // OCT
    00517   s = s.rightJustify( 11, ' ' );
    00518   strcpy( buffer + 0x7c, s.data() );
    00519   buffer[ 0x87 ] = ' '; // space-terminate (no null after)
    00520
    00521   // Dummy time
    00522   s.sprintf("%lo", static_cast<unsigned long>(time( 0 )) ); // OCT
    00523   s = s.rightJustify( 11, ' ' );
    00524   strcpy( buffer + 0x88, s.data() );
    00525   buffer[ 0x93 ] = ' '; // space-terminate (no null after)
    00526
    00527   // spaces, replaced by the check sum later
    00528   buffer[ 0x94 ] = 0x20;
    00529   buffer[ 0x95 ] = 0x20;
    00530   buffer[ 0x96 ] = 0x20;
    00531   buffer[ 0x97 ] = 0x20;
    00532   buffer[ 0x98 ] = 0x20;
    00533   buffer[ 0x99 ] = 0x20;
    00534
    00535   /* From the tar sources :
    00536      Fill in the checksum field.  It's formatted differently from the
    00537      other fields: it has [6] digits, a null, then a space -- rather than
    00538      digits, a space, then a null. */
    00539
    00540   buffer[ 0x9a ] = '\0';
    00541   buffer[ 0x9b ] = ' ';
    00542
    00543   // type flag (dir, file, link)
    00544   buffer[ 0x9c ] = typeflag;
    00545
    00546  // magic + version
    00547   strcpy( buffer + 0x101, "ustar");
    00548   strcpy( buffer + 0x107, "00" );
    00549
    00550   // user
    00551   strcpy( buffer + 0x109, uname );
    00552   // group
    00553   strcpy( buffer + 0x129, gname );
    00554
    00555   // Header check sum
    00556   int check = 32;
    00557   for( uint j = 0; j < 0x200; ++j )
    00558     check += buffer[j];
    00559   s.sprintf("%o", check ); // OCT
    00560   s = s.rightJustify( 7, ' ' );
    00561   strcpy( buffer + 0x94, s.data() );
    00562 }
    00563
    00564 void KTar::virtual_hook( int id, void* data )
    00565 { KArchive::virtual_hook( id, data ); }
    00566

This file is part of the documentation for kdelibs Version 3.1.5.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Wed Jan 28 13:14:29 2004 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001