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