KArchive

karchive.cpp
1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
3 SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
4
5 Moved from ktar.cpp by Roberto Teixeira <maragato@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "karchive.h"
11#include "karchive_p.h"
12#include "klimitediodevice_p.h"
13#include "loggingcategory.h"
14
15#include <qplatformdefs.h> // QT_STATBUF, QT_LSTAT
16
17#include <QDebug>
18#include <QDir>
19#include <QFile>
20#include <QMap>
21#include <QStack>
22
23#include <cerrno>
24#include <stdio.h>
25#include <stdlib.h>
26
27#include <assert.h>
28
29#ifdef Q_OS_UNIX
30#include <grp.h>
31#include <limits.h> // PATH_MAX
32#include <pwd.h>
33#include <unistd.h>
34#endif
35#ifdef Q_OS_WIN
36#include <windows.h> // DWORD, GetUserNameW
37#endif // Q_OS_WIN
38
39#if defined(Q_OS_UNIX)
40#define STAT_METHOD QT_LSTAT
41#else
42#define STAT_METHOD QT_STAT
43#endif
44
45////////////////////////////////////////////////////////////////////////
46/////////////////// KArchiveDirectoryPrivate ///////////////////////////
47////////////////////////////////////////////////////////////////////////
48
49class KArchiveDirectoryPrivate
50{
51public:
52 KArchiveDirectoryPrivate(KArchiveDirectory *parent)
53 : q(parent)
54 {
55 }
56
57 ~KArchiveDirectoryPrivate()
58 {
59 qDeleteAll(entries);
60 }
61
62 KArchiveDirectoryPrivate(const KArchiveDirectoryPrivate &) = delete;
63 KArchiveDirectoryPrivate &operator=(const KArchiveDirectoryPrivate &) = delete;
64
65 static KArchiveDirectoryPrivate *get(KArchiveDirectory *directory)
66 {
67 return directory->d;
68 }
69
70 // Returns in containingDirectory the directory that actually contains the returned entry
71 const KArchiveEntry *entry(const QString &_name, KArchiveDirectory **containingDirectory) const
72 {
73 *containingDirectory = q;
74
76 int pos = name.indexOf(QLatin1Char('/'));
77 if (pos == 0) { // absolute path (see also KArchive::findOrCreate)
78 if (name.length() > 1) {
79 name = name.mid(1); // remove leading slash
80 pos = name.indexOf(QLatin1Char('/')); // look again
81 } else { // "/"
82 return q;
83 }
84 }
85 // trailing slash ? -> remove
86 if (pos != -1 && pos == name.length() - 1) {
87 name = name.left(pos);
88 pos = name.indexOf(QLatin1Char('/')); // look again
89 }
90 if (pos != -1) {
91 const QString left = name.left(pos);
92 const QString right = name.mid(pos + 1);
93
94 // qCDebug(KArchiveLog) << "left=" << left << "right=" << right;
95
96 KArchiveEntry *e = entries.value(left);
97 if (!e || !e->isDirectory()) {
98 return nullptr;
99 }
100 *containingDirectory = static_cast<KArchiveDirectory *>(e);
101 return (*containingDirectory)->d->entry(right, containingDirectory);
102 }
103
104 return entries.value(name);
105 }
106
109};
110
111////////////////////////////////////////////////////////////////////////
112/////////////////////////// KArchive ///////////////////////////////////
113////////////////////////////////////////////////////////////////////////
114
116 : d(new KArchivePrivate(this))
117{
118 if (fileName.isEmpty()) {
119 qCWarning(KArchiveLog) << "KArchive: No file name specified";
120 }
121 d->fileName = fileName;
122 // This constructor leaves the device set to 0.
123 // This is for the use of QSaveFile, see open().
124}
125
127 : d(new KArchivePrivate(this))
128{
129 if (!dev) {
130 qCWarning(KArchiveLog) << "KArchive: Null device specified";
131 }
132 d->dev = dev;
133}
134
135KArchive::~KArchive()
136{
137 Q_ASSERT(!isOpen()); // the derived class destructor must have closed already
138 delete d;
139}
140
142{
143 Q_ASSERT(mode != QIODevice::NotOpen);
144
145 if (isOpen()) {
146 close();
147 }
148
149 if (!d->fileName.isEmpty()) {
150 Q_ASSERT(!d->dev);
151 if (!createDevice(mode)) {
152 return false;
153 }
154 }
155
156 if (!d->dev) {
157 setErrorString(tr("No filename or device was specified"));
158 return false;
159 }
160
161 if (!d->dev->isOpen() && !d->dev->open(mode)) {
162 setErrorString(tr("Could not open device in mode %1").arg(mode));
163 return false;
164 }
165
166 d->mode = mode;
167
168 Q_ASSERT(!d->rootDir);
169 d->rootDir = nullptr;
170
171 return openArchive(mode);
172}
173
175{
176 switch (mode) {
178 if (!d->fileName.isEmpty()) {
179 // The use of QSaveFile can't be done in the ctor (no mode known yet)
180 // qCDebug(KArchiveLog) << "Writing to a file using QSaveFile";
181 d->saveFile = new QSaveFile(d->fileName);
182#ifdef Q_OS_ANDROID
183 // we cannot rename on to Android content: URLs
184 if (d->fileName.startsWith(QLatin1String("content://"))) {
185 d->saveFile->setDirectWriteFallback(true);
186 }
187#endif
188 if (!d->saveFile->open(QIODevice::WriteOnly)) {
189 setErrorString(tr("QSaveFile creation for %1 failed: %2").arg(d->fileName, d->saveFile->errorString()));
190
191 delete d->saveFile;
192 d->saveFile = nullptr;
193 return false;
194 }
195 d->dev = d->saveFile;
196 Q_ASSERT(d->dev);
197 }
198 break;
201 // ReadWrite mode still uses QFile for now; we'd need to copy to the tempfile, in fact.
202 if (!d->fileName.isEmpty()) {
203 d->dev = new QFile(d->fileName);
204 d->deviceOwned = true;
205 }
206 break; // continued below
207 default:
208 setErrorString(tr("Unsupported mode %1").arg(d->mode));
209 return false;
210 }
211 return true;
212}
213
215{
216 if (!isOpen()) {
217 setErrorString(tr("Archive already closed"));
218 return false; // already closed (return false or true? arguable...)
219 }
220
221 // moved by holger to allow kzip to write the zip central dir
222 // to the file in closeArchive()
223 // DF: added d->dev so that we skip closeArchive if saving aborted.
224 bool closeSucceeded = true;
225 if (d->dev) {
226 closeSucceeded = closeArchive();
227 if (d->mode == QIODevice::WriteOnly && !closeSucceeded) {
228 d->abortWriting();
229 }
230 }
231
232 if (d->dev && d->dev != d->saveFile) {
233 d->dev->close();
234 }
235
236 // if d->saveFile is not null then it is equal to d->dev.
237 if (d->saveFile) {
238 closeSucceeded = d->saveFile->commit();
239 delete d->saveFile;
240 d->saveFile = nullptr;
241 }
242 if (d->deviceOwned) {
243 delete d->dev; // we created it ourselves in open()
244 }
245
246 delete d->rootDir;
247 d->rootDir = nullptr;
248 d->mode = QIODevice::NotOpen;
249 d->dev = nullptr;
250 return closeSucceeded;
251}
252
254{
255 return d->errorStr;
256}
257
259{
260 // rootDir isn't const so that parsing-on-demand is possible
261 return const_cast<KArchive *>(this)->rootDir();
262}
263
264bool KArchive::addLocalFile(const QString &fileName, const QString &destName)
265{
266 QFileInfo fileInfo(fileName);
267 if (!fileInfo.isFile() && !fileInfo.isSymLink()) {
268 setErrorString(tr("%1 doesn't exist or is not a regular file.").arg(fileName));
269 return false;
270 }
271
272 QT_STATBUF fi;
273 if (STAT_METHOD(QFile::encodeName(fileName).constData(), &fi) == -1) {
274 setErrorString(tr("Failed accessing the file %1 for adding to the archive. The error was: %2").arg(fileName).arg(QLatin1String{strerror(errno)}));
275 return false;
276 }
277
278 if (fileInfo.isSymLink()) {
279 QString symLinkTarget;
280 // Do NOT use fileInfo.symLinkTarget() for unix symlinks!
281 // It returns the -full- path to the target, while we want the target string "as is".
282#if defined(Q_OS_UNIX) && !defined(Q_OS_OS2EMX)
283 const QByteArray encodedFileName = QFile::encodeName(fileName);
284 QByteArray s;
285#if defined(PATH_MAX)
286 s.resize(PATH_MAX + 1);
287#else
288 int path_max = pathconf(encodedFileName.data(), _PC_PATH_MAX);
289 if (path_max <= 0) {
290 path_max = 4096;
291 }
292 s.resize(path_max);
293#endif
294 int len = readlink(encodedFileName.data(), s.data(), s.size() - 1);
295 if (len >= 0) {
296 s[len] = '\0';
297 symLinkTarget = QFile::decodeName(s.constData());
298 }
299#endif
300 if (symLinkTarget.isEmpty()) { // Mac or Windows
301 symLinkTarget = fileInfo.symLinkTarget();
302 }
303 return writeSymLink(destName,
304 symLinkTarget,
305 fileInfo.owner(),
306 fileInfo.group(),
307 fi.st_mode,
308 fileInfo.lastRead(),
309 fileInfo.lastModified(),
310 fileInfo.birthTime());
311 } /*end if*/
312
313 qint64 size = fileInfo.size();
314
315 // the file must be opened before prepareWriting is called, otherwise
316 // if the opening fails, no content will follow the already written
317 // header and the tar file is incorrect
318 QFile file(fileName);
319 if (!file.open(QIODevice::ReadOnly)) {
320 setErrorString(tr("Couldn't open file %1: %2").arg(fileName, file.errorString()));
321 return false;
322 }
323
324 if (!prepareWriting(destName, fileInfo.owner(), fileInfo.group(), size, fi.st_mode, fileInfo.lastRead(), fileInfo.lastModified(), fileInfo.birthTime())) {
325 // qCWarning(KArchiveLog) << " prepareWriting" << destName << "failed";
326 return false;
327 }
328
329 // Read and write data in chunks to minimize memory usage
330 QByteArray array;
331 array.resize(int(qMin(qint64(1024 * 1024), size)));
332 qint64 n;
333 qint64 total = 0;
334 while ((n = file.read(array.data(), array.size())) > 0) {
335 if (!writeData(array.data(), n)) {
336 // qCWarning(KArchiveLog) << "writeData failed";
337 return false;
338 }
339 total += n;
340 }
341 Q_ASSERT(total == size);
342
343 if (!finishWriting(size)) {
344 // qCWarning(KArchiveLog) << "finishWriting failed";
345 return false;
346 }
347 return true;
348}
349
350bool KArchive::addLocalDirectory(const QString &path, const QString &destName)
351{
352 QDir dir(path);
353 if (!dir.exists()) {
354 setErrorString(tr("Directory %1 does not exist").arg(path));
355 return false;
356 }
357 dir.setFilter(dir.filter() | QDir::Hidden);
358 const QStringList files = dir.entryList();
359 for (const QString &file : files) {
360 if (file != QLatin1String(".") && file != QLatin1String("..")) {
361 const QString fileName = path + QLatin1Char('/') + file;
362 // qCDebug(KArchiveLog) << "storing " << fileName;
363 const QString dest = destName.isEmpty() ? file : (destName + QLatin1Char('/') + file);
364 QFileInfo fileInfo(fileName);
365
366 if (fileInfo.isFile() || fileInfo.isSymLink()) {
367 addLocalFile(fileName, dest);
368 } else if (fileInfo.isDir()) {
369 // Write directory, so that empty dirs are preserved (and permissions written out, etc.)
370 int perms = 0;
371 QT_STATBUF fi;
372 if (STAT_METHOD(QFile::encodeName(fileName).constData(), &fi) != -1) {
373 perms = fi.st_mode;
374 }
375 writeDir(dest, fileInfo.owner(), fileInfo.group(), perms, fileInfo.lastRead(), fileInfo.lastModified(), fileInfo.birthTime());
376 // Recurse
378 }
379 // We omit sockets
380 }
381 }
382 return true;
383}
384
386 QByteArrayView data,
387 mode_t perm,
388 const QString &user,
389 const QString &group,
390 const QDateTime &atime,
391 const QDateTime &mtime,
392 const QDateTime &ctime)
393{
394 const qint64 size = data.size();
395 if (!prepareWriting(name, user, group, size, perm, atime, mtime, ctime)) {
396 // qCWarning(KArchiveLog) << "prepareWriting failed";
397 return false;
398 }
399
400 // Write data
401 // Note: if data is null, don't call write, it would terminate the KCompressionDevice
402 if (data.constData() && size && !writeData(data.constData(), size)) {
403 // qCWarning(KArchiveLog) << "writeData failed";
404 return false;
405 }
406
407 if (!finishWriting(size)) {
408 // qCWarning(KArchiveLog) << "finishWriting failed";
409 return false;
410 }
411 return true;
412}
413
414bool KArchive::writeData(const char *data, qint64 size)
415{
416 return doWriteData(data, size);
417}
418
420{
421 return doWriteData(data.constData(), data.size());
422}
423
424bool KArchive::doWriteData(const char *data, qint64 size)
425{
426 bool ok = device()->write(data, size) == size;
427 if (!ok) {
428 setErrorString(tr("Writing failed: %1").arg(device()->errorString()));
429 d->abortWriting();
430 }
431 return ok;
432}
433
434// The writeDir -> doWriteDir pattern allows to avoid propagating the default
435// values into all virtual methods of subclasses, and it allows more extensibility:
436// if a new argument is needed, we can add a writeDir overload which stores the
437// additional argument in the d pointer, and doWriteDir reimplementations can fetch
438// it from there.
439
441 const QString &user,
442 const QString &group,
443 mode_t perm,
444 const QDateTime &atime,
445 const QDateTime &mtime,
446 const QDateTime &ctime)
447{
448 return doWriteDir(name, user, group, perm | 040000, atime, mtime, ctime);
449}
450
452 const QString &target,
453 const QString &user,
454 const QString &group,
455 mode_t perm,
456 const QDateTime &atime,
457 const QDateTime &mtime,
458 const QDateTime &ctime)
459{
460 return doWriteSymLink(name, target, user, group, perm, atime, mtime, ctime);
461}
462
464 const QString &user,
465 const QString &group,
466 qint64 size,
467 mode_t perm,
468 const QDateTime &atime,
469 const QDateTime &mtime,
470 const QDateTime &ctime)
471{
472 bool ok = doPrepareWriting(name, user, group, size, perm, atime, mtime, ctime);
473 if (!ok) {
474 d->abortWriting();
475 }
476 return ok;
477}
478
479bool KArchive::finishWriting(qint64 size)
480{
481 return doFinishWriting(size);
482}
483
485{
486 d->errorStr = errorStr;
487}
488
489static QString getCurrentUserName()
490{
491#if defined(Q_OS_UNIX)
492 struct passwd *pw = getpwuid(getuid());
493 return pw ? QFile::decodeName(pw->pw_name) : QString::number(getuid());
494#elif defined(Q_OS_WIN)
495 wchar_t buffer[255];
496 DWORD size = 255;
497 bool ok = GetUserNameW(buffer, &size);
498 if (!ok) {
499 return QString();
500 }
501 return QString::fromWCharArray(buffer);
502#else
503 return QString();
504#endif
505}
506
507static QString getCurrentGroupName()
508{
509#if defined(Q_OS_UNIX)
510 struct group *grp = getgrgid(getgid());
511 return grp ? QFile::decodeName(grp->gr_name) : QString::number(getgid());
512#elif defined(Q_OS_WIN)
513 return QString();
514#else
515 return QString();
516#endif
517}
518
520{
521 if (!d->rootDir) {
522 // qCDebug(KArchiveLog) << "Making root dir ";
523 QString username = ::getCurrentUserName();
524 QString groupname = ::getCurrentGroupName();
525
526 d->rootDir = new KArchiveDirectory(this, QStringLiteral("/"), int(0777 + S_IFDIR), QDateTime(), username, groupname, QString());
527 }
528 return d->rootDir;
529}
530
532{
533 return d->findOrCreate(path, 0 /*recursionCounter*/);
534}
535
536KArchiveDirectory *KArchivePrivate::findOrCreate(const QString &path, int recursionCounter)
537{
538 // Check we're not in a path that is ultra deep, this is most probably fine since PATH_MAX on Linux
539 // is defined as 4096, so even on /a/a/a/a/a/a 2500 recursions puts us over that limit
540 // an ultra deep recursion will make us crash due to not enough stack. Tests show that 1MB stack
541 // (default on Linux seems to be 8MB) gives us up to around 4000 recursions
542 if (recursionCounter > 2500) {
543 qCWarning(KArchiveLog) << "path recursion limit exceeded, bailing out";
544 return nullptr;
545 }
546 // qCDebug(KArchiveLog) << path;
547 if (path.isEmpty() || path == QLatin1String("/") || path == QLatin1String(".")) { // root dir => found
548 // qCDebug(KArchiveLog) << "returning rootdir";
549 return q->rootDir();
550 }
551 // Important note : for tar files containing absolute paths
552 // (i.e. beginning with "/"), this means the leading "/" will
553 // be removed (no KDirectory for it), which is exactly the way
554 // the "tar" program works (though it displays a warning about it)
555 // See also KArchiveDirectory::entry().
556
557 // Already created ? => found
558 KArchiveDirectory *existingEntryParentDirectory;
559 const KArchiveEntry *existingEntry = KArchiveDirectoryPrivate::get(q->rootDir())->entry(path, &existingEntryParentDirectory);
560 if (existingEntry) {
561 if (existingEntry->isDirectory())
562 // qCDebug(KArchiveLog) << "found it";
563 {
564 const KArchiveDirectory *dir = static_cast<const KArchiveDirectory *>(existingEntry);
565 return const_cast<KArchiveDirectory *>(dir);
566 } else {
567 const KArchiveFile *file = static_cast<const KArchiveFile *>(existingEntry);
568 if (file->size() > 0) {
569 qCWarning(KArchiveLog) << path << "is normal file, but there are file paths in the archive assuming it is a directory, bailing out";
570 return nullptr;
571 }
572
573 qCDebug(KArchiveLog) << path << " is an empty file, assuming it is actually a directory and replacing";
574 KArchiveEntry *myEntry = const_cast<KArchiveEntry *>(existingEntry);
575 existingEntryParentDirectory->removeEntry(myEntry);
576 delete myEntry;
577 }
578 }
579
580 // Otherwise go up and try again
581 int pos = path.lastIndexOf(QLatin1Char('/'));
582 KArchiveDirectory *parent;
583 QString dirname;
584 if (pos == -1) { // no more slash => create in root dir
585 parent = q->rootDir();
586 dirname = path;
587 } else {
588 QString left = path.left(pos);
589 dirname = path.mid(pos + 1);
590 parent = findOrCreate(left, recursionCounter + 1); // recursive call... until we find an existing dir.
591 }
592
593 if (!parent) {
594 return nullptr;
595 }
596
597 // qCDebug(KArchiveLog) << "found parent " << parent->name() << " adding " << dirname << " to ensure " << path;
598 // Found -> add the missing piece
599 KArchiveDirectory *e = new KArchiveDirectory(q, dirname, rootDir->permissions(), rootDir->date(), rootDir->user(), rootDir->group(), QString());
600 if (parent->addEntryV2(e)) {
601 return e; // now a directory to <path> exists
602 } else {
603 return nullptr;
604 }
605}
606
608{
609 if (d->deviceOwned) {
610 delete d->dev;
611 }
612 d->dev = dev;
613 d->deviceOwned = false;
614}
615
617{
618 Q_ASSERT(!d->rootDir); // Call setRootDir only once during parsing please ;)
619 delete d->rootDir; // but if it happens, don't leak
620 d->rootDir = rootDir;
621}
622
624{
625 return d->mode;
626}
627
629{
630 return d->dev;
631}
632
634{
635 return d->mode != QIODevice::NotOpen;
636}
637
639{
640 return d->fileName;
641}
642
643void KArchivePrivate::abortWriting()
644{
645 if (saveFile) {
646 saveFile->cancelWriting();
647 delete saveFile;
648 saveFile = nullptr;
649 dev = nullptr;
650 }
651}
652
653// this is a hacky wrapper to check if time_t value is invalid
654QDateTime KArchivePrivate::time_tToDateTime(uint time_t)
655{
656 if (time_t == uint(-1)) {
657 return QDateTime();
658 }
659 return QDateTime::fromSecsSinceEpoch(time_t);
660}
661
662////////////////////////////////////////////////////////////////////////
663/////////////////////// KArchiveEntry //////////////////////////////////
664////////////////////////////////////////////////////////////////////////
665
666class KArchiveEntryPrivate
667{
668public:
669 KArchiveEntryPrivate(KArchive *_archive,
670 const QString &_name,
671 int _access,
672 const QDateTime &_date,
673 const QString &_user,
674 const QString &_group,
675 const QString &_symlink)
676 : name(_name)
677 , date(_date)
678 , access(_access)
679 , user(_user)
680 , group(_group)
681 , symlink(_symlink)
682 , archive(_archive)
683 {
684 }
686 QDateTime date;
687 mode_t access;
688 QString user;
689 QString group;
690 QString symlink;
691 KArchive *archive;
692};
693
695 const QString &name,
696 int access,
697 const QDateTime &date,
698 const QString &user,
699 const QString &group,
700 const QString &symlink)
701 : d(new KArchiveEntryPrivate(t, name, access, date, user, group, symlink))
702{
703}
704
705KArchiveEntry::~KArchiveEntry()
706{
707 delete d;
708}
709
711{
712 return d->date;
713}
714
716{
717 return d->name;
718}
719
721{
722 return d->access;
723}
724
726{
727 return d->user;
728}
729
731{
732 return d->group;
733}
734
736{
737 return d->symlink;
738}
739
741{
742 return false;
743}
744
746{
747 return false;
748}
749
750KArchive *KArchiveEntry::archive() const
751{
752 return d->archive;
753}
754
755////////////////////////////////////////////////////////////////////////
756/////////////////////// KArchiveFile ///////////////////////////////////
757////////////////////////////////////////////////////////////////////////
758
759class KArchiveFilePrivate
760{
761public:
762 KArchiveFilePrivate(qint64 _pos, qint64 _size)
763 : pos(_pos)
764 , size(_size)
765 {
766 }
767 qint64 pos;
768 qint64 size;
769};
770
772 const QString &name,
773 int access,
774 const QDateTime &date,
775 const QString &user,
776 const QString &group,
777 const QString &symlink,
778 qint64 pos,
779 qint64 size)
780 : KArchiveEntry(t, name, access, date, user, group, symlink)
781 , d(new KArchiveFilePrivate(pos, size))
782{
783}
784
786{
787 delete d;
788}
789
791{
792 return d->pos;
793}
794
795qint64 KArchiveFile::size() const
796{
797 return d->size;
798}
799
801{
802 d->size = s;
803}
804
806{
807 bool ok = archive()->device()->seek(d->pos);
808 if (!ok) {
809 // qCWarning(KArchiveLog) << "Failed to sync to" << d->pos << "to read" << name();
810 }
811
812 // Read content
813 QByteArray arr;
814 if (d->size) {
815 arr = archive()->device()->read(d->size);
816 Q_ASSERT(arr.size() == d->size);
817 }
818 return arr;
819}
820
822{
823 return new KLimitedIODevice(archive()->device(), d->pos, d->size);
824}
825
827{
828 return true;
829}
830
831static QFileDevice::Permissions withExecutablePerms(QFileDevice::Permissions filePerms, mode_t perms)
832{
833 if (perms & 01) {
834 filePerms |= QFileDevice::ExeOther;
835 }
836
837 if (perms & 010) {
838 filePerms |= QFileDevice::ExeGroup;
839 }
840
841 if (perms & 0100) {
842 filePerms |= QFileDevice::ExeOwner;
843 }
844
845 return filePerms;
846}
847
848bool KArchiveFile::copyTo(const QString &dest) const
849{
850 QFile f(dest + QLatin1Char('/') + name());
852 QIODevice *inputDev = createDevice();
853 if (!inputDev) {
854 f.remove();
855 return false;
856 }
857
858 // Read and write data in chunks to minimize memory usage
859 const qint64 chunkSize = 1024 * 1024;
860 qint64 remainingSize = d->size;
861 QByteArray array;
862 array.resize(int(qMin(chunkSize, remainingSize)));
863
864 while (remainingSize > 0) {
865 const qint64 currentChunkSize = qMin(chunkSize, remainingSize);
866 const qint64 n = inputDev->read(array.data(), currentChunkSize);
867 Q_UNUSED(n) // except in Q_ASSERT
868 Q_ASSERT(n == currentChunkSize);
869 f.write(array.data(), currentChunkSize);
870 remainingSize -= currentChunkSize;
871 }
872 f.setPermissions(withExecutablePerms(f.permissions(), permissions()));
873 f.close();
874
875 delete inputDev;
876 return true;
877 }
878 return false;
879}
880
881////////////////////////////////////////////////////////////////////////
882//////////////////////// KArchiveDirectory /////////////////////////////////
883////////////////////////////////////////////////////////////////////////
884
886 const QString &name,
887 int access,
888 const QDateTime &date,
889 const QString &user,
890 const QString &group,
891 const QString &symlink)
892 : KArchiveEntry(t, name, access, date, user, group, symlink)
893 , d(new KArchiveDirectoryPrivate(this))
894{
895}
896
897KArchiveDirectory::~KArchiveDirectory()
898{
899 delete d;
900}
901
903{
904 return d->entries.keys();
905}
906
908{
909 KArchiveDirectory *dummy;
910 return d->entry(_name, &dummy);
911}
912
914{
915 const KArchiveEntry *e = entry(name);
916 if (e && e->isFile()) {
917 return static_cast<const KArchiveFile *>(e);
918 }
919 return nullptr;
920}
921
926
928{
929 if (d->entries.value(entry->name())) {
930 qCWarning(KArchiveLog) << "directory " << name() << "has entry" << entry->name() << "already";
931 delete entry;
932 return false;
933 }
934 d->entries.insert(entry->name(), entry);
935 return true;
936}
937
939{
940 if (!entry) {
941 return;
942 }
943
945 // nothing removed?
946 if (it == d->entries.end()) {
947 qCWarning(KArchiveLog) << "directory " << name() << "has no entry with name " << entry->name();
948 return;
949 }
950 if (it.value() != entry) {
951 qCWarning(KArchiveLog) << "directory " << name() << "has another entry for name " << entry->name();
952 return;
953 }
954 d->entries.erase(it);
955}
956
958{
959 return true;
960}
961
962static bool sortByPosition(const KArchiveFile *file1, const KArchiveFile *file2)
963{
964 return file1->position() < file2->position();
965}
966
967bool KArchiveDirectory::copyTo(const QString &dest, bool recursiveCopy) const
968{
969 QDir root;
970 const QString destDir(QDir(dest).absolutePath()); // get directory path without any "." or ".."
971
973 QMap<qint64, QString> fileToDir;
974
975 // placeholders for iterated items
977 QStack<QString> dirNameStack;
978
979 dirStack.push(this); // init stack at current directory
980 dirNameStack.push(destDir); // ... with given path
981 do {
982 const KArchiveDirectory *curDir = dirStack.pop();
983
984 // extract only to specified folder if it is located within archive's extraction folder
985 // otherwise put file under root position in extraction folder
986 QString curDirName = dirNameStack.pop();
987 if (!QDir(curDirName).absolutePath().startsWith(destDir)) {
988 qCWarning(KArchiveLog) << "Attempted export into folder" << curDirName << "which is outside of the extraction root folder" << destDir << "."
989 << "Changing export of contained files to extraction root folder.";
990 curDirName = destDir;
991 }
992
993 if (!root.mkpath(curDirName)) {
994 return false;
995 }
996
997 const QStringList dirEntries = curDir->entries();
998 for (QStringList::const_iterator it = dirEntries.begin(); it != dirEntries.end(); ++it) {
999 const KArchiveEntry *curEntry = curDir->entry(*it);
1000 if (!curEntry->symLinkTarget().isEmpty()) {
1001 QString linkName = curDirName + QLatin1Char('/') + curEntry->name();
1002 // To create a valid link on Windows, linkName must have a .lnk file extension.
1003#ifdef Q_OS_WIN
1004 if (!linkName.endsWith(QLatin1String(".lnk"))) {
1005 linkName += QLatin1String(".lnk");
1006 }
1007#endif
1008 QFile symLinkTarget(curEntry->symLinkTarget());
1009 if (!symLinkTarget.link(linkName)) {
1010 // qCDebug(KArchiveLog) << "symlink(" << curEntry->symLinkTarget() << ',' << linkName << ") failed:" << strerror(errno);
1011 }
1012 } else {
1013 if (curEntry->isFile()) {
1014 const KArchiveFile *curFile = dynamic_cast<const KArchiveFile *>(curEntry);
1015 if (curFile) {
1016 fileList.append(curFile);
1017 fileToDir.insert(curFile->position(), curDirName);
1018 }
1019 }
1020
1021 if (curEntry->isDirectory() && recursiveCopy) {
1022 const KArchiveDirectory *ad = dynamic_cast<const KArchiveDirectory *>(curEntry);
1023 if (ad) {
1024 dirStack.push(ad);
1025 dirNameStack.push(curDirName + QLatin1Char('/') + curEntry->name());
1026 }
1027 }
1028 }
1029 }
1030 } while (!dirStack.isEmpty());
1031
1032 std::sort(fileList.begin(), fileList.end(), sortByPosition); // sort on d->pos, so we have a linear access
1033
1034 for (QList<const KArchiveFile *>::const_iterator it = fileList.constBegin(), end = fileList.constEnd(); it != end; ++it) {
1035 const KArchiveFile *f = *it;
1036 qint64 pos = f->position();
1037 if (!f->copyTo(fileToDir[pos])) {
1038 return false;
1039 }
1040 }
1041 return true;
1042}
1043
1044void KArchive::virtual_hook(int, void *)
1045{
1046 /*BASE::virtual_hook( id, data )*/;
1047}
1048
1049void KArchiveEntry::virtual_hook(int, void *)
1050{
1051 /*BASE::virtual_hook( id, data );*/
1052}
1053
1054void KArchiveFile::virtual_hook(int id, void *data)
1055{
1056 KArchiveEntry::virtual_hook(id, data);
1057}
1058
1059void KArchiveDirectory::virtual_hook(int id, void *data)
1060{
1061 KArchiveEntry::virtual_hook(id, data);
1062}
Represents a directory entry in a KArchive.
void addEntry(KArchiveEntry *)
Definition karchive.cpp:922
KArchiveDirectory(KArchive *archive, const QString &name, int access, const QDateTime &date, const QString &user, const QString &group, const QString &symlink)
Creates a new directory entry.
Definition karchive.cpp:885
QStringList entries() const
Returns a list of sub-entries.
Definition karchive.cpp:902
bool copyTo(const QString &dest, bool recursive=true) const
Extracts all entries in this archive directory to the directory dest.
Definition karchive.cpp:967
void removeEntry(KArchiveEntry *)
Definition karchive.cpp:938
bool addEntryV2(KArchiveEntry *)
Definition karchive.cpp:927
const KArchiveEntry * entry(const QString &name) const
Returns the entry in the archive with the given name.
Definition karchive.cpp:907
bool isDirectory() const override
Checks whether this entry is a directory.
Definition karchive.cpp:957
const KArchiveFile * file(const QString &name) const
Returns the file entry in the archive with the given name.
Definition karchive.cpp:913
A base class for entries in an KArchive.
mode_t permissions() const
The permissions and mode flags as returned by the stat() function in st_mode.
Definition karchive.cpp:720
QString user() const
User who created the file.
Definition karchive.cpp:725
virtual bool isDirectory() const
Checks whether the entry is a directory.
Definition karchive.cpp:745
KArchiveEntry(KArchive *archive, const QString &name, int access, const QDateTime &date, const QString &user, const QString &group, const QString &symlink)
Creates a new entry.
Definition karchive.cpp:694
QDateTime date() const
Creation date of the file.
Definition karchive.cpp:710
QString group() const
Group of the user who created the file.
Definition karchive.cpp:730
QString name() const
Name of the file without path.
Definition karchive.cpp:715
QString symLinkTarget() const
Symlink if there is one.
Definition karchive.cpp:735
virtual bool isFile() const
Checks whether the entry is a file.
Definition karchive.cpp:740
Represents a file entry in a KArchive.
KArchiveFile(KArchive *archive, const QString &name, int access, const QDateTime &date, const QString &user, const QString &group, const QString &symlink, qint64 pos, qint64 size)
Creates a new file entry.
Definition karchive.cpp:771
bool copyTo(const QString &dest) const
Extracts the file to the directory dest.
Definition karchive.cpp:848
qint64 size() const
Size of the data.
Definition karchive.cpp:795
virtual QIODevice * createDevice() const
This method returns QIODevice (internal class: KLimitedIODevice) on top of the underlying QIODevice.
Definition karchive.cpp:821
virtual QByteArray data() const
Returns the data of the file.
Definition karchive.cpp:805
void setSize(qint64 s)
Set size of data, usually after writing the file.
Definition karchive.cpp:800
~KArchiveFile() override
Destructor.
Definition karchive.cpp:785
qint64 position() const
Position of the data in the [uncompressed] archive.
Definition karchive.cpp:790
bool isFile() const override
Checks whether this entry is a file.
Definition karchive.cpp:826
KArchive is a base class for reading and writing archives.
Definition karchive.h:41
QString errorString() const
Returns a description of the last error.
Definition karchive.cpp:253
QIODevice * device() const
The underlying device.
Definition karchive.cpp:628
virtual bool createDevice(QIODevice::OpenMode mode)
Can be reimplemented in order to change the creation of the device (when using the fileName construct...
Definition karchive.cpp:174
virtual bool close()
Closes the archive.
Definition karchive.cpp:214
virtual bool doFinishWriting(qint64 size)=0
Called after writing the data.
bool addLocalFile(const QString &fileName, const QString &destName)
Writes a local file into the archive.
Definition karchive.cpp:264
virtual KArchiveDirectory * rootDir()
Retrieves or create the root directory.
Definition karchive.cpp:519
bool finishWriting(qint64 size)
Call finishWriting after writing the data.
Definition karchive.cpp:479
KArchive(const QString &fileName)
Base constructor (protected since this is a pure virtual class).
Definition karchive.cpp:115
virtual bool doWriteDir(const QString &name, const QString &user, const QString &group, mode_t perm, const QDateTime &atime, const QDateTime &mtime, const QDateTime &ctime)=0
Write a directory to the archive.
virtual bool doWriteSymLink(const QString &name, const QString &target, const QString &user, const QString &group, mode_t perm, const QDateTime &atime, const QDateTime &mtime, const QDateTime &ctime)=0
Writes a symbolic link to the archive.
virtual bool open(QIODevice::OpenMode mode)
Opens the archive for reading or writing.
Definition karchive.cpp:141
virtual bool closeArchive()=0
Closes the archive.
bool writeSymLink(const QString &name, const QString &target, const QString &user=QString(), const QString &group=QString(), mode_t perm=0120755, const QDateTime &atime=QDateTime(), const QDateTime &mtime=QDateTime(), const QDateTime &ctime=QDateTime())
Writes a symbolic link to the archive if supported.
Definition karchive.cpp:451
const KArchiveDirectory * directory() const
If an archive is opened for reading, then the contents of the archive can be accessed via this functi...
Definition karchive.cpp:258
virtual bool doPrepareWriting(const QString &name, const QString &user, const QString &group, qint64 size, mode_t perm, const QDateTime &atime, const QDateTime &mtime, const QDateTime &ctime)=0
This virtual method must be implemented by subclasses.
bool writeFile(const QString &name, QByteArrayView data, mode_t perm=0100644, const QString &user=QString(), const QString &group=QString(), const QDateTime &atime=QDateTime(), const QDateTime &mtime=QDateTime(), const QDateTime &ctime=QDateTime())
Writes a new file into the archive.
Definition karchive.cpp:385
bool prepareWriting(const QString &name, const QString &user, const QString &group, qint64 size, mode_t perm=0100644, const QDateTime &atime=QDateTime(), const QDateTime &mtime=QDateTime(), const QDateTime &ctime=QDateTime())
Here's another way of writing a file into an archive: Call prepareWriting(), then call writeData() as...
Definition karchive.cpp:463
virtual bool openArchive(QIODevice::OpenMode mode)=0
Opens an archive for reading or writing.
bool writeDir(const QString &name, const QString &user=QString(), const QString &group=QString(), mode_t perm=040755, const QDateTime &atime=QDateTime(), const QDateTime &mtime=QDateTime(), const QDateTime &ctime=QDateTime())
If an archive is opened for writing then you can add new directories using this function.
Definition karchive.cpp:440
bool writeData(const char *data, qint64 size)
Write data into the current file - to be called after calling prepareWriting.
Definition karchive.cpp:414
KArchiveDirectory * findOrCreate(const QString &path)
Ensures that path exists, create otherwise.
Definition karchive.cpp:531
bool addLocalDirectory(const QString &path, const QString &destName)
Writes a local directory into the archive, including all its contents, recursively.
Definition karchive.cpp:350
QIODevice::OpenMode mode() const
Returns the mode in which the archive was opened.
Definition karchive.cpp:623
virtual bool doWriteData(const char *data, qint64 size)
Write data into the current file.
Definition karchive.cpp:424
QString fileName() const
The name of the archive file, as passed to the constructor that takes a fileName, or an empty string ...
Definition karchive.cpp:638
void setRootDir(KArchiveDirectory *rootDir)
Derived classes call setRootDir from openArchive, to set the root directory after parsing an existing...
Definition karchive.cpp:616
bool isOpen() const
Checks whether the archive is open.
Definition karchive.cpp:633
void setDevice(QIODevice *dev)
Can be called by derived classes in order to set the underlying device.
Definition karchive.cpp:607
void setErrorString(const QString &errorStr)
Sets error description.
Definition karchive.cpp:484
KIOCORE_EXPORT QString number(KIO::filesize_t size)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardAction id)
const char * constData() const const
char * data()
void resize(qsizetype newSize, char c)
qsizetype size() const const
const_pointer constData() const const
qsizetype size() const const
QDateTime fromSecsSinceEpoch(qint64 secs)
QString cleanPath(const QString &path)
bool mkpath(const QString &dirPath) const const
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
Permissions permissions(const QString &fileName)
bool remove()
virtual bool setPermissions(Permissions permissions) override
virtual void close() override
QDateTime birthTime() const const
QString group() const const
bool isDir() const const
bool isFile() const const
QDateTime lastModified() const const
QDateTime lastRead() const const
QString owner() const const
qint64 size() const const
QString symLinkTarget() const const
iterator end()
iterator erase(const_iterator pos)
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
QList< Key > keys() const const
T value(const Key &key) const const
QString errorString() const const
QByteArray read(qint64 maxSize)
virtual bool seek(qint64 pos)
qint64 write(const QByteArray &data)
void append(QList< T > &&value)
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
iterator end()
bool isEmpty() const const
iterator insert(const Key &key, const T &value)
void push(const T &t)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromWCharArray(const wchar_t *string, qsizetype size)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:11:56 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.