KArchive

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

KDE's Doxygen guidelines are available online.