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