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 }