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 #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 
49 class KArchiveDirectoryPrivate
50 {
51 public:
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 
75  QString name = QDir::cleanPath(_name);
76  int pos = name.indexOf(QLatin1Char('/'));
77  if (pos == 0) { // ouch 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 
115 KArchive::KArchive(const QString &fileName)
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 
135 KArchive::~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;
199  case QIODevice::ReadOnly:
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 
264 bool 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 effectively f*cked up
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 
350 bool 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(file, 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 
385 bool KArchive::writeFile(const QString &name,
386  const QByteArray &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 
414 bool KArchive::writeData(const char *data, qint64 size)
415 {
416  bool ok = device()->write(data, size) == size;
417  if (!ok) {
418  setErrorString(tr("Writing failed: %1").arg(device()->errorString()));
419  d->abortWriting();
420  }
421  return ok;
422 }
423 
424 // The writeDir -> doWriteDir pattern allows to avoid propagating the default
425 // values into all virtual methods of subclasses, and it allows more extensibility:
426 // if a new argument is needed, we can add a writeDir overload which stores the
427 // additional argument in the d pointer, and doWriteDir reimplementations can fetch
428 // it from there.
429 
430 bool KArchive::writeDir(const QString &name,
431  const QString &user,
432  const QString &group,
433  mode_t perm,
434  const QDateTime &atime,
435  const QDateTime &mtime,
436  const QDateTime &ctime)
437 {
438  return doWriteDir(name, user, group, perm | 040000, atime, mtime, ctime);
439 }
440 
442  const QString &target,
443  const QString &user,
444  const QString &group,
445  mode_t perm,
446  const QDateTime &atime,
447  const QDateTime &mtime,
448  const QDateTime &ctime)
449 {
450  return doWriteSymLink(name, target, user, group, perm, atime, mtime, ctime);
451 }
452 
454  const QString &user,
455  const QString &group,
456  qint64 size,
457  mode_t perm,
458  const QDateTime &atime,
459  const QDateTime &mtime,
460  const QDateTime &ctime)
461 {
462  bool ok = doPrepareWriting(name, user, group, size, perm, atime, mtime, ctime);
463  if (!ok) {
464  d->abortWriting();
465  }
466  return ok;
467 }
468 
469 bool KArchive::finishWriting(qint64 size)
470 {
471  return doFinishWriting(size);
472 }
473 
474 void KArchive::setErrorString(const QString &errorStr)
475 {
476  d->errorStr = errorStr;
477 }
478 
479 static QString getCurrentUserName()
480 {
481 #if defined(Q_OS_UNIX)
482  struct passwd *pw = getpwuid(getuid());
483  return pw ? QFile::decodeName(pw->pw_name) : QString::number(getuid());
484 #elif defined(Q_OS_WIN)
485  wchar_t buffer[255];
486  DWORD size = 255;
487  bool ok = GetUserNameW(buffer, &size);
488  if (!ok) {
489  return QString();
490  }
491  return QString::fromWCharArray(buffer);
492 #else
493  return QString();
494 #endif
495 }
496 
497 static QString getCurrentGroupName()
498 {
499 #if defined(Q_OS_UNIX)
500  struct group *grp = getgrgid(getgid());
501  return grp ? QFile::decodeName(grp->gr_name) : QString::number(getgid());
502 #elif defined(Q_OS_WIN)
503  return QString();
504 #else
505  return QString();
506 #endif
507 }
508 
510 {
511  if (!d->rootDir) {
512  // qCDebug(KArchiveLog) << "Making root dir ";
513  QString username = ::getCurrentUserName();
514  QString groupname = ::getCurrentGroupName();
515 
516  d->rootDir = new KArchiveDirectory(this, QStringLiteral("/"), int(0777 + S_IFDIR), QDateTime(), username, groupname, QString());
517  }
518  return d->rootDir;
519 }
520 
522 {
523  return d->findOrCreate(path, 0 /*recursionCounter*/);
524 }
525 
526 KArchiveDirectory *KArchivePrivate::findOrCreate(const QString &path, int recursionCounter)
527 {
528  // Check we're not in a path that is ultra deep, this is most probably fine since PATH_MAX on Linux
529  // is defined as 4096, so even on /a/a/a/a/a/a 2500 recursions puts us over that limit
530  // an ultra deep recursion will makes us crash due to not enough stack. Tests show that 1MB stack
531  // (default on Linux seems to be 8MB) gives us up to around 4000 recursions
532  if (recursionCounter > 2500) {
533  qCWarning(KArchiveLog) << "path recursion limit exceeded, bailing out";
534  return nullptr;
535  }
536  // qCDebug(KArchiveLog) << path;
537  if (path.isEmpty() || path == QLatin1String("/") || path == QLatin1String(".")) { // root dir => found
538  // qCDebug(KArchiveLog) << "returning rootdir";
539  return q->rootDir();
540  }
541  // Important note : for tar files containing absolute paths
542  // (i.e. beginning with "/"), this means the leading "/" will
543  // be removed (no KDirectory for it), which is exactly the way
544  // the "tar" program works (though it displays a warning about it)
545  // See also KArchiveDirectory::entry().
546 
547  // Already created ? => found
548  KArchiveDirectory *existingEntryParentDirectory;
549  const KArchiveEntry *existingEntry = KArchiveDirectoryPrivate::get(q->rootDir())->entry(path, &existingEntryParentDirectory);
550  if (existingEntry) {
551  if (existingEntry->isDirectory())
552  // qCDebug(KArchiveLog) << "found it";
553  {
554  const KArchiveDirectory *dir = static_cast<const KArchiveDirectory *>(existingEntry);
555  return const_cast<KArchiveDirectory *>(dir);
556  } else {
557  const KArchiveFile *file = static_cast<const KArchiveFile *>(existingEntry);
558  if (file->size() > 0) {
559  qCWarning(KArchiveLog) << path << "is normal file, but there are file paths in the archive assuming it is a directory, bailing out";
560  return nullptr;
561  }
562 
563  qCDebug(KArchiveLog) << path << " is an empty file, assuming it is actually a directory and replacing";
564  KArchiveEntry *myEntry = const_cast<KArchiveEntry *>(existingEntry);
565  existingEntryParentDirectory->removeEntry(myEntry);
566  delete myEntry;
567  }
568  }
569 
570  // Otherwise go up and try again
571  int pos = path.lastIndexOf(QLatin1Char('/'));
572  KArchiveDirectory *parent;
573  QString dirname;
574  if (pos == -1) { // no more slash => create in root dir
575  parent = q->rootDir();
576  dirname = path;
577  } else {
578  QString left = path.left(pos);
579  dirname = path.mid(pos + 1);
580  parent = findOrCreate(left, recursionCounter + 1); // recursive call... until we find an existing dir.
581  }
582 
583  if (!parent) {
584  return nullptr;
585  }
586 
587  // qCDebug(KArchiveLog) << "found parent " << parent->name() << " adding " << dirname << " to ensure " << path;
588  // Found -> add the missing piece
589  KArchiveDirectory *e = new KArchiveDirectory(q, dirname, rootDir->permissions(), rootDir->date(), rootDir->user(), rootDir->group(), QString());
590  if (parent->addEntryV2(e)) {
591  return e; // now a directory to <path> exists
592  } else {
593  return nullptr;
594  }
595 }
596 
598 {
599  if (d->deviceOwned) {
600  delete d->dev;
601  }
602  d->dev = dev;
603  d->deviceOwned = false;
604 }
605 
607 {
608  Q_ASSERT(!d->rootDir); // Call setRootDir only once during parsing please ;)
609  delete d->rootDir; // but if it happens, don't leak
610  d->rootDir = rootDir;
611 }
612 
614 {
615  return d->mode;
616 }
617 
619 {
620  return d->dev;
621 }
622 
623 bool KArchive::isOpen() const
624 {
625  return d->mode != QIODevice::NotOpen;
626 }
627 
629 {
630  return d->fileName;
631 }
632 
633 void KArchivePrivate::abortWriting()
634 {
635  if (saveFile) {
636  saveFile->cancelWriting();
637  delete saveFile;
638  saveFile = nullptr;
639  dev = nullptr;
640  }
641 }
642 
643 // this is a hacky wrapper to check if time_t value is invalid
644 QDateTime KArchivePrivate::time_tToDateTime(uint time_t)
645 {
646  if (time_t == uint(-1)) {
647  return QDateTime();
648  }
649  return QDateTime::fromSecsSinceEpoch(time_t);
650 }
651 
652 ////////////////////////////////////////////////////////////////////////
653 /////////////////////// KArchiveEntry //////////////////////////////////
654 ////////////////////////////////////////////////////////////////////////
655 
656 class KArchiveEntryPrivate
657 {
658 public:
659  KArchiveEntryPrivate(KArchive *_archive,
660  const QString &_name,
661  int _access,
662  const QDateTime &_date,
663  const QString &_user,
664  const QString &_group,
665  const QString &_symlink)
666  : name(_name)
667  , date(_date)
668  , access(_access)
669  , user(_user)
670  , group(_group)
671  , symlink(_symlink)
672  , archive(_archive)
673  {
674  }
675  QString name;
676  QDateTime date;
677  mode_t access;
678  QString user;
679  QString group;
681  KArchive *archive;
682 };
683 
685  const QString &name,
686  int access,
687  const QDateTime &date,
688  const QString &user,
689  const QString &group,
690  const QString &symlink)
691  : d(new KArchiveEntryPrivate(t, name, access, date, user, group, symlink))
692 {
693 }
694 
695 KArchiveEntry::~KArchiveEntry()
696 {
697  delete d;
698 }
699 
701 {
702  return d->date;
703 }
704 
706 {
707  return d->name;
708 }
709 
711 {
712  return d->access;
713 }
714 
716 {
717  return d->user;
718 }
719 
721 {
722  return d->group;
723 }
724 
726 {
727  return d->symlink;
728 }
729 
731 {
732  return false;
733 }
734 
736 {
737  return false;
738 }
739 
740 KArchive *KArchiveEntry::archive() const
741 {
742  return d->archive;
743 }
744 
745 ////////////////////////////////////////////////////////////////////////
746 /////////////////////// KArchiveFile ///////////////////////////////////
747 ////////////////////////////////////////////////////////////////////////
748 
749 class KArchiveFilePrivate
750 {
751 public:
752  KArchiveFilePrivate(qint64 _pos, qint64 _size)
753  : pos(_pos)
754  , size(_size)
755  {
756  }
757  qint64 pos;
758  qint64 size;
759 };
760 
762  const QString &name,
763  int access,
764  const QDateTime &date,
765  const QString &user,
766  const QString &group,
767  const QString &symlink,
768  qint64 pos,
769  qint64 size)
770  : KArchiveEntry(t, name, access, date, user, group, symlink)
771  , d(new KArchiveFilePrivate(pos, size))
772 {
773 }
774 
776 {
777  delete d;
778 }
779 
781 {
782  return d->pos;
783 }
784 
785 qint64 KArchiveFile::size() const
786 {
787  return d->size;
788 }
789 
790 void KArchiveFile::setSize(qint64 s)
791 {
792  d->size = s;
793 }
794 
796 {
797  bool ok = archive()->device()->seek(d->pos);
798  if (!ok) {
799  // qCWarning(KArchiveLog) << "Failed to sync to" << d->pos << "to read" << name();
800  }
801 
802  // Read content
803  QByteArray arr;
804  if (d->size) {
805  arr = archive()->device()->read(d->size);
806  Q_ASSERT(arr.size() == d->size);
807  }
808  return arr;
809 }
810 
812 {
813  return new KLimitedIODevice(archive()->device(), d->pos, d->size);
814 }
815 
817 {
818  return true;
819 }
820 
821 static QFileDevice::Permissions withExecutablePerms(QFileDevice::Permissions filePerms, mode_t perms)
822 {
823  if (perms & 01) {
824  filePerms |= QFileDevice::ExeOther;
825  }
826 
827  if (perms & 010) {
828  filePerms |= QFileDevice::ExeGroup;
829  }
830 
831  if (perms & 0100) {
832  filePerms |= QFileDevice::ExeOwner;
833  }
834 
835  return filePerms;
836 }
837 
838 bool KArchiveFile::copyTo(const QString &dest) const
839 {
840  QFile f(dest + QLatin1Char('/') + name());
842  QIODevice *inputDev = createDevice();
843  if (!inputDev) {
844  f.remove();
845  return false;
846  }
847 
848  // Read and write data in chunks to minimize memory usage
849  const qint64 chunkSize = 1024 * 1024;
850  qint64 remainingSize = d->size;
851  QByteArray array;
852  array.resize(int(qMin(chunkSize, remainingSize)));
853 
854  while (remainingSize > 0) {
855  const qint64 currentChunkSize = qMin(chunkSize, remainingSize);
856  const qint64 n = inputDev->read(array.data(), currentChunkSize);
857  Q_UNUSED(n) // except in Q_ASSERT
858  Q_ASSERT(n == currentChunkSize);
859  f.write(array.data(), currentChunkSize);
860  remainingSize -= currentChunkSize;
861  }
862  f.setPermissions(withExecutablePerms(f.permissions(), permissions()));
863  f.close();
864 
865  delete inputDev;
866  return true;
867  }
868  return false;
869 }
870 
871 ////////////////////////////////////////////////////////////////////////
872 //////////////////////// KArchiveDirectory /////////////////////////////////
873 ////////////////////////////////////////////////////////////////////////
874 
876  const QString &name,
877  int access,
878  const QDateTime &date,
879  const QString &user,
880  const QString &group,
881  const QString &symlink)
882  : KArchiveEntry(t, name, access, date, user, group, symlink)
883  , d(new KArchiveDirectoryPrivate(this))
884 {
885 }
886 
887 KArchiveDirectory::~KArchiveDirectory()
888 {
889  delete d;
890 }
891 
893 {
894  return d->entries.keys();
895 }
896 
898 {
899  KArchiveDirectory *dummy;
900  return d->entry(_name, &dummy);
901 }
902 
904 {
905  const KArchiveEntry *e = entry(name);
906  if (e && e->isFile()) {
907  return static_cast<const KArchiveFile *>(e);
908  }
909  return nullptr;
910 }
911 
913 {
914  addEntryV2(entry);
915 }
916 
918 {
919  if (d->entries.value(entry->name())) {
920  qCWarning(KArchiveLog) << "directory " << name() << "has entry" << entry->name() << "already";
921  delete entry;
922  return false;
923  }
924  d->entries.insert(entry->name(), entry);
925  return true;
926 }
927 
929 {
930  if (!entry) {
931  return;
932  }
933 
934  QHash<QString, KArchiveEntry *>::Iterator it = d->entries.find(entry->name());
935  // nothing removed?
936  if (it == d->entries.end()) {
937  qCWarning(KArchiveLog) << "directory " << name() << "has no entry with name " << entry->name();
938  return;
939  }
940  if (it.value() != entry) {
941  qCWarning(KArchiveLog) << "directory " << name() << "has another entry for name " << entry->name();
942  return;
943  }
944  d->entries.erase(it);
945 }
946 
948 {
949  return true;
950 }
951 
952 static bool sortByPosition(const KArchiveFile *file1, const KArchiveFile *file2)
953 {
954  return file1->position() < file2->position();
955 }
956 
957 bool KArchiveDirectory::copyTo(const QString &dest, bool recursiveCopy) const
958 {
959  QDir root;
960  const QString destDir(QDir(dest).absolutePath()); // get directory path without any "." or ".."
961 
963  QMap<qint64, QString> fileToDir;
964 
965  // placeholders for iterated items
967  QStack<QString> dirNameStack;
968 
969  dirStack.push(this); // init stack at current directory
970  dirNameStack.push(destDir); // ... with given path
971  do {
972  const KArchiveDirectory *curDir = dirStack.pop();
973 
974  // extract only to specified folder if it is located within archive's extraction folder
975  // otherwise put file under root position in extraction folder
976  QString curDirName = dirNameStack.pop();
977  if (!QDir(curDirName).absolutePath().startsWith(destDir)) {
978  qCWarning(KArchiveLog) << "Attempted export into folder" << curDirName << "which is outside of the extraction root folder" << destDir << "."
979  << "Changing export of contained files to extraction root folder.";
980  curDirName = destDir;
981  }
982 
983  if (!root.mkpath(curDirName)) {
984  return false;
985  }
986 
987  const QStringList dirEntries = curDir->entries();
988  for (QStringList::const_iterator it = dirEntries.begin(); it != dirEntries.end(); ++it) {
989  const KArchiveEntry *curEntry = curDir->entry(*it);
990  if (!curEntry->symLinkTarget().isEmpty()) {
991  QString linkName = curDirName + QLatin1Char('/') + curEntry->name();
992  // To create a valid link on Windows, linkName must have a .lnk file extension.
993 #ifdef Q_OS_WIN
994  if (!linkName.endsWith(QLatin1String(".lnk"))) {
995  linkName += QLatin1String(".lnk");
996  }
997 #endif
998  QFile symLinkTarget(curEntry->symLinkTarget());
999  if (!symLinkTarget.link(linkName)) {
1000  // qCDebug(KArchiveLog) << "symlink(" << curEntry->symLinkTarget() << ',' << linkName << ") failed:" << strerror(errno);
1001  }
1002  } else {
1003  if (curEntry->isFile()) {
1004  const KArchiveFile *curFile = dynamic_cast<const KArchiveFile *>(curEntry);
1005  if (curFile) {
1006  fileList.append(curFile);
1007  fileToDir.insert(curFile->position(), curDirName);
1008  }
1009  }
1010 
1011  if (curEntry->isDirectory() && recursiveCopy) {
1012  const KArchiveDirectory *ad = dynamic_cast<const KArchiveDirectory *>(curEntry);
1013  if (ad) {
1014  dirStack.push(ad);
1015  dirNameStack.push(curDirName + QLatin1Char('/') + curEntry->name());
1016  }
1017  }
1018  }
1019  }
1020  } while (!dirStack.isEmpty());
1021 
1022  std::sort(fileList.begin(), fileList.end(), sortByPosition); // sort on d->pos, so we have a linear access
1023 
1024  for (QList<const KArchiveFile *>::const_iterator it = fileList.constBegin(), end = fileList.constEnd(); it != end; ++it) {
1025  const KArchiveFile *f = *it;
1026  qint64 pos = f->position();
1027  if (!f->copyTo(fileToDir[pos])) {
1028  return false;
1029  }
1030  }
1031  return true;
1032 }
1033 
1034 void KArchive::virtual_hook(int, void *)
1035 {
1036  /*BASE::virtual_hook( id, data )*/;
1037 }
1038 
1039 void KArchiveEntry::virtual_hook(int, void *)
1040 {
1041  /*BASE::virtual_hook( id, data );*/
1042 }
1043 
1044 void KArchiveFile::virtual_hook(int id, void *data)
1045 {
1046  KArchiveEntry::virtual_hook(id, data);
1047 }
1048 
1049 void KArchiveDirectory::virtual_hook(int id, void *data)
1050 {
1051  KArchiveEntry::virtual_hook(id, data);
1052 }
void append(const T &value)
bool isFile() const override
Checks whether this entry is a file.
Definition: karchive.cpp:816
QTextStream & right(QTextStream &stream)
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
const T value(const Key &key) const const
QString errorString() const const
bool addEntryV2(KArchiveEntry *)
Definition: karchive.cpp:917
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.
bool isEmpty() const const
virtual bool openArchive(QIODevice::OpenMode mode)=0
Opens an archive for reading or writing.
typedef OpenMode
const KArchiveFile * file(const QString &name) const
Returns the file entry in the archive with the given name.
Definition: karchive.cpp:903
QDateTime lastRead() const const
bool remove()
bool isDir() const const
KArchive(const QString &fileName)
Base constructor (protected since this is a pure virtual class).
Definition: karchive.cpp:115
virtual QFileDevice::Permissions permissions() const const override
virtual bool open(QIODevice::OpenMode mode) override
QString name() const
Name of the file without path.
Definition: karchive.cpp:705
void setRootDir(KArchiveDirectory *rootDir)
Derived classes call setRootDir from openArchive, to set the root directory after parsing an existing...
Definition: karchive.cpp:606
qint64 size() const
Size of the data.
Definition: karchive.cpp:785
QByteArray encodeName(const QString &fileName)
QTextStream & left(QTextStream &stream)
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:875
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 setPermissions(QFileDevice::Permissions permissions) override
QIODevice * device() const
The underlying device.
Definition: karchive.cpp:618
QList::const_iterator constBegin() const const
virtual QIODevice * createDevice() const
This method returns QIODevice (internal class: KLimitedIODevice) on top of the underlying QIODevice.
Definition: karchive.cpp:811
virtual bool seek(qint64 pos)
QString owner() const const
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QDateTime date() const
Creation date of the file.
Definition: karchive.cpp:700
QString group() const
Group of the user who created the file.
Definition: karchive.cpp:720
virtual KArchiveDirectory * rootDir()
Retrieves or create the root directory.
Definition: karchive.cpp:509
bool copyTo(const QString &dest, bool recursive=true) const
Extracts all entries in this archive directory to the directory dest.
Definition: karchive.cpp:957
bool isFile() const const
bool isDirectory() const override
Checks whether this entry is a directory.
Definition: karchive.cpp:947
QMap::iterator insert(const Key &key, const T &value)
virtual bool close()
Closes the archive.
Definition: karchive.cpp:214
~KArchiveFile() override
Destructor.
Definition: karchive.cpp:775
A directory in an archive.
Base class for the archive-file's directory structure.
Definition: karchiveentry.h:34
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:628
QString fromWCharArray(const wchar_t *string, int size)
void setSize(qint64 s)
Set size of data, usually after writing the file.
Definition: karchive.cpp:790
bool isEmpty() const const
int length() const const
bool mkpath(const QString &dirPath) const const
QStringList entries() const
Returns a list of sub-entries.
Definition: karchive.cpp:892
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:761
QString symLinkTarget() const
Symlink if there is one.
Definition: karchive.cpp:725
qint64 size() const const
typedef Permissions
bool finishWriting(qint64 size)
Call finishWriting after writing the data.
Definition: karchive.cpp:469
virtual bool writeData(const char *data, qint64 size)
Write data into the current file - to be called after calling prepareWriting.
Definition: karchive.cpp:414
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:430
mode_t permissions() const
The permissions and mode flags as returned by the stat() function in st_mode.
Definition: karchive.cpp:710
virtual void close() override
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
virtual bool isFile() const
Checks whether the entry is a file.
Definition: karchive.cpp:730
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:684
generic class for reading/writing archives
Definition: karchive.h:39
virtual bool open(QIODevice::OpenMode mode)
Opens the archive for reading or writing.
Definition: karchive.cpp:141
bool isOpen() const
Checks whether the archive is open.
Definition: karchive.cpp:623
QIODevice::OpenMode mode() const
Returns the mode in which the archive was opened.
Definition: karchive.cpp:613
QString cleanPath(const QString &path)
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.
qint64 read(char *data, qint64 maxSize)
void resize(int size)
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
QList::const_iterator constEnd() const const
void addEntry(KArchiveEntry *)
Definition: karchive.cpp:912
void push(const T &t)
const char * constData() const const
bool addLocalFile(const QString &fileName, const QString &destName)
Writes a local file into the archive.
Definition: karchive.cpp:264
QString left(int n) const const
QString name(StandardShortcut id)
QDateTime fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offsetSeconds)
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
QString group() const const
void setErrorString(const QString &errorStr)
Sets error description.
Definition: karchive.cpp:474
A file in an archive.
Definition: karchivefile.h:24
QList::iterator begin()
virtual bool doFinishWriting(qint64 size)=0
Called after writing the data.
int size() const const
KIOCORE_EXPORT QString number(KIO::filesize_t size)
virtual bool closeArchive()=0
Closes the archive.
QString symLinkTarget() const const
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
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.
QDateTime lastModified() const const
KIOCORE_EXPORT SimpleJob * symlink(const QString &target, const QUrl &dest, JobFlags flags=DefaultFlags)
QString errorString() const
Returns a description of the last error.
Definition: karchive.cpp:253
QList::iterator end()
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:453
QString mid(int position, int n) const const
QString user() const
User who created the file.
Definition: karchive.cpp:715
KArchiveDirectory * findOrCreate(const QString &path)
Ensures that path exists, create otherwise.
Definition: karchive.cpp:521
virtual QByteArray data() const
Returns the data of the file.
Definition: karchive.cpp:795
qint64 position() const
Position of the data in the [uncompressed] archive.
Definition: karchive.cpp:780
void setDevice(QIODevice *dev)
Can be called by derived classes in order to set the underlying device.
Definition: karchive.cpp:597
virtual bool isDirectory() const
Checks whether the entry is a directory.
Definition: karchive.cpp:735
const KArchiveEntry * entry(const QString &name) const
Returns the entry in the archive with the given name.
Definition: karchive.cpp:897
bool copyTo(const QString &dest) const
Extracts the file to the directory dest.
Definition: karchive.cpp:838
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:441
char * data()
QDateTime birthTime() const const
int access(const QString &path, int mode)
qint64 write(const char *data, qint64 maxSize)
void removeEntry(KArchiveEntry *)
Definition: karchive.cpp:928
virtual QVariant get(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName)
QString decodeName(const QByteArray &localFileName)
bool addLocalDirectory(const QString &path, const QString &destName)
Writes a local directory into the archive, including all its contents, recursively.
Definition: karchive.cpp:350
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sat Jan 28 2023 04:00:43 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.