KDECore
ktar.cpp
Go to the documentation of this file.
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 "ktar.h" 00021 00022 #include <stdlib.h> // strtol 00023 #include <time.h> // time() 00024 #include <assert.h> 00025 00026 #include <QtCore/QDir> 00027 #include <QtCore/QFile> 00028 #include <kdebug.h> 00029 #include <kmimetype.h> 00030 #include <ktemporaryfile.h> 00031 00032 #include <kfilterdev.h> 00033 #include <kfilterbase.h> 00034 00038 00039 // Mime types of known filters 00040 static const char application_gzip[] = "application/x-gzip"; 00041 static const char application_bzip[] = "application/x-bzip"; 00042 static const char application_lzma[] = "application/x-lzma"; 00043 static const char application_xz[] = "application/x-xz"; 00044 static const char application_zip[] = "application/zip"; 00045 00046 class KTar::KTarPrivate 00047 { 00048 public: 00049 KTarPrivate(KTar *parent) 00050 : q(parent), 00051 tarEnd( 0 ), 00052 tmpFile( 0 ) 00053 { 00054 } 00055 00056 KTar *q; 00057 QStringList dirList; 00058 qint64 tarEnd; 00059 KTemporaryFile* tmpFile; 00060 QString mimetype; 00061 QByteArray origFileName; 00062 00063 bool fillTempFile(const QString & fileName); 00064 bool writeBackTempFile( const QString & fileName ); 00065 void fillBuffer( char * buffer, const char * mode, qint64 size, time_t mtime, 00066 char typeflag, const char * uname, const char * gname ); 00067 void writeLonglink(char *buffer, const QByteArray &name, char typeflag, 00068 const char *uname, const char *gname); 00069 qint64 readRawHeader(char *buffer); 00070 bool readLonglink(char *buffer, QByteArray &longlink); 00071 qint64 readHeader(char *buffer, QString &name, QString &symlink); 00072 }; 00073 00074 KTar::KTar( const QString& fileName, const QString & _mimetype ) 00075 : KArchive( fileName ), d(new KTarPrivate(this)) 00076 { 00077 d->mimetype = _mimetype; 00078 } 00079 00080 KTar::KTar( QIODevice * dev ) 00081 : KArchive( dev ), d(new KTarPrivate(this)) 00082 { 00083 Q_ASSERT( dev ); 00084 } 00085 00086 // Only called when a filename was given 00087 bool KTar::createDevice(QIODevice::OpenMode mode) 00088 { 00089 if (d->mimetype.isEmpty()) { 00090 // Find out mimetype manually 00091 00092 KMimeType::Ptr mime; 00093 if (mode != QIODevice::WriteOnly && QFile::exists(fileName())) { 00094 // Give priority to file contents: if someone renames a .tar.bz2 to .tar.gz, 00095 // we can still do the right thing here. 00096 mime = KMimeType::findByFileContent(fileName()); 00097 if (mime == KMimeType::defaultMimeTypePtr()) { 00098 // Unable to determine mimetype from contents, get it from file name 00099 mime = KMimeType::findByPath(fileName(), 0, true); 00100 } 00101 } else { 00102 mime = KMimeType::findByPath(fileName(), 0, true); 00103 } 00104 00105 //kDebug(7041) << mode << mime->name(); 00106 00107 if (mime->is(QString::fromLatin1("application/x-compressed-tar")) || mime->is(QString::fromLatin1(application_gzip))) { 00108 // gzipped tar file (with possibly invalid file name), ask for gzip filter 00109 d->mimetype = QString::fromLatin1(application_gzip); 00110 } else if (mime->is(QString::fromLatin1("application/x-bzip-compressed-tar")) || mime->is(QString::fromLatin1(application_bzip))) { 00111 // bzipped2 tar file (with possibly invalid file name), ask for bz2 filter 00112 d->mimetype = QString::fromLatin1(application_bzip); 00113 } else if (mime->is(QString::fromLatin1("application/x-lzma-compressed-tar")) || mime->is(QString::fromLatin1(application_lzma))) { 00114 // lzma compressed tar file (with possibly invalid file name), ask for xz filter 00115 d->mimetype = QString::fromLatin1(application_lzma); 00116 } else if (mime->is(QString::fromLatin1("application/x-xz-compressed-tar")) || mime->is(QString::fromLatin1(application_xz))) { 00117 // xz compressed tar file (with possibly invalid name), ask for xz filter 00118 d->mimetype = QString::fromLatin1(application_xz); 00119 } 00120 } 00121 00122 if (d->mimetype == QLatin1String("application/x-tar")) { 00123 return KArchive::createDevice(mode); 00124 } else if (mode == QIODevice::WriteOnly) { 00125 if (!KArchive::createDevice(mode)) 00126 return false; 00127 if (!d->mimetype.isEmpty()) { 00128 // Create a compression filter on top of the KSaveFile device that KArchive created. 00129 //kDebug(7041) << "creating KFilterDev for" << d->mimetype; 00130 QIODevice *filterDev = KFilterDev::device(device(), d->mimetype); 00131 Q_ASSERT(filterDev); 00132 setDevice(filterDev); 00133 } 00134 return true; 00135 } else { 00136 // The compression filters are very slow with random access. 00137 // So instead of applying the filter to the device, 00138 // the file is completely extracted instead, 00139 // and we work on the extracted tar file. 00140 // This improves the extraction speed by the tar ioslave dramatically, 00141 // if the archive file contains many files. 00142 // This is because the tar ioslave extracts one file after the other and normally 00143 // has to walk through the decompression filter each time. 00144 // Which is in fact nearly as slow as a complete decompression for each file. 00145 00146 Q_ASSERT(!d->tmpFile); 00147 d->tmpFile = new KTemporaryFile(); 00148 d->tmpFile->setPrefix(QLatin1String("ktar-")); 00149 d->tmpFile->setSuffix(QLatin1String(".tar")); 00150 d->tmpFile->open(); 00151 //kDebug(7041) << "creating tempfile:" << d->tmpFile->fileName(); 00152 00153 setDevice(d->tmpFile); 00154 return true; 00155 } 00156 } 00157 00158 KTar::~KTar() 00159 { 00160 // mjarrett: Closes to prevent ~KArchive from aborting w/o device 00161 if( isOpen() ) 00162 close(); 00163 00164 delete d->tmpFile; 00165 delete d; 00166 } 00167 00168 void KTar::setOrigFileName( const QByteArray & fileName ) { 00169 if ( !isOpen() || !(mode() & QIODevice::WriteOnly) ) 00170 { 00171 kWarning(7041) << "KTar::setOrigFileName: File must be opened for writing first.\n"; 00172 return; 00173 } 00174 d->origFileName = fileName; 00175 } 00176 00177 qint64 KTar::KTarPrivate::readRawHeader( char *buffer ) { 00178 // Read header 00179 qint64 n = q->device()->read( buffer, 0x200 ); 00180 if ( n == 0x200 && buffer[0] != 0 ) { 00181 // Make sure this is actually a tar header 00182 if (strncmp(buffer + 257, "ustar", 5)) { 00183 // The magic isn't there (broken/old tars), but maybe a correct checksum? 00184 00185 int check = 0; 00186 for( uint j = 0; j < 0x200; ++j ) 00187 check += buffer[j]; 00188 00189 // adjust checksum to count the checksum fields as blanks 00190 for( uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++ ) 00191 check -= buffer[148 + j]; 00192 check += 8 * ' '; 00193 00194 QByteArray s = QByteArray::number( check, 8 ); // octal 00195 00196 // only compare those of the 6 checksum digits that mean something, 00197 // because the other digits are filled with all sorts of different chars by different tars ... 00198 // Some tars right-justify the checksum so it could start in one of three places - we have to check each. 00199 if( strncmp( buffer + 148 + 6 - s.length(), s.data(), s.length() ) 00200 && strncmp( buffer + 148 + 7 - s.length(), s.data(), s.length() ) 00201 && strncmp( buffer + 148 + 8 - s.length(), s.data(), s.length() ) ) { 00202 kWarning(7041) << "KTar: invalid TAR file. Header is:" << QByteArray( buffer+257, 5 ) 00203 << "instead of ustar. Reading from wrong pos in file?" 00204 << "checksum=" << QByteArray( buffer + 148 + 6 - s.length(), s.length() ); 00205 return -1; 00206 } 00207 }/*end if*/ 00208 } else { 00209 // reset to 0 if 0x200 because logical end of archive has been reached 00210 if (n == 0x200) n = 0; 00211 }/*end if*/ 00212 return n; 00213 } 00214 00215 bool KTar::KTarPrivate::readLonglink(char *buffer,QByteArray &longlink) { 00216 qint64 n = 0; 00217 //kDebug() << "reading longlink from pos " << device()->pos(); 00218 QIODevice *dev = q->device(); 00219 // read size of longlink from size field in header 00220 // size is in bytes including the trailing null (which we ignore) 00221 qint64 size = QByteArray( buffer + 0x7c, 12 ).trimmed().toLongLong( 0, 8 /*octal*/ ); 00222 00223 size--; // ignore trailing null 00224 longlink.resize(size); 00225 qint64 offset = 0; 00226 while (size > 0) { 00227 int chunksize = qMin(size, 0x200LL); 00228 n = dev->read( longlink.data() + offset, chunksize ); 00229 if (n == -1) return false; 00230 size -= chunksize; 00231 offset += 0x200; 00232 }/*wend*/ 00233 // jump over the rest 00234 const int skip = 0x200 - (n % 0x200); 00235 if (skip < 0x200) { 00236 if (dev->read(buffer,skip) != skip) 00237 return false; 00238 } 00239 return true; 00240 } 00241 00242 qint64 KTar::KTarPrivate::readHeader( char *buffer, QString &name, QString &symlink ) { 00243 name.truncate(0); 00244 symlink.truncate(0); 00245 while (true) { 00246 qint64 n = readRawHeader(buffer); 00247 if (n != 0x200) return n; 00248 00249 // is it a longlink? 00250 if (strcmp(buffer,"././@LongLink") == 0) { 00251 char typeflag = buffer[0x9c]; 00252 QByteArray longlink; 00253 readLonglink(buffer,longlink); 00254 switch (typeflag) { 00255 case 'L': name = QFile::decodeName(longlink); break; 00256 case 'K': symlink = QFile::decodeName(longlink); break; 00257 }/*end switch*/ 00258 } else { 00259 break; 00260 }/*end if*/ 00261 }/*wend*/ 00262 00263 // if not result of longlink, read names directly from the header 00264 if (name.isEmpty()) 00265 // there are names that are exactly 100 bytes long 00266 // and neither longlink nor \0 terminated (bug:101472) 00267 name = QFile::decodeName(QByteArray(buffer, 100)); 00268 if (symlink.isEmpty()) 00269 symlink = QFile::decodeName(QByteArray(buffer + 0x9d /*?*/, 100)); 00270 00271 return 0x200; 00272 } 00273 00274 /* 00275 * If we have created a temporary file, we have 00276 * to decompress the original file now and write 00277 * the contents to the temporary file. 00278 */ 00279 bool KTar::KTarPrivate::fillTempFile( const QString & fileName) { 00280 if ( ! tmpFile ) 00281 return true; 00282 00283 //kDebug(7041) << "filling tmpFile of mimetype" << mimetype; 00284 00285 bool forced = false; 00286 if ( QLatin1String(application_gzip) == mimetype || QLatin1String(application_bzip) == mimetype ) 00287 forced = true; 00288 00289 QIODevice *filterDev = KFilterDev::deviceForFile( fileName, mimetype, forced ); 00290 00291 if( filterDev ) { 00292 QFile* file = tmpFile; 00293 Q_ASSERT(file->isOpen()); 00294 Q_ASSERT(file->openMode() & QIODevice::WriteOnly); 00295 file->seek(0); 00296 QByteArray buffer; 00297 buffer.resize(8*1024); 00298 if ( ! filterDev->open( QIODevice::ReadOnly ) ) 00299 { 00300 delete filterDev; 00301 return false; 00302 } 00303 qint64 len = -1; 00304 while ( !filterDev->atEnd() && len != 0 ) { 00305 len = filterDev->read(buffer.data(),buffer.size()); 00306 if ( len < 0 ) { // corrupted archive 00307 delete filterDev; 00308 return false; 00309 } 00310 if ( file->write(buffer.data(), len) != len ) { // disk full 00311 delete filterDev; 00312 return false; 00313 } 00314 } 00315 filterDev->close(); 00316 delete filterDev; 00317 00318 file->flush(); 00319 file->seek(0); 00320 Q_ASSERT(file->isOpen()); 00321 Q_ASSERT(file->openMode() & QIODevice::ReadOnly); 00322 } else { 00323 kDebug(7041) << "no filterdevice found!"; 00324 } 00325 00326 //kDebug( 7041 ) << "filling tmpFile finished."; 00327 return true; 00328 } 00329 00330 bool KTar::openArchive( QIODevice::OpenMode mode ) { 00331 00332 if ( !(mode & QIODevice::ReadOnly) ) 00333 return true; 00334 00335 if ( !d->fillTempFile( fileName() ) ) 00336 return false; 00337 00338 // We'll use the permission and user/group of d->rootDir 00339 // for any directory we emulate (see findOrCreate) 00340 //struct stat buf; 00341 //stat( fileName(), &buf ); 00342 00343 d->dirList.clear(); 00344 QIODevice* dev = device(); 00345 00346 if ( !dev ) 00347 return false; 00348 00349 // read dir information 00350 char buffer[ 0x200 ]; 00351 bool ende = false; 00352 do 00353 { 00354 QString name; 00355 QString symlink; 00356 00357 // Read header 00358 qint64 n = d->readHeader( buffer, name, symlink ); 00359 if (n < 0) return false; 00360 if (n == 0x200) 00361 { 00362 bool isdir = false; 00363 00364 if ( name.endsWith( QLatin1Char( '/' ) ) ) 00365 { 00366 isdir = true; 00367 name.truncate( name.length() - 1 ); 00368 } 00369 00370 int pos = name.lastIndexOf( QLatin1Char('/') ); 00371 QString nm = ( pos == -1 ) ? name : name.mid( pos + 1 ); 00372 00373 // read access 00374 buffer[ 0x6b ] = 0; 00375 char *dummy; 00376 const char* p = buffer + 0x64; 00377 while( *p == ' ' ) ++p; 00378 int access = (int)strtol( p, &dummy, 8 ); 00379 00380 // read user and group 00381 QString user = QString::fromLocal8Bit( buffer + 0x109 ); 00382 QString group = QString::fromLocal8Bit( buffer + 0x129 ); 00383 00384 // read time 00385 buffer[ 0x93 ] = 0; 00386 p = buffer + 0x88; 00387 while( *p == ' ' ) ++p; 00388 int time = (int)strtol( p, &dummy, 8 ); 00389 00390 // read type flag 00391 char typeflag = buffer[ 0x9c ]; 00392 // '0' for files, '1' hard link, '2' symlink, '5' for directory 00393 // (and 'L' for longlink fileNames, 'K' for longlink symlink targets) 00394 // and 'D' for GNU tar extension DUMPDIR 00395 if ( typeflag == '5' ) 00396 isdir = true; 00397 00398 bool isDumpDir = false; 00399 if ( typeflag == 'D' ) 00400 { 00401 isdir = false; 00402 isDumpDir = true; 00403 } 00404 //kDebug(7041) << "typeflag=" << typeflag << " islink=" << ( typeflag == '1' || typeflag == '2' ); 00405 00406 if (isdir) 00407 access |= S_IFDIR; // f*cking broken tar files 00408 00409 KArchiveEntry* e; 00410 if ( isdir ) 00411 { 00412 //kDebug(7041) << "directory" << nm; 00413 e = new KArchiveDirectory( this, nm, access, time, user, group, symlink ); 00414 } 00415 else 00416 { 00417 // read size 00418 QByteArray sizeBuffer( buffer + 0x7c, 12 ); 00419 qint64 size = sizeBuffer.trimmed().toLongLong( 0, 8 /*octal*/ ); 00420 //kDebug(7041) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size; 00421 00422 // for isDumpDir we will skip the additional info about that dirs contents 00423 if ( isDumpDir ) 00424 { 00425 //kDebug(7041) << nm << "isDumpDir"; 00426 e = new KArchiveDirectory( this, nm, access, time, user, group, symlink ); 00427 } 00428 else 00429 { 00430 00431 // Let's hack around hard links. Our classes don't support that, so make them symlinks 00432 if ( typeflag == '1' ) 00433 { 00434 kDebug(7041) << "Hard link, setting size to 0 instead of" << size; 00435 size = 0; // no contents 00436 } 00437 00438 //kDebug(7041) << "file" << nm << "size=" << size; 00439 e = new KArchiveFile( this, nm, access, time, user, group, symlink, 00440 dev->pos(), size ); 00441 } 00442 00443 // Skip contents + align bytes 00444 qint64 rest = size % 0x200; 00445 qint64 skip = size + (rest ? 0x200 - rest : 0); 00446 //kDebug(7041) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip; 00447 if (! dev->seek( dev->pos() + skip ) ) 00448 kWarning(7041) << "skipping" << skip << "failed"; 00449 } 00450 00451 if ( pos == -1 ) 00452 { 00453 if (nm == QLatin1String(".")) { // special case 00454 Q_ASSERT( isdir ); 00455 if ( isdir ) 00456 setRootDir( static_cast<KArchiveDirectory *>( e ) ); 00457 } 00458 else 00459 rootDir()->addEntry( e ); 00460 } 00461 else 00462 { 00463 // In some tar files we can find dir/./file => call cleanPath 00464 QString path = QDir::cleanPath( name.left( pos ) ); 00465 // Ensure container directory exists, create otherwise 00466 KArchiveDirectory * d = findOrCreate( path ); 00467 d->addEntry( e ); 00468 } 00469 } 00470 else 00471 { 00472 //qDebug("Terminating. Read %d bytes, first one is %d", n, buffer[0]); 00473 d->tarEnd = dev->pos() - n; // Remember end of archive 00474 ende = true; 00475 } 00476 } while( !ende ); 00477 return true; 00478 } 00479 00480 /* 00481 * Writes back the changes of the temporary file 00482 * to the original file. 00483 * Must only be called if in write mode, not in read mode 00484 */ 00485 bool KTar::KTarPrivate::writeBackTempFile( const QString & fileName ) 00486 { 00487 if ( !tmpFile ) 00488 return true; 00489 00490 //kDebug(7041) << "Write temporary file to compressed file" << fileName << mimetype; 00491 00492 bool forced = false; 00493 if (QLatin1String(application_gzip) == mimetype || QLatin1String(application_bzip) == mimetype || 00494 QLatin1String(application_lzma) == mimetype || QLatin1String(application_xz) == mimetype) 00495 forced = true; 00496 00497 // #### TODO this should use KSaveFile to avoid problems on disk full 00498 // (KArchive uses KSaveFile by default, but the temp-uncompressed-file trick 00499 // circumvents that). 00500 00501 QIODevice *dev = KFilterDev::deviceForFile( fileName, mimetype, forced ); 00502 if( dev ) { 00503 QFile* file = tmpFile; 00504 if ( !dev->open(QIODevice::WriteOnly) ) 00505 { 00506 file->close(); 00507 delete dev; 00508 return false; 00509 } 00510 if ( forced ) 00511 static_cast<KFilterDev *>(dev)->setOrigFileName( origFileName ); 00512 file->seek(0); 00513 QByteArray buffer; 00514 buffer.resize(8*1024); 00515 qint64 len; 00516 while ( !file->atEnd()) { 00517 len = file->read(buffer.data(), buffer.size()); 00518 dev->write(buffer.data(),len); // TODO error checking 00519 } 00520 file->close(); 00521 dev->close(); 00522 delete dev; 00523 } 00524 00525 //kDebug(7041) << "Write temporary file to compressed file done."; 00526 return true; 00527 } 00528 00529 bool KTar::closeArchive() { 00530 d->dirList.clear(); 00531 00532 bool ok = true; 00533 00534 // If we are in readwrite mode and had created 00535 // a temporary tar file, we have to write 00536 // back the changes to the original file 00537 if (d->tmpFile && (mode() & QIODevice::WriteOnly)) { 00538 ok = d->writeBackTempFile( fileName() ); 00539 delete d->tmpFile; 00540 d->tmpFile = 0; 00541 setDevice(0); 00542 } 00543 00544 return ok; 00545 } 00546 00547 bool KTar::doFinishWriting( qint64 size ) { 00548 // Write alignment 00549 int rest = size % 0x200; 00550 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00551 d->tarEnd = device()->pos() + (rest ? 0x200 - rest : 0); // Record our new end of archive 00552 if ( rest ) 00553 { 00554 char buffer[ 0x201 ]; 00555 for( uint i = 0; i < 0x200; ++i ) 00556 buffer[i] = 0; 00557 qint64 nwritten = device()->write( buffer, 0x200 - rest ); 00558 return nwritten == 0x200 - rest; 00559 } 00560 return true; 00561 } 00562 00563 /*** Some help from the tar sources 00564 struct posix_header 00565 { byte offset 00566 char name[100]; * 0 * 0x0 00567 char mode[8]; * 100 * 0x64 00568 char uid[8]; * 108 * 0x6c 00569 char gid[8]; * 116 * 0x74 00570 char size[12]; * 124 * 0x7c 00571 char mtime[12]; * 136 * 0x88 00572 char chksum[8]; * 148 * 0x94 00573 char typeflag; * 156 * 0x9c 00574 char linkname[100]; * 157 * 0x9d 00575 char magic[6]; * 257 * 0x101 00576 char version[2]; * 263 * 0x107 00577 char uname[32]; * 265 * 0x109 00578 char gname[32]; * 297 * 0x129 00579 char devmajor[8]; * 329 * 0x149 00580 char devminor[8]; * 337 * ... 00581 char prefix[155]; * 345 * 00582 * 500 * 00583 }; 00584 */ 00585 00586 void KTar::KTarPrivate::fillBuffer( char * buffer, 00587 const char * mode, qint64 size, time_t mtime, char typeflag, 00588 const char * uname, const char * gname ) { 00589 // mode (as in stpos()) 00590 assert( strlen(mode) == 6 ); 00591 memcpy( buffer+0x64, mode, 6 ); 00592 buffer[ 0x6a ] = ' '; 00593 buffer[ 0x6b ] = '\0'; 00594 00595 // dummy uid 00596 strcpy( buffer + 0x6c, " 765 "); 00597 // dummy gid 00598 strcpy( buffer + 0x74, " 144 "); 00599 00600 // size 00601 QByteArray s = QByteArray::number( size, 8 ); // octal 00602 s = s.rightJustified( 11, '0' ); 00603 memcpy( buffer + 0x7c, s.data(), 11 ); 00604 buffer[ 0x87 ] = ' '; // space-terminate (no null after) 00605 00606 // modification time 00607 s = QByteArray::number( static_cast<qulonglong>(mtime), 8 ); // octal 00608 s = s.rightJustified( 11, '0' ); 00609 memcpy( buffer + 0x88, s.data(), 11 ); 00610 buffer[ 0x93 ] = ' '; // space-terminate (no null after) -- well current tar writes a null byte 00611 00612 // spaces, replaced by the check sum later 00613 buffer[ 0x94 ] = 0x20; 00614 buffer[ 0x95 ] = 0x20; 00615 buffer[ 0x96 ] = 0x20; 00616 buffer[ 0x97 ] = 0x20; 00617 buffer[ 0x98 ] = 0x20; 00618 buffer[ 0x99 ] = 0x20; 00619 00620 /* From the tar sources : 00621 Fill in the checksum field. It's formatted differently from the 00622 other fields: it has [6] digits, a null, then a space -- rather than 00623 digits, a space, then a null. */ 00624 00625 buffer[ 0x9a ] = '\0'; 00626 buffer[ 0x9b ] = ' '; 00627 00628 // type flag (dir, file, link) 00629 buffer[ 0x9c ] = typeflag; 00630 00631 // magic + version 00632 strcpy( buffer + 0x101, "ustar"); 00633 strcpy( buffer + 0x107, "00" ); 00634 00635 // user 00636 strcpy( buffer + 0x109, uname ); 00637 // group 00638 strcpy( buffer + 0x129, gname ); 00639 00640 // Header check sum 00641 int check = 32; 00642 for( uint j = 0; j < 0x200; ++j ) 00643 check += buffer[j]; 00644 s = QByteArray::number( check, 8 ); // octal 00645 s = s.rightJustified( 6, '0' ); 00646 memcpy( buffer + 0x94, s.constData(), 6 ); 00647 } 00648 00649 void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag, 00650 const char *uname, const char *gname) { 00651 strcpy( buffer, "././@LongLink" ); 00652 qint64 namelen = name.length() + 1; 00653 fillBuffer( buffer, " 0", namelen, 0, typeflag, uname, gname ); 00654 q->device()->write( buffer, 0x200 ); // TODO error checking 00655 qint64 offset = 0; 00656 while (namelen > 0) { 00657 int chunksize = qMin(namelen, 0x200LL); 00658 memcpy(buffer, name.data()+offset, chunksize); 00659 // write long name 00660 q->device()->write( buffer, 0x200 ); // TODO error checking 00661 // not even needed to reclear the buffer, tar doesn't do it 00662 namelen -= chunksize; 00663 offset += 0x200; 00664 }/*wend*/ 00665 } 00666 00667 bool KTar::doPrepareWriting(const QString &name, const QString &user, 00668 const QString &group, qint64 size, mode_t perm, 00669 time_t /*atime*/, time_t mtime, time_t /*ctime*/) { 00670 if ( !isOpen() ) 00671 { 00672 kWarning(7041) << "You must open the tar file before writing to it\n"; 00673 return false; 00674 } 00675 00676 if ( !(mode() & QIODevice::WriteOnly) ) 00677 { 00678 kWarning(7041) << "You must open the tar file for writing\n"; 00679 return false; 00680 } 00681 00682 // In some tar files we can find dir/./file => call cleanPath 00683 QString fileName ( QDir::cleanPath( name ) ); 00684 00685 /* 00686 // Create toplevel dirs 00687 // Commented out by David since it's not necessary, and if anybody thinks it is, 00688 // he needs to implement a findOrCreate equivalent in writeDir. 00689 // But as KTar and the "tar" program both handle tar files without 00690 // dir entries, there's really no need for that 00691 QString tmp ( fileName ); 00692 int i = tmp.lastIndexOf( '/' ); 00693 if ( i != -1 ) 00694 { 00695 QString d = tmp.left( i + 1 ); // contains trailing slash 00696 if ( !m_dirList.contains( d ) ) 00697 { 00698 tmp = tmp.mid( i + 1 ); 00699 writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs 00700 } 00701 } 00702 */ 00703 00704 char buffer[ 0x201 ]; 00705 memset( buffer, 0, 0x200 ); 00706 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00707 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read 00708 00709 // provide converted stuff we need later on 00710 const QByteArray encodedFileName = QFile::encodeName(fileName); 00711 const QByteArray uname = user.toLocal8Bit(); 00712 const QByteArray gname = group.toLocal8Bit(); 00713 00714 // If more than 100 chars, we need to use the LongLink trick 00715 if ( fileName.length() > 99 ) 00716 d->writeLonglink(buffer,encodedFileName,'L',uname,gname); 00717 00718 // Write (potentially truncated) name 00719 strncpy( buffer, encodedFileName, 99 ); 00720 buffer[99] = 0; 00721 // zero out the rest (except for what gets filled anyways) 00722 memset(buffer+0x9d, 0, 0x200 - 0x9d); 00723 00724 QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 ); 00725 permstr = permstr.rightJustified(6, '0'); 00726 d->fillBuffer(buffer, permstr, size, mtime, 0x30, uname, gname); 00727 00728 // Write header 00729 return device()->write( buffer, 0x200 ) == 0x200; 00730 } 00731 00732 bool KTar::doWriteDir(const QString &name, const QString &user, 00733 const QString &group, mode_t perm, 00734 time_t /*atime*/, time_t mtime, time_t /*ctime*/) { 00735 if ( !isOpen() ) 00736 { 00737 kWarning(7041) << "You must open the tar file before writing to it\n"; 00738 return false; 00739 } 00740 00741 if ( !(mode() & QIODevice::WriteOnly) ) 00742 { 00743 kWarning(7041) << "You must open the tar file for writing\n"; 00744 return false; 00745 } 00746 00747 // In some tar files we can find dir/./ => call cleanPath 00748 QString dirName ( QDir::cleanPath( name ) ); 00749 00750 // Need trailing '/' 00751 if ( !dirName.endsWith( QLatin1Char( '/' ) ) ) 00752 dirName += QLatin1Char( '/' ); 00753 00754 if ( d->dirList.contains( dirName ) ) 00755 return true; // already there 00756 00757 char buffer[ 0x201 ]; 00758 memset( buffer, 0, 0x200 ); 00759 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00760 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read 00761 00762 // provide converted stuff we need lateron 00763 QByteArray encodedDirname = QFile::encodeName(dirName); 00764 QByteArray uname = user.toLocal8Bit(); 00765 QByteArray gname = group.toLocal8Bit(); 00766 00767 // If more than 100 chars, we need to use the LongLink trick 00768 if ( dirName.length() > 99 ) 00769 d->writeLonglink(buffer,encodedDirname,'L',uname,gname); 00770 00771 // Write (potentially truncated) name 00772 strncpy( buffer, encodedDirname, 99 ); 00773 buffer[99] = 0; 00774 // zero out the rest (except for what gets filled anyways) 00775 memset(buffer+0x9d, 0, 0x200 - 0x9d); 00776 00777 QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 ); 00778 permstr = permstr.rightJustified(6, ' '); 00779 d->fillBuffer( buffer, permstr, 0, mtime, 0x35, uname, gname); 00780 00781 // Write header 00782 device()->write( buffer, 0x200 ); 00783 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00784 d->tarEnd = device()->pos(); 00785 00786 d->dirList.append( dirName ); // contains trailing slash 00787 return true; // TODO if wanted, better error control 00788 } 00789 00790 bool KTar::doWriteSymLink(const QString &name, const QString &target, 00791 const QString &user, const QString &group, 00792 mode_t perm, time_t /*atime*/, time_t mtime, time_t /*ctime*/) { 00793 if ( !isOpen() ) 00794 { 00795 kWarning(7041) << "You must open the tar file before writing to it\n"; 00796 return false; 00797 } 00798 00799 if ( !(mode() & QIODevice::WriteOnly) ) 00800 { 00801 kWarning(7041) << "You must open the tar file for writing\n"; 00802 return false; 00803 } 00804 00805 // In some tar files we can find dir/./file => call cleanPath 00806 QString fileName ( QDir::cleanPath( name ) ); 00807 00808 char buffer[ 0x201 ]; 00809 memset( buffer, 0, 0x200 ); 00810 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00811 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read 00812 00813 // provide converted stuff we need lateron 00814 QByteArray encodedFileName = QFile::encodeName(fileName); 00815 QByteArray encodedTarget = QFile::encodeName(target); 00816 QByteArray uname = user.toLocal8Bit(); 00817 QByteArray gname = group.toLocal8Bit(); 00818 00819 // If more than 100 chars, we need to use the LongLink trick 00820 if (target.length() > 99) 00821 d->writeLonglink(buffer,encodedTarget,'K',uname,gname); 00822 if ( fileName.length() > 99 ) 00823 d->writeLonglink(buffer,encodedFileName,'L',uname,gname); 00824 00825 // Write (potentially truncated) name 00826 strncpy( buffer, encodedFileName, 99 ); 00827 buffer[99] = 0; 00828 // Write (potentially truncated) symlink target 00829 strncpy(buffer+0x9d, encodedTarget, 99); 00830 buffer[0x9d+99] = 0; 00831 // zero out the rest 00832 memset(buffer+0x9d+100, 0, 0x200 - 100 - 0x9d); 00833 00834 QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 ); 00835 permstr = permstr.rightJustified(6, ' '); 00836 d->fillBuffer(buffer, permstr, 0, mtime, 0x32, uname, gname); 00837 00838 // Write header 00839 bool retval = device()->write( buffer, 0x200 ) == 0x200; 00840 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00841 d->tarEnd = device()->pos(); 00842 return retval; 00843 } 00844 00845 void KTar::virtual_hook( int id, void* data ) { 00846 KArchive::virtual_hook( id, data ); 00847 }
KDE 4.7 API Reference