File indexing completed on 2024-12-15 04:01:21

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "tar.hpp"
0008 
0009 #include <archive.h>
0010 #include <archive_entry.h>
0011 
0012 #include "app/log/log.hpp"
0013 
0014 class glaxnimate::utils::tar::ArchiveEntry::Private
0015 {
0016 public:
0017     Private(archive_entry *entry)
0018         : entry(entry),
0019 #if ARCHIVE_VERSION_NUMBER > 3001002
0020           path(QString::fromUtf8(archive_entry_pathname_utf8(entry)))
0021 #else
0022           path(archive_entry_pathname(entry))
0023 #endif
0024     {}
0025 
0026     archive_entry *entry;
0027     QString path;
0028 };
0029 
0030 glaxnimate::utils::tar::ArchiveEntry::ArchiveEntry(std::unique_ptr<Private> d)
0031     : d(std::move(d))
0032 {}
0033 
0034 glaxnimate::utils::tar::ArchiveEntry::ArchiveEntry(const glaxnimate::utils::tar::ArchiveEntry& oth)
0035     : d(std::make_unique<Private>(*oth.d))
0036 {
0037 }
0038 
0039 glaxnimate::utils::tar::ArchiveEntry & glaxnimate::utils::tar::ArchiveEntry::operator=(const ArchiveEntry& oth)
0040 {
0041     *d = *oth.d;
0042     return *this;
0043 }
0044 
0045 glaxnimate::utils::tar::ArchiveEntry::ArchiveEntry(glaxnimate::utils::tar::ArchiveEntry && oth) = default;
0046 glaxnimate::utils::tar::ArchiveEntry & glaxnimate::utils::tar::ArchiveEntry::operator=(ArchiveEntry && oth)  = default;
0047 
0048 glaxnimate::utils::tar::ArchiveEntry::~ArchiveEntry() = default;
0049 
0050 bool glaxnimate::utils::tar::ArchiveEntry::operator==(const glaxnimate::utils::tar::ArchiveEntry& oth) const
0051 {
0052     if ( !d != !oth.d )
0053         return false;
0054     if ( !d )
0055         return true;
0056     return d->entry == oth.d->entry;
0057 }
0058 
0059 bool glaxnimate::utils::tar::ArchiveEntry::operator!=(const glaxnimate::utils::tar::ArchiveEntry& oth) const
0060 {
0061     return !(*this == oth);
0062 }
0063 
0064 const QString & glaxnimate::utils::tar::ArchiveEntry::path() const
0065 {
0066     return d->path;
0067 }
0068 
0069 bool glaxnimate::utils::tar::ArchiveEntry::valid() const
0070 {
0071     return d && d->entry;
0072 }
0073 
0074 
0075 class glaxnimate::utils::tar::TapeArchive::Private
0076 {
0077 public:
0078     Private(TapeArchive* parent) : parent(parent) {}
0079 
0080     void open(const QString& filename)
0081     {
0082         input = archive_read_new();
0083         archive_read_support_format_all(input);
0084         archive_read_support_filter_all(input);
0085         int result = archive_read_open_filename(input, filename.toStdString().c_str(), 10240);
0086         if ( result < 0 )
0087         {
0088             handle_message(result, input);
0089             close();
0090         }
0091         else
0092         {
0093             finished = false;
0094         }
0095     }
0096 
0097     void load_data(const QByteArray& data)
0098     {
0099         input = archive_read_new();
0100         archive_read_support_format_all(input);
0101         archive_read_support_filter_all(input);
0102         int result = archive_read_open_memory(input, (void*)data.data(), data.size());
0103         if ( result < 0 )
0104         {
0105             handle_message(result, input);
0106             close();
0107         }
0108         else
0109         {
0110             finished = false;
0111         }
0112     }
0113 
0114     int copy_data(archive *output)
0115     {
0116         int result;
0117         const void *buff;
0118         size_t size;
0119         int64_t offset;
0120 
0121         while ( true )
0122         {
0123             result = archive_read_data_block(input, &buff, &size, &offset);
0124 
0125             if ( result == ARCHIVE_EOF )
0126                 return ARCHIVE_OK;
0127 
0128             if ( result < ARCHIVE_OK )
0129             {
0130                 handle_message(result, input);
0131                 return result;
0132             }
0133 
0134             result = archive_write_data_block(output, buff, size, offset);
0135             if ( result < ARCHIVE_OK )
0136             {
0137                 handle_message(result, output);
0138                 return result;
0139             }
0140         }
0141     }
0142 
0143     void extract_begin()
0144     {
0145         output = archive_write_disk_new();
0146         archive_write_disk_set_options(output, ARCHIVE_EXTRACT_TIME|ARCHIVE_EXTRACT_PERM|ARCHIVE_EXTRACT_ACL|ARCHIVE_EXTRACT_FFLAGS);
0147         archive_write_disk_set_standard_lookup(output);
0148     }
0149 
0150     archive_entry* next_entry()
0151     {
0152         if ( !input || !output || finished )
0153             return nullptr;
0154 
0155         while ( true )
0156         {
0157             archive_entry *entry;
0158             int result = archive_read_next_header(input, &entry);
0159             if ( result == ARCHIVE_EOF )
0160             {
0161                 finished = true;
0162                 return nullptr;
0163             }
0164 
0165             if ( result < ARCHIVE_OK )
0166                 handle_message(result, input);
0167             if ( result == ARCHIVE_FAILED )
0168                 continue;
0169             if ( result == ARCHIVE_FATAL )
0170             {
0171                 finished = true;
0172                 return nullptr;
0173             }
0174 
0175             if ( archive_entry_size(entry) < 0 )
0176                 continue;
0177 
0178             return entry;
0179         }
0180     }
0181 
0182     bool extract(const glaxnimate::utils::tar::ArchiveEntry& entry, const QDir& destination)
0183     {
0184         QString output_file_path = destination.absoluteFilePath(entry.d->path);
0185         archive_entry_set_pathname(entry.d->entry, output_file_path.toStdString().c_str());
0186 
0187         int result = archive_write_header(output, entry.d->entry);
0188         if ( result < ARCHIVE_OK )
0189         {
0190             handle_message(result, output);
0191         }
0192         else
0193         {
0194             result = copy_data(output);
0195             if ( result == ARCHIVE_FAILED )
0196                 return false;
0197             if ( result == ARCHIVE_FATAL )
0198             {
0199                 finished = true;
0200                 return false;
0201             }
0202         }
0203 
0204         result = archive_write_finish_entry(output);
0205         if ( result < ARCHIVE_OK )
0206             handle_message(result, output);
0207 
0208         if ( result == ARCHIVE_FATAL )
0209             finished = true;
0210 
0211         return result >= ARCHIVE_WARN;
0212     }
0213 
0214     void handle_message(int result, archive* arch)
0215     {
0216         if ( result < ARCHIVE_OK )
0217         {
0218             QString message = archive_error_string(arch);
0219 
0220             app::log::Severity severity = app::log::Info;
0221             if ( result == ARCHIVE_FATAL )
0222             {
0223                 error = message;
0224                 severity = app::log::Error;
0225             }
0226             else if ( result < ARCHIVE_WARN )
0227             {
0228                 severity = app::log::Warning;
0229             }
0230 
0231             app::log::Log("tar").log(message, severity);
0232             parent->message(message, severity);
0233         }
0234     }
0235 
0236     void extract_end()
0237     {
0238         if ( output )
0239         {
0240             archive_write_close(output);
0241             archive_write_free(output);
0242             output = nullptr;
0243         }
0244     }
0245 
0246     void close()
0247     {
0248         extract_end();
0249 
0250         if ( input )
0251         {
0252             archive_read_close(input);
0253             archive_read_free(input);
0254             input = nullptr;
0255         }
0256     }
0257 
0258     archive* input = nullptr;
0259     archive* output = nullptr;
0260     TapeArchive* parent;
0261     QString error;
0262     bool finished = true;
0263 };
0264 
0265 
0266 glaxnimate::utils::tar::TapeArchive::TapeArchive(const QString& filename)
0267     : d(std::make_unique<Private>(this))
0268 {
0269     d->open(filename);
0270 }
0271 
0272 glaxnimate::utils::tar::TapeArchive::TapeArchive(const QByteArray& data)
0273     : d(std::make_unique<Private>(this))
0274 {
0275     d->load_data(data);
0276 }
0277 
0278 glaxnimate::utils::tar::TapeArchive::~TapeArchive()
0279 {
0280     d->close();
0281 }
0282 
0283 const QString & glaxnimate::utils::tar::TapeArchive::error() const
0284 {
0285     return d->error;
0286 }
0287 
0288 bool glaxnimate::utils::tar::TapeArchive::finished() const
0289 {
0290     return d->finished;
0291 }
0292 
0293 glaxnimate::utils::tar::ArchiveEntry glaxnimate::utils::tar::TapeArchive::next()
0294 {
0295     if ( d->finished )
0296         return ArchiveEntry({});
0297 
0298     if ( !d->output )
0299         d->extract_begin();
0300 
0301     if ( auto entry = d->next_entry() )
0302         return ArchiveEntry(std::make_unique<ArchiveEntry::Private>(entry));
0303 
0304     d->extract_end();
0305     return ArchiveEntry({});
0306 }
0307 
0308 
0309 bool glaxnimate::utils::tar::TapeArchive::extract(const glaxnimate::utils::tar::ArchiveEntry& entry, const QDir& destination)
0310 {
0311     return d->extract(entry, destination);
0312 }
0313 
0314 glaxnimate::utils::tar::TapeArchive::iterator glaxnimate::utils::tar::TapeArchive::begin()
0315 {
0316     return iterator(this, next());
0317 }
0318 
0319 glaxnimate::utils::tar::TapeArchive::iterator glaxnimate::utils::tar::TapeArchive::end()
0320 {
0321     return iterator(this, ArchiveEntry({}));
0322 }
0323 
0324 QString glaxnimate::utils::tar::libarchive_version()
0325 {
0326     int vint = ARCHIVE_VERSION_NUMBER;
0327     int patch = vint % 1000;
0328     vint /= 1000;
0329     int minor = vint % 1000;
0330     vint /= 1000;
0331 
0332     return QString("%1.%2.%3").arg(vint).arg(minor).arg(patch);
0333 }