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