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 // we need to test if there is a prefix value because the file name can be null 00181 // and the prefix can have a value and in this case we don't reset n. 00182 if ( n == 0x200 && (buffer[0] != 0 || buffer[0x159] != 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 } else { 00325 kDebug(7041) << "no filterdevice found!"; 00326 } 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 bool isGlobalHeader = false; 00366 00367 if ( name.endsWith( QLatin1Char( '/' ) ) ) 00368 { 00369 isdir = true; 00370 name.truncate( name.length() - 1 ); 00371 } 00372 00373 QByteArray prefix = QByteArray(buffer + 0x159, 155); 00374 if (prefix[0] != '\0') { 00375 name = (QString::fromLatin1(prefix.constData()) + QLatin1Char('/') + name); 00376 } 00377 00378 int pos = name.lastIndexOf( QLatin1Char('/') ); 00379 QString nm = ( pos == -1 ) ? name : name.mid( pos + 1 ); 00380 00381 // read access 00382 buffer[ 0x6b ] = 0; 00383 char *dummy; 00384 const char* p = buffer + 0x64; 00385 while( *p == ' ' ) ++p; 00386 int access = (int)strtol( p, &dummy, 8 ); 00387 00388 // read user and group 00389 QString user = QString::fromLocal8Bit( buffer + 0x109 ); 00390 QString group = QString::fromLocal8Bit( buffer + 0x129 ); 00391 00392 // read time 00393 buffer[ 0x93 ] = 0; 00394 p = buffer + 0x88; 00395 while( *p == ' ' ) ++p; 00396 int time = (int)strtol( p, &dummy, 8 ); 00397 00398 // read type flag 00399 char typeflag = buffer[ 0x9c ]; 00400 // '0' for files, '1' hard link, '2' symlink, '5' for directory 00401 // (and 'L' for longlink fileNames, 'K' for longlink symlink targets) 00402 // 'D' for GNU tar extension DUMPDIR, 'x' for Extended header referring 00403 // to the next file in the archive and 'g' for Global extended header 00404 if ( typeflag == 'g' ) 00405 isGlobalHeader = true; 00406 00407 if ( typeflag == '5' ) 00408 isdir = true; 00409 00410 bool isDumpDir = false; 00411 if ( typeflag == 'D' ) 00412 { 00413 isdir = false; 00414 isDumpDir = true; 00415 } 00416 //kDebug(7041) << "typeflag=" << typeflag << " islink=" << ( typeflag == '1' || typeflag == '2' ); 00417 00418 if (isdir) 00419 access |= S_IFDIR; // f*cking broken tar files 00420 00421 KArchiveEntry* e; 00422 if ( isdir ) 00423 { 00424 //kDebug(7041) << "directory" << nm; 00425 e = new KArchiveDirectory( this, nm, access, time, user, group, symlink ); 00426 } 00427 else 00428 { 00429 // read size 00430 QByteArray sizeBuffer( buffer + 0x7c, 12 ); 00431 qint64 size = sizeBuffer.trimmed().toLongLong( 0, 8 /*octal*/ ); 00432 //kDebug(7041) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size; 00433 00434 // for isDumpDir we will skip the additional info about that dirs contents 00435 if ( isDumpDir ) 00436 { 00437 //kDebug(7041) << nm << "isDumpDir"; 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 kDebug(7041) << "Hard link, setting size to 0 instead of" << size; 00447 size = 0; // no contents 00448 } 00449 00450 //kDebug(7041) << "file" << nm << "size=" << size; 00451 e = new KArchiveFile( this, nm, access, time, user, group, symlink, 00452 dev->pos(), size ); 00453 } 00454 00455 // Skip contents + align bytes 00456 qint64 rest = size % 0x200; 00457 qint64 skip = size + (rest ? 0x200 - rest : 0); 00458 //kDebug(7041) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip; 00459 if (! dev->seek( dev->pos() + skip ) ) 00460 kWarning(7041) << "skipping" << skip << "failed"; 00461 } 00462 00463 if (isGlobalHeader) 00464 continue; 00465 00466 if ( pos == -1 ) 00467 { 00468 if (nm == QLatin1String(".")) { // special case 00469 Q_ASSERT( isdir ); 00470 if ( isdir ) 00471 setRootDir( static_cast<KArchiveDirectory *>( e ) ); 00472 } 00473 else 00474 rootDir()->addEntry( e ); 00475 } 00476 else 00477 { 00478 // In some tar files we can find dir/./file => call cleanPath 00479 QString path = QDir::cleanPath( name.left( pos ) ); 00480 // Ensure container directory exists, create otherwise 00481 KArchiveDirectory * d = findOrCreate( path ); 00482 d->addEntry( e ); 00483 } 00484 } 00485 else 00486 { 00487 //qDebug("Terminating. Read %d bytes, first one is %d", n, buffer[0]); 00488 d->tarEnd = dev->pos() - n; // Remember end of archive 00489 ende = true; 00490 } 00491 } while( !ende ); 00492 return true; 00493 } 00494 00495 /* 00496 * Writes back the changes of the temporary file 00497 * to the original file. 00498 * Must only be called if in write mode, not in read mode 00499 */ 00500 bool KTar::KTarPrivate::writeBackTempFile( const QString & fileName ) 00501 { 00502 if ( !tmpFile ) 00503 return true; 00504 00505 //kDebug(7041) << "Write temporary file to compressed file" << fileName << mimetype; 00506 00507 bool forced = false; 00508 if (QLatin1String(application_gzip) == mimetype || QLatin1String(application_bzip) == mimetype || 00509 QLatin1String(application_lzma) == mimetype || QLatin1String(application_xz) == mimetype) 00510 forced = true; 00511 00512 // #### TODO this should use KSaveFile to avoid problems on disk full 00513 // (KArchive uses KSaveFile by default, but the temp-uncompressed-file trick 00514 // circumvents that). 00515 00516 QIODevice *dev = KFilterDev::deviceForFile( fileName, mimetype, forced ); 00517 if( dev ) { 00518 QFile* file = tmpFile; 00519 if ( !dev->open(QIODevice::WriteOnly) ) 00520 { 00521 file->close(); 00522 delete dev; 00523 return false; 00524 } 00525 if ( forced ) 00526 static_cast<KFilterDev *>(dev)->setOrigFileName( origFileName ); 00527 file->seek(0); 00528 QByteArray buffer; 00529 buffer.resize(8*1024); 00530 qint64 len; 00531 while ( !file->atEnd()) { 00532 len = file->read(buffer.data(), buffer.size()); 00533 dev->write(buffer.data(),len); // TODO error checking 00534 } 00535 file->close(); 00536 dev->close(); 00537 delete dev; 00538 } 00539 00540 //kDebug(7041) << "Write temporary file to compressed file done."; 00541 return true; 00542 } 00543 00544 bool KTar::closeArchive() { 00545 d->dirList.clear(); 00546 00547 bool ok = true; 00548 00549 // If we are in readwrite mode and had created 00550 // a temporary tar file, we have to write 00551 // back the changes to the original file 00552 if (d->tmpFile && (mode() & QIODevice::WriteOnly)) { 00553 ok = d->writeBackTempFile( fileName() ); 00554 delete d->tmpFile; 00555 d->tmpFile = 0; 00556 setDevice(0); 00557 } 00558 00559 return ok; 00560 } 00561 00562 bool KTar::doFinishWriting( qint64 size ) { 00563 // Write alignment 00564 int rest = size % 0x200; 00565 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00566 d->tarEnd = device()->pos() + (rest ? 0x200 - rest : 0); // Record our new end of archive 00567 if ( rest ) 00568 { 00569 char buffer[ 0x201 ]; 00570 for( uint i = 0; i < 0x200; ++i ) 00571 buffer[i] = 0; 00572 qint64 nwritten = device()->write( buffer, 0x200 - rest ); 00573 return nwritten == 0x200 - rest; 00574 } 00575 return true; 00576 } 00577 00578 /*** Some help from the tar sources 00579 struct posix_header 00580 { byte offset 00581 char name[100]; * 0 * 0x0 00582 char mode[8]; * 100 * 0x64 00583 char uid[8]; * 108 * 0x6c 00584 char gid[8]; * 116 * 0x74 00585 char size[12]; * 124 * 0x7c 00586 char mtime[12]; * 136 * 0x88 00587 char chksum[8]; * 148 * 0x94 00588 char typeflag; * 156 * 0x9c 00589 char linkname[100]; * 157 * 0x9d 00590 char magic[6]; * 257 * 0x101 00591 char version[2]; * 263 * 0x107 00592 char uname[32]; * 265 * 0x109 00593 char gname[32]; * 297 * 0x129 00594 char devmajor[8]; * 329 * 0x149 00595 char devminor[8]; * 337 * ... 00596 char prefix[155]; * 345 * 00597 * 500 * 00598 }; 00599 */ 00600 00601 void KTar::KTarPrivate::fillBuffer( char * buffer, 00602 const char * mode, qint64 size, time_t mtime, char typeflag, 00603 const char * uname, const char * gname ) { 00604 // mode (as in stpos()) 00605 assert( strlen(mode) == 6 ); 00606 memcpy( buffer+0x64, mode, 6 ); 00607 buffer[ 0x6a ] = ' '; 00608 buffer[ 0x6b ] = '\0'; 00609 00610 // dummy uid 00611 strcpy( buffer + 0x6c, " 765 "); 00612 // dummy gid 00613 strcpy( buffer + 0x74, " 144 "); 00614 00615 // size 00616 QByteArray s = QByteArray::number( size, 8 ); // octal 00617 s = s.rightJustified( 11, '0' ); 00618 memcpy( buffer + 0x7c, s.data(), 11 ); 00619 buffer[ 0x87 ] = ' '; // space-terminate (no null after) 00620 00621 // modification time 00622 s = QByteArray::number( static_cast<qulonglong>(mtime), 8 ); // octal 00623 s = s.rightJustified( 11, '0' ); 00624 memcpy( buffer + 0x88, s.data(), 11 ); 00625 buffer[ 0x93 ] = ' '; // space-terminate (no null after) -- well current tar writes a null byte 00626 00627 // spaces, replaced by the check sum later 00628 buffer[ 0x94 ] = 0x20; 00629 buffer[ 0x95 ] = 0x20; 00630 buffer[ 0x96 ] = 0x20; 00631 buffer[ 0x97 ] = 0x20; 00632 buffer[ 0x98 ] = 0x20; 00633 buffer[ 0x99 ] = 0x20; 00634 00635 /* From the tar sources : 00636 Fill in the checksum field. It's formatted differently from the 00637 other fields: it has [6] digits, a null, then a space -- rather than 00638 digits, a space, then a null. */ 00639 00640 buffer[ 0x9a ] = '\0'; 00641 buffer[ 0x9b ] = ' '; 00642 00643 // type flag (dir, file, link) 00644 buffer[ 0x9c ] = typeflag; 00645 00646 // magic + version 00647 strcpy( buffer + 0x101, "ustar"); 00648 strcpy( buffer + 0x107, "00" ); 00649 00650 // user 00651 strcpy( buffer + 0x109, uname ); 00652 // group 00653 strcpy( buffer + 0x129, gname ); 00654 00655 // Header check sum 00656 int check = 32; 00657 for( uint j = 0; j < 0x200; ++j ) 00658 check += buffer[j]; 00659 s = QByteArray::number( check, 8 ); // octal 00660 s = s.rightJustified( 6, '0' ); 00661 memcpy( buffer + 0x94, s.constData(), 6 ); 00662 } 00663 00664 void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag, 00665 const char *uname, const char *gname) { 00666 strcpy( buffer, "././@LongLink" ); 00667 qint64 namelen = name.length() + 1; 00668 fillBuffer( buffer, " 0", namelen, 0, typeflag, uname, gname ); 00669 q->device()->write( buffer, 0x200 ); // TODO error checking 00670 qint64 offset = 0; 00671 while (namelen > 0) { 00672 int chunksize = qMin(namelen, 0x200LL); 00673 memcpy(buffer, name.data()+offset, chunksize); 00674 // write long name 00675 q->device()->write( buffer, 0x200 ); // TODO error checking 00676 // not even needed to reclear the buffer, tar doesn't do it 00677 namelen -= chunksize; 00678 offset += 0x200; 00679 }/*wend*/ 00680 } 00681 00682 bool KTar::doPrepareWriting(const QString &name, const QString &user, 00683 const QString &group, qint64 size, mode_t perm, 00684 time_t /*atime*/, time_t mtime, time_t /*ctime*/) { 00685 if ( !isOpen() ) 00686 { 00687 kWarning(7041) << "You must open the tar file before writing to it\n"; 00688 return false; 00689 } 00690 00691 if ( !(mode() & QIODevice::WriteOnly) ) 00692 { 00693 kWarning(7041) << "You must open the tar file for writing\n"; 00694 return false; 00695 } 00696 00697 // In some tar files we can find dir/./file => call cleanPath 00698 QString fileName ( QDir::cleanPath( name ) ); 00699 00700 /* 00701 // Create toplevel dirs 00702 // Commented out by David since it's not necessary, and if anybody thinks it is, 00703 // he needs to implement a findOrCreate equivalent in writeDir. 00704 // But as KTar and the "tar" program both handle tar files without 00705 // dir entries, there's really no need for that 00706 QString tmp ( fileName ); 00707 int i = tmp.lastIndexOf( '/' ); 00708 if ( i != -1 ) 00709 { 00710 QString d = tmp.left( i + 1 ); // contains trailing slash 00711 if ( !m_dirList.contains( d ) ) 00712 { 00713 tmp = tmp.mid( i + 1 ); 00714 writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs 00715 } 00716 } 00717 */ 00718 00719 char buffer[ 0x201 ]; 00720 memset( buffer, 0, 0x200 ); 00721 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00722 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read 00723 00724 // provide converted stuff we need later on 00725 const QByteArray encodedFileName = QFile::encodeName(fileName); 00726 const QByteArray uname = user.toLocal8Bit(); 00727 const QByteArray gname = group.toLocal8Bit(); 00728 00729 // If more than 100 chars, we need to use the LongLink trick 00730 if ( fileName.length() > 99 ) 00731 d->writeLonglink(buffer,encodedFileName,'L',uname,gname); 00732 00733 // Write (potentially truncated) name 00734 strncpy( buffer, encodedFileName, 99 ); 00735 buffer[99] = 0; 00736 // zero out the rest (except for what gets filled anyways) 00737 memset(buffer+0x9d, 0, 0x200 - 0x9d); 00738 00739 QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 ); 00740 permstr = permstr.rightJustified(6, '0'); 00741 d->fillBuffer(buffer, permstr, size, mtime, 0x30, uname, gname); 00742 00743 // Write header 00744 return device()->write( buffer, 0x200 ) == 0x200; 00745 } 00746 00747 bool KTar::doWriteDir(const QString &name, const QString &user, 00748 const QString &group, mode_t perm, 00749 time_t /*atime*/, time_t mtime, time_t /*ctime*/) { 00750 if ( !isOpen() ) 00751 { 00752 kWarning(7041) << "You must open the tar file before writing to it\n"; 00753 return false; 00754 } 00755 00756 if ( !(mode() & QIODevice::WriteOnly) ) 00757 { 00758 kWarning(7041) << "You must open the tar file for writing\n"; 00759 return false; 00760 } 00761 00762 // In some tar files we can find dir/./ => call cleanPath 00763 QString dirName ( QDir::cleanPath( name ) ); 00764 00765 // Need trailing '/' 00766 if ( !dirName.endsWith( QLatin1Char( '/' ) ) ) 00767 dirName += QLatin1Char( '/' ); 00768 00769 if ( d->dirList.contains( dirName ) ) 00770 return true; // already there 00771 00772 char buffer[ 0x201 ]; 00773 memset( buffer, 0, 0x200 ); 00774 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00775 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read 00776 00777 // provide converted stuff we need lateron 00778 QByteArray encodedDirname = QFile::encodeName(dirName); 00779 QByteArray uname = user.toLocal8Bit(); 00780 QByteArray gname = group.toLocal8Bit(); 00781 00782 // If more than 100 chars, we need to use the LongLink trick 00783 if ( dirName.length() > 99 ) 00784 d->writeLonglink(buffer,encodedDirname,'L',uname,gname); 00785 00786 // Write (potentially truncated) name 00787 strncpy( buffer, encodedDirname, 99 ); 00788 buffer[99] = 0; 00789 // zero out the rest (except for what gets filled anyways) 00790 memset(buffer+0x9d, 0, 0x200 - 0x9d); 00791 00792 QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 ); 00793 permstr = permstr.rightJustified(6, ' '); 00794 d->fillBuffer( buffer, permstr, 0, mtime, 0x35, uname, gname); 00795 00796 // Write header 00797 device()->write( buffer, 0x200 ); 00798 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00799 d->tarEnd = device()->pos(); 00800 00801 d->dirList.append( dirName ); // contains trailing slash 00802 return true; // TODO if wanted, better error control 00803 } 00804 00805 bool KTar::doWriteSymLink(const QString &name, const QString &target, 00806 const QString &user, const QString &group, 00807 mode_t perm, time_t /*atime*/, time_t mtime, time_t /*ctime*/) { 00808 if ( !isOpen() ) 00809 { 00810 kWarning(7041) << "You must open the tar file before writing to it\n"; 00811 return false; 00812 } 00813 00814 if ( !(mode() & QIODevice::WriteOnly) ) 00815 { 00816 kWarning(7041) << "You must open the tar file for writing\n"; 00817 return false; 00818 } 00819 00820 // In some tar files we can find dir/./file => call cleanPath 00821 QString fileName ( QDir::cleanPath( name ) ); 00822 00823 char buffer[ 0x201 ]; 00824 memset( buffer, 0, 0x200 ); 00825 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00826 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read 00827 00828 // provide converted stuff we need lateron 00829 QByteArray encodedFileName = QFile::encodeName(fileName); 00830 QByteArray encodedTarget = QFile::encodeName(target); 00831 QByteArray uname = user.toLocal8Bit(); 00832 QByteArray gname = group.toLocal8Bit(); 00833 00834 // If more than 100 chars, we need to use the LongLink trick 00835 if (target.length() > 99) 00836 d->writeLonglink(buffer,encodedTarget,'K',uname,gname); 00837 if ( fileName.length() > 99 ) 00838 d->writeLonglink(buffer,encodedFileName,'L',uname,gname); 00839 00840 // Write (potentially truncated) name 00841 strncpy( buffer, encodedFileName, 99 ); 00842 buffer[99] = 0; 00843 // Write (potentially truncated) symlink target 00844 strncpy(buffer+0x9d, encodedTarget, 99); 00845 buffer[0x9d+99] = 0; 00846 // zero out the rest 00847 memset(buffer+0x9d+100, 0, 0x200 - 100 - 0x9d); 00848 00849 QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 ); 00850 permstr = permstr.rightJustified(6, ' '); 00851 d->fillBuffer(buffer, permstr, 0, mtime, 0x32, uname, gname); 00852 00853 // Write header 00854 bool retval = device()->write( buffer, 0x200 ) == 0x200; 00855 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite ) 00856 d->tarEnd = device()->pos(); 00857 return retval; 00858 } 00859 00860 void KTar::virtual_hook( int id, void* data ) { 00861 KArchive::virtual_hook( id, data ); 00862 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu May 10 2012 20:49:33 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu May 10 2012 20:49:33 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.