ktar.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    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License version 2 as published by the Free Software Foundation.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017    Boston, MA 02110-1301, USA.
00018 */
00019 
00020 //#include <stdio.h>
00021 #include <stdlib.h> // strtol
00022 #include <time.h> // time()
00023 /*#include <unistd.h>
00024 #include <grp.h>
00025 #include <pwd.h>*/
00026 #include <assert.h>
00027 
00028 #include <qcstring.h>
00029 #include <qdir.h>
00030 #include <qfile.h>
00031 #include <kdebug.h>
00032 #include <kmimetype.h>
00033 #include <ktempfile.h>
00034 
00035 #include <kfilterdev.h>
00036 #include <kfilterbase.h>
00037 
00038 #include "ktar.h"
00039 #include <kstandarddirs.h>
00040 
00044 
00045 class KTar::KTarPrivate
00046 {
00047 public:
00048     KTarPrivate() : tarEnd( 0 ), tmpFile( 0 ) {}
00049     QStringList dirList;
00050     int tarEnd;
00051     KTempFile* tmpFile;
00052     QString mimetype;
00053     QCString origFileName;
00054 
00055     bool fillTempFile(const QString & filename);
00056     bool writeBackTempFile( const QString & filename );
00057 };
00058 
00059 KTar::KTar( const QString& filename, const QString & _mimetype )
00060     : KArchive( 0 )
00061 {
00062     m_filename = filename;
00063     d = new KTarPrivate;
00064     QString mimetype( _mimetype );
00065     bool forced = true;
00066     if ( mimetype.isEmpty() ) // Find out mimetype manually
00067     {
00068         if ( QFile::exists( filename ) )
00069             mimetype = KMimeType::findByFileContent( filename )->name();
00070         else
00071             mimetype = KMimeType::findByPath( filename, 0, true )->name();
00072         kdDebug(7041) << "KTar::KTar mimetype = " << mimetype << endl;
00073 
00074         // Don't move to prepareDevice - the other constructor theoretically allows ANY filter
00075         if ( mimetype == "application/x-tgz" || mimetype == "application/x-targz" || // the latter is deprecated but might still be around
00076              mimetype == "application/x-webarchive" )
00077         {
00078             // that's a gzipped tar file, so ask for gzip filter
00079             mimetype = "application/x-gzip";
00080         }
00081         else if ( mimetype == "application/x-tbz" ) // that's a bzipped2 tar file, so ask for bz2 filter
00082         {
00083             mimetype = "application/x-bzip2";
00084         }
00085         else
00086         {
00087             // Something else. Check if it's not really gzip though (e.g. for KOffice docs)
00088             QFile file( filename );
00089             if ( file.open( IO_ReadOnly ) )
00090             {
00091                 unsigned char firstByte = file.getch();
00092                 unsigned char secondByte = file.getch();
00093                 unsigned char thirdByte = file.getch();
00094                 if ( firstByte == 0037 && secondByte == 0213 )
00095                     mimetype = "application/x-gzip";
00096                 else if ( firstByte == 'B' && secondByte == 'Z' && thirdByte == 'h' )
00097                     mimetype = "application/x-bzip2";
00098                 else if ( firstByte == 'P' && secondByte == 'K' && thirdByte == 3 )
00099                 {
00100                     unsigned char fourthByte = file.getch();
00101                     if ( fourthByte == 4 )
00102                         mimetype = "application/x-zip";
00103                 }
00104             }
00105             file.close();
00106         }
00107         forced = false;
00108     }
00109     d->mimetype = mimetype;
00110 }
00111 
00112 void KTar::prepareDevice( const QString & filename,
00113                           const QString & mimetype, int ioMode )
00114 {
00115   if( "application/x-tar" == mimetype )
00116   {
00117       setDevice( new QFile( filename ) );
00118   }
00119   else if ( ioMode == IO_WriteOnly )
00120   {
00121     const bool forced = ( "application/x-gzip" == mimetype
00122                           || "application/x-bzip2" == mimetype );
00123     QIODevice *filterDev = KFilterDev::deviceForFile( filename, mimetype, forced );
00124     if ( filterDev )
00125     {
00126         setDevice( filterDev );
00127     }
00128     else
00129     {
00130         kdDebug( 7041 ) << "KTar::prepareDevice: no filterdevice found!" << endl;
00131     }
00132   }
00133   else
00134   {
00135     // The compression filters are very slow with random access.
00136     // So instead of applying the filter to the device,
00137     // the file is completly extracted instead,
00138     // and we work on the extracted tar file.
00139     // This improves the extraction speed by the tar ioslave dramatically,
00140     // if the archive file contains many files.
00141     // This is because the tar ioslave extracts one file after the other and normally
00142     // has to walk through the decompression filter each time.
00143     // Which is in fact nearly as slow as a complete decompression for each file.
00144     d->tmpFile = new KTempFile(locateLocal("tmp", "ktar-"),".tar");
00145     kdDebug( 7041 ) << "KTar::prepareDevice creating TempFile: " << d->tmpFile->name() << endl;
00146     d->tmpFile->setAutoDelete(true);
00147 
00148     // KTempFile opens the file automatically,
00149     // the device must be closed, however, for KArchive.setDevice()
00150     QFile* file = d->tmpFile->file();
00151     file->close();
00152     setDevice(file);
00153   }
00154 }
00155 
00156 KTar::KTar( QIODevice * dev )
00157     : KArchive( dev )
00158 {
00159     Q_ASSERT( dev );
00160     d = new KTarPrivate;
00161 }
00162 
00163 KTar::~KTar()
00164 {
00165     // mjarrett: Closes to prevent ~KArchive from aborting w/o device
00166     if( isOpened() )
00167         close();
00168 
00169     if (d->tmpFile)
00170         delete d->tmpFile; // will delete the device
00171     else if ( !m_filename.isEmpty() )
00172         delete device(); // we created it ourselves
00173 
00174 
00175     delete d;
00176 }
00177 
00178 void KTar::setOrigFileName( const QCString & fileName )
00179 {
00180     if ( !isOpened() || !(mode() & IO_WriteOnly) )
00181     {
00182         kdWarning(7041) << "KTar::setOrigFileName: File must be opened for writing first.\n";
00183         return;
00184     }
00185     d->origFileName = fileName;
00186 }
00187 
00188 Q_LONG KTar::readRawHeader(char *buffer) {
00189   // Read header
00190   Q_LONG n = device()->readBlock( buffer, 0x200 );
00191   if ( n == 0x200 && buffer[0] != 0 ) {
00192     // Make sure this is actually a tar header
00193     if (strncmp(buffer + 257, "ustar", 5)) {
00194       // The magic isn't there (broken/old tars), but maybe a correct checksum?
00195       QCString s;
00196 
00197       int check = 0;
00198       for( uint j = 0; j < 0x200; ++j )
00199         check += buffer[j];
00200 
00201       // adjust checksum to count the checksum fields as blanks
00202       for( uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++ )
00203         check -= buffer[148 + j];
00204       check += 8 * ' ';
00205 
00206       s.sprintf("%o", check );
00207 
00208       // only compare those of the 6 checksum digits that mean something,
00209       // because the other digits are filled with all sorts of different chars by different tars ...
00210       // Some tars right-justify the checksum so it could start in one of three places - we have to check each.
00211       if( strncmp( buffer + 148 + 6 - s.length(), s.data(), s.length() )
00212         && strncmp( buffer + 148 + 7 - s.length(), s.data(), s.length() )
00213         && strncmp( buffer + 148 + 8 - s.length(), s.data(), s.length() ) ) {
00214         kdWarning(7041) << "KTar: invalid TAR file. Header is: " << QCString( buffer+257, 5 ) << endl;
00215         return -1;
00216       }
00217     }/*end if*/
00218   } else {
00219     // reset to 0 if 0x200 because logical end of archive has been reached
00220     if (n == 0x200) n = 0;
00221   }/*end if*/
00222   return n;
00223 }
00224 
00225 bool KTar::readLonglink(char *buffer,QCString &longlink) {
00226   Q_LONG n = 0;
00227   QIODevice *dev = device();
00228   // read size of longlink from size field in header
00229   // size is in bytes including the trailing null (which we ignore)
00230   buffer[ 0x88 ] = 0; // was 0x87, but 0x88 fixes BR #26437
00231   char *dummy;
00232   const char* p = buffer + 0x7c;
00233   while( *p == ' ' ) ++p;
00234   int size = (int)strtol( p, &dummy, 8 );
00235 
00236   longlink.resize(size);
00237   size--;   // ignore trailing null
00238   dummy = longlink.data();
00239   int offset = 0;
00240   while (size > 0) {
00241     int chunksize = QMIN(size, 0x200);
00242     n = dev->readBlock( dummy + offset, chunksize );
00243     if (n == -1) return false;
00244     size -= chunksize;
00245     offset += 0x200;
00246   }/*wend*/
00247   // jump over the rest
00248   int skip = 0x200 - (n % 0x200);
00249   if (skip < 0x200) {
00250     if (dev->readBlock(buffer,skip) != skip) return false;
00251   }
00252   return true;
00253 }
00254 
00255 Q_LONG KTar::readHeader(char *buffer,QString &name,QString &symlink) {
00256   name.truncate(0);
00257   symlink.truncate(0);
00258   while (true) {
00259     Q_LONG n = readRawHeader(buffer);
00260     if (n != 0x200) return n;
00261 
00262     // is it a longlink?
00263     if (strcmp(buffer,"././@LongLink") == 0) {
00264       char typeflag = buffer[0x9c];
00265       QCString longlink;
00266       readLonglink(buffer,longlink);
00267       switch (typeflag) {
00268         case 'L': name = QFile::decodeName(longlink); break;
00269         case 'K': symlink = QFile::decodeName(longlink); break;
00270       }/*end switch*/
00271     } else {
00272       break;
00273     }/*end if*/
00274   }/*wend*/
00275 
00276   // if not result of longlink, read names directly from the header
00277   if (name.isEmpty())
00278     // there are names that are exactly 100 bytes long
00279     // and neither longlink nor \0 terminated (bug:101472)
00280     name = QFile::decodeName(QCString(buffer, 101));
00281   if (symlink.isEmpty())
00282     symlink = QFile::decodeName(QCString(buffer + 0x9d, 101));
00283 
00284   return 0x200;
00285 }
00286 
00287 /*
00288  * If we have created a temporary file, we have
00289  * to decompress the original file now and write
00290  * the contents to the temporary file.
00291  */
00292 bool KTar::KTarPrivate::fillTempFile( const QString & filename) {
00293     if ( ! tmpFile )
00294         return true;
00295 
00296     kdDebug( 7041 ) <<
00297         "KTar::openArchive: filling tmpFile of mimetype '" << mimetype <<
00298         "' ... " << endl;
00299 
00300     bool forced = false;
00301     if( "application/x-gzip" == mimetype
00302     || "application/x-bzip2" == mimetype)
00303         forced = true;
00304 
00305     QIODevice *filterDev = KFilterDev::deviceForFile( filename, mimetype, forced );
00306 
00307     if( filterDev ) {
00308         QFile* file = tmpFile->file();
00309         file->close();
00310         if ( ! file->open( IO_WriteOnly ) )
00311         {
00312             delete filterDev;
00313             return false;
00314         }
00315         QByteArray buffer(8*1024);
00316         if ( ! filterDev->open( IO_ReadOnly ) )
00317         {
00318             delete filterDev;
00319             return false;
00320         }
00321         Q_LONG len = -1;
00322         while ( !filterDev->atEnd() && len != 0) {
00323             len = filterDev->readBlock(buffer.data(),buffer.size());
00324             if ( len < 0 ) { // corrupted archive
00325                 delete filterDev;
00326                 return false;
00327             }
00328             file->writeBlock(buffer.data(),len);
00329         }
00330         filterDev->close();
00331         delete filterDev;
00332 
00333         file->close();
00334         if ( ! file->open( IO_ReadOnly ) )
00335             return false;
00336     }
00337     else
00338         kdDebug( 7041 ) << "KTar::openArchive: no filterdevice found!" << endl;
00339 
00340     kdDebug( 7041 ) << "KTar::openArchive: filling tmpFile finished." << endl;
00341     return true;
00342 }
00343 
00344 bool KTar::openArchive( int mode )
00345 {
00346     kdDebug( 7041 ) << "KTar::openArchive filename=" << m_filename << " mode=" << (mode&IO_ReadOnly?"ReadOnly":"Write") << endl;
00347 
00348     if (!m_filename.isEmpty()) {
00349         // Finish what the constructor used to do
00350         prepareDevice( m_filename, d->mimetype, mode );
00351         // Then do what KArchive::open wasn't able to do yet
00352         if ( device() && !device()->open( mode ) )
00353             return false;
00354     }
00355 
00356     if ( !(mode & IO_ReadOnly) )
00357         return true;
00358 
00359     if ( !d->fillTempFile( m_filename ) )
00360         return false;
00361 
00362     // We'll use the permission and user/group of d->rootDir
00363     // for any directory we emulate (see findOrCreate)
00364     //struct stat buf;
00365     //stat( m_filename, &buf );
00366 
00367     d->dirList.clear();
00368     QIODevice* dev = device();
00369 
00370     if ( !dev )
00371         return false;
00372 
00373     // read dir infos
00374     char buffer[ 0x200 ];
00375     bool ende = false;
00376     do
00377     {
00378         QString name;
00379         QString symlink;
00380 
00381         // Read header
00382         Q_LONG n = readHeader(buffer,name,symlink);
00383         if (n < 0) return false;
00384         if (n == 0x200)
00385         {
00386             bool isdir = false;
00387             QString nm;
00388 
00389             if ( name.right(1) == "/" )
00390             {
00391                 isdir = true;
00392                 name = name.left( name.length() - 1 );
00393             }
00394 
00395             int pos = name.findRev( '/' );
00396             if ( pos == -1 )
00397                 nm = name;
00398             else
00399                 nm = name.mid( pos + 1 );
00400 
00401             // read access
00402             buffer[ 0x6b ] = 0;
00403             char *dummy;
00404             const char* p = buffer + 0x64;
00405             while( *p == ' ' ) ++p;
00406             int access = (int)strtol( p, &dummy, 8 );
00407 
00408             // read user and group
00409             QString user( buffer + 0x109 );
00410             QString group( buffer + 0x129 );
00411 
00412             // read time
00413             buffer[ 0x93 ] = 0;
00414             p = buffer + 0x88;
00415             while( *p == ' ' ) ++p;
00416             int time = (int)strtol( p, &dummy, 8 );
00417 
00418             // read type flag
00419             char typeflag = buffer[ 0x9c ];
00420             // '0' for files, '1' hard link, '2' symlink, '5' for directory
00421             // (and 'L' for longlink filenames, 'K' for longlink symlink targets)
00422             // and 'D' for GNU tar extension DUMPDIR
00423             if ( typeflag == '5' )
00424                 isdir = true;
00425 
00426             bool isDumpDir = false;
00427             if ( typeflag == 'D' )
00428             {
00429                 isdir = false;
00430                 isDumpDir = true;
00431             }
00432             //bool islink = ( typeflag == '1' || typeflag == '2' );
00433             //kdDebug(7041) << "typeflag=" << typeflag << " islink=" << islink << endl;
00434 
00435             if (isdir)
00436                 access |= S_IFDIR; // f*cking broken tar files
00437 
00438             KArchiveEntry* e;
00439             if ( isdir )
00440             {
00441                 //kdDebug(7041) << "KTar::openArchive directory " << nm << endl;
00442                 e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
00443             }
00444             else
00445             {
00446                 // read size
00447                 buffer[ 0x88 ] = 0; // was 0x87, but 0x88 fixes BR #26437
00448                 char *dummy;
00449                 const char* p = buffer + 0x7c;
00450                 while( *p == ' ' ) ++p;
00451                 int size = (int)strtol( p, &dummy, 8 );
00452 
00453                 // for isDumpDir we will skip the additional info about that dirs contents
00454                 if ( isDumpDir )
00455                 {
00456                     //kdDebug(7041) << "KTar::openArchive " << nm << " isDumpDir" << endl;
00457                     e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
00458                 }
00459                 else
00460                 {
00461 
00462                     // Let's hack around hard links. Our classes don't support that, so make them symlinks
00463                     if ( typeflag == '1' )
00464                     {
00465                         kdDebug(7041) << "HARD LINK, setting size to 0 instead of " << size << endl;
00466                         size = 0; // no contents
00467                     }
00468 
00469                     //kdDebug(7041) << "KTar::openArchive file " << nm << " size=" << size << endl;
00470                     e = new KArchiveFile( this, nm, access, time, user, group, symlink,
00471                                           dev->at(), size );
00472                 }
00473 
00474                 // Skip contents + align bytes
00475                 int rest = size % 0x200;
00476                 int skip = size + (rest ? 0x200 - rest : 0);
00477                 //kdDebug(7041) << "KTar::openArchive, at()=" << dev->at() << " rest=" << rest << " skipping " << skip << endl;
00478                 if (! dev->at( dev->at() + skip ) )
00479                     kdWarning(7041) << "KTar::openArchive skipping " << skip << " failed" << endl;
00480             }
00481 
00482             if ( pos == -1 )
00483             {
00484                 if ( nm == "." ) // special case
00485                 {
00486                     Q_ASSERT( isdir );
00487                     if ( isdir )
00488                         setRootDir( static_cast<KArchiveDirectory *>( e ) );
00489                 }
00490                 else
00491                     rootDir()->addEntry( e );
00492             }
00493             else
00494             {
00495                 // In some tar files we can find dir/./file => call cleanDirPath
00496                 QString path = QDir::cleanDirPath( name.left( pos ) );
00497                 // Ensure container directory exists, create otherwise
00498                 KArchiveDirectory * d = findOrCreate( path );
00499                 d->addEntry( e );
00500             }
00501         }
00502         else
00503         {
00504             //qDebug("Terminating. Read %d bytes, first one is %d", n, buffer[0]);
00505             d->tarEnd = dev->at() - n; // Remember end of archive
00506             ende = true;
00507         }
00508     } while( !ende );
00509     return true;
00510 }
00511 
00512 /*
00513  * Writes back the changes of the temporary file
00514  * to the original file.
00515  * Must only be called if in IO_WriteOnly mode
00516  */
00517 bool KTar::KTarPrivate::writeBackTempFile( const QString & filename ) {
00518     if ( ! tmpFile )
00519         return true;
00520 
00521     kdDebug(7041) << "Write temporary file to compressed file" << endl;
00522     kdDebug(7041) << filename << " " << mimetype << endl;
00523 
00524     bool forced = false;
00525     if( "application/x-gzip" == mimetype
00526         || "application/x-bzip2" == mimetype)
00527         forced = true;
00528 
00529 
00530     QIODevice *dev = KFilterDev::deviceForFile( filename, mimetype, forced );
00531     if( dev ) {
00532         QFile* file = tmpFile->file();
00533         file->close();
00534         if ( ! file->open(IO_ReadOnly) || ! dev->open(IO_WriteOnly) )
00535         {
00536             file->close();
00537             delete dev;
00538             return false;
00539         }
00540         if ( forced )
00541             static_cast<KFilterDev *>(dev)->setOrigFileName( origFileName );
00542         QByteArray buffer(8*1024);
00543         Q_LONG len;
00544         while ( ! file->atEnd()) {
00545             len = file->readBlock(buffer.data(),buffer.size());
00546             dev->writeBlock(buffer.data(),len);
00547         }
00548         file->close();
00549         dev->close();
00550         delete dev;
00551     }
00552 
00553     kdDebug(7041) << "Write temporary file to compressed file done." << endl;
00554     return true;
00555 }
00556 
00557 bool KTar::closeArchive()
00558 {
00559     d->dirList.clear();
00560 
00561     // If we are in write mode and had created
00562     // a temporary tar file, we have to write
00563     // back the changes to the original file
00564     if( mode() == IO_WriteOnly)
00565         return d->writeBackTempFile( m_filename );
00566 
00567     return true;
00568 }
00569 
00570 bool KTar::writeDir( const QString& name, const QString& user, const QString& group )
00571 {
00572     mode_t perm = 040755;
00573     time_t the_time = time(0);
00574     return writeDir(name,user,group,perm,the_time,the_time,the_time);
00575 #if 0
00576     if ( !isOpened() )
00577     {
00578         kdWarning(7041) << "KTar::writeDir: You must open the tar file before writing to it\n";
00579         return false;
00580     }
00581 
00582     if ( !(mode() & IO_WriteOnly) )
00583     {
00584         kdWarning(7041) << "KTar::writeDir: You must open the tar file for writing\n";
00585         return false;
00586     }
00587 
00588     // In some tar files we can find dir/./ => call cleanDirPath
00589     QString dirName ( QDir::cleanDirPath( name ) );
00590 
00591     // Need trailing '/'
00592     if ( dirName.right(1) != "/" )
00593         dirName += "/";
00594 
00595     if ( d->dirList.contains( dirName ) )
00596         return true; // already there
00597 
00598     char buffer[ 0x201 ];
00599     memset( buffer, 0, 0x200 );
00600     if ( mode() & IO_ReadWrite ) device()->at(d->tarEnd); // Go to end of archive as might have moved with a read
00601 
00602     // If more than 100 chars, we need to use the LongLink trick
00603     if ( dirName.length() > 99 )
00604     {
00605         strcpy( buffer, "././@LongLink" );
00606         fillBuffer( buffer, "     0", dirName.length()+1, 'L', user.local8Bit(), group.local8Bit() );
00607         device()->writeBlock( buffer, 0x200 );
00608         strncpy( buffer, QFile::encodeName(dirName), 0x200 );
00609         buffer[0x200] = 0;
00610         // write long name
00611         device()->writeBlock( buffer, 0x200 );
00612         // not even needed to reclear the buffer, tar doesn't do it
00613     }
00614     else
00615     {
00616         // Write name
00617         strncpy( buffer, QFile::encodeName(dirName), 0x200 );
00618         buffer[0x200] = 0;
00619     }
00620 
00621     fillBuffer( buffer, " 40755", 0, 0x35, user.local8Bit(), group.local8Bit());
00622 
00623     // Write header
00624     device()->writeBlock( buffer, 0x200 );
00625     if ( mode() & IO_ReadWrite )  d->tarEnd = device()->at();
00626 
00627     d->dirList.append( dirName ); // contains trailing slash
00628     return true; // TODO if wanted, better error control
00629 #endif
00630 }
00631 
00632 bool KTar::prepareWriting( const QString& name, const QString& user, const QString& group, uint size )
00633 {
00634     mode_t dflt_perm = 0100644;
00635     time_t the_time = time(0);
00636     return prepareWriting(name,user,group,size,dflt_perm,
00637             the_time,the_time,the_time);
00638 }
00639 
00640 bool KTar::doneWriting( uint size )
00641 {
00642     // Write alignment
00643     int rest = size % 0x200;
00644     if ( mode() & IO_ReadWrite )
00645         d->tarEnd = device()->at() + (rest ? 0x200 - rest : 0); // Record our new end of archive
00646     if ( rest )
00647     {
00648         char buffer[ 0x201 ];
00649         for( uint i = 0; i < 0x200; ++i )
00650             buffer[i] = 0;
00651         Q_LONG nwritten = device()->writeBlock( buffer, 0x200 - rest );
00652         return nwritten == 0x200 - rest;
00653     }
00654     return true;
00655 }
00656 
00657 /*** Some help from the tar sources
00658 struct posix_header
00659 {                               byte offset
00660   char name[100];               *   0 *     0x0
00661   char mode[8];                 * 100 *     0x64
00662   char uid[8];                  * 108 *     0x6c
00663   char gid[8];                  * 116 *     0x74
00664   char size[12];                * 124 *     0x7c
00665   char mtime[12];               * 136 *     0x88
00666   char chksum[8];               * 148 *     0x94
00667   char typeflag;                * 156 *     0x9c
00668   char linkname[100];           * 157 *     0x9d
00669   char magic[6];                * 257 *     0x101
00670   char version[2];              * 263 *     0x107
00671   char uname[32];               * 265 *     0x109
00672   char gname[32];               * 297 *     0x129
00673   char devmajor[8];             * 329 *     0x149
00674   char devminor[8];             * 337 *     ...
00675   char prefix[155];             * 345 *
00676                                 * 500 *
00677 };
00678 */
00679 
00680 void KTar::fillBuffer( char * buffer,
00681     const char * mode, int size, time_t mtime, char typeflag,
00682     const char * uname, const char * gname )
00683 {
00684   // mode (as in stat())
00685   assert( strlen(mode) == 6 );
00686   strcpy( buffer+0x64, mode );
00687   buffer[ 0x6a ] = ' ';
00688   buffer[ 0x6b ] = '\0';
00689 
00690   // dummy uid
00691   strcpy( buffer + 0x6c, "   765 ");
00692   // dummy gid
00693   strcpy( buffer + 0x74, "   144 ");
00694 
00695   // size
00696   QCString s;
00697   s.sprintf("%o", size); // OCT
00698   s = s.rightJustify( 11, ' ' );
00699   strcpy( buffer + 0x7c, s.data() );
00700   buffer[ 0x87 ] = ' '; // space-terminate (no null after)
00701 
00702   // modification time
00703   s.sprintf("%lo", static_cast<unsigned long>(mtime) ); // OCT
00704   s = s.rightJustify( 11, ' ' );
00705   strcpy( buffer + 0x88, s.data() );
00706   buffer[ 0x93 ] = ' '; // space-terminate (no null after)
00707 
00708   // spaces, replaced by the check sum later
00709   buffer[ 0x94 ] = 0x20;
00710   buffer[ 0x95 ] = 0x20;
00711   buffer[ 0x96 ] = 0x20;
00712   buffer[ 0x97 ] = 0x20;
00713   buffer[ 0x98 ] = 0x20;
00714   buffer[ 0x99 ] = 0x20;
00715 
00716   /* From the tar sources :
00717      Fill in the checksum field.  It's formatted differently from the
00718      other fields: it has [6] digits, a null, then a space -- rather than
00719      digits, a space, then a null. */
00720 
00721   buffer[ 0x9a ] = '\0';
00722   buffer[ 0x9b ] = ' ';
00723 
00724   // type flag (dir, file, link)
00725   buffer[ 0x9c ] = typeflag;
00726 
00727  // magic + version
00728   strcpy( buffer + 0x101, "ustar");
00729   strcpy( buffer + 0x107, "00" );
00730 
00731   // user
00732   strcpy( buffer + 0x109, uname );
00733   // group
00734   strcpy( buffer + 0x129, gname );
00735 
00736   // Header check sum
00737   int check = 32;
00738   for( uint j = 0; j < 0x200; ++j )
00739     check += buffer[j];
00740   s.sprintf("%o", check ); // OCT
00741   s = s.rightJustify( 7, ' ' );
00742   strcpy( buffer + 0x94, s.data() );
00743 }
00744 
00745 void KTar::writeLonglink(char *buffer, const QCString &name, char typeflag,
00746     const char *uname, const char *gname) {
00747   strcpy( buffer, "././@LongLink" );
00748   int namelen = name.length() + 1;
00749   fillBuffer( buffer, "     0", namelen, 0, typeflag, uname, gname );
00750   device()->writeBlock( buffer, 0x200 );
00751   int offset = 0;
00752   while (namelen > 0) {
00753     int chunksize = QMIN(namelen, 0x200);
00754     memcpy(buffer, name.data()+offset, chunksize);
00755     // write long name
00756     device()->writeBlock( buffer, 0x200 );
00757     // not even needed to reclear the buffer, tar doesn't do it
00758     namelen -= chunksize;
00759     offset += 0x200;
00760   }/*wend*/
00761 }
00762 
00763 bool KTar::prepareWriting(const QString& name, const QString& user,
00764                 const QString& group, uint size, mode_t perm,
00765                 time_t atime, time_t mtime, time_t ctime) {
00766   return KArchive::prepareWriting(name,user,group,size,perm,atime,mtime,ctime);
00767 }
00768 
00769 bool KTar::prepareWriting_impl(const QString &name, const QString &user,
00770                 const QString &group, uint size, mode_t perm,
00771                 time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
00772     if ( !isOpened() )
00773     {
00774         kdWarning(7041) << "KTar::prepareWriting: You must open the tar file before writing to it\n";
00775         return false;
00776     }
00777 
00778     if ( !(mode() & IO_WriteOnly) )
00779     {
00780         kdWarning(7041) << "KTar::prepareWriting: You must open the tar file for writing\n";
00781         return false;
00782     }
00783 
00784     // In some tar files we can find dir/./file => call cleanDirPath
00785     QString fileName ( QDir::cleanDirPath( name ) );
00786 
00787     /*
00788       // Create toplevel dirs
00789       // Commented out by David since it's not necessary, and if anybody thinks it is,
00790       // he needs to implement a findOrCreate equivalent in writeDir.
00791       // But as KTar and the "tar" program both handle tar files without
00792       // dir entries, there's really no need for that
00793       QString tmp ( fileName );
00794       int i = tmp.findRev( '/' );
00795       if ( i != -1 )
00796       {
00797       QString d = tmp.left( i + 1 ); // contains trailing slash
00798       if ( !m_dirList.contains( d ) )
00799       {
00800       tmp = tmp.mid( i + 1 );
00801       writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
00802       }
00803       }
00804     */
00805 
00806     char buffer[ 0x201 ];
00807     memset( buffer, 0, 0x200 );
00808     if ( mode() & IO_ReadWrite ) device()->at(d->tarEnd); // Go to end of archive as might have moved with a read
00809 
00810     // provide converted stuff we need lateron
00811     QCString encodedFilename = QFile::encodeName(fileName);
00812     QCString uname = user.local8Bit();
00813     QCString gname = group.local8Bit();
00814 
00815     // If more than 100 chars, we need to use the LongLink trick
00816     if ( fileName.length() > 99 )
00817         writeLonglink(buffer,encodedFilename,'L',uname,gname);
00818 
00819     // Write (potentially truncated) name
00820     strncpy( buffer, encodedFilename, 99 );
00821     buffer[99] = 0;
00822     // zero out the rest (except for what gets filled anyways)
00823     memset(buffer+0x9d, 0, 0x200 - 0x9d);
00824 
00825     QCString permstr;
00826     permstr.sprintf("%o",perm);
00827     permstr = permstr.rightJustify(6, ' ');
00828     fillBuffer(buffer, permstr, size, mtime, 0x30, uname, gname);
00829 
00830     // Write header
00831     return device()->writeBlock( buffer, 0x200 ) == 0x200;
00832 }
00833 
00834 bool KTar::writeDir(const QString& name, const QString& user,
00835                 const QString& group, mode_t perm,
00836                 time_t atime, time_t mtime, time_t ctime) {
00837   return KArchive::writeDir(name,user,group,perm,atime,mtime,ctime);
00838 }
00839 
00840 bool KTar::writeDir_impl(const QString &name, const QString &user,
00841                 const QString &group, mode_t perm,
00842                 time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
00843     if ( !isOpened() )
00844     {
00845         kdWarning(7041) << "KTar::writeDir: You must open the tar file before writing to it\n";
00846         return false;
00847     }
00848 
00849     if ( !(mode() & IO_WriteOnly) )
00850     {
00851         kdWarning(7041) << "KTar::writeDir: You must open the tar file for writing\n";
00852         return false;
00853     }
00854 
00855     // In some tar files we can find dir/./ => call cleanDirPath
00856     QString dirName ( QDir::cleanDirPath( name ) );
00857 
00858     // Need trailing '/'
00859     if ( dirName.right(1) != "/" )
00860         dirName += "/";
00861 
00862     if ( d->dirList.contains( dirName ) )
00863         return true; // already there
00864 
00865     char buffer[ 0x201 ];
00866     memset( buffer, 0, 0x200 );
00867     if ( mode() & IO_ReadWrite ) device()->at(d->tarEnd); // Go to end of archive as might have moved with a read
00868 
00869     // provide converted stuff we need lateron
00870     QCString encodedDirname = QFile::encodeName(dirName);
00871     QCString uname = user.local8Bit();
00872     QCString gname = group.local8Bit();
00873 
00874     // If more than 100 chars, we need to use the LongLink trick
00875     if ( dirName.length() > 99 )
00876         writeLonglink(buffer,encodedDirname,'L',uname,gname);
00877 
00878     // Write (potentially truncated) name
00879     strncpy( buffer, encodedDirname, 99 );
00880     buffer[99] = 0;
00881     // zero out the rest (except for what gets filled anyways)
00882     memset(buffer+0x9d, 0, 0x200 - 0x9d);
00883 
00884     QCString permstr;
00885     permstr.sprintf("%o",perm);
00886     permstr = permstr.rightJustify(6, ' ');
00887     fillBuffer( buffer, permstr, 0, mtime, 0x35, uname, gname);
00888 
00889     // Write header
00890     device()->writeBlock( buffer, 0x200 );
00891     if ( mode() & IO_ReadWrite )  d->tarEnd = device()->at();
00892 
00893     d->dirList.append( dirName ); // contains trailing slash
00894     return true; // TODO if wanted, better error control
00895 }
00896 
00897 bool KTar::writeSymLink(const QString &name, const QString &target,
00898                 const QString &user, const QString &group,
00899                 mode_t perm, time_t atime, time_t mtime, time_t ctime) {
00900   return KArchive::writeSymLink(name,target,user,group,perm,atime,mtime,ctime);
00901 }
00902 
00903 bool KTar::writeSymLink_impl(const QString &name, const QString &target,
00904                 const QString &user, const QString &group,
00905                 mode_t perm, time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
00906     if ( !isOpened() )
00907     {
00908         kdWarning(7041) << "KTar::writeSymLink: You must open the tar file before writing to it\n";
00909         return false;
00910     }
00911 
00912     if ( !(mode() & IO_WriteOnly) )
00913     {
00914         kdWarning(7041) << "KTar::writeSymLink: You must open the tar file for writing\n";
00915         return false;
00916     }
00917 
00918     device()->flush();
00919 
00920     // In some tar files we can find dir/./file => call cleanDirPath
00921     QString fileName ( QDir::cleanDirPath( name ) );
00922 
00923     char buffer[ 0x201 ];
00924     memset( buffer, 0, 0x200 );
00925     if ( mode() & IO_ReadWrite ) device()->at(d->tarEnd); // Go to end of archive as might have moved with a read
00926 
00927     // provide converted stuff we need lateron
00928     QCString encodedFilename = QFile::encodeName(fileName);
00929     QCString encodedTarget = QFile::encodeName(target);
00930     QCString uname = user.local8Bit();
00931     QCString gname = group.local8Bit();
00932 
00933     // If more than 100 chars, we need to use the LongLink trick
00934     if (target.length() > 99)
00935         writeLonglink(buffer,encodedTarget,'K',uname,gname);
00936     if ( fileName.length() > 99 )
00937         writeLonglink(buffer,encodedFilename,'L',uname,gname);
00938 
00939     // Write (potentially truncated) name
00940     strncpy( buffer, encodedFilename, 99 );
00941     buffer[99] = 0;
00942     // Write (potentially truncated) symlink target
00943     strncpy(buffer+0x9d, encodedTarget, 99);
00944     buffer[0x9d+99] = 0;
00945     // zero out the rest
00946     memset(buffer+0x9d+100, 0, 0x200 - 100 - 0x9d);
00947 
00948     QCString permstr;
00949     permstr.sprintf("%o",perm);
00950     permstr = permstr.rightJustify(6, ' ');
00951     fillBuffer(buffer, permstr, 0, mtime, 0x32, uname, gname);
00952 
00953     // Write header
00954     bool retval = device()->writeBlock( buffer, 0x200 ) == 0x200;
00955     if ( mode() & IO_ReadWrite )  d->tarEnd = device()->at();
00956     return retval;
00957 }
00958 
00959 void KTar::virtual_hook( int id, void* data ) {
00960   switch (id) {
00961     case VIRTUAL_WRITE_SYMLINK: {
00962       WriteSymlinkParams *params = reinterpret_cast<WriteSymlinkParams *>(data);
00963       params->retval = writeSymLink_impl(*params->name,*params->target,
00964                 *params->user,*params->group,params->perm,
00965                 params->atime,params->mtime,params->ctime);
00966       break;
00967     }
00968     case VIRTUAL_WRITE_DIR: {
00969       WriteDirParams *params = reinterpret_cast<WriteDirParams *>(data);
00970       params->retval = writeDir_impl(*params->name,*params->user,
00971             *params->group,params->perm,
00972                 params->atime,params->mtime,params->ctime);
00973       break;
00974     }
00975     case VIRTUAL_PREPARE_WRITING: {
00976       PrepareWritingParams *params = reinterpret_cast<PrepareWritingParams *>(data);
00977       params->retval = prepareWriting_impl(*params->name,*params->user,
00978                 *params->group,params->size,params->perm,
00979                 params->atime,params->mtime,params->ctime);
00980       break;
00981     }
00982     default:
00983       KArchive::virtual_hook( id, data );
00984   }/*end switch*/
00985 }
00986 
KDE Home | KDE Accessibility Home | Description of Access Keys