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