cpp-utilities/io/archive.cpp

226 lines
7.8 KiB
C++

#include "./archive.h"
#include "../conversion/stringbuilder.h"
#include "../io/misc.h"
#include <archive.h>
#include <archive_entry.h>
#include <filesystem>
using namespace CppUtilities;
namespace CppUtilities {
/*!
* \brief Destroys the ArchiveException.
*/
ArchiveException::~ArchiveException()
{
}
/// \cond
///
struct AddDirectoryToFileMap {
bool operator()(std::string_view path)
{
fileMap[std::string(path)];
return false;
}
FileMap &fileMap;
};
struct AddFileToFileMap {
bool operator()(std::string_view directoryPath, ArchiveFile &&file)
{
fileMap[std::string(directoryPath)].emplace_back(std::move(file));
return false;
}
FileMap &fileMap;
};
void walkThroughArchiveInternal(struct archive *ar, std::string_view archiveName, const FilePredicate &isFileRelevant, FileHandler &&fileHandler,
DirectoryHandler &&directoryHandler)
{
// iterate through all archive entries
struct archive_entry *const entry = archive_entry_new();
auto fileContent = std::string();
while (archive_read_next_header2(ar, entry) == ARCHIVE_OK) {
// check entry type (only dirs, files and symlinks relevant here)
const auto entryType(archive_entry_filetype(entry));
if (entryType != AE_IFDIR && entryType != AE_IFREG && entryType != AE_IFLNK) {
continue;
}
// get file path
const char *filePath = archive_entry_pathname_utf8(entry);
if (!filePath) {
filePath = archive_entry_pathname(entry);
}
if (!filePath) {
continue;
}
// get permissions
const mode_t perm = archive_entry_perm(entry);
// add directories explicitly to get the entire tree though skipping irrelevant files
if (entryType == AE_IFDIR) {
// remove trailing slashes
const char *dirEnd = filePath;
for (const char *i = filePath; *i; ++i) {
if (*i != '/') {
dirEnd = i + 1;
}
}
if (directoryHandler(std::string_view(filePath, static_cast<std::size_t>(dirEnd - filePath)))) {
goto free;
}
continue;
}
// split the path into dir and fileName
const char *fileName = filePath, *dirEnd = filePath;
for (const char *i = filePath; *i; ++i) {
if (*i == '/') {
fileName = i + 1;
dirEnd = i;
}
}
// prevent looking into irrelevant files
if (isFileRelevant && !isFileRelevant(filePath, fileName, perm)) {
continue;
}
// read timestamps
const auto creationTime = DateTime::fromTimeStampGmt(archive_entry_ctime(entry));
const auto modificationTime = DateTime::fromTimeStampGmt(archive_entry_mtime(entry));
// read symlink
if (entryType == AE_IFLNK) {
if (fileHandler(std::string_view(filePath, static_cast<std::string::size_type>(dirEnd - filePath)),
ArchiveFile(fileName, std::string(archive_entry_symlink_utf8(entry)), ArchiveFileType::Link, creationTime, modificationTime))) {
goto free;
}
continue;
}
// determine file size to pre-allocate buffer for file content
const la_int64_t fileSize = archive_entry_size(entry);
fileContent.clear();
if (fileSize > 0) {
fileContent.reserve(static_cast<std::string::size_type>(fileSize));
}
// read file content
const char *buff;
auto size = std::size_t();
auto offset = la_int64_t();
for (;;) {
const auto returnCode = archive_read_data_block(ar, reinterpret_cast<const void **>(&buff), &size, &offset);
if (returnCode == ARCHIVE_EOF || returnCode < ARCHIVE_OK) {
break;
}
fileContent.append(buff, size);
}
// move it to results
if (fileHandler(std::string_view(filePath, static_cast<std::string::size_type>(dirEnd - filePath)),
ArchiveFile(fileName, std::move(fileContent), ArchiveFileType::Regular, creationTime, modificationTime))) {
goto free;
}
}
// free resources used by libarchive
free:
archive_entry_free(entry);
const auto returnCode = archive_read_free(ar);
if (returnCode != ARCHIVE_OK) {
throw ArchiveException(argsToString("Unable to free archive: ", archiveName));
}
}
/// \endcond
/*!
* \brief Invokes callbacks for files and directories in the specified archive.
*/
void walkThroughArchiveFromBuffer(std::string_view archiveData, std::string_view archiveName, const FilePredicate &isFileRelevant,
FileHandler &&fileHandler, DirectoryHandler &&directoryHandler)
{
// refuse opening empty buffer
if (archiveData.empty()) {
throw ArchiveException("Unable to open archive \"" % archiveName + "\": archive data is empty");
}
// open archive buffer using libarchive
struct archive *ar = archive_read_new();
archive_read_support_filter_all(ar);
archive_read_support_format_all(ar);
const auto returnCode = archive_read_open_memory(ar, archiveData.data(), archiveData.size());
if (returnCode != ARCHIVE_OK) {
archive_read_free(ar);
if (const char *const error = archive_error_string(ar)) {
throw ArchiveException("Unable to open/read archive \"" % archiveName % "\": " + error);
} else {
throw ArchiveException("Unable to open/read archive \"" % archiveName + "\": unable to open archive from memory");
}
}
walkThroughArchiveInternal(ar, archiveName, isFileRelevant, std::move(fileHandler), std::move(directoryHandler));
}
/*!
* \brief Extracts the specified archive.
*/
FileMap extractFilesFromBuffer(std::string_view archiveData, std::string_view archiveName, const FilePredicate &isFileRelevant)
{
auto results = FileMap();
walkThroughArchiveFromBuffer(archiveData, archiveName, isFileRelevant, AddFileToFileMap{ results }, AddDirectoryToFileMap{ results });
return results;
}
/*!
* \brief Invokes callbacks for files and directories in the specified archive.
*/
void walkThroughArchive(
std::string_view archivePath, const FilePredicate &isFileRelevant, FileHandler &&fileHandler, DirectoryHandler &&directoryHandler)
{
// open archive file using libarchive
if (archivePath.empty()) {
throw ArchiveException("Unable to open archive: no path specified");
}
auto ec = std::error_code();
auto size = std::filesystem::file_size(archivePath, ec);
if (ec) {
throw ArchiveException("Unable to determine size of \"" % archivePath % "\": " + ec.message());
}
if (!size) {
throw ArchiveException("Unable to open archive \"" % archivePath + "\": file is empty");
}
struct archive *ar = archive_read_new();
archive_read_support_filter_all(ar);
archive_read_support_format_all(ar);
const auto returnCode = archive_read_open_filename(ar, archivePath.data(), 10240);
if (returnCode != ARCHIVE_OK) {
archive_read_free(ar);
if (const char *const error = archive_error_string(ar)) {
throw ArchiveException("Unable to open/read archive \"" % archivePath % "\": " + error);
} else {
throw ArchiveException("Unable to open/read archive \"" % archivePath + "\": unable to open archive from file");
}
}
walkThroughArchiveInternal(ar, archivePath, isFileRelevant, std::move(fileHandler), std::move(directoryHandler));
}
/*!
* \brief Extracts the specified archive.
*/
FileMap extractFiles(std::string_view archivePath, const FilePredicate &isFileRelevant)
{
auto results = FileMap();
walkThroughArchive(archivePath, isFileRelevant, AddFileToFileMap{ results }, AddDirectoryToFileMap{ results });
return results;
}
} // namespace CppUtilities