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