1 /* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
3 SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6 */
7
8 #include "ktar.h"
9 #include "karchive_p.h"
10 #include "loggingcategory.h"
11
12 #include <stdlib.h> // strtol
13 #include <assert.h>
14
15 #include <QDebug>
16 #include <QDir>
17 #include <QFile>
18 #include <QMimeDatabase>
19 #include <QTemporaryFile>
20
21 #include <kfilterdev.h>
22 #include <kfilterbase.h>
23
24 ////////////////////////////////////////////////////////////////////////
25 /////////////////////////// KTar ///////////////////////////////////
26 ////////////////////////////////////////////////////////////////////////
27
28 // Mime types of known filters
29 static const char application_gzip[] = "application/x-gzip";
30 static const char application_bzip[] = "application/x-bzip";
31 static const char application_lzma[] = "application/x-lzma";
32 static const char application_xz[] = "application/x-xz";
33
34 class Q_DECL_HIDDEN KTar::KTarPrivate
35 {
36 public:
KTarPrivate(KTar * parent)37 KTarPrivate(KTar *parent)
38 : q(parent)
39 , tarEnd(0)
40 , tmpFile(nullptr)
41 , compressionDevice(nullptr)
42 {
43 }
44
45 KTar *q;
46 QStringList dirList;
47 qint64 tarEnd;
48 QTemporaryFile *tmpFile;
49 QString mimetype;
50 QByteArray origFileName;
51 KCompressionDevice *compressionDevice;
52
53 bool fillTempFile(const QString &fileName);
54 bool writeBackTempFile(const QString &fileName);
55 void fillBuffer(char *buffer, const char *mode, qint64 size, const QDateTime &mtime,
56 char typeflag, const char *uname, const char *gname);
57 void writeLonglink(char *buffer, const QByteArray &name, char typeflag,
58 const char *uname, const char *gname);
59 qint64 readRawHeader(char *buffer);
60 bool readLonglink(char *buffer, QByteArray &longlink);
61 qint64 readHeader(char *buffer, QString &name, QString &symlink);
62 };
63
KTar(const QString & fileName,const QString & _mimetype)64 KTar::KTar(const QString &fileName, const QString &_mimetype)
65 : KArchive(fileName)
66 , d(new KTarPrivate(this))
67 {
68 d->mimetype = _mimetype;
69 }
70
KTar(QIODevice * dev)71 KTar::KTar(QIODevice *dev)
72 : KArchive(dev)
73 , d(new KTarPrivate(this))
74 {
75 }
76
77 // Only called when a filename was given
createDevice(QIODevice::OpenMode mode)78 bool KTar::createDevice(QIODevice::OpenMode mode)
79 {
80 if (d->mimetype.isEmpty()) {
81 // Find out mimetype manually
82
83 QMimeDatabase db;
84 QMimeType mime;
85 if (mode != QIODevice::WriteOnly && QFile::exists(fileName())) {
86 // Give priority to file contents: if someone renames a .tar.bz2 to .tar.gz,
87 // we can still do the right thing here.
88 QFile f(fileName());
89 if (f.open(QIODevice::ReadOnly)) {
90 mime = db.mimeTypeForData(&f);
91 }
92 if (!mime.isValid()) {
93 // Unable to determine mimetype from contents, get it from file name
94 mime = db.mimeTypeForFile(fileName(), QMimeDatabase::MatchExtension);
95 }
96 } else {
97 mime = db.mimeTypeForFile(fileName(), QMimeDatabase::MatchExtension);
98 }
99
100 //qCDebug(KArchiveLog) << mode << mime->name();
101
102 if (mime.inherits(QStringLiteral("application/x-compressed-tar")) || mime.inherits(QString::fromLatin1(application_gzip))) {
103 // gzipped tar file (with possibly invalid file name), ask for gzip filter
104 d->mimetype = QString::fromLatin1(application_gzip);
105 } else if (mime.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || mime.inherits(QString::fromLatin1(application_bzip))) {
106 // bzipped2 tar file (with possibly invalid file name), ask for bz2 filter
107 d->mimetype = QString::fromLatin1(application_bzip);
108 } else if (mime.inherits(QStringLiteral("application/x-lzma-compressed-tar")) || mime.inherits(QString::fromLatin1(application_lzma))) {
109 // lzma compressed tar file (with possibly invalid file name), ask for xz filter
110 d->mimetype = QString::fromLatin1(application_lzma);
111 } else if (mime.inherits(QStringLiteral("application/x-xz-compressed-tar")) || mime.inherits(QString::fromLatin1(application_xz))) {
112 // xz compressed tar file (with possibly invalid name), ask for xz filter
113 d->mimetype = QString::fromLatin1(application_xz);
114 }
115 }
116
117 if (d->mimetype == QLatin1String("application/x-tar")) {
118 return KArchive::createDevice(mode);
119 } else if (mode == QIODevice::WriteOnly) {
120 if (!KArchive::createDevice(mode)) {
121 return false;
122 }
123 if (!d->mimetype.isEmpty()) {
124 // Create a compression filter on top of the QSaveFile device that KArchive created.
125 //qCDebug(KArchiveLog) << "creating KFilterDev for" << d->mimetype;
126 KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType(d->mimetype);
127 d->compressionDevice = new KCompressionDevice(device(), false, type);
128 setDevice(d->compressionDevice);
129 }
130 return true;
131 } else {
132 // The compression filters are very slow with random access.
133 // So instead of applying the filter to the device,
134 // the file is completely extracted instead,
135 // and we work on the extracted tar file.
136 // This improves the extraction speed by the tar ioslave dramatically,
137 // if the archive file contains many files.
138 // This is because the tar ioslave extracts one file after the other and normally
139 // has to walk through the decompression filter each time.
140 // Which is in fact nearly as slow as a complete decompression for each file.
141
142 Q_ASSERT(!d->tmpFile);
143 d->tmpFile = new QTemporaryFile();
144 d->tmpFile->setFileTemplate(QDir::tempPath() + QStringLiteral("/") + QLatin1String("ktar-XXXXXX.tar"));
145 d->tmpFile->open();
146 //qCDebug(KArchiveLog) << "creating tempfile:" << d->tmpFile->fileName();
147
148 setDevice(d->tmpFile);
149 return true;
150 }
151 }
152
~KTar()153 KTar::~KTar()
154 {
155 // mjarrett: Closes to prevent ~KArchive from aborting w/o device
156 if (isOpen()) {
157 close();
158 }
159
160 delete d->tmpFile;
161 delete d->compressionDevice;
162 delete d;
163 }
164
setOrigFileName(const QByteArray & fileName)165 void KTar::setOrigFileName(const QByteArray &fileName)
166 {
167 if (!isOpen() || !(mode() & QIODevice::WriteOnly)) {
168 //qCWarning(KArchiveLog) << "KTar::setOrigFileName: File must be opened for writing first.\n";
169 return;
170 }
171 d->origFileName = fileName;
172 }
173
readRawHeader(char * buffer)174 qint64 KTar::KTarPrivate::readRawHeader(char *buffer)
175 {
176 // Read header
177 qint64 n = q->device()->read(buffer, 0x200);
178 // we need to test if there is a prefix value because the file name can be null
179 // and the prefix can have a value and in this case we don't reset n.
180 if (n == 0x200 && (buffer[0] != 0 || buffer[0x159] != 0)) {
181 // Make sure this is actually a tar header
182 if (strncmp(buffer + 257, "ustar", 5)) {
183 // The magic isn't there (broken/old tars), but maybe a correct checksum?
184
185 int check = 0;
186 for (uint j = 0; j < 0x200; ++j) {
187 check += static_cast<unsigned char>(buffer[j]);
188 }
189
190 // adjust checksum to count the checksum fields as blanks
191 for (uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++) {
192 check -= static_cast<unsigned char>(buffer[148 + j]);
193 }
194 check += 8 * ' ';
195
196 QByteArray s = QByteArray::number(check, 8); // octal
197
198 // only compare those of the 6 checksum digits that mean something,
199 // because the other digits are filled with all sorts of different chars by different tars ...
200 // Some tars right-justify the checksum so it could start in one of three places - we have to check each.
201 if (strncmp(buffer + 148 + 6 - s.length(), s.data(), s.length())
202 && strncmp(buffer + 148 + 7 - s.length(), s.data(), s.length())
203 && strncmp(buffer + 148 + 8 - s.length(), s.data(), s.length())) {
204 /*qCWarning(KArchiveLog) << "KTar: invalid TAR file. Header is:" << QByteArray( buffer+257, 5 )
205 << "instead of ustar. Reading from wrong pos in file?"
206 << "checksum=" << QByteArray( buffer + 148 + 6 - s.length(), s.length() );*/
207 return -1;
208 }
209 }/*end if*/
210 } else {
211 // reset to 0 if 0x200 because logical end of archive has been reached
212 if (n == 0x200) {
213 n = 0;
214 }
215 }/*end if*/
216 return n;
217 }
218
readLonglink(char * buffer,QByteArray & longlink)219 bool KTar::KTarPrivate::readLonglink(char *buffer, QByteArray &longlink)
220 {
221 qint64 n = 0;
222 //qCDebug(KArchiveLog) << "reading longlink from pos " << q->device()->pos();
223 QIODevice *dev = q->device();
224 // read size of longlink from size field in header
225 // size is in bytes including the trailing null (which we ignore)
226 qint64 size = QByteArray(buffer + 0x7c, 12).trimmed().toLongLong(nullptr, 8 /*octal*/);
227
228 size--; // ignore trailing null
229 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 depending the platform so just be safe
230 qCWarning(KArchiveLog) << "Failed to allocate memory for longlink of size" << size;
231 return false;
232 }
233 if (size < 0) {
234 qCWarning(KArchiveLog) << "Invalid longlink size" << size;
235 return false;
236 }
237 longlink.resize(size);
238 qint64 offset = 0;
239 while (size > 0) {
240 int chunksize = qMin(size, 0x200LL);
241 n = dev->read(longlink.data() + offset, chunksize);
242 if (n == -1) {
243 return false;
244 }
245 size -= chunksize;
246 offset += 0x200;
247 }/*wend*/
248 // jump over the rest
249 const int skip = 0x200 - (n % 0x200);
250 if (skip <= 0x200) {
251 if (dev->read(buffer, skip) != skip) {
252 return false;
253 }
254 }
255 longlink.truncate(qstrlen(longlink.constData()));
256 return true;
257 }
258
readHeader(char * buffer,QString & name,QString & symlink)259 qint64 KTar::KTarPrivate::readHeader(char *buffer, QString &name, QString &symlink)
260 {
261 name.truncate(0);
262 symlink.truncate(0);
263 while (true) {
264 qint64 n = readRawHeader(buffer);
265 if (n != 0x200) {
266 return n;
267 }
268
269 // is it a longlink?
270 if (strcmp(buffer, "././@LongLink") == 0) {
271 char typeflag = buffer[0x9c];
272 QByteArray longlink;
273 if (readLonglink(buffer, longlink)) {
274 switch (typeflag) {
275 case 'L':
276 name = QFile::decodeName(longlink.constData());
277 break;
278 case 'K':
279 symlink = QFile::decodeName(longlink.constData());
280 break;
281 }/*end switch*/
282 }
283 } else {
284 break;
285 }/*end if*/
286 }/*wend*/
287
288 // if not result of longlink, read names directly from the header
289 if (name.isEmpty())
290 // there are names that are exactly 100 bytes long
291 // and neither longlink nor \0 terminated (bug:101472)
292 {
293 name = QFile::decodeName(QByteArray(buffer, qstrnlen(buffer, 100)));
294 }
295 if (symlink.isEmpty()) {
296 char *symlinkBuffer = buffer + 0x9d /*?*/;
297 symlink = QFile::decodeName(QByteArray(symlinkBuffer, qstrnlen(symlinkBuffer, 100)));
298 }
299
300 return 0x200;
301 }
302
303 /*
304 * If we have created a temporary file, we have
305 * to decompress the original file now and write
306 * the contents to the temporary file.
307 */
fillTempFile(const QString & fileName)308 bool KTar::KTarPrivate::fillTempFile(const QString &fileName)
309 {
310 if (! tmpFile) {
311 return true;
312 }
313
314 //qCDebug(KArchiveLog) << "filling tmpFile of mimetype" << mimetype;
315
316 KCompressionDevice::CompressionType compressionType = KFilterDev::compressionTypeForMimeType(mimetype);
317 KCompressionDevice filterDev(fileName, compressionType);
318
319 QFile *file = tmpFile;
320 Q_ASSERT(file->isOpen());
321 Q_ASSERT(file->openMode() & QIODevice::WriteOnly);
322 file->seek(0);
323 QByteArray buffer;
324 buffer.resize(8 * 1024);
325 if (! filterDev.open(QIODevice::ReadOnly)) {
326 q->setErrorString(
327 tr("File %1 does not exist")
328 .arg(fileName));
329 return false;
330 }
331 qint64 len = -1;
332 while (!filterDev.atEnd() && len != 0) {
333 len = filterDev.read(buffer.data(), buffer.size());
334 if (len < 0) { // corrupted archive
335 q->setErrorString(tr("Archive %1 is corrupt").arg(fileName));
336 return false;
337 }
338 if (file->write(buffer.data(), len) != len) { // disk full
339 q->setErrorString(tr("Disk full"));
340 return false;
341 }
342 }
343 filterDev.close();
344
345 file->flush();
346 file->seek(0);
347 Q_ASSERT(file->isOpen());
348 Q_ASSERT(file->openMode() & QIODevice::ReadOnly);
349
350 //qCDebug(KArchiveLog) << "filling tmpFile finished.";
351 return true;
352 }
353
openArchive(QIODevice::OpenMode mode)354 bool KTar::openArchive(QIODevice::OpenMode mode)
355 {
356
357 if (!(mode & QIODevice::ReadOnly)) {
358 return true;
359 }
360
361 if (!d->fillTempFile(fileName())) {
362 return false;
363 }
364
365 // We'll use the permission and user/group of d->rootDir
366 // for any directory we emulate (see findOrCreate)
367 //struct stat buf;
368 //stat( fileName(), &buf );
369
370 d->dirList.clear();
371 QIODevice *dev = device();
372
373 if (!dev) {
374 setErrorString(tr("Could not get underlying device"));
375 qCWarning(KArchiveLog) << "Could not get underlying device";
376 return false;
377 }
378
379 // read dir information
380 char buffer[0x200];
381 bool ende = false;
382 do {
383 QString name;
384 QString symlink;
385
386 // Read header
387 qint64 n = d->readHeader(buffer, name, symlink);
388 if (n < 0) {
389 setErrorString(tr("Could not read tar header"));
390 return false;
391 }
392 if (n == 0x200) {
393 bool isdir = false;
394
395 if (name.isEmpty()) {
396 continue;
397 }
398 if (name.endsWith(QLatin1Char('/'))) {
399 isdir = true;
400 name.truncate(name.length() - 1);
401 }
402
403 QByteArray prefix = QByteArray(buffer + 0x159, 155);
404 if (prefix[0] != '\0') {
405 name = (QString::fromLatin1(prefix.constData()) + QLatin1Char('/') + name);
406 }
407
408 int pos = name.lastIndexOf(QLatin1Char('/'));
409 QString nm = (pos == -1) ? name : name.mid(pos + 1);
410
411 // read access
412 buffer[0x6b] = 0;
413 char *dummy;
414 const char *p = buffer + 0x64;
415 while (*p == ' ') {
416 ++p;
417 }
418 int access = strtol(p, &dummy, 8);
419
420 // read user and group
421 const int maxUserGroupLength = 32;
422 const char *userStart = buffer + 0x109;
423 const int userLen = qstrnlen(userStart, maxUserGroupLength);
424 const QString user = QString::fromLocal8Bit(userStart, userLen);
425 const char *groupStart = buffer + 0x129;
426 const int groupLen = qstrnlen(groupStart, maxUserGroupLength);
427 const QString group = QString::fromLocal8Bit(groupStart, groupLen);
428
429 // read time
430 buffer[0x93] = 0;
431 p = buffer + 0x88;
432 while (*p == ' ') {
433 ++p;
434 }
435 uint time = strtol(p, &dummy, 8);
436
437 // read type flag
438 char typeflag = buffer[0x9c];
439 // '0' for files, '1' hard link, '2' symlink, '5' for directory
440 // (and 'L' for longlink fileNames, 'K' for longlink symlink targets)
441 // 'D' for GNU tar extension DUMPDIR, 'x' for Extended header referring
442 // to the next file in the archive and 'g' for Global extended header
443
444 if (typeflag == '5') {
445 isdir = true;
446 }
447
448 bool isDumpDir = false;
449 if (typeflag == 'D') {
450 isdir = false;
451 isDumpDir = true;
452 }
453 //qCDebug(KArchiveLog) << nm << "isdir=" << isdir << "pos=" << dev->pos() << "typeflag=" << typeflag << " islink=" << ( typeflag == '1' || typeflag == '2' );
454
455 if (typeflag == 'x' || typeflag == 'g') { // pax extended header, or pax global extended header
456 // Skip it for now. TODO: implement reading of extended header, as per http://pubs.opengroup.org/onlinepubs/009695399/utilities/pax.html
457 (void)dev->read(buffer, 0x200);
458 continue;
459 }
460
461 if (isdir) {
462 access |= S_IFDIR; // f*cking broken tar files
463 }
464
465 KArchiveEntry *e;
466 if (isdir) {
467 //qCDebug(KArchiveLog) << "directory" << nm;
468 e = new KArchiveDirectory(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink);
469 } else {
470 // read size
471 QByteArray sizeBuffer(buffer + 0x7c, 12);
472 qint64 size = sizeBuffer.trimmed().toLongLong(nullptr, 8 /*octal*/);
473 //qCDebug(KArchiveLog) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size;
474
475 // for isDumpDir we will skip the additional info about that dirs contents
476 if (isDumpDir) {
477 //qCDebug(KArchiveLog) << nm << "isDumpDir";
478 e = new KArchiveDirectory(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink);
479 } else {
480
481 // Let's hack around hard links. Our classes don't support that, so make them symlinks
482 if (typeflag == '1') {
483 //qCDebug(KArchiveLog) << "Hard link, setting size to 0 instead of" << size;
484 size = 0; // no contents
485 }
486
487 //qCDebug(KArchiveLog) << "file" << nm << "size=" << size;
488 e = new KArchiveFile(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink,
489 dev->pos(), size);
490 }
491
492 // Skip contents + align bytes
493 qint64 rest = size % 0x200;
494 qint64 skip = size + (rest ? 0x200 - rest : 0);
495 //qCDebug(KArchiveLog) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip;
496 if (! dev->seek(dev->pos() + skip)) {
497 //qCWarning(KArchiveLog) << "skipping" << skip << "failed";
498 }
499 }
500
501 if (pos == -1) {
502 if (nm == QLatin1String(".")) { // special case
503 if (isdir) {
504 if (KArchivePrivate::hasRootDir(this)) {
505 qWarning() << "Broken tar file has two root dir entries";
506 delete e;
507 } else {
508 setRootDir(static_cast<KArchiveDirectory *>(e));
509 }
510 } else {
511 delete e;
512 }
513 } else {
514 rootDir()->addEntry(e);
515 }
516 } else {
517 // In some tar files we can find dir/./file => call cleanPath
518 QString path = QDir::cleanPath(name.left(pos));
519 // Ensure container directory exists, create otherwise
520 KArchiveDirectory *d = findOrCreate(path);
521 if (d) {
522 d->addEntry(e);
523 } else {
524 delete e;
525 return false;
526 }
527 }
528 } else {
529 //qCDebug(KArchiveLog) << "Terminating. Read " << n << " bytes, first one is " << buffer[0];
530 d->tarEnd = dev->pos() - n; // Remember end of archive
531 ende = true;
532 }
533 } while (!ende);
534 return true;
535 }
536
537 /*
538 * Writes back the changes of the temporary file
539 * to the original file.
540 * Must only be called if in write mode, not in read mode
541 */
writeBackTempFile(const QString & fileName)542 bool KTar::KTarPrivate::writeBackTempFile(const QString &fileName)
543 {
544 if (!tmpFile) {
545 return true;
546 }
547
548 //qCDebug(KArchiveLog) << "Write temporary file to compressed file" << fileName << mimetype;
549
550 bool forced = false;
551 if (QLatin1String(application_gzip) == mimetype || QLatin1String(application_bzip) == mimetype ||
552 QLatin1String(application_lzma) == mimetype || QLatin1String(application_xz) == mimetype) {
553 forced = true;
554 }
555
556 // #### TODO this should use QSaveFile to avoid problems on disk full
557 // (KArchive uses QSaveFile by default, but the temp-uncompressed-file trick
558 // circumvents that).
559
560 KFilterDev dev(fileName);
561 QFile *file = tmpFile;
562 if (!dev.open(QIODevice::WriteOnly)) {
563 file->close();
564 q->setErrorString(tr("Failed to write back temp file: %1").arg(dev.errorString()));
565 return false;
566 }
567 if (forced) {
568 dev.setOrigFileName(origFileName);
569 }
570 file->seek(0);
571 QByteArray buffer;
572 buffer.resize(8 * 1024);
573 qint64 len;
574 while (!file->atEnd()) {
575 len = file->read(buffer.data(), buffer.size());
576 dev.write(buffer.data(), len); // TODO error checking
577 }
578 file->close();
579 dev.close();
580
581 //qCDebug(KArchiveLog) << "Write temporary file to compressed file done.";
582 return true;
583 }
584
closeArchive()585 bool KTar::closeArchive()
586 {
587 d->dirList.clear();
588
589 bool ok = true;
590
591 // If we are in readwrite mode and had created
592 // a temporary tar file, we have to write
593 // back the changes to the original file
594 if (d->tmpFile && (mode() & QIODevice::WriteOnly)) {
595 ok = d->writeBackTempFile(fileName());
596 delete d->tmpFile;
597 d->tmpFile = nullptr;
598 setDevice(nullptr);
599 }
600
601 return ok;
602 }
603
doFinishWriting(qint64 size)604 bool KTar::doFinishWriting(qint64 size)
605 {
606 // Write alignment
607 int rest = size % 0x200;
608 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
609 d->tarEnd = device()->pos() + (rest ? 0x200 - rest : 0); // Record our new end of archive
610 }
611 if (rest) {
612 char buffer[0x201];
613 for (uint i = 0; i < 0x200; ++i) {
614 buffer[i] = 0;
615 }
616 qint64 nwritten = device()->write(buffer, 0x200 - rest);
617 const bool ok = nwritten == 0x200 - rest;
618
619 if (!ok) {
620 setErrorString(
621 tr("Couldn't write alignment: %1")
622 .arg(device()->errorString()));
623 }
624
625 return ok;
626 }
627 return true;
628 }
629
630 /*** Some help from the tar sources
631 struct posix_header
632 { byte offset
633 char name[100]; * 0 * 0x0
634 char mode[8]; * 100 * 0x64
635 char uid[8]; * 108 * 0x6c
636 char gid[8]; * 116 * 0x74
637 char size[12]; * 124 * 0x7c
638 char mtime[12]; * 136 * 0x88
639 char chksum[8]; * 148 * 0x94
640 char typeflag; * 156 * 0x9c
641 char linkname[100]; * 157 * 0x9d
642 char magic[6]; * 257 * 0x101
643 char version[2]; * 263 * 0x107
644 char uname[32]; * 265 * 0x109
645 char gname[32]; * 297 * 0x129
646 char devmajor[8]; * 329 * 0x149
647 char devminor[8]; * 337 * ...
648 char prefix[155]; * 345 *
649 * 500 *
650 };
651 */
652
fillBuffer(char * buffer,const char * mode,qint64 size,const QDateTime & mtime,char typeflag,const char * uname,const char * gname)653 void KTar::KTarPrivate::fillBuffer(char *buffer,
654 const char *mode, qint64 size, const QDateTime &mtime, char typeflag,
655 const char *uname, const char *gname)
656 {
657 // mode (as in stpos())
658 assert(strlen(mode) == 6);
659 memcpy(buffer + 0x64, mode, 6);
660 buffer[0x6a] = ' ';
661 buffer[0x6b] = '\0';
662
663 // dummy uid
664 strcpy(buffer + 0x6c, " 765 "); // 501 in decimal
665 // dummy gid
666 strcpy(buffer + 0x74, " 144 "); // 100 in decimal
667
668 // size
669 QByteArray s = QByteArray::number(size, 8); // octal
670 s = s.rightJustified(11, '0');
671 memcpy(buffer + 0x7c, s.data(), 11);
672 buffer[0x87] = ' '; // space-terminate (no null after)
673
674 // modification time
675 const QDateTime modificationTime = mtime.isValid() ? mtime : QDateTime::currentDateTime();
676 s = QByteArray::number(static_cast<qulonglong>(modificationTime.toMSecsSinceEpoch() / 1000), 8); // octal
677 s = s.rightJustified(11, '0');
678 memcpy(buffer + 0x88, s.data(), 11);
679 buffer[0x93] = ' '; // space-terminate (no null after) -- well current tar writes a null byte
680
681 // spaces, replaced by the check sum later
682 buffer[0x94] = 0x20;
683 buffer[0x95] = 0x20;
684 buffer[0x96] = 0x20;
685 buffer[0x97] = 0x20;
686 buffer[0x98] = 0x20;
687 buffer[0x99] = 0x20;
688
689 /* From the tar sources :
690 Fill in the checksum field. It's formatted differently from the
691 other fields: it has [6] digits, a null, then a space -- rather than
692 digits, a space, then a null. */
693
694 buffer[0x9a] = '\0';
695 buffer[0x9b] = ' ';
696
697 // type flag (dir, file, link)
698 buffer[0x9c] = typeflag;
699
700 // magic + version
701 strcpy(buffer + 0x101, "ustar");
702 strcpy(buffer + 0x107, "00");
703
704 // user
705 strcpy(buffer + 0x109, uname);
706 // group
707 strcpy(buffer + 0x129, gname);
708
709 // Header check sum
710 int check = 32;
711 for (uint j = 0; j < 0x200; ++j) {
712 check += static_cast<unsigned char>(buffer[j]);
713 }
714 s = QByteArray::number(check, 8); // octal
715 s = s.rightJustified(6, '0');
716 memcpy(buffer + 0x94, s.constData(), 6);
717 }
718
writeLonglink(char * buffer,const QByteArray & name,char typeflag,const char * uname,const char * gname)719 void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag,
720 const char *uname, const char *gname)
721 {
722 strcpy(buffer, "././@LongLink");
723 qint64 namelen = name.length() + 1;
724 fillBuffer(buffer, " 0", namelen, QDateTime(), typeflag, uname, gname);
725 q->device()->write(buffer, 0x200); // TODO error checking
726 qint64 offset = 0;
727 while (namelen > 0) {
728 int chunksize = qMin(namelen, 0x200LL);
729 memcpy(buffer, name.data() + offset, chunksize);
730 // write long name
731 q->device()->write(buffer, 0x200); // TODO error checking
732 // not even needed to reclear the buffer, tar doesn't do it
733 namelen -= chunksize;
734 offset += 0x200;
735 }/*wend*/
736 }
737
doPrepareWriting(const QString & name,const QString & user,const QString & group,qint64 size,mode_t perm,const QDateTime &,const QDateTime & mtime,const QDateTime &)738 bool KTar::doPrepareWriting(const QString &name, const QString &user,
739 const QString &group, qint64 size, mode_t perm,
740 const QDateTime & /*atime*/, const QDateTime &mtime, const QDateTime & /*ctime*/)
741 {
742 if (!isOpen()) {
743 setErrorString(tr("Application error: TAR file must be open before being written into"));
744 qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()";
745 return false;
746 }
747
748 if (!(mode() & QIODevice::WriteOnly)) {
749 setErrorString(tr("Application error: attempted to write into non-writable 7-Zip file"));
750 qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)";
751 return false;
752 }
753
754 // In some tar files we can find dir/./file => call cleanPath
755 QString fileName(QDir::cleanPath(name));
756
757 /*
758 // Create toplevel dirs
759 // Commented out by David since it's not necessary, and if anybody thinks it is,
760 // he needs to implement a findOrCreate equivalent in writeDir.
761 // But as KTar and the "tar" program both handle tar files without
762 // dir entries, there's really no need for that
763 QString tmp ( fileName );
764 int i = tmp.lastIndexOf( '/' );
765 if ( i != -1 )
766 {
767 QString d = tmp.left( i + 1 ); // contains trailing slash
768 if ( !m_dirList.contains( d ) )
769 {
770 tmp = tmp.mid( i + 1 );
771 writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
772 }
773 }
774 */
775
776 char buffer[0x201];
777 memset(buffer, 0, 0x200);
778 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
779 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
780 }
781
782 // provide converted stuff we need later on
783 const QByteArray encodedFileName = QFile::encodeName(fileName);
784 const QByteArray uname = user.toLocal8Bit();
785 const QByteArray gname = group.toLocal8Bit();
786
787 // If more than 100 bytes, we need to use the LongLink trick
788 if (encodedFileName.length() > 99) {
789 d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData());
790 }
791
792 // Write (potentially truncated) name
793 strncpy(buffer, encodedFileName.constData(), 99);
794 buffer[99] = 0;
795 // zero out the rest (except for what gets filled anyways)
796 memset(buffer + 0x9d, 0, 0x200 - 0x9d);
797
798 QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
799 permstr = permstr.rightJustified(6, '0');
800 d->fillBuffer(buffer, permstr.constData(), size, mtime, 0x30, uname.constData(), gname.constData());
801
802 // Write header
803 if (device()->write(buffer, 0x200) != 0x200) {
804 setErrorString(
805 tr("Failed to write header: %1")
806 .arg(device()->errorString()));
807 return false;
808 } else {
809 return true;
810 }
811 }
812
doWriteDir(const QString & name,const QString & user,const QString & group,mode_t perm,const QDateTime &,const QDateTime & mtime,const QDateTime &)813 bool KTar::doWriteDir(const QString &name, const QString &user,
814 const QString &group, mode_t perm,
815 const QDateTime & /*atime*/, const QDateTime &mtime, const QDateTime & /*ctime*/)
816 {
817 if (!isOpen()) {
818 setErrorString(tr("Application error: TAR file must be open before being written into"));
819 qCWarning(KArchiveLog) << "doWriteDir failed: !isOpen()";
820 return false;
821 }
822
823 if (!(mode() & QIODevice::WriteOnly)) {
824 setErrorString(tr("Application error: attempted to write into non-writable TAR file"));
825 qCWarning(KArchiveLog) << "doWriteDir failed: !(mode() & QIODevice::WriteOnly)";
826 return false;
827 }
828
829 // In some tar files we can find dir/./ => call cleanPath
830 QString dirName(QDir::cleanPath(name));
831
832 // Need trailing '/'
833 if (!dirName.endsWith(QLatin1Char('/'))) {
834 dirName += QLatin1Char('/');
835 }
836
837 if (d->dirList.contains(dirName)) {
838 return true; // already there
839 }
840
841 char buffer[0x201];
842 memset(buffer, 0, 0x200);
843 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
844 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
845 }
846
847 // provide converted stuff we need lateron
848 QByteArray encodedDirname = QFile::encodeName(dirName);
849 QByteArray uname = user.toLocal8Bit();
850 QByteArray gname = group.toLocal8Bit();
851
852 // If more than 100 bytes, we need to use the LongLink trick
853 if (encodedDirname.length() > 99) {
854 d->writeLonglink(buffer, encodedDirname, 'L', uname.constData(), gname.constData());
855 }
856
857 // Write (potentially truncated) name
858 strncpy(buffer, encodedDirname.constData(), 99);
859 buffer[99] = 0;
860 // zero out the rest (except for what gets filled anyways)
861 memset(buffer + 0x9d, 0, 0x200 - 0x9d);
862
863 QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
864 permstr = permstr.rightJustified(6, ' ');
865 d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x35, uname.constData(), gname.constData());
866
867 // Write header
868 device()->write(buffer, 0x200);
869 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
870 d->tarEnd = device()->pos();
871 }
872
873 d->dirList.append(dirName); // contains trailing slash
874 return true; // TODO if wanted, better error control
875 }
876
doWriteSymLink(const QString & name,const QString & target,const QString & user,const QString & group,mode_t perm,const QDateTime &,const QDateTime & mtime,const QDateTime &)877 bool KTar::doWriteSymLink(const QString &name, const QString &target,
878 const QString &user, const QString &group,
879 mode_t perm, const QDateTime & /*atime*/, const QDateTime &mtime, const QDateTime & /*ctime*/)
880 {
881 if (!isOpen()) {
882 setErrorString(tr("Application error: TAR file must be open before being written into"));
883 qCWarning(KArchiveLog) << "doWriteSymLink failed: !isOpen()";
884 return false;
885 }
886
887 if (!(mode() & QIODevice::WriteOnly)) {
888 setErrorString(tr("Application error: attempted to write into non-writable TAR file"));
889 qCWarning(KArchiveLog) << "doWriteSymLink failed: !(mode() & QIODevice::WriteOnly)";
890 return false;
891 }
892
893 // In some tar files we can find dir/./file => call cleanPath
894 QString fileName(QDir::cleanPath(name));
895
896 char buffer[0x201];
897 memset(buffer, 0, 0x200);
898 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
899 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
900 }
901
902 // provide converted stuff we need lateron
903 QByteArray encodedFileName = QFile::encodeName(fileName);
904 QByteArray encodedTarget = QFile::encodeName(target);
905 QByteArray uname = user.toLocal8Bit();
906 QByteArray gname = group.toLocal8Bit();
907
908 // If more than 100 bytes, we need to use the LongLink trick
909 if (encodedTarget.length() > 99) {
910 d->writeLonglink(buffer, encodedTarget, 'K', uname.constData(), gname.constData());
911 }
912 if (encodedFileName.length() > 99) {
913 d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData());
914 }
915
916 // Write (potentially truncated) name
917 strncpy(buffer, encodedFileName.constData(), 99);
918 buffer[99] = 0;
919 // Write (potentially truncated) symlink target
920 strncpy(buffer + 0x9d, encodedTarget.constData(), 99);
921 buffer[0x9d + 99] = 0;
922 // zero out the rest
923 memset(buffer + 0x9d + 100, 0, 0x200 - 100 - 0x9d);
924
925 QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
926 permstr = permstr.rightJustified(6, ' ');
927 d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x32, uname.constData(), gname.constData());
928
929 // Write header
930 bool retval = device()->write(buffer, 0x200) == 0x200;
931 if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
932 d->tarEnd = device()->pos();
933 }
934 return retval;
935 }
936
virtual_hook(int id,void * data)937 void KTar::virtual_hook(int id, void *data)
938 {
939 KArchive::virtual_hook(id, data);
940 }
941