File indexing completed on 2025-02-16 13:00:37
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org> 0003 SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "ktar.h" 0009 #include "karchive_p.h" 0010 #include "kcompressiondevice.h" 0011 #include "kfilterbase.h" 0012 #include "loggingcategory.h" 0013 0014 #include <QDebug> 0015 #include <QDir> 0016 #include <QFile> 0017 #include <QMimeDatabase> 0018 #include <QTemporaryFile> 0019 0020 #include <assert.h> 0021 #include <stdlib.h> // strtol 0022 0023 //////////////////////////////////////////////////////////////////////// 0024 /////////////////////////// KTar /////////////////////////////////// 0025 //////////////////////////////////////////////////////////////////////// 0026 0027 // Mime types of known filters 0028 static const char application_bzip[] = "application/x-bzip"; 0029 static const char application_lzma[] = "application/x-lzma"; 0030 static const char application_xz[] = "application/x-xz"; 0031 static const char application_zstd[] = "application/zstd"; 0032 0033 /* clang-format off */ 0034 namespace MimeType 0035 { 0036 QString application_gzip() { return QStringLiteral("application/gzip"); } 0037 QString application_gzip_old() { return QStringLiteral("application/x-gzip"); } 0038 } 0039 /* clang-format on */ 0040 0041 class Q_DECL_HIDDEN KTar::KTarPrivate 0042 { 0043 public: 0044 KTarPrivate(KTar *parent) 0045 : q(parent) 0046 , tarEnd(0) 0047 , tmpFile(nullptr) 0048 , compressionDevice(nullptr) 0049 { 0050 } 0051 0052 KTar *q; 0053 QStringList dirList; 0054 qint64 tarEnd; 0055 QTemporaryFile *tmpFile; 0056 QString mimetype; 0057 QByteArray origFileName; 0058 KCompressionDevice *compressionDevice; 0059 0060 bool fillTempFile(const QString &fileName); 0061 bool writeBackTempFile(const QString &fileName); 0062 void fillBuffer(char *buffer, const char *mode, qint64 size, const QDateTime &mtime, char typeflag, const char *uname, const char *gname); 0063 void writeLonglink(char *buffer, const QByteArray &name, char typeflag, const char *uname, const char *gname); 0064 qint64 readRawHeader(char *buffer); 0065 bool readLonglink(char *buffer, QByteArray &longlink); 0066 qint64 readHeader(char *buffer, QString &name, QString &symlink); 0067 }; 0068 0069 KTar::KTar(const QString &fileName, const QString &_mimetype) 0070 : KArchive(fileName) 0071 , d(new KTarPrivate(this)) 0072 { 0073 // shared-mime-info < 1.1 does not know about application/gzip. 0074 // While Qt has optionally a copy of shared-mime-info (1.10 for 5.15.0), 0075 // it uses the system one if it exists. 0076 // Once shared-mime-info 1.1 is required or can be assumed on all targeted 0077 // platforms (right now RHEL/CentOS 6 as target of appimage-based apps 0078 // bundling also karchive does not meet this requirement) 0079 // switch to use the new application/gzip id instead when interacting with QMimeDatabase 0080 // For now: map new name to legacy name and use that 0081 d->mimetype = (_mimetype == MimeType::application_gzip()) ? MimeType::application_gzip_old() : _mimetype; 0082 } 0083 0084 KTar::KTar(QIODevice *dev) 0085 : KArchive(dev) 0086 , d(new KTarPrivate(this)) 0087 { 0088 } 0089 0090 // Only called when a filename was given 0091 bool KTar::createDevice(QIODevice::OpenMode mode) 0092 { 0093 if (d->mimetype.isEmpty()) { 0094 // Find out mimetype manually 0095 0096 QMimeDatabase db; 0097 QMimeType mime; 0098 if (mode != QIODevice::WriteOnly && QFile::exists(fileName())) { 0099 // Give priority to file contents: if someone renames a .tar.bz2 to .tar.gz, 0100 // we can still do the right thing here. 0101 QFile f(fileName()); 0102 if (f.open(QIODevice::ReadOnly)) { 0103 mime = db.mimeTypeForData(&f); 0104 } 0105 if (!mime.isValid()) { 0106 // Unable to determine mimetype from contents, get it from file name 0107 mime = db.mimeTypeForFile(fileName(), QMimeDatabase::MatchExtension); 0108 } 0109 } else { 0110 mime = db.mimeTypeForFile(fileName(), QMimeDatabase::MatchExtension); 0111 } 0112 0113 // qCDebug(KArchiveLog) << mode << mime->name(); 0114 0115 if (mime.inherits(QStringLiteral("application/x-compressed-tar")) || mime.inherits(MimeType::application_gzip_old())) { 0116 // gzipped tar file (with possibly invalid file name), ask for gzip filter 0117 d->mimetype = MimeType::application_gzip_old(); 0118 } else if (mime.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || mime.inherits(QStringLiteral("application/x-bzip2-compressed-tar")) 0119 || mime.inherits(QStringLiteral("application/x-bzip2")) || mime.inherits(QString::fromLatin1(application_bzip))) { 0120 // bzipped2 tar file (with possibly invalid file name), ask for bz2 filter 0121 d->mimetype = QString::fromLatin1(application_bzip); 0122 } else if (mime.inherits(QStringLiteral("application/x-lzma-compressed-tar")) || mime.inherits(QString::fromLatin1(application_lzma))) { 0123 // lzma compressed tar file (with possibly invalid file name), ask for xz filter 0124 d->mimetype = QString::fromLatin1(application_lzma); 0125 } else if (mime.inherits(QStringLiteral("application/x-xz-compressed-tar")) || mime.inherits(QString::fromLatin1(application_xz))) { 0126 // xz compressed tar file (with possibly invalid name), ask for xz filter 0127 d->mimetype = QString::fromLatin1(application_xz); 0128 } else if (mime.inherits(QStringLiteral("application/x-zstd-compressed-tar")) || mime.inherits(QString::fromLatin1(application_zstd))) { 0129 // zstd compressed tar file (with possibly invalid name), ask for zstd filter 0130 d->mimetype = QString::fromLatin1(application_zstd); 0131 } 0132 } 0133 0134 if (d->mimetype == QLatin1String("application/x-tar")) { 0135 return KArchive::createDevice(mode); 0136 } else if (mode == QIODevice::WriteOnly) { 0137 if (!KArchive::createDevice(mode)) { 0138 return false; 0139 } 0140 if (!d->mimetype.isEmpty()) { 0141 // Create a compression filter on top of the QSaveFile device that KArchive created. 0142 // qCDebug(KArchiveLog) << "creating KCompressionDevice for" << d->mimetype; 0143 KCompressionDevice::CompressionType type = KCompressionDevice::compressionTypeForMimeType(d->mimetype); 0144 d->compressionDevice = new KCompressionDevice(device(), false, type); 0145 setDevice(d->compressionDevice); 0146 } 0147 return true; 0148 } else { 0149 // The compression filters are very slow with random access. 0150 // So instead of applying the filter to the device, 0151 // the file is completely extracted instead, 0152 // and we work on the extracted tar file. 0153 // This improves the extraction speed by the archive KIO worker supporting the tar protocol dramatically, 0154 // if the archive file contains many files. 0155 // This is because the archive KIO worker extracts one file after the other and normally 0156 // has to walk through the decompression filter each time. 0157 // Which is in fact nearly as slow as a complete decompression for each file. 0158 0159 Q_ASSERT(!d->tmpFile); 0160 d->tmpFile = new QTemporaryFile(); 0161 d->tmpFile->setFileTemplate(QDir::tempPath() + QLatin1Char('/') + QLatin1String("ktar-XXXXXX.tar")); 0162 d->tmpFile->open(); 0163 // qCDebug(KArchiveLog) << "creating tempfile:" << d->tmpFile->fileName(); 0164 0165 setDevice(d->tmpFile); 0166 return true; 0167 } 0168 } 0169 0170 KTar::~KTar() 0171 { 0172 // mjarrett: Closes to prevent ~KArchive from aborting w/o device 0173 if (isOpen()) { 0174 close(); 0175 } 0176 0177 delete d->tmpFile; 0178 delete d->compressionDevice; 0179 delete d; 0180 } 0181 0182 void KTar::setOrigFileName(const QByteArray &fileName) 0183 { 0184 if (!isOpen() || !(mode() & QIODevice::WriteOnly)) { 0185 // qCWarning(KArchiveLog) << "KTar::setOrigFileName: File must be opened for writing first.\n"; 0186 return; 0187 } 0188 d->origFileName = fileName; 0189 } 0190 0191 qint64 KTar::KTarPrivate::readRawHeader(char *buffer) 0192 { 0193 // Read header 0194 qint64 n = q->device()->read(buffer, 0x200); 0195 // we need to test if there is a prefix value because the file name can be null 0196 // and the prefix can have a value and in this case we don't reset n. 0197 if (n == 0x200 && (buffer[0] != 0 || buffer[0x159] != 0)) { 0198 // Make sure this is actually a tar header 0199 if (strncmp(buffer + 257, "ustar", 5)) { 0200 // The magic isn't there (broken/old tars), but maybe a correct checksum? 0201 0202 int check = 0; 0203 for (uint j = 0; j < 0x200; ++j) { 0204 check += static_cast<unsigned char>(buffer[j]); 0205 } 0206 0207 // adjust checksum to count the checksum fields as blanks 0208 for (uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++) { 0209 check -= static_cast<unsigned char>(buffer[148 + j]); 0210 } 0211 check += 8 * ' '; 0212 0213 QByteArray s = QByteArray::number(check, 8); // octal 0214 0215 // only compare those of the 6 checksum digits that mean something, 0216 // because the other digits are filled with all sorts of different chars by different tars ... 0217 // Some tars right-justify the checksum so it could start in one of three places - we have to check each. 0218 if (strncmp(buffer + 148 + 6 - s.length(), s.data(), s.length()) // 0219 && strncmp(buffer + 148 + 7 - s.length(), s.data(), s.length()) // 0220 && strncmp(buffer + 148 + 8 - s.length(), s.data(), s.length())) { 0221 /*qCWarning(KArchiveLog) << "KTar: invalid TAR file. Header is:" << QByteArray( buffer+257, 5 ) 0222 << "instead of ustar. Reading from wrong pos in file?" 0223 << "checksum=" << QByteArray( buffer + 148 + 6 - s.length(), s.length() );*/ 0224 return -1; 0225 } 0226 } /*end if*/ 0227 } else { 0228 // reset to 0 if 0x200 because logical end of archive has been reached 0229 if (n == 0x200) { 0230 n = 0; 0231 } 0232 } /*end if*/ 0233 return n; 0234 } 0235 0236 bool KTar::KTarPrivate::readLonglink(char *buffer, QByteArray &longlink) 0237 { 0238 qint64 n = 0; 0239 // qCDebug(KArchiveLog) << "reading longlink from pos " << q->device()->pos(); 0240 QIODevice *dev = q->device(); 0241 // read size of longlink from size field in header 0242 // size is in bytes including the trailing null (which we ignore) 0243 qint64 size = QByteArray(buffer + 0x7c, 12).trimmed().toLongLong(nullptr, 8 /*octal*/); 0244 0245 size--; // ignore trailing null 0246 if (size > std::numeric_limits<int>::max() - 32) { // QByteArray can't really be INT_MAX big, it's max size is something between INT_MAX - 32 and INT_MAX 0247 // depending the platform so just be safe 0248 qCWarning(KArchiveLog) << "Failed to allocate memory for longlink of size" << size; 0249 return false; 0250 } 0251 if (size < 0) { 0252 qCWarning(KArchiveLog) << "Invalid longlink size" << size; 0253 return false; 0254 } 0255 longlink.resize(size); 0256 qint64 offset = 0; 0257 while (size > 0) { 0258 int chunksize = qMin(size, 0x200LL); 0259 n = dev->read(longlink.data() + offset, chunksize); 0260 if (n == -1) { 0261 return false; 0262 } 0263 size -= chunksize; 0264 offset += 0x200; 0265 } /*wend*/ 0266 // jump over the rest 0267 const int skip = 0x200 - (n % 0x200); 0268 if (skip <= 0x200) { 0269 if (dev->read(buffer, skip) != skip) { 0270 return false; 0271 } 0272 } 0273 longlink.truncate(qstrlen(longlink.constData())); 0274 return true; 0275 } 0276 0277 qint64 KTar::KTarPrivate::readHeader(char *buffer, QString &name, QString &symlink) 0278 { 0279 name.truncate(0); 0280 symlink.truncate(0); 0281 while (true) { 0282 qint64 n = readRawHeader(buffer); 0283 if (n != 0x200) { 0284 return n; 0285 } 0286 0287 // is it a longlink? 0288 if (strcmp(buffer, "././@LongLink") == 0) { 0289 char typeflag = buffer[0x9c]; 0290 QByteArray longlink; 0291 if (readLonglink(buffer, longlink)) { 0292 switch (typeflag) { 0293 case 'L': 0294 name = QFile::decodeName(longlink.constData()); 0295 break; 0296 case 'K': 0297 symlink = QFile::decodeName(longlink.constData()); 0298 break; 0299 } /*end switch*/ 0300 } 0301 } else { 0302 break; 0303 } /*end if*/ 0304 } /*wend*/ 0305 0306 // if not result of longlink, read names directly from the header 0307 if (name.isEmpty()) 0308 // there are names that are exactly 100 bytes long 0309 // and neither longlink nor \0 terminated (bug:101472) 0310 { 0311 name = QFile::decodeName(QByteArray(buffer, qstrnlen(buffer, 100))); 0312 } 0313 if (symlink.isEmpty()) { 0314 char *symlinkBuffer = buffer + 0x9d /*?*/; 0315 symlink = QFile::decodeName(QByteArray(symlinkBuffer, qstrnlen(symlinkBuffer, 100))); 0316 } 0317 0318 return 0x200; 0319 } 0320 0321 /* 0322 * If we have created a temporary file, we have 0323 * to decompress the original file now and write 0324 * the contents to the temporary file. 0325 */ 0326 bool KTar::KTarPrivate::fillTempFile(const QString &fileName) 0327 { 0328 if (!tmpFile) { 0329 return true; 0330 } 0331 0332 // qCDebug(KArchiveLog) << "filling tmpFile of mimetype" << mimetype; 0333 0334 KCompressionDevice::CompressionType compressionType = KCompressionDevice::compressionTypeForMimeType(mimetype); 0335 KCompressionDevice filterDev(fileName, compressionType); 0336 0337 QFile *file = tmpFile; 0338 Q_ASSERT(file->isOpen()); 0339 Q_ASSERT(file->openMode() & QIODevice::WriteOnly); 0340 file->seek(0); 0341 QByteArray buffer; 0342 buffer.resize(8 * 1024); 0343 if (!filterDev.open(QIODevice::ReadOnly)) { 0344 q->setErrorString(tr("File %1 does not exist").arg(fileName)); 0345 return false; 0346 } 0347 qint64 len = -1; 0348 while (!filterDev.atEnd() && len != 0) { 0349 len = filterDev.read(buffer.data(), buffer.size()); 0350 if (len < 0) { // corrupted archive 0351 q->setErrorString(tr("Archive %1 is corrupt").arg(fileName)); 0352 return false; 0353 } 0354 if (file->write(buffer.data(), len) != len) { // disk full 0355 q->setErrorString(tr("Disk full")); 0356 return false; 0357 } 0358 } 0359 filterDev.close(); 0360 0361 file->flush(); 0362 file->seek(0); 0363 Q_ASSERT(file->isOpen()); 0364 Q_ASSERT(file->openMode() & QIODevice::ReadOnly); 0365 0366 // qCDebug(KArchiveLog) << "filling tmpFile finished."; 0367 return true; 0368 } 0369 0370 bool KTar::openArchive(QIODevice::OpenMode mode) 0371 { 0372 if (!(mode & QIODevice::ReadOnly)) { 0373 return true; 0374 } 0375 0376 if (!d->fillTempFile(fileName())) { 0377 return false; 0378 } 0379 0380 // We'll use the permission and user/group of d->rootDir 0381 // for any directory we emulate (see findOrCreate) 0382 // struct stat buf; 0383 // stat( fileName(), &buf ); 0384 0385 d->dirList.clear(); 0386 QIODevice *dev = device(); 0387 0388 if (!dev) { 0389 setErrorString(tr("Could not get underlying device")); 0390 qCWarning(KArchiveLog) << "Could not get underlying device"; 0391 return false; 0392 } 0393 0394 // read dir information 0395 char buffer[0x200]; 0396 bool ende = false; 0397 do { 0398 QString name; 0399 QString symlink; 0400 0401 // Read header 0402 qint64 n = d->readHeader(buffer, name, symlink); 0403 if (n < 0) { 0404 setErrorString(tr("Could not read tar header")); 0405 return false; 0406 } 0407 if (n == 0x200) { 0408 bool isdir = false; 0409 0410 if (name.isEmpty()) { 0411 continue; 0412 } 0413 if (name.endsWith(QLatin1Char('/'))) { 0414 isdir = true; 0415 name.truncate(name.length() - 1); 0416 } 0417 0418 QByteArray prefix = QByteArray(buffer + 0x159, 155); 0419 if (prefix[0] != '\0') { 0420 name = (QString::fromLatin1(prefix.constData()) + QLatin1Char('/') + name); 0421 } 0422 0423 int pos = name.lastIndexOf(QLatin1Char('/')); 0424 QString nm = (pos == -1) ? name : name.mid(pos + 1); 0425 0426 // read access 0427 buffer[0x6b] = 0; 0428 char *dummy; 0429 const char *p = buffer + 0x64; 0430 while (*p == ' ') { 0431 ++p; 0432 } 0433 int access = strtol(p, &dummy, 8); 0434 0435 // read user and group 0436 const int maxUserGroupLength = 32; 0437 const char *userStart = buffer + 0x109; 0438 const int userLen = qstrnlen(userStart, maxUserGroupLength); 0439 const QString user = QString::fromLocal8Bit(userStart, userLen); 0440 const char *groupStart = buffer + 0x129; 0441 const int groupLen = qstrnlen(groupStart, maxUserGroupLength); 0442 const QString group = QString::fromLocal8Bit(groupStart, groupLen); 0443 0444 // read time 0445 buffer[0x93] = 0; 0446 p = buffer + 0x88; 0447 while (*p == ' ') { 0448 ++p; 0449 } 0450 uint time = strtol(p, &dummy, 8); 0451 0452 // read type flag 0453 char typeflag = buffer[0x9c]; 0454 // '0' for files, '1' hard link, '2' symlink, '5' for directory 0455 // (and 'L' for longlink fileNames, 'K' for longlink symlink targets) 0456 // 'D' for GNU tar extension DUMPDIR, 'x' for Extended header referring 0457 // to the next file in the archive and 'g' for Global extended header 0458 0459 if (typeflag == '5') { 0460 isdir = true; 0461 } 0462 0463 bool isDumpDir = false; 0464 if (typeflag == 'D') { 0465 isdir = false; 0466 isDumpDir = true; 0467 } 0468 // qCDebug(KArchiveLog) << nm << "isdir=" << isdir << "pos=" << dev->pos() << "typeflag=" << typeflag << " islink=" << ( typeflag == '1' || typeflag 0469 // == '2' ); 0470 0471 if (typeflag == 'x' || typeflag == 'g') { // pax extended header, or pax global extended header 0472 // Skip it for now. TODO: implement reading of extended header, as per https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html 0473 (void)dev->read(buffer, 0x200); 0474 continue; 0475 } 0476 0477 if (isdir) { 0478 access |= S_IFDIR; // broken tar files... 0479 } 0480 0481 KArchiveEntry *e; 0482 if (isdir) { 0483 // qCDebug(KArchiveLog) << "directory" << nm; 0484 e = new KArchiveDirectory(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink); 0485 } else { 0486 // read size 0487 QByteArray sizeBuffer(buffer + 0x7c, 12); 0488 qint64 size = sizeBuffer.trimmed().toLongLong(nullptr, 8 /*octal*/); 0489 // qCDebug(KArchiveLog) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size; 0490 0491 // for isDumpDir we will skip the additional info about that dirs contents 0492 if (isDumpDir) { 0493 // qCDebug(KArchiveLog) << nm << "isDumpDir"; 0494 e = new KArchiveDirectory(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink); 0495 } else { 0496 // Let's hack around hard links. Our classes don't support that, so make them symlinks 0497 if (typeflag == '1') { 0498 // qCDebug(KArchiveLog) << "Hard link, setting size to 0 instead of" << size; 0499 size = 0; // no contents 0500 } 0501 0502 // qCDebug(KArchiveLog) << "file" << nm << "size=" << size; 0503 e = new KArchiveFile(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink, dev->pos(), size); 0504 } 0505 0506 // Skip contents + align bytes 0507 qint64 rest = size % 0x200; 0508 qint64 skip = size + (rest ? 0x200 - rest : 0); 0509 // qCDebug(KArchiveLog) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip; 0510 if (!dev->seek(dev->pos() + skip)) { 0511 // qCWarning(KArchiveLog) << "skipping" << skip << "failed"; 0512 } 0513 } 0514 0515 if (pos == -1) { 0516 if (nm == QLatin1String(".")) { // special case 0517 if (isdir) { 0518 if (KArchivePrivate::hasRootDir(this)) { 0519 qWarning() << "Broken tar file has two root dir entries"; 0520 delete e; 0521 } else { 0522 setRootDir(static_cast<KArchiveDirectory *>(e)); 0523 } 0524 } else { 0525 delete e; 0526 } 0527 } else { 0528 rootDir()->addEntry(e); 0529 } 0530 } else { 0531 // In some tar files we can find dir/./file => call cleanPath 0532 QString path = QDir::cleanPath(name.left(pos)); 0533 // Ensure container directory exists, create otherwise 0534 KArchiveDirectory *d = findOrCreate(path); 0535 if (d) { 0536 d->addEntry(e); 0537 } else { 0538 delete e; 0539 return false; 0540 } 0541 } 0542 } else { 0543 // qCDebug(KArchiveLog) << "Terminating. Read " << n << " bytes, first one is " << buffer[0]; 0544 d->tarEnd = dev->pos() - n; // Remember end of archive 0545 ende = true; 0546 } 0547 } while (!ende); 0548 return true; 0549 } 0550 0551 /* 0552 * Writes back the changes of the temporary file 0553 * to the original file. 0554 * Must only be called if in write mode, not in read mode 0555 */ 0556 bool KTar::KTarPrivate::writeBackTempFile(const QString &fileName) 0557 { 0558 if (!tmpFile) { 0559 return true; 0560 } 0561 0562 // qCDebug(KArchiveLog) << "Write temporary file to compressed file" << fileName << mimetype; 0563 0564 bool forced = false; 0565 /* clang-format off */ 0566 if (MimeType::application_gzip_old() == mimetype || 0567 QLatin1String(application_bzip) == mimetype || 0568 QLatin1String(application_lzma) == mimetype || 0569 QLatin1String(application_xz) == mimetype) { 0570 /* clang-format on */ 0571 forced = true; 0572 } 0573 0574 // #### TODO this should use QSaveFile to avoid problems on disk full 0575 // (KArchive uses QSaveFile by default, but the temp-uncompressed-file trick 0576 // circumvents that). 0577 0578 KCompressionDevice dev(fileName); 0579 QFile *file = tmpFile; 0580 if (!dev.open(QIODevice::WriteOnly)) { 0581 file->close(); 0582 q->setErrorString(tr("Failed to write back temp file: %1").arg(dev.errorString())); 0583 return false; 0584 } 0585 if (forced) { 0586 dev.setOrigFileName(origFileName); 0587 } 0588 file->seek(0); 0589 QByteArray buffer; 0590 buffer.resize(8 * 1024); 0591 qint64 len; 0592 while (!file->atEnd()) { 0593 len = file->read(buffer.data(), buffer.size()); 0594 dev.write(buffer.data(), len); // TODO error checking 0595 } 0596 file->close(); 0597 dev.close(); 0598 0599 // qCDebug(KArchiveLog) << "Write temporary file to compressed file done."; 0600 return true; 0601 } 0602 0603 bool KTar::closeArchive() 0604 { 0605 d->dirList.clear(); 0606 0607 bool ok = true; 0608 0609 // If we are in readwrite mode and had created 0610 // a temporary tar file, we have to write 0611 // back the changes to the original file 0612 if (d->tmpFile && (mode() & QIODevice::WriteOnly)) { 0613 ok = d->writeBackTempFile(fileName()); 0614 delete d->tmpFile; 0615 d->tmpFile = nullptr; 0616 setDevice(nullptr); 0617 } 0618 0619 return ok; 0620 } 0621 0622 bool KTar::doFinishWriting(qint64 size) 0623 { 0624 // Write alignment 0625 int rest = size % 0x200; 0626 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) { 0627 d->tarEnd = device()->pos() + (rest ? 0x200 - rest : 0); // Record our new end of archive 0628 } 0629 if (rest) { 0630 char buffer[0x201]; 0631 for (uint i = 0; i < 0x200; ++i) { 0632 buffer[i] = 0; 0633 } 0634 qint64 nwritten = device()->write(buffer, 0x200 - rest); 0635 const bool ok = nwritten == 0x200 - rest; 0636 0637 if (!ok) { 0638 setErrorString(tr("Couldn't write alignment: %1").arg(device()->errorString())); 0639 } 0640 0641 return ok; 0642 } 0643 return true; 0644 } 0645 0646 /*** Some help from the tar sources 0647 struct posix_header 0648 { byte offset 0649 char name[100]; * 0 * 0x0 0650 char mode[8]; * 100 * 0x64 0651 char uid[8]; * 108 * 0x6c 0652 char gid[8]; * 116 * 0x74 0653 char size[12]; * 124 * 0x7c 0654 char mtime[12]; * 136 * 0x88 0655 char chksum[8]; * 148 * 0x94 0656 char typeflag; * 156 * 0x9c 0657 char linkname[100]; * 157 * 0x9d 0658 char magic[6]; * 257 * 0x101 0659 char version[2]; * 263 * 0x107 0660 char uname[32]; * 265 * 0x109 0661 char gname[32]; * 297 * 0x129 0662 char devmajor[8]; * 329 * 0x149 0663 char devminor[8]; * 337 * ... 0664 char prefix[155]; * 345 * 0665 * 500 * 0666 }; 0667 */ 0668 0669 void KTar::KTarPrivate::fillBuffer(char *buffer, const char *mode, qint64 size, const QDateTime &mtime, char typeflag, const char *uname, const char *gname) 0670 { 0671 // mode (as in stpos()) 0672 assert(strlen(mode) == 6); 0673 memcpy(buffer + 0x64, mode, 6); 0674 buffer[0x6a] = ' '; 0675 buffer[0x6b] = '\0'; 0676 0677 // dummy uid 0678 strcpy(buffer + 0x6c, " 765 "); // 501 in decimal 0679 // dummy gid 0680 strcpy(buffer + 0x74, " 144 "); // 100 in decimal 0681 0682 // size 0683 QByteArray s = QByteArray::number(size, 8); // octal 0684 s = s.rightJustified(11, '0'); 0685 memcpy(buffer + 0x7c, s.data(), 11); 0686 buffer[0x87] = ' '; // space-terminate (no null after) 0687 0688 // modification time 0689 const QDateTime modificationTime = mtime.isValid() ? mtime : QDateTime::currentDateTime(); 0690 s = QByteArray::number(static_cast<qulonglong>(modificationTime.toMSecsSinceEpoch() / 1000), 8); // octal 0691 s = s.rightJustified(11, '0'); 0692 memcpy(buffer + 0x88, s.data(), 11); 0693 buffer[0x93] = ' '; // space-terminate (no null after) -- well current tar writes a null byte 0694 0695 // spaces, replaced by the check sum later 0696 buffer[0x94] = 0x20; 0697 buffer[0x95] = 0x20; 0698 buffer[0x96] = 0x20; 0699 buffer[0x97] = 0x20; 0700 buffer[0x98] = 0x20; 0701 buffer[0x99] = 0x20; 0702 0703 /* From the tar sources : 0704 Fill in the checksum field. It's formatted differently from the 0705 other fields: it has [6] digits, a null, then a space -- rather than 0706 digits, a space, then a null. */ 0707 0708 buffer[0x9a] = '\0'; 0709 buffer[0x9b] = ' '; 0710 0711 // type flag (dir, file, link) 0712 buffer[0x9c] = typeflag; 0713 0714 // magic + version 0715 strcpy(buffer + 0x101, "ustar"); 0716 strcpy(buffer + 0x107, "00"); 0717 0718 // user 0719 strcpy(buffer + 0x109, uname); 0720 // group 0721 strcpy(buffer + 0x129, gname); 0722 0723 // Header check sum 0724 int check = 32; 0725 for (uint j = 0; j < 0x200; ++j) { 0726 check += static_cast<unsigned char>(buffer[j]); 0727 } 0728 s = QByteArray::number(check, 8); // octal 0729 s = s.rightJustified(6, '0'); 0730 memcpy(buffer + 0x94, s.constData(), 6); 0731 } 0732 0733 void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag, const char *uname, const char *gname) 0734 { 0735 strcpy(buffer, "././@LongLink"); 0736 qint64 namelen = name.length() + 1; 0737 fillBuffer(buffer, " 0", namelen, QDateTime(), typeflag, uname, gname); 0738 q->device()->write(buffer, 0x200); // TODO error checking 0739 qint64 offset = 0; 0740 while (namelen > 0) { 0741 int chunksize = qMin(namelen, 0x200LL); 0742 memcpy(buffer, name.data() + offset, chunksize); 0743 // write long name 0744 q->device()->write(buffer, 0x200); // TODO error checking 0745 // not even needed to reclear the buffer, tar doesn't do it 0746 namelen -= chunksize; 0747 offset += 0x200; 0748 } /*wend*/ 0749 } 0750 0751 bool KTar::doPrepareWriting(const QString &name, 0752 const QString &user, 0753 const QString &group, 0754 qint64 size, 0755 mode_t perm, 0756 const QDateTime & /*atime*/, 0757 const QDateTime &mtime, 0758 const QDateTime & /*ctime*/) 0759 { 0760 if (!isOpen()) { 0761 setErrorString(tr("Application error: TAR file must be open before being written into")); 0762 qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()"; 0763 return false; 0764 } 0765 0766 if (!(mode() & QIODevice::WriteOnly)) { 0767 setErrorString(tr("Application error: attempted to write into non-writable 7-Zip file")); 0768 qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)"; 0769 return false; 0770 } 0771 0772 const qint64 MAX_FILESIZE = 077777777777L; // the format we use only allows 11 octal digits for size 0773 if (size > MAX_FILESIZE) { 0774 setErrorString(tr("Application limitation: Can not add file larger than %1 bytes").arg(MAX_FILESIZE)); 0775 return false; 0776 } 0777 0778 // In some tar files we can find dir/./file => call cleanPath 0779 QString fileName(QDir::cleanPath(name)); 0780 0781 /* 0782 // Create toplevel dirs 0783 // Commented out by David since it's not necessary, and if anybody thinks it is, 0784 // he needs to implement a findOrCreate equivalent in writeDir. 0785 // But as KTar and the "tar" program both handle tar files without 0786 // dir entries, there's really no need for that 0787 QString tmp ( fileName ); 0788 int i = tmp.lastIndexOf( '/' ); 0789 if ( i != -1 ) 0790 { 0791 QString d = tmp.left( i + 1 ); // contains trailing slash 0792 if ( !m_dirList.contains( d ) ) 0793 { 0794 tmp = tmp.mid( i + 1 ); 0795 writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs 0796 } 0797 } 0798 */ 0799 0800 char buffer[0x201] = {0}; 0801 0802 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) { 0803 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read 0804 } 0805 0806 // provide converted stuff we need later on 0807 const QByteArray encodedFileName = QFile::encodeName(fileName); 0808 const QByteArray uname = user.toLocal8Bit(); 0809 const QByteArray gname = group.toLocal8Bit(); 0810 0811 // If more than 100 bytes, we need to use the LongLink trick 0812 if (encodedFileName.length() > 99) { 0813 d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData()); 0814 } 0815 0816 // Write (potentially truncated) name 0817 strncpy(buffer, encodedFileName.constData(), 99); 0818 buffer[99] = 0; 0819 // zero out the rest (except for what gets filled anyways) 0820 memset(buffer + 0x9d, 0, 0x200 - 0x9d); 0821 0822 QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8); 0823 permstr = permstr.rightJustified(6, '0'); 0824 d->fillBuffer(buffer, permstr.constData(), size, mtime, 0x30, uname.constData(), gname.constData()); 0825 0826 // Write header 0827 if (device()->write(buffer, 0x200) != 0x200) { 0828 setErrorString(tr("Failed to write header: %1").arg(device()->errorString())); 0829 return false; 0830 } else { 0831 return true; 0832 } 0833 } 0834 0835 bool KTar::doWriteDir(const QString &name, 0836 const QString &user, 0837 const QString &group, 0838 mode_t perm, 0839 const QDateTime & /*atime*/, 0840 const QDateTime &mtime, 0841 const QDateTime & /*ctime*/) 0842 { 0843 if (!isOpen()) { 0844 setErrorString(tr("Application error: TAR file must be open before being written into")); 0845 qCWarning(KArchiveLog) << "doWriteDir failed: !isOpen()"; 0846 return false; 0847 } 0848 0849 if (!(mode() & QIODevice::WriteOnly)) { 0850 setErrorString(tr("Application error: attempted to write into non-writable TAR file")); 0851 qCWarning(KArchiveLog) << "doWriteDir failed: !(mode() & QIODevice::WriteOnly)"; 0852 return false; 0853 } 0854 0855 // In some tar files we can find dir/./ => call cleanPath 0856 QString dirName(QDir::cleanPath(name)); 0857 0858 // Need trailing '/' 0859 if (!dirName.endsWith(QLatin1Char('/'))) { 0860 dirName += QLatin1Char('/'); 0861 } 0862 0863 if (d->dirList.contains(dirName)) { 0864 return true; // already there 0865 } 0866 0867 char buffer[0x201] = {0}; 0868 0869 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) { 0870 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read 0871 } 0872 0873 // provide converted stuff we need lateron 0874 QByteArray encodedDirname = QFile::encodeName(dirName); 0875 QByteArray uname = user.toLocal8Bit(); 0876 QByteArray gname = group.toLocal8Bit(); 0877 0878 // If more than 100 bytes, we need to use the LongLink trick 0879 if (encodedDirname.length() > 99) { 0880 d->writeLonglink(buffer, encodedDirname, 'L', uname.constData(), gname.constData()); 0881 } 0882 0883 // Write (potentially truncated) name 0884 strncpy(buffer, encodedDirname.constData(), 99); 0885 buffer[99] = 0; 0886 // zero out the rest (except for what gets filled anyways) 0887 memset(buffer + 0x9d, 0, 0x200 - 0x9d); 0888 0889 QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8); 0890 permstr = permstr.rightJustified(6, ' '); 0891 d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x35, uname.constData(), gname.constData()); 0892 0893 // Write header 0894 device()->write(buffer, 0x200); 0895 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) { 0896 d->tarEnd = device()->pos(); 0897 } 0898 0899 d->dirList.append(dirName); // contains trailing slash 0900 return true; // TODO if wanted, better error control 0901 } 0902 0903 bool KTar::doWriteSymLink(const QString &name, 0904 const QString &target, 0905 const QString &user, 0906 const QString &group, 0907 mode_t perm, 0908 const QDateTime & /*atime*/, 0909 const QDateTime &mtime, 0910 const QDateTime & /*ctime*/) 0911 { 0912 if (!isOpen()) { 0913 setErrorString(tr("Application error: TAR file must be open before being written into")); 0914 qCWarning(KArchiveLog) << "doWriteSymLink failed: !isOpen()"; 0915 return false; 0916 } 0917 0918 if (!(mode() & QIODevice::WriteOnly)) { 0919 setErrorString(tr("Application error: attempted to write into non-writable TAR file")); 0920 qCWarning(KArchiveLog) << "doWriteSymLink failed: !(mode() & QIODevice::WriteOnly)"; 0921 return false; 0922 } 0923 0924 // In some tar files we can find dir/./file => call cleanPath 0925 QString fileName(QDir::cleanPath(name)); 0926 0927 char buffer[0x201] = {0}; 0928 0929 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) { 0930 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read 0931 } 0932 0933 // provide converted stuff we need lateron 0934 QByteArray encodedFileName = QFile::encodeName(fileName); 0935 QByteArray encodedTarget = QFile::encodeName(target); 0936 QByteArray uname = user.toLocal8Bit(); 0937 QByteArray gname = group.toLocal8Bit(); 0938 0939 // If more than 100 bytes, we need to use the LongLink trick 0940 if (encodedTarget.length() > 99) { 0941 d->writeLonglink(buffer, encodedTarget, 'K', uname.constData(), gname.constData()); 0942 } 0943 if (encodedFileName.length() > 99) { 0944 d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData()); 0945 } 0946 0947 // Write (potentially truncated) name 0948 strncpy(buffer, encodedFileName.constData(), 99); 0949 buffer[99] = 0; 0950 // Write (potentially truncated) symlink target 0951 strncpy(buffer + 0x9d, encodedTarget.constData(), 99); 0952 buffer[0x9d + 99] = 0; 0953 // zero out the rest 0954 memset(buffer + 0x9d + 100, 0, 0x200 - 100 - 0x9d); 0955 0956 QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8); 0957 permstr = permstr.rightJustified(6, ' '); 0958 d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x32, uname.constData(), gname.constData()); 0959 0960 // Write header 0961 bool retval = device()->write(buffer, 0x200) == 0x200; 0962 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) { 0963 d->tarEnd = device()->pos(); 0964 } 0965 return retval; 0966 } 0967 0968 void KTar::virtual_hook(int id, void *data) 0969 { 0970 KArchive::virtual_hook(id, data); 0971 }